// -*- tab-width: 4; -*- // vi: set sw=2 ts=4 expandtab: // Copyright 2010-2020 The Khronos Group Inc. // SPDX-License-Identifier: Apache-2.0 //! //! @internal //! @~English //! @file //! //! @brief Internal Image class //! #ifndef IMAGE_HPP #define IMAGE_HPP #include #include #include #include #include #include #include #include #include #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4201) #endif #include #ifdef _MSC_VER #pragma warning(pop) #endif #include "imageio_utility.h" #include "unused.h" #include "encoder/basisu_resampler.h" #include "encoder/basisu_resampler_filters.h" // cclamp to avoid conflict in toktx.cc with clamp template defined in scApp. template inline T cclamp(T value, T low, T high) { return (value < low) ? low : ((value > high) ? high : value); } template inline T saturate(T value) { return cclamp(value, 0, 1.0f); } template inline S maximum(S a, S b) { return (a > b) ? a : b; } template inline S minimum(S a, S b) { return (a < b) ? a : b; } #if defined(_MSC_VER) #define INLINE __inline #else #define INLINE __inline__ #endif struct ColorPrimaryTransform { ColorPrimaryTransform() {} ColorPrimaryTransform(const std::vector& elements) { for (uint32_t i = 0; i < 3; ++i) for (uint32_t j = 0; j < 3; ++j) matrix[i][j] = elements[i * 3 + j]; } float matrix[3][3]; }; // The detailed description of the TransferFunctions can be found at: // https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html#TRANSFER_CONVERSION struct TransferFunction { virtual float encode(const float intensity) const = 0; virtual float decode(const float brightness) const = 0; virtual ~TransferFunction() {} }; struct TransferFunctionLinear : public TransferFunction { float encode(const float intensity) const override { return intensity; } float decode(const float brightness) const override { return brightness; } }; struct TransferFunctionGamma : public TransferFunction { TransferFunctionGamma(float oeGamma) : oeGamma_{oeGamma}, eoGamma_{1.f / oeGamma} {} float encode(const float intensity) const override { return saturate(powf(intensity, oeGamma_)); } float decode(const float brightness) const override { return saturate(powf(brightness, eoGamma_)); } private: const float oeGamma_; const float eoGamma_; }; struct TransferFunctionSRGB : public TransferFunction { float encode(const float intensity) const override { float brightness; if (intensity < 0.0031308f) brightness = 12.92f * intensity; else brightness = 1.055f * pow(intensity, 1.0f/2.4f) - 0.055f; return brightness; } float decode(const float brightness) const override { float intensity; if (brightness < .04045f) intensity = saturate(brightness * (1.0f/12.92f)); else intensity = saturate(powf((brightness + .055f) * (1.0f/1.055f), 2.4f)); return intensity; } }; struct TransferFunctionITU : public TransferFunction { float encode(const float intensity) const override { float brightness; if (intensity < linearCutoff_) brightness = intensity * linearExpansion_; else brightness = 1.099f * pow(intensity, oeGamma_) - 0.099f; return brightness; } float decode(const float brightness) const override { float intensity; if (brightness < linearCutoff_ * linearExpansion_) intensity = brightness / linearExpansion_; else intensity = pow((brightness + 0.099f) / 1.099f, eoGamma_); return intensity; } private: /* We're following what Netpbm does. This is their comment and code. */ /* Here are parameters of the gamma transfer function for the Netpbm formats. This is ITU-R Recommendation BT.709, FKA CIE Rec 709. It is also ITU-R Recommendation BT.601, FKA CCIR 601. This transfer function is linear for sample values 0 .. .018 and an exponential for larger sample values. The exponential is slightly stretched and translated, though, unlike the popular pure exponential gamma transfer function. The standard actually defines the linear expansion as 4.500, which means there is a discontinuity at linear intensity .018. We instead use ~4.514 to make a continuous function. This may have been simply a mistake when this code was written or based on an actual benefit to having a continuous function -- The history is not clear. Note that the discrepancy is below the precision of a maxval 255 image. */ const float eoGamma_{2.2f}; const float oeGamma_{1.0f / eoGamma_}; const float linearCutoff_{0.018f}; const float linearExpansion_{(1.099f * pow(linearCutoff_, oeGamma_) - 0.099f) / linearCutoff_}; }; struct TransferFunctionBT2100_PQ_EOTF : public TransferFunction { float decode(const float brightness) const override { float Ym1 = pow(brightness, m1_); return pow((c1_ + c2_ * Ym1) / (1 + c3_ * Ym1), m2_); } float encode(const float intensity) const override { float Erm2 = pow(intensity, rm2_); return std::pow(std::max(Erm2 - c1_, 0.f) / (c2_ - c3_ * Erm2), m1_); } private: const float m1_{0.1593017578125f}; // const float rm1_{1.f / m1_}; // Commented to stop unused warning const float m2_{78.84375f}; const float rm2_{1.f / m2_}; const float c1_{0.8359375f}; const float c2_{18.8515625f}; const float c3_{18.6875}; }; // The detailed description of the ColorPrimaries can be found at: // https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html#PRIMARY_CONVERSION struct ColorPrimaries { ColorPrimaries(const ColorPrimaryTransform& inToXYZ, const ColorPrimaryTransform& inFromXYZ) : toXYZ(inToXYZ), fromXYZ(inFromXYZ) {} ColorPrimaryTransform transformTo(const ColorPrimaries& targetPrimaries) const { ColorPrimaryTransform result; for (uint32_t i = 0; i < 3; ++i) for (uint32_t j = 0; j < 3; ++j) { result.matrix[i][j] = 0; for (uint32_t k = 0; k < 3; ++k) result.matrix[i][j] += toXYZ.matrix[i][k] * targetPrimaries.fromXYZ.matrix[k][j]; } return result; } const ColorPrimaryTransform toXYZ; const ColorPrimaryTransform fromXYZ; }; struct ColorPrimariesBT709 : public ColorPrimaries { ColorPrimariesBT709() : ColorPrimaries( ColorPrimaryTransform({ +0.412391f, +0.357584f, +0.180481f, +0.212639f, +0.715169f, +0.072192f, +0.019331f, +0.119195f, +0.950532f }), ColorPrimaryTransform({ +3.240970f, -1.537383f, -0.498611f, -0.969244f, +1.875968f, +0.041555f, +0.055630f, -0.203977f, +1.056972f }) ) {} }; struct ColorPrimariesBT601_625_EBU : public ColorPrimaries { ColorPrimariesBT601_625_EBU() : ColorPrimaries( ColorPrimaryTransform({ +0.430554f, +0.341550f, +0.178352f, +0.222004f, +0.706655f, +0.071341f, +0.020182f, +0.129553f, +0.939322f }), ColorPrimaryTransform({ +3.063361f, -1.393390f, -0.475824f, -0.969244f, +1.875968f, +0.041555f, +0.067861f, -0.228799f, +1.069090f }) ) {} }; struct ColorPrimariesBT601_525_SMPTE : public ColorPrimaries { ColorPrimariesBT601_525_SMPTE() : ColorPrimaries( ColorPrimaryTransform({ +0.393521f, +0.365258f, +0.191677f, +0.212376f, +0.701060f, +0.086564f, +0.018739f, +0.111934f, +0.958385f }), ColorPrimaryTransform({ +3.506003f, -1.739791f, -0.544058f, -1.069048f, +1.977779f, +0.035171f, +0.056307f, -0.196976f, +1.049952f }) ) {} }; struct ColorPrimariesBT2020 : public ColorPrimaries { ColorPrimariesBT2020() : ColorPrimaries( ColorPrimaryTransform({ +0.636958f, +0.144617f, +0.168881f, +0.262700f, +0.677998f, +0.059302f, +0.000000f, +0.028073f, +1.060985f }), ColorPrimaryTransform({ +1.716651f, -0.355671f, -0.253366f, -0.666684f, +1.616481f, +0.015769f, +0.017640f, -0.042771f, +0.942103f }) ) {} }; struct ColorPrimariesCIEXYZ : public ColorPrimaries { ColorPrimariesCIEXYZ() : ColorPrimaries( ColorPrimaryTransform({ 1.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 1.f }), ColorPrimaryTransform({ 1.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 1.f }) ) {} }; struct ColorPrimariesACES : public ColorPrimaries { ColorPrimariesACES() : ColorPrimaries( ColorPrimaryTransform({ +0.9525523959f, 0.0000000000f, +0.0000936786f, +0.3439664498f, +0.7281660966f, -0.0721325464f, 0.0000000000f, 0.0000000000f, +1.0088251844f }), ColorPrimaryTransform({ +1.0498110175f, 0.0000000000f, -0.0000974845f, -0.4959030231f, +1.3733130458f, +0.0982400361f, 0.0000000000f, 0.0000000000f, +0.9912520182f }) ) {} }; struct ColorPrimariesACEScc : public ColorPrimaries { ColorPrimariesACEScc() : ColorPrimaries( ColorPrimaryTransform({ +0.6624541811f, +0.1340042065f, +0.1561876870f, +0.2722287168f, +0.6740817658f, +0.0536895174f, -0.0055746495f, +0.0040607335f, +1.0103391003f }), ColorPrimaryTransform({ +1.6410233797f, -0.3248032942f, -0.2464246952f, -0.6636628587f, +1.6153315917f, +0.0167563477f, +0.0117218943f, -0.0082844420f, +0.9883948585f }) ) {} }; struct ColorPrimariesNTSC1953 : public ColorPrimaries { ColorPrimariesNTSC1953() : ColorPrimaries( ColorPrimaryTransform({ +0.606993f, +0.173449f, +0.200571f, +0.298967f, +0.586421f, +0.114612f, +0.000000f, +0.066076f, +1.117469f }), ColorPrimaryTransform({ +1.909675f, -0.532365f, -0.288161f, -0.984965f, +1.999777f, -0.028317f, +0.058241f, -0.118246f, +0.896554f }) ) {} }; struct ColorPrimariesPAL525 : public ColorPrimaries { ColorPrimariesPAL525() : ColorPrimaries( ColorPrimaryTransform({ +0.415394f, +0.354637f, +0.210677f, +0.224181f, +0.680675f, +0.095145f, +0.019781f, +0.108679f, +1.053387f }), ColorPrimaryTransform({ +3.321392f, -1.648181f, -0.515410f, -1.101064f, +2.037011f, +0.036225f, +0.051228f, -0.179211f, +0.955260f }) ) {} }; struct ColorPrimariesDisplayP3 : public ColorPrimaries { ColorPrimariesDisplayP3() : ColorPrimaries( ColorPrimaryTransform({ +0.4865709486f, +0.2656676932f, +0.1982172852f, +0.2289745641f, +0.6917385218f, +0.0792869141f, 0.0000000000f, +0.0451133819f, +1.0439441689f }), ColorPrimaryTransform({ +2.4934969119f, -0.9313836179f, -0.4027107845f, -0.8294889696f, +1.7626640603f, +0.0236246858f, +0.0358458302f, -0.0761723893f, +0.9568845240f }) ) {} }; struct ColorPrimariesAdobeRGB : public ColorPrimaries { ColorPrimariesAdobeRGB() : ColorPrimaries( ColorPrimaryTransform({ +0.5766690429f, +0.1855582379f, +0.1882286462f, +0.2973449753f, +0.6273635663f, +0.0752914585f, +0.0270313614f, +0.0706888525f, +0.9913375368f }), ColorPrimaryTransform({ +2.0415879038f, -0.5650069743f, -0.3447313508f, -0.9692436363f, +1.8759675015f, +0.0415550574f, +0.0134442806f, -0.1183623922f, +1.0151749944f }) ) {} }; template class color_base { public: constexpr static uint32_t getComponentCount() { return componentCount; } constexpr static uint32_t getComponentSize() { return sizeof(componentType); } constexpr static uint32_t getPixelSize() { return componentCount * sizeof(componentType); } constexpr static componentType one() { if (std::is_floating_point_v) return componentType{1}; else return std::numeric_limits::max(); } constexpr static float rcpOne() { if (std::is_floating_point_v) return 1.f; else return 1.f / static_cast(std::numeric_limits::max()); } constexpr static float halfUnit() { if (std::is_floating_point_v) return 0.f; else return 0.5f / static_cast(std::numeric_limits::max()); } constexpr static componentType min() { return std::numeric_limits::min(); } constexpr static componentType max() { return std::numeric_limits::max(); } componentType clamp(componentType value) { return (value < min()) ? min() : ((value > max()) ? max() : value); } }; struct vec3_base { float r; float g; float b; vec3_base() : r(0.0f), g(0.0f), b(0.0f) {} vec3_base(float _r, float _g, float _b) : r(_r), g(_g), b(_b) {} void base_normalize() { float len = r * r + g * g + b * b; len = sqrtf(len); if (len > 0.0f) { r /= len; g /= len; b /= len; } } void clamp(float _a, float _b) { r = cclamp(r, _a, _b); g = cclamp(g, _a, _b); b = cclamp(b, _a, _b); } }; static constexpr float gc_m[5]={0.0f, 128.0f, 32768.0f, 0.0f, 2147483648.0f}; static constexpr uint32_t gc_s[5]={0, 255, 65535, 0, 4294967295}; template struct vec3 : public vec3_base { static constexpr uint32_t i = sizeof(componentType); explicit vec3(float _r) : vec3_base(_r, 0.0f, 0.0f) {} vec3(float _r, float _g) : vec3_base(_r, _g, 0.0f) {} vec3(float _r, float _g, float _b) : vec3_base(_r, _g, _b) {} void normalize() { // Zero normals in range [-1, 1] can't be normalized if (gc_m[i] == r && gc_m[i] == g && gc_m[i] == b) { return; } else { r = (float)(r / (double)gc_s[i]) * 2.0f - 1.0f; g = (float)(g / (double)gc_s[i]) * 2.0f - 1.0f; b = (float)(b / (double)gc_s[i]) * 2.0f - 1.0f; clamp(-1.0f, 1.0f); base_normalize(); r = (std::floor((r + 1.0f) * (float)gc_s[i] * 0.5f + 0.5f)); g = (std::floor((g + 1.0f) * (float)gc_s[i] * 0.5f + 0.5f)); b = (std::floor((b + 1.0f) * (float)gc_s[i] * 0.5f + 0.5f)); clamp(0.0f, (float)gc_s[i]); } } }; template<> struct vec3 : public vec3_base { explicit vec3(float _r) : vec3_base(_r, 0.0f, 0.0f) {} vec3(float _r, float _g) : vec3_base(_r, _g, 0.0f) {} vec3(float _r, float _g, float _b) : vec3_base(_r, _g, _b) {} void normalize(){ base_normalize(); } }; template class color { }; template class color : public color_base { public: using value_type = componentType; union { componentType comps[4]; struct { componentType r; componentType g; componentType b; componentType a; }; }; color() {} color(componentType _r, componentType _g, componentType _b, componentType _a) : r(_r), g(_g), b(_b), a(_a) {} const componentType& operator [](unsigned int i) const { if (i > 3) i = 3; return comps[i]; } componentType& operator [](unsigned int i) { if (i > 3) i = 3; return comps[i]; } template void set(uint32_t i, T val) { if (i > 3) i = 3; comps[i] = color_base::clamp((componentType)val); } void set(uint32_t i, componentType val) { if (i > 3) i = 3; comps[i] = val; } constexpr uint32_t comps_count() const { return 4; } void normalize() { vec3 v((float)r, (float)g, (float)b); v.normalize(); r = (componentType)v.r; g = (componentType)v.g; b = (componentType)v.b; } }; template class color : public color_base { public: using value_type = componentType; union { componentType comps[3]; struct { componentType r; componentType g; componentType b; }; }; color() {} color(componentType _r, componentType _g, componentType _b) : r(_r), g(_g), b(_b) {} color(componentType _r, componentType _g, componentType _b, componentType) : r(_r), g(_g), b(_b) {} const componentType& operator [](unsigned int i) const { if (i > 2) i = 2; return comps[i]; } componentType& operator [](unsigned int i) { if (i > 2) i = 2; return comps[i]; } template void set(uint32_t i, T val) { if (i > 2) i = 2; comps[i] = color_base::clamp((componentType)val); } void set(uint32_t i, componentType val) { if (i > 2) i = 2; comps[i] = val; } constexpr uint32_t comps_count() const { return 3; } void normalize() { vec3 v((float)r, (float)g, (float)b); v.normalize(); r = (componentType)v.r; g = (componentType)v.g; b = (componentType)v.b; } }; template class color : public color_base { public: using value_type = componentType; union { componentType comps[2]; struct { componentType r; componentType g; }; }; color() {} color(componentType _r, componentType _g) : r(_r), g(_g) {} color(componentType _r, componentType _g, componentType, componentType) : r(_r), g(_g) {} const componentType& operator [](unsigned int i) const { if (i > 1) i = 1; return comps[i]; } componentType& operator [](unsigned int i) { if (i > 1) i = 1; return comps[i]; } template void set(uint32_t i, T val) { if (i > 1) i = 1; comps[i] = color_base::clamp((componentType)val); } void set(uint32_t i, componentType val) { if (i > 1) i = 1; comps[i] = val; } constexpr uint32_t comps_count() const { return 2; } void normalize() { vec3 v((float)r, (float)g, (float)gc_s[sizeof(componentType)] * 0.5f); v.normalize(); r = (componentType)v.r; g = (componentType)v.g; } }; template class color : public color_base { public: using value_type = componentType; union { componentType comps[1]; struct { componentType r; }; }; color() {} color(componentType _r) : r(_r) {} color(componentType _r, componentType, componentType, componentType) : r(_r) {} const componentType& operator [](unsigned int i) const { if (i > 0) i = 0; return comps[i]; } componentType& operator [](unsigned int i) { if (i > 0) i = 0; return comps[i]; } template void set(uint32_t i, T val) { if (i > 0) i = 0; comps[i] = color_base::clamp((componentType)val); } void set(uint32_t i, componentType val) { if (i > 0) i = 0; comps[i] = val; } constexpr uint32_t comps_count() const { return 1; } void normalize() { // Normalizing single channel image doesn't make much sense // Here I assume single channel color is (X, 0, 0, 0) if (r != 0) r = (componentType)gc_s[sizeof(componentType)]; } }; // Abstract base class for all Images. class Image { public: class different_format : public std::runtime_error { public: different_format() : std::runtime_error("") { } }; class invalid_file : public std::runtime_error { public: invalid_file(std::string error) : std::runtime_error("Invalid file: " + error) { } }; virtual ~Image() { }; uint32_t getWidth() const { return width; } uint32_t getHeight() const { return height; } uint32_t getPixelCount() const { return width * height; } khr_df_transfer_e getTransferFunction() const { return transferFunction; } void setTransferFunction(khr_df_transfer_e tf) { transferFunction = tf; } khr_df_primaries_e getPrimaries() const { return primaries; } void setPrimaries(khr_df_primaries_e nprimaries) { this->primaries = nprimaries; } virtual operator uint8_t*() = 0; virtual size_t getByteCount() const = 0; virtual uint32_t getPixelSize() const = 0; virtual uint32_t getComponentCount() const = 0; virtual uint32_t getComponentSize() const = 0; virtual Image* createImage(uint32_t width, uint32_t height) = 0; /// Should only be used if the stored image data is UNORM convertable (with optional significant bit count) virtual std::vector getUNORM(uint32_t numChannels, uint32_t targetBits, uint32_t sBits = 0) const = 0; /// Should only be used if the stored image data is UNORM convertable (packed into a single word) virtual std::vector getUNORMPacked( uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3) const = 0; /// Should only be used if the stored image data is SFloat convertable virtual std::vector getSFloat(uint32_t numChannels, uint32_t targetBits) const = 0; /// Should only be used if the stored image data is UFloat convertable virtual std::vector getB10G11R11() const = 0; /// Should only be used if the stored image data is UFloat convertable virtual std::vector getE5B9G9R9() const = 0; /// Should only be used if the stored image data is UINT convertable virtual std::vector getUINT(uint32_t numChannels, uint32_t targetBits) const = 0; /// Should only be used if the stored image data is SINT convertable virtual std::vector getSINT(uint32_t numChannels, uint32_t targetBits) const = 0; /// Should only be used if the stored image data is UINT convertable virtual std::vector getUINTPacked(uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3) const = 0; /// Should only be used if the stored image data is SINT convertable virtual std::vector getSINTPacked(uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3) const = 0; virtual std::unique_ptr resample(uint32_t targetWidth, uint32_t targetHeight, const char* filter, float filterScale, basisu::Resampler::Boundary_Op wrapMode) = 0; virtual Image& yflip() = 0; virtual Image& transformColorSpace(const TransferFunction& decode, const TransferFunction& encode, const ColorPrimaryTransform* transformPrimaries = nullptr) = 0; virtual Image& normalize() = 0; virtual Image& swizzle(std::string_view swizzle) = 0; virtual Image& copyToR(Image&, std::string_view swizzle) = 0; virtual Image& copyToRG(Image&, std::string_view swizzle) = 0; virtual Image& copyToRGB(Image&, std::string_view swizzle) = 0; virtual Image& copyToRGBA(Image&, std::string_view swizzle) = 0; protected: Image() : Image(0, 0) { } Image(uint32_t w, uint32_t h) : width(w), height(h), transferFunction(KHR_DF_TRANSFER_UNSPECIFIED), primaries(KHR_DF_PRIMARIES_BT709) { } uint32_t width, height; // In pixels khr_df_transfer_e transferFunction; khr_df_primaries_e primaries; }; // Base class for template and specializations template class ImageT : public Image { friend class ImageT; friend class ImageT; friend class ImageT; friend class ImageT; public: using Color = color; ImageT(uint32_t w, uint32_t h) : Image(w, h) { size_t bytes = sizeof(Color) * w * h; pixels = (Color*)malloc(bytes); if (!pixels) throw std::bad_alloc(); freePixels = true; for (uint32_t p = 0; p < w * h; ++p) for(uint32_t c = 0; c < componentCount; ++c) pixels[p].comps[c] = componentType{0}; } ImageT(uint32_t w, uint32_t h, Color* pixels) : Image(w, h), pixels(pixels), freePixels(false) { } ~ImageT() { if (freePixels) free(pixels); } const Color& operator() (uint32_t x, uint32_t y) const { assert(x < width && y < height); return pixels[x + y * width]; } Color& operator() (uint32_t x, uint32_t y) { assert(x < width && y < height); return pixels[x + y * width]; } virtual operator uint8_t*() override { return (uint8_t*)pixels; } virtual size_t getByteCount() const override { return getPixelCount() * sizeof(Color); } virtual uint32_t getPixelSize() const override { return Color::getPixelSize(); } virtual uint32_t getComponentCount() const override { return Color::getComponentCount(); } virtual uint32_t getComponentSize() const override { return Color::getComponentSize(); } virtual Image* createImage(uint32_t w, uint32_t h) override { ImageT* image = new ImageT(w, h); return image; } virtual std::vector getUNORM(uint32_t numChannels, uint32_t targetBits, uint32_t sBits) const override { assert(numChannels <= componentCount); assert(targetBits == 8 || targetBits == 16 || targetBits == 32); const uint32_t sourceBits = sizeof(componentType) * 8; const uint32_t targetBytes = targetBits / 8; const uint32_t mask = (sBits == 0) ? 0xFFFFFFFF : ((1u << sBits) - 1u) << (targetBits - sBits); std::vector data(height * width * numChannels * targetBytes); for (uint32_t y = 0; y < height; ++y) { for (uint32_t x = 0; x < width; ++x) { for (uint32_t c = 0; c < numChannels; ++c) { const auto sourceValue = c < componentCount ? pixels[y * width + x][c] : (c != 3 ? componentType{0} : Color::one()); const auto value = imageio::convertUNORM(static_cast(sourceValue), sourceBits, targetBits); auto* target = data.data() + (y * width * numChannels + x * numChannels + c) * targetBytes; if (targetBytes == 1) { const auto outValue = static_cast(value & mask); std::memcpy(target, &outValue, sizeof(outValue)); } else if (targetBytes == 2) { const auto outValue = static_cast(value & mask); std::memcpy(target, &outValue, sizeof(outValue)); } else if (targetBytes == 4) { const auto outValue = static_cast(value & mask); std::memcpy(target, &outValue, sizeof(outValue)); } } } } return data; } virtual std::vector getUNORMPacked( uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3) const override { assert((c0 + c1 + c2 + c3) % 8 == 0); const auto targetPackBytes = (c0 + c1 + c2 + c3) / 8; assert(targetPackBytes == 1 || targetPackBytes == 2 || targetPackBytes == 4 || targetPackBytes == 8); const auto packC0 = c0 > 0; const auto packC1 = c1 > 0; const auto packC2 = c2 > 0; const auto packC3 = c3 > 0; const auto numChannels = (packC0 ? 1u : 0) + (packC1 ? 1u : 0) + (packC2 ? 1u : 0) + (packC3 ? 1u : 0); assert(numChannels <= componentCount); (void) numChannels; const uint32_t sourceBits = sizeof(componentType) * 8; static constexpr auto hasC0 = componentCount > 0; static constexpr auto hasC1 = componentCount > 1; static constexpr auto hasC2 = componentCount > 2; static constexpr auto hasC3 = componentCount > 3; std::vector data(height * width * targetPackBytes); for (uint32_t y = 0; y < height; ++y) { for (uint32_t x = 0; x < width; ++x) { const auto& pixel = pixels[y * width + x]; auto* target = data.data() + (y * width + x) * targetPackBytes; const auto copy = [&](auto& pack) { using PackType = std::remove_reference_t; if (packC0) { const auto sourceValue = hasC0 ? pixel[0] : componentType{0}; const auto value = imageio::convertUNORM(static_cast(sourceValue), sourceBits, c0); pack |= static_cast(value) << (c1 + c2 + c3); } if (packC1) { const auto sourceValue = hasC1 ? pixel[1] : componentType{0}; const auto value = imageio::convertUNORM(static_cast(sourceValue), sourceBits, c1); pack |= static_cast(value) << (c2 + c3); } if (packC2) { const auto sourceValue = hasC2 ? pixel[2] : componentType{0}; const auto value = imageio::convertUNORM(static_cast(sourceValue), sourceBits, c2); pack |= static_cast(value) << c3; } if (packC3) { const auto sourceValue = hasC3 ? pixel[3] : Color::one(); const auto value = imageio::convertUNORM(static_cast(sourceValue), sourceBits, c3); pack |= static_cast(value); } }; if (targetPackBytes == 1) { uint8_t pack = 0; copy(pack); std::memcpy(target, &pack, sizeof(pack)); } else if (targetPackBytes == 2) { uint16_t pack = 0; copy(pack); std::memcpy(target, &pack, sizeof(pack)); } else if (targetPackBytes == 4) { uint32_t pack = 0; copy(pack); std::memcpy(target, &pack, sizeof(pack)); } else if (targetPackBytes == 8) { uint64_t pack = 0; copy(pack); std::memcpy(target, &pack, sizeof(pack)); } } } return data; } virtual std::vector getSFloat(uint32_t numChannels, uint32_t targetBits) const override { assert(numChannels <= componentCount); assert(targetBits == 16 || targetBits == 32); const uint32_t targetBytes = targetBits / 8; std::vector data(height * width * numChannels * targetBytes); for (uint32_t y = 0; y < height; ++y) { for (uint32_t x = 0; x < width; ++x) { for (uint32_t c = 0; c < numChannels; ++c) { const auto value = c < componentCount ? pixels[y * width + x][c] : (c != 3 ? componentType{0} : componentType{1}); auto* target = data.data() + (y * width * numChannels + x * numChannels + c) * targetBytes; if (sizeof(componentType) == targetBytes) { *reinterpret_cast(target) = value; } else if (targetBytes == 2) { const auto outValue = imageio::float_to_half(static_cast(value)); std::memcpy(target, &outValue, targetBytes); } else if (targetBytes == 4) { const auto outValue = static_cast(value); std::memcpy(target, &outValue, targetBytes); } } } } return data; } virtual std::vector getB10G11R11() const override { assert(3 <= componentCount); assert(std::is_floating_point_v); std::vector data(height * width * 4); for (uint32_t y = 0; y < height; ++y) { for (uint32_t x = 0; x < width; ++x) { auto* target = data.data() + (y * width + x) * 4; const auto pixel = (*this)(x, y); const auto r = pixel[0]; const auto g = pixel[1]; const auto b = pixel[2]; const auto outValue = glm::packF2x11_1x10(glm::vec3(r, g, b)); std::memcpy(target, &outValue, sizeof(outValue)); } } return data; } virtual std::vector getE5B9G9R9() const override { assert(3 <= componentCount); assert(std::is_floating_point_v); std::vector data(height * width * 4); for (uint32_t y = 0; y < height; ++y) { for (uint32_t x = 0; x < width; ++x) { auto* target = data.data() + (y * width + x) * 4; const auto pixel = (*this)(x, y); const auto r = pixel[0]; const auto g = pixel[1]; const auto b = pixel[2]; const auto outValue = glm::packF3x9_E1x5(glm::vec3(r, g, b)); std::memcpy(target, &outValue, sizeof(outValue)); } } return data; } virtual std::vector getUINT(uint32_t numChannels, uint32_t targetBits) const override { assert(numChannels <= componentCount); assert(targetBits == 8 || targetBits == 16 || targetBits == 32 || targetBits == 64); const uint32_t targetBytes = targetBits / 8; std::vector data(height * width * numChannels * targetBytes); for (uint32_t y = 0; y < height; ++y) { for (uint32_t x = 0; x < width; ++x) { for (uint32_t c = 0; c < numChannels; ++c) { const auto value = c < componentCount ? pixels[y * width + x][c] : c != 3 ? 0 : componentType{1}; auto* target = data.data() + (y * width * numChannels + x * numChannels + c) * targetBytes; if (targetBytes == 1) { const auto outValue = static_cast(value); std::memcpy(target, &outValue, targetBytes); } else if (targetBytes == 2) { const auto outValue = static_cast(value); std::memcpy(target, &outValue, targetBytes); } else if (targetBytes == 4) { const auto outValue = static_cast(value); std::memcpy(target, &outValue, targetBytes); } else if (targetBytes == 8) { const auto outValue = static_cast(value); std::memcpy(target, &outValue, targetBytes); } } } } return data; } virtual std::vector getSINT(uint32_t numChannels, uint32_t targetBits) const override { assert(numChannels <= componentCount); assert(targetBits == 8 || targetBits == 16 || targetBits == 32 || targetBits == 64); const uint32_t targetBytes = targetBits / 8; std::vector data(height * width * numChannels * targetBytes); for (uint32_t y = 0; y < height; ++y) { for (uint32_t x = 0; x < width; ++x) { for (uint32_t c = 0; c < numChannels; ++c) { const auto value = c < componentCount ? pixels[y * width + x][c] : c != 3 ? 0 : componentType{1}; auto* target = data.data() + (y * width * numChannels + x * numChannels + c) * targetBytes; if (targetBytes == 1) { const auto outValue = static_cast(value); std::memcpy(target, &outValue, targetBytes); } else if (targetBytes == 2) { const auto outValue = static_cast(value); std::memcpy(target, &outValue, targetBytes); } else if (targetBytes == 4) { const auto outValue = static_cast(value); std::memcpy(target, &outValue, targetBytes); } else if (targetBytes == 8) { const auto outValue = static_cast(value); std::memcpy(target, &outValue, targetBytes); } } } } return data; } virtual std::vector getUINTPacked(uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3) const override { assert(c0 + c1 + c2 + c3 == 32); assert(c0 != 0 && c1 != 0 && c2 != 0 && c3 != 0); assert(componentCount == 4); std::vector data(height * width * sizeof(uint32_t)); for (uint32_t y = 0; y < height; ++y) { for (uint32_t x = 0; x < width; ++x) { const auto& pixel = pixels[y * width + x]; auto* target = data.data() + (y * width + x) * sizeof(uint32_t); uint32_t pack = 0; pack |= imageio::convertUINT(static_cast(pixel[0]), sizeof(uint32_t) * 8, c0) << (c1 + c2 + c3); pack |= imageio::convertUINT(static_cast(pixel[1]), sizeof(uint32_t) * 8, c1) << (c2 + c3); pack |= imageio::convertUINT(static_cast(pixel[2]), sizeof(uint32_t) * 8, c2) << c3; pack |= imageio::convertUINT(static_cast(pixel[3]), sizeof(uint32_t) * 8, c3); std::memcpy(target, &pack, sizeof(pack)); } } return data; } virtual std::vector getSINTPacked(uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3) const override { assert(c0 + c1 + c2 + c3 == 32); assert(c0 != 0 && c1 != 0 && c2 != 0 && c3 != 0); assert(componentCount == 4); std::vector data(height * width * sizeof(uint32_t)); for (uint32_t y = 0; y < height; ++y) { for (uint32_t x = 0; x < width; ++x) { const auto& pixel = pixels[y * width + x]; auto* target = data.data() + (y * width + x) * sizeof(uint32_t); uint32_t pack = 0; pack |= imageio::convertSINT(imageio::bit_cast(static_cast(pixel[0])), sizeof(uint32_t) * 8, c0) << (c1 + c2 + c3); pack |= imageio::convertSINT(imageio::bit_cast(static_cast(pixel[1])), sizeof(uint32_t) * 8, c1) << (c2 + c3); pack |= imageio::convertSINT(imageio::bit_cast(static_cast(pixel[2])), sizeof(uint32_t) * 8, c2) << c3; pack |= imageio::convertSINT(imageio::bit_cast(static_cast(pixel[3])), sizeof(uint32_t) * 8, c3); std::memcpy(target, &pack, sizeof(pack)); } } return data; } static void checkResamplerStatus(basisu::Resampler& resampler, const char* pFilter) { using Status = basisu::Resampler::Status; switch (resampler.status()) { case Status::STATUS_OKAY: break; case Status::STATUS_OUT_OF_MEMORY: throw std::runtime_error("Resampler or Resampler::put_line out of memory."); case Status::STATUS_BAD_FILTER_NAME: throw std::runtime_error(fmt::format("Unknown filter: {}", pFilter)); case Status::STATUS_SCAN_BUFFER_FULL: throw std::runtime_error("Resampler::put_line scan buffer full."); } } virtual std::unique_ptr resample(uint32_t targetWidth, uint32_t targetHeight, const char* filter, float filterScale, basisu::Resampler::Boundary_Op wrapMode) override { using namespace basisu; auto target = std::make_unique>(targetWidth, targetHeight); target->setTransferFunction(transferFunction); target->setPrimaries(primaries); const auto sourceWidth = width; const auto sourceHeight = height; assert(sourceWidth && sourceHeight && targetWidth && targetHeight); if (std::max(sourceWidth, sourceHeight) > BASISU_RESAMPLER_MAX_DIMENSION || std::max(targetWidth, targetHeight) > BASISU_RESAMPLER_MAX_DIMENSION) { throw std::runtime_error(fmt::format( "Image larger than max supported size of {}", BASISU_RESAMPLER_MAX_DIMENSION)); } std::array, componentCount> samples; std::array, componentCount> resamplers; // Float types handled as SFloat HDR otherwise UNROM LDR is assumed const auto isHDR = std::is_floating_point_v; for (uint32_t i = 0; i < componentCount; ++i) { resamplers[i] = std::make_unique( sourceWidth, sourceHeight, targetWidth, targetHeight, wrapMode, 0.0f, isHDR ? 0.0f : 1.0f, filter, i == 0 ? nullptr : resamplers[0]->get_clist_x(), i == 0 ? nullptr : resamplers[0]->get_clist_y(), filterScale, filterScale, 0.f, 0.f); checkResamplerStatus(*resamplers[i], filter); samples[i].resize(sourceWidth); } const TransferFunctionSRGB tfSRGB; const TransferFunctionLinear tfLinear; const TransferFunction& tf = transferFunction == KHR_DF_TRANSFER_SRGB ? static_cast(tfSRGB) : static_cast(tfLinear); uint32_t targetY = 0; for (uint32_t sourceY = 0; sourceY < sourceHeight; ++sourceY) { // Put source lines into resampler(s) for (uint32_t sourceX = 0; sourceX < sourceWidth; ++sourceX) { const auto& sourcePixel = pixels[sourceY * sourceWidth + sourceX]; for (uint32_t c = 0; c < componentCount; ++c) { const float value = std::is_floating_point_v ? sourcePixel[c] : static_cast(sourcePixel[c]) * (1.f / static_cast(Color::one())); // c == 3: Alpha channel always uses tfLinear samples[c][sourceX] = (c == 3 ? tfLinear : tf).decode(value); } } for (uint32_t c = 0; c < componentCount; ++c) if (!resamplers[c]->put_line(&samples[c][0])) checkResamplerStatus(*resamplers[c], filter); // Retrieve any output lines while (true) { std::array outputLine{nullptr}; for (uint32_t c = 0; c < componentCount; ++c) outputLine[c] = resamplers[c]->get_line(); if (outputLine[0] == nullptr) break; // No new output line, break from retrieve and place in a new source line for (uint32_t targetX = 0; targetX < targetWidth; ++targetX) { Color& targetPixel = target->pixels[targetY * targetWidth + targetX]; for (uint32_t c = 0; c < componentCount; ++c) { const auto linearValue = outputLine[c][targetX]; // c == 3: Alpha channel always uses tfLinear const float outValue = (c == 3 ? tfLinear : tf).encode(linearValue); if constexpr (std::is_floating_point_v) { targetPixel[c] = outValue; } else { const auto unormValue = std::isnan(outValue) ? componentType{0} : outValue < 0.f ? componentType{0} : outValue > 1.f ? Color::one() : static_cast(outValue * static_cast(Color::one()) + 0.5f); targetPixel[c] = unormValue; } } } ++targetY; } } return target; } virtual ImageT& yflip() override { uint32_t rowSize = width * sizeof(Color); // Minimize memory use by only buffering a single row. Color* rowBuffer = new Color[width]; for (uint32_t sy = height-1, dy = 0; sy >= height / 2; sy--, dy++) { Color* srcRow = &pixels[width * sy]; Color* dstRow = &pixels[width * dy]; memcpy(rowBuffer, dstRow, rowSize); memcpy(dstRow, srcRow, rowSize); memcpy(srcRow, rowBuffer, rowSize); } delete[] rowBuffer; return *this; } virtual ImageT& transformColorSpace(const TransferFunction& decode, const TransferFunction& encode, const ColorPrimaryTransform* transformPrimaries) override { uint32_t pixelCount = getPixelCount(); for (uint32_t i = 0; i < pixelCount; ++i) { Color& c = pixels[i]; // Don't transform the alpha component. uint32_t components = cclamp(getComponentCount(), 0u, 3u); float intensity[3]; float brightness[3]; // Decode source transfer function for (uint32_t comp = 0; comp < components; comp++) { brightness[comp] = (float)(c[comp]) * Color::rcpOne(); intensity[comp] = decode.decode(brightness[comp]); } // If needed, transform primaries if (transformPrimaries != nullptr) { float origIntensity[3] = { intensity[0], intensity[1], intensity[2] }; for (uint32_t j = 0; j < components; ++j) { intensity[j] = 0.f; for (uint32_t k = 0; k < components; ++k) intensity[j] += transformPrimaries->matrix[j][k] * origIntensity[k]; } } // Encode destination transfer function for (uint32_t comp = 0; comp < components; comp++) { brightness[comp] = encode.encode(intensity[comp]); // clamp(value, color::min, color::max) is required as static_cast has platform-specific behaviors // and on certain platforms can over or underflow c.set(comp, cclamp( roundf(brightness[comp] * static_cast(Color::one())), static_cast(Color::min()), static_cast(Color::max()))); } } return *this; } virtual ImageT& normalize() override { uint32_t pixelCount = getPixelCount(); for (uint32_t i = 0; i < pixelCount; ++i) { Color& c = pixels[i]; c.normalize(); } return *this; } virtual ImageT& swizzle(std::string_view swizzle) override { assert(swizzle.size() == 4); for (size_t i = 0; i < getPixelCount(); i++) { Color srcPixel = pixels[i]; for (uint32_t c = 0; c < getComponentCount(); c++) { pixels[i].set(c, swizzlePixel(srcPixel, swizzle[c])); } } return *this; } template ImageT& copyTo(DstImage& dst, std::string_view swizzle) { assert(getComponentSize() == dst.getComponentSize()); assert(width == dst.getWidth() && height == dst.getHeight()); dst.setTransferFunction(transferFunction); dst.setPrimaries(primaries); for (size_t i = 0; i < getPixelCount(); i++) { uint32_t c; for (c = 0; c < dst.getComponentCount(); c++) { if (c < getComponentCount()) dst.pixels[i].set(c, swizzlePixel(pixels[i], swizzle[c])); else break; } for (; c < dst.getComponentCount(); c++) if (c < 3) dst.pixels[i].set(c, componentType{0}); else dst.pixels[i].set(c, Color::one()); } return *this; } virtual ImageT& copyToR(Image& dst, std::string_view swizzle) override { return copyTo((ImageT&)dst, swizzle); } virtual ImageT& copyToRG(Image& dst, std::string_view swizzle) override { return copyTo((ImageT&)dst, swizzle); } virtual ImageT& copyToRGB(Image& dst, std::string_view swizzle) override { return copyTo((ImageT&)dst, swizzle); } virtual ImageT& copyToRGBA(Image& dst, std::string_view swizzle) override { return copyTo((ImageT&)dst, swizzle); } protected: componentType swizzlePixel(const Color& srcPixel, char swizzle) { switch (swizzle) { case 'r': return srcPixel[0]; case 'g': return srcPixel[1]; case 'b': return srcPixel[2]; case 'a': return srcPixel[3]; case '0': return componentType{0}; case '1': return Color::one(); default: assert(false); return componentType{0}; } } Color* pixels; bool freePixels; }; using r8color = color; using rg8color = color; using rgb8color = color; using rgba8color = color; using r16color = color; using rg16color = color; using rgb16color = color; using rgba16color = color; using r32color = color; using rg32color = color; using rgb32color = color; using rgba32color = color; using r8scolor = color; using rg8scolor = color; using rgb8scolor = color; using rgba8scolor = color; using r16scolor = color; using rg16scolor = color; using rgb16scolor = color; using rgba16scolor = color; using r32scolor = color; using rg32scolor = color; using rgb32scolor = color; using rgba32scolor = color; using r32fcolor = color; using rg32fcolor = color; using rgb32fcolor = color; using rgba32fcolor = color; using r8image = ImageT; using rg8image = ImageT; using rgb8image = ImageT; using rgba8image = ImageT; using r16image = ImageT; using rg16image = ImageT; using rgb16image = ImageT; using rgba16image = ImageT; using r32image = ImageT; using rg32image = ImageT; using rgb32image = ImageT; using rgba32image = ImageT; using r8simage = ImageT; using rg8simage = ImageT; using rgb8simage = ImageT; using rgba8simage = ImageT; using r16simage = ImageT; using rg16simage = ImageT; using rgb16simage = ImageT; using rgba16simage = ImageT; using r32simage = ImageT; using rg32simage = ImageT; using rgb32simage = ImageT; using rgba32simage = ImageT; using r32fimage = ImageT; using rg32fimage = ImageT; using rgb32fimage = ImageT; using rgba32fimage = ImageT; #endif /* IMAGE_HPP */