summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.cc')
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.cc706
1 files changed, 706 insertions, 0 deletions
diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.cc b/chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.cc
new file mode 100644
index 00000000000..cad2f68db51
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.cc
@@ -0,0 +1,706 @@
+/*
+ * 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>
+
+#if (defined(__ARM_NEON__) || defined(__ARM_NEON))
+#include <arm_neon.h>
+#endif
+
+namespace blink {
+
+PNGImageDecoder::PNGImageDecoder(AlphaOption alpha_option,
+ const ColorBehavior& color_behavior,
+ size_t max_decoded_bytes,
+ size_t offset)
+ : ImageDecoder(alpha_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) {}
+
+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];
+
+ 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 sk_sp<SkColorSpace> ReadColorSpace(png_structp png, png_infop info) {
+ if (png_get_valid(png, info, PNG_INFO_sRGB))
+ return SkColorSpace::MakeSRGB();
+
+ png_charp name;
+ int compression;
+ png_bytep profile;
+ png_uint_32 length;
+ if (png_get_iCCP(png, info, &name, &compression, &profile, &length))
+ return SkColorSpace::MakeICC(profile, 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;
+ };
+
+ SkColorSpacePrimaries primaries;
+ primaries.fRX = pngFixedToFloat(chrm[2]);
+ primaries.fRY = pngFixedToFloat(chrm[3]);
+ primaries.fGX = pngFixedToFloat(chrm[4]);
+ primaries.fGY = pngFixedToFloat(chrm[5]);
+ primaries.fBX = pngFixedToFloat(chrm[6]);
+ primaries.fBY = pngFixedToFloat(chrm[7]);
+ primaries.fWX = pngFixedToFloat(chrm[0]);
+ primaries.fWY = pngFixedToFloat(chrm[1]);
+
+ SkMatrix44 to_xyzd50(SkMatrix44::kUninitialized_Constructor);
+ if (!primaries.toXYZD50(&to_xyzd50))
+ return nullptr;
+
+ SkColorSpaceTransferFn fn;
+ fn.fG = 1.0f / pngFixedToFloat(inverse_gamma);
+ fn.fA = 1.0f;
+ fn.fB = fn.fC = fn.fD = fn.fE = fn.fF = 0.0f;
+
+ return SkColorSpace::MakeRGB(fn, to_xyzd50);
+}
+
+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?
+ sk_sp<SkColorSpace> color_space = ReadColorSpace(png, info);
+ if (color_space)
+ SetEmbeddedColorSpace(color_space);
+}
+
+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 (bit_depth == 16)
+ 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 (!HasEmbeddedColorSpace()) {
+ 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;
+ reader_->CreateInterlaceBuffer(color_channels * Size().Area());
+ 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 color_channels = has_alpha ? 4 : 3;
+ row = interlace_buffer + (row_index * color_channels * 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.
+ ImageFrame::PixelData* const dst_row = buffer.GetAddr(frame_rect.X(), y);
+ const int width = frame_rect.Width();
+
+ png_bytep src_ptr = row;
+ if (has_alpha) {
+ // Here we apply the color space transformation to the dst space.
+ // It does not really make sense to transform to a gamma-encoded
+ // space and then immediately after, perform a linear premultiply.
+ // Ideally we would pass kPremul_SkAlphaType to xform->apply(),
+ // instructing SkColorSpaceXform to perform the linear premultiply
+ // while the pixels are a linear space.
+ // We cannot do this because when we apply the gamma encoding after
+ // the premultiply, we will very likely end up with valid pixels
+ // where R, G, and/or B are greater than A. The legacy drawing
+ // pipeline does not know how to handle this.
+ if (SkColorSpaceXform* xform = ColorTransform()) {
+ SkColorSpaceXform::ColorFormat color_format =
+ SkColorSpaceXform::kRGBA_8888_ColorFormat;
+ bool color_converison_successful =
+ xform->apply(color_format, dst_row, color_format, src_ptr, width,
+ kUnpremul_SkAlphaType);
+ DCHECK(color_converison_successful);
+ src_ptr = png_bytep(dst_row);
+ }
+
+ 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, purely because SkColorSpaceXform supports
+ // RGBA (and not RGB).
+ if (SkColorSpaceXform* xform = ColorTransform()) {
+ bool color_converison_successful =
+ xform->apply(XformColorFormat(), dst_row, XformColorFormat(), dst_row,
+ width, kOpaque_SkAlphaType);
+ DCHECK(color_converison_successful);
+ }
+ }
+
+ 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