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