| 1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
| 2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 |
| 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 | friend class Window; |
| 68 | uint32_t m_width; |
| 69 | uint32_t m_height; |
| 70 | SharedVector<Pixel> m_data; |
| 71 | }; |
| 72 | |
| 73 | /// An image type that can be displayed by the Image element |
| 74 | /// |
| 75 | /// You can construct Image objects from a path to an image file on disk, using |
| 76 | /// Image::load_from_path(). |
| 77 | /// |
| 78 | /// Another typical use-case is to render the image content with C++ code. |
| 79 | /// For this it’s most efficient to create a new SharedPixelBuffer with the known dimensions and |
| 80 | /// pass the pixel pointer returned by begin() to your rendering function. Afterwards you can create |
| 81 | /// an Image using the constructor taking a SharedPixelBuffer. |
| 82 | /// |
| 83 | /// The following example creates a 320x200 RGB pixel buffer and calls a function to draw a shape |
| 84 | /// into it: |
| 85 | /// ```cpp |
| 86 | /// slint::SharedPixelBuffer::<slint::Rgb8Pixel> pixel_buffer(320, 200); |
| 87 | /// low_level_render(pixel_buffer.width(), pixel_buffer.height(), |
| 88 | /// static_cast<unsigned char *>(pixel_buffer.begin())); |
| 89 | /// slint::Image image(pixel_buffer); |
| 90 | /// ``` |
| 91 | /// |
| 92 | /// Another use-case is to import existing image data into Slint, by |
| 93 | /// creating a new Image through copying of the buffer: |
| 94 | /// |
| 95 | /// ```cpp |
| 96 | /// slint::Image image(slint::SharedPixelBuffer<slint::Rgb8Pixel>(the_width, the_height, |
| 97 | /// static_cast<slint::Rgb8Pixel*>(the_data)); |
| 98 | /// ``` |
| 99 | /// |
| 100 | /// This only works if the static_cast is valid and the underlying data has the same |
| 101 | /// memory layout as slint::Rgb8Pixel or slint::Rgba8Pixel. Otherwise, you will have to do a |
| 102 | /// pixel conversion as you copy the pixels: |
| 103 | /// |
| 104 | /// ```cpp |
| 105 | /// slint::SharedPixelBuffer::<slint::Rgb8Pixel> pixel_buffer(the_width, the_height); |
| 106 | /// slint::Rgb8Pixel *raw_data = pixel_buffer.begin(); |
| 107 | /// for (int i = 0; i < the_width * the_height; i++) { |
| 108 | /// raw_data[i] = { bgr_data[i * 3 + 2], bgr_data[i * 3 + 1], bgr_data[i * 3] }; |
| 109 | /// } |
| 110 | /// ``` |
| 111 | struct Image |
| 112 | { |
| 113 | public: |
| 114 | /// This enum describes the origin to use when rendering a borrowed OpenGL texture. |
| 115 | enum class BorrowedOpenGLTextureOrigin { |
| 116 | /// The top-left of the texture is the top-left of the texture drawn on the screen. |
| 117 | TopLeft, |
| 118 | /// The bottom-left of the texture is the top-left of the texture draw on the screen, |
| 119 | /// flipping it vertically. |
| 120 | BottomLeft, |
| 121 | }; |
| 122 | |
| 123 | Image() : data(Data::ImageInner_None()) { } |
| 124 | |
| 125 | #if !defined(SLINT_FEATURE_FREESTANDING) || defined(DOXYGEN) |
| 126 | /// Load an image from an image file |
| 127 | [[nodiscard]] static Image load_from_path(const SharedString &file_path) |
| 128 | { |
| 129 | Image img; |
| 130 | cbindgen_private::types::slint_image_load_from_path(path: &file_path, image: &img.data); |
| 131 | return img; |
| 132 | } |
| 133 | #endif |
| 134 | |
| 135 | /// Constructs a new Image from an existing OpenGL texture. The texture remains borrowed by |
| 136 | /// Slint for the duration of being used for rendering, such as when assigned as source property |
| 137 | /// to an `Image` element. It's the application's responsibility to delete the texture when it |
| 138 | /// is not used anymore. |
| 139 | /// |
| 140 | /// The texture must be bindable against the `GL_TEXTURE_2D` target, have `GL_RGBA` as format |
| 141 | /// for the pixel data. |
| 142 | /// |
| 143 | /// When Slint renders the texture, it assumes that the origin of the texture is at the |
| 144 | /// top-left. This is different from the default OpenGL coordinate system. If you want to |
| 145 | /// flip the origin, use BorrowedOpenGLTextureOrigin::BottomLeft. |
| 146 | /// |
| 147 | /// Safety: |
| 148 | /// |
| 149 | /// This function is unsafe because invalid texture ids may lead to undefined behavior in OpenGL |
| 150 | /// drivers. A valid texture id is one that was created by the same OpenGL context that is |
| 151 | /// current during any of the invocations of the callback set on |
| 152 | /// [`Window::set_rendering_notifier()`]. OpenGL contexts between instances of [`slint::Window`] |
| 153 | /// are not sharing resources. Consequently |
| 154 | /// [`slint::Image`] objects created from borrowed OpenGL textures cannot be shared between |
| 155 | /// different windows. |
| 156 | [[nodiscard]] static Image create_from_borrowed_gl_2d_rgba_texture( |
| 157 | uint32_t texture_id, Size<uint32_t> size, |
| 158 | BorrowedOpenGLTextureOrigin origin = BorrowedOpenGLTextureOrigin::TopLeft) |
| 159 | { |
| 160 | cbindgen_private::types::BorrowedOpenGLTextureOrigin origin_private = |
| 161 | origin == BorrowedOpenGLTextureOrigin::TopLeft |
| 162 | ? cbindgen_private::types::BorrowedOpenGLTextureOrigin::TopLeft |
| 163 | : cbindgen_private::types::BorrowedOpenGLTextureOrigin::BottomLeft; |
| 164 | return Image(Data::ImageInner_BorrowedOpenGLTexture( |
| 165 | 0: cbindgen_private::types::BorrowedOpenGLTexture { |
| 166 | .texture_id: texture_id, |
| 167 | .size: size, |
| 168 | .origin: origin_private, |
| 169 | }) |
| 170 | |
| 171 | ); |
| 172 | } |
| 173 | |
| 174 | /// Construct an image from a SharedPixelBuffer of RGB pixels. |
| 175 | Image(SharedPixelBuffer<Rgb8Pixel> buffer) |
| 176 | : data(Data::ImageInner_EmbeddedImage( |
| 177 | cbindgen_private::types::ImageCacheKey::Invalid(), |
| 178 | cbindgen_private::types::SharedImageBuffer::RGB8( |
| 179 | cbindgen_private::types::SharedPixelBuffer<Rgb8Pixel> { |
| 180 | .width = buffer.width(), |
| 181 | .height = buffer.height(), |
| 182 | .data = buffer.m_data }))) |
| 183 | { |
| 184 | } |
| 185 | |
| 186 | /// Construct an image from a SharedPixelBuffer of RGBA pixels. |
| 187 | Image(SharedPixelBuffer<Rgba8Pixel> buffer) |
| 188 | : data(Data::ImageInner_EmbeddedImage( |
| 189 | cbindgen_private::types::ImageCacheKey::Invalid(), |
| 190 | cbindgen_private::types::SharedImageBuffer::RGBA8( |
| 191 | cbindgen_private::types::SharedPixelBuffer<Rgba8Pixel> { |
| 192 | .width = buffer.width(), |
| 193 | .height = buffer.height(), |
| 194 | .data = buffer.m_data }))) |
| 195 | { |
| 196 | } |
| 197 | |
| 198 | /// Returns the size of the Image in pixels. |
| 199 | Size<uint32_t> size() const { return cbindgen_private::types::slint_image_size(&data); } |
| 200 | |
| 201 | /// Returns the path of the image on disk, if it was constructed via Image::load_from_path(). |
| 202 | std::optional<slint::SharedString> path() const |
| 203 | { |
| 204 | if (auto *str = cbindgen_private::types::slint_image_path(&data)) { |
| 205 | return *str; |
| 206 | } else { |
| 207 | return {}; |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | /// Sets the nine-slice edges of the image. |
| 212 | /// |
| 213 | /// [Nine-slice scaling](https://en.wikipedia.org/wiki/9-slice_scaling) is a method for scaling |
| 214 | /// images in such a way that the corners are not distorted. |
| 215 | /// The arguments define the pixel sizes of the edges that cut the image into 9 slices. |
| 216 | void set_nine_slice_edges(unsigned short top, unsigned short right, unsigned short bottom, |
| 217 | unsigned short left) |
| 218 | { |
| 219 | cbindgen_private::types::slint_image_set_nine_slice_edges(&data, top, right, bottom, left); |
| 220 | } |
| 221 | |
| 222 | /// Returns the pixel buffer for the Image if available in RGB format without alpha. |
| 223 | /// Returns nullopt if the pixels cannot be obtained, for example when the image was created |
| 224 | /// from borrowed OpenGL textures. |
| 225 | std::optional<SharedPixelBuffer<Rgb8Pixel>> to_rgb8() const |
| 226 | { |
| 227 | SharedPixelBuffer<Rgb8Pixel> result; |
| 228 | if (cbindgen_private ::types::slint_image_to_rgb8(&data, &result.m_data, &result.m_width, |
| 229 | &result.m_height)) { |
| 230 | return result; |
| 231 | } else { |
| 232 | return {}; |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | /// Returns the pixel buffer for the Image if available in RGBA format. |
| 237 | /// Returns nullopt if the pixels cannot be obtained, for example when the image was created |
| 238 | /// from borrowed OpenGL textures. |
| 239 | std::optional<SharedPixelBuffer<Rgba8Pixel>> to_rgba8() const |
| 240 | { |
| 241 | SharedPixelBuffer<Rgba8Pixel> result; |
| 242 | if (cbindgen_private ::types::slint_image_to_rgba8(&data, &result.m_data, &result.m_width, |
| 243 | &result.m_height)) { |
| 244 | return result; |
| 245 | } else { |
| 246 | return {}; |
| 247 | } |
| 248 | } |
| 249 | |
| 250 | /// Returns the pixel buffer for the Image if available in RGBA format, with the alpha channel |
| 251 | /// pre-multiplied to the red, green, and blue channels. Returns nullopt if the pixels cannot be |
| 252 | /// obtained, for example when the image was created from borrowed OpenGL textures. |
| 253 | std::optional<SharedPixelBuffer<Rgba8Pixel>> to_rgba8_premultiplied() const |
| 254 | { |
| 255 | SharedPixelBuffer<Rgba8Pixel> result; |
| 256 | if (cbindgen_private ::types::slint_image_to_rgba8_premultiplied( |
| 257 | &data, &result.m_data, &result.m_width, &result.m_height)) { |
| 258 | return result; |
| 259 | } else { |
| 260 | return {}; |
| 261 | } |
| 262 | } |
| 263 | |
| 264 | /// Returns true if \a a refers to the same image as \a b; false otherwise. |
| 265 | friend bool operator==(const Image &a, const Image &b) |
| 266 | { |
| 267 | return cbindgen_private::types::slint_image_compare_equal(image1: &a.data, image2: &b.data); |
| 268 | } |
| 269 | /// Returns false if \a a refers to the same image as \a b; true otherwise. |
| 270 | friend bool operator!=(const Image &a, const Image &b) { return !(a == b); } |
| 271 | |
| 272 | /// \private |
| 273 | explicit Image(cbindgen_private::types::Image inner) : data(inner) { } |
| 274 | |
| 275 | private: |
| 276 | using Tag = cbindgen_private::types::ImageInner::Tag; |
| 277 | using Data = cbindgen_private::types::Image; |
| 278 | Data data; |
| 279 | }; |
| 280 | |
| 281 | namespace private_api { |
| 282 | inline Image load_image_from_embedded_data(std::span<const uint8_t> data, |
| 283 | std::string_view extension) |
| 284 | { |
| 285 | cbindgen_private::types::Image img(cbindgen_private::types::Image::ImageInner_None()); |
| 286 | cbindgen_private::types::slint_image_load_from_embedded_data( |
| 287 | data: slint::cbindgen_private::Slice<uint8_t> { const_cast<uint8_t *>(data.data()), |
| 288 | data.size() }, |
| 289 | format: slint::cbindgen_private::Slice<uint8_t> { |
| 290 | const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(extension.data())), |
| 291 | extension.size() }, |
| 292 | image: &img); |
| 293 | return Image(img); |
| 294 | } |
| 295 | |
| 296 | inline Image image_from_embedded_textures(const cbindgen_private::types::StaticTextures *textures) |
| 297 | { |
| 298 | cbindgen_private::types::Image img(cbindgen_private::types::Image::ImageInner_None()); |
| 299 | cbindgen_private::types::slint_image_from_embedded_textures(textures, image: &img); |
| 300 | return Image(img); |
| 301 | } |
| 302 | } |
| 303 | |
| 304 | } |
| 305 | |