blob: 45f2b9541748bd6e8abdcf08f03ee6e25d4162f7 [file] [log] [blame]
/*
* Copyright (C) 2006 Apple Computer, Inc.
* Copyright (C) Research In Motion Limited 2009-2010. All rights reserved.
*
* Portions are Copyright (C) 2001 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/png/png_image_decoder.h"
#include <memory>
#include "third_party/skia/third_party/skcms/skcms.h"
#if (defined(__ARM_NEON__) || defined(__ARM_NEON))
#include <arm_neon.h>
#endif
namespace blink {
PNGImageDecoder::PNGImageDecoder(
AlphaOption alpha_option,
HighBitDepthDecodingOption high_bit_depth_decoding_option,
const ColorBehavior& color_behavior,
size_t max_decoded_bytes,
size_t offset)
: ImageDecoder(alpha_option,
high_bit_depth_decoding_option,
color_behavior,
max_decoded_bytes),
offset_(offset),
current_frame_(0),
// It would be logical to default to kAnimationNone, but BitmapImage uses
// that as a signal to never check again, meaning the actual count will
// never be respected.
repetition_count_(kAnimationLoopOnce),
has_alpha_channel_(false),
current_buffer_saw_alpha_(false),
decode_to_half_float_(false),
bit_depth_(0) {}
PNGImageDecoder::~PNGImageDecoder() = default;
bool PNGImageDecoder::SetFailed() {
reader_.reset();
return ImageDecoder::SetFailed();
}
size_t PNGImageDecoder::DecodeFrameCount() {
Parse(ParseQuery::kMetaData);
return Failed() ? frame_buffer_cache_.size() : reader_->FrameCount();
}
void PNGImageDecoder::Decode(size_t index) {
Parse(ParseQuery::kMetaData);
if (Failed())
return;
UpdateAggressivePurging(index);
Vector<size_t> frames_to_decode = FindFramesToDecode(index);
for (auto i = frames_to_decode.rbegin(); i != frames_to_decode.rend(); i++) {
current_frame_ = *i;
if (!reader_->Decode(*data_, *i)) {
SetFailed();
return;
}
// If this returns false, we need more data to continue decoding.
if (!PostDecodeProcessing(*i))
break;
}
// It is also a fatal error if all data is received and we have decoded all
// frames available but the file is truncated.
if (index >= frame_buffer_cache_.size() - 1 && IsAllDataReceived() &&
reader_ && !reader_->ParseCompleted())
SetFailed();
}
void PNGImageDecoder::Parse(ParseQuery query) {
if (Failed() || (reader_ && reader_->ParseCompleted()))
return;
if (!reader_)
reader_ = std::make_unique<PNGImageReader>(this, offset_);
if (!reader_->Parse(*data_, query))
SetFailed();
}
void PNGImageDecoder::ClearFrameBuffer(size_t index) {
if (reader_)
reader_->ClearDecodeState(index);
ImageDecoder::ClearFrameBuffer(index);
}
bool PNGImageDecoder::CanReusePreviousFrameBuffer(size_t index) const {
DCHECK(index < frame_buffer_cache_.size());
return frame_buffer_cache_[index].GetDisposalMethod() !=
ImageFrame::kDisposeOverwritePrevious;
}
void PNGImageDecoder::SetRepetitionCount(int repetition_count) {
repetition_count_ = repetition_count;
}
int PNGImageDecoder::RepetitionCount() const {
return Failed() ? kAnimationLoopOnce : repetition_count_;
}
void PNGImageDecoder::InitializeNewFrame(size_t index) {
const PNGImageReader::FrameInfo& frame_info = reader_->GetFrameInfo(index);
ImageFrame& buffer = frame_buffer_cache_[index];
if (decode_to_half_float_)
buffer.SetPixelFormat(ImageFrame::PixelFormat::kRGBA_F16);
DCHECK(IntRect(IntPoint(), Size()).Contains(frame_info.frame_rect));
buffer.SetOriginalFrameRect(frame_info.frame_rect);
buffer.SetDuration(TimeDelta::FromMilliseconds(frame_info.duration));
buffer.SetDisposalMethod(frame_info.disposal_method);
buffer.SetAlphaBlendSource(frame_info.alpha_blend);
size_t previous_frame_index = FindRequiredPreviousFrame(index, false);
buffer.SetRequiredPreviousFrameIndex(previous_frame_index);
}
inline std::unique_ptr<ColorProfile> ReadColorProfile(png_structp png,
png_infop info) {
if (png_get_valid(png, info, PNG_INFO_sRGB)) {
return std::make_unique<ColorProfile>(*skcms_sRGB_profile());
}
png_charp name;
int compression;
png_bytep buffer;
png_uint_32 length;
if (png_get_iCCP(png, info, &name, &compression, &buffer, &length)) {
return ColorProfile::Create(buffer, length);
}
png_fixed_point chrm[8];
if (!png_get_cHRM_fixed(png, info, &chrm[0], &chrm[1], &chrm[2], &chrm[3],
&chrm[4], &chrm[5], &chrm[6], &chrm[7]))
return nullptr;
png_fixed_point inverse_gamma;
if (!png_get_gAMA_fixed(png, info, &inverse_gamma))
return nullptr;
// cHRM and gAMA tags are both present. The PNG spec states that cHRM is
// valid even without gAMA but we cannot apply the cHRM without guessing
// a gAMA. Color correction is not a guessing game: match the behavior
// of Safari and Firefox instead (compat).
struct pngFixedToFloat {
explicit pngFixedToFloat(png_fixed_point value)
: float_value(.00001f * value) {}
operator float() { return float_value; }
float float_value;
};
float rx = pngFixedToFloat(chrm[2]);
float ry = pngFixedToFloat(chrm[3]);
float gx = pngFixedToFloat(chrm[4]);
float gy = pngFixedToFloat(chrm[5]);
float bx = pngFixedToFloat(chrm[6]);
float by = pngFixedToFloat(chrm[7]);
float wx = pngFixedToFloat(chrm[0]);
float wy = pngFixedToFloat(chrm[1]);
skcms_Matrix3x3 to_xyzd50;
if (!skcms_PrimariesToXYZD50(rx, ry, gx, gy, bx, by, wx, wy, &to_xyzd50))
return nullptr;
skcms_TransferFunction fn;
fn.g = 1.0f / pngFixedToFloat(inverse_gamma);
fn.a = 1.0f;
fn.b = fn.c = fn.d = fn.e = fn.f = 0.0f;
skcms_ICCProfile profile;
skcms_Init(&profile);
skcms_SetTransferFunction(&profile, &fn);
skcms_SetXYZD50(&profile, &to_xyzd50);
return std::make_unique<ColorProfile>(profile);
}
void PNGImageDecoder::SetColorSpace() {
if (IgnoresColorSpace())
return;
png_structp png = reader_->PngPtr();
png_infop info = reader_->InfoPtr();
const int color_type = png_get_color_type(png, info);
if (!(color_type & PNG_COLOR_MASK_COLOR))
return;
// We only support color profiles for color PALETTE and RGB[A] PNG.
// TODO(msarett): Add GRAY profile support, block CYMK?
if (auto profile = ReadColorProfile(png, info)) {
SetEmbeddedColorProfile(std::move(profile));
}
}
void PNGImageDecoder::SetBitDepth() {
if (bit_depth_)
return;
png_structp png = reader_->PngPtr();
png_infop info = reader_->InfoPtr();
bit_depth_ = png_get_bit_depth(png, info);
decode_to_half_float_ =
(bit_depth_ == 16) &&
(high_bit_depth_decoding_option_ == kHighBitDepthToHalfFloat) &&
// TODO(zakerinasab): https://crbug.com/874057
// Due to a lack of 16 bit APNG encoders, multi-frame 16 bit APNGs are not
// supported. In this case the decoder falls back to 8888 decode mode.
(repetition_count_ == kAnimationNone);
}
bool PNGImageDecoder::ImageIsHighBitDepth() {
SetBitDepth();
return bit_depth_ == 16;
}
bool PNGImageDecoder::SetSize(unsigned width, unsigned height) {
DCHECK(!IsDecodedSizeAvailable());
// Protect against large PNGs. See http://bugzil.la/251381 for more details.
const unsigned long kMaxPNGSize = 1000000UL;
return (width <= kMaxPNGSize) && (height <= kMaxPNGSize) &&
ImageDecoder::SetSize(width, height);
}
void PNGImageDecoder::HeaderAvailable() {
DCHECK(IsDecodedSizeAvailable());
png_structp png = reader_->PngPtr();
png_infop info = reader_->InfoPtr();
png_uint_32 width, height;
int bit_depth, color_type, interlace_type, compression_type;
png_get_IHDR(png, info, &width, &height, &bit_depth, &color_type,
&interlace_type, &compression_type, nullptr);
// The options we set here match what Mozilla does.
// Expand to ensure we use 24-bit for RGB and 32-bit for RGBA.
if (color_type == PNG_COLOR_TYPE_PALETTE ||
(color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8))
png_set_expand(png);
if (png_get_valid(png, info, PNG_INFO_tRNS))
png_set_expand(png);
if (!decode_to_half_float_)
png_set_strip_16(png);
if (color_type == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(png);
if (!HasEmbeddedColorProfile()) {
const double kInverseGamma = 0.45455;
const double kDefaultGamma = 2.2;
double gamma;
if (!IgnoresColorSpace() && png_get_gAMA(png, info, &gamma)) {
const double kMaxGamma = 21474.83;
if ((gamma <= 0.0) || (gamma > kMaxGamma)) {
gamma = kInverseGamma;
png_set_gAMA(png, info, gamma);
}
png_set_gamma(png, kDefaultGamma, gamma);
} else {
png_set_gamma(png, kDefaultGamma, kInverseGamma);
}
}
// Tell libpng to send us rows for interlaced pngs.
if (interlace_type == PNG_INTERLACE_ADAM7)
png_set_interlace_handling(png);
// Update our info now (so we can get color channel info).
png_read_update_info(png, info);
int channels = png_get_channels(png, info);
DCHECK(channels == 3 || channels == 4);
has_alpha_channel_ = (channels == 4);
}
#if (defined(__ARM_NEON__) || defined(__ARM_NEON))
// Premultiply RGB color channels by alpha, swizzle RGBA to SkPMColor
// order, and return the AND of all alpha channels.
static inline void SetRGBAPremultiplyRowNeon(png_bytep src_ptr,
const int pixel_count,
ImageFrame::PixelData* dst_pixel,
unsigned* const alpha_mask) {
assert(dst_pixel);
assert(alpha_mask);
constexpr int kPixelsPerLoad = 8;
// Input registers.
uint8x8x4_t rgba;
// Alpha mask.
uint8x8_t alpha_mask_vector = vdup_n_u8(255);
// Scale the color channel by alpha - the opacity coefficient.
auto premultiply = [](uint8x8_t c, uint8x8_t a) {
// First multiply the color by alpha, expanding to 16-bit (max 255*255).
uint16x8_t ca = vmull_u8(c, a);
// Now we need to round back down to 8-bit, returning (x+127)/255.
// (x+127)/255 == (x + ((x+128)>>8) + 128)>>8. This form is well suited
// to NEON: vrshrq_n_u16(...,8) gives the inner (x+128)>>8, and
// vraddhn_u16() both the outer add-shift and our conversion back to 8-bit.
return vraddhn_u16(ca, vrshrq_n_u16(ca, 8));
};
int i = pixel_count;
for (; i >= kPixelsPerLoad; i -= kPixelsPerLoad) {
// Reads 8 pixels at once, each color channel in a different
// 64-bit register.
rgba = vld4_u8(src_ptr);
// AND pixel alpha values into the alpha detection mask.
alpha_mask_vector = vand_u8(alpha_mask_vector, rgba.val[3]);
uint64_t alphas_u64 = vget_lane_u64(vreinterpret_u64_u8(rgba.val[3]), 0);
// If all of the pixels are opaque, no need to premultiply.
if (~alphas_u64 == 0) {
#if SK_PMCOLOR_BYTE_ORDER(R, G, B, A)
// Already in right order, write back (interleaved) results to memory.
vst4_u8(reinterpret_cast<uint8_t*>(dst_pixel), rgba);
#elif SK_PMCOLOR_BYTE_ORDER(B, G, R, A)
// Re-order color channels for BGRA.
uint8x8x4_t bgra = {rgba.val[2], rgba.val[1], rgba.val[0], rgba.val[3]};
// Write back (interleaved) results to memory.
vst4_u8(reinterpret_cast<uint8_t*>(dst_pixel), bgra);
#endif
} else {
#if SK_PMCOLOR_BYTE_ORDER(R, G, B, A)
// Premultiply color channels, already in right order.
rgba.val[0] = premultiply(rgba.val[0], rgba.val[3]);
rgba.val[1] = premultiply(rgba.val[1], rgba.val[3]);
rgba.val[2] = premultiply(rgba.val[2], rgba.val[3]);
// Write back (interleaved) results to memory.
vst4_u8(reinterpret_cast<uint8_t*>(dst_pixel), rgba);
#elif SK_PMCOLOR_BYTE_ORDER(B, G, R, A)
uint8x8x4_t bgra;
// Premultiply and re-order color channels for BGRA.
bgra.val[0] = premultiply(rgba.val[2], rgba.val[3]);
bgra.val[1] = premultiply(rgba.val[1], rgba.val[3]);
bgra.val[2] = premultiply(rgba.val[0], rgba.val[3]);
bgra.val[3] = rgba.val[3];
// Write back (interleaved) results to memory.
vst4_u8(reinterpret_cast<uint8_t*>(dst_pixel), bgra);
#endif
}
// Advance to next elements.
src_ptr += kPixelsPerLoad * 4;
dst_pixel += kPixelsPerLoad;
}
// AND together the 8 alpha values in the alpha_mask_vector.
uint64_t alpha_mask_u64 =
vget_lane_u64(vreinterpret_u64_u8(alpha_mask_vector), 0);
alpha_mask_u64 &= (alpha_mask_u64 >> 32);
alpha_mask_u64 &= (alpha_mask_u64 >> 16);
alpha_mask_u64 &= (alpha_mask_u64 >> 8);
*alpha_mask &= alpha_mask_u64;
// Handle the tail elements.
for (; i > 0; i--, dst_pixel++, src_ptr += 4) {
ImageFrame::SetRGBAPremultiply(dst_pixel, src_ptr[0], src_ptr[1],
src_ptr[2], src_ptr[3]);
*alpha_mask &= src_ptr[3];
}
}
// Swizzle RGBA to SkPMColor order, and return the AND of all alpha channels.
static inline void SetRGBARawRowNeon(png_bytep src_ptr,
const int pixel_count,
ImageFrame::PixelData* dst_pixel,
unsigned* const alpha_mask) {
assert(dst_pixel);
assert(alpha_mask);
constexpr int kPixelsPerLoad = 16;
// Input registers.
uint8x16x4_t rgba;
// Alpha mask.
uint8x16_t alpha_mask_vector = vdupq_n_u8(255);
int i = pixel_count;
for (; i >= kPixelsPerLoad; i -= kPixelsPerLoad) {
// Reads 16 pixels at once, each color channel in a different
// 128-bit register.
rgba = vld4q_u8(src_ptr);
// AND pixel alpha values into the alpha detection mask.
alpha_mask_vector = vandq_u8(alpha_mask_vector, rgba.val[3]);
#if SK_PMCOLOR_BYTE_ORDER(R, G, B, A)
// Already in right order, write back (interleaved) results to memory.
vst4q_u8(reinterpret_cast<uint8_t*>(dst_pixel), rgba);
#elif SK_PMCOLOR_BYTE_ORDER(B, G, R, A)
// Re-order color channels for BGRA.
uint8x16x4_t bgra = {rgba.val[2], rgba.val[1], rgba.val[0], rgba.val[3]};
// Write back (interleaved) results to memory.
vst4q_u8(reinterpret_cast<uint8_t*>(dst_pixel), bgra);
#endif
// Advance to next elements.
src_ptr += kPixelsPerLoad * 4;
dst_pixel += kPixelsPerLoad;
}
// AND together the 16 alpha values in the alpha_mask_vector.
uint64_t alpha_mask_u64 =
vget_lane_u64(vreinterpret_u64_u8(vget_low_u8(alpha_mask_vector)), 0);
alpha_mask_u64 &=
vget_lane_u64(vreinterpret_u64_u8(vget_high_u8(alpha_mask_vector)), 0);
alpha_mask_u64 &= (alpha_mask_u64 >> 32);
alpha_mask_u64 &= (alpha_mask_u64 >> 16);
alpha_mask_u64 &= (alpha_mask_u64 >> 8);
*alpha_mask &= alpha_mask_u64;
// Handle the tail elements.
for (; i > 0; i--, dst_pixel++, src_ptr += 4) {
ImageFrame::SetRGBARaw(dst_pixel, src_ptr[0], src_ptr[1], src_ptr[2],
src_ptr[3]);
*alpha_mask &= src_ptr[3];
}
}
// Swizzle RGB to opaque SkPMColor order, and return the AND
// of all alpha channels.
static inline void SetRGBARawRowNoAlphaNeon(png_bytep src_ptr,
const int pixel_count,
ImageFrame::PixelData* dst_pixel) {
assert(dst_pixel);
constexpr int kPixelsPerLoad = 16;
// Input registers.
uint8x16x3_t rgb;
int i = pixel_count;
for (; i >= kPixelsPerLoad; i -= kPixelsPerLoad) {
// Reads 16 pixels at once, each color channel in a different
// 128-bit register.
rgb = vld3q_u8(src_ptr);
#if SK_PMCOLOR_BYTE_ORDER(R, G, B, A)
// RGB already in right order, add opaque alpha channel.
uint8x16x4_t rgba = {rgb.val[0], rgb.val[1], rgb.val[2], vdupq_n_u8(255)};
// Write back (interleaved) results to memory.
vst4q_u8(reinterpret_cast<uint8_t*>(dst_pixel), rgba);
#elif SK_PMCOLOR_BYTE_ORDER(B, G, R, A)
// Re-order color channels for BGR, add opaque alpha channel.
uint8x16x4_t bgra = {rgb.val[2], rgb.val[1], rgb.val[0], vdupq_n_u8(255)};
// Write back (interleaved) results to memory.
vst4q_u8(reinterpret_cast<uint8_t*>(dst_pixel), bgra);
#endif
// Advance to next elements.
src_ptr += kPixelsPerLoad * 3;
dst_pixel += kPixelsPerLoad;
}
// Handle the tail elements.
for (; i > 0; i--, dst_pixel++, src_ptr += 3) {
ImageFrame::SetRGBARaw(dst_pixel, src_ptr[0], src_ptr[1], src_ptr[2], 255);
}
}
#endif
void PNGImageDecoder::RowAvailable(unsigned char* row_buffer,
unsigned row_index,
int) {
if (current_frame_ >= frame_buffer_cache_.size())
return;
ImageFrame& buffer = frame_buffer_cache_[current_frame_];
if (buffer.GetStatus() == ImageFrame::kFrameEmpty) {
png_structp png = reader_->PngPtr();
if (!InitFrameBuffer(current_frame_)) {
longjmp(JMPBUF(png), 1);
return;
}
DCHECK_EQ(ImageFrame::kFramePartial, buffer.GetStatus());
if (PNG_INTERLACE_ADAM7 ==
png_get_interlace_type(png, reader_->InfoPtr())) {
unsigned color_channels = has_alpha_channel_ ? 4 : 3;
unsigned interlace_buffer_size = color_channels * Size().Area();
if (decode_to_half_float_)
interlace_buffer_size *= 2;
reader_->CreateInterlaceBuffer(interlace_buffer_size);
if (!reader_->InterlaceBuffer()) {
longjmp(JMPBUF(png), 1);
return;
}
}
current_buffer_saw_alpha_ = false;
}
const IntRect& frame_rect = buffer.OriginalFrameRect();
DCHECK(IntRect(IntPoint(), Size()).Contains(frame_rect));
/* libpng comments (here to explain what follows).
*
* this function is called for every row in the image. If the
* image is interlacing, and you turned on the interlace handler,
* this function will be called for every row in every pass.
* Some of these rows will not be changed from the previous pass.
* When the row is not changed, the new_row variable will be NULL.
* The rows and passes are called in order, so you don't really
* need the row_num and pass, but I'm supplying them because it
* may make your life easier.
*/
// Nothing to do if the row is unchanged, or the row is outside the image
// bounds. In the case that a frame presents more data than the indicated
// frame size, ignore the extra rows and use the frame size as the source
// of truth. libpng can send extra rows: ignore them too, this to prevent
// memory writes outside of the image bounds (security).
if (!row_buffer)
return;
DCHECK_GT(frame_rect.Height(), 0);
if (row_index >= static_cast<unsigned>(frame_rect.Height()))
return;
int y = row_index + frame_rect.Y();
if (y < 0)
return;
DCHECK_LT(y, Size().Height());
/* libpng comments (continued).
*
* For the non-NULL rows of interlaced images, you must call
* png_progressive_combine_row() passing in the row and the
* old row. You can call this function for NULL rows (it will
* just return) and for non-interlaced images (it just does the
* memcpy for you) if it will make the code easier. Thus, you
* can just do this for all cases:
*
* png_progressive_combine_row(png_ptr, old_row, new_row);
*
* where old_row is what was displayed for previous rows. Note
* that the first pass (pass == 0 really) will completely cover
* the old row, so the rows do not have to be initialized. After
* the first pass (and only for interlaced images), you will have
* to pass the current row, and the function will combine the
* old row and the new row.
*/
bool has_alpha = has_alpha_channel_;
png_bytep row = row_buffer;
if (png_bytep interlace_buffer = reader_->InterlaceBuffer()) {
unsigned bytes_per_pixel = has_alpha ? 4 : 3;
if (decode_to_half_float_)
bytes_per_pixel *= 2;
row = interlace_buffer + (row_index * bytes_per_pixel * Size().Width());
png_progressive_combine_row(reader_->PngPtr(), row, row_buffer);
}
// Write the decoded row pixels to the frame buffer. The repetitive
// form of the row write loops is for speed.
const int width = frame_rect.Width();
png_bytep src_ptr = row;
if (!decode_to_half_float_) {
ImageFrame::PixelData* const dst_row = buffer.GetAddr(frame_rect.X(), y);
if (has_alpha) {
if (ColorProfileTransform* xform = ColorTransform()) {
ImageFrame::PixelData* xform_dst = dst_row;
// If we're blending over the previous frame, we can't overwrite that
// when we do the color transform. So we allocate another row of pixels
// to hold the temporary result before blending. In all other cases,
// we can safely transform directly to the destination buffer, then do
// any operations in-place (premul, swizzle).
if (frame_buffer_cache_[current_frame_].GetAlphaBlendSource() ==
ImageFrame::kBlendAtopPreviousFrame) {
if (!color_transform_scanline_) {
// This buffer may be wider than necessary for this frame, but by
// allocating the full width of the PNG, we know it will be able to
// hold temporary data for any subsequent frame.
color_transform_scanline_.reset(
new ImageFrame::PixelData[Size().Width()]);
}
xform_dst = color_transform_scanline_.get();
}
skcms_PixelFormat color_format = skcms_PixelFormat_RGBA_8888;
skcms_AlphaFormat alpha_format = skcms_AlphaFormat_Unpremul;
bool color_conversion_successful = skcms_Transform(
src_ptr, color_format, alpha_format, xform->SrcProfile(), xform_dst,
color_format, alpha_format, xform->DstProfile(), width);
DCHECK(color_conversion_successful);
src_ptr = png_bytep(xform_dst);
}
unsigned alpha_mask = 255;
if (frame_buffer_cache_[current_frame_].GetAlphaBlendSource() ==
ImageFrame::kBlendAtopBgcolor) {
if (buffer.PremultiplyAlpha()) {
#if (defined(__ARM_NEON__) || defined(__ARM_NEON))
SetRGBAPremultiplyRowNeon(src_ptr, width, dst_row, &alpha_mask);
#else
for (auto *dst_pixel = dst_row; dst_pixel < dst_row + width;
dst_pixel++, src_ptr += 4) {
ImageFrame::SetRGBAPremultiply(dst_pixel, src_ptr[0], src_ptr[1],
src_ptr[2], src_ptr[3]);
alpha_mask &= src_ptr[3];
}
#endif
} else {
#if (defined(__ARM_NEON__) || defined(__ARM_NEON))
SetRGBARawRowNeon(src_ptr, width, dst_row, &alpha_mask);
#else
for (auto *dst_pixel = dst_row; dst_pixel < dst_row + width;
dst_pixel++, src_ptr += 4) {
ImageFrame::SetRGBARaw(dst_pixel, src_ptr[0], src_ptr[1],
src_ptr[2], src_ptr[3]);
alpha_mask &= src_ptr[3];
}
#endif
}
} else {
// Now, the blend method is ImageFrame::BlendAtopPreviousFrame. Since
// the frame data of the previous frame is copied at InitFrameBuffer, we
// can blend the pixel of this frame, stored in |src_ptr|, over the
// previous pixel stored in |dst_pixel|.
if (buffer.PremultiplyAlpha()) {
for (auto *dst_pixel = dst_row; dst_pixel < dst_row + width;
dst_pixel++, src_ptr += 4) {
ImageFrame::BlendRGBAPremultiplied(
dst_pixel, src_ptr[0], src_ptr[1], src_ptr[2], src_ptr[3]);
alpha_mask &= src_ptr[3];
}
} else {
for (auto *dst_pixel = dst_row; dst_pixel < dst_row + width;
dst_pixel++, src_ptr += 4) {
ImageFrame::BlendRGBARaw(dst_pixel, src_ptr[0], src_ptr[1],
src_ptr[2], src_ptr[3]);
alpha_mask &= src_ptr[3];
}
}
}
if (alpha_mask != 255)
current_buffer_saw_alpha_ = true;
} else {
#if (defined(__ARM_NEON__) || defined(__ARM_NEON))
SetRGBARawRowNoAlphaNeon(src_ptr, width, dst_row);
#else
for (auto *dst_pixel = dst_row; dst_pixel < dst_row + width;
src_ptr += 3, ++dst_pixel) {
ImageFrame::SetRGBARaw(dst_pixel, src_ptr[0], src_ptr[1], src_ptr[2],
255);
}
#endif
// We'll apply the color space xform to opaque pixels after they have been
// written to the ImageFrame.
// TODO: Apply the xform to the RGB pixels, skipping second pass over
// data.
if (ColorProfileTransform* xform = ColorTransform()) {
skcms_AlphaFormat alpha_format = skcms_AlphaFormat_Opaque;
bool color_conversion_successful =
skcms_Transform(dst_row, XformColorFormat(), alpha_format,
xform->SrcProfile(), dst_row, XformColorFormat(),
alpha_format, xform->DstProfile(), width);
DCHECK(color_conversion_successful);
}
}
} else { // for if (!decode_to_half_float_)
ImageFrame::PixelDataF16* const dst_row_f16 =
buffer.GetAddrF16(frame_rect.X(), y);
// TODO(zakerinasab): https://crbug.com/874057
// Due to a lack of 16 bit APNG encoders, multi-frame 16 bit APNGs are not
// supported. Hence, we expect the blending mode always be
// kBlendAtopBgcolor.
DCHECK(frame_buffer_cache_[current_frame_].GetAlphaBlendSource() ==
ImageFrame::kBlendAtopBgcolor);
// Color space transformation to the dst space and converting the decoded
// color componenets from uint16 to float16.
auto* xform = ColorTransform();
auto* src_profile = xform ? xform->SrcProfile() : nullptr;
auto* dst_profile = xform ? xform->DstProfile() : nullptr;
auto src_format = has_alpha ? skcms_PixelFormat_RGBA_16161616
: skcms_PixelFormat_RGB_161616;
auto src_alpha_format =
has_alpha ? skcms_AlphaFormat_Unpremul : skcms_AlphaFormat_Opaque;
auto dst_alpha_format = has_alpha ? (buffer.PremultiplyAlpha()
? skcms_AlphaFormat_PremulAsEncoded
: skcms_AlphaFormat_Unpremul)
: skcms_AlphaFormat_Opaque;
bool success = skcms_Transform(
src_ptr, src_format, src_alpha_format, src_profile, dst_row_f16,
skcms_PixelFormat_RGBA_hhhh, dst_alpha_format, dst_profile, width);
DCHECK(success);
current_buffer_saw_alpha_ = has_alpha;
}
buffer.SetPixelsChanged(true);
}
void PNGImageDecoder::FrameComplete() {
if (current_frame_ >= frame_buffer_cache_.size())
return;
if (reader_->InterlaceBuffer())
reader_->ClearInterlaceBuffer();
ImageFrame& buffer = frame_buffer_cache_[current_frame_];
if (buffer.GetStatus() == ImageFrame::kFrameEmpty) {
longjmp(JMPBUF(reader_->PngPtr()), 1);
return;
}
if (!current_buffer_saw_alpha_)
CorrectAlphaWhenFrameBufferSawNoAlpha(current_frame_);
buffer.SetStatus(ImageFrame::kFrameComplete);
}
bool PNGImageDecoder::FrameIsReceivedAtIndex(size_t index) const {
if (!IsDecodedSizeAvailable())
return false;
DCHECK(!Failed() && reader_);
// For non-animated images, return ImageDecoder::FrameIsReceivedAtIndex.
// This matches the behavior of WEBPImageDecoder.
if (reader_->ParseCompleted() && reader_->FrameCount() == 1)
return ImageDecoder::FrameIsReceivedAtIndex(index);
return reader_->FrameIsReceivedAtIndex(index);
}
TimeDelta PNGImageDecoder::FrameDurationAtIndex(size_t index) const {
if (index < frame_buffer_cache_.size())
return frame_buffer_cache_[index].Duration();
return TimeDelta();
}
} // namespace blink