// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file.
// Adapts white point x, y to D50 static Status AdaptToXYZD50(float wx, float wy, Matrix3x3& matrix) { bool ok = (wx >= 0) && (wx <= 1) && (wy > 0) && (wy <= 1); if (!ok) { // Out of range values can cause division through zero // further down with the bradford adaptation too. return JXL_FAILURE("Invalid white point");
}
Vector3 w{wx / wy, 1.0f, (1.0f - wx - wy) / wy}; // 1 / tiny float can still overflow
JXL_RETURN_IF_ERROR(std::isfinite(w[0]) && std::isfinite(w[2]));
Vector3 w50{0.96422f, 1.0f, 0.82521f};
template <size_t N, ExtraTF tf> static std::vector<uint16_t> CreateTableCurve(bool tone_map) { // The generated PQ curve will make room for highlights up to this luminance. // TODO(sboukortt): make this variable? static constexpr float kPQIntensityTarget = 10000;
staticbool CanToneMap(const JxlColorEncoding& encoding) { // If the color space cannot be represented by a CICP tag in the ICC profile // then the rest of the profile must unambiguously identify it; we have less // freedom to do use it for tone mapping.
JxlTransferFunction tf = encoding.transfer_function;
JxlPrimaries p = encoding.primaries;
JxlWhitePoint wp = encoding.white_point; return encoding.color_space == JXL_COLOR_SPACE_RGB &&
(tf == JXL_TRANSFER_FUNCTION_PQ || tf == JXL_TRANSFER_FUNCTION_HLG) &&
((p == JXL_PRIMARIES_P3 &&
(wp == JXL_WHITE_POINT_D65 || wp == JXL_WHITE_POINT_DCI)) ||
(p != JXL_PRIMARIES_CUSTOM && wp == JXL_WHITE_POINT_D65));
}
staticvoid ICCComputeMD5(const std::vector<uint8_t>& data, uint8_t sum[16])
JXL_NO_SANITIZE("unsigned-integer-overflow") {
std::vector<uint8_t> data64 = data;
data64.push_back(128); // Add bytes such that ((size + 8) & 63) == 0.
size_t extra = ((64 - ((data64.size() + 8) & 63)) & 63);
data64.resize(data64.size() + extra, 0); for (uint64_t i = 0; i < 64; i += 8) {
data64.push_back(static_cast<uint64_t>(data.size() << 3u) >> i);
}
for (size_t i = 0; i < data64.size(); i += 64) {
uint32_t a = a0;
uint32_t b = b0;
uint32_t c = c0;
uint32_t d = d0;
uint32_t f;
uint32_t g; for (size_t j = 0; j < 64; j++) { if (j < 16) {
f = (b & c) | ((~b) & d);
g = j;
} elseif (j < 32) {
f = (d & b) | ((~d) & c);
g = (5 * j + 1) & 0xf;
} elseif (j < 48) {
f = b ^ c ^ d;
g = (3 * j + 5) & 0xf;
} else {
f = c ^ (b | (~d));
g = (7 * j) & 0xf;
}
uint32_t dg0 = data64[i + g * 4 + 0];
uint32_t dg1 = data64[i + g * 4 + 1];
uint32_t dg2 = data64[i + g * 4 + 2];
uint32_t dg3 = data64[i + g * 4 + 3];
uint32_t u = dg0 | (dg1 << 8u) | (dg2 << 16u) | (dg3 << 24u);
f += a + sineparts[j] + u;
a = d;
d = c;
c = b;
b += (f << shift[j]) | (f >> (32u - shift[j]));
}
a0 += a;
b0 += b;
c0 += c;
d0 += d;
}
sum[0] = a0;
sum[1] = a0 >> 8u;
sum[2] = a0 >> 16u;
sum[3] = a0 >> 24u;
sum[4] = b0;
sum[5] = b0 >> 8u;
sum[6] = b0 >> 16u;
sum[7] = b0 >> 24u;
sum[8] = c0;
sum[9] = c0 >> 8u;
sum[10] = c0 >> 16u;
sum[11] = c0 >> 24u;
sum[12] = d0;
sum[13] = d0 >> 8u;
sum[14] = d0 >> 16u;
sum[15] = d0 >> 24u;
}
static Status CreateICCChadMatrix(double wx, double wy, Matrix3x3& result) {
Matrix3x3 m; if (wy == 0) { // WhitePoint can not be pitch-black. return JXL_FAILURE("Invalid WhitePoint");
}
JXL_RETURN_IF_ERROR(AdaptToXYZD50(wx, wy, m));
result = m; returntrue;
}
// Creates RGB to XYZ matrix given RGB primaries and white point in xy. static Status CreateICCRGBMatrix(double rx, double ry, double gx, double gy, double bx, double by, double wx, double wy,
Matrix3x3& result) {
Matrix3x3 m;
JXL_RETURN_IF_ERROR(PrimariesToXYZD50(rx, ry, gx, gy, bx, by, wx, wy, m));
result = m; returntrue;
}
// Writes a 4-character tag staticvoid WriteICCTag(constchar* value, size_t pos,
std::vector<uint8_t>* icc) { if (icc->size() < pos + 4) icc->resize(pos + 4);
memcpy(icc->data() + pos, value, 4);
}
static Status WriteICCS15Fixed16(float value, size_t pos,
std::vector<uint8_t>* icc) { // "nextafterf" for 32768.0f towards zero are: // 32767.998046875, 32767.99609375, 32767.994140625 // Even the first value works well,... bool ok = (-32767.995f <= value) && (value <= 32767.995f); if (!ok) return JXL_FAILURE("ICC value is out of range / NaN");
int32_t i = static_cast<int32_t>(std::lround(value * 65536.0f)); // Use two's complement
uint32_t u = static_cast<uint32_t>(i);
WriteICCUint32(u, pos, icc); returntrue;
}
static Status CreateICCHeader(const JxlColorEncoding& c,
std::vector<uint8_t>* header) { // TODO(lode): choose color management engine name, e.g. "skia" if // integrated in skia. staticconstchar* kCmm = "jxl ";
header->resize(128, 0);
WriteICCUint32(0, 0, header); // size, correct value filled in at end
WriteICCTag(kCmm, 4, header);
WriteICCUint32(0x04400000u, 8, header); constchar* profile_type =
c.color_space == JXL_COLOR_SPACE_XYB ? "scnr" : "mntr";
WriteICCTag(profile_type, 12, header);
WriteICCTag(c.color_space == JXL_COLOR_SPACE_GRAY ? "GRAY" : "RGB ", 16,
header); if (kEnable3DToneMapping && CanToneMap(c)) { // We are going to use a 3D LUT for tone mapping, which will be more compact // with an 8-bit LUT to CIELAB than with a 16-bit LUT to XYZ. 8-bit XYZ // would not be viable due to XYZ being linear, whereas it is fine with // CIELAB's ~cube root.
WriteICCTag("Lab ", 20, header);
} else {
WriteICCTag("XYZ ", 20, header);
}
// Three uint32_t's date/time encoding. // TODO(lode): encode actual date and time, this is a placeholder
uint32_t year = 2019;
uint32_t month = 12;
uint32_t day = 1;
uint32_t hour = 0;
uint32_t minute = 0;
uint32_t second = 0;
WriteICCUint16(year, 24, header);
WriteICCUint16(month, 26, header);
WriteICCUint16(day, 28, header);
WriteICCUint16(hour, 30, header);
WriteICCUint16(minute, 32, header);
WriteICCUint16(second, 34, header);
// Mandatory D50 white point of profile connection space
WriteICCUint32(0x0000f6d6, 68, header);
WriteICCUint32(0x00010000, 72, header);
WriteICCUint32(0x0000d32d, 76, header);
static Status CreateICCLutAtoBTagForXYB(std::vector<uint8_t>* tags) {
WriteICCTag("mAB ", tags->size(), tags); // 4 reserved bytes set to 0
WriteICCUint32(0, tags->size(), tags); // number of input channels
WriteICCUint8(3, tags->size(), tags); // number of output channels
WriteICCUint8(3, tags->size(), tags); // 2 reserved bytes for padding
WriteICCUint16(0, tags->size(), tags); // offset to first B curve
WriteICCUint32(32, tags->size(), tags); // offset to matrix
WriteICCUint32(244, tags->size(), tags); // offset to first M curve
WriteICCUint32(148, tags->size(), tags); // offset to CLUT
WriteICCUint32(80, tags->size(), tags); // offset to first A curve // (reuse linear B curves)
WriteICCUint32(32, tags->size(), tags);
// offset = 32 // no-op curves
JXL_RETURN_IF_ERROR(CreateICCCurvParaTag({1.0f}, 0, tags));
JXL_RETURN_IF_ERROR(CreateICCCurvParaTag({1.0f}, 0, tags));
JXL_RETURN_IF_ERROR(CreateICCCurvParaTag({1.0f}, 0, tags)); // offset = 80 // number of grid points for each input channel for (int i = 0; i < 16; ++i) {
WriteICCUint8(i < 3 ? 2 : 0, tags->size(), tags);
} // precision = 2
WriteICCUint8(2, tags->size(), tags); // 3 bytes of padding
WriteICCUint8(0, tags->size(), tags);
WriteICCUint16(0, tags->size(), tags); // 2*2*2*3 entries of 2 bytes each = 48 bytes const jxl::cms::ColorCube3D& cube = jxl::cms::UnscaledA2BCube(); for (size_t ix = 0; ix < 2; ++ix) { for (size_t iy = 0; iy < 2; ++iy) { for (size_t ib = 0; ib < 2; ++ib) { const jxl::cms::ColorCube0D& out_f = cube[ix][iy][ib]; for (int i = 0; i < 3; ++i) {
int32_t val = static_cast<int32_t>(std::lroundf(65535 * out_f[i]));
JXL_DASSERT(val >= 0 && val <= 65535);
WriteICCUint16(val, tags->size(), tags);
}
}
}
} // offset = 148 // 3 curves with 5 parameters = 3 * (12 + 5 * 4) = 96 bytes for (size_t i = 0; i < 3; ++i) { constfloat b = -jxl::cms::kXYBOffset[i] -
std::cbrt(jxl::cms::kNegOpsinAbsorbanceBiasRGB[i]);
std::vector<float> params = {
3,
1.0f / jxl::cms::kXYBScale[i],
b,
0, // unused
std::max(0.f, -b * jxl::cms::kXYBScale[i]), // make skcms happy
};
JXL_RETURN_IF_ERROR(CreateICCCurvParaTag(params, 3, tags));
} // offset = 244 constdouble matrix[] = {1.5170095, -1.1065225, 0.071623,
-0.050022, 0.5683655, -0.018344,
-1.387676, 1.1145555, 0.6857255}; // 12 * 4 = 48 bytes for (double v : matrix) {
JXL_RETURN_IF_ERROR(WriteICCS15Fixed16(v, tags->size(), tags));
} for (size_t i = 0; i < 3; ++i) { float intercept = 0; for (size_t j = 0; j < 3; ++j) {
intercept += matrix[i * 3 + j] * jxl::cms::kNegOpsinAbsorbanceBiasRGB[j];
}
JXL_RETURN_IF_ERROR(WriteICCS15Fixed16(intercept, tags->size(), tags));
} returntrue;
}
static Status CreateICCLutAtoBTagForHDR(JxlColorEncoding c,
std::vector<uint8_t>* tags) { static constexpr size_t k3DLutDim = 9;
WriteICCTag("mft1", tags->size(), tags); // 4 reserved bytes set to 0
WriteICCUint32(0, tags->size(), tags); // number of input channels
WriteICCUint8(3, tags->size(), tags); // number of output channels
WriteICCUint8(3, tags->size(), tags); // number of CLUT grid points
WriteICCUint8(k3DLutDim, tags->size(), tags); // 1 reserved bytes for padding
WriteICCUint8(0, tags->size(), tags);
// Matrix (per specification, must be identity if input is not XYZ) for (size_t i = 0; i < 3; ++i) { for (size_t j = 0; j < 3; ++j) {
JXL_RETURN_IF_ERROR(
WriteICCS15Fixed16(i == j ? 1.f : 0.f, tags->size(), tags));
}
}
// Input tables for (size_t c = 0; c < 3; ++c) { for (size_t i = 0; i < 256; ++i) {
WriteICCUint8(i, tags->size(), tags);
}
}
// Output tables for (size_t c = 0; c < 3; ++c) { for (size_t i = 0; i < 256; ++i) {
WriteICCUint8(i, tags->size(), tags);
}
}
returntrue;
}
// Some software (Apple Safari, Preview) requires this. static Status CreateICCNoOpBToATag(std::vector<uint8_t>* tags) {
WriteICCTag("mBA ", tags->size(), tags); // notypo // 4 reserved bytes set to 0
WriteICCUint32(0, tags->size(), tags); // number of input channels
WriteICCUint8(3, tags->size(), tags); // number of output channels
WriteICCUint8(3, tags->size(), tags); // 2 reserved bytes for padding
WriteICCUint16(0, tags->size(), tags); // offset to first B curve
WriteICCUint32(32, tags->size(), tags); // offset to matrix
WriteICCUint32(0, tags->size(), tags); // offset to first M curve
WriteICCUint32(0, tags->size(), tags); // offset to CLUT
WriteICCUint32(0, tags->size(), tags); // offset to first A curve
WriteICCUint32(0, tags->size(), tags);
// These strings are baked into Description - do not change.
static std::string ToString(JxlColorSpace color_space) { switch (color_space) { case JXL_COLOR_SPACE_RGB: return"RGB"; case JXL_COLOR_SPACE_GRAY: return"Gra"; case JXL_COLOR_SPACE_XYB: return"XYB"; case JXL_COLOR_SPACE_UNKNOWN: return"CS?"; default: // Should not happen - visitor fails if enum is invalid.
JXL_DEBUG_ABORT("Invalid ColorSpace %u", static_cast<uint32_t>(color_space)); return"Invalid";
}
}
static std::string ToString(JxlWhitePoint white_point) { switch (white_point) { case JXL_WHITE_POINT_D65: return"D65"; case JXL_WHITE_POINT_CUSTOM: return"Cst"; case JXL_WHITE_POINT_E: return"EER"; case JXL_WHITE_POINT_DCI: return"DCI"; default: // Should not happen - visitor fails if enum is invalid.
JXL_DEBUG_ABORT("Invalid WhitePoint %u", static_cast<uint32_t>(white_point)); return"Invalid";
}
}
static std::string ToString(JxlPrimaries primaries) { switch (primaries) { case JXL_PRIMARIES_SRGB: return"SRG"; case JXL_PRIMARIES_2100: return"202"; case JXL_PRIMARIES_P3: return"DCI"; case JXL_PRIMARIES_CUSTOM: return"Cst"; default: // Should not happen - visitor fails if enum is invalid.
JXL_DEBUG_ABORT("Invalid Primaries %u", static_cast<uint32_t>(primaries)); return"Invalid";
}
}
static std::string ToString(JxlTransferFunction transfer_function) { switch (transfer_function) { case JXL_TRANSFER_FUNCTION_SRGB: return"SRG"; case JXL_TRANSFER_FUNCTION_LINEAR: return"Lin"; case JXL_TRANSFER_FUNCTION_709: return"709"; case JXL_TRANSFER_FUNCTION_PQ: return"PeQ"; case JXL_TRANSFER_FUNCTION_HLG: return"HLG"; case JXL_TRANSFER_FUNCTION_DCI: return"DCI"; case JXL_TRANSFER_FUNCTION_UNKNOWN: return"TF?"; case JXL_TRANSFER_FUNCTION_GAMMA:
JXL_DEBUG_ABORT("Invalid TransferFunction: gamma"); return"Invalid"; default: // Should not happen - visitor fails if enum is invalid.
JXL_DEBUG_ABORT("Invalid TransferFunction %u", static_cast<uint32_t>(transfer_function)); return"Invalid";
}
}
static std::string ToString(JxlRenderingIntent rendering_intent) { switch (rendering_intent) { case JXL_RENDERING_INTENT_PERCEPTUAL: return"Per"; case JXL_RENDERING_INTENT_RELATIVE: return"Rel"; case JXL_RENDERING_INTENT_SATURATION: return"Sat"; case JXL_RENDERING_INTENT_ABSOLUTE: return"Abs";
} // Should not happen - visitor fails if enum is invalid.
JXL_DEBUG_ABORT("Invalid RenderingIntent %u", static_cast<uint32_t>(rendering_intent)); return"Invalid";
}
static std::string ColorEncodingDescriptionImpl(const JxlColorEncoding& c) { if (c.color_space == JXL_COLOR_SPACE_RGB &&
c.white_point == JXL_WHITE_POINT_D65) { if (c.rendering_intent == JXL_RENDERING_INTENT_PERCEPTUAL &&
c.transfer_function == JXL_TRANSFER_FUNCTION_SRGB) { if (c.primaries == JXL_PRIMARIES_SRGB) return"sRGB"; if (c.primaries == JXL_PRIMARIES_P3) return"DisplayP3";
} if (c.rendering_intent == JXL_RENDERING_INTENT_RELATIVE &&
c.primaries == JXL_PRIMARIES_2100) { if (c.transfer_function == JXL_TRANSFER_FUNCTION_PQ) return"Rec2100PQ"; if (c.transfer_function == JXL_TRANSFER_FUNCTION_HLG) return"Rec2100HLG";
}
}
std::string d = ToString(c.color_space);
bool explicit_wp_tf = (c.color_space != JXL_COLOR_SPACE_XYB); if (explicit_wp_tf) {
d += '_'; if (c.white_point == JXL_WHITE_POINT_CUSTOM) {
d += jxl::ToString(c.white_point_xy[0]) + ';';
d += jxl::ToString(c.white_point_xy[1]);
} else {
d += ToString(c.white_point);
}
}
if ((c.color_space != JXL_COLOR_SPACE_GRAY) &&
(c.color_space != JXL_COLOR_SPACE_XYB)) {
d += '_'; if (c.primaries == JXL_PRIMARIES_CUSTOM) {
d += jxl::ToString(c.primaries_red_xy[0]) + ';';
d += jxl::ToString(c.primaries_red_xy[1]) + ';';
d += jxl::ToString(c.primaries_green_xy[0]) + ';';
d += jxl::ToString(c.primaries_green_xy[1]) + ';';
d += jxl::ToString(c.primaries_blue_xy[0]) + ';';
d += jxl::ToString(c.primaries_blue_xy[1]);
} else {
d += ToString(c.primaries);
}
}
d += '_';
d += ToString(c.rendering_intent);
if (explicit_wp_tf) {
JxlTransferFunction tf = c.transfer_function;
d += '_'; if (tf == JXL_TRANSFER_FUNCTION_GAMMA) {
d += 'g';
d += jxl::ToString(c.gamma);
} else {
d += ToString(tf);
}
} return d;
}
static Status MaybeCreateProfileImpl(const JxlColorEncoding& c,
std::vector<uint8_t>* icc) {
std::vector<uint8_t> header;
std::vector<uint8_t> tagtable;
std::vector<uint8_t> tags;
JxlTransferFunction tf = c.transfer_function; if (c.color_space == JXL_COLOR_SPACE_UNKNOWN ||
tf == JXL_TRANSFER_FUNCTION_UNKNOWN) { returnfalse; // Not an error
}
switch (c.color_space) { case JXL_COLOR_SPACE_RGB: case JXL_COLOR_SPACE_GRAY: case JXL_COLOR_SPACE_XYB: break; // OK default: return JXL_FAILURE("Invalid CS %u", static_cast<unsignedint>(c.color_space));
}
// The MD5 checksum must be computed on the profile with profile flags, // rendering intent, and region of the checksum itself, set to 0. // TODO(lode): manually verify with a reliable tool that this creates correct // signature (profile id) for ICC profiles.
std::vector<uint8_t> icc_sum = *icc; if (icc_sum.size() >= 64 + 4) {
memset(icc_sum.data() + 44, 0, 4);
memset(icc_sum.data() + 64, 0, 4);
}
uint8_t checksum[16];
detail::ICCComputeMD5(icc_sum, checksum);
// Returns a representation of the ColorEncoding fields (not icc). // Example description: "RGB_D65_SRG_Rel_Lin" static JXL_MAYBE_UNUSED std::string ColorEncodingDescription( const JxlColorEncoding& c) { return detail::ColorEncodingDescriptionImpl(c);
}
// NOTE: for XYB colorspace, the created profile can be used to transform a // *scaled* XYB image (created by ScaleXYB()) to another colorspace. static JXL_MAYBE_UNUSED Status MaybeCreateProfile(const JxlColorEncoding& c,
std::vector<uint8_t>* icc) { return detail::MaybeCreateProfileImpl(c, icc);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.