From 8dd16259287f58f9273002717ec4d27e97127719 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 12 Jun 2024 07:43:14 +0200 Subject: Merging upstream version 127.0. Signed-off-by: Daniel Baumann --- third_party/jpeg-xl/lib/extras/dec/apng.cc | 1226 ++++++++++---------- third_party/jpeg-xl/lib/extras/dec/apng.h | 3 +- .../jpeg-xl/lib/extras/dec/color_description.cc | 38 +- third_party/jpeg-xl/lib/extras/dec/decode.cc | 9 + third_party/jpeg-xl/lib/extras/dec/decode.h | 2 + third_party/jpeg-xl/lib/extras/dec/gif.cc | 1 + third_party/jpeg-xl/lib/extras/dec/jpg.cc | 3 +- third_party/jpeg-xl/lib/extras/dec/jxl.cc | 4 +- third_party/jpeg-xl/lib/extras/dec/pnm.cc | 10 +- third_party/jpeg-xl/lib/extras/enc/encode.cc | 8 + third_party/jpeg-xl/lib/extras/enc/encode.h | 2 + third_party/jpeg-xl/lib/extras/enc/jpegli.cc | 4 + third_party/jpeg-xl/lib/extras/enc/jpg.cc | 7 +- third_party/jpeg-xl/lib/extras/jpegli_test.cc | 2 +- third_party/jpeg-xl/lib/extras/metrics.cc | 1 + .../jpeg-xl/lib/extras/packed_image_convert.cc | 1 + .../jpeg-xl/lib/include/jxl/color_encoding.h | 2 - third_party/jpeg-xl/lib/jpegli/color_transform.cc | 316 ++++- third_party/jpeg-xl/lib/jpegli/common.h | 7 +- third_party/jpeg-xl/lib/jpegli/decode.cc | 49 +- third_party/jpeg-xl/lib/jpegli/decode.h | 1 + third_party/jpeg-xl/lib/jpegli/decode_api_test.cc | 24 +- third_party/jpeg-xl/lib/jpegli/decode_internal.h | 4 +- third_party/jpeg-xl/lib/jpegli/decode_marker.cc | 6 +- third_party/jpeg-xl/lib/jpegli/encode.cc | 40 +- third_party/jpeg-xl/lib/jpegli/encode.h | 1 + third_party/jpeg-xl/lib/jpegli/encode_api_test.cc | 47 +- .../jpeg-xl/lib/jpegli/error_handling_test.cc | 22 +- .../jpeg-xl/lib/jpegli/input_suspension_test.cc | 14 +- .../jpeg-xl/lib/jpegli/libjpeg_test_util.cc | 7 +- .../jpeg-xl/lib/jpegli/output_suspension_test.cc | 12 + .../jpeg-xl/lib/jpegli/source_manager_test.cc | 3 +- third_party/jpeg-xl/lib/jpegli/streaming_test.cc | 10 + third_party/jpeg-xl/lib/jpegli/test_utils.cc | 63 +- third_party/jpeg-xl/lib/jpegli/test_utils.h | 28 +- .../jpeg-xl/lib/jpegli/transcode_api_test.cc | 9 + third_party/jpeg-xl/lib/jxl/ac_context.h | 11 +- third_party/jpeg-xl/lib/jxl/ac_strategy.h | 3 + third_party/jpeg-xl/lib/jxl/ac_strategy_test.cc | 8 +- third_party/jpeg-xl/lib/jxl/ans_common.h | 11 +- third_party/jpeg-xl/lib/jxl/ans_test.cc | 3 +- .../jpeg-xl/lib/jxl/base/compiler_specific.h | 10 +- third_party/jpeg-xl/lib/jxl/base/exif.h | 6 +- third_party/jpeg-xl/lib/jxl/base/include_jpeglib.h | 20 + third_party/jpeg-xl/lib/jxl/base/matrix_ops.h | 6 +- third_party/jpeg-xl/lib/jxl/base/rect.h | 194 ++++ third_party/jpeg-xl/lib/jxl/base/span.h | 7 +- third_party/jpeg-xl/lib/jxl/bit_reader_test.cc | 1 + third_party/jpeg-xl/lib/jxl/bits_test.cc | 3 + third_party/jpeg-xl/lib/jxl/blending.cc | 9 + third_party/jpeg-xl/lib/jxl/blending.h | 2 + .../jpeg-xl/lib/jxl/butteraugli/butteraugli.cc | 10 +- third_party/jpeg-xl/lib/jxl/cache_aligned.h | 2 - third_party/jpeg-xl/lib/jxl/chroma_from_luma.h | 7 +- third_party/jpeg-xl/lib/jxl/cms/jxl_cms_internal.h | 16 +- third_party/jpeg-xl/lib/jxl/cms/opsin_params.h | 3 + third_party/jpeg-xl/lib/jxl/cms/tone_mapping.h | 1 + third_party/jpeg-xl/lib/jxl/coeff_order.h | 17 +- third_party/jpeg-xl/lib/jxl/coeff_order_fwd.h | 4 +- .../jpeg-xl/lib/jxl/color_encoding_internal.h | 1 + .../jpeg-xl/lib/jxl/color_management_test.cc | 4 +- third_party/jpeg-xl/lib/jxl/compressed_dc.cc | 1 + third_party/jpeg-xl/lib/jxl/compressed_dc.h | 5 +- third_party/jpeg-xl/lib/jxl/convolve-inl.h | 2 + third_party/jpeg-xl/lib/jxl/convolve.h | 1 + third_party/jpeg-xl/lib/jxl/convolve_separable5.cc | 1 + third_party/jpeg-xl/lib/jxl/convolve_slow.cc | 4 +- third_party/jpeg-xl/lib/jxl/convolve_symmetric3.cc | 1 + third_party/jpeg-xl/lib/jxl/convolve_symmetric5.cc | 1 + third_party/jpeg-xl/lib/jxl/convolve_test.cc | 3 + third_party/jpeg-xl/lib/jxl/dct_for_test.h | 3 +- third_party/jpeg-xl/lib/jxl/dct_util.h | 5 +- third_party/jpeg-xl/lib/jxl/dec_ans.h | 7 +- third_party/jpeg-xl/lib/jxl/dec_frame.cc | 1 + third_party/jpeg-xl/lib/jxl/dec_group.cc | 6 +- third_party/jpeg-xl/lib/jxl/dec_group_border.cc | 7 + third_party/jpeg-xl/lib/jxl/dec_group_border.h | 9 +- third_party/jpeg-xl/lib/jxl/dec_modular.cc | 4 +- third_party/jpeg-xl/lib/jxl/dec_modular.h | 11 +- third_party/jpeg-xl/lib/jxl/dec_noise.cc | 13 +- third_party/jpeg-xl/lib/jxl/dec_noise.h | 6 +- third_party/jpeg-xl/lib/jxl/dec_patch_dictionary.h | 6 +- third_party/jpeg-xl/lib/jxl/dec_transforms-inl.h | 4 +- .../jpeg-xl/lib/jxl/dec_transforms_testonly.cc | 1 - .../jpeg-xl/lib/jxl/dec_transforms_testonly.h | 3 +- third_party/jpeg-xl/lib/jxl/dec_xyb.cc | 5 +- third_party/jpeg-xl/lib/jxl/dec_xyb.h | 2 + third_party/jpeg-xl/lib/jxl/decode_test.cc | 4 +- third_party/jpeg-xl/lib/jxl/enc_ac_strategy.cc | 6 +- third_party/jpeg-xl/lib/jxl/enc_ac_strategy.h | 3 + .../jpeg-xl/lib/jxl/enc_adaptive_quantization.cc | 3 +- .../jpeg-xl/lib/jxl/enc_adaptive_quantization.h | 3 +- .../jpeg-xl/lib/jxl/enc_ar_control_field.cc | 6 +- third_party/jpeg-xl/lib/jxl/enc_ar_control_field.h | 5 +- third_party/jpeg-xl/lib/jxl/enc_cache.cc | 6 +- third_party/jpeg-xl/lib/jxl/enc_cache.h | 5 +- .../jpeg-xl/lib/jxl/enc_chroma_from_luma.cc | 6 +- third_party/jpeg-xl/lib/jxl/enc_chroma_from_luma.h | 2 + third_party/jpeg-xl/lib/jxl/enc_cluster.h | 8 +- third_party/jpeg-xl/lib/jxl/enc_coeff_order.cc | 4 +- third_party/jpeg-xl/lib/jxl/enc_coeff_order.h | 10 +- third_party/jpeg-xl/lib/jxl/enc_comparator.h | 2 + third_party/jpeg-xl/lib/jxl/enc_debug_image.cc | 5 +- third_party/jpeg-xl/lib/jxl/enc_debug_image.h | 4 +- third_party/jpeg-xl/lib/jxl/enc_detect_dots.cc | 4 +- third_party/jpeg-xl/lib/jxl/enc_detect_dots.h | 7 +- third_party/jpeg-xl/lib/jxl/enc_dot_dictionary.cc | 6 +- third_party/jpeg-xl/lib/jxl/enc_dot_dictionary.h | 4 +- third_party/jpeg-xl/lib/jxl/enc_entropy_coder.cc | 12 +- third_party/jpeg-xl/lib/jxl/enc_entropy_coder.h | 11 +- third_party/jpeg-xl/lib/jxl/enc_fast_lossless.cc | 3 +- third_party/jpeg-xl/lib/jxl/enc_fields.cc | 2 +- third_party/jpeg-xl/lib/jxl/enc_frame.cc | 22 +- third_party/jpeg-xl/lib/jxl/enc_gaborish.cc | 5 +- third_party/jpeg-xl/lib/jxl/enc_gaborish.h | 1 + third_party/jpeg-xl/lib/jxl/enc_gaborish_test.cc | 1 + third_party/jpeg-xl/lib/jxl/enc_group.cc | 2 +- third_party/jpeg-xl/lib/jxl/enc_group.h | 3 +- third_party/jpeg-xl/lib/jxl/enc_heuristics.cc | 5 +- third_party/jpeg-xl/lib/jxl/enc_heuristics.h | 2 +- third_party/jpeg-xl/lib/jxl/enc_image_bundle.cc | 1 + third_party/jpeg-xl/lib/jxl/enc_image_bundle.h | 1 + third_party/jpeg-xl/lib/jxl/enc_linalg_test.cc | 2 + third_party/jpeg-xl/lib/jxl/enc_modular.cc | 9 +- third_party/jpeg-xl/lib/jxl/enc_modular.h | 1 + third_party/jpeg-xl/lib/jxl/enc_optimize_test.cc | 4 + .../jpeg-xl/lib/jxl/enc_patch_dictionary.cc | 5 +- third_party/jpeg-xl/lib/jxl/enc_patch_dictionary.h | 8 +- third_party/jpeg-xl/lib/jxl/enc_photon_noise.h | 4 +- .../jpeg-xl/lib/jxl/enc_photon_noise_test.cc | 1 - third_party/jpeg-xl/lib/jxl/enc_quant_weights.h | 2 + third_party/jpeg-xl/lib/jxl/enc_xyb.cc | 1 + third_party/jpeg-xl/lib/jxl/encode_internal.h | 2 +- third_party/jpeg-xl/lib/jxl/encode_test.cc | 6 +- third_party/jpeg-xl/lib/jxl/epf.cc | 18 +- third_party/jpeg-xl/lib/jxl/epf.h | 2 +- .../lib/jxl/fake_parallel_runner_testonly.h | 2 +- third_party/jpeg-xl/lib/jxl/fields.cc | 2 +- third_party/jpeg-xl/lib/jxl/fields.h | 3 - third_party/jpeg-xl/lib/jxl/fields_test.cc | 11 +- third_party/jpeg-xl/lib/jxl/frame_dimensions.h | 2 +- third_party/jpeg-xl/lib/jxl/frame_header.h | 10 +- third_party/jpeg-xl/lib/jxl/icc_codec.h | 2 +- third_party/jpeg-xl/lib/jxl/image.h | 184 +-- third_party/jpeg-xl/lib/jxl/image_bundle.h | 5 +- third_party/jpeg-xl/lib/jxl/image_metadata.h | 1 + third_party/jpeg-xl/lib/jxl/image_ops.h | 1 + third_party/jpeg-xl/lib/jxl/image_ops_test.cc | 6 +- third_party/jpeg-xl/lib/jxl/image_test_utils.h | 8 +- third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.cc | 2 +- third_party/jpeg-xl/lib/jxl/jxl_test.cc | 78 +- third_party/jpeg-xl/lib/jxl/lehmer_code_test.cc | 7 +- .../lib/jxl/modular/encoding/context_predict.h | 10 +- .../lib/jxl/modular/encoding/enc_debug_tree.cc | 3 +- third_party/jpeg-xl/lib/jxl/modular/options.h | 4 +- .../lib/jxl/modular/transform/enc_palette.cc | 1 + .../jpeg-xl/lib/jxl/modular/transform/palette.cc | 4 + .../jpeg-xl/lib/jxl/modular/transform/palette.h | 8 +- .../jpeg-xl/lib/jxl/modular/transform/squeeze.h | 4 +- .../jpeg-xl/lib/jxl/modular/transform/transform.cc | 2 +- .../jpeg-xl/lib/jxl/modular/transform/transform.h | 6 +- third_party/jpeg-xl/lib/jxl/opsin_image_test.cc | 2 + third_party/jpeg-xl/lib/jxl/opsin_inverse_test.cc | 2 + third_party/jpeg-xl/lib/jxl/passes_test.cc | 5 +- third_party/jpeg-xl/lib/jxl/preview_test.cc | 2 + third_party/jpeg-xl/lib/jxl/quant_weights.h | 4 +- third_party/jpeg-xl/lib/jxl/quant_weights_test.cc | 7 + third_party/jpeg-xl/lib/jxl/quantizer.cc | 4 +- third_party/jpeg-xl/lib/jxl/quantizer.h | 14 +- third_party/jpeg-xl/lib/jxl/quantizer_test.cc | 8 + .../render_pipeline/low_memory_render_pipeline.cc | 7 + .../render_pipeline/low_memory_render_pipeline.h | 9 +- .../lib/jxl/render_pipeline/render_pipeline.cc | 4 + .../lib/jxl/render_pipeline/render_pipeline.h | 12 +- .../jxl/render_pipeline/render_pipeline_stage.h | 4 +- .../jxl/render_pipeline/render_pipeline_test.cc | 2 +- .../jxl/render_pipeline/simple_render_pipeline.cc | 6 + .../jxl/render_pipeline/simple_render_pipeline.h | 7 +- .../jpeg-xl/lib/jxl/render_pipeline/stage_noise.h | 11 +- .../lib/jxl/render_pipeline/stage_splines.cc | 8 + .../jpeg-xl/lib/jxl/render_pipeline/stage_write.h | 6 +- .../render_pipeline/test_render_pipeline_stages.h | 6 +- third_party/jpeg-xl/lib/jxl/roundtrip_test.cc | 3 +- third_party/jpeg-xl/lib/jxl/sanitizers.h | 8 +- third_party/jpeg-xl/lib/jxl/speed_tier_test.cc | 2 +- third_party/jpeg-xl/lib/jxl/splines.cc | 3 +- third_party/jpeg-xl/lib/jxl/splines.h | 1 + third_party/jpeg-xl/lib/jxl/splines_gbench.cc | 5 +- third_party/jpeg-xl/lib/jxl/splines_test.cc | 1 + third_party/jpeg-xl/lib/jxl/test_image.cc | 3 + third_party/jpeg-xl/lib/jxl/test_image.h | 3 +- third_party/jpeg-xl/lib/jxl/test_utils.h | 12 + third_party/jpeg-xl/lib/jxl/xorshift128plus-inl.h | 4 +- third_party/jpeg-xl/lib/jxl_extras.cmake | 2 +- third_party/jpeg-xl/lib/jxl_lists.bzl | 2 + third_party/jpeg-xl/lib/jxl_lists.cmake | 2 + third_party/jpeg-xl/lib/jxl_tests.cmake | 2 +- .../lib/threads/thread_parallel_runner_test.cc | 3 + 198 files changed, 2031 insertions(+), 1217 deletions(-) create mode 100644 third_party/jpeg-xl/lib/jxl/base/include_jpeglib.h create mode 100644 third_party/jpeg-xl/lib/jxl/base/rect.h (limited to 'third_party/jpeg-xl/lib') diff --git a/third_party/jpeg-xl/lib/extras/dec/apng.cc b/third_party/jpeg-xl/lib/extras/dec/apng.cc index c607a71d08..824a6f47ee 100644 --- a/third_party/jpeg-xl/lib/extras/dec/apng.cc +++ b/third_party/jpeg-xl/lib/extras/dec/apng.cc @@ -38,8 +38,10 @@ #include #include -#include +#include +#include +#include #include #include #include @@ -49,8 +51,7 @@ #include "lib/jxl/base/common.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/printf_macros.h" -#include "lib/jxl/base/scope_guard.h" -#include "lib/jxl/sanitizers.h" +#include "lib/jxl/base/span.h" #if JPEGXL_ENABLE_APNG #include "png.h" /* original (unpatched) libpng is ok */ #endif @@ -58,39 +59,49 @@ namespace jxl { namespace extras { -#if JPEGXL_ENABLE_APNG +#if !JPEGXL_ENABLE_APNG + +bool CanDecodeAPNG() { return false; } +Status DecodeImageAPNG(const Span bytes, + const ColorHints& color_hints, PackedPixelFile* ppf, + const SizeConstraints* constraints) { + return false; +} + +#else // JPEGXL_ENABLE_APNG + namespace { -constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69, - 0x66, 0x00, 0x00}; +constexpr uint8_t kExifSignature[6] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00}; /* hIST chunk tail is not proccesed properly; skip this chunk completely; see https://github.com/glennrp/libpng/pull/413 */ -const png_byte kIgnoredPngChunks[] = { - 104, 73, 83, 84, '\0' /* hIST */ -}; +const uint8_t kIgnoredPngChunks[] = {'h', 'I', 'S', 'T', '\0'}; // Returns floating-point value from the PNG encoding (times 10^5). double F64FromU32(const uint32_t x) { return static_cast(x) * 1E-5; } -Status DecodeSRGB(const unsigned char* payload, const size_t payload_size, - JxlColorEncoding* color_encoding) { - if (payload_size != 1) return JXL_FAILURE("Wrong sRGB size"); +/** Extract information from 'sRGB' chunk. */ +Status DecodeSrgbChunk(const Bytes payload, JxlColorEncoding* color_encoding) { + if (payload.size() != 1) return JXL_FAILURE("Wrong sRGB size"); + uint8_t ri = payload[0]; // (PNG uses the same values as ICC.) - if (payload[0] >= 4) return JXL_FAILURE("Invalid Rendering Intent"); + if (ri >= 4) return JXL_FAILURE("Invalid Rendering Intent"); color_encoding->white_point = JXL_WHITE_POINT_D65; color_encoding->primaries = JXL_PRIMARIES_SRGB; color_encoding->transfer_function = JXL_TRANSFER_FUNCTION_SRGB; - color_encoding->rendering_intent = - static_cast(payload[0]); + color_encoding->rendering_intent = static_cast(ri); return true; } -// If the cICP profile is not fully supported, return false and leave -// color_encoding unmodified. -Status DecodeCICP(const unsigned char* payload, const size_t payload_size, - JxlColorEncoding* color_encoding) { - if (payload_size != 4) return JXL_FAILURE("Wrong cICP size"); +/** + * Extract information from 'cICP' chunk. + * + * If the cICP profile is not fully supported, return `false` and leave + * `color_encoding` unmodified. + */ +Status DecodeCicpChunk(const Bytes payload, JxlColorEncoding* color_encoding) { + if (payload.size() != 4) return JXL_FAILURE("Wrong cICP size"); JxlColorEncoding color_enc = *color_encoding; // From https://www.itu.int/rec/T-REC-H.273-202107-I/en @@ -217,257 +228,279 @@ Status DecodeCICP(const unsigned char* payload, const size_t payload_size, return true; } -Status DecodeGAMA(const unsigned char* payload, const size_t payload_size, - JxlColorEncoding* color_encoding) { - if (payload_size != 4) return JXL_FAILURE("Wrong gAMA size"); +/** Extract information from 'gAMA' chunk. */ +Status DecodeGamaChunk(Bytes payload, JxlColorEncoding* color_encoding) { + if (payload.size() != 4) return JXL_FAILURE("Wrong gAMA size"); color_encoding->transfer_function = JXL_TRANSFER_FUNCTION_GAMMA; - color_encoding->gamma = F64FromU32(LoadBE32(payload)); + color_encoding->gamma = F64FromU32(LoadBE32(payload.data())); return true; } -Status DecodeCHRM(const unsigned char* payload, const size_t payload_size, - JxlColorEncoding* color_encoding) { - if (payload_size != 32) return JXL_FAILURE("Wrong cHRM size"); - +/** Extract information from 'cHTM' chunk. */ +Status DecodeChrmChunk(Bytes payload, JxlColorEncoding* color_encoding) { + if (payload.size() != 32) return JXL_FAILURE("Wrong cHRM size"); + const uint8_t* data = payload.data(); color_encoding->white_point = JXL_WHITE_POINT_CUSTOM; - color_encoding->white_point_xy[0] = F64FromU32(LoadBE32(payload + 0)); - color_encoding->white_point_xy[1] = F64FromU32(LoadBE32(payload + 4)); + color_encoding->white_point_xy[0] = F64FromU32(LoadBE32(data + 0)); + color_encoding->white_point_xy[1] = F64FromU32(LoadBE32(data + 4)); color_encoding->primaries = JXL_PRIMARIES_CUSTOM; - color_encoding->primaries_red_xy[0] = F64FromU32(LoadBE32(payload + 8)); - color_encoding->primaries_red_xy[1] = F64FromU32(LoadBE32(payload + 12)); - color_encoding->primaries_green_xy[0] = F64FromU32(LoadBE32(payload + 16)); - color_encoding->primaries_green_xy[1] = F64FromU32(LoadBE32(payload + 20)); - color_encoding->primaries_blue_xy[0] = F64FromU32(LoadBE32(payload + 24)); - color_encoding->primaries_blue_xy[1] = F64FromU32(LoadBE32(payload + 28)); + color_encoding->primaries_red_xy[0] = F64FromU32(LoadBE32(data + 8)); + color_encoding->primaries_red_xy[1] = F64FromU32(LoadBE32(data + 12)); + color_encoding->primaries_green_xy[0] = F64FromU32(LoadBE32(data + 16)); + color_encoding->primaries_green_xy[1] = F64FromU32(LoadBE32(data + 20)); + color_encoding->primaries_blue_xy[0] = F64FromU32(LoadBE32(data + 24)); + color_encoding->primaries_blue_xy[1] = F64FromU32(LoadBE32(data + 28)); return true; } -// Retrieves XMP and EXIF/IPTC from itext and text. -class BlobsReaderPNG { - public: - static Status Decode(const png_text_struct& info, PackedMetadata* metadata) { - // We trust these are properly null-terminated by libpng. - const char* key = info.key; - const char* value = info.text; - if (strstr(key, "XML:com.adobe.xmp")) { - metadata->xmp.resize(strlen(value)); // safe, see above - memcpy(metadata->xmp.data(), value, metadata->xmp.size()); - } - - std::string type; - std::vector bytes; - - // Handle text chunks annotated with key "Raw profile type ####", with - // #### a type, which may contain metadata. - const char* kKey = "Raw profile type "; - if (strncmp(key, kKey, strlen(kKey)) != 0) return false; +/** Returns false if invalid. */ +JXL_INLINE Status DecodeHexNibble(const char c, uint32_t* JXL_RESTRICT nibble) { + if ('a' <= c && c <= 'f') { + *nibble = 10 + c - 'a'; + } else if ('0' <= c && c <= '9') { + *nibble = c - '0'; + } else { + *nibble = 0; + return JXL_FAILURE("Invalid metadata nibble"); + } + JXL_ASSERT(*nibble < 16); + return true; +} - if (!MaybeDecodeBase16(key, value, &type, &bytes)) { - JXL_WARNING("Couldn't parse 'Raw format type' text chunk"); - return false; - } - if (type == "exif") { - // Remove "Exif\0\0" prefix if present - if (bytes.size() >= sizeof kExifSignature && - memcmp(bytes.data(), kExifSignature, sizeof kExifSignature) == 0) { - bytes.erase(bytes.begin(), bytes.begin() + sizeof kExifSignature); - } - if (!metadata->exif.empty()) { - JXL_WARNING("overwriting EXIF (%" PRIuS " bytes) with base16 (%" PRIuS - " bytes)", - metadata->exif.size(), bytes.size()); - } - metadata->exif = std::move(bytes); - } else if (type == "iptc") { - // TODO(jon): Deal with IPTC in some way - } else if (type == "8bim") { - // TODO(jon): Deal with 8bim in some way - } else if (type == "xmp") { - if (!metadata->xmp.empty()) { - JXL_WARNING("overwriting XMP (%" PRIuS " bytes) with base16 (%" PRIuS - " bytes)", - metadata->xmp.size(), bytes.size()); +/** Returns false if invalid. */ +JXL_INLINE Status DecodeDecimal(const char** pos, const char* end, + uint32_t* JXL_RESTRICT value) { + size_t len = 0; + *value = 0; + while (*pos < end) { + char next = **pos; + if (next >= '0' && next <= '9') { + *value = (*value * 10) + static_cast(next - '0'); + len++; + if (len > 8) { + break; } - metadata->xmp = std::move(bytes); } else { - JXL_WARNING("Unknown type in 'Raw format type' text chunk: %s: %" PRIuS - " bytes", - type.c_str(), bytes.size()); + // Do not consume terminator (non-decimal digit). + break; } - return true; + (*pos)++; + } + if (len == 0 || len > 8) { + return JXL_FAILURE("Failed to parse decimal"); } + return true; +} - private: - // Returns false if invalid. - static JXL_INLINE Status DecodeNibble(const char c, - uint32_t* JXL_RESTRICT nibble) { - if ('a' <= c && c <= 'f') { - *nibble = 10 + c - 'a'; - } else if ('0' <= c && c <= '9') { - *nibble = c - '0'; - } else { - *nibble = 0; - return JXL_FAILURE("Invalid metadata nibble"); +/** + * Parses a PNG text chunk with key of the form "Raw profile type ####", with + * #### a type. + * + * Returns whether it could successfully parse the content. + * We trust key and encoded are null-terminated because they come from + * libpng. + */ +Status MaybeDecodeBase16(const char* key, const char* encoded, + std::string* type, std::vector* bytes) { + const char* encoded_end = encoded + strlen(encoded); + + const char* kKey = "Raw profile type "; + if (strncmp(key, kKey, strlen(kKey)) != 0) return false; + *type = key + strlen(kKey); + const size_t kMaxTypeLen = 20; + if (type->length() > kMaxTypeLen) return false; // Type too long + + // Header: freeform string and number of bytes + // Expected format is: + // \n + // profile name/description\n + // 40\n (the number of bytes after hex-decoding) + // 01234566789abcdef....\n (72 bytes per line max). + // 012345667\n (last line) + const char* pos = encoded; + + if (*(pos++) != '\n') return false; + while (pos < encoded_end && *pos != '\n') { + pos++; + } + if (pos == encoded_end) return false; + // We parsed so far a \n, some number of non \n characters and are now + // pointing at a \n. + if (*(pos++) != '\n') return false; + // Skip leading spaces + while (pos < encoded_end && *pos == ' ') { + pos++; + } + uint32_t bytes_to_decode = 0; + JXL_RETURN_IF_ERROR(DecodeDecimal(&pos, encoded_end, &bytes_to_decode)); + + // We need 2*bytes for the hex values plus 1 byte every 36 values, + // plus terminal \n for length. + size_t tail = static_cast(encoded_end - pos); + bool ok = ((tail / 2) >= bytes_to_decode); + if (ok) tail -= 2 * static_cast(bytes_to_decode); + ok = ok && (tail == 1 + DivCeil(bytes_to_decode, 36)); + if (!ok) { + return JXL_FAILURE("Not enough bytes to parse %d bytes in hex", + bytes_to_decode); + } + JXL_ASSERT(bytes->empty()); + bytes->reserve(bytes_to_decode); + + // Encoding: base16 with newline after 72 chars. + // pos points to the \n before the first line of hex values. + for (size_t i = 0; i < bytes_to_decode; ++i) { + if (i % 36 == 0) { + if (pos + 1 >= encoded_end) return false; // Truncated base16 1 + if (*pos != '\n') return false; // Expected newline + ++pos; } - JXL_ASSERT(*nibble < 16); - return true; + + if (pos + 2 >= encoded_end) return false; // Truncated base16 2; + uint32_t nibble0; + uint32_t nibble1; + JXL_RETURN_IF_ERROR(DecodeHexNibble(pos[0], &nibble0)); + JXL_RETURN_IF_ERROR(DecodeHexNibble(pos[1], &nibble1)); + bytes->push_back(static_cast((nibble0 << 4) + nibble1)); + pos += 2; } + if (pos + 1 != encoded_end) return false; // Too many encoded bytes + if (pos[0] != '\n') return false; // Incorrect metadata terminator + return true; +} - // Returns false if invalid. - static JXL_INLINE Status DecodeDecimal(const char** pos, const char* end, - uint32_t* JXL_RESTRICT value) { - size_t len = 0; - *value = 0; - while (*pos < end) { - char next = **pos; - if (next >= '0' && next <= '9') { - *value = (*value * 10) + static_cast(next - '0'); - len++; - if (len > 8) { - break; - } - } else { - // Do not consume terminator (non-decimal digit). - break; - } - (*pos)++; - } - if (len == 0 || len > 8) { - return JXL_FAILURE("Failed to parse decimal"); - } - return true; +/** Retrieves XMP and EXIF/IPTC from itext and text. */ +Status DecodeBlob(const png_text_struct& info, PackedMetadata* metadata) { + // We trust these are properly null-terminated by libpng. + const char* key = info.key; + const char* value = info.text; + if (strstr(key, "XML:com.adobe.xmp")) { + metadata->xmp.resize(strlen(value)); // safe, see above + memcpy(metadata->xmp.data(), value, metadata->xmp.size()); } - // Parses a PNG text chunk with key of the form "Raw profile type ####", with - // #### a type. - // Returns whether it could successfully parse the content. - // We trust key and encoded are null-terminated because they come from - // libpng. - static Status MaybeDecodeBase16(const char* key, const char* encoded, - std::string* type, - std::vector* bytes) { - const char* encoded_end = encoded + strlen(encoded); - - const char* kKey = "Raw profile type "; - if (strncmp(key, kKey, strlen(kKey)) != 0) return false; - *type = key + strlen(kKey); - const size_t kMaxTypeLen = 20; - if (type->length() > kMaxTypeLen) return false; // Type too long - - // Header: freeform string and number of bytes - // Expected format is: - // \n - // profile name/description\n - // 40\n (the number of bytes after hex-decoding) - // 01234566789abcdef....\n (72 bytes per line max). - // 012345667\n (last line) - const char* pos = encoded; - - if (*(pos++) != '\n') return false; - while (pos < encoded_end && *pos != '\n') { - pos++; - } - if (pos == encoded_end) return false; - // We parsed so far a \n, some number of non \n characters and are now - // pointing at a \n. - if (*(pos++) != '\n') return false; - // Skip leading spaces - while (pos < encoded_end && *pos == ' ') { - pos++; + std::string type; + std::vector bytes; + + // Handle text chunks annotated with key "Raw profile type ####", with + // #### a type, which may contain metadata. + const char* kKey = "Raw profile type "; + if (strncmp(key, kKey, strlen(kKey)) != 0) return false; + + if (!MaybeDecodeBase16(key, value, &type, &bytes)) { + JXL_WARNING("Couldn't parse 'Raw format type' text chunk"); + return false; + } + if (type == "exif") { + // Remove "Exif\0\0" prefix if present + if (bytes.size() >= sizeof kExifSignature && + memcmp(bytes.data(), kExifSignature, sizeof kExifSignature) == 0) { + bytes.erase(bytes.begin(), bytes.begin() + sizeof kExifSignature); } - uint32_t bytes_to_decode = 0; - JXL_RETURN_IF_ERROR(DecodeDecimal(&pos, encoded_end, &bytes_to_decode)); - - // We need 2*bytes for the hex values plus 1 byte every 36 values, - // plus terminal \n for length. - size_t tail = static_cast(encoded_end - pos); - bool ok = ((tail / 2) >= bytes_to_decode); - if (ok) tail -= 2 * static_cast(bytes_to_decode); - ok = ok && (tail == 1 + DivCeil(bytes_to_decode, 36)); - if (!ok) { - return JXL_FAILURE("Not enough bytes to parse %d bytes in hex", - bytes_to_decode); + if (!metadata->exif.empty()) { + JXL_WARNING("overwriting EXIF (%" PRIuS " bytes) with base16 (%" PRIuS + " bytes)", + metadata->exif.size(), bytes.size()); } - JXL_ASSERT(bytes->empty()); - bytes->reserve(bytes_to_decode); - - // Encoding: base16 with newline after 72 chars. - // pos points to the \n before the first line of hex values. - for (size_t i = 0; i < bytes_to_decode; ++i) { - if (i % 36 == 0) { - if (pos + 1 >= encoded_end) return false; // Truncated base16 1 - if (*pos != '\n') return false; // Expected newline - ++pos; - } - - if (pos + 2 >= encoded_end) return false; // Truncated base16 2; - uint32_t nibble0; - uint32_t nibble1; - JXL_RETURN_IF_ERROR(DecodeNibble(pos[0], &nibble0)); - JXL_RETURN_IF_ERROR(DecodeNibble(pos[1], &nibble1)); - bytes->push_back(static_cast((nibble0 << 4) + nibble1)); - pos += 2; + metadata->exif = std::move(bytes); + } else if (type == "iptc") { + // TODO(jon): Deal with IPTC in some way + } else if (type == "8bim") { + // TODO(jon): Deal with 8bim in some way + } else if (type == "xmp") { + if (!metadata->xmp.empty()) { + JXL_WARNING("overwriting XMP (%" PRIuS " bytes) with base16 (%" PRIuS + " bytes)", + metadata->xmp.size(), bytes.size()); } - if (pos + 1 != encoded_end) return false; // Too many encoded bytes - if (pos[0] != '\n') return false; // Incorrect metadata terminator - return true; + metadata->xmp = std::move(bytes); + } else { + JXL_WARNING("Unknown type in 'Raw format type' text chunk: %s: %" PRIuS + " bytes", + type.c_str(), bytes.size()); } -}; + return true; +} constexpr bool isAbc(char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); } -constexpr uint32_t kId_IHDR = 0x52444849; -constexpr uint32_t kId_acTL = 0x4C546361; -constexpr uint32_t kId_fcTL = 0x4C546366; -constexpr uint32_t kId_IDAT = 0x54414449; -constexpr uint32_t kId_fdAT = 0x54416466; -constexpr uint32_t kId_IEND = 0x444E4549; -constexpr uint32_t kId_cICP = 0x50434963; -constexpr uint32_t kId_iCCP = 0x50434369; -constexpr uint32_t kId_sRGB = 0x42475273; -constexpr uint32_t kId_gAMA = 0x414D4167; -constexpr uint32_t kId_cHRM = 0x4D524863; -constexpr uint32_t kId_eXIf = 0x66495865; - -struct APNGFrame { - APNGFrame() : pixels(nullptr, free) {} - std::unique_ptr pixels; +/** Wrap 4-char tag name into ID. */ +constexpr uint32_t MakeTag(uint8_t a, uint8_t b, uint8_t c, uint8_t d) { + return a | (b << 8) | (c << 16) | (d << 24); +} + +/** Reusable image data container. */ +struct Pixels { + // Use array instead of vector to avoid memory initialization. + std::unique_ptr pixels; size_t pixels_size = 0; std::vector rows; - unsigned int w, h, delay_num, delay_den; - Status Resize(size_t new_size) { + + Status Resize(size_t row_bytes, size_t num_rows) { + size_t new_size = row_bytes * num_rows; // it is assumed size is sane if (new_size > pixels_size) { - pixels.reset(malloc(new_size)); + pixels.reset(new uint8_t[new_size]); if (!pixels) { // TODO(szabadka): use specialized OOM error code return JXL_FAILURE("Failed to allocate memory for image buffer"); } pixels_size = new_size; } + rows.resize(num_rows); + for (size_t y = 0; y < num_rows; y++) { + rows[y] = pixels.get() + y * row_bytes; + } return true; } }; +/** + * Helper that chunks in-memory input. + */ struct Reader { - const uint8_t* next; - const uint8_t* last; - bool Read(void* data, size_t len) { - size_t cap = last - next; + explicit Reader(Span data) : data_(data) {} + + const Span data_; + size_t offset_ = 0; + + Bytes Peek(size_t len) const { + size_t cap = data_.size() - offset_; size_t to_copy = std::min(cap, len); - memcpy(data, next, to_copy); - next += to_copy; - return (len == to_copy); + return {data_.data() + offset_, to_copy}; } - bool Eof() const { return next == last; } -}; -const uint32_t cMaxPNGSize = 1000000UL; -const size_t kMaxPNGChunkSize = 1lu << 30; // 1 GB + Bytes Read(size_t len) { + Bytes result = Peek(len); + offset_ += result.size(); + return result; + } -void info_fn(png_structp png_ptr, png_infop info_ptr) { + /* Returns empty Span on errror. */ + Bytes ReadChunk() { + Bytes len = Peek(4); + if (len.size() != 4) { + return Bytes(); + } + const auto size = png_get_uint_32(len.data()); + // NB: specification allows 2^31 - 1 + constexpr size_t kMaxPNGChunkSize = 1u << 30; // 1 GB + // Check first, to avoid overflow. + if (size > kMaxPNGChunkSize) { + JXL_WARNING("APNG chunk size is too big"); + return Bytes(); + } + size_t full_size = size + 12; // size does not include itself, tag and CRC. + Bytes result = Read(full_size); + return (result.size() == full_size) ? result : Bytes(); + } + + bool Eof() const { return offset_ == data_.size(); } +}; + +void ProgressiveRead_OnInfo(png_structp png_ptr, png_infop info_ptr) { png_set_expand(png_ptr); png_set_palette_to_rgb(png_ptr); png_set_tRNS_to_alpha(png_ptr); @@ -475,432 +508,437 @@ void info_fn(png_structp png_ptr, png_infop info_ptr) { png_read_update_info(png_ptr, info_ptr); } -void row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num, - int pass) { - APNGFrame* frame = - reinterpret_cast(png_get_progressive_ptr(png_ptr)); +void ProgressiveRead_OnRow(png_structp png_ptr, png_bytep new_row, + png_uint_32 row_num, int pass) { + Pixels* frame = reinterpret_cast(png_get_progressive_ptr(png_ptr)); JXL_CHECK(frame); JXL_CHECK(row_num < frame->rows.size()); JXL_CHECK(frame->rows[row_num] < frame->rows[0] + frame->pixels_size); png_progressive_combine_row(png_ptr, frame->rows[row_num], new_row); } -inline unsigned int read_chunk(Reader* r, std::vector* pChunk) { - unsigned char len[4]; - if (r->Read(&len, 4)) { - const auto size = png_get_uint_32(len); - // Check first, to avoid overflow. - if (size > kMaxPNGChunkSize) { - JXL_WARNING("APNG chunk size is too big"); - return 0; - } - pChunk->resize(size + 12); - memcpy(pChunk->data(), len, 4); - if (r->Read(pChunk->data() + 4, pChunk->size() - 4)) { - return LoadLE32(pChunk->data() + 4); - } +// Holds intermediate state during parsing APNG file. +struct Context { + ~Context() { + // Make sure png memory is released in any case. + ResetPngDecoder(); } - return 0; -} - -int processing_start(png_structp& png_ptr, png_infop& info_ptr, void* frame_ptr, - bool hasInfo, std::vector& chunkIHDR, - std::vector>& chunksInfo) { - unsigned char header[8] = {137, 80, 78, 71, 13, 10, 26, 10}; - // Cleanup prior decoder, if any. - png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); - // Just in case. Not all versions on libpng wipe-out the pointers. - png_ptr = nullptr; - info_ptr = nullptr; + bool CreatePngDecoder() { + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, + nullptr); + info_ptr = png_create_info_struct(png_ptr); + return (png_ptr != nullptr && info_ptr != nullptr); + } - png_ptr = - png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); - info_ptr = png_create_info_struct(png_ptr); - if (!png_ptr || !info_ptr) return 1; + /** + * Initialize PNG decoder. + * + * TODO(eustas): add details + */ + bool InitPngDecoder(bool hasInfo, std::vector& chunksInfo) { + ResetPngDecoder(); + + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, + nullptr); + info_ptr = png_create_info_struct(png_ptr); + if (png_ptr == nullptr || info_ptr == nullptr) { + return false; + } - if (setjmp(png_jmpbuf(png_ptr))) { - return 1; - } + if (setjmp(png_jmpbuf(png_ptr))) { + return false; + } - png_set_keep_unknown_chunks(png_ptr, 1, kIgnoredPngChunks, - static_cast(sizeof(kIgnoredPngChunks) / 5)); + png_set_keep_unknown_chunks( + png_ptr, 1, kIgnoredPngChunks, + static_cast(sizeof(kIgnoredPngChunks) / 5)); - png_set_crc_action(png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); - png_set_progressive_read_fn(png_ptr, frame_ptr, info_fn, row_fn, nullptr); + png_set_crc_action(png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); + png_set_progressive_read_fn(png_ptr, static_cast(&frameRaw), + ProgressiveRead_OnInfo, ProgressiveRead_OnRow, + nullptr); - png_process_data(png_ptr, info_ptr, header, 8); - png_process_data(png_ptr, info_ptr, chunkIHDR.data(), chunkIHDR.size()); + std::array header = {137, 80, 78, 71, 13, 10, 26, 10}; + png_process_data(png_ptr, info_ptr, header.data(), header.size()); + png_process_data(png_ptr, info_ptr, ihdr.data(), ihdr.size()); - if (hasInfo) { - for (auto& chunk : chunksInfo) { - png_process_data(png_ptr, info_ptr, chunk.data(), chunk.size()); + if (hasInfo) { + for (auto& chunk : chunksInfo) { + png_process_data(png_ptr, info_ptr, const_cast(chunk.data()), + chunk.size()); + } } + return true; } - return 0; -} -int processing_data(png_structp png_ptr, png_infop info_ptr, unsigned char* p, - unsigned int size) { - if (!png_ptr || !info_ptr) return 1; + /** + * Pass chunk to PNG decoder. + */ + bool FeedChunks(const Bytes& chunk1, const Bytes& chunk2 = Bytes()) { + // TODO(eustas): turn to DCHECK + if (!png_ptr || !info_ptr) return false; + + if (setjmp(png_jmpbuf(png_ptr))) { + return false; + } - if (setjmp(png_jmpbuf(png_ptr))) { - return 1; + for (const auto& chunk : {chunk1, chunk2}) { + if (!chunk.empty()) { + png_process_data(png_ptr, info_ptr, const_cast(chunk.data()), + chunk.size()); + } + } + return true; } - png_process_data(png_ptr, info_ptr, p, size); - return 0; -} + bool FinalizeStream(PackedMetadata* metadata) { + // TODO(eustas): turn to DCHECK + if (!png_ptr || !info_ptr) return false; -int processing_finish(png_structp png_ptr, png_infop info_ptr, - PackedMetadata* metadata) { - unsigned char footer[12] = {0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130}; + if (setjmp(png_jmpbuf(png_ptr))) { + return false; + } - if (!png_ptr || !info_ptr) return 1; + const std::array kFooter = {0, 0, 0, 0, 73, 69, + 78, 68, 174, 66, 96, 130}; + png_process_data(png_ptr, info_ptr, const_cast(kFooter.data()), + kFooter.size()); + // before destroying: check if we encountered any metadata chunks + png_textp text_ptr; + int num_text; + png_get_text(png_ptr, info_ptr, &text_ptr, &num_text); + for (int i = 0; i < num_text; i++) { + Status result = DecodeBlob(text_ptr[i], metadata); + // Ignore unknown / malformed blob. + (void)result; + } - if (setjmp(png_jmpbuf(png_ptr))) { - return 1; + return true; } - png_process_data(png_ptr, info_ptr, footer, 12); - // before destroying: check if we encountered any metadata chunks - png_textp text_ptr; - int num_text; - png_get_text(png_ptr, info_ptr, &text_ptr, &num_text); - for (int i = 0; i < num_text; i++) { - (void)BlobsReaderPNG::Decode(text_ptr[i], metadata); + void ResetPngDecoder() { + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + // Just in case. Not all versions on libpng wipe-out the pointers. + png_ptr = nullptr; + info_ptr = nullptr; } - return 0; -} + std::vector ihdr; // (modified) copy of file IHDR chunk + png_structp png_ptr = nullptr; + png_infop info_ptr = nullptr; + Pixels frameRaw = {}; +}; + +constexpr uint32_t kMaxPNGSize = 1000000UL; + +struct FrameInfo { + PackedImage data; + uint32_t duration; + size_t x0, xsize; + size_t y0, ysize; + uint32_t dispose_op; + uint32_t blend_op; +}; } // namespace -#endif -bool CanDecodeAPNG() { -#if JPEGXL_ENABLE_APNG - return true; -#else - return false; -#endif -} +bool CanDecodeAPNG() { return true; } Status DecodeImageAPNG(const Span bytes, const ColorHints& color_hints, PackedPixelFile* ppf, const SizeConstraints* constraints) { -#if JPEGXL_ENABLE_APNG - Reader r; - unsigned char sig[8]; - png_structp png_ptr = nullptr; - png_infop info_ptr = nullptr; - std::vector chunk; - std::vector chunkIHDR; - std::vector> chunksInfo; - bool isAnimated = false; - bool hasInfo = false; - bool seenFctl = false; - APNGFrame frameRaw = {}; - uint32_t num_channels; - JxlPixelFormat format = {}; - unsigned int bytes_per_pixel = 0; - - struct FrameInfo { - PackedImage data; - uint32_t duration; - size_t x0, xsize; - size_t y0, ysize; - uint32_t dispose_op; - uint32_t blend_op; - }; - - std::vector frames; + // Initialize output (default settings in case e.g. only gAMA is given). + ppf->frames.clear(); + ppf->info.exponent_bits_per_sample = 0; + ppf->info.alpha_exponent_bits = 0; + ppf->info.orientation = JXL_ORIENT_IDENTITY; + ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB; + ppf->color_encoding.white_point = JXL_WHITE_POINT_D65; + ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB; + ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB; + ppf->color_encoding.rendering_intent = JXL_RENDERING_INTENT_RELATIVE; - // Make sure png memory is released in any case. - auto scope_guard = MakeScopeGuard([&]() { - png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); - // Just in case. Not all versions on libpng wipe-out the pointers. - png_ptr = nullptr; - info_ptr = nullptr; - }); + Reader input(bytes); - r = {bytes.data(), bytes.data() + bytes.size()}; // Not a PNG => not an error unsigned char png_signature[8] = {137, 80, 78, 71, 13, 10, 26, 10}; - if (!r.Read(sig, 8) || memcmp(sig, png_signature, 8) != 0) { + Bytes sig = input.Read(8); + if (sig.size() != 8 || memcmp(sig.data(), png_signature, 8) != 0) { return false; } - unsigned int id = read_chunk(&r, &chunkIHDR); - ppf->info.exponent_bits_per_sample = 0; - ppf->info.alpha_exponent_bits = 0; - ppf->info.orientation = JXL_ORIENT_IDENTITY; + Bytes chunk_ihdr = input.ReadChunk(); + if (chunk_ihdr.empty()) { + return false; + } + uint32_t id = LoadLE32(chunk_ihdr.data() + 4); + if (id != MakeTag('I', 'H', 'D', 'R') || chunk_ihdr.size() != 25) { + return false; + } + const uint32_t w = png_get_uint_32(chunk_ihdr.data() + 8); + const uint32_t h = png_get_uint_32(chunk_ihdr.data() + 12); + if (w > kMaxPNGSize || h > kMaxPNGSize) { + return false; + } - ppf->frames.clear(); + Context ctx; + ctx.ihdr = chunk_ihdr.Copy(); + + std::vector chunksInfo; + if (!ctx.InitPngDecoder(false, chunksInfo)) { + return false; + } + bool isAnimated = false; + bool hasInfo = false; + uint32_t num_channels; + JxlPixelFormat format = {}; + unsigned int bytes_per_pixel = 0; + std::vector frames; bool have_color = false; bool have_cicp = false; bool have_iccp = false; bool have_srgb = false; - bool errorstate = true; - if (id == kId_IHDR && chunkIHDR.size() == 25) { - uint32_t x0 = 0; - uint32_t y0 = 0; - uint32_t delay_num = 1; - uint32_t delay_den = 10; - uint32_t dop = 0; - uint32_t bop = 0; - - uint32_t w = png_get_uint_32(chunkIHDR.data() + 8); - uint32_t h = png_get_uint_32(chunkIHDR.data() + 12); - uint32_t w0 = w; - uint32_t h0 = h; - if (w > cMaxPNGSize || h > cMaxPNGSize) { + uint32_t x0 = 0; + uint32_t y0 = 0; + uint32_t delay_num = 1; + uint32_t delay_den = 10; + uint32_t dop = 0; + uint32_t bop = 0; + uint32_t w0 = w; + uint32_t h0 = h; + + while (!input.Eof()) { + Bytes chunk = input.ReadChunk(); + if (chunk.empty()) { return false; } - - // default settings in case e.g. only gAMA is given - ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB; - ppf->color_encoding.white_point = JXL_WHITE_POINT_D65; - ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB; - ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB; - ppf->color_encoding.rendering_intent = JXL_RENDERING_INTENT_RELATIVE; - - if (!processing_start(png_ptr, info_ptr, static_cast(&frameRaw), - hasInfo, chunkIHDR, chunksInfo)) { - while (!r.Eof()) { - id = read_chunk(&r, &chunk); - if (!id) break; - seenFctl |= (id == kId_fcTL); - - if (id == kId_acTL && !hasInfo && !isAnimated) { - isAnimated = true; - ppf->info.have_animation = JXL_TRUE; - ppf->info.animation.tps_numerator = 1000; - ppf->info.animation.tps_denominator = 1; - } else if (id == kId_IEND || - (id == kId_fcTL && (!hasInfo || isAnimated))) { - if (hasInfo) { - if (!processing_finish(png_ptr, info_ptr, &ppf->metadata)) { - // Allocates the frame buffer. - uint32_t duration = delay_num * 1000 / delay_den; - JXL_ASSIGN_OR_RETURN(PackedImage image, - PackedImage::Create(w0, h0, format)); - frames.push_back(FrameInfo{std::move(image), duration, x0, w0, y0, - h0, dop, bop}); - auto& frame = frames.back().data; - for (size_t y = 0; y < h0; ++y) { - memcpy(static_cast(frame.pixels()) + frame.stride * y, - frameRaw.rows[y], bytes_per_pixel * w0); - } - } else { - break; - } + id = LoadLE32(chunk.data() + 4); + + if (id == MakeTag('a', 'c', 'T', 'L') && !hasInfo && !isAnimated) { + isAnimated = true; + ppf->info.have_animation = JXL_TRUE; + ppf->info.animation.tps_numerator = 1000; + ppf->info.animation.tps_denominator = 1; + } else if (id == MakeTag('I', 'E', 'N', 'D') || + (id == MakeTag('f', 'c', 'T', 'L') && + (!hasInfo || isAnimated))) { + if (hasInfo) { + if (ctx.FinalizeStream(&ppf->metadata)) { + // Allocates the frame buffer. + uint32_t duration = delay_num * 1000 / delay_den; + JXL_ASSIGN_OR_RETURN(PackedImage image, + PackedImage::Create(w0, h0, format)); + frames.push_back( + FrameInfo{std::move(image), duration, x0, w0, y0, h0, dop, bop}); + auto& frame = frames.back().data; + for (size_t y = 0; y < h0; ++y) { + // TODO(eustas): ensure multiplication is safe + memcpy(static_cast(frame.pixels()) + frame.stride * y, + ctx.frameRaw.rows[y], bytes_per_pixel * w0); } + } else { + return false; + } + } - if (id == kId_IEND) { - errorstate = false; - break; - } - if (chunk.size() < 34) { - return JXL_FAILURE("Received a chunk that is too small (%" PRIuS - "B)", - chunk.size()); - } - // At this point the old frame is done. Let's start a new one. - w0 = png_get_uint_32(chunk.data() + 12); - h0 = png_get_uint_32(chunk.data() + 16); - x0 = png_get_uint_32(chunk.data() + 20); - y0 = png_get_uint_32(chunk.data() + 24); - delay_num = png_get_uint_16(chunk.data() + 28); - delay_den = png_get_uint_16(chunk.data() + 30); - dop = chunk[32]; - bop = chunk[33]; - - if (!delay_den) delay_den = 100; - - if (w0 > cMaxPNGSize || h0 > cMaxPNGSize || x0 > cMaxPNGSize || - y0 > cMaxPNGSize || x0 + w0 > w || y0 + h0 > h || dop > 2 || - bop > 1) { - break; - } + if (id == MakeTag('I', 'E', 'N', 'D')) { + break; + } + if (chunk.size() < 34) { + return JXL_FAILURE("Received a chunk that is too small (%" PRIuS "B)", + chunk.size()); + } + // At this point the old frame is done. Let's start a new one. + w0 = png_get_uint_32(chunk.data() + 12); + h0 = png_get_uint_32(chunk.data() + 16); + x0 = png_get_uint_32(chunk.data() + 20); + y0 = png_get_uint_32(chunk.data() + 24); + delay_num = png_get_uint_16(chunk.data() + 28); + delay_den = png_get_uint_16(chunk.data() + 30); + dop = chunk[32]; + bop = chunk[33]; + + if (!delay_den) delay_den = 100; + + if (w0 > kMaxPNGSize || h0 > kMaxPNGSize || x0 > kMaxPNGSize || + y0 > kMaxPNGSize || x0 + w0 > w || y0 + h0 > h || dop > 2 || + bop > 1) { + return false; + } - if (hasInfo) { - memcpy(chunkIHDR.data() + 8, chunk.data() + 12, 8); - if (processing_start(png_ptr, info_ptr, - static_cast(&frameRaw), hasInfo, - chunkIHDR, chunksInfo)) { - break; - } - } - } else if (id == kId_IDAT) { - // First IDAT chunk means we now have all header info - if (seenFctl) { - // `fcTL` chunk must appear after all `IDAT` chunks - return JXL_FAILURE("IDAT chunk after fcTL chunk"); - } - hasInfo = true; - JXL_CHECK(w == png_get_image_width(png_ptr, info_ptr)); - JXL_CHECK(h == png_get_image_height(png_ptr, info_ptr)); - int colortype = png_get_color_type(png_ptr, info_ptr); - int png_bit_depth = png_get_bit_depth(png_ptr, info_ptr); - ppf->info.bits_per_sample = png_bit_depth; - png_color_8p sigbits = nullptr; - png_get_sBIT(png_ptr, info_ptr, &sigbits); - if (colortype & 1) { - // palette will actually be 8-bit regardless of the index bitdepth - ppf->info.bits_per_sample = 8; - } - if (colortype & 2) { - ppf->info.num_color_channels = 3; - ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB; - if (sigbits && sigbits->red == sigbits->green && - sigbits->green == sigbits->blue) { - ppf->info.bits_per_sample = sigbits->red; - } else if (sigbits) { - int maxbps = std::max(sigbits->red, - std::max(sigbits->green, sigbits->blue)); - JXL_WARNING( - "sBIT chunk: bit depths for R, G, and B are not the same (%i " - "%i %i), while in JPEG XL they have to be the same. Setting " - "RGB bit depth to %i.", - sigbits->red, sigbits->green, sigbits->blue, maxbps); - ppf->info.bits_per_sample = maxbps; - } - } else { - ppf->info.num_color_channels = 1; - ppf->color_encoding.color_space = JXL_COLOR_SPACE_GRAY; - if (sigbits) ppf->info.bits_per_sample = sigbits->gray; - } - if (colortype & 4 || - png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { - ppf->info.alpha_bits = ppf->info.bits_per_sample; - if (sigbits && sigbits->alpha != ppf->info.bits_per_sample) { - JXL_WARNING( - "sBIT chunk: bit depths for RGBA are inconsistent " - "(%i %i %i %i). Setting A bitdepth to %i.", - sigbits->red, sigbits->green, sigbits->blue, sigbits->alpha, - ppf->info.bits_per_sample); - } - } else { - ppf->info.alpha_bits = 0; - } - ppf->color_encoding.color_space = - (ppf->info.num_color_channels == 1 ? JXL_COLOR_SPACE_GRAY - : JXL_COLOR_SPACE_RGB); - ppf->info.xsize = w; - ppf->info.ysize = h; - JXL_RETURN_IF_ERROR(VerifyDimensions(constraints, w, h)); - num_channels = - ppf->info.num_color_channels + (ppf->info.alpha_bits ? 1 : 0); - format = { - /*num_channels=*/num_channels, - /*data_type=*/ppf->info.bits_per_sample > 8 ? JXL_TYPE_UINT16 - : JXL_TYPE_UINT8, - /*endianness=*/JXL_BIG_ENDIAN, - /*align=*/0, - }; - if (png_bit_depth > 8 && format.data_type == JXL_TYPE_UINT8) { - png_set_strip_16(png_ptr); - } - bytes_per_pixel = - num_channels * (format.data_type == JXL_TYPE_UINT16 ? 2 : 1); - size_t rowbytes = w * bytes_per_pixel; - if (h > std::numeric_limits::max() / rowbytes) { - return JXL_FAILURE("Image too big."); - } - size_t imagesize = h * rowbytes; - JXL_RETURN_IF_ERROR(frameRaw.Resize(imagesize)); - frameRaw.rows.resize(h); - for (size_t j = 0; j < h; j++) { - frameRaw.rows[j] = - reinterpret_cast(frameRaw.pixels.get()) + - j * rowbytes; - } + if (hasInfo) { + // Copy dimensions. + memcpy(ctx.ihdr.data() + 8, chunk.data() + 12, 8); + if (!ctx.InitPngDecoder(hasInfo, chunksInfo)) { + return false; + } + } + } else if (id == MakeTag('I', 'D', 'A', 'T')) { + // First IDAT chunk means we now have all header info + hasInfo = true; + JXL_CHECK(w == png_get_image_width(ctx.png_ptr, ctx.info_ptr)); + JXL_CHECK(h == png_get_image_height(ctx.png_ptr, ctx.info_ptr)); + int colortype = png_get_color_type(ctx.png_ptr, ctx.info_ptr); + int png_bit_depth = png_get_bit_depth(ctx.png_ptr, ctx.info_ptr); + ppf->info.bits_per_sample = png_bit_depth; + png_color_8p sigbits = nullptr; + png_get_sBIT(ctx.png_ptr, ctx.info_ptr, &sigbits); + if (colortype & 1) { + // palette will actually be 8-bit regardless of the index bitdepth + ppf->info.bits_per_sample = 8; + } + if (colortype & 2) { + ppf->info.num_color_channels = 3; + ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB; + if (sigbits && sigbits->red == sigbits->green && + sigbits->green == sigbits->blue) { + ppf->info.bits_per_sample = sigbits->red; + } else if (sigbits) { + int maxbps = + std::max(sigbits->red, std::max(sigbits->green, sigbits->blue)); + JXL_WARNING( + "sBIT chunk: bit depths for R, G, and B are not the same (%i " + "%i %i), while in JPEG XL they have to be the same. Setting " + "RGB bit depth to %i.", + sigbits->red, sigbits->green, sigbits->blue, maxbps); + ppf->info.bits_per_sample = maxbps; + } + } else { + ppf->info.num_color_channels = 1; + ppf->color_encoding.color_space = JXL_COLOR_SPACE_GRAY; + if (sigbits) ppf->info.bits_per_sample = sigbits->gray; + } + if (colortype & 4 || + png_get_valid(ctx.png_ptr, ctx.info_ptr, PNG_INFO_tRNS)) { + ppf->info.alpha_bits = ppf->info.bits_per_sample; + if (sigbits && sigbits->alpha != ppf->info.bits_per_sample) { + JXL_WARNING( + "sBIT chunk: bit depths for RGBA are inconsistent " + "(%i %i %i %i). Setting A bitdepth to %i.", + sigbits->red, sigbits->green, sigbits->blue, sigbits->alpha, + ppf->info.bits_per_sample); + } + } else { + ppf->info.alpha_bits = 0; + } + ppf->color_encoding.color_space = + (ppf->info.num_color_channels == 1 ? JXL_COLOR_SPACE_GRAY + : JXL_COLOR_SPACE_RGB); + ppf->info.xsize = w; + ppf->info.ysize = h; + JXL_RETURN_IF_ERROR(VerifyDimensions(constraints, w, h)); + num_channels = + ppf->info.num_color_channels + (ppf->info.alpha_bits ? 1 : 0); + format = { + /*num_channels=*/num_channels, + /*data_type=*/ppf->info.bits_per_sample > 8 ? JXL_TYPE_UINT16 + : JXL_TYPE_UINT8, + /*endianness=*/JXL_BIG_ENDIAN, + /*align=*/0, + }; + if (png_bit_depth > 8 && format.data_type == JXL_TYPE_UINT8) { + png_set_strip_16(ctx.png_ptr); + } + bytes_per_pixel = + num_channels * (format.data_type == JXL_TYPE_UINT16 ? 2 : 1); + // TODO(eustas): ensure multiplication is safe + size_t rowbytes = w * bytes_per_pixel; + if (h > std::numeric_limits::max() / rowbytes) { + return JXL_FAILURE("Image too big."); + } + JXL_RETURN_IF_ERROR(ctx.frameRaw.Resize(rowbytes, h)); - if (processing_data(png_ptr, info_ptr, chunk.data(), chunk.size())) { - break; - } - } else if (id == kId_fdAT && isAnimated) { - if (!hasInfo) { - return JXL_FAILURE("fDAT chunk before iDAT"); - } - png_save_uint_32(chunk.data() + 4, chunk.size() - 16); - memcpy(chunk.data() + 8, "IDAT", 4); - if (processing_data(png_ptr, info_ptr, chunk.data() + 4, - chunk.size() - 4)) { - break; - } - } else if (id == kId_cICP) { - // Color profile chunks: cICP has the highest priority, followed by - // iCCP and sRGB (which shouldn't co-exist, but if they do, we use - // iCCP), followed finally by gAMA and cHRM. - if (DecodeCICP(chunk.data() + 8, chunk.size() - 12, - &ppf->color_encoding)) { - have_cicp = true; - have_color = true; - ppf->icc.clear(); - ppf->primary_color_representation = - PackedPixelFile::kColorEncodingIsPrimary; - } - } else if (!have_cicp && id == kId_iCCP) { - if (processing_data(png_ptr, info_ptr, chunk.data(), chunk.size())) { - JXL_WARNING("Corrupt iCCP chunk"); - break; - } + if (!ctx.FeedChunks(chunk)) { + return false; + } + } else if (id == MakeTag('f', 'd', 'A', 'T') && isAnimated) { + if (!hasInfo) { + return JXL_FAILURE("fdAT chunk before IDAT"); + } + /* The 'fdAT' chunk has... the same structure as an 'IDAT' chunk, + * except preceded by a sequence number. */ + size_t payload_size = chunk.size() - 12; + if (payload_size < 4) { + return JXL_FAILURE("Corrupted fdAT chunk"); + } + // Turn 'fdAT' to 'IDAT' by cutting sequence number and replacing tag. + std::array preamble; + png_save_uint_32(preamble.data(), payload_size - 4); + memcpy(preamble.data() + 4, "IDAT", 4); + if (!ctx.FeedChunks(Bytes(preamble), + Bytes(chunk.data() + 12, chunk.size() - 12))) { + return false; + } + } else if (id == MakeTag('c', 'I', 'C', 'P')) { + // Color profile chunks: cICP has the highest priority, followed by + // iCCP and sRGB (which shouldn't co-exist, but if they do, we use + // iCCP), followed finally by gAMA and cHRM. + if (DecodeCicpChunk(Bytes(chunk.data() + 8, chunk.size() - 12), + &ppf->color_encoding)) { + have_cicp = true; + have_color = true; + ppf->icc.clear(); + ppf->primary_color_representation = + PackedPixelFile::kColorEncodingIsPrimary; + } + } else if (!have_cicp && id == MakeTag('i', 'C', 'C', 'P')) { + if (!ctx.FeedChunks(chunk)) { + JXL_WARNING("Corrupt iCCP chunk"); + return false; + } - // TODO(jon): catch special case of PQ and synthesize color encoding - // in that case - int compression_type; - png_bytep profile; - png_charp name; - png_uint_32 proflen = 0; - auto ok = png_get_iCCP(png_ptr, info_ptr, &name, &compression_type, - &profile, &proflen); - if (ok && proflen) { - ppf->icc.assign(profile, profile + proflen); - ppf->primary_color_representation = PackedPixelFile::kIccIsPrimary; - have_color = true; - have_iccp = true; - } else { - // TODO(eustas): JXL_WARNING? - } - } else if (!have_cicp && !have_iccp && id == kId_sRGB) { - JXL_RETURN_IF_ERROR(DecodeSRGB(chunk.data() + 8, chunk.size() - 12, - &ppf->color_encoding)); - have_srgb = true; - have_color = true; - } else if (!have_cicp && !have_srgb && !have_iccp && id == kId_gAMA) { - JXL_RETURN_IF_ERROR(DecodeGAMA(chunk.data() + 8, chunk.size() - 12, - &ppf->color_encoding)); - have_color = true; - } else if (!have_cicp && !have_srgb && !have_iccp && id == kId_cHRM) { - JXL_RETURN_IF_ERROR(DecodeCHRM(chunk.data() + 8, chunk.size() - 12, - &ppf->color_encoding)); - have_color = true; - } else if (id == kId_eXIf) { - ppf->metadata.exif.resize(chunk.size() - 12); - memcpy(ppf->metadata.exif.data(), chunk.data() + 8, - chunk.size() - 12); - } else if (!isAbc(chunk[4]) || !isAbc(chunk[5]) || !isAbc(chunk[6]) || - !isAbc(chunk[7])) { - break; - } else { - if (processing_data(png_ptr, info_ptr, chunk.data(), chunk.size())) { - break; - } - if (!hasInfo) { - chunksInfo.push_back(chunk); - continue; - } - } + // TODO(jon): catch special case of PQ and synthesize color encoding + // in that case + int compression_type; + png_bytep profile; + png_charp name; + png_uint_32 proflen = 0; + auto ok = png_get_iCCP(ctx.png_ptr, ctx.info_ptr, &name, + &compression_type, &profile, &proflen); + if (ok && proflen) { + ppf->icc.assign(profile, profile + proflen); + ppf->primary_color_representation = PackedPixelFile::kIccIsPrimary; + have_color = true; + have_iccp = true; + } else { + // TODO(eustas): JXL_WARNING? + } + } else if (!have_cicp && !have_iccp && id == MakeTag('s', 'R', 'G', 'B')) { + JXL_RETURN_IF_ERROR(DecodeSrgbChunk( + Bytes(chunk.data() + 8, chunk.size() - 12), &ppf->color_encoding)); + have_srgb = true; + have_color = true; + } else if (!have_cicp && !have_srgb && !have_iccp && + id == MakeTag('g', 'A', 'M', 'A')) { + JXL_RETURN_IF_ERROR(DecodeGamaChunk( + Bytes(chunk.data() + 8, chunk.size() - 12), &ppf->color_encoding)); + have_color = true; + } else if (!have_cicp && !have_srgb && !have_iccp && + id == MakeTag('c', 'H', 'R', 'M')) { + JXL_RETURN_IF_ERROR(DecodeChrmChunk( + Bytes(chunk.data() + 8, chunk.size() - 12), &ppf->color_encoding)); + have_color = true; + } else if (id == MakeTag('e', 'X', 'I', 'f')) { + ppf->metadata.exif.resize(chunk.size() - 12); + memcpy(ppf->metadata.exif.data(), chunk.data() + 8, chunk.size() - 12); + } else if (!isAbc(chunk[4]) || !isAbc(chunk[5]) || !isAbc(chunk[6]) || + !isAbc(chunk[7])) { + return false; + } else { + if (!ctx.FeedChunks(chunk)) { + return false; + } + if (!hasInfo) { + chunksInfo.push_back(chunk); + continue; } } - - JXL_RETURN_IF_ERROR(ApplyColorHints( - color_hints, have_color, ppf->info.num_color_channels == 1, ppf)); } - if (errorstate) return false; + JXL_RETURN_IF_ERROR(ApplyColorHints(color_hints, have_color, + ppf->info.num_color_channels == 1, ppf)); bool has_nontrivial_background = false; bool previous_frame_should_be_cleared = false; @@ -1014,14 +1052,14 @@ Status DecodeImageAPNG(const Span bytes, previous_frame_should_be_cleared = has_nontrivial_background && frame.dispose_op == DISPOSE_OP_BACKGROUND; } + if (ppf->frames.empty()) return JXL_FAILURE("No frames decoded"); ppf->frames.back().frame_info.is_last = JXL_TRUE; return true; -#else - return false; -#endif } +#endif // JPEGXL_ENABLE_APNG + } // namespace extras } // namespace jxl diff --git a/third_party/jpeg-xl/lib/extras/dec/apng.h b/third_party/jpeg-xl/lib/extras/dec/apng.h index d91364b1e6..7ebc2ee7c8 100644 --- a/third_party/jpeg-xl/lib/extras/dec/apng.h +++ b/third_party/jpeg-xl/lib/extras/dec/apng.h @@ -8,11 +8,10 @@ // Decodes APNG images in memory. -#include +#include #include "lib/extras/dec/color_hints.h" #include "lib/extras/packed_image.h" -#include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" diff --git a/third_party/jpeg-xl/lib/extras/dec/color_description.cc b/third_party/jpeg-xl/lib/extras/dec/color_description.cc index bf229632d0..87fff6e54a 100644 --- a/third_party/jpeg-xl/lib/extras/dec/color_description.cc +++ b/third_party/jpeg-xl/lib/extras/dec/color_description.cc @@ -203,12 +203,38 @@ Status ParseTransferFunction(Tokenizer* tokenizer, JxlColorEncoding* c) { Status ParseDescription(const std::string& description, JxlColorEncoding* c) { *c = {}; - Tokenizer tokenizer(&description, '_'); - JXL_RETURN_IF_ERROR(ParseColorSpace(&tokenizer, c)); - JXL_RETURN_IF_ERROR(ParseWhitePoint(&tokenizer, c)); - JXL_RETURN_IF_ERROR(ParsePrimaries(&tokenizer, c)); - JXL_RETURN_IF_ERROR(ParseRenderingIntent(&tokenizer, c)); - JXL_RETURN_IF_ERROR(ParseTransferFunction(&tokenizer, c)); + if (description == "sRGB") { + c->color_space = JXL_COLOR_SPACE_RGB; + c->white_point = JXL_WHITE_POINT_D65; + c->primaries = JXL_PRIMARIES_SRGB; + c->transfer_function = JXL_TRANSFER_FUNCTION_SRGB; + c->rendering_intent = JXL_RENDERING_INTENT_PERCEPTUAL; + } else if (description == "DisplayP3") { + c->color_space = JXL_COLOR_SPACE_RGB; + c->white_point = JXL_WHITE_POINT_D65; + c->primaries = JXL_PRIMARIES_P3; + c->transfer_function = JXL_TRANSFER_FUNCTION_SRGB; + c->rendering_intent = JXL_RENDERING_INTENT_PERCEPTUAL; + } else if (description == "Rec2100PQ") { + c->color_space = JXL_COLOR_SPACE_RGB; + c->white_point = JXL_WHITE_POINT_D65; + c->primaries = JXL_PRIMARIES_2100; + c->transfer_function = JXL_TRANSFER_FUNCTION_PQ; + c->rendering_intent = JXL_RENDERING_INTENT_RELATIVE; + } else if (description == "Rec2100HLG") { + c->color_space = JXL_COLOR_SPACE_RGB; + c->white_point = JXL_WHITE_POINT_D65; + c->primaries = JXL_PRIMARIES_2100; + c->transfer_function = JXL_TRANSFER_FUNCTION_HLG; + c->rendering_intent = JXL_RENDERING_INTENT_RELATIVE; + } else { + Tokenizer tokenizer(&description, '_'); + JXL_RETURN_IF_ERROR(ParseColorSpace(&tokenizer, c)); + JXL_RETURN_IF_ERROR(ParseWhitePoint(&tokenizer, c)); + JXL_RETURN_IF_ERROR(ParsePrimaries(&tokenizer, c)); + JXL_RETURN_IF_ERROR(ParseRenderingIntent(&tokenizer, c)); + JXL_RETURN_IF_ERROR(ParseTransferFunction(&tokenizer, c)); + } return true; } diff --git a/third_party/jpeg-xl/lib/extras/dec/decode.cc b/third_party/jpeg-xl/lib/extras/dec/decode.cc index 3546cb65c0..2581d53f63 100644 --- a/third_party/jpeg-xl/lib/extras/dec/decode.cc +++ b/third_party/jpeg-xl/lib/extras/dec/decode.cc @@ -91,6 +91,15 @@ bool CanDecode(Codec codec) { } } +std::string ListOfDecodeCodecs() { + std::string list_of_codecs("JXL, PPM, PNM, PFM, PAM, PGX"); + if (CanDecode(Codec::kPNG)) list_of_codecs.append(", PNG, APNG"); + if (CanDecode(Codec::kGIF)) list_of_codecs.append(", GIF"); + if (CanDecode(Codec::kJPG)) list_of_codecs.append(", JPEG"); + if (CanDecode(Codec::kEXR)) list_of_codecs.append(", EXR"); + return list_of_codecs; +} + Status DecodeBytes(const Span bytes, const ColorHints& color_hints, extras::PackedPixelFile* ppf, const SizeConstraints* constraints, Codec* orig_codec) { diff --git a/third_party/jpeg-xl/lib/extras/dec/decode.h b/third_party/jpeg-xl/lib/extras/dec/decode.h index 1a90f4c6a3..26dc1409df 100644 --- a/third_party/jpeg-xl/lib/extras/dec/decode.h +++ b/third_party/jpeg-xl/lib/extras/dec/decode.h @@ -38,6 +38,8 @@ enum class Codec : uint32_t { bool CanDecode(Codec codec); +std::string ListOfDecodeCodecs(); + // If and only if extension is ".pfm", *bits_per_sample is updated to 32 so // that Encode() would encode to PFM instead of PPM. Codec CodecFromPath(const std::string& path, diff --git a/third_party/jpeg-xl/lib/extras/dec/gif.cc b/third_party/jpeg-xl/lib/extras/dec/gif.cc index 3f89d460b8..243d8b5103 100644 --- a/third_party/jpeg-xl/lib/extras/dec/gif.cc +++ b/third_party/jpeg-xl/lib/extras/dec/gif.cc @@ -17,6 +17,7 @@ #include "lib/extras/size_constraints.h" #include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/rect.h" #include "lib/jxl/sanitizers.h" namespace jxl { diff --git a/third_party/jpeg-xl/lib/extras/dec/jpg.cc b/third_party/jpeg-xl/lib/extras/dec/jpg.cc index 4a3e0d3b21..a65b46b4c8 100644 --- a/third_party/jpeg-xl/lib/extras/dec/jpg.cc +++ b/third_party/jpeg-xl/lib/extras/dec/jpg.cc @@ -6,8 +6,7 @@ #include "lib/extras/dec/jpg.h" #if JPEGXL_ENABLE_JPEG -#include -#include +#include "lib/jxl/base/include_jpeglib.h" // NOLINT #endif #include diff --git a/third_party/jpeg-xl/lib/extras/dec/jxl.cc b/third_party/jpeg-xl/lib/extras/dec/jxl.cc index 5b7fa03f02..e2534fa745 100644 --- a/third_party/jpeg-xl/lib/extras/dec/jxl.cc +++ b/third_party/jpeg-xl/lib/extras/dec/jxl.cc @@ -10,7 +10,7 @@ #include #include -#include +#include // PRIu32 #include "lib/extras/common.h" #include "lib/extras/dec/color_description.h" @@ -211,7 +211,7 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size, return false; } uint32_t progression_index = 0; - bool codestream_done = accepted_formats.empty(); + bool codestream_done = jpeg_bytes == nullptr && accepted_formats.empty(); BoxProcessor boxes(dec); for (;;) { JxlDecoderStatus status = JxlDecoderProcessInput(dec); diff --git a/third_party/jpeg-xl/lib/extras/dec/pnm.cc b/third_party/jpeg-xl/lib/extras/dec/pnm.cc index e64d7e95f9..b3f9cd1206 100644 --- a/third_party/jpeg-xl/lib/extras/dec/pnm.cc +++ b/third_party/jpeg-xl/lib/extras/dec/pnm.cc @@ -5,17 +5,17 @@ #include "lib/extras/dec/pnm.h" -#include -#include +#include #include +#include #include -#include +#include +#include -#include "jxl/encode.h" #include "lib/extras/size_constraints.h" #include "lib/jxl/base/bits.h" -#include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" namespace jxl { diff --git a/third_party/jpeg-xl/lib/extras/enc/encode.cc b/third_party/jpeg-xl/lib/extras/enc/encode.cc index c5e22d8c7e..71be78e36c 100644 --- a/third_party/jpeg-xl/lib/extras/enc/encode.cc +++ b/third_party/jpeg-xl/lib/extras/enc/encode.cc @@ -134,5 +134,13 @@ std::unique_ptr Encoder::FromExtension(std::string extension) { return nullptr; } +std::string ListOfEncodeCodecs() { + std::string list_of_codecs("PPM, PNM, PFM, PAM, PGX"); + if (GetAPNGEncoder()) list_of_codecs.append(", PNG, APNG"); + if (GetJPEGEncoder()) list_of_codecs.append(", JPEG"); + if (GetEXREncoder()) list_of_codecs.append(", EXR"); + return list_of_codecs; +} + } // 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 index 2502d9976b..a71f3b220f 100644 --- a/third_party/jpeg-xl/lib/extras/enc/encode.h +++ b/third_party/jpeg-xl/lib/extras/enc/encode.h @@ -82,6 +82,8 @@ class Encoder { std::unordered_map options_; }; +std::string ListOfEncodeCodecs(); + } // namespace extras } // namespace jxl diff --git a/third_party/jpeg-xl/lib/extras/enc/jpegli.cc b/third_party/jpeg-xl/lib/extras/enc/jpegli.cc index cb473a1290..9735cd8cb9 100644 --- a/third_party/jpeg-xl/lib/extras/enc/jpegli.cc +++ b/third_party/jpeg-xl/lib/extras/enc/jpegli.cc @@ -454,6 +454,10 @@ Status EncodeJpeg(const PackedPixelFile& ppf, const JpegSettings& jpeg_settings, cinfo.comp_info[i].h_samp_factor = 1; cinfo.comp_info[i].v_samp_factor = 1; } + } else if (!jpeg_settings.xyb) { + // Default is no chroma subsampling. + cinfo.comp_info[0].h_samp_factor = 1; + cinfo.comp_info[0].v_samp_factor = 1; } jpegli_enable_adaptive_quantization( &cinfo, TO_JXL_BOOL(jpeg_settings.use_adaptive_quantization)); diff --git a/third_party/jpeg-xl/lib/extras/enc/jpg.cc b/third_party/jpeg-xl/lib/extras/enc/jpg.cc index 0095ac9294..a2ef4a9fc4 100644 --- a/third_party/jpeg-xl/lib/extras/enc/jpg.cc +++ b/third_party/jpeg-xl/lib/extras/enc/jpg.cc @@ -6,18 +6,15 @@ #include "lib/extras/enc/jpg.h" #if JPEGXL_ENABLE_JPEG -#include -#include +#include "lib/jxl/base/include_jpeglib.h" // NOLINT #endif -#include #include #include #include +#include #include -#include #include -#include #include #include #include diff --git a/third_party/jpeg-xl/lib/extras/jpegli_test.cc b/third_party/jpeg-xl/lib/extras/jpegli_test.cc index 3049049a64..96b546755c 100644 --- a/third_party/jpeg-xl/lib/extras/jpegli_test.cc +++ b/third_party/jpeg-xl/lib/extras/jpegli_test.cc @@ -255,7 +255,7 @@ TEST(JpegliTest, JpegliHDRRoundtripTest) { std::string testimage = "jxl/hdr_room.png"; PackedPixelFile ppf_in; ASSERT_TRUE(ReadTestImage(testimage, &ppf_in)); - EXPECT_EQ("RGB_D65_202_Rel_HLG", Description(ppf_in.color_encoding)); + EXPECT_EQ("Rec2100HLG", Description(ppf_in.color_encoding)); EXPECT_EQ(16, ppf_in.info.bits_per_sample); std::vector compressed; diff --git a/third_party/jpeg-xl/lib/extras/metrics.cc b/third_party/jpeg-xl/lib/extras/metrics.cc index 4259d3c375..f70ab0a61d 100644 --- a/third_party/jpeg-xl/lib/extras/metrics.cc +++ b/third_party/jpeg-xl/lib/extras/metrics.cc @@ -16,6 +16,7 @@ #include #include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/rect.h" #include "lib/jxl/base/status.h" #include "lib/jxl/color_encoding_internal.h" HWY_BEFORE_NAMESPACE(); diff --git a/third_party/jpeg-xl/lib/extras/packed_image_convert.cc b/third_party/jpeg-xl/lib/extras/packed_image_convert.cc index 2ad001bf09..7e4b592fc4 100644 --- a/third_party/jpeg-xl/lib/extras/packed_image_convert.cc +++ b/third_party/jpeg-xl/lib/extras/packed_image_convert.cc @@ -11,6 +11,7 @@ #include +#include "lib/jxl/base/rect.h" #include "lib/jxl/base/status.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/dec_external_image.h" diff --git a/third_party/jpeg-xl/lib/include/jxl/color_encoding.h b/third_party/jpeg-xl/lib/include/jxl/color_encoding.h index e6325dcb30..f5de188223 100644 --- a/third_party/jpeg-xl/lib/include/jxl/color_encoding.h +++ b/third_party/jpeg-xl/lib/include/jxl/color_encoding.h @@ -14,8 +14,6 @@ #ifndef JXL_COLOR_ENCODING_H_ #define JXL_COLOR_ENCODING_H_ -#include - #if defined(__cplusplus) || defined(c_plusplus) extern "C" { #endif diff --git a/third_party/jpeg-xl/lib/jpegli/color_transform.cc b/third_party/jpeg-xl/lib/jpegli/color_transform.cc index 60a0dc83bb..ec906bedce 100644 --- a/third_party/jpeg-xl/lib/jpegli/color_transform.cc +++ b/third_party/jpeg-xl/lib/jpegli/color_transform.cc @@ -26,11 +26,16 @@ using hwy::HWY_NAMESPACE::Mul; using hwy::HWY_NAMESPACE::MulAdd; using hwy::HWY_NAMESPACE::Sub; -void YCbCrToRGB(float* row[kMaxComponents], size_t xsize) { +template +void YCbCrToExtRGB(float* row[kMaxComponents], size_t xsize) { const HWY_CAPPED(float, 8) df; - float* JXL_RESTRICT row0 = row[0]; - float* JXL_RESTRICT row1 = row[1]; - float* JXL_RESTRICT row2 = row[2]; + const float* row_y = row[0]; + const float* row_cb = row[1]; + const float* row_cr = row[2]; + float* row_r = row[kRed]; + float* row_g = row[kGreen]; + float* row_b = row[kBlue]; + float* row_a = row[kAlpha]; // Full-range BT.601 as defined by JFIF Clause 7: // https://www.itu.int/rec/T-REC-T.871-201105-I/en @@ -38,20 +43,48 @@ void YCbCrToRGB(float* row[kMaxComponents], size_t xsize) { const auto cgcb = Set(df, -0.114f * 1.772f / 0.587f); const auto cgcr = Set(df, -0.299f * 1.402f / 0.587f); const auto cbcb = Set(df, 1.772f); + const auto alpha_opaque = Set(df, 127.0f / 255.0f); for (size_t x = 0; x < xsize; x += Lanes(df)) { - const auto y_vec = Load(df, row0 + x); - const auto cb_vec = Load(df, row1 + x); - const auto cr_vec = Load(df, row2 + x); + const auto y_vec = Load(df, row_y + x); + const auto cb_vec = Load(df, row_cb + x); + const auto cr_vec = Load(df, row_cr + x); const auto r_vec = MulAdd(crcr, cr_vec, y_vec); const auto g_vec = MulAdd(cgcr, cr_vec, MulAdd(cgcb, cb_vec, y_vec)); const auto b_vec = MulAdd(cbcb, cb_vec, y_vec); - Store(r_vec, df, row0 + x); - Store(g_vec, df, row1 + x); - Store(b_vec, df, row2 + x); + Store(r_vec, df, row_r + x); + Store(g_vec, df, row_g + x); + Store(b_vec, df, row_b + x); + if (kAlpha >= 0) { + Store(alpha_opaque, df, row_a + x); + } } } +void YCbCrToRGB(float* row[kMaxComponents], size_t xsize) { + YCbCrToExtRGB<0, 1, 2, -1>(row, xsize); +} + +void YCbCrToBGR(float* row[kMaxComponents], size_t xsize) { + YCbCrToExtRGB<2, 1, 0, -1>(row, xsize); +} + +void YCbCrToRGBA(float* row[kMaxComponents], size_t xsize) { + YCbCrToExtRGB<0, 1, 2, 3>(row, xsize); +} + +void YCbCrToBGRA(float* row[kMaxComponents], size_t xsize) { + YCbCrToExtRGB<2, 1, 0, 3>(row, xsize); +} + +void YCbCrToARGB(float* row[kMaxComponents], size_t xsize) { + YCbCrToExtRGB<1, 2, 3, 0>(row, xsize); +} + +void YCbCrToABGR(float* row[kMaxComponents], size_t xsize) { + YCbCrToExtRGB<3, 2, 1, 0>(row, xsize); +} + void YCCKToCMYK(float* row[kMaxComponents], size_t xsize) { const HWY_CAPPED(float, 8) df; float* JXL_RESTRICT row0 = row[0]; @@ -66,11 +99,15 @@ void YCCKToCMYK(float* row[kMaxComponents], size_t xsize) { } } -void RGBToYCbCr(float* row[kMaxComponents], size_t xsize) { +template +void ExtRGBToYCbCr(float* row[kMaxComponents], size_t xsize) { const HWY_CAPPED(float, 8) df; - float* JXL_RESTRICT row0 = row[0]; - float* JXL_RESTRICT row1 = row[1]; - float* JXL_RESTRICT row2 = row[2]; + const float* row_r = row[kRed]; + const float* row_g = row[kGreen]; + const float* row_b = row[kBlue]; + float* row_y = row[0]; + float* row_cb = row[1]; + float* row_cr = row[2]; // Full-range BT.601 as defined by JFIF Clause 7: // https://www.itu.int/rec/T-REC-T.871-201105-I/en const auto c128 = Set(df, 128.0f); @@ -85,9 +122,9 @@ void RGBToYCbCr(float* row[kMaxComponents], size_t xsize) { const auto kNormB = Div(Set(df, 1.0f), (Add(kR, Add(kG, kAmpB)))); for (size_t x = 0; x < xsize; x += Lanes(df)) { - const auto r = Load(df, row0 + x); - const auto g = Load(df, row1 + x); - const auto b = Load(df, row2 + x); + const auto r = Load(df, row_r + x); + const auto g = Load(df, row_g + x); + const auto b = Load(df, row_b + x); const auto r_base = Mul(r, kR); const auto r_diff = Mul(r, kDiffR); const auto g_base = Mul(g, kG); @@ -96,12 +133,28 @@ void RGBToYCbCr(float* row[kMaxComponents], size_t xsize) { const auto y_base = Add(r_base, Add(g_base, b_base)); const auto cb_vec = MulAdd(Sub(b_diff, y_base), kNormB, c128); const auto cr_vec = MulAdd(Sub(r_diff, y_base), kNormR, c128); - Store(y_base, df, row0 + x); - Store(cb_vec, df, row1 + x); - Store(cr_vec, df, row2 + x); + Store(y_base, df, row_y + x); + Store(cb_vec, df, row_cb + x); + Store(cr_vec, df, row_cr + x); } } +void RGBToYCbCr(float* row[kMaxComponents], size_t xsize) { + ExtRGBToYCbCr<0, 1, 2>(row, xsize); +} + +void BGRToYCbCr(float* row[kMaxComponents], size_t xsize) { + ExtRGBToYCbCr<2, 1, 0>(row, xsize); +} + +void ARGBToYCbCr(float* row[kMaxComponents], size_t xsize) { + ExtRGBToYCbCr<1, 2, 3>(row, xsize); +} + +void ABGRToYCbCr(float* row[kMaxComponents], size_t xsize) { + ExtRGBToYCbCr<3, 2, 1>(row, xsize); +} + void CMYKToYCCK(float* row[kMaxComponents], size_t xsize) { const HWY_CAPPED(float, 8) df; float* JXL_RESTRICT row0 = row[0]; @@ -127,7 +180,15 @@ namespace jpegli { HWY_EXPORT(CMYKToYCCK); HWY_EXPORT(YCCKToCMYK); HWY_EXPORT(YCbCrToRGB); +HWY_EXPORT(YCbCrToBGR); +HWY_EXPORT(YCbCrToRGBA); +HWY_EXPORT(YCbCrToBGRA); +HWY_EXPORT(YCbCrToARGB); +HWY_EXPORT(YCbCrToABGR); HWY_EXPORT(RGBToYCbCr); +HWY_EXPORT(BGRToYCbCr); +HWY_EXPORT(ARGBToYCbCr); +HWY_EXPORT(ABGRToYCbCr); bool CheckColorSpaceComponents(int num_components, J_COLOR_SPACE colorspace) { switch (colorspace) { @@ -164,16 +225,73 @@ bool CheckColorSpaceComponents(int num_components, J_COLOR_SPACE colorspace) { void NullTransform(float* row[kMaxComponents], size_t len) {} +void FillAlpha(float* row, size_t len) { + static const float kAlpha = 127.0f / 255.0f; + for (size_t i = 0; i < len; ++i) { + row[i] = kAlpha; + } +} + +// Works for BGR as well. void GrayscaleToRGB(float* row[kMaxComponents], size_t len) { memcpy(row[1], row[0], len * sizeof(row[1][0])); memcpy(row[2], row[0], len * sizeof(row[2][0])); } +// Works for BGRA as well. +void GrayscaleToRGBA(float* row[kMaxComponents], size_t len) { + memcpy(row[1], row[0], len * sizeof(row[1][0])); + memcpy(row[2], row[0], len * sizeof(row[2][0])); + FillAlpha(row[3], len); +} + +// Works for ABGR as well. +void GrayscaleToARGB(float* row[kMaxComponents], size_t len) { + memcpy(row[1], row[0], len * sizeof(row[1][0])); + memcpy(row[2], row[0], len * sizeof(row[2][0])); + memcpy(row[3], row[0], len * sizeof(row[1][0])); + FillAlpha(row[0], len); +} + void GrayscaleToYCbCr(float* row[kMaxComponents], size_t len) { memset(row[1], 0, len * sizeof(row[1][0])); memset(row[2], 0, len * sizeof(row[2][0])); } +void RGBToBGR(float* row[kMaxComponents], size_t len) { + for (size_t i = 0; i < len; ++i) { + std::swap(row[0][i], row[2][i]); + } +} + +void RGBToRGBA(float* row[kMaxComponents], size_t len) { + FillAlpha(row[3], len); +} + +void RGBToBGRA(float* row[kMaxComponents], size_t len) { + static const float kAlpha = 127.0f / 255.0f; + for (size_t i = 0; i < len; ++i) { + std::swap(row[0][i], row[2][i]); + row[3][i] = kAlpha; + } +} + +void RGBToARGB(float* row[kMaxComponents], size_t len) { + memcpy(row[3], row[2], len * sizeof(row[1][0])); + memcpy(row[2], row[1], len * sizeof(row[2][0])); + memcpy(row[1], row[0], len * sizeof(row[1][0])); + FillAlpha(row[0], len); +} + +void RGBToABGR(float* row[kMaxComponents], size_t len) { + static const float kAlpha = 127.0f / 255.0f; + for (size_t i = 0; i < len; ++i) { + std::swap(row[1][i], row[2][i]); + row[3][i] = row[0][i]; + row[0][i] = kAlpha; + } +} + void ChooseColorTransform(j_compress_ptr cinfo) { jpeg_comp_master* m = cinfo->master; if (!CheckColorSpaceComponents(cinfo->input_components, @@ -226,6 +344,43 @@ void ChooseColorTransform(j_compress_ptr cinfo) { } } + if (cinfo->jpeg_color_space == JCS_GRAYSCALE || + cinfo->jpeg_color_space == JCS_YCbCr) { + switch (cinfo->in_color_space) { +#ifdef JCS_EXTENSIONS + case JCS_EXT_RGB: + case JCS_EXT_RGBX: + m->color_transform = HWY_DYNAMIC_DISPATCH(RGBToYCbCr); + break; + case JCS_EXT_BGR: + case JCS_EXT_BGRX: + m->color_transform = HWY_DYNAMIC_DISPATCH(BGRToYCbCr); + break; + case JCS_EXT_XRGB: + m->color_transform = HWY_DYNAMIC_DISPATCH(ARGBToYCbCr); + break; + case JCS_EXT_XBGR: + m->color_transform = HWY_DYNAMIC_DISPATCH(ABGRToYCbCr); + break; +#endif +#ifdef JCS_ALPHA_EXTENSIONS + case JCS_EXT_RGBA: + m->color_transform = HWY_DYNAMIC_DISPATCH(RGBToYCbCr); + break; + case JCS_EXT_BGRA: + m->color_transform = HWY_DYNAMIC_DISPATCH(BGRToYCbCr); + break; + case JCS_EXT_ARGB: + m->color_transform = HWY_DYNAMIC_DISPATCH(ARGBToYCbCr); + break; + case JCS_EXT_ABGR: + m->color_transform = HWY_DYNAMIC_DISPATCH(ABGRToYCbCr); + break; +#endif + default:; // Nothing to do. + } + } + if (m->color_transform == nullptr) { // TODO(szabadka) Support more color transforms. JPEGLI_ERROR("Unsupported color transform %d -> %d", cinfo->in_color_space, @@ -257,18 +412,123 @@ void ChooseColorTransform(j_decompress_ptr cinfo) { m->color_transform = nullptr; if (cinfo->jpeg_color_space == JCS_GRAYSCALE) { - if (cinfo->out_color_space == JCS_RGB) { - m->color_transform = GrayscaleToRGB; + switch (cinfo->out_color_space) { + case JCS_RGB: + m->color_transform = GrayscaleToRGB; + break; +#ifdef JCS_EXTENSIONS + case JCS_EXT_RGB: + case JCS_EXT_BGR: + m->color_transform = GrayscaleToRGB; + break; + case JCS_EXT_RGBX: + case JCS_EXT_BGRX: + m->color_transform = GrayscaleToRGBA; + break; + case JCS_EXT_XRGB: + case JCS_EXT_XBGR: + m->color_transform = GrayscaleToARGB; + break; +#endif +#ifdef JCS_ALPHA_EXTENSIONS + case JCS_EXT_RGBA: + case JCS_EXT_BGRA: + m->color_transform = GrayscaleToRGBA; + break; + case JCS_EXT_ARGB: + case JCS_EXT_ABGR: + m->color_transform = GrayscaleToARGB; + break; +#endif + default: + m->color_transform = nullptr; } } else if (cinfo->jpeg_color_space == JCS_RGB) { - if (cinfo->out_color_space == JCS_GRAYSCALE) { - m->color_transform = HWY_DYNAMIC_DISPATCH(RGBToYCbCr); + switch (cinfo->out_color_space) { + case JCS_GRAYSCALE: + m->color_transform = HWY_DYNAMIC_DISPATCH(RGBToYCbCr); + break; +#ifdef JCS_EXTENSIONS + case JCS_EXT_RGB: + m->color_transform = NullTransform; + break; + case JCS_EXT_BGR: + m->color_transform = RGBToBGR; + break; + case JCS_EXT_RGBX: + m->color_transform = RGBToRGBA; + break; + case JCS_EXT_BGRX: + m->color_transform = RGBToBGRA; + break; + case JCS_EXT_XRGB: + m->color_transform = RGBToARGB; + break; + case JCS_EXT_XBGR: + m->color_transform = RGBToABGR; + break; +#endif +#ifdef JCS_ALPHA_EXTENSIONS + case JCS_EXT_RGBA: + m->color_transform = RGBToRGBA; + break; + case JCS_EXT_BGRA: + m->color_transform = RGBToBGRA; + break; + case JCS_EXT_ARGB: + m->color_transform = RGBToARGB; + break; + case JCS_EXT_ABGR: + m->color_transform = RGBToABGR; + break; +#endif + default: + m->color_transform = nullptr; } } else if (cinfo->jpeg_color_space == JCS_YCbCr) { - if (cinfo->out_color_space == JCS_RGB) { - m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToRGB); - } else if (cinfo->out_color_space == JCS_GRAYSCALE) { - m->color_transform = NullTransform; + switch (cinfo->out_color_space) { + case JCS_GRAYSCALE: + m->color_transform = NullTransform; + break; + case JCS_RGB: + m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToRGB); + break; +#ifdef JCS_EXTENSIONS + case JCS_EXT_RGB: + m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToRGB); + break; + case JCS_EXT_BGR: + m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToBGR); + break; + case JCS_EXT_RGBX: + m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToRGBA); + break; + case JCS_EXT_BGRX: + m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToBGRA); + break; + case JCS_EXT_XRGB: + m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToARGB); + break; + case JCS_EXT_XBGR: + m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToABGR); + break; +#endif +#ifdef JCS_ALPHA_EXTENSIONS + case JCS_EXT_RGBA: + m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToRGBA); + break; + case JCS_EXT_BGRA: + m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToBGRA); + break; + case JCS_EXT_ARGB: + m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToARGB); + break; + case JCS_EXT_ABGR: + m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToABGR); + break; +#endif + default: + m->color_transform = nullptr; } } else if (cinfo->jpeg_color_space == JCS_YCCK) { if (cinfo->out_color_space == JCS_CMYK) { diff --git a/third_party/jpeg-xl/lib/jpegli/common.h b/third_party/jpeg-xl/lib/jpegli/common.h index 42487f2b89..514483afef 100644 --- a/third_party/jpeg-xl/lib/jpegli/common.h +++ b/third_party/jpeg-xl/lib/jpegli/common.h @@ -20,12 +20,7 @@ #ifndef LIB_JPEGLI_COMMON_H_ #define LIB_JPEGLI_COMMON_H_ -/* clang-format off */ -#include -#include -/* clang-format on */ - -#include "lib/jpegli/types.h" +#include "lib/jxl/base/include_jpeglib.h" // NOLINT #if defined(__cplusplus) || defined(c_plusplus) extern "C" { diff --git a/third_party/jpeg-xl/lib/jpegli/decode.cc b/third_party/jpeg-xl/lib/jpegli/decode.cc index 9fdf68dd18..d967b787d3 100644 --- a/third_party/jpeg-xl/lib/jpegli/decode.cc +++ b/third_party/jpeg-xl/lib/jpegli/decode.cc @@ -54,6 +54,7 @@ void InitializeImage(j_decompress_ptr cinfo) { m->found_soi_ = false; m->found_dri_ = false; m->found_sof_ = false; + m->found_sos_ = false; m->found_eoi_ = false; m->icc_index_ = 0; m->icc_total_ = 0; @@ -243,10 +244,14 @@ void PrepareForScan(j_decompress_ptr cinfo) { // Copy quantization tables into comp_info. for (int i = 0; i < cinfo->comps_in_scan; ++i) { jpeg_component_info* comp = cinfo->cur_comp_info[i]; + int quant_tbl_idx = comp->quant_tbl_no; + JQUANT_TBL* quant_table = cinfo->quant_tbl_ptrs[quant_tbl_idx]; + if (!quant_table) { + JPEGLI_ERROR("Quantization table with index %d not found", quant_tbl_idx); + } if (comp->quant_table == nullptr) { comp->quant_table = Allocate(cinfo, 1, JPOOL_IMAGE); - memcpy(comp->quant_table, cinfo->quant_tbl_ptrs[comp->quant_tbl_no], - sizeof(JQUANT_TBL)); + memcpy(comp->quant_table, quant_table, sizeof(JQUANT_TBL)); } } if (cinfo->comps_in_scan == 1) { @@ -723,16 +728,36 @@ void jpegli_calc_output_dimensions(j_decompress_ptr cinfo) { } } } - if (cinfo->out_color_space == JCS_GRAYSCALE) { - cinfo->out_color_components = 1; - } else if (cinfo->out_color_space == JCS_RGB || - cinfo->out_color_space == JCS_YCbCr) { - cinfo->out_color_components = 3; - } else if (cinfo->out_color_space == JCS_CMYK || - cinfo->out_color_space == JCS_YCCK) { - cinfo->out_color_components = 4; - } else { - cinfo->out_color_components = cinfo->num_components; + switch (cinfo->out_color_space) { + case JCS_GRAYSCALE: + cinfo->out_color_components = 1; + break; + case JCS_RGB: + case JCS_YCbCr: +#ifdef JCS_EXTENSIONS + case JCS_EXT_RGB: + case JCS_EXT_BGR: +#endif + cinfo->out_color_components = 3; + break; + case JCS_CMYK: + case JCS_YCCK: +#ifdef JCS_EXTENSIONS + case JCS_EXT_RGBX: + case JCS_EXT_BGRX: + case JCS_EXT_XBGR: + case JCS_EXT_XRGB: +#endif +#ifdef JCS_ALPHA_EXTENSIONS + case JCS_EXT_RGBA: + case JCS_EXT_BGRA: + case JCS_EXT_ABGR: + case JCS_EXT_ARGB: +#endif + cinfo->out_color_components = 4; + break; + default: + cinfo->out_color_components = cinfo->num_components; } cinfo->output_components = cinfo->quantize_colors ? 1 : cinfo->out_color_components; diff --git a/third_party/jpeg-xl/lib/jpegli/decode.h b/third_party/jpeg-xl/lib/jpegli/decode.h index f5b099eda3..668d630586 100644 --- a/third_party/jpeg-xl/lib/jpegli/decode.h +++ b/third_party/jpeg-xl/lib/jpegli/decode.h @@ -21,6 +21,7 @@ #define LIB_JPEGLI_DECODE_H_ #include "lib/jpegli/common.h" +#include "lib/jpegli/types.h" #if defined(__cplusplus) || defined(c_plusplus) extern "C" { diff --git a/third_party/jpeg-xl/lib/jpegli/decode_api_test.cc b/third_party/jpeg-xl/lib/jpegli/decode_api_test.cc index 3ecd479951..c429f0f810 100644 --- a/third_party/jpeg-xl/lib/jpegli/decode_api_test.cc +++ b/third_party/jpeg-xl/lib/jpegli/decode_api_test.cc @@ -3,17 +3,27 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +#include + +#include #include #include +#include +#include +#include +#include +#include +#include #include #include "lib/jpegli/decode.h" #include "lib/jpegli/encode.h" +#include "lib/jpegli/libjpeg_test_util.h" +#include "lib/jpegli/test_params.h" #include "lib/jpegli/test_utils.h" #include "lib/jpegli/testing.h" -#include "lib/jxl/base/byte_order.h" +#include "lib/jpegli/types.h" #include "lib/jxl/base/status.h" -#include "lib/jxl/sanitizers.h" namespace jpegli { namespace { @@ -894,7 +904,9 @@ std::vector GenerateTests(bool buffered) { all_tests.push_back(config); } // Tests for color transforms. - for (J_COLOR_SPACE out_color_space : {JCS_RGB, JCS_GRAYSCALE}) { + for (J_COLOR_SPACE out_color_space : + {JCS_RGB, JCS_GRAYSCALE, JCS_EXT_RGB, JCS_EXT_BGR, JCS_EXT_RGBA, + JCS_EXT_BGRA, JCS_EXT_ARGB, JCS_EXT_ABGR}) { TestConfig config; config.input.xsize = config.input.ysize = 256; config.input.color_space = JCS_GRAYSCALE; @@ -903,7 +915,9 @@ std::vector GenerateTests(bool buffered) { all_tests.push_back(config); } for (J_COLOR_SPACE jpeg_color_space : {JCS_RGB, JCS_YCbCr}) { - for (J_COLOR_SPACE out_color_space : {JCS_RGB, JCS_YCbCr, JCS_GRAYSCALE}) { + for (J_COLOR_SPACE out_color_space : + {JCS_RGB, JCS_YCbCr, JCS_GRAYSCALE, JCS_EXT_RGB, JCS_EXT_BGR, + JCS_EXT_RGBA, JCS_EXT_BGRA, JCS_EXT_ARGB, JCS_EXT_ABGR}) { if (jpeg_color_space == JCS_RGB && out_color_space == JCS_YCbCr) continue; TestConfig config; config.input.xsize = config.input.ysize = 256; @@ -1108,6 +1122,8 @@ std::vector GenerateTests(bool buffered) { TestConfig config; config.input.xsize = xsize; config.input.ysize = ysize; + config.jparams.h_sampling = {1, 1, 1}; + config.jparams.v_sampling = {1, 1, 1}; all_tests.push_back(config); } } diff --git a/third_party/jpeg-xl/lib/jpegli/decode_internal.h b/third_party/jpeg-xl/lib/jpegli/decode_internal.h index 37dfcc4526..8455fae392 100644 --- a/third_party/jpeg-xl/lib/jpegli/decode_internal.h +++ b/third_party/jpeg-xl/lib/jpegli/decode_internal.h @@ -6,14 +6,15 @@ #ifndef LIB_JPEGLI_DECODE_INTERNAL_H_ #define LIB_JPEGLI_DECODE_INTERNAL_H_ -#include #include +#include #include #include "lib/jpegli/common.h" #include "lib/jpegli/common_internal.h" #include "lib/jpegli/huffman.h" +#include "lib/jpegli/types.h" namespace jpegli { @@ -58,6 +59,7 @@ struct jpeg_decomp_master { bool found_soi_; bool found_dri_; bool found_sof_; + bool found_sos_; bool found_eoi_; // Whether this jpeg has multiple scans (progressive or non-interleaved diff --git a/third_party/jpeg-xl/lib/jpegli/decode_marker.cc b/third_party/jpeg-xl/lib/jpegli/decode_marker.cc index a9ed4df329..2621ed0867 100644 --- a/third_party/jpeg-xl/lib/jpegli/decode_marker.cc +++ b/third_party/jpeg-xl/lib/jpegli/decode_marker.cc @@ -103,9 +103,6 @@ void ProcessSOF(j_decompress_ptr cinfo, const uint8_t* data, size_t len) { int quant_tbl_idx = ReadUint8(data, &pos); JPEG_VERIFY_INPUT(quant_tbl_idx, 0, NUM_QUANT_TBLS - 1); comp->quant_tbl_no = quant_tbl_idx; - if (cinfo->quant_tbl_ptrs[quant_tbl_idx] == nullptr) { - JPEGLI_ERROR("Quantization table with index %u not found", quant_tbl_idx); - } comp->quant_table = nullptr; // will be allocated after SOS marker } JPEG_VERIFY_MARKER_END(); @@ -168,6 +165,7 @@ void ProcessSOS(j_decompress_ptr cinfo, const uint8_t* data, size_t len) { if (!m->found_sof_) { JPEGLI_ERROR("Unexpected SOS marker."); } + m->found_sos_ = true; size_t pos = 2; JPEG_VERIFY_LEN(1); cinfo->comps_in_scan = ReadUint8(data, &pos); @@ -337,7 +335,7 @@ void ProcessDHT(j_decompress_ptr cinfo, const uint8_t* data, size_t len) { void ProcessDQT(j_decompress_ptr cinfo, const uint8_t* data, size_t len) { jpeg_decomp_master* m = cinfo->master; - if (m->found_sof_) { + if (m->found_sos_) { JPEGLI_ERROR("Updating quant tables between scans is not supported."); } size_t pos = 2; diff --git a/third_party/jpeg-xl/lib/jpegli/encode.cc b/third_party/jpeg-xl/lib/jpegli/encode.cc index 5326f2cb0f..6cfd54ad30 100644 --- a/third_party/jpeg-xl/lib/jpegli/encode.cc +++ b/third_party/jpeg-xl/lib/jpegli/encode.cc @@ -283,15 +283,15 @@ void ProcessCompressionParams(j_compress_ptr cinfo) { JPEGLI_ERROR("Invalid sampling factor %d x %d", comp->h_samp_factor, comp->v_samp_factor); } + if (cinfo->num_components == 1) { + // Force samp factors to 1x1 for single-component images. + comp->h_samp_factor = comp->v_samp_factor = 1; + } cinfo->max_h_samp_factor = std::max(comp->h_samp_factor, cinfo->max_h_samp_factor); cinfo->max_v_samp_factor = std::max(comp->v_samp_factor, cinfo->max_v_samp_factor); } - if (cinfo->num_components == 1 && - (cinfo->max_h_samp_factor != 1 || cinfo->max_v_samp_factor != 1)) { - JPEGLI_ERROR("Sampling is not supported for simgle component image."); - } size_t iMCU_width = DCTSIZE * cinfo->max_h_samp_factor; size_t iMCU_height = DCTSIZE * cinfo->max_v_samp_factor; size_t total_iMCU_cols = DivCeil(cinfo->image_width, iMCU_width); @@ -713,18 +713,31 @@ void jpegli_set_defaults(j_compress_ptr cinfo) { void jpegli_default_colorspace(j_compress_ptr cinfo) { CheckState(cinfo, jpegli::kEncStart); + if (cinfo->in_color_space == JCS_RGB && cinfo->master->xyb_mode) { + jpegli_set_colorspace(cinfo, JCS_RGB); + return; + } switch (cinfo->in_color_space) { case JCS_GRAYSCALE: jpegli_set_colorspace(cinfo, JCS_GRAYSCALE); break; - case JCS_RGB: { - if (cinfo->master->xyb_mode) { - jpegli_set_colorspace(cinfo, JCS_RGB); - } else { - jpegli_set_colorspace(cinfo, JCS_YCbCr); - } + case JCS_RGB: +#ifdef JCS_EXTENSIONS + case JCS_EXT_RGB: + case JCS_EXT_BGR: + case JCS_EXT_RGBX: + case JCS_EXT_BGRX: + case JCS_EXT_XRGB: + case JCS_EXT_XBGR: +#endif +#if JCS_ALPHA_EXTENSIONS + case JCS_EXT_RGBA: + case JCS_EXT_BGRA: + case JCS_EXT_ARGB: + case JCS_EXT_ABGR: +#endif + jpegli_set_colorspace(cinfo, JCS_YCbCr); break; - } case JCS_YCbCr: jpegli_set_colorspace(cinfo, JCS_YCbCr); break; @@ -806,6 +819,11 @@ void jpegli_set_colorspace(j_compress_ptr cinfo, J_COLOR_SPACE colorspace) { cinfo->comp_info[2].quant_tbl_no = 1; cinfo->comp_info[1].dc_tbl_no = cinfo->comp_info[1].ac_tbl_no = 1; cinfo->comp_info[2].dc_tbl_no = cinfo->comp_info[2].ac_tbl_no = 1; + // Use chroma subsampling by default + cinfo->comp_info[0].h_samp_factor = cinfo->comp_info[0].v_samp_factor = 2; + if (colorspace == JCS_YCCK) { + cinfo->comp_info[3].h_samp_factor = cinfo->comp_info[3].v_samp_factor = 2; + } } } diff --git a/third_party/jpeg-xl/lib/jpegli/encode.h b/third_party/jpeg-xl/lib/jpegli/encode.h index ed34838450..33de674471 100644 --- a/third_party/jpeg-xl/lib/jpegli/encode.h +++ b/third_party/jpeg-xl/lib/jpegli/encode.h @@ -21,6 +21,7 @@ #define LIB_JPEGLI_ENCODE_H_ #include "lib/jpegli/common.h" +#include "lib/jpegli/types.h" #if defined(__cplusplus) || defined(c_plusplus) extern "C" { diff --git a/third_party/jpeg-xl/lib/jpegli/encode_api_test.cc b/third_party/jpeg-xl/lib/jpegli/encode_api_test.cc index 2978b3f35d..81b1b25bef 100644 --- a/third_party/jpeg-xl/lib/jpegli/encode_api_test.cc +++ b/third_party/jpeg-xl/lib/jpegli/encode_api_test.cc @@ -4,14 +4,23 @@ // license that can be found in the LICENSE file. #include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include "lib/jpegli/encode.h" -#include "lib/jpegli/error.h" +#include "lib/jpegli/libjpeg_test_util.h" +#include "lib/jpegli/test_params.h" #include "lib/jpegli/test_utils.h" #include "lib/jpegli/testing.h" -#include "lib/jxl/sanitizers.h" +#include "lib/jpegli/types.h" +#include "lib/jxl/base/status.h" namespace jpegli { namespace { @@ -372,6 +381,8 @@ std::vector GenerateTests() { { TestConfig config; config.jparams.quality = 100; + config.jparams.h_sampling = {1, 1, 1}; + config.jparams.v_sampling = {1, 1, 1}; config.max_bpp = 6.6; config.max_dist = 0.6; all_tests.push_back(config); @@ -510,17 +521,23 @@ std::vector GenerateTests() { config.jparams.libjpeg_mode = true; config.max_bpp = 2.1; config.max_dist = 1.7; + config.jparams.h_sampling = {1, 1, 1}; + config.jparams.v_sampling = {1, 1, 1}; all_tests.push_back(config); } - for (J_COLOR_SPACE in_color_space : {JCS_RGB, JCS_YCbCr, JCS_GRAYSCALE}) { + for (J_COLOR_SPACE in_color_space : + {JCS_RGB, JCS_YCbCr, JCS_GRAYSCALE, JCS_EXT_RGB, JCS_EXT_BGR, + JCS_EXT_RGBA, JCS_EXT_BGRA, JCS_EXT_ARGB, JCS_EXT_ABGR}) { for (J_COLOR_SPACE jpeg_color_space : {JCS_RGB, JCS_YCbCr, JCS_GRAYSCALE}) { - if (jpeg_color_space == JCS_RGB && in_color_space == JCS_YCbCr) continue; + if (jpeg_color_space == JCS_RGB && in_color_space >= JCS_YCbCr) continue; TestConfig config; config.input.xsize = config.input.ysize = 256; config.input.color_space = in_color_space; config.jparams.set_jpeg_colorspace = true; config.jparams.jpeg_color_space = jpeg_color_space; + config.jparams.h_sampling = {1, 1, 1}; + config.jparams.v_sampling = {1, 1, 1}; config.max_bpp = jpeg_color_space == JCS_RGB ? 4.5 : 1.85; config.max_dist = jpeg_color_space == JCS_RGB ? 1.4 : 2.05; all_tests.push_back(config); @@ -536,6 +553,8 @@ std::vector GenerateTests() { config.jparams.set_jpeg_colorspace = true; config.jparams.jpeg_color_space = jpeg_color_space; } + config.jparams.h_sampling = {1, 1, 1, 1}; + config.jparams.v_sampling = {1, 1, 1, 1}; config.max_bpp = jpeg_color_space == JCS_CMYK ? 4.0 : 3.6; config.max_dist = jpeg_color_space == JCS_CMYK ? 1.2 : 1.5; all_tests.push_back(config); @@ -546,6 +565,8 @@ std::vector GenerateTests() { config.input.color_space = JCS_YCbCr; config.max_bpp = 1.6; config.max_dist = 1.35; + config.jparams.h_sampling = {1, 1, 1}; + config.jparams.v_sampling = {1, 1, 1}; all_tests.push_back(config); } for (bool xyb : {false, true}) { @@ -596,6 +617,8 @@ std::vector GenerateTests() { table.add_raw = add_raw; table.Generate(); config.jparams.optimize_coding = 1; + config.jparams.h_sampling = {1, 1, 1}; + config.jparams.v_sampling = {1, 1, 1}; config.jparams.quant_tables.push_back(table); config.jparams.quant_indexes = {0, 0, 0}; float q = (type == 0 ? 16 : type) * scale * 0.01f; @@ -614,6 +637,8 @@ std::vector GenerateTests() { config.input.ysize = 256; config.jparams.quant_indexes = {(qidx >> 2) & 1, (qidx >> 1) & 1, (qidx >> 0) & 1}; + config.jparams.h_sampling = {1, 1, 1}; + config.jparams.v_sampling = {1, 1, 1}; config.max_bpp = 2.25; config.max_dist = 2.8; all_tests.push_back(config); @@ -626,6 +651,8 @@ std::vector GenerateTests() { config.input.ysize = 256; config.jparams.quant_indexes = {(qidx >> 2) & 1, (qidx >> 1) & 1, (qidx >> 0) & 1}; + config.jparams.h_sampling = {1, 1, 1}; + config.jparams.v_sampling = {1, 1, 1}; CustomQuantTable table; table.slot_idx = slot_idx; table.Generate(); @@ -643,6 +670,10 @@ std::vector GenerateTests() { config.jparams.xyb_mode = xyb; config.jparams.quant_indexes = {(qidx >> 2) & 1, (qidx >> 1) & 1, (qidx >> 0) & 1}; + if (!xyb) { + config.jparams.h_sampling = {1, 1, 1}; + config.jparams.v_sampling = {1, 1, 1}; + } { CustomQuantTable table; table.slot_idx = 0; @@ -667,6 +698,10 @@ std::vector GenerateTests() { config.input.ysize = 256; config.jparams.xyb_mode = xyb; config.jparams.quant_indexes = {0, 1, 2}; + if (!xyb) { + config.jparams.h_sampling = {1, 1, 1}; + config.jparams.v_sampling = {1, 1, 1}; + } { CustomQuantTable table; table.slot_idx = 0; @@ -738,6 +773,8 @@ std::vector GenerateTests() { } config.jparams.progressive_mode = 0; config.jparams.optimize_coding = 0; + config.jparams.h_sampling = {1, 1, 1}; + config.jparams.v_sampling = {1, 1, 1}; config.max_bpp = 1.85; config.max_dist = 2.05; if (input_mode == COEFFICIENTS) { diff --git a/third_party/jpeg-xl/lib/jpegli/error_handling_test.cc b/third_party/jpeg-xl/lib/jpegli/error_handling_test.cc index bcd7355124..582c6b170b 100644 --- a/third_party/jpeg-xl/lib/jpegli/error_handling_test.cc +++ b/third_party/jpeg-xl/lib/jpegli/error_handling_test.cc @@ -3,12 +3,20 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +#include +#include +#include +#include +#include + +#include "lib/jpegli/common.h" #include "lib/jpegli/decode.h" #include "lib/jpegli/encode.h" -#include "lib/jpegli/error.h" +#include "lib/jpegli/libjpeg_test_util.h" +#include "lib/jpegli/test_params.h" #include "lib/jpegli/test_utils.h" #include "lib/jpegli/testing.h" -#include "lib/jxl/sanitizers.h" +#include "lib/jxl/base/status.h" namespace jpegli { namespace { @@ -996,6 +1004,9 @@ TEST(EncoderErrorHandlingTest, AddOnTableNoStringParam) { const uint8_t kCompressed0[] = { // SOI 0xff, 0xd8, // + // SOF + 0xff, 0xc0, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01, 0x01, // + 0x01, 0x11, 0x00, // // DQT 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x03, 0x02, // 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x03, 0x03, 0x04, 0x05, // @@ -1004,9 +1015,6 @@ const uint8_t kCompressed0[] = { 0x0e, 0x12, 0x10, 0x0d, 0x0e, 0x11, 0x0e, 0x0b, 0x0b, 0x10, // 0x16, 0x10, 0x11, 0x13, 0x14, 0x15, 0x15, 0x15, 0x0c, 0x0f, // 0x17, 0x18, 0x16, 0x14, 0x18, 0x12, 0x14, 0x15, 0x14, // - // SOF - 0xff, 0xc0, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01, 0x01, // - 0x01, 0x11, 0x00, // // DHT 0xff, 0xc4, 0x00, 0xd2, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, // 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @@ -1039,8 +1047,8 @@ const uint8_t kCompressed0[] = { }; const size_t kLen0 = sizeof(kCompressed0); -const size_t kDQTOffset = 2; -const size_t kSOFOffset = 71; +const size_t kSOFOffset = 2; +const size_t kDQTOffset = 15; const size_t kDHTOffset = 84; const size_t kSOSOffset = 296; diff --git a/third_party/jpeg-xl/lib/jpegli/input_suspension_test.cc b/third_party/jpeg-xl/lib/jpegli/input_suspension_test.cc index 6546b7b087..dc5aee2fc5 100644 --- a/third_party/jpeg-xl/lib/jpegli/input_suspension_test.cc +++ b/third_party/jpeg-xl/lib/jpegli/input_suspension_test.cc @@ -3,16 +3,24 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -#include +#include + +#include +#include #include +#include +#include +#include +#include +#include #include #include "lib/jpegli/decode.h" +#include "lib/jpegli/libjpeg_test_util.h" +#include "lib/jpegli/test_params.h" #include "lib/jpegli/test_utils.h" #include "lib/jpegli/testing.h" -#include "lib/jxl/base/byte_order.h" #include "lib/jxl/base/status.h" -#include "lib/jxl/sanitizers.h" namespace jpegli { namespace { diff --git a/third_party/jpeg-xl/lib/jpegli/libjpeg_test_util.cc b/third_party/jpeg-xl/lib/jpegli/libjpeg_test_util.cc index 020adf5e9e..d34ec7e999 100644 --- a/third_party/jpeg-xl/lib/jpegli/libjpeg_test_util.cc +++ b/third_party/jpeg-xl/lib/jpegli/libjpeg_test_util.cc @@ -5,12 +5,7 @@ #include "lib/jpegli/libjpeg_test_util.h" -/* clang-format off */ -#include -#include -#include -/* clang-format on */ - +#include "lib/jxl/base/include_jpeglib.h" // NOLINT #include "lib/jxl/sanitizers.h" namespace jpegli { diff --git a/third_party/jpeg-xl/lib/jpegli/output_suspension_test.cc b/third_party/jpeg-xl/lib/jpegli/output_suspension_test.cc index 3cb2fd3ee4..44d63fdcbb 100644 --- a/third_party/jpeg-xl/lib/jpegli/output_suspension_test.cc +++ b/third_party/jpeg-xl/lib/jpegli/output_suspension_test.cc @@ -3,7 +3,18 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +#include +#include +#include +#include +#include +#include +#include +#include + #include "lib/jpegli/encode.h" +#include "lib/jpegli/libjpeg_test_util.h" +#include "lib/jpegli/test_params.h" #include "lib/jpegli/test_utils.h" #include "lib/jpegli/testing.h" @@ -130,6 +141,7 @@ TEST_P(OutputSuspensionTestParam, RawData) { cinfo.input_components = input.components; cinfo.in_color_space = JCS_YCbCr; jpegli_set_defaults(&cinfo); + cinfo.comp_info[0].h_samp_factor = config.jparams.h_sampling[0]; cinfo.comp_info[0].v_samp_factor = config.jparams.v_sampling[0]; jpegli_set_progressive_level(&cinfo, 0); cinfo.optimize_coding = FALSE; diff --git a/third_party/jpeg-xl/lib/jpegli/source_manager_test.cc b/third_party/jpeg-xl/lib/jpegli/source_manager_test.cc index a513b7063b..2d49ac16ba 100644 --- a/third_party/jpeg-xl/lib/jpegli/source_manager_test.cc +++ b/third_party/jpeg-xl/lib/jpegli/source_manager_test.cc @@ -3,14 +3,13 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -#include #include #include #include "lib/jpegli/decode.h" +#include "lib/jpegli/libjpeg_test_util.h" #include "lib/jpegli/test_utils.h" #include "lib/jpegli/testing.h" -#include "lib/jxl/base/status.h" namespace jpegli { namespace { diff --git a/third_party/jpeg-xl/lib/jpegli/streaming_test.cc b/third_party/jpeg-xl/lib/jpegli/streaming_test.cc index 1f19dc2045..29a224385f 100644 --- a/third_party/jpeg-xl/lib/jpegli/streaming_test.cc +++ b/third_party/jpeg-xl/lib/jpegli/streaming_test.cc @@ -3,8 +3,18 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +#include +#include +#include +#include +#include +#include +#include +#include + #include "lib/jpegli/decode.h" #include "lib/jpegli/encode.h" +#include "lib/jpegli/test_params.h" #include "lib/jpegli/test_utils.h" #include "lib/jpegli/testing.h" diff --git a/third_party/jpeg-xl/lib/jpegli/test_utils.cc b/third_party/jpeg-xl/lib/jpegli/test_utils.cc index db5a30e8dc..5315c692a1 100644 --- a/third_party/jpeg-xl/lib/jpegli/test_utils.cc +++ b/third_party/jpeg-xl/lib/jpegli/test_utils.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include "lib/jpegli/decode.h" #include "lib/jpegli/encode.h" @@ -171,6 +172,18 @@ std::string ColorSpaceName(J_COLOR_SPACE colorspace) { return "CMYK"; case JCS_YCCK: return "YCCK"; + case JCS_EXT_RGB: + return "EXT_RGB"; + case JCS_EXT_BGR: + return "EXT_BGR"; + case JCS_EXT_RGBA: + return "EXT_RGBA"; + case JCS_EXT_BGRA: + return "EXT_BGRA"; + case JCS_EXT_ARGB: + return "EXT_ARGB"; + case JCS_EXT_ABGR: + return "EXT_ABGR"; default: return ""; } @@ -301,9 +314,12 @@ std::ostream& operator<<(std::ostream& os, const CompressParams& jparams) { void SetNumChannels(J_COLOR_SPACE colorspace, size_t* channels) { if (colorspace == JCS_GRAYSCALE) { *channels = 1; - } else if (colorspace == JCS_RGB || colorspace == JCS_YCbCr) { + } else if (colorspace == JCS_RGB || colorspace == JCS_YCbCr || + colorspace == JCS_EXT_RGB || colorspace == JCS_EXT_BGR) { *channels = 3; - } else if (colorspace == JCS_CMYK || colorspace == JCS_YCCK) { + } else if (colorspace == JCS_CMYK || colorspace == JCS_YCCK || + colorspace == JCS_EXT_RGBA || colorspace == JCS_EXT_BGRA || + colorspace == JCS_EXT_ARGB || colorspace == JCS_EXT_ABGR) { *channels = 4; } else if (colorspace == JCS_UNKNOWN) { JXL_CHECK(*channels <= 4); @@ -330,7 +346,28 @@ void ConvertPixel(const uint8_t* input_rgb, uint8_t* out, if (colorspace == JCS_GRAYSCALE) { const float Y = 0.299f * r + 0.587f * g + 0.114f * b; out8[0] = static_cast(std::round(Y * kMul)); - } else if (colorspace == JCS_RGB || colorspace == JCS_UNKNOWN) { + } else if (colorspace == JCS_RGB || colorspace == JCS_EXT_RGB || + colorspace == JCS_EXT_RGBA) { + out8[0] = input_rgb[0]; + out8[1] = input_rgb[1]; + out8[2] = input_rgb[2]; + if (colorspace == JCS_EXT_RGBA) out8[3] = 255; + } else if (colorspace == JCS_EXT_BGR || colorspace == JCS_EXT_BGRA) { + out8[2] = input_rgb[0]; + out8[1] = input_rgb[1]; + out8[0] = input_rgb[2]; + if (colorspace == JCS_EXT_BGRA) out8[3] = 255; + } else if (colorspace == JCS_EXT_ABGR) { + out8[0] = 255; + out8[3] = input_rgb[0]; + out8[2] = input_rgb[1]; + out8[1] = input_rgb[2]; + } else if (colorspace == JCS_EXT_ARGB) { + out8[0] = 255; + out8[1] = input_rgb[0]; + out8[2] = input_rgb[1]; + out8[3] = input_rgb[2]; + } else if (colorspace == JCS_UNKNOWN) { for (size_t c = 0; c < num_channels; ++c) { out8[c] = input_rgb[std::min(2, c)]; } @@ -390,9 +427,23 @@ void ConvertPixel(const uint8_t* input_rgb, uint8_t* out, void ConvertToGrayscale(TestImage* img) { if (img->color_space == JCS_GRAYSCALE) return; JXL_CHECK(img->data_type == JPEGLI_TYPE_UINT8); - for (size_t i = 0; i < img->pixels.size(); i += 3) { - if (img->color_space == JCS_RGB) { - ConvertPixel(&img->pixels[i], &img->pixels[i / 3], JCS_GRAYSCALE, 1); + bool rgb_pre_alpha = + img->color_space == JCS_EXT_ARGB || img->color_space == JCS_EXT_ABGR; + bool rgb_post_alpha = + img->color_space == JCS_EXT_RGBA || img->color_space == JCS_EXT_BGRA; + bool rgb_alpha = rgb_pre_alpha || rgb_post_alpha; + bool is_rgb = img->color_space == JCS_RGB || + img->color_space == JCS_EXT_RGB || + img->color_space == JCS_EXT_BGR || rgb_alpha; + bool switch_br = img->color_space == JCS_EXT_BGR || + img->color_space == JCS_EXT_ABGR || + img->color_space == JCS_EXT_BGRA; + size_t stride = rgb_alpha ? 4 : 3; + size_t offset = rgb_pre_alpha ? 1 : 0; + for (size_t i = offset; i < img->pixels.size(); i += stride) { + if (is_rgb) { + if (switch_br) std::swap(img->pixels[i], img->pixels[i + 2]); + ConvertPixel(&img->pixels[i], &img->pixels[i / stride], JCS_GRAYSCALE, 1); } else if (img->color_space == JCS_YCbCr) { img->pixels[i / 3] = img->pixels[i]; } diff --git a/third_party/jpeg-xl/lib/jpegli/test_utils.h b/third_party/jpeg-xl/lib/jpegli/test_utils.h index 132cfd042a..22c620c46c 100644 --- a/third_party/jpeg-xl/lib/jpegli/test_utils.h +++ b/third_party/jpeg-xl/lib/jpegli/test_utils.h @@ -6,22 +6,15 @@ #ifndef LIB_JPEGLI_TEST_UTILS_H_ #define LIB_JPEGLI_TEST_UTILS_H_ -#include -#include - -#include +#include +#include #include #include -/* clang-format off */ -#include -#include -#include -/* clang-format on */ - -#include "lib/jpegli/common.h" -#include "lib/jpegli/libjpeg_test_util.h" #include "lib/jpegli/test_params.h" +#include "lib/jpegli/types.h" +#include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/include_jpeglib.h" // NOLINT namespace jpegli { @@ -127,4 +120,15 @@ void VerifyOutputImage(const TestImage& input, const TestImage& output, } // namespace jpegli +#if !defined(FUZZ_TEST) +struct FuzzTestSink { + template + FuzzTestSink WithSeeds(F) { + return *this; + } +}; +#define FUZZ_TEST(A, B) \ + const JXL_MAYBE_UNUSED FuzzTestSink unused##A##B = FuzzTestSink() +#endif + #endif // LIB_JPEGLI_TEST_UTILS_H_ diff --git a/third_party/jpeg-xl/lib/jpegli/transcode_api_test.cc b/third_party/jpeg-xl/lib/jpegli/transcode_api_test.cc index 13c81a1119..413d5ae996 100644 --- a/third_party/jpeg-xl/lib/jpegli/transcode_api_test.cc +++ b/third_party/jpeg-xl/lib/jpegli/transcode_api_test.cc @@ -3,10 +3,19 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +#include +#include +#include +#include +#include +#include +#include #include #include "lib/jpegli/decode.h" #include "lib/jpegli/encode.h" +#include "lib/jpegli/libjpeg_test_util.h" +#include "lib/jpegli/test_params.h" #include "lib/jpegli/test_utils.h" #include "lib/jpegli/testing.h" #include "lib/jxl/base/status.h" diff --git a/third_party/jpeg-xl/lib/jxl/ac_context.h b/third_party/jpeg-xl/lib/jxl/ac_context.h index a2b9e046d1..6529a9bb88 100644 --- a/third_party/jpeg-xl/lib/jxl/ac_context.h +++ b/third_party/jpeg-xl/lib/jxl/ac_context.h @@ -62,7 +62,8 @@ static JXL_INLINE size_t ZeroDensityContext(size_t nonzeros_left, size_t k, size_t covered_blocks, size_t log2_covered_blocks, size_t prev) { - JXL_DASSERT((1u << log2_covered_blocks) == covered_blocks); + JXL_DASSERT((static_cast(1) << log2_covered_blocks) == + covered_blocks); nonzeros_left = (nonzeros_left + covered_blocks - 1) >> log2_covered_blocks; k >>= log2_covered_blocks; JXL_DASSERT(k > 0); @@ -109,7 +110,8 @@ struct BlockCtxMap { // Non-zero context is based on number of non-zeros and block context. // For better clustering, contexts with same number of non-zeros are grouped. constexpr uint32_t ZeroDensityContextsOffset(uint32_t block_ctx) const { - return num_ctxs * kNonZeroBuckets + kZeroDensityContextCount * block_ctx; + return static_cast(num_ctxs * kNonZeroBuckets + + kZeroDensityContextCount * block_ctx); } // Context map for AC coefficients consists of 2 blocks: @@ -121,7 +123,8 @@ struct BlockCtxMap { // number of non-zeros left and // index in scan order constexpr uint32_t NumACContexts() const { - return num_ctxs * (kNonZeroBuckets + kZeroDensityContextCount); + return static_cast(num_ctxs * + (kNonZeroBuckets + kZeroDensityContextCount)); } // Non-zero context is based on number of non-zeros and block context. @@ -134,7 +137,7 @@ struct BlockCtxMap { } else { ctx = 4 + non_zeros / 2; } - return ctx * num_ctxs + block_ctx; + return static_cast(ctx * num_ctxs + block_ctx); } BlockCtxMap() { diff --git a/third_party/jpeg-xl/lib/jxl/ac_strategy.h b/third_party/jpeg-xl/lib/jxl/ac_strategy.h index 9e5917ff1b..fd40b0ced8 100644 --- a/third_party/jpeg-xl/lib/jxl/ac_strategy.h +++ b/third_party/jpeg-xl/lib/jxl/ac_strategy.h @@ -11,9 +11,12 @@ #include // kMaxVectorSize +#include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/rect.h" #include "lib/jxl/base/status.h" #include "lib/jxl/coeff_order_fwd.h" #include "lib/jxl/frame_dimensions.h" +#include "lib/jxl/image.h" #include "lib/jxl/image_ops.h" // Defines the different kinds of transforms, and heuristics to choose between diff --git a/third_party/jpeg-xl/lib/jxl/ac_strategy_test.cc b/third_party/jpeg-xl/lib/jxl/ac_strategy_test.cc index b1d9103466..dc25c89898 100644 --- a/third_party/jpeg-xl/lib/jxl/ac_strategy_test.cc +++ b/third_party/jpeg-xl/lib/jxl/ac_strategy_test.cc @@ -5,16 +5,14 @@ #include "lib/jxl/ac_strategy.h" -#include - -#include +#include +#include #include #include // HWY_ALIGN_MAX #include -#include #include "lib/jxl/base/random.h" -#include "lib/jxl/dct_scales.h" +#include "lib/jxl/coeff_order_fwd.h" #include "lib/jxl/dec_transforms_testonly.h" #include "lib/jxl/enc_transforms.h" #include "lib/jxl/simd_util.h" diff --git a/third_party/jpeg-xl/lib/jxl/ans_common.h b/third_party/jpeg-xl/lib/jxl/ans_common.h index 44b8e3fba1..8236bb20ec 100644 --- a/third_party/jpeg-xl/lib/jxl/ans_common.h +++ b/third_party/jpeg-xl/lib/jxl/ans_common.h @@ -6,23 +6,24 @@ #ifndef LIB_JXL_ANS_COMMON_H_ #define LIB_JXL_ANS_COMMON_H_ -#include - #include +#include +#include +#include +#include #include // Prefetch #include #include "lib/jxl/ans_params.h" #include "lib/jxl/base/byte_order.h" #include "lib/jxl/base/compiler_specific.h" -#include "lib/jxl/base/status.h" namespace jxl { // Returns the precision (number of bits) that should be used to store // a histogram count such that Log2Floor(count) == logcount. -static JXL_INLINE uint32_t GetPopulationCountPrecision(uint32_t logcount, - uint32_t shift) { +static JXL_MAYBE_UNUSED JXL_INLINE uint32_t +GetPopulationCountPrecision(uint32_t logcount, uint32_t shift) { int32_t r = std::min( logcount, static_cast(shift) - static_cast((ANS_LOG_TAB_SIZE - logcount) >> 1)); diff --git a/third_party/jpeg-xl/lib/jxl/ans_test.cc b/third_party/jpeg-xl/lib/jxl/ans_test.cc index 5d6a5ef090..83a2e732f8 100644 --- a/third_party/jpeg-xl/lib/jxl/ans_test.cc +++ b/third_party/jpeg-xl/lib/jxl/ans_test.cc @@ -10,11 +10,10 @@ #include "lib/jxl/ans_params.h" #include "lib/jxl/base/random.h" -#include "lib/jxl/base/span.h" +#include "lib/jxl/base/status.h" #include "lib/jxl/dec_ans.h" #include "lib/jxl/dec_bit_reader.h" #include "lib/jxl/enc_ans.h" -#include "lib/jxl/enc_aux_out.h" #include "lib/jxl/enc_bit_writer.h" #include "lib/jxl/testing.h" diff --git a/third_party/jpeg-xl/lib/jxl/base/compiler_specific.h b/third_party/jpeg-xl/lib/jxl/base/compiler_specific.h index 702ff8e058..52f88c50f8 100644 --- a/third_party/jpeg-xl/lib/jxl/base/compiler_specific.h +++ b/third_party/jpeg-xl/lib/jxl/base/compiler_specific.h @@ -8,7 +8,6 @@ // Macros for compiler version + nonstandard keywords, e.g. __builtin_expect. -#include #include #include "lib/jxl/base/sanitizer_definitions.h" @@ -97,6 +96,11 @@ #define JXL_UNLIKELY(expr) __builtin_expect(!!(expr), 0) #endif +#if JXL_COMPILER_MSVC +#include +using ssize_t = intptr_t; +#endif + // Returns a void* pointer which the compiler then assumes is N-byte aligned. // Example: float* JXL_RESTRICT aligned = (float*)JXL_ASSUME_ALIGNED(in, 32); // @@ -150,8 +154,4 @@ #define JXL_FORMAT(idx_fmt, idx_arg) #endif -#if JXL_COMPILER_MSVC -using ssize_t = intptr_t; -#endif - #endif // LIB_JXL_BASE_COMPILER_SPECIFIC_H_ diff --git a/third_party/jpeg-xl/lib/jxl/base/exif.h b/third_party/jpeg-xl/lib/jxl/base/exif.h index a3574a16ff..acaa1a1ce4 100644 --- a/third_party/jpeg-xl/lib/jxl/base/exif.h +++ b/third_party/jpeg-xl/lib/jxl/base/exif.h @@ -3,8 +3,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -#ifndef LIB_JXL_EXIF_H_ -#define LIB_JXL_EXIF_H_ +#ifndef LIB_JXL_BASE_EXIF_H_ +#define LIB_JXL_BASE_EXIF_H_ // Basic parsing of Exif (just enough for the render-impacting things // like orientation) @@ -87,4 +87,4 @@ JXL_INLINE void InterpretExif(const std::vector& exif, } // namespace jxl -#endif // LIB_JXL_EXIF_H_ +#endif // LIB_JXL_BASE_EXIF_H_ diff --git a/third_party/jpeg-xl/lib/jxl/base/include_jpeglib.h b/third_party/jpeg-xl/lib/jxl/base/include_jpeglib.h new file mode 100644 index 0000000000..f72d13d04b --- /dev/null +++ b/third_party/jpeg-xl/lib/jxl/base/include_jpeglib.h @@ -0,0 +1,20 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef LIB_JXL_BASE_INCLUDE_JPEGLIB_H_ +#define LIB_JXL_BASE_INCLUDE_JPEGLIB_H_ + +// Using this header ensures that includes go in the right order, +// not alphabetically sorted. + +// NOLINTBEGIN +/* clang-format off */ +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include // IWYU pragma: keep +/* clang-format on */ +// NOLINTEND + +#endif // LIB_JXL_BASE_INCLUDE_JPEGLIB_H_ diff --git a/third_party/jpeg-xl/lib/jxl/base/matrix_ops.h b/third_party/jpeg-xl/lib/jxl/base/matrix_ops.h index cde6a64b1e..e1f8753932 100644 --- a/third_party/jpeg-xl/lib/jxl/base/matrix_ops.h +++ b/third_party/jpeg-xl/lib/jxl/base/matrix_ops.h @@ -3,8 +3,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -#ifndef LIB_JXL_MATRIX_OPS_H_ -#define LIB_JXL_MATRIX_OPS_H_ +#ifndef LIB_JXL_BASE_MATRIX_OPS_H_ +#define LIB_JXL_BASE_MATRIX_OPS_H_ // 3x3 matrix operations. @@ -83,4 +83,4 @@ Status Inv3x3Matrix(Matrix& matrix) { } // namespace jxl -#endif // LIB_JXL_MATRIX_OPS_H_ +#endif // LIB_JXL_BASE_MATRIX_OPS_H_ diff --git a/third_party/jpeg-xl/lib/jxl/base/rect.h b/third_party/jpeg-xl/lib/jxl/base/rect.h new file mode 100644 index 0000000000..666c3d73ec --- /dev/null +++ b/third_party/jpeg-xl/lib/jxl/base/rect.h @@ -0,0 +1,194 @@ +// 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_JXL_BASE_RECT_H_ +#define LIB_JXL_BASE_RECT_H_ + +#include +#include +#include +#include +#include +#include +#include // std::move + +#include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/status.h" + +namespace jxl { + +// Rectangular region in image(s). Factoring this out of Image instead of +// shifting the pointer by x0/y0 allows this to apply to multiple images with +// different resolutions (e.g. color transform and quantization field). +// Can compare using SameSize(rect1, rect2). +template +class RectT { + public: + // Most windows are xsize_max * ysize_max, except those on the borders where + // begin + size_max > end. + constexpr RectT(T xbegin, T ybegin, size_t xsize_max, size_t ysize_max, + T xend, T yend) + : x0_(xbegin), + y0_(ybegin), + xsize_(ClampedSize(xbegin, xsize_max, xend)), + ysize_(ClampedSize(ybegin, ysize_max, yend)) {} + + // Construct with origin and known size (typically from another Rect). + constexpr RectT(T xbegin, T ybegin, size_t xsize, size_t ysize) + : x0_(xbegin), y0_(ybegin), xsize_(xsize), ysize_(ysize) {} + + // Construct a rect that covers a whole image/plane/ImageBundle etc. + template + explicit RectT(const ImageT& image) + : RectT(0, 0, image.xsize(), image.ysize()) {} + + RectT() : RectT(0, 0, 0, 0) {} + + RectT(const RectT&) = default; + RectT& operator=(const RectT&) = default; + + // Construct a subrect that resides in an image/plane/ImageBundle etc. + template + RectT Crop(const ImageT& image) const { + return Intersection(RectT(image)); + } + + // Construct a subrect that resides in the [0, ysize) x [0, xsize) region of + // the current rect. + RectT Crop(size_t area_xsize, size_t area_ysize) const { + return Intersection(RectT(0, 0, area_xsize, area_ysize)); + } + + // Returns a rect that only contains `num` lines with offset `y` from `y0()`. + RectT Lines(size_t y, size_t num) const { + JXL_DASSERT(y + num <= ysize_); + return RectT(x0_, y0_ + y, xsize_, num); + } + + RectT Line(size_t y) const { return Lines(y, 1); } + + JXL_MUST_USE_RESULT RectT Intersection(const RectT& other) const { + return RectT(std::max(x0_, other.x0_), std::max(y0_, other.y0_), xsize_, + ysize_, std::min(x1(), other.x1()), + std::min(y1(), other.y1())); + } + + JXL_MUST_USE_RESULT RectT Translate(int64_t x_offset, + int64_t y_offset) const { + return RectT(x0_ + x_offset, y0_ + y_offset, xsize_, ysize_); + } + + template