diff options
Diffstat (limited to 'media/libjxl/src/lib/extras/enc')
-rw-r--r-- | media/libjxl/src/lib/extras/enc/apng.cc | 280 | ||||
-rw-r--r-- | media/libjxl/src/lib/extras/enc/apng.h | 28 | ||||
-rw-r--r-- | media/libjxl/src/lib/extras/enc/exr.cc | 167 | ||||
-rw-r--r-- | media/libjxl/src/lib/extras/enc/exr.h | 30 | ||||
-rw-r--r-- | media/libjxl/src/lib/extras/enc/jpg.cc | 239 | ||||
-rw-r--r-- | media/libjxl/src/lib/extras/enc/jpg.h | 36 | ||||
-rw-r--r-- | media/libjxl/src/lib/extras/enc/pgx.cc | 93 | ||||
-rw-r--r-- | media/libjxl/src/lib/extras/enc/pgx.h | 33 | ||||
-rw-r--r-- | media/libjxl/src/lib/extras/enc/pnm.cc | 133 | ||||
-rw-r--r-- | media/libjxl/src/lib/extras/enc/pnm.h | 32 |
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_ |