diff options
Diffstat (limited to 'third_party/jpeg-xl/lib/jpegli/color_transform.cc')
-rw-r--r-- | third_party/jpeg-xl/lib/jpegli/color_transform.cc | 281 |
1 files changed, 281 insertions, 0 deletions
diff --git a/third_party/jpeg-xl/lib/jpegli/color_transform.cc b/third_party/jpeg-xl/lib/jpegli/color_transform.cc new file mode 100644 index 0000000000..020a6fd80c --- /dev/null +++ b/third_party/jpeg-xl/lib/jpegli/color_transform.cc @@ -0,0 +1,281 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "lib/jpegli/color_transform.h" + +#undef HWY_TARGET_INCLUDE +#define HWY_TARGET_INCLUDE "lib/jpegli/color_transform.cc" +#include <hwy/foreach_target.h> +#include <hwy/highway.h> + +#include "lib/jpegli/decode_internal.h" +#include "lib/jpegli/encode_internal.h" +#include "lib/jpegli/error.h" +#include "lib/jxl/base/compiler_specific.h" + +HWY_BEFORE_NAMESPACE(); +namespace jpegli { +namespace HWY_NAMESPACE { + +// These templates are not found via ADL. +using hwy::HWY_NAMESPACE::Add; +using hwy::HWY_NAMESPACE::Div; +using hwy::HWY_NAMESPACE::Mul; +using hwy::HWY_NAMESPACE::MulAdd; +using hwy::HWY_NAMESPACE::Sub; + +void YCbCrToRGB(float* row[kMaxComponents], size_t xsize) { + const HWY_CAPPED(float, 8) df; + float* JXL_RESTRICT row0 = row[0]; + float* JXL_RESTRICT row1 = row[1]; + float* JXL_RESTRICT row2 = row[2]; + + // Full-range BT.601 as defined by JFIF Clause 7: + // https://www.itu.int/rec/T-REC-T.871-201105-I/en + const auto crcr = Set(df, 1.402f); + const auto cgcb = Set(df, -0.114f * 1.772f / 0.587f); + const auto cgcr = Set(df, -0.299f * 1.402f / 0.587f); + const auto cbcb = Set(df, 1.772f); + + for (size_t x = 0; x < xsize; x += Lanes(df)) { + const auto y_vec = Load(df, row0 + x); + const auto cb_vec = Load(df, row1 + x); + const auto cr_vec = Load(df, row2 + x); + const auto r_vec = MulAdd(crcr, cr_vec, y_vec); + const auto g_vec = MulAdd(cgcr, cr_vec, MulAdd(cgcb, cb_vec, y_vec)); + const auto b_vec = MulAdd(cbcb, cb_vec, y_vec); + Store(r_vec, df, row0 + x); + Store(g_vec, df, row1 + x); + Store(b_vec, df, row2 + x); + } +} + +void YCCKToCMYK(float* row[kMaxComponents], size_t xsize) { + const HWY_CAPPED(float, 8) df; + float* JXL_RESTRICT row0 = row[0]; + float* JXL_RESTRICT row1 = row[1]; + float* JXL_RESTRICT row2 = row[2]; + YCbCrToRGB(row, xsize); + const auto offset = Set(df, -1.0f / 255.0f); + for (size_t x = 0; x < xsize; x += Lanes(df)) { + Store(Sub(offset, Load(df, row0 + x)), df, row0 + x); + Store(Sub(offset, Load(df, row1 + x)), df, row1 + x); + Store(Sub(offset, Load(df, row2 + x)), df, row2 + x); + } +} + +void RGBToYCbCr(float* row[kMaxComponents], size_t xsize) { + const HWY_CAPPED(float, 8) df; + float* JXL_RESTRICT row0 = row[0]; + float* JXL_RESTRICT row1 = row[1]; + float* JXL_RESTRICT row2 = row[2]; + // Full-range BT.601 as defined by JFIF Clause 7: + // https://www.itu.int/rec/T-REC-T.871-201105-I/en + const auto c128 = Set(df, 128.0f); + const auto kR = Set(df, 0.299f); // NTSC luma + const auto kG = Set(df, 0.587f); + const auto kB = Set(df, 0.114f); + const auto kAmpR = Set(df, 0.701f); + const auto kAmpB = Set(df, 0.886f); + const auto kDiffR = Add(kAmpR, kR); + const auto kDiffB = Add(kAmpB, kB); + const auto kNormR = Div(Set(df, 1.0f), (Add(kAmpR, Add(kG, kB)))); + const auto kNormB = Div(Set(df, 1.0f), (Add(kR, Add(kG, kAmpB)))); + + for (size_t x = 0; x < xsize; x += Lanes(df)) { + const auto r = Load(df, row0 + x); + const auto g = Load(df, row1 + x); + const auto b = Load(df, row2 + x); + const auto r_base = Mul(r, kR); + const auto r_diff = Mul(r, kDiffR); + const auto g_base = Mul(g, kG); + const auto b_base = Mul(b, kB); + const auto b_diff = Mul(b, kDiffB); + const auto y_base = Add(r_base, Add(g_base, b_base)); + const auto cb_vec = MulAdd(Sub(b_diff, y_base), kNormB, c128); + const auto cr_vec = MulAdd(Sub(r_diff, y_base), kNormR, c128); + Store(y_base, df, row0 + x); + Store(cb_vec, df, row1 + x); + Store(cr_vec, df, row2 + x); + } +} + +void CMYKToYCCK(float* row[kMaxComponents], size_t xsize) { + const HWY_CAPPED(float, 8) df; + float* JXL_RESTRICT row0 = row[0]; + float* JXL_RESTRICT row1 = row[1]; + float* JXL_RESTRICT row2 = row[2]; + const auto unity = Set(df, 255.0f); + for (size_t x = 0; x < xsize; x += Lanes(df)) { + Store(Sub(unity, Load(df, row0 + x)), df, row0 + x); + Store(Sub(unity, Load(df, row1 + x)), df, row1 + x); + Store(Sub(unity, Load(df, row2 + x)), df, row2 + x); + } + RGBToYCbCr(row, xsize); +} + +// NOLINTNEXTLINE(google-readability-namespace-comments) +} // namespace HWY_NAMESPACE +} // namespace jpegli +HWY_AFTER_NAMESPACE(); + +#if HWY_ONCE +namespace jpegli { + +HWY_EXPORT(CMYKToYCCK); +HWY_EXPORT(YCCKToCMYK); +HWY_EXPORT(YCbCrToRGB); +HWY_EXPORT(RGBToYCbCr); + +bool CheckColorSpaceComponents(int num_components, J_COLOR_SPACE colorspace) { + switch (colorspace) { + case JCS_GRAYSCALE: + return num_components == 1; + case JCS_RGB: + case JCS_YCbCr: + case JCS_EXT_RGB: + case JCS_EXT_BGR: + return num_components == 3; + case JCS_CMYK: + case JCS_YCCK: + case JCS_EXT_RGBX: + case JCS_EXT_BGRX: + case JCS_EXT_XBGR: + case JCS_EXT_XRGB: + case JCS_EXT_RGBA: + case JCS_EXT_BGRA: + case JCS_EXT_ABGR: + case JCS_EXT_ARGB: + return num_components == 4; + default: + // Unrecognized colorspaces can have any number of channels, since no + // color transform will be performed on them. + return true; + } +} + +void NullTransform(float* row[kMaxComponents], size_t len) {} + +void GrayscaleToRGB(float* row[kMaxComponents], size_t len) { + memcpy(row[1], row[0], len * sizeof(row[1][0])); + memcpy(row[2], row[0], len * sizeof(row[2][0])); +} + +void GrayscaleToYCbCr(float* row[kMaxComponents], size_t len) { + memset(row[1], 0, len * sizeof(row[1][0])); + memset(row[2], 0, len * sizeof(row[2][0])); +} + +void ChooseColorTransform(j_compress_ptr cinfo) { + jpeg_comp_master* m = cinfo->master; + if (!CheckColorSpaceComponents(cinfo->input_components, + cinfo->in_color_space)) { + JPEGLI_ERROR("Invalid number of input components %d for colorspace %d", + cinfo->input_components, cinfo->in_color_space); + } + if (!CheckColorSpaceComponents(cinfo->num_components, + cinfo->jpeg_color_space)) { + JPEGLI_ERROR("Invalid number of components %d for colorspace %d", + cinfo->num_components, cinfo->jpeg_color_space); + } + if (cinfo->jpeg_color_space == cinfo->in_color_space) { + if (cinfo->num_components != cinfo->input_components) { + JPEGLI_ERROR("Input/output components mismatch: %d vs %d", + cinfo->input_components, cinfo->num_components); + } + // No color transform requested. + m->color_transform = NullTransform; + return; + } + + if (cinfo->in_color_space == JCS_RGB && m->xyb_mode) { + JPEGLI_ERROR("Color transform on XYB colorspace is not supported."); + } + + m->color_transform = nullptr; + if (cinfo->jpeg_color_space == JCS_GRAYSCALE) { + if (cinfo->in_color_space == JCS_RGB) { + m->color_transform = HWY_DYNAMIC_DISPATCH(RGBToYCbCr); + } else if (cinfo->in_color_space == JCS_YCbCr || + cinfo->in_color_space == JCS_YCCK) { + // Since the first luminance channel is the grayscale version of the + // image, nothing to do here + m->color_transform = NullTransform; + } + } else if (cinfo->jpeg_color_space == JCS_RGB) { + if (cinfo->in_color_space == JCS_GRAYSCALE) { + m->color_transform = GrayscaleToRGB; + } + } else if (cinfo->jpeg_color_space == JCS_YCbCr) { + if (cinfo->in_color_space == JCS_RGB) { + m->color_transform = HWY_DYNAMIC_DISPATCH(RGBToYCbCr); + } else if (cinfo->in_color_space == JCS_GRAYSCALE) { + m->color_transform = GrayscaleToYCbCr; + } + } else if (cinfo->jpeg_color_space == JCS_YCCK) { + if (cinfo->in_color_space == JCS_CMYK) { + m->color_transform = HWY_DYNAMIC_DISPATCH(CMYKToYCCK); + } + } + + if (m->color_transform == nullptr) { + // TODO(szabadka) Support more color transforms. + JPEGLI_ERROR("Unsupported color transform %d -> %d", cinfo->in_color_space, + cinfo->jpeg_color_space); + } +} + +void ChooseColorTransform(j_decompress_ptr cinfo) { + jpeg_decomp_master* m = cinfo->master; + if (!CheckColorSpaceComponents(cinfo->out_color_components, + cinfo->out_color_space)) { + JPEGLI_ERROR("Invalid number of output components %d for colorspace %d", + cinfo->out_color_components, cinfo->out_color_space); + } + if (!CheckColorSpaceComponents(cinfo->num_components, + cinfo->jpeg_color_space)) { + JPEGLI_ERROR("Invalid number of components %d for colorspace %d", + cinfo->num_components, cinfo->jpeg_color_space); + } + if (cinfo->jpeg_color_space == cinfo->out_color_space) { + if (cinfo->num_components != cinfo->out_color_components) { + JPEGLI_ERROR("Input/output components mismatch: %d vs %d", + cinfo->num_components, cinfo->out_color_components); + } + // No color transform requested. + m->color_transform = NullTransform; + return; + } + + m->color_transform = nullptr; + if (cinfo->jpeg_color_space == JCS_GRAYSCALE) { + if (cinfo->out_color_space == JCS_RGB) { + m->color_transform = GrayscaleToRGB; + } + } else if (cinfo->jpeg_color_space == JCS_RGB) { + if (cinfo->out_color_space == JCS_GRAYSCALE) { + m->color_transform = HWY_DYNAMIC_DISPATCH(RGBToYCbCr); + } + } else if (cinfo->jpeg_color_space == JCS_YCbCr) { + if (cinfo->out_color_space == JCS_RGB) { + m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToRGB); + } else if (cinfo->out_color_space == JCS_GRAYSCALE) { + m->color_transform = NullTransform; + } + } else if (cinfo->jpeg_color_space == JCS_YCCK) { + if (cinfo->out_color_space == JCS_CMYK) { + m->color_transform = HWY_DYNAMIC_DISPATCH(YCCKToCMYK); + } + } + + if (m->color_transform == nullptr) { + // TODO(szabadka) Support more color transforms. + JPEGLI_ERROR("Unsupported color transform %d -> %d", + cinfo->jpeg_color_space, cinfo->out_color_space); + } +} + +} // namespace jpegli +#endif // HWY_ONCE |