// zlib open source license // // Copyright (c) 2019 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. #ifndef DFPSR_IMAGE_TYPES #define DFPSR_IMAGE_TYPES #include "PackOrder.h" #include "../../math/IRect.h" #include "../../api/bufferAPI.h" namespace dsr { enum class ImageFileFormat { Unknown, // Used as an error code for unidentified formats. JPG, // Lossy compressed image format storing brightness separated from red and blue offsets using the discrete cosine transform of each block. PNG, // Lossless compressed image format. Some image editors don't save RGB values where alpha is zero, which will bleed through black edges in bi-linear interpolation when the interpolated alpha is not zero. TGA, // Lossless compressed format. Applications usually give Targa better control over the alpha channel than PNG, but it's more common that the Targa specification is interpreted in incompatible ways. BMP // Uncompressed image format for storing data that does not really represent an image and you just want it to be exact. }; // Packed into 2 bits in ImageDimensions. enum class PixelFormat : uint32_t { MonoU8, // Gray-scale image of 8 bits per pixel (0..255). MonoU16, MonoF32, RgbaU8 // RGBA colors in any order. 8 bits per channel (0..255). 32 bits per pixel. }; // Start offset and stride is stored in pixels and the getters in imageAPI can automatically convert them into byte offsets as needed. // Maximum image dimensions are 65536 x 65536, because that will precisely fit the worst case start offset into uint32_t. // maxPixelCount = 65536² = 4294967296 // maxStartOffset = maxPixelCount - 1 = 4294967295 // largest uint32_t = 2³² - 1 = 4294967295 // Because the computer will do bitwise operations to read and write small integers anyway, // there is usually no performance penalty for choosing an odd number of bits to pack more information. class ImageDimensions { private: // Bit masks and offsets for the properties that are packed into the same 64-bit integer. static const uint64_t readMask_width = 0b1111111111111111100000000000000000000000000000000000000000000000; // 0 zeroes, 17 ones, 47 zeroes static const uint32_t inputMask_width = 0b11111111111111111 ; // 17 ones static const int bitOffset_width = 47 ; static const uint64_t readMask_height = 0b0000000000000000011111111111111111000000000000000000000000000000; // 17 zeroes, 17 ones, 30 zeroes static const uint32_t inputMask_height = 0b11111111111111111 ; // 17 ones static const int bitOffset_height = 30 ; static const uint64_t readMask_stride = 0b0000000000000000000000000000000000111111111111111110000000000000; // 34 zeroes, 17 ones, 13 zeroes static const uint32_t inputMask_stride = 0b11111111111111111 ; // 17 ones static const int bitOffset_stride = 13 ; static const uint64_t readMask_packOrder = 0b0000000000000000000000000000000000000000000000000001100000000000; // 51 zeroes, 2 ones, 11 zeroes static const uint32_t inputMask_packOrder = 0b11 ; // 2 ones static const int bitOffset_packOrder = 11 ; static const uint64_t readMask_format = 0b0000000000000000000000000000000000000000000000000000011000000000; // 52 zeroes, 2 ones, 9 zeroes static const uint32_t inputMask_format = 0b11 ; // 2 ones static const int bitOffset_format = 9 ; static const uint64_t readMask_subImage = 0b0000000000000000000000000000000000000000000000000000000100000000; // 55 zeroes, 1 ones, 8 zeroes private: // Actual members. uint64_t data = 0; uint32_t pixelStartOffset = 0; // This one fits exactly into 32 bits, but we will have 32 more bits of padding. private: // Helper functions. static inline uint64_t readFrom64(const uint64_t &source, uint64_t readMask, uint32_t bitOffset) { return (source & readMask) >> bitOffset; } public: // Access to the data. inline uint32_t getWidth() const { return ImageDimensions::readFrom64(this->data, readMask_width, bitOffset_width); } inline uint32_t getHeight() const { return ImageDimensions::readFrom64(this->data, readMask_height, bitOffset_height); } inline uint32_t getPixelStride() const { return ImageDimensions::readFrom64(this->data, readMask_stride, bitOffset_stride); } inline PackOrderIndex getPackOrderIndex() const { return (PackOrderIndex)readFrom64(this->data, readMask_packOrder, bitOffset_packOrder); } inline PixelFormat getPixelFormat() const { return (PixelFormat)readFrom64(this->data, readMask_format, bitOffset_format); } inline bool isSubImage() const { // No need to shift the bit before normalizing, because anything else than zero becomes 1. return (this->data & readMask_subImage) != 0; } inline uint32_t getLog2PixelSize() const { // Shift the constants instead of the index to save a cycle. uint64_t shifterPixelFormatIndex = this->data & readMask_format; if (shifterPixelFormatIndex == ((uint64_t)PixelFormat::MonoU8 << bitOffset_format)) { return 0; } else if (shifterPixelFormatIndex == ((uint64_t)PixelFormat::MonoU16 << bitOffset_format)) { return 1; } else if (shifterPixelFormatIndex == ((uint64_t)PixelFormat::MonoF32 << bitOffset_format)) { return 2; } else if (shifterPixelFormatIndex == ((uint64_t)PixelFormat::RgbaU8 << bitOffset_format)) { return 2; } else { return 0; // Unknown pixel format! } } inline uint32_t getPixelSize() const { // Shift the constants instead of the index to save a cycle. uint64_t shifterPixelFormatIndex = this->data & readMask_format; if (shifterPixelFormatIndex == ((uint64_t)PixelFormat::MonoU8 << bitOffset_format)) { return 1; } else if (shifterPixelFormatIndex == ((uint64_t)PixelFormat::MonoU16 << bitOffset_format)) { return 2; } else if (shifterPixelFormatIndex == ((uint64_t)PixelFormat::MonoF32 << bitOffset_format)) { return 4; } else if (shifterPixelFormatIndex == ((uint64_t)PixelFormat::RgbaU8 << bitOffset_format)) { return 4; } else { return 0; // Unknown pixel format! } } inline uint32_t getPixelStartOffset() const { return this->pixelStartOffset; } inline uintptr_t getByteStartOffset() const { return uintptr_t(this->getPixelStartOffset()) << this->getLog2PixelSize(); } inline uintptr_t getByteStride() const { return uintptr_t(this->getPixelStride()) << this->getLog2PixelSize(); } // Constuction that truncates individual inputs in modulo, just to make sure that too large values do not affect other values and make debugging into a nightmare. ImageDimensions(uint32_t width, uint32_t height, uint32_t pixelStride, PackOrderIndex packOrderIndex, PixelFormat pixelFormat, uint32_t pixelStartOffset) noexcept : data(((uint64_t)( width & inputMask_width ) << bitOffset_width) | ((uint64_t)( height & inputMask_height ) << bitOffset_height) | ((uint64_t)( pixelStride & inputMask_stride ) << bitOffset_stride) | ((uint64_t)(((uint32_t)packOrderIndex) & inputMask_packOrder ) << bitOffset_packOrder) | ((uint64_t)(((uint32_t)pixelFormat) & inputMask_format ) << bitOffset_format) ), pixelStartOffset(pixelStartOffset) {} ImageDimensions() {} void setWidthHeightStartSubImage(uint32_t width, uint32_t height, uint32_t pixelStartOffset) { this->data = (this->data & ~(readMask_width | readMask_height)) | ((uint64_t)(width & inputMask_width ) << bitOffset_width) | ((uint64_t)(height & inputMask_height ) << bitOffset_height) | readMask_subImage; this->pixelStartOffset = pixelStartOffset; } }; #define IMPL_IMAGE_CONSTRUCTORS(NEW_TYPE, BASE_TYPE) \ NEW_TYPE() {} \ NEW_TYPE(const Buffer &buffer, ImageDimensions dimensions) : BASE_TYPE(buffer, dimensions) {} \ NEW_TYPE(const NEW_TYPE &source) : BASE_TYPE(source.impl_buffer, source.impl_dimensions) {} \ NEW_TYPE(const NEW_TYPE &source, const IRect ®ion) \ : BASE_TYPE(source.impl_buffer, source.impl_dimensions) { \ IRect cut = IRect::cut(IRect(0, 0, source.impl_dimensions.getWidth(), source.impl_dimensions.getHeight()), region); \ if (cut.hasArea()) { \ this->impl_dimensions.setWidthHeightStartSubImage(cut.width(), cut.height(), source.impl_dimensions.getPixelStartOffset() + cut.left() + cut.top() * source.impl_dimensions.getPixelStride()); \ } else { \ this->impl_buffer = Buffer(); \ this->impl_dimensions = ImageDimensions(); \ } \ } #define IMPL_IMAGE_HIGHER_CONSTRUCTORS(NEW_TYPE, BASE_TYPE) \ IMPL_IMAGE_CONSTRUCTORS(NEW_TYPE, BASE_TYPE) \ NEW_TYPE(const Buffer &buffer, uint32_t pixelStartOffset, uint32_t width, uint32_t height, uint32_t pixelStride, const PackOrderIndex &packOrderIndex) \ : BASE_TYPE(buffer, pixelStartOffset, width, height, pixelStride, packOrderIndex) {} // Use imageAPI.h to access the content of images! // The content may change between library versions but is public to simplify access for inlined getters. struct Image { // PRIVATE: // To maintain encapsulation from version specific details, do not touch things starting with IMPL_ or impl_. // Use the inlined getters in imageAPI.h instead. // Reference counted pointer to the pixel data. Buffer impl_buffer; // Dimensions and pack order of the image. ImageDimensions impl_dimensions; // New. Image(const Buffer &buffer, ImageDimensions dimensions) : impl_buffer(buffer), impl_dimensions(dimensions) {} // Generic cut. Image(const Image &source, const IRect ®ion) : impl_buffer(source.impl_buffer), impl_dimensions(source.impl_dimensions) { IRect cut = IRect::cut(IRect(0, 0, source.impl_dimensions.getWidth(), source.impl_dimensions.getHeight()), region); if (cut.hasArea()) { this->impl_dimensions.setWidthHeightStartSubImage(cut.width(), cut.height(), source.impl_dimensions.getPixelStartOffset() + cut.left() + cut.top() * source.impl_dimensions.getPixelStride()); } else { this->impl_buffer = Buffer(); this->impl_dimensions = ImageDimensions(); } } // Empty. Image() {} }; // Can be unaligned. // Is not allowed to overwrite padding bytes, because it does not know the difference between padding and pixels belonging to a larger image sharing the same pixel buffer. struct ImageU8 : public Image { static const int impl_pixelSize = 1; ImageU8(const Buffer &buffer, uint32_t pixelStartOffset, uint32_t width, uint32_t height, uint32_t pixelStride, const PackOrderIndex &packOrderIndex) : Image(buffer, ImageDimensions(width, height, pixelStride, packOrderIndex, PixelFormat::MonoU8, pixelStartOffset)) {} IMPL_IMAGE_CONSTRUCTORS(ImageU8, Image) }; // The start of each row is aligned to DSR_MAXIMUM_ALIGNMENT for SIMD vectorization and thread safety. // Owns the padding bytes and may overwrite them during SIMD vectorization. struct AlignedImageU8 : public ImageU8 { IMPL_IMAGE_HIGHER_CONSTRUCTORS(AlignedImageU8, ImageU8) }; // Can be unaligned. // Is not allowed to overwrite padding bytes, because it does not know the difference between padding and pixels belonging to a larger image sharing the same pixel buffer. struct ImageU16 : public Image { static const int impl_pixelSize = 2; ImageU16(const Buffer &buffer, uint32_t pixelStartOffset, uint32_t width, uint32_t height, uint32_t pixelStride, const PackOrderIndex &packOrderIndex) : Image(buffer, ImageDimensions(width, height, pixelStride, packOrderIndex, PixelFormat::MonoU16, pixelStartOffset)) {} IMPL_IMAGE_CONSTRUCTORS(ImageU16, Image) }; // The start of each row is aligned to DSR_MAXIMUM_ALIGNMENT for SIMD vectorization and thread safety. // Owns the padding bytes and may overwrite them during SIMD vectorization. struct AlignedImageU16 : public ImageU16 { IMPL_IMAGE_HIGHER_CONSTRUCTORS(AlignedImageU16, ImageU16) }; // Can be unaligned. // Is not allowed to overwrite padding bytes, because it does not know the difference between padding and pixels belonging to a larger image sharing the same pixel buffer. struct ImageF32 : public Image { static const int impl_pixelSize = 4; ImageF32(const Buffer &buffer, uint32_t pixelStartOffset, uint32_t width, uint32_t height, uint32_t pixelStride, const PackOrderIndex &packOrderIndex) : Image(buffer, ImageDimensions(width, height, pixelStride, packOrderIndex, PixelFormat::MonoF32, pixelStartOffset)) {} IMPL_IMAGE_CONSTRUCTORS(ImageF32, Image) }; // The start of each row is aligned to DSR_MAXIMUM_ALIGNMENT for SIMD vectorization and thread safety. // Owns the padding bytes and may overwrite them during SIMD vectorization. struct AlignedImageF32 : public ImageF32 { IMPL_IMAGE_HIGHER_CONSTRUCTORS(AlignedImageF32, ImageF32) }; // Can be unaligned. // Is not allowed to overwrite padding bytes, because it does not know the difference between padding and pixels belonging to a larger image sharing the same pixel buffer. // Can have any pack order. struct ImageRgbaU8 : public Image { static const int impl_pixelSize = 4; ImageRgbaU8(const Buffer &buffer, uint32_t pixelStartOffset, uint32_t width, uint32_t height, uint32_t pixelStride, const PackOrderIndex &packOrderIndex) : Image(buffer, ImageDimensions(width, height, pixelStride, packOrderIndex, PixelFormat::RgbaU8, pixelStartOffset)) {} IMPL_IMAGE_CONSTRUCTORS(ImageRgbaU8, Image) }; // The start of each row is aligned to DSR_MAXIMUM_ALIGNMENT for SIMD vectorization and thread safety. // Owns the padding bytes and may overwrite them during SIMD vectorization. // Can have any pack order. struct AlignedImageRgbaU8 : public ImageRgbaU8 { IMPL_IMAGE_HIGHER_CONSTRUCTORS(AlignedImageRgbaU8, ImageRgbaU8) }; // The start of each row is aligned to DSR_MAXIMUM_ALIGNMENT for SIMD vectorization and thread safety. // Owns the padding bytes and may overwrite them during SIMD vectorization. // Always in RGBA order. struct OrderedImageRgbaU8 : public AlignedImageRgbaU8 { IMPL_IMAGE_HIGHER_CONSTRUCTORS(OrderedImageRgbaU8, AlignedImageRgbaU8) }; #undef IMPL_IMAGE_CONSTRUCTORS #undef IMPL_IMAGE_HIGHER_CONSTRUCTORS } #endif