libjxl

FORK: libjxl patches used on blog
git clone https://git.neptards.moe/blog/libjxl.git
Log | Files | Refs | Submodules | README | LICENSE

apng.cc (37785B)


      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 #include <string.h>
     42 
     43 #include <string>
     44 #include <utility>
     45 #include <vector>
     46 
     47 #include "lib/extras/size_constraints.h"
     48 #include "lib/jxl/base/byte_order.h"
     49 #include "lib/jxl/base/common.h"
     50 #include "lib/jxl/base/compiler_specific.h"
     51 #include "lib/jxl/base/printf_macros.h"
     52 #include "lib/jxl/base/scope_guard.h"
     53 #include "lib/jxl/sanitizers.h"
     54 #if JPEGXL_ENABLE_APNG
     55 #include "png.h" /* original (unpatched) libpng is ok */
     56 #endif
     57 
     58 namespace jxl {
     59 namespace extras {
     60 
     61 #if JPEGXL_ENABLE_APNG
     62 namespace {
     63 
     64 constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69,
     65                                              0x66, 0x00, 0x00};
     66 
     67 /* hIST chunk tail is not proccesed properly; skip this chunk completely;
     68    see https://github.com/glennrp/libpng/pull/413 */
     69 const png_byte kIgnoredPngChunks[] = {
     70     104, 73, 83, 84, '\0' /* hIST */
     71 };
     72 
     73 // Returns floating-point value from the PNG encoding (times 10^5).
     74 double F64FromU32(const uint32_t x) { return static_cast<int32_t>(x) * 1E-5; }
     75 
     76 Status DecodeSRGB(const unsigned char* payload, const size_t payload_size,
     77                   JxlColorEncoding* color_encoding) {
     78   if (payload_size != 1) return JXL_FAILURE("Wrong sRGB size");
     79   // (PNG uses the same values as ICC.)
     80   if (payload[0] >= 4) return JXL_FAILURE("Invalid Rendering Intent");
     81   color_encoding->white_point = JXL_WHITE_POINT_D65;
     82   color_encoding->primaries = JXL_PRIMARIES_SRGB;
     83   color_encoding->transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
     84   color_encoding->rendering_intent =
     85       static_cast<JxlRenderingIntent>(payload[0]);
     86   return true;
     87 }
     88 
     89 // If the cICP profile is not fully supported, return false and leave
     90 // color_encoding unmodified.
     91 Status DecodeCICP(const unsigned char* payload, const size_t payload_size,
     92                   JxlColorEncoding* color_encoding) {
     93   if (payload_size != 4) return JXL_FAILURE("Wrong cICP size");
     94   JxlColorEncoding color_enc = *color_encoding;
     95 
     96   // From https://www.itu.int/rec/T-REC-H.273-202107-I/en
     97   if (payload[0] == 1) {
     98     // IEC 61966-2-1 sRGB
     99     color_enc.primaries = JXL_PRIMARIES_SRGB;
    100     color_enc.white_point = JXL_WHITE_POINT_D65;
    101   } else if (payload[0] == 4) {
    102     // Rec. ITU-R BT.470-6 System M
    103     color_enc.primaries = JXL_PRIMARIES_CUSTOM;
    104     color_enc.primaries_red_xy[0] = 0.67;
    105     color_enc.primaries_red_xy[1] = 0.33;
    106     color_enc.primaries_green_xy[0] = 0.21;
    107     color_enc.primaries_green_xy[1] = 0.71;
    108     color_enc.primaries_blue_xy[0] = 0.14;
    109     color_enc.primaries_blue_xy[1] = 0.08;
    110     color_enc.white_point = JXL_WHITE_POINT_CUSTOM;
    111     color_enc.white_point_xy[0] = 0.310;
    112     color_enc.white_point_xy[1] = 0.316;
    113   } else if (payload[0] == 5) {
    114     // Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM
    115     color_enc.primaries = JXL_PRIMARIES_CUSTOM;
    116     color_enc.primaries_red_xy[0] = 0.64;
    117     color_enc.primaries_red_xy[1] = 0.33;
    118     color_enc.primaries_green_xy[0] = 0.29;
    119     color_enc.primaries_green_xy[1] = 0.60;
    120     color_enc.primaries_blue_xy[0] = 0.15;
    121     color_enc.primaries_blue_xy[1] = 0.06;
    122     color_enc.white_point = JXL_WHITE_POINT_D65;
    123   } else if (payload[0] == 6 || payload[0] == 7) {
    124     // SMPTE ST 170 (2004) / SMPTE ST 240 (1999)
    125     color_enc.primaries = JXL_PRIMARIES_CUSTOM;
    126     color_enc.primaries_red_xy[0] = 0.630;
    127     color_enc.primaries_red_xy[1] = 0.340;
    128     color_enc.primaries_green_xy[0] = 0.310;
    129     color_enc.primaries_green_xy[1] = 0.595;
    130     color_enc.primaries_blue_xy[0] = 0.155;
    131     color_enc.primaries_blue_xy[1] = 0.070;
    132     color_enc.white_point = JXL_WHITE_POINT_D65;
    133   } else if (payload[0] == 8) {
    134     // Generic film (colour filters using Illuminant C)
    135     color_enc.primaries = JXL_PRIMARIES_CUSTOM;
    136     color_enc.primaries_red_xy[0] = 0.681;
    137     color_enc.primaries_red_xy[1] = 0.319;
    138     color_enc.primaries_green_xy[0] = 0.243;
    139     color_enc.primaries_green_xy[1] = 0.692;
    140     color_enc.primaries_blue_xy[0] = 0.145;
    141     color_enc.primaries_blue_xy[1] = 0.049;
    142     color_enc.white_point = JXL_WHITE_POINT_CUSTOM;
    143     color_enc.white_point_xy[0] = 0.310;
    144     color_enc.white_point_xy[1] = 0.316;
    145   } else if (payload[0] == 9) {
    146     // Rec. ITU-R BT.2100-2
    147     color_enc.primaries = JXL_PRIMARIES_2100;
    148     color_enc.white_point = JXL_WHITE_POINT_D65;
    149   } else if (payload[0] == 10) {
    150     // CIE 1931 XYZ
    151     color_enc.primaries = JXL_PRIMARIES_CUSTOM;
    152     color_enc.primaries_red_xy[0] = 1;
    153     color_enc.primaries_red_xy[1] = 0;
    154     color_enc.primaries_green_xy[0] = 0;
    155     color_enc.primaries_green_xy[1] = 1;
    156     color_enc.primaries_blue_xy[0] = 0;
    157     color_enc.primaries_blue_xy[1] = 0;
    158     color_enc.white_point = JXL_WHITE_POINT_E;
    159   } else if (payload[0] == 11) {
    160     // SMPTE RP 431-2 (2011)
    161     color_enc.primaries = JXL_PRIMARIES_P3;
    162     color_enc.white_point = JXL_WHITE_POINT_DCI;
    163   } else if (payload[0] == 12) {
    164     // SMPTE EG 432-1 (2010)
    165     color_enc.primaries = JXL_PRIMARIES_P3;
    166     color_enc.white_point = JXL_WHITE_POINT_D65;
    167   } else if (payload[0] == 22) {
    168     color_enc.primaries = JXL_PRIMARIES_CUSTOM;
    169     color_enc.primaries_red_xy[0] = 0.630;
    170     color_enc.primaries_red_xy[1] = 0.340;
    171     color_enc.primaries_green_xy[0] = 0.295;
    172     color_enc.primaries_green_xy[1] = 0.605;
    173     color_enc.primaries_blue_xy[0] = 0.155;
    174     color_enc.primaries_blue_xy[1] = 0.077;
    175     color_enc.white_point = JXL_WHITE_POINT_D65;
    176   } else {
    177     JXL_WARNING("Unsupported primaries specified in cICP chunk: %d",
    178                 static_cast<int>(payload[0]));
    179     return false;
    180   }
    181 
    182   if (payload[1] == 1 || payload[1] == 6 || payload[1] == 14 ||
    183       payload[1] == 15) {
    184     // Rec. ITU-R BT.709-6
    185     color_enc.transfer_function = JXL_TRANSFER_FUNCTION_709;
    186   } else if (payload[1] == 4) {
    187     // Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM
    188     color_enc.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
    189     color_enc.gamma = 1 / 2.2;
    190   } else if (payload[1] == 5) {
    191     // Rec. ITU-R BT.470-6 System B, G
    192     color_enc.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
    193     color_enc.gamma = 1 / 2.8;
    194   } else if (payload[1] == 8 || payload[1] == 13 || payload[1] == 16 ||
    195              payload[1] == 17 || payload[1] == 18) {
    196     // These codes all match the corresponding JXL enum values
    197     color_enc.transfer_function = static_cast<JxlTransferFunction>(payload[1]);
    198   } else {
    199     JXL_WARNING("Unsupported transfer function specified in cICP chunk: %d",
    200                 static_cast<int>(payload[1]));
    201     return false;
    202   }
    203 
    204   if (payload[2] != 0) {
    205     JXL_WARNING("Unsupported color space specified in cICP chunk: %d",
    206                 static_cast<int>(payload[2]));
    207     return false;
    208   }
    209   if (payload[3] != 1) {
    210     JXL_WARNING("Unsupported full-range flag specified in cICP chunk: %d",
    211                 static_cast<int>(payload[3]));
    212     return false;
    213   }
    214   // cICP has no rendering intent, so use the default
    215   color_enc.rendering_intent = JXL_RENDERING_INTENT_RELATIVE;
    216   *color_encoding = color_enc;
    217   return true;
    218 }
    219 
    220 Status DecodeGAMA(const unsigned char* payload, const size_t payload_size,
    221                   JxlColorEncoding* color_encoding) {
    222   if (payload_size != 4) return JXL_FAILURE("Wrong gAMA size");
    223   color_encoding->transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
    224   color_encoding->gamma = F64FromU32(LoadBE32(payload));
    225   return true;
    226 }
    227 
    228 Status DecodeCHRM(const unsigned char* payload, const size_t payload_size,
    229                   JxlColorEncoding* color_encoding) {
    230   if (payload_size != 32) return JXL_FAILURE("Wrong cHRM size");
    231 
    232   color_encoding->white_point = JXL_WHITE_POINT_CUSTOM;
    233   color_encoding->white_point_xy[0] = F64FromU32(LoadBE32(payload + 0));
    234   color_encoding->white_point_xy[1] = F64FromU32(LoadBE32(payload + 4));
    235 
    236   color_encoding->primaries = JXL_PRIMARIES_CUSTOM;
    237   color_encoding->primaries_red_xy[0] = F64FromU32(LoadBE32(payload + 8));
    238   color_encoding->primaries_red_xy[1] = F64FromU32(LoadBE32(payload + 12));
    239   color_encoding->primaries_green_xy[0] = F64FromU32(LoadBE32(payload + 16));
    240   color_encoding->primaries_green_xy[1] = F64FromU32(LoadBE32(payload + 20));
    241   color_encoding->primaries_blue_xy[0] = F64FromU32(LoadBE32(payload + 24));
    242   color_encoding->primaries_blue_xy[1] = F64FromU32(LoadBE32(payload + 28));
    243   return true;
    244 }
    245 
    246 // Retrieves XMP and EXIF/IPTC from itext and text.
    247 class BlobsReaderPNG {
    248  public:
    249   static Status Decode(const png_text_struct& info, PackedMetadata* metadata) {
    250     // We trust these are properly null-terminated by libpng.
    251     const char* key = info.key;
    252     const char* value = info.text;
    253     if (strstr(key, "XML:com.adobe.xmp")) {
    254       metadata->xmp.resize(strlen(value));  // safe, see above
    255       memcpy(metadata->xmp.data(), value, metadata->xmp.size());
    256     }
    257 
    258     std::string type;
    259     std::vector<uint8_t> bytes;
    260 
    261     // Handle text chunks annotated with key "Raw profile type ####", with
    262     // #### a type, which may contain metadata.
    263     const char* kKey = "Raw profile type ";
    264     if (strncmp(key, kKey, strlen(kKey)) != 0) return false;
    265 
    266     if (!MaybeDecodeBase16(key, value, &type, &bytes)) {
    267       JXL_WARNING("Couldn't parse 'Raw format type' text chunk");
    268       return false;
    269     }
    270     if (type == "exif") {
    271       // Remove "Exif\0\0" prefix if present
    272       if (bytes.size() >= sizeof kExifSignature &&
    273           memcmp(bytes.data(), kExifSignature, sizeof kExifSignature) == 0) {
    274         bytes.erase(bytes.begin(), bytes.begin() + sizeof kExifSignature);
    275       }
    276       if (!metadata->exif.empty()) {
    277         JXL_WARNING("overwriting EXIF (%" PRIuS " bytes) with base16 (%" PRIuS
    278                     " bytes)",
    279                     metadata->exif.size(), bytes.size());
    280       }
    281       metadata->exif = std::move(bytes);
    282     } else if (type == "iptc") {
    283       // TODO(jon): Deal with IPTC in some way
    284     } else if (type == "8bim") {
    285       // TODO(jon): Deal with 8bim in some way
    286     } else if (type == "xmp") {
    287       if (!metadata->xmp.empty()) {
    288         JXL_WARNING("overwriting XMP (%" PRIuS " bytes) with base16 (%" PRIuS
    289                     " bytes)",
    290                     metadata->xmp.size(), bytes.size());
    291       }
    292       metadata->xmp = std::move(bytes);
    293     } else {
    294       JXL_WARNING("Unknown type in 'Raw format type' text chunk: %s: %" PRIuS
    295                   " bytes",
    296                   type.c_str(), bytes.size());
    297     }
    298     return true;
    299   }
    300 
    301  private:
    302   // Returns false if invalid.
    303   static JXL_INLINE Status DecodeNibble(const char c,
    304                                         uint32_t* JXL_RESTRICT nibble) {
    305     if ('a' <= c && c <= 'f') {
    306       *nibble = 10 + c - 'a';
    307     } else if ('0' <= c && c <= '9') {
    308       *nibble = c - '0';
    309     } else {
    310       *nibble = 0;
    311       return JXL_FAILURE("Invalid metadata nibble");
    312     }
    313     JXL_ASSERT(*nibble < 16);
    314     return true;
    315   }
    316 
    317   // Returns false if invalid.
    318   static JXL_INLINE Status DecodeDecimal(const char** pos, const char* end,
    319                                          uint32_t* JXL_RESTRICT value) {
    320     size_t len = 0;
    321     *value = 0;
    322     while (*pos < end) {
    323       char next = **pos;
    324       if (next >= '0' && next <= '9') {
    325         *value = (*value * 10) + static_cast<uint32_t>(next - '0');
    326         len++;
    327         if (len > 8) {
    328           break;
    329         }
    330       } else {
    331         // Do not consume terminator (non-decimal digit).
    332         break;
    333       }
    334       (*pos)++;
    335     }
    336     if (len == 0 || len > 8) {
    337       return JXL_FAILURE("Failed to parse decimal");
    338     }
    339     return true;
    340   }
    341 
    342   // Parses a PNG text chunk with key of the form "Raw profile type ####", with
    343   // #### a type.
    344   // Returns whether it could successfully parse the content.
    345   // We trust key and encoded are null-terminated because they come from
    346   // libpng.
    347   static Status MaybeDecodeBase16(const char* key, const char* encoded,
    348                                   std::string* type,
    349                                   std::vector<uint8_t>* bytes) {
    350     const char* encoded_end = encoded + strlen(encoded);
    351 
    352     const char* kKey = "Raw profile type ";
    353     if (strncmp(key, kKey, strlen(kKey)) != 0) return false;
    354     *type = key + strlen(kKey);
    355     const size_t kMaxTypeLen = 20;
    356     if (type->length() > kMaxTypeLen) return false;  // Type too long
    357 
    358     // Header: freeform string and number of bytes
    359     // Expected format is:
    360     // \n
    361     // profile name/description\n
    362     //       40\n               (the number of bytes after hex-decoding)
    363     // 01234566789abcdef....\n  (72 bytes per line max).
    364     // 012345667\n              (last line)
    365     const char* pos = encoded;
    366 
    367     if (*(pos++) != '\n') return false;
    368     while (pos < encoded_end && *pos != '\n') {
    369       pos++;
    370     }
    371     if (pos == encoded_end) return false;
    372     // We parsed so far a \n, some number of non \n characters and are now
    373     // pointing at a \n.
    374     if (*(pos++) != '\n') return false;
    375     // Skip leading spaces
    376     while (pos < encoded_end && *pos == ' ') {
    377       pos++;
    378     }
    379     uint32_t bytes_to_decode = 0;
    380     JXL_RETURN_IF_ERROR(DecodeDecimal(&pos, encoded_end, &bytes_to_decode));
    381 
    382     // We need 2*bytes for the hex values plus 1 byte every 36 values,
    383     // plus terminal \n for length.
    384     const unsigned long needed_bytes =
    385         bytes_to_decode * 2 + 1 + DivCeil(bytes_to_decode, 36);
    386     if (needed_bytes != static_cast<size_t>(encoded_end - pos)) {
    387       return JXL_FAILURE("Not enough bytes to parse %d bytes in hex",
    388                          bytes_to_decode);
    389     }
    390     JXL_ASSERT(bytes->empty());
    391     bytes->reserve(bytes_to_decode);
    392 
    393     // Encoding: base16 with newline after 72 chars.
    394     // pos points to the \n before the first line of hex values.
    395     for (size_t i = 0; i < bytes_to_decode; ++i) {
    396       if (i % 36 == 0) {
    397         if (pos + 1 >= encoded_end) return false;  // Truncated base16 1
    398         if (*pos != '\n') return false;            // Expected newline
    399         ++pos;
    400       }
    401 
    402       if (pos + 2 >= encoded_end) return false;  // Truncated base16 2;
    403       uint32_t nibble0;
    404       uint32_t nibble1;
    405       JXL_RETURN_IF_ERROR(DecodeNibble(pos[0], &nibble0));
    406       JXL_RETURN_IF_ERROR(DecodeNibble(pos[1], &nibble1));
    407       bytes->push_back(static_cast<uint8_t>((nibble0 << 4) + nibble1));
    408       pos += 2;
    409     }
    410     if (pos + 1 != encoded_end) return false;  // Too many encoded bytes
    411     if (pos[0] != '\n') return false;          // Incorrect metadata terminator
    412     return true;
    413   }
    414 };
    415 
    416 constexpr bool isAbc(char c) {
    417   return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
    418 }
    419 
    420 constexpr uint32_t kId_IHDR = 0x52444849;
    421 constexpr uint32_t kId_acTL = 0x4C546361;
    422 constexpr uint32_t kId_fcTL = 0x4C546366;
    423 constexpr uint32_t kId_IDAT = 0x54414449;
    424 constexpr uint32_t kId_fdAT = 0x54416466;
    425 constexpr uint32_t kId_IEND = 0x444E4549;
    426 constexpr uint32_t kId_cICP = 0x50434963;
    427 constexpr uint32_t kId_iCCP = 0x50434369;
    428 constexpr uint32_t kId_sRGB = 0x42475273;
    429 constexpr uint32_t kId_gAMA = 0x414D4167;
    430 constexpr uint32_t kId_cHRM = 0x4D524863;
    431 constexpr uint32_t kId_eXIf = 0x66495865;
    432 
    433 struct APNGFrame {
    434   std::vector<uint8_t> pixels;
    435   std::vector<uint8_t*> rows;
    436   unsigned int w, h, delay_num, delay_den;
    437 };
    438 
    439 struct Reader {
    440   const uint8_t* next;
    441   const uint8_t* last;
    442   bool Read(void* data, size_t len) {
    443     size_t cap = last - next;
    444     size_t to_copy = std::min(cap, len);
    445     memcpy(data, next, to_copy);
    446     next += to_copy;
    447     return (len == to_copy);
    448   }
    449   bool Eof() const { return next == last; }
    450 };
    451 
    452 const unsigned long cMaxPNGSize = 1000000UL;
    453 const size_t kMaxPNGChunkSize = 1lu << 30;  // 1 GB
    454 
    455 void info_fn(png_structp png_ptr, png_infop info_ptr) {
    456   png_set_expand(png_ptr);
    457   png_set_palette_to_rgb(png_ptr);
    458   png_set_tRNS_to_alpha(png_ptr);
    459   (void)png_set_interlace_handling(png_ptr);
    460   png_read_update_info(png_ptr, info_ptr);
    461 }
    462 
    463 void row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num,
    464             int pass) {
    465   APNGFrame* frame =
    466       reinterpret_cast<APNGFrame*>(png_get_progressive_ptr(png_ptr));
    467   JXL_CHECK(frame);
    468   JXL_CHECK(row_num < frame->rows.size());
    469   JXL_CHECK(frame->rows[row_num] < frame->pixels.data() + frame->pixels.size());
    470   png_progressive_combine_row(png_ptr, frame->rows[row_num], new_row);
    471 }
    472 
    473 inline unsigned int read_chunk(Reader* r, std::vector<uint8_t>* pChunk) {
    474   unsigned char len[4];
    475   if (r->Read(&len, 4)) {
    476     const auto size = png_get_uint_32(len);
    477     // Check first, to avoid overflow.
    478     if (size > kMaxPNGChunkSize) {
    479       JXL_WARNING("APNG chunk size is too big");
    480       return 0;
    481     }
    482     pChunk->resize(size + 12);
    483     memcpy(pChunk->data(), len, 4);
    484     if (r->Read(pChunk->data() + 4, pChunk->size() - 4)) {
    485       return LoadLE32(pChunk->data() + 4);
    486     }
    487   }
    488   return 0;
    489 }
    490 
    491 int processing_start(png_structp& png_ptr, png_infop& info_ptr, void* frame_ptr,
    492                      bool hasInfo, std::vector<uint8_t>& chunkIHDR,
    493                      std::vector<std::vector<uint8_t>>& chunksInfo) {
    494   unsigned char header[8] = {137, 80, 78, 71, 13, 10, 26, 10};
    495 
    496   // Cleanup prior decoder, if any.
    497   png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
    498   // Just in case. Not all versions on libpng wipe-out the pointers.
    499   png_ptr = nullptr;
    500   info_ptr = nullptr;
    501 
    502   png_ptr =
    503       png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
    504   info_ptr = png_create_info_struct(png_ptr);
    505   if (!png_ptr || !info_ptr) return 1;
    506 
    507   if (setjmp(png_jmpbuf(png_ptr))) {
    508     return 1;
    509   }
    510 
    511   png_set_keep_unknown_chunks(png_ptr, 1, kIgnoredPngChunks,
    512                               static_cast<int>(sizeof(kIgnoredPngChunks) / 5));
    513 
    514   png_set_crc_action(png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE);
    515   png_set_progressive_read_fn(png_ptr, frame_ptr, info_fn, row_fn, nullptr);
    516 
    517   png_process_data(png_ptr, info_ptr, header, 8);
    518   png_process_data(png_ptr, info_ptr, chunkIHDR.data(), chunkIHDR.size());
    519 
    520   if (hasInfo) {
    521     for (auto& chunk : chunksInfo) {
    522       png_process_data(png_ptr, info_ptr, chunk.data(), chunk.size());
    523     }
    524   }
    525   return 0;
    526 }
    527 
    528 int processing_data(png_structp png_ptr, png_infop info_ptr, unsigned char* p,
    529                     unsigned int size) {
    530   if (!png_ptr || !info_ptr) return 1;
    531 
    532   if (setjmp(png_jmpbuf(png_ptr))) {
    533     return 1;
    534   }
    535 
    536   png_process_data(png_ptr, info_ptr, p, size);
    537   return 0;
    538 }
    539 
    540 int processing_finish(png_structp png_ptr, png_infop info_ptr,
    541                       PackedMetadata* metadata) {
    542   unsigned char footer[12] = {0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130};
    543 
    544   if (!png_ptr || !info_ptr) return 1;
    545 
    546   if (setjmp(png_jmpbuf(png_ptr))) {
    547     return 1;
    548   }
    549 
    550   png_process_data(png_ptr, info_ptr, footer, 12);
    551   // before destroying: check if we encountered any metadata chunks
    552   png_textp text_ptr;
    553   int num_text;
    554   png_get_text(png_ptr, info_ptr, &text_ptr, &num_text);
    555   for (int i = 0; i < num_text; i++) {
    556     (void)BlobsReaderPNG::Decode(text_ptr[i], metadata);
    557   }
    558 
    559   return 0;
    560 }
    561 
    562 }  // namespace
    563 #endif
    564 
    565 bool CanDecodeAPNG() {
    566 #if JPEGXL_ENABLE_APNG
    567   return true;
    568 #else
    569   return false;
    570 #endif
    571 }
    572 
    573 Status DecodeImageAPNG(const Span<const uint8_t> bytes,
    574                        const ColorHints& color_hints, PackedPixelFile* ppf,
    575                        const SizeConstraints* constraints) {
    576 #if JPEGXL_ENABLE_APNG
    577   Reader r;
    578   unsigned char sig[8];
    579   png_structp png_ptr = nullptr;
    580   png_infop info_ptr = nullptr;
    581   std::vector<uint8_t> chunk;
    582   std::vector<uint8_t> chunkIHDR;
    583   std::vector<std::vector<uint8_t>> chunksInfo;
    584   bool isAnimated = false;
    585   bool hasInfo = false;
    586   bool seenFctl = false;
    587   APNGFrame frameRaw = {};
    588   uint32_t num_channels;
    589   JxlPixelFormat format;
    590   unsigned int bytes_per_pixel = 0;
    591 
    592   struct FrameInfo {
    593     PackedImage data;
    594     uint32_t duration;
    595     size_t x0, xsize;
    596     size_t y0, ysize;
    597     uint32_t dispose_op;
    598     uint32_t blend_op;
    599   };
    600 
    601   std::vector<FrameInfo> frames;
    602 
    603   // Make sure png memory is released in any case.
    604   auto scope_guard = MakeScopeGuard([&]() {
    605     png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
    606     // Just in case. Not all versions on libpng wipe-out the pointers.
    607     png_ptr = nullptr;
    608     info_ptr = nullptr;
    609   });
    610 
    611   r = {bytes.data(), bytes.data() + bytes.size()};
    612   // Not a PNG => not an error
    613   unsigned char png_signature[8] = {137, 80, 78, 71, 13, 10, 26, 10};
    614   if (!r.Read(sig, 8) || memcmp(sig, png_signature, 8) != 0) {
    615     return false;
    616   }
    617   unsigned int id = read_chunk(&r, &chunkIHDR);
    618 
    619   ppf->info.exponent_bits_per_sample = 0;
    620   ppf->info.alpha_exponent_bits = 0;
    621   ppf->info.orientation = JXL_ORIENT_IDENTITY;
    622 
    623   ppf->frames.clear();
    624 
    625   bool have_color = false;
    626   bool have_cicp = false;
    627   bool have_iccp = false;
    628   bool have_srgb = false;
    629   bool errorstate = true;
    630   if (id == kId_IHDR && chunkIHDR.size() == 25) {
    631     unsigned int x0 = 0;
    632     unsigned int y0 = 0;
    633     unsigned int delay_num = 1;
    634     unsigned int delay_den = 10;
    635     unsigned int dop = 0;
    636     unsigned int bop = 0;
    637 
    638     unsigned int w = png_get_uint_32(chunkIHDR.data() + 8);
    639     unsigned int h = png_get_uint_32(chunkIHDR.data() + 12);
    640     unsigned int w0 = w;
    641     unsigned int h0 = h;
    642     if (w > cMaxPNGSize || h > cMaxPNGSize) {
    643       return false;
    644     }
    645 
    646     // default settings in case e.g. only gAMA is given
    647     ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB;
    648     ppf->color_encoding.white_point = JXL_WHITE_POINT_D65;
    649     ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB;
    650     ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
    651     ppf->color_encoding.rendering_intent = JXL_RENDERING_INTENT_RELATIVE;
    652 
    653     if (!processing_start(png_ptr, info_ptr, static_cast<void*>(&frameRaw),
    654                           hasInfo, chunkIHDR, chunksInfo)) {
    655       while (!r.Eof()) {
    656         id = read_chunk(&r, &chunk);
    657         if (!id) break;
    658         seenFctl |= (id == kId_fcTL);
    659 
    660         if (id == kId_acTL && !hasInfo && !isAnimated) {
    661           isAnimated = true;
    662           ppf->info.have_animation = JXL_TRUE;
    663           ppf->info.animation.tps_numerator = 1000;
    664           ppf->info.animation.tps_denominator = 1;
    665         } else if (id == kId_IEND ||
    666                    (id == kId_fcTL && (!hasInfo || isAnimated))) {
    667           if (hasInfo) {
    668             if (!processing_finish(png_ptr, info_ptr, &ppf->metadata)) {
    669               // Allocates the frame buffer.
    670               uint32_t duration = delay_num * 1000 / delay_den;
    671               frames.push_back(FrameInfo{PackedImage(w0, h0, format), duration,
    672                                          x0, w0, y0, h0, dop, bop});
    673               auto& frame = frames.back().data;
    674               for (size_t y = 0; y < h0; ++y) {
    675                 memcpy(static_cast<uint8_t*>(frame.pixels()) + frame.stride * y,
    676                        frameRaw.rows[y], bytes_per_pixel * w0);
    677               }
    678             } else {
    679               break;
    680             }
    681           }
    682 
    683           if (id == kId_IEND) {
    684             errorstate = false;
    685             break;
    686           }
    687           if (chunk.size() < 34) {
    688             return JXL_FAILURE("Received a chunk that is too small (%" PRIuS
    689                                "B)",
    690                                chunk.size());
    691           }
    692           // At this point the old frame is done. Let's start a new one.
    693           w0 = png_get_uint_32(chunk.data() + 12);
    694           h0 = png_get_uint_32(chunk.data() + 16);
    695           x0 = png_get_uint_32(chunk.data() + 20);
    696           y0 = png_get_uint_32(chunk.data() + 24);
    697           delay_num = png_get_uint_16(chunk.data() + 28);
    698           delay_den = png_get_uint_16(chunk.data() + 30);
    699           dop = chunk[32];
    700           bop = chunk[33];
    701 
    702           if (!delay_den) delay_den = 100;
    703 
    704           if (w0 > cMaxPNGSize || h0 > cMaxPNGSize || x0 > cMaxPNGSize ||
    705               y0 > cMaxPNGSize || x0 + w0 > w || y0 + h0 > h || dop > 2 ||
    706               bop > 1) {
    707             break;
    708           }
    709 
    710           if (hasInfo) {
    711             memcpy(chunkIHDR.data() + 8, chunk.data() + 12, 8);
    712             if (processing_start(png_ptr, info_ptr,
    713                                  static_cast<void*>(&frameRaw), hasInfo,
    714                                  chunkIHDR, chunksInfo)) {
    715               break;
    716             }
    717           }
    718         } else if (id == kId_IDAT) {
    719           // First IDAT chunk means we now have all header info
    720           if (seenFctl) {
    721             // `fcTL` chunk must appear after all `IDAT` chunks
    722             return JXL_FAILURE("IDAT chunk after fcTL chunk");
    723           }
    724           hasInfo = true;
    725           JXL_CHECK(w == png_get_image_width(png_ptr, info_ptr));
    726           JXL_CHECK(h == png_get_image_height(png_ptr, info_ptr));
    727           int colortype = png_get_color_type(png_ptr, info_ptr);
    728           int png_bit_depth = png_get_bit_depth(png_ptr, info_ptr);
    729           ppf->info.bits_per_sample = png_bit_depth;
    730           png_color_8p sigbits = nullptr;
    731           png_get_sBIT(png_ptr, info_ptr, &sigbits);
    732           if (colortype & 1) {
    733             // palette will actually be 8-bit regardless of the index bitdepth
    734             ppf->info.bits_per_sample = 8;
    735           }
    736           if (colortype & 2) {
    737             ppf->info.num_color_channels = 3;
    738             ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB;
    739             if (sigbits && sigbits->red == sigbits->green &&
    740                 sigbits->green == sigbits->blue) {
    741               ppf->info.bits_per_sample = sigbits->red;
    742             } else if (sigbits) {
    743               int maxbps = std::max(sigbits->red,
    744                                     std::max(sigbits->green, sigbits->blue));
    745               JXL_WARNING(
    746                   "sBIT chunk: bit depths for R, G, and B are not the same (%i "
    747                   "%i %i), while in JPEG XL they have to be the same. Setting "
    748                   "RGB bit depth to %i.",
    749                   sigbits->red, sigbits->green, sigbits->blue, maxbps);
    750               ppf->info.bits_per_sample = maxbps;
    751             }
    752           } else {
    753             ppf->info.num_color_channels = 1;
    754             ppf->color_encoding.color_space = JXL_COLOR_SPACE_GRAY;
    755             if (sigbits) ppf->info.bits_per_sample = sigbits->gray;
    756           }
    757           if (colortype & 4 ||
    758               png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
    759             ppf->info.alpha_bits = ppf->info.bits_per_sample;
    760             if (sigbits && sigbits->alpha != ppf->info.bits_per_sample) {
    761               JXL_WARNING(
    762                   "sBIT chunk: bit depths for RGBA are inconsistent "
    763                   "(%i %i %i %i). Setting A bitdepth to %i.",
    764                   sigbits->red, sigbits->green, sigbits->blue, sigbits->alpha,
    765                   ppf->info.bits_per_sample);
    766             }
    767           } else {
    768             ppf->info.alpha_bits = 0;
    769           }
    770           ppf->color_encoding.color_space =
    771               (ppf->info.num_color_channels == 1 ? JXL_COLOR_SPACE_GRAY
    772                                                  : JXL_COLOR_SPACE_RGB);
    773           ppf->info.xsize = w;
    774           ppf->info.ysize = h;
    775           JXL_RETURN_IF_ERROR(VerifyDimensions(constraints, w, h));
    776           num_channels =
    777               ppf->info.num_color_channels + (ppf->info.alpha_bits ? 1 : 0);
    778           format = {
    779               /*num_channels=*/num_channels,
    780               /*data_type=*/ppf->info.bits_per_sample > 8 ? JXL_TYPE_UINT16
    781                                                           : JXL_TYPE_UINT8,
    782               /*endianness=*/JXL_BIG_ENDIAN,
    783               /*align=*/0,
    784           };
    785           if (png_bit_depth > 8 && format.data_type == JXL_TYPE_UINT8) {
    786             png_set_strip_16(png_ptr);
    787           }
    788           bytes_per_pixel =
    789               num_channels * (format.data_type == JXL_TYPE_UINT16 ? 2 : 1);
    790           unsigned int rowbytes = w * bytes_per_pixel;
    791           unsigned int imagesize = h * rowbytes;
    792           frameRaw.pixels.resize(imagesize);
    793           frameRaw.rows.resize(h);
    794           for (unsigned int j = 0; j < h; j++) {
    795             frameRaw.rows[j] = frameRaw.pixels.data() + j * rowbytes;
    796           }
    797 
    798           if (processing_data(png_ptr, info_ptr, chunk.data(), chunk.size())) {
    799             break;
    800           }
    801         } else if (id == kId_fdAT && isAnimated) {
    802           if (!hasInfo) {
    803             return JXL_FAILURE("fDAT chunk before iDAT");
    804           }
    805           png_save_uint_32(chunk.data() + 4, chunk.size() - 16);
    806           memcpy(chunk.data() + 8, "IDAT", 4);
    807           if (processing_data(png_ptr, info_ptr, chunk.data() + 4,
    808                               chunk.size() - 4)) {
    809             break;
    810           }
    811         } else if (id == kId_cICP) {
    812           // Color profile chunks: cICP has the highest priority, followed by
    813           // iCCP and sRGB (which shouldn't co-exist, but if they do, we use
    814           // iCCP), followed finally by gAMA and cHRM.
    815           if (DecodeCICP(chunk.data() + 8, chunk.size() - 12,
    816                          &ppf->color_encoding)) {
    817             have_cicp = true;
    818             have_color = true;
    819             ppf->icc.clear();
    820             ppf->primary_color_representation =
    821                 PackedPixelFile::kColorEncodingIsPrimary;
    822           }
    823         } else if (!have_cicp && id == kId_iCCP) {
    824           if (processing_data(png_ptr, info_ptr, chunk.data(), chunk.size())) {
    825             JXL_WARNING("Corrupt iCCP chunk");
    826             break;
    827           }
    828 
    829           // TODO(jon): catch special case of PQ and synthesize color encoding
    830           // in that case
    831           int compression_type;
    832           png_bytep profile;
    833           png_charp name;
    834           png_uint_32 proflen = 0;
    835           auto ok = png_get_iCCP(png_ptr, info_ptr, &name, &compression_type,
    836                                  &profile, &proflen);
    837           if (ok && proflen) {
    838             ppf->icc.assign(profile, profile + proflen);
    839             ppf->primary_color_representation = PackedPixelFile::kIccIsPrimary;
    840             have_color = true;
    841             have_iccp = true;
    842           } else {
    843             // TODO(eustas): JXL_WARNING?
    844           }
    845         } else if (!have_cicp && !have_iccp && id == kId_sRGB) {
    846           JXL_RETURN_IF_ERROR(DecodeSRGB(chunk.data() + 8, chunk.size() - 12,
    847                                          &ppf->color_encoding));
    848           have_srgb = true;
    849           have_color = true;
    850         } else if (!have_cicp && !have_srgb && !have_iccp && id == kId_gAMA) {
    851           JXL_RETURN_IF_ERROR(DecodeGAMA(chunk.data() + 8, chunk.size() - 12,
    852                                          &ppf->color_encoding));
    853           have_color = true;
    854         } else if (!have_cicp && !have_srgb && !have_iccp && id == kId_cHRM) {
    855           JXL_RETURN_IF_ERROR(DecodeCHRM(chunk.data() + 8, chunk.size() - 12,
    856                                          &ppf->color_encoding));
    857           have_color = true;
    858         } else if (id == kId_eXIf) {
    859           ppf->metadata.exif.resize(chunk.size() - 12);
    860           memcpy(ppf->metadata.exif.data(), chunk.data() + 8,
    861                  chunk.size() - 12);
    862         } else if (!isAbc(chunk[4]) || !isAbc(chunk[5]) || !isAbc(chunk[6]) ||
    863                    !isAbc(chunk[7])) {
    864           break;
    865         } else {
    866           if (processing_data(png_ptr, info_ptr, chunk.data(), chunk.size())) {
    867             break;
    868           }
    869           if (!hasInfo) {
    870             chunksInfo.push_back(chunk);
    871             continue;
    872           }
    873         }
    874       }
    875     }
    876 
    877     JXL_RETURN_IF_ERROR(ApplyColorHints(
    878         color_hints, have_color, ppf->info.num_color_channels == 1, ppf));
    879   }
    880 
    881   if (errorstate) return false;
    882 
    883   bool has_nontrivial_background = false;
    884   bool previous_frame_should_be_cleared = false;
    885   enum {
    886     DISPOSE_OP_NONE = 0,
    887     DISPOSE_OP_BACKGROUND = 1,
    888     DISPOSE_OP_PREVIOUS = 2,
    889   };
    890   enum {
    891     BLEND_OP_SOURCE = 0,
    892     BLEND_OP_OVER = 1,
    893   };
    894   for (size_t i = 0; i < frames.size(); i++) {
    895     auto& frame = frames[i];
    896     JXL_ASSERT(frame.data.xsize == frame.xsize);
    897     JXL_ASSERT(frame.data.ysize == frame.ysize);
    898 
    899     // Before encountering a DISPOSE_OP_NONE frame, the canvas is filled with 0,
    900     // so DISPOSE_OP_BACKGROUND and DISPOSE_OP_PREVIOUS are equivalent.
    901     if (frame.dispose_op == DISPOSE_OP_NONE) {
    902       has_nontrivial_background = true;
    903     }
    904     bool should_blend = frame.blend_op == BLEND_OP_OVER;
    905     bool use_for_next_frame =
    906         has_nontrivial_background && frame.dispose_op != DISPOSE_OP_PREVIOUS;
    907     size_t x0 = frame.x0;
    908     size_t y0 = frame.y0;
    909     size_t xsize = frame.data.xsize;
    910     size_t ysize = frame.data.ysize;
    911     if (previous_frame_should_be_cleared) {
    912       size_t px0 = frames[i - 1].x0;
    913       size_t py0 = frames[i - 1].y0;
    914       size_t pxs = frames[i - 1].xsize;
    915       size_t pys = frames[i - 1].ysize;
    916       if (px0 >= x0 && py0 >= y0 && px0 + pxs <= x0 + xsize &&
    917           py0 + pys <= y0 + ysize && frame.blend_op == BLEND_OP_SOURCE &&
    918           use_for_next_frame) {
    919         // If the previous frame is entirely contained in the current frame and
    920         // we are using BLEND_OP_SOURCE, nothing special needs to be done.
    921         ppf->frames.emplace_back(std::move(frame.data));
    922       } else if (px0 == x0 && py0 == y0 && px0 + pxs == x0 + xsize &&
    923                  py0 + pys == y0 + ysize && use_for_next_frame) {
    924         // If the new frame has the same size as the old one, but we are
    925         // blending, we can instead just not blend.
    926         should_blend = false;
    927         ppf->frames.emplace_back(std::move(frame.data));
    928       } else if (px0 <= x0 && py0 <= y0 && px0 + pxs >= x0 + xsize &&
    929                  py0 + pys >= y0 + ysize && use_for_next_frame) {
    930         // If the new frame is contained within the old frame, we can pad the
    931         // new frame with zeros and not blend.
    932         PackedImage new_data(pxs, pys, frame.data.format);
    933         memset(new_data.pixels(), 0, new_data.pixels_size);
    934         for (size_t y = 0; y < ysize; y++) {
    935           size_t bytes_per_pixel =
    936               PackedImage::BitsPerChannel(new_data.format.data_type) *
    937               new_data.format.num_channels / 8;
    938           memcpy(static_cast<uint8_t*>(new_data.pixels()) +
    939                      new_data.stride * (y + y0 - py0) +
    940                      bytes_per_pixel * (x0 - px0),
    941                  static_cast<const uint8_t*>(frame.data.pixels()) +
    942                      frame.data.stride * y,
    943                  xsize * bytes_per_pixel);
    944         }
    945 
    946         x0 = px0;
    947         y0 = py0;
    948         xsize = pxs;
    949         ysize = pys;
    950         should_blend = false;
    951         ppf->frames.emplace_back(std::move(new_data));
    952       } else {
    953         // If all else fails, insert a placeholder blank frame with kReplace.
    954         PackedImage blank(pxs, pys, frame.data.format);
    955         memset(blank.pixels(), 0, blank.pixels_size);
    956         ppf->frames.emplace_back(std::move(blank));
    957         auto& pframe = ppf->frames.back();
    958         pframe.frame_info.layer_info.crop_x0 = px0;
    959         pframe.frame_info.layer_info.crop_y0 = py0;
    960         pframe.frame_info.layer_info.xsize = pxs;
    961         pframe.frame_info.layer_info.ysize = pys;
    962         pframe.frame_info.duration = 0;
    963         bool is_full_size = px0 == 0 && py0 == 0 && pxs == ppf->info.xsize &&
    964                             pys == ppf->info.ysize;
    965         pframe.frame_info.layer_info.have_crop = is_full_size ? 0 : 1;
    966         pframe.frame_info.layer_info.blend_info.blendmode = JXL_BLEND_REPLACE;
    967         pframe.frame_info.layer_info.blend_info.source = 1;
    968         pframe.frame_info.layer_info.save_as_reference = 1;
    969         ppf->frames.emplace_back(std::move(frame.data));
    970       }
    971     } else {
    972       ppf->frames.emplace_back(std::move(frame.data));
    973     }
    974 
    975     auto& pframe = ppf->frames.back();
    976     pframe.frame_info.layer_info.crop_x0 = x0;
    977     pframe.frame_info.layer_info.crop_y0 = y0;
    978     pframe.frame_info.layer_info.xsize = xsize;
    979     pframe.frame_info.layer_info.ysize = ysize;
    980     pframe.frame_info.duration = frame.duration;
    981     pframe.frame_info.layer_info.blend_info.blendmode =
    982         should_blend ? JXL_BLEND_BLEND : JXL_BLEND_REPLACE;
    983     bool is_full_size = x0 == 0 && y0 == 0 && xsize == ppf->info.xsize &&
    984                         ysize == ppf->info.ysize;
    985     pframe.frame_info.layer_info.have_crop = is_full_size ? 0 : 1;
    986     pframe.frame_info.layer_info.blend_info.source = 1;
    987     pframe.frame_info.layer_info.blend_info.alpha = 0;
    988     pframe.frame_info.layer_info.save_as_reference = use_for_next_frame ? 1 : 0;
    989 
    990     previous_frame_should_be_cleared =
    991         has_nontrivial_background && frame.dispose_op == DISPOSE_OP_BACKGROUND;
    992   }
    993   if (ppf->frames.empty()) return JXL_FAILURE("No frames decoded");
    994   ppf->frames.back().frame_info.is_last = JXL_TRUE;
    995 
    996   return true;
    997 #else
    998   return false;
    999 #endif
   1000 }
   1001 
   1002 }  // namespace extras
   1003 }  // namespace jxl