text: /*
   * Copyright (C) 2006 Apple Computer, Inc.
   *
   * Portions are Copyright (C) 2001-6 mozilla.org
   *
   * Other contributors:
   *   Stuart Parmenter <stuart@mozilla.com>
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   * Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
   *
   * Alternatively, the contents of this file may be used under the terms
   * of either the Mozilla Public License Version 1.1, found at
   * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public
   * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html
   * (the "GPL"), in which case the provisions of the MPL or the GPL are
   * applicable instead of those above.  If you wish to allow use of your
   * version of this file only under the terms of one of those two
   * licenses (the MPL or the GPL) and not to allow others to use your
   * version of this file under the LGPL, indicate your decision by
   * deletingthe provisions above and replace them with the notice and
   * other provisions required by the MPL or the GPL, as the case may be.
   * If you do not delete the provisions above, a recipient may use your
   * version of this file under any of the LGPL, the MPL or the GPL.
   */
  
  #include "third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder.h"
  
  #include <limits>
  #include <memory>
  
  #include "base/logging.h"
  #include "base/numerics/checked_math.h"
  #include "build/build_config.h"
  #include "third_party/blink/renderer/platform/geometry/float_size.h"
  #include "third_party/blink/renderer/platform/graphics/bitmap_image_metrics.h"
  #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
  #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
  
  extern "C" {
  #include <stdio.h>  // jpeglib.h needs stdio FILE.
  #include "jpeglib.h"
  #include "iccjpeg.h"
  #include <setjmp.h>
  }
  
  #if defined(ARCH_CPU_BIG_ENDIAN)
  #error Blink assumes a little-endian target.
  #endif
  
  #if defined(JCS_ALPHA_EXTENSIONS)
  #define TURBO_JPEG_RGB_SWIZZLE
  #if SK_B32_SHIFT  // Output little-endian RGBA pixels (Android).
  inline J_COLOR_SPACE rgbOutputColorSpace() {
    return JCS_EXT_RGBA;
  }
  #else  // Output little-endian BGRA pixels.
  inline J_COLOR_SPACE rgbOutputColorSpace() {
    return JCS_EXT_BGRA;
  }
  #endif
  inline bool turboSwizzled(J_COLOR_SPACE colorSpace) {
    return colorSpace == JCS_EXT_RGBA || colorSpace == JCS_EXT_BGRA;
  }
  #else
  inline J_COLOR_SPACE rgbOutputColorSpace() {
    return JCS_RGB;
  }
  #endif
  
  namespace {
  
  const int exifMarker = JPEG_APP0 + 1;
  
  // JPEG only supports a denominator of 8.
  const unsigned g_scale_denominator = 8;
  
  // Extracts the YUV subsampling format of an image given |info| which is assumed
  // to have gone through a jpeg_read_header() call.
  cc::YUVSubsampling YuvSubsampling(const jpeg_decompress_struct& info) {
    if (info.jpeg_color_space == JCS_YCbCr && info.num_components == 3 &&
        info.comp_info && info.comp_info[1].h_samp_factor == 1 &&
        info.comp_info[1].v_samp_factor == 1 &&
        info.comp_info[2].h_samp_factor == 1 &&
        info.comp_info[2].v_samp_factor == 1) {
      const int h = info.comp_info[0].h_samp_factor;
      const int v = info.comp_info[0].v_samp_factor;
      if (v == 1) {
        switch (h) {
          case 1:
            return cc::YUVSubsampling::k444;
          case 2:
            return cc::YUVSubsampling::k422;
          case 4:
            return cc::YUVSubsampling::k411;
        }
      } else if (v == 2) {
        switch (h) {
          case 1:
            return cc::YUVSubsampling::k440;
          case 2:
            return cc::YUVSubsampling::k420;
          case 4:
            return cc::YUVSubsampling::k410;
        }
      }
    }
    return cc::YUVSubsampling::kUnknown;
  }
  
  // Extracts the JPEG color space of an image for UMA purposes given |info| which
  // is assumed to have gone through a jpeg_read_header(). When the color space is
  // YCbCr, we also extract the chroma subsampling. The caveat is that the
  // extracted color space is really libjpeg_turbo's guess. According to
  // libjpeg.txt, "[t]he JPEG color space, unfortunately, is something of a guess
  // since the JPEG standard proper does not provide a way to record it. In
  // practice most files adhere to the JFIF or Adobe conventions, and the decoder
  // will recognize these correctly."
  blink::BitmapImageMetrics::JpegColorSpace ExtractUMAJpegColorSpace(
      const jpeg_decompress_struct& info) {
    switch (info.jpeg_color_space) {
      case JCS_GRAYSCALE:
        return blink::BitmapImageMetrics::JpegColorSpace::kGrayscale;
      case JCS_RGB:
        return blink::BitmapImageMetrics::JpegColorSpace::kRGB;
      case JCS_CMYK:
        return blink::BitmapImageMetrics::JpegColorSpace::kCMYK;
      case JCS_YCCK:
        return blink::BitmapImageMetrics::JpegColorSpace::kYCCK;
      case JCS_YCbCr:
        switch (YuvSubsampling(info)) {
          case cc::YUVSubsampling::k444:
            return blink::BitmapImageMetrics::JpegColorSpace::kYCbCr444;
          case cc::YUVSubsampling::k422:
            return blink::BitmapImageMetrics::JpegColorSpace::kYCbCr422;
          case cc::YUVSubsampling::k411:
            return blink::BitmapImageMetrics::JpegColorSpace::kYCbCr411;
          case cc::YUVSubsampling::k440:
            return blink::BitmapImageMetrics::JpegColorSpace::kYCbCr440;
          case cc::YUVSubsampling::k420:
            return blink::BitmapImageMetrics::JpegColorSpace::kYCbCr420;
          case cc::YUVSubsampling::k410:
            return blink::BitmapImageMetrics::JpegColorSpace::kYCbCr410;
          case cc::YUVSubsampling::kUnknown:
            return blink::BitmapImageMetrics::JpegColorSpace::kYCbCrOther;
        }
        NOTREACHED();
      default:
        return blink::BitmapImageMetrics::JpegColorSpace::kUnknown;
    }
  }
  
  // Rounds |size| to the smallest multiple of |alignment| that is greater than or
  // equal to |size|.
  // Note that base::bits::Align is not used here because the alignment is not
  // guaranteed to be a power of two.
  int Align(int size, int alignment) {
    // Width and height are 16 bits for a JPEG (i.e. < 65536) and the maximum
    // size of a JPEG MCU in either dimension is 8 * 4 == 32.
    DCHECK_GE(size, 0);
    DCHECK_LT(size, 1 << 16);
    DCHECK_GT(alignment, 0);
    DCHECK_LE(alignment, 32);
  
    if (size % alignment == 0)
      return size;
  
    return ((size + alignment) / alignment) * alignment;
  }
  
  }  // namespace
  
  namespace blink {
  
  struct decoder_error_mgr {
    DISALLOW_NEW();
    struct jpeg_error_mgr pub;  // "public" fields for IJG library
    int num_corrupt_warnings;   // Counts corrupt warning messages
    jmp_buf setjmp_buffer;      // For handling catastropic errors
  };
  
  struct decoder_source_mgr {
    DISALLOW_NEW();
    struct jpeg_source_mgr pub;  // "public" fields for IJG library
    JPEGImageReader* reader;
  };
  
  enum jstate {
    kJpegHeader,  // Reading JFIF headers
    kJpegStartDecompress,
    kJpegDecompressProgressive,  // Output progressive pixels
    kJpegDecompressSequential,   // Output sequential pixels
    kJpegDone
  };
  
  void init_source(j_decompress_ptr jd);
  boolean fill_input_buffer(j_decompress_ptr jd);
  void skip_input_data(j_decompress_ptr jd, long num_bytes);
  void term_source(j_decompress_ptr jd);
  void error_exit(j_common_ptr cinfo);
  void emit_message(j_common_ptr cinfo, int msg_level);
  
  static unsigned ReadUint16(JOCTET* data, bool is_big_endian) {
    if (is_big_endian)
      return (GETJOCTET(data[0]) << 8) | GETJOCTET(data[1]);
    return (GETJOCTET(data[1]) << 8) | GETJOCTET(data[0]);
  }
  
  static unsigned ReadUint32(JOCTET* data, bool is_big_endian) {
    if (is_big_endian)
      return (GETJOCTET(data[0]) << 24) | (GETJOCTET(data[1]) << 16) |
             (GETJOCTET(data[2]) << 8) | GETJOCTET(data[3]);
    return (GETJOCTET(data[3]) << 24) | (GETJOCTET(data[2]) << 16) |
           (GETJOCTET(data[1]) << 8) | GETJOCTET(data[0]);
  }
  
  static JOCTET* ReadPointerOffset(JOCTET* data,
                                   JOCTET* start,
                                   JOCTET* end,
                                   bool is_big_endian) {
    unsigned max_offset = base::checked_cast<unsigned>(end - start);
    unsigned offset = ReadUint32(data, is_big_endian);
    if (offset > max_offset)
      return nullptr;
  
    return start + offset;
  }
  
  static float ReadUnsignedRational(JOCTET* data, bool is_big_endian) {
    unsigned nom = ReadUint32(data, is_big_endian);
    unsigned denom = ReadUint32(data + 4, is_big_endian);
    if (!denom)
      return 0;
    return float(nom) / float(denom);
  }
  
  static bool CheckExifHeader(jpeg_saved_marker_ptr marker,
                              bool& is_big_endian,
                              unsigned& ifd_offset) {
    // For exif data, the APP1 block is followed by 'E', 'x', 'i', 'f', '\0',
    // then a fill byte, and then a tiff file that contains the metadata.
    // A tiff file starts with 'I', 'I' (intel / little endian byte order) or
    // 'M', 'M' (motorola / big endian byte order), followed by (uint16_t)42,
    // followed by an uint32_t with the offset to the tag block, relative to the
    // tiff file start.
    const unsigned kExifHeaderSize = 14;
    if (!(marker->marker == exifMarker &&
          marker->data_length >= kExifHeaderSize && marker->data[0] == 'E' &&
          marker->data[1] == 'x' && marker->data[2] == 'i' &&
          marker->data[3] == 'f' &&
          marker->data[4] == '\0'
          // data[5] is a fill byte
          && ((marker->data[6] == 'I' && marker->data[7] == 'I') ||
              (marker->data[6] == 'M' && marker->data[7] == 'M'))))
      return false;
  
    is_big_endian = marker->data[6] == 'M';
    if (ReadUint16(marker->data + 8, is_big_endian) != 42)
      return false;
  
    ifd_offset = ReadUint32(marker->data + 10, is_big_endian);
    return true;
  }
  
  struct DecodedImageMetaData {
    ImageOrientation orientation;
    FloatSize resolution;
    IntSize size;
    unsigned resolution_unit { 0 };
  };
  
  static IntSize ExtractDensityCorrectedSize(const DecodedImageMetaData& metadata, const IntSize& physical_size) {
    const unsigned kDefaultResolution = 72;
    const unsigned kresolution_unitDPI = 2;
  
    if (metadata.resolution_unit != kresolution_unitDPI || metadata.resolution.IsEmpty() || metadata.size.IsEmpty())
      return physical_size;
  
    CHECK(metadata.resolution.width());
    CHECK(metadata.resolution.height());
  
    // Division by zero is not possible since we check for empty resolution earlier.
    FloatSize size_from_resolution(
        physical_size.width() * kDefaultResolution / metadata.resolution.width(),
        physical_size.height() * kDefaultResolution /
            metadata.resolution.height());
  
    if (RoundedIntSize(size_from_resolution) == metadata.size)
      return metadata.size;
  
    return physical_size;
  }
  
  static void ReadExifDirectory(JOCTET* dir_start,
                                JOCTET* tiff_start,
                                JOCTET* root_dir_start,
                                JOCTET* data_end,
                                bool is_big_endian,
                                DecodedImageMetaData& metadata,
                                bool is_root = true) {
    const unsigned kUnsignedShortType = 3;
    const unsigned kUnsignedLongType = 4;
    const unsigned kUnsignedRationalType = 5;
  
    enum ExifTags {
      kOrientationTag = 0x112,
      kResolutionXTag = 0x11a,
      kResolutionYTag = 0x11b,
      kResolutionUnitTag = 0x128,
      kPixelXDimensionTag = 0xa002,
      kPixelYDimensionTag = 0xa003,
      kExifOffsetTag = 0x8769
    };
  
    if (data_end - dir_start < 2)
      return;
  
    unsigned tag_count = ReadUint16(dir_start, is_big_endian);
    JOCTET* ifd = dir_start + 2;  // Skip over the uint16 that was just read.
  
    // Every ifd entry is 2 bytes of tag, 2 bytes of contents datatype,
    // 4 bytes of number-of-elements, and 4 bytes of either offset to the
    // tag data, or if the data is small enough, the inlined data itself.
    const int kIfdEntrySize = 12;
    for (unsigned i = 0; i < tag_count && data_end - ifd >= kIfdEntrySize;
          ++i, ifd += kIfdEntrySize) {
      unsigned tag = ReadUint16(ifd, is_big_endian);
      unsigned type = ReadUint16(ifd + 2, is_big_endian);
      unsigned count = ReadUint32(ifd + 4, is_big_endian);
      JOCTET* value_ptr = ifd + 8;
  
      // EXIF stores the value with an offset if it's bigger than 4 bytes, e.g. for rational values.
      if (type == kUnsignedRationalType) {
        value_ptr =
            ReadPointerOffset(value_ptr, tiff_start, data_end, is_big_endian);
        // Make sure offset points to a valid location.
        if (!value_ptr || value_ptr < ifd || value_ptr > data_end - 16)
          continue;
      }
  
      switch (tag) {
        case ExifTags::kOrientationTag:
          if (type == kUnsignedShortType && count == 1)
            metadata.orientation = ImageOrientation::FromEXIFValue(ReadUint16(value_ptr, is_big_endian));
          break;
  
        case ExifTags::kResolutionUnitTag:
          if (type == kUnsignedShortType && count == 1)
            metadata.resolution_unit = ReadUint16(value_ptr, is_big_endian);
          break;
  
        case ExifTags::kResolutionXTag:
          if (type == kUnsignedRationalType && count == 1) {
            metadata.resolution.set_width(
                ReadUnsignedRational(value_ptr, is_big_endian));
          }
          break;
  
        case ExifTags::kResolutionYTag:
          if (type == kUnsignedRationalType && count == 1) {
            metadata.resolution.set_height(
                ReadUnsignedRational(value_ptr, is_big_endian));
          }
          break;
  
        case ExifTags::kPixelXDimensionTag:
          if (count != 1)
            break;
          switch (type) {
            case kUnsignedShortType:
              metadata.size.set_width(ReadUint16(value_ptr, is_big_endian));
              break;
            case kUnsignedLongType:
              metadata.size.set_width(ReadUint32(value_ptr, is_big_endian));
              break;
          }
          break;
  
        case ExifTags::kPixelYDimensionTag:
          if (count != 1)
            break;
          switch (type) {
            case kUnsignedShortType:
              metadata.size.set_height(ReadUint16(value_ptr, is_big_endian));
              break;
            case kUnsignedLongType:
              metadata.size.set_height(ReadUint32(value_ptr, is_big_endian));
              break;
          }
          break;
  
        case ExifTags::kExifOffsetTag:
          if (type == kUnsignedLongType && count == 1 && is_root) {
            JOCTET* subdir = ReadPointerOffset(value_ptr, root_dir_start,
                                               data_end, is_big_endian);
  
            if (subdir) {
              ReadExifDirectory(subdir, tiff_start, root_dir_start, data_end,
                                is_big_endian, metadata, false);
            }
          }
          break;
      }
    }
  }
  
  static void ReadImageMetaData(jpeg_decompress_struct* info, DecodedImageMetaData& metadata) {
    // The JPEG decoder looks at EXIF metadata.
    // FIXME: Possibly implement XMP and IPTC support.
    for (jpeg_saved_marker_ptr marker = info->marker_list; marker;
         marker = marker->next) {
      bool is_big_endian;
      unsigned ifd_offset;
      if (!CheckExifHeader(marker, is_big_endian, ifd_offset))
        continue;
      const unsigned kOffsetToTiffData =
          6;  // Account for 'Exif\0<fill byte>' header.
      if (marker->data_length < kOffsetToTiffData ||
          ifd_offset >= marker->data_length - kOffsetToTiffData)
        continue;
  
      // The jpeg exif container format contains a tiff block for metadata.
      // A tiff image file directory (ifd) consists of a uint16_t describing
      // the number of ifd entries, followed by that many entries.
      // When touching this code, it's useful to look at the tiff spec:
      // http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf
      JOCTET* data_end = marker->data + marker->data_length;
      JOCTET* root_start = marker->data + kOffsetToTiffData;
      JOCTET* tiff_start = marker->data + ifd_offset - 2;
      JOCTET* ifd0 = root_start + ifd_offset;
  
      ReadExifDirectory(ifd0, tiff_start, root_start, data_end, is_big_endian, metadata);
    }
  }
  
  static IntSize ComputeYUVSize(const jpeg_decompress_struct* info,
                                int component) {
    return IntSize(info->comp_info[component].downsampled_width,
                   info->comp_info[component].downsampled_height);
  }
  
  static wtf_size_t ComputeYUVWidthBytes(const jpeg_decompress_struct* info,
                                         int component) {
    return info->comp_info[component].width_in_blocks * DCTSIZE;
  }
  
  static void ProgressMonitor(j_common_ptr info) {
    int scan = ((j_decompress_ptr)info)->input_scan_number;
    // Progressive images with a very large number of scans can cause the
    // decoder to hang.  Here we use the progress monitor to abort on
    // a very large number of scans.  100 is arbitrary, but much larger
    // than the number of scans we might expect in a normal image.
    if (scan >= 100) {
      error_exit(info);
    }
  }
  
  class JPEGImageReader final {
    USING_FAST_MALLOC(JPEGImageReader);
  
   public:
    JPEGImageReader(JPEGImageDecoder* decoder, wtf_size_t initial_offset)
        : decoder_(decoder),
          needs_restart_(false),
          restart_position_(initial_offset),
          next_read_position_(initial_offset),
          last_set_byte_(nullptr),
          state_(kJpegHeader),
          samples_(nullptr) {
      memset(&info_, 0, sizeof(jpeg_decompress_struct));
  
      // Set up the normal JPEG error routines, then override error_exit.
      info_.err = jpeg_std_error(&err_.pub);
      err_.pub.error_exit = error_exit;
  
      // Allocate and initialize JPEG decompression object.
      jpeg_create_decompress(&info_);
  
      // Initialize source manager.
      memset(&src_, 0, sizeof(decoder_source_mgr));
      info_.src = reinterpret_cast_ptr<jpeg_source_mgr*>(&src_);
  
      // Set up callback functions.
      src_.pub.init_source = init_source;
      src_.pub.fill_input_buffer = fill_input_buffer;
      src_.pub.skip_input_data = skip_input_data;
      src_.pub.resync_to_restart = jpeg_resync_to_restart;
      src_.pub.term_source = term_source;
      src_.reader = this;
  
      // Set up a progress monitor.
      info_.progress = &progress_mgr_;
      progress_mgr_.progress_monitor = ProgressMonitor;
  
      // Retain ICC color profile markers for color management.
      setup_read_icc_profile(&info_);
  
      // Keep APP1 blocks, for obtaining exif data.
      jpeg_save_markers(&info_, exifMarker, 0xFFFF);
    }
  
    JPEGImageReader(const JPEGImageReader&) = delete;
    JPEGImageReader& operator=(const JPEGImageReader&) = delete;
  
    ~JPEGImageReader() { jpeg_destroy_decompress(&info_); }
  
    void SkipBytes(long num_bytes) {
      if (num_bytes <= 0)
        return;
  
      wtf_size_t bytes_to_skip = static_cast<wtf_size_t>(num_bytes);
  
      if (bytes_to_skip < info_.src->bytes_in_buffer) {
        // The next byte needed is in the buffer. Move to it.
        info_.src->bytes_in_buffer -= bytes_to_skip;
        info_.src->next_input_byte += bytes_to_skip;
      } else {
        // Move beyond the buffer and empty it.
        next_read_position_ = static_cast<wtf_size_t>(
            next_read_position_ + bytes_to_skip - info_.src->bytes_in_buffer);
        info_.src->bytes_in_buffer = 0;
        info_.src->next_input_byte = nullptr;
      }
  
      // This is a valid restart position.
      restart_position_ = static_cast<wtf_size_t>(next_read_position_ -
                                                  info_.src->bytes_in_buffer);
      // We updated |next_input_byte|, so we need to update |last_byte_set_|
      // so we know not to update |restart_position_| again.
      last_set_byte_ = info_.src->next_input_byte;
    }
  
    bool FillBuffer() {
      if (needs_restart_) {
        needs_restart_ = false;
        next_read_position_ = restart_position_;
      } else {
        UpdateRestartPosition();
      }
  
      const char* segment;
      const size_t bytes = data_->GetSomeData(segment, next_read_position_);
      if (bytes == 0) {
        // We had to suspend. When we resume, we will need to start from the
        // restart position.
        needs_restart_ = true;
        ClearBuffer();
        return false;
      }
  
      next_read_position_ += bytes;
      info_.src->bytes_in_buffer = bytes;
      const JOCTET* next_byte = reinterpret_cast_ptr<const JOCTET*>(segment);
      info_.src->next_input_byte = next_byte;
      last_set_byte_ = next_byte;
      return true;
    }
  
    void SetData(SegmentReader* data) {
      if (data_.get() == data)
        return;
  
      data_ = data;
  
      // If a restart is needed, the next call to fillBuffer will read from the
      // new SegmentReader.
      if (needs_restart_)
        return;
  
      // Otherwise, empty the buffer, and leave the position the same, so
      // FillBuffer continues reading from the same position in the new
      // SegmentReader.
      next_read_position_ -= info_.src->bytes_in_buffer;
      ClearBuffer();
    }
  
    bool ShouldDecodeToOriginalSize() const {
      // We should decode only to original size if either dimension cannot fit a
      // whole number of MCUs.
      const int max_h_samp_factor = info_.max_h_samp_factor;
      const int max_v_samp_factor = info_.max_v_samp_factor;
      DCHECK_GE(max_h_samp_factor, 1);
      DCHECK_GE(max_v_samp_factor, 1);
      DCHECK_LE(max_h_samp_factor, 4);
      DCHECK_LE(max_v_samp_factor, 4);
      const int mcu_width = info_.max_h_samp_factor * DCTSIZE;
      const int mcu_height = info_.max_v_samp_factor * DCTSIZE;
      return info_.image_width % mcu_width != 0 ||
             info_.image_height % mcu_height != 0;
    }
  
    // Whether or not the horizontal and vertical sample factors of all components
    // hold valid values (i.e. 1, 2, 3, or 4). It also returns the maximal
    // horizontal and vertical sample factors via |max_h| and |max_v|.
    bool AreValidSampleFactorsAvailable(int* max_h, int* max_v) const {
      if (!info_.num_components)
        return false;
  
      const jpeg_component_info* comp_info = info_.comp_info;
      if (!comp_info)
        return false;
  
      *max_h = 0;
      *max_v = 0;
      for (int i = 0; i < info_.num_components; ++i) {
        if (comp_info[i].h_samp_factor < 1 || comp_info[i].h_samp_factor > 4 ||
            comp_info[i].v_samp_factor < 1 || comp_info[i].v_samp_factor > 4) {
          return false;
        }
  
        *max_h = std::max(*max_h, comp_info[i].h_samp_factor);
        *max_v = std::max(*max_v, comp_info[i].v_samp_factor);
      }
      return true;
    }
  
    // Decode the JPEG data.
    bool Decode(JPEGImageDecoder::DecodingMode decoding_mode) {
      // We need to do the setjmp here. Otherwise bad things will happen
      if (setjmp(err_.setjmp_buffer))
        return decoder_->SetFailed();
  
      switch (state_) {
        case kJpegHeader: {
          // Read file parameters with jpeg_read_header().
          if (jpeg_read_header(&info_, true) == JPEG_SUSPENDED)
            return false;  // I/O suspension.
  
          switch (info_.jpeg_color_space) {
            case JCS_YCbCr:
              FALLTHROUGH;  // libjpeg can convert YCbCr image pixels to RGB.
            case JCS_GRAYSCALE:
              FALLTHROUGH;  // libjpeg can convert GRAYSCALE image pixels to RGB.
            case JCS_RGB:
              info_.out_color_space = rgbOutputColorSpace();
              break;
            case JCS_CMYK:
            case JCS_YCCK:
              // libjpeg can convert YCCK to CMYK, but neither to RGB, so we
              // manually convert CMKY to RGB.
              info_.out_color_space = JCS_CMYK;
              break;
            default:
              return decoder_->SetFailed();
          }
  
          state_ = kJpegStartDecompress;
  
          // We can fill in the size now that the header is available.
          if (!decoder_->SetSize(info_.image_width, info_.image_height))
            return false;
  
          // Calculate and set decoded size.
          int max_numerator = decoder_->DesiredScaleNumerator();
          info_.scale_denom = g_scale_denominator;
  
          if (decoder_->ShouldGenerateAllSizes()) {
            // Some images should not be scaled down by libjpeg_turbo because
            // doing so may cause artifacts. Specifically, if the image contains a
            // non-whole number of MCUs in either dimension, it's possible that
            // the encoder used bogus data to create the last row or column of
            // MCUs. This data may manifest when downscaling using libjpeg_turbo.
            // See https://crbug.com/890745 and
            // https://github.com/libjpeg-turbo/libjpeg-turbo/issues/297. Hence,
            // we'll only allow downscaling an image if both dimensions fit a
            // whole number of MCUs or if decoding to the original size would
            // cause us to exceed memory limits. The latter case is detected by
            // checking the |max_numerator| returned by DesiredScaleNumerator():
            // this method will return either |g_scale_denominator| if decoding to
            // the original size won't exceed the memory limit (see
            // |max_decoded_bytes_| in ImageDecoder) or something less than
            // |g_scale_denominator| otherwise to ensure the image is downscaled.
            Vector<SkISize> sizes;
            if (max_numerator == g_scale_denominator &&
                ShouldDecodeToOriginalSize()) {
              sizes.push_back(
                  SkISize::Make(info_.image_width, info_.image_height));
            } else {
              sizes.ReserveCapacity(max_numerator);
              for (int numerator = 1; numerator <= max_numerator; ++numerator) {
                info_.scale_num = numerator;
                jpeg_calc_output_dimensions(&info_);
                sizes.push_back(
                    SkISize::Make(info_.output_width, info_.output_height));
              }
            }
            decoder_->SetSupportedDecodeSizes(std::move(sizes));
          }
  
          info_.scale_num = max_numerator;
          jpeg_calc_output_dimensions(&info_);
          decoder_->SetDecodedSize(info_.output_width, info_.output_height);
  
          DecodedImageMetaData metadata;
          ReadImageMetaData(Info(), metadata);
          decoder_->SetOrientation(metadata.orientation);
          decoder_->SetDensityCorrectedSize(ExtractDensityCorrectedSize(metadata, IntSize(info_.output_width, info_.output_height)));
  
          // Allow color management of the decoded RGBA pixels if possible.
          if (!decoder_->IgnoresColorSpace()) {
            JOCTET* profile_buf = nullptr;
            unsigned profile_length = 0;
            if (read_icc_profile(Info(), &profile_buf, &profile_length)) {
              std::unique_ptr<ColorProfile> profile =
                  ColorProfile::Create(profile_buf, profile_length);
              if (profile) {
                uint32_t data_color_space =
                    profile->GetProfile()->data_color_space;
                switch (info_.jpeg_color_space) {
                  case JCS_CMYK:
                  case JCS_YCCK:
                    if (data_color_space != skcms_Signature_CMYK)
                      profile = nullptr;
                    break;
                  case JCS_GRAYSCALE:
                    if (data_color_space != skcms_Signature_Gray &&
                        data_color_space != skcms_Signature_RGB)
                      profile = nullptr;
                    break;
                  default:
                    if (data_color_space != skcms_Signature_RGB)
                      profile = nullptr;
                    break;
                }
                if (profile)
                  Decoder()->SetEmbeddedColorProfile(std::move(profile));
              } else {
                DLOG(ERROR) << "Failed to parse image ICC profile";
              }
              free(profile_buf);
            }
          }
  
          // Don't allocate a giant and superfluous memory buffer when the
          // image is a sequential JPEG.
          info_.buffered_image = jpeg_has_multiple_scans(&info_);
          if (info_.buffered_image) {
            err_.pub.emit_message = emit_message;
            err_.num_corrupt_warnings = 0;
          }
  
          if (decoding_mode == JPEGImageDecoder::DecodingMode::kDecodeHeader) {
            // This exits the function while there is still potentially
            // data in the buffer. Before this function is called again,
            // the SharedBuffer may be collapsed (by a call to
            // MergeSegmentsIntoBuffer), invalidating the "buffer" (which
            // in reality is a pointer into the SharedBuffer's data).
            // Defensively empty the buffer, but first find the latest
            // restart position and signal to restart, so the next call to
            // FillBuffer will resume from the correct point.
            needs_restart_ = true;
            UpdateRestartPosition();
            ClearBuffer();
            return true;
          }
        }
        FALLTHROUGH;
        case kJpegStartDecompress:
          if (decoding_mode == JPEGImageDecoder::DecodingMode::kDecodeToYuv) {
            DCHECK(decoder_->CanDecodeToYUV());
            DCHECK(decoder_->HasImagePlanes());
            info_.out_color_space = JCS_YCbCr;
            info_.raw_data_out = TRUE;
            uv_size_ = ComputeYUVSize(&info_, 1);
            // U size and V size have to be the same if we got here
            DCHECK_EQ(uv_size_, ComputeYUVSize(&info_, 2));
          }
  
          // Set parameters for decompression.
          // FIXME -- Should reset dct_method and dither mode for final pass
          // of progressive JPEG.
          info_.dct_method = JDCT_ISLOW;
          info_.dither_mode = JDITHER_FS;
          info_.do_fancy_upsampling = true;
          info_.do_block_smoothing = true;
          info_.enable_2pass_quant = false;
          // FIXME: should we just assert these?
          info_.enable_external_quant = false;
          info_.enable_1pass_quant = false;
          info_.quantize_colors = false;
          info_.colormap = nullptr;
  
          // Make a one-row-high sample array that will go away when done with
          // image. Always make it big enough to hold one RGBA row. Since this
          // uses the IJG memory manager, it must be allocated before the call
          // to jpeg_start_decompress().
          samples_ = AllocateSampleArray();
  
          // Start decompressor.
          if (!jpeg_start_decompress(&info_))
            return false;  // I/O suspension.
  
          // If this is a progressive JPEG ...
          state_ = (info_.buffered_image) ? kJpegDecompressProgressive
                                          : kJpegDecompressSequential;
          FALLTHROUGH;
  
        case kJpegDecompressSequential:
          if (state_ == kJpegDecompressSequential) {
            if (!decoder_->OutputScanlines())
              return false;  // I/O suspension.
  
            // If we've completed image output...
            DCHECK_EQ(info_.output_scanline, info_.output_height);
            state_ = kJpegDone;
          }
          FALLTHROUGH;
  
        case kJpegDecompressProgressive:
          if (state_ == kJpegDecompressProgressive) {
            auto all_components_seen = [](const jpeg_decompress_struct& info) {
              if (info.coef_bits) {
                for (int c = 0; c < info.num_components; ++c) {
                  if (info.coef_bits[c][0] == -1) {
                    // Haven't seen this component yet.
                    return false;
                  }
                }
              }
              return true;
            };
            int status = 0;
            int first_scan_to_display =
                all_components_seen(info_) ? info_.input_scan_number : 0;
            do {
              decoder_error_mgr* err =
                  reinterpret_cast_ptr<decoder_error_mgr*>(info_.err);
              if (err->num_corrupt_warnings)
                break;
              status = jpeg_consume_input(&info_);
              if (status == JPEG_REACHED_SOS || status == JPEG_REACHED_EOI ||
                  status == JPEG_SUSPENDED) {
                // record the first scan where all components are present
                if (!first_scan_to_display && all_components_seen(info_)) {
                  first_scan_to_display = info_.input_scan_number;
                }
              }
            } while (!(status == JPEG_SUSPENDED || status == JPEG_REACHED_EOI));
  
            if (!first_scan_to_display) {
              return false;  // I/O suspension
            }
  
            for (;;) {
              if (!info_.output_scanline) {
                int scan = info_.input_scan_number;
  
                // If we haven't displayed anything yet
                // (output_scan_number == 0) and we have enough data for
                // a complete scan, force output of the last full scan, but only
                // if this last scan has seen DC data from all components.
                if (!info_.output_scan_number && (scan > first_scan_to_display) &&
                    (status != JPEG_REACHED_EOI))
                  --scan;
  
                if (!jpeg_start_output(&info_, scan))
                  return false;  // I/O suspension.
              }
  
              if (info_.output_scanline == 0xffffff)
                info_.output_scanline = 0;
  
              if (!decoder_->OutputScanlines()) {
                if (decoder_->Failed())
                  return false;
                // If no scan lines were read, flag it so we don't call
                // jpeg_start_output() multiple times for the same scan.
                if (!info_.output_scanline)
                  info_.output_scanline = 0xffffff;
  
                return false;  // I/O suspension.
              }
  
              if (info_.output_scanline == info_.output_height) {
                if (!jpeg_finish_output(&info_))
                  return false;  // I/O suspension.
  
                if (jpeg_input_complete(&info_) &&
                    (info_.input_scan_number == info_.output_scan_number))
                  break;
  
                info_.output_scanline = 0;
              }
            }
  
            state_ = kJpegDone;
          }
          FALLTHROUGH;
  
        case kJpegDone:
          // Finish decompression.
          BitmapImageMetrics::CountJpegArea(decoder_->Size());
          BitmapImageMetrics::CountJpegColorSpace(
              ExtractUMAJpegColorSpace(info_));
          return jpeg_finish_decompress(&info_);
      }
  
      return true;
    }
  
    jpeg_decompress_struct* Info() { return &info_; }
    JSAMPARRAY Samples() const { return samples_; }
    JPEGImageDecoder* Decoder() { return decoder_; }
    IntSize UvSize() const { return uv_size_; }
    bool HasStartedDecompression() const { return state_ > kJpegStartDecompress; }
  
   private:
  #if defined(USE_SYSTEM_LIBJPEG)
    NO_SANITIZE_CFI_ICALL
  #endif
    JSAMPARRAY AllocateSampleArray() {
  // Some output color spaces don't need the sample array: don't allocate in that
  // case.
  #if defined(TURBO_JPEG_RGB_SWIZZLE)
      if (turboSwizzled(info_.out_color_space))
        return nullptr;
  #endif
  
      if (info_.out_color_space != JCS_YCbCr)
        return (*info_.mem->alloc_sarray)(
            reinterpret_cast_ptr<j_common_ptr>(&info_), JPOOL_IMAGE,
            4 * info_.output_width, 1);
  
      // Compute the width of the Y plane in bytes.  This may be larger than the
      // output width, since the jpeg library requires that the allocated width be
      // a multiple of DCTSIZE.  Note that this buffer will be used as garbage
      // memory for rows that extend below the actual height of the image.  We can
      // reuse the same memory for the U and V planes, since we are guaranteed
      // that the Y plane width is at least as large as the U and V plane widths.
      int width_bytes = ComputeYUVWidthBytes(&info_, 0);
      return (*info_.mem->alloc_sarray)(
          reinterpret_cast_ptr<j_common_ptr>(&info_), JPOOL_IMAGE, width_bytes,
          1);
    }
  
    void UpdateRestartPosition() {
      if (last_set_byte_ != info_.src->next_input_byte) {
        // next_input_byte was updated by jpeg, meaning that it found a restart
        // position.
        restart_position_ = static_cast<wtf_size_t>(next_read_position_ -
                                                    info_.src->bytes_in_buffer);
      }
    }
  
    void ClearBuffer() {
      // Let libjpeg know that the buffer needs to be refilled.
      info_.src->bytes_in_buffer = 0;
      info_.src->next_input_byte = nullptr;
      last_set_byte_ = nullptr;
    }
  
    scoped_refptr<SegmentReader> data_;
    JPEGImageDecoder* decoder_;
  
    // Input reading: True if we need to back up to restart_position_.
    bool needs_restart_;
    // If libjpeg needed to restart, this is the position to restart from.
    wtf_size_t restart_position_;
    // This is the position where we will read from, unless there is a restart.
    wtf_size_t next_read_position_;
    // This is how we know to update the restart position. It is the last value
    // we set to next_input_byte. libjpeg will update next_input_byte when it
    // has found the next restart position, so if it no longer matches this
    // value, we know we've reached the next restart position.
    const JOCTET* last_set_byte_;
  
    jpeg_decompress_struct info_;
    decoder_error_mgr err_;
    decoder_source_mgr src_;
    jpeg_progress_mgr progress_mgr_;
    jstate state_;
  
    JSAMPARRAY samples_;
    IntSize uv_size_;
  };
  
  void error_exit(
      j_common_ptr cinfo)  // Decoding failed: return control to the setjmp point.
  {
    longjmp(reinterpret_cast_ptr<decoder_error_mgr*>(cinfo->err)->setjmp_buffer,
            -1);
  }
  
  void emit_message(j_common_ptr cinfo, int msg_level) {
    if (msg_level >= 0)
      return;
  
    decoder_error_mgr* err = reinterpret_cast_ptr<decoder_error_mgr*>(cinfo->err);
    err->pub.num_warnings++;
  
    // Detect and count corrupt JPEG warning messages.
    const char* warning = nullptr;
    int code = err->pub.msg_code;
    if (code > 0 && code <= err->pub.last_jpeg_message)
      warning = err->pub.jpeg_message_table[code];
    if (warning && !strncmp("Corrupt JPEG", warning, 12))
      err->num_corrupt_warnings++;
  }
  
  void init_source(j_decompress_ptr) {}
  
  void skip_input_data(j_decompress_ptr jd, long num_bytes) {
    reinterpret_cast_ptr<decoder_source_mgr*>(jd->src)->reader->SkipBytes(
        num_bytes);
  }
  
  boolean fill_input_buffer(j_decompress_ptr jd) {
    return reinterpret_cast_ptr<decoder_source_mgr*>(jd->src)
        ->reader->FillBuffer();
  }
  
  void term_source(j_decompress_ptr jd) {
    reinterpret_cast_ptr<decoder_source_mgr*>(jd->src)
        ->reader->Decoder()
        ->Complete();
  }
  
  JPEGImageDecoder::JPEGImageDecoder(AlphaOption alpha_option,
                                     const ColorBehavior& color_behavior,
                                     wtf_size_t max_decoded_bytes,
                                     wtf_size_t offset)
      : ImageDecoder(alpha_option,
                     ImageDecoder::kDefaultBitDepth,
                     color_behavior,
                     max_decoded_bytes),
        offset_(offset) {}
  
  JPEGImageDecoder::~JPEGImageDecoder() = default;
  
  bool JPEGImageDecoder::SetSize(unsigned width, unsigned height) {
    if (!ImageDecoder::SetSize(width, height))
      return false;
  
    if (!DesiredScaleNumerator())
      return SetFailed();
  
    SetDecodedSize(width, height);
    return true;
  }
  
  void JPEGImageDecoder::OnSetData(SegmentReader* data) {
    if (reader_) {
      reader_->SetData(data);
  
      // Changing YUV decoding mode is not allowed after decompression starts.
      if (reader_->HasStartedDecompression())
        return;
    }
  
    if (allow_decode_to_yuv_)
      return;
  
    allow_decode_to_yuv_ =
        // Incremental YUV decoding is not currently supported (crbug.com/943519).
        IsAllDataReceived() &&
        // TODO(sashamcintosh): Cleanup. Finch experiment is enabled by default.
        RuntimeEnabledFeatures::DecodeJpeg420ImagesToYUVEnabled() &&
        // Ensures that the reader is created, the scale numbers are known,
        // the color profile is known, and the subsampling is known.
        IsSizeAvailable() &&
        // YUV decoding to a smaller size is not supported.
        reader_ && reader_->Info()->scale_num == reader_->Info()->scale_denom &&
        // TODO(crbug.com/911246): Support color space transformations on planar
        // data.
        !ColorTransform() &&
        // Only subsamplings 4:4:4, 4:2:2, and 4:2:0 are supported.
        (GetYUVSubsampling() == cc::YUVSubsampling::k444 ||
         GetYUVSubsampling() == cc::YUVSubsampling::k422 ||
         GetYUVSubsampling() == cc::YUVSubsampling::k420);
  }
  
  void JPEGImageDecoder::SetDecodedSize(unsigned width, unsigned height) {
    decoded_size_ = IntSize(width, height);
  }
  
  cc::YUVSubsampling JPEGImageDecoder::GetYUVSubsampling() const {
    DCHECK(reader_->Info());
    // reader_->Info() should have gone through a jpeg_read_header() call.
    DCHECK(IsDecodedSizeAvailable());
    return YuvSubsampling(*reader_->Info());
  }
  
  IntSize JPEGImageDecoder::DecodedYUVSize(cc::YUVIndex index) const {
    DCHECK(reader_);
    const jpeg_decompress_struct* info = reader_->Info();
  
    DCHECK_EQ(info->jpeg_color_space, JCS_YCbCr);
    return ComputeYUVSize(info, static_cast<int>(index));
  }
  
  wtf_size_t JPEGImageDecoder::DecodedYUVWidthBytes(cc::YUVIndex index) const {
    DCHECK(reader_);
    const jpeg_decompress_struct* info = reader_->Info();
  
    DCHECK_EQ(info->jpeg_color_space, JCS_YCbCr);
    return ComputeYUVWidthBytes(info, static_cast<int>(index));
  }
  
  unsigned JPEGImageDecoder::DesiredScaleNumerator() const {
    wtf_size_t original_bytes = Size().width() * Size().height() * 4;
  
    return JPEGImageDecoder::DesiredScaleNumerator(
        max_decoded_bytes_, original_bytes, g_scale_denominator);
  }
  
  // static
  unsigned JPEGImageDecoder::DesiredScaleNumerator(wtf_size_t max_decoded_bytes,
                                                   wtf_size_t original_bytes,
                                                   unsigned scale_denominator) {
    if (original_bytes <= max_decoded_bytes)
      return scale_denominator;
  
    // Downsample according to the maximum decoded size.
    return static_cast<unsigned>(floor(sqrt(
        // MSVC needs explicit parameter type for sqrt().
        static_cast<float>(max_decoded_bytes) / original_bytes *
        scale_denominator * scale_denominator)));
  }
  
  bool JPEGImageDecoder::ShouldGenerateAllSizes() const {
    return supported_decode_sizes_.IsEmpty();
  }
  
  void JPEGImageDecoder::DecodeToYUV() {
    DCHECK(HasImagePlanes());
    DCHECK(CanDecodeToYUV());
  
    // Only 8-bit YUV decode is currently supported.
    DCHECK_EQ(image_planes_->color_type(), kGray_8_SkColorType);
  
    {
      TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "Decode Image",
                   "imageType", "JPEG");
      Decode(DecodingMode::kDecodeToYuv);
    }
  }
  
  // TODO(crbug.com/919627): Confirm that this is correct for all cases.
  SkYUVColorSpace JPEGImageDecoder::GetYUVColorSpace() const {
    return SkYUVColorSpace::kJPEG_SkYUVColorSpace;
  }
  
  void JPEGImageDecoder::SetSupportedDecodeSizes(Vector<SkISize> sizes) {
    supported_decode_sizes_ = std::move(sizes);
  }
  
  Vector<SkISize> JPEGImageDecoder::GetSupportedDecodeSizes() const {
    // DCHECK IsDecodedSizeAvailable instead of IsSizeAvailable, since the latter
    // has side effects of actually doing the decode.
    DCHECK(IsDecodedSizeAvailable());
    return supported_decode_sizes_;
  }
  
  gfx::Size JPEGImageDecoder::GetImageCodedSize() const {
    // We use the |max_{h,v}_samp_factor|s returned by
    // AreValidSampleFactorsAvailable() since the ones available via
    // Info()->max_{h,v}_samp_factor are not updated until the image is actually
    // being decoded.
    int max_h_samp_factor;
    int max_v_samp_factor;
    if (!reader_->AreValidSampleFactorsAvailable(&max_h_samp_factor,
                                                 &max_v_samp_factor)) {
      return gfx::Size();
    }
  
    const int coded_width = Align(Size().width(), max_h_samp_factor * 8);
    const int coded_height = Align(Size().height(), max_v_samp_factor * 8);
  
    return gfx::Size(coded_width, coded_height);
  }
  
  cc::ImageHeaderMetadata JPEGImageDecoder::MakeMetadataForDecodeAcceleration()
      const {
    cc::ImageHeaderMetadata image_metadata =
        ImageDecoder::MakeMetadataForDecodeAcceleration();
    image_metadata.jpeg_is_progressive = reader_->Info()->buffered_image;
    image_metadata.coded_size = GetImageCodedSize();
    return image_metadata;
  }
  
  // At the moment we support only JCS_RGB and JCS_CMYK values of the
  // J_COLOR_SPACE enum.
  // If you need a specific implementation for other J_COLOR_SPACE values,
  // please add a full template specialization for this function below.
  template <J_COLOR_SPACE colorSpace>
  void SetPixel(ImageFrame::PixelData*, JSAMPARRAY samples, int column) = delete;
  
  // Used only for debugging with libjpeg (instead of libjpeg-turbo).
  template <>
  void SetPixel<JCS_RGB>(ImageFrame::PixelData* pixel,
                         JSAMPARRAY samples,
                         int column) {
    JSAMPLE* jsample = *samples + column * 3;
    ImageFrame::SetRGBARaw(pixel, jsample[0], jsample[1], jsample[2], 255);
  }
  
  template <>
  void SetPixel<JCS_CMYK>(ImageFrame::PixelData* pixel,
                          JSAMPARRAY samples,
                          int column) {
    JSAMPLE* jsample = *samples + column * 4;
  
    // Source is 'Inverted CMYK', output is RGB.
    // See: http://www.easyrgb.com/math.php?MATH=M12#text12
    // Or: http://www.ilkeratalay.com/colorspacesfaq.php#rgb
    // From CMYK to CMY:
    // X =   X    * (1 -   K   ) +   K  [for X = C, M, or Y]
    // Thus, from Inverted CMYK to CMY is:
    // X = (1-iX) * (1 - (1-iK)) + (1-iK) => 1 - iX*iK
    // From CMY (0..1) to RGB (0..1):
    // R = 1 - C => 1 - (1 - iC*iK) => iC*iK  [G and B similar]
    unsigned k = jsample[3];
    ImageFrame::SetRGBARaw(pixel, jsample[0] * k / 255, jsample[1] * k / 255,
                           jsample[2] * k / 255, 255);
  }
  
  // Used only for JCS_CMYK and JCS_RGB output.  Note that JCS_RGB is used only
  // for debugging with libjpeg (instead of libjpeg-turbo).
  template <J_COLOR_SPACE colorSpace>
  bool OutputRows(JPEGImageReader* reader, ImageFrame& buffer) {
    JSAMPARRAY samples = reader->Samples();
    jpeg_decompress_struct* info = reader->Info();
    int width = info->output_width;
  
    while (info->output_scanline < info->output_height) {
      // jpeg_read_scanlines will increase the scanline counter, so we
      // save the scanline before calling it.
      int y = info->output_scanline;
      // Request one scanline: returns 0 or 1 scanlines.
      if (jpeg_read_scanlines(info, samples, 1) != 1)
        return false;
  
      ImageFrame::PixelData* pixel = buffer.GetAddr(0, y);
      for (int x = 0; x < width; ++pixel, ++x)
        SetPixel<colorSpace>(pixel, samples, x);
  
      ColorProfileTransform* xform = reader->Decoder()->ColorTransform();
      if (xform) {
        ImageFrame::PixelData* row = buffer.GetAddr(0, y);
        skcms_AlphaFormat alpha_format = skcms_AlphaFormat_Unpremul;
        bool color_conversion_successful = skcms_Transform(
            row, XformColorFormat(), alpha_format, xform->SrcProfile(), row,
            XformColorFormat(), alpha_format, xform->DstProfile(), width);
        DCHECK(color_conversion_successful);
      }
    }
  
    buffer.SetPixelsChanged(true);
    return true;
  }
  
  static bool OutputRawData(JPEGImageReader* reader, ImagePlanes* image_planes) {
    JSAMPARRAY samples = reader->Samples();
    jpeg_decompress_struct* info = reader->Info();
  
    DCHECK_EQ(info->out_color_space, JCS_YCbCr);
  
    JSAMPARRAY bufferraw[3];
    JSAMPROW bufferraw2[32];
    bufferraw[0] = &bufferraw2[0];   // Y channel rows (8 or 16)
    bufferraw[1] = &bufferraw2[16];  // U channel rows (8)
    bufferraw[2] = &bufferraw2[24];  // V channel rows (8)
    int y_height = info->output_height;
    int v = info->comp_info[0].v_samp_factor;
    IntSize uv_size = reader->UvSize();
    int uv_height = uv_size.height();
    JSAMPROW output_y =
        static_cast<JSAMPROW>(image_planes->Plane(cc::YUVIndex::kY));
    JSAMPROW output_u =
        static_cast<JSAMPROW>(image_planes->Plane(cc::YUVIndex::kU));
    JSAMPROW output_v =
        static_cast<JSAMPROW>(image_planes->Plane(cc::YUVIndex::kV));
    wtf_size_t row_bytes_y = image_planes->RowBytes(cc::YUVIndex::kY);
    wtf_size_t row_bytes_u = image_planes->RowBytes(cc::YUVIndex::kU);
    wtf_size_t row_bytes_v = image_planes->RowBytes(cc::YUVIndex::kV);
  
    // Request 8 or 16 scanlines: returns 0 or more scanlines.
    int y_scanlines_to_read = DCTSIZE * v;
    JSAMPROW dummy_row = *samples;
    while (info->output_scanline < info->output_height) {
      // Assign 8 or 16 rows of memory to read the Y channel.
      for (int i = 0; i < y_scanlines_to_read; ++i) {
        int scanline = info->output_scanline + i;
        if (scanline < y_height) {
          bufferraw2[i] = &output_y[scanline * row_bytes_y];
        } else {
          bufferraw2[i] = dummy_row;
        }
      }
  
      // Assign 8 rows of memory to read the U and V channels.
      int scaled_scanline = info->output_scanline / v;
      for (int i = 0; i < 8; ++i) {
        int scanline = scaled_scanline + i;
        if (scanline < uv_height) {
          bufferraw2[16 + i] = &output_u[scanline * row_bytes_u];
          bufferraw2[24 + i] = &output_v[scanline * row_bytes_v];
        } else {
          bufferraw2[16 + i] = dummy_row;
          bufferraw2[24 + i] = dummy_row;
        }
      }
  
      JDIMENSION scanlines_read =
          jpeg_read_raw_data(info, bufferraw, y_scanlines_to_read);
      if (!scanlines_read)
        return false;
    }
  
    info->output_scanline = std::min(info->output_scanline, info->output_height);
    image_planes->SetHasCompleteScan();
    return true;
  }
  
  bool JPEGImageDecoder::OutputScanlines() {
    if (HasImagePlanes())
      return OutputRawData(reader_.get(), image_planes_.get());
  
    if (frame_buffer_cache_.IsEmpty())
      return false;
  
    jpeg_decompress_struct* info = reader_->Info();
  
    // Initialize the framebuffer if needed.
    ImageFrame& buffer = frame_buffer_cache_[0];
    if (buffer.GetStatus() == ImageFrame::kFrameEmpty) {
      DCHECK_EQ(info->output_width,
                static_cast<JDIMENSION>(decoded_size_.width()));
      DCHECK_EQ(info->output_height,
                static_cast<JDIMENSION>(decoded_size_.height()));
  
      if (!buffer.AllocatePixelData(info->output_width, info->output_height,
                                    ColorSpaceForSkImages()))
        return SetFailed();
  
      buffer.ZeroFillPixelData();
      // The buffer is transparent outside the decoded area while the image is
      // loading. The image will be marked fully opaque in Complete().
      buffer.SetStatus(ImageFrame::kFramePartial);
      buffer.SetHasAlpha(true);
  
      // For JPEGs, the frame always fills the entire image.
      buffer.SetOriginalFrameRect(IntRect(gfx::Point(), Size()));
    }
  
  #if defined(TURBO_JPEG_RGB_SWIZZLE)
    if (turboSwizzled(info->out_color_space)) {
      while (info->output_scanline < info->output_height) {
        unsigned char* row = reinterpret_cast_ptr<unsigned char*>(
            buffer.GetAddr(0, info->output_scanline));
        if (jpeg_read_scanlines(info, &row, 1) != 1)
          return false;
  
        ColorProfileTransform* xform = ColorTransform();
        if (xform) {
          skcms_AlphaFormat alpha_format = skcms_AlphaFormat_Unpremul;
          bool color_conversion_successful = skcms_Transform(
              row, XformColorFormat(), alpha_format, xform->SrcProfile(), row,
              XformColorFormat(), alpha_format, xform->DstProfile(),
              info->output_width);
          DCHECK(color_conversion_successful);
        }
      }
      buffer.SetPixelsChanged(true);
      return true;
    }
  #endif
  
    switch (info->out_color_space) {
      case JCS_RGB:
        return OutputRows<JCS_RGB>(reader_.get(), buffer);
      case JCS_CMYK:
        return OutputRows<JCS_CMYK>(reader_.get(), buffer);
      default:
        NOTREACHED();
    }
  
    return SetFailed();
  }
  
  void JPEGImageDecoder::Complete() {
    if (frame_buffer_cache_.IsEmpty())
      return;
  
    frame_buffer_cache_[0].SetHasAlpha(false);
    frame_buffer_cache_[0].SetStatus(ImageFrame::kFrameComplete);
  }
  
  inline bool IsComplete(const JPEGImageDecoder* decoder,
                         JPEGImageDecoder::DecodingMode decoding_mode) {
    if (decoding_mode == JPEGImageDecoder::DecodingMode::kDecodeToYuv) {
      DCHECK(decoder->HasImagePlanes());
      return true;
    }
  
    return decoder->FrameIsDecodedAtIndex(0);
  }
  
  void JPEGImageDecoder::Decode(DecodingMode decoding_mode) {
    if (Failed())
      return;
  
    if (!reader_) {
      reader_ = std::make_unique<JPEGImageReader>(this, offset_);
      reader_->SetData(data_.get());
    }
  
    // If we couldn't decode the image but have received all the data, decoding
    // has failed.
    if (!reader_->Decode(decoding_mode) && IsAllDataReceived())
      SetFailed();
  
    // If decoding is done or failed, we don't need the JPEGImageReader anymore.
    if (IsComplete(this, decoding_mode) || Failed())
      reader_.reset();
  }
  
  }  // namespace blink
  