blob: a1ee1c7e9666b25e61580b89388b4103c09a3372 [file] [log] [blame]
/*
* 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/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 {
JPEG_HEADER, // Reading JFIF headers
JPEG_START_DECOMPRESS,
JPEG_DECOMPRESS_PROGRESSIVE, // Output progressive pixels
JPEG_DECOMPRESS_SEQUENTIAL, // Output sequential pixels
JPEG_DONE
};
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 = 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.SetWidth(ReadUnsignedRational(value_ptr, is_big_endian));
break;
case ExifTags::kResolutionYTag:
if (type == kUnsignedRationalType && count == 1)
metadata.resolution.SetHeight(ReadUnsignedRational(value_ptr, is_big_endian));
break;
case ExifTags::kPixelXDimensionTag:
if (count != 1)
break;
switch (type) {
case kUnsignedShortType:
metadata.size.SetWidth(ReadUint16(value_ptr, is_big_endian));
break;
case kUnsignedLongType:
metadata.size.SetWidth(ReadUint32(value_ptr, is_big_endian));
break;
}
break;
case ExifTags::kPixelYDimensionTag:
if (count != 1)
break;
switch (type) {
case kUnsignedShortType:
metadata.size.SetHeight(ReadUint16(value_ptr, is_big_endian));
break;
case kUnsignedLongType:
metadata.size.SetHeight(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 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, size_t initial_offset)
: decoder_(decoder),
needs_restart_(false),
restart_position_(initial_offset),
next_read_position_(initial_offset),
last_set_byte_(nullptr),
state_(JPEG_HEADER),
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() { jpeg_destroy_decompress(&info_); }
void SkipBytes(long num_bytes) {
if (num_bytes <= 0)
return;
size_t bytes_to_skip = static_cast<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_ =
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_ = 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 JPEG_HEADER: {
// 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_ = JPEG_START_DECOMPRESS;
// 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 JPEG_START_DECOMPRESS:
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) ? JPEG_DECOMPRESS_PROGRESSIVE
: JPEG_DECOMPRESS_SEQUENTIAL;
FALLTHROUGH;
case JPEG_DECOMPRESS_SEQUENTIAL:
if (state_ == JPEG_DECOMPRESS_SEQUENTIAL) {
if (!decoder_->OutputScanlines())
return false; // I/O suspension.
// If we've completed image output...
DCHECK_EQ(info_.output_scanline, info_.output_height);
state_ = JPEG_DONE;
}
FALLTHROUGH;
case JPEG_DECOMPRESS_PROGRESSIVE:
if (state_ == JPEG_DECOMPRESS_PROGRESSIVE) {
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_ = JPEG_DONE;
}
FALLTHROUGH;
case JPEG_DONE:
// 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_; }
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_ = 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.
size_t restart_position_;
// This is the position where we will read from, unless there is a restart.
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_;
DISALLOW_COPY_AND_ASSIGN(JPEGImageReader);
};
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,
size_t max_decoded_bytes,
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);
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() &&
// TODO(crbug.com/919627): Support 4:4:4 and 4:2:2 sub samplings.
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));
}
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 {
size_t original_bytes = Size().Width() * Size().Height() * 4;
if (original_bytes <= max_decoded_bytes_)
return g_scale_denominator;
// Downsample according to the maximum decoded size.
unsigned scale_numerator = static_cast<unsigned>(floor(sqrt(
// MSVC needs explicit parameter type for sqrt().
static_cast<float>(max_decoded_bytes_ * g_scale_denominator *
g_scale_denominator / original_bytes))));
return scale_numerator;
}
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));
size_t row_bytes_y = image_planes->RowBytes(cc::YUVIndex::kY);
size_t row_bytes_u = image_planes->RowBytes(cc::YUVIndex::kU);
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(IntPoint(), 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