summaryrefslogtreecommitdiff
path: root/media/libjxl/src/lib/extras/enc
diff options
context:
space:
mode:
Diffstat (limited to 'media/libjxl/src/lib/extras/enc')
-rw-r--r--media/libjxl/src/lib/extras/enc/apng.cc280
-rw-r--r--media/libjxl/src/lib/extras/enc/apng.h28
-rw-r--r--media/libjxl/src/lib/extras/enc/exr.cc167
-rw-r--r--media/libjxl/src/lib/extras/enc/exr.h30
-rw-r--r--media/libjxl/src/lib/extras/enc/jpg.cc239
-rw-r--r--media/libjxl/src/lib/extras/enc/jpg.h36
-rw-r--r--media/libjxl/src/lib/extras/enc/pgx.cc93
-rw-r--r--media/libjxl/src/lib/extras/enc/pgx.h33
-rw-r--r--media/libjxl/src/lib/extras/enc/pnm.cc133
-rw-r--r--media/libjxl/src/lib/extras/enc/pnm.h32
10 files changed, 1071 insertions, 0 deletions
diff --git a/media/libjxl/src/lib/extras/enc/apng.cc b/media/libjxl/src/lib/extras/enc/apng.cc
new file mode 100644
index 0000000000..372b3b32ea
--- /dev/null
+++ b/media/libjxl/src/lib/extras/enc/apng.cc
@@ -0,0 +1,280 @@
+// 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 "jxl/encode.h"
+#include "lib/jxl/base/compiler_specific.h"
+#include "lib/jxl/base/printf_macros.h"
+#include "lib/jxl/color_encoding_internal.h"
+#include "lib/jxl/dec_external_image.h"
+#include "lib/jxl/enc_color_management.h"
+#include "lib/jxl/enc_image_bundle.h"
+#include "lib/jxl/exif.h"
+#include "lib/jxl/frame_header.h"
+#include "lib/jxl/headers.h"
+#include "lib/jxl/image.h"
+#include "lib/jxl/image_bundle.h"
+#include "png.h" /* original (unpatched) libpng is ok */
+
+namespace jxl {
+namespace extras {
+
+namespace {
+
+static void PngWrite(png_structp png_ptr, png_bytep data, png_size_t length) {
+ PaddedBytes* bytes = static_cast<PaddedBytes*>(png_get_io_ptr(png_ptr));
+ bytes->append(data, data + length);
+}
+
+// Stores XMP and EXIF/IPTC into key/value strings for PNG
+class BlobsWriterPNG {
+ public:
+ static Status Encode(const Blobs& blobs, std::vector<std::string>* strings) {
+ 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);
+ 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()) {
+ 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;
+ }
+};
+
+} // namespace
+
+Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
+ size_t bits_per_sample, ThreadPool* pool,
+ PaddedBytes* bytes) {
+ if (bits_per_sample > 8) {
+ bits_per_sample = 16;
+ } else if (bits_per_sample < 8) {
+ // PNG can also do 4, 2, and 1 bits per sample, but it isn't implemented
+ bits_per_sample = 8;
+ }
+
+ size_t count = 0;
+ bool have_anim = io->metadata.m.have_animation;
+ size_t anim_chunks = 0;
+ int W = 0, H = 0;
+
+ for (size_t i = 0; i < io->frames.size(); i++) {
+ auto& frame = io->frames[i];
+ if (!have_anim && i + 1 < io->frames.size()) continue;
+ 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);
+
+ ImageBundle ib = frame.Copy();
+ const size_t alpha_bits = ib.HasAlpha() ? bits_per_sample : 0;
+ ImageMetadata metadata = io->metadata.m;
+ ImageBundle store(&metadata);
+ const ImageBundle* transformed;
+ JXL_RETURN_IF_ERROR(TransformIfNeeded(ib, c_desired, GetJxlCms(), pool,
+ &store, &transformed));
+ size_t stride = ib.oriented_xsize() *
+ DivCeil(c_desired.Channels() * bits_per_sample + alpha_bits,
+ kBitsPerByte);
+ std::vector<uint8_t> raw_bytes(stride * ib.oriented_ysize());
+ JXL_RETURN_IF_ERROR(ConvertToExternal(
+ *transformed, bits_per_sample, /*float_out=*/false,
+ c_desired.Channels() + (ib.HasAlpha() ? 1 : 0), JXL_BIG_ENDIAN, stride,
+ pool, raw_bytes.data(), raw_bytes.size(),
+ /*out_callback=*/{}, metadata.GetOrientation()));
+
+ int width = ib.oriented_xsize();
+ int height = ib.oriented_ysize();
+
+ png_byte color_type =
+ (c_desired.Channels() == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_GRAY);
+ if (ib.HasAlpha()) color_type |= PNG_COLOR_MASK_ALPHA;
+ png_byte bit_depth = bits_per_sample;
+
+ png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type,
+ PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
+ PNG_FILTER_TYPE_BASE);
+ if (count == 0) {
+ W = width;
+ H = height;
+
+ // TODO(jon): instead of always setting an iCCP, could try to avoid that
+ // have to avoid warnings on the ICC profile becoming fatal
+ png_set_benign_errors(png_ptr, 1);
+ png_set_iCCP(png_ptr, info_ptr, "1", 0, c_desired.ICC().data(),
+ c_desired.ICC().size());
+
+ std::vector<std::string> textstrings;
+ JXL_RETURN_IF_ERROR(BlobsWriterPNG::Encode(io->blobs, &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 (have_anim) {
+ if (count == 0) {
+ png_byte adata[8];
+ png_save_uint_32(adata, io->frames.size());
+ png_save_uint_32(adata + 4, io->metadata.m.animation.num_loops);
+ png_byte actl[5] = "acTL";
+ png_write_chunk(png_ptr, actl, adata, 8);
+ }
+ png_byte fdata[26];
+ JXL_ASSERT(W == width);
+ JXL_ASSERT(H == height);
+ // TODO(jon): also make this work for the non-coalesced case
+ png_save_uint_32(fdata, anim_chunks++);
+ png_save_uint_32(fdata + 4, width);
+ png_save_uint_32(fdata + 8, height);
+ png_save_uint_32(fdata + 12, 0);
+ png_save_uint_32(fdata + 16, 0);
+ png_save_uint_16(
+ fdata + 20,
+ frame.duration * io->metadata.m.animation.tps_denominator);
+ png_save_uint_16(fdata + 22, io->metadata.m.animation.tps_numerator);
+ 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] = raw_bytes.data() + y * 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 == io->frames.size() || !have_anim) png_write_end(png_ptr, NULL);
+
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ }
+
+ return true;
+}
+
+} // namespace extras
+} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/enc/apng.h b/media/libjxl/src/lib/extras/enc/apng.h
new file mode 100644
index 0000000000..7f0fb94d9b
--- /dev/null
+++ b/media/libjxl/src/lib/extras/enc/apng.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_APNG_H_
+#define LIB_EXTRAS_ENC_APNG_H_
+
+// Encodes APNG images in memory.
+
+#include "lib/jxl/base/data_parallel.h"
+#include "lib/jxl/base/padded_bytes.h"
+#include "lib/jxl/base/span.h"
+#include "lib/jxl/base/status.h"
+#include "lib/jxl/codec_in_out.h"
+
+namespace jxl {
+namespace extras {
+
+// Encodes `io` into `bytes`.
+Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
+ size_t bits_per_sample, ThreadPool* pool,
+ PaddedBytes* bytes);
+
+} // namespace extras
+} // namespace jxl
+
+#endif // LIB_EXTRAS_ENC_APNG_H_
diff --git a/media/libjxl/src/lib/extras/enc/exr.cc b/media/libjxl/src/lib/extras/enc/exr.cc
new file mode 100644
index 0000000000..3d88404888
--- /dev/null
+++ b/media/libjxl/src/lib/extras/enc/exr.cc
@@ -0,0 +1,167 @@
+// 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 <vector>
+
+#include "lib/jxl/alpha.h"
+#include "lib/jxl/color_encoding_internal.h"
+#include "lib/jxl/color_management.h"
+#include "lib/jxl/enc_color_management.h"
+#include "lib/jxl/enc_image_bundle.h"
+
+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());
+
+size_t GetNumThreads(ThreadPool* pool) {
+ size_t exr_num_threads = 1;
+ JXL_CHECK(RunOnPool(
+ pool, 0, 1,
+ [&](size_t num_threads) {
+ exr_num_threads = num_threads;
+ return true;
+ },
+ [&](uint32_t /* task */, size_t /*thread*/) {}, "DecodeImageEXRThreads"));
+ return exr_num_threads;
+}
+
+class InMemoryOStream : public OpenEXR::OStream {
+ public:
+ // `bytes` must outlive the InMemoryOStream.
+ explicit InMemoryOStream(PaddedBytes* const bytes)
+ : 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:
+ PaddedBytes& bytes_;
+ size_t pos_ = 0;
+};
+
+} // namespace
+
+Status EncodeImageEXR(const CodecInOut* io, const ColorEncoding& c_desired,
+ ThreadPool* pool, PaddedBytes* bytes) {
+ // As in `DecodeImageEXR`, `pool` is only used for pixel conversion, not for
+ // actual OpenEXR I/O.
+ OpenEXR::setGlobalThreadCount(GetNumThreads(pool));
+
+ ColorEncoding c_linear = c_desired;
+ c_linear.tf.SetTransferFunction(TransferFunction::kLinear);
+ JXL_RETURN_IF_ERROR(c_linear.CreateICC());
+ ImageMetadata metadata = io->metadata.m;
+ ImageBundle store(&metadata);
+ const ImageBundle* linear;
+ JXL_RETURN_IF_ERROR(TransformIfNeeded(io->Main(), c_linear, GetJxlCms(), pool,
+ &store, &linear));
+
+ const bool has_alpha = io->Main().HasAlpha();
+ const bool alpha_is_premultiplied = io->Main().AlphaIsPremultiplied();
+
+ OpenEXR::Header header(io->xsize(), io->ysize());
+ const PrimariesCIExy& primaries = c_linear.HasPrimaries()
+ ? c_linear.GetPrimaries()
+ : ColorEncoding::SRGB().GetPrimaries();
+ OpenEXR::Chromaticities chromaticities;
+ chromaticities.red = Imath::V2f(primaries.r.x, primaries.r.y);
+ chromaticities.green = Imath::V2f(primaries.g.x, primaries.g.y);
+ chromaticities.blue = Imath::V2f(primaries.b.x, primaries.b.y);
+ chromaticities.white =
+ Imath::V2f(c_linear.GetWhitePoint().x, c_linear.GetWhitePoint().y);
+ OpenEXR::addChromaticities(header, chromaticities);
+ OpenEXR::addWhiteLuminance(header, io->metadata.m.IntensityTarget());
+
+ // 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 = io->ysize();
+ std::vector<OpenEXR::Rgba> output_rows(io->xsize() * y_chunk_size);
+
+ for (size_t start_y = 0; start_y < io->ysize(); start_y += y_chunk_size) {
+ // Inclusive.
+ const size_t end_y =
+ std::min(start_y + y_chunk_size - 1, io->ysize() - 1);
+ output.setFrameBuffer(output_rows.data() - start_y * io->xsize(),
+ /*xStride=*/1, /*yStride=*/io->xsize());
+ JXL_RETURN_IF_ERROR(RunOnPool(
+ pool, start_y, end_y + 1, ThreadPool::NoInit,
+ [&](const uint32_t y, size_t /* thread */) {
+ const float* const JXL_RESTRICT input_rows[] = {
+ linear->color().ConstPlaneRow(0, y),
+ linear->color().ConstPlaneRow(1, y),
+ linear->color().ConstPlaneRow(2, y),
+ };
+ OpenEXR::Rgba* const JXL_RESTRICT row_data =
+ &output_rows[(y - start_y) * io->xsize()];
+ if (has_alpha) {
+ const float* const JXL_RESTRICT alpha_row =
+ io->Main().alpha().ConstRow(y);
+ if (alpha_is_premultiplied) {
+ for (size_t x = 0; x < io->xsize(); ++x) {
+ row_data[x] =
+ OpenEXR::Rgba(input_rows[0][x], input_rows[1][x],
+ input_rows[2][x], alpha_row[x]);
+ }
+ } else {
+ for (size_t x = 0; x < io->xsize(); ++x) {
+ row_data[x] = OpenEXR::Rgba(alpha_row[x] * input_rows[0][x],
+ alpha_row[x] * input_rows[1][x],
+ alpha_row[x] * input_rows[2][x],
+ alpha_row[x]);
+ }
+ }
+ } else {
+ for (size_t x = 0; x < io->xsize(); ++x) {
+ row_data[x] = OpenEXR::Rgba(input_rows[0][x], input_rows[1][x],
+ input_rows[2][x], 1.f);
+ }
+ }
+ },
+ "EncodeImageEXR"));
+ output.writePixels(/*numScanLines=*/end_y - start_y + 1);
+ }
+ }
+
+ return true;
+}
+
+} // namespace extras
+} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/enc/exr.h b/media/libjxl/src/lib/extras/enc/exr.h
new file mode 100644
index 0000000000..0423bcbadb
--- /dev/null
+++ b/media/libjxl/src/lib/extras/enc/exr.h
@@ -0,0 +1,30 @@
+// 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 "lib/extras/packed_image.h"
+#include "lib/jxl/base/data_parallel.h"
+#include "lib/jxl/base/padded_bytes.h"
+#include "lib/jxl/base/span.h"
+#include "lib/jxl/base/status.h"
+#include "lib/jxl/codec_in_out.h"
+#include "lib/jxl/color_encoding_internal.h"
+
+namespace jxl {
+namespace extras {
+
+// Transforms from io->c_current to `c_desired` (with the transfer function set
+// to linear as that is the OpenEXR convention) and encodes into `bytes`.
+Status EncodeImageEXR(const CodecInOut* io, const ColorEncoding& c_desired,
+ ThreadPool* pool, PaddedBytes* bytes);
+
+} // namespace extras
+} // namespace jxl
+
+#endif // LIB_EXTRAS_ENC_EXR_H_
diff --git a/media/libjxl/src/lib/extras/enc/jpg.cc b/media/libjxl/src/lib/extras/enc/jpg.cc
new file mode 100644
index 0000000000..83b2895756
--- /dev/null
+++ b/media/libjxl/src/lib/extras/enc/jpg.cc
@@ -0,0 +1,239 @@
+// 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 <iterator>
+#include <numeric>
+#include <utility>
+#include <vector>
+
+#include "lib/jxl/base/compiler_specific.h"
+#include "lib/jxl/base/status.h"
+#include "lib/jxl/color_encoding_internal.h"
+#include "lib/jxl/common.h"
+#include "lib/jxl/dec_external_image.h"
+#include "lib/jxl/enc_color_management.h"
+#include "lib/jxl/enc_image_bundle.h"
+#include "lib/jxl/exif.h"
+#include "lib/jxl/image.h"
+#include "lib/jxl/image_bundle.h"
+#include "lib/jxl/sanitizers.h"
+#if JPEGXL_ENABLE_SJPEG
+#include "sjpeg.h"
+#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;
+
+void WriteICCProfile(jpeg_compress_struct* const cinfo,
+ const PaddedBytes& 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 YCbCrChromaSubsampling& chroma_subsampling,
+ jpeg_compress_struct* const cinfo) {
+ for (size_t i = 0; i < 3; i++) {
+ cinfo->comp_info[i].h_samp_factor =
+ 1 << (chroma_subsampling.MaxHShift() -
+ chroma_subsampling.HShift(i < 2 ? i ^ 1 : i));
+ cinfo->comp_info[i].v_samp_factor =
+ 1 << (chroma_subsampling.MaxVShift() -
+ chroma_subsampling.VShift(i < 2 ? i ^ 1 : i));
+ }
+ return true;
+}
+
+} // namespace
+
+Status EncodeWithLibJpeg(const ImageBundle* ib, const CodecInOut* io,
+ size_t quality,
+ const YCbCrChromaSubsampling& chroma_subsampling,
+ PaddedBytes* bytes) {
+ jpeg_compress_struct cinfo;
+ // cinfo is initialized by libjpeg, which we are not instrumenting with
+ // msan.
+ msan::UnpoisonMemory(&cinfo, sizeof(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 = ib->oriented_xsize();
+ cinfo.image_height = ib->oriented_ysize();
+ if (ib->IsGray()) {
+ cinfo.input_components = 1;
+ cinfo.in_color_space = JCS_GRAYSCALE;
+ } else {
+ cinfo.input_components = 3;
+ cinfo.in_color_space = JCS_RGB;
+ }
+ jpeg_set_defaults(&cinfo);
+ cinfo.optimize_coding = TRUE;
+ if (cinfo.input_components == 3) {
+ JXL_RETURN_IF_ERROR(SetChromaSubsampling(chroma_subsampling, &cinfo));
+ }
+ jpeg_set_quality(&cinfo, quality, TRUE);
+ jpeg_start_compress(&cinfo, TRUE);
+ if (!ib->IsSRGB()) {
+ WriteICCProfile(&cinfo, ib->c_current().ICC());
+ }
+ if (!io->blobs.exif.empty()) {
+ std::vector<uint8_t> exif = io->blobs.exif;
+ ResetExifOrientation(exif);
+ WriteExif(&cinfo, exif);
+ }
+ if (cinfo.input_components > 3 || cinfo.input_components < 0)
+ return JXL_FAILURE("invalid numbers of components");
+
+ size_t stride =
+ ib->oriented_xsize() * cinfo.input_components * sizeof(JSAMPLE);
+ PaddedBytes raw_bytes(stride * ib->oriented_ysize());
+ JXL_RETURN_IF_ERROR(ConvertToExternal(
+ *ib, BITS_IN_JSAMPLE, /*float_out=*/false, cinfo.input_components,
+ JXL_BIG_ENDIAN, stride, nullptr, raw_bytes.data(), raw_bytes.size(),
+ /*out_callback=*/{}, ib->metadata()->GetOrientation()));
+
+ for (size_t y = 0; y < ib->oriented_ysize(); ++y) {
+ JSAMPROW row[] = {raw_bytes.data() + y * stride};
+
+ 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 ImageBundle* ib, const CodecInOut* io,
+ size_t quality,
+ const YCbCrChromaSubsampling& chroma_subsampling,
+ PaddedBytes* bytes) {
+#if !JPEGXL_ENABLE_SJPEG
+ return JXL_FAILURE("JPEG XL was built without sjpeg support");
+#else
+ sjpeg::EncoderParam param(quality);
+ if (!ib->IsSRGB()) {
+ param.iccp.assign(ib->metadata()->color_encoding.ICC().begin(),
+ ib->metadata()->color_encoding.ICC().end());
+ }
+ std::vector<uint8_t> exif = io->blobs.exif;
+ if (!exif.empty()) {
+ ResetExifOrientation(exif);
+ param.exif.assign(exif.begin(), exif.end());
+ }
+ if (chroma_subsampling.Is444()) {
+ param.yuv_mode = SJPEG_YUV_444;
+ } else if (chroma_subsampling.Is420()) {
+ param.yuv_mode = SJPEG_YUV_SHARP;
+ } else {
+ return JXL_FAILURE("sjpeg does not support this chroma subsampling mode");
+ }
+ size_t stride = ib->oriented_xsize() * 3;
+ PaddedBytes rgb(ib->xsize() * ib->ysize() * 3);
+ JXL_RETURN_IF_ERROR(
+ ConvertToExternal(*ib, 8, /*float_out=*/false, 3, JXL_BIG_ENDIAN, stride,
+ nullptr, rgb.data(), rgb.size(),
+ /*out_callback=*/{}, ib->metadata()->GetOrientation()));
+
+ std::string output;
+ JXL_RETURN_IF_ERROR(sjpeg::Encode(rgb.data(), ib->oriented_xsize(),
+ ib->oriented_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 CodecInOut* io, JpegEncoder encoder, size_t quality,
+ YCbCrChromaSubsampling chroma_subsampling,
+ ThreadPool* pool, PaddedBytes* bytes) {
+ if (io->Main().HasAlpha()) {
+ return JXL_FAILURE("alpha is not supported");
+ }
+ if (quality > 100) {
+ return JXL_FAILURE("please specify a 0-100 JPEG quality");
+ }
+
+ const ImageBundle* ib;
+ ImageMetadata metadata = io->metadata.m;
+ ImageBundle ib_store(&metadata);
+ JXL_RETURN_IF_ERROR(TransformIfNeeded(io->Main(),
+ io->metadata.m.color_encoding,
+ GetJxlCms(), pool, &ib_store, &ib));
+
+ switch (encoder) {
+ case JpegEncoder::kLibJpeg:
+ JXL_RETURN_IF_ERROR(
+ EncodeWithLibJpeg(ib, io, quality, chroma_subsampling, bytes));
+ break;
+ case JpegEncoder::kSJpeg:
+ JXL_RETURN_IF_ERROR(
+ EncodeWithSJpeg(ib, io, quality, chroma_subsampling, bytes));
+ break;
+ default:
+ return JXL_FAILURE("tried to use an unknown JPEG encoder");
+ }
+
+ return true;
+}
+
+} // namespace extras
+} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/enc/jpg.h b/media/libjxl/src/lib/extras/enc/jpg.h
new file mode 100644
index 0000000000..ccea1415a8
--- /dev/null
+++ b/media/libjxl/src/lib/extras/enc/jpg.h
@@ -0,0 +1,36 @@
+// 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 <stdint.h>
+
+#include "lib/extras/codec.h"
+#include "lib/jxl/base/data_parallel.h"
+#include "lib/jxl/base/padded_bytes.h"
+#include "lib/jxl/base/span.h"
+#include "lib/jxl/base/status.h"
+#include "lib/jxl/codec_in_out.h"
+
+namespace jxl {
+namespace extras {
+
+enum class JpegEncoder {
+ kLibJpeg,
+ kSJpeg,
+};
+
+// Encodes into `bytes`.
+Status EncodeImageJPG(const CodecInOut* io, JpegEncoder encoder, size_t quality,
+ YCbCrChromaSubsampling chroma_subsampling,
+ ThreadPool* pool, PaddedBytes* bytes);
+
+} // namespace extras
+} // namespace jxl
+
+#endif // LIB_EXTRAS_ENC_JPG_H_
diff --git a/media/libjxl/src/lib/extras/enc/pgx.cc b/media/libjxl/src/lib/extras/enc/pgx.cc
new file mode 100644
index 0000000000..cc333a62ac
--- /dev/null
+++ b/media/libjxl/src/lib/extras/enc/pgx.cc
@@ -0,0 +1,93 @@
+// 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 <stdio.h>
+#include <string.h>
+
+#include "lib/jxl/base/bits.h"
+#include "lib/jxl/base/compiler_specific.h"
+#include "lib/jxl/base/file_io.h"
+#include "lib/jxl/base/printf_macros.h"
+#include "lib/jxl/color_management.h"
+#include "lib/jxl/dec_external_image.h"
+#include "lib/jxl/enc_color_management.h"
+#include "lib/jxl/enc_external_image.h"
+#include "lib/jxl/enc_image_bundle.h"
+#include "lib/jxl/fields.h" // AllDefault
+#include "lib/jxl/image.h"
+#include "lib/jxl/image_bundle.h"
+
+namespace jxl {
+namespace extras {
+namespace {
+
+constexpr size_t kMaxHeaderSize = 200;
+
+Status EncodeHeader(const ImageBundle& ib, const size_t bits_per_sample,
+ char* header, int* JXL_RESTRICT chars_written) {
+ if (ib.HasAlpha()) return JXL_FAILURE("PGX: can't store alpha");
+ if (!ib.IsGray()) return JXL_FAILURE("PGX: must be grayscale");
+ // TODO(lode): verify other bit depths: for other bit depths such as 1 or 4
+ // bits, have a test case to verify it works correctly. For bits > 16, we may
+ // need to change the way external_image works.
+ if (bits_per_sample != 8 && bits_per_sample != 16) {
+ return JXL_FAILURE("PGX: bits other than 8 or 16 not yet supported");
+ }
+
+ // Use ML (Big Endian), LM may not be well supported by all decoders.
+ *chars_written = snprintf(header, kMaxHeaderSize,
+ "PG ML + %" PRIuS " %" PRIuS " %" PRIuS "\n",
+ bits_per_sample, ib.xsize(), ib.ysize());
+ JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
+ kMaxHeaderSize);
+ return true;
+}
+
+} // namespace
+
+Status EncodeImagePGX(const CodecInOut* io, const ColorEncoding& c_desired,
+ size_t bits_per_sample, ThreadPool* pool,
+ PaddedBytes* bytes) {
+ if (!Bundle::AllDefault(io->metadata.m)) {
+ JXL_WARNING("PGX encoder ignoring metadata - use a different codec");
+ }
+ if (!c_desired.IsSRGB()) {
+ JXL_WARNING(
+ "PGX encoder cannot store custom ICC profile; decoder\n"
+ "will need hint key=color_space to get the same values");
+ }
+
+ ImageBundle ib = io->Main().Copy();
+
+ ImageMetadata metadata = io->metadata.m;
+ ImageBundle store(&metadata);
+ const ImageBundle* transformed;
+ JXL_RETURN_IF_ERROR(TransformIfNeeded(ib, c_desired, GetJxlCms(), pool,
+ &store, &transformed));
+ PaddedBytes pixels(ib.xsize() * ib.ysize() *
+ (bits_per_sample / kBitsPerByte));
+ size_t stride = ib.xsize() * (bits_per_sample / kBitsPerByte);
+ JXL_RETURN_IF_ERROR(
+ ConvertToExternal(*transformed, bits_per_sample,
+ /*float_out=*/false,
+ /*num_channels=*/1, JXL_BIG_ENDIAN, stride, pool,
+ pixels.data(), pixels.size(),
+ /*out_callback=*/{}, metadata.GetOrientation()));
+
+ char header[kMaxHeaderSize];
+ int header_size = 0;
+ JXL_RETURN_IF_ERROR(EncodeHeader(ib, bits_per_sample, header, &header_size));
+
+ 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;
+}
+
+} // namespace extras
+} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/enc/pgx.h b/media/libjxl/src/lib/extras/enc/pgx.h
new file mode 100644
index 0000000000..6f14e20668
--- /dev/null
+++ b/media/libjxl/src/lib/extras/enc/pgx.h
@@ -0,0 +1,33 @@
+// 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/packed_image.h"
+#include "lib/jxl/base/data_parallel.h"
+#include "lib/jxl/base/padded_bytes.h"
+#include "lib/jxl/base/span.h"
+#include "lib/jxl/base/status.h"
+#include "lib/jxl/codec_in_out.h"
+#include "lib/jxl/color_encoding_internal.h"
+
+namespace jxl {
+namespace extras {
+
+// Transforms from io->c_current to `c_desired` and encodes into `bytes`.
+Status EncodeImagePGX(const CodecInOut* io, const ColorEncoding& c_desired,
+ size_t bits_per_sample, ThreadPool* pool,
+ PaddedBytes* bytes);
+
+} // namespace extras
+} // namespace jxl
+
+#endif // LIB_EXTRAS_ENC_PGX_H_
diff --git a/media/libjxl/src/lib/extras/enc/pnm.cc b/media/libjxl/src/lib/extras/enc/pnm.cc
new file mode 100644
index 0000000000..9cedda09d5
--- /dev/null
+++ b/media/libjxl/src/lib/extras/enc/pnm.cc
@@ -0,0 +1,133 @@
+// 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;
+
+Status EncodeHeader(const PackedPixelFile& ppf, const size_t bits_per_sample,
+ const bool little_endian, char* header,
+ int* JXL_RESTRICT chars_written) {
+ bool is_gray = ppf.info.num_color_channels <= 2;
+ size_t oriented_xsize =
+ ppf.info.orientation <= 4 ? ppf.info.xsize : ppf.info.ysize;
+ size_t oriented_ysize =
+ ppf.info.orientation <= 4 ? ppf.info.ysize : ppf.info.xsize;
+ if (ppf.info.alpha_bits > 0) { // PAM
+ if (bits_per_sample > 16) return JXL_FAILURE("PNM cannot have > 16 bits");
+ const uint32_t max_val = (1U << bits_per_sample) - 1;
+ *chars_written =
+ snprintf(header, kMaxHeaderSize,
+ "P7\nWIDTH %" PRIuS "\nHEIGHT %" PRIuS
+ "\nDEPTH %u\nMAXVAL %u\nTUPLTYPE %s\nENDHDR\n",
+ oriented_xsize, oriented_ysize, is_gray ? 2 : 4, max_val,
+ is_gray ? "GRAYSCALE_ALPHA" : "RGB_ALPHA");
+ JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
+ kMaxHeaderSize);
+ } else if (bits_per_sample == 32) { // PFM
+ const char type = is_gray ? 'f' : 'F';
+ const double scale = little_endian ? -1.0 : 1.0;
+ *chars_written =
+ snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%.1f\n",
+ type, oriented_xsize, oriented_ysize, scale);
+ JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
+ kMaxHeaderSize);
+ } else { // PGM/PPM
+ const uint32_t max_val = (1U << bits_per_sample) - 1;
+ if (max_val >= 65536) return JXL_FAILURE("PNM cannot have > 16 bits");
+ const char type = is_gray ? '5' : '6';
+ *chars_written =
+ snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%u\n",
+ type, oriented_xsize, oriented_ysize, max_val);
+ JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
+ kMaxHeaderSize);
+ }
+ return true;
+}
+
+Span<const uint8_t> MakeSpan(const char* str) {
+ return Span<const uint8_t>(reinterpret_cast<const uint8_t*>(str),
+ strlen(str));
+}
+
+// Flip the image vertically for loading/saving PFM files which have the
+// scanlines inverted.
+void VerticallyFlipImage(float* const float_image, const size_t xsize,
+ const size_t ysize, const size_t num_channels) {
+ for (size_t y = 0; y < ysize / 2; y++) {
+ float* first_row = &float_image[y * num_channels * xsize];
+ float* other_row = &float_image[(ysize - y - 1) * num_channels * xsize];
+ for (size_t c = 0; c < num_channels; c++) {
+ for (size_t x = 0; x < xsize; ++x) {
+ float tmp = first_row[x * num_channels + c];
+ first_row[x * num_channels + c] = other_row[x * num_channels + c];
+ other_row[x * num_channels + c] = tmp;
+ }
+ }
+ }
+}
+
+} // namespace
+
+Status EncodeImagePNM(const PackedPixelFile& ppf, size_t bits_per_sample,
+ ThreadPool* pool, size_t frame_index,
+ std::vector<uint8_t>* bytes) {
+ const bool floating_point = bits_per_sample > 16;
+ // Choose native for PFM; PGM/PPM require big-endian
+ const JxlEndianness endianness =
+ floating_point ? JXL_NATIVE_ENDIAN : JXL_BIG_ENDIAN;
+ if (!ppf.metadata.exif.empty() || !ppf.metadata.iptc.empty() ||
+ !ppf.metadata.jumbf.empty() || !ppf.metadata.xmp.empty()) {
+ JXL_WARNING("PNM encoder ignoring metadata - use a different codec");
+ }
+
+ char header[kMaxHeaderSize];
+ int header_size = 0;
+ bool is_little_endian = endianness == JXL_LITTLE_ENDIAN ||
+ (endianness == JXL_NATIVE_ENDIAN && IsLittleEndian());
+ JXL_RETURN_IF_ERROR(EncodeHeader(ppf, bits_per_sample, is_little_endian,
+ header, &header_size));
+ bytes->resize(static_cast<size_t>(header_size) +
+ ppf.frames[frame_index].color.pixels_size);
+ memcpy(bytes->data(), header, static_cast<size_t>(header_size));
+ memcpy(bytes->data() + header_size, ppf.frames[frame_index].color.pixels(),
+ ppf.frames[frame_index].color.pixels_size);
+ if (floating_point) {
+ VerticallyFlipImage(reinterpret_cast<float*>(bytes->data() + header_size),
+ ppf.frames[frame_index].color.xsize,
+ ppf.frames[frame_index].color.ysize,
+ ppf.info.num_color_channels);
+ }
+
+ return true;
+}
+
+} // namespace extras
+} // namespace jxl
diff --git a/media/libjxl/src/lib/extras/enc/pnm.h b/media/libjxl/src/lib/extras/enc/pnm.h
new file mode 100644
index 0000000000..ecf0526a1a
--- /dev/null
+++ b/media/libjxl/src/lib/extras/enc/pnm.h
@@ -0,0 +1,32 @@
+// 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 "lib/extras/packed_image.h"
+#include "lib/jxl/base/data_parallel.h"
+#include "lib/jxl/base/padded_bytes.h"
+#include "lib/jxl/base/status.h"
+#include "lib/jxl/codec_in_out.h"
+#include "lib/jxl/color_encoding_internal.h"
+
+namespace jxl {
+namespace extras {
+
+// Transforms from io->c_current to `c_desired` and encodes into `bytes`.
+Status EncodeImagePNM(const PackedPixelFile& ppf, size_t bits_per_sample,
+ ThreadPool* pool, size_t frame_index,
+ std::vector<uint8_t>* bytes);
+
+} // namespace extras
+} // namespace jxl
+
+#endif // LIB_EXTRAS_ENC_PNM_H_