// zlib open source license // // Copyright (c) 2017 to 2025 David Forsgren Piuva // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // Everything stored directly in the image types is immutable to allow value types to behave like reference types using the data that they point to. // Image types can not be dynamically casted, because the inheritance is entirely static without any virtual functions. #ifndef DFPSR_API_IMAGE #define DFPSR_API_IMAGE #include "../implementation/image/Image.h" #include "../implementation/image/Color.h" #include "../implementation/math/scalar.h" #include "../base/heap.h" namespace dsr { // Constructors // Pre-conditions: // 1 <= width <= 65536 // 1 <= height <= 65536 // Post-condition: // Returns a new image of width x height pixels, or an empty image on failure. // Warning: Setting zeroed to false will initialize the image with non-deterministic pixel data. // This can make execution faster but also complicate tracking down errors. // Only recommended for temporary images when you know for sure that all content will be overwritten. AlignedImageU8 image_create_U8(int32_t width, int32_t height, bool zeroed = true); AlignedImageU16 image_create_U16(int32_t width, int32_t height, bool zeroed = true); AlignedImageF32 image_create_F32(int32_t width, int32_t height, bool zeroed = true); OrderedImageRgbaU8 image_create_RgbaU8(int32_t width, int32_t height, bool zeroed = true); AlignedImageRgbaU8 image_create_RgbaU8_native(int32_t width, int32_t height, PackOrderIndex packOrderIndex, bool zeroed = true); // Properties // Returns image's width in pixels, or 0 from an empty image inline int32_t image_getWidth(const Image& image) { return image.impl_dimensions.getWidth(); } // Returns image's height in pixels, or 0 from an empty image inline int32_t image_getHeight(const Image& image) { return image.impl_dimensions.getHeight(); } // Stride is the offset from the beginning of one row to another. // May be larger than the image's width to align with cache lines or share pixel data with a wider image. // When you add a variable offset to a pointer in C++, the added offset is multiplied by the element size because the address is always stored in bytes. // Because all pixels have a power of two size, the multiplication can be optimized into a bit shift. // On ARM, adding whole elements to a pointer is just as fast as adding bytes, by shifting and adding in the same instruction. // On Intel/AMD, shifting and adding needs two instructions, so then it makes sense to pre-calculate the stride as bytes and cast to uint8_t* when adding. // Returns image's stride in whole pixels, or 0 from an empty image // Used when incrementing indices instead of pointers. inline int32_t image_getPixelStride(const Image& image) { return (intptr_t)image.impl_dimensions.getPixelStride(); } // Returns image's stride in bytes, or 0 from an empty image. inline int32_t image_getStride(const Image& image) { return (image_getPixelStride(image) << (uintptr_t)image.impl_dimensions.getLog2PixelSize()); } inline int32_t image_getStride(const ImageU8& image) { return image_getPixelStride(image); } // pixelStride * sizeof(uint8_t ) inline int32_t image_getStride(const ImageU16& image) { return image_getPixelStride(image) << 1; } // pixelStride * sizeof(uint16_t) inline int32_t image_getStride(const ImageF32& image) { return image_getPixelStride(image) << 2; } // pixelStride * sizeof(float ) inline int32_t image_getStride(const ImageRgbaU8& image) { return image_getPixelStride(image) << 2; } // pixelStride * sizeof(uint32_t) // Returns image's offset from the allocation start in whole pixels, or 0 from an empty image inline int64_t image_getPixelStartOffset(const Image& image) { return (int64_t)image.impl_dimensions.getPixelStartOffset(); } // Returns image's offset from the allocation start in bytes, or 0 from an empty image inline int64_t image_getStartOffset(const ImageU8& image) { return image_getPixelStartOffset(image); } // pixelStartOffset * sizeof(uint8_t ) inline int64_t image_getStartOffset(const ImageU16& image) { return image_getPixelStartOffset(image) << 1; } // pixelStartOffset * sizeof(uint16_t) inline int64_t image_getStartOffset(const ImageF32& image) { return image_getPixelStartOffset(image) << 2; } // pixelStartOffset * sizeof(float ) inline int64_t image_getStartOffset(const ImageRgbaU8& image) { return image_getPixelStartOffset(image) << 2; } // pixelStartOffset * sizeof(uint32_t) // Get a rectangle from the image's dimensions with the top left corner set to (0, 0). // Useful for clipping to an image's bounds or subdividing space for a graphical user interface. // Returns IRect(0, 0, 0, 0) for empty images. inline IRect image_getBound(const Image& image) { return IRect(0, 0, image.impl_dimensions.getWidth(), image.impl_dimensions.getHeight()); } // Returns false on null, true otherwise. inline bool image_exists(const Image& image) { return image.impl_buffer.isNotNull(); } // TODO: Rename into image_getUseCount for easier use. // Returns the number of handles to the image. // References to a handle doesn't count, only when a handle is stored by value. inline uintptr_t image_useCount(const Image& image) { return image.impl_buffer.getUseCount(); } // Returns the image's pack order index. inline PackOrderIndex image_getPackOrderIndex(const ImageRgbaU8& image) { return image.impl_dimensions.getPackOrderIndex(); } // Returns the image's pack order, containing bit masks and offsets needed to pack and unpack colors. inline PackOrder image_getPackOrder(const ImageRgbaU8& image) { return PackOrder::getPackOrder(image.impl_dimensions.getPackOrderIndex()); }; // Returns true iff the pixel at (x, y) is inside of image. inline bool image_isPixelInside(const Image& image, int32_t x, int32_t y) { return x >= 0 && x < image_getWidth(image) && y >= 0 && y < image_getHeight(image); } // Returns the size of one pixel in bytes dynamically by looking it up. inline int32_t image_getPixelSize(const Image& image) { return image.impl_dimensions.getPixelSize(); } // Returns the size of one pixel in bytes statically from the type. template int32_t image_getPixelSize() { return T::impl_pixelSize; } // Channel packing // Extract one channel AlignedImageU8 image_get_red (const ImageRgbaU8& image); AlignedImageU8 image_get_green(const ImageRgbaU8& image); AlignedImageU8 image_get_blue (const ImageRgbaU8& image); AlignedImageU8 image_get_alpha(const ImageRgbaU8& image); // Pack one channel OrderedImageRgbaU8 image_pack(const ImageU8& red, int32_t green, int32_t blue, int32_t alpha); OrderedImageRgbaU8 image_pack(int32_t red, const ImageU8& green, int32_t blue, int32_t alpha); OrderedImageRgbaU8 image_pack(int32_t red, int32_t green, const ImageU8& blue, int32_t alpha); OrderedImageRgbaU8 image_pack(int32_t red, int32_t green, int32_t blue, const ImageU8& alpha); // Pack two channels OrderedImageRgbaU8 image_pack(const ImageU8& red, const ImageU8& green, int32_t blue, int32_t alpha); OrderedImageRgbaU8 image_pack(const ImageU8& red, int32_t green, const ImageU8& blue, int32_t alpha); OrderedImageRgbaU8 image_pack(const ImageU8& red, int32_t green, int32_t blue, const ImageU8& alpha); OrderedImageRgbaU8 image_pack(int32_t red, const ImageU8& green, const ImageU8& blue, int32_t alpha); OrderedImageRgbaU8 image_pack(int32_t red, const ImageU8& green, int32_t blue, const ImageU8& alpha); OrderedImageRgbaU8 image_pack(int32_t red, int32_t green, const ImageU8& blue, const ImageU8& alpha); // Pack three channels OrderedImageRgbaU8 image_pack(int32_t red, const ImageU8& green, const ImageU8& blue, const ImageU8& alpha); OrderedImageRgbaU8 image_pack(const ImageU8& red, int32_t green, const ImageU8& blue, const ImageU8& alpha); OrderedImageRgbaU8 image_pack(const ImageU8& red, const ImageU8& green, int32_t blue, const ImageU8& alpha); OrderedImageRgbaU8 image_pack(const ImageU8& red, const ImageU8& green, const ImageU8& blue, int32_t alpha); // Pack four channels OrderedImageRgbaU8 image_pack(const ImageU8& red, const ImageU8& green, const ImageU8& blue, const ImageU8& alpha); // Pack a color to draw with using the image's pack order, as it would be represented as a pixel in the buffer. inline uint32_t image_pack(const ImageRgbaU8& image, uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha) { return PackOrder::getPackOrder(image.impl_dimensions.getPackOrderIndex()).packRgba(red, green, blue, alpha); } // Saturate and pack a color for an image's pack order, as it would be represented as a pixel in the buffer. inline uint32_t image_saturateAndPack(const ImageRgbaU8& image, const ColorRgbaI32& color) { return PackOrder::getPackOrder(image.impl_dimensions.getPackOrderIndex()).saturateAndPackRgba(color); } // Truncate and pack a color for an image's pack order, as it would be represented as a pixel in the buffer. inline uint32_t image_truncateAndPack(const ImageRgbaU8& image, const ColorRgbaI32& color) { return PackOrder::getPackOrder(image.impl_dimensions.getPackOrderIndex()).truncateAndPackRgba(color); } // Unpack a color back into an expanded and ordered RGBA format. // packedColor is expressed in image's pack order. inline ColorRgbaI32 image_unpack(const ImageRgbaU8& image, uint32_t packedColor) { return PackOrder::getPackOrder(image.impl_dimensions.getPackOrderIndex()).unpackRgba(packedColor); } // Pixel access // Pre-condition: // The pixel at (x, y) must exist within the image, or else the program may crash. // image_isPixelInsize(image, x, y) // Post-condition: // Returns a reference to the pixel at (x, y) in image. inline uint8_t &image_accessPixel(const ImageU8& image, int32_t x, int32_t y) { uintptr_t pixelOffset = image_getPixelStartOffset(image) + y * image_getPixelStride(image) + x; return *(buffer_getSafeData(image.impl_buffer, "ImageU8 pixel access buffer") + pixelOffset); } inline uint16_t &image_accessPixel(const ImageU16& image, int32_t x, int32_t y) { uintptr_t pixelOffset = image_getPixelStartOffset(image) + y * image_getPixelStride(image) + x; return *(buffer_getSafeData(image.impl_buffer, "ImageU16 pixel access buffer") + pixelOffset); } inline float &image_accessPixel(const ImageF32& image, int32_t x, int32_t y) { uintptr_t pixelOffset = image_getPixelStartOffset(image) + y * image_getPixelStride(image) + x; return *(buffer_getSafeData(image.impl_buffer, "ImageF32 pixel access buffer") + pixelOffset); } inline uint32_t &image_accessPixel(const ImageRgbaU8& image, int32_t x, int32_t y) { uintptr_t pixelOffset = image_getPixelStartOffset(image) + y * image_getPixelStride(image) + x; return *(buffer_getSafeData(image.impl_buffer, "ImageRgbaU8 pixel access buffer") + pixelOffset); } // Write a pixel to an image. // Out of bound is ignored silently without writing. // Empty images will be ignored safely. // Packed is faster if the color can be packed in advance for multiple pixels or comes directly from an image of the same rgba order. // Saturated to 0..255 inline void image_writePixel(const ImageU8& image, int32_t x, int32_t y, int32_t color) { if (image_isPixelInside(image, x, y)) image_accessPixel(image, x, y) = clamp(0, color, 255); } // Saturated to 0..65535 inline void image_writePixel(const ImageU16& image, int32_t x, int32_t y, int32_t color) { if (image_isPixelInside(image, x, y)) image_accessPixel(image, x, y) = clamp(0, color, 65535); } // No saturation needed inline void image_writePixel(const ImageF32& image, int32_t x, int32_t y, float color) { if (image_isPixelInside(image, x, y)) image_accessPixel(image, x, y) = color; } // Saturated to 0..255 in all channels inline void image_writePixel(const ImageRgbaU8& image, int32_t x, int32_t y, const ColorRgbaI32& color) { if (image_isPixelInside(image, x, y)) image_accessPixel(image, x, y) = image_saturateAndPack(image, color); } // Pre-packed color using image_saturateAndPack to create the pixel in advance. inline void image_writePixel(const ImageRgbaU8& image, int32_t x, int32_t y, uint32_t packedColor) { if (image_isPixelInside(image, x, y)) image_accessPixel(image, x, y) = packedColor; } // Read a pixel from an image with a solid border outside. // Out of bound will return the border color. // The border color does not have to be constrained to the limits of pixel storage. // Empty images will return zero. inline int32_t image_readPixel_border(const ImageU8& image, int32_t x, int32_t y, int32_t border = 0) { if (!image_exists(image)) { return 0; } else if (image_isPixelInside(image, x, y)) { return image_accessPixel(image, x, y); } else { return border; } } // The packed version is identical to the unpacked version, so we make a wrapper for template functions to call. //inline int32_t image_readPixel_border_packed(const ImageU8& image, int32_t x, int32_t y, int32_t border = 0) { return image_readPixel_border(image, x, y, border); } inline int32_t image_readPixel_border(const ImageU16& image, int32_t x, int32_t y, int32_t border = 0) { if (!image_exists(image)) { return 0; } else if (image_isPixelInside(image, x, y)) { return image_accessPixel(image, x, y); } else { return border; } } //inline int32_t image_readPixel_border_packed(const ImageU16& image, int32_t x, int32_t y, int32_t border = 0) { return image_readPixel_border(image, x, y, border); } inline float image_readPixel_border(const ImageF32& image, int32_t x, int32_t y, float border = 0.0f) { if (!image_exists(image)) { return 0.0f; } else if (image_isPixelInside(image, x, y)) { return image_accessPixel(image, x, y); } else { return border; } } //inline float image_readPixel_border_packed(const ImageF32& image, int32_t x, int32_t y, int32_t border = 0) { return image_readPixel_border(image, x, y, border); } inline ColorRgbaI32 image_readPixel_border(const ImageRgbaU8& image, int32_t x, int32_t y, const ColorRgbaI32& border = ColorRgbaI32()) { if (!image_exists(image)) { return ColorRgbaI32(0, 0, 0, 0); } else if (image_isPixelInside(image, x, y)) { return image_unpack(image, image_accessPixel(image, x, y)); } else { return border; } } // Read the color directly as it is packed in image's pack order. inline uint32_t image_readPixel_border_packed(const ImageRgbaU8& image, int32_t x, int32_t y, uint32_t border = 0) { if (!image_exists(image)) { return 0; } else if (image_isPixelInside(image, x, y)) { return image_accessPixel(image, x, y); } else { return border; } } // Read a pixel from an image stretched edges. // Out of bound will return the closest pixel. // Empty images will return zero. inline uint8_t image_readPixel_clamp(const ImageU8& image, int32_t x, int32_t y) { if (image_exists(image)) { return image_accessPixel(image, clamp(0, x, image_getWidth(image) - 1), clamp(0, y, image_getHeight(image) - 1)); } else { return 0; } } //inline uint8_t image_readPixel_clamp_packed(const ImageU8& image, int32_t x, int32_t y) { return image_readPixel_clamp(image, x, y); } inline uint16_t image_readPixel_clamp(const ImageU16& image, int32_t x, int32_t y) { if (image_exists(image)) { return image_accessPixel(image, clamp(0, x, image_getWidth(image) - 1), clamp(0, y, image_getHeight(image) - 1)); } else { return 0; } } //inline uint16_t image_readPixel_clamp_packed(const ImageU16& image, int32_t x, int32_t y) { return image_readPixel_clamp(image, x, y); } inline float image_readPixel_clamp(const ImageF32& image, int32_t x, int32_t y) { if (image_exists(image)) { return image_accessPixel(image, clamp(0, x, image_getWidth(image) - 1), clamp(0, y, image_getHeight(image) - 1)); } else { return 0.0f; } } //inline float image_readPixel_clamp_packed(const ImageF32& image, int32_t x, int32_t y) { return image_readPixel_clamp(image, x, y); } inline ColorRgbaI32 image_readPixel_clamp(const ImageRgbaU8& image, int32_t x, int32_t y) { if (image_exists(image)) { return image_unpack(image, image_accessPixel(image, clamp(0, x, image_getWidth(image) - 1), clamp(0, y, image_getHeight(image) - 1))); } else { return ColorRgbaI32(); } } // Read the color directly as it is packed in image's pack order. inline uint32_t image_readPixel_clamp_packed(const ImageRgbaU8& image, int32_t x, int32_t y) { if (image_exists(image)) { return image_accessPixel(image, clamp(0, x, image_getWidth(image) - 1), clamp(0, y, image_getHeight(image) - 1)); } else { return 0; } } // Read a pixel from an image with tiling. // Out of bound will take the coordinates in modulo of the size. // Empty images will return zero. inline uint8_t image_readPixel_tile(const ImageU8& image, int32_t x, int32_t y) { if (image_exists(image)) { return image_accessPixel(image, signedModulo(x, image_getWidth(image)), signedModulo(y, image_getHeight(image))); } else { return 0; } } //inline uint8_t image_readPixel_tile_packed(const ImageU8& image, int32_t x, int32_t y) { return image_readPixel_tile(image, x, y); } inline uint16_t image_readPixel_tile(const ImageU16& image, int32_t x, int32_t y) { if (image_exists(image)) { return image_accessPixel(image, signedModulo(x, image_getWidth(image)), signedModulo(y, image_getHeight(image))); } else { return 0; } } //inline uint16_t image_readPixel_tile_packed(const ImageU16& image, int32_t x, int32_t y) { return image_readPixel_tile(image, x, y); } inline float image_readPixel_tile(const ImageF32& image, int32_t x, int32_t y) { if (image_exists(image)) { return image_accessPixel(image, signedModulo(x, image_getWidth(image)), signedModulo(y, image_getHeight(image))); } else { return 0.0f; } } //inline float image_readPixel_tile_packed(const ImageF32& image, int32_t x, int32_t y) { return image_readPixel_tile(image, x, y); } inline ColorRgbaI32 image_readPixel_tile(const ImageRgbaU8& image, int32_t x, int32_t y) { if (image_exists(image)) { return image_unpack(image, image_accessPixel(image, signedModulo(x, image_getWidth(image)), signedModulo(y, image_getHeight(image)))); } else { return ColorRgbaI32(); } } // Read the color directly as it is packed in image's pack order. inline uint32_t image_readPixel_tile_packed(const ImageRgbaU8& image, int32_t x, int32_t y) { if (image_exists(image)) { return image_accessPixel(image, signedModulo(x, image_getWidth(image)), signedModulo(y, image_getHeight(image))); } else { return 0; } } // Loading // Load an image from a file by giving the filename including folder path and extension. // If mustExist is true, an exception will be raised on failure. // If mustExist is false, failure will return an empty handle. OrderedImageRgbaU8 image_load_RgbaU8(const ReadableString& filename, bool mustExist = true); // Load an image from a memory buffer, which can be loaded with file_loadBuffer to get the same result as loading directly from the file. // A convenient way of loading compressed images from larger files. // Failure will return an empty handle. OrderedImageRgbaU8 image_decode_RgbaU8(const Buffer& fileContent); // A faster and more flexible way to load compressed images from memory. // If you just want to point directly to a memory location to avoid allocating many small buffers, you can use a safe pointer and a size in bytes. // Failure will return an empty handle. OrderedImageRgbaU8 image_decode_RgbaU8(SafePointer data, int size); // Saving // Save the image to the path specified by filename and return true iff the operation was successful. // The file extension is case insensitive after the last dot in filename. // Accepted file extensions: // *.jpg or *.jpeg // *.png // *.tga or *.targa // *.bmp // If mustWork is true, an exception will be raised on failure. // If mustWork is false, failure will return false. // The optional quality setting goes from 1% to 100% and is at the maximum by default. bool image_save(const ImageRgbaU8 &image, const ReadableString& filename, bool mustWork = true, int quality = 100); // Save the image to a memory buffer. // Post-condition: Returns a buffer with the encoded image format as it would be saved to a file, or empty on failure. // No exceptions will be raised on failure, because an error message without a filename would not explain much. // The optional quality setting goes from 1% to 100% and is at the maximum by default. Buffer image_encode(const ImageRgbaU8 &image, ImageFileFormat format, int quality = 90); // Fill all pixels with a uniform color void image_fill(const ImageU8& image, int32_t color); void image_fill(const ImageU16& image, int32_t color); void image_fill(const ImageF32& image, float color); void image_fill(const ImageRgbaU8& image, const ColorRgbaI32& color); // Clone // Get a deep clone of an image's content while discarding any pack order, padding and texture pyramids. // If the input image had a different pack order, it will automatically be converted into RGBA to preserve the colors. AlignedImageU8 image_clone(const ImageU8& image); AlignedImageU16 image_clone(const ImageU16& image); AlignedImageF32 image_clone(const ImageF32& image); OrderedImageRgbaU8 image_clone(const ImageRgbaU8& image); // Returns a copy of the image without any padding, which means that alignment cannot be guaranteed. // The pack order is the same as the input, becuase it just copies the memory one row at a time to be fast. // Used when external image libraries don't allow giving stride as a separate argument. ImageRgbaU8 image_removePadding(const ImageRgbaU8& image); // Ascii images String image_toAscii(const ImageU8& image, const String &alphabet); String image_toAscii(const ImageU8& image); AlignedImageU8 image_fromAscii(const String &content); // Comparisons // Get the maximum pixelwise difference between two images of the same format, or the highest possible value on failure // Useful for regression tests uint8_t image_maxDifference(const ImageU8& imageA, const ImageU8& imageB); uint16_t image_maxDifference(const ImageU16& imageA, const ImageU16& imageB); float image_maxDifference(const ImageF32& imageA, const ImageF32& imageB); uint8_t image_maxDifference(const ImageRgbaU8& imageA, const ImageRgbaU8& imageB); // TODO: Create sub-image constructors in the image types. // Sub-images are read/write views to a smaller region of the same pixel data. // Get a sub-image sharing buffer and side-effects with the parent image // Returns the overlapping region if out of bound // Returns a null image if there are no overlapping pixels to return inline ImageU8 image_getSubImage(const ImageU8& image, const IRect& region) { static_assert(sizeof(ImageU8) == sizeof(Image), "ImageU8 must have the same size as Image, to prevent slicing in assignments!"); return ImageU8(image, region); } inline ImageU16 image_getSubImage(const ImageU16& image, const IRect& region) { static_assert(sizeof(ImageU16) == sizeof(Image), "ImageU16 must have the same size as Image, to prevent slicing in assignments!"); return ImageU16(image, region); } inline ImageF32 image_getSubImage(const ImageF32& image, const IRect& region) { static_assert(sizeof(ImageF32) == sizeof(Image), "ImageF32 must have the same size as Image, to prevent slicing in assignments!"); return ImageF32(image, region); } inline ImageRgbaU8 image_getSubImage(const ImageRgbaU8& image, const IRect& region) { static_assert(sizeof(ImageRgbaU8) == sizeof(Image), "ImageRgbaU8 must have the same size as Image, to prevent slicing in assignments!"); return ImageRgbaU8(image, region); } // Check dynamically if the image was created as a sub-image. // Returns true if the image is a sub-image, created using image_getSubImage. // Returns false if the image is not a sub-image, created using image_create or default constructed as an empty image. inline bool image_isSubImage(const Image& image) { return image.impl_dimensions.isSubImage(); } // Bound-checked pointer access (relatively safe compared to a raw pointer) // Returns a bound-checked pointer to the first pixel. template inline SafePointer image_getSafePointer(const ImageU8& image) { return image.impl_buffer.getSafe("Pointer to ImageU8 pixels").increaseBytes(image_getStartOffset(image)); } // Returns a bound-checked pointer to the first pixel at rowIndex. template inline SafePointer image_getSafePointer(const ImageU8& image, int32_t rowIndex) { return image_getSafePointer(image).increaseBytes(image_getStride(image) * rowIndex); } // Returns a bound-checked pointer to the first pixel. template inline SafePointer image_getSafePointer(const ImageU16& image) { return image.impl_buffer.getSafe("Pointer to ImageU16 pixels").increaseBytes(image_getStartOffset(image)); } // Returns a bound-checked pointer to the first pixel at rowIndex. template inline SafePointer image_getSafePointer(const ImageU16& image, int32_t rowIndex) { return image_getSafePointer(image).increaseBytes(image_getStride(image) * rowIndex); } // Returns a bound-checked pointer to the first pixel. template inline SafePointer image_getSafePointer(const ImageF32& image) { return image.impl_buffer.getSafe("Pointer to ImageF32 pixels").increaseBytes(image_getStartOffset(image)); } // Returns a bound-checked pointer to the first pixel at rowIndex. template inline SafePointer image_getSafePointer(const ImageF32& image, int32_t rowIndex) { return image_getSafePointer(image).increaseBytes(image_getStride(image) * rowIndex); } // Returns a bound-checked pointer to the first pixel. template inline SafePointer image_getSafePointer(const ImageRgbaU8& image) { return image.impl_buffer.getSafe("Pointer to ImageRgbaU8 pixels").increaseBytes(image_getStartOffset(image)); } // Returns a bound-checked pointer to the first pixel at rowIndex. template inline SafePointer image_getSafePointer(const ImageRgbaU8& image, int32_t rowIndex) { return image_getSafePointer(image).increaseBytes(image_getStride(image) * rowIndex); } // Returns a bound-checked pointer to the first channel in the first pixel. inline SafePointer image_getSafePointer_channels(const ImageRgbaU8& image) { return image.impl_buffer.getSafe("Pointer to ImageRgbaU8 channels").increaseBytes(image_getStartOffset(image)); } // Returns a bound-checked pointer to the first channel in the first pixel at rowIndex. inline SafePointer image_getSafePointer_channels(const ImageRgbaU8& image, int32_t rowIndex) { return image.impl_buffer.getSafe("Pointer to ImageRgbaU8 channels").increaseBytes(image_getStartOffset(image)).increaseBytes(image_getStride(image) * rowIndex); } // The dangerous image API // Use of these methods can be spotted using a search for "_dangerous_" in your code // Replaces the destructor in image's buffer, which. // newDestructor should not free the given data, only invoke destruction of any external resources that may depend on it before the data is freed automatically. inline void image_dangerous_replaceDestructor(ImageU8& image, const HeapDestructor &newDestructor) { if (image_exists(image)) { return buffer_replaceDestructor(image.impl_buffer, newDestructor); } } inline void image_dangerous_replaceDestructor(ImageU16& image, const HeapDestructor &newDestructor) { if (image_exists(image)) { return buffer_replaceDestructor(image.impl_buffer, newDestructor); } } inline void image_dangerous_replaceDestructor(ImageF32& image, const HeapDestructor &newDestructor) { if (image_exists(image)) { return buffer_replaceDestructor(image.impl_buffer, newDestructor); } } inline void image_dangerous_replaceDestructor(ImageRgbaU8& image, const HeapDestructor &newDestructor) { if (image_exists(image)) { return buffer_replaceDestructor(image.impl_buffer, newDestructor); } } // Returns a pointer to the image's pixels // Warning! Reading elements larger than 8 bits will have lower and higher bytes stored based on local endianness // Warning! Using bytes outside of the [0 .. stride * height - 1] range may cause crashes and undefined behaviour // Warning! Using the pointer after the image's lifetime may cause crashes from trying to access freed memory inline uint8_t* image_dangerous_getData(const ImageU8& image) { return image.impl_buffer.getUnsafe() + image_getStartOffset(image); } inline uint8_t* image_dangerous_getData(const ImageU16& image) { return image.impl_buffer.getUnsafe() + image_getStartOffset(image); } inline uint8_t* image_dangerous_getData(const ImageF32& image) { return image.impl_buffer.getUnsafe() + image_getStartOffset(image); } inline uint8_t* image_dangerous_getData(const ImageRgbaU8& image) { return image.impl_buffer.getUnsafe() + image_getStartOffset(image); } } #endif