зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1863255 - Update to libjxl d3a69dbeef78f036969a2500f949f931df857e17 r=tnikkel
Somehow the tests are down for mingw builds with the latest commit 9487d3ab76
, so this only updates to a slightly newer commit.
Differential Revision: https://phabricator.services.mozilla.com/D192872
This commit is contained in:
Родитель
3c2f4aaaa5
Коммит
d8f8727b8d
|
@ -10,9 +10,9 @@ origin:
|
|||
|
||||
url: https://github.com/libjxl/libjxl
|
||||
|
||||
release: 4c23a53dde8840884f424bcca7c60947235fb421 (2023-10-16T11:08:25Z).
|
||||
release: d3a69dbeef78f036969a2500f949f931df857e17 (2023-10-18T13:56:09Z).
|
||||
|
||||
revision: 4c23a53dde8840884f424bcca7c60947235fb421
|
||||
revision: d3a69dbeef78f036969a2500f949f931df857e17
|
||||
|
||||
license: Apache-2.0
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
# license that can be found in the LICENSE file.
|
||||
|
||||
# Continuous integration helper module. This module is meant to be called from
|
||||
# the .gitlab-ci.yml file during the continuous integration build, as well as
|
||||
# from the command line for developers.
|
||||
# workflows during the continuous integration build, as well as from the
|
||||
# command line for developers.
|
||||
|
||||
set -eu
|
||||
|
||||
|
@ -84,8 +84,8 @@ if [[ ! -z "${HWY_BASELINE_TARGETS}" ]]; then
|
|||
fi
|
||||
|
||||
# Version inferred from the CI variables.
|
||||
CI_COMMIT_SHA=${CI_COMMIT_SHA:-${GITHUB_SHA:-}}
|
||||
JPEGXL_VERSION=${JPEGXL_VERSION:-${CI_COMMIT_SHA:0:8}}
|
||||
CI_COMMIT_SHA=${GITHUB_SHA:-}
|
||||
JPEGXL_VERSION=${JPEGXL_VERSION:-}
|
||||
|
||||
# Benchmark parameters
|
||||
STORE_IMAGES=${STORE_IMAGES:-1}
|
||||
|
@ -182,27 +182,6 @@ on_exit() {
|
|||
local retcode="$1"
|
||||
# Always cleanup the CLEANUP_FILES.
|
||||
cleanup
|
||||
|
||||
# Post a message in the MR when requested with POST_MESSAGE_ON_ERROR but only
|
||||
# if the run failed and we are not running from a MR pipeline.
|
||||
if [[ ${retcode} -ne 0 && -n "${CI_BUILD_NAME:-}" &&
|
||||
-n "${POST_MESSAGE_ON_ERROR}" && -z "${CI_MERGE_REQUEST_ID:-}" &&
|
||||
"${CI_BUILD_REF_NAME}" = "master" ]]; then
|
||||
load_mr_vars_from_commit
|
||||
{ set +xeu; } 2>/dev/null
|
||||
local message="**Run ${CI_BUILD_NAME} @ ${CI_COMMIT_SHORT_SHA} failed.**
|
||||
|
||||
Check the output of the job at ${CI_JOB_URL:-} to see if this was your problem.
|
||||
If it was, please rollback this change or fix the problem ASAP, broken builds
|
||||
slow down development. Check if the error already existed in the previous build
|
||||
as well.
|
||||
|
||||
Pipeline: ${CI_PIPELINE_URL}
|
||||
|
||||
Previous build commit: ${CI_COMMIT_BEFORE_SHA}
|
||||
"
|
||||
cmd_post_mr_comment "${message}"
|
||||
fi
|
||||
}
|
||||
|
||||
trap 'retcode=$?; { set +x; } 2>/dev/null; on_exit ${retcode}' INT TERM EXIT
|
||||
|
@ -227,29 +206,23 @@ merge_request_commits() {
|
|||
# changes on the Pull Request if needed. This fetches 10 more commits which
|
||||
# should be enough given that PR normally should have 1 commit.
|
||||
git -C "${MYDIR}" fetch -q origin "${GITHUB_SHA}" --depth 10
|
||||
MR_HEAD_SHA=$(git -C "${MYDIR}" rev-parse HEAD)
|
||||
if [ "${GITHUB_EVENT_NAME}" = "pull_request" ]; then
|
||||
MR_HEAD_SHA="$(git rev-parse "FETCH_HEAD^2" 2>/dev/null ||
|
||||
echo "${GITHUB_SHA}")"
|
||||
else
|
||||
MR_HEAD_SHA="${GITHUB_SHA}"
|
||||
fi
|
||||
else
|
||||
# CI_BUILD_REF is the reference currently being build in the CI workflow.
|
||||
MR_HEAD_SHA=$(git -C "${MYDIR}" rev-parse -q "${CI_BUILD_REF:-HEAD}")
|
||||
MR_HEAD_SHA=$(git -C "${MYDIR}" rev-parse -q "HEAD")
|
||||
fi
|
||||
|
||||
if [[ -n "${CI_MERGE_REQUEST_IID:-}" ]]; then
|
||||
# Merge request pipeline in CI. In this case the upstream is called "origin"
|
||||
# but it refers to the forked project that's the source of the merge
|
||||
# request. We need to get the target of the merge request, for which we need
|
||||
# to query that repository using our CI_JOB_TOKEN.
|
||||
echo "machine gitlab.com login gitlab-ci-token password ${CI_JOB_TOKEN}" \
|
||||
>> "${HOME}/.netrc"
|
||||
git -C "${MYDIR}" fetch "${CI_MERGE_REQUEST_PROJECT_URL}" \
|
||||
"${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}"
|
||||
MR_ANCESTOR_SHA=$(git -C "${MYDIR}" rev-parse -q FETCH_HEAD)
|
||||
elif [[ -n "${GITHUB_BASE_REF:-}" ]]; then
|
||||
if [[ -n "${GITHUB_BASE_REF:-}" ]]; then
|
||||
# Pull request workflow in GitHub Actions. GitHub checkout action uses
|
||||
# "origin" as the remote for the git checkout.
|
||||
git -C "${MYDIR}" fetch -q origin "${GITHUB_BASE_REF}"
|
||||
MR_ANCESTOR_SHA=$(git -C "${MYDIR}" rev-parse -q FETCH_HEAD)
|
||||
else
|
||||
# We are in a local branch, not a merge request.
|
||||
# We are in a local branch, not a pull request workflow.
|
||||
MR_ANCESTOR_SHA=$(git -C "${MYDIR}" rev-parse -q HEAD@{upstream} || true)
|
||||
fi
|
||||
|
||||
|
@ -268,40 +241,6 @@ merge_request_commits() {
|
|||
set -x
|
||||
}
|
||||
|
||||
# Load the MR iid from the landed commit message when running not from a
|
||||
# merge request workflow. This is useful to post back results at the merge
|
||||
# request when running pipelines from master.
|
||||
load_mr_vars_from_commit() {
|
||||
{ set +x; } 2>/dev/null
|
||||
if [[ -z "${CI_MERGE_REQUEST_IID:-}" ]]; then
|
||||
local mr_iid=$(git rev-list --format=%B --max-count=1 HEAD |
|
||||
grep -F "${CI_PROJECT_URL}" | grep -F "/merge_requests" | head -n 1)
|
||||
# mr_iid contains a string like this if it matched:
|
||||
# Part-of: <https://gitlab.com/wg1/jpeg-xlm/merge_requests/123456>
|
||||
if [[ -n "${mr_iid}" ]]; then
|
||||
mr_iid=$(echo "${mr_iid}" |
|
||||
sed -E 's,^.*merge_requests/([0-9]+)>.*$,\1,')
|
||||
CI_MERGE_REQUEST_IID="${mr_iid}"
|
||||
CI_MERGE_REQUEST_PROJECT_ID=${CI_PROJECT_ID}
|
||||
fi
|
||||
fi
|
||||
set -x
|
||||
}
|
||||
|
||||
# Posts a comment to the current merge request.
|
||||
cmd_post_mr_comment() {
|
||||
{ set +x; } 2>/dev/null
|
||||
local comment="$1"
|
||||
if [[ -n "${BOT_TOKEN:-}" && -n "${CI_MERGE_REQUEST_IID:-}" ]]; then
|
||||
local url="${CI_API_V4_URL}/projects/${CI_MERGE_REQUEST_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}/notes"
|
||||
curl -X POST -g \
|
||||
-H "PRIVATE-TOKEN: ${BOT_TOKEN}" \
|
||||
--data-urlencode "body=${comment}" \
|
||||
--output /dev/null \
|
||||
"${url}"
|
||||
fi
|
||||
set -x
|
||||
}
|
||||
|
||||
# Set up and export the environment variables needed by the child processes.
|
||||
export_env() {
|
||||
|
@ -947,15 +886,7 @@ run_benchmark() {
|
|||
return ${PIPESTATUS[0]}
|
||||
)
|
||||
|
||||
if [[ -n "${CI_BUILD_NAME:-}" ]]; then
|
||||
{ set +x; } 2>/dev/null
|
||||
local message="Results for ${CI_BUILD_NAME} @ ${CI_COMMIT_SHORT_SHA} (job ${CI_JOB_URL:-}):
|
||||
|
||||
$(cat "${output_dir}/results.txt")
|
||||
"
|
||||
cmd_post_mr_comment "${message}"
|
||||
set -x
|
||||
fi
|
||||
}
|
||||
|
||||
# Helper function to wait for the CPU temperature to cool down on ARM.
|
||||
|
@ -1183,18 +1114,6 @@ cmd_arm_benchmark() {
|
|||
cmd_cpuset "${RUNNER_CPU_ALL:-}"
|
||||
cat "${runs_file}"
|
||||
|
||||
if [[ -n "${CI_BUILD_NAME:-}" ]]; then
|
||||
load_mr_vars_from_commit
|
||||
{ set +x; } 2>/dev/null
|
||||
local message="Results for ${CI_BUILD_NAME} @ ${CI_COMMIT_SHORT_SHA} (job ${CI_JOB_URL:-}):
|
||||
|
||||
\`\`\`
|
||||
$(column -t -s " " "${runs_file}")
|
||||
\`\`\`
|
||||
"
|
||||
cmd_post_mr_comment "${message}"
|
||||
set -x
|
||||
fi
|
||||
}
|
||||
|
||||
# Generate a corpus and run the fuzzer on that corpus.
|
||||
|
|
|
@ -48,12 +48,12 @@ Status VerifyInput(const PackedPixelFile& ppf) {
|
|||
return true;
|
||||
}
|
||||
|
||||
Status GetColorEncoding(const PackedPixelFile& ppf, const JxlCmsInterface* cms,
|
||||
Status GetColorEncoding(const PackedPixelFile& ppf,
|
||||
ColorEncoding* color_encoding) {
|
||||
if (!ppf.icc.empty()) {
|
||||
IccBytes icc;
|
||||
icc.assign(ppf.icc.data(), ppf.icc.data() + ppf.icc.size());
|
||||
JXL_RETURN_IF_ERROR(color_encoding->SetICC(std::move(icc), cms));
|
||||
IccBytes icc = ppf.icc;
|
||||
JXL_RETURN_IF_ERROR(
|
||||
color_encoding->SetICC(std::move(icc), JxlGetDefaultCms()));
|
||||
} else {
|
||||
JXL_RETURN_IF_ERROR(color_encoding->FromExternal(ppf.color_encoding));
|
||||
}
|
||||
|
@ -325,12 +325,10 @@ Status EncodeJpeg(const PackedPixelFile& ppf, const JpegSettings& jpeg_settings,
|
|||
}
|
||||
JXL_RETURN_IF_ERROR(VerifyInput(ppf));
|
||||
|
||||
const JxlCmsInterface& cms = *JxlGetDefaultCms();
|
||||
|
||||
ColorEncoding color_encoding;
|
||||
JXL_RETURN_IF_ERROR(GetColorEncoding(ppf, &cms, &color_encoding));
|
||||
JXL_RETURN_IF_ERROR(GetColorEncoding(ppf, &color_encoding));
|
||||
|
||||
ColorSpaceTransform c_transform(cms);
|
||||
ColorSpaceTransform c_transform(*JxlGetDefaultCms());
|
||||
ColorEncoding xyb_encoding;
|
||||
if (jpeg_settings.xyb) {
|
||||
if (ppf.info.num_color_channels != 3) {
|
||||
|
@ -343,8 +341,7 @@ Status EncodeJpeg(const PackedPixelFile& ppf, const JpegSettings& jpeg_settings,
|
|||
JXL_RETURN_IF_ERROR(
|
||||
c_transform.Init(color_encoding, c_desired, 255.0f, ppf.info.xsize, 1));
|
||||
xyb_encoding.SetColorSpace(jxl::ColorSpace::kXYB);
|
||||
JXL_RETURN_IF_ERROR(
|
||||
xyb_encoding.SetRenderingIntent(jxl::RenderingIntent::kPerceptual));
|
||||
xyb_encoding.SetRenderingIntent(jxl::RenderingIntent::kPerceptual);
|
||||
JXL_RETURN_IF_ERROR(xyb_encoding.CreateICC());
|
||||
}
|
||||
const ColorEncoding& output_encoding =
|
||||
|
|
|
@ -21,7 +21,7 @@ Status HlgOOTF(ImageBundle* ib, const float gamma, ThreadPool* pool) {
|
|||
linear_rec2020.SetColorSpace(ColorSpace::kRGB);
|
||||
JXL_RETURN_IF_ERROR(linear_rec2020.SetPrimariesType(Primaries::k2100));
|
||||
JXL_RETURN_IF_ERROR(linear_rec2020.SetWhitePointType(WhitePoint::kD65));
|
||||
linear_rec2020.tf.SetTransferFunction(TransferFunction::kLinear);
|
||||
linear_rec2020.Tf().SetTransferFunction(TransferFunction::kLinear);
|
||||
JXL_RETURN_IF_ERROR(linear_rec2020.CreateICC());
|
||||
JXL_RETURN_IF_ERROR(
|
||||
ib->TransformTo(linear_rec2020, *JxlGetDefaultCms(), pool));
|
||||
|
|
|
@ -82,7 +82,7 @@ Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
|
|||
ppf.info.exponent_bits_per_sample);
|
||||
}
|
||||
|
||||
const bool is_gray = ppf.info.num_color_channels == 1;
|
||||
const bool is_gray = (ppf.info.num_color_channels == 1);
|
||||
JXL_ASSERT(ppf.info.num_color_channels == 1 ||
|
||||
ppf.info.num_color_channels == 3);
|
||||
|
||||
|
@ -113,11 +113,15 @@ Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
|
|||
// Convert the color encoding.
|
||||
if (!ppf.icc.empty()) {
|
||||
IccBytes icc = ppf.icc;
|
||||
const JxlCmsInterface& cms = *JxlGetDefaultCms();
|
||||
if (!io->metadata.m.color_encoding.SetICC(std::move(icc), &cms)) {
|
||||
if (!io->metadata.m.color_encoding.SetICC(std::move(icc),
|
||||
JxlGetDefaultCms())) {
|
||||
fprintf(stderr, "Warning: error setting ICC profile, assuming SRGB\n");
|
||||
io->metadata.m.color_encoding = ColorEncoding::SRGB(is_gray);
|
||||
} else {
|
||||
if (io->metadata.m.color_encoding.IsCMYK()) {
|
||||
// We expect gray or tri-color.
|
||||
return JXL_FAILURE("Embedded ICC is CMYK");
|
||||
}
|
||||
if (io->metadata.m.color_encoding.IsGray() != is_gray) {
|
||||
// E.g. JPG image has 3 channels, but gray ICC.
|
||||
return JXL_FAILURE("Embedded ICC does not match image color type");
|
||||
|
|
|
@ -33,7 +33,7 @@ Status ToneMapFrame(const std::pair<float, float> display_nits,
|
|||
linear_rec2020.SetColorSpace(ColorSpace::kRGB);
|
||||
JXL_RETURN_IF_ERROR(linear_rec2020.SetPrimariesType(Primaries::k2100));
|
||||
JXL_RETURN_IF_ERROR(linear_rec2020.SetWhitePointType(WhitePoint::kD65));
|
||||
linear_rec2020.tf.SetTransferFunction(TransferFunction::kLinear);
|
||||
linear_rec2020.Tf().SetTransferFunction(TransferFunction::kLinear);
|
||||
JXL_RETURN_IF_ERROR(linear_rec2020.CreateICC());
|
||||
JXL_RETURN_IF_ERROR(
|
||||
ib->TransformTo(linear_rec2020, *JxlGetDefaultCms(), pool));
|
||||
|
@ -71,7 +71,7 @@ Status GamutMapFrame(ImageBundle* const ib, float preserve_saturation,
|
|||
linear_rec2020.SetColorSpace(ColorSpace::kRGB);
|
||||
JXL_RETURN_IF_ERROR(linear_rec2020.SetPrimariesType(Primaries::k2100));
|
||||
JXL_RETURN_IF_ERROR(linear_rec2020.SetWhitePointType(WhitePoint::kD65));
|
||||
linear_rec2020.tf.SetTransferFunction(TransferFunction::kLinear);
|
||||
linear_rec2020.Tf().SetTransferFunction(TransferFunction::kLinear);
|
||||
JXL_RETURN_IF_ERROR(linear_rec2020.CreateICC());
|
||||
JXL_RETURN_IF_ERROR(
|
||||
ib->TransformTo(linear_rec2020, *JxlGetDefaultCms(), pool));
|
||||
|
|
|
@ -19,7 +19,7 @@ static void BM_ToneMapping(benchmark::State& state) {
|
|||
linear_rec2020.SetColorSpace(ColorSpace::kRGB);
|
||||
JXL_CHECK(linear_rec2020.SetPrimariesType(Primaries::k2100));
|
||||
JXL_CHECK(linear_rec2020.SetWhitePointType(WhitePoint::kD65));
|
||||
linear_rec2020.tf.SetTransferFunction(TransferFunction::kLinear);
|
||||
linear_rec2020.Tf().SetTransferFunction(TransferFunction::kLinear);
|
||||
JXL_CHECK(linear_rec2020.CreateICC());
|
||||
|
||||
for (auto _ : state) {
|
||||
|
|
|
@ -6,14 +6,34 @@
|
|||
#ifndef LIB_JXL_CMS_COLOR_ENCODING_CMS_H_
|
||||
#define LIB_JXL_CMS_COLOR_ENCODING_CMS_H_
|
||||
|
||||
#include <jxl/cms_interface.h>
|
||||
#include <jxl/color_encoding.h>
|
||||
#include <jxl/types.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jxl/base/common.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/cms/color_management.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace cms {
|
||||
|
||||
using IccBytes = std::vector<uint8_t>;
|
||||
|
||||
// Returns whether the two inputs are approximately equal.
|
||||
static inline bool ApproxEq(const double a, const double b,
|
||||
double max_l1 = 1E-3) {
|
||||
// Threshold should be sufficient for ICC's 15-bit fixed-point numbers.
|
||||
// We have seen differences of 7.1E-5 with lcms2 and 1E-3 with skcms.
|
||||
return std::abs(a - b) <= max_l1;
|
||||
}
|
||||
|
||||
// (All CIE units are for the standard 1931 2 degree observer)
|
||||
|
||||
// Color space the color pixel data is encoded in. The color pixel data is
|
||||
|
@ -91,35 +111,220 @@ struct PrimariesCIExy {
|
|||
|
||||
// Serializable form of CIExy.
|
||||
struct Customxy {
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
static constexpr uint32_t kMul = 1000000;
|
||||
static constexpr double kRoughLimit = 4.0;
|
||||
static constexpr int32_t kMin = -0x200000;
|
||||
static constexpr int32_t kMax = 0x1FFFFF;
|
||||
|
||||
int32_t x = 0;
|
||||
int32_t y = 0;
|
||||
|
||||
CIExy GetValue() const {
|
||||
CIExy xy;
|
||||
xy.x = x * (1.0 / kMul);
|
||||
xy.y = y * (1.0 / kMul);
|
||||
return xy;
|
||||
}
|
||||
|
||||
Status SetValue(const CIExy& xy) {
|
||||
bool ok = (std::abs(xy.x) < kRoughLimit) && (std::abs(xy.y) < kRoughLimit);
|
||||
if (!ok) return JXL_FAILURE("X or Y is out of bounds");
|
||||
x = static_cast<int32_t>(roundf(xy.x * kMul));
|
||||
if (x < kMin || x > kMax) return JXL_FAILURE("X is out of bounds");
|
||||
y = static_cast<int32_t>(roundf(xy.y * kMul));
|
||||
if (y < kMin || y > kMax) return JXL_FAILURE("Y is out of bounds");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsSame(const Customxy& other) const {
|
||||
return (x == other.x) && (y == other.y);
|
||||
}
|
||||
};
|
||||
|
||||
static inline Status WhitePointFromExternal(const JxlWhitePoint external,
|
||||
WhitePoint* out) {
|
||||
switch (external) {
|
||||
case JXL_WHITE_POINT_D65:
|
||||
*out = WhitePoint::kD65;
|
||||
return true;
|
||||
case JXL_WHITE_POINT_CUSTOM:
|
||||
*out = WhitePoint::kCustom;
|
||||
return true;
|
||||
case JXL_WHITE_POINT_E:
|
||||
*out = WhitePoint::kE;
|
||||
return true;
|
||||
case JXL_WHITE_POINT_DCI:
|
||||
*out = WhitePoint::kDCI;
|
||||
return true;
|
||||
}
|
||||
return JXL_FAILURE("Invalid WhitePoint enum value %d",
|
||||
static_cast<int>(external));
|
||||
}
|
||||
|
||||
static inline Status PrimariesFromExternal(const JxlPrimaries external,
|
||||
Primaries* out) {
|
||||
switch (external) {
|
||||
case JXL_PRIMARIES_SRGB:
|
||||
*out = Primaries::kSRGB;
|
||||
return true;
|
||||
case JXL_PRIMARIES_CUSTOM:
|
||||
*out = Primaries::kCustom;
|
||||
return true;
|
||||
case JXL_PRIMARIES_2100:
|
||||
*out = Primaries::k2100;
|
||||
return true;
|
||||
case JXL_PRIMARIES_P3:
|
||||
*out = Primaries::kP3;
|
||||
return true;
|
||||
}
|
||||
return JXL_FAILURE("Invalid Primaries enum value");
|
||||
}
|
||||
|
||||
static inline Status RenderingIntentFromExternal(
|
||||
const JxlRenderingIntent external, RenderingIntent* out) {
|
||||
switch (external) {
|
||||
case JXL_RENDERING_INTENT_PERCEPTUAL:
|
||||
*out = RenderingIntent::kPerceptual;
|
||||
return true;
|
||||
case JXL_RENDERING_INTENT_RELATIVE:
|
||||
*out = RenderingIntent::kRelative;
|
||||
return true;
|
||||
case JXL_RENDERING_INTENT_SATURATION:
|
||||
*out = RenderingIntent::kSaturation;
|
||||
return true;
|
||||
case JXL_RENDERING_INTENT_ABSOLUTE:
|
||||
*out = RenderingIntent::kAbsolute;
|
||||
return true;
|
||||
}
|
||||
return JXL_FAILURE("Invalid RenderingIntent enum value");
|
||||
}
|
||||
|
||||
struct CustomTransferFunction {
|
||||
// Highest reasonable value for the gamma of a transfer curve.
|
||||
static constexpr uint32_t kMaxGamma = 8192;
|
||||
static constexpr uint32_t kGammaMul = 10000000;
|
||||
|
||||
bool have_gamma;
|
||||
bool have_gamma = false;
|
||||
|
||||
// OETF exponent to go from linear to gamma-compressed.
|
||||
uint32_t gamma; // Only used if have_gamma_.
|
||||
uint32_t gamma = 0; // Only used if have_gamma_.
|
||||
|
||||
// Can be kUnknown.
|
||||
TransferFunction transfer_function; // Only used if !have_gamma_.
|
||||
TransferFunction transfer_function =
|
||||
TransferFunction::kSRGB; // Only used if !have_gamma_.
|
||||
|
||||
TransferFunction GetTransferFunction() const {
|
||||
JXL_ASSERT(!have_gamma);
|
||||
return transfer_function;
|
||||
}
|
||||
void SetTransferFunction(const TransferFunction tf) {
|
||||
have_gamma = false;
|
||||
transfer_function = tf;
|
||||
}
|
||||
|
||||
bool IsUnknown() const {
|
||||
return !have_gamma && (transfer_function == TransferFunction::kUnknown);
|
||||
}
|
||||
bool IsSRGB() const {
|
||||
return !have_gamma && (transfer_function == TransferFunction::kSRGB);
|
||||
}
|
||||
bool IsLinear() const {
|
||||
return !have_gamma && (transfer_function == TransferFunction::kLinear);
|
||||
}
|
||||
bool IsPQ() const {
|
||||
return !have_gamma && (transfer_function == TransferFunction::kPQ);
|
||||
}
|
||||
bool IsHLG() const {
|
||||
return !have_gamma && (transfer_function == TransferFunction::kHLG);
|
||||
}
|
||||
bool Is709() const {
|
||||
return !have_gamma && (transfer_function == TransferFunction::k709);
|
||||
}
|
||||
bool IsDCI() const {
|
||||
return !have_gamma && (transfer_function == TransferFunction::kDCI);
|
||||
}
|
||||
|
||||
double GetGamma() const {
|
||||
JXL_ASSERT(have_gamma);
|
||||
return gamma * (1.0 / kGammaMul); // (0, 1)
|
||||
}
|
||||
Status SetGamma(double new_gamma) {
|
||||
if (new_gamma < (1.0 / kMaxGamma) || new_gamma > 1.0) {
|
||||
return JXL_FAILURE("Invalid gamma %f", new_gamma);
|
||||
}
|
||||
|
||||
have_gamma = false;
|
||||
if (ApproxEq(new_gamma, 1.0)) {
|
||||
transfer_function = TransferFunction::kLinear;
|
||||
return true;
|
||||
}
|
||||
if (ApproxEq(new_gamma, 1.0 / 2.6)) {
|
||||
transfer_function = TransferFunction::kDCI;
|
||||
return true;
|
||||
}
|
||||
// Don't translate 0.45.. to kSRGB nor k709 - that might change pixel
|
||||
// values because those curves also have a linear part.
|
||||
|
||||
have_gamma = true;
|
||||
gamma = roundf(new_gamma * kGammaMul);
|
||||
transfer_function = TransferFunction::kUnknown;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsSame(const CustomTransferFunction& other) const {
|
||||
if (have_gamma != other.have_gamma) {
|
||||
return false;
|
||||
}
|
||||
if (have_gamma) {
|
||||
if (gamma != other.gamma) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (transfer_function != other.transfer_function) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
static inline Status ConvertExternalToInternalTransferFunction(
|
||||
const JxlTransferFunction external, TransferFunction* internal) {
|
||||
switch (external) {
|
||||
case JXL_TRANSFER_FUNCTION_709:
|
||||
*internal = TransferFunction::k709;
|
||||
return true;
|
||||
case JXL_TRANSFER_FUNCTION_UNKNOWN:
|
||||
*internal = TransferFunction::kUnknown;
|
||||
return true;
|
||||
case JXL_TRANSFER_FUNCTION_LINEAR:
|
||||
*internal = TransferFunction::kLinear;
|
||||
return true;
|
||||
case JXL_TRANSFER_FUNCTION_SRGB:
|
||||
*internal = TransferFunction::kSRGB;
|
||||
return true;
|
||||
case JXL_TRANSFER_FUNCTION_PQ:
|
||||
*internal = TransferFunction::kPQ;
|
||||
return true;
|
||||
case JXL_TRANSFER_FUNCTION_DCI:
|
||||
*internal = TransferFunction::kDCI;
|
||||
return true;
|
||||
case JXL_TRANSFER_FUNCTION_HLG:
|
||||
*internal = TransferFunction::kHLG;
|
||||
return true;
|
||||
case JXL_TRANSFER_FUNCTION_GAMMA:
|
||||
return JXL_FAILURE("Gamma should be handled separately");
|
||||
}
|
||||
return JXL_FAILURE("Invalid TransferFunction enum value");
|
||||
}
|
||||
|
||||
// Compact encoding of data required to interpret and translate pixels to a
|
||||
// known color space. Stored in Metadata. Thread-compatible.
|
||||
struct ColorEncoding {
|
||||
// Only valid if HaveFields()
|
||||
WhitePoint white_point;
|
||||
Primaries primaries; // Only valid if HasPrimaries()
|
||||
RenderingIntent rendering_intent;
|
||||
|
||||
// If true, the codestream contains an ICC profile and we do not serialize
|
||||
// fields. Otherwise, fields are serialized and we create an ICC profile.
|
||||
bool want_icc;
|
||||
WhitePoint white_point = WhitePoint::kD65;
|
||||
Primaries primaries = Primaries::kSRGB; // Only valid if HasPrimaries()
|
||||
RenderingIntent rendering_intent = RenderingIntent::kRelative;
|
||||
|
||||
// When false, fields such as white_point and tf are invalid and must not be
|
||||
// used. This occurs after setting a raw bytes-only ICC profile, only the
|
||||
|
@ -128,7 +333,7 @@ struct ColorEncoding {
|
|||
|
||||
IccBytes icc; // Valid ICC profile
|
||||
|
||||
ColorSpace color_space; // Can be kUnknown
|
||||
ColorSpace color_space = ColorSpace::kRGB; // Can be kUnknown
|
||||
bool cmyk = false;
|
||||
|
||||
// "late sync" fields
|
||||
|
@ -137,8 +342,430 @@ struct ColorEncoding {
|
|||
Customxy red; // Only used if primaries == kCustom
|
||||
Customxy green; // Only used if primaries == kCustom
|
||||
Customxy blue; // Only used if primaries == kCustom
|
||||
|
||||
// Returns false if the field is invalid and unusable.
|
||||
bool HasPrimaries() const {
|
||||
return (color_space != ColorSpace::kGray) &&
|
||||
(color_space != ColorSpace::kXYB);
|
||||
}
|
||||
|
||||
size_t Channels() const { return (color_space == ColorSpace::kGray) ? 1 : 3; }
|
||||
|
||||
PrimariesCIExy GetPrimaries() const {
|
||||
JXL_DASSERT(have_fields);
|
||||
JXL_ASSERT(HasPrimaries());
|
||||
PrimariesCIExy xy;
|
||||
switch (primaries) {
|
||||
case Primaries::kCustom:
|
||||
xy.r = red.GetValue();
|
||||
xy.g = green.GetValue();
|
||||
xy.b = blue.GetValue();
|
||||
return xy;
|
||||
|
||||
case Primaries::kSRGB:
|
||||
xy.r.x = 0.639998686;
|
||||
xy.r.y = 0.330010138;
|
||||
xy.g.x = 0.300003784;
|
||||
xy.g.y = 0.600003357;
|
||||
xy.b.x = 0.150002046;
|
||||
xy.b.y = 0.059997204;
|
||||
return xy;
|
||||
|
||||
case Primaries::k2100:
|
||||
xy.r.x = 0.708;
|
||||
xy.r.y = 0.292;
|
||||
xy.g.x = 0.170;
|
||||
xy.g.y = 0.797;
|
||||
xy.b.x = 0.131;
|
||||
xy.b.y = 0.046;
|
||||
return xy;
|
||||
|
||||
case Primaries::kP3:
|
||||
xy.r.x = 0.680;
|
||||
xy.r.y = 0.320;
|
||||
xy.g.x = 0.265;
|
||||
xy.g.y = 0.690;
|
||||
xy.b.x = 0.150;
|
||||
xy.b.y = 0.060;
|
||||
return xy;
|
||||
}
|
||||
JXL_UNREACHABLE("Invalid Primaries %u", static_cast<uint32_t>(primaries));
|
||||
}
|
||||
|
||||
Status SetPrimaries(const PrimariesCIExy& xy) {
|
||||
JXL_DASSERT(have_fields);
|
||||
JXL_ASSERT(HasPrimaries());
|
||||
if (xy.r.x == 0.0 || xy.r.y == 0.0 || xy.g.x == 0.0 || xy.g.y == 0.0 ||
|
||||
xy.b.x == 0.0 || xy.b.y == 0.0) {
|
||||
return JXL_FAILURE("Invalid primaries %f %f %f %f %f %f", xy.r.x, xy.r.y,
|
||||
xy.g.x, xy.g.y, xy.b.x, xy.b.y);
|
||||
}
|
||||
|
||||
if (ApproxEq(xy.r.x, 0.64) && ApproxEq(xy.r.y, 0.33) &&
|
||||
ApproxEq(xy.g.x, 0.30) && ApproxEq(xy.g.y, 0.60) &&
|
||||
ApproxEq(xy.b.x, 0.15) && ApproxEq(xy.b.y, 0.06)) {
|
||||
primaries = Primaries::kSRGB;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ApproxEq(xy.r.x, 0.708) && ApproxEq(xy.r.y, 0.292) &&
|
||||
ApproxEq(xy.g.x, 0.170) && ApproxEq(xy.g.y, 0.797) &&
|
||||
ApproxEq(xy.b.x, 0.131) && ApproxEq(xy.b.y, 0.046)) {
|
||||
primaries = Primaries::k2100;
|
||||
return true;
|
||||
}
|
||||
if (ApproxEq(xy.r.x, 0.680) && ApproxEq(xy.r.y, 0.320) &&
|
||||
ApproxEq(xy.g.x, 0.265) && ApproxEq(xy.g.y, 0.690) &&
|
||||
ApproxEq(xy.b.x, 0.150) && ApproxEq(xy.b.y, 0.060)) {
|
||||
primaries = Primaries::kP3;
|
||||
return true;
|
||||
}
|
||||
|
||||
primaries = Primaries::kCustom;
|
||||
JXL_RETURN_IF_ERROR(red.SetValue(xy.r));
|
||||
JXL_RETURN_IF_ERROR(green.SetValue(xy.g));
|
||||
JXL_RETURN_IF_ERROR(blue.SetValue(xy.b));
|
||||
return true;
|
||||
}
|
||||
|
||||
CIExy GetWhitePoint() const {
|
||||
JXL_DASSERT(have_fields);
|
||||
CIExy xy;
|
||||
switch (white_point) {
|
||||
case WhitePoint::kCustom:
|
||||
return white.GetValue();
|
||||
|
||||
case WhitePoint::kD65:
|
||||
xy.x = 0.3127;
|
||||
xy.y = 0.3290;
|
||||
return xy;
|
||||
|
||||
case WhitePoint::kDCI:
|
||||
// From https://ieeexplore.ieee.org/document/7290729 C.2 page 11
|
||||
xy.x = 0.314;
|
||||
xy.y = 0.351;
|
||||
return xy;
|
||||
|
||||
case WhitePoint::kE:
|
||||
xy.x = xy.y = 1.0 / 3;
|
||||
return xy;
|
||||
}
|
||||
JXL_UNREACHABLE("Invalid WhitePoint %u",
|
||||
static_cast<uint32_t>(white_point));
|
||||
}
|
||||
|
||||
Status SetWhitePoint(const CIExy& xy) {
|
||||
JXL_DASSERT(have_fields);
|
||||
if (xy.x == 0.0 || xy.y == 0.0) {
|
||||
return JXL_FAILURE("Invalid white point %f %f", xy.x, xy.y);
|
||||
}
|
||||
if (ApproxEq(xy.x, 0.3127) && ApproxEq(xy.y, 0.3290)) {
|
||||
white_point = WhitePoint::kD65;
|
||||
return true;
|
||||
}
|
||||
if (ApproxEq(xy.x, 1.0 / 3) && ApproxEq(xy.y, 1.0 / 3)) {
|
||||
white_point = WhitePoint::kE;
|
||||
return true;
|
||||
}
|
||||
if (ApproxEq(xy.x, 0.314) && ApproxEq(xy.y, 0.351)) {
|
||||
white_point = WhitePoint::kDCI;
|
||||
return true;
|
||||
}
|
||||
white_point = WhitePoint::kCustom;
|
||||
return white.SetValue(xy);
|
||||
}
|
||||
|
||||
// Checks if the color spaces (including white point / primaries) are the
|
||||
// same, but ignores the transfer function, rendering intent and ICC bytes.
|
||||
bool SameColorSpace(const ColorEncoding& other) const {
|
||||
if (color_space != other.color_space) return false;
|
||||
|
||||
if (white_point != other.white_point) return false;
|
||||
if (white_point == WhitePoint::kCustom) {
|
||||
if (!white.IsSame(other.white)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (HasPrimaries() != other.HasPrimaries()) return false;
|
||||
if (HasPrimaries()) {
|
||||
if (primaries != other.primaries) return false;
|
||||
if (primaries == Primaries::kCustom) {
|
||||
if (!red.IsSame(other.red)) return false;
|
||||
if (!green.IsSame(other.green)) return false;
|
||||
if (!blue.IsSame(other.blue)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Checks if the color space and transfer function are the same, ignoring
|
||||
// rendering intent and ICC bytes
|
||||
bool SameColorEncoding(const ColorEncoding& other) const {
|
||||
return SameColorSpace(other) && tf.IsSame(other.tf);
|
||||
}
|
||||
|
||||
Status CreateICC() {
|
||||
icc.clear();
|
||||
return MaybeCreateProfile(*this, &icc);
|
||||
}
|
||||
|
||||
// Returns true if all fields have been initialized (possibly to kUnknown).
|
||||
// Returns false if the ICC profile is invalid or decoding it fails.
|
||||
Status SetFieldsFromICC(IccBytes&& new_icc, const JxlCmsInterface& cms) {
|
||||
// TODO(eustas): take icc
|
||||
// TODO(eustas): clean icc on error
|
||||
// In case parsing fails, mark the ColorEncoding as invalid.
|
||||
JXL_ASSERT(!new_icc.empty());
|
||||
color_space = ColorSpace::kUnknown;
|
||||
tf.transfer_function = TransferFunction::kUnknown;
|
||||
icc.clear();
|
||||
|
||||
JxlColorEncoding external;
|
||||
JXL_BOOL new_cmyk;
|
||||
JXL_RETURN_IF_ERROR(cms.set_fields_from_icc(cms.set_fields_data,
|
||||
new_icc.data(), new_icc.size(),
|
||||
&external, &new_cmyk));
|
||||
cmyk = new_cmyk;
|
||||
if (cmyk) return true;
|
||||
JXL_RETURN_IF_ERROR(FromExternal(external));
|
||||
icc = std::move(new_icc);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ToExternal(JxlColorEncoding* external) const {
|
||||
// TODO(eustas): update copy/update storage and call .ToExternal on it
|
||||
if (!have_fields) {
|
||||
external->color_space = JXL_COLOR_SPACE_UNKNOWN;
|
||||
external->primaries = JXL_PRIMARIES_CUSTOM;
|
||||
external->rendering_intent = JXL_RENDERING_INTENT_PERCEPTUAL; //?
|
||||
external->transfer_function = JXL_TRANSFER_FUNCTION_UNKNOWN;
|
||||
external->white_point = JXL_WHITE_POINT_CUSTOM;
|
||||
return;
|
||||
}
|
||||
external->color_space = static_cast<JxlColorSpace>(color_space);
|
||||
|
||||
external->white_point = static_cast<JxlWhitePoint>(white_point);
|
||||
|
||||
CIExy wp = GetWhitePoint();
|
||||
external->white_point_xy[0] = wp.x;
|
||||
external->white_point_xy[1] = wp.y;
|
||||
|
||||
if (external->color_space == JXL_COLOR_SPACE_RGB ||
|
||||
external->color_space == JXL_COLOR_SPACE_UNKNOWN) {
|
||||
external->primaries = static_cast<JxlPrimaries>(primaries);
|
||||
PrimariesCIExy p = GetPrimaries();
|
||||
external->primaries_red_xy[0] = p.r.x;
|
||||
external->primaries_red_xy[1] = p.r.y;
|
||||
external->primaries_green_xy[0] = p.g.x;
|
||||
external->primaries_green_xy[1] = p.g.y;
|
||||
external->primaries_blue_xy[0] = p.b.x;
|
||||
external->primaries_blue_xy[1] = p.b.y;
|
||||
}
|
||||
|
||||
if (tf.have_gamma) {
|
||||
external->transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
|
||||
external->gamma = tf.GetGamma();
|
||||
} else {
|
||||
external->transfer_function =
|
||||
static_cast<JxlTransferFunction>(tf.GetTransferFunction());
|
||||
external->gamma = 0;
|
||||
}
|
||||
|
||||
external->rendering_intent =
|
||||
static_cast<JxlRenderingIntent>(rendering_intent);
|
||||
}
|
||||
|
||||
Status FromExternal(const JxlColorEncoding& external) {
|
||||
// TODO(eustas): update non-serializable on call-site
|
||||
color_space = static_cast<ColorSpace>(external.color_space);
|
||||
|
||||
JXL_RETURN_IF_ERROR(
|
||||
WhitePointFromExternal(external.white_point, &white_point));
|
||||
if (external.white_point == JXL_WHITE_POINT_CUSTOM) {
|
||||
CIExy wp;
|
||||
wp.x = external.white_point_xy[0];
|
||||
wp.y = external.white_point_xy[1];
|
||||
JXL_RETURN_IF_ERROR(SetWhitePoint(wp));
|
||||
}
|
||||
|
||||
if (external.color_space == JXL_COLOR_SPACE_RGB ||
|
||||
external.color_space == JXL_COLOR_SPACE_UNKNOWN) {
|
||||
JXL_RETURN_IF_ERROR(
|
||||
PrimariesFromExternal(external.primaries, &primaries));
|
||||
if (external.primaries == JXL_PRIMARIES_CUSTOM) {
|
||||
PrimariesCIExy primaries;
|
||||
primaries.r.x = external.primaries_red_xy[0];
|
||||
primaries.r.y = external.primaries_red_xy[1];
|
||||
primaries.g.x = external.primaries_green_xy[0];
|
||||
primaries.g.y = external.primaries_green_xy[1];
|
||||
primaries.b.x = external.primaries_blue_xy[0];
|
||||
primaries.b.y = external.primaries_blue_xy[1];
|
||||
JXL_RETURN_IF_ERROR(SetPrimaries(primaries));
|
||||
}
|
||||
}
|
||||
CustomTransferFunction tf;
|
||||
if (external.transfer_function == JXL_TRANSFER_FUNCTION_GAMMA) {
|
||||
JXL_RETURN_IF_ERROR(tf.SetGamma(external.gamma));
|
||||
} else {
|
||||
TransferFunction tf_enum;
|
||||
// JXL_TRANSFER_FUNCTION_GAMMA is not handled by this function since
|
||||
// there's no internal enum value for it.
|
||||
JXL_RETURN_IF_ERROR(ConvertExternalToInternalTransferFunction(
|
||||
external.transfer_function, &tf_enum));
|
||||
tf.SetTransferFunction(tf_enum);
|
||||
}
|
||||
this->tf = tf;
|
||||
|
||||
JXL_RETURN_IF_ERROR(RenderingIntentFromExternal(external.rendering_intent,
|
||||
&rendering_intent));
|
||||
|
||||
// The ColorEncoding caches an ICC profile it created earlier that may no
|
||||
// longer match the profile with the changed fields, so re-create it.
|
||||
if (!(CreateICC())) {
|
||||
// This is not an error: for example, it doesn't have ICC profile creation
|
||||
// implemented for XYB. This should not be returned as error, since
|
||||
// FromExternal still worked correctly, and what
|
||||
// matters is that internal->ICC() will not return the wrong profile.
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// These strings are baked into Description - do not change.
|
||||
|
||||
static inline std::string ToString(ColorSpace color_space) {
|
||||
switch (color_space) {
|
||||
case ColorSpace::kRGB:
|
||||
return "RGB";
|
||||
case ColorSpace::kGray:
|
||||
return "Gra";
|
||||
case ColorSpace::kXYB:
|
||||
return "XYB";
|
||||
case ColorSpace::kUnknown:
|
||||
return "CS?";
|
||||
}
|
||||
// Should not happen - visitor fails if enum is invalid.
|
||||
JXL_UNREACHABLE("Invalid ColorSpace %u", static_cast<uint32_t>(color_space));
|
||||
}
|
||||
|
||||
static inline std::string ToString(WhitePoint white_point) {
|
||||
switch (white_point) {
|
||||
case WhitePoint::kD65:
|
||||
return "D65";
|
||||
case WhitePoint::kCustom:
|
||||
return "Cst";
|
||||
case WhitePoint::kE:
|
||||
return "EER";
|
||||
case WhitePoint::kDCI:
|
||||
return "DCI";
|
||||
}
|
||||
// Should not happen - visitor fails if enum is invalid.
|
||||
JXL_UNREACHABLE("Invalid WhitePoint %u", static_cast<uint32_t>(white_point));
|
||||
}
|
||||
|
||||
static inline std::string ToString(Primaries primaries) {
|
||||
switch (primaries) {
|
||||
case Primaries::kSRGB:
|
||||
return "SRG";
|
||||
case Primaries::k2100:
|
||||
return "202";
|
||||
case Primaries::kP3:
|
||||
return "DCI";
|
||||
case Primaries::kCustom:
|
||||
return "Cst";
|
||||
}
|
||||
// Should not happen - visitor fails if enum is invalid.
|
||||
JXL_UNREACHABLE("Invalid Primaries %u", static_cast<uint32_t>(primaries));
|
||||
}
|
||||
|
||||
static inline std::string ToString(TransferFunction transfer_function) {
|
||||
switch (transfer_function) {
|
||||
case TransferFunction::kSRGB:
|
||||
return "SRG";
|
||||
case TransferFunction::kLinear:
|
||||
return "Lin";
|
||||
case TransferFunction::k709:
|
||||
return "709";
|
||||
case TransferFunction::kPQ:
|
||||
return "PeQ";
|
||||
case TransferFunction::kHLG:
|
||||
return "HLG";
|
||||
case TransferFunction::kDCI:
|
||||
return "DCI";
|
||||
case TransferFunction::kUnknown:
|
||||
return "TF?";
|
||||
}
|
||||
// Should not happen - visitor fails if enum is invalid.
|
||||
JXL_UNREACHABLE("Invalid TransferFunction %u",
|
||||
static_cast<uint32_t>(transfer_function));
|
||||
}
|
||||
|
||||
static inline std::string ToString(RenderingIntent rendering_intent) {
|
||||
switch (rendering_intent) {
|
||||
case RenderingIntent::kPerceptual:
|
||||
return "Per";
|
||||
case RenderingIntent::kRelative:
|
||||
return "Rel";
|
||||
case RenderingIntent::kSaturation:
|
||||
return "Sat";
|
||||
case RenderingIntent::kAbsolute:
|
||||
return "Abs";
|
||||
}
|
||||
// Should not happen - visitor fails if enum is invalid.
|
||||
JXL_UNREACHABLE("Invalid RenderingIntent %u",
|
||||
static_cast<uint32_t>(rendering_intent));
|
||||
}
|
||||
|
||||
// Returns a representation of the ColorEncoding fields (not icc).
|
||||
// Example description: "RGB_D65_SRG_Rel_Lin"
|
||||
static inline std::string Description(const ColorEncoding& c) {
|
||||
std::string d = ToString(c.color_space);
|
||||
|
||||
bool explicit_wp_tf = (c.color_space != ColorSpace::kXYB);
|
||||
if (explicit_wp_tf) {
|
||||
d += '_';
|
||||
if (c.white_point == WhitePoint::kCustom) {
|
||||
const CIExy wp = c.GetWhitePoint();
|
||||
d += jxl::ToString(wp.x) + ';';
|
||||
d += jxl::ToString(wp.y);
|
||||
} else {
|
||||
d += ToString(c.white_point);
|
||||
}
|
||||
}
|
||||
|
||||
if (c.HasPrimaries()) {
|
||||
d += '_';
|
||||
if (c.primaries == Primaries::kCustom) {
|
||||
const PrimariesCIExy pr = c.GetPrimaries();
|
||||
d += jxl::ToString(pr.r.x) + ';';
|
||||
d += jxl::ToString(pr.r.y) + ';';
|
||||
d += jxl::ToString(pr.g.x) + ';';
|
||||
d += jxl::ToString(pr.g.y) + ';';
|
||||
d += jxl::ToString(pr.b.x) + ';';
|
||||
d += jxl::ToString(pr.b.y);
|
||||
} else {
|
||||
d += ToString(c.primaries);
|
||||
}
|
||||
}
|
||||
|
||||
d += '_';
|
||||
d += ToString(c.rendering_intent);
|
||||
|
||||
if (explicit_wp_tf) {
|
||||
d += '_';
|
||||
if (c.tf.have_gamma) {
|
||||
d += 'g';
|
||||
d += jxl::ToString(c.tf.GetGamma());
|
||||
} else {
|
||||
d += ToString(c.tf.transfer_function);
|
||||
}
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
} // namespace cms
|
||||
} // namespace jxl
|
||||
|
||||
|
|
|
@ -12,6 +12,18 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jxl/cms/color_encoding_cms.h"
|
||||
|
||||
using jxl::cms::CIExy;
|
||||
using jxl::cms::ColorEncoding;
|
||||
using jxl::cms::ColorSpace;
|
||||
using jxl::cms::IccBytes;
|
||||
using jxl::cms::Primaries;
|
||||
using jxl::cms::PrimariesCIExy;
|
||||
using jxl::cms::RenderingIntent;
|
||||
using jxl::cms::TransferFunction;
|
||||
using jxl::cms::WhitePoint;
|
||||
|
||||
#undef HWY_TARGET_INCLUDE
|
||||
#define HWY_TARGET_INCLUDE "lib/jxl/cms/color_management.cc"
|
||||
#include <hwy/foreach_target.h>
|
||||
|
@ -35,12 +47,11 @@ namespace HWY_NAMESPACE {
|
|||
|
||||
Status ToneMapPixel(const ColorEncoding& c, const float in[3],
|
||||
uint8_t pcslab_out[3]) {
|
||||
const PrimariesCIExy primaries = c.GetPrimaries();
|
||||
const CIExy white_point = c.GetWhitePoint();
|
||||
const PrimariesCIExy p = c.GetPrimaries();
|
||||
const CIExy wp = c.GetWhitePoint();
|
||||
float primaries_XYZ[9];
|
||||
JXL_RETURN_IF_ERROR(PrimariesToXYZ(
|
||||
primaries.r.x, primaries.r.y, primaries.g.x, primaries.g.y, primaries.b.x,
|
||||
primaries.b.y, white_point.x, white_point.y, primaries_XYZ));
|
||||
JXL_RETURN_IF_ERROR(PrimariesToXYZ(p.r.x, p.r.y, p.g.x, p.g.y, p.b.x, p.b.y,
|
||||
wp.x, wp.y, primaries_XYZ));
|
||||
const float luminances[3] = {primaries_XYZ[3], primaries_XYZ[4],
|
||||
primaries_XYZ[5]};
|
||||
float linear[3];
|
||||
|
@ -71,7 +82,7 @@ Status ToneMapPixel(const ColorEncoding& c, const float in[3],
|
|||
StoreU(b, d, &linear[2]);
|
||||
|
||||
float chad[9];
|
||||
JXL_RETURN_IF_ERROR(AdaptToXYZD50(white_point.x, white_point.y, chad));
|
||||
JXL_RETURN_IF_ERROR(AdaptToXYZD50(wp.x, wp.y, chad));
|
||||
float to_xyzd50[9];
|
||||
Mul3x3Matrix(chad, primaries_XYZ, to_xyzd50);
|
||||
|
||||
|
@ -176,14 +187,13 @@ bool CanToneMap(const ColorEncoding& 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.
|
||||
return encoding.GetColorSpace() == ColorSpace::kRGB &&
|
||||
encoding.HasPrimaries() &&
|
||||
return encoding.color_space == ColorSpace::kRGB && encoding.HasPrimaries() &&
|
||||
(encoding.tf.IsPQ() || encoding.tf.IsHLG()) &&
|
||||
((encoding.GetPrimariesType() == Primaries::kP3 &&
|
||||
(encoding.GetWhitePointType() == WhitePoint::kD65 ||
|
||||
encoding.GetWhitePointType() == WhitePoint::kDCI)) ||
|
||||
(encoding.GetPrimariesType() != Primaries::kCustom &&
|
||||
encoding.GetWhitePointType() == WhitePoint::kD65));
|
||||
((encoding.primaries == Primaries::kP3 &&
|
||||
(encoding.white_point == WhitePoint::kD65 ||
|
||||
encoding.white_point == WhitePoint::kDCI)) ||
|
||||
(encoding.primaries != Primaries::kCustom &&
|
||||
encoding.white_point == WhitePoint::kD65));
|
||||
}
|
||||
|
||||
void ICCComputeMD5(const IccBytes& data, uint8_t sum[16])
|
||||
|
@ -335,9 +345,9 @@ Status CreateICCHeader(const ColorEncoding& c, IccBytes* JXL_RESTRICT header) {
|
|||
WriteICCTag(kCmm, 4, header);
|
||||
WriteICCUint32(0x04400000u, 8, header);
|
||||
const char* profile_type =
|
||||
c.GetColorSpace() == ColorSpace::kXYB ? "scnr" : "mntr";
|
||||
c.color_space == ColorSpace::kXYB ? "scnr" : "mntr";
|
||||
WriteICCTag(profile_type, 12, header);
|
||||
WriteICCTag(c.IsGray() ? "GRAY" : "RGB ", 16, header);
|
||||
WriteICCTag(c.color_space == ColorSpace::kGray ? "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
|
||||
|
@ -366,7 +376,7 @@ Status CreateICCHeader(const ColorEncoding& c, IccBytes* JXL_RESTRICT header) {
|
|||
WriteICCUint32(0, 52, header); // device model
|
||||
WriteICCUint32(0, 56, header); // device attributes
|
||||
WriteICCUint32(0, 60, header); // device attributes
|
||||
WriteICCUint32(static_cast<uint32_t>(c.GetRenderingIntent()), 64, header);
|
||||
WriteICCUint32(static_cast<uint32_t>(c.rendering_intent), 64, header);
|
||||
|
||||
// Mandatory D50 white point of profile connection space
|
||||
WriteICCUint32(0x0000f6d6, 68, header);
|
||||
|
@ -434,25 +444,25 @@ void MaybeCreateICCCICPTag(const ColorEncoding& c, IccBytes* JXL_RESTRICT tags,
|
|||
size_t* offset, size_t* size,
|
||||
IccBytes* JXL_RESTRICT tagtable,
|
||||
std::vector<size_t>* offsets) {
|
||||
if (c.GetColorSpace() != ColorSpace::kRGB) {
|
||||
if (c.color_space != ColorSpace::kRGB) {
|
||||
return;
|
||||
}
|
||||
uint8_t primaries = 0;
|
||||
if (c.GetPrimariesType() == Primaries::kP3) {
|
||||
if (c.GetWhitePointType() == WhitePoint::kD65) {
|
||||
if (c.primaries == Primaries::kP3) {
|
||||
if (c.white_point == WhitePoint::kD65) {
|
||||
primaries = 12;
|
||||
} else if (c.GetWhitePointType() == WhitePoint::kDCI) {
|
||||
} else if (c.white_point == WhitePoint::kDCI) {
|
||||
primaries = 11;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if (c.GetPrimariesType() != Primaries::kCustom &&
|
||||
c.GetWhitePointType() == WhitePoint::kD65) {
|
||||
primaries = static_cast<uint8_t>(c.GetPrimariesType());
|
||||
} else if (c.primaries != Primaries::kCustom &&
|
||||
c.white_point == WhitePoint::kD65) {
|
||||
primaries = static_cast<uint8_t>(c.primaries);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if (c.tf.IsUnknown() || c.tf.IsGamma()) {
|
||||
if (c.tf.IsUnknown() || c.tf.have_gamma) {
|
||||
return;
|
||||
}
|
||||
WriteICCTag("cicp", tags->size(), tags);
|
||||
|
@ -662,25 +672,112 @@ Status CreateICCNoOpBToATag(IccBytes* JXL_RESTRICT tags) {
|
|||
|
||||
} // namespace
|
||||
|
||||
Status PrimariesToXYZ(float rx, float ry, float gx, float gy, float bx,
|
||||
float by, float wx, float wy, float matrix[9]) {
|
||||
bool ok = (wx >= 0) && (wx <= 1) && (wy > 0) && (wy <= 1);
|
||||
if (!ok) {
|
||||
return JXL_FAILURE("Invalid white point");
|
||||
}
|
||||
// TODO(lode): also require rx, ry, gx, gy, bx, to be in range 0-1? ICC
|
||||
// profiles in theory forbid negative XYZ values, but in practice the ACES P0
|
||||
// color space uses a negative y for the blue primary.
|
||||
float primaries[9] = {
|
||||
rx, gx, bx, ry, gy, by, 1.0f - rx - ry, 1.0f - gx - gy, 1.0f - bx - by};
|
||||
float primaries_inv[9];
|
||||
memcpy(primaries_inv, primaries, sizeof(float) * 9);
|
||||
JXL_RETURN_IF_ERROR(Inv3x3Matrix(primaries_inv));
|
||||
|
||||
float w[3] = {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]));
|
||||
float xyz[3];
|
||||
Mul3x3Vector(primaries_inv, w, xyz);
|
||||
|
||||
float a[9] = {
|
||||
xyz[0], 0, 0, 0, xyz[1], 0, 0, 0, xyz[2],
|
||||
};
|
||||
|
||||
Mul3x3Matrix(primaries, a, matrix);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Chromatic adaptation matrices*/
|
||||
constexpr float kBradford[9] = {
|
||||
0.8951f, 0.2664f, -0.1614f, -0.7502f, 1.7135f,
|
||||
0.0367f, 0.0389f, -0.0685f, 1.0296f,
|
||||
};
|
||||
constexpr float kBradfordInv[9] = {
|
||||
0.9869929f, -0.1470543f, 0.1599627f, 0.4323053f, 0.5183603f,
|
||||
0.0492912f, -0.0085287f, 0.0400428f, 0.9684867f,
|
||||
};
|
||||
|
||||
// Adapts whitepoint x, y to D50
|
||||
Status AdaptToXYZD50(float wx, float wy, float matrix[9]) {
|
||||
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");
|
||||
}
|
||||
float w[3] = {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]));
|
||||
float w50[3] = {0.96422f, 1.0f, 0.82521f};
|
||||
|
||||
float lms[3];
|
||||
float lms50[3];
|
||||
|
||||
Mul3x3Vector(kBradford, w, lms);
|
||||
Mul3x3Vector(kBradford, w50, lms50);
|
||||
|
||||
if (lms[0] == 0 || lms[1] == 0 || lms[2] == 0) {
|
||||
return JXL_FAILURE("Invalid white point");
|
||||
}
|
||||
float a[9] = {
|
||||
// /----> 0, 1, 2, 3, /----> 4, 5, 6, 7, /----> 8,
|
||||
lms50[0] / lms[0], 0, 0, 0, lms50[1] / lms[1], 0, 0, 0, lms50[2] / lms[2],
|
||||
};
|
||||
if (!std::isfinite(a[0]) || !std::isfinite(a[4]) || !std::isfinite(a[8])) {
|
||||
return JXL_FAILURE("Invalid white point");
|
||||
}
|
||||
|
||||
float b[9];
|
||||
Mul3x3Matrix(a, kBradford, b);
|
||||
Mul3x3Matrix(kBradfordInv, b, matrix);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Status PrimariesToXYZD50(float rx, float ry, float gx, float gy, float bx,
|
||||
float by, float wx, float wy, float matrix[9]) {
|
||||
float toXYZ[9];
|
||||
JXL_RETURN_IF_ERROR(PrimariesToXYZ(rx, ry, gx, gy, bx, by, wx, wy, toXYZ));
|
||||
float d50[9];
|
||||
JXL_RETURN_IF_ERROR(AdaptToXYZD50(wx, wy, d50));
|
||||
|
||||
Mul3x3Matrix(d50, toXYZ, matrix);
|
||||
return true;
|
||||
}
|
||||
|
||||
Status MaybeCreateProfile(const ColorEncoding& c, IccBytes* JXL_RESTRICT icc) {
|
||||
IccBytes header, tagtable, tags;
|
||||
|
||||
if (c.GetColorSpace() == ColorSpace::kUnknown || c.tf.IsUnknown()) {
|
||||
if (c.color_space == ColorSpace::kUnknown || c.tf.IsUnknown()) {
|
||||
return false; // Not an error
|
||||
}
|
||||
|
||||
switch (c.GetColorSpace()) {
|
||||
switch (c.color_space) {
|
||||
case ColorSpace::kRGB:
|
||||
case ColorSpace::kGray:
|
||||
case ColorSpace::kXYB:
|
||||
break; // OK
|
||||
default:
|
||||
return JXL_FAILURE("Invalid CS %u",
|
||||
static_cast<unsigned int>(c.GetColorSpace()));
|
||||
static_cast<unsigned int>(c.color_space));
|
||||
}
|
||||
|
||||
if (c.GetColorSpace() == ColorSpace::kXYB &&
|
||||
c.GetRenderingIntent() != RenderingIntent::kPerceptual) {
|
||||
if (c.color_space == ColorSpace::kXYB &&
|
||||
c.rendering_intent != RenderingIntent::kPerceptual) {
|
||||
return JXL_FAILURE(
|
||||
"Only perceptual rendering intent implemented for XYB "
|
||||
"ICC profile.");
|
||||
|
@ -704,7 +801,7 @@ Status MaybeCreateProfile(const ColorEncoding& c, IccBytes* JXL_RESTRICT icc) {
|
|||
AddToICCTagTable("cprt", tag_offset, tag_size, &tagtable, &offsets);
|
||||
|
||||
// TODO(eustas): isn't it the other way round: gray image has d50 WhitePoint?
|
||||
if (c.IsGray()) {
|
||||
if (c.color_space == ColorSpace::kGray) {
|
||||
float wtpt[3];
|
||||
JXL_RETURN_IF_ERROR(CIEXYZFromWhiteCIExy(c.GetWhitePoint(), wtpt));
|
||||
JXL_RETURN_IF_ERROR(CreateICCXYZTag(wtpt, &tags));
|
||||
|
@ -715,7 +812,7 @@ Status MaybeCreateProfile(const ColorEncoding& c, IccBytes* JXL_RESTRICT icc) {
|
|||
FinalizeICCTag(&tags, &tag_offset, &tag_size);
|
||||
AddToICCTagTable("wtpt", tag_offset, tag_size, &tagtable, &offsets);
|
||||
|
||||
if (!c.IsGray()) {
|
||||
if (c.color_space != ColorSpace::kGray) {
|
||||
// Chromatic adaptation matrix
|
||||
float chad[9];
|
||||
JXL_RETURN_IF_ERROR(CreateICCChadMatrix(c.GetWhitePoint(), chad));
|
||||
|
@ -725,7 +822,7 @@ Status MaybeCreateProfile(const ColorEncoding& c, IccBytes* JXL_RESTRICT icc) {
|
|||
AddToICCTagTable("chad", tag_offset, tag_size, &tagtable, &offsets);
|
||||
}
|
||||
|
||||
if (c.GetColorSpace() == ColorSpace::kRGB) {
|
||||
if (c.color_space == ColorSpace::kRGB) {
|
||||
MaybeCreateICCCICPTag(c, &tags, &tag_offset, &tag_size, &tagtable,
|
||||
&offsets);
|
||||
|
||||
|
@ -750,7 +847,7 @@ Status MaybeCreateProfile(const ColorEncoding& c, IccBytes* JXL_RESTRICT icc) {
|
|||
AddToICCTagTable("bXYZ", tag_offset, tag_size, &tagtable, &offsets);
|
||||
}
|
||||
|
||||
if (c.GetColorSpace() == ColorSpace::kXYB) {
|
||||
if (c.color_space == ColorSpace::kXYB) {
|
||||
JXL_RETURN_IF_ERROR(CreateICCLutAtoBTagForXYB(&tags));
|
||||
FinalizeICCTag(&tags, &tag_offset, &tag_size);
|
||||
AddToICCTagTable("A2B0", tag_offset, tag_size, &tagtable, &offsets);
|
||||
|
@ -765,11 +862,11 @@ Status MaybeCreateProfile(const ColorEncoding& c, IccBytes* JXL_RESTRICT icc) {
|
|||
FinalizeICCTag(&tags, &tag_offset, &tag_size);
|
||||
AddToICCTagTable("B2A0", tag_offset, tag_size, &tagtable, &offsets);
|
||||
} else {
|
||||
if (c.tf.IsGamma()) {
|
||||
if (c.tf.have_gamma) {
|
||||
float gamma = 1.0 / c.tf.GetGamma();
|
||||
JXL_RETURN_IF_ERROR(CreateICCCurvParaTag({gamma}, 0, &tags));
|
||||
} else if (c.GetColorSpace() != ColorSpace::kXYB) {
|
||||
switch (c.tf.GetTransferFunction()) {
|
||||
} else if (c.color_space != ColorSpace::kXYB) {
|
||||
switch (c.tf.transfer_function) {
|
||||
case TransferFunction::kHLG:
|
||||
CreateICCCurvCurvTag(HWY_DYNAMIC_DISPATCH(CreateTableCurve)(
|
||||
64, ExtraTF::kHLG, CanToneMap(c)),
|
||||
|
@ -799,12 +896,12 @@ Status MaybeCreateProfile(const ColorEncoding& c, IccBytes* JXL_RESTRICT icc) {
|
|||
CreateICCCurvParaTag({2.6, 1.0, 0.0, 1.0, 0.0}, 3, &tags));
|
||||
break;
|
||||
default:
|
||||
JXL_UNREACHABLE("Unknown TF %u", static_cast<unsigned int>(
|
||||
c.tf.GetTransferFunction()));
|
||||
JXL_UNREACHABLE("Unknown TF %u",
|
||||
static_cast<unsigned int>(c.tf.transfer_function));
|
||||
}
|
||||
}
|
||||
FinalizeICCTag(&tags, &tag_offset, &tag_size);
|
||||
if (c.IsGray()) {
|
||||
if (c.color_space == ColorSpace::kGray) {
|
||||
AddToICCTagTable("kTRC", tag_offset, tag_size, &tagtable, &offsets);
|
||||
} else {
|
||||
AddToICCTagTable("rTRC", tag_offset, tag_size, &tagtable, &offsets);
|
||||
|
|
|
@ -8,12 +8,12 @@
|
|||
|
||||
// ICC profiles and color space conversions.
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
|
||||
// TODO(eustas): migrate to lib/jxl/cms/color_encoding_cms.h
|
||||
#include "lib/jxl/color_encoding_internal.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
enum class ExtraTF {
|
||||
|
@ -23,11 +23,24 @@ enum class ExtraTF {
|
|||
kSRGB,
|
||||
};
|
||||
|
||||
namespace cms {
|
||||
struct ColorEncoding;
|
||||
struct CIExy;
|
||||
} // namespace cms
|
||||
|
||||
// NOTE: for XYB colorspace, the created profile can be used to transform a
|
||||
// *scaled* XYB image (created by ScaleXYB()) to another colorspace.
|
||||
Status MaybeCreateProfile(const ColorEncoding& c, IccBytes* JXL_RESTRICT icc);
|
||||
Status MaybeCreateProfile(const jxl::cms::ColorEncoding& c,
|
||||
std::vector<uint8_t>* JXL_RESTRICT icc);
|
||||
|
||||
Status CIEXYZFromWhiteCIExy(const CIExy& xy, float XYZ[3]);
|
||||
Status CIEXYZFromWhiteCIExy(const jxl::cms::CIExy& xy, float XYZ[3]);
|
||||
|
||||
Status PrimariesToXYZ(float rx, float ry, float gx, float gy, float bx,
|
||||
float by, float wx, float wy, float matrix[9]);
|
||||
// Adapts whitepoint x, y to D50
|
||||
Status AdaptToXYZD50(float wx, float wy, float matrix[9]);
|
||||
Status PrimariesToXYZD50(float rx, float ry, float gx, float gy, float bx,
|
||||
float by, float wx, float wy, float matrix[9]);
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
|
|
|
@ -49,6 +49,8 @@
|
|||
namespace jxl {
|
||||
namespace {
|
||||
|
||||
using ::jxl::cms::ColorEncoding;
|
||||
|
||||
struct JxlCms {
|
||||
#if JPEGXL_ENABLE_SKCMS
|
||||
IccBytes icc_src, icc_dst;
|
||||
|
@ -486,7 +488,7 @@ Status IdentifyPrimaries(const skcms_ICCProfile& profile,
|
|||
|
||||
void DetectTransferFunction(const skcms_ICCProfile& profile,
|
||||
ColorEncoding* JXL_RESTRICT c) {
|
||||
if (c->tf.SetImplicit()) return;
|
||||
JXL_CHECK(c->color_space != ColorSpace::kXYB);
|
||||
|
||||
float gamma[3] = {};
|
||||
if (profile.has_trc) {
|
||||
|
@ -546,12 +548,12 @@ void DetectTransferFunction(const skcms_ICCProfile& profile,
|
|||
|
||||
uint32_t Type32(const ColorEncoding& c, bool cmyk) {
|
||||
if (cmyk) return TYPE_CMYK_FLT;
|
||||
if (c.IsGray()) return TYPE_GRAY_FLT;
|
||||
if (c.color_space == ColorSpace::kGray) return TYPE_GRAY_FLT;
|
||||
return TYPE_RGB_FLT;
|
||||
}
|
||||
|
||||
uint32_t Type64(const ColorEncoding& c) {
|
||||
if (c.IsGray()) return TYPE_GRAY_DBL;
|
||||
if (c.color_space == ColorSpace::kGray) return TYPE_GRAY_DBL;
|
||||
return TYPE_RGB_DBL;
|
||||
}
|
||||
|
||||
|
@ -600,12 +602,12 @@ Status ProfileEquivalentToICC(const cmsContext context, const Profile& profile1,
|
|||
const double init = 1E-3;
|
||||
const double step = 0.2;
|
||||
|
||||
if (c.IsGray()) {
|
||||
if (c.color_space == ColorSpace::kGray) {
|
||||
// Finer sampling and replicate each component.
|
||||
for (in[0] = init; in[0] < 1.0; in[0] += step / 8) {
|
||||
cmsDoTransform(xform1.get(), in, out1, 1);
|
||||
cmsDoTransform(xform2.get(), in, out2, 1);
|
||||
if (!ApproxEq(out1[0], out2[0], 2E-4)) {
|
||||
if (!cms::ApproxEq(out1[0], out2[0], 2E-4)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -616,7 +618,7 @@ Status ProfileEquivalentToICC(const cmsContext context, const Profile& profile1,
|
|||
cmsDoTransform(xform1.get(), in, out1, 1);
|
||||
cmsDoTransform(xform2.get(), in, out2, 1);
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
if (!ApproxEq(out1[i], out2[i], 2E-4)) {
|
||||
if (!cms::ApproxEq(out1[i], out2[i], 2E-4)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -723,7 +725,7 @@ Status IdentifyPrimaries(const cmsContext context, const Profile& profile,
|
|||
|
||||
void DetectTransferFunction(const cmsContext context, const Profile& profile,
|
||||
ColorEncoding* JXL_RESTRICT c) {
|
||||
if (c->tf.SetImplicit()) return;
|
||||
JXL_CHECK(c->color_space != ColorSpace::kXYB);
|
||||
|
||||
float gamma = 0;
|
||||
if (const auto* gray_trc = reinterpret_cast<const cmsToneCurve*>(
|
||||
|
@ -924,17 +926,17 @@ bool ApplyCICP(const uint8_t color_primaries,
|
|||
const auto tf = static_cast<TransferFunction>(transfer_characteristics);
|
||||
if (!IsKnownTransferFunction(tf)) return false;
|
||||
if (!IsKnownColorPrimaries(color_primaries)) return false;
|
||||
c->SetColorSpace(ColorSpace::kRGB);
|
||||
c->color_space = ColorSpace::kRGB;
|
||||
c->tf.SetTransferFunction(tf);
|
||||
if (primaries == Primaries::kP3) {
|
||||
if (!c->SetWhitePointType(WhitePoint::kDCI)) return false;
|
||||
if (!c->SetPrimariesType(Primaries::kP3)) return false;
|
||||
c->white_point = WhitePoint::kDCI;
|
||||
c->primaries = Primaries::kP3;
|
||||
} else if (color_primaries == kColorPrimariesP3_D65) {
|
||||
if (!c->SetWhitePointType(WhitePoint::kD65)) return false;
|
||||
if (!c->SetPrimariesType(Primaries::kP3)) return false;
|
||||
c->white_point = WhitePoint::kD65;
|
||||
c->primaries = Primaries::kP3;
|
||||
} else {
|
||||
if (!c->SetWhitePointType(WhitePoint::kD65)) return false;
|
||||
if (!c->SetPrimariesType(primaries)) return false;
|
||||
c->white_point = WhitePoint::kD65;
|
||||
c->primaries = primaries;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -971,8 +973,7 @@ JXL_BOOL JxlCmsSetFieldsFromICC(void* user_data, const uint8_t* icc_data,
|
|||
return JXL_FAILURE("Invalid rendering intent %u\n", rendering_intent32);
|
||||
}
|
||||
// ICC and RenderingIntent have the same values (0..3).
|
||||
JXL_RETURN_IF_ERROR(c_enc.SetRenderingIntent(
|
||||
static_cast<RenderingIntent>(rendering_intent32)));
|
||||
c_enc.rendering_intent = static_cast<RenderingIntent>(rendering_intent32);
|
||||
|
||||
if (profile.has_CICP &&
|
||||
ApplyCICP(profile.CICP.color_primaries,
|
||||
|
@ -983,7 +984,7 @@ JXL_BOOL JxlCmsSetFieldsFromICC(void* user_data, const uint8_t* icc_data,
|
|||
return true;
|
||||
}
|
||||
|
||||
c_enc.SetColorSpace(ColorSpaceFromProfile(profile));
|
||||
c_enc.color_space = ColorSpaceFromProfile(profile);
|
||||
*cmyk = (profile.data_color_space == skcms_Signature_CMYK);
|
||||
|
||||
CIExy wp_unadapted;
|
||||
|
@ -1009,8 +1010,7 @@ JXL_BOOL JxlCmsSetFieldsFromICC(void* user_data, const uint8_t* icc_data,
|
|||
return JXL_FAILURE("Invalid rendering intent %u\n", rendering_intent32);
|
||||
}
|
||||
// ICC and RenderingIntent have the same values (0..3).
|
||||
JXL_RETURN_IF_ERROR(c_enc.SetRenderingIntent(
|
||||
static_cast<RenderingIntent>(rendering_intent32)));
|
||||
c_enc.rendering_intent = static_cast<RenderingIntent>(rendering_intent32);
|
||||
|
||||
static constexpr size_t kCICPSize = 12;
|
||||
static constexpr auto kCICPSignature =
|
||||
|
@ -1024,7 +1024,7 @@ JXL_BOOL JxlCmsSetFieldsFromICC(void* user_data, const uint8_t* icc_data,
|
|||
return true;
|
||||
}
|
||||
|
||||
c_enc.SetColorSpace(ColorSpaceFromProfile(profile));
|
||||
c_enc.color_space = ColorSpaceFromProfile(profile);
|
||||
if (cmsGetColorSpace(profile.get()) == cmsSigCmykData) {
|
||||
*cmyk = JXL_TRUE;
|
||||
c_enc.ToExternal(c);
|
||||
|
@ -1078,18 +1078,27 @@ void AllocateBuffer(size_t length, size_t num_threads,
|
|||
void* JxlCmsInit(void* init_data, size_t num_threads, size_t xsize,
|
||||
const JxlColorProfile* input, const JxlColorProfile* output,
|
||||
float intensity_target) {
|
||||
JXL_ASSERT(init_data != nullptr);
|
||||
auto cms = static_cast<const JxlCmsInterface*>(init_data);
|
||||
auto t = jxl::make_unique<JxlCms>();
|
||||
IccBytes icc_src, icc_dst;
|
||||
if (input->icc.size == 0) {
|
||||
JXL_NOTIFY_ERROR("JxlCmsInit: empty input ICC");
|
||||
return nullptr;
|
||||
}
|
||||
if (output->icc.size == 0) {
|
||||
JXL_NOTIFY_ERROR("JxlCmsInit: empty OUTPUT ICC");
|
||||
return nullptr;
|
||||
}
|
||||
icc_src.assign(input->icc.data, input->icc.data + input->icc.size);
|
||||
ColorEncoding c_src;
|
||||
if (!c_src.SetICC(std::move(icc_src), cms)) {
|
||||
if (!c_src.SetFieldsFromICC(std::move(icc_src), *cms)) {
|
||||
JXL_NOTIFY_ERROR("JxlCmsInit: failed to parse input ICC");
|
||||
return nullptr;
|
||||
}
|
||||
icc_dst.assign(output->icc.data, output->icc.data + output->icc.size);
|
||||
ColorEncoding c_dst;
|
||||
if (!c_dst.SetICC(std::move(icc_dst), cms)) {
|
||||
if (!c_dst.SetFieldsFromICC(std::move(icc_dst), *cms)) {
|
||||
JXL_NOTIFY_ERROR("JxlCmsInit: failed to parse output ICC");
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -1109,11 +1118,11 @@ void* JxlCmsInit(void* init_data, size_t num_threads, size_t xsize,
|
|||
#else // JPEGXL_ENABLE_SKCMS
|
||||
const cmsContext context = GetContext();
|
||||
Profile profile_src, profile_dst;
|
||||
if (!DecodeProfile(context, Span<const uint8_t>(c_src.ICC()), &profile_src)) {
|
||||
if (!DecodeProfile(context, Span<const uint8_t>(c_src.icc), &profile_src)) {
|
||||
JXL_NOTIFY_ERROR("JxlCmsInit: lcms failed to parse input ICC");
|
||||
return nullptr;
|
||||
}
|
||||
if (!DecodeProfile(context, Span<const uint8_t>(c_dst.ICC()), &profile_dst)) {
|
||||
if (!DecodeProfile(context, Span<const uint8_t>(c_dst.icc), &profile_dst)) {
|
||||
JXL_NOTIFY_ERROR("JxlCmsInit: lcms failed to parse output ICC");
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -1243,7 +1252,7 @@ void* JxlCmsInit(void* init_data, size_t num_threads, size_t xsize,
|
|||
#endif // JPEGXL_ENABLE_SKCMS
|
||||
|
||||
// Not including alpha channel (copied separately).
|
||||
const size_t channels_src = (c_src.IsCMYK() ? 4 : c_src.Channels());
|
||||
const size_t channels_src = (c_src.cmyk ? 4 : c_src.Channels());
|
||||
const size_t channels_dst = c_dst.Channels();
|
||||
JXL_CHECK(channels_src == channels_dst ||
|
||||
(channels_src == 4 && channels_dst == 3));
|
||||
|
@ -1256,7 +1265,7 @@ void* JxlCmsInit(void* init_data, size_t num_threads, size_t xsize,
|
|||
// Type includes color space (XYZ vs RGB), so can be different.
|
||||
const uint32_t type_src = Type32(c_src, channels_src == 4);
|
||||
const uint32_t type_dst = Type32(c_dst, false);
|
||||
const uint32_t intent = static_cast<uint32_t>(c_dst.GetRenderingIntent());
|
||||
const uint32_t intent = static_cast<uint32_t>(c_dst.rendering_intent);
|
||||
// Use cmsFLAGS_NOCACHE to disable the 1-pixel cache and make calling
|
||||
// cmsDoTransform() thread-safe.
|
||||
const uint32_t flags = cmsFLAGS_NOCACHE | cmsFLAGS_BLACKPOINTCOMPENSATION |
|
||||
|
|
|
@ -5,253 +5,24 @@
|
|||
|
||||
#include "lib/jxl/color_encoding_internal.h"
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
#include "lib/jxl/base/common.h"
|
||||
#include "lib/jxl/base/matrix_ops.h"
|
||||
#include "lib/jxl/cms/color_encoding_cms.h"
|
||||
#include "lib/jxl/cms/color_management.h" // MaybeCreateProfile
|
||||
#include "lib/jxl/fields.h"
|
||||
#include "lib/jxl/pack_signed.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace {
|
||||
|
||||
// These strings are baked into Description - do not change.
|
||||
|
||||
std::string ToString(ColorSpace color_space) {
|
||||
switch (color_space) {
|
||||
case ColorSpace::kRGB:
|
||||
return "RGB";
|
||||
case ColorSpace::kGray:
|
||||
return "Gra";
|
||||
case ColorSpace::kXYB:
|
||||
return "XYB";
|
||||
case ColorSpace::kUnknown:
|
||||
return "CS?";
|
||||
}
|
||||
// Should not happen - visitor fails if enum is invalid.
|
||||
JXL_UNREACHABLE("Invalid ColorSpace %u", static_cast<uint32_t>(color_space));
|
||||
}
|
||||
|
||||
std::string ToString(WhitePoint white_point) {
|
||||
switch (white_point) {
|
||||
case WhitePoint::kD65:
|
||||
return "D65";
|
||||
case WhitePoint::kCustom:
|
||||
return "Cst";
|
||||
case WhitePoint::kE:
|
||||
return "EER";
|
||||
case WhitePoint::kDCI:
|
||||
return "DCI";
|
||||
}
|
||||
// Should not happen - visitor fails if enum is invalid.
|
||||
JXL_UNREACHABLE("Invalid WhitePoint %u", static_cast<uint32_t>(white_point));
|
||||
}
|
||||
|
||||
std::string ToString(Primaries primaries) {
|
||||
switch (primaries) {
|
||||
case Primaries::kSRGB:
|
||||
return "SRG";
|
||||
case Primaries::k2100:
|
||||
return "202";
|
||||
case Primaries::kP3:
|
||||
return "DCI";
|
||||
case Primaries::kCustom:
|
||||
return "Cst";
|
||||
}
|
||||
// Should not happen - visitor fails if enum is invalid.
|
||||
JXL_UNREACHABLE("Invalid Primaries %u", static_cast<uint32_t>(primaries));
|
||||
}
|
||||
|
||||
std::string ToString(TransferFunction transfer_function) {
|
||||
switch (transfer_function) {
|
||||
case TransferFunction::kSRGB:
|
||||
return "SRG";
|
||||
case TransferFunction::kLinear:
|
||||
return "Lin";
|
||||
case TransferFunction::k709:
|
||||
return "709";
|
||||
case TransferFunction::kPQ:
|
||||
return "PeQ";
|
||||
case TransferFunction::kHLG:
|
||||
return "HLG";
|
||||
case TransferFunction::kDCI:
|
||||
return "DCI";
|
||||
case TransferFunction::kUnknown:
|
||||
return "TF?";
|
||||
}
|
||||
// Should not happen - visitor fails if enum is invalid.
|
||||
JXL_UNREACHABLE("Invalid TransferFunction %u",
|
||||
static_cast<uint32_t>(transfer_function));
|
||||
}
|
||||
|
||||
std::string ToString(RenderingIntent rendering_intent) {
|
||||
switch (rendering_intent) {
|
||||
case RenderingIntent::kPerceptual:
|
||||
return "Per";
|
||||
case RenderingIntent::kRelative:
|
||||
return "Rel";
|
||||
case RenderingIntent::kSaturation:
|
||||
return "Sat";
|
||||
case RenderingIntent::kAbsolute:
|
||||
return "Abs";
|
||||
}
|
||||
// Should not happen - visitor fails if enum is invalid.
|
||||
JXL_UNREACHABLE("Invalid RenderingIntent %u",
|
||||
static_cast<uint32_t>(rendering_intent));
|
||||
}
|
||||
|
||||
static double F64FromCustomxyI32(const int32_t i) { return i * 1E-6; }
|
||||
static Status F64ToCustomxyI32(const double f, int32_t* JXL_RESTRICT i) {
|
||||
if (!(-4 <= f && f <= 4)) {
|
||||
return JXL_FAILURE("F64 out of bounds for CustomxyI32");
|
||||
}
|
||||
*i = static_cast<int32_t>(roundf(f * 1E6));
|
||||
return true;
|
||||
}
|
||||
|
||||
Status WhitePointFromExternal(const JxlWhitePoint external, WhitePoint* out) {
|
||||
switch (external) {
|
||||
case JXL_WHITE_POINT_D65:
|
||||
*out = WhitePoint::kD65;
|
||||
return true;
|
||||
case JXL_WHITE_POINT_CUSTOM:
|
||||
*out = WhitePoint::kCustom;
|
||||
return true;
|
||||
case JXL_WHITE_POINT_E:
|
||||
*out = WhitePoint::kE;
|
||||
return true;
|
||||
case JXL_WHITE_POINT_DCI:
|
||||
*out = WhitePoint::kDCI;
|
||||
return true;
|
||||
}
|
||||
return JXL_FAILURE("Invalid WhitePoint enum value %d",
|
||||
static_cast<int>(external));
|
||||
}
|
||||
|
||||
Status PrimariesFromExternal(const JxlPrimaries external, Primaries* out) {
|
||||
switch (external) {
|
||||
case JXL_PRIMARIES_SRGB:
|
||||
*out = Primaries::kSRGB;
|
||||
return true;
|
||||
case JXL_PRIMARIES_CUSTOM:
|
||||
*out = Primaries::kCustom;
|
||||
return true;
|
||||
case JXL_PRIMARIES_2100:
|
||||
*out = Primaries::k2100;
|
||||
return true;
|
||||
case JXL_PRIMARIES_P3:
|
||||
*out = Primaries::kP3;
|
||||
return true;
|
||||
}
|
||||
return JXL_FAILURE("Invalid Primaries enum value");
|
||||
}
|
||||
|
||||
Status ConvertExternalToInternalTransferFunction(
|
||||
const JxlTransferFunction external, TransferFunction* internal) {
|
||||
switch (external) {
|
||||
case JXL_TRANSFER_FUNCTION_709:
|
||||
*internal = TransferFunction::k709;
|
||||
return true;
|
||||
case JXL_TRANSFER_FUNCTION_UNKNOWN:
|
||||
*internal = TransferFunction::kUnknown;
|
||||
return true;
|
||||
case JXL_TRANSFER_FUNCTION_LINEAR:
|
||||
*internal = TransferFunction::kLinear;
|
||||
return true;
|
||||
case JXL_TRANSFER_FUNCTION_SRGB:
|
||||
*internal = TransferFunction::kSRGB;
|
||||
return true;
|
||||
case JXL_TRANSFER_FUNCTION_PQ:
|
||||
*internal = TransferFunction::kPQ;
|
||||
return true;
|
||||
case JXL_TRANSFER_FUNCTION_DCI:
|
||||
*internal = TransferFunction::kDCI;
|
||||
return true;
|
||||
case JXL_TRANSFER_FUNCTION_HLG:
|
||||
*internal = TransferFunction::kHLG;
|
||||
return true;
|
||||
case JXL_TRANSFER_FUNCTION_GAMMA:
|
||||
return JXL_FAILURE("Gamma should be handled separately");
|
||||
}
|
||||
return JXL_FAILURE("Invalid TransferFunction enum value");
|
||||
}
|
||||
|
||||
Status RenderingIntentFromExternal(const JxlRenderingIntent external,
|
||||
RenderingIntent* out) {
|
||||
switch (external) {
|
||||
case JXL_RENDERING_INTENT_PERCEPTUAL:
|
||||
*out = RenderingIntent::kPerceptual;
|
||||
return true;
|
||||
case JXL_RENDERING_INTENT_RELATIVE:
|
||||
*out = RenderingIntent::kRelative;
|
||||
return true;
|
||||
case JXL_RENDERING_INTENT_SATURATION:
|
||||
*out = RenderingIntent::kSaturation;
|
||||
return true;
|
||||
case JXL_RENDERING_INTENT_ABSOLUTE:
|
||||
*out = RenderingIntent::kAbsolute;
|
||||
return true;
|
||||
}
|
||||
return JXL_FAILURE("Invalid RenderingIntent enum value");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CIExy Customxy::Get() const {
|
||||
CIExy xy;
|
||||
xy.x = F64FromCustomxyI32(storage_.x);
|
||||
xy.y = F64FromCustomxyI32(storage_.y);
|
||||
return xy;
|
||||
}
|
||||
|
||||
Status Customxy::Set(const CIExy& xy) {
|
||||
JXL_RETURN_IF_ERROR(F64ToCustomxyI32(xy.x, &storage_.x));
|
||||
JXL_RETURN_IF_ERROR(F64ToCustomxyI32(xy.y, &storage_.y));
|
||||
size_t extension_bits, total_bits;
|
||||
if (!Bundle::CanEncode(*this, &extension_bits, &total_bits)) {
|
||||
return JXL_FAILURE("Unable to encode XY %f %f", xy.x, xy.y);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CustomTransferFunction::SetImplicit() {
|
||||
if (nonserialized_color_space == ColorSpace::kXYB) {
|
||||
if (!SetGamma(1.0 / 3)) JXL_ASSERT(false);
|
||||
if (!storage_.SetGamma(1.0 / 3)) JXL_ASSERT(false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Status CustomTransferFunction::SetGamma(double gamma) {
|
||||
if (gamma < (1.0f / ::jxl::cms::CustomTransferFunction::kMaxGamma) ||
|
||||
gamma > 1.0) {
|
||||
return JXL_FAILURE("Invalid gamma %f", gamma);
|
||||
}
|
||||
|
||||
storage_.have_gamma = false;
|
||||
if (ApproxEq(gamma, 1.0)) {
|
||||
storage_.transfer_function = TransferFunction::kLinear;
|
||||
return true;
|
||||
}
|
||||
if (ApproxEq(gamma, 1.0 / 2.6)) {
|
||||
storage_.transfer_function = TransferFunction::kDCI;
|
||||
return true;
|
||||
}
|
||||
// Don't translate 0.45.. to kSRGB nor k709 - that might change pixel
|
||||
// values because those curves also have a linear part.
|
||||
|
||||
storage_.have_gamma = true;
|
||||
storage_.gamma =
|
||||
roundf(gamma * ::jxl::cms::CustomTransferFunction::kGammaMul);
|
||||
storage_.transfer_function = TransferFunction::kUnknown;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::array<ColorEncoding, 2> ColorEncoding::CreateC2(Primaries pr,
|
||||
TransferFunction tf) {
|
||||
std::array<ColorEncoding, 2> c2;
|
||||
|
@ -260,14 +31,14 @@ std::array<ColorEncoding, 2> ColorEncoding::CreateC2(Primaries pr,
|
|||
c_rgb->SetColorSpace(ColorSpace::kRGB);
|
||||
c_rgb->storage_.white_point = WhitePoint::kD65;
|
||||
c_rgb->storage_.primaries = pr;
|
||||
c_rgb->tf.SetTransferFunction(tf);
|
||||
c_rgb->storage_.tf.SetTransferFunction(tf);
|
||||
JXL_CHECK(c_rgb->CreateICC());
|
||||
|
||||
ColorEncoding* c_gray = c2.data() + 1;
|
||||
c_gray->SetColorSpace(ColorSpace::kGray);
|
||||
c_gray->storage_.white_point = WhitePoint::kD65;
|
||||
c_gray->storage_.primaries = pr;
|
||||
c_gray->tf.SetTransferFunction(tf);
|
||||
c_gray->storage_.tf.SetTransferFunction(tf);
|
||||
JXL_CHECK(c_gray->CreateICC());
|
||||
|
||||
return c2;
|
||||
|
@ -284,106 +55,12 @@ const ColorEncoding& ColorEncoding::LinearSRGB(bool is_gray) {
|
|||
return c2[is_gray];
|
||||
}
|
||||
|
||||
CIExy ColorEncoding::GetWhitePoint() const {
|
||||
JXL_DASSERT(storage_.have_fields);
|
||||
CIExy xy;
|
||||
switch (storage_.white_point) {
|
||||
case WhitePoint::kCustom:
|
||||
return white_.Get();
|
||||
|
||||
case WhitePoint::kD65:
|
||||
xy.x = 0.3127;
|
||||
xy.y = 0.3290;
|
||||
return xy;
|
||||
|
||||
case WhitePoint::kDCI:
|
||||
// From https://ieeexplore.ieee.org/document/7290729 C.2 page 11
|
||||
xy.x = 0.314;
|
||||
xy.y = 0.351;
|
||||
return xy;
|
||||
|
||||
case WhitePoint::kE:
|
||||
xy.x = xy.y = 1.0 / 3;
|
||||
return xy;
|
||||
}
|
||||
JXL_UNREACHABLE("Invalid WhitePoint %u",
|
||||
static_cast<uint32_t>(storage_.white_point));
|
||||
}
|
||||
|
||||
Status ColorEncoding::SetWhitePointType(const WhitePoint& wp) {
|
||||
JXL_DASSERT(storage_.have_fields);
|
||||
storage_.white_point = wp;
|
||||
return true;
|
||||
}
|
||||
|
||||
Status ColorEncoding::SetWhitePoint(const CIExy& xy) {
|
||||
JXL_DASSERT(storage_.have_fields);
|
||||
if (xy.x == 0.0 || xy.y == 0.0) {
|
||||
return JXL_FAILURE("Invalid white point %f %f", xy.x, xy.y);
|
||||
}
|
||||
if (ApproxEq(xy.x, 0.3127) && ApproxEq(xy.y, 0.3290)) {
|
||||
storage_.white_point = WhitePoint::kD65;
|
||||
return true;
|
||||
}
|
||||
if (ApproxEq(xy.x, 1.0 / 3) && ApproxEq(xy.y, 1.0 / 3)) {
|
||||
storage_.white_point = WhitePoint::kE;
|
||||
return true;
|
||||
}
|
||||
if (ApproxEq(xy.x, 0.314) && ApproxEq(xy.y, 0.351)) {
|
||||
storage_.white_point = WhitePoint::kDCI;
|
||||
return true;
|
||||
}
|
||||
storage_.white_point = WhitePoint::kCustom;
|
||||
return white_.Set(xy);
|
||||
}
|
||||
|
||||
Status ColorEncoding::SetRenderingIntent(const RenderingIntent& ri) {
|
||||
storage_.rendering_intent = ri;
|
||||
return true;
|
||||
}
|
||||
|
||||
PrimariesCIExy ColorEncoding::GetPrimaries() const {
|
||||
JXL_DASSERT(storage_.have_fields);
|
||||
JXL_ASSERT(HasPrimaries());
|
||||
PrimariesCIExy xy;
|
||||
switch (storage_.primaries) {
|
||||
case Primaries::kCustom:
|
||||
xy.r = red_.Get();
|
||||
xy.g = green_.Get();
|
||||
xy.b = blue_.Get();
|
||||
return xy;
|
||||
|
||||
case Primaries::kSRGB:
|
||||
xy.r.x = 0.639998686;
|
||||
xy.r.y = 0.330010138;
|
||||
xy.g.x = 0.300003784;
|
||||
xy.g.y = 0.600003357;
|
||||
xy.b.x = 0.150002046;
|
||||
xy.b.y = 0.059997204;
|
||||
return xy;
|
||||
|
||||
case Primaries::k2100:
|
||||
xy.r.x = 0.708;
|
||||
xy.r.y = 0.292;
|
||||
xy.g.x = 0.170;
|
||||
xy.g.y = 0.797;
|
||||
xy.b.x = 0.131;
|
||||
xy.b.y = 0.046;
|
||||
return xy;
|
||||
|
||||
case Primaries::kP3:
|
||||
xy.r.x = 0.680;
|
||||
xy.r.y = 0.320;
|
||||
xy.g.x = 0.265;
|
||||
xy.g.y = 0.690;
|
||||
xy.b.x = 0.150;
|
||||
xy.b.y = 0.060;
|
||||
return xy;
|
||||
}
|
||||
JXL_UNREACHABLE("Invalid Primaries %u",
|
||||
static_cast<uint32_t>(storage_.primaries));
|
||||
}
|
||||
|
||||
Status ColorEncoding::SetPrimariesType(const Primaries& p) {
|
||||
JXL_DASSERT(storage_.have_fields);
|
||||
JXL_ASSERT(HasPrimaries());
|
||||
|
@ -391,69 +68,6 @@ Status ColorEncoding::SetPrimariesType(const Primaries& p) {
|
|||
return true;
|
||||
}
|
||||
|
||||
Status ColorEncoding::SetPrimaries(const PrimariesCIExy& xy) {
|
||||
JXL_DASSERT(storage_.have_fields);
|
||||
JXL_ASSERT(HasPrimaries());
|
||||
if (xy.r.x == 0.0 || xy.r.y == 0.0 || xy.g.x == 0.0 || xy.g.y == 0.0 ||
|
||||
xy.b.x == 0.0 || xy.b.y == 0.0) {
|
||||
return JXL_FAILURE("Invalid primaries %f %f %f %f %f %f", xy.r.x, xy.r.y,
|
||||
xy.g.x, xy.g.y, xy.b.x, xy.b.y);
|
||||
}
|
||||
|
||||
if (ApproxEq(xy.r.x, 0.64) && ApproxEq(xy.r.y, 0.33) &&
|
||||
ApproxEq(xy.g.x, 0.30) && ApproxEq(xy.g.y, 0.60) &&
|
||||
ApproxEq(xy.b.x, 0.15) && ApproxEq(xy.b.y, 0.06)) {
|
||||
storage_.primaries = Primaries::kSRGB;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ApproxEq(xy.r.x, 0.708) && ApproxEq(xy.r.y, 0.292) &&
|
||||
ApproxEq(xy.g.x, 0.170) && ApproxEq(xy.g.y, 0.797) &&
|
||||
ApproxEq(xy.b.x, 0.131) && ApproxEq(xy.b.y, 0.046)) {
|
||||
storage_.primaries = Primaries::k2100;
|
||||
return true;
|
||||
}
|
||||
if (ApproxEq(xy.r.x, 0.680) && ApproxEq(xy.r.y, 0.320) &&
|
||||
ApproxEq(xy.g.x, 0.265) && ApproxEq(xy.g.y, 0.690) &&
|
||||
ApproxEq(xy.b.x, 0.150) && ApproxEq(xy.b.y, 0.060)) {
|
||||
storage_.primaries = Primaries::kP3;
|
||||
return true;
|
||||
}
|
||||
|
||||
storage_.primaries = Primaries::kCustom;
|
||||
JXL_RETURN_IF_ERROR(red_.Set(xy.r));
|
||||
JXL_RETURN_IF_ERROR(green_.Set(xy.g));
|
||||
JXL_RETURN_IF_ERROR(blue_.Set(xy.b));
|
||||
return true;
|
||||
}
|
||||
|
||||
Status ColorEncoding::CreateICC() {
|
||||
storage_.icc.clear();
|
||||
return MaybeCreateProfile(*this, &storage_.icc);
|
||||
}
|
||||
|
||||
Status ColorEncoding::SetFieldsFromICC(const JxlCmsInterface& cms) {
|
||||
// In case parsing fails, mark the ColorEncoding as invalid.
|
||||
SetColorSpace(ColorSpace::kUnknown);
|
||||
tf.SetTransferFunction(TransferFunction::kUnknown);
|
||||
|
||||
if (storage_.icc.empty()) return JXL_FAILURE("Empty ICC profile");
|
||||
|
||||
JxlColorEncoding external;
|
||||
JXL_BOOL cmyk;
|
||||
JXL_RETURN_IF_ERROR(
|
||||
cms.set_fields_from_icc(cms.set_fields_data, storage_.icc.data(),
|
||||
storage_.icc.size(), &external, &cmyk));
|
||||
if (cmyk) {
|
||||
storage_.cmyk = true;
|
||||
return true;
|
||||
}
|
||||
IccBytes icc = std::move(storage_.icc);
|
||||
JXL_RETURN_IF_ERROR(FromExternal(external));
|
||||
storage_.icc = std::move(icc);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ColorEncoding::DecideIfWantICC(const JxlCmsInterface& cms) {
|
||||
if (storage_.icc.empty()) return;
|
||||
|
||||
|
@ -466,56 +80,9 @@ void ColorEncoding::DecideIfWantICC(const JxlCmsInterface& cms) {
|
|||
if (cmyk) return;
|
||||
|
||||
IccBytes new_icc;
|
||||
if (!MaybeCreateProfile(*this, &new_icc)) return;
|
||||
if (!MaybeCreateProfile(storage_, &new_icc)) return;
|
||||
|
||||
storage_.want_icc = false;
|
||||
}
|
||||
|
||||
std::string Description(const ColorEncoding& c) { return c.Description(); }
|
||||
std::string ColorEncoding::Description() const {
|
||||
std::string d = ToString(GetColorSpace());
|
||||
|
||||
bool explicit_wp_tf = (storage_.color_space != ColorSpace::kXYB);
|
||||
if (explicit_wp_tf) {
|
||||
d += '_';
|
||||
if (storage_.white_point == WhitePoint::kCustom) {
|
||||
const CIExy wp = GetWhitePoint();
|
||||
d += ToString(wp.x) + ';';
|
||||
d += ToString(wp.y);
|
||||
} else {
|
||||
d += ToString(storage_.white_point);
|
||||
}
|
||||
}
|
||||
|
||||
if (HasPrimaries()) {
|
||||
d += '_';
|
||||
if (storage_.primaries == Primaries::kCustom) {
|
||||
const PrimariesCIExy pr = GetPrimaries();
|
||||
d += ToString(pr.r.x) + ';';
|
||||
d += ToString(pr.r.y) + ';';
|
||||
d += ToString(pr.g.x) + ';';
|
||||
d += ToString(pr.g.y) + ';';
|
||||
d += ToString(pr.b.x) + ';';
|
||||
d += ToString(pr.b.y);
|
||||
} else {
|
||||
d += ToString(storage_.primaries);
|
||||
}
|
||||
}
|
||||
|
||||
d += '_';
|
||||
d += ToString(storage_.rendering_intent);
|
||||
|
||||
if (explicit_wp_tf) {
|
||||
d += '_';
|
||||
if (tf.IsGamma()) {
|
||||
d += 'g';
|
||||
d += ToString(tf.GetGamma());
|
||||
} else {
|
||||
d += ToString(tf.GetTransferFunction());
|
||||
}
|
||||
}
|
||||
|
||||
return d;
|
||||
want_icc_ = false;
|
||||
}
|
||||
|
||||
Customxy::Customxy() { Bundle::Init(this); }
|
||||
|
@ -569,7 +136,7 @@ Status ColorEncoding::VisitFields(Visitor* JXL_RESTRICT visitor) {
|
|||
return true;
|
||||
}
|
||||
|
||||
JXL_QUIET_RETURN_IF_ERROR(visitor->Bool(false, &storage_.want_icc));
|
||||
JXL_QUIET_RETURN_IF_ERROR(visitor->Bool(false, &want_icc_));
|
||||
|
||||
// Always send even if want_icc_ because this affects decoding.
|
||||
// We can skip the white point/primaries because they do not.
|
||||
|
@ -584,7 +151,9 @@ Status ColorEncoding::VisitFields(Visitor* JXL_RESTRICT visitor) {
|
|||
JXL_QUIET_RETURN_IF_ERROR(
|
||||
visitor->Enum(WhitePoint::kD65, &storage_.white_point));
|
||||
if (visitor->Conditional(storage_.white_point == WhitePoint::kCustom)) {
|
||||
white_.storage_ = storage_.white;
|
||||
JXL_QUIET_RETURN_IF_ERROR(visitor->VisitNested(&white_));
|
||||
storage_.white = white_.storage_;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -592,25 +161,36 @@ Status ColorEncoding::VisitFields(Visitor* JXL_RESTRICT visitor) {
|
|||
JXL_QUIET_RETURN_IF_ERROR(
|
||||
visitor->Enum(Primaries::kSRGB, &storage_.primaries));
|
||||
if (visitor->Conditional(storage_.primaries == Primaries::kCustom)) {
|
||||
red_.storage_ = storage_.red;
|
||||
JXL_QUIET_RETURN_IF_ERROR(visitor->VisitNested(&red_));
|
||||
storage_.red = red_.storage_;
|
||||
green_.storage_ = storage_.green;
|
||||
JXL_QUIET_RETURN_IF_ERROR(visitor->VisitNested(&green_));
|
||||
storage_.green = green_.storage_;
|
||||
blue_.storage_ = storage_.blue;
|
||||
JXL_QUIET_RETURN_IF_ERROR(visitor->VisitNested(&blue_));
|
||||
storage_.blue = blue_.storage_;
|
||||
}
|
||||
}
|
||||
|
||||
JXL_QUIET_RETURN_IF_ERROR(visitor->VisitNested(&tf));
|
||||
tf_.nonserialized_color_space = storage_.color_space;
|
||||
tf_.storage_ = storage_.tf;
|
||||
JXL_QUIET_RETURN_IF_ERROR(visitor->VisitNested(&tf_));
|
||||
storage_.tf = tf_.storage_;
|
||||
|
||||
JXL_QUIET_RETURN_IF_ERROR(
|
||||
visitor->Enum(RenderingIntent::kRelative, &storage_.rendering_intent));
|
||||
|
||||
// We didn't have ICC, so all fields should be known.
|
||||
if (storage_.color_space == ColorSpace::kUnknown || tf.IsUnknown()) {
|
||||
if (storage_.color_space == ColorSpace::kUnknown ||
|
||||
storage_.tf.IsUnknown()) {
|
||||
return JXL_FAILURE(
|
||||
"No ICC but cs %u and tf %u%s",
|
||||
static_cast<unsigned int>(storage_.color_space),
|
||||
tf.IsGamma() ? 0
|
||||
: static_cast<unsigned int>(tf.GetTransferFunction()),
|
||||
tf.IsGamma() ? "(gamma)" : "");
|
||||
storage_.tf.have_gamma
|
||||
? 0
|
||||
: static_cast<unsigned int>(storage_.tf.transfer_function),
|
||||
storage_.tf.have_gamma ? "(gamma)" : "");
|
||||
}
|
||||
|
||||
JXL_RETURN_IF_ERROR(CreateICC());
|
||||
|
@ -625,189 +205,4 @@ Status ColorEncoding::VisitFields(Visitor* JXL_RESTRICT visitor) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void ColorEncoding::ToExternal(JxlColorEncoding* external) const {
|
||||
// TODO(eustas): update copy/update storage and call .ToExternal on it
|
||||
if (!HaveFields()) {
|
||||
external->color_space = JXL_COLOR_SPACE_UNKNOWN;
|
||||
external->primaries = JXL_PRIMARIES_CUSTOM;
|
||||
external->rendering_intent = JXL_RENDERING_INTENT_PERCEPTUAL; //?
|
||||
external->transfer_function = JXL_TRANSFER_FUNCTION_UNKNOWN;
|
||||
external->white_point = JXL_WHITE_POINT_CUSTOM;
|
||||
return;
|
||||
}
|
||||
external->color_space = static_cast<JxlColorSpace>(GetColorSpace());
|
||||
|
||||
external->white_point = static_cast<JxlWhitePoint>(storage_.white_point);
|
||||
|
||||
jxl::CIExy whitepoint = GetWhitePoint();
|
||||
external->white_point_xy[0] = whitepoint.x;
|
||||
external->white_point_xy[1] = whitepoint.y;
|
||||
|
||||
if (external->color_space == JXL_COLOR_SPACE_RGB ||
|
||||
external->color_space == JXL_COLOR_SPACE_UNKNOWN) {
|
||||
external->primaries = static_cast<JxlPrimaries>(storage_.primaries);
|
||||
jxl::PrimariesCIExy primaries = GetPrimaries();
|
||||
external->primaries_red_xy[0] = primaries.r.x;
|
||||
external->primaries_red_xy[1] = primaries.r.y;
|
||||
external->primaries_green_xy[0] = primaries.g.x;
|
||||
external->primaries_green_xy[1] = primaries.g.y;
|
||||
external->primaries_blue_xy[0] = primaries.b.x;
|
||||
external->primaries_blue_xy[1] = primaries.b.y;
|
||||
}
|
||||
|
||||
if (tf.IsGamma()) {
|
||||
external->transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
|
||||
external->gamma = tf.GetGamma();
|
||||
} else {
|
||||
external->transfer_function =
|
||||
static_cast<JxlTransferFunction>(tf.GetTransferFunction());
|
||||
external->gamma = 0;
|
||||
}
|
||||
|
||||
external->rendering_intent =
|
||||
static_cast<JxlRenderingIntent>(storage_.rendering_intent);
|
||||
}
|
||||
|
||||
Status ColorEncoding::FromExternal(const JxlColorEncoding& external) {
|
||||
SetColorSpace(static_cast<ColorSpace>(external.color_space));
|
||||
|
||||
JXL_RETURN_IF_ERROR(
|
||||
WhitePointFromExternal(external.white_point, &storage_.white_point));
|
||||
if (external.white_point == JXL_WHITE_POINT_CUSTOM) {
|
||||
CIExy wp;
|
||||
wp.x = external.white_point_xy[0];
|
||||
wp.y = external.white_point_xy[1];
|
||||
JXL_RETURN_IF_ERROR(SetWhitePoint(wp));
|
||||
}
|
||||
|
||||
if (external.color_space == JXL_COLOR_SPACE_RGB ||
|
||||
external.color_space == JXL_COLOR_SPACE_UNKNOWN) {
|
||||
JXL_RETURN_IF_ERROR(
|
||||
PrimariesFromExternal(external.primaries, &storage_.primaries));
|
||||
if (external.primaries == JXL_PRIMARIES_CUSTOM) {
|
||||
PrimariesCIExy primaries;
|
||||
primaries.r.x = external.primaries_red_xy[0];
|
||||
primaries.r.y = external.primaries_red_xy[1];
|
||||
primaries.g.x = external.primaries_green_xy[0];
|
||||
primaries.g.y = external.primaries_green_xy[1];
|
||||
primaries.b.x = external.primaries_blue_xy[0];
|
||||
primaries.b.y = external.primaries_blue_xy[1];
|
||||
JXL_RETURN_IF_ERROR(SetPrimaries(primaries));
|
||||
}
|
||||
}
|
||||
CustomTransferFunction tf;
|
||||
tf.nonserialized_color_space = GetColorSpace();
|
||||
if (external.transfer_function == JXL_TRANSFER_FUNCTION_GAMMA) {
|
||||
JXL_RETURN_IF_ERROR(tf.SetGamma(external.gamma));
|
||||
} else {
|
||||
TransferFunction tf_enum;
|
||||
// JXL_TRANSFER_FUNCTION_GAMMA is not handled by this function since there's
|
||||
// no internal enum value for it.
|
||||
JXL_RETURN_IF_ERROR(ConvertExternalToInternalTransferFunction(
|
||||
external.transfer_function, &tf_enum));
|
||||
tf.SetTransferFunction(tf_enum);
|
||||
}
|
||||
this->tf = tf;
|
||||
|
||||
JXL_RETURN_IF_ERROR(RenderingIntentFromExternal(external.rendering_intent,
|
||||
&storage_.rendering_intent));
|
||||
|
||||
// The ColorEncoding caches an ICC profile it created earlier that may no
|
||||
// longer match the profile with the changed fields, so re-create it.
|
||||
if (!(CreateICC())) {
|
||||
// This is not an error: for example, it doesn't have ICC profile creation
|
||||
// implemented for XYB. This should not be returned as error, since
|
||||
// FromExternal still worked correctly, and what
|
||||
// matters is that internal->ICC() will not return the wrong profile.
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Chromatic adaptation matrices*/
|
||||
static const float kBradford[9] = {
|
||||
0.8951f, 0.2664f, -0.1614f, -0.7502f, 1.7135f,
|
||||
0.0367f, 0.0389f, -0.0685f, 1.0296f,
|
||||
};
|
||||
|
||||
static const float kBradfordInv[9] = {
|
||||
0.9869929f, -0.1470543f, 0.1599627f, 0.4323053f, 0.5183603f,
|
||||
0.0492912f, -0.0085287f, 0.0400428f, 0.9684867f,
|
||||
};
|
||||
|
||||
// Adapts whitepoint x, y to D50
|
||||
Status AdaptToXYZD50(float wx, float wy, float matrix[9]) {
|
||||
if (wx < 0 || wx > 1 || wy <= 0 || wy > 1) {
|
||||
// Out of range values can cause division through zero
|
||||
// further down with the bradford adaptation too.
|
||||
return JXL_FAILURE("Invalid white point");
|
||||
}
|
||||
float w[3] = {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]));
|
||||
float w50[3] = {0.96422f, 1.0f, 0.82521f};
|
||||
|
||||
float lms[3];
|
||||
float lms50[3];
|
||||
|
||||
Mul3x3Vector(kBradford, w, lms);
|
||||
Mul3x3Vector(kBradford, w50, lms50);
|
||||
|
||||
if (lms[0] == 0 || lms[1] == 0 || lms[2] == 0) {
|
||||
return JXL_FAILURE("Invalid white point");
|
||||
}
|
||||
float a[9] = {
|
||||
// /----> 0, 1, 2, 3, /----> 4, 5, 6, 7, /----> 8,
|
||||
lms50[0] / lms[0], 0, 0, 0, lms50[1] / lms[1], 0, 0, 0, lms50[2] / lms[2],
|
||||
};
|
||||
if (!std::isfinite(a[0]) || !std::isfinite(a[4]) || !std::isfinite(a[8])) {
|
||||
return JXL_FAILURE("Invalid white point");
|
||||
}
|
||||
|
||||
float b[9];
|
||||
Mul3x3Matrix(a, kBradford, b);
|
||||
Mul3x3Matrix(kBradfordInv, b, matrix);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Status PrimariesToXYZ(float rx, float ry, float gx, float gy, float bx,
|
||||
float by, float wx, float wy, float matrix[9]) {
|
||||
if (wx < 0 || wx > 1 || wy <= 0 || wy > 1) {
|
||||
return JXL_FAILURE("Invalid white point");
|
||||
}
|
||||
// TODO(lode): also require rx, ry, gx, gy, bx, to be in range 0-1? ICC
|
||||
// profiles in theory forbid negative XYZ values, but in practice the ACES P0
|
||||
// color space uses a negative y for the blue primary.
|
||||
float primaries[9] = {
|
||||
rx, gx, bx, ry, gy, by, 1.0f - rx - ry, 1.0f - gx - gy, 1.0f - bx - by};
|
||||
float primaries_inv[9];
|
||||
memcpy(primaries_inv, primaries, sizeof(float) * 9);
|
||||
JXL_RETURN_IF_ERROR(Inv3x3Matrix(primaries_inv));
|
||||
|
||||
float w[3] = {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]));
|
||||
float xyz[3];
|
||||
Mul3x3Vector(primaries_inv, w, xyz);
|
||||
|
||||
float a[9] = {
|
||||
xyz[0], 0, 0, 0, xyz[1], 0, 0, 0, xyz[2],
|
||||
};
|
||||
|
||||
Mul3x3Matrix(primaries, a, matrix);
|
||||
return true;
|
||||
}
|
||||
|
||||
Status PrimariesToXYZD50(float rx, float ry, float gx, float gy, float bx,
|
||||
float by, float wx, float wy, float matrix[9]) {
|
||||
float toXYZ[9];
|
||||
JXL_RETURN_IF_ERROR(PrimariesToXYZ(rx, ry, gx, gy, bx, by, wx, wy, toXYZ));
|
||||
float d50[9];
|
||||
JXL_RETURN_IF_ERROR(AdaptToXYZD50(wx, wy, d50));
|
||||
|
||||
Mul3x3Matrix(d50, toXYZ, matrix);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
|
|
|
@ -12,14 +12,11 @@
|
|||
#include <jxl/color_encoding.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <array>
|
||||
#include <cmath> // std::abs
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
|
@ -85,6 +82,8 @@ static inline constexpr uint64_t EnumBits(RenderingIntent /*unused*/) {
|
|||
|
||||
} // namespace cms
|
||||
|
||||
struct ColorEncoding;
|
||||
|
||||
// Serializable form of CIExy.
|
||||
struct Customxy : public Fields {
|
||||
Customxy();
|
||||
|
@ -92,15 +91,8 @@ struct Customxy : public Fields {
|
|||
|
||||
Status VisitFields(Visitor* JXL_RESTRICT visitor) override;
|
||||
|
||||
CIExy Get() const;
|
||||
// Returns false if x or y do not fit in the encoding.
|
||||
Status Set(const CIExy& xy);
|
||||
|
||||
bool IsSame(const Customxy& other) const {
|
||||
return (storage_.x == other.storage_.x) && (storage_.y == other.storage_.y);
|
||||
}
|
||||
|
||||
private:
|
||||
friend struct ColorEncoding;
|
||||
::jxl::cms::Customxy storage_;
|
||||
};
|
||||
|
||||
|
@ -112,73 +104,13 @@ struct CustomTransferFunction : public Fields {
|
|||
// transfer function, otherwise leaves fields unchanged and returns false.
|
||||
bool SetImplicit();
|
||||
|
||||
// Gamma: only used for PNG inputs
|
||||
bool IsGamma() const { return storage_.have_gamma; }
|
||||
double GetGamma() const {
|
||||
JXL_ASSERT(IsGamma());
|
||||
return storage_.gamma * 1E-7; // (0, 1)
|
||||
}
|
||||
Status SetGamma(double gamma);
|
||||
|
||||
TransferFunction GetTransferFunction() const {
|
||||
JXL_ASSERT(!IsGamma());
|
||||
return storage_.transfer_function;
|
||||
}
|
||||
void SetTransferFunction(const TransferFunction tf) {
|
||||
storage_.have_gamma = false;
|
||||
storage_.transfer_function = tf;
|
||||
}
|
||||
|
||||
bool IsUnknown() const {
|
||||
return !storage_.have_gamma &&
|
||||
(storage_.transfer_function == TransferFunction::kUnknown);
|
||||
}
|
||||
bool IsSRGB() const {
|
||||
return !storage_.have_gamma &&
|
||||
(storage_.transfer_function == TransferFunction::kSRGB);
|
||||
}
|
||||
bool IsLinear() const {
|
||||
return !storage_.have_gamma &&
|
||||
(storage_.transfer_function == TransferFunction::kLinear);
|
||||
}
|
||||
bool IsPQ() const {
|
||||
return !storage_.have_gamma &&
|
||||
(storage_.transfer_function == TransferFunction::kPQ);
|
||||
}
|
||||
bool IsHLG() const {
|
||||
return !storage_.have_gamma &&
|
||||
(storage_.transfer_function == TransferFunction::kHLG);
|
||||
}
|
||||
bool Is709() const {
|
||||
return !storage_.have_gamma &&
|
||||
(storage_.transfer_function == TransferFunction::k709);
|
||||
}
|
||||
bool IsDCI() const {
|
||||
return !storage_.have_gamma &&
|
||||
(storage_.transfer_function == TransferFunction::kDCI);
|
||||
}
|
||||
bool IsSame(const CustomTransferFunction& other) const {
|
||||
if (storage_.have_gamma != other.storage_.have_gamma) {
|
||||
return false;
|
||||
}
|
||||
if (storage_.have_gamma) {
|
||||
if (storage_.gamma != other.storage_.gamma) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (storage_.transfer_function != other.storage_.transfer_function) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Status VisitFields(Visitor* JXL_RESTRICT visitor) override;
|
||||
|
||||
// Must be set before calling VisitFields!
|
||||
ColorSpace nonserialized_color_space = ColorSpace::kRGB;
|
||||
|
||||
private:
|
||||
friend struct ColorEncoding;
|
||||
::jxl::cms::CustomTransferFunction storage_;
|
||||
};
|
||||
|
||||
|
@ -194,7 +126,7 @@ struct ColorEncoding : public Fields {
|
|||
|
||||
// Returns true if an ICC profile was successfully created from fields.
|
||||
// Must be called after modifying fields. Defined in color_management.cc.
|
||||
Status CreateICC();
|
||||
Status CreateICC() { return storage_.CreateICC(); }
|
||||
|
||||
// Returns non-empty and valid ICC profile, unless:
|
||||
// - WantICC() == true and SetICC() was not yet called;
|
||||
|
@ -205,22 +137,10 @@ struct ColorEncoding : public Fields {
|
|||
// subsequent WantICC() will return true until DecideIfWantICC() changes it.
|
||||
// Returning false indicates data has been lost.
|
||||
Status SetICC(IccBytes&& icc, const JxlCmsInterface* cms) {
|
||||
if (icc.empty()) return false;
|
||||
storage_.icc = std::move(icc);
|
||||
|
||||
if (cms == nullptr) {
|
||||
storage_.want_icc = true;
|
||||
storage_.have_fields = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!SetFieldsFromICC(*cms)) {
|
||||
storage_.icc.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
storage_.want_icc = true;
|
||||
return true;
|
||||
JXL_ASSERT(cms != nullptr);
|
||||
JXL_ASSERT(!icc.empty());
|
||||
want_icc_ = storage_.SetFieldsFromICC(std::move(icc), *cms);
|
||||
return want_icc_;
|
||||
}
|
||||
|
||||
// Sets the raw ICC profile bytes, without parsing the ICC, and without
|
||||
|
@ -228,17 +148,15 @@ struct ColorEncoding : public Fields {
|
|||
// space. Functions to get and set fields, such as SetWhitePoint, cannot be
|
||||
// used anymore after this and functions such as IsSRGB return false no matter
|
||||
// what the contents of the icc profile.
|
||||
Status SetICCRaw(IccBytes&& icc) {
|
||||
if (icc.empty()) return false;
|
||||
void SetICCRaw(IccBytes&& icc) {
|
||||
JXL_ASSERT(!icc.empty());
|
||||
storage_.icc = std::move(icc);
|
||||
|
||||
storage_.want_icc = true;
|
||||
storage_.have_fields = false;
|
||||
return true;
|
||||
want_icc_ = true;
|
||||
}
|
||||
|
||||
// Returns whether to send the ICC profile in the codestream.
|
||||
bool WantICC() const { return storage_.want_icc; }
|
||||
bool WantICC() const { return want_icc_; }
|
||||
|
||||
// Return whether the direct fields are set, if false but ICC is set, only
|
||||
// raw ICC bytes are known.
|
||||
|
@ -249,16 +167,15 @@ struct ColorEncoding : public Fields {
|
|||
|
||||
bool IsGray() const { return storage_.color_space == ColorSpace::kGray; }
|
||||
bool IsCMYK() const { return storage_.cmyk; }
|
||||
size_t Channels() const { return IsGray() ? 1 : 3; }
|
||||
size_t Channels() const { return storage_.Channels(); }
|
||||
|
||||
// Returns false if the field is invalid and unusable.
|
||||
bool HasPrimaries() const {
|
||||
return !IsGray() && storage_.color_space != ColorSpace::kXYB;
|
||||
}
|
||||
bool HasPrimaries() const { return storage_.HasPrimaries(); }
|
||||
|
||||
// Returns true after setting the field to a value defined by color_space,
|
||||
// otherwise false and leaves the field unchanged.
|
||||
bool ImplicitWhitePoint() {
|
||||
// TODO(eustas): inline
|
||||
if (storage_.color_space == ColorSpace::kXYB) {
|
||||
storage_.white_point = WhitePoint::kD65;
|
||||
return true;
|
||||
|
@ -274,7 +191,7 @@ struct ColorEncoding : public Fields {
|
|||
if (!IsGray() && storage_.color_space != ColorSpace::kRGB) return false;
|
||||
if (storage_.white_point != WhitePoint::kD65) return false;
|
||||
if (storage_.primaries != Primaries::kSRGB) return false;
|
||||
if (!tf.IsSRGB()) return false;
|
||||
if (!storage_.tf.IsSRGB()) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -286,7 +203,7 @@ struct ColorEncoding : public Fields {
|
|||
if (!IsGray() && storage_.color_space != ColorSpace::kRGB) return false;
|
||||
if (storage_.white_point != WhitePoint::kD65) return false;
|
||||
if (storage_.primaries != Primaries::kSRGB) return false;
|
||||
if (!tf.IsLinear()) return false;
|
||||
if (!storage_.tf.IsLinear()) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -297,7 +214,7 @@ struct ColorEncoding : public Fields {
|
|||
storage_.color_space = cs;
|
||||
storage_.white_point = WhitePoint::kD65;
|
||||
storage_.primaries = Primaries::kSRGB;
|
||||
tf.SetTransferFunction(TransferFunction::kSRGB);
|
||||
storage_.tf.transfer_function = TransferFunction::kSRGB;
|
||||
storage_.rendering_intent = ri;
|
||||
return CreateICC();
|
||||
}
|
||||
|
@ -306,107 +223,70 @@ struct ColorEncoding : public Fields {
|
|||
|
||||
// Accessors ensure tf.nonserialized_color_space is updated at the same time.
|
||||
ColorSpace GetColorSpace() const { return storage_.color_space; }
|
||||
void SetColorSpace(const ColorSpace cs) {
|
||||
storage_.color_space = cs;
|
||||
tf.nonserialized_color_space = cs;
|
||||
}
|
||||
|
||||
CIExy GetWhitePoint() const;
|
||||
Status SetWhitePoint(const CIExy& xy);
|
||||
void SetColorSpace(const ColorSpace cs) { storage_.color_space = cs; }
|
||||
CIExy GetWhitePoint() const { return storage_.GetWhitePoint(); }
|
||||
|
||||
WhitePoint GetWhitePointType() const { return storage_.white_point; }
|
||||
Status SetWhitePointType(const WhitePoint& wp);
|
||||
|
||||
PrimariesCIExy GetPrimaries() const;
|
||||
Status SetPrimaries(const PrimariesCIExy& xy);
|
||||
PrimariesCIExy GetPrimaries() const { return storage_.GetPrimaries(); }
|
||||
|
||||
Primaries GetPrimariesType() const { return storage_.primaries; }
|
||||
Status SetPrimariesType(const Primaries& p);
|
||||
|
||||
jxl::cms::CustomTransferFunction& Tf() { return storage_.tf; }
|
||||
const jxl::cms::CustomTransferFunction& Tf() const { return storage_.tf; }
|
||||
|
||||
RenderingIntent GetRenderingIntent() const {
|
||||
return storage_.rendering_intent;
|
||||
}
|
||||
Status SetRenderingIntent(const RenderingIntent& ri);
|
||||
|
||||
// Checks if the color spaces (including white point / primaries) are the
|
||||
// same, but ignores the transfer function, rendering intent and ICC bytes.
|
||||
bool SameColorSpace(const ColorEncoding& other) const {
|
||||
if (storage_.color_space != other.storage_.color_space) return false;
|
||||
|
||||
if (storage_.white_point != other.storage_.white_point) return false;
|
||||
if (storage_.white_point == WhitePoint::kCustom) {
|
||||
if (!white_.IsSame(other.white_)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (HasPrimaries() != other.HasPrimaries()) return false;
|
||||
if (HasPrimaries()) {
|
||||
if (storage_.primaries != other.storage_.primaries) return false;
|
||||
if (storage_.primaries == Primaries::kCustom) {
|
||||
if (!red_.IsSame(other.red_)) return false;
|
||||
if (!green_.IsSame(other.green_)) return false;
|
||||
if (!blue_.IsSame(other.blue_)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
void SetRenderingIntent(const RenderingIntent& ri) {
|
||||
storage_.rendering_intent = ri;
|
||||
}
|
||||
|
||||
// Checks if the color space and transfer function are the same, ignoring
|
||||
// rendering intent and ICC bytes
|
||||
bool SameColorEncoding(const ColorEncoding& other) const {
|
||||
return SameColorSpace(other) && tf.IsSame(other.tf);
|
||||
return storage_.SameColorEncoding(other.storage_);
|
||||
}
|
||||
|
||||
mutable bool all_default;
|
||||
|
||||
// Only valid if HaveFields()
|
||||
CustomTransferFunction tf;
|
||||
|
||||
void ToExternal(JxlColorEncoding* external) const;
|
||||
Status FromExternal(const JxlColorEncoding& external);
|
||||
void ToExternal(JxlColorEncoding* external) const {
|
||||
storage_.ToExternal(external);
|
||||
}
|
||||
Status FromExternal(const JxlColorEncoding& external) {
|
||||
return storage_.FromExternal(external);
|
||||
}
|
||||
const jxl::cms::ColorEncoding& View() const { return storage_; }
|
||||
std::string Description() const;
|
||||
|
||||
private:
|
||||
// Returns true if all fields have been initialized (possibly to kUnknown).
|
||||
// Returns false if the ICC profile is invalid or decoding it fails.
|
||||
Status SetFieldsFromICC(const JxlCmsInterface& cms);
|
||||
|
||||
static std::array<ColorEncoding, 2> CreateC2(Primaries pr,
|
||||
TransferFunction tf);
|
||||
|
||||
// If true, the codestream contains an ICC profile and we do not serialize
|
||||
// fields. Otherwise, fields are serialized and we create an ICC profile.
|
||||
bool want_icc_;
|
||||
|
||||
::jxl::cms::ColorEncoding storage_;
|
||||
// Only used if white_point == kCustom.
|
||||
Customxy white_;
|
||||
|
||||
// Only valid if HaveFields()
|
||||
CustomTransferFunction tf_;
|
||||
|
||||
// Only used if primaries == kCustom.
|
||||
Customxy red_;
|
||||
Customxy green_;
|
||||
Customxy blue_;
|
||||
};
|
||||
|
||||
// Returns whether the two inputs are approximately equal.
|
||||
static inline bool ApproxEq(const double a, const double b,
|
||||
double max_l1 = 1E-3) {
|
||||
// Threshold should be sufficient for ICC's 15-bit fixed-point numbers.
|
||||
// We have seen differences of 7.1E-5 with lcms2 and 1E-3 with skcms.
|
||||
return std::abs(a - b) <= max_l1;
|
||||
static inline std::string Description(const ColorEncoding& c) {
|
||||
return Description(c.View());
|
||||
}
|
||||
|
||||
// Returns a representation of the ColorEncoding fields (not icc).
|
||||
// Example description: "RGB_D65_SRG_Rel_Lin"
|
||||
std::string Description(const ColorEncoding& c);
|
||||
static inline std::ostream& operator<<(std::ostream& os,
|
||||
const ColorEncoding& c) {
|
||||
return os << Description(c);
|
||||
}
|
||||
|
||||
Status PrimariesToXYZ(float rx, float ry, float gx, float gy, float bx,
|
||||
float by, float wx, float wy, float matrix[9]);
|
||||
Status PrimariesToXYZD50(float rx, float ry, float gx, float gy, float bx,
|
||||
float by, float wx, float wy, float matrix[9]);
|
||||
Status AdaptToXYZD50(float wx, float wy, float matrix[9]);
|
||||
|
||||
class ColorSpaceTransform {
|
||||
public:
|
||||
explicit ColorSpaceTransform(const JxlCmsInterface& cms) : cms_(cms) {}
|
||||
|
|
|
@ -5,6 +5,11 @@
|
|||
|
||||
#include "lib/jxl/color_encoding_internal.h"
|
||||
|
||||
#include <jxl/color_encoding.h>
|
||||
|
||||
#include <cstdlib> // rand
|
||||
|
||||
#include "lib/jxl/cms/color_encoding_cms.h"
|
||||
#include "lib/jxl/encode_internal.h"
|
||||
#include "lib/jxl/test_utils.h"
|
||||
#include "lib/jxl/testing.h"
|
||||
|
@ -12,21 +17,23 @@
|
|||
namespace jxl {
|
||||
namespace {
|
||||
|
||||
using jxl::cms::ColorEncoding;
|
||||
|
||||
TEST(ColorEncodingTest, RoundTripAll) {
|
||||
for (const test::ColorEncodingDescriptor& cdesc : test::AllEncodings()) {
|
||||
const ColorEncoding c_original = test::ColorEncodingFromDescriptor(cdesc);
|
||||
ColorEncoding c_original = test::ColorEncodingFromDescriptor(cdesc).View();
|
||||
// Verify Set(Get) yields the same white point/primaries/gamma.
|
||||
{
|
||||
ColorEncoding c;
|
||||
EXPECT_TRUE(c.SetWhitePoint(c_original.GetWhitePoint()));
|
||||
EXPECT_EQ(c_original.GetWhitePointType(), c.GetWhitePointType());
|
||||
EXPECT_EQ(c_original.white_point, c.white_point);
|
||||
}
|
||||
{
|
||||
ColorEncoding c;
|
||||
EXPECT_TRUE(c.SetPrimaries(c_original.GetPrimaries()));
|
||||
EXPECT_EQ(c_original.GetPrimariesType(), c.GetPrimariesType());
|
||||
EXPECT_EQ(c_original.primaries, c.primaries);
|
||||
}
|
||||
if (c_original.tf.IsGamma()) {
|
||||
if (c_original.tf.have_gamma) {
|
||||
ColorEncoding c;
|
||||
EXPECT_TRUE(c.tf.SetGamma(c_original.tf.GetGamma()));
|
||||
EXPECT_TRUE(c_original.tf.IsSame(c.tf));
|
||||
|
@ -74,26 +81,26 @@ TEST(ColorEncodingTest, CustomGamma) {
|
|||
EXPECT_FALSE(c.tf.SetGamma(1.001));
|
||||
#endif
|
||||
EXPECT_TRUE(c.tf.SetGamma(1.0));
|
||||
EXPECT_FALSE(c.tf.IsGamma());
|
||||
EXPECT_FALSE(c.tf.have_gamma);
|
||||
EXPECT_TRUE(c.tf.IsLinear());
|
||||
|
||||
EXPECT_TRUE(c.tf.SetGamma(0.123));
|
||||
EXPECT_TRUE(c.tf.IsGamma());
|
||||
EXPECT_TRUE(c.tf.have_gamma);
|
||||
const double gamma = c.tf.GetGamma();
|
||||
|
||||
ColorEncoding c2;
|
||||
EXPECT_TRUE(c2.tf.SetGamma(gamma));
|
||||
EXPECT_TRUE(c.SameColorEncoding(c2));
|
||||
EXPECT_TRUE(c2.tf.IsGamma());
|
||||
EXPECT_TRUE(c2.tf.have_gamma);
|
||||
}
|
||||
|
||||
TEST(ColorEncodingTest, InternalExternalConversion) {
|
||||
ColorEncoding source_internal;
|
||||
JxlColorEncoding external;
|
||||
JxlColorEncoding external = {};
|
||||
ColorEncoding destination_internal;
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
source_internal.SetColorSpace(static_cast<ColorSpace>(rand() % 4));
|
||||
source_internal.color_space = static_cast<ColorSpace>(rand() % 4);
|
||||
CIExy wp;
|
||||
wp.x = (float(rand()) / float((RAND_MAX)) * 0.5) + 0.25;
|
||||
wp.y = (float(rand()) / float((RAND_MAX)) * 0.5) + 0.25;
|
||||
|
@ -108,47 +115,41 @@ TEST(ColorEncodingTest, InternalExternalConversion) {
|
|||
primaries.b.y = (float(rand()) / float((RAND_MAX)) * 0.5) + 0.25;
|
||||
EXPECT_TRUE(source_internal.SetPrimaries(primaries));
|
||||
}
|
||||
CustomTransferFunction tf;
|
||||
jxl::cms::CustomTransferFunction tf;
|
||||
EXPECT_TRUE(tf.SetGamma((float(rand()) / float((RAND_MAX)) * 0.5) + 0.25));
|
||||
source_internal.tf = tf;
|
||||
ASSERT_TRUE(source_internal.SetRenderingIntent(
|
||||
static_cast<RenderingIntent>(rand() % 4)));
|
||||
source_internal.rendering_intent = static_cast<RenderingIntent>(rand() % 4);
|
||||
|
||||
source_internal.ToExternal(&external);
|
||||
EXPECT_TRUE(destination_internal.FromExternal(external));
|
||||
|
||||
EXPECT_EQ(source_internal.GetColorSpace(),
|
||||
destination_internal.GetColorSpace());
|
||||
EXPECT_EQ(source_internal.GetWhitePointType(),
|
||||
destination_internal.GetWhitePointType());
|
||||
EXPECT_EQ(source_internal.GetWhitePoint().x,
|
||||
destination_internal.GetWhitePoint().x);
|
||||
EXPECT_EQ(source_internal.GetWhitePoint().y,
|
||||
destination_internal.GetWhitePoint().y);
|
||||
EXPECT_EQ(source_internal.color_space, destination_internal.color_space);
|
||||
EXPECT_EQ(source_internal.white_point, destination_internal.white_point);
|
||||
CIExy src_wp = source_internal.GetWhitePoint();
|
||||
CIExy dst_wp = destination_internal.GetWhitePoint();
|
||||
EXPECT_EQ(src_wp.x, dst_wp.x);
|
||||
EXPECT_EQ(src_wp.y, dst_wp.y);
|
||||
if (source_internal.HasPrimaries()) {
|
||||
EXPECT_EQ(source_internal.GetPrimaries().r.x,
|
||||
destination_internal.GetPrimaries().r.x);
|
||||
EXPECT_EQ(source_internal.GetPrimaries().r.y,
|
||||
destination_internal.GetPrimaries().r.y);
|
||||
EXPECT_EQ(source_internal.GetPrimaries().g.x,
|
||||
destination_internal.GetPrimaries().g.x);
|
||||
EXPECT_EQ(source_internal.GetPrimaries().g.y,
|
||||
destination_internal.GetPrimaries().g.y);
|
||||
EXPECT_EQ(source_internal.GetPrimaries().b.x,
|
||||
destination_internal.GetPrimaries().b.x);
|
||||
EXPECT_EQ(source_internal.GetPrimaries().b.y,
|
||||
destination_internal.GetPrimaries().b.y);
|
||||
PrimariesCIExy src_p = source_internal.GetPrimaries();
|
||||
PrimariesCIExy dst_p = destination_internal.GetPrimaries();
|
||||
EXPECT_EQ(src_p.r.x, dst_p.r.x);
|
||||
EXPECT_EQ(src_p.r.y, dst_p.r.y);
|
||||
EXPECT_EQ(src_p.g.x, dst_p.g.x);
|
||||
EXPECT_EQ(src_p.g.y, dst_p.g.y);
|
||||
EXPECT_EQ(src_p.b.x, dst_p.b.x);
|
||||
EXPECT_EQ(src_p.b.y, dst_p.b.y);
|
||||
}
|
||||
EXPECT_EQ(source_internal.tf.IsGamma(), destination_internal.tf.IsGamma());
|
||||
if (source_internal.tf.IsGamma()) {
|
||||
EXPECT_EQ(source_internal.tf.have_gamma,
|
||||
destination_internal.tf.have_gamma);
|
||||
if (source_internal.tf.have_gamma) {
|
||||
EXPECT_EQ(source_internal.tf.GetGamma(),
|
||||
destination_internal.tf.GetGamma());
|
||||
} else {
|
||||
EXPECT_EQ(source_internal.tf.GetTransferFunction(),
|
||||
destination_internal.tf.GetTransferFunction());
|
||||
}
|
||||
EXPECT_EQ(source_internal.GetRenderingIntent(),
|
||||
destination_internal.GetRenderingIntent());
|
||||
EXPECT_EQ(source_internal.rendering_intent,
|
||||
destination_internal.rendering_intent);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,11 +11,13 @@
|
|||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "lib/jxl/base/common.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/padded_bytes.h"
|
||||
#include "lib/jxl/base/random.h"
|
||||
#include "lib/jxl/base/span.h"
|
||||
#include "lib/jxl/cms/color_encoding_cms.h"
|
||||
#include "lib/jxl/cms/jxl_cms.h"
|
||||
#include "lib/jxl/cms/opsin_params.h"
|
||||
#include "lib/jxl/color_encoding_internal.h"
|
||||
|
@ -71,16 +73,17 @@ MATCHER_P(HasSameFieldsAs, expected, "") {
|
|||
<< ToString(expected.GetPrimariesType());
|
||||
return false;
|
||||
}
|
||||
if (!arg.tf.IsSame(expected.tf)) {
|
||||
static const auto tf_to_string = [](const CustomTransferFunction& tf) {
|
||||
if (tf.IsGamma()) {
|
||||
return "g" + ToString(tf.GetGamma());
|
||||
}
|
||||
return ToString(tf.GetTransferFunction());
|
||||
};
|
||||
if (!arg.Tf().IsSame(expected.Tf())) {
|
||||
static const auto tf_to_string =
|
||||
[](const jxl::cms::CustomTransferFunction& tf) {
|
||||
if (tf.have_gamma) {
|
||||
return "g" + ToString(tf.GetGamma());
|
||||
}
|
||||
return ToString(tf.transfer_function);
|
||||
};
|
||||
*result_listener << "which has a different transfer function: "
|
||||
<< tf_to_string(arg.tf) << " instead of "
|
||||
<< tf_to_string(expected.tf);
|
||||
<< tf_to_string(arg.Tf()) << " instead of "
|
||||
<< tf_to_string(expected.Tf());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -164,7 +167,7 @@ class ColorManagementTest
|
|||
ColorSpaceTransform xform_fwd(cms);
|
||||
ColorSpaceTransform xform_rev(cms);
|
||||
const float intensity_target =
|
||||
c.tf.IsHLG() ? 1000 : kDefaultIntensityTarget;
|
||||
c.Tf().IsHLG() ? 1000 : kDefaultIntensityTarget;
|
||||
ASSERT_TRUE(
|
||||
xform_fwd.Init(c_native, c, intensity_target, kWidth, kNumThreads));
|
||||
ASSERT_TRUE(
|
||||
|
@ -246,15 +249,14 @@ TEST_F(ColorManagementTest, D2700Chromaticity) {
|
|||
}
|
||||
|
||||
TEST_F(ColorManagementTest, D2700ToSRGB) {
|
||||
const JxlCmsInterface& cms = *JxlGetDefaultCms();
|
||||
PaddedBytes icc_data =
|
||||
jxl::test::ReadTestData("jxl/color_management/sRGB-D2700.icc");
|
||||
IccBytes icc;
|
||||
Span<const uint8_t>(icc_data).AppendTo(&icc);
|
||||
ColorEncoding sRGB_D2700;
|
||||
ASSERT_TRUE(sRGB_D2700.SetICC(std::move(icc), &cms));
|
||||
ASSERT_TRUE(sRGB_D2700.SetICC(std::move(icc), JxlGetDefaultCms()));
|
||||
|
||||
ColorSpaceTransform transform(cms);
|
||||
ColorSpaceTransform transform(*JxlGetDefaultCms());
|
||||
ASSERT_TRUE(transform.Init(sRGB_D2700, ColorEncoding::SRGB(),
|
||||
kDefaultIntensityTarget, 1, 1));
|
||||
const float sRGB_D2700_values[3] = {0.863, 0.737, 0.490};
|
||||
|
@ -270,7 +272,7 @@ TEST_F(ColorManagementTest, P3HlgTo2020Hlg) {
|
|||
p3_hlg.SetColorSpace(ColorSpace::kRGB);
|
||||
ASSERT_TRUE(p3_hlg.SetWhitePointType(WhitePoint::kD65));
|
||||
ASSERT_TRUE(p3_hlg.SetPrimariesType(Primaries::kP3));
|
||||
p3_hlg.tf.SetTransferFunction(TransferFunction::kHLG);
|
||||
p3_hlg.Tf().SetTransferFunction(TransferFunction::kHLG);
|
||||
ASSERT_TRUE(p3_hlg.CreateICC());
|
||||
|
||||
ColorEncoding rec2020_hlg = p3_hlg;
|
||||
|
@ -292,7 +294,7 @@ TEST_F(ColorManagementTest, HlgOotf) {
|
|||
p3_hlg.SetColorSpace(ColorSpace::kRGB);
|
||||
ASSERT_TRUE(p3_hlg.SetWhitePointType(WhitePoint::kD65));
|
||||
ASSERT_TRUE(p3_hlg.SetPrimariesType(Primaries::kP3));
|
||||
p3_hlg.tf.SetTransferFunction(TransferFunction::kHLG);
|
||||
p3_hlg.Tf().SetTransferFunction(TransferFunction::kHLG);
|
||||
ASSERT_TRUE(p3_hlg.CreateICC());
|
||||
|
||||
ColorSpaceTransform transform_to_1000(*JxlGetDefaultCms());
|
||||
|
@ -335,7 +337,7 @@ TEST_F(ColorManagementTest, HlgOotf) {
|
|||
ColorEncoding grayscale_hlg;
|
||||
grayscale_hlg.SetColorSpace(ColorSpace::kGray);
|
||||
ASSERT_TRUE(grayscale_hlg.SetWhitePointType(WhitePoint::kD65));
|
||||
grayscale_hlg.tf.SetTransferFunction(TransferFunction::kHLG);
|
||||
grayscale_hlg.Tf().SetTransferFunction(TransferFunction::kHLG);
|
||||
ASSERT_TRUE(grayscale_hlg.CreateICC());
|
||||
|
||||
ColorSpaceTransform grayscale_transform(*JxlGetDefaultCms());
|
||||
|
@ -351,7 +353,7 @@ TEST_F(ColorManagementTest, HlgOotf) {
|
|||
TEST_F(ColorManagementTest, XYBProfile) {
|
||||
ColorEncoding c_xyb;
|
||||
c_xyb.SetColorSpace(ColorSpace::kXYB);
|
||||
ASSERT_TRUE(c_xyb.SetRenderingIntent(RenderingIntent::kPerceptual));
|
||||
c_xyb.SetRenderingIntent(RenderingIntent::kPerceptual);
|
||||
ASSERT_TRUE(c_xyb.CreateICC());
|
||||
ColorEncoding c_native = ColorEncoding::LinearSRGB(false);
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/color_encoding_internal.h"
|
||||
#include "lib/jxl/dec_cache.h"
|
||||
|
|
|
@ -199,9 +199,9 @@ bool CanOutputToColorEncoding(const ColorEncoding& c_desired) {
|
|||
return false;
|
||||
}
|
||||
// TODO(veluca): keep in sync with dec_reconstruct.cc
|
||||
if (!c_desired.tf.IsPQ() && !c_desired.tf.IsSRGB() &&
|
||||
!c_desired.tf.IsGamma() && !c_desired.tf.IsLinear() &&
|
||||
!c_desired.tf.IsHLG() && !c_desired.tf.IsDCI() && !c_desired.tf.Is709()) {
|
||||
const auto& tf = c_desired.Tf();
|
||||
if (!tf.IsPQ() && !tf.IsSRGB() && !tf.have_gamma && !tf.IsLinear() &&
|
||||
!tf.IsHLG() && !tf.IsDCI() && !tf.Is709()) {
|
||||
return false;
|
||||
}
|
||||
if (c_desired.IsGray() && c_desired.GetWhitePointType() != WhitePoint::kD65) {
|
||||
|
@ -239,7 +239,7 @@ Status OutputEncodingInfo::MaybeSetColorEncoding(
|
|||
if (c_desired.GetColorSpace() == ColorSpace::kXYB &&
|
||||
((color_encoding.GetColorSpace() == ColorSpace::kRGB &&
|
||||
color_encoding.GetPrimariesType() != Primaries::kSRGB) ||
|
||||
color_encoding.tf.IsPQ())) {
|
||||
color_encoding.Tf().IsPQ())) {
|
||||
return false;
|
||||
}
|
||||
if (!xyb_encoded && !CanOutputToColorEncoding(c_desired)) {
|
||||
|
@ -264,18 +264,15 @@ Status OutputEncodingInfo::SetColorEncoding(const ColorEncoding& c_desired) {
|
|||
!c_desired.IsGray()) {
|
||||
float srgb_to_xyzd50[9];
|
||||
const auto& srgb = ColorEncoding::SRGB(/*is_gray=*/false);
|
||||
JXL_CHECK(PrimariesToXYZD50(
|
||||
srgb.GetPrimaries().r.x, srgb.GetPrimaries().r.y,
|
||||
srgb.GetPrimaries().g.x, srgb.GetPrimaries().g.y,
|
||||
srgb.GetPrimaries().b.x, srgb.GetPrimaries().b.y,
|
||||
srgb.GetWhitePoint().x, srgb.GetWhitePoint().y, srgb_to_xyzd50));
|
||||
PrimariesCIExy p = srgb.GetPrimaries();
|
||||
CIExy w = srgb.GetWhitePoint();
|
||||
JXL_CHECK(PrimariesToXYZD50(p.r.x, p.r.y, p.g.x, p.g.y, p.b.x, p.b.y, w.x,
|
||||
w.y, srgb_to_xyzd50));
|
||||
float original_to_xyz[3][3];
|
||||
JXL_RETURN_IF_ERROR(PrimariesToXYZ(
|
||||
c_desired.GetPrimaries().r.x, c_desired.GetPrimaries().r.y,
|
||||
c_desired.GetPrimaries().g.x, c_desired.GetPrimaries().g.y,
|
||||
c_desired.GetPrimaries().b.x, c_desired.GetPrimaries().b.y,
|
||||
c_desired.GetWhitePoint().x, c_desired.GetWhitePoint().y,
|
||||
&original_to_xyz[0][0]));
|
||||
p = c_desired.GetPrimaries();
|
||||
w = c_desired.GetWhitePoint();
|
||||
JXL_RETURN_IF_ERROR(PrimariesToXYZ(p.r.x, p.r.y, p.g.x, p.g.y, p.b.x, p.b.y,
|
||||
w.x, w.y, &original_to_xyz[0][0]));
|
||||
memcpy(luminances, original_to_xyz[1], sizeof luminances);
|
||||
if (xyb_encoded) {
|
||||
float adapt_to_d50[9];
|
||||
|
@ -313,9 +310,10 @@ Status OutputEncodingInfo::SetColorEncoding(const ColorEncoding& c_desired) {
|
|||
}
|
||||
|
||||
// Set the inverse gamma based on color space transfer function.
|
||||
inverse_gamma = (c_desired.tf.IsGamma() ? c_desired.tf.GetGamma()
|
||||
: c_desired.tf.IsDCI() ? 1.0f / 2.6f
|
||||
: 1.0);
|
||||
const auto& tf = c_desired.Tf();
|
||||
inverse_gamma = (tf.have_gamma ? tf.GetGamma()
|
||||
: tf.IsDCI() ? 1.0f / 2.6f
|
||||
: 1.0);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -1071,11 +1071,12 @@ JxlDecoderStatus JxlDecoderReadAllHeaders(JxlDecoder* dec) {
|
|||
// Other non-successful status is an error
|
||||
return JXL_DEC_ERROR;
|
||||
}
|
||||
IccBytes icc;
|
||||
Span<const uint8_t>(decoded_icc).AppendTo(&icc);
|
||||
if (!dec->metadata.m.color_encoding.SetICCRaw(std::move(icc))) {
|
||||
if (decoded_icc.empty()) {
|
||||
return JXL_DEC_ERROR;
|
||||
}
|
||||
IccBytes icc;
|
||||
Span<const uint8_t>(decoded_icc).AppendTo(&icc);
|
||||
dec->metadata.m.color_encoding.SetICCRaw(std::move(icc));
|
||||
}
|
||||
|
||||
dec->got_all_headers = true;
|
||||
|
|
|
@ -1740,7 +1740,7 @@ TEST_P(DecodeAllEncodingsTest, PreserveOriginalProfileTest) {
|
|||
jxl::ColorEncoding c_in = jxl::test::ColorEncodingFromDescriptor(cdesc);
|
||||
if (c_in.GetRenderingIntent() != jxl::RenderingIntent::kRelative) return;
|
||||
std::string color_space_in = Description(c_in);
|
||||
float intensity_in = c_in.tf.IsPQ() ? 10000 : 255;
|
||||
float intensity_in = c_in.Tf().IsPQ() ? 10000 : 255;
|
||||
printf("Testing input color space %s\n", color_space_in.c_str());
|
||||
jxl::TestCodestreamParams params;
|
||||
params.color_space = color_space_in;
|
||||
|
@ -1785,7 +1785,7 @@ void SetPreferredColorProfileTest(
|
|||
jxl::test::GetSomeTestImage(xsize, ysize, num_channels, 0);
|
||||
JxlPixelFormat format = {num_channels, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
|
||||
std::string color_space_in = Description(c_in);
|
||||
float intensity_in = c_in.tf.IsPQ() ? 10000 : 255;
|
||||
float intensity_in = c_in.Tf().IsPQ() ? 10000 : 255;
|
||||
jxl::TestCodestreamParams params;
|
||||
params.color_space = color_space_in;
|
||||
params.intensity_target = intensity_in;
|
||||
|
@ -1811,7 +1811,7 @@ void SetPreferredColorProfileTest(
|
|||
continue;
|
||||
}
|
||||
}
|
||||
if (c_out.tf.IsHLG() && intensity_out > 300) {
|
||||
if (c_out.Tf().IsHLG() && intensity_out > 300) {
|
||||
// The Linear->HLG OOTF function at this intensity level can push
|
||||
// saturated colors out of gamut, so we would need gamut mapping in
|
||||
// this case too.
|
||||
|
@ -1837,7 +1837,8 @@ void SetPreferredColorProfileTest(
|
|||
JxlColorEncoding encoding_out;
|
||||
EXPECT_TRUE(jxl::ParseDescription(color_space_out, &encoding_out));
|
||||
if (c_out.GetColorSpace() == jxl::ColorSpace::kXYB &&
|
||||
(c_in.GetPrimariesType() != jxl::Primaries::kSRGB || c_in.tf.IsPQ())) {
|
||||
(c_in.GetPrimariesType() != jxl::Primaries::kSRGB ||
|
||||
c_in.Tf().IsPQ())) {
|
||||
EXPECT_EQ(JXL_DEC_ERROR,
|
||||
JxlDecoderSetPreferredColorProfile(dec, &encoding_out));
|
||||
JxlDecoderDestroy(dec);
|
||||
|
@ -3741,7 +3742,8 @@ void AnalyzeCodestream(const jxl::PaddedBytes& data,
|
|||
ASSERT_TRUE(jxl::ReadICC(&br, &icc_data));
|
||||
jxl::IccBytes icc;
|
||||
jxl::Span<const uint8_t>(icc_data).AppendTo(&icc);
|
||||
ASSERT_TRUE(metadata.m.color_encoding.SetICCRaw(std::move(icc)));
|
||||
ASSERT_TRUE(!icc.empty());
|
||||
metadata.m.color_encoding.SetICCRaw(std::move(icc));
|
||||
}
|
||||
ASSERT_TRUE(br.JumpToByteBoundary());
|
||||
bool has_preview = metadata.m.have_preview;
|
||||
|
|
|
@ -2497,10 +2497,10 @@ struct UpTo8Bits {
|
|||
GenericEncodeChunk(residuals, n, skip, code, output);
|
||||
}
|
||||
|
||||
size_t NumSymbols(bool doing_ycocg) const {
|
||||
size_t NumSymbols(bool doing_ycocg_or_large_palette) const {
|
||||
// values gain 1 bit for YCoCg, 1 bit for prediction.
|
||||
// Maximum symbol is 1 + effective bit depth of residuals.
|
||||
if (doing_ycocg) {
|
||||
if (doing_ycocg_or_large_palette) {
|
||||
return bitdepth + 3;
|
||||
} else {
|
||||
return bitdepth + 2;
|
||||
|
@ -2560,10 +2560,10 @@ struct From9To13Bits {
|
|||
GenericEncodeChunk(residuals, n, skip, code, output);
|
||||
}
|
||||
|
||||
size_t NumSymbols(bool doing_ycocg) const {
|
||||
size_t NumSymbols(bool doing_ycocg_or_large_palette) const {
|
||||
// values gain 1 bit for YCoCg, 1 bit for prediction.
|
||||
// Maximum symbol is 1 + effective bit depth of residuals.
|
||||
if (doing_ycocg) {
|
||||
if (doing_ycocg_or_large_palette) {
|
||||
return bitdepth + 3;
|
||||
} else {
|
||||
return bitdepth + 2;
|
||||
|
@ -3623,7 +3623,9 @@ JxlFastLosslessFrameState* LLEnc(const unsigned char* rgba, size_t width,
|
|||
5, 1, 1, 1, 1, 1, 1, 1, 1};
|
||||
|
||||
bool doing_ycocg = nb_chans > 2 && collided;
|
||||
for (size_t i = bitdepth.NumSymbols(doing_ycocg); i < kNumRawSymbols; i++) {
|
||||
bool large_palette = !collided || pcolors >= 256;
|
||||
for (size_t i = bitdepth.NumSymbols(doing_ycocg || large_palette);
|
||||
i < kNumRawSymbols; i++) {
|
||||
base_raw_counts[i] = 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -1091,12 +1091,18 @@ JxlEncoderStatus JxlEncoderSetICCProfile(JxlEncoder* enc,
|
|||
return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
|
||||
"ICC profile is already set");
|
||||
}
|
||||
if (size == 0) {
|
||||
return JXL_API_ERROR(enc, JXL_ENC_ERR_BAD_INPUT, "Empty ICC profile");
|
||||
}
|
||||
jxl::IccBytes icc;
|
||||
icc.assign(icc_profile, icc_profile + size);
|
||||
if (!enc->metadata.m.color_encoding.SetICC(
|
||||
std::move(icc), enc->cms_set ? &enc->cms : nullptr)) {
|
||||
return JXL_API_ERROR(enc, JXL_ENC_ERR_BAD_INPUT,
|
||||
"ICC profile could not be set");
|
||||
if (enc->cms_set) {
|
||||
if (!enc->metadata.m.color_encoding.SetICC(std::move(icc), &enc->cms)) {
|
||||
return JXL_API_ERROR(enc, JXL_ENC_ERR_BAD_INPUT,
|
||||
"ICC profile could not be set");
|
||||
}
|
||||
} else {
|
||||
enc->metadata.m.color_encoding.SetICCRaw(std::move(icc));
|
||||
}
|
||||
if (enc->metadata.m.color_encoding.GetColorSpace() ==
|
||||
jxl::ColorSpace::kGray) {
|
||||
|
@ -2005,12 +2011,8 @@ JxlEncoderStatus JxlEncoderAddJPEGFrame(
|
|||
}
|
||||
|
||||
if (!frame_settings->enc->color_encoding_set) {
|
||||
if (!SetColorEncodingFromJpegData(
|
||||
*io.Main().jpeg_data,
|
||||
&frame_settings->enc->metadata.m.color_encoding)) {
|
||||
return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_BAD_INPUT,
|
||||
"Error in input JPEG color space");
|
||||
}
|
||||
SetColorEncodingFromJpegData(
|
||||
*io.Main().jpeg_data, &frame_settings->enc->metadata.m.color_encoding);
|
||||
}
|
||||
|
||||
if (!frame_settings->enc->basic_info_set) {
|
||||
|
|
|
@ -325,7 +325,7 @@ struct ImageMetadata : public Fields {
|
|||
// must still use kNone (or kYCbCr, which would mean applying the YCbCr
|
||||
// transform to the 3-channel XYB data), since with !xyb_encoded, the 3
|
||||
// channels are stored as-is, no matter what meaning the color profile assigns
|
||||
// to them. To use ColorEncoding::kXYB, xyb_encoded must be true.
|
||||
// to them. To use ColorSpace::kXYB, xyb_encoded must be true.
|
||||
//
|
||||
// This value is defined in image metadata because this is the global
|
||||
// codestream header. This value does not affect the image itself, so is not
|
||||
|
|
|
@ -215,8 +215,8 @@ static inline bool IsJPG(const Span<const uint8_t> bytes) {
|
|||
|
||||
} // namespace
|
||||
|
||||
Status SetColorEncodingFromJpegData(const jpeg::JPEGData& jpg,
|
||||
ColorEncoding* color_encoding) {
|
||||
void SetColorEncodingFromJpegData(const jpeg::JPEGData& jpg,
|
||||
ColorEncoding* color_encoding) {
|
||||
IccBytes icc_profile;
|
||||
if (!ParseChunkedMarker(jpg, kApp2, ByteSpan(kIccProfileTag), &icc_profile)) {
|
||||
JXL_WARNING("ReJPEG: corrupted ICC profile\n");
|
||||
|
@ -226,10 +226,9 @@ Status SetColorEncodingFromJpegData(const jpeg::JPEGData& jpg,
|
|||
if (icc_profile.empty()) {
|
||||
bool is_gray = (jpg.components.size() == 1);
|
||||
*color_encoding = ColorEncoding::SRGB(is_gray);
|
||||
return true;
|
||||
} else {
|
||||
color_encoding->SetICCRaw(std::move(icc_profile));
|
||||
}
|
||||
|
||||
return color_encoding->SetICC(std::move(icc_profile), /*cms=*/nullptr);
|
||||
}
|
||||
|
||||
Status EncodeJPEGData(JPEGData& jpeg_data, PaddedBytes* bytes,
|
||||
|
@ -309,8 +308,7 @@ Status DecodeImageJPG(const Span<const uint8_t> bytes, CodecInOut* io) {
|
|||
jpeg_data)) {
|
||||
return JXL_FAILURE("Error reading JPEG");
|
||||
}
|
||||
JXL_RETURN_IF_ERROR(
|
||||
SetColorEncodingFromJpegData(*jpeg_data, &io->metadata.m.color_encoding));
|
||||
SetColorEncodingFromJpegData(*jpeg_data, &io->metadata.m.color_encoding);
|
||||
JXL_RETURN_IF_ERROR(SetBlobsFromJpegData(*jpeg_data, &io->blobs));
|
||||
size_t nbcomp = jpeg_data->components.size();
|
||||
if (nbcomp != 1 && nbcomp != 3) {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include "lib/jxl/base/padded_bytes.h"
|
||||
#include "lib/jxl/codec_in_out.h"
|
||||
#include "lib/jxl/color_encoding_internal.h"
|
||||
#include "lib/jxl/enc_params.h"
|
||||
#include "lib/jxl/jpeg/jpeg_data.h"
|
||||
|
||||
|
@ -16,8 +17,8 @@ namespace jpeg {
|
|||
Status EncodeJPEGData(JPEGData& jpeg_data, PaddedBytes* bytes,
|
||||
const CompressParams& cparams);
|
||||
|
||||
Status SetColorEncodingFromJpegData(const jpeg::JPEGData& jpg,
|
||||
ColorEncoding* color_encoding);
|
||||
void SetColorEncodingFromJpegData(const jpeg::JPEGData& jpg,
|
||||
ColorEncoding* color_encoding);
|
||||
|
||||
/**
|
||||
* Decodes bytes containing JPEG codestream into a CodecInOut as coefficients
|
||||
|
|
|
@ -627,7 +627,7 @@ TEST(JxlTest, RoundtripGrayscale) {
|
|||
EXPECT_EQ(8u, io.metadata.m.bit_depth.bits_per_sample);
|
||||
EXPECT_FALSE(io.metadata.m.bit_depth.floating_point_sample);
|
||||
EXPECT_EQ(0u, io.metadata.m.bit_depth.exponent_bits_per_sample);
|
||||
EXPECT_TRUE(io.metadata.m.color_encoding.tf.IsSRGB());
|
||||
EXPECT_TRUE(io.metadata.m.color_encoding.Tf().IsSRGB());
|
||||
|
||||
PassesEncoderState enc_state;
|
||||
AuxOut* aux_out = nullptr;
|
||||
|
@ -710,7 +710,7 @@ TEST(JxlTest, RoundtripAlpha) {
|
|||
EXPECT_EQ(8u, io.metadata.m.bit_depth.bits_per_sample);
|
||||
EXPECT_FALSE(io.metadata.m.bit_depth.floating_point_sample);
|
||||
EXPECT_EQ(0u, io.metadata.m.bit_depth.exponent_bits_per_sample);
|
||||
EXPECT_TRUE(io.metadata.m.color_encoding.tf.IsSRGB());
|
||||
EXPECT_TRUE(io.metadata.m.color_encoding.Tf().IsSRGB());
|
||||
PassesEncoderState enc_state;
|
||||
AuxOut* aux_out = nullptr;
|
||||
PaddedBytes compressed;
|
||||
|
@ -1574,10 +1574,11 @@ TEST(JxlTest, LosslessPNMRoundtrip) {
|
|||
}
|
||||
}
|
||||
|
||||
TEST(JxlTest, LosslessSmallFewColors) {
|
||||
class JxlTest : public ::testing::TestWithParam<const char*> {};
|
||||
|
||||
TEST_P(JxlTest, LosslessSmallFewColors) {
|
||||
ThreadPoolForTests pool(8);
|
||||
const PaddedBytes orig =
|
||||
jxl::test::ReadTestData("jxl/blending/cropped_traffic_light_frame-0.png");
|
||||
const PaddedBytes orig = jxl::test::ReadTestData(GetParam());
|
||||
TestImage t;
|
||||
t.DecodeFromBytes(orig).ClearMetadata();
|
||||
|
||||
|
@ -1586,9 +1587,14 @@ TEST(JxlTest, LosslessSmallFewColors) {
|
|||
cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 1);
|
||||
|
||||
PackedPixelFile ppf_out;
|
||||
EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 563, 30);
|
||||
Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out);
|
||||
EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0);
|
||||
}
|
||||
|
||||
JXL_GTEST_INSTANTIATE_TEST_SUITE_P(
|
||||
ImageTests, JxlTest,
|
||||
::testing::Values("jxl/blending/cropped_traffic_light_frame-0.png",
|
||||
"palette/358colors.png"));
|
||||
|
||||
} // namespace
|
||||
} // namespace jxl
|
||||
|
|
|
@ -10,10 +10,10 @@
|
|||
namespace jxl {
|
||||
|
||||
void SetIntensityTarget(ImageMetadata* m) {
|
||||
if (m->color_encoding.tf.IsPQ()) {
|
||||
if (m->color_encoding.Tf().IsPQ()) {
|
||||
// Peak luminance of PQ as defined by SMPTE ST 2084:2014.
|
||||
m->SetIntensityTarget(10000);
|
||||
} else if (m->color_encoding.tf.IsHLG()) {
|
||||
} else if (m->color_encoding.Tf().IsHLG()) {
|
||||
// Nominal display peak luminance used as a reference by
|
||||
// Rec. ITU-R BT.2100-2.
|
||||
m->SetIntensityTarget(1000);
|
||||
|
|
|
@ -369,7 +369,7 @@ TEST(ModularTest, RoundtripLosslessCustomFloat) {
|
|||
io.metadata.m.bit_depth.floating_point_sample = true;
|
||||
io.metadata.m.modular_16_bit_buffer_sufficient = false;
|
||||
ColorEncoding color_encoding;
|
||||
color_encoding.tf.SetTransferFunction(TransferFunction::kLinear);
|
||||
color_encoding.Tf().SetTransferFunction(TransferFunction::kLinear);
|
||||
color_encoding.SetColorSpace(ColorSpace::kRGB);
|
||||
Image3F testimage(xsize, ysize);
|
||||
float factor = 1.f / (1 << 14);
|
||||
|
|
|
@ -151,21 +151,21 @@ std::unique_ptr<FromLinearStage<Op>> MakeFromLinearStage(Op&& op) {
|
|||
|
||||
std::unique_ptr<RenderPipelineStage> GetFromLinearStage(
|
||||
const OutputEncodingInfo& output_encoding_info) {
|
||||
if (output_encoding_info.color_encoding.tf.IsLinear()) {
|
||||
const auto& tf = output_encoding_info.color_encoding.Tf();
|
||||
if (tf.IsLinear()) {
|
||||
return MakeFromLinearStage(MakePerChannelOp(OpLinear()));
|
||||
} else if (output_encoding_info.color_encoding.tf.IsSRGB()) {
|
||||
} else if (tf.IsSRGB()) {
|
||||
return MakeFromLinearStage(MakePerChannelOp(OpRgb()));
|
||||
} else if (output_encoding_info.color_encoding.tf.IsPQ()) {
|
||||
} else if (tf.IsPQ()) {
|
||||
return MakeFromLinearStage(
|
||||
MakePerChannelOp(OpPq(output_encoding_info.orig_intensity_target)));
|
||||
} else if (output_encoding_info.color_encoding.tf.IsHLG()) {
|
||||
} else if (tf.IsHLG()) {
|
||||
return MakeFromLinearStage(
|
||||
OpHlg(output_encoding_info.luminances,
|
||||
output_encoding_info.desired_intensity_target));
|
||||
} else if (output_encoding_info.color_encoding.tf.Is709()) {
|
||||
} else if (tf.Is709()) {
|
||||
return MakeFromLinearStage(MakePerChannelOp(Op709()));
|
||||
} else if (output_encoding_info.color_encoding.tf.IsGamma() ||
|
||||
output_encoding_info.color_encoding.tf.IsDCI()) {
|
||||
} else if (tf.have_gamma || tf.IsDCI()) {
|
||||
return MakeFromLinearStage(
|
||||
MakePerChannelOp(OpGamma{output_encoding_info.inverse_gamma}));
|
||||
} else {
|
||||
|
|
|
@ -162,20 +162,20 @@ std::unique_ptr<ToLinearStage<Op>> MakeToLinearStage(Op&& op) {
|
|||
|
||||
std::unique_ptr<RenderPipelineStage> GetToLinearStage(
|
||||
const OutputEncodingInfo& output_encoding_info) {
|
||||
if (output_encoding_info.color_encoding.tf.IsLinear()) {
|
||||
const auto& tf = output_encoding_info.color_encoding.Tf();
|
||||
if (tf.IsLinear()) {
|
||||
return MakeToLinearStage(MakePerChannelOp(OpLinear()));
|
||||
} else if (output_encoding_info.color_encoding.tf.IsSRGB()) {
|
||||
} else if (tf.IsSRGB()) {
|
||||
return MakeToLinearStage(MakePerChannelOp(OpRgb()));
|
||||
} else if (output_encoding_info.color_encoding.tf.IsPQ()) {
|
||||
} else if (tf.IsPQ()) {
|
||||
return MakeToLinearStage(
|
||||
MakePerChannelOp(OpPq(output_encoding_info.orig_intensity_target)));
|
||||
} else if (output_encoding_info.color_encoding.tf.IsHLG()) {
|
||||
} else if (tf.IsHLG()) {
|
||||
return MakeToLinearStage(OpHlg(output_encoding_info.luminances,
|
||||
output_encoding_info.orig_intensity_target));
|
||||
} else if (output_encoding_info.color_encoding.tf.Is709()) {
|
||||
} else if (tf.Is709()) {
|
||||
return MakeToLinearStage(MakePerChannelOp(Op709()));
|
||||
} else if (output_encoding_info.color_encoding.tf.IsGamma() ||
|
||||
output_encoding_info.color_encoding.tf.IsDCI()) {
|
||||
} else if (tf.have_gamma || tf.IsDCI()) {
|
||||
return MakeToLinearStage(
|
||||
MakePerChannelOp(OpGamma{1.f / output_encoding_info.inverse_gamma}));
|
||||
} else {
|
||||
|
|
|
@ -28,9 +28,9 @@ class ToneMappingStage : public RenderPipelineStage {
|
|||
// No tone mapping requested.
|
||||
return;
|
||||
}
|
||||
if (output_encoding_info_.orig_color_encoding.tf.IsPQ() &&
|
||||
output_encoding_info_.desired_intensity_target <
|
||||
output_encoding_info_.orig_intensity_target) {
|
||||
const auto& tf = output_encoding_info_.orig_color_encoding.Tf();
|
||||
if (tf.IsPQ() && output_encoding_info_.desired_intensity_target <
|
||||
output_encoding_info_.orig_intensity_target) {
|
||||
tone_mapper_ = jxl::make_unique<ToneMapper>(
|
||||
/*source_range=*/std::pair<float, float>(
|
||||
0, output_encoding_info_.orig_intensity_target),
|
||||
|
@ -38,16 +38,14 @@ class ToneMappingStage : public RenderPipelineStage {
|
|||
std::pair<float, float>(
|
||||
0, output_encoding_info_.desired_intensity_target),
|
||||
output_encoding_info_.luminances);
|
||||
} else if (output_encoding_info_.orig_color_encoding.tf.IsHLG() &&
|
||||
!output_encoding_info_.color_encoding.tf.IsHLG()) {
|
||||
} else if (tf.IsHLG() && !tf.IsHLG()) {
|
||||
hlg_ootf_ = jxl::make_unique<HlgOOTF>(
|
||||
/*source_luminance=*/output_encoding_info_.orig_intensity_target,
|
||||
/*target_luminance=*/output_encoding_info_.desired_intensity_target,
|
||||
output_encoding_info_.luminances);
|
||||
}
|
||||
|
||||
if (output_encoding_info_.color_encoding.tf.IsPQ() &&
|
||||
(tone_mapper_ || hlg_ootf_)) {
|
||||
if (tf.IsPQ() && (tone_mapper_ || hlg_ootf_)) {
|
||||
to_intensity_target_ =
|
||||
10000.f / output_encoding_info_.orig_intensity_target;
|
||||
from_desired_intensity_target_ =
|
||||
|
|
|
@ -123,9 +123,9 @@ ColorEncoding ColorEncodingFromDescriptor(const ColorEncodingDescriptor& desc) {
|
|||
if (desc.color_space != ColorSpace::kGray) {
|
||||
JXL_CHECK(c.SetPrimariesType(desc.primaries));
|
||||
}
|
||||
c.tf.SetTransferFunction(desc.tf);
|
||||
c.Tf().SetTransferFunction(desc.tf);
|
||||
}
|
||||
JXL_CHECK(c.SetRenderingIntent(desc.rendering_intent));
|
||||
c.SetRenderingIntent(desc.rendering_intent);
|
||||
JXL_CHECK(c.CreateICC());
|
||||
return c;
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ void CheckSameEncodings(const std::vector<ColorEncoding>& a,
|
|||
for (size_t i = 0; i < a.size(); ++i) {
|
||||
if ((a[i].ICC() == b[i].ICC()) ||
|
||||
((a[i].GetPrimariesType() == b[i].GetPrimariesType()) &&
|
||||
a[i].tf.IsSame(b[i].tf))) {
|
||||
a[i].Tf().IsSame(b[i].Tf()))) {
|
||||
continue;
|
||||
}
|
||||
failures << "CheckSameEncodings " << check_name << ": " << i
|
||||
|
@ -233,30 +233,19 @@ size_t Roundtrip(const extras::PackedPixelFile& ppf_in,
|
|||
std::vector<ColorEncodingDescriptor> AllEncodings() {
|
||||
std::vector<ColorEncodingDescriptor> all_encodings;
|
||||
all_encodings.reserve(300);
|
||||
ColorEncoding c;
|
||||
|
||||
for (ColorSpace cs : Values<ColorSpace>()) {
|
||||
if (cs == ColorSpace::kUnknown || cs == ColorSpace::kXYB) continue;
|
||||
c.SetColorSpace(cs);
|
||||
if (cs == ColorSpace::kUnknown || cs == ColorSpace::kXYB ||
|
||||
cs == ColorSpace::kGray) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (WhitePoint wp : Values<WhitePoint>()) {
|
||||
if (wp == WhitePoint::kCustom) continue;
|
||||
if (c.ImplicitWhitePoint() && c.GetWhitePointType() != wp) continue;
|
||||
JXL_CHECK(c.SetWhitePointType(wp));
|
||||
|
||||
for (Primaries primaries : Values<Primaries>()) {
|
||||
if (primaries == Primaries::kCustom) continue;
|
||||
if (!c.HasPrimaries()) continue;
|
||||
JXL_CHECK(c.SetPrimariesType(primaries));
|
||||
|
||||
for (TransferFunction tf : Values<TransferFunction>()) {
|
||||
if (tf == TransferFunction::kUnknown) continue;
|
||||
if (c.tf.SetImplicit() &&
|
||||
(c.tf.IsGamma() || c.tf.GetTransferFunction() != tf)) {
|
||||
continue;
|
||||
}
|
||||
c.tf.SetTransferFunction(tf);
|
||||
|
||||
for (RenderingIntent ri : Values<RenderingIntent>()) {
|
||||
ColorEncodingDescriptor cdesc;
|
||||
cdesc.color_space = cs;
|
||||
|
|
Загрузка…
Ссылка в новой задаче