Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/third_party/jpeg-xl/lib/extras/dec/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 46 kB image not shown  

Quelle  apng.cc   Sprache: C

 
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

#include "lib/extras/dec/apng.h"

// Parts of this code are taken from apngdis, which has the following license:
/* APNG Disassembler 2.8
 *
 * Deconstructs APNG files into individual frames.
 *
 * http://apngdis.sourceforge.net
 *
 * Copyright (c) 2010-2015 Max Stepin
 * maxst at users.sourceforge.net
 *
 * zlib license
 * ------------
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 *
 */


#include <jxl/codestream_header.h>
#include <jxl/encode.h>

#include <array>
#include <atomic>
#include <cstdint>
#include <cstring>
#include <limits>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "lib/extras/packed_image.h"
#include "lib/extras/size_constraints.h"
#include "lib/jxl/base/byte_order.h"
#include "lib/jxl/base/common.h"
#include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/base/printf_macros.h"
#include "lib/jxl/base/rect.h"
#include "lib/jxl/base/sanitizers.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
#if JPEGXL_ENABLE_APNG
#include "png.h" /* original (unpatched) libpng is ok */
#endif

namespace jxl {
namespace extras {

#if !JPEGXL_ENABLE_APNG

bool CanDecodeAPNG() { return false; }
Status DecodeImageAPNG(const Span<const uint8_t> bytes,
                       const ColorHints& color_hints, PackedPixelFile* ppf,
                       const SizeConstraints* constraints) {
  return false;
}

#else  // JPEGXL_ENABLE_APNG

namespace {

constexpr std::array<uint8_t, 8> kPngSignature = {137,  'P',  'N''G',
                                                  '\r''\n', 26,  '\n'};

// Returns floating-point value from the PNG encoding (times 10^5).
double F64FromU32(const uint32_t x) { return static_cast<int32_t>(x) * 1E-5; }

/** Extract information from 'sRGB' chunk. */
Status DecodeSrgbChunk(const Bytes payload, JxlColorEncoding* color_encoding) {
  if (payload.size() != 1) return JXL_FAILURE("Wrong sRGB size");
  uint8_t ri = payload[0];
  // (PNG uses the same values as ICC.)
  if (ri >= 4) return JXL_FAILURE("Invalid Rendering Intent");
  color_encoding->white_point = JXL_WHITE_POINT_D65;
  color_encoding->primaries = JXL_PRIMARIES_SRGB;
  color_encoding->transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
  color_encoding->rendering_intent = static_cast<JxlRenderingIntent>(ri);
  return true;
}

/**
 * Extract information from 'cICP' chunk.
 *
 * If the cICP profile is not fully supported, return `false` and leave
 * `color_encoding` unmodified.
 */

Status DecodeCicpChunk(const Bytes payload, JxlColorEncoding* color_encoding) {
  if (payload.size() != 4) return JXL_FAILURE("Wrong cICP size");
  JxlColorEncoding color_enc = *color_encoding;

  // From https://www.itu.int/rec/T-REC-H.273-202107-I/en
  if (payload[0] == 1) {
    // IEC 61966-2-1 sRGB
    color_enc.primaries = JXL_PRIMARIES_SRGB;
    color_enc.white_point = JXL_WHITE_POINT_D65;
  } else if (payload[0] == 4) {
    // Rec. ITU-R BT.470-6 System M
    color_enc.primaries = JXL_PRIMARIES_CUSTOM;
    color_enc.primaries_red_xy[0] = 0.67;
    color_enc.primaries_red_xy[1] = 0.33;
    color_enc.primaries_green_xy[0] = 0.21;
    color_enc.primaries_green_xy[1] = 0.71;
    color_enc.primaries_blue_xy[0] = 0.14;
    color_enc.primaries_blue_xy[1] = 0.08;
    color_enc.white_point = JXL_WHITE_POINT_CUSTOM;
    color_enc.white_point_xy[0] = 0.310;
    color_enc.white_point_xy[1] = 0.316;
  } else if (payload[0] == 5) {
    // Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM
    color_enc.primaries = JXL_PRIMARIES_CUSTOM;
    color_enc.primaries_red_xy[0] = 0.64;
    color_enc.primaries_red_xy[1] = 0.33;
    color_enc.primaries_green_xy[0] = 0.29;
    color_enc.primaries_green_xy[1] = 0.60;
    color_enc.primaries_blue_xy[0] = 0.15;
    color_enc.primaries_blue_xy[1] = 0.06;
    color_enc.white_point = JXL_WHITE_POINT_D65;
  } else if (payload[0] == 6 || payload[0] == 7) {
    // SMPTE ST 170 (2004) / SMPTE ST 240 (1999)
    color_enc.primaries = JXL_PRIMARIES_CUSTOM;
    color_enc.primaries_red_xy[0] = 0.630;
    color_enc.primaries_red_xy[1] = 0.340;
    color_enc.primaries_green_xy[0] = 0.310;
    color_enc.primaries_green_xy[1] = 0.595;
    color_enc.primaries_blue_xy[0] = 0.155;
    color_enc.primaries_blue_xy[1] = 0.070;
    color_enc.white_point = JXL_WHITE_POINT_D65;
  } else if (payload[0] == 8) {
    // Generic film (colour filters using Illuminant C)
    color_enc.primaries = JXL_PRIMARIES_CUSTOM;
    color_enc.primaries_red_xy[0] = 0.681;
    color_enc.primaries_red_xy[1] = 0.319;
    color_enc.primaries_green_xy[0] = 0.243;
    color_enc.primaries_green_xy[1] = 0.692;
    color_enc.primaries_blue_xy[0] = 0.145;
    color_enc.primaries_blue_xy[1] = 0.049;
    color_enc.white_point = JXL_WHITE_POINT_CUSTOM;
    color_enc.white_point_xy[0] = 0.310;
    color_enc.white_point_xy[1] = 0.316;
  } else if (payload[0] == 9) {
    // Rec. ITU-R BT.2100-2
    color_enc.primaries = JXL_PRIMARIES_2100;
    color_enc.white_point = JXL_WHITE_POINT_D65;
  } else if (payload[0] == 10) {
    // CIE 1931 XYZ
    color_enc.primaries = JXL_PRIMARIES_CUSTOM;
    color_enc.primaries_red_xy[0] = 1;
    color_enc.primaries_red_xy[1] = 0;
    color_enc.primaries_green_xy[0] = 0;
    color_enc.primaries_green_xy[1] = 1;
    color_enc.primaries_blue_xy[0] = 0;
    color_enc.primaries_blue_xy[1] = 0;
    color_enc.white_point = JXL_WHITE_POINT_E;
  } else if (payload[0] == 11) {
    // SMPTE RP 431-2 (2011)
    color_enc.primaries = JXL_PRIMARIES_P3;
    color_enc.white_point = JXL_WHITE_POINT_DCI;
  } else if (payload[0] == 12) {
    // SMPTE EG 432-1 (2010)
    color_enc.primaries = JXL_PRIMARIES_P3;
    color_enc.white_point = JXL_WHITE_POINT_D65;
  } else if (payload[0] == 22) {
    color_enc.primaries = JXL_PRIMARIES_CUSTOM;
    color_enc.primaries_red_xy[0] = 0.630;
    color_enc.primaries_red_xy[1] = 0.340;
    color_enc.primaries_green_xy[0] = 0.295;
    color_enc.primaries_green_xy[1] = 0.605;
    color_enc.primaries_blue_xy[0] = 0.155;
    color_enc.primaries_blue_xy[1] = 0.077;
    color_enc.white_point = JXL_WHITE_POINT_D65;
  } else {
    JXL_WARNING("Unsupported primaries specified in cICP chunk: %d",
                static_cast<int>(payload[0]));
    return false;
  }

  if (payload[1] == 1 || payload[1] == 6 || payload[1] == 14 ||
      payload[1] == 15) {
    // Rec. ITU-R BT.709-6
    color_enc.transfer_function = JXL_TRANSFER_FUNCTION_709;
  } else if (payload[1] == 4) {
    // Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM
    color_enc.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
    color_enc.gamma = 1 / 2.2;
  } else if (payload[1] == 5) {
    // Rec. ITU-R BT.470-6 System B, G
    color_enc.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
    color_enc.gamma = 1 / 2.8;
  } else if (payload[1] == 8 || payload[1] == 13 || payload[1] == 16 ||
             payload[1] == 17 || payload[1] == 18) {
    // These codes all match the corresponding JXL enum values
    color_enc.transfer_function = static_cast<JxlTransferFunction>(payload[1]);
  } else {
    JXL_WARNING("Unsupported transfer function specified in cICP chunk: %d",
                static_cast<int>(payload[1]));
    return false;
  }

  if (payload[2] != 0) {
    JXL_WARNING("Unsupported color space specified in cICP chunk: %d",
                static_cast<int>(payload[2]));
    return false;
  }
  if (payload[3] != 1) {
    JXL_WARNING("Unsupported full-range flag specified in cICP chunk: %d",
                static_cast<int>(payload[3]));
    return false;
  }
  // cICP has no rendering intent, so use the default
  color_enc.rendering_intent = JXL_RENDERING_INTENT_RELATIVE;
  *color_encoding = color_enc;
  return true;
}

/** Extract information from 'gAMA' chunk. */
Status DecodeGamaChunk(Bytes payload, JxlColorEncoding* color_encoding) {
  if (payload.size() != 4) return JXL_FAILURE("Wrong gAMA size");
  color_encoding->transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
  color_encoding->gamma = F64FromU32(LoadBE32(payload.data()));
  return true;
}

/** Extract information from 'cHTM' chunk. */
Status DecodeChrmChunk(Bytes payload, JxlColorEncoding* color_encoding) {
  if (payload.size() != 32) return JXL_FAILURE("Wrong cHRM size");
  const uint8_t* data = payload.data();
  color_encoding->white_point = JXL_WHITE_POINT_CUSTOM;
  color_encoding->white_point_xy[0] = F64FromU32(LoadBE32(data + 0));
  color_encoding->white_point_xy[1] = F64FromU32(LoadBE32(data + 4));

  color_encoding->primaries = JXL_PRIMARIES_CUSTOM;
  color_encoding->primaries_red_xy[0] = F64FromU32(LoadBE32(data + 8));
  color_encoding->primaries_red_xy[1] = F64FromU32(LoadBE32(data + 12));
  color_encoding->primaries_green_xy[0] = F64FromU32(LoadBE32(data + 16));
  color_encoding->primaries_green_xy[1] = F64FromU32(LoadBE32(data + 20));
  color_encoding->primaries_blue_xy[0] = F64FromU32(LoadBE32(data + 24));
  color_encoding->primaries_blue_xy[1] = F64FromU32(LoadBE32(data + 28));
  return true;
}

/** Extracts information from 'cLLi' chunk. */
Status DecodeClliChunk(Bytes payload, float* max_content_light_level) {
  if (payload.size() != 8) return JXL_FAILURE("Wrong cLLi size");
  const uint8_t* data = payload.data();
  const uint32_t maxcll_png =
      Clamp1(png_get_uint_32(data), uint32_t{0}, uint32_t{10000 * 10000});
  // Ignore MaxFALL value.
  *max_content_light_level = static_cast<float>(maxcll_png) / 10000.f;
  return true;
}

/** Returns false if invalid. */
JXL_INLINE Status DecodeHexNibble(const char c, uint32_t* JXL_RESTRICT nibble) {
  if ('a' <= c && c <= 'f') {
    *nibble = 10 + c - 'a';
  } else if ('0' <= c && c <= '9') {
    *nibble = c - '0';
  } else {
    *nibble = 0;
    return JXL_FAILURE("Invalid metadata nibble");
  }
  JXL_ENSURE(*nibble < 16);
  return true;
}

/** Returns false if invalid. */
JXL_INLINE Status DecodeDecimal(const char** pos, const char* end,
                                uint32_t* JXL_RESTRICT value) {
  size_t len = 0;
  *value = 0;
  while (*pos < end) {
    char next = **pos;
    if (next >= '0' && next <= '9') {
      *value = (*value * 10) + static_cast<uint32_t>(next - '0');
      len++;
      if (len > 8) {
        break;
      }
    } else {
      // Do not consume terminator (non-decimal digit).
      break;
    }
    (*pos)++;
  }
  if (len == 0 || len > 8) {
    return JXL_FAILURE("Failed to parse decimal");
  }
  return true;
}

/**
 * Parses a PNG text chunk with key of the form "Raw profile type ####", with
 * #### a type.
 *
 * Returns whether it could successfully parse the content.
 * We trust key and encoded are null-terminated because they come from
 * libpng.
 */

Status MaybeDecodeBase16(const char* key, const char* encoded,
                         std::string* type, std::vector<uint8_t>* bytes) {
  const char* encoded_end = encoded + strlen(encoded);

  const char* kKey = "Raw profile type ";
  if (strncmp(key, kKey, strlen(kKey)) != 0) return false;
  *type = key + strlen(kKey);
  const size_t kMaxTypeLen = 20;
  if (type->length() > kMaxTypeLen) return false;  // Type too long

  // Header: freeform string and number of bytes
  // Expected format is:
  // \n
  // profile name/description\n
  //       40\n               (the number of bytes after hex-decoding)
  // 01234566789abcdef....\n  (72 bytes per line max).
  // 012345667\n              (last line)
  const char* pos = encoded;

  if (*(pos++) != '\n'return false;
  while (pos < encoded_end && *pos != '\n') {
    pos++;
  }
  if (pos == encoded_end) return false;
  // We parsed so far a \n, some number of non \n characters and are now
  // pointing at a \n.
  if (*(pos++) != '\n'return false;
  // Skip leading spaces
  while (pos < encoded_end && *pos == ' ') {
    pos++;
  }
  uint32_t bytes_to_decode = 0;
  JXL_RETURN_IF_ERROR(DecodeDecimal(&pos, encoded_end, &bytes_to_decode));

  // We need 2*bytes for the hex values plus 1 byte every 36 values,
  // plus terminal \n for length.
  size_t tail = static_cast<size_t>(encoded_end - pos);
  bool ok = ((tail / 2) >= bytes_to_decode);
  if (ok) tail -= 2 * static_cast<size_t>(bytes_to_decode);
  ok = ok && (tail == 1 + DivCeil(bytes_to_decode, 36));
  if (!ok) {
    return JXL_FAILURE("Not enough bytes to parse %d bytes in hex",
                       bytes_to_decode);
  }
  JXL_ENSURE(bytes->empty());
  bytes->reserve(bytes_to_decode);

  // Encoding: base16 with newline after 72 chars.
  // pos points to the \n before the first line of hex values.
  for (size_t i = 0; i < bytes_to_decode; ++i) {
    if (i % 36 == 0) {
      if (pos + 1 >= encoded_end) return false;  // Truncated base16 1
      if (*pos != '\n'return false;            // Expected newline
      ++pos;
    }

    if (pos + 2 >= encoded_end) return false;  // Truncated base16 2;
    uint32_t nibble0;
    uint32_t nibble1;
    JXL_RETURN_IF_ERROR(DecodeHexNibble(pos[0], &nibble0));
    JXL_RETURN_IF_ERROR(DecodeHexNibble(pos[1], &nibble1));
    bytes->push_back(static_cast<uint8_t>((nibble0 << 4) + nibble1));
    pos += 2;
  }
  if (pos + 1 != encoded_end) return false;  // Too many encoded bytes
  if (pos[0] != '\n'return false;          // Incorrect metadata terminator
  return true;
}

/** Retrieves XMP and EXIF/IPTC from itext and text. */
Status DecodeBlob(const png_text_struct& info, PackedMetadata* metadata) {
  // We trust these are properly null-terminated by libpng.
  const char* key = info.key;
  const char* value = info.text;
  if (strstr(key, "XML:com.adobe.xmp")) {
    metadata->xmp.resize(strlen(value));  // safe, see above
    memcpy(metadata->xmp.data(), value, metadata->xmp.size());
  }

  std::string type;
  std::vector<uint8_t> bytes;

  // Handle text chunks annotated with key "Raw profile type ####", with
  // #### a type, which may contain metadata.
  const char* kKey = "Raw profile type ";
  if (strncmp(key, kKey, strlen(kKey)) != 0) return false;

  if (!MaybeDecodeBase16(key, value, &type, &bytes)) {
    JXL_WARNING("Couldn't parse 'Raw format type' text chunk");
    return false;
  }
  if (type == "exif") {
    // Remove prefix if present.
    constexpr std::array<uint8_t, 6> kExifPrefix = {'E''x''i''f', 0, 0};
    if (bytes.size() >= kExifPrefix.size() &&
        memcmp(bytes.data(), kExifPrefix.data(), kExifPrefix.size()) == 0) {
      bytes.erase(bytes.begin(), bytes.begin() + kExifPrefix.size());
    }
    if (!metadata->exif.empty()) {
      JXL_DEBUG_V(2,
                  "overwriting EXIF (%" PRIuS " bytes) with base16 (%" PRIuS
                  " bytes)",
                  metadata->exif.size(), bytes.size());
    }
    metadata->exif = std::move(bytes);
  } else if (type == "iptc") {
    // TODO(jon): Deal with IPTC in some way
  } else if (type == "8bim") {
    // TODO(jon): Deal with 8bim in some way
  } else if (type == "xmp") {
    if (!metadata->xmp.empty()) {
      JXL_DEBUG_V(2,
                  "overwriting XMP (%" PRIuS " bytes) with base16 (%" PRIuS
                  " bytes)",
                  metadata->xmp.size(), bytes.size());
    }
    metadata->xmp = std::move(bytes);
  } else {
    JXL_DEBUG_V(
        2, "Unknown type in 'Raw format type' text chunk: %s: %" PRIuS " bytes",
        type.c_str(), bytes.size());
  }
  return true;
}

constexpr bool isAbc(char c) {
  return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}

/** Wrap 4-char tag name into ID. */
constexpr uint32_t MakeTag(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
  return a | (b << 8) | (c << 16) | (d << 24);
}

/** Reusable image data container. */
struct Pixels {
  // Use array instead of vector to avoid memory initialization.
  std::unique_ptr<uint8_t[]> pixels;
  size_t pixels_size = 0;
  std::vector<uint8_t*> rows;
  std::atomic<bool> has_error{false};

  Status Resize(size_t row_bytes, size_t num_rows) {
    size_t new_size = row_bytes * num_rows;  // it is assumed size is sane
    if (new_size > pixels_size) {
      pixels.reset(new uint8_t[new_size]);
      if (!pixels) {
        // TODO(szabadka): use specialized OOM error code
        return JXL_FAILURE("Failed to allocate memory for image buffer");
      }
      pixels_size = new_size;
    }
    rows.resize(num_rows);
    for (size_t y = 0; y < num_rows; y++) {
      rows[y] = pixels.get() + y * row_bytes;
    }
    return true;
  }
};

/**
 * Helper that chunks in-memory input.
 */

struct Reader {
  explicit Reader(Span<const uint8_t> data) : data_(data) {}

  const Span<const uint8_t> data_;
  size_t offset_ = 0;

  Bytes Peek(size_t len) const {
    size_t cap = data_.size() - offset_;
    size_t to_copy = std::min(cap, len);
    return {data_.data() + offset_, to_copy};
  }

  Bytes Read(size_t len) {
    Bytes result = Peek(len);
    offset_ += result.size();
    return result;
  }

  /* Returns empty Span on error. */
  Bytes ReadChunk() {
    Bytes len = Peek(4);
    if (len.size() != 4) {
      return Bytes();
    }
    const auto size = png_get_uint_32(len.data());
    // NB: specification allows 2^31 - 1
    constexpr size_t kMaxPNGChunkSize = 1u << 30;  // 1 GB
    // Check first, to avoid overflow.
    if (size > kMaxPNGChunkSize) {
      JXL_WARNING("APNG chunk size is too big");
      return Bytes();
    }
    size_t full_size = size + 12;  // size does not include itself, tag and CRC.
    Bytes result = Read(full_size);
    return (result.size() == full_size) ? result : Bytes();
  }

  bool Eof() const { return offset_ == data_.size(); }
};

void ProgressiveRead_OnInfo(png_structp png_ptr, png_infop info_ptr) {
  png_set_expand(png_ptr);
  png_set_palette_to_rgb(png_ptr);
  png_set_tRNS_to_alpha(png_ptr);
  (void)png_set_interlace_handling(png_ptr);
  png_read_update_info(png_ptr, info_ptr);
}

void ProgressiveRead_OnRow(png_structp png_ptr, png_bytep new_row,
                           png_uint_32 row_num, int pass) {
  Pixels* frame = reinterpret_cast<Pixels*>(png_get_progressive_ptr(png_ptr));
  if (!frame) {
    JXL_DEBUG_ABORT("Internal logic error");
    return;
  }
  if (row_num >= frame->rows.size()) {
    frame->has_error = true;
    return;
  }
  png_progressive_combine_row(png_ptr, frame->rows[row_num], new_row);
}

// Holds intermediate state during parsing APNG file.
struct Context {
  ~Context() {
    // Make sure png memory is released in any case.
    ResetPngDecoder();
  }

  bool CreatePngDecoder() {
    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr,
                                     nullptr);
    info_ptr = png_create_info_struct(png_ptr);
    return (png_ptr != nullptr && info_ptr != nullptr);
  }

  /**
   * Initialize PNG decoder.
   *
   * TODO(eustas): add details
   */

  bool InitPngDecoder(const std::vector<Bytes>& chunksInfo,
                      const RectT<uint64_t>& viewport) {
    ResetPngDecoder();

    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr,
                                     nullptr);
    info_ptr = png_create_info_struct(png_ptr);
    if (png_ptr == nullptr || info_ptr == nullptr) {
      return false;
    }

    if (setjmp(png_jmpbuf(png_ptr))) {
      return false;
    }

    /* hIST chunk tail is not processed properly; skip this chunk completely;
       see https://github.com/glennrp/libpng/pull/413 */

    constexpr std::array<uint8_t, 5> kIgnoredChunks = {'h''I''S''T', 0};
    png_set_keep_unknown_chunks(png_ptr, 1, kIgnoredChunks.data(),
                                static_cast<int>(kIgnoredChunks.size() / 5));

    png_set_crc_action(png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE);
    png_set_progressive_read_fn(png_ptr, static_cast<void*>(&frameRaw),
                                ProgressiveRead_OnInfo, ProgressiveRead_OnRow,
                                nullptr);

    png_process_data(png_ptr, info_ptr,
                     const_cast<uint8_t*>(kPngSignature.data()),
                     kPngSignature.size());

    // Patch dimensions.
    png_save_uint_32(ihdr.data() + 8, static_cast<uint32_t>(viewport.xsize()));
    png_save_uint_32(ihdr.data() + 12, static_cast<uint32_t>(viewport.ysize()));
    png_process_data(png_ptr, info_ptr, ihdr.data(), ihdr.size());

    for (const auto& chunk : chunksInfo) {
      png_process_data(png_ptr, info_ptr, const_cast<uint8_t*>(chunk.data()),
                       chunk.size());
    }

    return true;
  }

  /**
   * Pass chunk to PNG decoder.
   */

  bool FeedChunks(const Bytes& chunk1, const Bytes& chunk2 = Bytes()) {
    // TODO(eustas): turn to DCHECK
    if (!png_ptr || !info_ptr) return false;

    if (setjmp(png_jmpbuf(png_ptr))) {
      return false;
    }

    for (const auto& chunk : {chunk1, chunk2}) {
      if (!chunk.empty()) {
        png_process_data(png_ptr, info_ptr, const_cast<uint8_t*>(chunk.data()),
                         chunk.size());
      }
    }
    return true;
  }

  bool FinalizeStream(PackedMetadata* metadata) {
    // TODO(eustas): turn to DCHECK
    if (!png_ptr || !info_ptr) return false;

    if (setjmp(png_jmpbuf(png_ptr))) {
      return false;
    }

    const std::array<uint8_t, 12> kFooter = {0,  0,  0,   0,  73, 69,
                                             78, 68, 174, 66, 96, 130};
    png_process_data(png_ptr, info_ptr, const_cast<uint8_t*>(kFooter.data()),
                     kFooter.size());
    // before destroying: check if we encountered any metadata chunks
    png_textp text_ptr = nullptr;
    int num_text = 0;
    if (png_get_text(png_ptr, info_ptr, &text_ptr, &num_text) != 0) {
      msan::UnpoisonMemory(text_ptr, sizeof(png_text_struct) * num_text);
      for (int i = 0; i < num_text; i++) {
        Status result = DecodeBlob(text_ptr[i], metadata);
        // Ignore unknown / malformed blob.
        (void)result;
      }
    }

    return true;
  }

  void ResetPngDecoder() {
    png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
    // Just in case. Not all versions on libpng wipe-out the pointers.
    png_ptr = nullptr;
    info_ptr = nullptr;
  }

  std::array<uint8_t, 25> ihdr;  // (modified) copy of file IHDR chunk
  png_structp png_ptr = nullptr;
  png_infop info_ptr = nullptr;
  Pixels frameRaw = {};
};

enum class DisposeOp : uint8_t { NONE = 0, BACKGROUND = 1, PREVIOUS = 2 };

constexpr uint8_t kLastDisposeOp = static_cast<uint32_t>(DisposeOp::PREVIOUS);

enum class BlendOp : uint8_t { SOURCE = 0, OVER = 1 };

constexpr uint8_t kLastBlendOp = static_cast<uint32_t>(BlendOp::OVER);

// fcTL
struct FrameControl {
  uint32_t delay_num;
  uint32_t delay_den;
  RectT<uint64_t> viewport;
  DisposeOp dispose_op;
  BlendOp blend_op;
};

struct Frame {
  PackedImage pixels;
  FrameControl metadata;
};

bool ValidateViewport(const RectT<uint64_t>& r) {
  constexpr uint32_t kMaxPngDim = 1000000UL;
  return (r.xsize() <= kMaxPngDim) && (r.ysize() <= kMaxPngDim);
}

/**
 * Setup #channels, bpp, colorspace, etc. from PNG values.
 */

void SetColorData(PackedPixelFile* ppf, uint8_t color_type, uint8_t bit_depth,
                  png_color_8p sig_bits, uint32_t has_transparency) {
  bool palette_used = ((color_type & 1) != 0);
  bool color_used = ((color_type & 2) != 0);
  bool alpha_channel_used = ((color_type & 4) != 0);
  if (palette_used) {
    if (!color_used || alpha_channel_used) {
      JXL_DEBUG_V(2, "Unexpected PNG color type");
    }
  }

  ppf->info.bits_per_sample = bit_depth;

  if (palette_used) {
    // palette will actually be 8-bit regardless of the index bitdepth
    ppf->info.bits_per_sample = 8;
  }
  if (color_used) {
    ppf->info.num_color_channels = 3;
    ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB;
    if (sig_bits) {
      if (sig_bits->red == sig_bits->green &&
          sig_bits->green == sig_bits->blue) {
        ppf->info.bits_per_sample = sig_bits->red;
      } else {
        int maxbps =
            std::max(sig_bits->red, std::max(sig_bits->green, sig_bits->blue));
        JXL_DEBUG_V(2,
                    "sBIT chunk: bit depths for R, G, and B are not the same "
                    "(%i %i %i), while in JPEG XL they have to be the same. "
                    "Setting RGB bit depth to %i.",
                    sig_bits->red, sig_bits->green, sig_bits->blue, maxbps);
        ppf->info.bits_per_sample = maxbps;
      }
    }
  } else {
    ppf->info.num_color_channels = 1;
    ppf->color_encoding.color_space = JXL_COLOR_SPACE_GRAY;
    if (sig_bits) ppf->info.bits_per_sample = sig_bits->gray;
  }
  if (alpha_channel_used || has_transparency) {
    ppf->info.alpha_bits = ppf->info.bits_per_sample;
    if (sig_bits && sig_bits->alpha != ppf->info.bits_per_sample) {
      JXL_DEBUG_V(2,
                  "sBIT chunk: bit depths for RGBA are inconsistent "
                  "(%i %i %i %i). Setting A bitdepth to %i.",
                  sig_bits->red, sig_bits->green, sig_bits->blue,
                  sig_bits->alpha, ppf->info.bits_per_sample);
    }
  } else {
    ppf->info.alpha_bits = 0;
  }
  ppf->color_encoding.color_space = (ppf->info.num_color_channels == 1)
                                        ? JXL_COLOR_SPACE_GRAY
                                        : JXL_COLOR_SPACE_RGB;
}

// Color profile chunks: cICP has the highest priority, followed by
// iCCP and sRGB (which shouldn't co-exist, but if they do, we use
// iCCP), followed finally by gAMA and cHRM.
enum class ColorInfoType {
  NONE = 0,
  GAMA_OR_CHRM = 1,
  ICCP_OR_SRGB = 2,
  CICP = 3
};

}  // namespace

bool CanDecodeAPNG() { return true; }

/**
 * Parse and decode PNG file.
 *
 * Useful PNG chunks:
 *   acTL : animation control (#frames, loop count)
 *   fcTL : frame control (seq#, viewport, delay, disposal blending)
 *   bKGD : preferable background
 *   IDAT : "default image"
 *          if single fcTL goes before IDAT, then it is also first frame
 *   fdAT : seq# + IDAT-like content
 *   PLTE : palette
 *   cICP : coding-independent code points for video signal type identification
 *   iCCP : embedded ICC profile
 *   sRGB : standard RGB colour space
 *   eXIf : exchangeable image file profile
 *   gAMA : image gamma
 *   cHRM : primary chromaticities and white point
 *   tRNS : transparency
 *
 * PNG chunk ordering:
 *  - IHDR first
 *  - IEND last
 *  - acTL, cHRM, cICP, gAMA, iCCP, sRGB, bKGD, eXIf, PLTE before IDAT
 *  - fdAT after IDAT
 *
 * More rules:
 *  - iCCP and sRGB are exclusive
 *  - fcTL and fdAT seq# must be in order fro 0, with no gaps or duplicates
 *  - fcTL before corresponding IDAT / fdAT
 */

Status DecodeImageAPNG(const Span<const uint8_t> bytes,
                       const ColorHints& color_hints, PackedPixelFile* ppf,
                       const SizeConstraints* constraints) {
  // Initialize output (default settings in case e.g. only gAMA is given).
  ppf->frames.clear();
  ppf->info.exponent_bits_per_sample = 0;
  ppf->info.alpha_exponent_bits = 0;
  ppf->info.orientation = JXL_ORIENT_IDENTITY;
  ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB;
  ppf->color_encoding.white_point = JXL_WHITE_POINT_D65;
  ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB;
  ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
  ppf->color_encoding.rendering_intent = JXL_RENDERING_INTENT_RELATIVE;

  Reader input(bytes);

  // Check signature.
  Bytes sig = input.Read(kPngSignature.size());
  if (sig.size() != 8 ||
      memcmp(sig.data(), kPngSignature.data(), kPngSignature.size()) != 0) {
    return false;  // Return silently if it is not a PNG
  }

  // Check IHDR chunk.
  Context ctx;
  Bytes ihdr = input.ReadChunk();
  if (ihdr.size() != ctx.ihdr.size()) {
    return JXL_FAILURE("Unexpected first chunk payload size");
  }
  memcpy(ctx.ihdr.data(), ihdr.data(), ihdr.size());
  uint32_t id = LoadLE32(ihdr.data() + 4);
  if (id != MakeTag('I''H''D''R')) {
    return JXL_FAILURE("First chunk is not IHDR");
  }
  const RectT<uint64_t> image_rect(0, 0, png_get_uint_32(ihdr.data() + 8),
                                   png_get_uint_32(ihdr.data() + 12));
  if (!ValidateViewport(image_rect)) {
    return JXL_FAILURE("PNG image dimensions are too large");
  }

  // Chunks we supply to PNG decoder for every animation frame.
  std::vector<Bytes> passthrough_chunks;
  if (!ctx.InitPngDecoder(passthrough_chunks, image_rect)) {
    return JXL_FAILURE("Failed to initialize PNG decoder");
  }

  // Marker that this PNG is animated.
  bool seen_actl = false;
  // First IDAT is a very important milestone; at this moment we freeze
  // gathered metadata.
  bool seen_idat = false;
  // fCTL can occur multiple times, but only once before IDAT.
  bool seen_fctl = false;
  // Logical EOF.
  bool seen_iend = false;

  ColorInfoType color_info_type = ColorInfoType::NONE;

  // Flag that we processed some IDAT / fDAT after image / frame start.
  bool seen_pixel_data = false;

  uint32_t num_channels;
  JxlPixelFormat format = {};
  size_t bytes_per_pixel = 0;
  std::vector<Frame> frames;
  FrameControl current_frame = {/*delay_num=*/1, /*delay_den=*/10, image_rect,
                                DisposeOp::NONE, BlendOp::SOURCE};

  // Copies frame pixels / metadata from temporary storage.
  // TODO(eustas): avoid copying.
  const auto finalize_frame = [&]() -> Status {
    if (!seen_pixel_data) {
      return JXL_FAILURE("Frame / image without fdAT / IDAT chunks");
    }
    if (!ctx.FinalizeStream(&ppf->metadata)) {
      return JXL_FAILURE("Failed to finalize PNG substream");
    }
    if (ctx.frameRaw.has_error) {
      return JXL_FAILURE("Internal error");
    }
    // Allocates the frame buffer.
    const RectT<uint64_t>& vp = current_frame.viewport;
    size_t xsize = static_cast<size_t>(vp.xsize());
    size_t ysize = static_cast<size_t>(vp.ysize());
    JXL_ASSIGN_OR_RETURN(PackedImage image,
                         PackedImage::Create(xsize, ysize, format));
    for (size_t y = 0; y < ysize; ++y) {
      // TODO(eustas): ensure multiplication is safe
      memcpy(static_cast<uint8_t*>(image.pixels()) + image.stride * y,
             ctx.frameRaw.rows[y], bytes_per_pixel * xsize);
    }
    frames.push_back(Frame{std::move(image), current_frame});
    seen_pixel_data = false;
    return true;
  };

  while (!input.Eof()) {
    if (seen_iend) {
      return JXL_FAILURE("Exuberant input after IEND chunk");
    }
    Bytes chunk = input.ReadChunk();
    if (chunk.empty()) {
      return JXL_FAILURE("Malformed chunk");
    }
    Bytes type(chunk.data() + 4, 4);
    id = LoadLE32(type.data());
    // Cut 'size' and 'type' at front and 'CRC' at the end.
    Bytes payload(chunk.data() + 8, chunk.size() - 12);

    if (!isAbc(type[0]) || !isAbc(type[1]) || !isAbc(type[2]) ||
        !isAbc(type[3])) {
      return JXL_FAILURE("Exotic PNG chunk");
    }

    switch (id) {
      case MakeTag('a''c''T''L'):
        if (seen_idat) {
          JXL_DEBUG_V(2, "aCTL after IDAT ignored");
          continue;
        }
        if (seen_actl) {
          JXL_DEBUG_V(2, "Duplicate aCTL chunk ignored");
          continue;
        }
        seen_actl = true;
        ppf->info.have_animation = JXL_TRUE;
        // TODO(eustas): decode from chunk?
        ppf->info.animation.tps_numerator = 1000;
        ppf->info.animation.tps_denominator = 1;
        continue;

      case MakeTag('I''E''N''D'):
        seen_iend = true;
        JXL_RETURN_IF_ERROR(finalize_frame());
        continue;

      case MakeTag('f''c''T''L'): {
        if (payload.size() != 26) {
          return JXL_FAILURE("Unexpected fcTL payload size: %u",
                             static_cast<uint32_t>(payload.size()));
        }
        if (seen_fctl && !seen_idat) {
          return JXL_FAILURE("More than one fcTL before IDAT");
        }
        if (seen_idat && !seen_actl) {
          return JXL_FAILURE("fcTL after IDAT, but without acTL");
        }
        seen_fctl = true;

        // TODO(eustas): check order?
        // sequence_number = png_get_uint_32(payload.data());
        RectT<uint64_t> raw_viewport(png_get_uint_32(payload.data() + 12),
                                     png_get_uint_32(payload.data() + 16),
                                     png_get_uint_32(payload.data() + 4),
                                     png_get_uint_32(payload.data() + 8));
        uint8_t dispose_op = payload[24];
        if (dispose_op > kLastDisposeOp) {
          return JXL_FAILURE("Invalid DisposeOp");
        }
        uint8_t blend_op = payload[25];
        if (blend_op > kLastBlendOp) {
          return JXL_FAILURE("Invalid BlendOp");
        }
        FrameControl next_frame = {
            /*delay_num=*/png_get_uint_16(payload.data() + 20),
            /*delay_den=*/png_get_uint_16(payload.data() + 22), raw_viewport,
            static_cast<DisposeOp>(dispose_op), static_cast<BlendOp>(blend_op)};

        if (!raw_viewport.Intersection(image_rect).IsSame(raw_viewport)) {
          // Cropping happened.
          return JXL_FAILURE("PNG frame is outside of image rect");
        }

        if (!seen_idat) {
          // "Default" image is the first animation frame. Its viewport must
          // cover the whole image area.
          if (!raw_viewport.IsSame(image_rect)) {
            return JXL_FAILURE(
                "If the first animation frame is default image, its viewport "
                "must cover full image");
          }
        } else {
          JXL_RETURN_IF_ERROR(finalize_frame());
          if (!ctx.InitPngDecoder(passthrough_chunks, next_frame.viewport)) {
            return JXL_FAILURE("Failed to initialize PNG decoder");
          }
        }
        current_frame = next_frame;
        continue;
      }

      case MakeTag('I''D''A''T'): {
        if (!frames.empty()) {
          return JXL_FAILURE("IDAT after default image is over");
        }
        if (!seen_idat) {
          // First IDAT means that all metadata is ready.
          seen_idat = true;
          JXL_ENSURE(image_rect.xsize() ==
                     png_get_image_width(ctx.png_ptr, ctx.info_ptr));
          JXL_ENSURE(image_rect.ysize() ==
                     png_get_image_height(ctx.png_ptr, ctx.info_ptr));
          JXL_RETURN_IF_ERROR(VerifyDimensions(constraints, image_rect.xsize(),
                                               image_rect.ysize()));
          ppf->info.xsize = image_rect.xsize();
          ppf->info.ysize = image_rect.ysize();

          png_color_8p sig_bits = nullptr;
          // Error is OK -> sig_bits remains nullptr.
          png_get_sBIT(ctx.png_ptr, ctx.info_ptr, &sig_bits);
          SetColorData(ppf, png_get_color_type(ctx.png_ptr, ctx.info_ptr),
                       png_get_bit_depth(ctx.png_ptr, ctx.info_ptr), sig_bits,
                       png_get_valid(ctx.png_ptr, ctx.info_ptr, PNG_INFO_tRNS));
          num_channels =
              ppf->info.num_color_channels + (ppf->info.alpha_bits ? 1 : 0);
          format = {
              /*num_channels=*/num_channels,
              /*data_type=*/ppf->info.bits_per_sample > 8 ? JXL_TYPE_UINT16
                                                          : JXL_TYPE_UINT8,
              /*endianness=*/JXL_BIG_ENDIAN,
              /*align=*/0,
          };
          bytes_per_pixel =
              num_channels * (format.data_type == JXL_TYPE_UINT16 ? 2 : 1);
          // TODO(eustas): ensure multiplication is safe
          uint64_t row_bytes =
              static_cast<uint64_t>(image_rect.xsize()) * bytes_per_pixel;
          uint64_t max_rows = std::numeric_limits<size_t>::max() / row_bytes;
          if (image_rect.ysize() > max_rows) {
            return JXL_FAILURE("Image too big.");
          }
          // TODO(eustas): drop frameRaw
          JXL_RETURN_IF_ERROR(
              ctx.frameRaw.Resize(row_bytes, image_rect.ysize()));
        }

        if (!ctx.FeedChunks(chunk)) {
          return JXL_FAILURE("Decoding IDAT failed");
        }
        seen_pixel_data = true;
        continue;
      }

      case MakeTag('f''d''A''T'): {
        if (!seen_idat) {
          return JXL_FAILURE("fdAT chunk before IDAT");
        }
        if (!seen_actl) {
          return JXL_FAILURE("fdAT chunk before acTL");
        }
        /* The 'fdAT' chunk has... the same structure as an 'IDAT' chunk,
         * except preceded by a sequence number. */

        if (payload.size() < 4) {
          return JXL_FAILURE("Corrupted fdAT chunk");
        }
        // Turn 'fdAT' to 'IDAT' by cutting sequence number and replacing tag.
        std::array<uint8_t, 8> preamble;
        png_save_uint_32(preamble.data(), payload.size() - 4);
        memcpy(preamble.data() + 4, "IDAT", 4);
        // Cut-off 'size', 'type' and 'sequence_number'
        Bytes chunk_tail(chunk.data() + 12, chunk.size() - 12);
        if (!ctx.FeedChunks(Bytes(preamble), chunk_tail)) {
          return JXL_FAILURE("Decoding fdAT failed");
        }
        seen_pixel_data = true;
        continue;
      }

      case MakeTag('c''I''C''P'):
        if (color_info_type == ColorInfoType::CICP) {
          JXL_DEBUG_V(2, "Excessive colorspace definition; cICP chunk ignored");
          continue;
        }
        JXL_RETURN_IF_ERROR(DecodeCicpChunk(payload, &ppf->color_encoding));
        ppf->icc.clear();
        ppf->primary_color_representation =
            PackedPixelFile::kColorEncodingIsPrimary;
        color_info_type = ColorInfoType::CICP;
        continue;

      case MakeTag('i''C''C''P'): {
        if (color_info_type == ColorInfoType::ICCP_OR_SRGB) {
          return JXL_FAILURE("Repeated iCCP / sRGB chunk");
        }
        if (color_info_type > ColorInfoType::ICCP_OR_SRGB) {
          JXL_DEBUG_V(2, "Excessive colorspace definition; iCCP chunk ignored");
          continue;
        }
        // Let PNG decoder deal with chunk processing.
        if (!ctx.FeedChunks(chunk)) {
          return JXL_FAILURE("Corrupt iCCP chunk");
        }

        // TODO(jon): catch special case of PQ and synthesize color encoding
        // in that case
        int compression_type = 0;
        png_bytep profile = nullptr;
        png_charp name = nullptr;
        png_uint_32 profile_len = 0;
        png_uint_32 ok =
            png_get_iCCP(ctx.png_ptr, ctx.info_ptr, &name, &compression_type,
                         &profile, &profile_len);
        if (!ok || !profile_len) {
          return JXL_FAILURE("Malformed / incomplete iCCP chunk");
        }
        ppf->icc.assign(profile, profile + profile_len);
        ppf->primary_color_representation = PackedPixelFile::kIccIsPrimary;
        color_info_type = ColorInfoType::ICCP_OR_SRGB;
        continue;
      }

      case MakeTag('s''R''G''B'):
        if (color_info_type == ColorInfoType::ICCP_OR_SRGB) {
          return JXL_FAILURE("Repeated iCCP / sRGB chunk");
        }
        if (color_info_type > ColorInfoType::ICCP_OR_SRGB) {
          JXL_DEBUG_V(2, "Excessive colorspace definition; sRGB chunk ignored");
          continue;
        }
        JXL_RETURN_IF_ERROR(DecodeSrgbChunk(payload, &ppf->color_encoding));
        color_info_type = ColorInfoType::ICCP_OR_SRGB;
        continue;

      case MakeTag('g''A''M''A'):
        if (color_info_type >= ColorInfoType::GAMA_OR_CHRM) {
          JXL_DEBUG_V(2, "Excessive colorspace definition; gAMA chunk ignored");
          continue;
        }
        JXL_RETURN_IF_ERROR(DecodeGamaChunk(payload, &ppf->color_encoding));
        color_info_type = ColorInfoType::GAMA_OR_CHRM;
        continue;

      case MakeTag('c''H''R''M'):
        if (color_info_type >= ColorInfoType::GAMA_OR_CHRM) {
          JXL_DEBUG_V(2, "Excessive colorspace definition; cHRM chunk ignored");
          continue;
        }
        JXL_RETURN_IF_ERROR(DecodeChrmChunk(payload, &ppf->color_encoding));
        color_info_type = ColorInfoType::GAMA_OR_CHRM;
        continue;

      case MakeTag('c''L''L''i'):
        JXL_RETURN_IF_ERROR(
            DecodeClliChunk(payload, &ppf->info.intensity_target));
        continue;

      case MakeTag('e''X''I''f'):
        // TODO(eustas): next eXIF chunk overwrites current; is it ok?
        ppf->metadata.exif.resize(payload.size());
        memcpy(ppf->metadata.exif.data(), payload.data(), payload.size());
        continue;

      default:
        // We don't know what is that, just pass through.
        if (!ctx.FeedChunks(chunk)) {
          return JXL_FAILURE("PNG decoder failed to process chunk");
        }
        // If it happens before IDAT, we consider it metadata and pass to all
        // sub-decoders.
        if (!seen_idat) {
          passthrough_chunks.push_back(chunk);
        }
        continue;
    }
  }

  bool color_is_already_set = (color_info_type != ColorInfoType::NONE);
  bool is_gray = (ppf->info.num_color_channels == 1);
  JXL_RETURN_IF_ERROR(
      ApplyColorHints(color_hints, color_is_already_set, is_gray, ppf));

  if (ppf->color_encoding.transfer_function != JXL_TRANSFER_FUNCTION_PQ) {
    // Reset intensity target, in case we set it from cLLi but TF is not PQ.
    ppf->info.intensity_target = 0.f;
  }

  bool has_nontrivial_background = false;
  bool previous_frame_should_be_cleared = false;
  for (size_t i = 0; i < frames.size(); i++) {
    Frame& frame = frames[i];
    const FrameControl& fc = frame.metadata;
    const RectT<uint64_t> vp = fc.viewport;
    const auto& pixels = frame.pixels;
    size_t xsize = pixels.xsize;
    size_t ysize = pixels.ysize;
    JXL_ENSURE(xsize == vp.xsize());
    JXL_ENSURE(ysize == vp.ysize());

    // Before encountering a DISPOSE_OP_NONE frame, the canvas is filled with
    // 0, so DISPOSE_OP_BACKGROUND and DISPOSE_OP_PREVIOUS are equivalent.
    if (fc.dispose_op == DisposeOp::NONE) {
      has_nontrivial_background = true;
    }
    bool should_blend = fc.blend_op == BlendOp::OVER;
    bool use_for_next_frame =
        has_nontrivial_background && fc.dispose_op != DisposeOp::PREVIOUS;
    size_t x0 = vp.x0();
    size_t y0 = vp.y0();
    if (previous_frame_should_be_cleared) {
      const auto& pvp = frames[i - 1].metadata.viewport;
      size_t px0 = pvp.x0();
      size_t py0 = pvp.y0();
      size_t pxs = pvp.xsize();
      size_t pys = pvp.ysize();
      if (px0 >= x0 && py0 >= y0 && px0 + pxs <= x0 + xsize &&
          py0 + pys <= y0 + ysize && fc.blend_op == BlendOp::SOURCE &&
          use_for_next_frame) {
        // If the previous frame is entirely contained in the current frame
        // and we are using BLEND_OP_SOURCE, nothing special needs to be done.
        ppf->frames.emplace_back(std::move(frame.pixels));
      } else if (px0 == x0 && py0 == y0 && px0 + pxs == x0 + xsize &&
                 py0 + pys == y0 + ysize && use_for_next_frame) {
        // If the new frame has the same size as the old one, but we are
        // blending, we can instead just not blend.
        should_blend = false;
        ppf->frames.emplace_back(std::move(frame.pixels));
      } else if (px0 <= x0 && py0 <= y0 && px0 + pxs >= x0 + xsize &&
                 py0 + pys >= y0 + ysize && use_for_next_frame) {
        // If the new frame is contained within the old frame, we can pad the
        // new frame with zeros and not blend.
        JXL_ASSIGN_OR_RETURN(PackedImage new_data,
                             PackedImage::Create(pxs, pys, pixels.format));
        memset(new_data.pixels(), 0, new_data.pixels_size);
        for (size_t y = 0; y < ysize; y++) {
          JXL_RETURN_IF_ERROR(
              PackedImage::ValidateDataType(new_data.format.data_type));
          size_t bytes_per_pixel =
              PackedImage::BitsPerChannel(new_data.format.data_type) *
              new_data.format.num_channels / 8;
          memcpy(
              static_cast<uint8_t*>(new_data.pixels()) +
                  new_data.stride * (y + y0 - py0) +
                  bytes_per_pixel * (x0 - px0),
              static_cast<const uint8_t*>(pixels.pixels()) + pixels.stride * y,
              xsize * bytes_per_pixel);
        }

        x0 = px0;
        y0 = py0;
        xsize = pxs;
        ysize = pys;
        should_blend = false;
        ppf->frames.emplace_back(std::move(new_data));
      } else {
        // If all else fails, insert a placeholder blank frame with kReplace.
        JXL_ASSIGN_OR_RETURN(PackedImage blank,
                             PackedImage::Create(pxs, pys, pixels.format));
        memset(blank.pixels(), 0, blank.pixels_size);
        ppf->frames.emplace_back(std::move(blank));
        auto& pframe = ppf->frames.back();
        pframe.frame_info.layer_info.crop_x0 = px0;
        pframe.frame_info.layer_info.crop_y0 = py0;
        pframe.frame_info.layer_info.xsize = pxs;
        pframe.frame_info.layer_info.ysize = pys;
        pframe.frame_info.duration = 0;
        bool is_full_size = px0 == 0 && py0 == 0 && pxs == ppf->info.xsize &&
                            pys == ppf->info.ysize;
        pframe.frame_info.layer_info.have_crop = is_full_size ? 0 : 1;
        pframe.frame_info.layer_info.blend_info.blendmode = JXL_BLEND_REPLACE;
        pframe.frame_info.layer_info.blend_info.source = 1;
        pframe.frame_info.layer_info.save_as_reference = 1;
        ppf->frames.emplace_back(std::move(frame.pixels));
      }
    } else {
      ppf->frames.emplace_back(std::move(frame.pixels));
    }

    auto& pframe = ppf->frames.back();
    pframe.frame_info.layer_info.crop_x0 = x0;
    pframe.frame_info.layer_info.crop_y0 = y0;
    pframe.frame_info.layer_info.xsize = xsize;
    pframe.frame_info.layer_info.ysize = ysize;
    pframe.frame_info.duration =
        fc.delay_num * 1000 / (fc.delay_den ? fc.delay_den : 100);
    pframe.frame_info.layer_info.blend_info.blendmode =
        should_blend ? JXL_BLEND_BLEND : JXL_BLEND_REPLACE;
    bool is_full_size = x0 == 0 && y0 == 0 && xsize == ppf->info.xsize &&
                        ysize == ppf->info.ysize;
    pframe.frame_info.layer_info.have_crop = is_full_size ? 0 : 1;
    pframe.frame_info.layer_info.blend_info.source = 1;
    pframe.frame_info.layer_info.blend_info.alpha = 0;
    pframe.frame_info.layer_info.save_as_reference = use_for_next_frame ? 1 : 0;

    previous_frame_should_be_cleared =
        has_nontrivial_background && (fc.dispose_op == DisposeOp::BACKGROUND);
  }

  if (ppf->frames.empty()) return JXL_FAILURE("No frames decoded");
  ppf->frames.back().frame_info.is_last = JXL_TRUE;

  return true;
}

#endif  // JPEGXL_ENABLE_APNG

}  // namespace extras
}  // namespace jxl

Messung V0.5
C=93 H=94 G=93

¤ Dauer der Verarbeitung: 0.35 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.