diff options
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.cc | 706 |
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 |