tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

apng.cc (47311B)


      1 // Copyright (c) the JPEG XL Project Authors. All rights reserved.
      2 //
      3 // Use of this source code is governed by a BSD-style
      4 // license that can be found in the LICENSE file.
      5 
      6 #include "lib/extras/dec/apng.h"
      7 
      8 // Parts of this code are taken from apngdis, which has the following license:
      9 /* APNG Disassembler 2.8
     10 *
     11 * Deconstructs APNG files into individual frames.
     12 *
     13 * http://apngdis.sourceforge.net
     14 *
     15 * Copyright (c) 2010-2015 Max Stepin
     16 * maxst at users.sourceforge.net
     17 *
     18 * zlib license
     19 * ------------
     20 *
     21 * This software is provided 'as-is', without any express or implied
     22 * warranty.  In no event will the authors be held liable for any damages
     23 * arising from the use of this software.
     24 *
     25 * Permission is granted to anyone to use this software for any purpose,
     26 * including commercial applications, and to alter it and redistribute it
     27 * freely, subject to the following restrictions:
     28 *
     29 * 1. The origin of this software must not be misrepresented; you must not
     30 *    claim that you wrote the original software. If you use this software
     31 *    in a product, an acknowledgment in the product documentation would be
     32 *    appreciated but is not required.
     33 * 2. Altered source versions must be plainly marked as such, and must not be
     34 *    misrepresented as being the original software.
     35 * 3. This notice may not be removed or altered from any source distribution.
     36 *
     37 */
     38 
     39 #include <jxl/codestream_header.h>
     40 #include <jxl/encode.h>
     41 
     42 #include <array>
     43 #include <atomic>
     44 #include <cstdint>
     45 #include <cstring>
     46 #include <limits>
     47 #include <memory>
     48 #include <string>
     49 #include <utility>
     50 #include <vector>
     51 
     52 #include "lib/extras/packed_image.h"
     53 #include "lib/extras/size_constraints.h"
     54 #include "lib/jxl/base/byte_order.h"
     55 #include "lib/jxl/base/common.h"
     56 #include "lib/jxl/base/compiler_specific.h"
     57 #include "lib/jxl/base/printf_macros.h"
     58 #include "lib/jxl/base/rect.h"
     59 #include "lib/jxl/base/sanitizers.h"
     60 #include "lib/jxl/base/span.h"
     61 #include "lib/jxl/base/status.h"
     62 #if JPEGXL_ENABLE_APNG
     63 #include "png.h" /* original (unpatched) libpng is ok */
     64 #endif
     65 
     66 namespace jxl {
     67 namespace extras {
     68 
     69 #if !JPEGXL_ENABLE_APNG
     70 
     71 bool CanDecodeAPNG() { return false; }
     72 Status DecodeImageAPNG(const Span<const uint8_t> bytes,
     73                       const ColorHints& color_hints, PackedPixelFile* ppf,
     74                       const SizeConstraints* constraints) {
     75  return false;
     76 }
     77 
     78 #else  // JPEGXL_ENABLE_APNG
     79 
     80 namespace {
     81 
     82 constexpr std::array<uint8_t, 8> kPngSignature = {137,  'P',  'N', 'G',
     83                                                  '\r', '\n', 26,  '\n'};
     84 
     85 // Returns floating-point value from the PNG encoding (times 10^5).
     86 double F64FromU32(const uint32_t x) { return static_cast<int32_t>(x) * 1E-5; }
     87 
     88 /** Extract information from 'sRGB' chunk. */
     89 Status DecodeSrgbChunk(const Bytes payload, JxlColorEncoding* color_encoding) {
     90  if (payload.size() != 1) return JXL_FAILURE("Wrong sRGB size");
     91  uint8_t ri = payload[0];
     92  // (PNG uses the same values as ICC.)
     93  if (ri >= 4) return JXL_FAILURE("Invalid Rendering Intent");
     94  color_encoding->white_point = JXL_WHITE_POINT_D65;
     95  color_encoding->primaries = JXL_PRIMARIES_SRGB;
     96  color_encoding->transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
     97  color_encoding->rendering_intent = static_cast<JxlRenderingIntent>(ri);
     98  return true;
     99 }
    100 
    101 /**
    102 * Extract information from 'cICP' chunk.
    103 *
    104 * If the cICP profile is not fully supported, return `false` and leave
    105 * `color_encoding` unmodified.
    106 */
    107 Status DecodeCicpChunk(const Bytes payload, JxlColorEncoding* color_encoding) {
    108  if (payload.size() != 4) return JXL_FAILURE("Wrong cICP size");
    109  JxlColorEncoding color_enc = *color_encoding;
    110 
    111  // From https://www.itu.int/rec/T-REC-H.273-202107-I/en
    112  if (payload[0] == 1) {
    113    // IEC 61966-2-1 sRGB
    114    color_enc.primaries = JXL_PRIMARIES_SRGB;
    115    color_enc.white_point = JXL_WHITE_POINT_D65;
    116  } else if (payload[0] == 4) {
    117    // Rec. ITU-R BT.470-6 System M
    118    color_enc.primaries = JXL_PRIMARIES_CUSTOM;
    119    color_enc.primaries_red_xy[0] = 0.67;
    120    color_enc.primaries_red_xy[1] = 0.33;
    121    color_enc.primaries_green_xy[0] = 0.21;
    122    color_enc.primaries_green_xy[1] = 0.71;
    123    color_enc.primaries_blue_xy[0] = 0.14;
    124    color_enc.primaries_blue_xy[1] = 0.08;
    125    color_enc.white_point = JXL_WHITE_POINT_CUSTOM;
    126    color_enc.white_point_xy[0] = 0.310;
    127    color_enc.white_point_xy[1] = 0.316;
    128  } else if (payload[0] == 5) {
    129    // Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM
    130    color_enc.primaries = JXL_PRIMARIES_CUSTOM;
    131    color_enc.primaries_red_xy[0] = 0.64;
    132    color_enc.primaries_red_xy[1] = 0.33;
    133    color_enc.primaries_green_xy[0] = 0.29;
    134    color_enc.primaries_green_xy[1] = 0.60;
    135    color_enc.primaries_blue_xy[0] = 0.15;
    136    color_enc.primaries_blue_xy[1] = 0.06;
    137    color_enc.white_point = JXL_WHITE_POINT_D65;
    138  } else if (payload[0] == 6 || payload[0] == 7) {
    139    // SMPTE ST 170 (2004) / SMPTE ST 240 (1999)
    140    color_enc.primaries = JXL_PRIMARIES_CUSTOM;
    141    color_enc.primaries_red_xy[0] = 0.630;
    142    color_enc.primaries_red_xy[1] = 0.340;
    143    color_enc.primaries_green_xy[0] = 0.310;
    144    color_enc.primaries_green_xy[1] = 0.595;
    145    color_enc.primaries_blue_xy[0] = 0.155;
    146    color_enc.primaries_blue_xy[1] = 0.070;
    147    color_enc.white_point = JXL_WHITE_POINT_D65;
    148  } else if (payload[0] == 8) {
    149    // Generic film (colour filters using Illuminant C)
    150    color_enc.primaries = JXL_PRIMARIES_CUSTOM;
    151    color_enc.primaries_red_xy[0] = 0.681;
    152    color_enc.primaries_red_xy[1] = 0.319;
    153    color_enc.primaries_green_xy[0] = 0.243;
    154    color_enc.primaries_green_xy[1] = 0.692;
    155    color_enc.primaries_blue_xy[0] = 0.145;
    156    color_enc.primaries_blue_xy[1] = 0.049;
    157    color_enc.white_point = JXL_WHITE_POINT_CUSTOM;
    158    color_enc.white_point_xy[0] = 0.310;
    159    color_enc.white_point_xy[1] = 0.316;
    160  } else if (payload[0] == 9) {
    161    // Rec. ITU-R BT.2100-2
    162    color_enc.primaries = JXL_PRIMARIES_2100;
    163    color_enc.white_point = JXL_WHITE_POINT_D65;
    164  } else if (payload[0] == 10) {
    165    // CIE 1931 XYZ
    166    color_enc.primaries = JXL_PRIMARIES_CUSTOM;
    167    color_enc.primaries_red_xy[0] = 1;
    168    color_enc.primaries_red_xy[1] = 0;
    169    color_enc.primaries_green_xy[0] = 0;
    170    color_enc.primaries_green_xy[1] = 1;
    171    color_enc.primaries_blue_xy[0] = 0;
    172    color_enc.primaries_blue_xy[1] = 0;
    173    color_enc.white_point = JXL_WHITE_POINT_E;
    174  } else if (payload[0] == 11) {
    175    // SMPTE RP 431-2 (2011)
    176    color_enc.primaries = JXL_PRIMARIES_P3;
    177    color_enc.white_point = JXL_WHITE_POINT_DCI;
    178  } else if (payload[0] == 12) {
    179    // SMPTE EG 432-1 (2010)
    180    color_enc.primaries = JXL_PRIMARIES_P3;
    181    color_enc.white_point = JXL_WHITE_POINT_D65;
    182  } else if (payload[0] == 22) {
    183    color_enc.primaries = JXL_PRIMARIES_CUSTOM;
    184    color_enc.primaries_red_xy[0] = 0.630;
    185    color_enc.primaries_red_xy[1] = 0.340;
    186    color_enc.primaries_green_xy[0] = 0.295;
    187    color_enc.primaries_green_xy[1] = 0.605;
    188    color_enc.primaries_blue_xy[0] = 0.155;
    189    color_enc.primaries_blue_xy[1] = 0.077;
    190    color_enc.white_point = JXL_WHITE_POINT_D65;
    191  } else {
    192    JXL_WARNING("Unsupported primaries specified in cICP chunk: %d",
    193                static_cast<int>(payload[0]));
    194    return false;
    195  }
    196 
    197  if (payload[1] == 1 || payload[1] == 6 || payload[1] == 14 ||
    198      payload[1] == 15) {
    199    // Rec. ITU-R BT.709-6
    200    color_enc.transfer_function = JXL_TRANSFER_FUNCTION_709;
    201  } else if (payload[1] == 4) {
    202    // Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM
    203    color_enc.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
    204    color_enc.gamma = 1 / 2.2;
    205  } else if (payload[1] == 5) {
    206    // Rec. ITU-R BT.470-6 System B, G
    207    color_enc.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
    208    color_enc.gamma = 1 / 2.8;
    209  } else if (payload[1] == 8 || payload[1] == 13 || payload[1] == 16 ||
    210             payload[1] == 17 || payload[1] == 18) {
    211    // These codes all match the corresponding JXL enum values
    212    color_enc.transfer_function = static_cast<JxlTransferFunction>(payload[1]);
    213  } else {
    214    JXL_WARNING("Unsupported transfer function specified in cICP chunk: %d",
    215                static_cast<int>(payload[1]));
    216    return false;
    217  }
    218 
    219  if (payload[2] != 0) {
    220    JXL_WARNING("Unsupported color space specified in cICP chunk: %d",
    221                static_cast<int>(payload[2]));
    222    return false;
    223  }
    224  if (payload[3] != 1) {
    225    JXL_WARNING("Unsupported full-range flag specified in cICP chunk: %d",
    226                static_cast<int>(payload[3]));
    227    return false;
    228  }
    229  // cICP has no rendering intent, so use the default
    230  color_enc.rendering_intent = JXL_RENDERING_INTENT_RELATIVE;
    231  *color_encoding = color_enc;
    232  return true;
    233 }
    234 
    235 /** Extract information from 'gAMA' chunk. */
    236 Status DecodeGamaChunk(Bytes payload, JxlColorEncoding* color_encoding) {
    237  if (payload.size() != 4) return JXL_FAILURE("Wrong gAMA size");
    238  color_encoding->transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
    239  color_encoding->gamma = F64FromU32(LoadBE32(payload.data()));
    240  return true;
    241 }
    242 
    243 /** Extract information from 'cHTM' chunk. */
    244 Status DecodeChrmChunk(Bytes payload, JxlColorEncoding* color_encoding) {
    245  if (payload.size() != 32) return JXL_FAILURE("Wrong cHRM size");
    246  const uint8_t* data = payload.data();
    247  color_encoding->white_point = JXL_WHITE_POINT_CUSTOM;
    248  color_encoding->white_point_xy[0] = F64FromU32(LoadBE32(data + 0));
    249  color_encoding->white_point_xy[1] = F64FromU32(LoadBE32(data + 4));
    250 
    251  color_encoding->primaries = JXL_PRIMARIES_CUSTOM;
    252  color_encoding->primaries_red_xy[0] = F64FromU32(LoadBE32(data + 8));
    253  color_encoding->primaries_red_xy[1] = F64FromU32(LoadBE32(data + 12));
    254  color_encoding->primaries_green_xy[0] = F64FromU32(LoadBE32(data + 16));
    255  color_encoding->primaries_green_xy[1] = F64FromU32(LoadBE32(data + 20));
    256  color_encoding->primaries_blue_xy[0] = F64FromU32(LoadBE32(data + 24));
    257  color_encoding->primaries_blue_xy[1] = F64FromU32(LoadBE32(data + 28));
    258  return true;
    259 }
    260 
    261 /** Extracts information from 'cLLi' chunk. */
    262 Status DecodeClliChunk(Bytes payload, float* max_content_light_level) {
    263  if (payload.size() != 8) return JXL_FAILURE("Wrong cLLi size");
    264  const uint8_t* data = payload.data();
    265  const uint32_t maxcll_png =
    266      Clamp1(png_get_uint_32(data), uint32_t{0}, uint32_t{10000 * 10000});
    267  // Ignore MaxFALL value.
    268  *max_content_light_level = static_cast<float>(maxcll_png) / 10000.f;
    269  return true;
    270 }
    271 
    272 /** Returns false if invalid. */
    273 JXL_INLINE Status DecodeHexNibble(const char c, uint32_t* JXL_RESTRICT nibble) {
    274  if ('a' <= c && c <= 'f') {
    275    *nibble = 10 + c - 'a';
    276  } else if ('0' <= c && c <= '9') {
    277    *nibble = c - '0';
    278  } else {
    279    *nibble = 0;
    280    return JXL_FAILURE("Invalid metadata nibble");
    281  }
    282  JXL_ENSURE(*nibble < 16);
    283  return true;
    284 }
    285 
    286 /** Returns false if invalid. */
    287 JXL_INLINE Status DecodeDecimal(const char** pos, const char* end,
    288                                uint32_t* JXL_RESTRICT value) {
    289  size_t len = 0;
    290  *value = 0;
    291  while (*pos < end) {
    292    char next = **pos;
    293    if (next >= '0' && next <= '9') {
    294      *value = (*value * 10) + static_cast<uint32_t>(next - '0');
    295      len++;
    296      if (len > 8) {
    297        break;
    298      }
    299    } else {
    300      // Do not consume terminator (non-decimal digit).
    301      break;
    302    }
    303    (*pos)++;
    304  }
    305  if (len == 0 || len > 8) {
    306    return JXL_FAILURE("Failed to parse decimal");
    307  }
    308  return true;
    309 }
    310 
    311 /**
    312 * Parses a PNG text chunk with key of the form "Raw profile type ####", with
    313 * #### a type.
    314 *
    315 * Returns whether it could successfully parse the content.
    316 * We trust key and encoded are null-terminated because they come from
    317 * libpng.
    318 */
    319 Status MaybeDecodeBase16(const char* key, const char* encoded,
    320                         std::string* type, std::vector<uint8_t>* bytes) {
    321  const char* encoded_end = encoded + strlen(encoded);
    322 
    323  const char* kKey = "Raw profile type ";
    324  if (strncmp(key, kKey, strlen(kKey)) != 0) return false;
    325  *type = key + strlen(kKey);
    326  const size_t kMaxTypeLen = 20;
    327  if (type->length() > kMaxTypeLen) return false;  // Type too long
    328 
    329  // Header: freeform string and number of bytes
    330  // Expected format is:
    331  // \n
    332  // profile name/description\n
    333  //       40\n               (the number of bytes after hex-decoding)
    334  // 01234566789abcdef....\n  (72 bytes per line max).
    335  // 012345667\n              (last line)
    336  const char* pos = encoded;
    337 
    338  if (*(pos++) != '\n') return false;
    339  while (pos < encoded_end && *pos != '\n') {
    340    pos++;
    341  }
    342  if (pos == encoded_end) return false;
    343  // We parsed so far a \n, some number of non \n characters and are now
    344  // pointing at a \n.
    345  if (*(pos++) != '\n') return false;
    346  // Skip leading spaces
    347  while (pos < encoded_end && *pos == ' ') {
    348    pos++;
    349  }
    350  uint32_t bytes_to_decode = 0;
    351  JXL_RETURN_IF_ERROR(DecodeDecimal(&pos, encoded_end, &bytes_to_decode));
    352 
    353  // We need 2*bytes for the hex values plus 1 byte every 36 values,
    354  // plus terminal \n for length.
    355  size_t tail = static_cast<size_t>(encoded_end - pos);
    356  bool ok = ((tail / 2) >= bytes_to_decode);
    357  if (ok) tail -= 2 * static_cast<size_t>(bytes_to_decode);
    358  ok = ok && (tail == 1 + DivCeil(bytes_to_decode, 36));
    359  if (!ok) {
    360    return JXL_FAILURE("Not enough bytes to parse %d bytes in hex",
    361                       bytes_to_decode);
    362  }
    363  JXL_ENSURE(bytes->empty());
    364  bytes->reserve(bytes_to_decode);
    365 
    366  // Encoding: base16 with newline after 72 chars.
    367  // pos points to the \n before the first line of hex values.
    368  for (size_t i = 0; i < bytes_to_decode; ++i) {
    369    if (i % 36 == 0) {
    370      if (pos + 1 >= encoded_end) return false;  // Truncated base16 1
    371      if (*pos != '\n') return false;            // Expected newline
    372      ++pos;
    373    }
    374 
    375    if (pos + 2 >= encoded_end) return false;  // Truncated base16 2;
    376    uint32_t nibble0;
    377    uint32_t nibble1;
    378    JXL_RETURN_IF_ERROR(DecodeHexNibble(pos[0], &nibble0));
    379    JXL_RETURN_IF_ERROR(DecodeHexNibble(pos[1], &nibble1));
    380    bytes->push_back(static_cast<uint8_t>((nibble0 << 4) + nibble1));
    381    pos += 2;
    382  }
    383  if (pos + 1 != encoded_end) return false;  // Too many encoded bytes
    384  if (pos[0] != '\n') return false;          // Incorrect metadata terminator
    385  return true;
    386 }
    387 
    388 /** Retrieves XMP and EXIF/IPTC from itext and text. */
    389 Status DecodeBlob(const png_text_struct& info, PackedMetadata* metadata) {
    390  // We trust these are properly null-terminated by libpng.
    391  const char* key = info.key;
    392  const char* value = info.text;
    393  if (strstr(key, "XML:com.adobe.xmp")) {
    394    metadata->xmp.resize(strlen(value));  // safe, see above
    395    memcpy(metadata->xmp.data(), value, metadata->xmp.size());
    396  }
    397 
    398  std::string type;
    399  std::vector<uint8_t> bytes;
    400 
    401  // Handle text chunks annotated with key "Raw profile type ####", with
    402  // #### a type, which may contain metadata.
    403  const char* kKey = "Raw profile type ";
    404  if (strncmp(key, kKey, strlen(kKey)) != 0) return false;
    405 
    406  if (!MaybeDecodeBase16(key, value, &type, &bytes)) {
    407    JXL_WARNING("Couldn't parse 'Raw format type' text chunk");
    408    return false;
    409  }
    410  if (type == "exif") {
    411    // Remove prefix if present.
    412    constexpr std::array<uint8_t, 6> kExifPrefix = {'E', 'x', 'i', 'f', 0, 0};
    413    if (bytes.size() >= kExifPrefix.size() &&
    414        memcmp(bytes.data(), kExifPrefix.data(), kExifPrefix.size()) == 0) {
    415      bytes.erase(bytes.begin(), bytes.begin() + kExifPrefix.size());
    416    }
    417    if (!metadata->exif.empty()) {
    418      JXL_DEBUG_V(2,
    419                  "overwriting EXIF (%" PRIuS " bytes) with base16 (%" PRIuS
    420                  " bytes)",
    421                  metadata->exif.size(), bytes.size());
    422    }
    423    metadata->exif = std::move(bytes);
    424  } else if (type == "iptc") {
    425    // TODO(jon): Deal with IPTC in some way
    426  } else if (type == "8bim") {
    427    // TODO(jon): Deal with 8bim in some way
    428  } else if (type == "xmp") {
    429    if (!metadata->xmp.empty()) {
    430      JXL_DEBUG_V(2,
    431                  "overwriting XMP (%" PRIuS " bytes) with base16 (%" PRIuS
    432                  " bytes)",
    433                  metadata->xmp.size(), bytes.size());
    434    }
    435    metadata->xmp = std::move(bytes);
    436  } else {
    437    JXL_DEBUG_V(
    438        2, "Unknown type in 'Raw format type' text chunk: %s: %" PRIuS " bytes",
    439        type.c_str(), bytes.size());
    440  }
    441  return true;
    442 }
    443 
    444 constexpr bool isAbc(char c) {
    445  return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
    446 }
    447 
    448 /** Wrap 4-char tag name into ID. */
    449 constexpr uint32_t MakeTag(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
    450  return a | (b << 8) | (c << 16) | (d << 24);
    451 }
    452 
    453 /** Reusable image data container. */
    454 struct Pixels {
    455  // Use array instead of vector to avoid memory initialization.
    456  std::unique_ptr<uint8_t[]> pixels;
    457  size_t pixels_size = 0;
    458  std::vector<uint8_t*> rows;
    459  std::atomic<bool> has_error{false};
    460 
    461  Status Resize(size_t row_bytes, size_t num_rows) {
    462    size_t new_size = row_bytes * num_rows;  // it is assumed size is sane
    463    if (new_size > pixels_size) {
    464      pixels.reset(new uint8_t[new_size]);
    465      if (!pixels) {
    466        // TODO(szabadka): use specialized OOM error code
    467        return JXL_FAILURE("Failed to allocate memory for image buffer");
    468      }
    469      pixels_size = new_size;
    470    }
    471    rows.resize(num_rows);
    472    for (size_t y = 0; y < num_rows; y++) {
    473      rows[y] = pixels.get() + y * row_bytes;
    474    }
    475    return true;
    476  }
    477 };
    478 
    479 /**
    480 * Helper that chunks in-memory input.
    481 */
    482 struct Reader {
    483  explicit Reader(Span<const uint8_t> data) : data_(data) {}
    484 
    485  const Span<const uint8_t> data_;
    486  size_t offset_ = 0;
    487 
    488  Bytes Peek(size_t len) const {
    489    size_t cap = data_.size() - offset_;
    490    size_t to_copy = std::min(cap, len);
    491    return {data_.data() + offset_, to_copy};
    492  }
    493 
    494  Bytes Read(size_t len) {
    495    Bytes result = Peek(len);
    496    offset_ += result.size();
    497    return result;
    498  }
    499 
    500  /* Returns empty Span on error. */
    501  Bytes ReadChunk() {
    502    Bytes len = Peek(4);
    503    if (len.size() != 4) {
    504      return Bytes();
    505    }
    506    const auto size = png_get_uint_32(len.data());
    507    // NB: specification allows 2^31 - 1
    508    constexpr size_t kMaxPNGChunkSize = 1u << 30;  // 1 GB
    509    // Check first, to avoid overflow.
    510    if (size > kMaxPNGChunkSize) {
    511      JXL_WARNING("APNG chunk size is too big");
    512      return Bytes();
    513    }
    514    size_t full_size = size + 12;  // size does not include itself, tag and CRC.
    515    Bytes result = Read(full_size);
    516    return (result.size() == full_size) ? result : Bytes();
    517  }
    518 
    519  bool Eof() const { return offset_ == data_.size(); }
    520 };
    521 
    522 void ProgressiveRead_OnInfo(png_structp png_ptr, png_infop info_ptr) {
    523  png_set_expand(png_ptr);
    524  png_set_palette_to_rgb(png_ptr);
    525  png_set_tRNS_to_alpha(png_ptr);
    526  (void)png_set_interlace_handling(png_ptr);
    527  png_read_update_info(png_ptr, info_ptr);
    528 }
    529 
    530 void ProgressiveRead_OnRow(png_structp png_ptr, png_bytep new_row,
    531                           png_uint_32 row_num, int pass) {
    532  Pixels* frame = reinterpret_cast<Pixels*>(png_get_progressive_ptr(png_ptr));
    533  if (!frame) {
    534    JXL_DEBUG_ABORT("Internal logic error");
    535    return;
    536  }
    537  if (row_num >= frame->rows.size()) {
    538    frame->has_error = true;
    539    return;
    540  }
    541  png_progressive_combine_row(png_ptr, frame->rows[row_num], new_row);
    542 }
    543 
    544 // Holds intermediate state during parsing APNG file.
    545 struct Context {
    546  ~Context() {
    547    // Make sure png memory is released in any case.
    548    ResetPngDecoder();
    549  }
    550 
    551  bool CreatePngDecoder() {
    552    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr,
    553                                     nullptr);
    554    info_ptr = png_create_info_struct(png_ptr);
    555    return (png_ptr != nullptr && info_ptr != nullptr);
    556  }
    557 
    558  /**
    559   * Initialize PNG decoder.
    560   *
    561   * TODO(eustas): add details
    562   */
    563  bool InitPngDecoder(const std::vector<Bytes>& chunksInfo,
    564                      const RectT<uint64_t>& viewport) {
    565    ResetPngDecoder();
    566 
    567    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr,
    568                                     nullptr);
    569    info_ptr = png_create_info_struct(png_ptr);
    570    if (png_ptr == nullptr || info_ptr == nullptr) {
    571      return false;
    572    }
    573 
    574    if (setjmp(png_jmpbuf(png_ptr))) {
    575      return false;
    576    }
    577 
    578    /* hIST chunk tail is not processed properly; skip this chunk completely;
    579       see https://github.com/glennrp/libpng/pull/413 */
    580    constexpr std::array<uint8_t, 5> kIgnoredChunks = {'h', 'I', 'S', 'T', 0};
    581    png_set_keep_unknown_chunks(png_ptr, 1, kIgnoredChunks.data(),
    582                                static_cast<int>(kIgnoredChunks.size() / 5));
    583 
    584    png_set_crc_action(png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE);
    585    png_set_progressive_read_fn(png_ptr, static_cast<void*>(&frameRaw),
    586                                ProgressiveRead_OnInfo, ProgressiveRead_OnRow,
    587                                nullptr);
    588 
    589    png_process_data(png_ptr, info_ptr,
    590                     const_cast<uint8_t*>(kPngSignature.data()),
    591                     kPngSignature.size());
    592 
    593    // Patch dimensions.
    594    png_save_uint_32(ihdr.data() + 8, static_cast<uint32_t>(viewport.xsize()));
    595    png_save_uint_32(ihdr.data() + 12, static_cast<uint32_t>(viewport.ysize()));
    596    png_process_data(png_ptr, info_ptr, ihdr.data(), ihdr.size());
    597 
    598    for (const auto& chunk : chunksInfo) {
    599      png_process_data(png_ptr, info_ptr, const_cast<uint8_t*>(chunk.data()),
    600                       chunk.size());
    601    }
    602 
    603    return true;
    604  }
    605 
    606  /**
    607   * Pass chunk to PNG decoder.
    608   */
    609  bool FeedChunks(const Bytes& chunk1, const Bytes& chunk2 = Bytes()) {
    610    // TODO(eustas): turn to DCHECK
    611    if (!png_ptr || !info_ptr) return false;
    612 
    613    if (setjmp(png_jmpbuf(png_ptr))) {
    614      return false;
    615    }
    616 
    617    for (const auto& chunk : {chunk1, chunk2}) {
    618      if (!chunk.empty()) {
    619        png_process_data(png_ptr, info_ptr, const_cast<uint8_t*>(chunk.data()),
    620                         chunk.size());
    621      }
    622    }
    623    return true;
    624  }
    625 
    626  bool FinalizeStream(PackedMetadata* metadata) {
    627    // TODO(eustas): turn to DCHECK
    628    if (!png_ptr || !info_ptr) return false;
    629 
    630    if (setjmp(png_jmpbuf(png_ptr))) {
    631      return false;
    632    }
    633 
    634    const std::array<uint8_t, 12> kFooter = {0,  0,  0,   0,  73, 69,
    635                                             78, 68, 174, 66, 96, 130};
    636    png_process_data(png_ptr, info_ptr, const_cast<uint8_t*>(kFooter.data()),
    637                     kFooter.size());
    638    // before destroying: check if we encountered any metadata chunks
    639    png_textp text_ptr = nullptr;
    640    int num_text = 0;
    641    if (png_get_text(png_ptr, info_ptr, &text_ptr, &num_text) != 0) {
    642      msan::UnpoisonMemory(text_ptr, sizeof(png_text_struct) * num_text);
    643      for (int i = 0; i < num_text; i++) {
    644        Status result = DecodeBlob(text_ptr[i], metadata);
    645        // Ignore unknown / malformed blob.
    646        (void)result;
    647      }
    648    }
    649 
    650    return true;
    651  }
    652 
    653  void ResetPngDecoder() {
    654    png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
    655    // Just in case. Not all versions on libpng wipe-out the pointers.
    656    png_ptr = nullptr;
    657    info_ptr = nullptr;
    658  }
    659 
    660  std::array<uint8_t, 25> ihdr;  // (modified) copy of file IHDR chunk
    661  png_structp png_ptr = nullptr;
    662  png_infop info_ptr = nullptr;
    663  Pixels frameRaw = {};
    664 };
    665 
    666 enum class DisposeOp : uint8_t { NONE = 0, BACKGROUND = 1, PREVIOUS = 2 };
    667 
    668 constexpr uint8_t kLastDisposeOp = static_cast<uint32_t>(DisposeOp::PREVIOUS);
    669 
    670 enum class BlendOp : uint8_t { SOURCE = 0, OVER = 1 };
    671 
    672 constexpr uint8_t kLastBlendOp = static_cast<uint32_t>(BlendOp::OVER);
    673 
    674 // fcTL
    675 struct FrameControl {
    676  uint32_t delay_num;
    677  uint32_t delay_den;
    678  RectT<uint64_t> viewport;
    679  DisposeOp dispose_op;
    680  BlendOp blend_op;
    681 };
    682 
    683 struct Frame {
    684  PackedImage pixels;
    685  FrameControl metadata;
    686 };
    687 
    688 bool ValidateViewport(const RectT<uint64_t>& r) {
    689  constexpr uint32_t kMaxPngDim = 1000000UL;
    690  return (r.xsize() <= kMaxPngDim) && (r.ysize() <= kMaxPngDim);
    691 }
    692 
    693 /**
    694 * Setup #channels, bpp, colorspace, etc. from PNG values.
    695 */
    696 void SetColorData(PackedPixelFile* ppf, uint8_t color_type, uint8_t bit_depth,
    697                  png_color_8p sig_bits, uint32_t has_transparency) {
    698  bool palette_used = ((color_type & 1) != 0);
    699  bool color_used = ((color_type & 2) != 0);
    700  bool alpha_channel_used = ((color_type & 4) != 0);
    701  if (palette_used) {
    702    if (!color_used || alpha_channel_used) {
    703      JXL_DEBUG_V(2, "Unexpected PNG color type");
    704    }
    705  }
    706 
    707  ppf->info.bits_per_sample = bit_depth;
    708 
    709  if (palette_used) {
    710    // palette will actually be 8-bit regardless of the index bitdepth
    711    ppf->info.bits_per_sample = 8;
    712  }
    713  if (color_used) {
    714    ppf->info.num_color_channels = 3;
    715    ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB;
    716    if (sig_bits) {
    717      if (sig_bits->red == sig_bits->green &&
    718          sig_bits->green == sig_bits->blue) {
    719        ppf->info.bits_per_sample = sig_bits->red;
    720      } else {
    721        int maxbps =
    722            std::max(sig_bits->red, std::max(sig_bits->green, sig_bits->blue));
    723        JXL_DEBUG_V(2,
    724                    "sBIT chunk: bit depths for R, G, and B are not the same "
    725                    "(%i %i %i), while in JPEG XL they have to be the same. "
    726                    "Setting RGB bit depth to %i.",
    727                    sig_bits->red, sig_bits->green, sig_bits->blue, maxbps);
    728        ppf->info.bits_per_sample = maxbps;
    729      }
    730    }
    731  } else {
    732    ppf->info.num_color_channels = 1;
    733    ppf->color_encoding.color_space = JXL_COLOR_SPACE_GRAY;
    734    if (sig_bits) ppf->info.bits_per_sample = sig_bits->gray;
    735  }
    736  if (alpha_channel_used || has_transparency) {
    737    ppf->info.alpha_bits = ppf->info.bits_per_sample;
    738    if (sig_bits && sig_bits->alpha != ppf->info.bits_per_sample) {
    739      JXL_DEBUG_V(2,
    740                  "sBIT chunk: bit depths for RGBA are inconsistent "
    741                  "(%i %i %i %i). Setting A bitdepth to %i.",
    742                  sig_bits->red, sig_bits->green, sig_bits->blue,
    743                  sig_bits->alpha, ppf->info.bits_per_sample);
    744    }
    745  } else {
    746    ppf->info.alpha_bits = 0;
    747  }
    748  ppf->color_encoding.color_space = (ppf->info.num_color_channels == 1)
    749                                        ? JXL_COLOR_SPACE_GRAY
    750                                        : JXL_COLOR_SPACE_RGB;
    751 }
    752 
    753 // Color profile chunks: cICP has the highest priority, followed by
    754 // iCCP and sRGB (which shouldn't co-exist, but if they do, we use
    755 // iCCP), followed finally by gAMA and cHRM.
    756 enum class ColorInfoType {
    757  NONE = 0,
    758  GAMA_OR_CHRM = 1,
    759  ICCP_OR_SRGB = 2,
    760  CICP = 3
    761 };
    762 
    763 }  // namespace
    764 
    765 bool CanDecodeAPNG() { return true; }
    766 
    767 /**
    768 * Parse and decode PNG file.
    769 *
    770 * Useful PNG chunks:
    771 *   acTL : animation control (#frames, loop count)
    772 *   fcTL : frame control (seq#, viewport, delay, disposal blending)
    773 *   bKGD : preferable background
    774 *   IDAT : "default image"
    775 *          if single fcTL goes before IDAT, then it is also first frame
    776 *   fdAT : seq# + IDAT-like content
    777 *   PLTE : palette
    778 *   cICP : coding-independent code points for video signal type identification
    779 *   iCCP : embedded ICC profile
    780 *   sRGB : standard RGB colour space
    781 *   eXIf : exchangeable image file profile
    782 *   gAMA : image gamma
    783 *   cHRM : primary chromaticities and white point
    784 *   tRNS : transparency
    785 *
    786 * PNG chunk ordering:
    787 *  - IHDR first
    788 *  - IEND last
    789 *  - acTL, cHRM, cICP, gAMA, iCCP, sRGB, bKGD, eXIf, PLTE before IDAT
    790 *  - fdAT after IDAT
    791 *
    792 * More rules:
    793 *  - iCCP and sRGB are exclusive
    794 *  - fcTL and fdAT seq# must be in order fro 0, with no gaps or duplicates
    795 *  - fcTL before corresponding IDAT / fdAT
    796 */
    797 Status DecodeImageAPNG(const Span<const uint8_t> bytes,
    798                       const ColorHints& color_hints, PackedPixelFile* ppf,
    799                       const SizeConstraints* constraints) {
    800  // Initialize output (default settings in case e.g. only gAMA is given).
    801  ppf->frames.clear();
    802  ppf->info.exponent_bits_per_sample = 0;
    803  ppf->info.alpha_exponent_bits = 0;
    804  ppf->info.orientation = JXL_ORIENT_IDENTITY;
    805  ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB;
    806  ppf->color_encoding.white_point = JXL_WHITE_POINT_D65;
    807  ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB;
    808  ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
    809  ppf->color_encoding.rendering_intent = JXL_RENDERING_INTENT_RELATIVE;
    810 
    811  Reader input(bytes);
    812 
    813  // Check signature.
    814  Bytes sig = input.Read(kPngSignature.size());
    815  if (sig.size() != 8 ||
    816      memcmp(sig.data(), kPngSignature.data(), kPngSignature.size()) != 0) {
    817    return false;  // Return silently if it is not a PNG
    818  }
    819 
    820  // Check IHDR chunk.
    821  Context ctx;
    822  Bytes ihdr = input.ReadChunk();
    823  if (ihdr.size() != ctx.ihdr.size()) {
    824    return JXL_FAILURE("Unexpected first chunk payload size");
    825  }
    826  memcpy(ctx.ihdr.data(), ihdr.data(), ihdr.size());
    827  uint32_t id = LoadLE32(ihdr.data() + 4);
    828  if (id != MakeTag('I', 'H', 'D', 'R')) {
    829    return JXL_FAILURE("First chunk is not IHDR");
    830  }
    831  const RectT<uint64_t> image_rect(0, 0, png_get_uint_32(ihdr.data() + 8),
    832                                   png_get_uint_32(ihdr.data() + 12));
    833  if (!ValidateViewport(image_rect)) {
    834    return JXL_FAILURE("PNG image dimensions are too large");
    835  }
    836 
    837  // Chunks we supply to PNG decoder for every animation frame.
    838  std::vector<Bytes> passthrough_chunks;
    839  if (!ctx.InitPngDecoder(passthrough_chunks, image_rect)) {
    840    return JXL_FAILURE("Failed to initialize PNG decoder");
    841  }
    842 
    843  // Marker that this PNG is animated.
    844  bool seen_actl = false;
    845  // First IDAT is a very important milestone; at this moment we freeze
    846  // gathered metadata.
    847  bool seen_idat = false;
    848  // fCTL can occur multiple times, but only once before IDAT.
    849  bool seen_fctl = false;
    850  // Logical EOF.
    851  bool seen_iend = false;
    852 
    853  ColorInfoType color_info_type = ColorInfoType::NONE;
    854 
    855  // Flag that we processed some IDAT / fDAT after image / frame start.
    856  bool seen_pixel_data = false;
    857 
    858  uint32_t num_channels;
    859  JxlPixelFormat format = {};
    860  size_t bytes_per_pixel = 0;
    861  std::vector<Frame> frames;
    862  FrameControl current_frame = {/*delay_num=*/1, /*delay_den=*/10, image_rect,
    863                                DisposeOp::NONE, BlendOp::SOURCE};
    864 
    865  // Copies frame pixels / metadata from temporary storage.
    866  // TODO(eustas): avoid copying.
    867  const auto finalize_frame = [&]() -> Status {
    868    if (!seen_pixel_data) {
    869      return JXL_FAILURE("Frame / image without fdAT / IDAT chunks");
    870    }
    871    if (!ctx.FinalizeStream(&ppf->metadata)) {
    872      return JXL_FAILURE("Failed to finalize PNG substream");
    873    }
    874    if (ctx.frameRaw.has_error) {
    875      return JXL_FAILURE("Internal error");
    876    }
    877    // Allocates the frame buffer.
    878    const RectT<uint64_t>& vp = current_frame.viewport;
    879    size_t xsize = static_cast<size_t>(vp.xsize());
    880    size_t ysize = static_cast<size_t>(vp.ysize());
    881    JXL_ASSIGN_OR_RETURN(PackedImage image,
    882                         PackedImage::Create(xsize, ysize, format));
    883    for (size_t y = 0; y < ysize; ++y) {
    884      // TODO(eustas): ensure multiplication is safe
    885      memcpy(static_cast<uint8_t*>(image.pixels()) + image.stride * y,
    886             ctx.frameRaw.rows[y], bytes_per_pixel * xsize);
    887    }
    888    frames.push_back(Frame{std::move(image), current_frame});
    889    seen_pixel_data = false;
    890    return true;
    891  };
    892 
    893  while (!input.Eof()) {
    894    if (seen_iend) {
    895      return JXL_FAILURE("Exuberant input after IEND chunk");
    896    }
    897    Bytes chunk = input.ReadChunk();
    898    if (chunk.empty()) {
    899      return JXL_FAILURE("Malformed chunk");
    900    }
    901    Bytes type(chunk.data() + 4, 4);
    902    id = LoadLE32(type.data());
    903    // Cut 'size' and 'type' at front and 'CRC' at the end.
    904    Bytes payload(chunk.data() + 8, chunk.size() - 12);
    905 
    906    if (!isAbc(type[0]) || !isAbc(type[1]) || !isAbc(type[2]) ||
    907        !isAbc(type[3])) {
    908      return JXL_FAILURE("Exotic PNG chunk");
    909    }
    910 
    911    switch (id) {
    912      case MakeTag('a', 'c', 'T', 'L'):
    913        if (seen_idat) {
    914          JXL_DEBUG_V(2, "aCTL after IDAT ignored");
    915          continue;
    916        }
    917        if (seen_actl) {
    918          JXL_DEBUG_V(2, "Duplicate aCTL chunk ignored");
    919          continue;
    920        }
    921        seen_actl = true;
    922        ppf->info.have_animation = JXL_TRUE;
    923        // TODO(eustas): decode from chunk?
    924        ppf->info.animation.tps_numerator = 1000;
    925        ppf->info.animation.tps_denominator = 1;
    926        continue;
    927 
    928      case MakeTag('I', 'E', 'N', 'D'):
    929        seen_iend = true;
    930        JXL_RETURN_IF_ERROR(finalize_frame());
    931        continue;
    932 
    933      case MakeTag('f', 'c', 'T', 'L'): {
    934        if (payload.size() != 26) {
    935          return JXL_FAILURE("Unexpected fcTL payload size: %u",
    936                             static_cast<uint32_t>(payload.size()));
    937        }
    938        if (seen_fctl && !seen_idat) {
    939          return JXL_FAILURE("More than one fcTL before IDAT");
    940        }
    941        if (seen_idat && !seen_actl) {
    942          return JXL_FAILURE("fcTL after IDAT, but without acTL");
    943        }
    944        seen_fctl = true;
    945 
    946        // TODO(eustas): check order?
    947        // sequence_number = png_get_uint_32(payload.data());
    948        RectT<uint64_t> raw_viewport(png_get_uint_32(payload.data() + 12),
    949                                     png_get_uint_32(payload.data() + 16),
    950                                     png_get_uint_32(payload.data() + 4),
    951                                     png_get_uint_32(payload.data() + 8));
    952        uint8_t dispose_op = payload[24];
    953        if (dispose_op > kLastDisposeOp) {
    954          return JXL_FAILURE("Invalid DisposeOp");
    955        }
    956        uint8_t blend_op = payload[25];
    957        if (blend_op > kLastBlendOp) {
    958          return JXL_FAILURE("Invalid BlendOp");
    959        }
    960        FrameControl next_frame = {
    961            /*delay_num=*/png_get_uint_16(payload.data() + 20),
    962            /*delay_den=*/png_get_uint_16(payload.data() + 22), raw_viewport,
    963            static_cast<DisposeOp>(dispose_op), static_cast<BlendOp>(blend_op)};
    964 
    965        if (!raw_viewport.Intersection(image_rect).IsSame(raw_viewport)) {
    966          // Cropping happened.
    967          return JXL_FAILURE("PNG frame is outside of image rect");
    968        }
    969 
    970        if (!seen_idat) {
    971          // "Default" image is the first animation frame. Its viewport must
    972          // cover the whole image area.
    973          if (!raw_viewport.IsSame(image_rect)) {
    974            return JXL_FAILURE(
    975                "If the first animation frame is default image, its viewport "
    976                "must cover full image");
    977          }
    978        } else {
    979          JXL_RETURN_IF_ERROR(finalize_frame());
    980          if (!ctx.InitPngDecoder(passthrough_chunks, next_frame.viewport)) {
    981            return JXL_FAILURE("Failed to initialize PNG decoder");
    982          }
    983        }
    984        current_frame = next_frame;
    985        continue;
    986      }
    987 
    988      case MakeTag('I', 'D', 'A', 'T'): {
    989        if (!frames.empty()) {
    990          return JXL_FAILURE("IDAT after default image is over");
    991        }
    992        if (!seen_idat) {
    993          // First IDAT means that all metadata is ready.
    994          seen_idat = true;
    995          JXL_ENSURE(image_rect.xsize() ==
    996                     png_get_image_width(ctx.png_ptr, ctx.info_ptr));
    997          JXL_ENSURE(image_rect.ysize() ==
    998                     png_get_image_height(ctx.png_ptr, ctx.info_ptr));
    999          JXL_RETURN_IF_ERROR(VerifyDimensions(constraints, image_rect.xsize(),
   1000                                               image_rect.ysize()));
   1001          ppf->info.xsize = image_rect.xsize();
   1002          ppf->info.ysize = image_rect.ysize();
   1003 
   1004          png_color_8p sig_bits = nullptr;
   1005          // Error is OK -> sig_bits remains nullptr.
   1006          png_get_sBIT(ctx.png_ptr, ctx.info_ptr, &sig_bits);
   1007          SetColorData(ppf, png_get_color_type(ctx.png_ptr, ctx.info_ptr),
   1008                       png_get_bit_depth(ctx.png_ptr, ctx.info_ptr), sig_bits,
   1009                       png_get_valid(ctx.png_ptr, ctx.info_ptr, PNG_INFO_tRNS));
   1010          num_channels =
   1011              ppf->info.num_color_channels + (ppf->info.alpha_bits ? 1 : 0);
   1012          format = {
   1013              /*num_channels=*/num_channels,
   1014              /*data_type=*/ppf->info.bits_per_sample > 8 ? JXL_TYPE_UINT16
   1015                                                          : JXL_TYPE_UINT8,
   1016              /*endianness=*/JXL_BIG_ENDIAN,
   1017              /*align=*/0,
   1018          };
   1019          bytes_per_pixel =
   1020              num_channels * (format.data_type == JXL_TYPE_UINT16 ? 2 : 1);
   1021          // TODO(eustas): ensure multiplication is safe
   1022          uint64_t row_bytes =
   1023              static_cast<uint64_t>(image_rect.xsize()) * bytes_per_pixel;
   1024          uint64_t max_rows = std::numeric_limits<size_t>::max() / row_bytes;
   1025          if (image_rect.ysize() > max_rows) {
   1026            return JXL_FAILURE("Image too big.");
   1027          }
   1028          // TODO(eustas): drop frameRaw
   1029          JXL_RETURN_IF_ERROR(
   1030              ctx.frameRaw.Resize(row_bytes, image_rect.ysize()));
   1031        }
   1032 
   1033        if (!ctx.FeedChunks(chunk)) {
   1034          return JXL_FAILURE("Decoding IDAT failed");
   1035        }
   1036        seen_pixel_data = true;
   1037        continue;
   1038      }
   1039 
   1040      case MakeTag('f', 'd', 'A', 'T'): {
   1041        if (!seen_idat) {
   1042          return JXL_FAILURE("fdAT chunk before IDAT");
   1043        }
   1044        if (!seen_actl) {
   1045          return JXL_FAILURE("fdAT chunk before acTL");
   1046        }
   1047        /* The 'fdAT' chunk has... the same structure as an 'IDAT' chunk,
   1048         * except preceded by a sequence number. */
   1049        if (payload.size() < 4) {
   1050          return JXL_FAILURE("Corrupted fdAT chunk");
   1051        }
   1052        // Turn 'fdAT' to 'IDAT' by cutting sequence number and replacing tag.
   1053        std::array<uint8_t, 8> preamble;
   1054        png_save_uint_32(preamble.data(), payload.size() - 4);
   1055        memcpy(preamble.data() + 4, "IDAT", 4);
   1056        // Cut-off 'size', 'type' and 'sequence_number'
   1057        Bytes chunk_tail(chunk.data() + 12, chunk.size() - 12);
   1058        if (!ctx.FeedChunks(Bytes(preamble), chunk_tail)) {
   1059          return JXL_FAILURE("Decoding fdAT failed");
   1060        }
   1061        seen_pixel_data = true;
   1062        continue;
   1063      }
   1064 
   1065      case MakeTag('c', 'I', 'C', 'P'):
   1066        if (color_info_type == ColorInfoType::CICP) {
   1067          JXL_DEBUG_V(2, "Excessive colorspace definition; cICP chunk ignored");
   1068          continue;
   1069        }
   1070        JXL_RETURN_IF_ERROR(DecodeCicpChunk(payload, &ppf->color_encoding));
   1071        ppf->icc.clear();
   1072        ppf->primary_color_representation =
   1073            PackedPixelFile::kColorEncodingIsPrimary;
   1074        color_info_type = ColorInfoType::CICP;
   1075        continue;
   1076 
   1077      case MakeTag('i', 'C', 'C', 'P'): {
   1078        if (color_info_type == ColorInfoType::ICCP_OR_SRGB) {
   1079          return JXL_FAILURE("Repeated iCCP / sRGB chunk");
   1080        }
   1081        if (color_info_type > ColorInfoType::ICCP_OR_SRGB) {
   1082          JXL_DEBUG_V(2, "Excessive colorspace definition; iCCP chunk ignored");
   1083          continue;
   1084        }
   1085        // Let PNG decoder deal with chunk processing.
   1086        if (!ctx.FeedChunks(chunk)) {
   1087          return JXL_FAILURE("Corrupt iCCP chunk");
   1088        }
   1089 
   1090        // TODO(jon): catch special case of PQ and synthesize color encoding
   1091        // in that case
   1092        int compression_type = 0;
   1093        png_bytep profile = nullptr;
   1094        png_charp name = nullptr;
   1095        png_uint_32 profile_len = 0;
   1096        png_uint_32 ok =
   1097            png_get_iCCP(ctx.png_ptr, ctx.info_ptr, &name, &compression_type,
   1098                         &profile, &profile_len);
   1099        if (!ok || !profile_len) {
   1100          return JXL_FAILURE("Malformed / incomplete iCCP chunk");
   1101        }
   1102        ppf->icc.assign(profile, profile + profile_len);
   1103        ppf->primary_color_representation = PackedPixelFile::kIccIsPrimary;
   1104        color_info_type = ColorInfoType::ICCP_OR_SRGB;
   1105        continue;
   1106      }
   1107 
   1108      case MakeTag('s', 'R', 'G', 'B'):
   1109        if (color_info_type == ColorInfoType::ICCP_OR_SRGB) {
   1110          return JXL_FAILURE("Repeated iCCP / sRGB chunk");
   1111        }
   1112        if (color_info_type > ColorInfoType::ICCP_OR_SRGB) {
   1113          JXL_DEBUG_V(2, "Excessive colorspace definition; sRGB chunk ignored");
   1114          continue;
   1115        }
   1116        JXL_RETURN_IF_ERROR(DecodeSrgbChunk(payload, &ppf->color_encoding));
   1117        color_info_type = ColorInfoType::ICCP_OR_SRGB;
   1118        continue;
   1119 
   1120      case MakeTag('g', 'A', 'M', 'A'):
   1121        if (color_info_type >= ColorInfoType::GAMA_OR_CHRM) {
   1122          JXL_DEBUG_V(2, "Excessive colorspace definition; gAMA chunk ignored");
   1123          continue;
   1124        }
   1125        JXL_RETURN_IF_ERROR(DecodeGamaChunk(payload, &ppf->color_encoding));
   1126        color_info_type = ColorInfoType::GAMA_OR_CHRM;
   1127        continue;
   1128 
   1129      case MakeTag('c', 'H', 'R', 'M'):
   1130        if (color_info_type >= ColorInfoType::GAMA_OR_CHRM) {
   1131          JXL_DEBUG_V(2, "Excessive colorspace definition; cHRM chunk ignored");
   1132          continue;
   1133        }
   1134        JXL_RETURN_IF_ERROR(DecodeChrmChunk(payload, &ppf->color_encoding));
   1135        color_info_type = ColorInfoType::GAMA_OR_CHRM;
   1136        continue;
   1137 
   1138      case MakeTag('c', 'L', 'L', 'i'):
   1139        JXL_RETURN_IF_ERROR(
   1140            DecodeClliChunk(payload, &ppf->info.intensity_target));
   1141        continue;
   1142 
   1143      case MakeTag('e', 'X', 'I', 'f'):
   1144        // TODO(eustas): next eXIF chunk overwrites current; is it ok?
   1145        ppf->metadata.exif.resize(payload.size());
   1146        memcpy(ppf->metadata.exif.data(), payload.data(), payload.size());
   1147        continue;
   1148 
   1149      default:
   1150        // We don't know what is that, just pass through.
   1151        if (!ctx.FeedChunks(chunk)) {
   1152          return JXL_FAILURE("PNG decoder failed to process chunk");
   1153        }
   1154        // If it happens before IDAT, we consider it metadata and pass to all
   1155        // sub-decoders.
   1156        if (!seen_idat) {
   1157          passthrough_chunks.push_back(chunk);
   1158        }
   1159        continue;
   1160    }
   1161  }
   1162 
   1163  bool color_is_already_set = (color_info_type != ColorInfoType::NONE);
   1164  bool is_gray = (ppf->info.num_color_channels == 1);
   1165  JXL_RETURN_IF_ERROR(
   1166      ApplyColorHints(color_hints, color_is_already_set, is_gray, ppf));
   1167 
   1168  if (ppf->color_encoding.transfer_function != JXL_TRANSFER_FUNCTION_PQ) {
   1169    // Reset intensity target, in case we set it from cLLi but TF is not PQ.
   1170    ppf->info.intensity_target = 0.f;
   1171  }
   1172 
   1173  bool has_nontrivial_background = false;
   1174  bool previous_frame_should_be_cleared = false;
   1175  for (size_t i = 0; i < frames.size(); i++) {
   1176    Frame& frame = frames[i];
   1177    const FrameControl& fc = frame.metadata;
   1178    const RectT<uint64_t> vp = fc.viewport;
   1179    const auto& pixels = frame.pixels;
   1180    size_t xsize = pixels.xsize;
   1181    size_t ysize = pixels.ysize;
   1182    JXL_ENSURE(xsize == vp.xsize());
   1183    JXL_ENSURE(ysize == vp.ysize());
   1184 
   1185    // Before encountering a DISPOSE_OP_NONE frame, the canvas is filled with
   1186    // 0, so DISPOSE_OP_BACKGROUND and DISPOSE_OP_PREVIOUS are equivalent.
   1187    if (fc.dispose_op == DisposeOp::NONE) {
   1188      has_nontrivial_background = true;
   1189    }
   1190    bool should_blend = fc.blend_op == BlendOp::OVER;
   1191    bool use_for_next_frame =
   1192        has_nontrivial_background && fc.dispose_op != DisposeOp::PREVIOUS;
   1193    size_t x0 = vp.x0();
   1194    size_t y0 = vp.y0();
   1195    if (previous_frame_should_be_cleared) {
   1196      const auto& pvp = frames[i - 1].metadata.viewport;
   1197      size_t px0 = pvp.x0();
   1198      size_t py0 = pvp.y0();
   1199      size_t pxs = pvp.xsize();
   1200      size_t pys = pvp.ysize();
   1201      if (px0 >= x0 && py0 >= y0 && px0 + pxs <= x0 + xsize &&
   1202          py0 + pys <= y0 + ysize && fc.blend_op == BlendOp::SOURCE &&
   1203          use_for_next_frame) {
   1204        // If the previous frame is entirely contained in the current frame
   1205        // and we are using BLEND_OP_SOURCE, nothing special needs to be done.
   1206        ppf->frames.emplace_back(std::move(frame.pixels));
   1207      } else if (px0 == x0 && py0 == y0 && px0 + pxs == x0 + xsize &&
   1208                 py0 + pys == y0 + ysize && use_for_next_frame) {
   1209        // If the new frame has the same size as the old one, but we are
   1210        // blending, we can instead just not blend.
   1211        should_blend = false;
   1212        ppf->frames.emplace_back(std::move(frame.pixels));
   1213      } else if (px0 <= x0 && py0 <= y0 && px0 + pxs >= x0 + xsize &&
   1214                 py0 + pys >= y0 + ysize && use_for_next_frame) {
   1215        // If the new frame is contained within the old frame, we can pad the
   1216        // new frame with zeros and not blend.
   1217        JXL_ASSIGN_OR_RETURN(PackedImage new_data,
   1218                             PackedImage::Create(pxs, pys, pixels.format));
   1219        memset(new_data.pixels(), 0, new_data.pixels_size);
   1220        for (size_t y = 0; y < ysize; y++) {
   1221          JXL_RETURN_IF_ERROR(
   1222              PackedImage::ValidateDataType(new_data.format.data_type));
   1223          size_t bytes_per_pixel =
   1224              PackedImage::BitsPerChannel(new_data.format.data_type) *
   1225              new_data.format.num_channels / 8;
   1226          memcpy(
   1227              static_cast<uint8_t*>(new_data.pixels()) +
   1228                  new_data.stride * (y + y0 - py0) +
   1229                  bytes_per_pixel * (x0 - px0),
   1230              static_cast<const uint8_t*>(pixels.pixels()) + pixels.stride * y,
   1231              xsize * bytes_per_pixel);
   1232        }
   1233 
   1234        x0 = px0;
   1235        y0 = py0;
   1236        xsize = pxs;
   1237        ysize = pys;
   1238        should_blend = false;
   1239        ppf->frames.emplace_back(std::move(new_data));
   1240      } else {
   1241        // If all else fails, insert a placeholder blank frame with kReplace.
   1242        JXL_ASSIGN_OR_RETURN(PackedImage blank,
   1243                             PackedImage::Create(pxs, pys, pixels.format));
   1244        memset(blank.pixels(), 0, blank.pixels_size);
   1245        ppf->frames.emplace_back(std::move(blank));
   1246        auto& pframe = ppf->frames.back();
   1247        pframe.frame_info.layer_info.crop_x0 = px0;
   1248        pframe.frame_info.layer_info.crop_y0 = py0;
   1249        pframe.frame_info.layer_info.xsize = pxs;
   1250        pframe.frame_info.layer_info.ysize = pys;
   1251        pframe.frame_info.duration = 0;
   1252        bool is_full_size = px0 == 0 && py0 == 0 && pxs == ppf->info.xsize &&
   1253                            pys == ppf->info.ysize;
   1254        pframe.frame_info.layer_info.have_crop = is_full_size ? 0 : 1;
   1255        pframe.frame_info.layer_info.blend_info.blendmode = JXL_BLEND_REPLACE;
   1256        pframe.frame_info.layer_info.blend_info.source = 1;
   1257        pframe.frame_info.layer_info.save_as_reference = 1;
   1258        ppf->frames.emplace_back(std::move(frame.pixels));
   1259      }
   1260    } else {
   1261      ppf->frames.emplace_back(std::move(frame.pixels));
   1262    }
   1263 
   1264    auto& pframe = ppf->frames.back();
   1265    pframe.frame_info.layer_info.crop_x0 = x0;
   1266    pframe.frame_info.layer_info.crop_y0 = y0;
   1267    pframe.frame_info.layer_info.xsize = xsize;
   1268    pframe.frame_info.layer_info.ysize = ysize;
   1269    pframe.frame_info.duration =
   1270        fc.delay_num * 1000 / (fc.delay_den ? fc.delay_den : 100);
   1271    pframe.frame_info.layer_info.blend_info.blendmode =
   1272        should_blend ? JXL_BLEND_BLEND : JXL_BLEND_REPLACE;
   1273    bool is_full_size = x0 == 0 && y0 == 0 && xsize == ppf->info.xsize &&
   1274                        ysize == ppf->info.ysize;
   1275    pframe.frame_info.layer_info.have_crop = is_full_size ? 0 : 1;
   1276    pframe.frame_info.layer_info.blend_info.source = 1;
   1277    pframe.frame_info.layer_info.blend_info.alpha = 0;
   1278    pframe.frame_info.layer_info.save_as_reference = use_for_next_frame ? 1 : 0;
   1279 
   1280    previous_frame_should_be_cleared =
   1281        has_nontrivial_background && (fc.dispose_op == DisposeOp::BACKGROUND);
   1282  }
   1283 
   1284  if (ppf->frames.empty()) return JXL_FAILURE("No frames decoded");
   1285  ppf->frames.back().frame_info.is_last = JXL_TRUE;
   1286 
   1287  return true;
   1288 }
   1289 
   1290 #endif  // JPEGXL_ENABLE_APNG
   1291 
   1292 }  // namespace extras
   1293 }  // namespace jxl