summaryrefslogtreecommitdiff
path: root/media/libjxl/src/lib/extras
diff options
context:
space:
mode:
Diffstat (limited to 'media/libjxl/src/lib/extras')
-rw-r--r--media/libjxl/src/lib/extras/codec.cc77
-rw-r--r--media/libjxl/src/lib/extras/codec.h2
-rw-r--r--media/libjxl/src/lib/extras/codec_test.cc441
-rw-r--r--media/libjxl/src/lib/extras/dec/decode.cc19
-rw-r--r--media/libjxl/src/lib/extras/dec/decode.h22
-rw-r--r--media/libjxl/src/lib/extras/dec/jxl.cc480
-rw-r--r--media/libjxl/src/lib/extras/dec/jxl.h66
-rw-r--r--media/libjxl/src/lib/extras/dec/pnm.cc11
-rw-r--r--media/libjxl/src/lib/extras/enc/apng.cc235
-rw-r--r--media/libjxl/src/lib/extras/enc/apng.h13
-rw-r--r--media/libjxl/src/lib/extras/enc/encode.cc136
-rw-r--r--media/libjxl/src/lib/extras/enc/encode.h77
-rw-r--r--media/libjxl/src/lib/extras/enc/exr.cc211
-rw-r--r--media/libjxl/src/lib/extras/enc/exr.h15
-rw-r--r--media/libjxl/src/lib/extras/enc/jpg.cc225
-rw-r--r--media/libjxl/src/lib/extras/enc/jpg.h19
-rw-r--r--media/libjxl/src/lib/extras/enc/npy.cc322
-rw-r--r--media/libjxl/src/lib/extras/enc/npy.h23
-rw-r--r--media/libjxl/src/lib/extras/enc/pgx.cc129
-rw-r--r--media/libjxl/src/lib/extras/enc/pgx.h13
-rw-r--r--media/libjxl/src/lib/extras/enc/pnm.cc198
-rw-r--r--media/libjxl/src/lib/extras/enc/pnm.h16
-rw-r--r--media/libjxl/src/lib/extras/exif.cc55
-rw-r--r--media/libjxl/src/lib/extras/exif.h20
-rw-r--r--media/libjxl/src/lib/extras/packed_image.h56
-rw-r--r--media/libjxl/src/lib/extras/packed_image_convert.cc132
-rw-r--r--media/libjxl/src/lib/extras/render_hdr.cc60
-rw-r--r--media/libjxl/src/lib/extras/render_hdr.h27
-rw-r--r--media/libjxl/src/lib/extras/tone_mapping.cc118
-rw-r--r--media/libjxl/src/lib/extras/tone_mapping_gbench.cc3
30 files changed, 2410 insertions, 811 deletions
diff --git a/media/libjxl/src/lib/extras/codec.cc b/media/libjxl/src/lib/extras/codec.cc
index 80ed7c867c..774b4ccb6e 100644
--- a/media/libjxl/src/lib/extras/codec.cc
+++ b/media/libjxl/src/lib/extras/codec.cc
@@ -52,7 +52,7 @@ Status SetFromBytes(const Span<const uint8_t> bytes,
Status SetFromFile(const std::string& pathname,
const extras::ColorHints& color_hints, CodecInOut* io,
ThreadPool* pool, extras::Codec* orig_codec) {
- PaddedBytes encoded;
+ std::vector<uint8_t> encoded;
JXL_RETURN_IF_ERROR(ReadFile(pathname, &encoded));
JXL_RETURN_IF_ERROR(SetFromBytes(Span<const uint8_t>(encoded), color_hints,
io, pool, orig_codec));
@@ -61,67 +61,66 @@ Status SetFromFile(const std::string& pathname,
Status Encode(const CodecInOut& io, const extras::Codec codec,
const ColorEncoding& c_desired, size_t bits_per_sample,
- PaddedBytes* bytes, ThreadPool* pool) {
+ std::vector<uint8_t>* bytes, ThreadPool* pool) {
JXL_CHECK(!io.Main().c_current().ICC().empty());
JXL_CHECK(!c_desired.ICC().empty());
io.CheckMetadata();
if (io.Main().IsJPEG()) {
JXL_WARNING("Writing JPEG data as pixels");
}
-
- extras::PackedPixelFile ppf;
- size_t num_channels = io.metadata.m.color_encoding.Channels();
JxlPixelFormat format = {
- static_cast<uint32_t>(num_channels),
- bits_per_sample <= 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16,
- JXL_NATIVE_ENDIAN, 0};
- std::vector<uint8_t> bytes_vector;
+ 0, // num_channels is ignored by the converter
+ bits_per_sample <= 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16, JXL_BIG_ENDIAN,
+ 0};
const bool floating_point = bits_per_sample > 16;
+ std::unique_ptr<extras::Encoder> encoder;
+ std::ostringstream os;
switch (codec) {
case extras::Codec::kPNG:
#if JPEGXL_ENABLE_APNG
- return extras::EncodeImageAPNG(&io, c_desired, bits_per_sample, pool,
- bytes);
+ encoder = extras::GetAPNGEncoder();
+ break;
#else
return JXL_FAILURE("JPEG XL was built without (A)PNG support");
#endif
case extras::Codec::kJPG:
#if JPEGXL_ENABLE_JPEG
- return EncodeImageJPG(&io,
- io.use_sjpeg ? extras::JpegEncoder::kSJpeg
- : extras::JpegEncoder::kLibJpeg,
- io.jpeg_quality, YCbCrChromaSubsampling(), pool,
- bytes);
+ format.data_type = JXL_TYPE_UINT8;
+ encoder = extras::GetJPEGEncoder();
+ os << io.jpeg_quality;
+ encoder->SetOption("q", os.str());
+ break;
#else
return JXL_FAILURE("JPEG XL was built without JPEG support");
#endif
case extras::Codec::kPNM:
-
- // Choose native for PFM; PGM/PPM require big-endian (N/A for PBM)
- format.endianness = floating_point ? JXL_NATIVE_ENDIAN : JXL_BIG_ENDIAN;
- if (floating_point) {
+ if (io.Main().HasAlpha()) {
+ encoder = extras::GetPAMEncoder();
+ } else if (io.Main().IsGray()) {
+ encoder = extras::GetPGMEncoder();
+ } else if (!floating_point) {
+ encoder = extras::GetPPMEncoder();
+ } else {
format.data_type = JXL_TYPE_FLOAT;
+ format.endianness = JXL_NATIVE_ENDIAN;
+ encoder = extras::GetPFMEncoder();
}
if (!c_desired.IsSRGB()) {
JXL_WARNING(
- "PNM encoder cannot store custom ICC profile; decoder\n"
+ "PNM encoder cannot store custom ICC profile; decoder "
"will need hint key=color_space to get the same values");
}
- JXL_RETURN_IF_ERROR(extras::ConvertCodecInOutToPackedPixelFile(
- io, format, c_desired, pool, &ppf));
- JXL_RETURN_IF_ERROR(extras::EncodeImagePNM(
- ppf, bits_per_sample, pool, /*frame_index=*/0, &bytes_vector));
- bytes->assign(bytes_vector.data(),
- bytes_vector.data() + bytes_vector.size());
- return true;
+ break;
case extras::Codec::kPGX:
- return extras::EncodeImagePGX(&io, c_desired, bits_per_sample, pool,
- bytes);
+ encoder = extras::GetPGXEncoder();
+ break;
case extras::Codec::kGIF:
return JXL_FAILURE("Encoding to GIF is not implemented");
case extras::Codec::kEXR:
#if JPEGXL_ENABLE_EXR
- return extras::EncodeImageEXR(&io, c_desired, pool, bytes);
+ format.data_type = JXL_TYPE_FLOAT;
+ encoder = extras::GetEXREncoder();
+ break;
#else
return JXL_FAILURE("JPEG XL was built without OpenEXR support");
#endif
@@ -129,7 +128,19 @@ Status Encode(const CodecInOut& io, const extras::Codec codec,
return JXL_FAILURE("Cannot encode using Codec::kUnknown");
}
- return JXL_FAILURE("Invalid codec");
+ if (!encoder) {
+ return JXL_FAILURE("Invalid codec.");
+ }
+
+ extras::PackedPixelFile ppf;
+ JXL_RETURN_IF_ERROR(
+ ConvertCodecInOutToPackedPixelFile(io, format, c_desired, pool, &ppf));
+ extras::EncodedImage encoded_image;
+ JXL_RETURN_IF_ERROR(encoder->Encode(ppf, &encoded_image, pool));
+ JXL_ASSERT(encoded_image.bitstreams.size() == 1);
+ *bytes = encoded_image.bitstreams[0];
+
+ return true;
}
Status EncodeToFile(const CodecInOut& io, const ColorEncoding& c_desired,
@@ -163,7 +174,7 @@ Status EncodeToFile(const CodecInOut& io, const ColorEncoding& c_desired,
bits_per_sample = 16;
}
- PaddedBytes encoded;
+ std::vector<uint8_t> encoded;
return Encode(io, codec, c_desired, bits_per_sample, &encoded, pool) &&
WriteFile(encoded, pathname);
}
diff --git a/media/libjxl/src/lib/extras/codec.h b/media/libjxl/src/lib/extras/codec.h
index 88e96d308e..73fdc80be9 100644
--- a/media/libjxl/src/lib/extras/codec.h
+++ b/media/libjxl/src/lib/extras/codec.h
@@ -49,7 +49,7 @@ Status SetFromFile(const std::string& pathname,
// color space to c_desired.
Status Encode(const CodecInOut& io, extras::Codec codec,
const ColorEncoding& c_desired, size_t bits_per_sample,
- PaddedBytes* bytes, ThreadPool* pool = nullptr);
+ std::vector<uint8_t>* bytes, ThreadPool* pool = nullptr);
// Deduces codec, calls Encode and writes to file.
Status EncodeToFile(const CodecInOut& io, const ColorEncoding& c_desired,
diff --git a/media/libjxl/src/lib/extras/codec_test.cc b/media/libjxl/src/lib/extras/codec_test.cc
index d7d9b86954..19cac39979 100644
--- a/media/libjxl/src/lib/extras/codec_test.cc
+++ b/media/libjxl/src/lib/extras/codec_test.cc
@@ -9,12 +9,15 @@
#include <stdio.h>
#include <algorithm>
+#include <sstream>
+#include <string>
#include <utility>
#include <vector>
-#include "gtest/gtest.h"
#include "lib/extras/dec/pgx.h"
#include "lib/extras/dec/pnm.h"
+#include "lib/extras/enc/encode.h"
+#include "lib/extras/packed_image_convert.h"
#include "lib/jxl/base/printf_macros.h"
#include "lib/jxl/base/random.h"
#include "lib/jxl/base/thread_pool_internal.h"
@@ -23,12 +26,20 @@
#include "lib/jxl/image.h"
#include "lib/jxl/image_bundle.h"
#include "lib/jxl/image_test_utils.h"
+#include "lib/jxl/test_utils.h"
#include "lib/jxl/testdata.h"
namespace jxl {
namespace extras {
namespace {
+using ::testing::AllOf;
+using ::testing::Contains;
+using ::testing::Field;
+using ::testing::IsEmpty;
+using ::testing::NotNull;
+using ::testing::SizeIs;
+
std::string ExtensionFromCodec(Codec codec, const bool is_gray,
const bool has_alpha,
const size_t bits_per_sample) {
@@ -54,159 +65,233 @@ std::string ExtensionFromCodec(Codec codec, const bool is_gray,
return std::string();
}
-CodecInOut CreateTestImage(const size_t xsize, const size_t ysize,
- const bool is_gray, const bool add_alpha,
- const size_t bits_per_sample,
- const ColorEncoding& c_native) {
- Image3F image(xsize, ysize);
- Rng rng(129);
- if (is_gray) {
- for (size_t y = 0; y < ysize; ++y) {
- float* JXL_RESTRICT row0 = image.PlaneRow(0, y);
- float* JXL_RESTRICT row1 = image.PlaneRow(1, y);
- float* JXL_RESTRICT row2 = image.PlaneRow(2, y);
- for (size_t x = 0; x < xsize; ++x) {
- row0[x] = row1[x] = row2[x] = rng.UniformF(0.0f, 1.0f);
+void VerifySameImage(const PackedImage& im0, size_t bits_per_sample0,
+ const PackedImage& im1, size_t bits_per_sample1,
+ bool lossless = true) {
+ ASSERT_EQ(im0.xsize, im1.xsize);
+ ASSERT_EQ(im0.ysize, im1.ysize);
+ ASSERT_EQ(im0.format.num_channels, im1.format.num_channels);
+ auto get_factor = [](JxlPixelFormat f, size_t bits) -> double {
+ return 1.0 / ((1u << std::min(test::GetPrecision(f.data_type), bits)) - 1);
+ };
+ double factor0 = get_factor(im0.format, bits_per_sample0);
+ double factor1 = get_factor(im1.format, bits_per_sample1);
+ auto pixels0 = static_cast<const uint8_t*>(im0.pixels());
+ auto pixels1 = static_cast<const uint8_t*>(im1.pixels());
+ auto rgba0 =
+ test::ConvertToRGBA32(pixels0, im0.xsize, im0.ysize, im0.format, factor0);
+ auto rgba1 =
+ test::ConvertToRGBA32(pixels1, im1.xsize, im1.ysize, im1.format, factor1);
+ double tolerance =
+ lossless ? 0.5 * std::min(factor0, factor1) : 3.0f / 255.0f;
+ if (bits_per_sample0 == 32 || bits_per_sample1 == 32) {
+ tolerance = 0.5 * std::max(factor0, factor1);
+ }
+ for (size_t y = 0; y < im0.ysize; ++y) {
+ for (size_t x = 0; x < im0.xsize; ++x) {
+ for (size_t c = 0; c < im0.format.num_channels; ++c) {
+ size_t ix = (y * im0.xsize + x) * 4 + c;
+ double val0 = rgba0[ix];
+ double val1 = rgba1[ix];
+ ASSERT_NEAR(val1, val0, tolerance)
+ << "y = " << y << " x = " << x << " c = " << c;
}
}
- } else {
- RandomFillImage(&image, 0.0f, 1.0f);
}
- CodecInOut io;
+}
+
+JxlColorEncoding CreateTestColorEncoding(bool is_gray) {
+ JxlColorEncoding c;
+ c.color_space = is_gray ? JXL_COLOR_SPACE_GRAY : JXL_COLOR_SPACE_RGB;
+ c.white_point = JXL_WHITE_POINT_D65;
+ c.primaries = JXL_PRIMARIES_P3;
+ c.rendering_intent = JXL_RENDERING_INTENT_RELATIVE;
+ c.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR;
+ // Roundtrip through internal color encoding to fill in primaries and white
+ // point CIE xy coordinates.
+ ColorEncoding c_internal;
+ JXL_CHECK(ConvertExternalToInternalColorEncoding(c, &c_internal));
+ ConvertInternalToExternalColorEncoding(c_internal, &c);
+ return c;
+}
- if (bits_per_sample == 32) {
- io.metadata.m.SetFloat32Samples();
+std::vector<uint8_t> GenerateICC(JxlColorEncoding color_encoding) {
+ ColorEncoding c;
+ JXL_CHECK(ConvertExternalToInternalColorEncoding(color_encoding, &c));
+ JXL_CHECK(c.CreateICC());
+ PaddedBytes icc = c.ICC();
+ return std::vector<uint8_t>(icc.begin(), icc.end());
+}
+
+void StoreRandomValue(uint8_t* out, Rng* rng, JxlPixelFormat format,
+ size_t bits_per_sample) {
+ uint64_t max_val = (1ull << bits_per_sample) - 1;
+ if (format.data_type == JXL_TYPE_UINT8) {
+ *out = rng->UniformU(0, max_val);
+ } else if (format.data_type == JXL_TYPE_UINT16) {
+ uint32_t val = rng->UniformU(0, max_val);
+ if (format.endianness == JXL_BIG_ENDIAN) {
+ StoreBE16(val, out);
+ } else {
+ StoreLE16(val, out);
+ }
} else {
- io.metadata.m.SetUintSamples(bits_per_sample);
- }
- io.metadata.m.color_encoding = c_native;
- io.SetFromImage(std::move(image), c_native);
- if (add_alpha) {
- ImageF alpha(xsize, ysize);
- RandomFillImage(&alpha, 0.0f, 1.f);
- io.metadata.m.SetAlphaBits(bits_per_sample <= 8 ? 8 : 16);
- io.Main().SetAlpha(std::move(alpha), /*alpha_is_premultiplied=*/false);
+ ASSERT_EQ(format.data_type, JXL_TYPE_FLOAT);
+ float val = rng->UniformF(0.0, 1.0);
+ uint32_t uval;
+ memcpy(&uval, &val, 4);
+ if (format.endianness == JXL_BIG_ENDIAN) {
+ StoreBE32(uval, out);
+ } else {
+ StoreLE32(uval, out);
+ }
}
- return io;
}
-// Ensures reading a newly written file leads to the same image pixels.
-void TestRoundTrip(Codec codec, const size_t xsize, const size_t ysize,
- const bool is_gray, const bool add_alpha,
- const size_t bits_per_sample, ThreadPool* pool) {
- // JPEG encoding is not lossless.
- if (codec == Codec::kJPG) return;
- if (codec == Codec::kPNM && add_alpha) return;
- // Our EXR codec always uses 16-bit premultiplied alpha, does not support
- // grayscale, and somehow does not have sufficient precision for this test.
- if (codec == Codec::kEXR) return;
- printf("Codec %s bps:%" PRIuS " gr:%d al:%d\n",
- ExtensionFromCodec(codec, is_gray, add_alpha, bits_per_sample).c_str(),
- bits_per_sample, is_gray, add_alpha);
-
- ColorEncoding c_native;
- c_native.SetColorSpace(is_gray ? ColorSpace::kGray : ColorSpace::kRGB);
- // Note: this must not be wider than c_external, otherwise gamut clipping
- // will cause large round-trip errors.
- c_native.primaries = Primaries::kP3;
- c_native.tf.SetTransferFunction(TransferFunction::kLinear);
- JXL_CHECK(c_native.CreateICC());
-
- // Generally store same color space to reduce round trip errors..
- ColorEncoding c_external = c_native;
- // .. unless we have enough precision for some transforms.
- if (bits_per_sample >= 16) {
- c_external.white_point = WhitePoint::kE;
- c_external.primaries = Primaries::k2100;
- c_external.tf.SetTransferFunction(TransferFunction::kSRGB);
+void FillPackedImage(size_t bits_per_sample, PackedImage* image) {
+ JxlPixelFormat format = image->format;
+ size_t bytes_per_channel = PackedImage::BitsPerChannel(format.data_type) / 8;
+ uint8_t* out = static_cast<uint8_t*>(image->pixels());
+ size_t stride = image->xsize * format.num_channels * bytes_per_channel;
+ ASSERT_EQ(image->pixels_size, image->ysize * stride);
+ Rng rng(129);
+ for (size_t y = 0; y < image->ysize; ++y) {
+ for (size_t x = 0; x < image->xsize; ++x) {
+ for (size_t c = 0; c < format.num_channels; ++c) {
+ StoreRandomValue(out, &rng, format, bits_per_sample);
+ out += bytes_per_channel;
+ }
+ }
}
- JXL_CHECK(c_external.CreateICC());
+}
- const CodecInOut io = CreateTestImage(xsize, ysize, is_gray, add_alpha,
- bits_per_sample, c_native);
- const ImageBundle& ib1 = io.Main();
+struct TestImageParams {
+ Codec codec;
+ size_t xsize;
+ size_t ysize;
+ size_t bits_per_sample;
+ bool is_gray;
+ bool add_alpha;
+ bool big_endian;
+
+ bool ShouldTestRoundtrip() const {
+ if (codec == Codec::kPNG) {
+ return true;
+ } else if (codec == Codec::kPNM) {
+ // TODO(szabadka) Make PNM encoder endianness-aware.
+ return ((bits_per_sample <= 16 && big_endian) ||
+ (bits_per_sample == 32 && !add_alpha && !big_endian));
+ } else if (codec == Codec::kPGX) {
+ return ((bits_per_sample == 8 || bits_per_sample == 16) && is_gray &&
+ !add_alpha);
+ } else if (codec == Codec::kEXR) {
+#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
+ defined(THREAD_SANITIZER)
+ // OpenEXR 2.3 has a memory leak in IlmThread_2_3::ThreadPool
+ return false;
+#else
+ return bits_per_sample == 32 && !is_gray;
+#endif
+ } else if (codec == Codec::kJPG) {
+ return bits_per_sample == 8 && !add_alpha;
+ } else {
+ return false;
+ }
+ }
- PaddedBytes encoded;
- JXL_CHECK(Encode(io, codec, c_external, bits_per_sample, &encoded, pool));
+ JxlPixelFormat PixelFormat() const {
+ JxlPixelFormat format;
+ format.num_channels = (is_gray ? 1 : 3) + (add_alpha ? 1 : 0);
+ format.data_type = (bits_per_sample == 32 ? JXL_TYPE_FLOAT
+ : bits_per_sample > 8 ? JXL_TYPE_UINT16
+ : JXL_TYPE_UINT8);
+ format.endianness = big_endian ? JXL_BIG_ENDIAN : JXL_LITTLE_ENDIAN;
+ format.align = 0;
+ return format;
+ }
- CodecInOut io2;
- ColorHints color_hints;
- // Only for PNM because PNG will warn about ignoring them.
- if (codec == Codec::kPNM) {
- color_hints.Add("color_space", Description(c_external));
+ std::string DebugString() const {
+ std::ostringstream os;
+ os << "bps:" << bits_per_sample << " gr:" << is_gray << " al:" << add_alpha
+ << " be: " << big_endian;
+ return os.str();
}
- JXL_CHECK(SetFromBytes(Span<const uint8_t>(encoded), color_hints, &io2, pool,
- nullptr));
- ImageBundle& ib2 = io2.Main();
+};
+
+void CreateTestImage(const TestImageParams& params, PackedPixelFile* ppf) {
+ ppf->info.xsize = params.xsize;
+ ppf->info.ysize = params.ysize;
+ ppf->info.bits_per_sample = params.bits_per_sample;
+ ppf->info.exponent_bits_per_sample = params.bits_per_sample == 32 ? 8 : 0;
+ ppf->info.num_color_channels = params.is_gray ? 1 : 3;
+ ppf->info.alpha_bits = params.add_alpha ? params.bits_per_sample : 0;
+ ppf->info.alpha_premultiplied = (params.codec == Codec::kEXR);
+
+ JxlColorEncoding color_encoding = CreateTestColorEncoding(params.is_gray);
+ ppf->icc = GenerateICC(color_encoding);
+ ppf->color_encoding = color_encoding;
+
+ PackedFrame frame(params.xsize, params.ysize, params.PixelFormat());
+ FillPackedImage(params.bits_per_sample, &frame.color);
+ ppf->frames.emplace_back(std::move(frame));
+}
- EXPECT_EQ(Description(c_external),
- Description(io2.metadata.m.color_encoding));
+// Ensures reading a newly written file leads to the same image pixels.
+void TestRoundTrip(const TestImageParams& params, ThreadPool* pool) {
+ if (!params.ShouldTestRoundtrip()) return;
- // See c_external above - for low bits_per_sample the encoded space is
- // already the same.
- if (bits_per_sample < 16) {
- EXPECT_EQ(Description(ib1.c_current()), Description(ib2.c_current()));
- }
+ std::string extension = ExtensionFromCodec(
+ params.codec, params.is_gray, params.add_alpha, params.bits_per_sample);
+ printf("Codec %s %s\n", extension.c_str(), params.DebugString().c_str());
- if (add_alpha) {
- EXPECT_TRUE(SamePixels(ib1.alpha(), *ib2.alpha()));
- }
+ PackedPixelFile ppf_in;
+ CreateTestImage(params, &ppf_in);
- JXL_CHECK(ib2.TransformTo(ib1.c_current(), GetJxlCms(), pool));
+ EncodedImage encoded;
+ auto encoder = Encoder::FromExtension(extension);
+ ASSERT_TRUE(encoder.get());
+ ASSERT_TRUE(encoder->Encode(ppf_in, &encoded, pool));
+ ASSERT_EQ(encoded.bitstreams.size(), 1);
- double max_l1, max_rel;
- // Round-trip tolerances must be higher than in external_image_test because
- // codecs do not support unbounded ranges.
-#if JPEGXL_ENABLE_SKCMS
- if (bits_per_sample <= 12) {
- max_l1 = 0.5;
- max_rel = 6E-3;
- } else {
- max_l1 = 1E-3;
- max_rel = 5E-4;
- }
-#else // JPEGXL_ENABLE_SKCMS
- if (bits_per_sample <= 12) {
- max_l1 = 0.5;
- max_rel = 6E-3;
- } else if (bits_per_sample == 16) {
- max_l1 = 3E-3;
- max_rel = 1E-4;
- } else {
-#ifdef __ARM_ARCH
- // pow() implementation in arm is a bit less precise than in x86 and
- // therefore we need a bigger error margin in this case.
- max_l1 = 1E-7;
- max_rel = 1E-4;
-#else
- max_l1 = 1E-7;
- max_rel = 1E-5;
-#endif
+ PackedPixelFile ppf_out;
+ ASSERT_TRUE(DecodeBytes(Span<const uint8_t>(encoded.bitstreams[0]),
+ ColorHints(), SizeConstraints(), &ppf_out));
+
+ if (params.codec != Codec::kPNM && params.codec != Codec::kPGX &&
+ params.codec != Codec::kEXR) {
+ EXPECT_EQ(ppf_in.icc, ppf_out.icc);
}
-#endif // JPEGXL_ENABLE_SKCMS
- VerifyRelativeError(ib1.color(), *ib2.color(), max_l1, max_rel);
+ ASSERT_EQ(ppf_out.frames.size(), 1);
+ VerifySameImage(ppf_in.frames[0].color, ppf_in.info.bits_per_sample,
+ ppf_out.frames[0].color, ppf_out.info.bits_per_sample,
+ /*lossless=*/params.codec != Codec::kJPG);
}
-#if 0
TEST(CodecTest, TestRoundTrip) {
ThreadPoolInternal pool(12);
- const size_t xsize = 7;
- const size_t ysize = 4;
+ TestImageParams params;
+ params.xsize = 7;
+ params.ysize = 4;
- for (Codec codec : Values<Codec>()) {
- for (int bits_per_sample : {8, 10, 12, 16, 32}) {
+ for (Codec codec : AvailableCodecs()) {
+ for (int bits_per_sample : {4, 8, 10, 12, 16, 32}) {
for (bool is_gray : {false, true}) {
for (bool add_alpha : {false, true}) {
- TestRoundTrip(codec, xsize, ysize, is_gray, add_alpha,
- static_cast<size_t>(bits_per_sample), &pool);
+ for (bool big_endian : {false, true}) {
+ params.codec = codec;
+ params.bits_per_sample = static_cast<size_t>(bits_per_sample);
+ params.is_gray = is_gray;
+ params.add_alpha = add_alpha;
+ params.big_endian = big_endian;
+ TestRoundTrip(params, &pool);
+ }
}
}
}
}
}
-#endif
CodecInOut DecodeRoundtrip(const std::string& pathname, ThreadPool* pool,
const ColorHints& color_hints = ColorHints()) {
@@ -217,7 +302,7 @@ CodecInOut DecodeRoundtrip(const std::string& pathname, ThreadPool* pool,
const ImageBundle& ib1 = io.Main();
// Encode/Decode again to make sure Encode carries through all metadata.
- PaddedBytes encoded;
+ std::vector<uint8_t> encoded;
JXL_CHECK(Encode(io, Codec::kPNG, io.metadata.m.color_encoding,
io.metadata.m.bit_depth.bits_per_sample, &encoded, pool));
@@ -256,11 +341,11 @@ CodecInOut DecodeRoundtrip(const std::string& pathname, ThreadPool* pool,
TEST(CodecTest, TestMetadataSRGB) {
ThreadPoolInternal pool(12);
- const char* paths[] = {"third_party/raw.pixls/DJI-FC6310-16bit_srgb8_v4_krita.png",
- "third_party/raw.pixls/Google-Pixel2XL-16bit_srgb8_v4_krita.png",
- "third_party/raw.pixls/HUAWEI-EVA-L09-16bit_srgb8_dt.png",
- "third_party/raw.pixls/Nikon-D300-12bit_srgb8_dt.png",
- "third_party/raw.pixls/Sony-DSC-RX1RM2-14bit_srgb8_v4_krita.png"};
+ const char* paths[] = {"external/raw.pixls/DJI-FC6310-16bit_srgb8_v4_krita.png",
+ "external/raw.pixls/Google-Pixel2XL-16bit_srgb8_v4_krita.png",
+ "external/raw.pixls/HUAWEI-EVA-L09-16bit_srgb8_dt.png",
+ "external/raw.pixls/Nikon-D300-12bit_srgb8_dt.png",
+ "external/raw.pixls/Sony-DSC-RX1RM2-14bit_srgb8_v4_krita.png"};
for (const char* relative_pathname : paths) {
const CodecInOut io =
DecodeRoundtrip(relative_pathname, Codec::kPNG, &pool);
@@ -285,9 +370,9 @@ TEST(CodecTest, TestMetadataLinear) {
ThreadPoolInternal pool(12);
const char* paths[3] = {
- "third_party/raw.pixls/Google-Pixel2XL-16bit_acescg_g1_v4_krita.png",
- "third_party/raw.pixls/HUAWEI-EVA-L09-16bit_709_g1_dt.png",
- "third_party/raw.pixls/Nikon-D300-12bit_2020_g1_dt.png",
+ "external/raw.pixls/Google-Pixel2XL-16bit_acescg_g1_v4_krita.png",
+ "external/raw.pixls/HUAWEI-EVA-L09-16bit_709_g1_dt.png",
+ "external/raw.pixls/Nikon-D300-12bit_2020_g1_dt.png",
};
const WhitePoint white_points[3] = {WhitePoint::kCustom, WhitePoint::kD65,
WhitePoint::kD65};
@@ -317,8 +402,8 @@ TEST(CodecTest, TestMetadataICC) {
ThreadPoolInternal pool(12);
const char* paths[] = {
- "third_party/raw.pixls/DJI-FC6310-16bit_709_v4_krita.png",
- "third_party/raw.pixls/Sony-DSC-RX1RM2-14bit_709_v4_krita.png",
+ "external/raw.pixls/DJI-FC6310-16bit_709_v4_krita.png",
+ "external/raw.pixls/Sony-DSC-RX1RM2-14bit_709_v4_krita.png",
};
for (const char* relative_pathname : paths) {
const CodecInOut io =
@@ -340,28 +425,28 @@ TEST(CodecTest, TestMetadataICC) {
}
}
-TEST(CodecTest, Testthird_party/pngsuite) {
+TEST(CodecTest, Testexternal/pngsuite) {
ThreadPoolInternal pool(12);
// Ensure we can load PNG with text, japanese UTF-8, compressed text.
- (void)DecodeRoundtrip("third_party/pngsuite/ct1n0g04.png", Codec::kPNG, &pool);
- (void)DecodeRoundtrip("third_party/pngsuite/ctjn0g04.png", Codec::kPNG, &pool);
- (void)DecodeRoundtrip("third_party/pngsuite/ctzn0g04.png", Codec::kPNG, &pool);
+ (void)DecodeRoundtrip("external/pngsuite/ct1n0g04.png", Codec::kPNG, &pool);
+ (void)DecodeRoundtrip("external/pngsuite/ctjn0g04.png", Codec::kPNG, &pool);
+ (void)DecodeRoundtrip("external/pngsuite/ctzn0g04.png", Codec::kPNG, &pool);
// Extract gAMA
const CodecInOut b1 =
- DecodeRoundtrip("third_party/pngsuite/g10n3p04.png", Codec::kPNG, &pool);
+ DecodeRoundtrip("external/pngsuite/g10n3p04.png", Codec::kPNG, &pool);
EXPECT_TRUE(b1.metadata.color_encoding.tf.IsLinear());
// Extract cHRM
const CodecInOut b_p =
- DecodeRoundtrip("third_party/pngsuite/ccwn2c08.png", Codec::kPNG, &pool);
+ DecodeRoundtrip("external/pngsuite/ccwn2c08.png", Codec::kPNG, &pool);
EXPECT_EQ(Primaries::kSRGB, b_p.metadata.color_encoding.primaries);
EXPECT_EQ(WhitePoint::kD65, b_p.metadata.color_encoding.white_point);
// Extract EXIF from (new-style) dedicated chunk
const CodecInOut b_exif =
- DecodeRoundtrip("third_party/pngsuite/exif2c08.png", Codec::kPNG, &pool);
+ DecodeRoundtrip("external/pngsuite/exif2c08.png", Codec::kPNG, &pool);
EXPECT_EQ(978, b_exif.blobs.exif.size());
}
#endif
@@ -384,18 +469,88 @@ void VerifyWideGamutMetadata(const std::string& relative_pathname,
TEST(CodecTest, TestWideGamut) {
ThreadPoolInternal pool(12);
- // VerifyWideGamutMetadata("third_party/wide-gamut-tests/P3-sRGB-color-bars.png",
+ // VerifyWideGamutMetadata("external/wide-gamut-tests/P3-sRGB-color-bars.png",
// Primaries::kP3, &pool);
- VerifyWideGamutMetadata("third_party/wide-gamut-tests/P3-sRGB-color-ring.png",
+ VerifyWideGamutMetadata("external/wide-gamut-tests/P3-sRGB-color-ring.png",
Primaries::kP3, &pool);
- // VerifyWideGamutMetadata("third_party/wide-gamut-tests/R2020-sRGB-color-bars.png",
+ // VerifyWideGamutMetadata("external/wide-gamut-tests/R2020-sRGB-color-bars.png",
// Primaries::k2100, &pool);
- // VerifyWideGamutMetadata("third_party/wide-gamut-tests/R2020-sRGB-color-ring.png",
+ // VerifyWideGamutMetadata("external/wide-gamut-tests/R2020-sRGB-color-ring.png",
// Primaries::k2100, &pool);
}
TEST(CodecTest, TestPNM) { TestCodecPNM(); }
+TEST(CodecTest, FormatNegotiation) {
+ const std::vector<JxlPixelFormat> accepted_formats = {
+ {/*num_channels=*/4,
+ /*data_type=*/JXL_TYPE_UINT16,
+ /*endianness=*/JXL_NATIVE_ENDIAN,
+ /*align=*/0},
+ {/*num_channels=*/3,
+ /*data_type=*/JXL_TYPE_UINT8,
+ /*endianness=*/JXL_NATIVE_ENDIAN,
+ /*align=*/0},
+ {/*num_channels=*/3,
+ /*data_type=*/JXL_TYPE_UINT16,
+ /*endianness=*/JXL_NATIVE_ENDIAN,
+ /*align=*/0},
+ {/*num_channels=*/1,
+ /*data_type=*/JXL_TYPE_UINT8,
+ /*endianness=*/JXL_NATIVE_ENDIAN,
+ /*align=*/0},
+ };
+
+ JxlBasicInfo info;
+ JxlEncoderInitBasicInfo(&info);
+ info.bits_per_sample = 12;
+ info.num_color_channels = 2;
+
+ JxlPixelFormat format;
+ EXPECT_FALSE(SelectFormat(accepted_formats, info, &format));
+
+ info.num_color_channels = 3;
+ ASSERT_TRUE(SelectFormat(accepted_formats, info, &format));
+ EXPECT_EQ(format.num_channels, info.num_color_channels);
+ // 16 is the smallest accepted format that can accommodate the 12-bit data.
+ EXPECT_EQ(format.data_type, JXL_TYPE_UINT16);
+}
+
+TEST(CodecTest, EncodeToPNG) {
+ ThreadPool* const pool = nullptr;
+
+ std::unique_ptr<Encoder> png_encoder = Encoder::FromExtension(".png");
+ ASSERT_THAT(png_encoder, NotNull());
+
+ const PaddedBytes original_png =
+ ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
+ PackedPixelFile ppf;
+ ASSERT_TRUE(extras::DecodeBytes(Span<const uint8_t>(original_png),
+ ColorHints(), SizeConstraints(), &ppf));
+
+ const JxlPixelFormat& format = ppf.frames.front().color.format;
+ ASSERT_THAT(
+ png_encoder->AcceptedFormats(),
+ Contains(AllOf(Field(&JxlPixelFormat::num_channels, format.num_channels),
+ Field(&JxlPixelFormat::data_type, format.data_type),
+ Field(&JxlPixelFormat::endianness, format.endianness))));
+ EncodedImage encoded_png;
+ ASSERT_TRUE(png_encoder->Encode(ppf, &encoded_png, pool));
+ EXPECT_THAT(encoded_png.icc, IsEmpty());
+ ASSERT_THAT(encoded_png.bitstreams, SizeIs(1));
+
+ PackedPixelFile decoded_ppf;
+ ASSERT_TRUE(
+ extras::DecodeBytes(Span<const uint8_t>(encoded_png.bitstreams.front()),
+ ColorHints(), SizeConstraints(), &decoded_ppf));
+
+ ASSERT_EQ(decoded_ppf.info.bits_per_sample, ppf.info.bits_per_sample);
+ ASSERT_EQ(decoded_ppf.frames.size(), 1);
+ VerifySameImage(ppf.frames[0].color, ppf.info.bits_per_sample,
+ decoded_ppf.frames[0].color,
+ decoded_ppf.info.bits_per_sample);
+}
+
} // namespace
} // namespace extras
} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/dec/decode.cc b/media/libjxl/src/lib/extras/dec/decode.cc
index 55ce0ba3f2..8712e03aac 100644
--- a/media/libjxl/src/lib/extras/dec/decode.cc
+++ b/media/libjxl/src/lib/extras/dec/decode.cc
@@ -31,6 +31,25 @@ constexpr size_t kMinBytes = 9;
} // namespace
+std::vector<Codec> AvailableCodecs() {
+ std::vector<Codec> out;
+#if JPEGXL_ENABLE_APNG
+ out.push_back(Codec::kPNG);
+#endif
+#if JPEGXL_ENABLE_EXR
+ out.push_back(Codec::kEXR);
+#endif
+#if JPEGXL_ENABLE_GIF
+ out.push_back(Codec::kGIF);
+#endif
+#if JPEGXL_ENABLE_JPEG
+ out.push_back(Codec::kJPG);
+#endif
+ out.push_back(Codec::kPGX);
+ out.push_back(Codec::kPNM);
+ return out;
+}
+
Codec CodecFromExtension(std::string extension,
size_t* JXL_RESTRICT bits_per_sample) {
std::transform(
diff --git a/media/libjxl/src/lib/extras/dec/decode.h b/media/libjxl/src/lib/extras/dec/decode.h
index 32ec4c6368..7f0ff70aa8 100644
--- a/media/libjxl/src/lib/extras/dec/decode.h
+++ b/media/libjxl/src/lib/extras/dec/decode.h
@@ -12,15 +12,12 @@
#include <stdint.h>
#include <string>
+#include <vector>
#include "lib/extras/dec/color_hints.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/span.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/codec_in_out.h"
-#include "lib/jxl/field_encodings.h" // MakeBit
namespace jxl {
namespace extras {
@@ -36,27 +33,14 @@ enum class Codec : uint32_t {
kEXR
};
-static inline constexpr uint64_t EnumBits(Codec /*unused*/) {
- // Return only fully-supported codecs (kGIF is decode-only).
- return MakeBit(Codec::kPNM)
-#if JPEGXL_ENABLE_APNG
- | MakeBit(Codec::kPNG)
-#endif
-#if JPEGXL_ENABLE_JPEG
- | MakeBit(Codec::kJPG)
-#endif
-#if JPEGXL_ENABLE_EXR
- | MakeBit(Codec::kEXR)
-#endif
- ;
-}
+std::vector<Codec> AvailableCodecs();
// If and only if extension is ".pfm", *bits_per_sample is updated to 32 so
// that Encode() would encode to PFM instead of PPM.
Codec CodecFromExtension(std::string extension,
size_t* JXL_RESTRICT bits_per_sample = nullptr);
-// Decodes "bytes" and sets io->metadata.m.
+// Decodes "bytes" info *ppf.
// color_space_hint may specify the color space, otherwise, defaults to sRGB.
Status DecodeBytes(Span<const uint8_t> bytes, const ColorHints& color_hints,
const SizeConstraints& constraints,
diff --git a/media/libjxl/src/lib/extras/dec/jxl.cc b/media/libjxl/src/lib/extras/dec/jxl.cc
new file mode 100644
index 0000000000..0e1035646d
--- /dev/null
+++ b/media/libjxl/src/lib/extras/dec/jxl.cc
@@ -0,0 +1,480 @@
+// 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/extras/dec/jxl.h"
+
+#include "jxl/decode.h"
+#include "jxl/decode_cxx.h"
+#include "jxl/types.h"
+#include "lib/extras/dec/color_description.h"
+#include "lib/extras/enc/encode.h"
+#include "lib/jxl/base/printf_macros.h"
+
+namespace jxl {
+namespace extras {
+namespace {
+
+struct BoxProcessor {
+ BoxProcessor(JxlDecoder* dec) : dec_(dec) { Reset(); }
+
+ void InitializeOutput(std::vector<uint8_t>* out) {
+ box_data_ = out;
+ AddMoreOutput();
+ }
+
+ bool AddMoreOutput() {
+ Flush();
+ static const size_t kBoxOutputChunkSize = 1 << 16;
+ box_data_->resize(box_data_->size() + kBoxOutputChunkSize);
+ next_out_ = box_data_->data() + total_size_;
+ avail_out_ = box_data_->size() - total_size_;
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderSetBoxBuffer(dec_, next_out_, avail_out_)) {
+ fprintf(stderr, "JxlDecoderSetBoxBuffer failed\n");
+ return false;
+ }
+ return true;
+ }
+
+ void FinalizeOutput() {
+ if (box_data_ == nullptr) return;
+ Flush();
+ box_data_->resize(total_size_);
+ Reset();
+ }
+
+ private:
+ JxlDecoder* dec_;
+ std::vector<uint8_t>* box_data_;
+ uint8_t* next_out_;
+ size_t avail_out_;
+ size_t total_size_;
+
+ void Reset() {
+ box_data_ = nullptr;
+ next_out_ = nullptr;
+ avail_out_ = 0;
+ total_size_ = 0;
+ }
+ void Flush() {
+ if (box_data_ == nullptr) return;
+ size_t remaining = JxlDecoderReleaseBoxBuffer(dec_);
+ size_t bytes_written = avail_out_ - remaining;
+ next_out_ += bytes_written;
+ avail_out_ -= bytes_written;
+ total_size_ += bytes_written;
+ }
+};
+
+} // namespace
+
+bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
+ const JXLDecompressParams& dparams, size_t* decoded_bytes,
+ PackedPixelFile* ppf, std::vector<uint8_t>* jpeg_bytes) {
+ auto decoder = JxlDecoderMake(/*memory_manager=*/nullptr);
+ JxlDecoder* dec = decoder.get();
+ ppf->frames.clear();
+
+ if (dparams.runner_opaque != nullptr &&
+ JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec, dparams.runner,
+ dparams.runner_opaque)) {
+ fprintf(stderr, "JxlEncoderSetParallelRunner failed\n");
+ return false;
+ }
+
+ JxlPixelFormat format;
+ std::vector<JxlPixelFormat> accepted_formats = dparams.accepted_formats;
+ if (accepted_formats.empty()) {
+ for (const uint32_t num_channels : {1, 2, 3, 4}) {
+ accepted_formats.push_back(
+ {num_channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, /*align=*/0});
+ }
+ }
+ JxlColorEncoding color_encoding;
+ size_t num_color_channels = 0;
+ if (!dparams.color_space.empty()) {
+ if (!jxl::ParseDescription(dparams.color_space, &color_encoding)) {
+ fprintf(stderr, "Failed to parse color space %s.\n",
+ dparams.color_space.c_str());
+ return false;
+ }
+ num_color_channels =
+ color_encoding.color_space == JXL_COLOR_SPACE_GRAY ? 1 : 3;
+ }
+
+ bool can_reconstruct_jpeg = false;
+ std::vector<uint8_t> jpeg_data_chunk;
+ if (jpeg_bytes != nullptr) {
+ jpeg_data_chunk.resize(16384);
+ jpeg_bytes->resize(0);
+ }
+
+ int events = (JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE);
+
+ bool max_passes_defined =
+ (dparams.max_passes < std::numeric_limits<uint32_t>::max());
+ if (max_passes_defined || dparams.max_downsampling > 1) {
+ events |= JXL_DEC_FRAME_PROGRESSION;
+ if (max_passes_defined) {
+ JxlDecoderSetProgressiveDetail(dec, JxlProgressiveDetail::kPasses);
+ } else {
+ JxlDecoderSetProgressiveDetail(dec, JxlProgressiveDetail::kLastPasses);
+ }
+ }
+ if (jpeg_bytes != nullptr) {
+ events |= JXL_DEC_JPEG_RECONSTRUCTION;
+ } else {
+ events |= (JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME | JXL_DEC_PREVIEW_IMAGE |
+ JXL_DEC_BOX);
+ }
+ if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(dec, events)) {
+ fprintf(stderr, "JxlDecoderSubscribeEvents failed\n");
+ return false;
+ }
+ if (jpeg_bytes == nullptr) {
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderSetRenderSpotcolors(dec, dparams.render_spotcolors)) {
+ fprintf(stderr, "JxlDecoderSetRenderSpotColors failed\n");
+ return false;
+ }
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderSetKeepOrientation(dec, dparams.keep_orientation)) {
+ fprintf(stderr, "JxlDecoderSetKeepOrientation failed\n");
+ return false;
+ }
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderSetUnpremultiplyAlpha(dec, dparams.unpremultiply_alpha)) {
+ fprintf(stderr, "JxlDecoderSetUnpremultiplyAlpha failed\n");
+ return false;
+ }
+ if (dparams.display_nits > 0 &&
+ JXL_DEC_SUCCESS !=
+ JxlDecoderSetDesiredIntensityTarget(dec, dparams.display_nits)) {
+ fprintf(stderr, "Decoder failed to set desired intensity target\n");
+ return false;
+ }
+ if (JXL_DEC_SUCCESS != JxlDecoderSetDecompressBoxes(dec, JXL_TRUE)) {
+ fprintf(stderr, "JxlDecoderSetDecompressBoxes failed\n");
+ return false;
+ }
+ }
+ if (JXL_DEC_SUCCESS != JxlDecoderSetInput(dec, bytes, bytes_size)) {
+ fprintf(stderr, "Decoder failed to set input\n");
+ return false;
+ }
+ uint32_t progression_index = 0;
+ bool codestream_done = false;
+ BoxProcessor boxes(dec);
+ for (;;) {
+ JxlDecoderStatus status = JxlDecoderProcessInput(dec);
+ if (status == JXL_DEC_ERROR) {
+ fprintf(stderr, "Failed to decode image\n");
+ return false;
+ } else if (status == JXL_DEC_NEED_MORE_INPUT) {
+ if (codestream_done) {
+ break;
+ }
+ if (dparams.allow_partial_input) {
+ if (JXL_DEC_SUCCESS != JxlDecoderFlushImage(dec)) {
+ fprintf(stderr,
+ "Input file is truncated and there is no preview "
+ "available yet.\n");
+ return false;
+ }
+ break;
+ }
+ fprintf(stderr,
+ "Input file is truncated and allow_partial_input was disabled.");
+ return false;
+ } else if (status == JXL_DEC_BOX) {
+ boxes.FinalizeOutput();
+ JxlBoxType box_type;
+ if (JXL_DEC_SUCCESS != JxlDecoderGetBoxType(dec, box_type, JXL_TRUE)) {
+ fprintf(stderr, "JxlDecoderGetBoxType failed\n");
+ return false;
+ }
+ std::vector<uint8_t>* box_data = nullptr;
+ if (memcmp(box_type, "Exif", 4) == 0) {
+ box_data = &ppf->metadata.exif;
+ } else if (memcmp(box_type, "iptc", 4) == 0) {
+ box_data = &ppf->metadata.iptc;
+ } else if (memcmp(box_type, "jumb", 4) == 0) {
+ box_data = &ppf->metadata.jumbf;
+ } else if (memcmp(box_type, "xml ", 4) == 0) {
+ box_data = &ppf->metadata.xmp;
+ }
+ if (box_data) {
+ boxes.InitializeOutput(box_data);
+ }
+ } else if (status == JXL_DEC_BOX_NEED_MORE_OUTPUT) {
+ boxes.AddMoreOutput();
+ } else if (status == JXL_DEC_JPEG_RECONSTRUCTION) {
+ can_reconstruct_jpeg = true;
+ // Decoding to JPEG.
+ if (JXL_DEC_SUCCESS != JxlDecoderSetJPEGBuffer(dec,
+ jpeg_data_chunk.data(),
+ jpeg_data_chunk.size())) {
+ fprintf(stderr, "Decoder failed to set JPEG Buffer\n");
+ return false;
+ }
+ } else if (status == JXL_DEC_JPEG_NEED_MORE_OUTPUT) {
+ // Decoded a chunk to JPEG.
+ size_t used_jpeg_output =
+ jpeg_data_chunk.size() - JxlDecoderReleaseJPEGBuffer(dec);
+ jpeg_bytes->insert(jpeg_bytes->end(), jpeg_data_chunk.data(),
+ jpeg_data_chunk.data() + used_jpeg_output);
+ if (used_jpeg_output == 0) {
+ // Chunk is too small.
+ jpeg_data_chunk.resize(jpeg_data_chunk.size() * 2);
+ }
+ if (JXL_DEC_SUCCESS != JxlDecoderSetJPEGBuffer(dec,
+ jpeg_data_chunk.data(),
+ jpeg_data_chunk.size())) {
+ fprintf(stderr, "Decoder failed to set JPEG Buffer\n");
+ return false;
+ }
+ } else if (status == JXL_DEC_BASIC_INFO) {
+ if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec, &ppf->info)) {
+ fprintf(stderr, "JxlDecoderGetBasicInfo failed\n");
+ return false;
+ }
+ if (num_color_channels != 0) {
+ // Mark the change in number of color channels due to the requested
+ // color space.
+ ppf->info.num_color_channels = num_color_channels;
+ }
+ // Select format according to accepted formats.
+ if (!jxl::extras::SelectFormat(accepted_formats, ppf->info, &format)) {
+ fprintf(stderr, "SelectFormat failed\n");
+ return false;
+ }
+ bool have_alpha = (format.num_channels == 2 || format.num_channels == 4);
+ if (!have_alpha) {
+ // Mark in the basic info that alpha channel was dropped.
+ ppf->info.alpha_bits = 0;
+ } else if (dparams.unpremultiply_alpha) {
+ // Mark in the basic info that alpha was unpremultiplied.
+ ppf->info.alpha_premultiplied = false;
+ }
+ bool alpha_found = false;
+ for (uint32_t i = 0; i < ppf->info.num_extra_channels; ++i) {
+ JxlExtraChannelInfo eci;
+ if (JXL_DEC_SUCCESS != JxlDecoderGetExtraChannelInfo(dec, i, &eci)) {
+ fprintf(stderr, "JxlDecoderGetExtraChannelInfo failed\n");
+ return false;
+ }
+ if (eci.type == JXL_CHANNEL_ALPHA && have_alpha && !alpha_found) {
+ // Skip the first alpha channels because it is already present in the
+ // interleaved image.
+ alpha_found = true;
+ continue;
+ }
+ std::string name(eci.name_length + 1, 0);
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderGetExtraChannelName(dec, i, &name[0], name.size())) {
+ fprintf(stderr, "JxlDecoderGetExtraChannelName failed\n");
+ return false;
+ }
+ name.resize(eci.name_length);
+ ppf->extra_channels_info.push_back({eci, i, name});
+ }
+ } else if (status == JXL_DEC_COLOR_ENCODING) {
+ if (!dparams.color_space.empty()) {
+ if (ppf->info.uses_original_profile) {
+ fprintf(stderr,
+ "Warning: --color_space ignored because the image is "
+ "not XYB encoded.\n");
+ } else {
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderSetPreferredColorProfile(dec, &color_encoding)) {
+ fprintf(stderr, "Failed to set color space.\n");
+ return false;
+ }
+ }
+ }
+ size_t icc_size = 0;
+ JxlColorProfileTarget target = JXL_COLOR_PROFILE_TARGET_DATA;
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderGetICCProfileSize(dec, nullptr, target, &icc_size)) {
+ fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n");
+ }
+ if (icc_size != 0) {
+ ppf->icc.resize(icc_size);
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderGetColorAsICCProfile(dec, nullptr, target,
+ ppf->icc.data(), icc_size)) {
+ fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n");
+ return false;
+ }
+ }
+ if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsEncodedProfile(
+ dec, nullptr, target, &ppf->color_encoding)) {
+ ppf->color_encoding.color_space = JXL_COLOR_SPACE_UNKNOWN;
+ }
+ icc_size = 0;
+ target = JXL_COLOR_PROFILE_TARGET_ORIGINAL;
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderGetICCProfileSize(dec, nullptr, target, &icc_size)) {
+ fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n");
+ }
+ if (icc_size != 0) {
+ ppf->orig_icc.resize(icc_size);
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderGetColorAsICCProfile(dec, nullptr, target,
+ ppf->orig_icc.data(), icc_size)) {
+ fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n");
+ return false;
+ }
+ }
+ } else if (status == JXL_DEC_FRAME) {
+ jxl::extras::PackedFrame frame(ppf->info.xsize, ppf->info.ysize, format);
+ if (JXL_DEC_SUCCESS != JxlDecoderGetFrameHeader(dec, &frame.frame_info)) {
+ fprintf(stderr, "JxlDecoderGetFrameHeader failed\n");
+ return false;
+ }
+ frame.name.resize(frame.frame_info.name_length + 1, 0);
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderGetFrameName(dec, &frame.name[0], frame.name.size())) {
+ fprintf(stderr, "JxlDecoderGetFrameName failed\n");
+ return false;
+ }
+ frame.name.resize(frame.frame_info.name_length);
+ ppf->frames.emplace_back(std::move(frame));
+ progression_index = 0;
+ } else if (status == JXL_DEC_FRAME_PROGRESSION) {
+ size_t downsampling = JxlDecoderGetIntendedDownsamplingRatio(dec);
+ if ((max_passes_defined && progression_index >= dparams.max_passes) ||
+ (!max_passes_defined && downsampling <= dparams.max_downsampling)) {
+ if (JXL_DEC_SUCCESS != JxlDecoderFlushImage(dec)) {
+ fprintf(stderr, "JxlDecoderFlushImage failed\n");
+ return false;
+ }
+ if (ppf->frames.back().frame_info.is_last) {
+ break;
+ }
+ if (JXL_DEC_SUCCESS != JxlDecoderSkipCurrentFrame(dec)) {
+ fprintf(stderr, "JxlDecoderSkipCurrentFrame failed\n");
+ return false;
+ }
+ }
+ ++progression_index;
+ } else if (status == JXL_DEC_NEED_PREVIEW_OUT_BUFFER) {
+ size_t buffer_size;
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderPreviewOutBufferSize(dec, &format, &buffer_size)) {
+ fprintf(stderr, "JxlDecoderPreviewOutBufferSize failed\n");
+ return false;
+ }
+ ppf->preview_frame = std::unique_ptr<jxl::extras::PackedFrame>(
+ new jxl::extras::PackedFrame(ppf->info.preview.xsize,
+ ppf->info.preview.ysize, format));
+ if (buffer_size != ppf->preview_frame->color.pixels_size) {
+ fprintf(stderr, "Invalid out buffer size %" PRIuS " %" PRIuS "\n",
+ buffer_size, ppf->preview_frame->color.pixels_size);
+ return false;
+ }
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderSetPreviewOutBuffer(
+ dec, &format, ppf->preview_frame->color.pixels(), buffer_size)) {
+ fprintf(stderr, "JxlDecoderSetPreviewOutBuffer failed\n");
+ return false;
+ }
+ } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
+ if (jpeg_bytes != nullptr) {
+ break;
+ }
+ size_t buffer_size;
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderImageOutBufferSize(dec, &format, &buffer_size)) {
+ fprintf(stderr, "JxlDecoderImageOutBufferSize failed\n");
+ return false;
+ }
+ jxl::extras::PackedFrame& frame = ppf->frames.back();
+ if (buffer_size != frame.color.pixels_size) {
+ fprintf(stderr, "Invalid out buffer size %" PRIuS " %" PRIuS "\n",
+ buffer_size, frame.color.pixels_size);
+ return false;
+ }
+
+ if (dparams.use_image_callback) {
+ auto callback = [](void* opaque, size_t x, size_t y, size_t num_pixels,
+ const void* pixels) {
+ auto* ppf = reinterpret_cast<jxl::extras::PackedPixelFile*>(opaque);
+ jxl::extras::PackedImage& color = ppf->frames.back().color;
+ uint8_t* pixels_buffer = reinterpret_cast<uint8_t*>(color.pixels());
+ size_t sample_size = color.pixel_stride();
+ memcpy(pixels_buffer + (color.stride * y + sample_size * x), pixels,
+ num_pixels * sample_size);
+ };
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderSetImageOutCallback(dec, &format, callback, ppf)) {
+ fprintf(stderr, "JxlDecoderSetImageOutCallback failed\n");
+ return false;
+ }
+ } else {
+ if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec, &format,
+ frame.color.pixels(),
+ buffer_size)) {
+ fprintf(stderr, "JxlDecoderSetImageOutBuffer failed\n");
+ return false;
+ }
+ }
+ JxlPixelFormat ec_format = format;
+ ec_format.num_channels = 1;
+ for (const auto& eci : ppf->extra_channels_info) {
+ frame.extra_channels.emplace_back(jxl::extras::PackedImage(
+ ppf->info.xsize, ppf->info.ysize, ec_format));
+ auto& ec = frame.extra_channels.back();
+ size_t buffer_size;
+ if (JXL_DEC_SUCCESS != JxlDecoderExtraChannelBufferSize(
+ dec, &ec_format, &buffer_size, eci.index)) {
+ fprintf(stderr, "JxlDecoderExtraChannelBufferSize failed\n");
+ return false;
+ }
+ if (buffer_size != ec.pixels_size) {
+ fprintf(stderr,
+ "Invalid extra channel buffer size"
+ " %" PRIuS " %" PRIuS "\n",
+ buffer_size, ec.pixels_size);
+ return false;
+ }
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderSetExtraChannelBuffer(dec, &ec_format, ec.pixels(),
+ buffer_size, eci.index)) {
+ fprintf(stderr, "JxlDecoderSetExtraChannelBuffer failed\n");
+ return false;
+ }
+ }
+ } else if (status == JXL_DEC_SUCCESS) {
+ // Decoding finished successfully.
+ break;
+ } else if (status == JXL_DEC_PREVIEW_IMAGE) {
+ // Nothing to do.
+ } else if (status == JXL_DEC_FULL_IMAGE) {
+ if (jpeg_bytes != nullptr || ppf->frames.back().frame_info.is_last) {
+ codestream_done = true;
+ }
+ } else {
+ fprintf(stderr, "Error: unexpected status: %d\n",
+ static_cast<int>(status));
+ return false;
+ }
+ }
+ boxes.FinalizeOutput();
+ if (jpeg_bytes != nullptr) {
+ if (!can_reconstruct_jpeg) return false;
+ size_t used_jpeg_output =
+ jpeg_data_chunk.size() - JxlDecoderReleaseJPEGBuffer(dec);
+ jpeg_bytes->insert(jpeg_bytes->end(), jpeg_data_chunk.data(),
+ jpeg_data_chunk.data() + used_jpeg_output);
+ }
+ if (decoded_bytes) {
+ *decoded_bytes = bytes_size - JxlDecoderReleaseInput(dec);
+ }
+ return true;
+}
+
+} // namespace extras
+} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/dec/jxl.h b/media/libjxl/src/lib/extras/dec/jxl.h
new file mode 100644
index 0000000000..c462fa4b74
--- /dev/null
+++ b/media/libjxl/src/lib/extras/dec/jxl.h
@@ -0,0 +1,66 @@
+// 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.
+
+#ifndef LIB_EXTRAS_DEC_JXL_H_
+#define LIB_EXTRAS_DEC_JXL_H_
+
+// Decodes JPEG XL images in memory.
+
+#include <stdint.h>
+
+#include <limits>
+#include <string>
+#include <vector>
+
+#include "jxl/parallel_runner.h"
+#include "jxl/types.h"
+#include "lib/extras/packed_image.h"
+
+namespace jxl {
+namespace extras {
+
+struct JXLDecompressParams {
+ // If empty, little endian float formats will be accepted.
+ std::vector<JxlPixelFormat> accepted_formats;
+
+ // Requested output color space description.
+ std::string color_space;
+ // If set, performs tone mapping to this intensity target luminance.
+ float display_nits = 0.0;
+ // Whether spot colors are rendered on the image.
+ bool render_spotcolors = true;
+ // Whether to keep or undo the orientation given in the header.
+ bool keep_orientation = false;
+
+ // If runner_opaque is set, the decoder uses this parallel runner.
+ JxlParallelRunner runner;
+ void* runner_opaque = nullptr;
+
+ // Whether truncated input should be treated as an error.
+ bool allow_partial_input = false;
+
+ // How many passes to decode at most. By default, decode everything.
+ uint32_t max_passes = std::numeric_limits<uint32_t>::max();
+
+ // Alternatively, one can specify the maximum tolerable downscaling factor
+ // with respect to the full size of the image. By default, nothing less than
+ // the full size is requested.
+ size_t max_downsampling = 1;
+
+ // Whether to use the image callback or the image buffer to get the output.
+ bool use_image_callback = true;
+ // Whether to unpremultiply colors for associated alpha channels.
+ bool unpremultiply_alpha = false;
+};
+
+bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
+ const JXLDecompressParams& dparams, size_t* decoded_bytes,
+ PackedPixelFile* ppf,
+ std::vector<uint8_t>* jpeg_bytes = nullptr);
+
+} // namespace extras
+} // namespace jxl
+
+#endif // LIB_EXTRAS_DEC_JXL_H_
diff --git a/media/libjxl/src/lib/extras/dec/pnm.cc b/media/libjxl/src/lib/extras/dec/pnm.cc
index 93a42be321..03aecef29c 100644
--- a/media/libjxl/src/lib/extras/dec/pnm.cc
+++ b/media/libjxl/src/lib/extras/dec/pnm.cc
@@ -366,13 +366,18 @@ Status DecodeImagePNM(const Span<const uint8_t> bytes,
ppf->frames.emplace_back(header.xsize, header.ysize, format);
auto* frame = &ppf->frames.back();
- frame->color.bitdepth_from_format = false;
- frame->color.flipped_y = header.bits_per_sample == 32; // PFMs are flipped
size_t pnm_remaining_size = bytes.data() + bytes.size() - pos;
if (pnm_remaining_size < frame->color.pixels_size) {
return JXL_FAILURE("PNM file too small");
}
- memcpy(frame->color.pixels(), pos, frame->color.pixels_size);
+ const bool flipped_y = header.bits_per_sample == 32; // PFMs are flipped
+ uint8_t* out = reinterpret_cast<uint8_t*>(frame->color.pixels());
+ for (size_t y = 0; y < header.ysize; ++y) {
+ size_t y_in = flipped_y ? header.ysize - 1 - y : y;
+ const uint8_t* row_in = &pos[y_in * frame->color.stride];
+ uint8_t* row_out = &out[y * frame->color.stride];
+ memcpy(row_out, row_in, frame->color.stride);
+ }
return true;
}
diff --git a/media/libjxl/src/lib/extras/enc/apng.cc b/media/libjxl/src/lib/extras/enc/apng.cc
index 372b3b32ea..db6cf9ef4a 100644
--- a/media/libjxl/src/lib/extras/enc/apng.cc
+++ b/media/libjxl/src/lib/extras/enc/apng.cc
@@ -42,18 +42,9 @@
#include <string>
#include <vector>
-#include "jxl/encode.h"
-#include "lib/jxl/base/compiler_specific.h"
+#include "lib/extras/exif.h"
+#include "lib/jxl/base/byte_order.h"
#include "lib/jxl/base/printf_macros.h"
-#include "lib/jxl/color_encoding_internal.h"
-#include "lib/jxl/dec_external_image.h"
-#include "lib/jxl/enc_color_management.h"
-#include "lib/jxl/enc_image_bundle.h"
-#include "lib/jxl/exif.h"
-#include "lib/jxl/frame_header.h"
-#include "lib/jxl/headers.h"
-#include "lib/jxl/image.h"
-#include "lib/jxl/image_bundle.h"
#include "png.h" /* original (unpatched) libpng is ok */
namespace jxl {
@@ -61,15 +52,44 @@ namespace extras {
namespace {
+class APNGEncoder : public Encoder {
+ public:
+ std::vector<JxlPixelFormat> AcceptedFormats() const override {
+ std::vector<JxlPixelFormat> formats;
+ for (const uint32_t num_channels : {1, 2, 3, 4}) {
+ for (const JxlDataType data_type : {JXL_TYPE_UINT8, JXL_TYPE_UINT16}) {
+ formats.push_back(JxlPixelFormat{num_channels, data_type,
+ JXL_BIG_ENDIAN, /*align=*/0});
+ }
+ }
+ return formats;
+ }
+ Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
+ ThreadPool* pool) const override {
+ JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info));
+ encoded_image->icc.clear();
+ encoded_image->bitstreams.resize(1);
+ return EncodePackedPixelFileToAPNG(ppf, pool,
+ &encoded_image->bitstreams.front());
+ }
+
+ private:
+ Status EncodePackedPixelFileToAPNG(const PackedPixelFile& ppf,
+ ThreadPool* pool,
+ std::vector<uint8_t>* bytes) const;
+};
+
static void PngWrite(png_structp png_ptr, png_bytep data, png_size_t length) {
- PaddedBytes* bytes = static_cast<PaddedBytes*>(png_get_io_ptr(png_ptr));
- bytes->append(data, data + length);
+ std::vector<uint8_t>* bytes =
+ static_cast<std::vector<uint8_t>*>(png_get_io_ptr(png_ptr));
+ bytes->insert(bytes->end(), data, data + length);
}
// Stores XMP and EXIF/IPTC into key/value strings for PNG
class BlobsWriterPNG {
public:
- static Status Encode(const Blobs& blobs, std::vector<std::string>* strings) {
+ static Status Encode(const PackedMetadata& blobs,
+ std::vector<std::string>* strings) {
if (!blobs.exif.empty()) {
// PNG viewers typically ignore Exif orientation but not all of them do
// (and e.g. cjxl doesn't), so we overwrite the Exif orientation to the
@@ -122,26 +142,112 @@ class BlobsWriterPNG {
}
};
-} // namespace
+void MaybeAddCICP(JxlColorEncoding c_enc, png_structp png_ptr,
+ png_infop info_ptr) {
+ png_byte cicp_data[4] = {};
+ png_unknown_chunk cicp_chunk;
+ if (c_enc.color_space != JXL_COLOR_SPACE_RGB) {
+ return;
+ }
+ if (c_enc.primaries == JXL_PRIMARIES_P3) {
+ if (c_enc.white_point == JXL_WHITE_POINT_D65) {
+ cicp_data[0] = 12;
+ } else if (c_enc.white_point == JXL_WHITE_POINT_DCI) {
+ cicp_data[0] = 11;
+ } else {
+ return;
+ }
+ } else if (c_enc.primaries != JXL_PRIMARIES_CUSTOM &&
+ c_enc.white_point == JXL_WHITE_POINT_D65) {
+ cicp_data[0] = static_cast<png_byte>(c_enc.primaries);
+ } else {
+ return;
+ }
+ if (c_enc.transfer_function == JXL_TRANSFER_FUNCTION_UNKNOWN ||
+ c_enc.transfer_function == JXL_TRANSFER_FUNCTION_GAMMA) {
+ return;
+ }
+ cicp_data[1] = static_cast<png_byte>(c_enc.transfer_function);
+ cicp_data[2] = 0;
+ cicp_data[3] = 1;
+ cicp_chunk.data = cicp_data;
+ cicp_chunk.size = sizeof(cicp_data);
+ cicp_chunk.location = PNG_HAVE_PLTE;
+ memcpy(cicp_chunk.name, "cICP", 5);
+ png_set_keep_unknown_chunks(png_ptr, 3,
+ reinterpret_cast<const png_byte*>("cICP"), 1);
+ png_set_unknown_chunks(png_ptr, info_ptr, &cicp_chunk, 1);
+}
-Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
- size_t bits_per_sample, ThreadPool* pool,
- PaddedBytes* bytes) {
- if (bits_per_sample > 8) {
- bits_per_sample = 16;
- } else if (bits_per_sample < 8) {
- // PNG can also do 4, 2, and 1 bits per sample, but it isn't implemented
- bits_per_sample = 8;
+Status APNGEncoder::EncodePackedPixelFileToAPNG(
+ const PackedPixelFile& ppf, ThreadPool* pool,
+ std::vector<uint8_t>* bytes) const {
+ size_t xsize = ppf.info.xsize;
+ size_t ysize = ppf.info.ysize;
+ bool has_alpha = ppf.info.alpha_bits != 0;
+ bool is_gray = ppf.info.num_color_channels == 1;
+ size_t color_channels = ppf.info.num_color_channels;
+ size_t num_channels = color_channels + (has_alpha ? 1 : 0);
+ size_t num_samples = num_channels * xsize * ysize;
+
+ if (!ppf.info.have_animation && ppf.frames.size() != 1) {
+ return JXL_FAILURE("Invalid number of frames");
}
size_t count = 0;
- bool have_anim = io->metadata.m.have_animation;
size_t anim_chunks = 0;
- int W = 0, H = 0;
- for (size_t i = 0; i < io->frames.size(); i++) {
- auto& frame = io->frames[i];
- if (!have_anim && i + 1 < io->frames.size()) continue;
+ for (const auto& frame : ppf.frames) {
+ JXL_RETURN_IF_ERROR(VerifyPackedImage(frame.color, ppf.info));
+
+ const PackedImage& color = frame.color;
+ const JxlPixelFormat format = color.format;
+ const uint8_t* in = reinterpret_cast<const uint8_t*>(color.pixels());
+ size_t data_bits_per_sample = PackedImage::BitsPerChannel(format.data_type);
+ size_t bytes_per_sample = data_bits_per_sample / 8;
+ size_t out_bytes_per_sample = bytes_per_sample > 1 ? 2 : 1;
+ size_t out_stride = xsize * num_channels * out_bytes_per_sample;
+ size_t out_size = ysize * out_stride;
+ std::vector<uint8_t> out(out_size);
+
+ if (format.data_type == JXL_TYPE_UINT8) {
+ if (ppf.info.bits_per_sample < 8) {
+ float mul = 255.0 / ((1u << ppf.info.bits_per_sample) - 1);
+ for (size_t i = 0; i < num_samples; ++i) {
+ out[i] = static_cast<uint8_t>(in[i] * mul + 0.5);
+ }
+ } else {
+ memcpy(&out[0], in, out_size);
+ }
+ } else if (format.data_type == JXL_TYPE_UINT16) {
+ if (ppf.info.bits_per_sample < 16 ||
+ format.endianness != JXL_BIG_ENDIAN) {
+ float mul = 65535.0 / ((1u << ppf.info.bits_per_sample) - 1);
+ const uint8_t* p_in = in;
+ uint8_t* p_out = out.data();
+ for (size_t i = 0; i < num_samples; ++i, p_in += 2, p_out += 2) {
+ uint32_t val = (format.endianness == JXL_BIG_ENDIAN ? LoadBE16(p_in)
+ : LoadLE16(p_in));
+ StoreBE16(static_cast<uint32_t>(val * mul + 0.5), p_out);
+ }
+ } else {
+ memcpy(&out[0], in, out_size);
+ }
+ } else if (format.data_type == JXL_TYPE_FLOAT) {
+ float mul = 65535.0;
+ const uint8_t* p_in = in;
+ uint8_t* p_out = out.data();
+ for (size_t i = 0; i < num_samples; ++i, p_in += 4, p_out += 2) {
+ uint32_t val = (format.endianness == JXL_BIG_ENDIAN ? LoadBE32(p_in)
+ : LoadLE32(p_in));
+ float fval;
+ memcpy(&fval, &val, 4);
+ StoreBE16(static_cast<uint32_t>(fval * mul + 0.5), p_out);
+ }
+ } else {
+ return JXL_FAILURE("Unsupported pixel data type");
+ }
+
png_structp png_ptr;
png_infop info_ptr;
@@ -155,46 +261,24 @@ Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
png_set_write_fn(png_ptr, bytes, PngWrite, NULL);
png_set_flush(png_ptr, 0);
- ImageBundle ib = frame.Copy();
- const size_t alpha_bits = ib.HasAlpha() ? bits_per_sample : 0;
- ImageMetadata metadata = io->metadata.m;
- ImageBundle store(&metadata);
- const ImageBundle* transformed;
- JXL_RETURN_IF_ERROR(TransformIfNeeded(ib, c_desired, GetJxlCms(), pool,
- &store, &transformed));
- size_t stride = ib.oriented_xsize() *
- DivCeil(c_desired.Channels() * bits_per_sample + alpha_bits,
- kBitsPerByte);
- std::vector<uint8_t> raw_bytes(stride * ib.oriented_ysize());
- JXL_RETURN_IF_ERROR(ConvertToExternal(
- *transformed, bits_per_sample, /*float_out=*/false,
- c_desired.Channels() + (ib.HasAlpha() ? 1 : 0), JXL_BIG_ENDIAN, stride,
- pool, raw_bytes.data(), raw_bytes.size(),
- /*out_callback=*/{}, metadata.GetOrientation()));
-
- int width = ib.oriented_xsize();
- int height = ib.oriented_ysize();
-
- png_byte color_type =
- (c_desired.Channels() == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_GRAY);
- if (ib.HasAlpha()) color_type |= PNG_COLOR_MASK_ALPHA;
- png_byte bit_depth = bits_per_sample;
+ int width = xsize;
+ int height = ysize;
+
+ png_byte color_type = (is_gray ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_RGB);
+ if (has_alpha) color_type |= PNG_COLOR_MASK_ALPHA;
+ png_byte bit_depth = out_bytes_per_sample * 8;
png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
PNG_FILTER_TYPE_BASE);
if (count == 0) {
- W = width;
- H = height;
-
- // TODO(jon): instead of always setting an iCCP, could try to avoid that
- // have to avoid warnings on the ICC profile becoming fatal
- png_set_benign_errors(png_ptr, 1);
- png_set_iCCP(png_ptr, info_ptr, "1", 0, c_desired.ICC().data(),
- c_desired.ICC().size());
-
+ MaybeAddCICP(ppf.color_encoding, png_ptr, info_ptr);
+ if (!ppf.icc.empty()) {
+ png_set_benign_errors(png_ptr, 1);
+ png_set_iCCP(png_ptr, info_ptr, "1", 0, ppf.icc.data(), ppf.icc.size());
+ }
std::vector<std::string> textstrings;
- JXL_RETURN_IF_ERROR(BlobsWriterPNG::Encode(io->blobs, &textstrings));
+ JXL_RETURN_IF_ERROR(BlobsWriterPNG::Encode(ppf.metadata, &textstrings));
for (size_t kk = 0; kk + 1 < textstrings.size(); kk += 2) {
png_text text;
text.key = const_cast<png_charp>(textstrings[kk].c_str());
@@ -211,27 +295,24 @@ Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
bytes->resize(pos);
}
- if (have_anim) {
+ if (ppf.info.have_animation) {
if (count == 0) {
png_byte adata[8];
- png_save_uint_32(adata, io->frames.size());
- png_save_uint_32(adata + 4, io->metadata.m.animation.num_loops);
+ png_save_uint_32(adata, ppf.frames.size());
+ png_save_uint_32(adata + 4, ppf.info.animation.num_loops);
png_byte actl[5] = "acTL";
png_write_chunk(png_ptr, actl, adata, 8);
}
png_byte fdata[26];
- JXL_ASSERT(W == width);
- JXL_ASSERT(H == height);
// TODO(jon): also make this work for the non-coalesced case
png_save_uint_32(fdata, anim_chunks++);
png_save_uint_32(fdata + 4, width);
png_save_uint_32(fdata + 8, height);
png_save_uint_32(fdata + 12, 0);
png_save_uint_32(fdata + 16, 0);
- png_save_uint_16(
- fdata + 20,
- frame.duration * io->metadata.m.animation.tps_denominator);
- png_save_uint_16(fdata + 22, io->metadata.m.animation.tps_numerator);
+ png_save_uint_16(fdata + 20, frame.frame_info.duration *
+ ppf.info.animation.tps_denominator);
+ png_save_uint_16(fdata + 22, ppf.info.animation.tps_numerator);
fdata[24] = 1;
fdata[25] = 0;
png_byte fctl[5] = "fcTL";
@@ -240,7 +321,7 @@ Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
std::vector<uint8_t*> rows(height);
for (int y = 0; y < height; ++y) {
- rows[y] = raw_bytes.data() + y * stride;
+ rows[y] = out.data() + y * out_stride;
}
png_write_flush(png_ptr);
@@ -268,7 +349,9 @@ Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
}
count++;
- if (count == io->frames.size() || !have_anim) png_write_end(png_ptr, NULL);
+ if (count == ppf.frames.size() || !ppf.info.have_animation) {
+ png_write_end(png_ptr, NULL);
+ }
png_destroy_write_struct(&png_ptr, &info_ptr);
}
@@ -276,5 +359,11 @@ Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
return true;
}
+} // namespace
+
+std::unique_ptr<Encoder> GetAPNGEncoder() {
+ return jxl::make_unique<APNGEncoder>();
+}
+
} // namespace extras
} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/enc/apng.h b/media/libjxl/src/lib/extras/enc/apng.h
index 7f0fb94d9b..2a2139c8fa 100644
--- a/media/libjxl/src/lib/extras/enc/apng.h
+++ b/media/libjxl/src/lib/extras/enc/apng.h
@@ -8,19 +8,14 @@
// Encodes APNG images in memory.
-#include "lib/jxl/base/data_parallel.h"
-#include "lib/jxl/base/padded_bytes.h"
-#include "lib/jxl/base/span.h"
-#include "lib/jxl/base/status.h"
-#include "lib/jxl/codec_in_out.h"
+#include <memory>
+
+#include "lib/extras/enc/encode.h"
namespace jxl {
namespace extras {
-// Encodes `io` into `bytes`.
-Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
- size_t bits_per_sample, ThreadPool* pool,
- PaddedBytes* bytes);
+std::unique_ptr<Encoder> GetAPNGEncoder();
} // namespace extras
} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/enc/encode.cc b/media/libjxl/src/lib/extras/enc/encode.cc
new file mode 100644
index 0000000000..dc593d2900
--- /dev/null
+++ b/media/libjxl/src/lib/extras/enc/encode.cc
@@ -0,0 +1,136 @@
+// 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/extras/enc/encode.h"
+
+#include <locale>
+
+#if JPEGXL_ENABLE_APNG
+#include "lib/extras/enc/apng.h"
+#endif
+#if JPEGXL_ENABLE_EXR
+#include "lib/extras/enc/exr.h"
+#endif
+#if JPEGXL_ENABLE_JPEG
+#include "lib/extras/enc/jpg.h"
+#endif
+#include "lib/extras/enc/npy.h"
+#include "lib/extras/enc/pgx.h"
+#include "lib/extras/enc/pnm.h"
+#include "lib/jxl/base/printf_macros.h"
+
+namespace jxl {
+namespace extras {
+
+Status Encoder::VerifyBasicInfo(const JxlBasicInfo& info) const {
+ if (info.xsize == 0 || info.ysize == 0) {
+ return JXL_FAILURE("Empty image");
+ }
+ if (info.num_color_channels != 1 && info.num_color_channels != 3) {
+ return JXL_FAILURE("Invalid number of color channels");
+ }
+ if (info.alpha_bits > 0 && info.alpha_bits != info.bits_per_sample) {
+ return JXL_FAILURE("Alpha bit depth does not match image bit depth");
+ }
+ if (info.orientation != JXL_ORIENT_IDENTITY) {
+ return JXL_FAILURE("Orientation must be identity");
+ }
+ return true;
+}
+
+Status Encoder::VerifyPackedImage(const PackedImage& image,
+ const JxlBasicInfo& info) const {
+ if (image.pixels() == nullptr) {
+ return JXL_FAILURE("Invalid image.");
+ }
+ if (image.stride != image.xsize * image.pixel_stride()) {
+ return JXL_FAILURE("Invalid image stride.");
+ }
+ if (image.pixels_size != image.ysize * image.stride) {
+ return JXL_FAILURE("Invalid image size.");
+ }
+ size_t info_num_channels =
+ (info.num_color_channels + (info.alpha_bits > 0 ? 1 : 0));
+ if (image.xsize != info.xsize || image.ysize != info.ysize ||
+ image.format.num_channels != info_num_channels) {
+ return JXL_FAILURE("Frame size does not match image size");
+ }
+ if (info.bits_per_sample >
+ PackedImage::BitsPerChannel(image.format.data_type)) {
+ return JXL_FAILURE("Bit depth does not fit pixel data type");
+ }
+ return true;
+}
+
+Status SelectFormat(const std::vector<JxlPixelFormat>& accepted_formats,
+ const JxlBasicInfo& basic_info, JxlPixelFormat* format) {
+ const size_t original_bit_depth = basic_info.bits_per_sample;
+ size_t current_bit_depth = 0;
+ size_t num_alpha_channels = (basic_info.alpha_bits != 0 ? 1 : 0);
+ size_t num_channels = basic_info.num_color_channels + num_alpha_channels;
+ for (;;) {
+ for (const JxlPixelFormat& candidate : accepted_formats) {
+ if (candidate.num_channels != num_channels) continue;
+ const size_t candidate_bit_depth =
+ PackedImage::BitsPerChannel(candidate.data_type);
+ if (
+ // Candidate bit depth is less than what we have and still enough
+ (original_bit_depth <= candidate_bit_depth &&
+ candidate_bit_depth < current_bit_depth) ||
+ // Or larger than the too-small bit depth we currently have
+ (current_bit_depth < candidate_bit_depth &&
+ current_bit_depth < original_bit_depth)) {
+ *format = candidate;
+ current_bit_depth = candidate_bit_depth;
+ }
+ }
+ if (current_bit_depth == 0) {
+ if (num_channels > basic_info.num_color_channels) {
+ // Try dropping the alpha channel.
+ --num_channels;
+ continue;
+ }
+ return JXL_FAILURE("no appropriate format found");
+ }
+ break;
+ }
+ if (current_bit_depth < original_bit_depth) {
+ JXL_WARNING("encoding %" PRIuS "-bit original to %" PRIuS " bits",
+ original_bit_depth, current_bit_depth);
+ }
+ return true;
+}
+
+std::unique_ptr<Encoder> Encoder::FromExtension(std::string extension) {
+ std::transform(
+ extension.begin(), extension.end(), extension.begin(),
+ [](char c) { return std::tolower(c, std::locale::classic()); });
+#if JPEGXL_ENABLE_APNG
+ if (extension == ".png" || extension == ".apng") return GetAPNGEncoder();
+#endif
+
+#if JPEGXL_ENABLE_JPEG
+ if (extension == ".jpg") return GetJPEGEncoder();
+ if (extension == ".jpeg") return GetJPEGEncoder();
+#endif
+
+ if (extension == ".npy") return GetNumPyEncoder();
+
+ if (extension == ".pgx") return GetPGXEncoder();
+
+ if (extension == ".pam") return GetPAMEncoder();
+ if (extension == ".pgm") return GetPGMEncoder();
+ if (extension == ".ppm") return GetPPMEncoder();
+ if (extension == ".pfm") return GetPFMEncoder();
+
+#if JPEGXL_ENABLE_EXR
+ if (extension == ".exr") return GetEXREncoder();
+#endif
+
+ return nullptr;
+}
+
+} // namespace extras
+} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/enc/encode.h b/media/libjxl/src/lib/extras/enc/encode.h
new file mode 100644
index 0000000000..92eec50b6f
--- /dev/null
+++ b/media/libjxl/src/lib/extras/enc/encode.h
@@ -0,0 +1,77 @@
+// 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.
+
+#ifndef LIB_EXTRAS_ENC_ENCODE_H_
+#define LIB_EXTRAS_ENC_ENCODE_H_
+
+// Facade for image encoders.
+
+#include <string>
+#include <unordered_map>
+
+#include "lib/extras/dec/decode.h"
+#include "lib/jxl/base/data_parallel.h"
+#include "lib/jxl/base/status.h"
+
+namespace jxl {
+namespace extras {
+
+struct EncodedImage {
+ // One (if the format supports animations or the image has only one frame) or
+ // more sequential bitstreams.
+ std::vector<std::vector<uint8_t>> bitstreams;
+
+ // For each extra channel one or more sequential bitstreams.
+ std::vector<std::vector<std::vector<uint8_t>>> extra_channel_bitstreams;
+
+ std::vector<uint8_t> preview_bitstream;
+
+ // If the format does not support embedding color profiles into the bitstreams
+ // above, it will be present here, to be written as a separate file. If it
+ // does support them, this field will be empty.
+ std::vector<uint8_t> icc;
+
+ // Additional output for conformance testing, only filled in by NumPyEncoder.
+ std::vector<uint8_t> metadata;
+};
+
+class Encoder {
+ public:
+ static std::unique_ptr<Encoder> FromExtension(std::string extension);
+
+ virtual ~Encoder() = default;
+
+ virtual std::vector<JxlPixelFormat> AcceptedFormats() const = 0;
+
+ // Any existing data in encoded_image is discarded.
+ virtual Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
+ ThreadPool* pool = nullptr) const = 0;
+
+ void SetOption(std::string name, std::string value) {
+ options_[std::move(name)] = std::move(value);
+ }
+
+ protected:
+ const std::unordered_map<std::string, std::string>& options() const {
+ return options_;
+ }
+
+ Status VerifyBasicInfo(const JxlBasicInfo& info) const;
+
+ Status VerifyPackedImage(const PackedImage& image,
+ const JxlBasicInfo& info) const;
+
+ private:
+ std::unordered_map<std::string, std::string> options_;
+};
+
+// TODO(sboukortt): consider exposing this as part of the C API.
+Status SelectFormat(const std::vector<JxlPixelFormat>& accepted_formats,
+ const JxlBasicInfo& basic_info, JxlPixelFormat* format);
+
+} // namespace extras
+} // namespace jxl
+
+#endif // LIB_EXTRAS_ENC_ENCODE_H_
diff --git a/media/libjxl/src/lib/extras/enc/exr.cc b/media/libjxl/src/lib/extras/enc/exr.cc
index 3d88404888..05e05f96ce 100644
--- a/media/libjxl/src/lib/extras/enc/exr.cc
+++ b/media/libjxl/src/lib/extras/enc/exr.cc
@@ -12,11 +12,9 @@
#include <vector>
-#include "lib/jxl/alpha.h"
-#include "lib/jxl/color_encoding_internal.h"
-#include "lib/jxl/color_management.h"
-#include "lib/jxl/enc_color_management.h"
-#include "lib/jxl/enc_image_bundle.h"
+#include "jxl/codestream_header.h"
+#include "lib/extras/packed_image.h"
+#include "lib/jxl/base/byte_order.h"
namespace jxl {
namespace extras {
@@ -32,22 +30,10 @@ namespace Imath = IMATH_NAMESPACE;
// to uint64_t. This alternative should work in all cases.
using ExrInt64 = decltype(std::declval<OpenEXR::IStream>().tellg());
-size_t GetNumThreads(ThreadPool* pool) {
- size_t exr_num_threads = 1;
- JXL_CHECK(RunOnPool(
- pool, 0, 1,
- [&](size_t num_threads) {
- exr_num_threads = num_threads;
- return true;
- },
- [&](uint32_t /* task */, size_t /*thread*/) {}, "DecodeImageEXRThreads"));
- return exr_num_threads;
-}
-
class InMemoryOStream : public OpenEXR::OStream {
public:
// `bytes` must outlive the InMemoryOStream.
- explicit InMemoryOStream(PaddedBytes* const bytes)
+ explicit InMemoryOStream(std::vector<uint8_t>* const bytes)
: OStream(/*fileName=*/""), bytes_(*bytes) {}
void write(const char c[], const int n) override {
@@ -67,42 +53,69 @@ class InMemoryOStream : public OpenEXR::OStream {
}
private:
- PaddedBytes& bytes_;
+ std::vector<uint8_t>& bytes_;
size_t pos_ = 0;
};
-} // namespace
+// Loads a Big-Endian float
+float LoadBEFloat(const uint8_t* p) {
+ uint32_t u = LoadBE32(p);
+ float result;
+ memcpy(&result, &u, 4);
+ return result;
+}
+
+// Loads a Little-Endian float
+float LoadLEFloat(const uint8_t* p) {
+ uint32_t u = LoadLE32(p);
+ float result;
+ memcpy(&result, &u, 4);
+ return result;
+}
+
+Status EncodeImageEXR(const PackedImage& image, const JxlBasicInfo& info,
+ const JxlColorEncoding& c_enc, ThreadPool* pool,
+ std::vector<uint8_t>* bytes) {
+ OpenEXR::setGlobalThreadCount(0);
+
+ const size_t xsize = info.xsize;
+ const size_t ysize = info.ysize;
+ const bool has_alpha = info.alpha_bits > 0;
+ const bool alpha_is_premultiplied = info.alpha_premultiplied;
+
+ if (info.num_color_channels != 3 ||
+ c_enc.color_space != JXL_COLOR_SPACE_RGB ||
+ c_enc.transfer_function != JXL_TRANSFER_FUNCTION_LINEAR) {
+ return JXL_FAILURE("Unsupported color encoding for OpenEXR output.");
+ }
+
+ const size_t num_channels = 3 + (has_alpha ? 1 : 0);
+ const JxlPixelFormat format = image.format;
+
+ if (format.data_type != JXL_TYPE_FLOAT) {
+ return JXL_FAILURE("Unsupported pixel format for OpenEXR output");
+ }
-Status EncodeImageEXR(const CodecInOut* io, const ColorEncoding& c_desired,
- ThreadPool* pool, PaddedBytes* bytes) {
- // As in `DecodeImageEXR`, `pool` is only used for pixel conversion, not for
- // actual OpenEXR I/O.
- OpenEXR::setGlobalThreadCount(GetNumThreads(pool));
-
- ColorEncoding c_linear = c_desired;
- c_linear.tf.SetTransferFunction(TransferFunction::kLinear);
- JXL_RETURN_IF_ERROR(c_linear.CreateICC());
- ImageMetadata metadata = io->metadata.m;
- ImageBundle store(&metadata);
- const ImageBundle* linear;
- JXL_RETURN_IF_ERROR(TransformIfNeeded(io->Main(), c_linear, GetJxlCms(), pool,
- &store, &linear));
-
- const bool has_alpha = io->Main().HasAlpha();
- const bool alpha_is_premultiplied = io->Main().AlphaIsPremultiplied();
-
- OpenEXR::Header header(io->xsize(), io->ysize());
- const PrimariesCIExy& primaries = c_linear.HasPrimaries()
- ? c_linear.GetPrimaries()
- : ColorEncoding::SRGB().GetPrimaries();
+ const uint8_t* in = reinterpret_cast<const uint8_t*>(image.pixels());
+ size_t in_stride = num_channels * 4 * xsize;
+
+ OpenEXR::Header header(xsize, ysize);
OpenEXR::Chromaticities chromaticities;
- chromaticities.red = Imath::V2f(primaries.r.x, primaries.r.y);
- chromaticities.green = Imath::V2f(primaries.g.x, primaries.g.y);
- chromaticities.blue = Imath::V2f(primaries.b.x, primaries.b.y);
+ chromaticities.red =
+ Imath::V2f(c_enc.primaries_red_xy[0], c_enc.primaries_red_xy[1]);
+ chromaticities.green =
+ Imath::V2f(c_enc.primaries_green_xy[0], c_enc.primaries_green_xy[1]);
+ chromaticities.blue =
+ Imath::V2f(c_enc.primaries_blue_xy[0], c_enc.primaries_blue_xy[1]);
chromaticities.white =
- Imath::V2f(c_linear.GetWhitePoint().x, c_linear.GetWhitePoint().y);
+ Imath::V2f(c_enc.white_point_xy[0], c_enc.white_point_xy[1]);
OpenEXR::addChromaticities(header, chromaticities);
- OpenEXR::addWhiteLuminance(header, io->metadata.m.IntensityTarget());
+ OpenEXR::addWhiteLuminance(header, 255.0f);
+
+ auto loadFloat =
+ format.endianness == JXL_BIG_ENDIAN ? LoadBEFloat : LoadLEFloat;
+ auto loadAlpha =
+ has_alpha ? loadFloat : [](const uint8_t* p) -> float { return 1.0f; };
// Ensure that the destructor of RgbaOutputFile has run before we look at the
// size of `bytes`.
@@ -112,50 +125,32 @@ Status EncodeImageEXR(const CodecInOut* io, const ColorEncoding& c_desired,
os, header, has_alpha ? OpenEXR::WRITE_RGBA : OpenEXR::WRITE_RGB);
// How many rows to write at once. Again, the OpenEXR documentation
// recommends writing the whole image in one call.
- const int y_chunk_size = io->ysize();
- std::vector<OpenEXR::Rgba> output_rows(io->xsize() * y_chunk_size);
+ const int y_chunk_size = ysize;
+ std::vector<OpenEXR::Rgba> output_rows(xsize * y_chunk_size);
- for (size_t start_y = 0; start_y < io->ysize(); start_y += y_chunk_size) {
+ for (size_t start_y = 0; start_y < ysize; start_y += y_chunk_size) {
// Inclusive.
- const size_t end_y =
- std::min(start_y + y_chunk_size - 1, io->ysize() - 1);
- output.setFrameBuffer(output_rows.data() - start_y * io->xsize(),
- /*xStride=*/1, /*yStride=*/io->xsize());
- JXL_RETURN_IF_ERROR(RunOnPool(
- pool, start_y, end_y + 1, ThreadPool::NoInit,
- [&](const uint32_t y, size_t /* thread */) {
- const float* const JXL_RESTRICT input_rows[] = {
- linear->color().ConstPlaneRow(0, y),
- linear->color().ConstPlaneRow(1, y),
- linear->color().ConstPlaneRow(2, y),
- };
- OpenEXR::Rgba* const JXL_RESTRICT row_data =
- &output_rows[(y - start_y) * io->xsize()];
- if (has_alpha) {
- const float* const JXL_RESTRICT alpha_row =
- io->Main().alpha().ConstRow(y);
- if (alpha_is_premultiplied) {
- for (size_t x = 0; x < io->xsize(); ++x) {
- row_data[x] =
- OpenEXR::Rgba(input_rows[0][x], input_rows[1][x],
- input_rows[2][x], alpha_row[x]);
- }
- } else {
- for (size_t x = 0; x < io->xsize(); ++x) {
- row_data[x] = OpenEXR::Rgba(alpha_row[x] * input_rows[0][x],
- alpha_row[x] * input_rows[1][x],
- alpha_row[x] * input_rows[2][x],
- alpha_row[x]);
- }
- }
- } else {
- for (size_t x = 0; x < io->xsize(); ++x) {
- row_data[x] = OpenEXR::Rgba(input_rows[0][x], input_rows[1][x],
- input_rows[2][x], 1.f);
- }
- }
- },
- "EncodeImageEXR"));
+ const size_t end_y = std::min(start_y + y_chunk_size - 1, ysize - 1);
+ output.setFrameBuffer(output_rows.data() - start_y * xsize,
+ /*xStride=*/1, /*yStride=*/xsize);
+ for (size_t y = start_y; y <= end_y; ++y) {
+ const uint8_t* in_row = &in[(y - start_y) * in_stride];
+ OpenEXR::Rgba* const JXL_RESTRICT row_data =
+ &output_rows[(y - start_y) * xsize];
+ for (size_t x = 0; x < xsize; ++x) {
+ const uint8_t* in_pixel = &in_row[4 * num_channels * x];
+ float r = loadFloat(&in_pixel[0]);
+ float g = loadFloat(&in_pixel[4]);
+ float b = loadFloat(&in_pixel[8]);
+ const float alpha = loadAlpha(&in_pixel[12]);
+ if (!alpha_is_premultiplied) {
+ r *= alpha;
+ g *= alpha;
+ b *= alpha;
+ }
+ row_data[x] = OpenEXR::Rgba(r, g, b, alpha);
+ }
+ }
output.writePixels(/*numScanLines=*/end_y - start_y + 1);
}
}
@@ -163,5 +158,43 @@ Status EncodeImageEXR(const CodecInOut* io, const ColorEncoding& c_desired,
return true;
}
+class EXREncoder : public Encoder {
+ std::vector<JxlPixelFormat> AcceptedFormats() const override {
+ std::vector<JxlPixelFormat> formats;
+ for (const uint32_t num_channels : {1, 2, 3, 4}) {
+ for (const JxlDataType data_type : {JXL_TYPE_FLOAT, JXL_TYPE_FLOAT16}) {
+ for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) {
+ formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels,
+ /*data_type=*/data_type,
+ /*endianness=*/endianness,
+ /*align=*/0});
+ }
+ }
+ }
+ return formats;
+ }
+ Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
+ ThreadPool* pool = nullptr) const override {
+ JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info));
+ encoded_image->icc.clear();
+ encoded_image->bitstreams.clear();
+ encoded_image->bitstreams.reserve(ppf.frames.size());
+ for (const auto& frame : ppf.frames) {
+ JXL_RETURN_IF_ERROR(VerifyPackedImage(frame.color, ppf.info));
+ encoded_image->bitstreams.emplace_back();
+ JXL_RETURN_IF_ERROR(EncodeImageEXR(frame.color, ppf.info,
+ ppf.color_encoding, pool,
+ &encoded_image->bitstreams.back()));
+ }
+ return true;
+ }
+};
+
+} // namespace
+
+std::unique_ptr<Encoder> GetEXREncoder() {
+ return jxl::make_unique<EXREncoder>();
+}
+
} // namespace extras
} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/enc/exr.h b/media/libjxl/src/lib/extras/enc/exr.h
index 0423bcbadb..1baaa0272f 100644
--- a/media/libjxl/src/lib/extras/enc/exr.h
+++ b/media/libjxl/src/lib/extras/enc/exr.h
@@ -8,21 +8,14 @@
// Encodes OpenEXR images in memory.
-#include "lib/extras/packed_image.h"
-#include "lib/jxl/base/data_parallel.h"
-#include "lib/jxl/base/padded_bytes.h"
-#include "lib/jxl/base/span.h"
-#include "lib/jxl/base/status.h"
-#include "lib/jxl/codec_in_out.h"
-#include "lib/jxl/color_encoding_internal.h"
+#include <memory>
+
+#include "lib/extras/enc/encode.h"
namespace jxl {
namespace extras {
-// Transforms from io->c_current to `c_desired` (with the transfer function set
-// to linear as that is the OpenEXR convention) and encodes into `bytes`.
-Status EncodeImageEXR(const CodecInOut* io, const ColorEncoding& c_desired,
- ThreadPool* pool, PaddedBytes* bytes);
+std::unique_ptr<Encoder> GetEXREncoder();
} // namespace extras
} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/enc/jpg.cc b/media/libjxl/src/lib/extras/enc/jpg.cc
index 83b2895756..93a39dd2e4 100644
--- a/media/libjxl/src/lib/extras/enc/jpg.cc
+++ b/media/libjxl/src/lib/extras/enc/jpg.cc
@@ -12,19 +12,12 @@
#include <algorithm>
#include <iterator>
#include <numeric>
+#include <sstream>
#include <utility>
#include <vector>
-#include "lib/jxl/base/compiler_specific.h"
+#include "lib/extras/exif.h"
#include "lib/jxl/base/status.h"
-#include "lib/jxl/color_encoding_internal.h"
-#include "lib/jxl/common.h"
-#include "lib/jxl/dec_external_image.h"
-#include "lib/jxl/enc_color_management.h"
-#include "lib/jxl/enc_image_bundle.h"
-#include "lib/jxl/exif.h"
-#include "lib/jxl/image.h"
-#include "lib/jxl/image_bundle.h"
#include "lib/jxl/sanitizers.h"
#if JPEGXL_ENABLE_SJPEG
#include "sjpeg.h"
@@ -44,8 +37,21 @@ constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69,
0x66, 0x00, 0x00};
constexpr int kExifMarker = JPEG_APP0 + 1;
+enum class JpegEncoder {
+ kLibJpeg,
+ kSJpeg,
+};
+
+bool IsSRGBEncoding(const JxlColorEncoding& c) {
+ return ((c.color_space == JXL_COLOR_SPACE_RGB ||
+ c.color_space == JXL_COLOR_SPACE_GRAY) &&
+ c.primaries == JXL_PRIMARIES_SRGB &&
+ c.white_point == JXL_WHITE_POINT_D65 &&
+ c.transfer_function == JXL_TRANSFER_FUNCTION_SRGB);
+}
+
void WriteICCProfile(jpeg_compress_struct* const cinfo,
- const PaddedBytes& icc) {
+ const std::vector<uint8_t>& icc) {
constexpr size_t kMaxIccBytesInMarker =
kMaxBytesInMarker - sizeof kICCSignature - 2;
const int num_markers =
@@ -80,25 +86,34 @@ void WriteExif(jpeg_compress_struct* const cinfo,
}
}
-Status SetChromaSubsampling(const YCbCrChromaSubsampling& chroma_subsampling,
+Status SetChromaSubsampling(const std::string& subsampling,
jpeg_compress_struct* const cinfo) {
- for (size_t i = 0; i < 3; i++) {
- cinfo->comp_info[i].h_samp_factor =
- 1 << (chroma_subsampling.MaxHShift() -
- chroma_subsampling.HShift(i < 2 ? i ^ 1 : i));
- cinfo->comp_info[i].v_samp_factor =
- 1 << (chroma_subsampling.MaxVShift() -
- chroma_subsampling.VShift(i < 2 ? i ^ 1 : i));
+ const std::pair<const char*,
+ std::pair<std::array<uint8_t, 3>, std::array<uint8_t, 3>>>
+ options[] = {{"444", {{{1, 1, 1}}, {{1, 1, 1}}}},
+ {"420", {{{2, 1, 1}}, {{2, 1, 1}}}},
+ {"422", {{{2, 1, 1}}, {{1, 1, 1}}}},
+ {"440", {{{1, 1, 1}}, {{2, 1, 1}}}}};
+ for (const auto& option : options) {
+ if (subsampling == option.first) {
+ for (size_t i = 0; i < 3; i++) {
+ cinfo->comp_info[i].h_samp_factor = option.second.first[i];
+ cinfo->comp_info[i].v_samp_factor = option.second.second[i];
+ }
+ return true;
+ }
}
- return true;
+ return false;
}
-} // namespace
-
-Status EncodeWithLibJpeg(const ImageBundle* ib, const CodecInOut* io,
- size_t quality,
- const YCbCrChromaSubsampling& chroma_subsampling,
- PaddedBytes* bytes) {
+Status EncodeWithLibJpeg(const PackedImage& image, const JxlBasicInfo& info,
+ const std::vector<uint8_t>& icc,
+ std::vector<uint8_t> exif, size_t quality,
+ const std::string& chroma_subsampling,
+ std::vector<uint8_t>* bytes) {
+ if (BITS_IN_JSAMPLE != 8 || sizeof(JSAMPLE) != 1) {
+ return JXL_FAILURE("Only 8 bit JSAMPLE is supported.");
+ }
jpeg_compress_struct cinfo;
// cinfo is initialized by libjpeg, which we are not instrumenting with
// msan.
@@ -109,15 +124,10 @@ Status EncodeWithLibJpeg(const ImageBundle* ib, const CodecInOut* io,
unsigned char* buffer = nullptr;
unsigned long size = 0;
jpeg_mem_dest(&cinfo, &buffer, &size);
- cinfo.image_width = ib->oriented_xsize();
- cinfo.image_height = ib->oriented_ysize();
- if (ib->IsGray()) {
- cinfo.input_components = 1;
- cinfo.in_color_space = JCS_GRAYSCALE;
- } else {
- cinfo.input_components = 3;
- cinfo.in_color_space = JCS_RGB;
- }
+ cinfo.image_width = image.xsize;
+ cinfo.image_height = image.ysize;
+ cinfo.input_components = info.num_color_channels;
+ cinfo.in_color_space = info.num_color_channels == 1 ? JCS_GRAYSCALE : JCS_RGB;
jpeg_set_defaults(&cinfo);
cinfo.optimize_coding = TRUE;
if (cinfo.input_components == 3) {
@@ -125,27 +135,21 @@ Status EncodeWithLibJpeg(const ImageBundle* ib, const CodecInOut* io,
}
jpeg_set_quality(&cinfo, quality, TRUE);
jpeg_start_compress(&cinfo, TRUE);
- if (!ib->IsSRGB()) {
- WriteICCProfile(&cinfo, ib->c_current().ICC());
+ if (!icc.empty()) {
+ WriteICCProfile(&cinfo, icc);
}
- if (!io->blobs.exif.empty()) {
- std::vector<uint8_t> exif = io->blobs.exif;
+ if (!exif.empty()) {
ResetExifOrientation(exif);
WriteExif(&cinfo, exif);
}
if (cinfo.input_components > 3 || cinfo.input_components < 0)
return JXL_FAILURE("invalid numbers of components");
- size_t stride =
- ib->oriented_xsize() * cinfo.input_components * sizeof(JSAMPLE);
- PaddedBytes raw_bytes(stride * ib->oriented_ysize());
- JXL_RETURN_IF_ERROR(ConvertToExternal(
- *ib, BITS_IN_JSAMPLE, /*float_out=*/false, cinfo.input_components,
- JXL_BIG_ENDIAN, stride, nullptr, raw_bytes.data(), raw_bytes.size(),
- /*out_callback=*/{}, ib->metadata()->GetOrientation()));
-
- for (size_t y = 0; y < ib->oriented_ysize(); ++y) {
- JSAMPROW row[] = {raw_bytes.data() + y * stride};
+ std::vector<uint8_t> raw_bytes(image.pixels_size);
+ memcpy(&raw_bytes[0], reinterpret_cast<const uint8_t*>(image.pixels()),
+ image.pixels_size);
+ for (size_t y = 0; y < info.ysize; ++y) {
+ JSAMPROW row[] = {raw_bytes.data() + y * image.stride};
jpeg_write_scanlines(&cinfo, row, 1);
}
@@ -160,41 +164,34 @@ Status EncodeWithLibJpeg(const ImageBundle* ib, const CodecInOut* io,
return true;
}
-Status EncodeWithSJpeg(const ImageBundle* ib, const CodecInOut* io,
- size_t quality,
- const YCbCrChromaSubsampling& chroma_subsampling,
- PaddedBytes* bytes) {
+Status EncodeWithSJpeg(const PackedImage& image, const JxlBasicInfo& info,
+ const std::vector<uint8_t>& icc,
+ std::vector<uint8_t> exif, size_t quality,
+ const std::string& chroma_subsampling,
+ std::vector<uint8_t>* bytes) {
#if !JPEGXL_ENABLE_SJPEG
return JXL_FAILURE("JPEG XL was built without sjpeg support");
#else
sjpeg::EncoderParam param(quality);
- if (!ib->IsSRGB()) {
- param.iccp.assign(ib->metadata()->color_encoding.ICC().begin(),
- ib->metadata()->color_encoding.ICC().end());
+ if (!icc.empty()) {
+ param.iccp.assign(icc.begin(), icc.end());
}
- std::vector<uint8_t> exif = io->blobs.exif;
if (!exif.empty()) {
ResetExifOrientation(exif);
param.exif.assign(exif.begin(), exif.end());
}
- if (chroma_subsampling.Is444()) {
+ if (chroma_subsampling == "444") {
param.yuv_mode = SJPEG_YUV_444;
- } else if (chroma_subsampling.Is420()) {
+ } else if (chroma_subsampling == "420") {
param.yuv_mode = SJPEG_YUV_SHARP;
} else {
return JXL_FAILURE("sjpeg does not support this chroma subsampling mode");
}
- size_t stride = ib->oriented_xsize() * 3;
- PaddedBytes rgb(ib->xsize() * ib->ysize() * 3);
- JXL_RETURN_IF_ERROR(
- ConvertToExternal(*ib, 8, /*float_out=*/false, 3, JXL_BIG_ENDIAN, stride,
- nullptr, rgb.data(), rgb.size(),
- /*out_callback=*/{}, ib->metadata()->GetOrientation()));
-
+ size_t stride = info.xsize * 3;
+ const uint8_t* pixels = reinterpret_cast<const uint8_t*>(image.pixels());
std::string output;
- JXL_RETURN_IF_ERROR(sjpeg::Encode(rgb.data(), ib->oriented_xsize(),
- ib->oriented_ysize(), stride, param,
- &output));
+ JXL_RETURN_IF_ERROR(
+ sjpeg::Encode(pixels, image.xsize, image.ysize, stride, param, &output));
bytes->assign(
reinterpret_cast<const uint8_t*>(output.data()),
reinterpret_cast<const uint8_t*>(output.data() + output.size()));
@@ -202,31 +199,30 @@ Status EncodeWithSJpeg(const ImageBundle* ib, const CodecInOut* io,
#endif
}
-Status EncodeImageJPG(const CodecInOut* io, JpegEncoder encoder, size_t quality,
- YCbCrChromaSubsampling chroma_subsampling,
- ThreadPool* pool, PaddedBytes* bytes) {
- if (io->Main().HasAlpha()) {
+Status EncodeImageJPG(const PackedImage& image, const JxlBasicInfo& info,
+ const std::vector<uint8_t>& icc,
+ std::vector<uint8_t> exif, JpegEncoder encoder,
+ size_t quality, const std::string& chroma_subsampling,
+ ThreadPool* pool, std::vector<uint8_t>* bytes) {
+ if (image.format.data_type != JXL_TYPE_UINT8) {
+ return JXL_FAILURE("Unsupported pixel data type");
+ }
+ if (info.alpha_bits > 0) {
return JXL_FAILURE("alpha is not supported");
}
if (quality > 100) {
return JXL_FAILURE("please specify a 0-100 JPEG quality");
}
- const ImageBundle* ib;
- ImageMetadata metadata = io->metadata.m;
- ImageBundle ib_store(&metadata);
- JXL_RETURN_IF_ERROR(TransformIfNeeded(io->Main(),
- io->metadata.m.color_encoding,
- GetJxlCms(), pool, &ib_store, &ib));
-
switch (encoder) {
case JpegEncoder::kLibJpeg:
- JXL_RETURN_IF_ERROR(
- EncodeWithLibJpeg(ib, io, quality, chroma_subsampling, bytes));
+ JXL_RETURN_IF_ERROR(EncodeWithLibJpeg(image, info, icc, std::move(exif),
+ quality, chroma_subsampling,
+ bytes));
break;
case JpegEncoder::kSJpeg:
- JXL_RETURN_IF_ERROR(
- EncodeWithSJpeg(ib, io, quality, chroma_subsampling, bytes));
+ JXL_RETURN_IF_ERROR(EncodeWithSJpeg(image, info, icc, std::move(exif),
+ quality, chroma_subsampling, bytes));
break;
default:
return JXL_FAILURE("tried to use an unknown JPEG encoder");
@@ -235,5 +231,68 @@ Status EncodeImageJPG(const CodecInOut* io, JpegEncoder encoder, size_t quality,
return true;
}
+class JPEGEncoder : public Encoder {
+ std::vector<JxlPixelFormat> AcceptedFormats() const override {
+ std::vector<JxlPixelFormat> formats;
+ for (const uint32_t num_channels : {1, 3}) {
+ for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) {
+ formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels,
+ /*data_type=*/JXL_TYPE_UINT8,
+ /*endianness=*/endianness,
+ /*align=*/0});
+ }
+ }
+ return formats;
+ }
+ Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
+ ThreadPool* pool = nullptr) const override {
+ JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info));
+ const auto& options = this->options();
+ int quality = 100;
+ auto it_quality = options.find("q");
+ if (it_quality != options.end()) {
+ std::istringstream is(it_quality->second);
+ JXL_RETURN_IF_ERROR(static_cast<bool>(is >> quality));
+ }
+ std::string chroma_subsampling = "444";
+ auto it_chroma_subsampling = options.find("chroma_subsampling");
+ if (it_chroma_subsampling != options.end()) {
+ chroma_subsampling = it_chroma_subsampling->second;
+ }
+ JpegEncoder jpeg_encoder = JpegEncoder::kLibJpeg;
+ auto it_encoder = options.find("jpeg_encoder");
+ if (it_encoder != options.end()) {
+ if (it_encoder->second == "libjpeg") {
+ jpeg_encoder = JpegEncoder::kLibJpeg;
+ } else if (it_encoder->second == "sjpeg") {
+ jpeg_encoder = JpegEncoder::kSJpeg;
+ } else {
+ return JXL_FAILURE("unknown jpeg encoder \"%s\"",
+ it_encoder->second.c_str());
+ }
+ }
+ std::vector<uint8_t> icc;
+ if (!IsSRGBEncoding(ppf.color_encoding)) {
+ icc = ppf.icc;
+ }
+ encoded_image->bitstreams.clear();
+ encoded_image->bitstreams.reserve(ppf.frames.size());
+ for (const auto& frame : ppf.frames) {
+ JXL_RETURN_IF_ERROR(VerifyPackedImage(frame.color, ppf.info));
+ encoded_image->bitstreams.emplace_back();
+ JXL_RETURN_IF_ERROR(EncodeImageJPG(
+ frame.color, ppf.info, icc, ppf.metadata.exif, jpeg_encoder, quality,
+ chroma_subsampling, pool, &encoded_image->bitstreams.back()));
+ }
+ return true;
+ }
+};
+
+} // namespace
+
+std::unique_ptr<Encoder> GetJPEGEncoder() {
+ return jxl::make_unique<JPEGEncoder>();
+}
+
} // namespace extras
} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/enc/jpg.h b/media/libjxl/src/lib/extras/enc/jpg.h
index ccea1415a8..20b37cd168 100644
--- a/media/libjxl/src/lib/extras/enc/jpg.h
+++ b/media/libjxl/src/lib/extras/enc/jpg.h
@@ -8,27 +8,14 @@
// Encodes JPG pixels and metadata in memory.
-#include <stdint.h>
+#include <memory>
-#include "lib/extras/codec.h"
-#include "lib/jxl/base/data_parallel.h"
-#include "lib/jxl/base/padded_bytes.h"
-#include "lib/jxl/base/span.h"
-#include "lib/jxl/base/status.h"
-#include "lib/jxl/codec_in_out.h"
+#include "lib/extras/enc/encode.h"
namespace jxl {
namespace extras {
-enum class JpegEncoder {
- kLibJpeg,
- kSJpeg,
-};
-
-// Encodes into `bytes`.
-Status EncodeImageJPG(const CodecInOut* io, JpegEncoder encoder, size_t quality,
- YCbCrChromaSubsampling chroma_subsampling,
- ThreadPool* pool, PaddedBytes* bytes);
+std::unique_ptr<Encoder> GetJPEGEncoder();
} // namespace extras
} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/enc/npy.cc b/media/libjxl/src/lib/extras/enc/npy.cc
new file mode 100644
index 0000000000..1428e6427d
--- /dev/null
+++ b/media/libjxl/src/lib/extras/enc/npy.cc
@@ -0,0 +1,322 @@
+// 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/extras/enc/npy.h"
+
+#include <stdio.h>
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "jxl/types.h"
+#include "lib/extras/packed_image.h"
+
+namespace jxl {
+namespace extras {
+namespace {
+
+// JSON value writing
+
+class JSONField {
+ public:
+ virtual ~JSONField() = default;
+ virtual void Write(std::ostream& o, uint32_t indent) const = 0;
+
+ protected:
+ JSONField() = default;
+};
+
+class JSONValue : public JSONField {
+ public:
+ template <typename T>
+ explicit JSONValue(const T& value) : value_(std::to_string(value)) {}
+
+ explicit JSONValue(const std::string& value) : value_("\"" + value + "\"") {}
+
+ explicit JSONValue(bool value) : value_(value ? "true" : "false") {}
+
+ void Write(std::ostream& o, uint32_t indent) const override { o << value_; }
+
+ private:
+ std::string value_;
+};
+
+class JSONDict : public JSONField {
+ public:
+ JSONDict() = default;
+
+ template <typename T>
+ T* AddEmpty(const std::string& key) {
+ static_assert(std::is_convertible<T*, JSONField*>::value,
+ "T must be a JSONField");
+ T* ret = new T();
+ values_.emplace_back(
+ key, std::unique_ptr<JSONField>(static_cast<JSONField*>(ret)));
+ return ret;
+ }
+
+ template <typename T>
+ void Add(const std::string& key, const T& value) {
+ values_.emplace_back(key, std::unique_ptr<JSONField>(new JSONValue(value)));
+ }
+
+ void Write(std::ostream& o, uint32_t indent) const override {
+ std::string indent_str(indent, ' ');
+ o << "{";
+ bool is_first = true;
+ for (const auto& key_value : values_) {
+ if (!is_first) {
+ o << ",";
+ }
+ is_first = false;
+ o << std::endl << indent_str << " \"" << key_value.first << "\": ";
+ key_value.second->Write(o, indent + 2);
+ }
+ if (!values_.empty()) {
+ o << std::endl << indent_str;
+ }
+ o << "}";
+ }
+
+ private:
+ // Dictionary with order.
+ std::vector<std::pair<std::string, std::unique_ptr<JSONField>>> values_;
+};
+
+class JSONArray : public JSONField {
+ public:
+ JSONArray() = default;
+
+ template <typename T>
+ T* AddEmpty() {
+ static_assert(std::is_convertible<T*, JSONField*>::value,
+ "T must be a JSONField");
+ T* ret = new T();
+ values_.emplace_back(ret);
+ return ret;
+ }
+
+ template <typename T>
+ void Add(const T& value) {
+ values_.emplace_back(new JSONValue(value));
+ }
+
+ void Write(std::ostream& o, uint32_t indent) const override {
+ std::string indent_str(indent, ' ');
+ o << "[";
+ bool is_first = true;
+ for (const auto& value : values_) {
+ if (!is_first) {
+ o << ",";
+ }
+ is_first = false;
+ o << std::endl << indent_str << " ";
+ value->Write(o, indent + 2);
+ }
+ if (!values_.empty()) {
+ o << std::endl << indent_str;
+ }
+ o << "]";
+ }
+
+ private:
+ std::vector<std::unique_ptr<JSONField>> values_;
+};
+
+void GenerateMetadata(const PackedPixelFile& ppf, std::vector<uint8_t>* out) {
+ JSONDict meta;
+ // Same order as in 18181-3 CD.
+
+ // Frames.
+ auto* meta_frames = meta.AddEmpty<JSONArray>("frames");
+ for (size_t i = 0; i < ppf.frames.size(); i++) {
+ auto* frame_i = meta_frames->AddEmpty<JSONDict>();
+ if (ppf.info.have_animation) {
+ frame_i->Add("duration",
+ JSONValue(ppf.frames[i].frame_info.duration * 1.0f *
+ ppf.info.animation.tps_denominator /
+ ppf.info.animation.tps_numerator));
+ }
+
+ frame_i->Add("name", JSONValue(ppf.frames[i].name));
+
+ if (ppf.info.animation.have_timecodes) {
+ frame_i->Add("timecode", JSONValue(ppf.frames[i].frame_info.timecode));
+ }
+ }
+
+#define METADATA(FIELD) meta.Add(#FIELD, ppf.info.FIELD)
+
+ METADATA(intensity_target);
+ METADATA(min_nits);
+ METADATA(relative_to_max_display);
+ METADATA(linear_below);
+
+ if (ppf.info.have_preview) {
+ meta.AddEmpty<JSONDict>("preview");
+ // TODO(veluca): can we have duration/name/timecode here?
+ }
+
+ {
+ auto ectype = meta.AddEmpty<JSONArray>("extra_channel_type");
+ auto bps = meta.AddEmpty<JSONArray>("bits_per_sample");
+ auto ebps = meta.AddEmpty<JSONArray>("exp_bits_per_sample");
+ bps->Add(ppf.info.bits_per_sample);
+ ebps->Add(ppf.info.exponent_bits_per_sample);
+ for (size_t i = 0; i < ppf.extra_channels_info.size(); i++) {
+ switch (ppf.extra_channels_info[i].ec_info.type) {
+ case JXL_CHANNEL_ALPHA: {
+ ectype->Add(std::string("Alpha"));
+ break;
+ }
+ case JXL_CHANNEL_DEPTH: {
+ ectype->Add(std::string("Depth"));
+ break;
+ }
+ case JXL_CHANNEL_SPOT_COLOR: {
+ ectype->Add(std::string("SpotColor"));
+ break;
+ }
+ case JXL_CHANNEL_SELECTION_MASK: {
+ ectype->Add(std::string("SelectionMask"));
+ break;
+ }
+ case JXL_CHANNEL_BLACK: {
+ ectype->Add(std::string("Black"));
+ break;
+ }
+ case JXL_CHANNEL_CFA: {
+ ectype->Add(std::string("CFA"));
+ break;
+ }
+ case JXL_CHANNEL_THERMAL: {
+ ectype->Add(std::string("Thermal"));
+ break;
+ }
+ default: {
+ ectype->Add(std::string("UNKNOWN"));
+ break;
+ }
+ }
+ bps->Add(ppf.extra_channels_info[i].ec_info.bits_per_sample);
+ ebps->Add(ppf.extra_channels_info[i].ec_info.exponent_bits_per_sample);
+ }
+ }
+
+ std::ostringstream os;
+ meta.Write(os, 0);
+ out->resize(os.str().size());
+ memcpy(out->data(), os.str().data(), os.str().size());
+}
+
+void Append(std::vector<uint8_t>* out, const void* data, size_t size) {
+ size_t pos = out->size();
+ out->resize(pos + size);
+ memcpy(out->data() + pos, data, size);
+}
+
+void WriteNPYHeader(size_t xsize, size_t ysize, uint32_t num_channels,
+ size_t num_frames, std::vector<uint8_t>* out) {
+ const uint8_t header[] = "\x93NUMPY\x01\x00";
+ Append(out, header, 8);
+ std::stringstream ss;
+ ss << "{'descr': '<f4', 'fortran_order': False, 'shape': (" << num_frames
+ << ", " << ysize << ", " << xsize << ", " << num_channels << "), }\n";
+ // 16-bit little endian header length.
+ uint8_t header_len[2] = {static_cast<uint8_t>(ss.str().size() % 256),
+ static_cast<uint8_t>(ss.str().size() / 256)};
+ Append(out, header_len, 2);
+ Append(out, ss.str().data(), ss.str().size());
+}
+
+bool WriteFrameToNPYArray(size_t xsize, size_t ysize, const PackedFrame& frame,
+ std::vector<uint8_t>* out) {
+ const auto& color = frame.color;
+ if (color.xsize != xsize || color.ysize != ysize) {
+ return false;
+ }
+ for (const auto& ec : frame.extra_channels) {
+ if (ec.xsize != xsize || ec.ysize != ysize) {
+ return false;
+ }
+ }
+ // interleave the samples from color and extra channels
+ for (size_t y = 0; y < ysize; ++y) {
+ for (size_t x = 0; x < xsize; ++x) {
+ {
+ size_t sample_size = color.pixel_stride();
+ size_t offset = y * color.stride + x * sample_size;
+ uint8_t* pixels = reinterpret_cast<uint8_t*>(color.pixels());
+ JXL_ASSERT(offset + sample_size <= color.pixels_size);
+ Append(out, pixels + offset, sample_size);
+ }
+ for (const auto& ec : frame.extra_channels) {
+ size_t sample_size = ec.pixel_stride();
+ size_t offset = y * ec.stride + x * sample_size;
+ uint8_t* pixels = reinterpret_cast<uint8_t*>(ec.pixels());
+ JXL_ASSERT(offset + sample_size <= ec.pixels_size);
+ Append(out, pixels + offset, sample_size);
+ }
+ }
+ }
+ return true;
+}
+
+// Writes a PackedPixelFile as a numpy 4D ndarray in binary format.
+bool WriteNPYArray(const PackedPixelFile& ppf, std::vector<uint8_t>* out) {
+ size_t xsize = ppf.info.xsize;
+ size_t ysize = ppf.info.ysize;
+ WriteNPYHeader(xsize, ysize,
+ ppf.info.num_color_channels + ppf.extra_channels_info.size(),
+ ppf.frames.size(), out);
+ for (const auto& frame : ppf.frames) {
+ if (!WriteFrameToNPYArray(xsize, ysize, frame, out)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+class NumPyEncoder : public Encoder {
+ public:
+ Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
+ ThreadPool* pool = nullptr) const override {
+ JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info));
+ GenerateMetadata(ppf, &encoded_image->metadata);
+ encoded_image->bitstreams.emplace_back();
+ if (!WriteNPYArray(ppf, &encoded_image->bitstreams.back())) {
+ return false;
+ }
+ if (ppf.preview_frame) {
+ size_t xsize = ppf.info.preview.xsize;
+ size_t ysize = ppf.info.preview.ysize;
+ WriteNPYHeader(xsize, ysize, ppf.info.num_color_channels, 1,
+ &encoded_image->preview_bitstream);
+ if (!WriteFrameToNPYArray(xsize, ysize, *ppf.preview_frame,
+ &encoded_image->preview_bitstream)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ std::vector<JxlPixelFormat> AcceptedFormats() const override {
+ std::vector<JxlPixelFormat> formats;
+ for (const uint32_t num_channels : {1, 3}) {
+ formats.push_back(JxlPixelFormat{num_channels, JXL_TYPE_FLOAT,
+ JXL_LITTLE_ENDIAN, /*align=*/0});
+ }
+ return formats;
+ }
+};
+
+} // namespace
+
+std::unique_ptr<Encoder> GetNumPyEncoder() {
+ return jxl::make_unique<NumPyEncoder>();
+}
+
+} // namespace extras
+} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/enc/npy.h b/media/libjxl/src/lib/extras/enc/npy.h
new file mode 100644
index 0000000000..3ee6208ec2
--- /dev/null
+++ b/media/libjxl/src/lib/extras/enc/npy.h
@@ -0,0 +1,23 @@
+// 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.
+
+#ifndef LIB_EXTRAS_ENC_NPY_H_
+#define LIB_EXTRAS_ENC_NPY_H_
+
+// Encodes pixels to numpy array, used for conformance testing.
+
+#include <memory>
+
+#include "lib/extras/enc/encode.h"
+
+namespace jxl {
+namespace extras {
+
+std::unique_ptr<Encoder> GetNumPyEncoder();
+
+} // namespace extras
+} // namespace jxl
+
+#endif // LIB_EXTRAS_ENC_NPY_H_
diff --git a/media/libjxl/src/lib/extras/enc/pgx.cc b/media/libjxl/src/lib/extras/enc/pgx.cc
index cc333a62ac..ef204ad1c6 100644
--- a/media/libjxl/src/lib/extras/enc/pgx.cc
+++ b/media/libjxl/src/lib/extras/enc/pgx.cc
@@ -8,18 +8,10 @@
#include <stdio.h>
#include <string.h>
-#include "lib/jxl/base/bits.h"
-#include "lib/jxl/base/compiler_specific.h"
-#include "lib/jxl/base/file_io.h"
+#include "jxl/codestream_header.h"
+#include "lib/extras/packed_image.h"
+#include "lib/jxl/base/byte_order.h"
#include "lib/jxl/base/printf_macros.h"
-#include "lib/jxl/color_management.h"
-#include "lib/jxl/dec_external_image.h"
-#include "lib/jxl/enc_color_management.h"
-#include "lib/jxl/enc_external_image.h"
-#include "lib/jxl/enc_image_bundle.h"
-#include "lib/jxl/fields.h" // AllDefault
-#include "lib/jxl/image.h"
-#include "lib/jxl/image_bundle.h"
namespace jxl {
namespace extras {
@@ -27,60 +19,63 @@ namespace {
constexpr size_t kMaxHeaderSize = 200;
-Status EncodeHeader(const ImageBundle& ib, const size_t bits_per_sample,
- char* header, int* JXL_RESTRICT chars_written) {
- if (ib.HasAlpha()) return JXL_FAILURE("PGX: can't store alpha");
- if (!ib.IsGray()) return JXL_FAILURE("PGX: must be grayscale");
+Status EncodeHeader(const JxlBasicInfo& info, char* header,
+ int* chars_written) {
+ if (info.alpha_bits > 0) {
+ return JXL_FAILURE("PGX: can't store alpha");
+ }
+ if (info.num_color_channels != 1) {
+ return JXL_FAILURE("PGX: must be grayscale");
+ }
// TODO(lode): verify other bit depths: for other bit depths such as 1 or 4
// bits, have a test case to verify it works correctly. For bits > 16, we may
// need to change the way external_image works.
- if (bits_per_sample != 8 && bits_per_sample != 16) {
+ if (info.bits_per_sample != 8 && info.bits_per_sample != 16) {
return JXL_FAILURE("PGX: bits other than 8 or 16 not yet supported");
}
// Use ML (Big Endian), LM may not be well supported by all decoders.
- *chars_written = snprintf(header, kMaxHeaderSize,
- "PG ML + %" PRIuS " %" PRIuS " %" PRIuS "\n",
- bits_per_sample, ib.xsize(), ib.ysize());
+ *chars_written = snprintf(header, kMaxHeaderSize, "PG ML + %u %u %u\n",
+ info.bits_per_sample, info.xsize, info.ysize);
JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
kMaxHeaderSize);
return true;
}
-} // namespace
+Status EncodeImagePGX(const PackedFrame& frame, const JxlBasicInfo& info,
+ std::vector<uint8_t>* bytes) {
+ char header[kMaxHeaderSize];
+ int header_size = 0;
+ JXL_RETURN_IF_ERROR(EncodeHeader(info, header, &header_size));
-Status EncodeImagePGX(const CodecInOut* io, const ColorEncoding& c_desired,
- size_t bits_per_sample, ThreadPool* pool,
- PaddedBytes* bytes) {
- if (!Bundle::AllDefault(io->metadata.m)) {
- JXL_WARNING("PGX encoder ignoring metadata - use a different codec");
- }
- if (!c_desired.IsSRGB()) {
- JXL_WARNING(
- "PGX encoder cannot store custom ICC profile; decoder\n"
- "will need hint key=color_space to get the same values");
+ const PackedImage& color = frame.color;
+ const JxlPixelFormat format = color.format;
+ const uint8_t* in = reinterpret_cast<const uint8_t*>(color.pixels());
+ size_t data_bits_per_sample = PackedImage::BitsPerChannel(format.data_type);
+ size_t bytes_per_sample = data_bits_per_sample / kBitsPerByte;
+ size_t num_samples = info.xsize * info.ysize;
+
+ if (info.bits_per_sample != data_bits_per_sample) {
+ return JXL_FAILURE("Bit depth does not match pixel data type");
}
- ImageBundle ib = io->Main().Copy();
-
- ImageMetadata metadata = io->metadata.m;
- ImageBundle store(&metadata);
- const ImageBundle* transformed;
- JXL_RETURN_IF_ERROR(TransformIfNeeded(ib, c_desired, GetJxlCms(), pool,
- &store, &transformed));
- PaddedBytes pixels(ib.xsize() * ib.ysize() *
- (bits_per_sample / kBitsPerByte));
- size_t stride = ib.xsize() * (bits_per_sample / kBitsPerByte);
- JXL_RETURN_IF_ERROR(
- ConvertToExternal(*transformed, bits_per_sample,
- /*float_out=*/false,
- /*num_channels=*/1, JXL_BIG_ENDIAN, stride, pool,
- pixels.data(), pixels.size(),
- /*out_callback=*/{}, metadata.GetOrientation()));
+ std::vector<uint8_t> pixels(num_samples * bytes_per_sample);
- char header[kMaxHeaderSize];
- int header_size = 0;
- JXL_RETURN_IF_ERROR(EncodeHeader(ib, bits_per_sample, header, &header_size));
+ if (format.data_type == JXL_TYPE_UINT8) {
+ memcpy(&pixels[0], in, num_samples * bytes_per_sample);
+ } else if (format.data_type == JXL_TYPE_UINT16) {
+ if (format.endianness != JXL_BIG_ENDIAN) {
+ const uint8_t* p_in = in;
+ uint8_t* p_out = pixels.data();
+ for (size_t i = 0; i < num_samples; ++i, p_in += 2, p_out += 2) {
+ StoreBE16(LoadLE16(p_in), p_out);
+ }
+ } else {
+ memcpy(&pixels[0], in, num_samples * bytes_per_sample);
+ }
+ } else {
+ return JXL_FAILURE("Unsupported pixel data type");
+ }
bytes->resize(static_cast<size_t>(header_size) + pixels.size());
memcpy(bytes->data(), header, static_cast<size_t>(header_size));
@@ -89,5 +84,41 @@ Status EncodeImagePGX(const CodecInOut* io, const ColorEncoding& c_desired,
return true;
}
+class PGXEncoder : public Encoder {
+ public:
+ std::vector<JxlPixelFormat> AcceptedFormats() const override {
+ std::vector<JxlPixelFormat> formats;
+ for (const JxlDataType data_type : {JXL_TYPE_UINT8, JXL_TYPE_UINT16}) {
+ for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) {
+ formats.push_back(JxlPixelFormat{/*num_channels=*/1,
+ /*data_type=*/data_type,
+ /*endianness=*/endianness,
+ /*align=*/0});
+ }
+ }
+ return formats;
+ }
+ Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
+ ThreadPool* pool) const override {
+ JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info));
+ encoded_image->icc.assign(ppf.icc.begin(), ppf.icc.end());
+ encoded_image->bitstreams.clear();
+ encoded_image->bitstreams.reserve(ppf.frames.size());
+ for (const auto& frame : ppf.frames) {
+ JXL_RETURN_IF_ERROR(VerifyPackedImage(frame.color, ppf.info));
+ encoded_image->bitstreams.emplace_back();
+ JXL_RETURN_IF_ERROR(
+ EncodeImagePGX(frame, ppf.info, &encoded_image->bitstreams.back()));
+ }
+ return true;
+ }
+};
+
+} // namespace
+
+std::unique_ptr<Encoder> GetPGXEncoder() {
+ return jxl::make_unique<PGXEncoder>();
+}
+
} // namespace extras
} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/enc/pgx.h b/media/libjxl/src/lib/extras/enc/pgx.h
index 6f14e20668..f24e391b09 100644
--- a/media/libjxl/src/lib/extras/enc/pgx.h
+++ b/media/libjxl/src/lib/extras/enc/pgx.h
@@ -11,21 +11,12 @@
#include <stddef.h>
#include <stdint.h>
-#include "lib/extras/packed_image.h"
-#include "lib/jxl/base/data_parallel.h"
-#include "lib/jxl/base/padded_bytes.h"
-#include "lib/jxl/base/span.h"
-#include "lib/jxl/base/status.h"
-#include "lib/jxl/codec_in_out.h"
-#include "lib/jxl/color_encoding_internal.h"
+#include "lib/extras/enc/encode.h"
namespace jxl {
namespace extras {
-// Transforms from io->c_current to `c_desired` and encodes into `bytes`.
-Status EncodeImagePGX(const CodecInOut* io, const ColorEncoding& c_desired,
- size_t bits_per_sample, ThreadPool* pool,
- PaddedBytes* bytes);
+std::unique_ptr<Encoder> GetPGXEncoder();
} // namespace extras
} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/enc/pnm.cc b/media/libjxl/src/lib/extras/enc/pnm.cc
index 9cedda09d5..9b5f6cbc95 100644
--- a/media/libjxl/src/lib/extras/enc/pnm.cc
+++ b/media/libjxl/src/lib/extras/enc/pnm.cc
@@ -32,22 +32,19 @@ namespace {
constexpr size_t kMaxHeaderSize = 200;
-Status EncodeHeader(const PackedPixelFile& ppf, const size_t bits_per_sample,
- const bool little_endian, char* header,
- int* JXL_RESTRICT chars_written) {
- bool is_gray = ppf.info.num_color_channels <= 2;
- size_t oriented_xsize =
- ppf.info.orientation <= 4 ? ppf.info.xsize : ppf.info.ysize;
- size_t oriented_ysize =
- ppf.info.orientation <= 4 ? ppf.info.ysize : ppf.info.xsize;
- if (ppf.info.alpha_bits > 0) { // PAM
+Status EncodeHeader(const PackedImage& image, size_t bits_per_sample,
+ bool little_endian, char* header, int* chars_written) {
+ size_t num_channels = image.format.num_channels;
+ bool is_gray = num_channels <= 2;
+ bool has_alpha = num_channels == 2 || num_channels == 4;
+ if (has_alpha) { // PAM
if (bits_per_sample > 16) return JXL_FAILURE("PNM cannot have > 16 bits");
const uint32_t max_val = (1U << bits_per_sample) - 1;
*chars_written =
snprintf(header, kMaxHeaderSize,
"P7\nWIDTH %" PRIuS "\nHEIGHT %" PRIuS
"\nDEPTH %u\nMAXVAL %u\nTUPLTYPE %s\nENDHDR\n",
- oriented_xsize, oriented_ysize, is_gray ? 2 : 4, max_val,
+ image.xsize, image.ysize, is_gray ? 2 : 4, max_val,
is_gray ? "GRAYSCALE_ALPHA" : "RGB_ALPHA");
JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
kMaxHeaderSize);
@@ -56,77 +53,164 @@ Status EncodeHeader(const PackedPixelFile& ppf, const size_t bits_per_sample,
const double scale = little_endian ? -1.0 : 1.0;
*chars_written =
snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%.1f\n",
- type, oriented_xsize, oriented_ysize, scale);
+ type, image.xsize, image.ysize, scale);
JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
kMaxHeaderSize);
} else { // PGM/PPM
+ if (bits_per_sample > 16) return JXL_FAILURE("PNM cannot have > 16 bits");
const uint32_t max_val = (1U << bits_per_sample) - 1;
- if (max_val >= 65536) return JXL_FAILURE("PNM cannot have > 16 bits");
const char type = is_gray ? '5' : '6';
*chars_written =
snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%u\n",
- type, oriented_xsize, oriented_ysize, max_val);
+ type, image.xsize, image.ysize, max_val);
JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
kMaxHeaderSize);
}
return true;
}
-Span<const uint8_t> MakeSpan(const char* str) {
- return Span<const uint8_t>(reinterpret_cast<const uint8_t*>(str),
- strlen(str));
+Status EncodeImagePNM(const PackedImage& image, size_t bits_per_sample,
+ std::vector<uint8_t>* bytes) {
+ // Choose native for PFM; PGM/PPM require big-endian
+ bool is_little_endian = bits_per_sample > 16 && IsLittleEndian();
+ char header[kMaxHeaderSize];
+ int header_size = 0;
+ JXL_RETURN_IF_ERROR(EncodeHeader(image, bits_per_sample, is_little_endian,
+ header, &header_size));
+ bytes->resize(static_cast<size_t>(header_size) + image.pixels_size);
+ memcpy(bytes->data(), header, static_cast<size_t>(header_size));
+ const bool flipped_y = bits_per_sample == 32; // PFMs are flipped
+ const uint8_t* in = reinterpret_cast<const uint8_t*>(image.pixels());
+ uint8_t* out = bytes->data() + header_size;
+ for (size_t y = 0; y < image.ysize; ++y) {
+ size_t y_out = flipped_y ? image.ysize - 1 - y : y;
+ const uint8_t* row_in = &in[y * image.stride];
+ uint8_t* row_out = &out[y_out * image.stride];
+ memcpy(row_out, row_in, image.stride);
+ }
+ return true;
}
-// Flip the image vertically for loading/saving PFM files which have the
-// scanlines inverted.
-void VerticallyFlipImage(float* const float_image, const size_t xsize,
- const size_t ysize, const size_t num_channels) {
- for (size_t y = 0; y < ysize / 2; y++) {
- float* first_row = &float_image[y * num_channels * xsize];
- float* other_row = &float_image[(ysize - y - 1) * num_channels * xsize];
- for (size_t c = 0; c < num_channels; c++) {
- for (size_t x = 0; x < xsize; ++x) {
- float tmp = first_row[x * num_channels + c];
- first_row[x * num_channels + c] = other_row[x * num_channels + c];
- other_row[x * num_channels + c] = tmp;
+class PNMEncoder : public Encoder {
+ public:
+ Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
+ ThreadPool* pool = nullptr) const override {
+ JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info));
+ if (!ppf.metadata.exif.empty() || !ppf.metadata.iptc.empty() ||
+ !ppf.metadata.jumbf.empty() || !ppf.metadata.xmp.empty()) {
+ JXL_WARNING("PNM encoder ignoring metadata - use a different codec");
+ }
+ encoded_image->icc = ppf.icc;
+ encoded_image->bitstreams.clear();
+ encoded_image->bitstreams.reserve(ppf.frames.size());
+ for (const auto& frame : ppf.frames) {
+ JXL_RETURN_IF_ERROR(VerifyPackedImage(frame.color, ppf.info));
+ encoded_image->bitstreams.emplace_back();
+ JXL_RETURN_IF_ERROR(EncodeImagePNM(frame.color, ppf.info.bits_per_sample,
+ &encoded_image->bitstreams.back()));
+ }
+ for (size_t i = 0; i < ppf.extra_channels_info.size(); ++i) {
+ const auto& ec_info = ppf.extra_channels_info[i].ec_info;
+ encoded_image->extra_channel_bitstreams.emplace_back();
+ auto& ec_bitstreams = encoded_image->extra_channel_bitstreams.back();
+ for (const auto& frame : ppf.frames) {
+ ec_bitstreams.emplace_back();
+ JXL_RETURN_IF_ERROR(EncodeImagePNM(frame.extra_channels[i],
+ ec_info.bits_per_sample,
+ &ec_bitstreams.back()));
}
}
+ return true;
}
-}
+};
-} // namespace
+class PPMEncoder : public PNMEncoder {
+ public:
+ std::vector<JxlPixelFormat> AcceptedFormats() const override {
+ std::vector<JxlPixelFormat> formats;
+ for (const uint32_t num_channels : {1, 2, 3, 4}) {
+ for (const JxlDataType data_type : {JXL_TYPE_UINT8, JXL_TYPE_UINT16}) {
+ for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) {
+ formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels,
+ /*data_type=*/data_type,
+ /*endianness=*/endianness,
+ /*align=*/0});
+ }
+ }
+ }
+ return formats;
+ }
+};
-Status EncodeImagePNM(const PackedPixelFile& ppf, size_t bits_per_sample,
- ThreadPool* pool, size_t frame_index,
- std::vector<uint8_t>* bytes) {
- const bool floating_point = bits_per_sample > 16;
- // Choose native for PFM; PGM/PPM require big-endian
- const JxlEndianness endianness =
- floating_point ? JXL_NATIVE_ENDIAN : JXL_BIG_ENDIAN;
- if (!ppf.metadata.exif.empty() || !ppf.metadata.iptc.empty() ||
- !ppf.metadata.jumbf.empty() || !ppf.metadata.xmp.empty()) {
- JXL_WARNING("PNM encoder ignoring metadata - use a different codec");
+class PFMEncoder : public PNMEncoder {
+ public:
+ std::vector<JxlPixelFormat> AcceptedFormats() const override {
+ std::vector<JxlPixelFormat> formats;
+ for (const uint32_t num_channels : {1, 3}) {
+ for (const JxlDataType data_type : {JXL_TYPE_FLOAT16, JXL_TYPE_FLOAT}) {
+ for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) {
+ formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels,
+ /*data_type=*/data_type,
+ /*endianness=*/endianness,
+ /*align=*/0});
+ }
+ }
+ }
+ return formats;
}
+};
- char header[kMaxHeaderSize];
- int header_size = 0;
- bool is_little_endian = endianness == JXL_LITTLE_ENDIAN ||
- (endianness == JXL_NATIVE_ENDIAN && IsLittleEndian());
- JXL_RETURN_IF_ERROR(EncodeHeader(ppf, bits_per_sample, is_little_endian,
- header, &header_size));
- bytes->resize(static_cast<size_t>(header_size) +
- ppf.frames[frame_index].color.pixels_size);
- memcpy(bytes->data(), header, static_cast<size_t>(header_size));
- memcpy(bytes->data() + header_size, ppf.frames[frame_index].color.pixels(),
- ppf.frames[frame_index].color.pixels_size);
- if (floating_point) {
- VerticallyFlipImage(reinterpret_cast<float*>(bytes->data() + header_size),
- ppf.frames[frame_index].color.xsize,
- ppf.frames[frame_index].color.ysize,
- ppf.info.num_color_channels);
+class PGMEncoder : public PPMEncoder {
+ public:
+ std::vector<JxlPixelFormat> AcceptedFormats() const override {
+ std::vector<JxlPixelFormat> formats = PPMEncoder::AcceptedFormats();
+ for (auto it = formats.begin(); it != formats.end();) {
+ if (it->num_channels > 2) {
+ it = formats.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ return formats;
}
+};
- return true;
+class PAMEncoder : public PPMEncoder {
+ public:
+ std::vector<JxlPixelFormat> AcceptedFormats() const override {
+ std::vector<JxlPixelFormat> formats = PPMEncoder::AcceptedFormats();
+ for (auto it = formats.begin(); it != formats.end();) {
+ if (it->num_channels != 2 && it->num_channels != 4) {
+ it = formats.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ return formats;
+ }
+};
+
+Span<const uint8_t> MakeSpan(const char* str) {
+ return Span<const uint8_t>(reinterpret_cast<const uint8_t*>(str),
+ strlen(str));
+}
+
+} // namespace
+
+std::unique_ptr<Encoder> GetPPMEncoder() {
+ return jxl::make_unique<PPMEncoder>();
+}
+
+std::unique_ptr<Encoder> GetPFMEncoder() {
+ return jxl::make_unique<PFMEncoder>();
+}
+
+std::unique_ptr<Encoder> GetPGMEncoder() {
+ return jxl::make_unique<PGMEncoder>();
+}
+
+std::unique_ptr<Encoder> GetPAMEncoder() {
+ return jxl::make_unique<PAMEncoder>();
}
} // namespace extras
diff --git a/media/libjxl/src/lib/extras/enc/pnm.h b/media/libjxl/src/lib/extras/enc/pnm.h
index ecf0526a1a..403208cecd 100644
--- a/media/libjxl/src/lib/extras/enc/pnm.h
+++ b/media/libjxl/src/lib/extras/enc/pnm.h
@@ -10,21 +10,17 @@
// TODO(janwas): workaround for incorrect Win64 codegen (cause unknown)
#include <hwy/highway.h>
+#include <memory>
-#include "lib/extras/packed_image.h"
-#include "lib/jxl/base/data_parallel.h"
-#include "lib/jxl/base/padded_bytes.h"
-#include "lib/jxl/base/status.h"
-#include "lib/jxl/codec_in_out.h"
-#include "lib/jxl/color_encoding_internal.h"
+#include "lib/extras/enc/encode.h"
namespace jxl {
namespace extras {
-// Transforms from io->c_current to `c_desired` and encodes into `bytes`.
-Status EncodeImagePNM(const PackedPixelFile& ppf, size_t bits_per_sample,
- ThreadPool* pool, size_t frame_index,
- std::vector<uint8_t>* bytes);
+std::unique_ptr<Encoder> GetPAMEncoder();
+std::unique_ptr<Encoder> GetPGMEncoder();
+std::unique_ptr<Encoder> GetPPMEncoder();
+std::unique_ptr<Encoder> GetPFMEncoder();
} // namespace extras
} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/exif.cc b/media/libjxl/src/lib/extras/exif.cc
new file mode 100644
index 0000000000..7d926558c3
--- /dev/null
+++ b/media/libjxl/src/lib/extras/exif.cc
@@ -0,0 +1,55 @@
+// 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/extras/exif.h"
+
+#include "lib/jxl/base/byte_order.h"
+
+namespace jxl {
+
+constexpr uint16_t kExifOrientationTag = 274;
+
+void ResetExifOrientation(std::vector<uint8_t>& exif) {
+ if (exif.size() < 12) return; // not enough bytes for a valid exif blob
+ bool bigendian;
+ uint8_t* t = exif.data();
+ if (LoadLE32(t) == 0x2A004D4D) {
+ bigendian = true;
+ } else if (LoadLE32(t) == 0x002A4949) {
+ bigendian = false;
+ } else {
+ return; // not a valid tiff header
+ }
+ t += 4;
+ uint32_t offset = (bigendian ? LoadBE32(t) : LoadLE32(t));
+ if (exif.size() < 12 + offset + 2 || offset < 8) return;
+ t += offset - 4;
+ uint16_t nb_tags = (bigendian ? LoadBE16(t) : LoadLE16(t));
+ t += 2;
+ while (nb_tags > 0) {
+ if (t + 12 >= exif.data() + exif.size()) return;
+ uint16_t tag = (bigendian ? LoadBE16(t) : LoadLE16(t));
+ t += 2;
+ if (tag == kExifOrientationTag) {
+ uint16_t type = (bigendian ? LoadBE16(t) : LoadLE16(t));
+ t += 2;
+ uint32_t count = (bigendian ? LoadBE32(t) : LoadLE32(t));
+ t += 4;
+ if (type == 3 && count == 1) {
+ if (bigendian) {
+ StoreBE16(1, t);
+ } else {
+ StoreLE16(1, t);
+ }
+ }
+ return;
+ } else {
+ t += 10;
+ nb_tags--;
+ }
+ }
+}
+
+} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/exif.h b/media/libjxl/src/lib/extras/exif.h
new file mode 100644
index 0000000000..f22b2ccef5
--- /dev/null
+++ b/media/libjxl/src/lib/extras/exif.h
@@ -0,0 +1,20 @@
+// 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.
+
+#ifndef LIB_EXTRAS_EXIF_H_
+#define LIB_EXTRAS_EXIF_H_
+
+#include <stdint.h>
+
+#include <vector>
+
+namespace jxl {
+
+// Sets the Exif orientation to the identity, to avoid repeated orientation
+void ResetExifOrientation(std::vector<uint8_t>& exif);
+
+} // namespace jxl
+
+#endif // LIB_EXTRAS_EXIF_H_
diff --git a/media/libjxl/src/lib/extras/packed_image.h b/media/libjxl/src/lib/extras/packed_image.h
index 59dbfe57c4..1296472101 100644
--- a/media/libjxl/src/lib/extras/packed_image.h
+++ b/media/libjxl/src/lib/extras/packed_image.h
@@ -22,7 +22,6 @@
#include "jxl/codestream_header.h"
#include "jxl/encode.h"
#include "jxl/types.h"
-#include "lib/jxl/base/status.h"
#include "lib/jxl/common.h"
namespace jxl {
@@ -33,26 +32,6 @@ class PackedImage {
public:
PackedImage(size_t xsize, size_t ysize, const JxlPixelFormat& format)
: PackedImage(xsize, ysize, format, CalcStride(format, xsize)) {}
- PackedImage(size_t xsize, size_t ysize, const JxlPixelFormat& format,
- size_t stride)
- : xsize(xsize),
- ysize(ysize),
- stride(stride),
- format(format),
- pixels_size(ysize * stride),
- pixels_(malloc(std::max<size_t>(1, pixels_size)), free) {}
- // Construct the image using the passed pixel buffer. The buffer is owned by
- // this object and released with free().
- PackedImage(size_t xsize, size_t ysize, const JxlPixelFormat& format,
- void* pixels, size_t pixels_size)
- : xsize(xsize),
- ysize(ysize),
- stride(CalcStride(format, xsize)),
- format(format),
- pixels_size(pixels_size),
- pixels_(pixels, free) {
- JXL_ASSERT(pixels_size >= stride * ysize);
- }
// The interleaved pixels as defined in the storage format.
void* pixels() const { return pixels_.get(); }
@@ -61,15 +40,6 @@ class PackedImage {
size_t xsize;
size_t ysize;
- // Whether the y coordinate is flipped (y=0 is the last row).
- bool flipped_y = false;
-
- // Whether the range is determined by format or by JxlBasicInfo
- // e.g. if format is UINT16 and JxlBasicInfo bits_per_sample is 10,
- // then if bitdepth_from_format == true, the range is 0..65535
- // while if bitdepth_from_format == false, the range is 0..1023.
- bool bitdepth_from_format = true;
-
// The number of bytes per row.
size_t stride;
@@ -77,6 +47,11 @@ class PackedImage {
JxlPixelFormat format;
size_t pixels_size;
+ size_t pixel_stride() const {
+ return (BitsPerChannel(format.data_type) * format.num_channels /
+ jxl::kBitsPerByte);
+ }
+
static size_t BitsPerChannel(JxlDataType data_type) {
switch (data_type) {
case JXL_TYPE_UINT8:
@@ -93,6 +68,15 @@ class PackedImage {
}
private:
+ PackedImage(size_t xsize, size_t ysize, const JxlPixelFormat& format,
+ size_t stride)
+ : xsize(xsize),
+ ysize(ysize),
+ stride(stride),
+ format(format),
+ pixels_size(ysize * stride),
+ pixels_(malloc(std::max<size_t>(1, pixels_size)), free) {}
+
static size_t CalcStride(const JxlPixelFormat& format, size_t xsize) {
size_t stride = xsize * (BitsPerChannel(format.data_type) *
format.num_channels / jxl::kBitsPerByte);
@@ -140,20 +124,20 @@ class PackedPixelFile {
// The extra channel metadata information.
struct PackedExtraChannel {
- PackedExtraChannel(const JxlExtraChannelInfo& ec_info,
- const std::string& name)
- : ec_info(ec_info), name(name) {}
-
JxlExtraChannelInfo ec_info;
+ size_t index;
std::string name;
};
std::vector<PackedExtraChannel> extra_channels_info;
- // Color information. If the icc is empty, the JxlColorEncoding should be used
- // instead.
+ // Color information of the decoded pixels.
+ // If the icc is empty, the JxlColorEncoding should be used instead.
std::vector<uint8_t> icc;
JxlColorEncoding color_encoding = {};
+ // The icc profile of the original image.
+ std::vector<uint8_t> orig_icc;
+ std::unique_ptr<PackedFrame> preview_frame;
std::vector<PackedFrame> frames;
PackedMetadata metadata;
diff --git a/media/libjxl/src/lib/extras/packed_image_convert.cc b/media/libjxl/src/lib/extras/packed_image_convert.cc
index 65336a70a1..dcdd12a673 100644
--- a/media/libjxl/src/lib/extras/packed_image_convert.cc
+++ b/media/libjxl/src/lib/extras/packed_image_convert.cc
@@ -20,6 +20,60 @@
namespace jxl {
namespace extras {
+Status ConvertPackedFrameToImageBundle(const JxlBasicInfo& info,
+ const PackedFrame& frame,
+ const CodecInOut& io, ThreadPool* pool,
+ ImageBundle* bundle) {
+ JXL_ASSERT(frame.color.pixels() != nullptr);
+ const bool float_in = frame.color.format.data_type == JXL_TYPE_FLOAT16 ||
+ frame.color.format.data_type == JXL_TYPE_FLOAT;
+ size_t frame_bits_per_sample =
+ float_in ? PackedImage::BitsPerChannel(frame.color.format.data_type)
+ : info.bits_per_sample;
+ JXL_ASSERT(frame_bits_per_sample != 0);
+ // It is ok for the frame.color.format.num_channels to not match the
+ // number of channels on the image.
+ JXL_ASSERT(1 <= frame.color.format.num_channels &&
+ frame.color.format.num_channels <= 4);
+
+ const Span<const uint8_t> span(
+ static_cast<const uint8_t*>(frame.color.pixels()),
+ frame.color.pixels_size);
+ JXL_ASSERT(Rect(frame.frame_info.layer_info.crop_x0,
+ frame.frame_info.layer_info.crop_y0,
+ frame.frame_info.layer_info.xsize,
+ frame.frame_info.layer_info.ysize)
+ .IsInside(Rect(0, 0, info.xsize, info.ysize)));
+ if (info.have_animation) {
+ bundle->duration = frame.frame_info.duration;
+ bundle->blend = frame.frame_info.layer_info.blend_info.blendmode > 0;
+ bundle->use_for_next_frame =
+ frame.frame_info.layer_info.save_as_reference > 0;
+ bundle->origin.x0 = frame.frame_info.layer_info.crop_x0;
+ bundle->origin.y0 = frame.frame_info.layer_info.crop_y0;
+ }
+ bundle->name = frame.name; // frame.frame_info.name_length is ignored here.
+ JXL_ASSERT(io.metadata.m.color_encoding.IsGray() ==
+ (frame.color.format.num_channels <= 2));
+
+ JXL_RETURN_IF_ERROR(ConvertFromExternal(
+ span, frame.color.xsize, frame.color.ysize, io.metadata.m.color_encoding,
+ frame.color.format.num_channels,
+ /*alpha_is_premultiplied=*/info.alpha_premultiplied,
+ frame_bits_per_sample, frame.color.format.endianness, pool, bundle,
+ /*float_in=*/float_in, /*align=*/0));
+
+ bundle->extra_channels().resize(io.metadata.m.extra_channel_info.size());
+ for (size_t i = 0; i < frame.extra_channels.size(); i++) {
+ const auto& ppf_ec = frame.extra_channels[i];
+ bundle->extra_channels()[i] = ImageF(ppf_ec.xsize, ppf_ec.ysize);
+ JXL_CHECK(BufferToImageF(ppf_ec.format, ppf_ec.xsize, ppf_ec.ysize,
+ ppf_ec.pixels(), ppf_ec.pixels_size, pool,
+ &bundle->extra_channels()[i]));
+ }
+ return true;
+}
+
Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
ThreadPool* pool, CodecInOut* io) {
const bool has_alpha = ppf.info.alpha_bits != 0;
@@ -63,7 +117,7 @@ Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
PaddedBytes icc;
icc.append(ppf.icc);
if (!io->metadata.m.color_encoding.SetICC(std::move(icc))) {
- fprintf(stderr, "Warning: error setting ICC profile, assuming SRGB");
+ 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.IsGray() != is_gray) {
@@ -105,61 +159,24 @@ Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
io->metadata.m.extra_channel_info.push_back(std::move(out));
}
+ // Convert the preview
+ if (ppf.preview_frame) {
+ size_t preview_xsize = ppf.preview_frame->color.xsize;
+ size_t preview_ysize = ppf.preview_frame->color.ysize;
+ io->metadata.m.have_preview = true;
+ JXL_RETURN_IF_ERROR(
+ io->metadata.m.preview_size.Set(preview_xsize, preview_ysize));
+ JXL_RETURN_IF_ERROR(ConvertPackedFrameToImageBundle(
+ ppf.info, *ppf.preview_frame, *io, pool, &io->preview_frame));
+ }
+
// Convert the pixels
io->dec_pixels = 0;
io->frames.clear();
for (const auto& frame : ppf.frames) {
- JXL_ASSERT(frame.color.pixels() != nullptr);
- size_t frame_bits_per_sample =
- (frame.color.bitdepth_from_format
- ? frame.color.BitsPerChannel(frame.color.format.data_type)
- : ppf.info.bits_per_sample);
- JXL_ASSERT(frame_bits_per_sample != 0);
- // It is ok for the frame.color.format.num_channels to not match the
- // number of channels on the image.
- JXL_ASSERT(1 <= frame.color.format.num_channels &&
- frame.color.format.num_channels <= 4);
-
- const Span<const uint8_t> span(
- static_cast<const uint8_t*>(frame.color.pixels()),
- frame.color.pixels_size);
- Rect frame_rect = Rect(frame.frame_info.layer_info.crop_x0,
- frame.frame_info.layer_info.crop_y0,
- frame.frame_info.layer_info.xsize,
- frame.frame_info.layer_info.ysize);
- JXL_ASSERT(frame_rect.IsInside(Rect(0, 0, ppf.info.xsize, ppf.info.ysize)));
ImageBundle bundle(&io->metadata.m);
- if (ppf.info.have_animation) {
- bundle.duration = frame.frame_info.duration;
- bundle.blend = frame.frame_info.layer_info.blend_info.blendmode > 0;
- bundle.use_for_next_frame =
- frame.frame_info.layer_info.save_as_reference > 0;
- bundle.origin.x0 = frame.frame_info.layer_info.crop_x0;
- bundle.origin.y0 = frame.frame_info.layer_info.crop_y0;
- }
- bundle.name = frame.name; // frame.frame_info.name_length is ignored here.
- JXL_ASSERT(io->metadata.m.color_encoding.IsGray() ==
- (frame.color.format.num_channels <= 2));
-
- const bool float_in = frame.color.format.data_type == JXL_TYPE_FLOAT16 ||
- frame.color.format.data_type == JXL_TYPE_FLOAT;
- JXL_RETURN_IF_ERROR(ConvertFromExternal(
- span, frame.color.xsize, frame.color.ysize,
- io->metadata.m.color_encoding, frame.color.format.num_channels,
- /*alpha_is_premultiplied=*/ppf.info.alpha_premultiplied,
- frame_bits_per_sample, frame.color.format.endianness,
- /*flipped_y=*/frame.color.flipped_y, pool, &bundle,
- /*float_in=*/float_in, /*align=*/0));
-
- bundle.extra_channels().resize(io->metadata.m.extra_channel_info.size());
- for (size_t i = 0; i < frame.extra_channels.size(); i++) {
- const auto& ppf_ec = frame.extra_channels[i];
- bundle.extra_channels()[i] = ImageF(ppf_ec.xsize, ppf_ec.ysize);
- JXL_CHECK(BufferToImageF(ppf_ec.format, ppf_ec.xsize, ppf_ec.ysize,
- ppf_ec.pixels(), ppf_ec.pixels_size, pool,
- &bundle.extra_channels()[i]));
- }
-
+ JXL_RETURN_IF_ERROR(
+ ConvertPackedFrameToImageBundle(ppf.info, frame, *io, pool, &bundle));
io->frames.push_back(std::move(bundle));
io->dec_pixels += frame.color.xsize * frame.color.ysize;
}
@@ -222,9 +239,7 @@ Status ConvertCodecInOutToPackedPixelFile(const CodecInOut& io,
// Convert the color encoding
ppf->icc.assign(c_desired.ICC().begin(), c_desired.ICC().end());
- if (ppf->icc.empty()) {
- ConvertInternalToExternalColorEncoding(c_desired, &ppf->color_encoding);
- }
+ ConvertInternalToExternalColorEncoding(c_desired, &ppf->color_encoding);
// Convert the extra blobs
ppf->metadata.exif = io.blobs.exif;
@@ -236,8 +251,7 @@ Status ConvertCodecInOutToPackedPixelFile(const CodecInOut& io,
// Convert the pixels
ppf->frames.clear();
for (const auto& frame : io.frames) {
- size_t frame_bits_per_sample = frame.metadata()->bit_depth.bits_per_sample;
- JXL_ASSERT(frame_bits_per_sample != 0);
+ JXL_ASSERT(frame.metadata()->bit_depth.bits_per_sample != 0);
// It is ok for the frame.color().kNumPlanes to not match the
// number of channels on the image.
const uint32_t num_channels =
@@ -249,11 +263,9 @@ Status ConvertCodecInOutToPackedPixelFile(const CodecInOut& io,
PackedFrame packed_frame(frame.oriented_xsize(), frame.oriented_ysize(),
format);
- packed_frame.color.bitdepth_from_format = float_out;
const size_t bits_per_sample =
- packed_frame.color.bitdepth_from_format
- ? packed_frame.color.BitsPerChannel(pixel_format.data_type)
- : ppf->info.bits_per_sample;
+ float_out ? packed_frame.color.BitsPerChannel(pixel_format.data_type)
+ : ppf->info.bits_per_sample;
packed_frame.name = frame.name;
packed_frame.frame_info.name_length = frame.name.size();
// Color transform
diff --git a/media/libjxl/src/lib/extras/render_hdr.cc b/media/libjxl/src/lib/extras/render_hdr.cc
new file mode 100644
index 0000000000..b247699cd3
--- /dev/null
+++ b/media/libjxl/src/lib/extras/render_hdr.cc
@@ -0,0 +1,60 @@
+// 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/extras/render_hdr.h"
+
+#include "lib/extras/hlg.h"
+#include "lib/extras/tone_mapping.h"
+#include "lib/jxl/enc_color_management.h"
+
+namespace jxl {
+
+Status RenderHDR(CodecInOut* io, float display_nits, ThreadPool* pool) {
+ const ColorEncoding& original_color_encoding = io->metadata.m.color_encoding;
+ if (!(original_color_encoding.tf.IsPQ() ||
+ original_color_encoding.tf.IsHLG())) {
+ // Nothing to do.
+ return true;
+ }
+
+ if (original_color_encoding.tf.IsPQ()) {
+ JXL_RETURN_IF_ERROR(ToneMapTo({0, display_nits}, io, pool));
+ JXL_RETURN_IF_ERROR(GamutMap(io, /*preserve_saturation=*/0.1, pool));
+ } else {
+ const float intensity_target = io->metadata.m.IntensityTarget();
+ const float gamma_hlg_to_display = GetHlgGamma(display_nits);
+ // If the image is already in display space, we need to account for the
+ // already-applied OOTF.
+ const float gamma_display_to_display =
+ gamma_hlg_to_display / GetHlgGamma(intensity_target);
+ // Ensures that conversions to linear in HlgOOTF below will not themselves
+ // include the OOTF.
+ io->metadata.m.SetIntensityTarget(300);
+
+ bool need_gamut_mapping = false;
+ for (ImageBundle& ib : io->frames) {
+ const float gamma = ib.c_current().tf.IsHLG() ? gamma_hlg_to_display
+ : gamma_display_to_display;
+ if (gamma < 1) need_gamut_mapping = true;
+ JXL_RETURN_IF_ERROR(HlgOOTF(&ib, gamma, pool));
+ }
+ io->metadata.m.SetIntensityTarget(display_nits);
+
+ if (need_gamut_mapping) {
+ JXL_RETURN_IF_ERROR(GamutMap(io, /*preserve_saturation=*/0.1, pool));
+ }
+ }
+
+ ColorEncoding rec2020_pq;
+ rec2020_pq.SetColorSpace(ColorSpace::kRGB);
+ rec2020_pq.white_point = WhitePoint::kD65;
+ rec2020_pq.primaries = Primaries::k2100;
+ rec2020_pq.tf.SetTransferFunction(TransferFunction::kPQ);
+ JXL_RETURN_IF_ERROR(rec2020_pq.CreateICC());
+ io->metadata.m.color_encoding = rec2020_pq;
+ return io->TransformTo(rec2020_pq, GetJxlCms(), pool);
+}
+
+} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/render_hdr.h b/media/libjxl/src/lib/extras/render_hdr.h
new file mode 100644
index 0000000000..95127e074b
--- /dev/null
+++ b/media/libjxl/src/lib/extras/render_hdr.h
@@ -0,0 +1,27 @@
+// 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.
+
+#ifndef LIB_EXTRAS_RENDER_HDR_H_
+#define LIB_EXTRAS_RENDER_HDR_H_
+
+#include "lib/jxl/codec_in_out.h"
+
+namespace jxl {
+
+// If `io` has an original color space using PQ or HLG, this renders it
+// appropriately for a display with a peak luminance of `display_nits` and
+// converts the result to a Rec. 2020 / PQ image. Otherwise, leaves the image as
+// is.
+// PQ images are tone-mapped using the method described in Rep. ITU-R BT.2408-5
+// annex 5, while HLG images are rendered using the HLG OOTF with a gamma
+// appropriate for the given target luminance.
+// With a sufficiently bright SDR display, converting the output of this
+// function to an SDR colorspace may look decent.
+Status RenderHDR(CodecInOut* io, float display_nits,
+ ThreadPool* pool = nullptr);
+
+} // namespace jxl
+
+#endif // LIB_EXTRAS_RENDER_HDR_H_
diff --git a/media/libjxl/src/lib/extras/tone_mapping.cc b/media/libjxl/src/lib/extras/tone_mapping.cc
index ac4306f8ed..1ed1b29119 100644
--- a/media/libjxl/src/lib/extras/tone_mapping.cc
+++ b/media/libjxl/src/lib/extras/tone_mapping.cc
@@ -10,13 +10,15 @@
#include <hwy/foreach_target.h>
#include <hwy/highway.h>
+#include "lib/jxl/dec_tone_mapping-inl.h"
#include "lib/jxl/enc_color_management.h"
-#include "lib/jxl/transfer_functions-inl.h"
HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
+static constexpr float rec2020_luminances[3] = {0.2627f, 0.6780f, 0.0593f};
+
Status ToneMapFrame(const std::pair<float, float> display_nits,
ImageBundle* const ib, ThreadPool* const pool) {
// Perform tone mapping as described in Report ITU-R BT.2390-8, section 5.4
@@ -34,42 +36,12 @@ Status ToneMapFrame(const std::pair<float, float> display_nits,
JXL_RETURN_IF_ERROR(linear_rec2020.CreateICC());
JXL_RETURN_IF_ERROR(ib->TransformTo(linear_rec2020, GetJxlCms(), pool));
- const auto eotf_inv = [&df](const V luminance) -> V {
- return TF_PQ().EncodedFromDisplay(df, luminance * Set(df, 1. / 10000));
- };
-
- const V pq_mastering_min =
- eotf_inv(Set(df, ib->metadata()->tone_mapping.min_nits));
- const V pq_mastering_max =
- eotf_inv(Set(df, ib->metadata()->tone_mapping.intensity_target));
- const V pq_mastering_range = pq_mastering_max - pq_mastering_min;
- const V inv_pq_mastering_range =
- Set(df, 1) / (pq_mastering_max - pq_mastering_min);
- const V min_lum = (eotf_inv(Set(df, display_nits.first)) - pq_mastering_min) *
- inv_pq_mastering_range;
- const V max_lum =
- (eotf_inv(Set(df, display_nits.second)) - pq_mastering_min) *
- inv_pq_mastering_range;
- const V ks = MulAdd(Set(df, 1.5f), max_lum, Set(df, -0.5f));
- const V b = min_lum;
-
- const V inv_one_minus_ks = Set(df, 1) / Max(Set(df, 1e-6f), Set(df, 1) - ks);
- const auto T = [ks, inv_one_minus_ks](const V a) {
- return (a - ks) * inv_one_minus_ks;
- };
- const auto P = [&T, &df, ks, max_lum](const V b) {
- const V t_b = T(b);
- const V t_b_2 = t_b * t_b;
- const V t_b_3 = t_b_2 * t_b;
- return MulAdd(
- MulAdd(Set(df, 2), t_b_3, MulAdd(Set(df, -3), t_b_2, Set(df, 1))), ks,
- MulAdd(t_b_3 + MulAdd(Set(df, -2), t_b_2, t_b), Set(df, 1) - ks,
- MulAdd(Set(df, -2), t_b_3, Set(df, 3) * t_b_2) * max_lum));
- };
-
- const V inv_max_display_nits = Set(df, 1 / display_nits.second);
+ Rec2408ToneMapper<decltype(df)> tone_mapper(
+ {ib->metadata()->tone_mapping.min_nits,
+ ib->metadata()->IntensityTarget()},
+ display_nits, rec2020_luminances);
- JXL_RETURN_IF_ERROR(RunOnPool(
+ return RunOnPool(
pool, 0, ib->ysize(), ThreadPool::NoInit,
[&](const uint32_t y, size_t /* thread */) {
float* const JXL_RESTRICT row_r = ib->color()->PlaneRow(0, y);
@@ -79,43 +51,13 @@ Status ToneMapFrame(const std::pair<float, float> display_nits,
V red = Load(df, row_r + x);
V green = Load(df, row_g + x);
V blue = Load(df, row_b + x);
- const V luminance = Set(df, ib->metadata()->IntensityTarget()) *
- (MulAdd(Set(df, 0.2627f), red,
- MulAdd(Set(df, 0.6780f), green,
- Set(df, 0.0593f) * blue)));
- const V normalized_pq =
- Min(Set(df, 1.f), (eotf_inv(luminance) - pq_mastering_min) *
- inv_pq_mastering_range);
- const V e2 =
- IfThenElse(normalized_pq < ks, normalized_pq, P(normalized_pq));
- const V one_minus_e2 = Set(df, 1) - e2;
- const V one_minus_e2_2 = one_minus_e2 * one_minus_e2;
- const V one_minus_e2_4 = one_minus_e2_2 * one_minus_e2_2;
- const V e3 = MulAdd(b, one_minus_e2_4, e2);
- const V e4 = MulAdd(e3, pq_mastering_range, pq_mastering_min);
- const V new_luminance =
- Min(Set(df, display_nits.second),
- ZeroIfNegative(Set(df, 10000) *
- TF_PQ().DisplayFromEncoded(df, e4)));
-
- const V ratio = new_luminance / luminance;
- const V normalizer =
- Set(df, ib->metadata()->IntensityTarget()) * inv_max_display_nits;
-
- for (V* const val : {&red, &green, &blue}) {
- *val = IfThenElse(luminance <= Set(df, 1e-6f), new_luminance,
- *val * ratio) *
- normalizer;
- }
-
+ tone_mapper.ToneMap(&red, &green, &blue);
Store(red, df, row_r + x);
Store(green, df, row_g + x);
Store(blue, df, row_b + x);
}
},
- "ToneMap"));
-
- return true;
+ "ToneMap");
}
Status GamutMapFrame(ImageBundle* const ib, float preserve_saturation,
@@ -141,44 +83,8 @@ Status GamutMapFrame(ImageBundle* const ib, float preserve_saturation,
V red = Load(df, row_r + x);
V green = Load(df, row_g + x);
V blue = Load(df, row_b + x);
- const V luminance =
- MulAdd(Set(df, 0.2627f), red,
- MulAdd(Set(df, 0.6780f), green, Set(df, 0.0593f) * blue));
-
- // Desaturate out-of-gamut pixels. This is done by mixing each pixel
- // with just enough gray of the target luminance to make all
- // components non-negative.
- // - For saturation preservation, if a component is still larger than
- // 1 then the pixel is normalized to have a maximum component of 1.
- // That will reduce its luminance.
- // - For luminance preservation, getting all components below 1 is
- // done by mixing in yet more gray. That will desaturate it further.
- V gray_mix_saturation = Zero(df);
- V gray_mix_luminance = Zero(df);
- for (const V val : {red, green, blue}) {
- const V inv_val_minus_gray = Set(df, 1) / (val - luminance);
- gray_mix_saturation =
- IfThenElse(val >= luminance, gray_mix_saturation,
- Max(gray_mix_saturation, val * inv_val_minus_gray));
- gray_mix_luminance =
- Max(gray_mix_luminance,
- IfThenElse(val <= luminance, gray_mix_saturation,
- (val - Set(df, 1)) * inv_val_minus_gray));
- }
- const V gray_mix =
- Clamp(Set(df, preserve_saturation) *
- (gray_mix_saturation - gray_mix_luminance) +
- gray_mix_luminance,
- Zero(df), Set(df, 1));
- for (V* const val : {&red, &green, &blue}) {
- *val = MulAdd(gray_mix, luminance - *val, *val);
- }
- const V normalizer =
- Set(df, 1) / Max(Set(df, 1), Max(red, Max(green, blue)));
- for (V* const val : {&red, &green, &blue}) {
- *val = *val * normalizer;
- }
-
+ GamutMap(&red, &green, &blue, rec2020_luminances,
+ preserve_saturation);
Store(red, df, row_r + x);
Store(green, df, row_g + x);
Store(blue, df, row_b + x);
diff --git a/media/libjxl/src/lib/extras/tone_mapping_gbench.cc b/media/libjxl/src/lib/extras/tone_mapping_gbench.cc
index b156992813..2f97b88667 100644
--- a/media/libjxl/src/lib/extras/tone_mapping_gbench.cc
+++ b/media/libjxl/src/lib/extras/tone_mapping_gbench.cc
@@ -13,8 +13,7 @@ namespace jxl {
static void BM_ToneMapping(benchmark::State& state) {
CodecInOut image;
- const PaddedBytes image_bytes =
- ReadTestData("third_party/imagecompression.info/flower_foveon.png");
+ const PaddedBytes image_bytes = ReadTestData("jxl/flower/flower.png");
JXL_CHECK(SetFromBytes(Span<const uint8_t>(image_bytes), &image));
// Convert to linear Rec. 2020 so that `ToneMapTo` doesn't have to and we