1405 lines
52 KiB
C++
1405 lines
52 KiB
C++
// -*- 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 <algorithm>
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <string_view>
|
|
#include <vector>
|
|
#include <KHR/khr_df.h>
|
|
#include <fmt/format.h>
|
|
#ifdef _MSC_VER
|
|
#pragma warning(push)
|
|
#pragma warning(disable : 4201)
|
|
#endif
|
|
#include <glm/gtc/packing.hpp>
|
|
#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 <typename T> inline T cclamp(T value, T low, T high) {
|
|
return (value < low) ? low : ((value > high) ? high : value);
|
|
}
|
|
template <typename T> inline T saturate(T value) {
|
|
return cclamp<T>(value, 0, 1.0f);
|
|
}
|
|
|
|
template <typename S> inline S maximum(S a, S b) { return (a > b) ? a : b; }
|
|
template <typename S> 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<float>& 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 <typename componentType, uint32_t componentCount>
|
|
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<componentType>)
|
|
return componentType{1};
|
|
else
|
|
return std::numeric_limits<componentType>::max();
|
|
}
|
|
constexpr static float rcpOne() {
|
|
if (std::is_floating_point_v<componentType>)
|
|
return 1.f;
|
|
else
|
|
return 1.f / static_cast<float>(std::numeric_limits<componentType>::max());
|
|
}
|
|
constexpr static float halfUnit() {
|
|
if (std::is_floating_point_v<componentType>)
|
|
return 0.f;
|
|
else
|
|
return 0.5f / static_cast<float>(std::numeric_limits<componentType>::max());
|
|
}
|
|
constexpr static componentType min() {
|
|
return std::numeric_limits<componentType>::min();
|
|
}
|
|
constexpr static componentType max() {
|
|
return std::numeric_limits<componentType>::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 <typename componentType>
|
|
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<float> : 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 <typename componentType, uint32_t componentCount>
|
|
class color { };
|
|
|
|
template <typename componentType>
|
|
class color<componentType, 4> : public color_base<componentType, 4> {
|
|
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 <typename T>
|
|
void set(uint32_t i, T val) {
|
|
if (i > 3) i = 3;
|
|
comps[i] = color_base<componentType, 4>::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<componentType> v((float)r, (float)g, (float)b);
|
|
v.normalize();
|
|
r = (componentType)v.r;
|
|
g = (componentType)v.g;
|
|
b = (componentType)v.b;
|
|
}
|
|
};
|
|
|
|
template <typename componentType>
|
|
class color<componentType, 3> : public color_base<componentType, 3> {
|
|
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 <typename T>
|
|
void set(uint32_t i, T val) {
|
|
if (i > 2) i = 2;
|
|
comps[i] = color_base<componentType, 3>::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<componentType> v((float)r, (float)g, (float)b);
|
|
v.normalize();
|
|
r = (componentType)v.r;
|
|
g = (componentType)v.g;
|
|
b = (componentType)v.b;
|
|
}
|
|
};
|
|
|
|
template <typename componentType>
|
|
class color<componentType, 2> : public color_base<componentType, 2> {
|
|
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 <typename T>
|
|
void set(uint32_t i, T val) {
|
|
if (i > 1) i = 1;
|
|
comps[i] = color_base<componentType, 2>::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<componentType> v((float)r, (float)g,
|
|
(float)gc_s[sizeof(componentType)] * 0.5f);
|
|
v.normalize();
|
|
r = (componentType)v.r;
|
|
g = (componentType)v.g;
|
|
}
|
|
};
|
|
|
|
template <typename componentType>
|
|
class color<componentType, 1> : public color_base<componentType, 1> {
|
|
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 <typename T>
|
|
void set(uint32_t i, T val) {
|
|
if (i > 0) i = 0;
|
|
comps[i] = color_base<componentType, 1>::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<uint8_t> 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<uint8_t> 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<uint8_t> getSFloat(uint32_t numChannels, uint32_t targetBits) const = 0;
|
|
/// Should only be used if the stored image data is UFloat convertable
|
|
virtual std::vector<uint8_t> getB10G11R11() const = 0;
|
|
/// Should only be used if the stored image data is UFloat convertable
|
|
virtual std::vector<uint8_t> getE5B9G9R9() const = 0;
|
|
/// Should only be used if the stored image data is UINT convertable
|
|
virtual std::vector<uint8_t> getUINT(uint32_t numChannels, uint32_t targetBits) const = 0;
|
|
/// Should only be used if the stored image data is SINT convertable
|
|
virtual std::vector<uint8_t> getSINT(uint32_t numChannels, uint32_t targetBits) const = 0;
|
|
/// Should only be used if the stored image data is UINT convertable
|
|
virtual std::vector<uint8_t> 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<uint8_t> getSINTPacked(uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3) const = 0;
|
|
virtual std::unique_ptr<Image> 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<typename componentType, uint32_t componentCount>
|
|
class ImageT : public Image {
|
|
friend class ImageT<componentType, 1>;
|
|
friend class ImageT<componentType, 2>;
|
|
friend class ImageT<componentType, 3>;
|
|
friend class ImageT<componentType, 4>;
|
|
public:
|
|
using Color = color<componentType, componentCount>;
|
|
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<uint8_t> 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<uint8_t> 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<uint32_t>(sourceValue), sourceBits, targetBits);
|
|
auto* target = data.data() + (y * width * numChannels + x * numChannels + c) * targetBytes;
|
|
|
|
if (targetBytes == 1) {
|
|
const auto outValue = static_cast<uint8_t>(value & mask);
|
|
std::memcpy(target, &outValue, sizeof(outValue));
|
|
} else if (targetBytes == 2) {
|
|
const auto outValue = static_cast<uint16_t>(value & mask);
|
|
std::memcpy(target, &outValue, sizeof(outValue));
|
|
} else if (targetBytes == 4) {
|
|
const auto outValue = static_cast<uint32_t>(value & mask);
|
|
std::memcpy(target, &outValue, sizeof(outValue));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
virtual std::vector<uint8_t> 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<uint8_t> 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<decltype(pack)>;
|
|
|
|
if (packC0) {
|
|
const auto sourceValue = hasC0 ? pixel[0] : componentType{0};
|
|
const auto value = imageio::convertUNORM(static_cast<uint32_t>(sourceValue), sourceBits, c0);
|
|
pack |= static_cast<PackType>(value) << (c1 + c2 + c3);
|
|
}
|
|
if (packC1) {
|
|
const auto sourceValue = hasC1 ? pixel[1] : componentType{0};
|
|
const auto value = imageio::convertUNORM(static_cast<uint32_t>(sourceValue), sourceBits, c1);
|
|
pack |= static_cast<PackType>(value) << (c2 + c3);
|
|
}
|
|
if (packC2) {
|
|
const auto sourceValue = hasC2 ? pixel[2] : componentType{0};
|
|
const auto value = imageio::convertUNORM(static_cast<uint32_t>(sourceValue), sourceBits, c2);
|
|
pack |= static_cast<PackType>(value) << c3;
|
|
}
|
|
if (packC3) {
|
|
const auto sourceValue = hasC3 ? pixel[3] : Color::one();
|
|
const auto value = imageio::convertUNORM(static_cast<uint32_t>(sourceValue), sourceBits, c3);
|
|
pack |= static_cast<PackType>(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<uint8_t> 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<uint8_t> 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<componentType*>(target) = value;
|
|
} else if (targetBytes == 2) {
|
|
const auto outValue = imageio::float_to_half(static_cast<float>(value));
|
|
std::memcpy(target, &outValue, targetBytes);
|
|
} else if (targetBytes == 4) {
|
|
const auto outValue = static_cast<float>(value);
|
|
std::memcpy(target, &outValue, targetBytes);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
virtual std::vector<uint8_t> getB10G11R11() const override {
|
|
assert(3 <= componentCount);
|
|
assert(std::is_floating_point_v<componentType>);
|
|
|
|
std::vector<uint8_t> 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<uint8_t> getE5B9G9R9() const override {
|
|
assert(3 <= componentCount);
|
|
assert(std::is_floating_point_v<componentType>);
|
|
|
|
std::vector<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t>(value);
|
|
std::memcpy(target, &outValue, targetBytes);
|
|
} else if (targetBytes == 2) {
|
|
const auto outValue = static_cast<uint16_t>(value);
|
|
std::memcpy(target, &outValue, targetBytes);
|
|
} else if (targetBytes == 4) {
|
|
const auto outValue = static_cast<uint32_t>(value);
|
|
std::memcpy(target, &outValue, targetBytes);
|
|
} else if (targetBytes == 8) {
|
|
const auto outValue = static_cast<uint64_t>(value);
|
|
std::memcpy(target, &outValue, targetBytes);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
virtual std::vector<uint8_t> 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<uint8_t> 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<int8_t>(value);
|
|
std::memcpy(target, &outValue, targetBytes);
|
|
} else if (targetBytes == 2) {
|
|
const auto outValue = static_cast<int16_t>(value);
|
|
std::memcpy(target, &outValue, targetBytes);
|
|
} else if (targetBytes == 4) {
|
|
const auto outValue = static_cast<int32_t>(value);
|
|
std::memcpy(target, &outValue, targetBytes);
|
|
} else if (targetBytes == 8) {
|
|
const auto outValue = static_cast<int64_t>(value);
|
|
std::memcpy(target, &outValue, targetBytes);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
virtual std::vector<uint8_t> 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<uint8_t> 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<uint32_t>(pixel[0]), sizeof(uint32_t) * 8, c0) << (c1 + c2 + c3);
|
|
pack |= imageio::convertUINT(static_cast<uint32_t>(pixel[1]), sizeof(uint32_t) * 8, c1) << (c2 + c3);
|
|
pack |= imageio::convertUINT(static_cast<uint32_t>(pixel[2]), sizeof(uint32_t) * 8, c2) << c3;
|
|
pack |= imageio::convertUINT(static_cast<uint32_t>(pixel[3]), sizeof(uint32_t) * 8, c3);
|
|
|
|
std::memcpy(target, &pack, sizeof(pack));
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
virtual std::vector<uint8_t> 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<uint8_t> 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<uint32_t>(static_cast<int32_t>(pixel[0])), sizeof(uint32_t) * 8, c0) << (c1 + c2 + c3);
|
|
pack |= imageio::convertSINT(imageio::bit_cast<uint32_t>(static_cast<int32_t>(pixel[1])), sizeof(uint32_t) * 8, c1) << (c2 + c3);
|
|
pack |= imageio::convertSINT(imageio::bit_cast<uint32_t>(static_cast<int32_t>(pixel[2])), sizeof(uint32_t) * 8, c2) << c3;
|
|
pack |= imageio::convertSINT(imageio::bit_cast<uint32_t>(static_cast<int32_t>(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<Image> 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<ImageT<componentType, componentCount>>(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<std::vector<float>, componentCount> samples;
|
|
std::array<std::unique_ptr<Resampler>, componentCount> resamplers;
|
|
|
|
// Float types handled as SFloat HDR otherwise UNROM LDR is assumed
|
|
const auto isHDR = std::is_floating_point_v<componentType>;
|
|
|
|
for (uint32_t i = 0; i < componentCount; ++i) {
|
|
resamplers[i] = std::make_unique<Resampler>(
|
|
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<const TransferFunction&>(tfSRGB) :
|
|
static_cast<const TransferFunction&>(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<componentType> ?
|
|
sourcePixel[c] :
|
|
static_cast<float>(sourcePixel[c]) * (1.f / static_cast<float>(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<const float*, componentCount> 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<componentType>) {
|
|
targetPixel[c] = outValue;
|
|
} else {
|
|
const auto unormValue =
|
|
std::isnan(outValue) ? componentType{0} :
|
|
outValue < 0.f ? componentType{0} :
|
|
outValue > 1.f ? Color::one() :
|
|
static_cast<componentType>(outValue * static_cast<float>(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<float>(Color::one())),
|
|
static_cast<float>(Color::min()),
|
|
static_cast<float>(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<class DstImage>
|
|
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<componentType, 1>&)dst, swizzle); }
|
|
virtual ImageT& copyToRG(Image& dst, std::string_view swizzle) override { return copyTo((ImageT<componentType, 2>&)dst, swizzle); }
|
|
virtual ImageT& copyToRGB(Image& dst, std::string_view swizzle) override { return copyTo((ImageT<componentType, 3>&)dst, swizzle); }
|
|
virtual ImageT& copyToRGBA(Image& dst, std::string_view swizzle) override { return copyTo((ImageT<componentType, 4>&)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<uint8_t, 1>;
|
|
using rg8color = color<uint8_t, 2>;
|
|
using rgb8color = color<uint8_t, 3>;
|
|
using rgba8color = color<uint8_t, 4>;
|
|
using r16color = color<uint16_t, 1>;
|
|
using rg16color = color<uint16_t, 2>;
|
|
using rgb16color = color<uint16_t, 3>;
|
|
using rgba16color = color<uint16_t, 4>;
|
|
using r32color = color<uint32_t, 1>;
|
|
using rg32color = color<uint32_t, 2>;
|
|
using rgb32color = color<uint32_t, 3>;
|
|
using rgba32color = color<uint32_t, 4>;
|
|
|
|
using r8scolor = color<int8_t, 1>;
|
|
using rg8scolor = color<int8_t, 2>;
|
|
using rgb8scolor = color<int8_t, 3>;
|
|
using rgba8scolor = color<int8_t, 4>;
|
|
using r16scolor = color<int16_t, 1>;
|
|
using rg16scolor = color<int16_t, 2>;
|
|
using rgb16scolor = color<int16_t, 3>;
|
|
using rgba16scolor = color<int16_t, 4>;
|
|
using r32scolor = color<int32_t, 1>;
|
|
using rg32scolor = color<int32_t, 2>;
|
|
using rgb32scolor = color<int32_t, 3>;
|
|
using rgba32scolor = color<int32_t, 4>;
|
|
|
|
using r32fcolor = color<float, 1>;
|
|
using rg32fcolor = color<float, 2>;
|
|
using rgb32fcolor = color<float, 3>;
|
|
using rgba32fcolor = color<float, 4>;
|
|
|
|
using r8image = ImageT<uint8_t, 1>;
|
|
using rg8image = ImageT<uint8_t, 2>;
|
|
using rgb8image = ImageT<uint8_t, 3>;
|
|
using rgba8image = ImageT<uint8_t, 4>;
|
|
using r16image = ImageT<uint16_t, 1>;
|
|
using rg16image = ImageT<uint16_t, 2>;
|
|
using rgb16image = ImageT<uint16_t, 3>;
|
|
using rgba16image = ImageT<uint16_t, 4>;
|
|
using r32image = ImageT<uint32_t, 1>;
|
|
using rg32image = ImageT<uint32_t, 2>;
|
|
using rgb32image = ImageT<uint32_t, 3>;
|
|
using rgba32image = ImageT<uint32_t, 4>;
|
|
|
|
using r8simage = ImageT<int8_t, 1>;
|
|
using rg8simage = ImageT<int8_t, 2>;
|
|
using rgb8simage = ImageT<int8_t, 3>;
|
|
using rgba8simage = ImageT<int8_t, 4>;
|
|
using r16simage = ImageT<int16_t, 1>;
|
|
using rg16simage = ImageT<int16_t, 2>;
|
|
using rgb16simage = ImageT<int16_t, 3>;
|
|
using rgba16simage = ImageT<int16_t, 4>;
|
|
using r32simage = ImageT<int32_t, 1>;
|
|
using rg32simage = ImageT<int32_t, 2>;
|
|
using rgb32simage = ImageT<int32_t, 3>;
|
|
using rgba32simage = ImageT<int32_t, 4>;
|
|
|
|
using r32fimage = ImageT<float, 1>;
|
|
using rg32fimage = ImageT<float, 2>;
|
|
using rgb32fimage = ImageT<float, 3>;
|
|
using rgba32fimage = ImageT<float, 4>;
|
|
|
|
#endif /* IMAGE_HPP */
|