зеркало из https://github.com/mozilla/gecko-dev.git
Backed out 3 changesets (bug 1799258) for causing GTest-1proc failures on Span. CLOSED TREE
Backed out changeset 0facab7b9a1d (bug 1799258) Backed out changeset 4f9ee3537468 (bug 1799258) Backed out changeset 12e98a3054d0 (bug 1799258)
This commit is contained in:
Родитель
fef4d286b9
Коммит
74bda87ddf
|
@ -17,7 +17,7 @@ defaults pref(media.av1.enabled,true)
|
|||
fuzzy(16-51,5234-5622) fuzzy-if(swgl,32-38,1600-91746) fuzzy-if(useDrawSnapshot,16-16,11600-11600) fuzzy-if(OSX,16-73,5212-5622) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.av1.webm ../reftest_img.html?src=color_quads/720p.png
|
||||
fuzzy-if(winWidget&&swgl,0-20,0-5620) fuzzy-if(winWidget&&!swgl,0-1,0-78) fuzzy-if(Android,254-255,273680-273807) fuzzy-if(OSX,0-35,0-1947) fuzzy-if(OSX&&swgl,0-67,0-5451) fuzzy-if(appleSilicon,30-48,1760-187409) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.vp9.webm ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.av1.webm
|
||||
fuzzy-if(winWidget,0-1,0-78) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.av1.mp4 ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.av1.webm
|
||||
skip-if(winWidget&&isCoverageBuild) fuzzy(0-16,75-1941) fuzzy-if(Android,254-255,273680-273807) fuzzy-if(OSX,30-32,187326-187407) fuzzy-if(appleSilicon,30-48,1835-187409) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.h264.mp4 ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.av1.webm
|
||||
skip-if(winWidget&&isCoverageBuild) fuzzy(0-16,75-1861) fuzzy-if(Android,254-255,273680-273807) fuzzy-if(OSX,30-32,187326-187407) fuzzy-if(appleSilicon,30-48,1835-187409) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.h264.mp4 ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.av1.webm
|
||||
fuzzy-if(winWidget&&swgl,0-20,0-5620) fuzzy-if(winWidget&&!swgl,0-1,0-78) fuzzy-if(Android,254-255,273680-273807) fuzzy-if(OSX,0-35,0-1947) fuzzy-if(OSX&&swgl,0-67,0-5451) fuzzy-if(appleSilicon,30-48,1760-187409) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.vp9.mp4 ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.tv.yuv420p.av1.webm
|
||||
|
||||
skip-if(Android) fuzzy(16-48,8107-8818) fuzzy-if(winWidget&&swgl,31-38,8240-184080) fuzzy-if(appleSilicon,33-38,8819-11705) fuzzy-if(useDrawSnapshot,20-20,187200-187200) == ../reftest_video.html?src=color_quads/720p.png.bt709.bt709.pc.yuv420p.av1.webm ../reftest_img.html?src=color_quads/720p.png
|
||||
|
|
|
@ -498,8 +498,7 @@ enum class YUVRangedColorSpace : uint8_t {
|
|||
// one.
|
||||
// Some times Worse Is Better.
|
||||
enum class ColorSpace2 : uint8_t {
|
||||
Display,
|
||||
UNKNOWN = Display, // We feel sufficiently bad about this TODO.
|
||||
UNKNOWN, // Really "DISPLAY". Eventually we will remove this.
|
||||
SRGB,
|
||||
DISPLAY_P3,
|
||||
BT601_525, // aka smpte170m NTSC
|
||||
|
@ -507,7 +506,7 @@ enum class ColorSpace2 : uint8_t {
|
|||
BT601_625 =
|
||||
BT709, // aka bt470bg PAL. Basically BT709, just Xg is 0.290 not 0.300.
|
||||
BT2020,
|
||||
_First = Display,
|
||||
_First = UNKNOWN,
|
||||
_Last = BT2020,
|
||||
};
|
||||
|
||||
|
|
|
@ -7,9 +7,6 @@
|
|||
|
||||
#include "Colorspaces.h"
|
||||
|
||||
#include "nsDebug.h"
|
||||
#include "qcms.h"
|
||||
|
||||
namespace mozilla::color {
|
||||
|
||||
// tf = { k * linear | linear < b
|
||||
|
@ -79,12 +76,6 @@ mat4 YuvFromYcbcr(const YcbcrDesc& d) {
|
|||
return yuvFromYcbcr;
|
||||
}
|
||||
|
||||
inline vec3 CIEXYZ_from_CIExyY(const vec2 xy, const float Y = 1) {
|
||||
const auto xyz = vec3(xy, 1 - xy.x() - xy.y());
|
||||
const auto XYZ = xyz * (Y / xy.y());
|
||||
return XYZ;
|
||||
}
|
||||
|
||||
mat3 XyzFromLinearRgb(const Chromaticities& c) {
|
||||
// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
|
||||
|
||||
|
@ -124,7 +115,7 @@ mat3 XyzFromLinearRgb(const Chromaticities& c) {
|
|||
|
||||
const auto XYZrgb = mat3({Xrgb, Yrgb, Zrgb});
|
||||
const auto XYZrgb_inv = inverse(XYZrgb);
|
||||
const auto XYZwhitepoint = vec3({c.wx, c.wy, 1 - c.wx - c.wy}) / c.wy;
|
||||
const auto XYZwhitepoint = vec3({c.wx, c.wy, 1 - c.wx - c.wy});
|
||||
const auto Srgb = XYZrgb_inv * XYZwhitepoint;
|
||||
|
||||
const auto M = mat3({Srgb * Xrgb, Srgb * Yrgb, Srgb * Zrgb});
|
||||
|
@ -192,29 +183,6 @@ vec3 ColorspaceTransform::DstFromSrc(const vec3 src) const {
|
|||
|
||||
// -
|
||||
|
||||
mat3 XyzAFromXyzB_BradfordLinear(const vec2 xyA, const vec2 xyB) {
|
||||
// This is what ICC profiles use to do whitepoint transforms,
|
||||
// because ICC also requires D50 for the Profile Connection Space.
|
||||
|
||||
// From https://www.color.org/specification/ICC.1-2022-05.pdf
|
||||
// E.3 "Linearized Bradford transformation":
|
||||
|
||||
constexpr auto M_BFD = mat3{{
|
||||
vec3{{0.8951, 0.2664f, -0.1614f}},
|
||||
vec3{{-0.7502f, 1.7135f, 0.0367f}},
|
||||
vec3{{0.0389f, -0.0685f, 1.0296f}},
|
||||
}};
|
||||
// NB: They use rho/gamma/beta, but we'll use R/G/B here.
|
||||
const auto XYZDst = CIEXYZ_from_CIExyY(xyA); // "XYZ_W", WP of PCS
|
||||
const auto XYZSrc = CIEXYZ_from_CIExyY(xyB); // "XYZ_NAW", WP of src
|
||||
const auto rgbSrc = M_BFD * XYZSrc; // "RGB_SRC"
|
||||
const auto rgbDst = M_BFD * XYZDst; // "RGB_PCS"
|
||||
const auto rgbDstOverSrc = rgbDst / rgbSrc;
|
||||
const auto M_dstOverSrc = mat3::Scale(rgbDstOverSrc);
|
||||
const auto M_adapt = inverse(M_BFD) * M_dstOverSrc * M_BFD;
|
||||
return M_adapt;
|
||||
}
|
||||
|
||||
std::optional<mat4> ColorspaceTransform::ToMat4() const {
|
||||
mat4 fromSrc = srcRgbTfFromSrc;
|
||||
if (srcTf) return {};
|
||||
|
@ -258,178 +226,4 @@ vec3 Lut3::Sample(const vec3 in01) const {
|
|||
return fxyz;
|
||||
}
|
||||
|
||||
// -
|
||||
|
||||
ColorProfileDesc ColorProfileDesc::From(const ColorspaceDesc& cspace) {
|
||||
auto ret = ColorProfileDesc{};
|
||||
|
||||
if (cspace.yuv) {
|
||||
const auto yuvFromYcbcr = YuvFromYcbcr(cspace.yuv->ycbcr);
|
||||
const auto yuvFromRgb = YuvFromRgb(cspace.yuv->yCoeffs);
|
||||
const auto rgbFromYuv = inverse(yuvFromRgb);
|
||||
ret.rgbFromYcbcr = mat4(rgbFromYuv) * yuvFromYcbcr;
|
||||
}
|
||||
|
||||
if (cspace.tf) {
|
||||
const size_t tableSize = 256;
|
||||
auto& tableR = ret.linearFromTf.r;
|
||||
tableR.resize(tableSize);
|
||||
for (size_t i = 0; i < tableR.size(); i++) {
|
||||
const float tfVal = i / float(tableR.size() - 1);
|
||||
const float linearVal = LinearFromTf(*cspace.tf, tfVal);
|
||||
tableR[i] = linearVal;
|
||||
}
|
||||
ret.linearFromTf.g = tableR;
|
||||
ret.linearFromTf.b = tableR;
|
||||
}
|
||||
|
||||
ret.xyzd65FromLinearRgb = XyzFromLinearRgb(cspace.chrom);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// -
|
||||
|
||||
template <class T>
|
||||
constexpr inline T NewtonEstimateX(const T x1, const T y1, const T dydx,
|
||||
const T y2 = 0) {
|
||||
// Estimate x s.t. y=0
|
||||
// y = y0 + x*dydx;
|
||||
// y0 = y - x*dydx;
|
||||
// y1 - x1*dydx = y2 - x2*dydx
|
||||
// x2*dydx = y2 - y1 + x1*dydx
|
||||
// x2 = (y2 - y1)/dydx + x1
|
||||
return (y2 - y1) / dydx + x1;
|
||||
}
|
||||
|
||||
float GuessGamma(const std::vector<float>& vals, float exp_guess) {
|
||||
// Approximate (signed) error = 0.0.
|
||||
constexpr float d_exp = 0.001;
|
||||
constexpr float error_tolerance = 0.001;
|
||||
struct Samples {
|
||||
float y1, y2;
|
||||
};
|
||||
const auto Sample = [&](const float exp) {
|
||||
int i = -1;
|
||||
auto samples = Samples{};
|
||||
for (const auto& expected : vals) {
|
||||
i += 1;
|
||||
const auto in = i / float(vals.size() - 1);
|
||||
samples.y1 += powf(in, exp) - expected;
|
||||
samples.y2 += powf(in, exp + d_exp) - expected;
|
||||
}
|
||||
samples.y1 /= vals.size(); // Normalize by val count.
|
||||
samples.y2 /= vals.size();
|
||||
return samples;
|
||||
};
|
||||
constexpr int MAX_ITERS = 10;
|
||||
for (int i = 1;; i++) {
|
||||
const auto err = Sample(exp_guess);
|
||||
const auto derr = err.y2 - err.y1;
|
||||
exp_guess = NewtonEstimateX(exp_guess, err.y1, derr / d_exp);
|
||||
// Check if we were close before, because then this last round of estimation
|
||||
// should get us pretty much right on it.
|
||||
if (std::abs(err.y1) < error_tolerance) {
|
||||
return exp_guess;
|
||||
}
|
||||
if (i >= MAX_ITERS) {
|
||||
printf_stderr("GuessGamma() -> %f after %i iterations (avg err %f)\n",
|
||||
exp_guess, i, err.y1);
|
||||
MOZ_ASSERT(false, "GuessGamma failed.");
|
||||
return exp_guess;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -
|
||||
|
||||
ColorProfileDesc ColorProfileDesc::From(const qcms_profile& qcmsProfile) {
|
||||
ColorProfileDesc ret;
|
||||
|
||||
qcms_profile_data data = {};
|
||||
qcms_profile_get_data(&qcmsProfile, &data);
|
||||
|
||||
auto xyzd50FromLinearRgb = mat3{};
|
||||
// X contributions from [R,G,B]
|
||||
xyzd50FromLinearRgb.at(0, 0) = data.red_colorant_xyzd50[0];
|
||||
xyzd50FromLinearRgb.at(1, 0) = data.green_colorant_xyzd50[0];
|
||||
xyzd50FromLinearRgb.at(2, 0) = data.blue_colorant_xyzd50[0];
|
||||
// Y contributions from [R,G,B]
|
||||
xyzd50FromLinearRgb.at(0, 1) = data.red_colorant_xyzd50[1];
|
||||
xyzd50FromLinearRgb.at(1, 1) = data.green_colorant_xyzd50[1];
|
||||
xyzd50FromLinearRgb.at(2, 1) = data.blue_colorant_xyzd50[1];
|
||||
// Z contributions from [R,G,B]
|
||||
xyzd50FromLinearRgb.at(0, 2) = data.red_colorant_xyzd50[2];
|
||||
xyzd50FromLinearRgb.at(1, 2) = data.green_colorant_xyzd50[2];
|
||||
xyzd50FromLinearRgb.at(2, 2) = data.blue_colorant_xyzd50[2];
|
||||
|
||||
const auto d65FromD50 = XyzAFromXyzB_BradfordLinear(D65, D50);
|
||||
ret.xyzd65FromLinearRgb = d65FromD50 * xyzd50FromLinearRgb;
|
||||
|
||||
// -
|
||||
|
||||
const auto Fn = [&](std::vector<float>* const linearFromTf,
|
||||
int32_t claimed_samples,
|
||||
const qcms_color_channel channel) {
|
||||
if (claimed_samples == 0) return; // No tf.
|
||||
|
||||
if (claimed_samples == -1) {
|
||||
claimed_samples = 4096; // Ask it to generate a bunch.
|
||||
claimed_samples = 256; // Ask it to generate a bunch.
|
||||
}
|
||||
|
||||
linearFromTf->resize(AssertedCast<size_t>(claimed_samples));
|
||||
|
||||
const auto begin = linearFromTf->data();
|
||||
qcms_profile_get_lut(&qcmsProfile, channel, begin,
|
||||
begin + linearFromTf->size());
|
||||
};
|
||||
|
||||
Fn(&ret.linearFromTf.r, data.linear_from_trc_red_samples,
|
||||
qcms_color_channel::Red);
|
||||
Fn(&ret.linearFromTf.b, data.linear_from_trc_blue_samples,
|
||||
qcms_color_channel::Blue);
|
||||
Fn(&ret.linearFromTf.g, data.linear_from_trc_green_samples,
|
||||
qcms_color_channel::Green);
|
||||
|
||||
// -
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// -
|
||||
|
||||
ColorProfileConversionDesc ColorProfileConversionDesc::From(
|
||||
const FromDesc& desc) {
|
||||
const auto dstLinearRgbFromXyzd65 = inverse(desc.dst.xyzd65FromLinearRgb);
|
||||
auto ret = ColorProfileConversionDesc{
|
||||
.srcRgbFromSrcYuv = desc.src.rgbFromYcbcr,
|
||||
.srcLinearFromSrcTf = desc.src.linearFromTf,
|
||||
.dstLinearFromSrcLinear =
|
||||
dstLinearRgbFromXyzd65 * desc.src.xyzd65FromLinearRgb,
|
||||
.dstTfFromDstLinear = {},
|
||||
};
|
||||
bool sameTF = true;
|
||||
sameTF &= desc.src.linearFromTf.r == desc.dst.linearFromTf.r;
|
||||
sameTF &= desc.src.linearFromTf.g == desc.dst.linearFromTf.g;
|
||||
sameTF &= desc.src.linearFromTf.b == desc.dst.linearFromTf.b;
|
||||
if (sameTF) {
|
||||
ret.srcLinearFromSrcTf = {};
|
||||
ret.dstTfFromDstLinear = {};
|
||||
} else {
|
||||
const auto Invert = [](const std::vector<float>& linearFromTf,
|
||||
std::vector<float>* const tfFromLinear) {
|
||||
const auto size = linearFromTf.size();
|
||||
MOZ_ASSERT(size != 1); // Less than two is uninvertable.
|
||||
if (size < 2) return;
|
||||
(*tfFromLinear).resize(size);
|
||||
InvertLut(linearFromTf, &*tfFromLinear);
|
||||
};
|
||||
Invert(desc.dst.linearFromTf.r, &ret.dstTfFromDstLinear.r);
|
||||
Invert(desc.dst.linearFromTf.g, &ret.dstTfFromDstLinear.g);
|
||||
Invert(desc.dst.linearFromTf.b, &ret.dstTfFromDstLinear.b);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace mozilla::color
|
||||
|
|
|
@ -15,16 +15,12 @@
|
|||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "AutoMappable.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Span.h"
|
||||
|
||||
#ifdef DEBUG
|
||||
# define ASSERT(EXPR) \
|
||||
|
@ -37,9 +33,6 @@
|
|||
# define ASSERT(EXPR) (void)(EXPR)
|
||||
#endif
|
||||
|
||||
struct _qcms_profile;
|
||||
typedef struct _qcms_profile qcms_profile;
|
||||
|
||||
namespace mozilla::color {
|
||||
|
||||
struct YuvLumaCoeffs final {
|
||||
|
@ -81,7 +74,8 @@ struct PiecewiseGammaDesc final {
|
|||
4.5,
|
||||
};
|
||||
}
|
||||
// FYI: static constexpr auto Rec2020_10bit() { return Rec709(); }
|
||||
static constexpr auto Rec2020_10bit() { return Rec709(); }
|
||||
|
||||
static constexpr auto Rec2020_12bit() {
|
||||
return PiecewiseGammaDesc{
|
||||
1.0993,
|
||||
|
@ -296,7 +290,6 @@ struct avec final {
|
|||
return eq;
|
||||
}
|
||||
};
|
||||
using vec2 = avec<float, 2>;
|
||||
using vec3 = avec<float, 3>;
|
||||
using vec4 = avec<float, 4>;
|
||||
using ivec3 = avec<int32_t, 3>;
|
||||
|
@ -371,15 +364,10 @@ struct mat final {
|
|||
|
||||
static constexpr auto Identity() {
|
||||
auto ret = mat{};
|
||||
for (int i = 0; i < std::min(x_cols, y_rows); i++) {
|
||||
ret.at(i, i) = 1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
static constexpr auto Scale(const avec<float, std::min(x_cols, y_rows)>& v) {
|
||||
auto ret = mat{};
|
||||
for (int i = 0; i < v.N; i++) {
|
||||
ret.at(i, i) = v[i];
|
||||
for (int x = 0; x < x_cols; x++) {
|
||||
for (int y = 0; y < y_rows; y++) {
|
||||
ret.at(x, y) = (x == y ? 1 : 0);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
@ -439,30 +427,7 @@ struct mat final {
|
|||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
// For e.g. similarity evaluation
|
||||
friend auto operator-(const mat& a, const mat& b) {
|
||||
mat c;
|
||||
for (int y = 0; y < y_rows; y++) {
|
||||
c.rows[y] = a.rows[y] - b.rows[y];
|
||||
}
|
||||
return c;
|
||||
}
|
||||
};
|
||||
|
||||
template <class M>
|
||||
inline float dotDifference(const M& a, const M& b) {
|
||||
const auto c = a - b;
|
||||
const auto d = c * avec<float, M::x_cols>(1);
|
||||
const auto d2 = dot(d, d);
|
||||
return d2;
|
||||
}
|
||||
template <class M>
|
||||
inline bool approx(const M& a, const M& b, const float eps = 0.0001) {
|
||||
const auto errSquared = dotDifference(a, b);
|
||||
return errSquared <= (eps * eps);
|
||||
}
|
||||
|
||||
using mat3 = mat<3, 3>;
|
||||
using mat4 = mat<4, 4>;
|
||||
|
||||
|
@ -686,294 +651,6 @@ struct ColorspaceTransform final {
|
|||
}
|
||||
};
|
||||
|
||||
// -
|
||||
|
||||
struct RgbTransferTables {
|
||||
std::vector<float> r;
|
||||
std::vector<float> g;
|
||||
std::vector<float> b;
|
||||
};
|
||||
float GuessGamma(const std::vector<float>& vals, float exp_guess = 1.0);
|
||||
|
||||
static constexpr auto D65 = vec2{{0.3127, 0.3290}};
|
||||
static constexpr auto D50 = vec2{{0.34567, 0.35850}};
|
||||
mat3 XyzAFromXyzB_BradfordLinear(const vec2 xyA, const vec2 xyB);
|
||||
|
||||
// -
|
||||
|
||||
struct ColorProfileDesc {
|
||||
// ICC profiles are phrased as PCS-from-encoded (PCS is CIEXYZ-D50)
|
||||
// However, all of our colorspaces are D65, so let's normalize to that,
|
||||
// even though it's a reversible transform.
|
||||
color::mat4 rgbFromYcbcr = color::mat4::Identity();
|
||||
RgbTransferTables linearFromTf;
|
||||
color::mat3 xyzd65FromLinearRgb = color::mat3::Identity();
|
||||
|
||||
static ColorProfileDesc From(const ColorspaceDesc&);
|
||||
static ColorProfileDesc From(const qcms_profile&);
|
||||
};
|
||||
|
||||
template <class C>
|
||||
inline float SampleOutByIn(const C& outByIn, const float in) {
|
||||
MOZ_ASSERT(outByIn.size() >= 2);
|
||||
const auto begin = outByIn.begin();
|
||||
|
||||
const auto in0i = size_t(floorf(in * (outByIn.size() - 1)));
|
||||
const auto out0_itr = begin + std::min(in0i, outByIn.size() - 2);
|
||||
|
||||
const auto in0 = float(out0_itr - begin) / (outByIn.size() - 1);
|
||||
const auto out0 = *out0_itr;
|
||||
const auto d_in = float(1) / (outByIn.size() - 1);
|
||||
const auto d_out = *(out0_itr + 1) - *out0_itr;
|
||||
|
||||
const auto out = out0 + (d_out / d_in) * (in - in0);
|
||||
// printf("SampleOutByIn(%f)->%f\n", in, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
template <class C>
|
||||
inline float SampleInByOut(const C& outByIn, const float out) {
|
||||
MOZ_ASSERT(outByIn.size() >= 2);
|
||||
const auto begin = outByIn.begin();
|
||||
|
||||
const auto out0_itr = std::lower_bound(begin + 1, outByIn.end() - 1, out) - 1;
|
||||
|
||||
const auto in0 = float(out0_itr - begin) / (outByIn.size() - 1);
|
||||
const auto out0 = *out0_itr;
|
||||
const auto d_in = float(1) / (outByIn.size() - 1);
|
||||
const auto d_out = *(out0_itr + 1) - *out0_itr;
|
||||
|
||||
// printf("%f + (%f / %f) * (%f - %f)\n", in0, d_in, d_out, out, out0);
|
||||
const auto in = in0 + (d_in / d_out) * (out - out0);
|
||||
// printf("SampleInByOut(%f)->%f\n", out, in);
|
||||
return in;
|
||||
}
|
||||
|
||||
template <class C, class FnLessEqualT = std::less_equal<typename C::value_type>>
|
||||
inline bool IsMonotonic(const C& vals, const FnLessEqualT& LessEqual = {}) {
|
||||
bool ok = true;
|
||||
const auto begin = vals.begin();
|
||||
for (size_t i = 1; i < vals.size(); i++) {
|
||||
const auto itr = begin + i;
|
||||
ok &= LessEqual(*(itr - 1), *itr);
|
||||
// Assert(true, [&]() {
|
||||
// return prints("[%zu]->%f <= [%zu]->%f", i-1, *(itr-1), i, *itr);
|
||||
// });
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
template <class T, class I>
|
||||
inline std::optional<I> SeekNeq(const T& ref, const I first, const I last) {
|
||||
const auto inc = (last - first) > 0 ? 1 : -1;
|
||||
auto itr = first;
|
||||
while (true) {
|
||||
if (*itr != ref) return itr;
|
||||
if (itr == last) return {};
|
||||
itr += inc;
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
struct TwoPoints {
|
||||
struct {
|
||||
T x;
|
||||
T y;
|
||||
} p0;
|
||||
struct {
|
||||
T x;
|
||||
T y;
|
||||
} p1;
|
||||
|
||||
T y(const T x) const {
|
||||
const auto dx = p1.x - p0.x;
|
||||
const auto dy = p1.y - p0.y;
|
||||
return p0.y + dy / dx * (x - p0.x);
|
||||
}
|
||||
};
|
||||
|
||||
/// Fills `vals` with `x:[0..vals.size()-1] => line.y(x)`.
|
||||
template <class T>
|
||||
static void LinearFill(T& vals, const TwoPoints<float>& line) {
|
||||
float x = -1;
|
||||
for (auto& val : vals) {
|
||||
x += 1;
|
||||
val = line.y(x);
|
||||
}
|
||||
}
|
||||
|
||||
// -
|
||||
|
||||
inline void DequantizeMonotonic(const Span<float> vals) {
|
||||
MOZ_ASSERT(IsMonotonic(vals));
|
||||
|
||||
const auto first = vals.begin();
|
||||
const auto end = vals.end();
|
||||
if (first == end) return;
|
||||
const auto last = end - 1;
|
||||
if (first == last) return;
|
||||
|
||||
// Three monotonic cases:
|
||||
// 1. [0,0,0,0]
|
||||
// 2. [0,0,1,1]
|
||||
// 3. [0,1,1,2]
|
||||
|
||||
const auto body_first = SeekNeq(*first, first, last);
|
||||
if (!body_first) {
|
||||
// E.g. [0,0,0,0]
|
||||
return;
|
||||
}
|
||||
|
||||
const auto body_last = SeekNeq(*last, last, *body_first);
|
||||
if (!body_last) {
|
||||
// E.g. [0,0,1,1]
|
||||
// This isn't the most accurate, but close enough.
|
||||
// print("#2: %s", to_str(vals).c_str());
|
||||
LinearFill(vals, {
|
||||
{0, *first},
|
||||
{float(vals.size() - 1), *last},
|
||||
});
|
||||
// print(" -> %s\n", to_str(vals).c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// E.g. [0,1,1,2]
|
||||
// ^^^ body
|
||||
// => f(0.5)->0.5, f(2.5)->1.5
|
||||
// => f(x) = f(x0) + (x-x0) * (f(x1) - f(x0)) / (x1-x0)
|
||||
// => f(x) = f(x0) + (x-x0) * dfdx
|
||||
|
||||
const auto head_end = *body_first;
|
||||
const auto head = vals.subspan(0, head_end - vals.begin());
|
||||
const auto tail_begin = *body_last + 1;
|
||||
const auto tail = vals.subspan(tail_begin - vals.begin());
|
||||
// print("head tail: %s %s\n",
|
||||
// to_str(head).c_str(),
|
||||
// to_str(tail).c_str());
|
||||
|
||||
// const auto body = vals->subspan(head.size(), vals->size()-tail.size());
|
||||
auto next_part_first = head_end;
|
||||
while (next_part_first != tail_begin) {
|
||||
const auto part_first = next_part_first;
|
||||
// print("part_first: %f\n", *part_first);
|
||||
next_part_first = *SeekNeq(*part_first, part_first, tail_begin);
|
||||
// print("next_part_first: %f\n", *next_part_first);
|
||||
const auto part =
|
||||
Span<float>{part_first, size_t(next_part_first - part_first)};
|
||||
// print("part: %s\n", to_str(part).c_str());
|
||||
const auto prev_part_last = part_first - 1;
|
||||
const auto part_last = next_part_first - 1;
|
||||
const auto line = TwoPoints<float>{
|
||||
{-0.5, (*prev_part_last + *part_first) / 2},
|
||||
{part.size() - 0.5f, (*part_last + *next_part_first) / 2},
|
||||
};
|
||||
LinearFill(part, line);
|
||||
}
|
||||
|
||||
static constexpr bool INFER_HEAD_TAIL_FROM_BODY_EDGE = false;
|
||||
// Basically ignore contents of head and tail, and infer from edges of body.
|
||||
// print("3: %s\n", to_str(vals).c_str());
|
||||
if (!IsMonotonic(head, std::less<float>{})) {
|
||||
if (!INFER_HEAD_TAIL_FROM_BODY_EDGE) {
|
||||
LinearFill(head,
|
||||
{
|
||||
{0, *head.begin()},
|
||||
{head.size() - 0.5f, (*(head.end() - 1) + *head_end) / 2},
|
||||
});
|
||||
} else {
|
||||
LinearFill(head, {
|
||||
{head.size() + 0.0f, *head_end},
|
||||
{head.size() + 1.0f, *(head_end + 1)},
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!IsMonotonic(tail, std::less<float>{})) {
|
||||
if (!INFER_HEAD_TAIL_FROM_BODY_EDGE) {
|
||||
LinearFill(tail, {
|
||||
{-0.5, (*(tail_begin - 1) + *tail.begin()) / 2},
|
||||
{tail.size() - 1.0f, *(tail.end() - 1)},
|
||||
});
|
||||
} else {
|
||||
LinearFill(tail, {
|
||||
{-2.0f, *(tail_begin - 2)},
|
||||
{-1.0f, *(tail_begin - 1)},
|
||||
});
|
||||
}
|
||||
}
|
||||
// print("3: %s\n", to_str(vals).c_str());
|
||||
MOZ_ASSERT(IsMonotonic(vals, std::less<float>{}));
|
||||
|
||||
// Rescale, because we tend to lose range.
|
||||
static constexpr bool RESCALE = false;
|
||||
if (RESCALE) {
|
||||
const auto firstv = *first;
|
||||
const auto lastv = *last;
|
||||
for (auto& val : vals) {
|
||||
val = (val - firstv) / (lastv - firstv);
|
||||
}
|
||||
}
|
||||
// print("4: %s\n", to_str(vals).c_str());
|
||||
}
|
||||
|
||||
template <class In, class Out>
|
||||
static void InvertLut(const In& lut, Out* const out_invertedLut) {
|
||||
MOZ_ASSERT(IsMonotonic(lut));
|
||||
auto plut = &lut;
|
||||
auto vec = std::vector<float>{};
|
||||
if (!IsMonotonic(lut, std::less<float>{})) {
|
||||
// print("Not strictly monotonic...\n");
|
||||
vec.assign(lut.begin(), lut.end());
|
||||
DequantizeMonotonic(vec);
|
||||
plut = &vec;
|
||||
// print(" Now strictly monotonic: %i: %s\n",
|
||||
// int(IsMonotonic(*plut, std::less<float>{})), to_str(*plut).c_str());
|
||||
MOZ_ASSERT(IsMonotonic(*plut, std::less<float>{}));
|
||||
}
|
||||
MOZ_ASSERT(plut->size() >= 2);
|
||||
|
||||
auto& ret = *out_invertedLut;
|
||||
for (size_t i_out = 0; i_out < ret.size(); i_out++) {
|
||||
const auto f_out = i_out / float(ret.size() - 1);
|
||||
const auto f_in = SampleInByOut(*plut, f_out);
|
||||
ret[i_out] = f_in;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(IsMonotonic(ret));
|
||||
MOZ_ASSERT(IsMonotonic(ret, std::less<float>{}));
|
||||
}
|
||||
|
||||
// -
|
||||
|
||||
struct ColorProfileConversionDesc {
|
||||
// ICC profiles are phrased as PCS-from-encoded (PCS is CIEXYZ-D50)
|
||||
color::mat4 srcRgbFromSrcYuv = color::mat4::Identity();
|
||||
RgbTransferTables srcLinearFromSrcTf;
|
||||
color::mat3 dstLinearFromSrcLinear = color::mat3::Identity();
|
||||
RgbTransferTables dstTfFromDstLinear;
|
||||
|
||||
struct FromDesc {
|
||||
ColorProfileDesc src;
|
||||
ColorProfileDesc dst;
|
||||
};
|
||||
static ColorProfileConversionDesc From(const FromDesc&);
|
||||
|
||||
vec3 Apply(const vec3 src) const {
|
||||
const auto srcRgb = vec3(srcRgbFromSrcYuv * vec4(src, 1));
|
||||
const auto srcLinear = vec3{{
|
||||
SampleOutByIn(srcLinearFromSrcTf.r, srcRgb.x()),
|
||||
SampleOutByIn(srcLinearFromSrcTf.g, srcRgb.y()),
|
||||
SampleOutByIn(srcLinearFromSrcTf.b, srcRgb.z()),
|
||||
}};
|
||||
const auto dstLinear = dstLinearFromSrcLinear * srcLinear;
|
||||
const auto dstRgb = vec3{{
|
||||
SampleOutByIn(dstTfFromDstLinear.r, dstLinear.x()),
|
||||
SampleOutByIn(dstTfFromDstLinear.g, dstLinear.y()),
|
||||
SampleOutByIn(dstTfFromDstLinear.b, dstLinear.z()),
|
||||
}};
|
||||
return dstRgb;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mozilla::color
|
||||
|
||||
#undef ASSERT
|
||||
|
|
|
@ -6,14 +6,12 @@
|
|||
#include "gtest/gtest.h"
|
||||
#include "Colorspaces.h"
|
||||
|
||||
#include <array>
|
||||
#include <limits>
|
||||
|
||||
namespace mozilla::color {
|
||||
mat4 YuvFromYcbcr(const YcbcrDesc&);
|
||||
float TfFromLinear(const PiecewiseGammaDesc&, float linear);
|
||||
float LinearFromTf(const PiecewiseGammaDesc&, float tf);
|
||||
mat3 XyzFromLinearRgb(const Chromaticities&);
|
||||
} // namespace mozilla::color
|
||||
|
||||
using namespace mozilla::color;
|
||||
|
@ -316,24 +314,6 @@ TEST(Colorspaces, SrgbFromDisplayP3)
|
|||
|
||||
// -
|
||||
|
||||
template <class Fn, class Tuple, size_t... I>
|
||||
constexpr auto map_tups_seq(const Tuple& a, const Tuple& b, const Fn& fn,
|
||||
std::index_sequence<I...>) {
|
||||
return std::tuple{fn(std::get<I>(a), std::get<I>(b))...};
|
||||
}
|
||||
template <class Fn, class Tuple>
|
||||
constexpr auto map_tups(const Tuple& a, const Tuple& b, const Fn& fn) {
|
||||
return map_tups_seq(a, b, fn,
|
||||
std::make_index_sequence<std::tuple_size_v<Tuple>>{});
|
||||
}
|
||||
|
||||
template <class Fn, class Tuple>
|
||||
constexpr auto cmp_tups_all(const Tuple& a, const Tuple& b, const Fn& fn) {
|
||||
bool all = true;
|
||||
map_tups(a, b, [&](const auto& a, const auto& b) { return all &= fn(a, b); });
|
||||
return all;
|
||||
}
|
||||
|
||||
struct Stats {
|
||||
double mean = 0;
|
||||
double variance = 0;
|
||||
|
@ -349,7 +329,6 @@ struct Stats {
|
|||
ret.max = std::max(ret.max, cur);
|
||||
}
|
||||
ret.mean /= iterable.size();
|
||||
// Gather mean first before we can calc variance.
|
||||
for (const auto& cur : iterable) {
|
||||
ret.variance += pow(cur - ret.mean, 2);
|
||||
}
|
||||
|
@ -357,61 +336,8 @@ struct Stats {
|
|||
return ret;
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
static Stats Diff(const T& a, const U& b) {
|
||||
MOZ_ASSERT(a.size() == b.size());
|
||||
std::vector<double> diff;
|
||||
diff.reserve(a.size());
|
||||
for (size_t i = 0; i < diff.capacity(); i++) {
|
||||
diff.push_back(a[i] - b[i]);
|
||||
}
|
||||
return Stats::For(diff);
|
||||
}
|
||||
|
||||
double standardDeviation() const { return sqrt(variance); }
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& s, const Stats& a) {
|
||||
return s << "Stats"
|
||||
<< "{ mean:" << a.mean << ", stddev:" << a.standardDeviation()
|
||||
<< ", min:" << a.min << ", max:" << a.max << " }";
|
||||
}
|
||||
|
||||
struct Error {
|
||||
double absmean = std::numeric_limits<double>::infinity();
|
||||
double stddev = std::numeric_limits<double>::infinity();
|
||||
double absmax = std::numeric_limits<double>::infinity();
|
||||
|
||||
constexpr auto Fields() const { return std::tie(absmean, stddev, absmax); }
|
||||
|
||||
template <class Fn>
|
||||
friend constexpr bool cmp_all(const Error& a, const Error& b,
|
||||
const Fn& fn) {
|
||||
return cmp_tups_all(a.Fields(), b.Fields(), fn);
|
||||
}
|
||||
friend constexpr bool operator<(const Error& a, const Error& b) {
|
||||
return cmp_all(a, b, [](const auto& a, const auto& b) { return a < b; });
|
||||
}
|
||||
friend constexpr bool operator<=(const Error& a, const Error& b) {
|
||||
return cmp_all(a, b, [](const auto& a, const auto& b) { return a <= b; });
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& s, const Error& a) {
|
||||
return s << "Stats::Error"
|
||||
<< "{ absmean:" << a.absmean << ", stddev:" << a.stddev
|
||||
<< ", absmax:" << a.absmax << " }";
|
||||
}
|
||||
};
|
||||
|
||||
operator Error() const {
|
||||
return {abs(mean), standardDeviation(), std::max(abs(min), abs(max))};
|
||||
}
|
||||
};
|
||||
static_assert(Stats::Error{0, 0, 0} < Stats::Error{1, 1, 1});
|
||||
static_assert(!(Stats::Error{0, 1, 0} < Stats::Error{1, 1, 1}));
|
||||
static_assert(Stats::Error{0, 1, 0} <= Stats::Error{1, 1, 1});
|
||||
static_assert(!(Stats::Error{0, 2, 0} <= Stats::Error{1, 1, 1}));
|
||||
|
||||
// -
|
||||
|
||||
static Stats StatsForLutError(const ColorspaceTransform& ct,
|
||||
const ivec3 srcQuants, const ivec3 dstQuants) {
|
||||
|
@ -477,222 +403,3 @@ TEST(Colorspaces, LutError_Rec709Full_Srgb)
|
|||
EXPECT_NEAR(stats.min, 0, 0.001);
|
||||
EXPECT_NEAR(stats.max, 17, 0.001);
|
||||
}
|
||||
|
||||
// -
|
||||
// https://www.reedbeta.com/blog/python-like-enumerate-in-cpp17/
|
||||
|
||||
template <typename T, typename TIter = decltype(std::begin(std::declval<T>())),
|
||||
typename = decltype(std::end(std::declval<T>()))>
|
||||
constexpr auto enumerate(T&& iterable) {
|
||||
struct iterator {
|
||||
size_t i;
|
||||
TIter iter;
|
||||
bool operator!=(const iterator& other) const { return iter != other.iter; }
|
||||
void operator++() {
|
||||
++i;
|
||||
++iter;
|
||||
}
|
||||
auto operator*() const { return std::tie(i, *iter); }
|
||||
};
|
||||
struct iterable_wrapper {
|
||||
T iterable;
|
||||
auto begin() { return iterator{0, std::begin(iterable)}; }
|
||||
auto end() { return iterator{0, std::end(iterable)}; }
|
||||
};
|
||||
return iterable_wrapper{std::forward<T>(iterable)};
|
||||
}
|
||||
|
||||
inline auto MakeLinear(const float from, const float to, const int n) {
|
||||
std::vector<float> ret;
|
||||
ret.resize(n);
|
||||
for (auto [i, val] : enumerate(ret)) {
|
||||
const auto t = i / float(ret.size() - 1);
|
||||
val = from + (to - from) * t;
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
inline auto MakeGamma(const float exp, const int n) {
|
||||
std::vector<float> ret;
|
||||
ret.resize(n);
|
||||
for (auto [i, val] : enumerate(ret)) {
|
||||
const auto t = i / float(ret.size() - 1);
|
||||
val = powf(t, exp);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
// -
|
||||
|
||||
TEST(Colorspaces, GuessGamma)
|
||||
{
|
||||
EXPECT_NEAR(GuessGamma(MakeGamma(1, 11)), 1.0, 0);
|
||||
EXPECT_NEAR(GuessGamma(MakeGamma(2.2, 11)), 2.2, 4.8e-8);
|
||||
EXPECT_NEAR(GuessGamma(MakeGamma(1 / 2.2, 11)), 1 / 2.2, 1.1e-7);
|
||||
}
|
||||
|
||||
// -
|
||||
|
||||
template <class T, class U>
|
||||
float StdDev(const T& test, const U& ref) {
|
||||
float sum = 0;
|
||||
for (size_t i = 0; i < test.size(); i++) {
|
||||
const auto diff = test[i] - ref[i];
|
||||
sum += diff * diff;
|
||||
}
|
||||
const auto variance = sum / test.size();
|
||||
return sqrt(variance);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline void AutoLinearFill(T& vals) {
|
||||
LinearFill(vals, {
|
||||
{0, 0},
|
||||
{vals.size() - 1.0f, 1},
|
||||
});
|
||||
}
|
||||
|
||||
template <class T, class... More>
|
||||
auto MakeArray(const T& a0, const More&... args) {
|
||||
return std::array<T, 1 + sizeof...(More)>{a0, static_cast<float>(args)...};
|
||||
}
|
||||
|
||||
TEST(Colorspaces, LinearFill)
|
||||
{
|
||||
EXPECT_NEAR(StdDev(MakeLinear(0, 1, 3), MakeArray<float>(0, 0.5, 1)), 0,
|
||||
0.001);
|
||||
|
||||
auto vals = std::vector<float>(3);
|
||||
LinearFill(vals, {
|
||||
{0, 0},
|
||||
{vals.size() - 1.0f, 1},
|
||||
});
|
||||
EXPECT_NEAR(StdDev(vals, MakeArray<float>(0, 0.5, 1)), 0, 0.001);
|
||||
|
||||
LinearFill(vals, {
|
||||
{0, 1},
|
||||
{vals.size() - 1.0f, 0},
|
||||
});
|
||||
EXPECT_NEAR(StdDev(vals, MakeArray<float>(1, 0.5, 0)), 0, 0.001);
|
||||
}
|
||||
|
||||
TEST(Colorspaces, DequantizeMonotonic)
|
||||
{
|
||||
auto orig = std::vector<float>{0, 0, 0, 1, 1, 2};
|
||||
auto vals = orig;
|
||||
EXPECT_TRUE(IsMonotonic(vals));
|
||||
EXPECT_TRUE(!IsMonotonic(vals, std::less<float>{}));
|
||||
DequantizeMonotonic(vals);
|
||||
EXPECT_TRUE(IsMonotonic(vals, std::less<float>{}));
|
||||
EXPECT_LT(StdDev(vals, orig),
|
||||
StdDev(MakeLinear(orig.front(), orig.back(), vals.size()), orig));
|
||||
}
|
||||
|
||||
TEST(Colorspaces, InvertLut)
|
||||
{
|
||||
const auto linear = MakeLinear(0, 1, 256);
|
||||
auto linearFromSrgb = linear;
|
||||
for (auto& val : linearFromSrgb) {
|
||||
val = powf(val, 2.2);
|
||||
}
|
||||
auto srgbFromLinearExpected = linear;
|
||||
for (auto& val : srgbFromLinearExpected) {
|
||||
val = powf(val, 1 / 2.2);
|
||||
}
|
||||
|
||||
auto srgbFromLinearViaInvert = linearFromSrgb;
|
||||
InvertLut(linearFromSrgb, &srgbFromLinearViaInvert);
|
||||
// I just want to appreciate that InvertLut is a non-analytical approximation,
|
||||
// and yet it's extraordinarily close to the analytical inverse.
|
||||
EXPECT_LE(Stats::Diff(srgbFromLinearViaInvert, srgbFromLinearExpected),
|
||||
(Stats::Error{3e-6, 3e-6, 3e-5}));
|
||||
|
||||
const auto srcSrgb = MakeLinear(0, 1, 256);
|
||||
auto roundtripSrgb = srcSrgb;
|
||||
for (auto& srgb : roundtripSrgb) {
|
||||
const auto linear = SampleOutByIn(linearFromSrgb, srgb);
|
||||
const auto srgb2 = SampleOutByIn(srgbFromLinearViaInvert, linear);
|
||||
// printf("[%f] %f -> %f -> %f\n", srgb2-srgb, srgb, linear, srgb2);
|
||||
srgb = srgb2;
|
||||
}
|
||||
EXPECT_LE(Stats::Diff(roundtripSrgb, srcSrgb),
|
||||
(Stats::Error{0.0013, 0.0046, 0.023}));
|
||||
}
|
||||
|
||||
TEST(Colorspaces, XyzFromLinearRgb)
|
||||
{
|
||||
const auto xyzd65FromLinearRgb = XyzFromLinearRgb(Chromaticities::Srgb());
|
||||
|
||||
// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
|
||||
const auto XYZD65_FROM_LINEAR_RGB = mat3({
|
||||
vec3{{0.4124564, 0.3575761, 0.1804375}},
|
||||
vec3{{0.2126729, 0.7151522, 0.0721750}},
|
||||
vec3{{0.0193339, 0.1191920, 0.9503041}},
|
||||
});
|
||||
EXPECT_NEAR(sqrt(dotDifference(xyzd65FromLinearRgb, XYZD65_FROM_LINEAR_RGB)),
|
||||
0, 0.001);
|
||||
}
|
||||
|
||||
TEST(Colorspaces, ColorProfileConversionDesc_SrgbFromRec709)
|
||||
{
|
||||
const auto srgb = ColorProfileDesc::From({
|
||||
Chromaticities::Srgb(),
|
||||
PiecewiseGammaDesc::Srgb(),
|
||||
});
|
||||
const auto rec709 = ColorProfileDesc::From({
|
||||
Chromaticities::Rec709(),
|
||||
PiecewiseGammaDesc::Rec709(),
|
||||
});
|
||||
|
||||
{
|
||||
const auto conv = ColorProfileConversionDesc::From({
|
||||
.src = srgb,
|
||||
.dst = srgb,
|
||||
});
|
||||
auto src = vec3(16.0);
|
||||
auto dst = conv.Apply(src / 255) * 255;
|
||||
|
||||
const auto tfa = PiecewiseGammaDesc::Srgb();
|
||||
const auto tfb = PiecewiseGammaDesc::Srgb();
|
||||
const auto expected =
|
||||
TfFromLinear(tfb, LinearFromTf(tfa, src.x() / 255)) * 255;
|
||||
|
||||
printf("%f %f %f\n", src.x(), src.y(), src.z());
|
||||
printf("%f %f %f\n", dst.x(), dst.y(), dst.z());
|
||||
EXPECT_LT(Stats::Diff(dst.data, vec3(expected).data), (Stats::Error{0.42}));
|
||||
}
|
||||
{
|
||||
const auto conv = ColorProfileConversionDesc::From({
|
||||
.src = rec709,
|
||||
.dst = rec709,
|
||||
});
|
||||
auto src = vec3(16.0);
|
||||
auto dst = conv.Apply(src / 255) * 255;
|
||||
|
||||
const auto tfa = PiecewiseGammaDesc::Rec709();
|
||||
const auto tfb = PiecewiseGammaDesc::Rec709();
|
||||
const auto expected =
|
||||
TfFromLinear(tfb, LinearFromTf(tfa, src.x() / 255)) * 255;
|
||||
|
||||
printf("%f %f %f\n", src.x(), src.y(), src.z());
|
||||
printf("%f %f %f\n", dst.x(), dst.y(), dst.z());
|
||||
EXPECT_LT(Stats::Diff(dst.data, vec3(expected).data), (Stats::Error{1e-6}));
|
||||
}
|
||||
{
|
||||
const auto conv = ColorProfileConversionDesc::From({
|
||||
.src = rec709,
|
||||
.dst = srgb,
|
||||
});
|
||||
auto src = vec3(16.0);
|
||||
auto dst = conv.Apply(src / 255) * 255;
|
||||
|
||||
const auto tfa = PiecewiseGammaDesc::Rec709();
|
||||
const auto tfb = PiecewiseGammaDesc::Srgb();
|
||||
const auto expected =
|
||||
TfFromLinear(tfb, LinearFromTf(tfa, src.x() / 255)) * 255;
|
||||
printf("expected: %f\n", expected);
|
||||
printf("%f %f %f\n", src.x(), src.y(), src.z());
|
||||
printf("%f %f %f\n", dst.x(), dst.y(), dst.z());
|
||||
EXPECT_LT(Stats::Diff(dst.data, vec3(expected).data), (Stats::Error{0.12}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,35 +11,35 @@ extern "C" {
|
|||
#ifndef ICC_H
|
||||
/* icc34 defines */
|
||||
|
||||
/*****************************************************************
|
||||
/*****************************************************************
|
||||
Copyright (c) 1994-1996 SunSoft, Inc.
|
||||
|
||||
Rights Reserved
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without restrict-
|
||||
ion, including without limitation the rights to use, copy, modify,
|
||||
merge, publish distribute, sublicense, and/or sell copies of the
|
||||
Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
files (the "Software"), to deal in the Software without restrict-
|
||||
ion, including without limitation the rights to use, copy, modify,
|
||||
merge, publish distribute, sublicense, and/or sell copies of the
|
||||
Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-
|
||||
INFRINGEMENT. IN NO EVENT SHALL SUNSOFT, INC. OR ITS PARENT
|
||||
COMPANY BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the name of SunSoft, Inc.
|
||||
shall not be used in advertising or otherwise to promote the
|
||||
sale, use or other dealings in this Software without written
|
||||
authorization from SunSoft Inc.
|
||||
INFRINGEMENT. IN NO EVENT SHALL SUNSOFT, INC. OR ITS PARENT
|
||||
COMPANY BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the name of SunSoft, Inc.
|
||||
shall not be used in advertising or otherwise to promote the
|
||||
sale, use or other dealings in this Software without written
|
||||
authorization from SunSoft Inc.
|
||||
******************************************************************/
|
||||
|
||||
/*
|
||||
|
@ -48,11 +48,11 @@ authorization from SunSoft Inc.
|
|||
* don't use the same objects on different threads at the same time.
|
||||
*/
|
||||
|
||||
/*
|
||||
/*
|
||||
* Color Space Signatures
|
||||
* Note that only icSigXYZData and icSigLabData are valid
|
||||
* Profile Connection Spaces (PCSs)
|
||||
*/
|
||||
*/
|
||||
typedef enum {
|
||||
icSigXYZData = 0x58595A20L, /* 'XYZ ' */
|
||||
icSigLabData = 0x4C616220L, /* 'Lab ' */
|
||||
|
@ -79,13 +79,12 @@ typedef enum {
|
|||
icSig13colorData = 0x44434C52L, /* 'DCLR' */
|
||||
icSig14colorData = 0x45434C52L, /* 'ECLR' */
|
||||
icSig15colorData = 0x46434C52L, /* 'FCLR' */
|
||||
icMaxEnumData = 0xFFFFFFFFL
|
||||
icMaxEnumData = 0xFFFFFFFFL
|
||||
} icColorSpaceSignature;
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <cstdint>
|
||||
|
||||
struct _qcms_transform;
|
||||
typedef struct _qcms_transform qcms_transform;
|
||||
|
@ -197,42 +196,6 @@ void qcms_enable_iccv4();
|
|||
void qcms_enable_neon();
|
||||
void qcms_enable_avx();
|
||||
|
||||
|
||||
|
||||
// -
|
||||
/*
|
||||
struct qcms_mat3r3 {
|
||||
struct row {
|
||||
float cols[3];
|
||||
};
|
||||
row rows[3];
|
||||
};
|
||||
*/
|
||||
struct qcms_profile_data {
|
||||
uint32_t class_type;
|
||||
uint32_t color_space;
|
||||
uint32_t pcs;
|
||||
qcms_intent rendering_intent;
|
||||
float red_colorant_xyzd50[3];
|
||||
float blue_colorant_xyzd50[3];
|
||||
float green_colorant_xyzd50[3];
|
||||
int32_t linear_from_trc_red_samples;
|
||||
int32_t linear_from_trc_blue_samples;
|
||||
int32_t linear_from_trc_green_samples;
|
||||
};
|
||||
|
||||
void qcms_profile_get_data(const qcms_profile*, qcms_profile_data* out_data);
|
||||
|
||||
|
||||
enum class qcms_color_channel : uint8_t {
|
||||
Red,
|
||||
Green,
|
||||
Blue,
|
||||
};
|
||||
|
||||
void qcms_profile_get_lut(const qcms_profile*, qcms_color_channel,
|
||||
float* begin, float* end);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -7,11 +7,9 @@ use libc::{fclose, fopen, fread, free, malloc, memset, FILE};
|
|||
use crate::{
|
||||
double_to_s15Fixed16Number,
|
||||
iccread::*,
|
||||
s15Fixed16Number_to_float,
|
||||
transform::get_rgb_colorants,
|
||||
transform::DataType,
|
||||
transform::{qcms_transform, transform_create},
|
||||
transform_util,
|
||||
Intent,
|
||||
};
|
||||
|
||||
|
@ -377,124 +375,6 @@ pub unsafe extern "C" fn qcms_transform_data(
|
|||
length,
|
||||
);
|
||||
}
|
||||
/*
|
||||
use crate::matrix;
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct qcms_mat3r3 {
|
||||
pub rows: [[f32; 3] ; 3],
|
||||
}
|
||||
impl qcms_mat3r3 {
|
||||
fn from(m: matrix::Matrix) -> qcms_mat3r3 {
|
||||
qcms_mat3r3{
|
||||
rows: [
|
||||
m.row(0),
|
||||
m.row(1),
|
||||
m.row(2),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub struct qcms_profile_data {
|
||||
pub class_type: u32,
|
||||
pub color_space: u32,
|
||||
pub pcs: u32,
|
||||
pub rendering_intent: Intent,
|
||||
pub red_colorant_xyzd50: [f32; 3],
|
||||
pub blue_colorant_xyzd50: [f32; 3],
|
||||
pub green_colorant_xyzd50: [f32; 3],
|
||||
// Number of samples in the e.g. gamma->linear LUT.
|
||||
pub linear_from_trc_red_samples: i32,
|
||||
pub linear_from_trc_blue_samples: i32,
|
||||
pub linear_from_trc_green_samples: i32,
|
||||
}
|
||||
|
||||
pub use crate::iccread::Profile as qcms_profile;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn qcms_profile_get_data(
|
||||
profile: &qcms_profile,
|
||||
out_data: &mut qcms_profile_data,
|
||||
) {
|
||||
out_data.class_type = profile.class_type;
|
||||
out_data.color_space = profile.color_space;
|
||||
out_data.pcs = profile.pcs;
|
||||
out_data.rendering_intent = profile.rendering_intent;
|
||||
|
||||
fn colorant(c: &XYZNumber) -> [f32;3] {
|
||||
[c.X, c.Y, c.Z].map(s15Fixed16Number_to_float)
|
||||
}
|
||||
out_data.red_colorant_xyzd50 = colorant(&profile.redColorant);
|
||||
out_data.blue_colorant_xyzd50 = colorant(&profile.blueColorant);
|
||||
out_data.green_colorant_xyzd50 = colorant(&profile.greenColorant);
|
||||
|
||||
fn trc_to_samples(trc: &Option<Box<curveType>>) -> i32 {
|
||||
if let Some(ref trc) = *trc {
|
||||
match &**trc {
|
||||
curveType::Curve(v) => {
|
||||
let len = v.len();
|
||||
if len <= 1 {
|
||||
-1
|
||||
} else {
|
||||
len as i32
|
||||
}
|
||||
},
|
||||
curveType::Parametric(_) => -1,
|
||||
}
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
out_data.linear_from_trc_red_samples = trc_to_samples(&profile.redTRC);
|
||||
out_data.linear_from_trc_blue_samples = trc_to_samples(&profile.blueTRC);
|
||||
out_data.linear_from_trc_green_samples = trc_to_samples(&profile.greenTRC);
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum qcms_color_channel {
|
||||
Red,
|
||||
Green,
|
||||
Blue,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn qcms_profile_get_lut(
|
||||
profile: &qcms_profile,
|
||||
channel: qcms_color_channel, // FYI: UB if you give Rust something out of range!
|
||||
out_begin: *mut f32,
|
||||
out_end: *mut f32,
|
||||
) {
|
||||
let out_slice = unsafe {
|
||||
std::slice::from_raw_parts_mut(out_begin, out_end.offset_from(out_begin) as usize)
|
||||
};
|
||||
|
||||
let trc = match channel {
|
||||
qcms_color_channel::Red => &profile.redTRC,
|
||||
qcms_color_channel::Green => &profile.greenTRC,
|
||||
qcms_color_channel::Blue => &profile.blueTRC,
|
||||
};
|
||||
|
||||
let samples_u16 = if let Some(trc) = trc {
|
||||
let trc = &*trc;
|
||||
// Yes, sub-optimal, but easier to implement, and these aren't big or hot:
|
||||
// 1. Ask for a new vec<u16> lut based on the trc.
|
||||
// * (eat the extra alloc)
|
||||
// 2. Convert the u16s back out to f32s in our slice.
|
||||
// * (eat the copy and quantization error from f32->u16->f32 roundtrip)
|
||||
transform_util::build_lut_for_linear_from_tf(trc, Some(out_slice.len()))
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
assert_eq!(samples_u16.len(), out_slice.len());
|
||||
for (d, s) in out_slice.iter_mut().zip(samples_u16.into_iter()) {
|
||||
*d = (s as f32) / (u16::MAX as f32);
|
||||
}
|
||||
}
|
||||
|
||||
pub type icColorSpaceSignature = u32;
|
||||
pub const icSigGrayData: icColorSpaceSignature = 0x47524159; // 'GRAY'
|
||||
|
@ -502,6 +382,7 @@ pub const icSigRgbData: icColorSpaceSignature = 0x52474220; // 'RGB '
|
|||
pub const icSigCmykData: icColorSpaceSignature = 0x434d594b; // 'CMYK'
|
||||
|
||||
pub use crate::iccread::qcms_profile_is_bogus;
|
||||
pub use crate::iccread::Profile as qcms_profile;
|
||||
pub use crate::transform::{
|
||||
qcms_enable_iccv4, qcms_profile_precache_output_transform, qcms_transform_release,
|
||||
};
|
||||
|
|
|
@ -50,8 +50,6 @@ pub struct Profile {
|
|||
pub(crate) redColorant: XYZNumber,
|
||||
pub(crate) blueColorant: XYZNumber,
|
||||
pub(crate) greenColorant: XYZNumber,
|
||||
// "TRC" is EOTF, e.g. gamma->linear transfer function.
|
||||
// Because ICC profiles are phrased as decodings to the xyzd50-linear PCS.
|
||||
pub(crate) redTRC: Option<Box<curveType>>,
|
||||
pub(crate) blueTRC: Option<Box<curveType>>,
|
||||
pub(crate) greenTRC: Option<Box<curveType>>,
|
||||
|
@ -95,7 +93,7 @@ pub(crate) struct lutmABType {
|
|||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum curveType {
|
||||
Curve(Vec<uInt16Number>), // len=0 => Linear, len=1 => Gamma(v[0]), _ => lut
|
||||
Curve(Vec<uInt16Number>),
|
||||
/// The ICC parametricCurveType is specified in terms of s15Fixed16Number,
|
||||
/// so it's possible to use this variant to specify greater precision than
|
||||
/// any raw ICC profile could
|
||||
|
|
|
@ -11,11 +11,10 @@
|
|||
#![cfg_attr(
|
||||
feature = "neon",
|
||||
feature(arm_target_feature, raw_ref_op)
|
||||
|
||||
)]
|
||||
|
||||
/// These values match the Rendering Intent values from the ICC spec
|
||||
#[repr(C)]
|
||||
#[repr(u32)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Intent {
|
||||
AbsoluteColorimetric = 3,
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct Matrix {
|
||||
pub m: [[f32; 3]; 3], // Three rows of three elems.
|
||||
pub m: [[f32; 3]; 3],
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
|
@ -39,10 +39,6 @@ impl Matrix {
|
|||
result
|
||||
}
|
||||
|
||||
pub fn row(&self, r: usize) -> [f32; 3] {
|
||||
self.m[r]
|
||||
}
|
||||
|
||||
//probably reuse this computation in matrix_invert
|
||||
pub fn det(&self) -> f32 {
|
||||
let det: f32 = self.m[0][0] * self.m[1][1] * self.m[2][2]
|
||||
|
|
|
@ -440,10 +440,10 @@ invert_lut will produce an inverse of:
|
|||
which has an maximum error of about 9855 (pixel difference of ~38.346)
|
||||
|
||||
For now, we punt the decision of output size to the caller. */
|
||||
fn invert_lut(table: &[u16], out_length: usize) -> Vec<u16> {
|
||||
fn invert_lut(table: &[u16], out_length: i32) -> Vec<u16> {
|
||||
/* for now we invert the lut by creating a lut of size out_length
|
||||
* and attempting to lookup a value for each entry using lut_inverse_interp16 */
|
||||
let mut output = Vec::with_capacity(out_length);
|
||||
let mut output = Vec::with_capacity(out_length as usize);
|
||||
for i in 0..out_length {
|
||||
let x: f64 = i as f64 * 65535.0f64 / (out_length - 1) as f64;
|
||||
let input: uint16_fract_t = (x + 0.5f64).floor() as uint16_fract_t;
|
||||
|
@ -476,7 +476,7 @@ pub(crate) fn compute_precache(trc: &curveType, output: &mut [u8; PRECACHE_OUTPU
|
|||
curveType::Parametric(params) => {
|
||||
let mut gamma_table_uint: [u16; 256] = [0; 256];
|
||||
|
||||
let mut inverted_size: usize = 256;
|
||||
let mut inverted_size: i32 = 256;
|
||||
let gamma_table = compute_curve_gamma_table_type_parametric(params);
|
||||
let mut i: u16 = 0u16;
|
||||
while (i as i32) < 256 {
|
||||
|
@ -498,7 +498,7 @@ pub(crate) fn compute_precache(trc: &curveType, output: &mut [u8; PRECACHE_OUTPU
|
|||
0 => compute_precache_linear(output),
|
||||
1 => compute_precache_pow(output, 1. / u8Fixed8Number_to_float(data[0])),
|
||||
_ => {
|
||||
let mut inverted_size = data.len();
|
||||
let mut inverted_size = data.len() as i32;
|
||||
//XXX: the choice of a minimum of 256 here is not backed by any theory,
|
||||
// measurement or data, however it is what lcms uses.
|
||||
// the maximum number we would need is 65535 because that's the
|
||||
|
@ -514,8 +514,8 @@ pub(crate) fn compute_precache(trc: &curveType, output: &mut [u8; PRECACHE_OUTPU
|
|||
}
|
||||
true
|
||||
}
|
||||
fn build_linear_table(length: usize) -> Vec<u16> {
|
||||
let mut output = Vec::with_capacity(length);
|
||||
fn build_linear_table(length: i32) -> Vec<u16> {
|
||||
let mut output = Vec::with_capacity(length as usize);
|
||||
for i in 0..length {
|
||||
let x: f64 = i as f64 * 65535.0f64 / (length - 1) as f64;
|
||||
let input: uint16_fract_t = (x + 0.5f64).floor() as uint16_fract_t;
|
||||
|
@ -523,8 +523,8 @@ fn build_linear_table(length: usize) -> Vec<u16> {
|
|||
}
|
||||
output
|
||||
}
|
||||
fn build_pow_table(gamma: f32, length: usize) -> Vec<u16> {
|
||||
let mut output = Vec::with_capacity(length);
|
||||
fn build_pow_table(gamma: f32, length: i32) -> Vec<u16> {
|
||||
let mut output = Vec::with_capacity(length as usize);
|
||||
for i in 0..length {
|
||||
let mut x: f64 = i as f64 / (length - 1) as f64;
|
||||
x = x.powf(gamma as f64);
|
||||
|
@ -534,75 +534,36 @@ fn build_pow_table(gamma: f32, length: usize) -> Vec<u16> {
|
|||
output
|
||||
}
|
||||
|
||||
fn to_lut(params: &Param, len: usize) -> Vec<u16> {
|
||||
let mut output = Vec::with_capacity(len);
|
||||
for i in 0..len {
|
||||
let X = i as f32 / (len-1) as f32;
|
||||
output.push((params.eval(X) * 65535.) as u16);
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
pub(crate) fn build_lut_for_linear_from_tf(trc: &curveType,
|
||||
lut_len: Option<usize>) -> Vec<u16> {
|
||||
pub(crate) fn build_output_lut(trc: &curveType) -> Option<Vec<u16>> {
|
||||
match trc {
|
||||
curveType::Parametric(params) => {
|
||||
let lut_len = lut_len.unwrap_or(256);
|
||||
let params = Param::new(params);
|
||||
to_lut(¶ms, lut_len)
|
||||
},
|
||||
curveType::Curve(data) => {
|
||||
let autogen_lut_len = lut_len.unwrap_or(4096);
|
||||
match data.len() {
|
||||
0 => build_linear_table(autogen_lut_len),
|
||||
1 => {
|
||||
let gamma = u8Fixed8Number_to_float(data[0]);
|
||||
build_pow_table(gamma, autogen_lut_len)
|
||||
}
|
||||
_ => {
|
||||
let lut_len = lut_len.unwrap_or(data.len());
|
||||
assert_eq!(lut_len, data.len());
|
||||
data.clone() // I feel bad about this.
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
let inv_params = params.invert()?;
|
||||
|
||||
pub(crate) fn build_lut_for_tf_from_linear(trc: &curveType) -> Option<Vec<u16>> {
|
||||
match trc {
|
||||
curveType::Parametric(params) => {
|
||||
let lut_len = 256;
|
||||
let params = Param::new(params);
|
||||
if let Some(inv_params) = params.invert() {
|
||||
return Some(to_lut(&inv_params, lut_len));
|
||||
let mut output = Vec::with_capacity(256);
|
||||
for i in 0..256 {
|
||||
let X = i as f32 / 255.;
|
||||
output.push((inv_params.eval(X) * 65535.) as u16);
|
||||
}
|
||||
// else return None instead of fallthrough to generic lut inversion.
|
||||
return None;
|
||||
},
|
||||
Some(output)
|
||||
}
|
||||
curveType::Curve(data) => {
|
||||
let autogen_lut_len = 4096;
|
||||
match data.len() {
|
||||
0 => {
|
||||
return Some(build_linear_table(autogen_lut_len));
|
||||
},
|
||||
0 => Some(build_linear_table(4096)),
|
||||
1 => {
|
||||
let gamma = 1. / u8Fixed8Number_to_float(data[0]);
|
||||
return Some(build_pow_table(gamma, autogen_lut_len));
|
||||
},
|
||||
_ => {},
|
||||
Some(build_pow_table(gamma, 4096))
|
||||
}
|
||||
_ => {
|
||||
//XXX: the choice of a minimum of 256 here is not backed by any theory,
|
||||
// measurement or data, however it is what lcms uses.
|
||||
let mut output_gamma_lut_length = data.len();
|
||||
if output_gamma_lut_length < 256 {
|
||||
output_gamma_lut_length = 256
|
||||
}
|
||||
Some(invert_lut(data, output_gamma_lut_length as i32))
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let linear_from_tf = build_lut_for_linear_from_tf(trc, None);
|
||||
|
||||
//XXX: the choice of a minimum of 256 here is not backed by any theory,
|
||||
// measurement or data, however it is what lcms uses.
|
||||
let inverted_lut_len = std::cmp::max(linear_from_tf.len(), 256);
|
||||
Some(invert_lut(&linear_from_tf, inverted_lut_len))
|
||||
}
|
||||
|
||||
pub(crate) fn build_output_lut(trc: &curveType) -> Option<Vec<u16>> {
|
||||
build_lut_for_tf_from_linear(trc)
|
||||
}
|
||||
|
|
|
@ -30,10 +30,6 @@
|
|||
#undef NTDDI_VERSION
|
||||
#define NTDDI_VERSION NTDDI_WINBLUE
|
||||
|
||||
// We also need this, or dcomp.h won't give us e.g. IDCompositionDevice3:
|
||||
#undef _WIN32_WINNT_WINTHRESHOLD
|
||||
#define _WIN32_WINNT_WINTHRESHOLD _WIN32_WINNT
|
||||
|
||||
#include <d3d11.h>
|
||||
#include <dcomp.h>
|
||||
#include <ddraw.h>
|
||||
|
@ -54,7 +50,6 @@ decltype(D3D11CreateDevice)* sD3D11CreateDeviceFn = nullptr;
|
|||
|
||||
// It should only be used within CreateDirectCompositionDevice.
|
||||
decltype(DCompositionCreateDevice2)* sDcompCreateDevice2Fn = nullptr;
|
||||
decltype(DCompositionCreateDevice3)* sDcompCreateDevice3Fn = nullptr;
|
||||
|
||||
// It should only be used within CreateDCompSurfaceHandle
|
||||
decltype(DCompositionCreateSurfaceHandle)* sDcompCreateSurfaceHandleFn =
|
||||
|
@ -122,7 +117,7 @@ bool DeviceManagerDx::LoadDcomp() {
|
|||
MOZ_ASSERT(gfxVars::UseWebRenderDCompWin());
|
||||
|
||||
if (sDcompCreateDevice2Fn) {
|
||||
return true; // Already loaded.
|
||||
return true;
|
||||
}
|
||||
|
||||
nsModuleHandle module(LoadLibrarySystem32(L"dcomp.dll"));
|
||||
|
@ -132,8 +127,6 @@ bool DeviceManagerDx::LoadDcomp() {
|
|||
|
||||
sDcompCreateDevice2Fn = (decltype(DCompositionCreateDevice2)*)GetProcAddress(
|
||||
module, "DCompositionCreateDevice2");
|
||||
sDcompCreateDevice3Fn = (decltype(DCompositionCreateDevice3)*)GetProcAddress(
|
||||
module, "DCompositionCreateDevice3");
|
||||
if (!sDcompCreateDevice2Fn) {
|
||||
return false;
|
||||
}
|
||||
|
@ -142,6 +135,9 @@ bool DeviceManagerDx::LoadDcomp() {
|
|||
sDcompCreateSurfaceHandleFn =
|
||||
(decltype(DCompositionCreateSurfaceHandle)*)::GetProcAddress(
|
||||
module, "DCompositionCreateSurfaceHandle");
|
||||
if (!sDcompCreateDevice2Fn) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mDcompModule.steal(module);
|
||||
return true;
|
||||
|
@ -452,16 +448,10 @@ void DeviceManagerDx::CreateDirectCompositionDeviceLocked() {
|
|||
HRESULT hr;
|
||||
RefPtr<IDCompositionDesktopDevice> desktopDevice;
|
||||
MOZ_SEH_TRY {
|
||||
hr = sDcompCreateDevice3Fn(
|
||||
hr = sDcompCreateDevice2Fn(
|
||||
dxgiDevice.get(),
|
||||
IID_PPV_ARGS(
|
||||
(IDCompositionDesktopDevice**)getter_AddRefs(desktopDevice)));
|
||||
if (!desktopDevice) {
|
||||
hr = sDcompCreateDevice2Fn(
|
||||
dxgiDevice.get(),
|
||||
IID_PPV_ARGS(
|
||||
(IDCompositionDesktopDevice**)getter_AddRefs(desktopDevice)));
|
||||
}
|
||||
}
|
||||
MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { return; }
|
||||
|
||||
|
|
|
@ -2030,10 +2030,15 @@ DeviceColor gfxPlatform::TransformPixel(const sRGBColor& in,
|
|||
return DeviceColor(in.r, in.g, in.b, in.a);
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> gfxPlatform::GetPlatformCMSOutputProfileData() {
|
||||
return GetPrefCMSOutputProfileData();
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> gfxPlatform::GetPrefCMSOutputProfileData() {
|
||||
const auto mirror = StaticPrefs::gfx_color_management_display_profile();
|
||||
const auto fname = *mirror;
|
||||
if (fname == "") {
|
||||
nsAutoCString fname;
|
||||
Preferences::GetCString("gfx.color_management.display_profile", fname);
|
||||
|
||||
if (fname.IsEmpty()) {
|
||||
return nsTArray<uint8_t>();
|
||||
}
|
||||
|
||||
|
|
|
@ -751,9 +751,7 @@ class gfxPlatform : public mozilla::layers::MemoryPressureListener {
|
|||
* Returns a buffer containing the CMS output profile data. The way this
|
||||
* is obtained is platform-specific.
|
||||
*/
|
||||
virtual nsTArray<uint8_t> GetPlatformCMSOutputProfileData() {
|
||||
return GetPrefCMSOutputProfileData();
|
||||
}
|
||||
virtual nsTArray<uint8_t> GetPlatformCMSOutputProfileData();
|
||||
|
||||
/**
|
||||
* Return information on how child processes should initialize graphics
|
||||
|
@ -865,15 +863,13 @@ class gfxPlatform : public mozilla::layers::MemoryPressureListener {
|
|||
virtual void ImportContentDeviceData(
|
||||
const mozilla::gfx::ContentDeviceData& aData);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Returns the contents of the file pointed to by the
|
||||
* gfx.color_management.display_profile pref, if set.
|
||||
* Returns an empty array if not set, or if an error occurs
|
||||
*/
|
||||
static nsTArray<uint8_t> GetPrefCMSOutputProfileData();
|
||||
nsTArray<uint8_t> GetPrefCMSOutputProfileData();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* If inside a child process and currently being initialized by the
|
||||
* SetXPCOMProcessAttributes message, this can be used by subclasses to
|
||||
|
|
|
@ -1036,21 +1036,16 @@ nsTArray<uint8_t> gfxWindowsPlatform::GetPlatformCMSOutputProfileData() {
|
|||
return result;
|
||||
}
|
||||
|
||||
return GetPlatformCMSOutputProfileData_Impl();
|
||||
}
|
||||
if (!mCachedOutputColorProfile.IsEmpty()) {
|
||||
return mCachedOutputColorProfile.Clone();
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> gfxWindowsPlatform::GetPlatformCMSOutputProfileData_Impl() {
|
||||
static nsTArray<uint8_t> sCached = [&] {
|
||||
// Check override pref first:
|
||||
nsTArray<uint8_t> prefProfileData =
|
||||
gfxPlatform::GetPrefCMSOutputProfileData();
|
||||
mCachedOutputColorProfile = [&] {
|
||||
nsTArray<uint8_t> prefProfileData = GetPrefCMSOutputProfileData();
|
||||
if (!prefProfileData.IsEmpty()) {
|
||||
return prefProfileData;
|
||||
}
|
||||
|
||||
// -
|
||||
// Otherwise, create a dummy DC and pull from that.
|
||||
|
||||
HDC dc = ::GetDC(nullptr);
|
||||
if (!dc) {
|
||||
return nsTArray<uint8_t>();
|
||||
|
@ -1083,7 +1078,7 @@ nsTArray<uint8_t> gfxWindowsPlatform::GetPlatformCMSOutputProfileData_Impl() {
|
|||
return result;
|
||||
}();
|
||||
|
||||
return sCached.Clone();
|
||||
return mCachedOutputColorProfile.Clone();
|
||||
}
|
||||
|
||||
void gfxWindowsPlatform::GetDLLVersion(char16ptr_t aDLLPath,
|
||||
|
|
|
@ -200,13 +200,7 @@ class gfxWindowsPlatform final : public gfxPlatform {
|
|||
|
||||
protected:
|
||||
bool AccelerateLayersByDefault() override { return true; }
|
||||
|
||||
nsTArray<uint8_t> GetPlatformCMSOutputProfileData() override;
|
||||
|
||||
public:
|
||||
static nsTArray<uint8_t> GetPlatformCMSOutputProfileData_Impl();
|
||||
|
||||
protected:
|
||||
void GetPlatformDisplayInfo(mozilla::widget::InfoObject& aObj) override;
|
||||
|
||||
void ImportGPUDeviceData(const mozilla::gfx::GPUDeviceData& aData) override;
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
#include "DCLayerTree.h"
|
||||
|
||||
#include "gfxWindowsPlatform.h"
|
||||
#include "GLContext.h"
|
||||
#include "GLContextEGL.h"
|
||||
#include "mozilla/gfx/DeviceManagerDx.h"
|
||||
|
@ -29,10 +28,6 @@
|
|||
#undef NTDDI_VERSION
|
||||
#define NTDDI_VERSION NTDDI_WINBLUE
|
||||
|
||||
// We also need this, or dcomp.h won't give us e.g. IDCompositionFilterEffect:
|
||||
#undef _WIN32_WINNT_WINTHRESHOLD
|
||||
#define _WIN32_WINNT_WINTHRESHOLD _WIN32_WINNT
|
||||
|
||||
#include <d3d11.h>
|
||||
#include <d3d11_1.h>
|
||||
#include <dcomp.h>
|
||||
|
@ -572,16 +567,6 @@ void DCExternalSurfaceWrapper::AttachExternalImage(
|
|||
}
|
||||
}
|
||||
|
||||
template <class ToT>
|
||||
struct QI {
|
||||
template <class FromT>
|
||||
[[nodiscard]] static inline RefPtr<ToT> From(FromT* const from) {
|
||||
RefPtr<ToT> to;
|
||||
(void)from->QueryInterface(static_cast<ToT**>(getter_AddRefs(to)));
|
||||
return to;
|
||||
}
|
||||
};
|
||||
|
||||
DCSurface* DCExternalSurfaceWrapper::EnsureSurfaceForExternalImage(
|
||||
wr::ExternalImageId aExternalImage) {
|
||||
if (mSurface) {
|
||||
|
@ -606,117 +591,14 @@ DCSurface* DCExternalSurfaceWrapper::EnsureSurfaceForExternalImage(
|
|||
mSurface = nullptr;
|
||||
}
|
||||
}
|
||||
if (!mSurface) {
|
||||
|
||||
if (mSurface) {
|
||||
// Add surface's visual which will contain video data to our root visual.
|
||||
mVisual->AddVisual(mSurface->GetVisual(), TRUE, nullptr);
|
||||
} else {
|
||||
gfxCriticalNote << "Failed to create a surface for external image: "
|
||||
<< gfx::hexa(texture);
|
||||
return nullptr;
|
||||
}
|
||||
const auto textureSwgl = texture->AsRenderTextureHostSWGL();
|
||||
MOZ_ASSERT(textureSwgl); // Covered above.
|
||||
|
||||
// Add surface's visual which will contain video data to our root visual.
|
||||
const auto surfaceVisual = mSurface->GetVisual();
|
||||
mVisual->AddVisual(surfaceVisual, true, nullptr);
|
||||
|
||||
// -
|
||||
// Apply color management.
|
||||
|
||||
[&]() {
|
||||
const auto cmsMode = GfxColorManagementMode();
|
||||
if (cmsMode == CMSMode::Off) return;
|
||||
|
||||
const auto dcomp = mDCLayerTree->GetCompositionDevice();
|
||||
const auto dcomp3 = QI<IDCompositionDevice3>::From(dcomp);
|
||||
if (!dcomp3) {
|
||||
NS_WARNING(
|
||||
"No IDCompositionDevice3, cannot use dcomp for color management.");
|
||||
return;
|
||||
}
|
||||
|
||||
// -
|
||||
|
||||
const auto cspace = [&]() {
|
||||
const auto rangedCspace = textureSwgl->GetYUVColorSpace();
|
||||
const auto info = FromYUVRangedColorSpace(rangedCspace);
|
||||
auto ret = ToColorSpace2(info.space);
|
||||
if (ret == gfx::ColorSpace2::Display && cmsMode == CMSMode::All) {
|
||||
ret = gfx::ColorSpace2::SRGB;
|
||||
}
|
||||
return ret;
|
||||
}();
|
||||
|
||||
const bool rec709GammaAsSrgb =
|
||||
StaticPrefs::gfx_color_management_rec709_gamma_as_srgb();
|
||||
const bool rec2020GammaAsRec709 =
|
||||
StaticPrefs::gfx_color_management_rec2020_gamma_as_rec709();
|
||||
|
||||
auto cspaceDesc = color::ColorspaceDesc{};
|
||||
switch (cspace) {
|
||||
case gfx::ColorSpace2::Display:
|
||||
return; // No color management needed!
|
||||
case gfx::ColorSpace2::SRGB:
|
||||
cspaceDesc.chrom = color::Chromaticities::Srgb();
|
||||
cspaceDesc.tf = color::PiecewiseGammaDesc::Srgb();
|
||||
break;
|
||||
|
||||
case gfx::ColorSpace2::DISPLAY_P3:
|
||||
cspaceDesc.chrom = color::Chromaticities::DisplayP3();
|
||||
cspaceDesc.tf = color::PiecewiseGammaDesc::DisplayP3();
|
||||
break;
|
||||
|
||||
case gfx::ColorSpace2::BT601_525:
|
||||
cspaceDesc.chrom = color::Chromaticities::Rec601_525_Ntsc();
|
||||
if (rec709GammaAsSrgb) {
|
||||
cspaceDesc.tf = color::PiecewiseGammaDesc::Srgb();
|
||||
} else {
|
||||
cspaceDesc.tf = color::PiecewiseGammaDesc::Rec709();
|
||||
}
|
||||
break;
|
||||
|
||||
case gfx::ColorSpace2::BT709:
|
||||
cspaceDesc.chrom = color::Chromaticities::Rec709();
|
||||
if (rec709GammaAsSrgb) {
|
||||
cspaceDesc.tf = color::PiecewiseGammaDesc::Srgb();
|
||||
} else {
|
||||
cspaceDesc.tf = color::PiecewiseGammaDesc::Rec709();
|
||||
}
|
||||
break;
|
||||
|
||||
case gfx::ColorSpace2::BT2020:
|
||||
cspaceDesc.chrom = color::Chromaticities::Rec2020();
|
||||
if (rec2020GammaAsRec709 && rec709GammaAsSrgb) {
|
||||
cspaceDesc.tf = color::PiecewiseGammaDesc::Srgb();
|
||||
} else if (rec2020GammaAsRec709) {
|
||||
cspaceDesc.tf = color::PiecewiseGammaDesc::Rec709();
|
||||
} else {
|
||||
// Just Rec709 with slightly more precision.
|
||||
cspaceDesc.tf = color::PiecewiseGammaDesc::Rec2020_12bit();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const auto cprofileIn = color::ColorProfileDesc::From(cspaceDesc);
|
||||
auto cprofileOut = mDCLayerTree->OutputColorProfile();
|
||||
bool pretendSrgb = StaticPrefs::gfx_color_management_native_srgb();
|
||||
if (pretendSrgb) {
|
||||
cprofileOut = color::ColorProfileDesc::From({
|
||||
color::Chromaticities::Srgb(),
|
||||
color::PiecewiseGammaDesc::Srgb(),
|
||||
});
|
||||
}
|
||||
const auto conversion = color::ColorProfileConversionDesc::From({
|
||||
.src = cprofileIn,
|
||||
.dst = cprofileOut,
|
||||
});
|
||||
|
||||
// -
|
||||
|
||||
auto chain = ColorManagementChain::From(*dcomp3, conversion);
|
||||
mCManageChain = Some(chain);
|
||||
|
||||
surfaceVisual->SetEffect(mCManageChain->last.get());
|
||||
}();
|
||||
|
||||
return mSurface.get();
|
||||
}
|
||||
|
||||
|
@ -1706,114 +1588,6 @@ void DCLayerTree::DestroyEGLSurface() {
|
|||
}
|
||||
}
|
||||
|
||||
// -
|
||||
|
||||
color::ColorProfileDesc DCLayerTree::QueryOutputColorProfile() {
|
||||
// GPU process can't simply init gfxPlatform, (and we don't need most of it)
|
||||
// but we do need gfxPlatform::GetCMSOutputProfile().
|
||||
// So we steal what we need through the window:
|
||||
const auto outputProfileData =
|
||||
gfxWindowsPlatform::GetPlatformCMSOutputProfileData_Impl();
|
||||
|
||||
const auto qcmsProfile = qcms_profile_from_memory(
|
||||
outputProfileData.Elements(), outputProfileData.Length());
|
||||
MOZ_ASSERT(qcmsProfile);
|
||||
const auto release =
|
||||
MakeScopeExit([&]() { qcms_profile_release(qcmsProfile); });
|
||||
|
||||
const auto ret = color::ColorProfileDesc::From(*qcmsProfile);
|
||||
bool print = gfxEnv::MOZ_GL_SPEW();
|
||||
if (print) {
|
||||
const auto gammaGuess = color::GuessGamma(ret.linearFromTf.r);
|
||||
printf_stderr(
|
||||
"Display profile:\n"
|
||||
" Approx Gamma: %f\n"
|
||||
" XYZ-D65 Red : %f, %f, %f\n"
|
||||
" XYZ-D65 Green: %f, %f, %f\n"
|
||||
" XYZ-D65 Blue : %f, %f, %f\n",
|
||||
gammaGuess, ret.xyzd65FromLinearRgb.at(0, 0),
|
||||
ret.xyzd65FromLinearRgb.at(0, 1), ret.xyzd65FromLinearRgb.at(0, 2),
|
||||
|
||||
ret.xyzd65FromLinearRgb.at(1, 0), ret.xyzd65FromLinearRgb.at(1, 1),
|
||||
ret.xyzd65FromLinearRgb.at(1, 2),
|
||||
|
||||
ret.xyzd65FromLinearRgb.at(2, 0), ret.xyzd65FromLinearRgb.at(2, 1),
|
||||
ret.xyzd65FromLinearRgb.at(2, 2));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline D2D1_MATRIX_5X4_F to_D2D1_MATRIX_5X4_F(const color::mat4& m) {
|
||||
return D2D1_MATRIX_5X4_F{{{
|
||||
m.rows[0][0],
|
||||
m.rows[1][0],
|
||||
m.rows[2][0],
|
||||
m.rows[3][0],
|
||||
m.rows[0][1],
|
||||
m.rows[1][1],
|
||||
m.rows[2][1],
|
||||
m.rows[3][1],
|
||||
m.rows[0][2],
|
||||
m.rows[1][2],
|
||||
m.rows[2][2],
|
||||
m.rows[3][2],
|
||||
m.rows[0][3],
|
||||
m.rows[1][3],
|
||||
m.rows[2][3],
|
||||
m.rows[3][3],
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
}}};
|
||||
}
|
||||
|
||||
ColorManagementChain ColorManagementChain::From(
|
||||
IDCompositionDevice3& dcomp,
|
||||
const color::ColorProfileConversionDesc& conv) {
|
||||
auto ret = ColorManagementChain{};
|
||||
|
||||
const auto Append = [&](const RefPtr<IDCompositionFilterEffect>& afterLast) {
|
||||
if (ret.last) {
|
||||
afterLast->SetInput(0, ret.last, 0);
|
||||
}
|
||||
ret.last = afterLast;
|
||||
};
|
||||
|
||||
const auto MaybeAppendColorMatrix = [&](const color::mat4& m) {
|
||||
RefPtr<IDCompositionColorMatrixEffect> e;
|
||||
if (approx(m, color::mat4::Identity())) return e;
|
||||
dcomp.CreateColorMatrixEffect(getter_AddRefs(e));
|
||||
MOZ_ASSERT(e);
|
||||
if (!e) return e;
|
||||
e->SetMatrix(to_D2D1_MATRIX_5X4_F(m));
|
||||
Append(e);
|
||||
return e;
|
||||
};
|
||||
const auto MaybeAppendTableTransfer = [&](const color::RgbTransferTables& t) {
|
||||
RefPtr<IDCompositionTableTransferEffect> e;
|
||||
if (!t.r.size() && !t.g.size() && !t.b.size()) return e;
|
||||
dcomp.CreateTableTransferEffect(getter_AddRefs(e));
|
||||
MOZ_ASSERT(e);
|
||||
if (!e) return e;
|
||||
e->SetRedTable(t.r.data(), t.r.size());
|
||||
e->SetGreenTable(t.g.data(), t.g.size());
|
||||
e->SetBlueTable(t.b.data(), t.b.size());
|
||||
Append(e);
|
||||
return e;
|
||||
};
|
||||
|
||||
ret.srcRgbFromSrcYuv = MaybeAppendColorMatrix(conv.srcRgbFromSrcYuv);
|
||||
ret.srcLinearFromSrcTf = MaybeAppendTableTransfer(conv.srcLinearFromSrcTf);
|
||||
ret.dstLinearFromSrcLinear =
|
||||
MaybeAppendColorMatrix(color::mat4(conv.dstLinearFromSrcLinear));
|
||||
ret.dstTfFromDstLinear = MaybeAppendTableTransfer(conv.dstTfFromDstLinear);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ColorManagementChain::~ColorManagementChain() = default;
|
||||
|
||||
} // namespace wr
|
||||
} // namespace mozilla
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
#include <vector>
|
||||
#include <windows.h>
|
||||
|
||||
#include "Colorspaces.h"
|
||||
#include "GLTypes.h"
|
||||
#include "mozilla/HashFunctions.h"
|
||||
#include "mozilla/layers/OverlayInfo.h"
|
||||
|
@ -28,11 +27,7 @@ struct ID3D11VideoContext;
|
|||
struct ID3D11VideoProcessor;
|
||||
struct ID3D11VideoProcessorEnumerator;
|
||||
struct ID3D11VideoProcessorOutputView;
|
||||
struct IDCompositionColorMatrixEffect;
|
||||
struct IDCompositionFilterEffect;
|
||||
struct IDCompositionTableTransferEffect;
|
||||
struct IDCompositionDevice2;
|
||||
struct IDCompositionDevice3;
|
||||
struct IDCompositionSurface;
|
||||
struct IDCompositionTarget;
|
||||
struct IDCompositionVisual2;
|
||||
|
@ -71,23 +66,6 @@ struct GpuOverlayInfo {
|
|||
UINT mRgb10a2OverlaySupportFlags = 0;
|
||||
};
|
||||
|
||||
// -
|
||||
|
||||
struct ColorManagementChain {
|
||||
RefPtr<IDCompositionColorMatrixEffect> srcRgbFromSrcYuv;
|
||||
RefPtr<IDCompositionTableTransferEffect> srcLinearFromSrcTf;
|
||||
RefPtr<IDCompositionColorMatrixEffect> dstLinearFromSrcLinear;
|
||||
RefPtr<IDCompositionTableTransferEffect> dstTfFromDstLinear;
|
||||
RefPtr<IDCompositionFilterEffect> last;
|
||||
|
||||
static ColorManagementChain From(IDCompositionDevice3& dcomp,
|
||||
const color::ColorProfileConversionDesc&);
|
||||
|
||||
~ColorManagementChain();
|
||||
};
|
||||
|
||||
// -
|
||||
|
||||
/**
|
||||
* DCLayerTree manages direct composition layers.
|
||||
* It does not manage gecko's layers::Layer.
|
||||
|
@ -232,19 +210,6 @@ class DCLayerTree {
|
|||
|
||||
bool mPendingCommit;
|
||||
|
||||
static color::ColorProfileDesc QueryOutputColorProfile();
|
||||
|
||||
mutable Maybe<color::ColorProfileDesc> mOutputColorProfile;
|
||||
|
||||
public:
|
||||
const color::ColorProfileDesc& OutputColorProfile() const {
|
||||
if (!mOutputColorProfile) {
|
||||
mOutputColorProfile = Some(QueryOutputColorProfile());
|
||||
}
|
||||
return *mOutputColorProfile;
|
||||
}
|
||||
|
||||
protected:
|
||||
static UniquePtr<GpuOverlayInfo> sGpuOverlayInfo;
|
||||
};
|
||||
|
||||
|
@ -357,7 +322,6 @@ class DCExternalSurfaceWrapper : public DCSurface {
|
|||
|
||||
UniquePtr<DCSurface> mSurface;
|
||||
const bool mIsOpaque;
|
||||
Maybe<ColorManagementChain> mCManageChain;
|
||||
};
|
||||
|
||||
class DCSurfaceVideo : public DCSurface {
|
||||
|
|
21
mfbt/Span.h
21
mfbt/Span.h
|
@ -366,7 +366,6 @@ class Span {
|
|||
public:
|
||||
// constants and types
|
||||
using element_type = ElementType;
|
||||
using value_type = std::remove_cv_t<element_type>;
|
||||
using index_type = size_t;
|
||||
using pointer = element_type*;
|
||||
using reference = element_type&;
|
||||
|
@ -421,16 +420,6 @@ class Span {
|
|||
aEnd)
|
||||
: storage_(aBegin == aEnd ? nullptr : &*aBegin, aEnd - aBegin) {}
|
||||
|
||||
/**
|
||||
* Constructor for {iterator,size_t}
|
||||
*/
|
||||
template <typename OtherElementType, size_t OtherExtent, bool IsConst>
|
||||
constexpr Span(
|
||||
span_details::span_iterator<Span<OtherElementType, OtherExtent>, IsConst>
|
||||
aBegin,
|
||||
index_type aLength)
|
||||
: storage_(!aLength ? nullptr : &*aBegin, aLength) {}
|
||||
|
||||
/**
|
||||
* Constructor for C array.
|
||||
*/
|
||||
|
@ -675,16 +664,6 @@ class Span {
|
|||
return Subspan(0, aEnd);
|
||||
}
|
||||
|
||||
/// std::span-compatible method name
|
||||
constexpr auto subspan(index_type aStart,
|
||||
index_type aLength = dynamic_extent) const {
|
||||
return Subspan(aStart, aLength);
|
||||
}
|
||||
/// std::span-compatible method name
|
||||
constexpr auto from(index_type aStart) const { return From(aStart); }
|
||||
/// std::span-compatible method name
|
||||
constexpr auto to(index_type aEnd) const { return To(aEnd); }
|
||||
|
||||
/**
|
||||
* Subspan with run-time start index and exclusive end index.
|
||||
* (Rust's &foo[start..end])
|
||||
|
|
|
@ -5720,11 +5720,6 @@
|
|||
#endif
|
||||
mirror: always
|
||||
|
||||
- name: gfx.color_management.display_profile
|
||||
type: DataMutexString
|
||||
value: ""
|
||||
mirror: always # But be warned: We cache the result.
|
||||
|
||||
- name: gfx.color_management.force_srgb
|
||||
type: RelaxedAtomicBool
|
||||
value: false
|
||||
|
@ -5732,7 +5727,7 @@
|
|||
|
||||
- name: gfx.color_management.native_srgb
|
||||
type: RelaxedAtomicBool
|
||||
#if defined(XP_MACOSX) || defined(XP_WIN)
|
||||
#if defined(XP_MACOSX)
|
||||
value: true
|
||||
#else
|
||||
value: false
|
||||
|
@ -5757,16 +5752,6 @@
|
|||
value: 0
|
||||
mirror: always
|
||||
|
||||
- name: gfx.color_management.rec709_gamma_as_srgb
|
||||
type: RelaxedAtomicBool
|
||||
value: true # Tragic backwards compat.
|
||||
mirror: always
|
||||
|
||||
- name: gfx.color_management.rec2020_gamma_as_rec709
|
||||
type: RelaxedAtomicBool
|
||||
value: true # Match naive behavior, but hopefully we can stop soon!
|
||||
mirror: always
|
||||
|
||||
- name: gfx.compositor.clearstate
|
||||
type: RelaxedAtomicBool
|
||||
value: false
|
||||
|
|
|
@ -456,6 +456,8 @@ pref("formhelper.autozoom.force-disable.test-only", false);
|
|||
pref("gfx.hidpi.enabled", 2);
|
||||
#endif
|
||||
|
||||
pref("gfx.color_management.display_profile", "");
|
||||
|
||||
pref("gfx.downloadable_fonts.enabled", true);
|
||||
pref("gfx.downloadable_fonts.fallback_delay", 3000);
|
||||
pref("gfx.downloadable_fonts.fallback_delay_short", 100);
|
||||
|
|
Загрузка…
Ссылка в новой задаче