1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial |
3 | |
4 | #pragma once |
5 | #include <string_view> |
6 | #include <span> |
7 | #include "slint_generated_public.h" |
8 | #include "slint_size.h" |
9 | #include "slint_image_internal.h" |
10 | #include "slint_string.h" |
11 | #include "slint_sharedvector.h" |
12 | |
13 | namespace slint { |
14 | |
15 | /// SharedPixelBuffer is a container for storing image data as pixels. It is |
16 | /// internally reference counted and cheap to copy. |
17 | /// |
18 | /// You can construct a new empty shared pixel buffer with its default constructor, |
19 | /// or you can copy it from an existing contiguous buffer that you might already have, using the |
20 | /// range constructor. |
21 | /// |
22 | /// See the documentation for Image for examples how to use this type to integrate |
23 | /// Slint with external rendering functions. |
24 | template<typename Pixel> |
25 | struct SharedPixelBuffer |
26 | { |
27 | /// Construct an empty SharedPixelBuffer. |
28 | SharedPixelBuffer() = default; |
29 | |
30 | /// Construct a SharedPixelBuffer with the given \a width and \a height. The pixels are default |
31 | /// initialized. |
32 | SharedPixelBuffer(uint32_t width, uint32_t height) |
33 | : m_width(width), m_height(height), m_data(width * height) |
34 | { |
35 | } |
36 | |
37 | /// Construct a SharedPixelBuffer by copying the data from the \a data array. |
38 | /// The array must be of size \a width * \a height . |
39 | SharedPixelBuffer(uint32_t width, uint32_t height, const Pixel *data) |
40 | : m_width(width), m_height(height), m_data(data, data + (width * height)) |
41 | { |
42 | } |
43 | |
44 | /// Returns the width of the buffer in pixels. |
45 | uint32_t width() const { return m_width; } |
46 | /// Returns the height of the buffer in pixels. |
47 | uint32_t height() const { return m_height; } |
48 | |
49 | /// Returns a const pointer to the first pixel of this buffer. |
50 | const Pixel *begin() const { return m_data.begin(); } |
51 | /// Returns a const pointer past this buffer. |
52 | const Pixel *end() const { return m_data.end(); } |
53 | /// Returns a pointer to the first pixel of this buffer. |
54 | Pixel *begin() { return m_data.begin(); } |
55 | /// Returns a pointer past this buffer. |
56 | Pixel *end() { return m_data.end(); } |
57 | /// Returns a const pointer to the first pixel of this buffer. |
58 | const Pixel *cbegin() const { return m_data.begin(); } |
59 | /// Returns a const pointer past this buffer. |
60 | const Pixel *cend() const { return m_data.end(); } |
61 | |
62 | /// Compare two SharedPixelBuffers. They are considered equal if all their pixels are equal. |
63 | bool operator==(const SharedPixelBuffer &other) const = default; |
64 | |
65 | private: |
66 | friend struct Image; |
67 | uint32_t m_width; |
68 | uint32_t m_height; |
69 | SharedVector<Pixel> m_data; |
70 | }; |
71 | |
72 | /// An image type that can be displayed by the Image element |
73 | /// |
74 | /// You can construct Image objects from a path to an image file on disk, using |
75 | /// Image::load_from_path(). |
76 | /// |
77 | /// Another typical use-case is to render the image content with C++ code. |
78 | /// For this it’s most efficient to create a new SharedPixelBuffer with the known dimensions and |
79 | /// pass the pixel pointer returned by begin() to your rendering function. Afterwards you can create |
80 | /// an Image using the constructor taking a SharedPixelBuffer. |
81 | /// |
82 | /// The following example creates a 320x200 RGB pixel buffer and calls a function to draw a shape |
83 | /// into it: |
84 | /// ```cpp |
85 | /// slint::SharedPixelBuffer::<slint::Rgb8Pixel> pixel_buffer(320, 200); |
86 | /// low_level_render(pixel_buffer.width(), pixel_buffer.height(), |
87 | /// static_cast<unsigned char *>(pixel_buffer.begin())); |
88 | /// slint::Image image(pixel_buffer); |
89 | /// ``` |
90 | /// |
91 | /// Another use-case is to import existing image data into Slint, by |
92 | /// creating a new Image through copying of the buffer: |
93 | /// |
94 | /// ```cpp |
95 | /// slint::Image image(slint::SharedPixelBuffer<slint::Rgb8Pixel>(the_width, the_height, |
96 | /// static_cast<slint::Rgb8Pixel*>(the_data)); |
97 | /// ``` |
98 | /// |
99 | /// This only works if the static_cast is valid and the underlying data has the same |
100 | /// memory layout as slint::Rgb8Pixel or slint::Rgba8Pixel. Otherwise, you will have to do a |
101 | /// pixel conversion as you copy the pixels: |
102 | /// |
103 | /// ```cpp |
104 | /// slint::SharedPixelBuffer::<slint::Rgb8Pixel> pixel_buffer(the_width, the_height); |
105 | /// slint::Rgb8Pixel *raw_data = pixel_buffer.begin(); |
106 | /// for (int i = 0; i < the_width * the_height; i++) { |
107 | /// raw_data[i] = { bgr_data[i * 3 + 2], bgr_data[i * 3 + 1], bgr_data[i * 3] }; |
108 | /// } |
109 | /// ``` |
110 | struct Image |
111 | { |
112 | public: |
113 | /// This enum describes the origin to use when rendering a borrowed OpenGL texture. |
114 | enum class BorrowedOpenGLTextureOrigin { |
115 | /// The top-left of the texture is the top-left of the texture drawn on the screen. |
116 | TopLeft, |
117 | /// The bottom-left of the texture is the top-left of the texture draw on the screen, |
118 | /// flipping it vertically. |
119 | BottomLeft, |
120 | }; |
121 | |
122 | Image() : data(Data::ImageInner_None()) { } |
123 | |
124 | #ifndef SLINT_FEATURE_FREESTANDING |
125 | /// Load an image from an image file |
126 | [[nodiscard]] static Image load_from_path(const SharedString &file_path) |
127 | { |
128 | Image img; |
129 | cbindgen_private::types::slint_image_load_from_path(path: &file_path, image: &img.data); |
130 | return img; |
131 | } |
132 | #endif |
133 | |
134 | /// Constructs a new Image from an existing OpenGL texture. The texture remains borrowed by |
135 | /// Slint for the duration of being used for rendering, such as when assigned as source property |
136 | /// to an `Image` element. It's the application's responsibility to delete the texture when it |
137 | /// is not used anymore. |
138 | /// |
139 | /// The texture must be bindable against the `GL_TEXTURE_2D` target, have `GL_RGBA` as format |
140 | /// for the pixel data. |
141 | /// |
142 | /// When Slint renders the texture, it assumes that the origin of the texture is at the |
143 | /// top-left. This is different from the default OpenGL coordinate system. If you want to |
144 | /// flip the origin, use BorrowedOpenGLTextureOrigin::BottomLeft. |
145 | /// |
146 | /// Safety: |
147 | /// |
148 | /// This function is unsafe because invalid texture ids may lead to undefined behavior in OpenGL |
149 | /// drivers. A valid texture id is one that was created by the same OpenGL context that is |
150 | /// current during any of the invocations of the callback set on |
151 | /// [`Window::set_rendering_notifier()`]. OpenGL contexts between instances of [`slint::Window`] |
152 | /// are not sharing resources. Consequently |
153 | /// [`slint::Image`] objects created from borrowed OpenGL textures cannot be shared between |
154 | /// different windows. |
155 | [[nodiscard]] static Image create_from_borrowed_gl_2d_rgba_texture( |
156 | uint32_t texture_id, Size<uint32_t> size, |
157 | BorrowedOpenGLTextureOrigin origin = BorrowedOpenGLTextureOrigin::TopLeft) |
158 | { |
159 | cbindgen_private::types::BorrowedOpenGLTextureOrigin origin_private = |
160 | origin == BorrowedOpenGLTextureOrigin::TopLeft |
161 | ? cbindgen_private::types::BorrowedOpenGLTextureOrigin::TopLeft |
162 | : cbindgen_private::types::BorrowedOpenGLTextureOrigin::BottomLeft; |
163 | return Image(Data::ImageInner_BorrowedOpenGLTexture( |
164 | 0: cbindgen_private::types::BorrowedOpenGLTexture { |
165 | .texture_id: texture_id, |
166 | .size: size, |
167 | .origin: origin_private, |
168 | }) |
169 | |
170 | ); |
171 | } |
172 | |
173 | /// Construct an image from a SharedPixelBuffer of RGB pixels. |
174 | Image(SharedPixelBuffer<Rgb8Pixel> buffer) |
175 | : data(Data::ImageInner_EmbeddedImage( |
176 | cache_key: cbindgen_private::types::ImageCacheKey::Invalid(), |
177 | buffer: cbindgen_private::types::SharedImageBuffer::RGB8( |
178 | 0: cbindgen_private::types::SharedPixelBuffer<Rgb8Pixel> { |
179 | .width = buffer.width(), |
180 | .height = buffer.height(), |
181 | .data = buffer.m_data }))) |
182 | { |
183 | } |
184 | |
185 | /// Construct an image from a SharedPixelBuffer of RGBA pixels. |
186 | Image(SharedPixelBuffer<Rgba8Pixel> buffer) |
187 | : data(Data::ImageInner_EmbeddedImage( |
188 | cache_key: cbindgen_private::types::ImageCacheKey::Invalid(), |
189 | buffer: cbindgen_private::types::SharedImageBuffer::RGBA8( |
190 | 0: cbindgen_private::types::SharedPixelBuffer<Rgba8Pixel> { |
191 | .width = buffer.width(), |
192 | .height = buffer.height(), |
193 | .data = buffer.m_data }))) |
194 | { |
195 | } |
196 | |
197 | /// Returns the size of the Image in pixels. |
198 | Size<uint32_t> size() const |
199 | { |
200 | return cbindgen_private::types::slint_image_size(image: &data); |
201 | } |
202 | |
203 | /// Returns the path of the image on disk, if it was constructed via Image::load_from_path(). |
204 | std::optional<slint::SharedString> path() const |
205 | { |
206 | if (auto *str = cbindgen_private::types::slint_image_path(image: &data)) { |
207 | return *str; |
208 | } else { |
209 | return {}; |
210 | } |
211 | } |
212 | |
213 | /// Sets the nine-slice edges of the image. |
214 | /// |
215 | /// [Nine-slice scaling](https://en.wikipedia.org/wiki/9-slice_scaling) is a method for scaling |
216 | /// images in such a way that the corners are not distorted. |
217 | /// The arguments define the pixel sizes of the edges that cut the image into 9 slices. |
218 | void set_nine_slice_edges(unsigned short top, unsigned short right, unsigned short bottom, |
219 | unsigned short left) |
220 | { |
221 | cbindgen_private::types::slint_image_set_nine_slice_edges(image: &data, top, right, bottom, left); |
222 | } |
223 | |
224 | /// Returns true if \a a refers to the same image as \a b; false otherwise. |
225 | friend bool operator==(const Image &a, const Image &b) |
226 | { |
227 | return cbindgen_private::types::slint_image_compare_equal(image1: &a.data, image2: &b.data); |
228 | } |
229 | /// Returns false if \a a refers to the same image as \a b; true otherwise. |
230 | friend bool operator!=(const Image &a, const Image &b) |
231 | { |
232 | return !(a == b); |
233 | } |
234 | |
235 | /// \private |
236 | explicit Image(cbindgen_private::types::Image inner) : data(inner) { } |
237 | |
238 | private: |
239 | using Tag = cbindgen_private::types::ImageInner::Tag; |
240 | using Data = cbindgen_private::types::Image; |
241 | Data data; |
242 | }; |
243 | |
244 | namespace private_api { |
245 | inline Image load_image_from_embedded_data(std::span<const uint8_t> data, |
246 | std::string_view extension) |
247 | { |
248 | cbindgen_private::types::Image img(cbindgen_private::types::Image::ImageInner_None()); |
249 | cbindgen_private::types::slint_image_load_from_embedded_data( |
250 | data: slint::cbindgen_private::Slice<uint8_t> { const_cast<uint8_t *>(data.data()), |
251 | data.size() }, |
252 | format: slint::cbindgen_private::Slice<uint8_t> { |
253 | const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(extension.data())), |
254 | extension.size() }, |
255 | image: &img); |
256 | return Image(img); |
257 | } |
258 | |
259 | inline Image image_from_embedded_textures(const cbindgen_private::types::StaticTextures *textures) |
260 | { |
261 | cbindgen_private::types::Image img(cbindgen_private::types::Image::ImageInner_None()); |
262 | cbindgen_private::types::slint_image_from_embedded_textures(textures, image: &img); |
263 | return Image(img); |
264 | } |
265 | } |
266 | |
267 | } |
268 | |