summaryrefslogtreecommitdiffstats
path: root/third_party/jpeg-xl/lib/extras/enc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--third_party/jpeg-xl/lib/extras/enc/apng.cc371
-rw-r--r--third_party/jpeg-xl/lib/extras/enc/apng.h23
-rw-r--r--third_party/jpeg-xl/lib/extras/enc/encode.cc170
-rw-r--r--third_party/jpeg-xl/lib/extras/enc/encode.h83
-rw-r--r--third_party/jpeg-xl/lib/extras/enc/exr.cc200
-rw-r--r--third_party/jpeg-xl/lib/extras/enc/exr.h23
-rw-r--r--third_party/jpeg-xl/lib/extras/enc/jpegli.cc503
-rw-r--r--third_party/jpeg-xl/lib/extras/enc/jpegli.h47
-rw-r--r--third_party/jpeg-xl/lib/extras/enc/jpg.cc427
-rw-r--r--third_party/jpeg-xl/lib/extras/enc/jpg.h23
-rw-r--r--third_party/jpeg-xl/lib/extras/enc/jxl.cc276
-rw-r--r--third_party/jpeg-xl/lib/extras/enc/jxl.h78
-rw-r--r--third_party/jpeg-xl/lib/extras/enc/npy.cc322
-rw-r--r--third_party/jpeg-xl/lib/extras/enc/npy.h23
-rw-r--r--third_party/jpeg-xl/lib/extras/enc/pgx.cc123
-rw-r--r--third_party/jpeg-xl/lib/extras/enc/pgx.h24
-rw-r--r--third_party/jpeg-xl/lib/extras/enc/pnm.cc303
-rw-r--r--third_party/jpeg-xl/lib/extras/enc/pnm.h28
18 files changed, 3047 insertions, 0 deletions
diff --git a/third_party/jpeg-xl/lib/extras/enc/apng.cc b/third_party/jpeg-xl/lib/extras/enc/apng.cc
new file mode 100644
index 0000000000..79d083349d
--- /dev/null
+++ b/third_party/jpeg-xl/lib/extras/enc/apng.cc
@@ -0,0 +1,371 @@
+// 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/apng.h"
+
+// Parts of this code are taken from apngdis, which has the following license:
+/* APNG Disassembler 2.8
+ *
+ * Deconstructs APNG files into individual frames.
+ *
+ * http://apngdis.sourceforge.net
+ *
+ * Copyright (c) 2010-2015 Max Stepin
+ * maxst at users.sourceforge.net
+ *
+ * zlib license
+ * ------------
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include <string>
+#include <vector>
+
+#include "lib/extras/exif.h"
+#include "lib/jxl/base/byte_order.h"
+#include "lib/jxl/base/printf_macros.h"
+#include "png.h" /* original (unpatched) libpng is ok */
+
+namespace jxl {
+namespace extras {
+
+namespace {
+
+constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69,
+ 0x66, 0x00, 0x00};
+
+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}) {
+ for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) {
+ formats.push_back(
+ JxlPixelFormat{num_channels, data_type, 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.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) {
+ 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 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
+ // identity to avoid repeated orientation.
+ std::vector<uint8_t> exif = blobs.exif;
+ ResetExifOrientation(exif);
+ // By convention, the data is prefixed with "Exif\0\0" when stored in
+ // the legacy (and non-standard) "Raw profile type exif" text chunk
+ // currently used here.
+ // TODO: Store Exif data in an eXIf chunk instead, which always begins
+ // with the TIFF header.
+ if (exif.size() >= sizeof kExifSignature &&
+ memcmp(exif.data(), kExifSignature, sizeof kExifSignature) != 0) {
+ exif.insert(exif.begin(), kExifSignature,
+ kExifSignature + sizeof kExifSignature);
+ }
+ JXL_RETURN_IF_ERROR(EncodeBase16("exif", exif, strings));
+ }
+ if (!blobs.iptc.empty()) {
+ JXL_RETURN_IF_ERROR(EncodeBase16("iptc", blobs.iptc, strings));
+ }
+ if (!blobs.xmp.empty()) {
+ // TODO: Store XMP data in an "XML:com.adobe.xmp" text chunk instead.
+ JXL_RETURN_IF_ERROR(EncodeBase16("xmp", blobs.xmp, strings));
+ }
+ return true;
+ }
+
+ private:
+ static JXL_INLINE char EncodeNibble(const uint8_t nibble) {
+ JXL_ASSERT(nibble < 16);
+ return (nibble < 10) ? '0' + nibble : 'a' + nibble - 10;
+ }
+
+ static Status EncodeBase16(const std::string& type,
+ const std::vector<uint8_t>& bytes,
+ std::vector<std::string>* strings) {
+ // Encoding: base16 with newline after 72 chars.
+ const size_t base16_size =
+ 2 * bytes.size() + DivCeil(bytes.size(), size_t(36)) + 1;
+ std::string base16;
+ base16.reserve(base16_size);
+ for (size_t i = 0; i < bytes.size(); ++i) {
+ if (i % 36 == 0) base16.push_back('\n');
+ base16.push_back(EncodeNibble(bytes[i] >> 4));
+ base16.push_back(EncodeNibble(bytes[i] & 0x0F));
+ }
+ base16.push_back('\n');
+ JXL_ASSERT(base16.length() == base16_size);
+
+ char key[30];
+ snprintf(key, sizeof(key), "Raw profile type %s", type.c_str());
+
+ char header[30];
+ snprintf(header, sizeof(header), "\n%s\n%8" PRIuS, type.c_str(),
+ bytes.size());
+
+ strings->push_back(std::string(key));
+ strings->push_back(std::string(header) + base16);
+ return true;
+ }
+};
+
+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 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;
+ size_t anim_chunks = 0;
+
+ 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);
+ }
+ }
+ png_structp png_ptr;
+ png_infop info_ptr;
+
+ png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+
+ if (!png_ptr) return JXL_FAILURE("Could not init png encoder");
+
+ info_ptr = png_create_info_struct(png_ptr);
+ if (!info_ptr) return JXL_FAILURE("Could not init png info struct");
+
+ png_set_write_fn(png_ptr, bytes, PngWrite, NULL);
+ png_set_flush(png_ptr, 0);
+
+ 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) {
+ 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(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());
+ text.text = const_cast<png_charp>(textstrings[kk + 1].c_str());
+ text.compression = PNG_TEXT_COMPRESSION_zTXt;
+ png_set_text(png_ptr, info_ptr, &text, 1);
+ }
+
+ png_write_info(png_ptr, info_ptr);
+ } else {
+ // fake writing a header, otherwise libpng gets confused
+ size_t pos = bytes->size();
+ png_write_info(png_ptr, info_ptr);
+ bytes->resize(pos);
+ }
+
+ if (ppf.info.have_animation) {
+ if (count == 0) {
+ png_byte adata[8];
+ 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];
+ // 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.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";
+ png_write_chunk(png_ptr, fctl, fdata, 26);
+ }
+
+ std::vector<uint8_t*> rows(height);
+ for (int y = 0; y < height; ++y) {
+ rows[y] = out.data() + y * out_stride;
+ }
+
+ png_write_flush(png_ptr);
+ const size_t pos = bytes->size();
+ png_write_image(png_ptr, &rows[0]);
+ png_write_flush(png_ptr);
+ if (count > 0) {
+ std::vector<uint8_t> fdata(4);
+ png_save_uint_32(fdata.data(), anim_chunks++);
+ size_t p = pos;
+ while (p + 8 < bytes->size()) {
+ size_t len = png_get_uint_32(bytes->data() + p);
+ JXL_ASSERT(bytes->operator[](p + 4) == 'I');
+ JXL_ASSERT(bytes->operator[](p + 5) == 'D');
+ JXL_ASSERT(bytes->operator[](p + 6) == 'A');
+ JXL_ASSERT(bytes->operator[](p + 7) == 'T');
+ fdata.insert(fdata.end(), bytes->data() + p + 8,
+ bytes->data() + p + 8 + len);
+ p += len + 12;
+ }
+ bytes->resize(pos);
+
+ png_byte fdat[5] = "fdAT";
+ png_write_chunk(png_ptr, fdat, fdata.data(), fdata.size());
+ }
+
+ count++;
+ if (count == ppf.frames.size() || !ppf.info.have_animation) {
+ png_write_end(png_ptr, NULL);
+ }
+
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ }
+
+ return true;
+}
+
+} // namespace
+
+std::unique_ptr<Encoder> GetAPNGEncoder() {
+ return jxl::make_unique<APNGEncoder>();
+}
+
+} // namespace extras
+} // namespace jxl
diff --git a/third_party/jpeg-xl/lib/extras/enc/apng.h b/third_party/jpeg-xl/lib/extras/enc/apng.h
new file mode 100644
index 0000000000..2a2139c8fa
--- /dev/null
+++ b/third_party/jpeg-xl/lib/extras/enc/apng.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_APNG_H_
+#define LIB_EXTRAS_ENC_APNG_H_
+
+// Encodes APNG images in memory.
+
+#include <memory>
+
+#include "lib/extras/enc/encode.h"
+
+namespace jxl {
+namespace extras {
+
+std::unique_ptr<Encoder> GetAPNGEncoder();
+
+} // namespace extras
+} // namespace jxl
+
+#endif // LIB_EXTRAS_ENC_APNG_H_
diff --git a/third_party/jpeg-xl/lib/extras/enc/encode.cc b/third_party/jpeg-xl/lib/extras/enc/encode.cc
new file mode 100644
index 0000000000..9ffba9d0dd
--- /dev/null
+++ b/third_party/jpeg-xl/lib/extras/enc/encode.cc
@@ -0,0 +1,170 @@
+// 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) {
+ 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::VerifyFormat(const JxlPixelFormat& format) const {
+ for (auto f : AcceptedFormats()) {
+ if (f.num_channels != format.num_channels) continue;
+ if (f.data_type != format.data_type) continue;
+ if (f.data_type == JXL_TYPE_UINT8 || f.endianness == format.endianness) {
+ return true;
+ }
+ }
+ return JXL_FAILURE("Format is not in the list of accepted formats.");
+}
+
+Status Encoder::VerifyBitDepth(JxlDataType data_type, uint32_t bits_per_sample,
+ uint32_t exponent_bits) {
+ if ((data_type == JXL_TYPE_UINT8 &&
+ (bits_per_sample == 0 || bits_per_sample > 8 || exponent_bits != 0)) ||
+ (data_type == JXL_TYPE_UINT16 &&
+ (bits_per_sample <= 8 || bits_per_sample > 16 || exponent_bits != 0)) ||
+ (data_type == JXL_TYPE_FLOAT16 &&
+ (bits_per_sample != 16 || exponent_bits != 5)) ||
+ (data_type == JXL_TYPE_FLOAT &&
+ (bits_per_sample != 32 || exponent_bits != 8))) {
+ return JXL_FAILURE(
+ "Incompatible data_type %d and bit depth %u with exponent bits %u",
+ (int)data_type, bits_per_sample, exponent_bits);
+ }
+ return true;
+}
+
+Status Encoder::VerifyImageSize(const PackedImage& image,
+ const JxlBasicInfo& info) {
+ 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");
+ }
+ return true;
+}
+
+Status Encoder::VerifyPackedImage(const PackedImage& image,
+ const JxlBasicInfo& info) const {
+ JXL_RETURN_IF_ERROR(VerifyImageSize(image, info));
+ JXL_RETURN_IF_ERROR(VerifyFormat(image.format));
+ JXL_RETURN_IF_ERROR(VerifyBitDepth(image.format.data_type,
+ info.bits_per_sample,
+ info.exponent_bits_per_sample));
+ 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/third_party/jpeg-xl/lib/extras/enc/encode.h b/third_party/jpeg-xl/lib/extras/enc/encode.h
new file mode 100644
index 0000000000..63cabaf30f
--- /dev/null
+++ b/third_party/jpeg-xl/lib/extras/enc/encode.h
@@ -0,0 +1,83 @@
+// 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);
+ }
+
+ static Status VerifyBasicInfo(const JxlBasicInfo& info);
+ static Status VerifyImageSize(const PackedImage& image,
+ const JxlBasicInfo& info);
+ static Status VerifyBitDepth(JxlDataType data_type, uint32_t bits_per_sample,
+ uint32_t exponent_bits);
+
+ protected:
+ const std::unordered_map<std::string, std::string>& options() const {
+ return options_;
+ }
+
+ Status VerifyFormat(const JxlPixelFormat& format) 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/third_party/jpeg-xl/lib/extras/enc/exr.cc b/third_party/jpeg-xl/lib/extras/enc/exr.cc
new file mode 100644
index 0000000000..1d70913936
--- /dev/null
+++ b/third_party/jpeg-xl/lib/extras/enc/exr.cc
@@ -0,0 +1,200 @@
+// 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/exr.h"
+
+#include <ImfChromaticitiesAttribute.h>
+#include <ImfIO.h>
+#include <ImfRgbaFile.h>
+#include <ImfStandardAttributes.h>
+#include <jxl/codestream_header.h>
+
+#include <vector>
+
+#include "lib/extras/packed_image.h"
+#include "lib/jxl/base/byte_order.h"
+
+namespace jxl {
+namespace extras {
+
+namespace {
+
+namespace OpenEXR = OPENEXR_IMF_NAMESPACE;
+namespace Imath = IMATH_NAMESPACE;
+
+// OpenEXR::Int64 is deprecated in favor of using uint64_t directly, but using
+// uint64_t as recommended causes build failures with previous OpenEXR versions
+// on macOS, where the definition for OpenEXR::Int64 was actually not equivalent
+// to uint64_t. This alternative should work in all cases.
+using ExrInt64 = decltype(std::declval<OpenEXR::IStream>().tellg());
+
+class InMemoryOStream : public OpenEXR::OStream {
+ public:
+ // `bytes` must outlive the InMemoryOStream.
+ explicit InMemoryOStream(std::vector<uint8_t>* const bytes)
+ : OStream(/*fileName=*/""), bytes_(*bytes) {}
+
+ void write(const char c[], const int n) override {
+ if (bytes_.size() < pos_ + n) {
+ bytes_.resize(pos_ + n);
+ }
+ std::copy_n(c, n, bytes_.begin() + pos_);
+ pos_ += n;
+ }
+
+ ExrInt64 tellp() override { return pos_; }
+ void seekp(const ExrInt64 pos) override {
+ if (bytes_.size() + 1 < pos) {
+ bytes_.resize(pos - 1);
+ }
+ pos_ = pos;
+ }
+
+ private:
+ std::vector<uint8_t>& bytes_;
+ size_t pos_ = 0;
+};
+
+// 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");
+ }
+
+ 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(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_enc.white_point_xy[0], c_enc.white_point_xy[1]);
+ OpenEXR::addChromaticities(header, chromaticities);
+ OpenEXR::addWhiteLuminance(header, info.intensity_target);
+
+ 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`.
+ {
+ InMemoryOStream os(bytes);
+ OpenEXR::RgbaOutputFile output(
+ 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 = ysize;
+ std::vector<OpenEXR::Rgba> output_rows(xsize * 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, 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);
+ }
+ }
+
+ 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/third_party/jpeg-xl/lib/extras/enc/exr.h b/third_party/jpeg-xl/lib/extras/enc/exr.h
new file mode 100644
index 0000000000..1baaa0272f
--- /dev/null
+++ b/third_party/jpeg-xl/lib/extras/enc/exr.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_EXR_H_
+#define LIB_EXTRAS_ENC_EXR_H_
+
+// Encodes OpenEXR images in memory.
+
+#include <memory>
+
+#include "lib/extras/enc/encode.h"
+
+namespace jxl {
+namespace extras {
+
+std::unique_ptr<Encoder> GetEXREncoder();
+
+} // namespace extras
+} // namespace jxl
+
+#endif // LIB_EXTRAS_ENC_EXR_H_
diff --git a/third_party/jpeg-xl/lib/extras/enc/jpegli.cc b/third_party/jpeg-xl/lib/extras/enc/jpegli.cc
new file mode 100644
index 0000000000..43cf32a19c
--- /dev/null
+++ b/third_party/jpeg-xl/lib/extras/enc/jpegli.cc
@@ -0,0 +1,503 @@
+// 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/jpegli.h"
+
+#include <jxl/codestream_header.h>
+#include <setjmp.h>
+#include <stdint.h>
+
+#include "lib/extras/enc/encode.h"
+#include "lib/jpegli/encode.h"
+#include "lib/jxl/enc_color_management.h"
+#include "lib/jxl/enc_xyb.h"
+
+namespace jxl {
+namespace extras {
+
+namespace {
+
+void MyErrorExit(j_common_ptr cinfo) {
+ jmp_buf* env = static_cast<jmp_buf*>(cinfo->client_data);
+ (*cinfo->err->output_message)(cinfo);
+ jpegli_destroy_compress(reinterpret_cast<j_compress_ptr>(cinfo));
+ longjmp(*env, 1);
+}
+
+Status VerifyInput(const PackedPixelFile& ppf) {
+ const JxlBasicInfo& info = ppf.info;
+ JXL_RETURN_IF_ERROR(Encoder::VerifyBasicInfo(info));
+ if (info.alpha_bits > 0) {
+ return JXL_FAILURE("Alpha is not supported for JPEG output.");
+ }
+ if (ppf.frames.size() != 1) {
+ return JXL_FAILURE("JPEG input must have exactly one frame.");
+ }
+ const PackedImage& image = ppf.frames[0].color;
+ JXL_RETURN_IF_ERROR(Encoder::VerifyImageSize(image, info));
+ if (image.format.data_type == JXL_TYPE_FLOAT16) {
+ return JXL_FAILURE("FLOAT16 input is not supported.");
+ }
+ JXL_RETURN_IF_ERROR(Encoder::VerifyBitDepth(image.format.data_type,
+ info.bits_per_sample,
+ info.exponent_bits_per_sample));
+ if ((image.format.data_type == JXL_TYPE_UINT8 && info.bits_per_sample != 8) ||
+ (image.format.data_type == JXL_TYPE_UINT16 &&
+ info.bits_per_sample != 16)) {
+ return JXL_FAILURE("Only full bit depth unsigned types are supported.");
+ }
+ return true;
+}
+
+Status GetColorEncoding(const PackedPixelFile& ppf,
+ ColorEncoding* color_encoding) {
+ if (!ppf.icc.empty()) {
+ PaddedBytes icc;
+ icc.assign(ppf.icc.data(), ppf.icc.data() + ppf.icc.size());
+ JXL_RETURN_IF_ERROR(color_encoding->SetICC(std::move(icc)));
+ } else {
+ JXL_RETURN_IF_ERROR(ConvertExternalToInternalColorEncoding(
+ ppf.color_encoding, color_encoding));
+ }
+ if (color_encoding->ICC().empty()) {
+ return JXL_FAILURE("Invalid color encoding.");
+ }
+ return true;
+}
+
+bool HasICCProfile(const std::vector<uint8_t>& app_data) {
+ size_t pos = 0;
+ while (pos < app_data.size()) {
+ if (pos + 16 > app_data.size()) return false;
+ uint8_t marker = app_data[pos + 1];
+ size_t marker_len = (app_data[pos + 2] << 8) + app_data[pos + 3] + 2;
+ if (marker == 0xe2 && memcmp(&app_data[pos + 4], "ICC_PROFILE", 12) == 0) {
+ return true;
+ }
+ pos += marker_len;
+ }
+ return false;
+}
+
+Status WriteAppData(j_compress_ptr cinfo,
+ const std::vector<uint8_t>& app_data) {
+ size_t pos = 0;
+ while (pos < app_data.size()) {
+ if (pos + 4 > app_data.size()) {
+ return JXL_FAILURE("Incomplete APP header.");
+ }
+ uint8_t marker = app_data[pos + 1];
+ size_t marker_len = (app_data[pos + 2] << 8) + app_data[pos + 3] + 2;
+ if (app_data[pos] != 0xff || marker < 0xe0 || marker > 0xef) {
+ return JXL_FAILURE("Invalid APP marker %02x %02x", app_data[pos], marker);
+ }
+ if (marker_len <= 4) {
+ return JXL_FAILURE("Invalid APP marker length.");
+ }
+ if (pos + marker_len > app_data.size()) {
+ return JXL_FAILURE("Incomplete APP data");
+ }
+ jpegli_write_marker(cinfo, marker, &app_data[pos + 4], marker_len - 4);
+ pos += marker_len;
+ }
+ return true;
+}
+
+static constexpr int kICCMarker = 0xe2;
+constexpr unsigned char kICCSignature[12] = {
+ 0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00};
+static constexpr uint8_t kUnknownTf = 2;
+static constexpr unsigned char kCICPTagSignature[4] = {0x63, 0x69, 0x63, 0x70};
+static constexpr size_t kCICPTagSize = 12;
+
+bool FindCICPTag(const uint8_t* icc_data, size_t len, bool is_first_chunk,
+ size_t* cicp_offset, size_t* cicp_length, uint8_t* cicp_tag,
+ size_t* cicp_pos) {
+ if (is_first_chunk) {
+ // Look up the offset of the CICP tag from the first chunk of ICC data.
+ if (len < 132) {
+ return false;
+ }
+ uint32_t tag_count = LoadBE32(&icc_data[128]);
+ if (len < 132 + 12 * tag_count) {
+ return false;
+ }
+ for (uint32_t i = 0; i < tag_count; ++i) {
+ if (memcmp(&icc_data[132 + 12 * i], kCICPTagSignature, 4) == 0) {
+ *cicp_offset = LoadBE32(&icc_data[136 + 12 * i]);
+ *cicp_length = LoadBE32(&icc_data[140 + 12 * i]);
+ }
+ }
+ if (*cicp_length < kCICPTagSize) {
+ return false;
+ }
+ }
+ if (*cicp_offset < len) {
+ size_t n_bytes = std::min(len - *cicp_offset, kCICPTagSize - *cicp_pos);
+ memcpy(&cicp_tag[*cicp_pos], &icc_data[*cicp_offset], n_bytes);
+ *cicp_pos += n_bytes;
+ *cicp_offset = 0;
+ } else {
+ *cicp_offset -= len;
+ }
+ return true;
+}
+
+uint8_t LookupCICPTransferFunctionFromAppData(const uint8_t* app_data,
+ size_t len) {
+ size_t last_index = 0;
+ size_t cicp_offset = 0;
+ size_t cicp_length = 0;
+ uint8_t cicp_tag[kCICPTagSize] = {};
+ size_t cicp_pos = 0;
+ size_t pos = 0;
+ while (pos < len) {
+ const uint8_t* marker = &app_data[pos];
+ if (pos + 4 > len) {
+ return kUnknownTf;
+ }
+ size_t marker_size = (marker[2] << 8) + marker[3] + 2;
+ if (pos + marker_size > len) {
+ return kUnknownTf;
+ }
+ if (marker_size < 18 || marker[0] != 0xff || marker[1] != kICCMarker ||
+ memcmp(&marker[4], kICCSignature, 12) != 0) {
+ pos += marker_size;
+ continue;
+ }
+ uint8_t index = marker[16];
+ uint8_t total = marker[17];
+ const uint8_t* payload = marker + 18;
+ const size_t payload_size = marker_size - 18;
+ if (index != last_index + 1 || index > total) {
+ return kUnknownTf;
+ }
+ if (!FindCICPTag(payload, payload_size, last_index == 0, &cicp_offset,
+ &cicp_length, &cicp_tag[0], &cicp_pos)) {
+ return kUnknownTf;
+ }
+ if (cicp_pos == kCICPTagSize) {
+ break;
+ }
+ ++last_index;
+ }
+ if (cicp_pos >= kCICPTagSize && memcmp(cicp_tag, kCICPTagSignature, 4) == 0) {
+ return cicp_tag[9];
+ }
+ return kUnknownTf;
+}
+
+uint8_t LookupCICPTransferFunctionFromICCProfile(const uint8_t* icc_data,
+ size_t len) {
+ size_t cicp_offset = 0;
+ size_t cicp_length = 0;
+ uint8_t cicp_tag[kCICPTagSize] = {};
+ size_t cicp_pos = 0;
+ if (!FindCICPTag(icc_data, len, true, &cicp_offset, &cicp_length,
+ &cicp_tag[0], &cicp_pos)) {
+ return kUnknownTf;
+ }
+ if (cicp_pos >= kCICPTagSize && memcmp(cicp_tag, kCICPTagSignature, 4) == 0) {
+ return cicp_tag[9];
+ }
+ return kUnknownTf;
+}
+
+JpegliDataType ConvertDataType(JxlDataType type) {
+ switch (type) {
+ case JXL_TYPE_UINT8:
+ return JPEGLI_TYPE_UINT8;
+ case JXL_TYPE_UINT16:
+ return JPEGLI_TYPE_UINT16;
+ case JXL_TYPE_FLOAT:
+ return JPEGLI_TYPE_FLOAT;
+ default:
+ return JPEGLI_TYPE_UINT8;
+ }
+}
+
+JpegliEndianness ConvertEndianness(JxlEndianness endianness) {
+ switch (endianness) {
+ case JXL_NATIVE_ENDIAN:
+ return JPEGLI_NATIVE_ENDIAN;
+ case JXL_LITTLE_ENDIAN:
+ return JPEGLI_LITTLE_ENDIAN;
+ case JXL_BIG_ENDIAN:
+ return JPEGLI_BIG_ENDIAN;
+ default:
+ return JPEGLI_NATIVE_ENDIAN;
+ }
+}
+
+void ToFloatRow(const uint8_t* row_in, JxlPixelFormat format, size_t len,
+ float* row_out) {
+ bool is_little_endian =
+ (format.endianness == JXL_LITTLE_ENDIAN ||
+ (format.endianness == JXL_NATIVE_ENDIAN && IsLittleEndian()));
+ static constexpr double kMul8 = 1.0 / 255.0;
+ static constexpr double kMul16 = 1.0 / 65535.0;
+ if (format.data_type == JXL_TYPE_UINT8) {
+ for (size_t x = 0; x < len; ++x) {
+ row_out[x] = row_in[x] * kMul8;
+ }
+ } else if (format.data_type == JXL_TYPE_UINT16 && is_little_endian) {
+ for (size_t x = 0; x < len; ++x) {
+ row_out[x] = LoadLE16(&row_in[2 * x]) * kMul16;
+ }
+ } else if (format.data_type == JXL_TYPE_UINT16 && !is_little_endian) {
+ for (size_t x = 0; x < len; ++x) {
+ row_out[x] = LoadBE16(&row_in[2 * x]) * kMul16;
+ }
+ } else if (format.data_type == JXL_TYPE_FLOAT && is_little_endian) {
+ for (size_t x = 0; x < len; ++x) {
+ row_out[x] = LoadLEFloat(&row_in[4 * x]);
+ }
+ } else if (format.data_type == JXL_TYPE_FLOAT && !is_little_endian) {
+ for (size_t x = 0; x < len; ++x) {
+ row_out[x] = LoadBEFloat(&row_in[4 * x]);
+ }
+ }
+}
+
+Status EncodeJpegToTargetSize(const PackedPixelFile& ppf,
+ const JpegSettings& jpeg_settings,
+ size_t target_size, ThreadPool* pool,
+ std::vector<uint8_t>* output) {
+ output->clear();
+ size_t best_error = std::numeric_limits<size_t>::max();
+ float distance0 = -1.0f;
+ float distance1 = -1.0f;
+ float distance = 1.0f;
+ for (int step = 0; step < 15; ++step) {
+ JpegSettings settings = jpeg_settings;
+ settings.libjpeg_quality = 0;
+ settings.distance = distance;
+ settings.target_size = 0;
+ std::vector<uint8_t> compressed;
+ JXL_RETURN_IF_ERROR(EncodeJpeg(ppf, settings, pool, &compressed));
+ size_t size = compressed.size();
+ // prefer being under the target size to being over it
+ size_t error = size < target_size
+ ? target_size - size
+ : static_cast<size_t>(1.2f * (size - target_size));
+ if (error < best_error) {
+ best_error = error;
+ std::swap(*output, compressed);
+ }
+ float rel_error = size * 1.0f / target_size;
+ if (std::abs(rel_error - 1.0f) < 0.002f) {
+ break;
+ }
+ if (size < target_size) {
+ distance1 = distance;
+ } else {
+ distance0 = distance;
+ }
+ if (distance1 == -1) {
+ distance *= std::pow(rel_error, 1.5) * 1.05;
+ } else if (distance0 == -1) {
+ distance *= std::pow(rel_error, 1.5) * 0.95;
+ } else {
+ distance = 0.5 * (distance0 + distance1);
+ }
+ }
+ return true;
+}
+
+} // namespace
+
+Status EncodeJpeg(const PackedPixelFile& ppf, const JpegSettings& jpeg_settings,
+ ThreadPool* pool, std::vector<uint8_t>* compressed) {
+ if (jpeg_settings.libjpeg_quality > 0) {
+ auto encoder = Encoder::FromExtension(".jpg");
+ encoder->SetOption("q", std::to_string(jpeg_settings.libjpeg_quality));
+ if (!jpeg_settings.libjpeg_chroma_subsampling.empty()) {
+ encoder->SetOption("chroma_subsampling",
+ jpeg_settings.libjpeg_chroma_subsampling);
+ }
+ EncodedImage encoded;
+ JXL_RETURN_IF_ERROR(encoder->Encode(ppf, &encoded, pool));
+ size_t target_size = encoded.bitstreams[0].size();
+ return EncodeJpegToTargetSize(ppf, jpeg_settings, target_size, pool,
+ compressed);
+ }
+ if (jpeg_settings.target_size > 0) {
+ return EncodeJpegToTargetSize(ppf, jpeg_settings, jpeg_settings.target_size,
+ pool, compressed);
+ }
+ JXL_RETURN_IF_ERROR(VerifyInput(ppf));
+
+ ColorEncoding color_encoding;
+ JXL_RETURN_IF_ERROR(GetColorEncoding(ppf, &color_encoding));
+
+ ColorSpaceTransform c_transform(GetJxlCms());
+ ColorEncoding xyb_encoding;
+ if (jpeg_settings.xyb) {
+ if (ppf.info.num_color_channels != 3) {
+ return JXL_FAILURE("Only RGB input is supported in XYB mode.");
+ }
+ if (HasICCProfile(jpeg_settings.app_data)) {
+ return JXL_FAILURE("APP data ICC profile is not supported in XYB mode.");
+ }
+ const ColorEncoding& c_desired = ColorEncoding::LinearSRGB(false);
+ JXL_RETURN_IF_ERROR(
+ c_transform.Init(color_encoding, c_desired, 255.0f, ppf.info.xsize, 1));
+ xyb_encoding.SetColorSpace(jxl::ColorSpace::kXYB);
+ xyb_encoding.rendering_intent = jxl::RenderingIntent::kPerceptual;
+ JXL_RETURN_IF_ERROR(xyb_encoding.CreateICC());
+ }
+ const ColorEncoding& output_encoding =
+ jpeg_settings.xyb ? xyb_encoding : color_encoding;
+
+ // We need to declare all the non-trivial destructor local variables
+ // before the call to setjmp().
+ std::vector<uint8_t> pixels;
+ unsigned char* output_buffer = nullptr;
+ unsigned long output_size = 0;
+ std::vector<uint8_t> row_bytes;
+ size_t rowlen = RoundUpTo(ppf.info.xsize, VectorSize());
+ hwy::AlignedFreeUniquePtr<float[]> xyb_tmp =
+ hwy::AllocateAligned<float>(6 * rowlen);
+ hwy::AlignedFreeUniquePtr<float[]> premul_absorb =
+ hwy::AllocateAligned<float>(VectorSize() * 12);
+ ComputePremulAbsorb(255.0f, premul_absorb.get());
+
+ jpeg_compress_struct cinfo;
+ const auto try_catch_block = [&]() -> bool {
+ jpeg_error_mgr jerr;
+ jmp_buf env;
+ cinfo.err = jpegli_std_error(&jerr);
+ jerr.error_exit = &MyErrorExit;
+ if (setjmp(env)) {
+ return false;
+ }
+ cinfo.client_data = static_cast<void*>(&env);
+ jpegli_create_compress(&cinfo);
+ jpegli_mem_dest(&cinfo, &output_buffer, &output_size);
+ const JxlBasicInfo& info = ppf.info;
+ cinfo.image_width = info.xsize;
+ cinfo.image_height = info.ysize;
+ cinfo.input_components = info.num_color_channels;
+ cinfo.in_color_space =
+ cinfo.input_components == 1 ? JCS_GRAYSCALE : JCS_RGB;
+ if (jpeg_settings.xyb) {
+ jpegli_set_xyb_mode(&cinfo);
+ } else if (jpeg_settings.use_std_quant_tables) {
+ jpegli_use_standard_quant_tables(&cinfo);
+ }
+ uint8_t cicp_tf = kUnknownTf;
+ if (!jpeg_settings.app_data.empty()) {
+ cicp_tf = LookupCICPTransferFunctionFromAppData(
+ jpeg_settings.app_data.data(), jpeg_settings.app_data.size());
+ } else if (!output_encoding.IsSRGB()) {
+ cicp_tf = LookupCICPTransferFunctionFromICCProfile(
+ output_encoding.ICC().data(), output_encoding.ICC().size());
+ }
+ jpegli_set_cicp_transfer_function(&cinfo, cicp_tf);
+ jpegli_set_defaults(&cinfo);
+ if (!jpeg_settings.chroma_subsampling.empty()) {
+ if (jpeg_settings.chroma_subsampling == "444") {
+ cinfo.comp_info[0].h_samp_factor = 1;
+ cinfo.comp_info[0].v_samp_factor = 1;
+ } else if (jpeg_settings.chroma_subsampling == "440") {
+ cinfo.comp_info[0].h_samp_factor = 1;
+ cinfo.comp_info[0].v_samp_factor = 2;
+ } else if (jpeg_settings.chroma_subsampling == "422") {
+ cinfo.comp_info[0].h_samp_factor = 2;
+ cinfo.comp_info[0].v_samp_factor = 1;
+ } else if (jpeg_settings.chroma_subsampling == "420") {
+ cinfo.comp_info[0].h_samp_factor = 2;
+ cinfo.comp_info[0].v_samp_factor = 2;
+ } else {
+ return false;
+ }
+ for (int i = 1; i < cinfo.num_components; ++i) {
+ cinfo.comp_info[i].h_samp_factor = 1;
+ cinfo.comp_info[i].v_samp_factor = 1;
+ }
+ }
+ jpegli_enable_adaptive_quantization(
+ &cinfo, jpeg_settings.use_adaptive_quantization);
+ jpegli_set_distance(&cinfo, jpeg_settings.distance, TRUE);
+ jpegli_set_progressive_level(&cinfo, jpeg_settings.progressive_level);
+ cinfo.optimize_coding = jpeg_settings.optimize_coding;
+ if (!jpeg_settings.app_data.empty()) {
+ // Make sure jpegli_start_compress() does not write any APP markers.
+ cinfo.write_JFIF_header = false;
+ cinfo.write_Adobe_marker = false;
+ }
+ const PackedImage& image = ppf.frames[0].color;
+ if (jpeg_settings.xyb) {
+ jpegli_set_input_format(&cinfo, JPEGLI_TYPE_FLOAT, JPEGLI_NATIVE_ENDIAN);
+ } else {
+ jpegli_set_input_format(&cinfo, ConvertDataType(image.format.data_type),
+ ConvertEndianness(image.format.endianness));
+ }
+ jpegli_start_compress(&cinfo, TRUE);
+ if (!jpeg_settings.app_data.empty()) {
+ JXL_RETURN_IF_ERROR(WriteAppData(&cinfo, jpeg_settings.app_data));
+ }
+ if ((jpeg_settings.app_data.empty() && !output_encoding.IsSRGB()) ||
+ jpeg_settings.xyb) {
+ jpegli_write_icc_profile(&cinfo, output_encoding.ICC().data(),
+ output_encoding.ICC().size());
+ }
+ const uint8_t* pixels = reinterpret_cast<const uint8_t*>(image.pixels());
+ if (jpeg_settings.xyb) {
+ float* src_buf = c_transform.BufSrc(0);
+ float* dst_buf = c_transform.BufDst(0);
+ for (size_t y = 0; y < image.ysize; ++y) {
+ // convert to float
+ ToFloatRow(&pixels[y * image.stride], image.format, 3 * image.xsize,
+ src_buf);
+ // convert to linear srgb
+ if (!c_transform.Run(0, src_buf, dst_buf)) {
+ return false;
+ }
+ // deinterleave channels
+ float* row0 = &xyb_tmp[0];
+ float* row1 = &xyb_tmp[rowlen];
+ float* row2 = &xyb_tmp[2 * rowlen];
+ for (size_t x = 0; x < image.xsize; ++x) {
+ row0[x] = dst_buf[3 * x + 0];
+ row1[x] = dst_buf[3 * x + 1];
+ row2[x] = dst_buf[3 * x + 2];
+ }
+ // convert to xyb
+ LinearRGBRowToXYB(row0, row1, row2, premul_absorb.get(), image.xsize);
+ // scale xyb
+ ScaleXYBRow(row0, row1, row2, image.xsize);
+ // interleave channels
+ float* row_out = &xyb_tmp[3 * rowlen];
+ for (size_t x = 0; x < image.xsize; ++x) {
+ row_out[3 * x + 0] = row0[x];
+ row_out[3 * x + 1] = row1[x];
+ row_out[3 * x + 2] = row2[x];
+ }
+ // feed to jpegli as native endian floats
+ JSAMPROW row[] = {reinterpret_cast<uint8_t*>(row_out)};
+ jpegli_write_scanlines(&cinfo, row, 1);
+ }
+ } else {
+ row_bytes.resize(image.stride);
+ for (size_t y = 0; y < info.ysize; ++y) {
+ memcpy(&row_bytes[0], pixels + y * image.stride, image.stride);
+ JSAMPROW row[] = {row_bytes.data()};
+ jpegli_write_scanlines(&cinfo, row, 1);
+ }
+ }
+ jpegli_finish_compress(&cinfo);
+ compressed->resize(output_size);
+ std::copy_n(output_buffer, output_size, compressed->data());
+ return true;
+ };
+ bool success = try_catch_block();
+ jpegli_destroy_compress(&cinfo);
+ if (output_buffer) free(output_buffer);
+ return success;
+}
+
+} // namespace extras
+} // namespace jxl
diff --git a/third_party/jpeg-xl/lib/extras/enc/jpegli.h b/third_party/jpeg-xl/lib/extras/enc/jpegli.h
new file mode 100644
index 0000000000..194b9f7e48
--- /dev/null
+++ b/third_party/jpeg-xl/lib/extras/enc/jpegli.h
@@ -0,0 +1,47 @@
+// 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_JPEGLI_H_
+#define LIB_EXTRAS_ENC_JPEGLI_H_
+
+// Encodes JPG pixels and metadata in memory using the libjpegli library.
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "lib/extras/packed_image.h"
+#include "lib/jxl/base/data_parallel.h"
+#include "lib/jxl/base/status.h"
+
+namespace jxl {
+namespace extras {
+
+struct JpegSettings {
+ bool xyb = false;
+ size_t target_size = 0;
+ float distance = 1.f;
+ bool use_adaptive_quantization = true;
+ bool use_std_quant_tables = false;
+ int progressive_level = 2;
+ bool optimize_coding = true;
+ std::string chroma_subsampling;
+ int libjpeg_quality = 0;
+ std::string libjpeg_chroma_subsampling;
+ // If not empty, must contain concatenated APP marker segments. In this case,
+ // these and only these APP marker segments will be written to the JPEG
+ // output. In xyb mode app_data must not contain an ICC profile, in this
+ // case an additional APP2 ICC profile for the XYB colorspace will be emitted.
+ std::vector<uint8_t> app_data;
+};
+
+Status EncodeJpeg(const PackedPixelFile& ppf, const JpegSettings& jpeg_settings,
+ ThreadPool* pool, std::vector<uint8_t>* compressed);
+
+} // namespace extras
+} // namespace jxl
+
+#endif // LIB_EXTRAS_ENC_JPEGLI_H_
diff --git a/third_party/jpeg-xl/lib/extras/enc/jpg.cc b/third_party/jpeg-xl/lib/extras/enc/jpg.cc
new file mode 100644
index 0000000000..179bcbe777
--- /dev/null
+++ b/third_party/jpeg-xl/lib/extras/enc/jpg.cc
@@ -0,0 +1,427 @@
+// 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/jpg.h"
+
+#include <jpeglib.h>
+#include <setjmp.h>
+#include <stdint.h>
+
+#include <algorithm>
+#include <array>
+#include <iterator>
+#include <numeric>
+#include <sstream>
+#include <utility>
+#include <vector>
+
+#include "lib/extras/exif.h"
+#include "lib/jxl/base/status.h"
+#include "lib/jxl/sanitizers.h"
+#if JPEGXL_ENABLE_SJPEG
+#include "sjpeg.h"
+#endif
+
+namespace jxl {
+namespace extras {
+
+namespace {
+
+constexpr unsigned char kICCSignature[12] = {
+ 0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00};
+constexpr int kICCMarker = JPEG_APP0 + 2;
+constexpr size_t kMaxBytesInMarker = 65533;
+
+constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69,
+ 0x66, 0x00, 0x00};
+constexpr int kExifMarker = JPEG_APP0 + 1;
+
+enum class JpegEncoder {
+ kLibJpeg,
+ kSJpeg,
+};
+
+#define ARRAY_SIZE(X) (sizeof(X) / sizeof((X)[0]))
+
+// Popular jpeg scan scripts
+// The fields of the individual scans are:
+// comps_in_scan, component_index[], Ss, Se, Ah, Al
+static constexpr jpeg_scan_info kScanScript1[] = {
+ {1, {0}, 0, 0, 0, 0}, //
+ {1, {1}, 0, 0, 0, 0}, //
+ {1, {2}, 0, 0, 0, 0}, //
+ {1, {0}, 1, 8, 0, 0}, //
+ {1, {0}, 9, 63, 0, 0}, //
+ {1, {1}, 1, 63, 0, 0}, //
+ {1, {2}, 1, 63, 0, 0}, //
+};
+static constexpr size_t kNumScans1 = ARRAY_SIZE(kScanScript1);
+
+static constexpr jpeg_scan_info kScanScript2[] = {
+ {1, {0}, 0, 0, 0, 0}, //
+ {1, {1}, 0, 0, 0, 0}, //
+ {1, {2}, 0, 0, 0, 0}, //
+ {1, {0}, 1, 2, 0, 1}, //
+ {1, {0}, 3, 63, 0, 1}, //
+ {1, {0}, 1, 63, 1, 0}, //
+ {1, {1}, 1, 63, 0, 0}, //
+ {1, {2}, 1, 63, 0, 0}, //
+};
+static constexpr size_t kNumScans2 = ARRAY_SIZE(kScanScript2);
+
+static constexpr jpeg_scan_info kScanScript3[] = {
+ {1, {0}, 0, 0, 0, 0}, //
+ {1, {1}, 0, 0, 0, 0}, //
+ {1, {2}, 0, 0, 0, 0}, //
+ {1, {0}, 1, 63, 0, 2}, //
+ {1, {0}, 1, 63, 2, 1}, //
+ {1, {0}, 1, 63, 1, 0}, //
+ {1, {1}, 1, 63, 0, 0}, //
+ {1, {2}, 1, 63, 0, 0}, //
+};
+static constexpr size_t kNumScans3 = ARRAY_SIZE(kScanScript3);
+
+static constexpr jpeg_scan_info kScanScript4[] = {
+ {3, {0, 1, 2}, 0, 0, 0, 1}, //
+ {1, {0}, 1, 5, 0, 2}, //
+ {1, {2}, 1, 63, 0, 1}, //
+ {1, {1}, 1, 63, 0, 1}, //
+ {1, {0}, 6, 63, 0, 2}, //
+ {1, {0}, 1, 63, 2, 1}, //
+ {3, {0, 1, 2}, 0, 0, 1, 0}, //
+ {1, {2}, 1, 63, 1, 0}, //
+ {1, {1}, 1, 63, 1, 0}, //
+ {1, {0}, 1, 63, 1, 0}, //
+};
+static constexpr size_t kNumScans4 = ARRAY_SIZE(kScanScript4);
+
+static constexpr jpeg_scan_info kScanScript5[] = {
+ {3, {0, 1, 2}, 0, 0, 0, 1}, //
+ {1, {0}, 1, 5, 0, 2}, //
+ {1, {1}, 1, 5, 0, 2}, //
+ {1, {2}, 1, 5, 0, 2}, //
+ {1, {1}, 6, 63, 0, 2}, //
+ {1, {2}, 6, 63, 0, 2}, //
+ {1, {0}, 6, 63, 0, 2}, //
+ {1, {0}, 1, 63, 2, 1}, //
+ {1, {1}, 1, 63, 2, 1}, //
+ {1, {2}, 1, 63, 2, 1}, //
+ {3, {0, 1, 2}, 0, 0, 1, 0}, //
+ {1, {0}, 1, 63, 1, 0}, //
+ {1, {1}, 1, 63, 1, 0}, //
+ {1, {2}, 1, 63, 1, 0}, //
+};
+static constexpr size_t kNumScans5 = ARRAY_SIZE(kScanScript5);
+
+// Adapt RGB scan info to grayscale jpegs.
+void FilterScanComponents(const jpeg_compress_struct* cinfo,
+ jpeg_scan_info* si) {
+ const int all_comps_in_scan = si->comps_in_scan;
+ si->comps_in_scan = 0;
+ for (int j = 0; j < all_comps_in_scan; ++j) {
+ const int component = si->component_index[j];
+ if (component < cinfo->input_components) {
+ si->component_index[si->comps_in_scan++] = component;
+ }
+ }
+}
+
+Status SetJpegProgression(int progressive_id,
+ std::vector<jpeg_scan_info>* scan_infos,
+ jpeg_compress_struct* cinfo) {
+ if (progressive_id < 0) {
+ return true;
+ }
+ if (progressive_id == 0) {
+ jpeg_simple_progression(cinfo);
+ return true;
+ }
+ constexpr const jpeg_scan_info* kScanScripts[] = {
+ kScanScript1, kScanScript2, kScanScript3, kScanScript4, kScanScript5,
+ };
+ constexpr size_t kNumScans[] = {kNumScans1, kNumScans2, kNumScans3,
+ kNumScans4, kNumScans5};
+ if (progressive_id > static_cast<int>(ARRAY_SIZE(kNumScans))) {
+ return JXL_FAILURE("Unknown jpeg scan script id %d", progressive_id);
+ }
+ const jpeg_scan_info* scan_script = kScanScripts[progressive_id - 1];
+ const size_t num_scans = kNumScans[progressive_id - 1];
+ // filter scan script for number of components
+ for (size_t i = 0; i < num_scans; ++i) {
+ jpeg_scan_info scan_info = scan_script[i];
+ FilterScanComponents(cinfo, &scan_info);
+ if (scan_info.comps_in_scan > 0) {
+ scan_infos->emplace_back(std::move(scan_info));
+ }
+ }
+ cinfo->scan_info = scan_infos->data();
+ cinfo->num_scans = scan_infos->size();
+ return true;
+}
+
+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 std::vector<uint8_t>& icc) {
+ constexpr size_t kMaxIccBytesInMarker =
+ kMaxBytesInMarker - sizeof kICCSignature - 2;
+ const int num_markers =
+ static_cast<int>(DivCeil(icc.size(), kMaxIccBytesInMarker));
+ size_t begin = 0;
+ for (int current_marker = 0; current_marker < num_markers; ++current_marker) {
+ const size_t length = std::min(kMaxIccBytesInMarker, icc.size() - begin);
+ jpeg_write_m_header(
+ cinfo, kICCMarker,
+ static_cast<unsigned int>(length + sizeof kICCSignature + 2));
+ for (const unsigned char c : kICCSignature) {
+ jpeg_write_m_byte(cinfo, c);
+ }
+ jpeg_write_m_byte(cinfo, current_marker + 1);
+ jpeg_write_m_byte(cinfo, num_markers);
+ for (size_t i = 0; i < length; ++i) {
+ jpeg_write_m_byte(cinfo, icc[begin]);
+ ++begin;
+ }
+ }
+}
+void WriteExif(jpeg_compress_struct* const cinfo,
+ const std::vector<uint8_t>& exif) {
+ jpeg_write_m_header(
+ cinfo, kExifMarker,
+ static_cast<unsigned int>(exif.size() + sizeof kExifSignature));
+ for (const unsigned char c : kExifSignature) {
+ jpeg_write_m_byte(cinfo, c);
+ }
+ for (size_t i = 0; i < exif.size(); ++i) {
+ jpeg_write_m_byte(cinfo, exif[i]);
+ }
+}
+
+Status SetChromaSubsampling(const std::string& subsampling,
+ jpeg_compress_struct* const cinfo) {
+ 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 false;
+}
+
+Status EncodeWithLibJpeg(const PackedImage& image, const JxlBasicInfo& info,
+ const JxlColorEncoding& color_encoding,
+ const std::vector<uint8_t>& icc,
+ std::vector<uint8_t> exif, size_t quality,
+ const std::string& chroma_subsampling,
+ int progressive_id, bool optimize_coding,
+ 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 = {};
+ jpeg_error_mgr jerr;
+ cinfo.err = jpeg_std_error(&jerr);
+ jpeg_create_compress(&cinfo);
+ unsigned char* buffer = nullptr;
+ unsigned long size = 0;
+ jpeg_mem_dest(&cinfo, &buffer, &size);
+ 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 = optimize_coding;
+ if (cinfo.input_components == 3) {
+ JXL_RETURN_IF_ERROR(SetChromaSubsampling(chroma_subsampling, &cinfo));
+ }
+ if (color_encoding.color_space == JXL_COLOR_SPACE_XYB) {
+ // Tell libjpeg not to convert XYB data to YCbCr.
+ jpeg_set_colorspace(&cinfo, JCS_RGB);
+ }
+ jpeg_set_quality(&cinfo, quality, TRUE);
+ std::vector<jpeg_scan_info> scan_infos;
+ JXL_RETURN_IF_ERROR(SetJpegProgression(progressive_id, &scan_infos, &cinfo));
+ jpeg_start_compress(&cinfo, TRUE);
+ if (!icc.empty()) {
+ WriteICCProfile(&cinfo, icc);
+ }
+ if (!exif.empty()) {
+ ResetExifOrientation(exif);
+ WriteExif(&cinfo, exif);
+ }
+ if (cinfo.input_components > 3 || cinfo.input_components < 0)
+ return JXL_FAILURE("invalid numbers of components");
+
+ 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);
+ }
+ jpeg_finish_compress(&cinfo);
+ jpeg_destroy_compress(&cinfo);
+ bytes->resize(size);
+ // Compressed image data is initialized by libjpeg, which we are not
+ // instrumenting with msan.
+ msan::UnpoisonMemory(buffer, size);
+ std::copy_n(buffer, size, bytes->data());
+ std::free(buffer);
+ return true;
+}
+
+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 (!icc.empty()) {
+ param.iccp.assign(icc.begin(), icc.end());
+ }
+ if (!exif.empty()) {
+ ResetExifOrientation(exif);
+ param.exif.assign(exif.begin(), exif.end());
+ }
+ if (chroma_subsampling == "444") {
+ param.yuv_mode = SJPEG_YUV_444;
+ } 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 = info.xsize * 3;
+ const uint8_t* pixels = reinterpret_cast<const uint8_t*>(image.pixels());
+ std::string 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()));
+ return true;
+#endif
+}
+
+Status EncodeImageJPG(const PackedImage& image, const JxlBasicInfo& info,
+ const JxlColorEncoding& color_encoding,
+ const std::vector<uint8_t>& icc,
+ std::vector<uint8_t> exif, JpegEncoder encoder,
+ size_t quality, const std::string& chroma_subsampling,
+ int progressive_id, bool optimize_coding,
+ 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");
+ }
+
+ switch (encoder) {
+ case JpegEncoder::kLibJpeg:
+ JXL_RETURN_IF_ERROR(EncodeWithLibJpeg(
+ image, info, color_encoding, icc, std::move(exif), quality,
+ chroma_subsampling, progressive_id, optimize_coding, bytes));
+ break;
+ case JpegEncoder::kSJpeg:
+ 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");
+ }
+
+ 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));
+ int quality = 100;
+ std::string chroma_subsampling = "444";
+ JpegEncoder jpeg_encoder = JpegEncoder::kLibJpeg;
+ int progressive_id = -1;
+ bool optimize_coding = true;
+ for (const auto& it : options()) {
+ if (it.first == "q") {
+ std::istringstream is(it.second);
+ JXL_RETURN_IF_ERROR(static_cast<bool>(is >> quality));
+ } else if (it.first == "chroma_subsampling") {
+ chroma_subsampling = it.second;
+ } else if (it.first == "jpeg_encoder") {
+ if (it.second == "libjpeg") {
+ jpeg_encoder = JpegEncoder::kLibJpeg;
+ } else if (it.second == "sjpeg") {
+ jpeg_encoder = JpegEncoder::kSJpeg;
+ } else {
+ return JXL_FAILURE("unknown jpeg encoder \"%s\"", it.second.c_str());
+ }
+ } else if (it.first == "progressive") {
+ std::istringstream is(it.second);
+ JXL_RETURN_IF_ERROR(static_cast<bool>(is >> progressive_id));
+ } else if (it.first == "optimize" && it.second == "OFF") {
+ optimize_coding = false;
+ }
+ }
+ 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, ppf.color_encoding, icc, ppf.metadata.exif,
+ jpeg_encoder, quality, chroma_subsampling, progressive_id,
+ optimize_coding, 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/third_party/jpeg-xl/lib/extras/enc/jpg.h b/third_party/jpeg-xl/lib/extras/enc/jpg.h
new file mode 100644
index 0000000000..20b37cd168
--- /dev/null
+++ b/third_party/jpeg-xl/lib/extras/enc/jpg.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_JPG_H_
+#define LIB_EXTRAS_ENC_JPG_H_
+
+// Encodes JPG pixels and metadata in memory.
+
+#include <memory>
+
+#include "lib/extras/enc/encode.h"
+
+namespace jxl {
+namespace extras {
+
+std::unique_ptr<Encoder> GetJPEGEncoder();
+
+} // namespace extras
+} // namespace jxl
+
+#endif // LIB_EXTRAS_ENC_JPG_H_
diff --git a/third_party/jpeg-xl/lib/extras/enc/jxl.cc b/third_party/jpeg-xl/lib/extras/enc/jxl.cc
new file mode 100644
index 0000000000..633c6f2ade
--- /dev/null
+++ b/third_party/jpeg-xl/lib/extras/enc/jxl.cc
@@ -0,0 +1,276 @@
+// 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/jxl.h"
+
+#include <jxl/encode_cxx.h>
+
+#include "lib/jxl/exif.h"
+
+namespace jxl {
+namespace extras {
+
+JxlEncoderStatus SetOption(const JXLOption& opt,
+ JxlEncoderFrameSettings* settings) {
+ return opt.is_float
+ ? JxlEncoderFrameSettingsSetFloatOption(settings, opt.id, opt.fval)
+ : JxlEncoderFrameSettingsSetOption(settings, opt.id, opt.ival);
+}
+
+bool SetFrameOptions(const std::vector<JXLOption>& options, size_t frame_index,
+ size_t* option_idx, JxlEncoderFrameSettings* settings) {
+ while (*option_idx < options.size()) {
+ const auto& opt = options[*option_idx];
+ if (opt.frame_index > frame_index) {
+ break;
+ }
+ if (JXL_ENC_SUCCESS != SetOption(opt, settings)) {
+ fprintf(stderr, "Setting option id %d failed.\n", opt.id);
+ return false;
+ }
+ (*option_idx)++;
+ }
+ return true;
+}
+
+bool EncodeImageJXL(const JXLCompressParams& params, const PackedPixelFile& ppf,
+ const std::vector<uint8_t>* jpeg_bytes,
+ std::vector<uint8_t>* compressed) {
+ auto encoder = JxlEncoderMake(/*memory_manager=*/nullptr);
+ JxlEncoder* enc = encoder.get();
+
+ if (params.allow_expert_options) {
+ JxlEncoderAllowExpertOptions(enc);
+ }
+
+ if (params.runner_opaque != nullptr &&
+ JXL_ENC_SUCCESS != JxlEncoderSetParallelRunner(enc, params.runner,
+ params.runner_opaque)) {
+ fprintf(stderr, "JxlEncoderSetParallelRunner failed\n");
+ return false;
+ }
+
+ auto settings = JxlEncoderFrameSettingsCreate(enc, nullptr);
+ size_t option_idx = 0;
+ if (!SetFrameOptions(params.options, 0, &option_idx, settings)) {
+ return false;
+ }
+ if (JXL_ENC_SUCCESS !=
+ JxlEncoderSetFrameDistance(settings, params.distance)) {
+ fprintf(stderr, "Setting frame distance failed.\n");
+ return false;
+ }
+
+ bool use_boxes = !ppf.metadata.exif.empty() || !ppf.metadata.xmp.empty() ||
+ !ppf.metadata.jumbf.empty() || !ppf.metadata.iptc.empty();
+ bool use_container = params.use_container || use_boxes ||
+ (jpeg_bytes && params.jpeg_store_metadata);
+
+ if (JXL_ENC_SUCCESS !=
+ JxlEncoderUseContainer(enc, static_cast<int>(use_container))) {
+ fprintf(stderr, "JxlEncoderUseContainer failed.\n");
+ return false;
+ }
+
+ if (jpeg_bytes) {
+ if (params.jpeg_store_metadata &&
+ JXL_ENC_SUCCESS != JxlEncoderStoreJPEGMetadata(enc, JXL_TRUE)) {
+ fprintf(stderr, "Storing JPEG metadata failed.\n");
+ return false;
+ }
+ if (JXL_ENC_SUCCESS != JxlEncoderAddJPEGFrame(settings, jpeg_bytes->data(),
+ jpeg_bytes->size())) {
+ fprintf(stderr, "JxlEncoderAddJPEGFrame() failed.\n");
+ return false;
+ }
+ } else {
+ size_t num_alpha_channels = 0; // Adjusted below.
+ JxlBasicInfo basic_info = ppf.info;
+ if (basic_info.alpha_bits > 0) num_alpha_channels = 1;
+ if (params.intensity_target > 0) {
+ basic_info.intensity_target = params.intensity_target;
+ }
+ basic_info.num_extra_channels =
+ std::max<uint32_t>(num_alpha_channels, ppf.info.num_extra_channels);
+ basic_info.num_color_channels = ppf.info.num_color_channels;
+ const bool lossless = params.distance == 0;
+ basic_info.uses_original_profile = lossless;
+ if (params.override_bitdepth != 0) {
+ basic_info.bits_per_sample = params.override_bitdepth;
+ basic_info.exponent_bits_per_sample =
+ params.override_bitdepth == 32 ? 8 : 0;
+ }
+ if (JXL_ENC_SUCCESS !=
+ JxlEncoderSetCodestreamLevel(enc, params.codestream_level)) {
+ fprintf(stderr, "Setting --codestream_level failed.\n");
+ return false;
+ }
+ if (JXL_ENC_SUCCESS != JxlEncoderSetBasicInfo(enc, &basic_info)) {
+ fprintf(stderr, "JxlEncoderSetBasicInfo() failed.\n");
+ return false;
+ }
+ if (JXL_ENC_SUCCESS !=
+ JxlEncoderSetFrameBitDepth(settings, &params.input_bitdepth)) {
+ fprintf(stderr, "JxlEncoderSetFrameBitDepth() failed.\n");
+ return false;
+ }
+ if (num_alpha_channels != 0 &&
+ JXL_ENC_SUCCESS != JxlEncoderSetExtraChannelDistance(
+ settings, 0, params.alpha_distance)) {
+ fprintf(stderr, "Setting alpha distance failed.\n");
+ return false;
+ }
+ if (lossless &&
+ JXL_ENC_SUCCESS != JxlEncoderSetFrameLossless(settings, JXL_TRUE)) {
+ fprintf(stderr, "JxlEncoderSetFrameLossless() failed.\n");
+ return false;
+ }
+ if (!ppf.icc.empty()) {
+ if (JXL_ENC_SUCCESS !=
+ JxlEncoderSetICCProfile(enc, ppf.icc.data(), ppf.icc.size())) {
+ fprintf(stderr, "JxlEncoderSetICCProfile() failed.\n");
+ return false;
+ }
+ } else {
+ if (JXL_ENC_SUCCESS !=
+ JxlEncoderSetColorEncoding(enc, &ppf.color_encoding)) {
+ fprintf(stderr, "JxlEncoderSetColorEncoding() failed.\n");
+ return false;
+ }
+ }
+
+ if (use_boxes) {
+ if (JXL_ENC_SUCCESS != JxlEncoderUseBoxes(enc)) {
+ fprintf(stderr, "JxlEncoderUseBoxes() failed.\n");
+ return false;
+ }
+ // Prepend 4 zero bytes to exif for tiff header offset
+ std::vector<uint8_t> exif_with_offset;
+ bool bigendian;
+ if (IsExif(ppf.metadata.exif, &bigendian)) {
+ exif_with_offset.resize(ppf.metadata.exif.size() + 4);
+ memcpy(exif_with_offset.data() + 4, ppf.metadata.exif.data(),
+ ppf.metadata.exif.size());
+ }
+ const struct BoxInfo {
+ const char* type;
+ const std::vector<uint8_t>& bytes;
+ } boxes[] = {
+ {"Exif", exif_with_offset},
+ {"xml ", ppf.metadata.xmp},
+ {"jumb", ppf.metadata.jumbf},
+ {"xml ", ppf.metadata.iptc},
+ };
+ for (size_t i = 0; i < sizeof boxes / sizeof *boxes; ++i) {
+ const BoxInfo& box = boxes[i];
+ if (!box.bytes.empty() &&
+ JXL_ENC_SUCCESS != JxlEncoderAddBox(enc, box.type, box.bytes.data(),
+ box.bytes.size(),
+ params.compress_boxes)) {
+ fprintf(stderr, "JxlEncoderAddBox() failed (%s).\n", box.type);
+ return false;
+ }
+ }
+ JxlEncoderCloseBoxes(enc);
+ }
+
+ for (size_t num_frame = 0; num_frame < ppf.frames.size(); ++num_frame) {
+ const jxl::extras::PackedFrame& pframe = ppf.frames[num_frame];
+ const jxl::extras::PackedImage& pimage = pframe.color;
+ JxlPixelFormat ppixelformat = pimage.format;
+ if (JXL_ENC_SUCCESS !=
+ JxlEncoderSetFrameHeader(settings, &pframe.frame_info)) {
+ fprintf(stderr, "JxlEncoderSetFrameHeader() failed.\n");
+ return false;
+ }
+ if (!SetFrameOptions(params.options, num_frame, &option_idx, settings)) {
+ return false;
+ }
+ if (num_alpha_channels > 0) {
+ JxlExtraChannelInfo extra_channel_info;
+ JxlEncoderInitExtraChannelInfo(JXL_CHANNEL_ALPHA, &extra_channel_info);
+ extra_channel_info.bits_per_sample = ppf.info.alpha_bits;
+ extra_channel_info.exponent_bits_per_sample =
+ ppf.info.alpha_exponent_bits;
+ if (params.premultiply != -1) {
+ if (params.premultiply != 0 && params.premultiply != 1) {
+ fprintf(stderr, "premultiply must be one of: -1, 0, 1.\n");
+ return false;
+ }
+ extra_channel_info.alpha_premultiplied = params.premultiply;
+ }
+ if (JXL_ENC_SUCCESS !=
+ JxlEncoderSetExtraChannelInfo(enc, 0, &extra_channel_info)) {
+ fprintf(stderr, "JxlEncoderSetExtraChannelInfo() failed.\n");
+ return false;
+ }
+ // We take the extra channel blend info frame_info, but don't do
+ // clamping.
+ JxlBlendInfo extra_channel_blend_info =
+ pframe.frame_info.layer_info.blend_info;
+ extra_channel_blend_info.clamp = JXL_FALSE;
+ JxlEncoderSetExtraChannelBlendInfo(settings, 0,
+ &extra_channel_blend_info);
+ }
+ size_t num_interleaved_alpha =
+ (ppixelformat.num_channels - ppf.info.num_color_channels);
+ // Add extra channel info for the rest of the extra channels.
+ for (size_t i = 0; i < ppf.info.num_extra_channels; ++i) {
+ if (i < ppf.extra_channels_info.size()) {
+ const auto& ec_info = ppf.extra_channels_info[i].ec_info;
+ if (JXL_ENC_SUCCESS !=
+ JxlEncoderSetExtraChannelInfo(enc, num_interleaved_alpha + i,
+ &ec_info)) {
+ fprintf(stderr, "JxlEncoderSetExtraChannelInfo() failed.\n");
+ return false;
+ }
+ }
+ }
+ if (JXL_ENC_SUCCESS != JxlEncoderAddImageFrame(settings, &ppixelformat,
+ pimage.pixels(),
+ pimage.pixels_size)) {
+ fprintf(stderr, "JxlEncoderAddImageFrame() failed.\n");
+ return false;
+ }
+ // Only set extra channel buffer if it is provided non-interleaved.
+ for (size_t i = 0; i < pframe.extra_channels.size(); ++i) {
+ if (JXL_ENC_SUCCESS !=
+ JxlEncoderSetExtraChannelBuffer(settings, &ppixelformat,
+ pframe.extra_channels[i].pixels(),
+ pframe.extra_channels[i].stride *
+ pframe.extra_channels[i].ysize,
+ num_interleaved_alpha + i)) {
+ fprintf(stderr, "JxlEncoderSetExtraChannelBuffer() failed.\n");
+ return false;
+ }
+ }
+ }
+ }
+ JxlEncoderCloseInput(enc);
+ // Reading compressed output
+ compressed->clear();
+ compressed->resize(4096);
+ uint8_t* next_out = compressed->data();
+ size_t avail_out = compressed->size() - (next_out - compressed->data());
+ JxlEncoderStatus result = JXL_ENC_NEED_MORE_OUTPUT;
+ while (result == JXL_ENC_NEED_MORE_OUTPUT) {
+ result = JxlEncoderProcessOutput(enc, &next_out, &avail_out);
+ if (result == JXL_ENC_NEED_MORE_OUTPUT) {
+ size_t offset = next_out - compressed->data();
+ compressed->resize(compressed->size() * 2);
+ next_out = compressed->data() + offset;
+ avail_out = compressed->size() - offset;
+ }
+ }
+ compressed->resize(next_out - compressed->data());
+ if (result != JXL_ENC_SUCCESS) {
+ fprintf(stderr, "JxlEncoderProcessOutput failed.\n");
+ return false;
+ }
+ return true;
+}
+
+} // namespace extras
+} // namespace jxl
diff --git a/third_party/jpeg-xl/lib/extras/enc/jxl.h b/third_party/jpeg-xl/lib/extras/enc/jxl.h
new file mode 100644
index 0000000000..3e77fce3c1
--- /dev/null
+++ b/third_party/jpeg-xl/lib/extras/enc/jxl.h
@@ -0,0 +1,78 @@
+// 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_JXL_H_
+#define LIB_EXTRAS_ENC_JXL_H_
+
+#include <jxl/encode.h>
+#include <jxl/parallel_runner.h>
+#include <jxl/thread_parallel_runner.h>
+#include <jxl/types.h>
+#include <stdint.h>
+
+#include <vector>
+
+#include "lib/extras/packed_image.h"
+
+namespace jxl {
+namespace extras {
+
+struct JXLOption {
+ JXLOption(JxlEncoderFrameSettingId id, int64_t val, size_t frame_index)
+ : id(id), is_float(false), ival(val), frame_index(frame_index) {}
+ JXLOption(JxlEncoderFrameSettingId id, float val, size_t frame_index)
+ : id(id), is_float(true), fval(val), frame_index(frame_index) {}
+
+ JxlEncoderFrameSettingId id;
+ bool is_float;
+ union {
+ int64_t ival;
+ float fval;
+ };
+ size_t frame_index;
+};
+
+struct JXLCompressParams {
+ std::vector<JXLOption> options;
+ // Target butteraugli distance, 0.0 means lossless.
+ float distance = 1.0f;
+ float alpha_distance = 1.0f;
+ // If set to true, forces container mode.
+ bool use_container = false;
+ // Whether to enable/disable byte-exact jpeg reconstruction for jpeg inputs.
+ bool jpeg_store_metadata = true;
+ // Whether to create brob boxes.
+ bool compress_boxes = true;
+ // Upper bound on the intensity level present in the image in nits (zero means
+ // that the library chooses a default).
+ float intensity_target = 0;
+ // Overrides for bitdepth, codestream level and alpha premultiply.
+ size_t override_bitdepth = 0;
+ int32_t codestream_level = -1;
+ int32_t premultiply = -1;
+ // Override input buffer interpretation.
+ JxlBitDepth input_bitdepth = {JXL_BIT_DEPTH_FROM_PIXEL_FORMAT, 0, 0};
+ // If runner_opaque is set, the decoder uses this parallel runner.
+ JxlParallelRunner runner = JxlThreadParallelRunner;
+ void* runner_opaque = nullptr;
+
+ bool allow_expert_options = false;
+
+ void AddOption(JxlEncoderFrameSettingId id, int64_t val) {
+ options.emplace_back(JXLOption(id, val, 0));
+ }
+ void AddFloatOption(JxlEncoderFrameSettingId id, float val) {
+ options.emplace_back(JXLOption(id, val, 0));
+ }
+};
+
+bool EncodeImageJXL(const JXLCompressParams& params, const PackedPixelFile& ppf,
+ const std::vector<uint8_t>* jpeg_bytes,
+ std::vector<uint8_t>* compressed);
+
+} // namespace extras
+} // namespace jxl
+
+#endif // LIB_EXTRAS_ENC_JXL_H_
diff --git a/third_party/jpeg-xl/lib/extras/enc/npy.cc b/third_party/jpeg-xl/lib/extras/enc/npy.cc
new file mode 100644
index 0000000000..e7a659184b
--- /dev/null
+++ b/third_party/jpeg-xl/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 <jxl/types.h>
+#include <stdio.h>
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#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/third_party/jpeg-xl/lib/extras/enc/npy.h b/third_party/jpeg-xl/lib/extras/enc/npy.h
new file mode 100644
index 0000000000..3ee6208ec2
--- /dev/null
+++ b/third_party/jpeg-xl/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/third_party/jpeg-xl/lib/extras/enc/pgx.cc b/third_party/jpeg-xl/lib/extras/enc/pgx.cc
new file mode 100644
index 0000000000..201c8b4189
--- /dev/null
+++ b/third_party/jpeg-xl/lib/extras/enc/pgx.cc
@@ -0,0 +1,123 @@
+// 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/pgx.h"
+
+#include <jxl/codestream_header.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "lib/extras/packed_image.h"
+#include "lib/jxl/base/byte_order.h"
+
+namespace jxl {
+namespace extras {
+namespace {
+
+constexpr size_t kMaxHeaderSize = 200;
+
+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 (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 + %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;
+}
+
+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));
+
+ 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");
+ }
+
+ std::vector<uint8_t> pixels(num_samples * bytes_per_sample);
+
+ 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));
+ memcpy(bytes->data() + header_size, pixels.data(), pixels.size());
+
+ 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/third_party/jpeg-xl/lib/extras/enc/pgx.h b/third_party/jpeg-xl/lib/extras/enc/pgx.h
new file mode 100644
index 0000000000..f24e391b09
--- /dev/null
+++ b/third_party/jpeg-xl/lib/extras/enc/pgx.h
@@ -0,0 +1,24 @@
+// 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_PGX_H_
+#define LIB_EXTRAS_ENC_PGX_H_
+
+// Encodes PGX pixels in memory.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "lib/extras/enc/encode.h"
+
+namespace jxl {
+namespace extras {
+
+std::unique_ptr<Encoder> GetPGXEncoder();
+
+} // namespace extras
+} // namespace jxl
+
+#endif // LIB_EXTRAS_ENC_PGX_H_
diff --git a/third_party/jpeg-xl/lib/extras/enc/pnm.cc b/third_party/jpeg-xl/lib/extras/enc/pnm.cc
new file mode 100644
index 0000000000..7cebcc2a1f
--- /dev/null
+++ b/third_party/jpeg-xl/lib/extras/enc/pnm.cc
@@ -0,0 +1,303 @@
+// 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/pnm.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <string>
+#include <vector>
+
+#include "lib/extras/packed_image.h"
+#include "lib/jxl/base/byte_order.h"
+#include "lib/jxl/base/compiler_specific.h"
+#include "lib/jxl/base/file_io.h"
+#include "lib/jxl/base/printf_macros.h"
+#include "lib/jxl/base/status.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 {
+namespace {
+
+constexpr size_t kMaxHeaderSize = 200;
+
+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(
+ EncodeFrame(ppf, frame, &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(EncodeExtraChannel(frame.extra_channels[i],
+ ec_info.bits_per_sample,
+ &ec_bitstreams.back()));
+ }
+ }
+ return true;
+ }
+
+ protected:
+ virtual Status EncodeFrame(const PackedPixelFile& ppf,
+ const PackedFrame& frame,
+ std::vector<uint8_t>* bytes) const = 0;
+ virtual Status EncodeExtraChannel(const PackedImage& image,
+ size_t bits_per_sample,
+ std::vector<uint8_t>* bytes) const = 0;
+};
+
+class PPMEncoder : public PNMEncoder {
+ public:
+ std::vector<JxlPixelFormat> AcceptedFormats() const override {
+ return {JxlPixelFormat{3, JXL_TYPE_UINT8, JXL_BIG_ENDIAN, 0},
+ JxlPixelFormat{3, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}};
+ }
+ Status EncodeFrame(const PackedPixelFile& ppf, const PackedFrame& frame,
+ std::vector<uint8_t>* bytes) const override {
+ return EncodeImage(frame.color, ppf.info.bits_per_sample, bytes);
+ }
+ Status EncodeExtraChannel(const PackedImage& image, size_t bits_per_sample,
+ std::vector<uint8_t>* bytes) const override {
+ return EncodeImage(image, bits_per_sample, bytes);
+ }
+
+ private:
+ Status EncodeImage(const PackedImage& image, size_t bits_per_sample,
+ std::vector<uint8_t>* bytes) const {
+ uint32_t maxval = (1u << bits_per_sample) - 1;
+ char type = image.format.num_channels == 1 ? '5' : '6';
+ char header[kMaxHeaderSize];
+ size_t header_size =
+ snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%u\n",
+ type, image.xsize, image.ysize, maxval);
+ JXL_RETURN_IF_ERROR(header_size < kMaxHeaderSize);
+ bytes->resize(header_size + image.pixels_size);
+ memcpy(bytes->data(), header, header_size);
+ memcpy(bytes->data() + header_size,
+ reinterpret_cast<uint8_t*>(image.pixels()), image.pixels_size);
+ return true;
+ }
+};
+
+class PGMEncoder : public PPMEncoder {
+ public:
+ std::vector<JxlPixelFormat> AcceptedFormats() const override {
+ return {JxlPixelFormat{1, JXL_TYPE_UINT8, JXL_BIG_ENDIAN, 0},
+ JxlPixelFormat{1, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}};
+ }
+};
+
+class PFMEncoder : public PNMEncoder {
+ public:
+ 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_FLOAT,
+ /*endianness=*/endianness,
+ /*align=*/0});
+ }
+ }
+ return formats;
+ }
+ Status EncodeFrame(const PackedPixelFile& ppf, const PackedFrame& frame,
+ std::vector<uint8_t>* bytes) const override {
+ return EncodeImage(frame.color, bytes);
+ }
+ Status EncodeExtraChannel(const PackedImage& image, size_t bits_per_sample,
+ std::vector<uint8_t>* bytes) const override {
+ return EncodeImage(image, bytes);
+ }
+
+ private:
+ Status EncodeImage(const PackedImage& image,
+ std::vector<uint8_t>* bytes) const {
+ char type = image.format.num_channels == 1 ? 'f' : 'F';
+ double scale = image.format.endianness == JXL_LITTLE_ENDIAN ? -1.0 : 1.0;
+ char header[kMaxHeaderSize];
+ size_t header_size =
+ snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%.1f\n",
+ type, image.xsize, image.ysize, scale);
+ JXL_RETURN_IF_ERROR(header_size < kMaxHeaderSize);
+ bytes->resize(header_size + image.pixels_size);
+ memcpy(bytes->data(), header, header_size);
+ 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 = image.ysize - 1 - 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;
+ }
+};
+
+class PAMEncoder : 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}) {
+ formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels,
+ /*data_type=*/data_type,
+ /*endianness=*/JXL_BIG_ENDIAN,
+ /*align=*/0});
+ }
+ }
+ return formats;
+ }
+ Status EncodeFrame(const PackedPixelFile& ppf, const PackedFrame& frame,
+ std::vector<uint8_t>* bytes) const override {
+ const PackedImage& color = frame.color;
+ const auto& ec_info = ppf.extra_channels_info;
+ JXL_RETURN_IF_ERROR(frame.extra_channels.size() == ec_info.size());
+ for (const auto& ec : frame.extra_channels) {
+ if (ec.xsize != color.xsize || ec.ysize != color.ysize) {
+ return JXL_FAILURE("Extra channel and color size mismatch.");
+ }
+ if (ec.format.data_type != color.format.data_type ||
+ ec.format.endianness != color.format.endianness) {
+ return JXL_FAILURE("Extra channel and color format mismatch.");
+ }
+ }
+ if (ppf.info.bits_per_sample != ppf.info.alpha_bits) {
+ return JXL_FAILURE("Alpha bit depth does not match image bit depth");
+ }
+ for (const auto& it : ec_info) {
+ if (it.ec_info.bits_per_sample != ppf.info.bits_per_sample) {
+ return JXL_FAILURE(
+ "Extra channel bit depth does not match image bit depth");
+ }
+ }
+ const char* kColorTypes[4] = {"GRAYSCALE", "GRAYSCALE_ALPHA", "RGB",
+ "RGB_ALPHA"};
+ uint32_t maxval = (1u << ppf.info.bits_per_sample) - 1;
+ uint32_t depth = color.format.num_channels + ec_info.size();
+ char header[kMaxHeaderSize];
+ size_t pos = 0;
+ pos += snprintf(header + pos, kMaxHeaderSize - pos,
+ "P7\nWIDTH %" PRIuS "\nHEIGHT %" PRIuS
+ "\nDEPTH %u\n"
+ "MAXVAL %u\nTUPLTYPE %s\n",
+ color.xsize, color.ysize, depth, maxval,
+ kColorTypes[color.format.num_channels - 1]);
+ JXL_RETURN_IF_ERROR(pos < kMaxHeaderSize);
+ for (const auto& info : ec_info) {
+ pos += snprintf(header + pos, kMaxHeaderSize - pos, "TUPLTYPE %s\n",
+ ExtraChannelTypeName(info.ec_info.type).c_str());
+ JXL_RETURN_IF_ERROR(pos < kMaxHeaderSize);
+ }
+ pos += snprintf(header + pos, kMaxHeaderSize - pos, "ENDHDR\n");
+ JXL_RETURN_IF_ERROR(pos < kMaxHeaderSize);
+ size_t total_size = color.pixels_size;
+ for (const auto& ec : frame.extra_channels) {
+ total_size += ec.pixels_size;
+ }
+ bytes->resize(pos + total_size);
+ memcpy(bytes->data(), header, pos);
+ // If we have no extra channels, just copy color pixel data over.
+ if (frame.extra_channels.empty()) {
+ memcpy(bytes->data() + pos, reinterpret_cast<uint8_t*>(color.pixels()),
+ color.pixels_size);
+ return true;
+ }
+ // Interleave color and extra channels.
+ const uint8_t* in = reinterpret_cast<const uint8_t*>(color.pixels());
+ std::vector<const uint8_t*> ec_in(frame.extra_channels.size());
+ for (size_t i = 0; i < frame.extra_channels.size(); ++i) {
+ ec_in[i] =
+ reinterpret_cast<const uint8_t*>(frame.extra_channels[i].pixels());
+ }
+ uint8_t* out = bytes->data() + pos;
+ size_t pwidth = PackedImage::BitsPerChannel(color.format.data_type) / 8;
+ for (size_t y = 0; y < color.ysize; ++y) {
+ for (size_t x = 0; x < color.xsize; ++x) {
+ memcpy(out, in, color.pixel_stride());
+ out += color.pixel_stride();
+ in += color.pixel_stride();
+ for (auto& p : ec_in) {
+ memcpy(out, p, pwidth);
+ out += pwidth;
+ p += pwidth;
+ }
+ }
+ }
+ return true;
+ }
+ Status EncodeExtraChannel(const PackedImage& image, size_t bits_per_sample,
+ std::vector<uint8_t>* bytes) const override {
+ return true;
+ }
+
+ private:
+ static std::string ExtraChannelTypeName(JxlExtraChannelType type) {
+ switch (type) {
+ case JXL_CHANNEL_ALPHA:
+ return std::string("Alpha");
+ case JXL_CHANNEL_DEPTH:
+ return std::string("Depth");
+ case JXL_CHANNEL_SPOT_COLOR:
+ return std::string("SpotColor");
+ case JXL_CHANNEL_SELECTION_MASK:
+ return std::string("SelectionMask");
+ case JXL_CHANNEL_BLACK:
+ return std::string("Black");
+ case JXL_CHANNEL_CFA:
+ return std::string("CFA");
+ case JXL_CHANNEL_THERMAL:
+ return std::string("Thermal");
+ default:
+ return std::string("UNKNOWN");
+ }
+ }
+};
+
+} // 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
+} // namespace jxl
diff --git a/third_party/jpeg-xl/lib/extras/enc/pnm.h b/third_party/jpeg-xl/lib/extras/enc/pnm.h
new file mode 100644
index 0000000000..403208cecd
--- /dev/null
+++ b/third_party/jpeg-xl/lib/extras/enc/pnm.h
@@ -0,0 +1,28 @@
+// 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_PNM_H_
+#define LIB_EXTRAS_ENC_PNM_H_
+
+// Encodes/decodes PBM/PGM/PPM/PFM pixels in memory.
+
+// TODO(janwas): workaround for incorrect Win64 codegen (cause unknown)
+#include <hwy/highway.h>
+#include <memory>
+
+#include "lib/extras/enc/encode.h"
+
+namespace jxl {
+namespace extras {
+
+std::unique_ptr<Encoder> GetPAMEncoder();
+std::unique_ptr<Encoder> GetPGMEncoder();
+std::unique_ptr<Encoder> GetPPMEncoder();
+std::unique_ptr<Encoder> GetPFMEncoder();
+
+} // namespace extras
+} // namespace jxl
+
+#endif // LIB_EXTRAS_ENC_PNM_H_