diff options
Diffstat (limited to 'chromium/third_party/blink/renderer/platform/image-decoders')
47 files changed, 13081 insertions, 0 deletions
diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/DEPS b/chromium/third_party/blink/renderer/platform/image-decoders/DEPS new file mode 100644 index 00000000000..3c137cbce31 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+cc/paint/image_animation_count.h", +]
\ No newline at end of file diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/OWNERS b/chromium/third_party/blink/renderer/platform/image-decoders/OWNERS new file mode 100644 index 00000000000..9cf2710a1d5 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/OWNERS @@ -0,0 +1,6 @@ +urvang@chromium.org +pkasting@chromium.org +noel@chromium.org +scroggo@chromium.org + +# COMPONENT: Internals>Images>Codecs diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder.cc b/chromium/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder.cc new file mode 100644 index 00000000000..613de9cc507 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder.cc @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2008, 2009, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder.h" + +#include "third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_reader.h" +#include "third_party/blink/renderer/platform/image-decoders/fast_shared_buffer_reader.h" + +namespace blink { + +// Number of bits in .BMP used to store the file header (doesn't match +// "sizeof(BMPImageDecoder::BitmapFileHeader)" since we omit some fields and +// don't pack). +static const size_t kSizeOfFileHeader = 14; + +BMPImageDecoder::BMPImageDecoder(AlphaOption alpha_option, + const ColorBehavior& color_behavior, + size_t max_decoded_bytes) + : ImageDecoder(alpha_option, color_behavior, max_decoded_bytes), + decoded_offset_(0) {} + +BMPImageDecoder::~BMPImageDecoder() = default; + +void BMPImageDecoder::OnSetData(SegmentReader* data) { + if (reader_) + reader_->SetData(data); +} + +bool BMPImageDecoder::SetFailed() { + reader_.reset(); + return ImageDecoder::SetFailed(); +} + +void BMPImageDecoder::Decode(bool only_size) { + if (Failed()) + return; + + // If we couldn't decode the image but we've received all the data, decoding + // has failed. + if (!DecodeHelper(only_size) && IsAllDataReceived()) + SetFailed(); + // If we're done decoding the image, we don't need the BMPImageReader + // anymore. (If we failed, |reader_| has already been cleared.) + else if (!frame_buffer_cache_.IsEmpty() && + (frame_buffer_cache_.front().GetStatus() == + ImageFrame::kFrameComplete)) + reader_.reset(); +} + +bool BMPImageDecoder::DecodeHelper(bool only_size) { + size_t img_data_offset = 0; + if ((decoded_offset_ < kSizeOfFileHeader) && + !ProcessFileHeader(img_data_offset)) + return false; + + if (!reader_) { + reader_ = std::make_unique<BMPImageReader>(this, decoded_offset_, + img_data_offset, false); + reader_->SetData(data_.get()); + } + + if (!frame_buffer_cache_.IsEmpty()) + reader_->SetBuffer(&frame_buffer_cache_.front()); + + return reader_->DecodeBMP(only_size); +} + +bool BMPImageDecoder::ProcessFileHeader(size_t& img_data_offset) { + // Read file header. + DCHECK(!decoded_offset_); + if (data_->size() < kSizeOfFileHeader) + return false; + + char buffer[kSizeOfFileHeader]; + FastSharedBufferReader fast_reader(data_); + const char* file_header = + fast_reader.GetConsecutiveData(0, kSizeOfFileHeader, buffer); + const uint16_t file_type = + (file_header[0] << 8) | static_cast<uint8_t>(file_header[1]); + img_data_offset = BMPImageReader::ReadUint32(&file_header[10]); + decoded_offset_ = kSizeOfFileHeader; + + // See if this is a bitmap filetype we understand. + enum { + BMAP = 0x424D, // "BM" + // The following additional OS/2 2.x header values (see + // http://www.fileformat.info/format/os2bmp/egff.htm ) aren't widely + // decoded, and are unlikely to be in much use. + /* + ICON = 0x4943, // "IC" + POINTER = 0x5054, // "PT" + COLORICON = 0x4349, // "CI" + COLORPOINTER = 0x4350, // "CP" + BITMAPARRAY = 0x4241, // "BA" + */ + }; + return (file_type == BMAP) || SetFailed(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder.h b/chromium/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder.h new file mode 100644 index 00000000000..628069378b4 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2008, 2009, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_BMP_BMP_IMAGE_DECODER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_BMP_BMP_IMAGE_DECODER_H_ + +#include <memory> +#include "third_party/blink/renderer/platform/image-decoders/image_decoder.h" + +namespace blink { + +class BMPImageReader; + +// This class decodes the BMP image format. +class PLATFORM_EXPORT BMPImageDecoder final : public ImageDecoder { + public: + BMPImageDecoder(AlphaOption, const ColorBehavior&, size_t max_decoded_bytes); + + ~BMPImageDecoder() override; + + // ImageDecoder: + String FilenameExtension() const override { return "bmp"; } + void OnSetData(SegmentReader*) override; + // CAUTION: SetFailed() deletes |reader_|. Be careful to avoid + // accessing deleted memory, especially when calling this from inside + // BMPImageReader! + bool SetFailed() override; + + private: + // ImageDecoder: + void DecodeSize() override { Decode(true); } + void Decode(size_t) override { Decode(false); } + + // Decodes the image. If |only_size| is true, stops decoding after + // calculating the image size. If decoding fails but there is no more + // data coming, sets the "decode failure" flag. + void Decode(bool only_size); + + // Decodes the image. If |only_size| is true, stops decoding after + // calculating the image size. Returns whether decoding succeeded. + bool DecodeHelper(bool only_size); + + // Processes the file header at the beginning of the data. Sets + // |img_data_offset| based on the header contents. Returns true if the + // file header could be decoded. + bool ProcessFileHeader(size_t& img_data_offset); + + // An index into |data_| representing how much we've already decoded. + // Note that this only tracks data _this_ class decodes; once the + // BMPImageReader takes over this will not be updated further. + size_t decoded_offset_; + + // The reader used to do most of the BMP decoding. + std::unique_ptr<BMPImageReader> reader_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder_test.cc b/chromium/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder_test.cc new file mode 100644 index 00000000000..ec92a5b0469 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder_test.cc @@ -0,0 +1,98 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder.h" + +#include <memory> +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/platform/image-decoders/image_decoder_test_helpers.h" +#include "third_party/blink/renderer/platform/shared_buffer.h" + +namespace blink { + +namespace { + +std::unique_ptr<ImageDecoder> CreateBMPDecoder() { + return std::make_unique<BMPImageDecoder>( + ImageDecoder::kAlphaNotPremultiplied, ColorBehavior::TransformToSRGB(), + ImageDecoder::kNoDecodedImageByteLimit); +} + +} // anonymous namespace + +TEST(BMPImageDecoderTest, isSizeAvailable) { + static constexpr char kBmpFile[] = "/images/resources/lenna.bmp"; // 256x256 + scoped_refptr<SharedBuffer> data = ReadFile(kBmpFile); + ASSERT_TRUE(data.get()); + + std::unique_ptr<ImageDecoder> decoder = CreateBMPDecoder(); + decoder->SetData(data.get(), true); + EXPECT_TRUE(decoder->IsSizeAvailable()); + EXPECT_EQ(256, decoder->Size().Width()); + EXPECT_EQ(256, decoder->Size().Height()); +} + +TEST(BMPImageDecoderTest, parseAndDecode) { + static constexpr char kBmpFile[] = "/images/resources/lenna.bmp"; // 256x256 + scoped_refptr<SharedBuffer> data = ReadFile(kBmpFile); + ASSERT_TRUE(data.get()); + + std::unique_ptr<ImageDecoder> decoder = CreateBMPDecoder(); + decoder->SetData(data.get(), true); + + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0); + ASSERT_TRUE(frame); + EXPECT_EQ(ImageFrame::kFrameComplete, frame->GetStatus()); + EXPECT_EQ(256, frame->Bitmap().width()); + EXPECT_EQ(256, frame->Bitmap().height()); + EXPECT_FALSE(decoder->Failed()); +} + +// Test if a BMP decoder returns a proper error while decoding an empty image. +TEST(BMPImageDecoderTest, emptyImage) { + static constexpr char kBmpFile[] = "/images/resources/0x0.bmp"; // 0x0 + scoped_refptr<SharedBuffer> data = ReadFile(kBmpFile); + ASSERT_TRUE(data.get()); + + std::unique_ptr<ImageDecoder> decoder = CreateBMPDecoder(); + decoder->SetData(data.get(), true); + + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0); + ASSERT_TRUE(frame); + EXPECT_EQ(ImageFrame::kFrameEmpty, frame->GetStatus()); + EXPECT_TRUE(decoder->Failed()); +} + +TEST(BMPImageDecoderTest, int32MinHeight) { + static constexpr char kBmpFile[] = + "/images/resources/1xint32_min.bmp"; // 0xINT32_MIN + scoped_refptr<SharedBuffer> data = ReadFile(kBmpFile); + std::unique_ptr<ImageDecoder> decoder = CreateBMPDecoder(); + // Test when not all data is received. + decoder->SetData(data.get(), false); + EXPECT_FALSE(decoder->IsSizeAvailable()); + EXPECT_TRUE(decoder->Failed()); +} + +// This test verifies that calling SharedBuffer::MergeSegmentsIntoBuffer() does +// not break BMP decoding at a critical point: in between a call to decode the +// size (when BMPImageDecoder stops while it may still have input data to +// read) and a call to do a full decode. +TEST(BMPImageDecoderTest, mergeBuffer) { + static constexpr char kBmpFile[] = "/images/resources/lenna.bmp"; + TestMergeBuffer(&CreateBMPDecoder, kBmpFile); +} + +// Verify that decoding this image does not crash. +TEST(BMPImageDecoderTest, crbug752898) { + static constexpr char kBmpFile[] = "/images/resources/crbug752898.bmp"; + scoped_refptr<SharedBuffer> data = ReadFile(kBmpFile); + ASSERT_TRUE(data.get()); + + std::unique_ptr<ImageDecoder> decoder = CreateBMPDecoder(); + decoder->SetData(data.get(), true); + decoder->DecodeFrameBufferAtIndex(0); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_reader.cc b/chromium/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_reader.cc new file mode 100644 index 00000000000..0320face151 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_reader.cc @@ -0,0 +1,857 @@ +/* + * Copyright (c) 2008, 2009, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_reader.h" + +namespace { + +// See comments on lookup_table_addresses_ in the header. +const uint8_t nBitTo8BitlookupTable[] = { + // 1 bit + 0, 255, + // 2 bits + 0, 85, 170, 255, + // 3 bits + 0, 36, 73, 109, 146, 182, 219, 255, + // 4 bits + 0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255, + // 5 bits + 0, 8, 16, 25, 33, 41, 49, 58, 66, 74, 82, 90, 99, 107, 115, 123, 132, 140, + 148, 156, 165, 173, 181, 189, 197, 206, 214, 222, 230, 239, 247, 255, + // 6 bits + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 45, 49, 53, 57, 61, 65, 69, 73, 77, + 81, 85, 89, 93, 97, 101, 105, 109, 113, 117, 121, 125, 130, 134, 138, 142, + 146, 150, 154, 158, 162, 166, 170, 174, 178, 182, 186, 190, 194, 198, 202, + 206, 210, 215, 219, 223, 227, 231, 235, 239, 243, 247, 251, 255, + // 7 bits + 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, + 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, + 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, + 112, 114, 116, 118, 120, 122, 124, 126, 129, 131, 133, 135, 137, 139, 141, + 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, + 173, 175, 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, + 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, + 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255, +}; + +} // namespace + +namespace blink { + +BMPImageReader::BMPImageReader(ImageDecoder* parent, + size_t decoded_and_header_offset, + size_t img_data_offset, + bool is_in_ico) + : parent_(parent), + buffer_(nullptr), + fast_reader_(nullptr), + decoded_offset_(decoded_and_header_offset), + header_offset_(decoded_and_header_offset), + img_data_offset_(img_data_offset), + is_os21x_(false), + is_os22x_(false), + is_top_down_(false), + need_to_process_bitmasks_(false), + need_to_process_color_table_(false), + seen_non_zero_alpha_pixel_(false), + seen_zero_alpha_pixel_(false), + is_in_ico_(is_in_ico), + decoding_and_mask_(false) { + // Clue-in decodeBMP() that we need to detect the correct info header size. + memset(&info_header_, 0, sizeof(info_header_)); +} + +bool BMPImageReader::DecodeBMP(bool only_size) { + // Defensively clear the FastSharedBufferReader's cache, as another caller + // may have called SharedBuffer::MergeSegmentsIntoBuffer(). + fast_reader_.ClearCache(); + + // Calculate size of info header. + if (!info_header_.bi_size && !ReadInfoHeaderSize()) + return false; + + const size_t header_end = header_offset_ + info_header_.bi_size; + // Read and process info header. + if ((decoded_offset_ < header_end) && !ProcessInfoHeader()) + return false; + + // ProcessInfoHeader() set the size, so if that's all we needed, we're done. + if (only_size) + return true; + + // Read and process the bitmasks, if needed. + if (need_to_process_bitmasks_ && !ProcessBitmasks()) + return false; + + // Read and process the color table, if needed. + if (need_to_process_color_table_ && !ProcessColorTable()) + return false; + + // Initialize the framebuffer if needed. + DCHECK(buffer_); // Parent should set this before asking us to decode! + if (buffer_->GetStatus() == ImageFrame::kFrameEmpty) { + if (!buffer_->AllocatePixelData(parent_->Size().Width(), + parent_->Size().Height(), + parent_->ColorSpaceForSkImages())) { + return parent_->SetFailed(); // Unable to allocate. + } + buffer_->ZeroFillPixelData(); + buffer_->SetStatus(ImageFrame::kFramePartial); + // SetSize() calls EraseARGB(), which resets the alpha flag, so we force + // it back to false here. We'll set it true below in all cases where + // these 0s could actually show through. + buffer_->SetHasAlpha(false); + + // For BMPs, the frame always fills the entire image. + buffer_->SetOriginalFrameRect(IntRect(IntPoint(), parent_->Size())); + + if (!is_top_down_) + coord_.SetY(parent_->Size().Height() - 1); + } + + // Decode the data. + if (!decoding_and_mask_ && !PastEndOfImage(0) && + !DecodePixelData((info_header_.bi_compression != RLE4) && + (info_header_.bi_compression != RLE8) && + (info_header_.bi_compression != RLE24))) + return false; + + // If the image has an AND mask and there was no alpha data, process the + // mask. + if (is_in_ico_ && !decoding_and_mask_ && + ((info_header_.bi_bit_count < 16) || !bit_masks_[3] || + !seen_non_zero_alpha_pixel_)) { + // Reset decoding coordinates to start of image. + coord_.SetX(0); + coord_.SetY(is_top_down_ ? 0 : (parent_->Size().Height() - 1)); + + // The AND mask is stored as 1-bit data. + info_header_.bi_bit_count = 1; + + decoding_and_mask_ = true; + } + if (decoding_and_mask_ && !DecodePixelData(true)) + return false; + + // Done! + buffer_->SetStatus(ImageFrame::kFrameComplete); + return true; +} + +bool BMPImageReader::DecodePixelData(bool non_rle) { + const IntPoint coord(coord_); + const ProcessingResult result = + non_rle ? ProcessNonRLEData(false, 0) : ProcessRLEData(); + if (coord_ != coord) + buffer_->SetPixelsChanged(true); + return (result == kFailure) ? parent_->SetFailed() : (result == kSuccess); +} + +bool BMPImageReader::ReadInfoHeaderSize() { + // Get size of info header. + DCHECK_EQ(decoded_offset_, header_offset_); + if ((decoded_offset_ > data_->size()) || + ((data_->size() - decoded_offset_) < 4)) + return false; + info_header_.bi_size = ReadUint32(0); + // Don't increment decoded_offset here, it just makes the code in + // ProcessInfoHeader() more confusing. + + // Don't allow the header to overflow (which would be harmless here, but + // problematic or at least confusing in other places), or to overrun the + // image data. + const size_t header_end = header_offset_ + info_header_.bi_size; + if ((header_end < header_offset_) || + (img_data_offset_ && (img_data_offset_ < header_end))) + return parent_->SetFailed(); + + // See if this is a header size we understand: + // OS/2 1.x: 12 + if (info_header_.bi_size == 12) + is_os21x_ = true; + // Windows V3: 40 + else if ((info_header_.bi_size == 40) || IsWindowsV4Plus()) + ; + // OS/2 2.x: any multiple of 4 between 16 and 64, inclusive, or 42 or 46 + else if ((info_header_.bi_size >= 16) && (info_header_.bi_size <= 64) && + (!(info_header_.bi_size & 3) || (info_header_.bi_size == 42) || + (info_header_.bi_size == 46))) + is_os22x_ = true; + else + return parent_->SetFailed(); + + return true; +} + +bool BMPImageReader::ProcessInfoHeader() { + // Read info header. + DCHECK_EQ(decoded_offset_, header_offset_); + if ((decoded_offset_ > data_->size()) || + ((data_->size() - decoded_offset_) < info_header_.bi_size) || + !ReadInfoHeader()) + return false; + decoded_offset_ += info_header_.bi_size; + + // Sanity-check header values. + if (!IsInfoHeaderValid()) + return parent_->SetFailed(); + + // Set our size. + if (!parent_->SetSize(info_header_.bi_width, info_header_.bi_height)) + return false; + + // For paletted images, bitmaps can set biClrUsed to 0 to mean "all + // colors", so set it to the maximum number of colors for this bit depth. + // Also do this for bitmaps that put too large a value here. + if (info_header_.bi_bit_count < 16) { + const uint32_t max_colors = static_cast<uint32_t>(1) + << info_header_.bi_bit_count; + if (!info_header_.bi_clr_used || (info_header_.bi_clr_used > max_colors)) + info_header_.bi_clr_used = max_colors; + } + + // For any bitmaps that set their BitCount to the wrong value, reset the + // counts now that we've calculated the number of necessary colors, since + // other code relies on this value being correct. + if (info_header_.bi_compression == RLE8) + info_header_.bi_bit_count = 8; + else if (info_header_.bi_compression == RLE4) + info_header_.bi_bit_count = 4; + + // Tell caller what still needs to be processed. + if (info_header_.bi_bit_count >= 16) + need_to_process_bitmasks_ = true; + else if (info_header_.bi_bit_count) + need_to_process_color_table_ = true; + + return true; +} + +bool BMPImageReader::ReadInfoHeader() { + // Pre-initialize some fields that not all headers set. + info_header_.bi_compression = RGB; + info_header_.bi_clr_used = 0; + + if (is_os21x_) { + info_header_.bi_width = ReadUint16(4); + info_header_.bi_height = ReadUint16(6); + DCHECK(!is_in_ico_); // ICO is a Windows format, not OS/2! + info_header_.bi_bit_count = ReadUint16(10); + return true; + } + + info_header_.bi_width = ReadUint32(4); + info_header_.bi_height = ReadUint32(8); + if (is_in_ico_) + info_header_.bi_height /= 2; + info_header_.bi_bit_count = ReadUint16(14); + + // Read compression type, if present. + if (info_header_.bi_size >= 20) { + uint32_t bi_compression = ReadUint32(16); + + // Detect OS/2 2.x-specific compression types. + if ((bi_compression == 3) && (info_header_.bi_bit_count == 1)) { + info_header_.bi_compression = HUFFMAN1D; + is_os22x_ = true; + } else if ((bi_compression == 4) && (info_header_.bi_bit_count == 24)) { + info_header_.bi_compression = RLE24; + is_os22x_ = true; + } else if (bi_compression > 5) + return parent_->SetFailed(); // Some type we don't understand. + else + info_header_.bi_compression = + static_cast<CompressionType>(bi_compression); + } + + // Read colors used, if present. + if (info_header_.bi_size >= 36) + info_header_.bi_clr_used = ReadUint32(32); + + // Windows V4+ can safely read the four bitmasks from 40-56 bytes in, so do + // that here. If the bit depth is less than 16, these values will be ignored + // by the image data decoders. If the bit depth is at least 16 but the + // compression format isn't BITFIELDS, the RGB bitmasks will be ignored and + // overwritten in processBitmasks(). (The alpha bitmask will never be + // overwritten: images that actually want alpha have to specify a valid + // alpha mask. See comments in ProcessBitmasks().) + // + // For non-Windows V4+, bit_masks_[] et. al will be initialized later + // during ProcessBitmasks(). + if (IsWindowsV4Plus()) { + bit_masks_[0] = ReadUint32(40); + bit_masks_[1] = ReadUint32(44); + bit_masks_[2] = ReadUint32(48); + bit_masks_[3] = ReadUint32(52); + } + + // Detect top-down BMPs. + if (info_header_.bi_height < 0) { + // We can't negate INT32_MIN below to get a positive int32_t. + // IsInfoHeaderValid() will reject heights of 1 << 16 or larger anyway, + // so just reject this bitmap now. + if (info_header_.bi_height == INT32_MIN) + return parent_->SetFailed(); + is_top_down_ = true; + info_header_.bi_height = -info_header_.bi_height; + } + + return true; +} + +bool BMPImageReader::IsInfoHeaderValid() const { + // Non-positive widths/heights are invalid. (We've already flipped the + // sign of the height for top-down bitmaps.) + if ((info_header_.bi_width <= 0) || !info_header_.bi_height) + return false; + + // Only Windows V3+ has top-down bitmaps. + if (is_top_down_ && (is_os21x_ || is_os22x_)) + return false; + + // Only bit depths of 1, 4, 8, or 24 are universally supported. + if ((info_header_.bi_bit_count != 1) && (info_header_.bi_bit_count != 4) && + (info_header_.bi_bit_count != 8) && (info_header_.bi_bit_count != 24)) { + // Windows V3+ additionally supports bit depths of 0 (for embedded + // JPEG/PNG images), 16, and 32. + if (is_os21x_ || is_os22x_ || + (info_header_.bi_bit_count && (info_header_.bi_bit_count != 16) && + (info_header_.bi_bit_count != 32))) + return false; + } + + // Each compression type is only valid with certain bit depths (except RGB, + // which can be used with any bit depth). Also, some formats do not support + // some compression types. + switch (info_header_.bi_compression) { + case RGB: + if (!info_header_.bi_bit_count) + return false; + break; + + case RLE8: + // Supposedly there are undocumented formats like "BitCount = 1, + // Compression = RLE4" (which means "4 bit, but with a 2-color table"), + // so also allow the paletted RLE compression types to have too low a + // bit count; we'll correct this later. + if (!info_header_.bi_bit_count || (info_header_.bi_bit_count > 8)) + return false; + break; + + case RLE4: + // See comments in RLE8. + if (!info_header_.bi_bit_count || (info_header_.bi_bit_count > 4)) + return false; + break; + + case BITFIELDS: + // Only valid for Windows V3+. + if (is_os21x_ || is_os22x_ || + ((info_header_.bi_bit_count != 16) && + (info_header_.bi_bit_count != 32))) + return false; + break; + + case JPEG: + case PNG: + // Only valid for Windows V3+. + if (is_os21x_ || is_os22x_ || info_header_.bi_bit_count) + return false; + break; + + case HUFFMAN1D: + // Only valid for OS/2 2.x. + if (!is_os22x_ || (info_header_.bi_bit_count != 1)) + return false; + break; + + case RLE24: + // Only valid for OS/2 2.x. + if (!is_os22x_ || (info_header_.bi_bit_count != 24)) + return false; + break; + + default: + // Some type we don't understand. This should have been caught in + // ReadInfoHeader(). + NOTREACHED(); + return false; + } + + // Top-down bitmaps cannot be compressed; they must be RGB or BITFIELDS. + if (is_top_down_ && (info_header_.bi_compression != RGB) && + (info_header_.bi_compression != BITFIELDS)) + return false; + + // Reject the following valid bitmap types that we don't currently bother + // decoding. Few other people decode these either, they're unlikely to be + // in much use. + // TODO(pkasting): Consider supporting these someday. + // * Bitmaps larger than 2^16 pixels in either dimension (Windows + // probably doesn't draw these well anyway, and the decoded data would + // take a lot of memory). + if ((info_header_.bi_width >= (1 << 16)) || + (info_header_.bi_height >= (1 << 16))) + return false; + // * Windows V3+ JPEG-in-BMP and PNG-in-BMP bitmaps (supposedly not found + // in the wild, only used to send data to printers?). + if ((info_header_.bi_compression == JPEG) || + (info_header_.bi_compression == PNG)) + return false; + // * OS/2 2.x Huffman-encoded monochrome bitmaps (see + // http://www.fileformat.info/mirror/egff/ch09_05.htm , re: "G31D" + // algorithm). + if (info_header_.bi_compression == HUFFMAN1D) + return false; + + return true; +} + +bool BMPImageReader::ProcessBitmasks() { + // Create bit_masks_[] values for R/G/B. + if (info_header_.bi_compression != BITFIELDS) { + // The format doesn't actually use bitmasks. To simplify the decode + // logic later, create bitmasks for the RGB data. For Windows V4+, + // this overwrites the masks we read from the header, which are + // supposed to be ignored in non-BITFIELDS cases. + // 16 bits: MSB <- xRRRRRGG GGGBBBBB -> LSB + // 24/32 bits: MSB <- [AAAAAAAA] RRRRRRRR GGGGGGGG BBBBBBBB -> LSB + const int num_bits = (info_header_.bi_bit_count == 16) ? 5 : 8; + for (int i = 0; i <= 2; ++i) + bit_masks_[i] = ((static_cast<uint32_t>(1) << (num_bits * (3 - i))) - 1) ^ + ((static_cast<uint32_t>(1) << (num_bits * (2 - i))) - 1); + } else if (!IsWindowsV4Plus()) { + // For Windows V4+ BITFIELDS mode bitmaps, this was already done when + // we read the info header. + + // Fail if we don't have enough file space for the bitmasks. + const size_t header_end = header_offset_ + info_header_.bi_size; + const size_t kBitmasksSize = 12; + const size_t bitmasks_end = header_end + kBitmasksSize; + if ((bitmasks_end < header_end) || + (img_data_offset_ && (img_data_offset_ < bitmasks_end))) + return parent_->SetFailed(); + + // Read bitmasks. + if ((data_->size() - decoded_offset_) < kBitmasksSize) + return false; + bit_masks_[0] = ReadUint32(0); + bit_masks_[1] = ReadUint32(4); + bit_masks_[2] = ReadUint32(8); + + decoded_offset_ += kBitmasksSize; + } + + // Alpha is a poorly-documented and inconsistently-used feature. + // + // Windows V4+ has an alpha bitmask in the info header. Unlike the R/G/B + // bitmasks, the MSDN docs don't indicate that it is only valid for the + // BITFIELDS compression format, so we respect it at all times. + // + // To complicate things, Windows V3 BMPs, which lack this mask, can specify + // 32bpp format, which to any sane reader would imply an 8-bit alpha + // channel -- and for BMPs-in-ICOs, that's precisely what's intended to + // happen. There also exist standalone BMPs in this format which clearly + // expect the alpha channel to be respected. However, there are many other + // BMPs which, for example, fill this channel with all 0s, yet clearly + // expect to not be displayed as a fully-transparent rectangle. + // + // If these were the only two types of Windows V3, 32bpp BMPs in the wild, + // we could distinguish between them by scanning the alpha channel in the + // image, looking for nonzero values, and only enabling alpha if we found + // some. (It turns out we have to do this anyway, because, crazily, there + // are also Windows V4+ BMPs with an explicit, non-zero alpha mask, which + // then zero-fill their alpha channels! See comments in + // processNonRLEData().) + // + // Unfortunately there are also V3 BMPs -- indeed, probably more than the + // number of 32bpp, V3 BMPs which intentionally use alpha -- which specify + // 32bpp format, use nonzero (and non-255) alpha values, and yet expect to + // be rendered fully-opaque. And other browsers do so. + // + // So it's impossible to display every BMP in the way its creators intended, + // and we have to choose what to break. Given the paragraph above, we match + // other browsers and ignore alpha in Windows V3 BMPs except inside ICO + // files. + if (!IsWindowsV4Plus()) + bit_masks_[3] = (is_in_ico_ && (info_header_.bi_compression != BITFIELDS) && + (info_header_.bi_bit_count == 32)) + ? static_cast<uint32_t>(0xff000000) + : 0; + + // We've now decoded all the non-image data we care about. Skip anything + // else before the actual raster data. + if (img_data_offset_) + decoded_offset_ = img_data_offset_; + need_to_process_bitmasks_ = false; + + // Check masks and set shift and LUT address values. + for (int i = 0; i < 4; ++i) { + // Trim the mask to the allowed bit depth. Some Windows V4+ BMPs + // specify a bogus alpha channel in bits that don't exist in the pixel + // data (for example, bits 25-31 in a 24-bit RGB format). + if (info_header_.bi_bit_count < 32) + bit_masks_[i] &= + ((static_cast<uint32_t>(1) << info_header_.bi_bit_count) - 1); + + // For empty masks (common on the alpha channel, especially after the + // trimming above), quickly clear the shift and LUT address and + // continue, to avoid an infinite loop in the counting code below. + uint32_t temp_mask = bit_masks_[i]; + if (!temp_mask) { + bit_shifts_right_[i] = 0; + lookup_table_addresses_[i] = nullptr; + continue; + } + + // Make sure bitmask does not overlap any other bitmasks. + for (int j = 0; j < i; ++j) { + if (temp_mask & bit_masks_[j]) + return parent_->SetFailed(); + } + + // Count offset into pixel data. + for (bit_shifts_right_[i] = 0; !(temp_mask & 1); temp_mask >>= 1) + ++bit_shifts_right_[i]; + + // Count size of mask. + size_t num_bits = 0; + for (; temp_mask & 1; temp_mask >>= 1) + ++num_bits; + + // Make sure bitmask is contiguous. + if (temp_mask) + return parent_->SetFailed(); + + // Since RGBABuffer tops out at 8 bits per channel, adjust the shift + // amounts to use the most significant 8 bits of the channel. + if (num_bits >= 8) { + bit_shifts_right_[i] += (num_bits - 8); + num_bits = 0; + } + + // Calculate LUT address. + lookup_table_addresses_[i] = + num_bits ? (nBitTo8BitlookupTable + (1 << num_bits) - 2) : nullptr; + } + + return true; +} + +bool BMPImageReader::ProcessColorTable() { + // Fail if we don't have enough file space for the color table. + const size_t header_end = header_offset_ + info_header_.bi_size; + const size_t table_size_in_bytes = + info_header_.bi_clr_used * (is_os21x_ ? 3 : 4); + const size_t table_end = header_end + table_size_in_bytes; + if ((table_end < header_end) || + (img_data_offset_ && (img_data_offset_ < table_end))) + return parent_->SetFailed(); + + // Read color table. + if ((decoded_offset_ > data_->size()) || + ((data_->size() - decoded_offset_) < table_size_in_bytes)) + return false; + color_table_.resize(info_header_.bi_clr_used); + + // On non-OS/2 1.x, an extra padding byte is present, which we need to skip. + const size_t bytes_per_color = is_os21x_ ? 3 : 4; + for (size_t i = 0; i < info_header_.bi_clr_used; ++i) { + color_table_[i].rgb_blue = ReadUint8(0); + color_table_[i].rgb_green = ReadUint8(1); + color_table_[i].rgb_red = ReadUint8(2); + decoded_offset_ += bytes_per_color; + } + + // We've now decoded all the non-image data we care about. Skip anything + // else before the actual raster data. + if (img_data_offset_) + decoded_offset_ = img_data_offset_; + need_to_process_color_table_ = false; + + return true; +} + +BMPImageReader::ProcessingResult BMPImageReader::ProcessRLEData() { + if (decoded_offset_ > data_->size()) + return kInsufficientData; + + // RLE decoding is poorly specified. Two main problems: + // (1) Are EOL markers necessary? What happens when we have too many + // pixels for one row? + // http://www.fileformat.info/format/bmp/egff.htm says extra pixels + // should wrap to the next line. Real BMPs I've encountered seem to + // instead expect extra pixels to be ignored until the EOL marker is + // seen, although this has only happened in a few cases and I suspect + // those BMPs may be invalid. So we only change lines on EOL (or Delta + // with dy > 0), and fail in most cases when pixels extend past the end + // of the line. + // (2) When Delta, EOL, or EOF are seen, what happens to the "skipped" + // pixels? + // http://www.daubnet.com/formats/BMP.html says these should be filled + // with color 0. However, the "do nothing" and "don't care" comments + // of other references suggest leaving these alone, i.e. letting them + // be transparent to the background behind the image. This seems to + // match how MSPAINT treats BMPs, so we do that. Note that when we + // actually skip pixels for a case like this, we need to note on the + // framebuffer that we have alpha. + + // Impossible to decode row-at-a-time, so just do things as a stream of + // bytes. + while (true) { + // Every entry takes at least two bytes; bail if there isn't enough + // data. + if ((data_->size() - decoded_offset_) < 2) + return kInsufficientData; + + // For every entry except EOF, we'd better not have reached the end of + // the image. + const uint8_t count = ReadUint8(0); + const uint8_t code = ReadUint8(1); + if ((count || (code != 1)) && PastEndOfImage(0)) + return kFailure; + + // Decode. + if (!count) { + switch (code) { + case 0: // Magic token: EOL + // Skip any remaining pixels in this row. + if (coord_.X() < parent_->Size().Width()) + buffer_->SetHasAlpha(true); + MoveBufferToNextRow(); + + decoded_offset_ += 2; + break; + + case 1: // Magic token: EOF + // Skip any remaining pixels in the image. + if ((coord_.X() < parent_->Size().Width()) || + (is_top_down_ ? (coord_.Y() < (parent_->Size().Height() - 1)) + : (coord_.Y() > 0))) + buffer_->SetHasAlpha(true); + // There's no need to move |coord_| here to trigger the caller + // to call SetPixelsChanged(). If the only thing that's changed + // is the alpha state, that will be properly written into the + // underlying SkBitmap when we mark the frame complete. + return kSuccess; + + case 2: { // Magic token: Delta + // The next two bytes specify dx and dy. Bail if there isn't + // enough data. + if ((data_->size() - decoded_offset_) < 4) + return kInsufficientData; + + // Fail if this takes us past the end of the desired row or + // past the end of the image. + const uint8_t dx = ReadUint8(2); + const uint8_t dy = ReadUint8(3); + if (dx || dy) + buffer_->SetHasAlpha(true); + if (((coord_.X() + dx) > parent_->Size().Width()) || + PastEndOfImage(dy)) + return kFailure; + + // Skip intervening pixels. + coord_.Move(dx, is_top_down_ ? dy : -dy); + + decoded_offset_ += 4; + break; + } + + default: { // Absolute mode + // |code| pixels specified as in BI_RGB, zero-padded at the end + // to a multiple of 16 bits. + // Because ProcessNonRLEData() expects decoded_offset_ to + // point to the beginning of the pixel data, bump it past + // the escape bytes and then reset if decoding failed. + decoded_offset_ += 2; + const ProcessingResult result = ProcessNonRLEData(true, code); + if (result != kSuccess) { + decoded_offset_ -= 2; + return result; + } + break; + } + } + } else { // Encoded mode + // The following color data is repeated for |count| total pixels. + // Strangely, some BMPs seem to specify excessively large counts + // here; ignore pixels past the end of the row. + const int end_x = std::min(coord_.X() + count, parent_->Size().Width()); + + if (info_header_.bi_compression == RLE24) { + // Bail if there isn't enough data. + if ((data_->size() - decoded_offset_) < 4) + return kInsufficientData; + + // One BGR triple that we copy |count| times. + FillRGBA(end_x, ReadUint8(3), ReadUint8(2), code, 0xff); + decoded_offset_ += 4; + } else { + // RLE8 has one color index that gets repeated; RLE4 has two + // color indexes in the upper and lower 4 bits of the byte, + // which are alternated. + size_t color_indexes[2] = {code, code}; + if (info_header_.bi_compression == RLE4) { + color_indexes[0] = (color_indexes[0] >> 4) & 0xf; + color_indexes[1] &= 0xf; + } + for (int which = 0; coord_.X() < end_x;) { + // Some images specify color values past the end of the + // color table; set these pixels to black. + if (color_indexes[which] < info_header_.bi_clr_used) + SetI(color_indexes[which]); + else + SetRGBA(0, 0, 0, 255); + which = !which; + } + + decoded_offset_ += 2; + } + } + } +} + +BMPImageReader::ProcessingResult BMPImageReader::ProcessNonRLEData( + bool in_rle, + int num_pixels) { + if (decoded_offset_ > data_->size()) + return kInsufficientData; + + if (!in_rle) + num_pixels = parent_->Size().Width(); + + // Fail if we're being asked to decode more pixels than remain in the row. + const int end_x = coord_.X() + num_pixels; + if (end_x > parent_->Size().Width()) + return kFailure; + + // Determine how many bytes of data the requested number of pixels + // requires. + const size_t pixels_per_byte = 8 / info_header_.bi_bit_count; + const size_t bytes_per_pixel = info_header_.bi_bit_count / 8; + const size_t unpadded_num_bytes = + (info_header_.bi_bit_count < 16) + ? ((num_pixels + pixels_per_byte - 1) / pixels_per_byte) + : (num_pixels * bytes_per_pixel); + // RLE runs are zero-padded at the end to a multiple of 16 bits. Non-RLE + // data is in rows and is zero-padded to a multiple of 32 bits. + const size_t align_bits = in_rle ? 1 : 3; + const size_t padded_num_bytes = + (unpadded_num_bytes + align_bits) & ~align_bits; + + // Decode as many rows as we can. (For RLE, where we only want to decode + // one row, we've already checked that this condition is true.) + while (!PastEndOfImage(0)) { + // Bail if we don't have enough data for the desired number of pixels. + if ((data_->size() - decoded_offset_) < padded_num_bytes) + return kInsufficientData; + + if (info_header_.bi_bit_count < 16) { + // Paletted data. Pixels are stored little-endian within bytes. + // Decode pixels one byte at a time, left to right (so, starting at + // the most significant bits in the byte). + const uint8_t mask = (1 << info_header_.bi_bit_count) - 1; + for (size_t end_offset = decoded_offset_ + unpadded_num_bytes; + decoded_offset_ < end_offset; ++decoded_offset_) { + uint8_t pixel_data = ReadUint8(0); + for (size_t pixel = 0; + (pixel < pixels_per_byte) && (coord_.X() < end_x); ++pixel) { + const size_t color_index = + (pixel_data >> (8 - info_header_.bi_bit_count)) & mask; + if (decoding_and_mask_) { + // There's no way to accurately represent an AND + XOR + // operation as an RGBA image, so where the AND values + // are 1, we simply set the framebuffer pixels to fully + // transparent, on the assumption that most ICOs on the + // web will not be doing a lot of inverting. + if (color_index) { + SetRGBA(0, 0, 0, 0); + buffer_->SetHasAlpha(true); + } else + coord_.Move(1, 0); + } else { + // See comments near the end of ProcessRLEData(). + if (color_index < info_header_.bi_clr_used) + SetI(color_index); + else + SetRGBA(0, 0, 0, 255); + } + pixel_data <<= info_header_.bi_bit_count; + } + } + } else { + // RGB data. Decode pixels one at a time, left to right. + for (; coord_.X() < end_x; decoded_offset_ += bytes_per_pixel) { + const uint32_t pixel = ReadCurrentPixel(bytes_per_pixel); + + // Some BMPs specify an alpha channel but don't actually use it + // (it contains all 0s). To avoid displaying these images as + // fully-transparent, decode as if images are fully opaque + // until we actually see a non-zero alpha value; at that point, + // reset any previously-decoded pixels to fully transparent and + // continue decoding based on the real alpha channel values. + // As an optimization, avoid calling SetHasAlpha(true) for + // images where all alpha values are 255; opaque images are + // faster to draw. + int alpha = GetAlpha(pixel); + if (!seen_non_zero_alpha_pixel_ && !alpha) { + seen_zero_alpha_pixel_ = true; + alpha = 255; + } else { + seen_non_zero_alpha_pixel_ = true; + if (seen_zero_alpha_pixel_) { + buffer_->ZeroFillPixelData(); + seen_zero_alpha_pixel_ = false; + } else if (alpha != 255) + buffer_->SetHasAlpha(true); + } + + SetRGBA(GetComponent(pixel, 0), GetComponent(pixel, 1), + GetComponent(pixel, 2), alpha); + } + } + + // Success, keep going. + decoded_offset_ += (padded_num_bytes - unpadded_num_bytes); + if (in_rle) + return kSuccess; + MoveBufferToNextRow(); + } + + // Finished decoding whole image. + return kSuccess; +} + +void BMPImageReader::MoveBufferToNextRow() { + coord_.Move(-coord_.X(), is_top_down_ ? 1 : -1); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_reader.h b/chromium/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_reader.h new file mode 100644 index 00000000000..db5a170fe66 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_reader.h @@ -0,0 +1,367 @@ +/* + * Copyright (c) 2008, 2009, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_BMP_BMP_IMAGE_READER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_BMP_BMP_IMAGE_READER_H_ + +#include <stdint.h> +#include "third_party/blink/renderer/platform/image-decoders/fast_shared_buffer_reader.h" +#include "third_party/blink/renderer/platform/image-decoders/image_decoder.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/cpu.h" +#include "third_party/blink/renderer/platform/wtf/noncopyable.h" + +namespace blink { + +// This class decodes a BMP image. It is used in the BMP and ICO decoders, +// which wrap it in the appropriate code to read file headers, etc. +class PLATFORM_EXPORT BMPImageReader final { + USING_FAST_MALLOC(BMPImageReader); + WTF_MAKE_NONCOPYABLE(BMPImageReader); + + public: + // Read a value from |buffer|, converting to an int assuming little + // endianness + static inline uint16_t ReadUint16(const char* buffer) { + return *reinterpret_cast<const uint16_t*>(buffer); + } + + static inline uint32_t ReadUint32(const char* buffer) { + return *reinterpret_cast<const uint32_t*>(buffer); + } + + // |parent| is the decoder that owns us. + // |start_offset| points to the start of the BMP within the file. + // |buffer| points at an empty ImageFrame that we'll initialize and + // fill with decoded data. + BMPImageReader(ImageDecoder* parent, + size_t decoded_and_header_offset, + size_t img_data_offset, + bool is_in_ico); + + void SetBuffer(ImageFrame* buffer) { buffer_ = buffer; } + void SetData(SegmentReader* data) { + data_ = data; + fast_reader_.SetData(data); + } + + // Does the actual decoding. If |only_size| is true, decoding only + // progresses as far as necessary to get the image size. Returns + // whether decoding succeeded. + bool DecodeBMP(bool only_size); + + private: + friend class PixelChangedScoper; + + // Helper for DecodeBMP() which will call either ProcessRLEData() or + // ProcessNonRLEData(), depending on the value of |non_rle|, call any + // appropriate notifications to deal with the result, then return whether + // decoding succeeded. + bool DecodePixelData(bool non_rle); + + // The various BMP compression types. We don't currently decode all + // these. + enum CompressionType { + // Universal types + RGB = 0, + RLE8 = 1, + RLE4 = 2, + // Windows V3+ only + BITFIELDS = 3, + JPEG = 4, + PNG = 5, + // OS/2 2.x-only + HUFFMAN1D, // Stored in file as 3 + RLE24, // Stored in file as 4 + }; + enum ProcessingResult { + kSuccess, + kFailure, + kInsufficientData, + }; + + // These are based on the Windows BITMAPINFOHEADER and RGBTRIPLE + // structs, but with unnecessary entries removed. + struct BitmapInfoHeader { + DISALLOW_NEW(); + uint32_t bi_size; + int32_t bi_width; + int32_t bi_height; + uint16_t bi_bit_count; + CompressionType bi_compression; + uint32_t bi_clr_used; + }; + struct RGBTriple { + DISALLOW_NEW_EXCEPT_PLACEMENT_NEW(); + uint8_t rgb_blue; + uint8_t rgb_green; + uint8_t rgb_red; + }; + + inline uint8_t ReadUint8(size_t offset) const { + return fast_reader_.GetOneByte(decoded_offset_ + offset); + } + + inline uint16_t ReadUint16(int offset) const { + char buffer[2]; + const char* data = + fast_reader_.GetConsecutiveData(decoded_offset_ + offset, 2, buffer); + return ReadUint16(data); + } + + inline uint32_t ReadUint32(int offset) const { + char buffer[4]; + const char* data = + fast_reader_.GetConsecutiveData(decoded_offset_ + offset, 4, buffer); + return ReadUint32(data); + } + + // Determines the size of the BMP info header. Returns true if the size + // is valid. + bool ReadInfoHeaderSize(); + + // Processes the BMP info header. Returns true if the info header could + // be decoded. + bool ProcessInfoHeader(); + + // Helper function for ProcessInfoHeader() which does the actual reading + // of header values from the byte stream. Returns false on error. + bool ReadInfoHeader(); + + // Returns true if this is a Windows V4+ BMP. + inline bool IsWindowsV4Plus() const { + // Windows V4 info header is 108 bytes. V5 is 124 bytes. + return (info_header_.bi_size == 108) || (info_header_.bi_size == 124); + } + + // Returns false if consistency errors are found in the info header. + bool IsInfoHeaderValid() const; + + // For BI_BITFIELDS images, initializes the bit_masks_[] and + // bit_offsets_[] arrays. ProcessInfoHeader() will initialize these for + // other compression types where needed. + bool ProcessBitmasks(); + + // For paletted images, allocates and initializes the color_table_[] + // array. + bool ProcessColorTable(); + + // The next two functions return a ProcessingResult instead of a bool so + // they can avoid calling parent_->SetFailed(), which could lead to memory + // corruption since that will delete |this| but some callers still want + // to access member variables after they return. + + // Processes an RLE-encoded image. + ProcessingResult ProcessRLEData(); + + // Processes a set of non-RLE-compressed pixels. Two cases: + // * in_rle = true: the data is inside an RLE-encoded bitmap. Tries to + // process |num_pixels| pixels on the current row. + // * in_rle = false: the data is inside a non-RLE-encoded bitmap. + // |num_pixels| is ignored. Expects |coord_| to point at the + // beginning of the next row to be decoded. Tries to process as + // many complete rows as possible. Returns InsufficientData if + // there wasn't enough data to decode the whole image. + ProcessingResult ProcessNonRLEData(bool in_rle, int num_pixels); + + // Returns true if the current y-coordinate plus |num_rows| would be past + // the end of the image. Here "plus" means "toward the end of the + // image", so downwards for is_top_down_ images and upwards otherwise. + inline bool PastEndOfImage(int num_rows) { + return is_top_down_ ? ((coord_.Y() + num_rows) >= parent_->Size().Height()) + : ((coord_.Y() - num_rows) < 0); + } + + // Returns the pixel data for the current |decoded_offset_| in a uint32_t. + // NOTE: Only as many bytes of the return value as are needed to hold + // the pixel data will actually be set. + inline uint32_t ReadCurrentPixel(int bytes_per_pixel) const { + // We need at most 4 bytes, starting at decoded_offset_. + char buffer[4]; + const char* encoded_pixel = fast_reader_.GetConsecutiveData( + decoded_offset_, bytes_per_pixel, buffer); + switch (bytes_per_pixel) { + case 2: + return ReadUint16(encoded_pixel); + + case 3: { + // It doesn't matter that we never set the most significant byte + // of the return value, the caller won't read it. + uint32_t pixel; + memcpy(&pixel, encoded_pixel, 3); + return pixel; + } + + case 4: + return ReadUint32(encoded_pixel); + + default: + NOTREACHED(); + return 0; + } + } + + // Returns the value of the desired component (0, 1, 2, 3 == R, G, B, A) + // in the given pixel data. + inline unsigned GetComponent(uint32_t pixel, int component) const { + uint8_t value = + (pixel & bit_masks_[component]) >> bit_shifts_right_[component]; + return lookup_table_addresses_[component] + ? lookup_table_addresses_[component][value] + : value; + } + + inline unsigned GetAlpha(uint32_t pixel) const { + // For images without alpha, return alpha of 0xff. + return bit_masks_[3] ? GetComponent(pixel, 3) : 0xff; + } + + // Sets the current pixel to the color given by |color_index|. This also + // increments the relevant local variables to move the current pixel + // right by one. + inline void SetI(size_t color_index) { + SetRGBA(color_table_[color_index].rgb_red, + color_table_[color_index].rgb_green, + color_table_[color_index].rgb_blue, 0xff); + } + + // Like SetI(), but with the individual component values specified. + inline void SetRGBA(unsigned red, + unsigned green, + unsigned blue, + unsigned alpha) { + buffer_->SetRGBA(coord_.X(), coord_.Y(), red, green, blue, alpha); + coord_.Move(1, 0); + } + + // Fills pixels from the current X-coordinate up to, but not including, + // |end_coord| with the color given by the individual components. This + // also increments the relevant local variables to move the current + // pixel right to |end_coord|. + inline void FillRGBA(int end_coord, + unsigned red, + unsigned green, + unsigned blue, + unsigned alpha) { + while (coord_.X() < end_coord) + SetRGBA(red, green, blue, alpha); + } + + // Resets the relevant local variables to start drawing at the left edge + // of the "next" row, where "next" is above or below the current row + // depending on the value of |is_top_down_|. + void MoveBufferToNextRow(); + + // The decoder that owns us. + ImageDecoder* parent_; + + // The destination for the pixel data. + ImageFrame* buffer_; + + // The file to decode. + scoped_refptr<SegmentReader> data_; + FastSharedBufferReader fast_reader_; + + // An index into |data_| representing how much we've already decoded. + size_t decoded_offset_; + + // The file offset at which the BMP info header starts. + size_t header_offset_; + + // The file offset at which the actual image bits start. When decoding + // ICO files, this is set to 0, since it's not stored anywhere in a + // header; the reader functions expect the image data to start + // immediately after the header and (if necessary) color table. + size_t img_data_offset_; + + // The BMP info header. + BitmapInfoHeader info_header_; + + // True if this is an OS/2 1.x (aka Windows 2.x) BMP. The struct + // layouts for this type of BMP are slightly different from the later, + // more common formats. + bool is_os21x_; + + // True if this is an OS/2 2.x BMP. The meanings of compression types 3 + // and 4 for this type of BMP differ from Windows V3+ BMPs. + // + // This will be falsely negative in some cases, but only ones where the + // way we misinterpret the data is irrelevant. + bool is_os22x_; + + // True if the BMP is not vertically flipped, that is, the first line of + // raster data in the file is the top line of the image. + bool is_top_down_; + + // These flags get set to false as we finish each processing stage. + bool need_to_process_bitmasks_; + bool need_to_process_color_table_; + + // Masks/offsets for the color values for non-palette formats. These are + // bitwise, with array entries 0, 1, 2, 3 corresponding to R, G, B, A. + // These are uninitialized (and ignored) for images with less than 16bpp. + uint32_t bit_masks_[4]; + + // Right shift values, meant to be applied after the masks. We need to shift + // the bitfield values down from their offsets into the 32 bits of pixel + // data, as well as truncate the least significant bits of > 8-bit fields. + int bit_shifts_right_[4]; + + // We use a lookup table to convert < 8-bit values into 8-bit values. The + // values in the table are "round(val * 255.0 / ((1 << n) - 1))" for an + // n-bit source value. These elements are set to 0 for 8-bit sources. + const uint8_t* lookup_table_addresses_[4]; + + // The color palette, for paletted formats. + Vector<RGBTriple> color_table_; + + // The coordinate to which we've decoded the image. + IntPoint coord_; + + // Variables that track whether we've seen pixels with alpha values != 0 + // and == 0, respectively. See comments in ProcessNonRLEData() on how + // these are used. + bool seen_non_zero_alpha_pixel_; + bool seen_zero_alpha_pixel_; + + // BMPs-in-ICOs have a few differences from standalone BMPs, so we need to + // know if we're in an ICO container. + bool is_in_ico_; + + // ICOs store a 1bpp "mask" immediately after the main bitmap image data + // (and, confusingly, add its height to the biHeight value in the info + // header, thus doubling it). If |is_in_ico_| is true, this variable tracks + // whether we've begun decoding this mask yet. + bool decoding_and_mask_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/fast_shared_buffer_reader.cc b/chromium/third_party/blink/renderer/platform/image-decoders/fast_shared_buffer_reader.cc new file mode 100644 index 00000000000..edb658ec772 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/fast_shared_buffer_reader.cc @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2015 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/platform/image-decoders/fast_shared_buffer_reader.h" + +namespace blink { + +FastSharedBufferReader::FastSharedBufferReader( + scoped_refptr<SegmentReader> data) + : data_(std::move(data)), + segment_(nullptr), + segment_length_(0), + data_position_(0) {} + +void FastSharedBufferReader::SetData(scoped_refptr<SegmentReader> data) { + if (data == data_) + return; + data_ = std::move(data); + ClearCache(); +} + +void FastSharedBufferReader::ClearCache() { + segment_ = nullptr; + segment_length_ = 0; + data_position_ = 0; +} + +const char* FastSharedBufferReader::GetConsecutiveData(size_t data_position, + size_t length, + char* buffer) const { + CHECK_LE(data_position + length, data_->size()); + + // Use the cached segment if it can serve the request. + if (data_position >= data_position_ && + data_position + length <= data_position_ + segment_length_) + return segment_ + data_position - data_position_; + + // Return a pointer into |data_| if the request doesn't span segments. + GetSomeDataInternal(data_position); + if (length <= segment_length_) + return segment_; + + for (char* dest = buffer;;) { + size_t copy = std::min(length, segment_length_); + memcpy(dest, segment_, copy); + length -= copy; + if (!length) + return buffer; + + // Continue reading the next segment. + dest += copy; + GetSomeDataInternal(data_position_ + copy); + } +} + +size_t FastSharedBufferReader::GetSomeData(const char*& some_data, + size_t data_position) const { + GetSomeDataInternal(data_position); + some_data = segment_; + return segment_length_; +} + +void FastSharedBufferReader::GetSomeDataInternal(size_t data_position) const { + data_position_ = data_position; + segment_length_ = data_->GetSomeData(segment_, data_position); + DCHECK(segment_length_); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/fast_shared_buffer_reader.h b/chromium/third_party/blink/renderer/platform/image-decoders/fast_shared_buffer_reader.h new file mode 100644 index 00000000000..89244919e77 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/fast_shared_buffer_reader.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2015 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_FAST_SHARED_BUFFER_READER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_FAST_SHARED_BUFFER_READER_H_ + +#include "base/memory/scoped_refptr.h" +#include "third_party/blink/renderer/platform/image-decoders/segment_reader.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/noncopyable.h" + +namespace blink { + +// This class is used by image decoders to avoid memory consolidation and +// therefore minimizes the cost of memory copying when the decoders +// repeatedly read from a buffer that is continually growing due to network +// traffic. +class PLATFORM_EXPORT FastSharedBufferReader final { + DISALLOW_NEW(); + WTF_MAKE_NONCOPYABLE(FastSharedBufferReader); + + public: + FastSharedBufferReader(scoped_refptr<SegmentReader> data); + + void SetData(scoped_refptr<SegmentReader>); + + // Returns a consecutive buffer that carries the data starting + // at |data_position| with |length| bytes. + // This method returns a pointer to a memory segment stored in + // |data_| if such a consecutive buffer can be found. + // Otherwise copies into |buffer| and returns it. + // Caller must ensure there are enough bytes in |data_| and |buffer|. + const char* GetConsecutiveData(size_t data_position, + size_t length, + char* buffer) const; + + // Wraps SegmentReader::GetSomeData(). + size_t GetSomeData(const char*& some_data, size_t data_position) const; + + // Returns a byte at |data_position|. + // Caller must ensure there are enough bytes in |data_|. + inline char GetOneByte(size_t data_position) const { + return *GetConsecutiveData(data_position, 1, nullptr); + } + + size_t size() const { return data_->size(); } + + // This class caches the last access for faster subsequent reads. This + // method clears that cache in case the SegmentReader has been modified + // (e.g. with MergeSegmentsIntoBuffer on a wrapped SharedBuffer). + void ClearCache(); + + private: + void GetSomeDataInternal(size_t data_position) const; + + scoped_refptr<SegmentReader> data_; + + // Caches the last segment of |data_| accessed, since subsequent reads are + // likely to re-access it. + mutable const char* segment_; + mutable size_t segment_length_; + + // Data position in |data_| pointed to by |segment_|. + mutable size_t data_position_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/fast_shared_buffer_reader_test.cc b/chromium/third_party/blink/renderer/platform/image-decoders/fast_shared_buffer_reader_test.cc new file mode 100644 index 00000000000..29bf9fcd6c9 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/fast_shared_buffer_reader_test.cc @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2015 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/platform/image-decoders/fast_shared_buffer_reader.h" +#include "third_party/blink/renderer/platform/image-decoders/image_decoder_test_helpers.h" +#include "third_party/blink/renderer/platform/image-decoders/segment_reader.h" +#include "third_party/skia/include/core/SkData.h" +#include "third_party/skia/include/core/SkRWBuffer.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace blink { + +namespace { + +scoped_refptr<SegmentReader> CopyToROBufferSegmentReader( + scoped_refptr<SegmentReader> input) { + SkRWBuffer rw_buffer; + const char* segment = nullptr; + size_t position = 0; + while (size_t length = input->GetSomeData(segment, position)) { + rw_buffer.append(segment, length); + position += length; + } + return SegmentReader::CreateFromSkROBuffer(rw_buffer.makeROBufferSnapshot()); +} + +scoped_refptr<SegmentReader> CopyToDataSegmentReader( + scoped_refptr<SegmentReader> input) { + return SegmentReader::CreateFromSkData(input->GetAsSkData()); +} + +struct SegmentReaders { + scoped_refptr<SegmentReader> segment_readers[3]; + + SegmentReaders(scoped_refptr<SharedBuffer> input) { + segment_readers[0] = + SegmentReader::CreateFromSharedBuffer(std::move(input)); + segment_readers[1] = CopyToROBufferSegmentReader(segment_readers[0]); + segment_readers[2] = CopyToDataSegmentReader(segment_readers[0]); + } +}; + +} // namespace + +TEST(FastSharedBufferReaderTest, nonSequentialReads) { + char reference_data[kDefaultTestSize]; + PrepareReferenceData(reference_data, sizeof(reference_data)); + scoped_refptr<SharedBuffer> data = SharedBuffer::Create(); + data->Append(reference_data, sizeof(reference_data)); + + SegmentReaders reader_struct(data); + for (auto segment_reader : reader_struct.segment_readers) { + FastSharedBufferReader reader(segment_reader); + // Read size is prime such there will be a segment-spanning + // read eventually. + char temp_buffer[17]; + for (size_t data_position = 0; + data_position + sizeof(temp_buffer) < sizeof(reference_data); + data_position += sizeof(temp_buffer)) { + const char* block = reader.GetConsecutiveData( + data_position, sizeof(temp_buffer), temp_buffer); + ASSERT_FALSE( + memcmp(block, reference_data + data_position, sizeof(temp_buffer))); + } + } +} + +TEST(FastSharedBufferReaderTest, readBackwards) { + char reference_data[kDefaultTestSize]; + PrepareReferenceData(reference_data, sizeof(reference_data)); + scoped_refptr<SharedBuffer> data = SharedBuffer::Create(); + data->Append(reference_data, sizeof(reference_data)); + + SegmentReaders reader_struct(data); + for (auto segment_reader : reader_struct.segment_readers) { + FastSharedBufferReader reader(segment_reader); + // Read size is prime such there will be a segment-spanning + // read eventually. + char temp_buffer[17]; + for (size_t data_offset = sizeof(temp_buffer); + data_offset < sizeof(reference_data); + data_offset += sizeof(temp_buffer)) { + const char* block = + reader.GetConsecutiveData(sizeof(reference_data) - data_offset, + sizeof(temp_buffer), temp_buffer); + ASSERT_FALSE(memcmp(block, + reference_data + sizeof(reference_data) - data_offset, + sizeof(temp_buffer))); + } + } +} + +TEST(FastSharedBufferReaderTest, byteByByte) { + char reference_data[kDefaultTestSize]; + PrepareReferenceData(reference_data, sizeof(reference_data)); + scoped_refptr<SharedBuffer> data = SharedBuffer::Create(); + data->Append(reference_data, sizeof(reference_data)); + + SegmentReaders reader_struct(data); + for (auto segment_reader : reader_struct.segment_readers) { + FastSharedBufferReader reader(segment_reader); + for (size_t i = 0; i < sizeof(reference_data); ++i) { + ASSERT_EQ(reference_data[i], reader.GetOneByte(i)); + } + } +} + +// Tests that a read from inside the penultimate segment to the very end of the +// buffer doesn't try to read off the end of the buffer. +TEST(FastSharedBufferReaderTest, readAllOverlappingLastSegmentBoundary) { + const unsigned kDataSize = 2 * SharedBuffer::kSegmentSize; + char reference_data[kDataSize]; + PrepareReferenceData(reference_data, kDataSize); + scoped_refptr<SharedBuffer> data = SharedBuffer::Create(); + data->Append(reference_data, kDataSize); + + SegmentReaders reader_struct(data); + for (auto segment_reader : reader_struct.segment_readers) { + FastSharedBufferReader reader(segment_reader); + char buffer[kDataSize]; + reader.GetConsecutiveData(0, kDataSize, buffer); + ASSERT_FALSE(memcmp(buffer, reference_data, kDataSize)); + } +} + +// Verify that reading past the end of the buffer does not break future reads. +TEST(SegmentReaderTest, readPastEndThenRead) { + const unsigned kDataSize = 2 * SharedBuffer::kSegmentSize; + char reference_data[kDataSize]; + PrepareReferenceData(reference_data, kDataSize); + scoped_refptr<SharedBuffer> data = SharedBuffer::Create(); + data->Append(reference_data, kDataSize); + + SegmentReaders reader_struct(data); + for (auto segment_reader : reader_struct.segment_readers) { + const char* contents; + size_t length = segment_reader->GetSomeData(contents, kDataSize); + EXPECT_EQ(0u, length); + + length = segment_reader->GetSomeData(contents, 0); + EXPECT_LE(SharedBuffer::kSegmentSize, length); + } +} + +TEST(SegmentReaderTest, getAsSkData) { + const unsigned kDataSize = 4 * SharedBuffer::kSegmentSize; + char reference_data[kDataSize]; + PrepareReferenceData(reference_data, kDataSize); + scoped_refptr<SharedBuffer> data = SharedBuffer::Create(); + data->Append(reference_data, kDataSize); + + SegmentReaders reader_struct(data); + for (auto segment_reader : reader_struct.segment_readers) { + sk_sp<SkData> skdata = segment_reader->GetAsSkData(); + EXPECT_EQ(data->size(), skdata->size()); + + const char* segment; + size_t position = 0; + for (size_t length = segment_reader->GetSomeData(segment, position); length; + length = segment_reader->GetSomeData(segment, position)) { + ASSERT_FALSE(memcmp(segment, skdata->bytes() + position, length)); + position += length; + } + EXPECT_EQ(position, kDataSize); + } +} + +TEST(SegmentReaderTest, variableSegments) { + const size_t kDataSize = 3.5 * SharedBuffer::kSegmentSize; + char reference_data[kDataSize]; + PrepareReferenceData(reference_data, kDataSize); + + scoped_refptr<SegmentReader> segment_reader; + { + // Create a SegmentReader with difference sized segments, to test that + // the SkROBuffer implementation works when two consecutive segments + // are not the same size. This test relies on knowledge of the + // internals of SkRWBuffer: it ensures that each segment is at least + // 4096 (though the actual data may be smaller, if it has not been + // written to yet), but when appending a larger amount it may create a + // larger segment. + SkRWBuffer rw_buffer; + rw_buffer.append(reference_data, SharedBuffer::kSegmentSize); + rw_buffer.append(reference_data + SharedBuffer::kSegmentSize, + 2 * SharedBuffer::kSegmentSize); + rw_buffer.append(reference_data + 3 * SharedBuffer::kSegmentSize, + .5 * SharedBuffer::kSegmentSize); + + segment_reader = + SegmentReader::CreateFromSkROBuffer(rw_buffer.makeROBufferSnapshot()); + } + + const char* segment; + size_t position = 0; + size_t last_length = 0; + for (size_t length = segment_reader->GetSomeData(segment, position); length; + length = segment_reader->GetSomeData(segment, position)) { + // It is not a bug to have consecutive segments of the same length, but + // it does mean that the following test does not actually test what it + // is intended to test. + ASSERT_NE(length, last_length); + last_length = length; + + ASSERT_FALSE(memcmp(segment, reference_data + position, length)); + position += length; + } + EXPECT_EQ(position, kDataSize); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder.cc b/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder.cc new file mode 100644 index 00000000000..d7e37fe2be8 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder.cc @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder.h" + +#include <limits> +#include <memory> +#include "third_party/blink/renderer/platform/image-decoders/gif/gif_image_reader.h" +#include "third_party/blink/renderer/platform/wtf/not_found.h" + +namespace blink { + +GIFImageDecoder::GIFImageDecoder(AlphaOption alpha_option, + const ColorBehavior& color_behavior, + size_t max_decoded_bytes) + : ImageDecoder(alpha_option, color_behavior, max_decoded_bytes), + repetition_count_(kAnimationLoopOnce) {} + +GIFImageDecoder::~GIFImageDecoder() = default; + +void GIFImageDecoder::OnSetData(SegmentReader* data) { + if (reader_) + reader_->SetData(data); +} + +int GIFImageDecoder::RepetitionCount() const { + // This value can arrive at any point in the image data stream. Most GIFs + // in the wild declare it near the beginning of the file, so it usually is + // set by the time we've decoded the size, but (depending on the GIF and the + // packets sent back by the webserver) not always. If the reader hasn't + // seen a loop count yet, it will return kCLoopCountNotSeen, in which case we + // should default to looping once (the initial value for + // |repetition_count_|). + // + // There are some additional wrinkles here. First, ImageSource::Clear() + // may destroy the reader, making the result from the reader _less_ + // authoritative on future calls if the recreated reader hasn't seen the + // loop count. We don't need to special-case this because in this case the + // new reader will once again return kCLoopCountNotSeen, and we won't + // overwrite the cached correct value. + // + // Second, a GIF might never set a loop count at all, in which case we + // should continue to treat it as a "loop once" animation. We don't need + // special code here either, because in this case we'll never change + // |repetition_count_| from its default value. + // + // Third, we use the same GIFImageReader for counting frames and we might + // see the loop count and then encounter a decoding error which happens + // later in the stream. It is also possible that no frames are in the + // stream. In these cases we should just loop once. + if (IsAllDataReceived() && ParseCompleted() && reader_->ImagesCount() == 1) + repetition_count_ = kAnimationNone; + else if (Failed() || (reader_ && (!reader_->ImagesCount()))) + repetition_count_ = kAnimationLoopOnce; + else if (reader_ && reader_->LoopCount() != kCLoopCountNotSeen) + repetition_count_ = reader_->LoopCount(); + return repetition_count_; +} + +bool GIFImageDecoder::FrameIsReceivedAtIndex(size_t index) const { + return reader_ && (index < reader_->ImagesCount()) && + reader_->FrameContext(index)->IsComplete(); +} + +TimeDelta GIFImageDecoder::FrameDurationAtIndex(size_t index) const { + return (reader_ && (index < reader_->ImagesCount()) && + reader_->FrameContext(index)->IsHeaderDefined()) + ? TimeDelta::FromMilliseconds( + reader_->FrameContext(index)->DelayTime()) + : TimeDelta(); +} + +bool GIFImageDecoder::SetFailed() { + reader_.reset(); + return ImageDecoder::SetFailed(); +} + +bool GIFImageDecoder::HaveDecodedRow(size_t frame_index, + GIFRow::const_iterator row_begin, + size_t width, + size_t row_number, + unsigned repeat_count, + bool write_transparent_pixels) { + const GIFFrameContext* frame_context = reader_->FrameContext(frame_index); + // The pixel data and coordinates supplied to us are relative to the frame's + // origin within the entire image size, i.e. + // (frameC_context->xOffset, frame_context->yOffset). There is no guarantee + // that width == (size().width() - frame_context->xOffset), so + // we must ensure we don't run off the end of either the source data or the + // row's X-coordinates. + const int x_begin = frame_context->XOffset(); + const int y_begin = frame_context->YOffset() + row_number; + const int x_end = std::min(static_cast<int>(frame_context->XOffset() + width), + Size().Width()); + const int y_end = std::min( + static_cast<int>(frame_context->YOffset() + row_number + repeat_count), + Size().Height()); + if (!width || (x_begin < 0) || (y_begin < 0) || (x_end <= x_begin) || + (y_end <= y_begin)) + return true; + + const GIFColorMap::Table& color_table = + frame_context->LocalColorMap().IsDefined() + ? frame_context->LocalColorMap().GetTable() + : reader_->GlobalColorMap().GetTable(); + + if (color_table.IsEmpty()) + return true; + + GIFColorMap::Table::const_iterator color_table_iter = color_table.begin(); + + // Initialize the frame if necessary. + ImageFrame& buffer = frame_buffer_cache_[frame_index]; + if (!InitFrameBuffer(frame_index)) + return false; + + const size_t transparent_pixel = frame_context->TransparentPixel(); + GIFRow::const_iterator row_end = row_begin + (x_end - x_begin); + ImageFrame::PixelData* current_address = buffer.GetAddr(x_begin, y_begin); + + // We may or may not need to write transparent pixels to the buffer. + // If we're compositing against a previous image, it's wrong, and if + // we're writing atop a cleared, fully transparent buffer, it's + // unnecessary; but if we're decoding an interlaced gif and + // displaying it "Haeberli"-style, we must write these for passes + // beyond the first, or the initial passes will "show through" the + // later ones. + // + // The loops below are almost identical. One writes a transparent pixel + // and one doesn't based on the value of |write_transparent_pixels|. + // The condition check is taken out of the loop to enhance performance. + // This optimization reduces decoding time by about 15% for a 3MB image. + if (write_transparent_pixels) { + for (; row_begin != row_end; ++row_begin, ++current_address) { + const size_t source_value = *row_begin; + if ((source_value != transparent_pixel) && + (source_value < color_table.size())) { + *current_address = color_table_iter[source_value]; + } else { + *current_address = 0; + current_buffer_saw_alpha_ = true; + } + } + } else { + for (; row_begin != row_end; ++row_begin, ++current_address) { + const size_t source_value = *row_begin; + if ((source_value != transparent_pixel) && + (source_value < color_table.size())) + *current_address = color_table_iter[source_value]; + else + current_buffer_saw_alpha_ = true; + } + } + + // Tell the frame to copy the row data if need be. + if (repeat_count > 1) + buffer.CopyRowNTimes(x_begin, x_end, y_begin, y_end); + + buffer.SetPixelsChanged(true); + return true; +} + +bool GIFImageDecoder::ParseCompleted() const { + return reader_ && reader_->ParseCompleted(); +} + +bool GIFImageDecoder::FrameComplete(size_t frame_index) { + // Initialize the frame if necessary. Some GIFs insert do-nothing frames, + // in which case we never reach HaveDecodedRow() before getting here. + if (!InitFrameBuffer(frame_index)) + return SetFailed(); + + if (!current_buffer_saw_alpha_) + CorrectAlphaWhenFrameBufferSawNoAlpha(frame_index); + + frame_buffer_cache_[frame_index].SetStatus(ImageFrame::kFrameComplete); + + return true; +} + +void GIFImageDecoder::ClearFrameBuffer(size_t frame_index) { + if (reader_ && frame_buffer_cache_[frame_index].GetStatus() == + ImageFrame::kFramePartial) { + // Reset the state of the partial frame in the reader so that the frame + // can be decoded again when requested. + reader_->ClearDecodeState(frame_index); + } + ImageDecoder::ClearFrameBuffer(frame_index); +} + +size_t GIFImageDecoder::DecodeFrameCount() { + Parse(kGIFFrameCountQuery); + // If decoding fails, |reader_| will have been destroyed. Instead of + // returning 0 in this case, return the existing number of frames. This way + // if we get halfway through the image before decoding fails, we won't + // suddenly start reporting that the image has zero frames. + return Failed() ? frame_buffer_cache_.size() : reader_->ImagesCount(); +} + +void GIFImageDecoder::InitializeNewFrame(size_t index) { + ImageFrame* buffer = &frame_buffer_cache_[index]; + const GIFFrameContext* frame_context = reader_->FrameContext(index); + buffer->SetOriginalFrameRect( + Intersection(frame_context->FrameRect(), IntRect(IntPoint(), Size()))); + buffer->SetDuration(TimeDelta::FromMilliseconds(frame_context->DelayTime())); + buffer->SetDisposalMethod(frame_context->GetDisposalMethod()); + buffer->SetRequiredPreviousFrameIndex( + FindRequiredPreviousFrame(index, false)); +} + +void GIFImageDecoder::Decode(size_t index) { + Parse(kGIFFrameCountQuery); + + 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) { + if (!reader_->Decode(*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 GIFImageDecoder::Parse(GIFParseQuery query) { + if (Failed()) + return; + + if (!reader_) { + reader_ = std::make_unique<GIFImageReader>(this); + reader_->SetData(data_); + } + + if (!reader_->Parse(query)) + SetFailed(); +} + +void GIFImageDecoder::OnInitFrameBuffer(size_t frame_index) { + current_buffer_saw_alpha_ = false; +} + +bool GIFImageDecoder::CanReusePreviousFrameBuffer(size_t frame_index) const { + DCHECK(frame_index < frame_buffer_cache_.size()); + return frame_buffer_cache_[frame_index].GetDisposalMethod() != + ImageFrame::kDisposeOverwritePrevious; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder.h b/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder.h new file mode 100644 index 00000000000..504d556c1e9 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_GIF_GIF_IMAGE_DECODER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_GIF_GIF_IMAGE_DECODER_H_ + +#include <memory> +#include "third_party/blink/renderer/platform/image-decoders/image_decoder.h" +#include "third_party/blink/renderer/platform/wtf/noncopyable.h" +#include "third_party/blink/renderer/platform/wtf/time.h" + +namespace blink { + +class GIFImageReader; + +using GIFRow = Vector<unsigned char>; + +// This class decodes the GIF image format. +class PLATFORM_EXPORT GIFImageDecoder final : public ImageDecoder { + WTF_MAKE_NONCOPYABLE(GIFImageDecoder); + + public: + GIFImageDecoder(AlphaOption, const ColorBehavior&, size_t max_decoded_bytes); + ~GIFImageDecoder() override; + + enum GIFParseQuery { kGIFSizeQuery, kGIFFrameCountQuery }; + + // ImageDecoder: + String FilenameExtension() const override { return "gif"; } + void OnSetData(SegmentReader* data) override; + int RepetitionCount() const override; + bool FrameIsReceivedAtIndex(size_t) const override; + TimeDelta FrameDurationAtIndex(size_t) const override; + // CAUTION: SetFailed() deletes |reader_|. Be careful to avoid + // accessing deleted memory, especially when calling this from inside + // GIFImageReader! + bool SetFailed() override; + + // Callbacks from the GIF reader. + bool HaveDecodedRow(size_t frame_index, + GIFRow::const_iterator row_begin, + size_t width, + size_t row_number, + unsigned repeat_count, + bool write_transparent_pixels); + bool FrameComplete(size_t frame_index); + + // For testing. + bool ParseCompleted() const; + + private: + // ImageDecoder: + void ClearFrameBuffer(size_t frame_index) override; + virtual void DecodeSize() { Parse(kGIFSizeQuery); } + size_t DecodeFrameCount() override; + void InitializeNewFrame(size_t) override; + void Decode(size_t) override; + + // Parses as much as is needed to answer the query, ignoring bitmap + // data. If parsing fails, sets the "decode failure" flag. + void Parse(GIFParseQuery); + + // Reset the alpha tracker for this frame. Before calling this method, the + // caller must verify that the frame exists. + void OnInitFrameBuffer(size_t) override; + + // When the disposal method of the frame is DisposeOverWritePrevious, the + // next frame will use the previous frame's buffer as its starting state, so + // we can't take over the data in that case. Before calling this method, the + // caller must verify that the frame exists. + bool CanReusePreviousFrameBuffer(size_t) const override; + + bool current_buffer_saw_alpha_; + mutable int repetition_count_; + std::unique_ptr<GIFImageReader> reader_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder_test.cc b/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder_test.cc new file mode 100644 index 00000000000..1991f29bb08 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder_test.cc @@ -0,0 +1,507 @@ +/* + * Copyright (C) 2013 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder.h" + +#include <memory> +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/platform/web_data.h" +#include "third_party/blink/public/platform/web_size.h" +#include "third_party/blink/renderer/platform/image-decoders/image_decoder_test_helpers.h" +#include "third_party/blink/renderer/platform/shared_buffer.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { + +namespace { + +const char kLayoutTestResourcesDir[] = "LayoutTests/images/resources"; + +std::unique_ptr<ImageDecoder> CreateDecoder() { + return std::make_unique<GIFImageDecoder>( + ImageDecoder::kAlphaNotPremultiplied, ColorBehavior::TransformToSRGB(), + ImageDecoder::kNoDecodedImageByteLimit); +} + +void TestRepetitionCount(const char* dir, + const char* file, + int expected_repetition_count) { + std::unique_ptr<ImageDecoder> decoder = CreateDecoder(); + scoped_refptr<SharedBuffer> data = ReadFile(dir, file); + ASSERT_TRUE(data.get()); + decoder->SetData(data.get(), true); + EXPECT_EQ(kAnimationLoopOnce, + decoder->RepetitionCount()); // Default value before decode. + + for (size_t i = 0; i < decoder->FrameCount(); ++i) { + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(i); + EXPECT_EQ(ImageFrame::kFrameComplete, frame->GetStatus()); + } + + EXPECT_EQ(expected_repetition_count, + decoder->RepetitionCount()); // Expected value after decode. +} + +} // anonymous namespace + +TEST(GIFImageDecoderTest, decodeTwoFrames) { + std::unique_ptr<ImageDecoder> decoder = CreateDecoder(); + + scoped_refptr<SharedBuffer> data = + ReadFile(kLayoutTestResourcesDir, "animated.gif"); + ASSERT_TRUE(data.get()); + decoder->SetData(data.get(), true); + EXPECT_EQ(kAnimationLoopOnce, decoder->RepetitionCount()); + + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0); + uint32_t generation_id0 = frame->Bitmap().getGenerationID(); + EXPECT_EQ(ImageFrame::kFrameComplete, frame->GetStatus()); + EXPECT_EQ(16, frame->Bitmap().width()); + EXPECT_EQ(16, frame->Bitmap().height()); + + frame = decoder->DecodeFrameBufferAtIndex(1); + uint32_t generation_id1 = frame->Bitmap().getGenerationID(); + EXPECT_EQ(ImageFrame::kFrameComplete, frame->GetStatus()); + EXPECT_EQ(16, frame->Bitmap().width()); + EXPECT_EQ(16, frame->Bitmap().height()); + EXPECT_TRUE(generation_id0 != generation_id1); + + EXPECT_EQ(2u, decoder->FrameCount()); + EXPECT_EQ(kAnimationLoopInfinite, decoder->RepetitionCount()); +} + +TEST(GIFImageDecoderTest, crbug779261) { + std::unique_ptr<ImageDecoder> decoder = CreateDecoder(); + scoped_refptr<SharedBuffer> data = + ReadFile(kLayoutTestResourcesDir, "crbug779261.gif"); + ASSERT_TRUE(data.get()); + decoder->SetData(data.get(), true); + + for (size_t i = 0; i < decoder->FrameCount(); ++i) { + // In crbug.com/779261, an independent, transparent frame following an + // opaque frame failed to decode. This image has an opaque frame 0 with + // DisposalMethod::kDisposeOverwriteBgcolor, making frame 1, which has + // transparency, independent and contain alpha. + const bool has_alpha = 0 == i ? false : true; + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(i); + EXPECT_EQ(ImageFrame::kFrameComplete, frame->GetStatus()); + EXPECT_EQ(has_alpha, frame->HasAlpha()); + } + + EXPECT_FALSE(decoder->Failed()); +} + +TEST(GIFImageDecoderTest, parseAndDecode) { + std::unique_ptr<ImageDecoder> decoder = CreateDecoder(); + + scoped_refptr<SharedBuffer> data = + ReadFile(kLayoutTestResourcesDir, "animated.gif"); + ASSERT_TRUE(data.get()); + decoder->SetData(data.get(), true); + EXPECT_EQ(kAnimationLoopOnce, decoder->RepetitionCount()); + + // This call will parse the entire file. + EXPECT_EQ(2u, decoder->FrameCount()); + + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0); + EXPECT_EQ(ImageFrame::kFrameComplete, frame->GetStatus()); + EXPECT_EQ(16, frame->Bitmap().width()); + EXPECT_EQ(16, frame->Bitmap().height()); + + frame = decoder->DecodeFrameBufferAtIndex(1); + EXPECT_EQ(ImageFrame::kFrameComplete, frame->GetStatus()); + EXPECT_EQ(16, frame->Bitmap().width()); + EXPECT_EQ(16, frame->Bitmap().height()); + EXPECT_EQ(kAnimationLoopInfinite, decoder->RepetitionCount()); +} + +TEST(GIFImageDecoderTest, parseByteByByte) { + std::unique_ptr<ImageDecoder> decoder = CreateDecoder(); + + const Vector<char> data = + ReadFile(kLayoutTestResourcesDir, "animated.gif")->Copy(); + + size_t frame_count = 0; + + // Pass data to decoder byte by byte. + for (size_t length = 1; length <= data.size(); ++length) { + scoped_refptr<SharedBuffer> temp_data = + SharedBuffer::Create(data.data(), length); + decoder->SetData(temp_data.get(), length == data.size()); + + EXPECT_LE(frame_count, decoder->FrameCount()); + frame_count = decoder->FrameCount(); + } + + EXPECT_EQ(2u, decoder->FrameCount()); + + decoder->DecodeFrameBufferAtIndex(0); + decoder->DecodeFrameBufferAtIndex(1); + EXPECT_EQ(kAnimationLoopInfinite, decoder->RepetitionCount()); +} + +TEST(GIFImageDecoderTest, parseAndDecodeByteByByte) { + TestByteByByteDecode(&CreateDecoder, kLayoutTestResourcesDir, + "animated-gif-with-offsets.gif", 5u, + kAnimationLoopInfinite); +} + +TEST(GIFImageDecoderTest, brokenSecondFrame) { + std::unique_ptr<ImageDecoder> decoder = CreateDecoder(); + + scoped_refptr<SharedBuffer> data = + ReadFile(kDecodersTestingDir, "broken.gif"); + ASSERT_TRUE(data.get()); + decoder->SetData(data.get(), true); + + // One frame is detected but cannot be decoded. + EXPECT_EQ(1u, decoder->FrameCount()); + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(1); + EXPECT_FALSE(frame); +} + +TEST(GIFImageDecoderTest, progressiveDecode) { + TestProgressiveDecoding(&CreateDecoder, kDecodersTestingDir, "radient.gif"); +} + +TEST(GIFImageDecoderTest, allDataReceivedTruncation) { + std::unique_ptr<ImageDecoder> decoder = CreateDecoder(); + + const Vector<char> data = + ReadFile(kLayoutTestResourcesDir, "animated.gif")->Copy(); + + ASSERT_GE(data.size(), 10u); + scoped_refptr<SharedBuffer> temp_data = + SharedBuffer::Create(data.data(), data.size() - 10); + decoder->SetData(temp_data.get(), true); + + EXPECT_EQ(2u, decoder->FrameCount()); + EXPECT_FALSE(decoder->Failed()); + + decoder->DecodeFrameBufferAtIndex(0); + EXPECT_FALSE(decoder->Failed()); + decoder->DecodeFrameBufferAtIndex(1); + EXPECT_TRUE(decoder->Failed()); +} + +TEST(GIFImageDecoderTest, frameIsComplete) { + std::unique_ptr<ImageDecoder> decoder = CreateDecoder(); + + scoped_refptr<SharedBuffer> data = + ReadFile(kLayoutTestResourcesDir, "animated.gif"); + ASSERT_TRUE(data.get()); + decoder->SetData(data.get(), true); + + EXPECT_EQ(2u, decoder->FrameCount()); + EXPECT_FALSE(decoder->Failed()); + EXPECT_TRUE(decoder->FrameIsReceivedAtIndex(0)); + EXPECT_TRUE(decoder->FrameIsReceivedAtIndex(1)); + EXPECT_EQ(kAnimationLoopInfinite, decoder->RepetitionCount()); +} + +TEST(GIFImageDecoderTest, frameIsCompleteLoading) { + std::unique_ptr<ImageDecoder> decoder = CreateDecoder(); + + scoped_refptr<SharedBuffer> data_buffer = + ReadFile(kLayoutTestResourcesDir, "animated.gif"); + ASSERT_TRUE(data_buffer.get()); + const Vector<char> data = data_buffer->Copy(); + + ASSERT_GE(data.size(), 10u); + scoped_refptr<SharedBuffer> temp_data = + SharedBuffer::Create(data.data(), data.size() - 10); + decoder->SetData(temp_data.get(), false); + + EXPECT_EQ(2u, decoder->FrameCount()); + EXPECT_FALSE(decoder->Failed()); + EXPECT_TRUE(decoder->FrameIsReceivedAtIndex(0)); + EXPECT_FALSE(decoder->FrameIsReceivedAtIndex(1)); + + decoder->SetData(data_buffer.get(), true); + EXPECT_EQ(2u, decoder->FrameCount()); + EXPECT_TRUE(decoder->FrameIsReceivedAtIndex(0)); + EXPECT_TRUE(decoder->FrameIsReceivedAtIndex(1)); +} + +TEST(GIFImageDecoderTest, badTerminator) { + scoped_refptr<SharedBuffer> reference_data = + ReadFile(kDecodersTestingDir, "radient.gif"); + scoped_refptr<SharedBuffer> test_data = + ReadFile(kDecodersTestingDir, "radient-bad-terminator.gif"); + ASSERT_TRUE(reference_data.get()); + ASSERT_TRUE(test_data.get()); + + std::unique_ptr<ImageDecoder> reference_decoder = CreateDecoder(); + reference_decoder->SetData(reference_data.get(), true); + EXPECT_EQ(1u, reference_decoder->FrameCount()); + ImageFrame* reference_frame = reference_decoder->DecodeFrameBufferAtIndex(0); + DCHECK(reference_frame); + + std::unique_ptr<ImageDecoder> test_decoder = CreateDecoder(); + test_decoder->SetData(test_data.get(), true); + EXPECT_EQ(1u, test_decoder->FrameCount()); + ImageFrame* test_frame = test_decoder->DecodeFrameBufferAtIndex(0); + DCHECK(test_frame); + + EXPECT_EQ(HashBitmap(reference_frame->Bitmap()), + HashBitmap(test_frame->Bitmap())); +} + +TEST(GIFImageDecoderTest, updateRequiredPreviousFrameAfterFirstDecode) { + TestUpdateRequiredPreviousFrameAfterFirstDecode( + &CreateDecoder, kLayoutTestResourcesDir, "animated-10color.gif"); +} + +TEST(GIFImageDecoderTest, randomFrameDecode) { + // Single frame image. + TestRandomFrameDecode(&CreateDecoder, kDecodersTestingDir, "radient.gif"); + // Multiple frame images. + TestRandomFrameDecode(&CreateDecoder, kLayoutTestResourcesDir, + "animated-gif-with-offsets.gif"); + TestRandomFrameDecode(&CreateDecoder, kLayoutTestResourcesDir, + "animated-10color.gif"); +} + +TEST(GIFImageDecoderTest, randomDecodeAfterClearFrameBufferCache) { + // Single frame image. + TestRandomDecodeAfterClearFrameBufferCache( + &CreateDecoder, kDecodersTestingDir, "radient.gif"); + // Multiple frame images. + TestRandomDecodeAfterClearFrameBufferCache( + &CreateDecoder, kLayoutTestResourcesDir, "animated-gif-with-offsets.gif"); + TestRandomDecodeAfterClearFrameBufferCache( + &CreateDecoder, kLayoutTestResourcesDir, "animated-10color.gif"); +} + +TEST(GIFImageDecoderTest, resumePartialDecodeAfterClearFrameBufferCache) { + TestResumePartialDecodeAfterClearFrameBufferCache( + &CreateDecoder, kLayoutTestResourcesDir, "animated-10color.gif"); +} + +// The first LZW codes in the image are invalid values that try to create a loop +// in the dictionary. Decoding should fail, but not infinitely loop or corrupt +// memory. +TEST(GIFImageDecoderTest, badInitialCode) { + scoped_refptr<SharedBuffer> test_data = + ReadFile(kDecodersTestingDir, "bad-initial-code.gif"); + ASSERT_TRUE(test_data.get()); + + std::unique_ptr<ImageDecoder> test_decoder = CreateDecoder(); + test_decoder->SetData(test_data.get(), true); + EXPECT_EQ(1u, test_decoder->FrameCount()); + ASSERT_TRUE(test_decoder->DecodeFrameBufferAtIndex(0)); + EXPECT_TRUE(test_decoder->Failed()); +} + +// The image has an invalid LZW code that exceeds dictionary size. Decoding +// should fail. +TEST(GIFImageDecoderTest, badCode) { + scoped_refptr<SharedBuffer> test_data = + ReadFile(kDecodersTestingDir, "bad-code.gif"); + ASSERT_TRUE(test_data.get()); + + std::unique_ptr<ImageDecoder> test_decoder = CreateDecoder(); + test_decoder->SetData(test_data.get(), true); + EXPECT_EQ(1u, test_decoder->FrameCount()); + ASSERT_TRUE(test_decoder->DecodeFrameBufferAtIndex(0)); + EXPECT_TRUE(test_decoder->Failed()); +} + +TEST(GIFImageDecoderTest, invalidDisposalMethod) { + std::unique_ptr<ImageDecoder> decoder = CreateDecoder(); + + // The image has 2 frames, with disposal method 4 and 5, respectively. + scoped_refptr<SharedBuffer> data = + ReadFile(kDecodersTestingDir, "invalid-disposal-method.gif"); + ASSERT_TRUE(data.get()); + decoder->SetData(data.get(), true); + + EXPECT_EQ(2u, decoder->FrameCount()); + // Disposal method 4 is converted to ImageFrame::DisposeOverwritePrevious. + EXPECT_EQ(ImageFrame::kDisposeOverwritePrevious, + decoder->DecodeFrameBufferAtIndex(0)->GetDisposalMethod()); + // Disposal method 5 is ignored. + EXPECT_EQ(ImageFrame::kDisposeNotSpecified, + decoder->DecodeFrameBufferAtIndex(1)->GetDisposalMethod()); +} + +TEST(GIFImageDecoderTest, firstFrameHasGreaterSizeThanScreenSize) { + const Vector<char> full_data = + ReadFile(kDecodersTestingDir, + "first-frame-has-greater-size-than-screen-size.gif") + ->Copy(); + + std::unique_ptr<ImageDecoder> decoder; + IntSize frame_size; + + // Compute hashes when the file is truncated. + for (size_t i = 1; i <= full_data.size(); ++i) { + decoder = CreateDecoder(); + scoped_refptr<SharedBuffer> data = + SharedBuffer::Create(full_data.data(), i); + decoder->SetData(data.get(), i == full_data.size()); + + if (decoder->IsSizeAvailable() && !frame_size.Width() && + !frame_size.Height()) { + frame_size = decoder->DecodedSize(); + continue; + } + + ASSERT_EQ(frame_size.Width(), decoder->DecodedSize().Width()); + ASSERT_EQ(frame_size.Height(), decoder->DecodedSize().Height()); + } +} + +TEST(GIFImageDecoderTest, verifyRepetitionCount) { + TestRepetitionCount(kLayoutTestResourcesDir, "full2loop.gif", 2); + TestRepetitionCount(kDecodersTestingDir, "radient.gif", kAnimationNone); +} + +TEST(GIFImageDecoderTest, bitmapAlphaType) { + scoped_refptr<SharedBuffer> full_data_buffer = + ReadFile(kDecodersTestingDir, "radient.gif"); + ASSERT_TRUE(full_data_buffer.get()); + const Vector<char> full_data = full_data_buffer->Copy(); + + // Empirically chosen truncation size: + // a) large enough to produce a partial frame && + // b) small enough to not fully decode the frame + const size_t kTruncateSize = 800; + ASSERT_TRUE(kTruncateSize < full_data.size()); + scoped_refptr<SharedBuffer> partial_data = + SharedBuffer::Create(full_data.data(), kTruncateSize); + + std::unique_ptr<ImageDecoder> premul_decoder = + std::make_unique<GIFImageDecoder>(ImageDecoder::kAlphaPremultiplied, + ColorBehavior::TransformToSRGB(), + ImageDecoder::kNoDecodedImageByteLimit); + std::unique_ptr<ImageDecoder> unpremul_decoder = + std::make_unique<GIFImageDecoder>(ImageDecoder::kAlphaNotPremultiplied, + ColorBehavior::TransformToSRGB(), + ImageDecoder::kNoDecodedImageByteLimit); + + // Partially decoded frame => the frame alpha type is unknown and should + // reflect the requested format. + premul_decoder->SetData(partial_data.get(), false); + ASSERT_TRUE(premul_decoder->FrameCount()); + unpremul_decoder->SetData(partial_data.get(), false); + ASSERT_TRUE(unpremul_decoder->FrameCount()); + ImageFrame* premul_frame = premul_decoder->DecodeFrameBufferAtIndex(0); + EXPECT_TRUE(premul_frame && + premul_frame->GetStatus() != ImageFrame::kFrameComplete); + EXPECT_EQ(premul_frame->Bitmap().alphaType(), kPremul_SkAlphaType); + ImageFrame* unpremul_frame = unpremul_decoder->DecodeFrameBufferAtIndex(0); + EXPECT_TRUE(unpremul_frame && + unpremul_frame->GetStatus() != ImageFrame::kFrameComplete); + EXPECT_EQ(unpremul_frame->Bitmap().alphaType(), kUnpremul_SkAlphaType); + + // Fully decoded frame => the frame alpha type is known (opaque). + premul_decoder->SetData(full_data_buffer.get(), true); + ASSERT_TRUE(premul_decoder->FrameCount()); + unpremul_decoder->SetData(full_data_buffer.get(), true); + ASSERT_TRUE(unpremul_decoder->FrameCount()); + premul_frame = premul_decoder->DecodeFrameBufferAtIndex(0); + EXPECT_TRUE(premul_frame && + premul_frame->GetStatus() == ImageFrame::kFrameComplete); + EXPECT_EQ(premul_frame->Bitmap().alphaType(), kOpaque_SkAlphaType); + unpremul_frame = unpremul_decoder->DecodeFrameBufferAtIndex(0); + EXPECT_TRUE(unpremul_frame && + unpremul_frame->GetStatus() == ImageFrame::kFrameComplete); + EXPECT_EQ(unpremul_frame->Bitmap().alphaType(), kOpaque_SkAlphaType); +} + +namespace { +// Needed to exercise ImageDecoder::SetMemoryAllocator, but still does the +// default allocation. +class Allocator final : public SkBitmap::Allocator { + bool allocPixelRef(SkBitmap* dst) override { return dst->tryAllocPixels(); } +}; +} + +// Ensure that calling SetMemoryAllocator does not short-circuit +// InitializeNewFrame. +TEST(GIFImageDecoderTest, externalAllocator) { + auto data = ReadFile(kLayoutTestResourcesDir, "boston.gif"); + ASSERT_TRUE(data.get()); + + auto decoder = CreateDecoder(); + decoder->SetData(data.get(), true); + + Allocator allocator; + decoder->SetMemoryAllocator(&allocator); + EXPECT_EQ(1u, decoder->FrameCount()); + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0); + decoder->SetMemoryAllocator(nullptr); + + ASSERT_TRUE(frame); + EXPECT_EQ(IntRect(IntPoint(), decoder->Size()), frame->OriginalFrameRect()); + EXPECT_FALSE(frame->HasAlpha()); +} + +TEST(GIFImageDecoderTest, recursiveDecodeFailure) { + auto data = ReadFile(kLayoutTestResourcesDir, "count-down-color-test.gif"); + ASSERT_TRUE(data.get()); + + { + auto decoder = CreateDecoder(); + decoder->SetData(data.get(), true); + for (size_t i = 0; i <= 3; ++i) { + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(i); + ASSERT_NE(frame, nullptr); + ASSERT_EQ(frame->GetStatus(), ImageFrame::kFrameComplete); + } + } + + // Modify data to have an error in frame 2. + const size_t kErrorOffset = 15302u; + scoped_refptr<SharedBuffer> modified_data = + SharedBuffer::Create(data->Data(), kErrorOffset); + modified_data->Append("A", 1u); + modified_data->Append(data->Data() + kErrorOffset + 1, + data->size() - kErrorOffset - 1); + { + auto decoder = CreateDecoder(); + decoder->SetData(modified_data.get(), true); + decoder->DecodeFrameBufferAtIndex(2); + ASSERT_TRUE(decoder->Failed()); + } + + { + // Decode frame 3, recursively decoding frame 2, which 3 depends on. + auto decoder = CreateDecoder(); + decoder->SetData(modified_data.get(), true); + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(3); + EXPECT_TRUE(decoder->Failed()); + ASSERT_NE(frame, nullptr); + ASSERT_EQ(frame->RequiredPreviousFrameIndex(), 2u); + } +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_reader.cc b/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_reader.cc new file mode 100644 index 00000000000..908a76fc7ba --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_reader.cc @@ -0,0 +1,902 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Chris Saari <saari@netscape.com> + * Apple Computer + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* +The Graphics Interchange Format(c) is the copyright property of CompuServe +Incorporated. Only CompuServe Incorporated is authorized to define, redefine, +enhance, alter, modify or change in any way the definition of the format. + +CompuServe Incorporated hereby grants a limited, non-exclusive, royalty-free +license for the use of the Graphics Interchange Format(sm) in computer +software; computer software utilizing GIF(sm) must acknowledge ownership of the +Graphics Interchange Format and its Service Mark by CompuServe Incorporated, in +User and Technical Documentation. Computer software utilizing GIF, which is +distributed or may be distributed without User or Technical Documentation must +display to the screen or printer a message acknowledging ownership of the +Graphics Interchange Format and the Service Mark by CompuServe Incorporated; in +this case, the acknowledgement may be displayed in an opening screen or leading +banner, or a closing screen or trailing banner. A message such as the following +may be used: + + "The Graphics Interchange Format(c) is the Copyright property of + CompuServe Incorporated. GIF(sm) is a Service Mark property of + CompuServe Incorporated." + +For further information, please contact : + + CompuServe Incorporated + Graphics Technology Department + 5000 Arlington Center Boulevard + Columbus, Ohio 43220 + U. S. A. + +CompuServe Incorporated maintains a mailing list with all those individuals and +organizations who wish to receive copies of this document when it is corrected +or revised. This service is offered free of charge; please provide us with your +mailing address. +*/ + +#include "third_party/blink/renderer/platform/image-decoders/gif/gif_image_reader.h" + +#include <string.h> +#include <algorithm> + +#include "base/memory/ptr_util.h" +#include "third_party/blink/renderer/platform/image-decoders/fast_shared_buffer_reader.h" + +namespace blink { + +namespace { + +static constexpr unsigned kMaxColors = 256u; +static constexpr int kBytesPerColormapEntry = 3; + +} // namespace + +// GETN(n, s) requests at least 'n' bytes available from 'q', at start of state +// 's'. +// +// Note: the hold will never need to be bigger than 256 bytes, as each GIF block +// (except colormaps) can never be bigger than 256 bytes. Colormaps are directly +// copied in the resp. global_colormap or dynamically allocated local_colormap, +// so a fixed buffer in GIFImageReader is good enough. This buffer is only +// needed to copy left-over data from one GifWrite call to the next. +#define GETN(n, s) \ + do { \ + bytes_to_consume_ = (n); \ + state_ = (s); \ + } while (0) + +// Get a 16-bit value stored in little-endian format. +#define GETINT16(p) ((p)[1] << 8 | (p)[0]) + +// Send the data to the display front-end. +bool GIFLZWContext::OutputRow(GIFRow::const_iterator row_begin) { + int drow_start = irow; + int drow_end = irow; + + // Haeberli-inspired hack for interlaced GIFs: Replicate lines while + // displaying to diminish the "venetian-blind" effect as the image is + // loaded. Adjust pixel vertical positions to avoid the appearance of the + // image crawling up the screen as successive passes are drawn. + if (frame_context_->ProgressiveDisplay() && frame_context_->Interlaced() && + ipass < 4) { + unsigned row_dup = 0; + unsigned row_shift = 0; + + switch (ipass) { + case 1: + row_dup = 7; + row_shift = 3; + break; + case 2: + row_dup = 3; + row_shift = 1; + break; + case 3: + row_dup = 1; + row_shift = 0; + break; + default: + break; + } + + drow_start -= row_shift; + drow_end = drow_start + row_dup; + + // Extend if bottom edge isn't covered because of the shift upward. + if (((frame_context_->Height() - 1) - drow_end) <= row_shift) + drow_end = frame_context_->Height() - 1; + + // Clamp first and last rows to upper and lower edge of image. + if (drow_start < 0) + drow_start = 0; + + if ((unsigned)drow_end >= frame_context_->Height()) + drow_end = frame_context_->Height() - 1; + } + + // Protect against too much image data. + if ((unsigned)drow_start >= frame_context_->Height()) + return true; + + // CALLBACK: Let the client know we have decoded a row. + if (!client_->HaveDecodedRow(frame_context_->FrameId(), row_begin, + frame_context_->Width(), drow_start, + drow_end - drow_start + 1, + frame_context_->ProgressiveDisplay() && + frame_context_->Interlaced() && ipass > 1)) + return false; + + if (!frame_context_->Interlaced()) { + irow++; + } else { + do { + switch (ipass) { + case 1: + irow += 8; + if (irow >= frame_context_->Height()) { + ipass++; + irow = 4; + } + break; + + case 2: + irow += 8; + if (irow >= frame_context_->Height()) { + ipass++; + irow = 2; + } + break; + + case 3: + irow += 4; + if (irow >= frame_context_->Height()) { + ipass++; + irow = 1; + } + break; + + case 4: + irow += 2; + if (irow >= frame_context_->Height()) { + ipass++; + irow = 0; + } + break; + + default: + break; + } + } while (irow > (frame_context_->Height() - 1)); + } + return true; +} + +// Performs Lempel-Ziv-Welch decoding. Returns whether decoding was successful. +// If successful, the block will have been completely consumed and/or +// rowsRemaining will be 0. +bool GIFLZWContext::DoLZW(const unsigned char* block, size_t bytes_in_block) { + const size_t width = frame_context_->Width(); + + if (row_iter == row_buffer.end()) + return true; + + for (const unsigned char* ch = block; bytes_in_block-- > 0; ch++) { + // Feed the next byte into the decoder's 32-bit input buffer. + datum += ((int)*ch) << bits; + bits += 8; + + // Check for underflow of decoder's 32-bit input buffer. + while (bits >= codesize) { + // Get the leading variable-length symbol from the data stream. + int code = datum & codemask; + datum >>= codesize; + bits -= codesize; + + // Reset the dictionary to its original state, if requested. + if (code == clear_code) { + codesize = frame_context_->DataSize() + 1; + codemask = (1 << codesize) - 1; + avail = clear_code + 2; + oldcode = -1; + continue; + } + + // Check for explicit end-of-stream code. + if (code == (clear_code + 1)) { + // end-of-stream should only appear after all image data. + if (!rows_remaining) + return true; + return false; + } + + const int temp_code = code; + unsigned short code_length = 0; + if (code < avail) { + // This is a pre-existing code, so we already know what it + // encodes. + code_length = suffix_length[code]; + row_iter += code_length; + } else if (code == avail && oldcode != -1) { + // This is a new code just being added to the dictionary. + // It must encode the contents of the previous code, plus + // the first character of the previous code again. + code_length = suffix_length[oldcode] + 1; + row_iter += code_length; + *--row_iter = firstchar; + code = oldcode; + } else { + // This is an invalid code. The dictionary is just initialized + // and the code is incomplete. We don't know how to handle + // this case. + return false; + } + + while (code >= clear_code) { + *--row_iter = suffix[code]; + code = prefix[code]; + } + + *--row_iter = firstchar = suffix[code]; + + // Define a new codeword in the dictionary as long as we've read + // more than one value from the stream. + if (avail < kMaxDictionaryEntries && oldcode != -1) { + prefix[avail] = oldcode; + suffix[avail] = firstchar; + suffix_length[avail] = suffix_length[oldcode] + 1; + ++avail; + + // If we've used up all the codewords of a given length + // increase the length of codewords by one bit, but don't + // exceed the specified maximum codeword size. + if (!(avail & codemask) && avail < kMaxDictionaryEntries) { + ++codesize; + codemask += avail; + } + } + oldcode = temp_code; + row_iter += code_length; + + // Output as many rows as possible. + GIFRow::iterator row_begin = row_buffer.begin(); + for (; row_begin + width <= row_iter; row_begin += width) { + if (!OutputRow(row_begin)) + return false; + rows_remaining--; + if (!rows_remaining) + return true; + } + + if (row_begin != row_buffer.begin()) { + // Move the remaining bytes to the beginning of the buffer. + const size_t bytes_to_copy = row_iter - row_begin; + memcpy(row_buffer.begin(), row_begin, bytes_to_copy); + row_iter = row_buffer.begin() + bytes_to_copy; + } + } + } + return true; +} + +void GIFColorMap::BuildTable(FastSharedBufferReader* reader) { + if (!is_defined_ || !table_.IsEmpty()) + return; + + CHECK_LE(position_ + colors_ * kBytesPerColormapEntry, reader->size()); + DCHECK_LE(colors_, kMaxColors); + char buffer[kMaxColors * kBytesPerColormapEntry]; + const unsigned char* src_colormap = + reinterpret_cast<const unsigned char*>(reader->GetConsecutiveData( + position_, colors_ * kBytesPerColormapEntry, buffer)); + table_.resize(colors_); + for (Table::iterator iter = table_.begin(); iter != table_.end(); ++iter) { + *iter = SkPackARGB32NoCheck(255, src_colormap[0], src_colormap[1], + src_colormap[2]); + src_colormap += kBytesPerColormapEntry; + } +} + +// Decodes this frame. |frameDecoded| will be set to true if the entire frame is +// decoded. Returns true if decoding progressed further than before without +// error, or there is insufficient new data to decode further. Otherwise, a +// decoding error occurred; returns false in this case. +bool GIFFrameContext::Decode(FastSharedBufferReader* reader, + GIFImageDecoder* client, + bool* frame_decoded) { + local_color_map_.BuildTable(reader); + + *frame_decoded = false; + if (!lzw_context_) { + // Wait for more data to properly initialize GIFLZWContext. + if (!IsDataSizeDefined() || !IsHeaderDefined()) + return true; + + lzw_context_ = std::make_unique<GIFLZWContext>(client, this); + if (!lzw_context_->PrepareToDecode()) { + lzw_context_.reset(); + return false; + } + + current_lzw_block_ = 0; + } + + // Some bad GIFs have extra blocks beyond the last row, which we don't want to + // decode. + while (current_lzw_block_ < lzw_blocks_.size() && + lzw_context_->HasRemainingRows()) { + size_t block_position = lzw_blocks_[current_lzw_block_].block_position; + size_t block_size = lzw_blocks_[current_lzw_block_].block_size; + if (block_position + block_size > reader->size()) + return false; + + while (block_size) { + const char* segment = nullptr; + size_t segment_length = reader->GetSomeData(segment, block_position); + size_t decode_size = std::min(segment_length, block_size); + if (!lzw_context_->DoLZW(reinterpret_cast<const unsigned char*>(segment), + decode_size)) + return false; + block_position += decode_size; + block_size -= decode_size; + } + ++current_lzw_block_; + } + + // If this frame is data complete then the previous loop must have completely + // decoded all LZW blocks. + // There will be no more decoding for this frame so it's time to cleanup. + if (IsComplete()) { + *frame_decoded = true; + lzw_context_.reset(); + } + return true; +} + +// Decodes a frame using GIFFrameContext:decode(). Returns true if decoding has +// progressed, or false if an error has occurred. +bool GIFImageReader::Decode(size_t frame_index) { + FastSharedBufferReader reader(data_); + global_color_map_.BuildTable(&reader); + + bool frame_decoded = false; + GIFFrameContext* current_frame = frames_[frame_index].get(); + + return current_frame->Decode(&reader, client_, &frame_decoded) && + (!frame_decoded || client_->FrameComplete(frame_index)); +} + +bool GIFImageReader::Parse(GIFImageDecoder::GIFParseQuery query) { + if (bytes_read_ >= data_->size()) { + // This data has already been parsed. For example, in deferred + // decoding, a DecodingImageGenerator with more data may have already + // used this same ImageDecoder to decode. This can happen if two + // SkImages created by a DeferredImageDecoder are drawn/prerolled + // out of order (with respect to how much data they had at creation + // time). + return !client_->Failed(); + } + + return ParseData(bytes_read_, data_->size() - bytes_read_, query); +} + +// Parse incoming GIF data stream into internal data structures. +// Return true if parsing has progressed or there is not enough data. +// Return false if a fatal error is encountered. +bool GIFImageReader::ParseData(size_t data_position, + size_t len, + GIFImageDecoder::GIFParseQuery query) { + if (!len) { + // No new data has come in since the last call, just ignore this call. + return true; + } + + if (len < bytes_to_consume_) + return true; + + FastSharedBufferReader reader(data_); + + // A read buffer of 16 bytes is enough to accomodate all possible reads for + // parsing. + char read_buffer[16]; + + // Read as many components from |m_data| as possible. At the beginning of each + // iteration, |dataPosition| is advanced by m_bytesToConsume to point to the + // next component. |len| is decremented accordingly. + while (len >= bytes_to_consume_) { + const size_t current_component_position = data_position; + + // Mark the current component as consumed. Note that currentComponent will + // remain pointed at this component until the next loop iteration. + data_position += bytes_to_consume_; + len -= bytes_to_consume_; + + switch (state_) { + case GIFLZW: + DCHECK(!frames_.IsEmpty()); + // m_bytesToConsume is the current component size because it hasn't been + // updated. + frames_.back()->AddLzwBlock(current_component_position, + bytes_to_consume_); + GETN(1, kGIFSubBlock); + break; + + case kGIFLZWStart: { + DCHECK(!frames_.IsEmpty()); + frames_.back()->SetDataSize(static_cast<unsigned char>( + reader.GetOneByte(current_component_position))); + GETN(1, kGIFSubBlock); + break; + } + + case kGIFType: { + const char* current_component = reader.GetConsecutiveData( + current_component_position, 6, read_buffer); + + // All GIF files begin with "GIF87a" or "GIF89a". + if (!memcmp(current_component, "GIF89a", 6)) + version_ = 89; + else if (!memcmp(current_component, "GIF87a", 6)) + version_ = 87; + else + return false; + GETN(7, kGIFGlobalHeader); + break; + } + + case kGIFGlobalHeader: { + const unsigned char* current_component = + reinterpret_cast<const unsigned char*>(reader.GetConsecutiveData( + current_component_position, 5, read_buffer)); + + // This is the height and width of the "screen" or frame into which + // images are rendered. The individual images can be smaller than + // the screen size and located with an origin anywhere within the + // screen. + // Note that we don't inform the client of the size yet, as it might + // change after we read the first frame's image header. + screen_width_ = GETINT16(current_component); + screen_height_ = GETINT16(current_component + 2); + + const size_t global_color_map_colors = 2 + << (current_component[4] & 0x07); + + if ((current_component[4] & 0x80) && + global_color_map_colors > 0) { /* global map */ + global_color_map_.SetTablePositionAndSize(data_position, + global_color_map_colors); + GETN(kBytesPerColormapEntry * global_color_map_colors, + kGIFGlobalColormap); + break; + } + + GETN(1, kGIFImageStart); + break; + } + + case kGIFGlobalColormap: { + global_color_map_.SetDefined(); + GETN(1, kGIFImageStart); + break; + } + + case kGIFImageStart: { + const char current_component = + reader.GetOneByte(current_component_position); + + if (current_component == '!') { // extension. + GETN(2, kGIFExtension); + break; + } + + if (current_component == ',') { // image separator. + GETN(9, kGIFImageHeader); + break; + } + + // If we get anything other than ',' (image separator), '!' + // (extension), or ';' (trailer), there is extraneous data + // between blocks. The GIF87a spec tells us to keep reading + // until we find an image separator, but GIF89a says such + // a file is corrupt. We follow Mozilla's implementation and + // proceed as if the file were correctly terminated, so the + // GIF will display. + GETN(0, kGIFDone); + break; + } + + case kGIFExtension: { + const unsigned char* current_component = + reinterpret_cast<const unsigned char*>(reader.GetConsecutiveData( + current_component_position, 2, read_buffer)); + + size_t bytes_in_block = current_component[1]; + GIFState exception_state = kGIFSkipBlock; + + switch (*current_component) { + case 0xf9: + exception_state = kGIFControlExtension; + // The GIF spec mandates that the GIFControlExtension header block + // length is 4 bytes, and the parser for this block reads 4 bytes, + // so we must enforce that the buffer contains at least this many + // bytes. If the GIF specifies a different length, we allow that, so + // long as it's larger; the additional data will simply be ignored. + bytes_in_block = std::max(bytes_in_block, static_cast<size_t>(4)); + break; + + // The GIF spec also specifies the lengths of the following two + // extensions' headers (as 12 and 11 bytes, respectively). Because we + // ignore the plain text extension entirely and sanity-check the + // actual length of the application extension header before reading + // it, we allow GIFs to deviate from these values in either direction. + // This is important for real-world compatibility, as GIFs in the wild + // exist with application extension headers that are both shorter and + // longer than 11 bytes. + case 0x01: + // ignoring plain text extension + break; + + case 0xff: + exception_state = kGIFApplicationExtension; + break; + + case 0xfe: + exception_state = kGIFConsumeComment; + break; + } + + if (bytes_in_block) + GETN(bytes_in_block, exception_state); + else + GETN(1, kGIFImageStart); + break; + } + + case kGIFConsumeBlock: { + const unsigned char current_component = static_cast<unsigned char>( + reader.GetOneByte(current_component_position)); + if (!current_component) + GETN(1, kGIFImageStart); + else + GETN(current_component, kGIFSkipBlock); + break; + } + + case kGIFSkipBlock: { + GETN(1, kGIFConsumeBlock); + break; + } + + case kGIFControlExtension: { + const unsigned char* current_component = + reinterpret_cast<const unsigned char*>(reader.GetConsecutiveData( + current_component_position, 4, read_buffer)); + + AddFrameIfNecessary(); + GIFFrameContext* current_frame = frames_.back().get(); + if (*current_component & 0x1) + current_frame->SetTransparentPixel(current_component[3]); + + // We ignore the "user input" bit. + + // NOTE: This relies on the values in the FrameDisposalMethod enum + // matching those in the GIF spec! + int disposal_method = ((*current_component) >> 2) & 0x7; + if (disposal_method < 4) { + current_frame->SetDisposalMethod( + static_cast<ImageFrame::DisposalMethod>(disposal_method)); + } else if (disposal_method == 4) { + // Some specs say that disposal method 3 is "overwrite previous", + // others that setting the third bit of the field (i.e. method 4) is. + // We map both to the same value. + current_frame->SetDisposalMethod( + ImageFrame::kDisposeOverwritePrevious); + } + current_frame->SetDelayTime(GETINT16(current_component + 1) * 10); + GETN(1, kGIFConsumeBlock); + break; + } + + case kGIFCommentExtension: { + const unsigned char current_component = static_cast<unsigned char>( + reader.GetOneByte(current_component_position)); + if (current_component) + GETN(current_component, kGIFConsumeComment); + else + GETN(1, kGIFImageStart); + break; + } + + case kGIFConsumeComment: { + GETN(1, kGIFCommentExtension); + break; + } + + case kGIFApplicationExtension: { + // Check for netscape application extension. + if (bytes_to_consume_ == 11) { + const unsigned char* current_component = + reinterpret_cast<const unsigned char*>(reader.GetConsecutiveData( + current_component_position, 11, read_buffer)); + + if (!memcmp(current_component, "NETSCAPE2.0", 11) || + !memcmp(current_component, "ANIMEXTS1.0", 11)) + GETN(1, kGIFNetscapeExtensionBlock); + } + + if (state_ != kGIFNetscapeExtensionBlock) + GETN(1, kGIFConsumeBlock); + break; + } + + // Netscape-specific GIF extension: animation looping. + case kGIFNetscapeExtensionBlock: { + const int current_component = static_cast<unsigned char>( + reader.GetOneByte(current_component_position)); + // GIFConsumeNetscapeExtension always reads 3 bytes from the stream; we + // should at least wait for this amount. + if (current_component) + GETN(std::max(3, current_component), kGIFConsumeNetscapeExtension); + else + GETN(1, kGIFImageStart); + break; + } + + // Parse netscape-specific application extensions + case kGIFConsumeNetscapeExtension: { + const unsigned char* current_component = + reinterpret_cast<const unsigned char*>(reader.GetConsecutiveData( + current_component_position, 3, read_buffer)); + + int netscape_extension = current_component[0] & 7; + + // Loop entire animation specified # of times. Only read the loop count + // during the first iteration. + if (netscape_extension == 1) { + loop_count_ = GETINT16(current_component + 1); + + // Zero loop count is infinite animation loop request. + if (!loop_count_) + loop_count_ = kAnimationLoopInfinite; + + GETN(1, kGIFNetscapeExtensionBlock); + } else if (netscape_extension == 2) { + // Wait for specified # of bytes to enter buffer. + + // Don't do this, this extension doesn't exist (isn't used at all) + // and doesn't do anything, as our streaming/buffering takes care of + // it all. See http://semmix.pl/color/exgraf/eeg24.htm . + GETN(1, kGIFNetscapeExtensionBlock); + } else { + // 0,3-7 are yet to be defined netscape extension codes + return false; + } + break; + } + + case kGIFImageHeader: { + unsigned height, width, x_offset, y_offset; + const unsigned char* current_component = + reinterpret_cast<const unsigned char*>(reader.GetConsecutiveData( + current_component_position, 9, read_buffer)); + + /* Get image offsets, with respect to the screen origin */ + x_offset = GETINT16(current_component); + y_offset = GETINT16(current_component + 2); + + /* Get image width and height. */ + width = GETINT16(current_component + 4); + height = GETINT16(current_component + 6); + + // Some GIF files have frames that don't fit in the specified + // overall image size. For the first frame, we can simply enlarge + // the image size to allow the frame to be visible. We can't do + // this on subsequent frames because the rest of the decoding + // infrastructure assumes the image size won't change as we + // continue decoding, so any subsequent frames that are even + // larger will be cropped. + // Luckily, handling just the first frame is sufficient to deal + // with most cases, e.g. ones where the image size is erroneously + // set to zero, since usually the first frame completely fills + // the image. + if (CurrentFrameIsFirstFrame()) { + screen_height_ = std::max(screen_height_, y_offset + height); + screen_width_ = std::max(screen_width_, x_offset + width); + } + + // Inform the client of the final size. + if (!sent_size_to_client_ && client_ && + !client_->SetSize(screen_width_, screen_height_)) + return false; + sent_size_to_client_ = true; + + if (query == GIFImageDecoder::kGIFSizeQuery) { + // The decoder needs to stop. Hand back the number of bytes we + // consumed from the buffer minus 9 (the amount we consumed to read + // the header). + SetRemainingBytes(len + 9); + GETN(9, kGIFImageHeader); + return true; + } + + AddFrameIfNecessary(); + GIFFrameContext* current_frame = frames_.back().get(); + + current_frame->SetHeaderDefined(); + + // Work around more broken GIF files that have zero image width or + // height. + if (!height || !width) { + height = screen_height_; + width = screen_width_; + if (!height || !width) + return false; + } + current_frame->SetRect(x_offset, y_offset, width, height); + current_frame->SetInterlaced(current_component[8] & 0x40); + + // Overlaying interlaced, transparent GIFs over + // existing image data using the Haeberli display hack + // requires saving the underlying image in order to + // avoid jaggies at the transparency edges. We are + // unprepared to deal with that, so don't display such + // images progressively. Which means only the first + // frame can be progressively displayed. + // FIXME: It is possible that a non-transparent frame + // can be interlaced and progressively displayed. + current_frame->SetProgressiveDisplay(CurrentFrameIsFirstFrame()); + + const bool is_local_colormap_defined = current_component[8] & 0x80; + if (is_local_colormap_defined) { + // The three low-order bits of currentComponent[8] specify the bits + // per pixel. + const size_t num_colors = 2 << (current_component[8] & 0x7); + current_frame->LocalColorMap().SetTablePositionAndSize(data_position, + num_colors); + GETN(kBytesPerColormapEntry * num_colors, kGIFImageColormap); + break; + } + + GETN(1, kGIFLZWStart); + break; + } + + case kGIFImageColormap: { + DCHECK(!frames_.IsEmpty()); + frames_.back()->LocalColorMap().SetDefined(); + GETN(1, kGIFLZWStart); + break; + } + + case kGIFSubBlock: { + const size_t bytes_in_block = static_cast<unsigned char>( + reader.GetOneByte(current_component_position)); + if (bytes_in_block) { + GETN(bytes_in_block, GIFLZW); + } else { + // Finished parsing one frame; Process next frame. + DCHECK(!frames_.IsEmpty()); + // Note that some broken GIF files do not have enough LZW blocks to + // fully decode all rows; we treat this case as "frame complete". + frames_.back()->SetComplete(); + GETN(1, kGIFImageStart); + } + break; + } + + case kGIFDone: { + parse_completed_ = true; + return true; + } + + default: + // We shouldn't ever get here. + return false; + break; + } + } + + SetRemainingBytes(len); + return true; +} + +void GIFImageReader::SetRemainingBytes(size_t remaining_bytes) { + DCHECK_LE(remaining_bytes, data_->size()); + bytes_read_ = data_->size() - remaining_bytes; +} + +void GIFImageReader::AddFrameIfNecessary() { + if (frames_.IsEmpty() || frames_.back()->IsComplete()) + frames_.push_back(base::WrapUnique(new GIFFrameContext(frames_.size()))); +} + +// FIXME: Move this method to close to doLZW(). +bool GIFLZWContext::PrepareToDecode() { + DCHECK(frame_context_->IsDataSizeDefined()); + DCHECK(frame_context_->IsHeaderDefined()); + + // Since we use a codesize of 1 more than the datasize, we need to ensure + // that our datasize is strictly less than the kMaxDictionaryEntryBits. + if (frame_context_->DataSize() >= kMaxDictionaryEntryBits) + return false; + clear_code = 1 << frame_context_->DataSize(); + avail = clear_code + 2; + oldcode = -1; + codesize = frame_context_->DataSize() + 1; + codemask = (1 << codesize) - 1; + datum = bits = 0; + ipass = frame_context_->Interlaced() ? 1 : 0; + irow = 0; + + // We want to know the longest sequence encodable by a dictionary with + // kMaxDictionaryEntries entries. If we ignore the need to encode the base + // values themselves at the beginning of the dictionary, as well as the need + // for a clear code or a termination code, we could use every entry to + // encode a series of multiple values. If the input value stream looked + // like "AAAAA..." (a long string of just one value), the first dictionary + // entry would encode AA, the next AAA, the next AAAA, and so forth. Thus + // the longest sequence would be kMaxDictionaryEntries + 1 values. + // + // However, we have to account for reserved entries. The first |datasize| + // bits are reserved for the base values, and the next two entries are + // reserved for the clear code and termination code. In theory a GIF can + // set the datasize to 0, meaning we have just two reserved entries, making + // the longest sequence (kMaxDictionaryEntries + 1) - 2 values long. Since + // each value is a byte, this is also the number of bytes in the longest + // encodable sequence. + const size_t kMaxBytes = kMaxDictionaryEntries - 1; + + // Now allocate the output buffer. We decode directly into this buffer + // until we have at least one row worth of data, then call outputRow(). + // This means worst case we may have (row width - 1) bytes in the buffer + // and then decode a sequence |maxBytes| long to append. + row_buffer.resize(frame_context_->Width() - 1 + kMaxBytes); + row_iter = row_buffer.begin(); + rows_remaining = frame_context_->Height(); + + // Clearing the whole suffix table lets us be more tolerant of bad data. + for (int i = 0; i < clear_code; ++i) { + suffix[i] = i; + suffix_length[i] = 1; + } + return true; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_reader.h b/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_reader.h new file mode 100644 index 00000000000..8d56800b6f9 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_reader.h @@ -0,0 +1,371 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_GIF_GIF_IMAGE_READER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_GIF_GIF_IMAGE_READER_H_ + +// Define ourselves as the clientPtr. Mozilla just hacked their C++ callback +// class into this old C decoder, so we will too. +#include <memory> +#include "third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/noncopyable.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { + +class FastSharedBufferReader; + +const int kCLoopCountNotSeen = -2; + +// List of possible parsing states. +enum GIFState { + kGIFType, + kGIFGlobalHeader, + kGIFGlobalColormap, + kGIFImageStart, + kGIFImageHeader, + kGIFImageColormap, + kGIFImageBody, + kGIFLZWStart, + GIFLZW, + kGIFSubBlock, + kGIFExtension, + kGIFControlExtension, + kGIFConsumeBlock, + kGIFSkipBlock, + kGIFDone, + kGIFCommentExtension, + kGIFApplicationExtension, + kGIFNetscapeExtensionBlock, + kGIFConsumeNetscapeExtension, + kGIFConsumeComment +}; + +struct GIFFrameContext; + +// LZW decoder state machine. +class GIFLZWContext final { + USING_FAST_MALLOC(GIFLZWContext); + WTF_MAKE_NONCOPYABLE(GIFLZWContext); + + public: + GIFLZWContext(blink::GIFImageDecoder* client, + const GIFFrameContext* frame_context) + : codesize(0), + codemask(0), + clear_code(0), + avail(0), + oldcode(0), + firstchar(0), + bits(0), + datum(0), + ipass(0), + irow(0), + rows_remaining(0), + row_iter(nullptr), + client_(client), + frame_context_(frame_context) {} + + bool PrepareToDecode(); + bool OutputRow(GIFRow::const_iterator row_begin); + bool DoLZW(const unsigned char* block, size_t bytes_in_block); + bool HasRemainingRows() { return rows_remaining; } + + private: + enum { + kMaxDictionaryEntryBits = 12, + // 2^kMaxDictionaryEntryBits + kMaxDictionaryEntries = 4096, + }; + + // LZW decoding states and output states. + int codesize; + int codemask; + int clear_code; // Codeword used to trigger dictionary reset. + int avail; // Index of next available slot in dictionary. + int oldcode; + unsigned char firstchar; + int bits; // Number of unread bits in "datum". + int datum; // 32-bit input buffer. + int ipass; // Interlace pass; Ranges 1-4 if interlaced. + size_t irow; // Current output row, starting at zero. + size_t rows_remaining; // Rows remaining to be output. + + unsigned short prefix[kMaxDictionaryEntries]; + unsigned char suffix[kMaxDictionaryEntries]; + unsigned short suffix_length[kMaxDictionaryEntries]; + GIFRow row_buffer; // Single scanline temporary buffer. + GIFRow::iterator row_iter; + + // Initialized during construction and read-only. + blink::GIFImageDecoder* client_; + const GIFFrameContext* frame_context_; +}; + +// Data structure for one LZW block. +struct GIFLZWBlock { + DISALLOW_NEW_EXCEPT_PLACEMENT_NEW(); + + public: + GIFLZWBlock(size_t position, size_t size) + : block_position(position), block_size(size) {} + + size_t block_position; + size_t block_size; +}; + +class GIFColorMap final { + DISALLOW_NEW(); + + public: + typedef Vector<blink::ImageFrame::PixelData> Table; + + GIFColorMap() : is_defined_(false), position_(0), colors_(0) {} + + // Set position and number of colors for the RGB table in the data stream. + void SetTablePositionAndSize(size_t position, size_t colors) { + position_ = position; + colors_ = colors; + } + void SetDefined() { is_defined_ = true; } + bool IsDefined() const { return is_defined_; } + + // Build RGBA table using the data stream. + void BuildTable(blink::FastSharedBufferReader*); + const Table& GetTable() const { return table_; } + + private: + bool is_defined_; + size_t position_; + size_t colors_; + Table table_; +}; + +// LocalFrame output state machine. +struct GIFFrameContext { + USING_FAST_MALLOC(GIFFrameContext); + WTF_MAKE_NONCOPYABLE(GIFFrameContext); + + public: + GIFFrameContext(int id) + : frame_id_(id), + x_offset_(0), + y_offset_(0), + width_(0), + height_(0), + transparent_pixel_(kNotFound), + disposal_method_(blink::ImageFrame::kDisposeNotSpecified), + data_size_(0), + progressive_display_(false), + interlaced_(false), + delay_time_(0), + current_lzw_block_(0), + is_complete_(false), + is_header_defined_(false), + is_data_size_defined_(false) {} + + ~GIFFrameContext() = default; + + void AddLzwBlock(size_t position, size_t size) { + lzw_blocks_.push_back(GIFLZWBlock(position, size)); + } + + bool Decode(blink::FastSharedBufferReader*, + blink::GIFImageDecoder* client, + bool* frame_decoded); + + int FrameId() const { return frame_id_; } + void SetRect(unsigned x, unsigned y, unsigned width, unsigned height) { + x_offset_ = x; + y_offset_ = y; + width_ = width; + height_ = height; + } + blink::IntRect FrameRect() const { + return blink::IntRect(x_offset_, y_offset_, width_, height_); + } + unsigned XOffset() const { return x_offset_; } + unsigned YOffset() const { return y_offset_; } + unsigned Width() const { return width_; } + unsigned Height() const { return height_; } + size_t TransparentPixel() const { return transparent_pixel_; } + void SetTransparentPixel(size_t pixel) { transparent_pixel_ = pixel; } + blink::ImageFrame::DisposalMethod GetDisposalMethod() const { + return disposal_method_; + } + void SetDisposalMethod(blink::ImageFrame::DisposalMethod disposal_method) { + disposal_method_ = disposal_method; + } + unsigned DelayTime() const { return delay_time_; } + void SetDelayTime(unsigned delay) { delay_time_ = delay; } + bool IsComplete() const { return is_complete_; } + void SetComplete() { is_complete_ = true; } + bool IsHeaderDefined() const { return is_header_defined_; } + void SetHeaderDefined() { is_header_defined_ = true; } + bool IsDataSizeDefined() const { return is_data_size_defined_; } + int DataSize() const { return data_size_; } + void SetDataSize(int size) { + data_size_ = size; + is_data_size_defined_ = true; + } + bool ProgressiveDisplay() const { return progressive_display_; } + void SetProgressiveDisplay(bool progressive_display) { + progressive_display_ = progressive_display; + } + bool Interlaced() const { return interlaced_; } + void SetInterlaced(bool interlaced) { interlaced_ = interlaced; } + + void ClearDecodeState() { lzw_context_.reset(); } + const GIFColorMap& LocalColorMap() const { return local_color_map_; } + GIFColorMap& LocalColorMap() { return local_color_map_; } + + private: + int frame_id_; + unsigned x_offset_; + unsigned y_offset_; // With respect to "screen" origin. + unsigned width_; + unsigned height_; + size_t transparent_pixel_; // Index of transparent pixel. Value is kNotFound + // if there is no transparent pixel. + blink::ImageFrame::DisposalMethod + disposal_method_; // Restore to background, leave in place, etc. + int data_size_; + + bool progressive_display_; // If true, do Haeberli interlace hack. + bool interlaced_; // True, if scanlines arrive interlaced order. + + unsigned delay_time_; // Display time, in milliseconds, for this image in a + // multi-image GIF. + + std::unique_ptr<GIFLZWContext> lzw_context_; + Vector<GIFLZWBlock> lzw_blocks_; // LZW blocks for this frame. + GIFColorMap local_color_map_; + + size_t current_lzw_block_; + bool is_complete_; + bool is_header_defined_; + bool is_data_size_defined_; +}; + +class PLATFORM_EXPORT GIFImageReader final { + USING_FAST_MALLOC(GIFImageReader); + WTF_MAKE_NONCOPYABLE(GIFImageReader); + + public: + GIFImageReader(blink::GIFImageDecoder* client = nullptr) + : client_(client), + state_(kGIFType), + // Number of bytes for GIF type, either "GIF87a" or "GIF89a". + bytes_to_consume_(6), + bytes_read_(0), + version_(0), + screen_width_(0), + screen_height_(0), + sent_size_to_client_(false), + loop_count_(kCLoopCountNotSeen), + parse_completed_(false) {} + + ~GIFImageReader() = default; + + void SetData(scoped_refptr<blink::SegmentReader> data) { + data_ = std::move(data); + } + bool Parse(blink::GIFImageDecoder::GIFParseQuery); + bool Decode(size_t frame_index); + + size_t ImagesCount() const { + if (frames_.IsEmpty()) + return 0; + + // This avoids counting an empty frame when the file is truncated right + // after GIFControlExtension but before GIFImageHeader. + // FIXME: This extra complexity is not necessary and we should just report + // m_frames.size(). + return frames_.back()->IsHeaderDefined() ? frames_.size() + : frames_.size() - 1; + } + int LoopCount() const { return loop_count_; } + + const GIFColorMap& GlobalColorMap() const { return global_color_map_; } + + const GIFFrameContext* FrameContext(size_t index) const { + return index < frames_.size() ? frames_[index].get() : nullptr; + } + + bool ParseCompleted() const { return parse_completed_; } + + void ClearDecodeState(size_t index) { frames_[index]->ClearDecodeState(); } + + private: + bool ParseData(size_t data_position, + size_t len, + blink::GIFImageDecoder::GIFParseQuery); + void SetRemainingBytes(size_t); + + void AddFrameIfNecessary(); + bool CurrentFrameIsFirstFrame() const { + return frames_.IsEmpty() || + (frames_.size() == 1u && !frames_[0]->IsComplete()); + } + + blink::GIFImageDecoder* client_; + + // Parsing state machine. + GIFState state_; // Current decoder master state. + size_t bytes_to_consume_; // Number of bytes to consume for next stage of + // parsing. + size_t bytes_read_; // Number of bytes processed. + + // Global (multi-image) state. + int version_; // Either 89 for GIF89 or 87 for GIF87. + unsigned screen_width_; // Logical screen width & height. + unsigned screen_height_; + bool sent_size_to_client_; + GIFColorMap global_color_map_; + int loop_count_; // Netscape specific extension block to control the number + // of animation loops a GIF renders. + + Vector<std::unique_ptr<GIFFrameContext>> frames_; + + scoped_refptr<blink::SegmentReader> data_; + bool parse_completed_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/ico/ico_image_decoder.cc b/chromium/third_party/blink/renderer/platform/image-decoders/ico/ico_image_decoder.cc new file mode 100644 index 00000000000..9a52f370cf2 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/ico/ico_image_decoder.cc @@ -0,0 +1,344 @@ +/* + * Copyright (c) 2008, 2009, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/platform/image-decoders/ico/ico_image_decoder.h" + +#include <algorithm> +#include "third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.h" + +namespace blink { + +// Number of bits in .ICO/.CUR used to store the directory and its entries, +// respectively (doesn't match sizeof values for member structs since we omit +// some fields). +static const size_t kSizeOfDirectory = 6; +static const size_t kSizeOfDirEntry = 16; + +ICOImageDecoder::ICOImageDecoder(AlphaOption alpha_option, + const ColorBehavior& color_behavior, + size_t max_decoded_bytes) + : ImageDecoder(alpha_option, color_behavior, max_decoded_bytes), + fast_reader_(nullptr), + decoded_offset_(0), + dir_entries_count_(0), + color_behavior_(color_behavior) {} + +ICOImageDecoder::~ICOImageDecoder() = default; + +void ICOImageDecoder::OnSetData(SegmentReader* data) { + fast_reader_.SetData(data); + + for (BMPReaders::iterator i(bmp_readers_.begin()); i != bmp_readers_.end(); + ++i) { + if (*i) + (*i)->SetData(data); + } + for (size_t i = 0; i < png_decoders_.size(); ++i) + SetDataForPNGDecoderAtIndex(i); +} + +IntSize ICOImageDecoder::Size() const { + return frame_size_.IsEmpty() ? ImageDecoder::Size() : frame_size_; +} + +IntSize ICOImageDecoder::FrameSizeAtIndex(size_t index) const { + return (index && (index < dir_entries_.size())) ? dir_entries_[index].size_ + : Size(); +} + +bool ICOImageDecoder::SetSize(unsigned width, unsigned height) { + // The size calculated inside the BMPImageReader had better match the one in + // the icon directory. + return frame_size_.IsEmpty() + ? ImageDecoder::SetSize(width, height) + : ((IntSize(width, height) == frame_size_) || SetFailed()); +} + +bool ICOImageDecoder::FrameIsReceivedAtIndex(size_t index) const { + if (index >= dir_entries_.size()) + return false; + + SECURITY_DCHECK(data_); + const IconDirectoryEntry& dir_entry = dir_entries_[index]; + return (dir_entry.image_offset_ + dir_entry.byte_size_) <= data_->size(); +} + +bool ICOImageDecoder::SetFailed() { + bmp_readers_.clear(); + png_decoders_.clear(); + return ImageDecoder::SetFailed(); +} + +bool ICOImageDecoder::HotSpot(IntPoint& hot_spot) const { + // When unspecified, the default frame is always frame 0. This is consistent + // with BitmapImage, where CurrentFrame() starts at 0 and only increases when + // animation is requested. + return HotSpotAtIndex(0, hot_spot); +} + +bool ICOImageDecoder::HotSpotAtIndex(size_t index, IntPoint& hot_spot) const { + if (index >= dir_entries_.size() || file_type_ != CURSOR) + return false; + + hot_spot = dir_entries_[index].hot_spot_; + return true; +} + +// static +bool ICOImageDecoder::CompareEntries(const IconDirectoryEntry& a, + const IconDirectoryEntry& b) { + // Larger icons are better. After that, higher bit-depth icons are better. + const int a_entry_area = a.size_.Width() * a.size_.Height(); + const int b_entry_area = b.size_.Width() * b.size_.Height(); + return (a_entry_area == b_entry_area) ? (a.bit_count_ > b.bit_count_) + : (a_entry_area > b_entry_area); +} + +size_t ICOImageDecoder::DecodeFrameCount() { + DecodeSize(); + + // If DecodeSize() fails, return the existing number of frames. This way + // if we get halfway through the image before decoding fails, we won't + // suddenly start reporting that the image has zero frames. + if (Failed() || !data_) + return frame_buffer_cache_.size(); + + // If the file is incomplete, return the length of the sequence of completely + // received frames. We don't do this when the file is fully received, since + // some ICOs have entries whose claimed offset + size extends past the end of + // the file, and we still want to display these if they don't trigger decoding + // failures elsewhere. + if (!IsAllDataReceived()) { + for (size_t i = 0; i < dir_entries_.size(); ++i) { + const IconDirectoryEntry& dir_entry = dir_entries_[i]; + if ((dir_entry.image_offset_ + dir_entry.byte_size_) > data_->size()) + return i; + } + } + return dir_entries_.size(); +} + +void ICOImageDecoder::SetDataForPNGDecoderAtIndex(size_t index) { + if (!png_decoders_[index]) + return; + + png_decoders_[index]->SetData(data_.get(), IsAllDataReceived()); +} + +void ICOImageDecoder::Decode(size_t index, bool only_size) { + if (Failed() || !data_) + return; + + // Defensively clear the FastSharedBufferReader's cache, as another caller + // may have called SharedBuffer::MergeSegmentsIntoBuffer(). + fast_reader_.ClearCache(); + + // If we couldn't decode the image but we've received all the data, decoding + // has failed. + if ((!DecodeDirectory() || (!only_size && !DecodeAtIndex(index))) && + IsAllDataReceived()) { + SetFailed(); + // If we're done decoding this frame, we don't need the BMPImageReader or + // PNGImageDecoder anymore. (If we failed, these have already been + // cleared.) + } else if ((frame_buffer_cache_.size() > index) && + (frame_buffer_cache_[index].GetStatus() == + ImageFrame::kFrameComplete)) { + bmp_readers_[index].reset(); + png_decoders_[index].reset(); + } +} + +bool ICOImageDecoder::DecodeDirectory() { + // Read and process directory. + if ((decoded_offset_ < kSizeOfDirectory) && !ProcessDirectory()) + return false; + + // Read and process directory entries. + return (decoded_offset_ >= + (kSizeOfDirectory + (dir_entries_count_ * kSizeOfDirEntry))) || + ProcessDirectoryEntries(); +} + +bool ICOImageDecoder::DecodeAtIndex(size_t index) { + SECURITY_DCHECK(index < dir_entries_.size()); + const IconDirectoryEntry& dir_entry = dir_entries_[index]; + const ImageType image_type = ImageTypeAtIndex(index); + if (image_type == kUnknown) + return false; // Not enough data to determine image type yet. + + if (image_type == BMP) { + if (!bmp_readers_[index]) { + bmp_readers_[index] = std::make_unique<BMPImageReader>( + this, dir_entry.image_offset_, 0, true); + bmp_readers_[index]->SetData(data_.get()); + } + // Update the pointer to the buffer as it could change after + // frame_buffer_cache_.resize(). + bmp_readers_[index]->SetBuffer(&frame_buffer_cache_[index]); + frame_size_ = dir_entry.size_; + bool result = bmp_readers_[index]->DecodeBMP(false); + frame_size_ = IntSize(); + return result; + } + + if (!png_decoders_[index]) { + AlphaOption alpha_option = + premultiply_alpha_ ? kAlphaPremultiplied : kAlphaNotPremultiplied; + png_decoders_[index] = std::make_unique<PNGImageDecoder>( + alpha_option, color_behavior_, max_decoded_bytes_, + dir_entry.image_offset_); + SetDataForPNGDecoderAtIndex(index); + } + auto* png_decoder = png_decoders_[index].get(); + if (png_decoder->IsSizeAvailable()) { + // Fail if the size the PNGImageDecoder calculated does not match the size + // in the directory. + if (png_decoder->Size() != dir_entry.size_) + return SetFailed(); + + const auto* frame = png_decoder->DecodeFrameBufferAtIndex(0); + if (frame) + frame_buffer_cache_[index] = *frame; + } + if (png_decoder->Failed()) + return SetFailed(); + return frame_buffer_cache_[index].GetStatus() == ImageFrame::kFrameComplete; +} + +bool ICOImageDecoder::ProcessDirectory() { + // Read directory. + SECURITY_DCHECK(data_); + DCHECK(!decoded_offset_); + if (data_->size() < kSizeOfDirectory) + return false; + const uint16_t file_type = ReadUint16(2); + dir_entries_count_ = ReadUint16(4); + decoded_offset_ = kSizeOfDirectory; + + // See if this is an icon filetype we understand, and make sure we have at + // least one entry in the directory. + if (((file_type != ICON) && (file_type != CURSOR)) || (!dir_entries_count_)) + return SetFailed(); + + file_type_ = static_cast<FileType>(file_type); + return true; +} + +bool ICOImageDecoder::ProcessDirectoryEntries() { + // Read directory entries. + SECURITY_DCHECK(data_); + DCHECK_EQ(decoded_offset_, kSizeOfDirectory); + if ((decoded_offset_ > data_->size()) || + ((data_->size() - decoded_offset_) < + (dir_entries_count_ * kSizeOfDirEntry))) + return false; + + // Enlarge member vectors to hold all the entries. + dir_entries_.resize(dir_entries_count_); + bmp_readers_.resize(dir_entries_count_); + png_decoders_.resize(dir_entries_count_); + + for (IconDirectoryEntries::iterator i(dir_entries_.begin()); + i != dir_entries_.end(); ++i) + *i = ReadDirectoryEntry(); // Updates decoded_offset_. + + // Make sure the specified image offsets are past the end of the directory + // entries. + for (IconDirectoryEntries::iterator i(dir_entries_.begin()); + i != dir_entries_.end(); ++i) { + if (i->image_offset_ < decoded_offset_) + return SetFailed(); + } + + // Arrange frames in decreasing quality order. + std::sort(dir_entries_.begin(), dir_entries_.end(), CompareEntries); + + // The image size is the size of the largest entry. + const IconDirectoryEntry& dir_entry = dir_entries_.front(); + // Technically, this next call shouldn't be able to fail, since the width + // and height here are each <= 256, and |frame_size_| is empty. + return SetSize(dir_entry.size_.Width(), dir_entry.size_.Height()); +} + +ICOImageDecoder::IconDirectoryEntry ICOImageDecoder::ReadDirectoryEntry() { + // Read icon data. + // The following calls to ReadUint8() return a uint8_t, which is appropriate + // because that's the on-disk type of the width and height values. Storing + // them in ints (instead of matching uint8_ts) is so we can record dimensions + // of size 256 (which is what a zero byte really means). + int width = ReadUint8(0); + if (!width) + width = 256; + int height = ReadUint8(1); + if (!height) + height = 256; + IconDirectoryEntry entry; + entry.size_ = IntSize(width, height); + if (file_type_ == CURSOR) { + entry.bit_count_ = 0; + entry.hot_spot_ = IntPoint(ReadUint16(4), ReadUint16(6)); + } else { + entry.bit_count_ = ReadUint16(6); + entry.hot_spot_ = IntPoint(); + } + entry.byte_size_ = ReadUint32(8); + entry.image_offset_ = ReadUint32(12); + + // Some icons don't have a bit depth, only a color count. Convert the + // color count to the minimum necessary bit depth. It doesn't matter if + // this isn't quite what the bitmap info header says later, as we only use + // this value to determine which icon entry is best. + if (!entry.bit_count_) { + int color_count = ReadUint8(2); + if (!color_count) + color_count = 256; // Vague in the spec, needed by real-world icons. + for (--color_count; color_count; color_count >>= 1) + ++entry.bit_count_; + } + + decoded_offset_ += kSizeOfDirEntry; + return entry; +} + +ICOImageDecoder::ImageType ICOImageDecoder::ImageTypeAtIndex(size_t index) { + // Check if this entry is a BMP or a PNG; we need 4 bytes to check the magic + // number. + SECURITY_DCHECK(data_); + SECURITY_DCHECK(index < dir_entries_.size()); + const uint32_t image_offset = dir_entries_[index].image_offset_; + if ((image_offset > data_->size()) || ((data_->size() - image_offset) < 4)) + return kUnknown; + char buffer[4]; + const char* data = fast_reader_.GetConsecutiveData(image_offset, 4, buffer); + return strncmp(data, "\x89PNG", 4) ? BMP : PNG; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/ico/ico_image_decoder.h b/chromium/third_party/blink/renderer/platform/image-decoders/ico/ico_image_decoder.h new file mode 100644 index 00000000000..5524ec50702 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/ico/ico_image_decoder.h @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2008, 2009, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_ICO_ICO_IMAGE_DECODER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_ICO_ICO_IMAGE_DECODER_H_ + +#include <memory> +#include "third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_reader.h" +#include "third_party/blink/renderer/platform/image-decoders/fast_shared_buffer_reader.h" + +namespace blink { + +class PNGImageDecoder; + +// This class decodes the ICO and CUR image formats. +class PLATFORM_EXPORT ICOImageDecoder final : public ImageDecoder { + WTF_MAKE_NONCOPYABLE(ICOImageDecoder); + + public: + ICOImageDecoder(AlphaOption, const ColorBehavior&, size_t max_decoded_bytes); + ~ICOImageDecoder() override; + + // ImageDecoder: + String FilenameExtension() const override { return "ico"; } + void OnSetData(SegmentReader*) override; + IntSize Size() const override; + IntSize FrameSizeAtIndex(size_t) const override; + bool SetSize(unsigned width, unsigned height) override; + bool FrameIsReceivedAtIndex(size_t) const override; + // CAUTION: SetFailed() deletes all readers and decoders. Be careful to + // avoid accessing deleted memory, especially when calling this from + // inside BMPImageReader! + bool SetFailed() override; + bool HotSpot(IntPoint&) const override; + + private: + enum ImageType { + kUnknown, + BMP, + PNG, + }; + + enum FileType { + ICON = 1, + CURSOR = 2, + }; + + struct IconDirectoryEntry { + DISALLOW_NEW_EXCEPT_PLACEMENT_NEW(); + IntSize size_; + uint16_t bit_count_; + IntPoint hot_spot_; + uint32_t image_offset_; + uint32_t byte_size_; + }; + + // Returns true if |a| is a preferable icon entry to |b|. + // Larger sizes, or greater bitdepths at the same size, are preferable. + static bool CompareEntries(const IconDirectoryEntry& a, + const IconDirectoryEntry& b); + + // ImageDecoder: + void DecodeSize() override { Decode(0, true); } + size_t DecodeFrameCount() override; + void Decode(size_t index) override { Decode(index, false); } + + // TODO (scroggo): These functions are identical to functions in + // BMPImageReader. Share code? + inline uint8_t ReadUint8(size_t offset) const { + return fast_reader_.GetOneByte(decoded_offset_ + offset); + } + + inline uint16_t ReadUint16(int offset) const { + char buffer[2]; + const char* data = + fast_reader_.GetConsecutiveData(decoded_offset_ + offset, 2, buffer); + return BMPImageReader::ReadUint16(data); + } + + inline uint32_t ReadUint32(int offset) const { + char buffer[4]; + const char* data = + fast_reader_.GetConsecutiveData(decoded_offset_ + offset, 4, buffer); + return BMPImageReader::ReadUint32(data); + } + + // If the desired PNGImageDecoder exists, gives it the appropriate data. + void SetDataForPNGDecoderAtIndex(size_t); + + // Decodes the entry at |index|. If |only_size| is true, stops decoding + // after calculating the image size. If decoding fails but there is no + // more data coming, sets the "decode failure" flag. + void Decode(size_t index, bool only_size); + + // Decodes the directory and directory entries at the beginning of the + // data, and initializes members. Returns true if all decoding + // succeeded. Once this returns true, all entries' sizes are known. + bool DecodeDirectory(); + + // Decodes the specified entry. + bool DecodeAtIndex(size_t); + + // Processes the ICONDIR at the beginning of the data. Returns true if + // the directory could be decoded. + bool ProcessDirectory(); + + // Processes the ICONDIRENTRY records after the directory. Keeps the + // "best" entry as the one we'll decode. Returns true if the entries + // could be decoded. + bool ProcessDirectoryEntries(); + + // Stores the hot-spot for |index| in |hot_spot| and returns true, + // or returns false if there is none. + bool HotSpotAtIndex(size_t index, IntPoint& hot_spot) const; + + // Reads and returns a directory entry from the current offset into + // |data|. + IconDirectoryEntry ReadDirectoryEntry(); + + // Determines whether the desired entry is a BMP or PNG. Returns true + // if the type could be determined. + ImageType ImageTypeAtIndex(size_t); + + FastSharedBufferReader fast_reader_; + + // An index into |data_| representing how much we've already decoded. + // Note that this only tracks data _this_ class decodes; once the + // BMPImageReader takes over this will not be updated further. + size_t decoded_offset_; + + // Which type of file (ICO/CUR) this is. + FileType file_type_; + + // The headers for the ICO. + typedef Vector<IconDirectoryEntry> IconDirectoryEntries; + IconDirectoryEntries dir_entries_; + + // Count of directory entries is parsed from header before initializing + // dir_entries_. dir_entries_ is populated only when full header + // information including directory entries is available. + size_t dir_entries_count_; + + // The image decoders for the various frames. + typedef Vector<std::unique_ptr<BMPImageReader>> BMPReaders; + BMPReaders bmp_readers_; + typedef Vector<std::unique_ptr<PNGImageDecoder>> PNGDecoders; + PNGDecoders png_decoders_; + + // Valid only while a BMPImageReader is decoding, this holds the size + // for the particular entry being decoded. + IntSize frame_size_; + + // Used to pass on to an internally created PNG decoder. + const ColorBehavior color_behavior_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/ico/ico_image_decoder_test.cc b/chromium/third_party/blink/renderer/platform/image-decoders/ico/ico_image_decoder_test.cc new file mode 100644 index 00000000000..d17bcef08f3 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/ico/ico_image_decoder_test.cc @@ -0,0 +1,110 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/platform/image-decoders/ico/ico_image_decoder.h" + +#include <memory> +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/platform/image-decoders/image_decoder_test_helpers.h" + +namespace blink { + +namespace { + +std::unique_ptr<ImageDecoder> CreateICODecoder() { + return std::make_unique<ICOImageDecoder>( + ImageDecoder::kAlphaNotPremultiplied, ColorBehavior::TransformToSRGB(), + ImageDecoder::kNoDecodedImageByteLimit); +} +} + +TEST(ICOImageDecoderTests, trunctedIco) { + const Vector<char> data = + ReadFile("/images/resources/png-in-ico.ico")->Copy(); + ASSERT_FALSE(data.IsEmpty()); + + scoped_refptr<SharedBuffer> truncated_data = + SharedBuffer::Create(data.data(), data.size() / 2); + auto decoder = CreateICODecoder(); + + decoder->SetData(truncated_data.get(), false); + decoder->DecodeFrameBufferAtIndex(0); + EXPECT_FALSE(decoder->Failed()); + + decoder->SetData(truncated_data.get(), true); + decoder->DecodeFrameBufferAtIndex(0); + EXPECT_TRUE(decoder->Failed()); +} + +TEST(ICOImageDecoderTests, errorInPngInIco) { + const Vector<char> data = + ReadFile("/images/resources/png-in-ico.ico")->Copy(); + ASSERT_FALSE(data.IsEmpty()); + + // Modify the file to have a broken CRC in IHDR. + constexpr size_t kCrcOffset = 22 + 29; + constexpr size_t kCrcSize = 4; + scoped_refptr<SharedBuffer> modified_data = + SharedBuffer::Create(data.data(), kCrcOffset); + Vector<char> bad_crc(kCrcSize, 0); + modified_data->Append(bad_crc); + modified_data->Append(data.data() + kCrcOffset + kCrcSize, + data.size() - kCrcOffset - kCrcSize); + + auto decoder = CreateICODecoder(); + decoder->SetData(modified_data.get(), true); + + // ICOImageDecoder reports the frame count based on whether enough data has + // been received according to the icon directory. So even though the + // embedded PNG is broken, there is enough data to include it in the frame + // count. + EXPECT_EQ(1u, decoder->FrameCount()); + + decoder->DecodeFrameBufferAtIndex(0); + EXPECT_TRUE(decoder->Failed()); +} + +TEST(ICOImageDecoderTests, parseAndDecodeByteByByte) { + TestByteByByteDecode(&CreateICODecoder, "/images/resources/png-in-ico.ico", + 1u, kAnimationNone); + TestByteByByteDecode(&CreateICODecoder, "/images/resources/2entries.ico", 2u, + kAnimationNone); + TestByteByByteDecode(&CreateICODecoder, + "/images/resources/greenbox-3frames.cur", 3u, + kAnimationNone); + TestByteByByteDecode(&CreateICODecoder, + "/images/resources/icon-without-and-bitmap.ico", 1u, + kAnimationNone); + TestByteByByteDecode(&CreateICODecoder, "/images/resources/1bit.ico", 1u, + kAnimationNone); + TestByteByByteDecode(&CreateICODecoder, "/images/resources/bug653075.ico", 2u, + kAnimationNone); +} + +TEST(ICOImageDecoderTests, NullData) { + static constexpr size_t kSizeOfBadBlock = 6 + 16 + 1; + + scoped_refptr<SharedBuffer> ico_file_data = + ReadFile("/images/resources/png-in-ico.ico"); + ASSERT_FALSE(ico_file_data->IsEmpty()); + ASSERT_LT(kSizeOfBadBlock, ico_file_data->size()); + + scoped_refptr<SharedBuffer> truncated_data = + SharedBuffer::Create(ico_file_data->Data(), kSizeOfBadBlock); + auto decoder = CreateICODecoder(); + + decoder->SetData(truncated_data.get(), false); + decoder->SetMemoryAllocator(nullptr); + EXPECT_FALSE(decoder->Failed()); + + auto* frame = decoder->DecodeFrameBufferAtIndex(0); + EXPECT_EQ(nullptr, frame); + + decoder->SetData(scoped_refptr<SegmentReader>(nullptr), false); + decoder->ClearCacheExceptFrame(0); + decoder->SetMemoryAllocator(nullptr); + EXPECT_FALSE(decoder->Failed()); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/image_animation.h b/chromium/third_party/blink/renderer/platform/image-decoders/image_animation.h new file mode 100644 index 00000000000..4a222d98268 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/image_animation.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2015 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_IMAGE_ANIMATION_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_IMAGE_ANIMATION_H_ + +#include "cc/paint/image_animation_count.h" + +namespace blink { +using cc::kAnimationLoopOnce; +using cc::kAnimationLoopInfinite; +using cc::kAnimationNone; +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/image_decoder.cc b/chromium/third_party/blink/renderer/platform/image-decoders/image_decoder.cc new file mode 100644 index 00000000000..c3da2e76b9f --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/image_decoder.cc @@ -0,0 +1,564 @@ +/* + * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#include "third_party/blink/renderer/platform/image-decoders/image_decoder.h" + +#include <memory> +#include "third_party/blink/renderer/platform/graphics/bitmap_image_metrics.h" +#include "third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder.h" +#include "third_party/blink/renderer/platform/image-decoders/fast_shared_buffer_reader.h" +#include "third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder.h" +#include "third_party/blink/renderer/platform/image-decoders/ico/ico_image_decoder.h" +#include "third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder.h" +#include "third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.h" +#include "third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.h" +#include "third_party/blink/renderer/platform/instrumentation/platform_instrumentation.h" + +namespace blink { + +const size_t ImageDecoder::kNoDecodedImageByteLimit; + +inline bool MatchesJPEGSignature(const char* contents) { + return !memcmp(contents, "\xFF\xD8\xFF", 3); +} + +inline bool MatchesPNGSignature(const char* contents) { + return !memcmp(contents, "\x89PNG\r\n\x1A\n", 8); +} + +inline bool MatchesGIFSignature(const char* contents) { + return !memcmp(contents, "GIF87a", 6) || !memcmp(contents, "GIF89a", 6); +} + +inline bool MatchesWebPSignature(const char* contents) { + return !memcmp(contents, "RIFF", 4) && !memcmp(contents + 8, "WEBPVP", 6); +} + +inline bool MatchesICOSignature(const char* contents) { + return !memcmp(contents, "\x00\x00\x01\x00", 4); +} + +inline bool MatchesCURSignature(const char* contents) { + return !memcmp(contents, "\x00\x00\x02\x00", 4); +} + +inline bool MatchesBMPSignature(const char* contents) { + return !memcmp(contents, "BM", 2); +} + +static constexpr size_t kLongestSignatureLength = sizeof("RIFF????WEBPVP") - 1; + +std::unique_ptr<ImageDecoder> ImageDecoder::Create( + scoped_refptr<SegmentReader> data, + bool data_complete, + AlphaOption alpha_option, + const ColorBehavior& color_behavior, + const SkISize& desired_size) { + // At least kLongestSignatureLength bytes are needed to sniff the signature. + if (data->size() < kLongestSignatureLength) + return nullptr; + + size_t max_decoded_bytes = Platform::Current() + ? Platform::Current()->MaxDecodedImageBytes() + : kNoDecodedImageByteLimit; + if (!desired_size.isEmpty()) { + static const size_t kBytesPerPixels = 4; + size_t requested_decoded_bytes = + kBytesPerPixels * desired_size.width() * desired_size.height(); + max_decoded_bytes = std::min(requested_decoded_bytes, max_decoded_bytes); + } + + // Access the first kLongestSignatureLength chars to sniff the signature. + // (note: FastSharedBufferReader only makes a copy if the bytes are segmented) + char buffer[kLongestSignatureLength]; + const FastSharedBufferReader fast_reader(data); + const char* contents = + fast_reader.GetConsecutiveData(0, kLongestSignatureLength, buffer); + + std::unique_ptr<ImageDecoder> decoder; + if (MatchesJPEGSignature(contents)) { + decoder.reset( + new JPEGImageDecoder(alpha_option, color_behavior, max_decoded_bytes)); + } else if (MatchesPNGSignature(contents)) { + decoder.reset( + new PNGImageDecoder(alpha_option, color_behavior, max_decoded_bytes)); + } else if (MatchesGIFSignature(contents)) { + decoder.reset( + new GIFImageDecoder(alpha_option, color_behavior, max_decoded_bytes)); + } else if (MatchesWebPSignature(contents)) { + decoder.reset( + new WEBPImageDecoder(alpha_option, color_behavior, max_decoded_bytes)); + } else if (MatchesICOSignature(contents) || MatchesCURSignature(contents)) { + decoder.reset( + new ICOImageDecoder(alpha_option, color_behavior, max_decoded_bytes)); + } else if (MatchesBMPSignature(contents)) { + decoder.reset( + new BMPImageDecoder(alpha_option, color_behavior, max_decoded_bytes)); + } + + if (decoder) + decoder->SetData(std::move(data), data_complete); + + return decoder; +} + +bool ImageDecoder::HasSufficientDataToSniffImageType(const SharedBuffer& data) { + return data.size() >= kLongestSignatureLength; +} + +size_t ImageDecoder::FrameCount() { + const size_t old_size = frame_buffer_cache_.size(); + const size_t new_size = DecodeFrameCount(); + if (old_size != new_size) { + frame_buffer_cache_.resize(new_size); + for (size_t i = old_size; i < new_size; ++i) { + frame_buffer_cache_[i].SetPremultiplyAlpha(premultiply_alpha_); + InitializeNewFrame(i); + } + } + return new_size; +} + +ImageFrame* ImageDecoder::DecodeFrameBufferAtIndex(size_t index) { + if (index >= FrameCount()) + return nullptr; + ImageFrame* frame = &frame_buffer_cache_[index]; + if (frame->GetStatus() != ImageFrame::kFrameComplete) { + PlatformInstrumentation::WillDecodeImage(FilenameExtension()); + Decode(index); + PlatformInstrumentation::DidDecodeImage(); + } + + if (!has_histogrammed_color_space_) { + BitmapImageMetrics::CountImageGammaAndGamut(embedded_color_space_.get()); + has_histogrammed_color_space_ = true; + } + + frame->NotifyBitmapIfPixelsChanged(); + return frame; +} + +bool ImageDecoder::FrameHasAlphaAtIndex(size_t index) const { + return !FrameIsReceivedAtIndex(index) || + frame_buffer_cache_[index].HasAlpha(); +} + +bool ImageDecoder::FrameIsReceivedAtIndex(size_t index) const { + // Animated images override this method to return the status based on the data + // received for the queried frame. + return IsAllDataReceived(); +} + +bool ImageDecoder::FrameIsDecodedAtIndex(size_t index) const { + return index < frame_buffer_cache_.size() && + frame_buffer_cache_[index].GetStatus() == ImageFrame::kFrameComplete; +} + +size_t ImageDecoder::FrameBytesAtIndex(size_t index) const { + if (index >= frame_buffer_cache_.size() || + frame_buffer_cache_[index].GetStatus() == ImageFrame::kFrameEmpty) + return 0; + + struct ImageSize { + explicit ImageSize(IntSize size) { + area = static_cast<uint64_t>(size.Width()) * size.Height(); + } + + uint64_t area; + }; + + return ImageSize(FrameSizeAtIndex(index)).area * + sizeof(ImageFrame::PixelData); +} + +size_t ImageDecoder::ClearCacheExceptFrame(size_t clear_except_frame) { + // Don't clear if there are no frames or only one frame. + if (frame_buffer_cache_.size() <= 1) + return 0; + + // We expect that after this call, we'll be asked to decode frames after this + // one. So we want to avoid clearing frames such that those requests would + // force re-decoding from the beginning of the image. There are two cases in + // which preserving |clear_except_frame| is not enough to avoid that: + // + // 1. |clear_except_frame| is not yet sufficiently decoded to decode + // subsequent frames. We need the previous frame to sufficiently decode + // this frame. + // 2. The disposal method of |clear_except_frame| is DisposeOverwritePrevious. + // In that case, we need to keep the required previous frame in the cache + // to prevent re-decoding that frame when |clear_except_frame| is disposed. + // + // If either 1 or 2 is true, store the required previous frame in + // |clear_except_frame2| so it won't be cleared. + size_t clear_except_frame2 = kNotFound; + if (clear_except_frame < frame_buffer_cache_.size()) { + const ImageFrame& frame = frame_buffer_cache_[clear_except_frame]; + if (!FrameStatusSufficientForSuccessors(clear_except_frame) || + frame.GetDisposalMethod() == ImageFrame::kDisposeOverwritePrevious) + clear_except_frame2 = frame.RequiredPreviousFrameIndex(); + } + + // Now |clear_except_frame2| indicates the frame that |clear_except_frame| + // depends on, as described above. But if decoding is skipping forward past + // intermediate frames, this frame may be insufficiently decoded. So we need + // to keep traversing back through the required previous frames until we find + // the nearest ancestor that is sufficiently decoded. Preserving that will + // minimize the amount of future decoding needed. + while (clear_except_frame2 < frame_buffer_cache_.size() && + !FrameStatusSufficientForSuccessors(clear_except_frame2)) { + clear_except_frame2 = + frame_buffer_cache_[clear_except_frame2].RequiredPreviousFrameIndex(); + } + + return ClearCacheExceptTwoFrames(clear_except_frame, clear_except_frame2); +} + +size_t ImageDecoder::ClearCacheExceptTwoFrames(size_t clear_except_frame1, + size_t clear_except_frame2) { + size_t frame_bytes_cleared = 0; + for (size_t i = 0; i < frame_buffer_cache_.size(); ++i) { + if (frame_buffer_cache_[i].GetStatus() != ImageFrame::kFrameEmpty && + i != clear_except_frame1 && i != clear_except_frame2) { + frame_bytes_cleared += FrameBytesAtIndex(i); + ClearFrameBuffer(i); + } + } + return frame_bytes_cleared; +} + +void ImageDecoder::ClearFrameBuffer(size_t frame_index) { + frame_buffer_cache_[frame_index].ClearPixelData(); +} + +Vector<size_t> ImageDecoder::FindFramesToDecode(size_t index) const { + DCHECK(index < frame_buffer_cache_.size()); + + Vector<size_t> frames_to_decode; + do { + frames_to_decode.push_back(index); + index = frame_buffer_cache_[index].RequiredPreviousFrameIndex(); + } while (index != kNotFound && frame_buffer_cache_[index].GetStatus() != + ImageFrame::kFrameComplete); + return frames_to_decode; +} + +bool ImageDecoder::PostDecodeProcessing(size_t index) { + DCHECK(index < frame_buffer_cache_.size()); + + if (frame_buffer_cache_[index].GetStatus() != ImageFrame::kFrameComplete) + return false; + + if (purge_aggressively_) + ClearCacheExceptFrame(index); + + return true; +} + +void ImageDecoder::CorrectAlphaWhenFrameBufferSawNoAlpha(size_t index) { + DCHECK(index < frame_buffer_cache_.size()); + ImageFrame& buffer = frame_buffer_cache_[index]; + + // When this frame spans the entire image rect we can SetHasAlpha to false, + // since there are logically no transparent pixels outside of the frame rect. + if (buffer.OriginalFrameRect().Contains(IntRect(IntPoint(), Size()))) { + buffer.SetHasAlpha(false); + buffer.SetRequiredPreviousFrameIndex(kNotFound); + } else if (buffer.RequiredPreviousFrameIndex() != kNotFound) { + // When the frame rect does not span the entire image rect, and it does + // *not* have a required previous frame, the pixels outside of the frame + // rect will be fully transparent, so we shoudn't SetHasAlpha to false. + // + // It is a tricky case when the frame does have a required previous frame. + // The frame does not have alpha only if everywhere outside its rect + // doesn't have alpha. To know whether this is true, we check the start + // state of the frame -- if it doesn't have alpha, we're safe. + // + // We first check that the required previous frame does not have + // DisposeOverWritePrevious as its disposal method - this should never + // happen, since the required frame should in that case be the required + // frame of this frame's required frame. + // + // If |prev_buffer| is DisposeNotSpecified or DisposeKeep, |buffer| has no + // alpha if |prev_buffer| had no alpha. Since InitFrameBuffer() already + // copied the alpha state, there's nothing to do here. + // + // The only remaining case is a DisposeOverwriteBgcolor frame. If + // it had no alpha, and its rect is contained in the current frame's + // rect, we know the current frame has no alpha. + // + // For DisposeNotSpecified, DisposeKeep and DisposeOverwriteBgcolor there + // is one situation that is not taken into account - when |prev_buffer| + // *does* have alpha, but only in the frame rect of |buffer|, we can still + // say that this frame has no alpha. However, to determine this, we + // potentially need to analyze all image pixels of |prev_buffer|, which is + // too computationally expensive. + const ImageFrame* prev_buffer = + &frame_buffer_cache_[buffer.RequiredPreviousFrameIndex()]; + DCHECK(prev_buffer->GetDisposalMethod() != + ImageFrame::kDisposeOverwritePrevious); + + if ((prev_buffer->GetDisposalMethod() == + ImageFrame::kDisposeOverwriteBgcolor) && + !prev_buffer->HasAlpha() && + buffer.OriginalFrameRect().Contains(prev_buffer->OriginalFrameRect())) + buffer.SetHasAlpha(false); + } +} + +bool ImageDecoder::InitFrameBuffer(size_t frame_index) { + DCHECK(frame_index < frame_buffer_cache_.size()); + + ImageFrame* const buffer = &frame_buffer_cache_[frame_index]; + + // If the frame is already initialized, return true. + if (buffer->GetStatus() != ImageFrame::kFrameEmpty) + return true; + + size_t required_previous_frame_index = buffer->RequiredPreviousFrameIndex(); + if (required_previous_frame_index == kNotFound) { + // This frame doesn't rely on any previous data. + if (!buffer->AllocatePixelData(Size().Width(), Size().Height(), + ColorSpaceForSkImages())) { + return false; + } + buffer->ZeroFillPixelData(); + } else { + ImageFrame* const prev_buffer = + &frame_buffer_cache_[required_previous_frame_index]; + DCHECK(prev_buffer->GetStatus() == ImageFrame::kFrameComplete); + + // We try to reuse |prev_buffer| as starting state to avoid copying. + // If CanReusePreviousFrameBuffer returns false, we must copy the data since + // |prev_buffer| is necessary to decode this or later frames. In that case, + // copy the data instead. + if ((!CanReusePreviousFrameBuffer(frame_index) || + !buffer->TakeBitmapDataIfWritable(prev_buffer)) && + !buffer->CopyBitmapData(*prev_buffer)) + return false; + + if (prev_buffer->GetDisposalMethod() == + ImageFrame::kDisposeOverwriteBgcolor) { + // We want to clear the previous frame to transparent, without + // affecting pixels in the image outside of the frame. + const IntRect& prev_rect = prev_buffer->OriginalFrameRect(); + DCHECK(!prev_rect.Contains(IntRect(IntPoint(), Size()))); + buffer->ZeroFillFrameRect(prev_rect); + } + } + + OnInitFrameBuffer(frame_index); + + // Update our status to be partially complete. + buffer->SetStatus(ImageFrame::kFramePartial); + + return true; +} + +void ImageDecoder::UpdateAggressivePurging(size_t index) { + if (purge_aggressively_) + return; + + // We don't want to cache so much that we cause a memory issue. + // + // If we used a LRU cache we would fill it and then on next animation loop + // we would need to decode all the frames again -- the LRU would give no + // benefit and would consume more memory. + // So instead, simply purge unused frames if caching all of the frames of + // the image would use more memory than the image decoder is allowed + // (|max_decoded_bytes|) or would overflow 32 bits.. + // + // As we decode we will learn the total number of frames, and thus total + // possible image memory used. + + const uint64_t frame_memory_usage = + DecodedSize().Area() * 4; // 4 bytes per pixel + + // This condition never fails in the current code. Our existing image decoders + // parse for the image size and SetFailed() if that size overflows + DCHECK_EQ(frame_memory_usage / 4, DecodedSize().Area()); + + const uint64_t total_memory_usage = frame_memory_usage * index; + if (total_memory_usage / frame_memory_usage != index) { // overflow occurred + purge_aggressively_ = true; + return; + } + + if (total_memory_usage > max_decoded_bytes_) { + purge_aggressively_ = true; + } +} + +size_t ImageDecoder::FindRequiredPreviousFrame(size_t frame_index, + bool frame_rect_is_opaque) { + DCHECK_LT(frame_index, frame_buffer_cache_.size()); + if (!frame_index) { + // The first frame doesn't rely on any previous data. + return kNotFound; + } + + const ImageFrame* curr_buffer = &frame_buffer_cache_[frame_index]; + if ((frame_rect_is_opaque || + curr_buffer->GetAlphaBlendSource() == ImageFrame::kBlendAtopBgcolor) && + curr_buffer->OriginalFrameRect().Contains(IntRect(IntPoint(), Size()))) + return kNotFound; + + // The starting state for this frame depends on the previous frame's + // disposal method. + size_t prev_frame = frame_index - 1; + const ImageFrame* prev_buffer = &frame_buffer_cache_[prev_frame]; + + // Frames that use the DisposeOverwritePrevious method are effectively + // no-ops in terms of changing the starting state of a frame compared to + // the starting state of the previous frame, so skip over them. + while (prev_buffer->GetDisposalMethod() == + ImageFrame::kDisposeOverwritePrevious) { + if (prev_frame == 0) { + return kNotFound; + } + prev_frame--; + prev_buffer = &frame_buffer_cache_[prev_frame]; + } + + switch (prev_buffer->GetDisposalMethod()) { + case ImageFrame::kDisposeNotSpecified: + case ImageFrame::kDisposeKeep: + // |prev_frame| will be used as the starting state for this frame. + // FIXME: Be even smarter by checking the frame sizes and/or + // alpha-containing regions. + return prev_frame; + case ImageFrame::kDisposeOverwriteBgcolor: + // If the previous frame fills the whole image, then the current frame + // can be decoded alone. Likewise, if the previous frame could be + // decoded without reference to any prior frame, the starting state for + // this frame is a blank frame, so it can again be decoded alone. + // Otherwise, the previous frame contributes to this frame. + return (prev_buffer->OriginalFrameRect().Contains( + IntRect(IntPoint(), Size())) || + (prev_buffer->RequiredPreviousFrameIndex() == kNotFound)) + ? kNotFound + : prev_frame; + case ImageFrame::kDisposeOverwritePrevious: + default: + NOTREACHED(); + return kNotFound; + } +} + +ImagePlanes::ImagePlanes() { + for (int i = 0; i < 3; ++i) { + planes_[i] = nullptr; + row_bytes_[i] = 0; + } +} + +ImagePlanes::ImagePlanes(void* planes[3], const size_t row_bytes[3]) { + for (int i = 0; i < 3; ++i) { + planes_[i] = planes[i]; + row_bytes_[i] = row_bytes[i]; + } +} + +void* ImagePlanes::Plane(int i) { + DCHECK_GE(i, 0); + DCHECK_LT(i, 3); + return planes_[i]; +} + +size_t ImagePlanes::RowBytes(int i) const { + DCHECK_GE(i, 0); + DCHECK_LT(i, 3); + return row_bytes_[i]; +} + +void ImageDecoder::SetEmbeddedColorSpace(sk_sp<SkColorSpace> color_space) { + DCHECK(!IgnoresColorSpace()); + DCHECK(!has_histogrammed_color_space_); + + embedded_color_space_ = color_space; + source_to_target_color_transform_needs_update_ = true; +} + +SkColorSpaceXform* ImageDecoder::ColorTransform() { + if (!source_to_target_color_transform_needs_update_) + return source_to_target_color_transform_.get(); + source_to_target_color_transform_needs_update_ = false; + source_to_target_color_transform_ = nullptr; + + if (color_behavior_.IsIgnore()) { + return nullptr; + } + + sk_sp<SkColorSpace> src_color_space = nullptr; + sk_sp<SkColorSpace> dst_color_space = nullptr; + if (color_behavior_.IsTransformToSRGB()) { + if (!embedded_color_space_) { + return nullptr; + } + src_color_space = embedded_color_space_; + dst_color_space = SkColorSpace::MakeSRGB(); + } else { + DCHECK(color_behavior_.IsTag()); + src_color_space = embedded_color_space_; + if (!src_color_space) { + src_color_space = SkColorSpace::MakeSRGB(); + } + + // This will most likely be equal to the |src_color_space|. + // In that case, we skip the xform when we check for equality below. + dst_color_space = ColorSpaceForSkImages(); + } + + if (SkColorSpace::Equals(src_color_space.get(), dst_color_space.get())) { + return nullptr; + } + + source_to_target_color_transform_ = + SkColorSpaceXform::New(src_color_space.get(), dst_color_space.get()); + return source_to_target_color_transform_.get(); +} + +sk_sp<SkColorSpace> ImageDecoder::ColorSpaceForSkImages() const { + if (!color_behavior_.IsTag()) + return nullptr; + + if (embedded_color_space_) { + SkColorSpaceTransferFn fn; + if (embedded_color_space_->isNumericalTransferFn(&fn)) { + // The embedded color space is supported by Skia. + return embedded_color_space_; + } + + // In the rare case that the embedded color space is unsupported, xform at + // decode time. + SkMatrix44 to_xyz_d50(SkMatrix44::kUninitialized_Constructor); + if (embedded_color_space_->toXYZD50(&to_xyz_d50)) { + // Preserve the gamut, but convert to a standard transfer function. + return SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, + to_xyz_d50); + } + + // For color spaces without an identifiable gamut, just fall through to + // sRGB. + } + + return SkColorSpace::MakeSRGB(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/image_decoder.h b/chromium/third_party/blink/renderer/platform/image-decoders/image_decoder.h new file mode 100644 index 00000000000..bba85871cdf --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/image_decoder.h @@ -0,0 +1,437 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_IMAGE_DECODER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_IMAGE_DECODER_H_ + +#include <memory> +#include "base/memory/scoped_refptr.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/renderer/platform/graphics/color_behavior.h" +#include "third_party/blink/renderer/platform/graphics/image_orientation.h" +#include "third_party/blink/renderer/platform/image-decoders/image_animation.h" +#include "third_party/blink/renderer/platform/image-decoders/image_frame.h" +#include "third_party/blink/renderer/platform/image-decoders/segment_reader.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/shared_buffer.h" +#include "third_party/blink/renderer/platform/wtf/assertions.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" +#include "third_party/blink/renderer/platform/wtf/time.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" +#include "third_party/skia/include/core/SkColorSpaceXform.h" + +namespace blink { + +#if SK_B32_SHIFT +inline SkColorSpaceXform::ColorFormat XformColorFormat() { + return SkColorSpaceXform::kRGBA_8888_ColorFormat; +} +#else +inline SkColorSpaceXform::ColorFormat XformColorFormat() { + return SkColorSpaceXform::kBGRA_8888_ColorFormat; +} +#endif + +// ImagePlanes can be used to decode color components into provided buffers +// instead of using an ImageFrame. +class PLATFORM_EXPORT ImagePlanes final { + USING_FAST_MALLOC(ImagePlanes); + WTF_MAKE_NONCOPYABLE(ImagePlanes); + + public: + ImagePlanes(); + ImagePlanes(void* planes[3], const size_t row_bytes[3]); + + void* Plane(int); + size_t RowBytes(int) const; + + private: + void* planes_[3]; + size_t row_bytes_[3]; +}; + +// ImageDecoder is a base for all format-specific decoders +// (e.g. JPEGImageDecoder). This base manages the ImageFrame cache. +// +class PLATFORM_EXPORT ImageDecoder { + WTF_MAKE_NONCOPYABLE(ImageDecoder); + USING_FAST_MALLOC(ImageDecoder); + + public: + static const size_t kNoDecodedImageByteLimit = + Platform::kNoDecodedImageByteLimit; + + enum AlphaOption { kAlphaPremultiplied, kAlphaNotPremultiplied }; + + virtual ~ImageDecoder() = default; + + // Returns a caller-owned decoder of the appropriate type. Returns nullptr if + // we can't sniff a supported type from the provided data (possibly + // because there isn't enough data yet). + // Sets |max_decoded_bytes_| to Platform::MaxImageDecodedBytes(). + static std::unique_ptr<ImageDecoder> Create( + scoped_refptr<SegmentReader> data, + bool data_complete, + AlphaOption, + const ColorBehavior&, + const SkISize& desired_size = SkISize::MakeEmpty()); + static std::unique_ptr<ImageDecoder> Create( + scoped_refptr<SharedBuffer> data, + bool data_complete, + AlphaOption alpha_option, + const ColorBehavior& color_behavior, + const SkISize& desired_size = SkISize::MakeEmpty()) { + return Create(SegmentReader::CreateFromSharedBuffer(std::move(data)), + data_complete, alpha_option, color_behavior, desired_size); + } + + virtual String FilenameExtension() const = 0; + + bool IsAllDataReceived() const { return is_all_data_received_; } + + // Returns true if the buffer holds enough data to instantiate a decoder. + // This is useful for callers to determine whether a decoder instantiation + // failure is due to insufficient or bad data. + static bool HasSufficientDataToSniffImageType(const SharedBuffer&); + + void SetData(scoped_refptr<SegmentReader> data, bool all_data_received) { + if (failed_) + return; + data_ = std::move(data); + is_all_data_received_ = all_data_received; + OnSetData(data_.get()); + } + + void SetData(scoped_refptr<SharedBuffer> data, bool all_data_received) { + SetData(SegmentReader::CreateFromSharedBuffer(std::move(data)), + all_data_received); + } + + virtual void OnSetData(SegmentReader* data) {} + + bool IsSizeAvailable() { + if (failed_) + return false; + if (!size_available_) + DecodeSize(); + return IsDecodedSizeAvailable(); + } + + bool IsDecodedSizeAvailable() const { return !failed_ && size_available_; } + + virtual IntSize Size() const { return size_; } + virtual std::vector<SkISize> GetSupportedDecodeSizes() const { return {}; }; + + // Decoders which downsample images should override this method to + // return the actual decoded size. + virtual IntSize DecodedSize() const { return Size(); } + + // Image decoders that support YUV decoding must override this to + // provide the size of each component. + virtual IntSize DecodedYUVSize(int component) const { + NOTREACHED(); + return IntSize(); + } + + // Image decoders that support YUV decoding must override this to + // return the width of each row of the memory allocation. + virtual size_t DecodedYUVWidthBytes(int component) const { + NOTREACHED(); + return 0; + } + + // This will only differ from size() for ICO (where each frame is a + // different icon) or other formats where different frames are different + // sizes. This does NOT differ from size() for GIF or WebP, since + // decoding GIF or WebP composites any smaller frames against previous + // frames to create full-size frames. + virtual IntSize FrameSizeAtIndex(size_t) const { return Size(); } + + // Returns whether the size is legal (i.e. not going to result in + // overflow elsewhere). If not, marks decoding as failed. + virtual bool SetSize(unsigned width, unsigned height) { + if (SizeCalculationMayOverflow(width, height)) + return SetFailed(); + + size_ = IntSize(width, height); + size_available_ = true; + return true; + } + + // Calls DecodeFrameCount() to get the frame count (if possible), without + // decoding the individual frames. Resizes |frame_buffer_cache_| to the + // correct size and returns its size. + size_t FrameCount(); + + virtual int RepetitionCount() const { return kAnimationNone; } + + // Decodes as much of the requested frame as possible, and returns an + // ImageDecoder-owned pointer. + ImageFrame* DecodeFrameBufferAtIndex(size_t); + + // Whether the requested frame has alpha. + virtual bool FrameHasAlphaAtIndex(size_t) const; + + // Whether or not the frame is fully received. + virtual bool FrameIsReceivedAtIndex(size_t) const; + + // Returns true if a cached complete decode is available. + bool FrameIsDecodedAtIndex(size_t) const; + + // Duration for displaying a frame. This method is only used by animated + // images. + virtual TimeDelta FrameDurationAtIndex(size_t) const { return TimeDelta(); } + + // Number of bytes in the decoded frame. Returns 0 if the decoder doesn't + // have this frame cached (either because it hasn't been decoded, or because + // it has been cleared). + virtual size_t FrameBytesAtIndex(size_t) const; + + ImageOrientation Orientation() const { return orientation_; } + + bool IgnoresColorSpace() const { return color_behavior_.IsIgnore(); } + const ColorBehavior& GetColorBehavior() const { return color_behavior_; } + + // This returns the color space that will be included in the SkImageInfo of + // SkImages created from this decoder. This will be nullptr unless the + // decoder was created with the option ColorSpaceTagged. + sk_sp<SkColorSpace> ColorSpaceForSkImages() const; + + // This returns whether or not the image included a not-ignored embedded + // color space. This is independent of whether or not that space's transform + // has been baked into the pixel values. + bool HasEmbeddedColorSpace() const { return embedded_color_space_.get(); } + + void SetEmbeddedColorSpace(sk_sp<SkColorSpace> src_space); + + // Transformation from embedded color space to target color space. + SkColorSpaceXform* ColorTransform(); + + AlphaOption GetAlphaOption() const { + return premultiply_alpha_ ? kAlphaPremultiplied : kAlphaNotPremultiplied; + } + + // Sets the "decode failure" flag. For caller convenience (since so + // many callers want to return false after calling this), returns false + // to enable easy tailcalling. Subclasses may override this to also + // clean up any local data. + virtual bool SetFailed() { + failed_ = true; + return false; + } + + bool Failed() const { return failed_; } + + // Clears decoded pixel data from all frames except the provided frame. If + // subsequent frames depend on this frame's required previous frame, then that + // frame is also kept in cache to prevent re-decoding from the beginning. + // Callers may pass WTF::kNotFound to clear all frames. + // Note: If |frame_buffer_cache_| contains only one frame, it won't be + // cleared. Returns the number of bytes of frame data actually cleared. + // + // This is a virtual method because MockImageDecoder needs to override it in + // order to run the test ImageFrameGeneratorTest::ClearMultiFrameDecode. + // + // @TODO Let MockImageDecoder override ImageFrame::ClearFrameBuffer instead, + // so this method can be made non-virtual. It is used in the test + // ImageFrameGeneratorTest::ClearMultiFrameDecode. The test needs to + // be modified since two frames may be kept in cache, instead of + // always just one, with this ClearCacheExceptFrame implementation. + virtual size_t ClearCacheExceptFrame(size_t); + + // If the image has a cursor hot-spot, stores it in the argument + // and returns true. Otherwise returns false. + virtual bool HotSpot(IntPoint&) const { return false; } + + virtual void SetMemoryAllocator(SkBitmap::Allocator* allocator) { + // FIXME: this doesn't work for images with multiple frames. + if (frame_buffer_cache_.IsEmpty()) { + // Ensure that InitializeNewFrame is called, after parsing if + // necessary. + if (!FrameCount()) + return; + } + + frame_buffer_cache_[0].SetMemoryAllocator(allocator); + } + + virtual bool CanDecodeToYUV() { return false; } + virtual bool DecodeToYUV() { return false; } + virtual void SetImagePlanes(std::unique_ptr<ImagePlanes>) {} + + protected: + ImageDecoder(AlphaOption alpha_option, + const ColorBehavior& color_behavior, + size_t max_decoded_bytes) + : premultiply_alpha_(alpha_option == kAlphaPremultiplied), + color_behavior_(color_behavior), + max_decoded_bytes_(max_decoded_bytes), + purge_aggressively_(false) {} + + // Calculates the most recent frame whose image data may be needed in + // order to decode frame |frame_index|, based on frame disposal methods + // and |frame_rect_is_opaque|, where |frame_rect_is_opaque| signifies whether + // the rectangle of frame at |frame_index| is known to be opaque. + // If no previous frame's data is required, returns WTF::kNotFound. + // + // This function requires that the previous frame's + // |required_previous_frame_index_| member has been set correctly. The + // easiest way to ensure this is for subclasses to call this method and + // store the result on the frame via SetRequiredPreviousFrameIndex() + // as soon as the frame has been created and parsed sufficiently to + // determine the disposal method; assuming this happens for all frames + // in order, the required invariant will hold. + // + // Image formats which do not use more than one frame do not need to + // worry about this; see comments on + // ImageFrame::required_previous_frame+index_. + size_t FindRequiredPreviousFrame(size_t frame_index, + bool frame_rect_is_opaque); + + // This is called by ClearCacheExceptFrame() if that method decides it wants + // to preserve another frame, to avoid unnecessary redecoding. + size_t ClearCacheExceptTwoFrames(size_t, size_t); + virtual void ClearFrameBuffer(size_t frame_index); + + // Decodes the image sufficiently to determine the image size. + virtual void DecodeSize() = 0; + + // Decodes the image sufficiently to determine the number of frames and + // returns that number. + virtual size_t DecodeFrameCount() { return 1; } + + // Called to initialize the frame buffer with the given index, based on the + // provided and previous frame's characteristics. Returns true on success. + // Before calling this method, the caller must verify that the frame exists. + // On failure, the client should call SetFailed. This method does not call + // SetFailed itself because that might delete the object directly making this + // call. + bool InitFrameBuffer(size_t); + + // Performs any additional setup of the requested frame after it has been + // initially created, e.g. setting a duration or disposal method. + virtual void InitializeNewFrame(size_t) {} + + // Decodes the requested frame. + virtual void Decode(size_t) = 0; + + // This method is only required for animated images. It returns a vector with + // all frame indices that need to be decoded in order to succesfully decode + // the provided frame. The indices are returned in reverse order, so the + // last frame needs to be decoded first. Before calling this method, the + // caller must verify that the frame exists. + Vector<size_t> FindFramesToDecode(size_t) const; + + // This is called by Decode() after decoding a frame in an animated image. + // Before calling this method, the caller must verify that the frame exists. + // @return true if the frame was fully decoded, + // false otherwise. + bool PostDecodeProcessing(size_t); + + // The GIF and PNG decoders set the default alpha setting of the ImageFrame to + // true. When the frame rect does not contain any (semi-) transparent pixels, + // this may need to be changed to false. This depends on whether the required + // previous frame adds transparency to the image, outside of the frame rect. + // This methods corrects the alpha setting of the frame buffer to false when + // the whole frame is opaque. + // + // This method should be called by the GIF and PNG decoder when the pixels in + // the frame rect do *not* contain any transparent pixels. Before calling + // this method, the caller must verify that the frame exists. + void CorrectAlphaWhenFrameBufferSawNoAlpha(size_t); + + scoped_refptr<SegmentReader> data_; // The encoded data. + Vector<ImageFrame, 1> frame_buffer_cache_; + const bool premultiply_alpha_; + const ColorBehavior color_behavior_; + ImageOrientation orientation_; + + // The maximum amount of memory a decoded image should require. Ideally, + // image decoders should downsample large images to fit under this limit + // (and then return the downsampled size from DecodedSize()). Ignoring + // this limit can cause excessive memory use or even crashes on low- + // memory devices. + const size_t max_decoded_bytes_; + + // While decoding, we may learn that there are so many animation frames that + // we would go beyond our cache budget. + // If that happens, purge_aggressively_ is set to true. This signals + // future decodes to purge old frames as it goes. + void UpdateAggressivePurging(size_t index); + + // The method is only relevant for multi-frame images. + // + // This method indicates whether the provided frame has enough data to decode + // successive frames that depend on it. It is used by ClearCacheExceptFrame + // to determine which frame to keep in cache when the indicated frame is not + // yet sufficiently decoded. + // + // The default condition is that the frame status needs to be FramePartial or + // FrameComplete, since the data of previous frames is copied in + // InitFrameBuffer() before setting the status to FramePartial. For WebP, + // however, the status needs to be FrameComplete since the complete buffer is + // used to do alpha blending in WEBPImageDecoder::ApplyPostProcessing(). + // + // Before calling this, verify that frame |index| exists by checking that + // |index| is smaller than |frame_buffer_cache_|.size(). + virtual bool FrameStatusSufficientForSuccessors(size_t index) { + DCHECK(index < frame_buffer_cache_.size()); + return frame_buffer_cache_[index].GetStatus() != ImageFrame::kFrameEmpty; + } + + private: + // Some code paths compute the size of the image as "width * height * 4" + // and return it as a (signed) int. Avoid overflow. + static bool SizeCalculationMayOverflow(unsigned width, unsigned height) { + unsigned long long total_size = static_cast<unsigned long long>(width) * + static_cast<unsigned long long>(height); + return total_size > ((1 << 29) - 1); + } + + bool purge_aggressively_; + + // This methods gets called at the end of InitFrameBuffer. Subclasses can do + // format specific initialization, for e.g. alpha settings, here. + virtual void OnInitFrameBuffer(size_t){}; + + // Called by InitFrameBuffer to determine if it can take the bitmap of the + // previous frame. This condition is different for GIF and WEBP. + virtual bool CanReusePreviousFrameBuffer(size_t) const { return false; } + + IntSize size_; + bool size_available_ = false; + bool is_all_data_received_ = false; + bool failed_ = false; + bool has_histogrammed_color_space_ = false; + + sk_sp<SkColorSpace> embedded_color_space_ = nullptr; + bool source_to_target_color_transform_needs_update_ = false; + std::unique_ptr<SkColorSpaceXform> source_to_target_color_transform_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/image_decoder_test.cc b/chromium/third_party/blink/renderer/platform/image-decoders/image_decoder_test.cc new file mode 100644 index 00000000000..3c7d25bec40 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/image_decoder_test.cc @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2013 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/platform/image-decoders/image_decoder.h" + +#include <memory> +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/platform/image-decoders/image_frame.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { + +class TestImageDecoder : public ImageDecoder { + public: + TestImageDecoder() + : ImageDecoder(kAlphaNotPremultiplied, + ColorBehavior::TransformToSRGB(), + kNoDecodedImageByteLimit) {} + + String FilenameExtension() const override { return ""; } + + Vector<ImageFrame, 1>& FrameBufferCache() { return frame_buffer_cache_; } + + void ResetRequiredPreviousFrames(bool known_opaque = false) { + for (size_t i = 0; i < frame_buffer_cache_.size(); ++i) + frame_buffer_cache_[i].SetRequiredPreviousFrameIndex( + FindRequiredPreviousFrame(i, known_opaque)); + } + + void InitFrames(size_t num_frames, + unsigned width = 100, + unsigned height = 100) { + SetSize(width, height); + frame_buffer_cache_.resize(num_frames); + for (size_t i = 0; i < num_frames; ++i) + frame_buffer_cache_[i].SetOriginalFrameRect(IntRect(0, 0, width, height)); + } + + private: + void DecodeSize() override {} + void Decode(size_t index) override {} +}; + +TEST(ImageDecoderTest, sizeCalculationMayOverflow) { + std::unique_ptr<TestImageDecoder> decoder( + std::make_unique<TestImageDecoder>()); + EXPECT_FALSE(decoder->SetSize(1 << 29, 1)); + EXPECT_FALSE(decoder->SetSize(1, 1 << 29)); + EXPECT_FALSE(decoder->SetSize(1 << 15, 1 << 15)); + EXPECT_TRUE(decoder->SetSize(1 << 28, 1)); + EXPECT_TRUE(decoder->SetSize(1, 1 << 28)); + EXPECT_TRUE(decoder->SetSize(1 << 14, 1 << 14)); +} + +TEST(ImageDecoderTest, requiredPreviousFrameIndex) { + std::unique_ptr<TestImageDecoder> decoder( + std::make_unique<TestImageDecoder>()); + decoder->InitFrames(6); + Vector<ImageFrame, 1>& frame_buffers = decoder->FrameBufferCache(); + + frame_buffers[1].SetDisposalMethod(ImageFrame::kDisposeKeep); + frame_buffers[2].SetDisposalMethod(ImageFrame::kDisposeOverwritePrevious); + frame_buffers[3].SetDisposalMethod(ImageFrame::kDisposeOverwritePrevious); + frame_buffers[4].SetDisposalMethod(ImageFrame::kDisposeKeep); + + decoder->ResetRequiredPreviousFrames(); + + // The first frame doesn't require any previous frame. + EXPECT_EQ(kNotFound, frame_buffers[0].RequiredPreviousFrameIndex()); + // The previous DisposeNotSpecified frame is required. + EXPECT_EQ(0u, frame_buffers[1].RequiredPreviousFrameIndex()); + // DisposeKeep is treated as DisposeNotSpecified. + EXPECT_EQ(1u, frame_buffers[2].RequiredPreviousFrameIndex()); + // Previous DisposeOverwritePrevious frames are skipped. + EXPECT_EQ(1u, frame_buffers[3].RequiredPreviousFrameIndex()); + EXPECT_EQ(1u, frame_buffers[4].RequiredPreviousFrameIndex()); + EXPECT_EQ(4u, frame_buffers[5].RequiredPreviousFrameIndex()); +} + +TEST(ImageDecoderTest, requiredPreviousFrameIndexDisposeOverwriteBgcolor) { + std::unique_ptr<TestImageDecoder> decoder( + std::make_unique<TestImageDecoder>()); + decoder->InitFrames(3); + Vector<ImageFrame, 1>& frame_buffers = decoder->FrameBufferCache(); + + // Fully covering DisposeOverwriteBgcolor previous frame resets the starting + // state. + frame_buffers[1].SetDisposalMethod(ImageFrame::kDisposeOverwriteBgcolor); + decoder->ResetRequiredPreviousFrames(); + EXPECT_EQ(kNotFound, frame_buffers[2].RequiredPreviousFrameIndex()); + + // Partially covering DisposeOverwriteBgcolor previous frame is required by + // this frame. + frame_buffers[1].SetOriginalFrameRect(IntRect(50, 50, 50, 50)); + decoder->ResetRequiredPreviousFrames(); + EXPECT_EQ(1u, frame_buffers[2].RequiredPreviousFrameIndex()); +} + +TEST(ImageDecoderTest, requiredPreviousFrameIndexForFrame1) { + std::unique_ptr<TestImageDecoder> decoder( + std::make_unique<TestImageDecoder>()); + decoder->InitFrames(2); + Vector<ImageFrame, 1>& frame_buffers = decoder->FrameBufferCache(); + + decoder->ResetRequiredPreviousFrames(); + EXPECT_EQ(0u, frame_buffers[1].RequiredPreviousFrameIndex()); + + // The first frame with DisposeOverwritePrevious or DisposeOverwriteBgcolor + // resets the starting state. + frame_buffers[0].SetDisposalMethod(ImageFrame::kDisposeOverwritePrevious); + decoder->ResetRequiredPreviousFrames(); + EXPECT_EQ(kNotFound, frame_buffers[1].RequiredPreviousFrameIndex()); + frame_buffers[0].SetDisposalMethod(ImageFrame::kDisposeOverwriteBgcolor); + decoder->ResetRequiredPreviousFrames(); + EXPECT_EQ(kNotFound, frame_buffers[1].RequiredPreviousFrameIndex()); + + // ... even if it partially covers. + frame_buffers[0].SetOriginalFrameRect(IntRect(50, 50, 50, 50)); + + frame_buffers[0].SetDisposalMethod(ImageFrame::kDisposeOverwritePrevious); + decoder->ResetRequiredPreviousFrames(); + EXPECT_EQ(kNotFound, frame_buffers[1].RequiredPreviousFrameIndex()); + frame_buffers[0].SetDisposalMethod(ImageFrame::kDisposeOverwriteBgcolor); + decoder->ResetRequiredPreviousFrames(); + EXPECT_EQ(kNotFound, frame_buffers[1].RequiredPreviousFrameIndex()); +} + +TEST(ImageDecoderTest, requiredPreviousFrameIndexBlendAtopBgcolor) { + std::unique_ptr<TestImageDecoder> decoder( + std::make_unique<TestImageDecoder>()); + decoder->InitFrames(3); + Vector<ImageFrame, 1>& frame_buffers = decoder->FrameBufferCache(); + + frame_buffers[1].SetOriginalFrameRect(IntRect(25, 25, 50, 50)); + frame_buffers[2].SetAlphaBlendSource(ImageFrame::kBlendAtopBgcolor); + + // A full frame with 'blending method == BlendAtopBgcolor' doesn't depend on + // any prior frames. + for (int dispose_method = ImageFrame::kDisposeNotSpecified; + dispose_method <= ImageFrame::kDisposeOverwritePrevious; + ++dispose_method) { + frame_buffers[1].SetDisposalMethod( + static_cast<ImageFrame::DisposalMethod>(dispose_method)); + decoder->ResetRequiredPreviousFrames(); + EXPECT_EQ(kNotFound, frame_buffers[2].RequiredPreviousFrameIndex()); + } + + // A non-full frame with 'blending method == BlendAtopBgcolor' does depend on + // a prior frame. + frame_buffers[2].SetOriginalFrameRect(IntRect(50, 50, 50, 50)); + for (int dispose_method = ImageFrame::kDisposeNotSpecified; + dispose_method <= ImageFrame::kDisposeOverwritePrevious; + ++dispose_method) { + frame_buffers[1].SetDisposalMethod( + static_cast<ImageFrame::DisposalMethod>(dispose_method)); + decoder->ResetRequiredPreviousFrames(); + EXPECT_NE(kNotFound, frame_buffers[2].RequiredPreviousFrameIndex()); + } +} + +TEST(ImageDecoderTest, requiredPreviousFrameIndexKnownOpaque) { + std::unique_ptr<TestImageDecoder> decoder( + std::make_unique<TestImageDecoder>()); + decoder->InitFrames(3); + Vector<ImageFrame, 1>& frame_buffers = decoder->FrameBufferCache(); + + frame_buffers[1].SetOriginalFrameRect(IntRect(25, 25, 50, 50)); + + // A full frame that is known to be opaque doesn't depend on any prior frames. + for (int dispose_method = ImageFrame::kDisposeNotSpecified; + dispose_method <= ImageFrame::kDisposeOverwritePrevious; + ++dispose_method) { + frame_buffers[1].SetDisposalMethod( + static_cast<ImageFrame::DisposalMethod>(dispose_method)); + decoder->ResetRequiredPreviousFrames(true); + EXPECT_EQ(kNotFound, frame_buffers[2].RequiredPreviousFrameIndex()); + } + + // A non-full frame that is known to be opaque does depend on a prior frame. + frame_buffers[2].SetOriginalFrameRect(IntRect(50, 50, 50, 50)); + for (int dispose_method = ImageFrame::kDisposeNotSpecified; + dispose_method <= ImageFrame::kDisposeOverwritePrevious; + ++dispose_method) { + frame_buffers[1].SetDisposalMethod( + static_cast<ImageFrame::DisposalMethod>(dispose_method)); + decoder->ResetRequiredPreviousFrames(true); + EXPECT_NE(kNotFound, frame_buffers[2].RequiredPreviousFrameIndex()); + } +} + +TEST(ImageDecoderTest, clearCacheExceptFrameDoNothing) { + std::unique_ptr<TestImageDecoder> decoder( + std::make_unique<TestImageDecoder>()); + decoder->ClearCacheExceptFrame(0); + + // This should not crash. + decoder->InitFrames(20); + decoder->ClearCacheExceptFrame(kNotFound); +} + +TEST(ImageDecoderTest, clearCacheExceptFrameAll) { + const size_t kNumFrames = 10; + std::unique_ptr<TestImageDecoder> decoder( + std::make_unique<TestImageDecoder>()); + decoder->InitFrames(kNumFrames); + Vector<ImageFrame, 1>& frame_buffers = decoder->FrameBufferCache(); + for (size_t i = 0; i < kNumFrames; ++i) + frame_buffers[i].SetStatus(i % 2 ? ImageFrame::kFramePartial + : ImageFrame::kFrameComplete); + + decoder->ClearCacheExceptFrame(kNotFound); + + for (size_t i = 0; i < kNumFrames; ++i) { + SCOPED_TRACE(testing::Message() << i); + EXPECT_EQ(ImageFrame::kFrameEmpty, frame_buffers[i].GetStatus()); + } +} + +TEST(ImageDecoderTest, clearCacheExceptFramePreverveClearExceptFrame) { + const size_t kNumFrames = 10; + std::unique_ptr<TestImageDecoder> decoder( + std::make_unique<TestImageDecoder>()); + decoder->InitFrames(kNumFrames); + Vector<ImageFrame, 1>& frame_buffers = decoder->FrameBufferCache(); + for (size_t i = 0; i < kNumFrames; ++i) + frame_buffers[i].SetStatus(ImageFrame::kFrameComplete); + + decoder->ResetRequiredPreviousFrames(); + decoder->ClearCacheExceptFrame(5); + for (size_t i = 0; i < kNumFrames; ++i) { + SCOPED_TRACE(testing::Message() << i); + if (i == 5) + EXPECT_EQ(ImageFrame::kFrameComplete, frame_buffers[i].GetStatus()); + else + EXPECT_EQ(ImageFrame::kFrameEmpty, frame_buffers[i].GetStatus()); + } +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/image_decoder_test_helpers.cc b/chromium/third_party/blink/renderer/platform/image-decoders/image_decoder_test_helpers.cc new file mode 100644 index 00000000000..c4d4c787890 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/image_decoder_test_helpers.cc @@ -0,0 +1,598 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/platform/image-decoders/image_decoder_test_helpers.h" + +#include <memory> +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/platform/image-decoders/image_frame.h" +#include "third_party/blink/renderer/platform/shared_buffer.h" +#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h" +#include "third_party/blink/renderer/platform/wtf/string_hasher.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" + +namespace blink { + +scoped_refptr<SharedBuffer> ReadFile(const char* file_name) { + String file_path = test::BlinkLayoutTestsDir(); + file_path.append(file_name); + return test::ReadFromFile(file_path); +} + +scoped_refptr<SharedBuffer> ReadFile(const char* dir, const char* file_name) { + StringBuilder file_path; + if (strncmp(dir, "LayoutTests/", 12) == 0) { + file_path.Append(test::BlinkLayoutTestsDir()); + file_path.Append('/'); + file_path.Append(dir + 12); + } else { + file_path.Append(test::BlinkRootDir()); + file_path.Append('/'); + file_path.Append(dir); + } + file_path.Append('/'); + file_path.Append(file_name); + return test::ReadFromFile(file_path.ToString()); +} + +unsigned HashBitmap(const SkBitmap& bitmap) { + return StringHasher::HashMemory(bitmap.getPixels(), bitmap.computeByteSize()); +} + +static unsigned CreateDecodingBaseline(DecoderCreator create_decoder, + SharedBuffer* data) { + std::unique_ptr<ImageDecoder> decoder = create_decoder(); + decoder->SetData(data, true); + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0); + return HashBitmap(frame->Bitmap()); +} + +void CreateDecodingBaseline(DecoderCreator create_decoder, + SharedBuffer* data, + Vector<unsigned>* baseline_hashes) { + std::unique_ptr<ImageDecoder> decoder = create_decoder(); + decoder->SetData(data, true); + size_t frame_count = decoder->FrameCount(); + for (size_t i = 0; i < frame_count; ++i) { + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(i); + baseline_hashes->push_back(HashBitmap(frame->Bitmap())); + } +} + +void TestByteByByteDecode(DecoderCreator create_decoder, + SharedBuffer* shared_data, + size_t expected_frame_count, + int expected_repetition_count) { + const Vector<char> data = shared_data->Copy(); + + Vector<unsigned> baseline_hashes; + CreateDecodingBaseline(create_decoder, shared_data, &baseline_hashes); + + std::unique_ptr<ImageDecoder> decoder = create_decoder(); + + size_t frame_count = 0; + size_t frames_decoded = 0; + + // Pass data to decoder byte by byte. + scoped_refptr<SharedBuffer> source_data[2] = {SharedBuffer::Create(), + SharedBuffer::Create()}; + const char* source = data.data(); + + for (size_t length = 1; length <= data.size() && !decoder->Failed(); + ++length) { + source_data[0]->Append(source, 1u); + source_data[1]->Append(source++, 1u); + // Alternate the buffers to cover the JPEGImageDecoder::OnSetData restart + // code. + decoder->SetData(source_data[length & 1].get(), length == data.size()); + + EXPECT_LE(frame_count, decoder->FrameCount()); + frame_count = decoder->FrameCount(); + + if (!decoder->IsSizeAvailable()) + continue; + + for (size_t i = frames_decoded; i < frame_count; ++i) { + // In ICOImageDecoder memory layout could differ from frame order. + // E.g. memory layout could be |<frame1><frame0>| and frame_count + // would return 1 until receiving full file. + // When file is completely received frame_count would return 2 and + // only then both frames could be completely decoded. + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(i); + if (frame && frame->GetStatus() == ImageFrame::kFrameComplete) + ++frames_decoded; + } + } + + EXPECT_FALSE(decoder->Failed()); + EXPECT_EQ(expected_frame_count, decoder->FrameCount()); + EXPECT_EQ(expected_frame_count, frames_decoded); + EXPECT_EQ(expected_repetition_count, decoder->RepetitionCount()); + + ASSERT_EQ(expected_frame_count, baseline_hashes.size()); + for (size_t i = 0; i < decoder->FrameCount(); i++) { + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(i); + EXPECT_EQ(baseline_hashes[i], HashBitmap(frame->Bitmap())); + } +} + +// This test verifies that calling SharedBuffer::MergeSegmentsIntoBuffer() does +// not break decoding at a critical point: in between a call to decode the size +// (when the decoder stops while it may still have input data to read) and a +// call to do a full decode. +static void TestMergeBuffer(DecoderCreator create_decoder, + SharedBuffer* shared_data) { + const unsigned hash = CreateDecodingBaseline(create_decoder, shared_data); + const Vector<char> data = shared_data->Copy(); + + // In order to do any verification, this test needs to move the data owned + // by the SharedBuffer. A way to guarantee that is to create a new one, and + // then append a string of characters greater than kSegmentSize. This + // results in writing the data into a segment, skipping the internal + // contiguous buffer. + scoped_refptr<SharedBuffer> segmented_data = SharedBuffer::Create(); + segmented_data->Append(data.data(), data.size()); + + std::unique_ptr<ImageDecoder> decoder = create_decoder(); + decoder->SetData(segmented_data.get(), true); + + ASSERT_TRUE(decoder->IsSizeAvailable()); + + // This will call SharedBuffer::MergeSegmentsIntoBuffer, copying all + // segments into the contiguous buffer. If the ImageDecoder was pointing to + // data in a segment, its pointer would no longer be valid. + segmented_data->Data(); + + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0); + ASSERT_FALSE(decoder->Failed()); + EXPECT_EQ(frame->GetStatus(), ImageFrame::kFrameComplete); + EXPECT_EQ(HashBitmap(frame->Bitmap()), hash); +} + +static void TestRandomFrameDecode(DecoderCreator create_decoder, + SharedBuffer* full_data, + size_t skipping_step) { + Vector<unsigned> baseline_hashes; + CreateDecodingBaseline(create_decoder, full_data, &baseline_hashes); + size_t frame_count = baseline_hashes.size(); + + // Random decoding should get the same results as sequential decoding. + std::unique_ptr<ImageDecoder> decoder = create_decoder(); + decoder->SetData(full_data, true); + for (size_t i = 0; i < skipping_step; ++i) { + for (size_t j = i; j < frame_count; j += skipping_step) { + SCOPED_TRACE(testing::Message() << "Random i:" << i << " j:" << j); + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(j); + EXPECT_EQ(baseline_hashes[j], HashBitmap(frame->Bitmap())); + } + } + + // Decoding in reverse order. + decoder = create_decoder(); + decoder->SetData(full_data, true); + for (size_t i = frame_count; i; --i) { + SCOPED_TRACE(testing::Message() << "Reverse i:" << i); + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(i - 1); + EXPECT_EQ(baseline_hashes[i - 1], HashBitmap(frame->Bitmap())); + } +} + +static void TestRandomDecodeAfterClearFrameBufferCache( + DecoderCreator create_decoder, + SharedBuffer* data, + size_t skipping_step) { + Vector<unsigned> baseline_hashes; + CreateDecodingBaseline(create_decoder, data, &baseline_hashes); + size_t frame_count = baseline_hashes.size(); + + std::unique_ptr<ImageDecoder> decoder = create_decoder(); + decoder->SetData(data, true); + for (size_t clear_except_frame = 0; clear_except_frame < frame_count; + ++clear_except_frame) { + decoder->ClearCacheExceptFrame(clear_except_frame); + for (size_t i = 0; i < skipping_step; ++i) { + for (size_t j = 0; j < frame_count; j += skipping_step) { + SCOPED_TRACE(testing::Message() << "Random i:" << i << " j:" << j); + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(j); + EXPECT_EQ(baseline_hashes[j], HashBitmap(frame->Bitmap())); + } + } + } +} + +static void TestDecodeAfterReallocatingData(DecoderCreator create_decoder, + SharedBuffer* data) { + std::unique_ptr<ImageDecoder> decoder = create_decoder(); + + // Parse from 'data'. + decoder->SetData(data, true); + size_t frame_count = decoder->FrameCount(); + + // ... and then decode frames from 'reallocated_data'. + Vector<char> copy = data->Copy(); + scoped_refptr<SharedBuffer> reallocated_data = + SharedBuffer::AdoptVector(copy); + ASSERT_TRUE(reallocated_data.get()); + data->Clear(); + decoder->SetData(reallocated_data.get(), true); + + for (size_t i = 0; i < frame_count; ++i) { + const ImageFrame* const frame = decoder->DecodeFrameBufferAtIndex(i); + EXPECT_EQ(ImageFrame::kFrameComplete, frame->GetStatus()); + } +} + +static void TestByteByByteSizeAvailable(DecoderCreator create_decoder, + SharedBuffer* data, + size_t frame_offset, + bool has_color_space, + int expected_repetition_count) { + std::unique_ptr<ImageDecoder> decoder = create_decoder(); + EXPECT_LT(frame_offset, data->size()); + + // Send data to the decoder byte-by-byte and use the provided frame offset in + // the data to check that IsSizeAvailable() changes state only when that + // offset is reached. Also check other decoder state. + scoped_refptr<SharedBuffer> temp_data = SharedBuffer::Create(); + const Vector<char> source_buffer = data->Copy(); + const char* source = source_buffer.data(); + for (size_t length = 1; length <= frame_offset; ++length) { + temp_data->Append(source++, 1u); + decoder->SetData(temp_data.get(), false); + + if (length < frame_offset) { + EXPECT_FALSE(decoder->IsSizeAvailable()); + EXPECT_TRUE(decoder->Size().IsEmpty()); + EXPECT_FALSE(decoder->HasEmbeddedColorSpace()); + EXPECT_EQ(0u, decoder->FrameCount()); + EXPECT_EQ(kAnimationLoopOnce, decoder->RepetitionCount()); + EXPECT_FALSE(decoder->DecodeFrameBufferAtIndex(0)); + } else { + EXPECT_TRUE(decoder->IsSizeAvailable()); + EXPECT_FALSE(decoder->Size().IsEmpty()); + EXPECT_EQ(decoder->HasEmbeddedColorSpace(), has_color_space); + EXPECT_EQ(1u, decoder->FrameCount()); + EXPECT_EQ(expected_repetition_count, decoder->RepetitionCount()); + } + + ASSERT_FALSE(decoder->Failed()); + } +} + +static void TestProgressiveDecoding(DecoderCreator create_decoder, + SharedBuffer* full_buffer, + size_t increment) { + const Vector<char> full_data = full_buffer->Copy(); + const size_t full_length = full_data.size(); + + std::unique_ptr<ImageDecoder> decoder; + + Vector<unsigned> truncated_hashes; + Vector<unsigned> progressive_hashes; + + // Compute hashes when the file is truncated. + scoped_refptr<SharedBuffer> data = SharedBuffer::Create(); + const char* source = full_data.data(); + for (size_t i = 1; i <= full_length; i += increment) { + decoder = create_decoder(); + data->Append(source++, 1u); + decoder->SetData(data.get(), i == full_length); + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0); + if (!frame) { + truncated_hashes.push_back(0); + continue; + } + truncated_hashes.push_back(HashBitmap(frame->Bitmap())); + } + + // Compute hashes when the file is progressively decoded. + decoder = create_decoder(); + data = SharedBuffer::Create(); + source = full_data.data(); + for (size_t i = 1; i <= full_length; i += increment) { + data->Append(source++, 1u); + decoder->SetData(data.get(), i == full_length); + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0); + if (!frame) { + progressive_hashes.push_back(0); + continue; + } + progressive_hashes.push_back(HashBitmap(frame->Bitmap())); + } + + for (size_t i = 0; i < truncated_hashes.size(); ++i) + ASSERT_EQ(truncated_hashes[i], progressive_hashes[i]); +} + +void TestUpdateRequiredPreviousFrameAfterFirstDecode( + DecoderCreator create_decoder, + SharedBuffer* full_buffer) { + const Vector<char> full_data = full_buffer->Copy(); + std::unique_ptr<ImageDecoder> decoder = create_decoder(); + + // Give it data that is enough to parse but not decode in order to check the + // status of RequiredPreviousFrameIndex before decoding. + scoped_refptr<SharedBuffer> data = SharedBuffer::Create(); + const char* source = full_data.data(); + do { + data->Append(source++, 1u); + decoder->SetData(data.get(), false); + } while (!decoder->FrameCount() || + decoder->DecodeFrameBufferAtIndex(0)->GetStatus() == + ImageFrame::kFrameEmpty); + + EXPECT_EQ(kNotFound, + decoder->DecodeFrameBufferAtIndex(0)->RequiredPreviousFrameIndex()); + unsigned frame_count = decoder->FrameCount(); + for (size_t i = 1; i < frame_count; ++i) { + EXPECT_EQ( + i - 1, + decoder->DecodeFrameBufferAtIndex(i)->RequiredPreviousFrameIndex()); + } + + decoder->SetData(full_buffer, true); + for (size_t i = 0; i < frame_count; ++i) { + EXPECT_EQ( + kNotFound, + decoder->DecodeFrameBufferAtIndex(i)->RequiredPreviousFrameIndex()); + } +} + +void TestResumePartialDecodeAfterClearFrameBufferCache( + DecoderCreator create_decoder, + SharedBuffer* full_buffer) { + const Vector<char> full_data = full_buffer->Copy(); + Vector<unsigned> baseline_hashes; + CreateDecodingBaseline(create_decoder, full_buffer, &baseline_hashes); + size_t frame_count = baseline_hashes.size(); + + std::unique_ptr<ImageDecoder> decoder = create_decoder(); + + // Let frame 0 be partially decoded. + scoped_refptr<SharedBuffer> data = SharedBuffer::Create(); + const char* source = full_data.data(); + do { + data->Append(source++, 1u); + decoder->SetData(data.get(), false); + } while (!decoder->FrameCount() || + decoder->DecodeFrameBufferAtIndex(0)->GetStatus() == + ImageFrame::kFrameEmpty); + + // Skip to the last frame and clear. + decoder->SetData(full_buffer, true); + EXPECT_EQ(frame_count, decoder->FrameCount()); + ImageFrame* last_frame = decoder->DecodeFrameBufferAtIndex(frame_count - 1); + EXPECT_EQ(baseline_hashes[frame_count - 1], HashBitmap(last_frame->Bitmap())); + decoder->ClearCacheExceptFrame(kNotFound); + + // Resume decoding of the first frame. + ImageFrame* first_frame = decoder->DecodeFrameBufferAtIndex(0); + EXPECT_EQ(ImageFrame::kFrameComplete, first_frame->GetStatus()); + EXPECT_EQ(baseline_hashes[0], HashBitmap(first_frame->Bitmap())); +} + +void TestByteByByteDecode(DecoderCreator create_decoder, + const char* file, + size_t expected_frame_count, + int expected_repetition_count) { + scoped_refptr<SharedBuffer> data = ReadFile(file); + ASSERT_TRUE(data.get()); + TestByteByByteDecode(create_decoder, data.get(), expected_frame_count, + expected_repetition_count); +} +void TestByteByByteDecode(DecoderCreator create_decoder, + const char* dir, + const char* file, + size_t expected_frame_count, + int expected_repetition_count) { + scoped_refptr<SharedBuffer> data = ReadFile(dir, file); + ASSERT_TRUE(data.get()); + TestByteByByteDecode(create_decoder, data.get(), expected_frame_count, + expected_repetition_count); +} + +void TestMergeBuffer(DecoderCreator create_decoder, const char* file) { + scoped_refptr<SharedBuffer> data = ReadFile(file); + ASSERT_TRUE(data.get()); + TestMergeBuffer(create_decoder, data.get()); +} + +void TestMergeBuffer(DecoderCreator create_decoder, + const char* dir, + const char* file) { + scoped_refptr<SharedBuffer> data = ReadFile(dir, file); + ASSERT_TRUE(data.get()); + TestMergeBuffer(create_decoder, data.get()); +} + +void TestRandomFrameDecode(DecoderCreator create_decoder, + const char* file, + size_t skipping_step) { + scoped_refptr<SharedBuffer> data = ReadFile(file); + ASSERT_TRUE(data.get()); + SCOPED_TRACE(file); + TestRandomFrameDecode(create_decoder, data.get(), skipping_step); +} +void TestRandomFrameDecode(DecoderCreator create_decoder, + const char* dir, + const char* file, + size_t skipping_step) { + scoped_refptr<SharedBuffer> data = ReadFile(dir, file); + ASSERT_TRUE(data.get()); + SCOPED_TRACE(file); + TestRandomFrameDecode(create_decoder, data.get(), skipping_step); +} + +void TestRandomDecodeAfterClearFrameBufferCache(DecoderCreator create_decoder, + const char* file, + size_t skipping_step) { + scoped_refptr<SharedBuffer> data = ReadFile(file); + ASSERT_TRUE(data.get()); + SCOPED_TRACE(file); + TestRandomDecodeAfterClearFrameBufferCache(create_decoder, data.get(), + skipping_step); +} + +void TestRandomDecodeAfterClearFrameBufferCache(DecoderCreator create_decoder, + const char* dir, + const char* file, + size_t skipping_step) { + scoped_refptr<SharedBuffer> data = ReadFile(dir, file); + ASSERT_TRUE(data.get()); + SCOPED_TRACE(file); + TestRandomDecodeAfterClearFrameBufferCache(create_decoder, data.get(), + skipping_step); +} + +void TestDecodeAfterReallocatingData(DecoderCreator create_decoder, + const char* file) { + scoped_refptr<SharedBuffer> data = ReadFile(file); + ASSERT_TRUE(data.get()); + TestDecodeAfterReallocatingData(create_decoder, data.get()); +} + +void TestDecodeAfterReallocatingData(DecoderCreator create_decoder, + const char* dir, + const char* file) { + scoped_refptr<SharedBuffer> data = ReadFile(dir, file); + ASSERT_TRUE(data.get()); + TestDecodeAfterReallocatingData(create_decoder, data.get()); +} + +void TestByteByByteSizeAvailable(DecoderCreator create_decoder, + const char* file, + size_t frame_offset, + bool has_color_space, + int expected_repetition_count) { + scoped_refptr<SharedBuffer> data = ReadFile(file); + ASSERT_TRUE(data.get()); + TestByteByByteSizeAvailable(create_decoder, data.get(), frame_offset, + has_color_space, expected_repetition_count); +} + +void TestByteByByteSizeAvailable(DecoderCreator create_decoder, + const char* dir, + const char* file, + size_t frame_offset, + bool has_color_space, + int expected_repetition_count) { + scoped_refptr<SharedBuffer> data = ReadFile(dir, file); + ASSERT_TRUE(data.get()); + TestByteByByteSizeAvailable(create_decoder, data.get(), frame_offset, + has_color_space, expected_repetition_count); +} + +void TestProgressiveDecoding(DecoderCreator create_decoder, + const char* file, + size_t increment) { + scoped_refptr<SharedBuffer> data = ReadFile(file); + ASSERT_TRUE(data.get()); + TestProgressiveDecoding(create_decoder, data.get(), increment); +} + +void TestProgressiveDecoding(DecoderCreator create_decoder, + const char* dir, + const char* file, + size_t increment) { + scoped_refptr<SharedBuffer> data = ReadFile(dir, file); + ASSERT_TRUE(data.get()); + TestProgressiveDecoding(create_decoder, data.get(), increment); +} + +void TestUpdateRequiredPreviousFrameAfterFirstDecode( + DecoderCreator create_decoder, + const char* dir, + const char* file) { + scoped_refptr<SharedBuffer> data = ReadFile(dir, file); + ASSERT_TRUE(data.get()); + TestUpdateRequiredPreviousFrameAfterFirstDecode(create_decoder, data.get()); +} + +void TestUpdateRequiredPreviousFrameAfterFirstDecode( + DecoderCreator create_decoder, + const char* file) { + scoped_refptr<SharedBuffer> data = ReadFile(file); + ASSERT_TRUE(data.get()); + TestUpdateRequiredPreviousFrameAfterFirstDecode(create_decoder, data.get()); +} + +void TestResumePartialDecodeAfterClearFrameBufferCache( + DecoderCreator create_decoder, + const char* dir, + const char* file) { + scoped_refptr<SharedBuffer> data = ReadFile(dir, file); + ASSERT_TRUE(data.get()); + TestResumePartialDecodeAfterClearFrameBufferCache(create_decoder, data.get()); +} + +void TestResumePartialDecodeAfterClearFrameBufferCache( + DecoderCreator create_decoder, + const char* file) { + scoped_refptr<SharedBuffer> data = ReadFile(file); + ASSERT_TRUE(data.get()); + TestResumePartialDecodeAfterClearFrameBufferCache(create_decoder, data.get()); +} + +static uint32_t PremultiplyColor(uint32_t c) { + return SkPremultiplyARGBInline(SkGetPackedA32(c), SkGetPackedR32(c), + SkGetPackedG32(c), SkGetPackedB32(c)); +} + +static void VerifyFramesMatch(const char* file, + const ImageFrame* const a, + const ImageFrame* const b) { + const SkBitmap& bitmap_a = a->Bitmap(); + const SkBitmap& bitmap_b = b->Bitmap(); + ASSERT_EQ(bitmap_a.width(), bitmap_b.width()); + ASSERT_EQ(bitmap_a.height(), bitmap_b.height()); + + int max_difference = 0; + for (int y = 0; y < bitmap_a.height(); ++y) { + for (int x = 0; x < bitmap_a.width(); ++x) { + uint32_t color_a = *bitmap_a.getAddr32(x, y); + if (!a->PremultiplyAlpha()) + color_a = PremultiplyColor(color_a); + uint32_t color_b = *bitmap_b.getAddr32(x, y); + if (!b->PremultiplyAlpha()) + color_b = PremultiplyColor(color_b); + uint8_t* pixel_a = reinterpret_cast<uint8_t*>(&color_a); + uint8_t* pixel_b = reinterpret_cast<uint8_t*>(&color_b); + for (int channel = 0; channel < 4; ++channel) { + const int difference = abs(pixel_a[channel] - pixel_b[channel]); + if (difference > max_difference) + max_difference = difference; + } + } + } + + // Pre-multiplication could round the RGBA channel values. So, we declare + // that the frames match if the RGBA channel values differ by at most 2. + EXPECT_GE(2, max_difference) << file; +} + +// Verifies that result of alpha blending is similar for AlphaPremultiplied and +// AlphaNotPremultiplied cases. +void TestAlphaBlending(DecoderCreatorWithAlpha create_decoder, + const char* file) { + scoped_refptr<SharedBuffer> data = ReadFile(file); + ASSERT_TRUE(data.get()); + + std::unique_ptr<ImageDecoder> decoder_a = + create_decoder(ImageDecoder::kAlphaPremultiplied); + decoder_a->SetData(data.get(), true); + + std::unique_ptr<ImageDecoder> decoder_b = + create_decoder(ImageDecoder::kAlphaNotPremultiplied); + decoder_b->SetData(data.get(), true); + + size_t frame_count = decoder_a->FrameCount(); + ASSERT_EQ(frame_count, decoder_b->FrameCount()); + + for (size_t i = 0; i < frame_count; ++i) { + VerifyFramesMatch(file, decoder_a->DecodeFrameBufferAtIndex(i), + decoder_b->DecodeFrameBufferAtIndex(i)); + } +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/image_decoder_test_helpers.h b/chromium/third_party/blink/renderer/platform/image-decoders/image_decoder_test_helpers.h new file mode 100644 index 00000000000..51759b9a4db --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/image_decoder_test_helpers.h @@ -0,0 +1,122 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_IMAGE_DECODER_TEST_HELPERS_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_IMAGE_DECODER_TEST_HELPERS_H_ + +#include <memory> +#include "third_party/blink/renderer/platform/image-decoders/image_decoder.h" +#include "third_party/blink/renderer/platform/shared_buffer.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +class SkBitmap; + +namespace blink { +class ImageDecoder; + +const char kDecodersTestingDir[] = "renderer/platform/image-decoders/testing"; +const unsigned kDefaultTestSize = 4 * SharedBuffer::kSegmentSize; + +using DecoderCreator = std::unique_ptr<ImageDecoder> (*)(); +using DecoderCreatorWithAlpha = + std::unique_ptr<ImageDecoder> (*)(ImageDecoder::AlphaOption); + +inline void PrepareReferenceData(char* buffer, size_t size) { + for (size_t i = 0; i < size; ++i) + buffer[i] = static_cast<char>(i); +} + +scoped_refptr<SharedBuffer> ReadFile(const char* file_name); +scoped_refptr<SharedBuffer> ReadFile(const char* dir, const char* file_name); +unsigned HashBitmap(const SkBitmap&); +void CreateDecodingBaseline(DecoderCreator, + SharedBuffer*, + Vector<unsigned>* baseline_hashes); + +void TestByteByByteDecode(DecoderCreator create_decoder, + SharedBuffer* data, + size_t expected_frame_count, + int expected_repetition_count); +void TestByteByByteDecode(DecoderCreator create_decoder, + const char* file, + size_t expected_frame_count, + int expected_repetition_count); +void TestByteByByteDecode(DecoderCreator create_decoder, + const char* dir, + const char* file, + size_t expected_frame_count, + int expected_repetition_count); + +void TestMergeBuffer(DecoderCreator create_decoder, const char* file); +void TestMergeBuffer(DecoderCreator create_decoder, + const char* dir, + const char* file); + +// |skipping_step| is used to randomize the decoding order. For images with +// a small number of frames (e.g. < 10), this value should be smaller, on the +// order of (number of frames) / 2. +void TestRandomFrameDecode(DecoderCreator, + const char* file, + size_t skipping_step = 5); +void TestRandomFrameDecode(DecoderCreator, + const char* dir, + const char* file, + size_t skipping_step = 5); + +// |skipping_step| is used to randomize the decoding order. For images with +// a small number of frames (e.g. < 10), this value should be smaller, on the +// order of (number of frames) / 2. +void TestRandomDecodeAfterClearFrameBufferCache(DecoderCreator, + const char* file, + size_t skipping_step = 5); +void TestRandomDecodeAfterClearFrameBufferCache(DecoderCreator, + const char* dir, + const char* file, + size_t skipping_step = 5); + +void TestDecodeAfterReallocatingData(DecoderCreator, const char* file); +void TestDecodeAfterReallocatingData(DecoderCreator, + const char* dir, + const char* file); +void TestByteByByteSizeAvailable(DecoderCreator, + const char* file, + size_t frame_offset, + bool has_color_space, + int expected_repetition_count); +void TestByteByByteSizeAvailable(DecoderCreator, + const char* dir, + const char* file, + size_t frame_offset, + bool has_color_space, + int expected_repetition_count); + +// Data is provided in chunks of length |increment| to the decoder. This value +// can be increased to reduce processing time. +void TestProgressiveDecoding(DecoderCreator, + const char* file, + size_t increment = 1); +void TestProgressiveDecoding(DecoderCreator, + const char* dir, + const char* file, + size_t increment = 1); + +void TestUpdateRequiredPreviousFrameAfterFirstDecode(DecoderCreator, + const char* dir, + const char* file); +void TestUpdateRequiredPreviousFrameAfterFirstDecode(DecoderCreator, + const char* file); + +void TestResumePartialDecodeAfterClearFrameBufferCache(DecoderCreator, + const char* dir, + const char* file); +void TestResumePartialDecodeAfterClearFrameBufferCache(DecoderCreator, + const char* file); + +// Verifies that result of alpha blending is similar for AlphaPremultiplied and +// AlphaNotPremultiplied cases. +void TestAlphaBlending(DecoderCreatorWithAlpha, const char*); + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_IMAGE_DECODER_TEST_HELPERS_H_ diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/image_frame.cc b/chromium/third_party/blink/renderer/platform/image-decoders/image_frame.cc new file mode 100644 index 00000000000..e92d01e5ba7 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/image_frame.cc @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2008, 2009 Google, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/platform/image-decoders/image_frame.h" + +#include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h" +#include "third_party/blink/renderer/platform/image-decoders/image_decoder.h" + +namespace blink { + +ImageFrame::ImageFrame() + : allocator_(nullptr), + has_alpha_(true), + status_(kFrameEmpty), + disposal_method_(kDisposeNotSpecified), + alpha_blend_source_(kBlendAtopPreviousFrame), + premultiply_alpha_(true), + pixels_changed_(false), + required_previous_frame_index_(kNotFound) {} + +ImageFrame& ImageFrame::operator=(const ImageFrame& other) { + if (this == &other) + return *this; + + bitmap_ = other.bitmap_; + // Be sure to assign this before calling SetStatus(), since SetStatus() may + // call NotifyBitmapIfPixelsChanged(). + pixels_changed_ = other.pixels_changed_; + SetMemoryAllocator(other.GetAllocator()); + SetOriginalFrameRect(other.OriginalFrameRect()); + SetStatus(other.GetStatus()); + SetDuration(other.Duration()); + SetDisposalMethod(other.GetDisposalMethod()); + SetAlphaBlendSource(other.GetAlphaBlendSource()); + SetPremultiplyAlpha(other.PremultiplyAlpha()); + // Be sure that this is called after we've called SetStatus(), since we + // look at our status to know what to do with the alpha value. + SetHasAlpha(other.HasAlpha()); + SetRequiredPreviousFrameIndex(other.RequiredPreviousFrameIndex()); + return *this; +} + +void ImageFrame::ClearPixelData() { + bitmap_.reset(); + status_ = kFrameEmpty; + // NOTE: Do not reset other members here; ClearFrameBufferCache() + // calls this to free the bitmap data, but other functions like + // InitFrameBuffer() and FrameComplete() may still need to read + // other metadata out of this frame later. +} + +void ImageFrame::ZeroFillPixelData() { + bitmap_.eraseARGB(0, 0, 0, 0); + has_alpha_ = true; +} + +bool ImageFrame::CopyBitmapData(const ImageFrame& other) { + DCHECK_NE(this, &other); + has_alpha_ = other.has_alpha_; + bitmap_.reset(); + SkImageInfo info = other.bitmap_.info(); + return bitmap_.tryAllocPixels(info) && + other.bitmap_.readPixels(info, bitmap_.getPixels(), bitmap_.rowBytes(), + 0, 0); +} + +bool ImageFrame::TakeBitmapDataIfWritable(ImageFrame* other) { + DCHECK(other); + DCHECK_EQ(kFrameComplete, other->status_); + DCHECK_EQ(kFrameEmpty, status_); + DCHECK_NE(this, other); + if (other->bitmap_.isImmutable()) + return false; + has_alpha_ = other->has_alpha_; + bitmap_.reset(); + bitmap_.swap(other->bitmap_); + other->status_ = kFrameEmpty; + return true; +} + +bool ImageFrame::AllocatePixelData(int new_width, + int new_height, + sk_sp<SkColorSpace> color_space) { + // AllocatePixelData() should only be called once. + DCHECK(!Width() && !Height()); + + bitmap_.setInfo(SkImageInfo::MakeN32( + new_width, new_height, + premultiply_alpha_ ? kPremul_SkAlphaType : kUnpremul_SkAlphaType, + std::move(color_space))); + return bitmap_.tryAllocPixels(allocator_); +} + +bool ImageFrame::HasAlpha() const { + return has_alpha_; +} + +sk_sp<SkImage> ImageFrame::FinalizePixelsAndGetImage() { + DCHECK_EQ(kFrameComplete, status_); + bitmap_.setImmutable(); + return SkImage::MakeFromBitmap(bitmap_); +} + +void ImageFrame::SetHasAlpha(bool alpha) { + has_alpha_ = alpha; + + bitmap_.setAlphaType(ComputeAlphaType()); +} + +void ImageFrame::SetStatus(Status status) { + status_ = status; + if (status_ == kFrameComplete) { + bitmap_.setAlphaType(ComputeAlphaType()); + // Send pending pixels changed notifications now, because we can't do + // this after the bitmap has been marked immutable. We don't set the + // bitmap immutable here because it would defeat + // TakeBitmapDataIfWritable(). Instead we let the bitmap stay mutable + // until someone calls FinalizePixelsAndGetImage() to actually get the + // SkImage. + NotifyBitmapIfPixelsChanged(); + } +} + +void ImageFrame::ZeroFillFrameRect(const IntRect& rect) { + if (rect.IsEmpty()) + return; + + bitmap_.eraseArea(rect, SkColorSetARGB(0, 0, 0, 0)); + SetHasAlpha(true); +} + +static uint8_t BlendChannel(uint8_t src, + uint8_t src_a, + uint8_t dst, + uint8_t dst_a, + unsigned scale) { + unsigned blend_unscaled = src * src_a + dst * dst_a; + DCHECK(blend_unscaled < (1ULL << 32) / scale); + return (blend_unscaled * scale) >> 24; +} + +static uint32_t BlendSrcOverDstNonPremultiplied(uint32_t src, uint32_t dst) { + uint8_t src_a = SkGetPackedA32(src); + if (src_a == 0) + return dst; + + uint8_t dst_a = SkGetPackedA32(dst); + uint8_t dst_factor_a = (dst_a * SkAlpha255To256(255 - src_a)) >> 8; + DCHECK(src_a + dst_factor_a < (1U << 8)); + uint8_t blend_a = src_a + dst_factor_a; + unsigned scale = (1UL << 24) / blend_a; + + uint8_t blend_r = BlendChannel(SkGetPackedR32(src), src_a, + SkGetPackedR32(dst), dst_factor_a, scale); + uint8_t blend_g = BlendChannel(SkGetPackedG32(src), src_a, + SkGetPackedG32(dst), dst_factor_a, scale); + uint8_t blend_b = BlendChannel(SkGetPackedB32(src), src_a, + SkGetPackedB32(dst), dst_factor_a, scale); + + return SkPackARGB32NoCheck(blend_a, blend_r, blend_g, blend_b); +} + +void ImageFrame::BlendRGBARaw(PixelData* dest, + unsigned r, + unsigned g, + unsigned b, + unsigned a) { + *dest = + BlendSrcOverDstNonPremultiplied(SkPackARGB32NoCheck(a, r, g, b), *dest); +} + +void ImageFrame::BlendSrcOverDstRaw(PixelData* src, PixelData dst) { + *src = BlendSrcOverDstNonPremultiplied(*src, dst); +} + +SkAlphaType ImageFrame::ComputeAlphaType() const { + // If the frame is not fully loaded, there will be transparent pixels, + // so we can't tell skia we're opaque, even for image types that logically + // always are (e.g. jpeg). + if (!has_alpha_ && status_ == kFrameComplete) + return kOpaque_SkAlphaType; + + return premultiply_alpha_ ? kPremul_SkAlphaType : kUnpremul_SkAlphaType; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/image_frame.h b/chromium/third_party/blink/renderer/platform/image-decoders/image_frame.h new file mode 100644 index 00000000000..f67a5309479 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/image_frame.h @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_IMAGE_FRAME_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_IMAGE_FRAME_H_ + +#include "third_party/blink/public/platform/web_vector.h" +#include "third_party/blink/renderer/platform/geometry/int_rect.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/assertions.h" +#include "third_party/blink/renderer/platform/wtf/time.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkColorPriv.h" + +class SkImage; + +namespace blink { + +// ImageFrame represents the decoded image data. This buffer is what all +// decoders write a single frame into. +class PLATFORM_EXPORT ImageFrame final { + DISALLOW_NEW_EXCEPT_PLACEMENT_NEW(); + + public: + enum Status { kFrameEmpty, kFramePartial, kFrameComplete }; + enum DisposalMethod { + // If you change the numeric values of these, make sure you audit + // all users, as some users may cast raw values to/from these + // constants. + kDisposeNotSpecified, // Leave frame in framebuffer + kDisposeKeep, // Leave frame in framebuffer + kDisposeOverwriteBgcolor, // Clear frame to fully transparent + kDisposeOverwritePrevious // Clear frame to previous framebuffer contents + }; + // Indicates how non-opaque pixels in the current frame rectangle + // are blended with those in the previous frame. + // Notes: + // * GIF always uses 'BlendAtopPreviousFrame'. + // * WebP also uses the 'BlendAtopBgcolor' option. This is useful for + // cases where one wants to transform a few opaque pixels of the + // previous frame into non-opaque pixels in the current frame. + enum AlphaBlendSource { + // Blend non-opaque pixels atop the corresponding pixels in the + // initial buffer state (i.e. any previous frame buffer after having + // been properly disposed). + kBlendAtopPreviousFrame, + + // Blend non-opaque pixels against fully transparent (i.e. simply + // overwrite the corresponding pixels). + kBlendAtopBgcolor, + }; + typedef uint32_t PixelData; + + typedef WebVector<char> ICCProfile; + + ImageFrame(); + + // The assignment operator reads has_alpha_ (inside SetStatus()) before it + // sets it (in SetHasAlpha()). This doesn't cause any problems, since the + // SetHasAlpha() call ensures all state is set correctly, but it means we + // need to initialize has_alpha_ to some value before calling the operator + // lest any tools complain about using an uninitialized value. + ImageFrame(const ImageFrame& other) : has_alpha_(false) { operator=(other); } + + // For backends which refcount their data, this operator doesn't need to + // create a new copy of the image data, only increase the ref count. + ImageFrame& operator=(const ImageFrame& other); + + // These do not touch other metadata, only the raw pixel data. + void ClearPixelData(); + void ZeroFillPixelData(); + void ZeroFillFrameRect(const IntRect&); + + // Makes this frame have an independent copy of the provided image's + // pixel data, so that modifications in one frame are not reflected in + // the other. Returns whether the copy succeeded. + bool CopyBitmapData(const ImageFrame&); + + // Moves the bitmap data from the provided frame to this one, leaving the + // provided frame empty. Operation is successful only if bitmap data is not + // marked as done (immutable). Returns whether the move succeeded. + bool TakeBitmapDataIfWritable(ImageFrame*); + + // Copies the pixel data at [(start_x, start_y), (end_x, start_y)) to the + // same X-coordinates on each subsequent row up to but not including + // end_y. + void CopyRowNTimes(int start_x, int end_x, int start_y, int end_y) { + DCHECK_LT(start_x, Width()); + DCHECK_LE(end_x, Width()); + DCHECK_LT(start_y, Height()); + DCHECK_LE(end_y, Height()); + const int row_bytes = (end_x - start_x) * sizeof(PixelData); + const PixelData* const start_addr = GetAddr(start_x, start_y); + for (int dest_y = start_y + 1; dest_y < end_y; ++dest_y) + memcpy(GetAddr(start_x, dest_y), start_addr, row_bytes); + } + + // Allocates space for the pixel data. Must be called before any pixels are + // written, and should only be called once. The specified color space may be + // null if and only if color correct rendering is enabled. Returns true if the + // allocation succeeded. + bool AllocatePixelData(int new_width, int new_height, sk_sp<SkColorSpace>); + + bool HasAlpha() const; + const IntRect& OriginalFrameRect() const { return original_frame_rect_; } + Status GetStatus() const { return status_; } + TimeDelta Duration() const { return duration_; } + DisposalMethod GetDisposalMethod() const { return disposal_method_; } + AlphaBlendSource GetAlphaBlendSource() const { return alpha_blend_source_; } + bool PremultiplyAlpha() const { return premultiply_alpha_; } + SkBitmap::Allocator* GetAllocator() const { return allocator_; } + + // Returns the bitmap that is the output of decoding. + const SkBitmap& Bitmap() const { return bitmap_; } + + // Create SkImage from Bitmap() and return it. This should be called only + // if frame is complete. The bitmap is set immutable before creating + // SkImage to avoid copying bitmap in SkImage::MakeFromBitmap(bitmap_). + sk_sp<SkImage> FinalizePixelsAndGetImage(); + + // Returns true if the pixels changed, but the bitmap has not yet been + // notified. + bool PixelsChanged() const { return pixels_changed_; } + size_t RequiredPreviousFrameIndex() const { + return required_previous_frame_index_; + } + void SetHasAlpha(bool alpha); + void SetOriginalFrameRect(const IntRect& r) { original_frame_rect_ = r; } + void SetStatus(Status); + void SetDuration(TimeDelta duration) { duration_ = duration; } + void SetDisposalMethod(DisposalMethod disposal_method) { + disposal_method_ = disposal_method; + } + void SetAlphaBlendSource(AlphaBlendSource alpha_blend_source) { + alpha_blend_source_ = alpha_blend_source; + } + void SetPremultiplyAlpha(bool premultiply_alpha) { + premultiply_alpha_ = premultiply_alpha; + } + void SetMemoryAllocator(SkBitmap::Allocator* allocator) { + allocator_ = allocator; + } + // The pixels_changed flag needs to be set when the raw pixel data was + // directly modified (e.g. through a pointer or SetRGBA). The flag is usually + // set after a batch of changes has been made. + void SetPixelsChanged(bool pixels_changed) { + pixels_changed_ = pixels_changed; + } + void SetRequiredPreviousFrameIndex(size_t previous_frame_index) { + required_previous_frame_index_ = previous_frame_index; + } + + inline PixelData* GetAddr(int x, int y) { return bitmap_.getAddr32(x, y); } + + inline void SetRGBA(int x, + int y, + unsigned r, + unsigned g, + unsigned b, + unsigned a) { + SetRGBA(GetAddr(x, y), r, g, b, a); + } + + inline void SetRGBA(PixelData* dest, + unsigned r, + unsigned g, + unsigned b, + unsigned a) { + if (premultiply_alpha_) + SetRGBAPremultiply(dest, r, g, b, a); + else + *dest = SkPackARGB32NoCheck(a, r, g, b); + } + + static inline void SetRGBAPremultiply(PixelData* dest, + unsigned r, + unsigned g, + unsigned b, + unsigned a) { + enum FractionControl { kRoundFractionControl = 257 * 128 }; + + if (a < 255) { + unsigned alpha = a * 257; + r = (r * alpha + kRoundFractionControl) >> 16; + g = (g * alpha + kRoundFractionControl) >> 16; + b = (b * alpha + kRoundFractionControl) >> 16; + } + + *dest = SkPackARGB32NoCheck(a, r, g, b); + } + + static inline void SetRGBARaw(PixelData* dest, + unsigned r, + unsigned g, + unsigned b, + unsigned a) { + *dest = SkPackARGB32NoCheck(a, r, g, b); + } + + // Blend the RGBA pixel provided by |red|, |green|, |blue| and |alpha| over + // the pixel in |dest|, without premultiplication, and overwrite |dest| with + // the result. + static void BlendRGBARaw(PixelData* dest, + unsigned red, + unsigned green, + unsigned blue, + unsigned alpha); + + // Blend the pixel, without premultiplication, in |src| over |dst| and + // overwrite |src| with the result. + static void BlendSrcOverDstRaw(PixelData* src, PixelData dst); + + // Blend the RGBA pixel provided by |r|, |g|, |b|, |a| over the pixel in + // |dest| and overwrite |dest| with the result. Premultiply the pixel values + // before blending. + static inline void BlendRGBAPremultiplied(PixelData* dest, + unsigned r, + unsigned g, + unsigned b, + unsigned a) { + // If the new pixel is completely transparent, no operation is necessary + // since |dest| contains the background pixel. + if (a == 0x0) + return; + + // If the new pixel is opaque, no need for blending - just write the pixel. + if (a == 0xFF) { + SetRGBAPremultiply(dest, r, g, b, a); + return; + } + + PixelData src; + SetRGBAPremultiply(&src, r, g, b, a); + *dest = SkPMSrcOver(src, *dest); + } + + // Blend the pixel in |src| over |dst| and overwrite |src| with the result. + static inline void BlendSrcOverDstPremultiplied(PixelData* src, + PixelData dst) { + *src = SkPMSrcOver(*src, dst); + } + + // Notifies the SkBitmap if any pixels changed and resets the flag. + inline void NotifyBitmapIfPixelsChanged() { + if (pixels_changed_) + bitmap_.notifyPixelsChanged(); + pixels_changed_ = false; + } + + private: + int Width() const { return bitmap_.width(); } + + int Height() const { return bitmap_.height(); } + + SkAlphaType ComputeAlphaType() const; + + SkBitmap bitmap_; + SkBitmap::Allocator* allocator_; + bool has_alpha_; + // This will always just be the entire buffer except for GIF or WebP + // frames whose original rect was smaller than the overall image size. + IntRect original_frame_rect_; + Status status_; + TimeDelta duration_; + DisposalMethod disposal_method_; + AlphaBlendSource alpha_blend_source_; + bool premultiply_alpha_; + // True if the pixels changed, but the bitmap has not yet been notified. + bool pixels_changed_; + + // The frame that must be decoded before this frame can be decoded. + // WTF::kNotFound if this frame doesn't require any previous frame. + // This is used by ImageDecoder::ClearCacheExceptFrame(), and will never + // be read for image formats that do not have multiple frames. + size_t required_previous_frame_index_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder.cc b/chromium/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder.cc new file mode 100644 index 00000000000..f3e4416d756 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder.cc @@ -0,0 +1,1074 @@ +/* + * 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 <memory> +#include "build/build_config.h" +#include "third_party/blink/renderer/platform/instrumentation/platform_instrumentation.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_denomiator = 8; + +} // 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 +}; + +enum yuv_subsampling { + YUV_UNKNOWN, + YUV_410, + YUV_411, + YUV_420, + YUV_422, + YUV_440, + YUV_444 +}; + +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 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; +} + +static ImageOrientation ReadImageOrientation(jpeg_decompress_struct* info) { + // The JPEG decoder looks at EXIF metadata. + // FIXME: Possibly implement XMP and IPTC support. + const unsigned kOrientationTag = 0x112; + const unsigned kShortType = 3; + 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; + ifd_offset += kOffsetToTiffData; + + // 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* ifd = marker->data + ifd_offset; + JOCTET* end = marker->data + marker->data_length; + if (end - ifd < 2) + continue; + unsigned tag_count = ReadUint16(ifd, is_big_endian); + ifd += 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 && 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); + if (tag == kOrientationTag && type == kShortType && count == 1) + return ImageOrientation::FromEXIFValue( + ReadUint16(ifd + 8, is_big_endian)); + } + } + + return ImageOrientation(); +} + +static IntSize ComputeYUVSize(const jpeg_decompress_struct* info, + int component) { + return IntSize(info->cur_comp_info[component]->downsampled_width, + info->cur_comp_info[component]->downsampled_height); +} + +static size_t ComputeYUVWidthBytes(const jpeg_decompress_struct* info, + int component) { + return info->cur_comp_info[component]->width_in_blocks * DCTSIZE; +} + +static yuv_subsampling YuvSubsampling(const jpeg_decompress_struct& info) { + if ((DCTSIZE == 8) && (info.num_components == 3) && (info.scale_denom <= 8) && + (info.cur_comp_info[0]) && (info.cur_comp_info[1]) && + (info.cur_comp_info[2]) && (info.cur_comp_info[1]->h_samp_factor == 1) && + (info.cur_comp_info[1]->v_samp_factor == 1) && + (info.cur_comp_info[2]->h_samp_factor == 1) && + (info.cur_comp_info[2]->v_samp_factor == 1)) { + int h = info.cur_comp_info[0]->h_samp_factor; + int v = info.cur_comp_info[0]->v_samp_factor; + // 4:4:4 : (h == 1) && (v == 1) + // 4:4:0 : (h == 1) && (v == 2) + // 4:2:2 : (h == 2) && (v == 1) + // 4:2:0 : (h == 2) && (v == 2) + // 4:1:1 : (h == 4) && (v == 1) + // 4:1:0 : (h == 4) && (v == 2) + if (v == 1) { + switch (h) { + case 1: + return YUV_444; + case 2: + return YUV_422; + case 4: + return YUV_411; + default: + break; + } + } else if (v == 2) { + switch (h) { + case 1: + return YUV_440; + case 2: + return YUV_420; + case 4: + return YUV_410; + default: + break; + } + } + } + + return YUV_UNKNOWN; +} + +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); + WTF_MAKE_NONCOPYABLE(JPEGImageReader); + + public: + JPEGImageReader(JPEGImageDecoder* decoder) + : decoder_(decoder), + needs_restart_(false), + restart_position_(0), + next_read_position_(0), + 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(); + } + + // Decode the JPEG data. If |only_size| is specified, then only the size + // information will be decoded. + bool Decode(bool only_size) { + // We need to do the setjmp here. Otherwise bad things will happen + if (setjmp(err_.setjmp_buffer)) + return decoder_->SetFailed(); + + J_COLOR_SPACE override_color_space = JCS_UNKNOWN; + 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: + // libjpeg can convert YCbCr image pixels to RGB. + info_.out_color_space = rgbOutputColorSpace(); + if (decoder_->HasImagePlanes() && + (YuvSubsampling(info_) != YUV_UNKNOWN)) + override_color_space = JCS_YCbCr; + break; + case JCS_GRAYSCALE: + case JCS_RGB: + // libjpeg can convert GRAYSCALE image pixels to 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_denomiator; + + if (decoder_->ShouldGenerateAllSizes()) { + std::vector<SkISize> sizes; + sizes.reserve(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; + // Scaling caused by running low on memory isn't supported by YUV + // decoding since YUV decoding is performed on full sized images. At + // this point, buffers and various image info structs have already been + // set up for the scaled size after reading the image header using this + // decoder, so using the full size is no longer possible. + if (info_.scale_num != info_.scale_denom) + override_color_space = JCS_UNKNOWN; + jpeg_calc_output_dimensions(&info_); + decoder_->SetDecodedSize(info_.output_width, info_.output_height); + + decoder_->SetOrientation(ReadImageOrientation(Info())); + + // Allow color management of the decoded RGBA pixels if possible. + if (!decoder_->IgnoresColorSpace()) { + JOCTET* profile = nullptr; + unsigned profile_length = 0; + if (read_icc_profile(Info(), &profile, &profile_length)) { + sk_sp<SkColorSpace> color_space = + SkColorSpace::MakeICC(profile, profile_length); + if (color_space) { + const SkColorSpace::Type type = color_space->type(); + switch (info_.jpeg_color_space) { + case JCS_CMYK: + case JCS_YCCK: + if (type != SkColorSpace::kCMYK_Type) + color_space = nullptr; + break; + case JCS_GRAYSCALE: + if (type != SkColorSpace::kGray_Type && + type != SkColorSpace::kRGB_Type) + color_space = nullptr; + break; + default: + if (type != SkColorSpace::kRGB_Type) + color_space = nullptr; + break; + } + Decoder()->SetEmbeddedColorSpace(std::move(color_space)); + } else { + DLOG(ERROR) << "Failed to parse image ICC profile"; + } + free(profile); + } + if (Decoder()->ColorTransform()) { + override_color_space = JCS_UNKNOWN; + } + } + if (override_color_space == JCS_YCbCr) { + 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 + } + + // 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 (only_size) { + // 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: + // 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) { + int status = 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_); + } while ((status != JPEG_SUSPENDED) && (status != JPEG_REACHED_EOI)); + + 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. + if (!info_.output_scan_number && (scan > 1) && + (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. + 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: + 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_; +}; + +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) + : ImageDecoder(alpha_option, color_behavior, max_decoded_bytes) {} + +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); +} + +void JPEGImageDecoder::SetDecodedSize(unsigned width, unsigned height) { + decoded_size_ = IntSize(width, height); +} + +IntSize JPEGImageDecoder::DecodedYUVSize(int component) const { + DCHECK_GE(component, 0); + DCHECK_LE(component, 2); + DCHECK(reader_); + const jpeg_decompress_struct* info = reader_->Info(); + + DCHECK_EQ(info->out_color_space, JCS_YCbCr); + return ComputeYUVSize(info, component); +} + +size_t JPEGImageDecoder::DecodedYUVWidthBytes(int component) const { + DCHECK_GE(component, 0); + DCHECK_LE(component, 2); + DCHECK(reader_); + const jpeg_decompress_struct* info = reader_->Info(); + + DCHECK_EQ(info->out_color_space, JCS_YCbCr); + return ComputeYUVWidthBytes(info, component); +} + +unsigned JPEGImageDecoder::DesiredScaleNumerator() const { + size_t original_bytes = Size().Width() * Size().Height() * 4; + + if (original_bytes <= max_decoded_bytes_) + return g_scale_denomiator; + + // 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_denomiator * + g_scale_denomiator / original_bytes)))); + + return scale_numerator; +} + +bool JPEGImageDecoder::ShouldGenerateAllSizes() const { + return supported_decode_sizes_.empty(); +} + +bool JPEGImageDecoder::CanDecodeToYUV() { + // Calling IsSizeAvailable() ensures the reader is created and the output + // color space is set. + return IsSizeAvailable() && reader_->Info()->out_color_space == JCS_YCbCr; +} + +bool JPEGImageDecoder::DecodeToYUV() { + if (!HasImagePlanes()) + return false; + + PlatformInstrumentation::WillDecodeImage("JPEG"); + Decode(false); + PlatformInstrumentation::DidDecodeImage(); + return !Failed(); +} + +void JPEGImageDecoder::SetImagePlanes( + std::unique_ptr<ImagePlanes> image_planes) { + image_planes_ = std::move(image_planes); +} + +void JPEGImageDecoder::SetSupportedDecodeSizes(std::vector<SkISize> sizes) { + supported_decode_sizes_ = std::move(sizes); +} + +std::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_; +} + +// 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); + + SkColorSpaceXform* xform = reader->Decoder()->ColorTransform(); + if (xform) { + ImageFrame::PixelData* row = buffer.GetAddr(0, y); + bool color_converison_successful = + xform->apply(XformColorFormat(), row, XformColorFormat(), row, width, + kOpaque_SkAlphaType); + DCHECK(color_converison_successful); + } + } + + buffer.SetPixelsChanged(true); + return true; +} + +static bool OutputRawData(JPEGImageReader* reader, ImagePlanes* image_planes) { + JSAMPARRAY samples = reader->Samples(); + jpeg_decompress_struct* info = reader->Info(); + + 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(0)); + JSAMPROW output_u = static_cast<JSAMPROW>(image_planes->Plane(1)); + JSAMPROW output_v = static_cast<JSAMPROW>(image_planes->Plane(2)); + size_t row_bytes_y = image_planes->RowBytes(0); + size_t row_bytes_u = image_planes->RowBytes(1); + size_t row_bytes_v = image_planes->RowBytes(2); + + // 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); + 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; + + SkColorSpaceXform* xform = ColorTransform(); + if (xform) { + bool color_converison_successful = + xform->apply(XformColorFormat(), row, XformColorFormat(), row, + info->output_width, kOpaque_SkAlphaType); + DCHECK(color_converison_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, bool only_size) { + if (decoder->HasImagePlanes() && !only_size) + return true; + + return decoder->FrameIsDecodedAtIndex(0); +} + +void JPEGImageDecoder::Decode(bool only_size) { + if (Failed()) + return; + + if (!reader_) { + reader_ = std::make_unique<JPEGImageReader>(this); + reader_->SetData(data_.get()); + } + + // If we couldn't decode the image but have received all the data, decoding + // has failed. + if (!reader_->Decode(only_size) && IsAllDataReceived()) + SetFailed(); + + // If decoding is done or failed, we don't need the JPEGImageReader anymore. + if (IsComplete(this, only_size) || Failed()) + reader_.reset(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder.h b/chromium/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder.h new file mode 100644 index 00000000000..b721bd8cec0 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_JPEG_JPEG_IMAGE_DECODER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_JPEG_JPEG_IMAGE_DECODER_H_ + +#include <memory> +#include "third_party/blink/renderer/platform/image-decoders/image_decoder.h" + +namespace blink { + +class JPEGImageReader; + +class PLATFORM_EXPORT JPEGImageDecoder final : public ImageDecoder { + WTF_MAKE_NONCOPYABLE(JPEGImageDecoder); + + public: + JPEGImageDecoder(AlphaOption, const ColorBehavior&, size_t max_decoded_bytes); + ~JPEGImageDecoder() override; + + // ImageDecoder: + String FilenameExtension() const override { return "jpg"; } + void OnSetData(SegmentReader* data) override; + IntSize DecodedSize() const override { return decoded_size_; } + bool SetSize(unsigned width, unsigned height) override; + IntSize DecodedYUVSize(int component) const override; + size_t DecodedYUVWidthBytes(int component) const override; + bool CanDecodeToYUV() override; + bool DecodeToYUV() override; + void SetImagePlanes(std::unique_ptr<ImagePlanes>) override; + std::vector<SkISize> GetSupportedDecodeSizes() const override; + bool HasImagePlanes() const { return image_planes_.get(); } + + bool OutputScanlines(); + unsigned DesiredScaleNumerator() const; + bool ShouldGenerateAllSizes() const; + void Complete(); + + void SetOrientation(ImageOrientation orientation) { + orientation_ = orientation; + } + void SetDecodedSize(unsigned width, unsigned height); + + void SetSupportedDecodeSizes(std::vector<SkISize> sizes); + + private: + // ImageDecoder: + void DecodeSize() override { Decode(true); } + void Decode(size_t) override { Decode(false); } + + // Decodes the image. If |only_size| is true, stops decoding after + // calculating the image size. If decoding fails but there is no more + // data coming, sets the "decode failure" flag. + void Decode(bool only_size); + + std::unique_ptr<JPEGImageReader> reader_; + std::unique_ptr<ImagePlanes> image_planes_; + IntSize decoded_size_; + std::vector<SkISize> supported_decode_sizes_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder_test.cc b/chromium/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder_test.cc new file mode 100644 index 00000000000..bbc8ebf1cfa --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder_test.cc @@ -0,0 +1,387 @@ +/* + * Copyright (C) 2013 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder.h" + +#include <limits> +#include <memory> +#include <utility> +#include <vector> + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/platform/web_data.h" +#include "third_party/blink/public/platform/web_size.h" +#include "third_party/blink/renderer/platform/image-decoders/image_animation.h" +#include "third_party/blink/renderer/platform/image-decoders/image_decoder_test_helpers.h" +#include "third_party/blink/renderer/platform/shared_buffer.h" +#include "third_party/blink/renderer/platform/wtf/typed_arrays/array_buffer.h" + +namespace blink { + +static const size_t kLargeEnoughSize = 1000 * 1000; + +namespace { + +std::unique_ptr<ImageDecoder> CreateJPEGDecoder(size_t max_decoded_bytes) { + return std::make_unique<JPEGImageDecoder>( + ImageDecoder::kAlphaNotPremultiplied, ColorBehavior::TransformToSRGB(), + max_decoded_bytes); +} + +std::unique_ptr<ImageDecoder> CreateJPEGDecoder() { + return CreateJPEGDecoder(ImageDecoder::kNoDecodedImageByteLimit); +} + +} // anonymous namespace + +void Downsample(size_t max_decoded_bytes, + unsigned* output_width, + unsigned* output_height, + const char* image_file_path) { + scoped_refptr<SharedBuffer> data = ReadFile(image_file_path); + ASSERT_TRUE(data); + + std::unique_ptr<ImageDecoder> decoder = CreateJPEGDecoder(max_decoded_bytes); + decoder->SetData(data.get(), true); + + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0); + ASSERT_TRUE(frame); + *output_width = frame->Bitmap().width(); + *output_height = frame->Bitmap().height(); + EXPECT_EQ(IntSize(*output_width, *output_height), decoder->DecodedSize()); +} + +void ReadYUV(size_t max_decoded_bytes, + unsigned* output_y_width, + unsigned* output_y_height, + unsigned* output_uv_width, + unsigned* output_uv_height, + const char* image_file_path) { + scoped_refptr<SharedBuffer> data = ReadFile(image_file_path); + ASSERT_TRUE(data); + + std::unique_ptr<ImageDecoder> decoder = CreateJPEGDecoder(max_decoded_bytes); + decoder->SetData(data.get(), true); + + // Setting a dummy ImagePlanes object signals to the decoder that we want to + // do YUV decoding. + std::unique_ptr<ImagePlanes> dummy_image_planes = + std::make_unique<ImagePlanes>(); + decoder->SetImagePlanes(std::move(dummy_image_planes)); + + bool size_is_available = decoder->IsSizeAvailable(); + ASSERT_TRUE(size_is_available); + + IntSize size = decoder->DecodedSize(); + IntSize y_size = decoder->DecodedYUVSize(0); + IntSize u_size = decoder->DecodedYUVSize(1); + IntSize v_size = decoder->DecodedYUVSize(2); + + ASSERT_TRUE(size.Width() == y_size.Width()); + ASSERT_TRUE(size.Height() == y_size.Height()); + ASSERT_TRUE(u_size.Width() == v_size.Width()); + ASSERT_TRUE(u_size.Height() == v_size.Height()); + + *output_y_width = y_size.Width(); + *output_y_height = y_size.Height(); + *output_uv_width = u_size.Width(); + *output_uv_height = u_size.Height(); + + size_t row_bytes[3]; + row_bytes[0] = decoder->DecodedYUVWidthBytes(0); + row_bytes[1] = decoder->DecodedYUVWidthBytes(1); + row_bytes[2] = decoder->DecodedYUVWidthBytes(2); + + scoped_refptr<ArrayBuffer> buffer(ArrayBuffer::Create( + row_bytes[0] * y_size.Height() + row_bytes[1] * u_size.Height() + + row_bytes[2] * v_size.Height(), + 1)); + void* planes[3]; + planes[0] = buffer->Data(); + planes[1] = ((char*)planes[0]) + row_bytes[0] * y_size.Height(); + planes[2] = ((char*)planes[1]) + row_bytes[1] * u_size.Height(); + + std::unique_ptr<ImagePlanes> image_planes = + std::make_unique<ImagePlanes>(planes, row_bytes); + decoder->SetImagePlanes(std::move(image_planes)); + + ASSERT_TRUE(decoder->DecodeToYUV()); +} + +// Tests failure on a too big image. +TEST(JPEGImageDecoderTest, tooBig) { + std::unique_ptr<ImageDecoder> decoder = CreateJPEGDecoder(100); + EXPECT_FALSE(decoder->SetSize(10000, 10000)); + EXPECT_TRUE(decoder->Failed()); +} + +// Tests that the JPEG decoder can downsample image whose width and height are +// multiples of 8, to ensure we compute the correct DecodedSize and pass correct +// parameters to libjpeg to output the image with the expected size. +TEST(JPEGImageDecoderTest, downsampleImageSizeMultipleOf8) { + const char* jpeg_file = "/images/resources/lenna.jpg"; // 256x256 + unsigned output_width, output_height; + + // 1/8 downsample. + Downsample(40 * 40 * 4, &output_width, &output_height, jpeg_file); + EXPECT_EQ(32u, output_width); + EXPECT_EQ(32u, output_height); + + // 2/8 downsample. + Downsample(70 * 70 * 4, &output_width, &output_height, jpeg_file); + EXPECT_EQ(64u, output_width); + EXPECT_EQ(64u, output_height); + + // 3/8 downsample. + Downsample(100 * 100 * 4, &output_width, &output_height, jpeg_file); + EXPECT_EQ(96u, output_width); + EXPECT_EQ(96u, output_height); + + // 4/8 downsample. + Downsample(130 * 130 * 4, &output_width, &output_height, jpeg_file); + EXPECT_EQ(128u, output_width); + EXPECT_EQ(128u, output_height); + + // 5/8 downsample. + Downsample(170 * 170 * 4, &output_width, &output_height, jpeg_file); + EXPECT_EQ(160u, output_width); + EXPECT_EQ(160u, output_height); + + // 6/8 downsample. + Downsample(200 * 200 * 4, &output_width, &output_height, jpeg_file); + EXPECT_EQ(192u, output_width); + EXPECT_EQ(192u, output_height); + + // 7/8 downsample. + Downsample(230 * 230 * 4, &output_width, &output_height, jpeg_file); + EXPECT_EQ(224u, output_width); + EXPECT_EQ(224u, output_height); +} + +// Tests that JPEG decoder can downsample image whose width and height are not +// multiple of 8. Ensures that we round using the same algorithm as libjpeg. +TEST(JPEGImageDecoderTest, downsampleImageSizeNotMultipleOf8) { + const char* jpeg_file = "/images/resources/icc-v2-gbr.jpg"; // 275x207 + unsigned output_width, output_height; + + // 1/8 downsample. + Downsample(40 * 40 * 4, &output_width, &output_height, jpeg_file); + EXPECT_EQ(35u, output_width); + EXPECT_EQ(26u, output_height); + + // 2/8 downsample. + Downsample(70 * 70 * 4, &output_width, &output_height, jpeg_file); + EXPECT_EQ(69u, output_width); + EXPECT_EQ(52u, output_height); + + // 3/8 downsample. + Downsample(100 * 100 * 4, &output_width, &output_height, jpeg_file); + EXPECT_EQ(104u, output_width); + EXPECT_EQ(78u, output_height); + + // 4/8 downsample. + Downsample(130 * 130 * 4, &output_width, &output_height, jpeg_file); + EXPECT_EQ(138u, output_width); + EXPECT_EQ(104u, output_height); + + // 5/8 downsample. + Downsample(170 * 170 * 4, &output_width, &output_height, jpeg_file); + EXPECT_EQ(172u, output_width); + EXPECT_EQ(130u, output_height); + + // 6/8 downsample. + Downsample(200 * 200 * 4, &output_width, &output_height, jpeg_file); + EXPECT_EQ(207u, output_width); + EXPECT_EQ(156u, output_height); + + // 7/8 downsample. + Downsample(230 * 230 * 4, &output_width, &output_height, jpeg_file); + EXPECT_EQ(241u, output_width); + EXPECT_EQ(182u, output_height); +} + +// Tests that upsampling is not allowed. +TEST(JPEGImageDecoderTest, upsample) { + const char* jpeg_file = "/images/resources/lenna.jpg"; // 256x256 + unsigned output_width, output_height; + Downsample(kLargeEnoughSize, &output_width, &output_height, jpeg_file); + EXPECT_EQ(256u, output_width); + EXPECT_EQ(256u, output_height); +} + +TEST(JPEGImageDecoderTest, yuv) { + const char* jpeg_file = "/images/resources/lenna.jpg"; // 256x256, YUV 4:2:0 + unsigned output_y_width, output_y_height, output_uv_width, output_uv_height; + ReadYUV(kLargeEnoughSize, &output_y_width, &output_y_height, &output_uv_width, + &output_uv_height, jpeg_file); + EXPECT_EQ(256u, output_y_width); + EXPECT_EQ(256u, output_y_height); + EXPECT_EQ(128u, output_uv_width); + EXPECT_EQ(128u, output_uv_height); + + const char* jpeg_file_image_size_not_multiple_of8 = + "/images/resources/cropped_mandrill.jpg"; // 439x154 + ReadYUV(kLargeEnoughSize, &output_y_width, &output_y_height, &output_uv_width, + &output_uv_height, jpeg_file_image_size_not_multiple_of8); + EXPECT_EQ(439u, output_y_width); + EXPECT_EQ(154u, output_y_height); + EXPECT_EQ(220u, output_uv_width); + EXPECT_EQ(77u, output_uv_height); + + // Make sure we revert to RGBA decoding when we're about to downscale, + // which can occur on memory-constrained android devices. + scoped_refptr<SharedBuffer> data = ReadFile(jpeg_file); + ASSERT_TRUE(data); + + std::unique_ptr<ImageDecoder> decoder = CreateJPEGDecoder(230 * 230 * 4); + decoder->SetData(data.get(), true); + + std::unique_ptr<ImagePlanes> image_planes = std::make_unique<ImagePlanes>(); + decoder->SetImagePlanes(std::move(image_planes)); + ASSERT_TRUE(decoder->IsSizeAvailable()); + ASSERT_FALSE(decoder->CanDecodeToYUV()); +} + +TEST(JPEGImageDecoderTest, + byteByByteBaselineJPEGWithColorProfileAndRestartMarkers) { + TestByteByByteDecode(&CreateJPEGDecoder, + "/images/resources/" + "small-square-with-colorspin-profile.jpg", + 1u, kAnimationNone); +} + +TEST(JPEGImageDecoderTest, byteByByteProgressiveJPEG) { + TestByteByByteDecode(&CreateJPEGDecoder, "/images/resources/bug106024.jpg", + 1u, kAnimationNone); +} + +TEST(JPEGImageDecoderTest, byteByByteRGBJPEGWithAdobeMarkers) { + TestByteByByteDecode(&CreateJPEGDecoder, + "/images/resources/rgb-jpeg-with-adobe-marker-only.jpg", + 1u, kAnimationNone); +} + +// This test verifies that calling SharedBuffer::MergeSegmentsIntoBuffer() does +// not break JPEG decoding at a critical point: in between a call to decode the +// size (when JPEGImageDecoder stops while it may still have input data to +// read) and a call to do a full decode. +TEST(JPEGImageDecoderTest, mergeBuffer) { + const char* jpeg_file = "/images/resources/lenna.jpg"; + TestMergeBuffer(&CreateJPEGDecoder, jpeg_file); +} + +// This tests decoding a JPEG with many progressive scans. Decoding should +// fail, but not hang (crbug.com/642462). +TEST(JPEGImageDecoderTest, manyProgressiveScans) { + scoped_refptr<SharedBuffer> test_data = + ReadFile(kDecodersTestingDir, "many-progressive-scans.jpg"); + ASSERT_TRUE(test_data.get()); + + std::unique_ptr<ImageDecoder> test_decoder = CreateJPEGDecoder(); + test_decoder->SetData(test_data.get(), true); + EXPECT_EQ(1u, test_decoder->FrameCount()); + ASSERT_TRUE(test_decoder->DecodeFrameBufferAtIndex(0)); + EXPECT_TRUE(test_decoder->Failed()); +} + +TEST(JPEGImageDecoderTest, SupportedSizesSquare) { + const char* jpeg_file = "/images/resources/lenna.jpg"; // 256x256 + scoped_refptr<SharedBuffer> data = ReadFile(jpeg_file); + ASSERT_TRUE(data); + + std::unique_ptr<ImageDecoder> decoder = + CreateJPEGDecoder(std::numeric_limits<int>::max()); + decoder->SetData(data.get(), true); + // This will decode the size and needs to be called to avoid DCHECKs + ASSERT_TRUE(decoder->IsSizeAvailable()); + std::vector<SkISize> expected_sizes = { + SkISize::Make(32, 32), SkISize::Make(64, 64), SkISize::Make(96, 96), + SkISize::Make(128, 128), SkISize::Make(160, 160), SkISize::Make(192, 192), + SkISize::Make(224, 224), SkISize::Make(256, 256)}; + auto sizes = decoder->GetSupportedDecodeSizes(); + ASSERT_EQ(expected_sizes.size(), sizes.size()); + for (size_t i = 0; i < sizes.size(); ++i) { + EXPECT_TRUE(expected_sizes[i] == sizes[i]) + << "Expected " << expected_sizes[i].width() << "x" + << expected_sizes[i].height() << ". Got " << sizes[i].width() << "x" + << sizes[i].height(); + } +} + +TEST(JPEGImageDecoderTest, SupportedSizesRectangle) { + const char* jpeg_file = "/images/resources/icc-v2-gbr.jpg"; // 275x207 + + scoped_refptr<SharedBuffer> data = ReadFile(jpeg_file); + ASSERT_TRUE(data); + + std::unique_ptr<ImageDecoder> decoder = + CreateJPEGDecoder(std::numeric_limits<int>::max()); + decoder->SetData(data.get(), true); + // This will decode the size and needs to be called to avoid DCHECKs + ASSERT_TRUE(decoder->IsSizeAvailable()); + std::vector<SkISize> expected_sizes = { + SkISize::Make(35, 26), SkISize::Make(69, 52), SkISize::Make(104, 78), + SkISize::Make(138, 104), SkISize::Make(172, 130), SkISize::Make(207, 156), + SkISize::Make(241, 182), SkISize::Make(275, 207)}; + + auto sizes = decoder->GetSupportedDecodeSizes(); + ASSERT_EQ(expected_sizes.size(), sizes.size()); + for (size_t i = 0; i < sizes.size(); ++i) { + EXPECT_TRUE(expected_sizes[i] == sizes[i]) + << "Expected " << expected_sizes[i].width() << "x" + << expected_sizes[i].height() << ". Got " << sizes[i].width() << "x" + << sizes[i].height(); + } +} + +TEST(JPEGImageDecoderTest, SupportedSizesTruncatedIfMemoryBound) { + const char* jpeg_file = "/images/resources/lenna.jpg"; // 256x256 + scoped_refptr<SharedBuffer> data = ReadFile(jpeg_file); + ASSERT_TRUE(data); + + // Limit the memory so that 128 would be the largest size possible. + std::unique_ptr<ImageDecoder> decoder = CreateJPEGDecoder(130 * 130 * 4); + decoder->SetData(data.get(), true); + // This will decode the size and needs to be called to avoid DCHECKs + ASSERT_TRUE(decoder->IsSizeAvailable()); + std::vector<SkISize> expected_sizes = { + SkISize::Make(32, 32), SkISize::Make(64, 64), SkISize::Make(96, 96), + SkISize::Make(128, 128)}; + auto sizes = decoder->GetSupportedDecodeSizes(); + ASSERT_EQ(expected_sizes.size(), sizes.size()); + for (size_t i = 0; i < sizes.size(); ++i) { + EXPECT_TRUE(expected_sizes[i] == sizes[i]) + << "Expected " << expected_sizes[i].width() << "x" + << expected_sizes[i].height() << ". Got " << sizes[i].width() << "x" + << sizes[i].height(); + } +} + +} // namespace blink 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 diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.h b/chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.h new file mode 100644 index 00000000000..dc39091673b --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_PNG_PNG_IMAGE_DECODER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_PNG_PNG_IMAGE_DECODER_H_ + +#include <memory> +#include "third_party/blink/renderer/platform/image-decoders/image_decoder.h" +#include "third_party/blink/renderer/platform/image-decoders/png/png_image_reader.h" +#include "third_party/blink/renderer/platform/wtf/time.h" + +namespace blink { + +class PLATFORM_EXPORT PNGImageDecoder final : public ImageDecoder { + WTF_MAKE_NONCOPYABLE(PNGImageDecoder); + + public: + PNGImageDecoder(AlphaOption, + const ColorBehavior&, + size_t max_decoded_bytes, + size_t offset = 0); + ~PNGImageDecoder() override; + + // ImageDecoder: + String FilenameExtension() const override { return "png"; } + bool SetSize(unsigned, unsigned) override; + int RepetitionCount() const override; + bool FrameIsReceivedAtIndex(size_t) const override; + TimeDelta FrameDurationAtIndex(size_t) const override; + bool SetFailed() override; + + // Callbacks from libpng + void HeaderAvailable(); + void RowAvailable(unsigned char* row, unsigned row_index, int); + void FrameComplete(); + + void SetColorSpace(); + void SetRepetitionCount(int); + + private: + using ParseQuery = PNGImageReader::ParseQuery; + + // ImageDecoder: + void DecodeSize() override { Parse(ParseQuery::kSize); } + void Decode(size_t) override; + void Parse(ParseQuery); + size_t DecodeFrameCount() override; + void InitializeNewFrame(size_t) override; + void ClearFrameBuffer(size_t) override; + bool CanReusePreviousFrameBuffer(size_t) const override; + + std::unique_ptr<PNGImageReader> reader_; + const unsigned offset_; + size_t current_frame_; + int repetition_count_; + bool has_alpha_channel_; + bool current_buffer_saw_alpha_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder_test.cc b/chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder_test.cc new file mode 100644 index 00000000000..00daa8a2d8f --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder_test.cc @@ -0,0 +1,1084 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.h" + +#include <memory> +#include "png.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/platform/image-decoders/image_decoder_test_helpers.h" +#include "third_party/blink/renderer/platform/wtf/time.h" + +// /LayoutTests/images/resources/png-animated-idat-part-of-animation.png +// is modified in multiple tests to simulate erroneous PNGs. As a reference, +// the table below shows how the file is structured. +// +// Offset | 8 33 95 133 172 210 241 279 314 352 422 +// ------------------------------------------------------------------------- +// Chunk | IHDR acTL fcTL IDAT fcTL fdAT fcTL fdAT fcTL fdAT IEND +// +// In between the acTL and fcTL there are two other chunks, PLTE and tRNS, but +// those are not specifically used in this test suite. The same holds for a +// tEXT chunk in between the last fdAT and IEND. +// +// In the current behavior of PNG image decoders, the 4 frames are detected when +// respectively 141, 249, 322 and 430 bytes are received. The first frame should +// be detected when the IDAT has been received, and non-first frames when the +// next fcTL or IEND chunk has been received. Note that all offsets are +8, +// because a chunk is identified by byte 4-7. + +namespace blink { + +namespace { + +std::unique_ptr<ImageDecoder> CreatePNGDecoder( + ImageDecoder::AlphaOption alpha_option) { + return std::make_unique<PNGImageDecoder>( + alpha_option, ColorBehavior::TransformToSRGB(), + ImageDecoder::kNoDecodedImageByteLimit); +} + +std::unique_ptr<ImageDecoder> CreatePNGDecoder() { + return CreatePNGDecoder(ImageDecoder::kAlphaNotPremultiplied); +} + +std::unique_ptr<ImageDecoder> CreatePNGDecoderWithPngData( + const char* png_file) { + auto decoder = CreatePNGDecoder(); + scoped_refptr<SharedBuffer> data = ReadFile(png_file); + EXPECT_FALSE(data->IsEmpty()); + decoder->SetData(data.get(), true); + return decoder; +} + +void TestSize(const char* png_file, IntSize expected_size) { + auto decoder = CreatePNGDecoderWithPngData(png_file); + EXPECT_TRUE(decoder->IsSizeAvailable()); + EXPECT_EQ(expected_size, decoder->Size()); +} + +// Test whether querying for the size of the image works if we present the +// data byte by byte. +void TestSizeByteByByte(const char* png_file, + size_t bytes_needed_to_decode_size, + IntSize expected_size) { + auto decoder = CreatePNGDecoder(); + scoped_refptr<SharedBuffer> data = ReadFile(png_file); + ASSERT_FALSE(data->IsEmpty()); + ASSERT_LT(bytes_needed_to_decode_size, data->size()); + + const char* source = data->Data(); + scoped_refptr<SharedBuffer> partial_data = SharedBuffer::Create(); + for (size_t length = 1; length <= bytes_needed_to_decode_size; length++) { + partial_data->Append(source++, 1u); + decoder->SetData(partial_data.get(), false); + + if (length < bytes_needed_to_decode_size) { + EXPECT_FALSE(decoder->IsSizeAvailable()); + EXPECT_TRUE(decoder->Size().IsEmpty()); + EXPECT_FALSE(decoder->Failed()); + } else { + EXPECT_TRUE(decoder->IsSizeAvailable()); + EXPECT_EQ(expected_size, decoder->Size()); + } + } + EXPECT_FALSE(decoder->Failed()); +} + +void WriteUint32(uint32_t val, png_byte* data) { + data[0] = val >> 24; + data[1] = val >> 16; + data[2] = val >> 8; + data[3] = val; +} + +void TestRepetitionCount(const char* png_file, int expected_repetition_count) { + auto decoder = CreatePNGDecoderWithPngData(png_file); + // Decoding the frame count sets the number of repetitions as well. + decoder->FrameCount(); + EXPECT_FALSE(decoder->Failed()); + EXPECT_EQ(expected_repetition_count, decoder->RepetitionCount()); +} + +struct PublicFrameInfo { + TimeDelta duration; + IntRect frame_rect; + ImageFrame::AlphaBlendSource alpha_blend; + ImageFrame::DisposalMethod disposal_method; +}; + +// This is the frame data for the following PNG image: +// /LayoutTests/images/resources/png-animated-idat-part-of-animation.png +static PublicFrameInfo g_png_animated_frame_info[] = { + {TimeDelta::FromMilliseconds(500), + {IntPoint(0, 0), IntSize(5, 5)}, + ImageFrame::kBlendAtopBgcolor, + ImageFrame::kDisposeKeep}, + {TimeDelta::FromMilliseconds(900), + {IntPoint(1, 1), IntSize(3, 1)}, + ImageFrame::kBlendAtopBgcolor, + ImageFrame::kDisposeOverwriteBgcolor}, + {TimeDelta::FromMilliseconds(2000), + {IntPoint(1, 2), IntSize(3, 2)}, + ImageFrame::kBlendAtopPreviousFrame, + ImageFrame::kDisposeKeep}, + {TimeDelta::FromMilliseconds(1500), + {IntPoint(1, 2), IntSize(3, 1)}, + ImageFrame::kBlendAtopBgcolor, + ImageFrame::kDisposeKeep}, +}; + +void CompareFrameWithExpectation(const PublicFrameInfo& expected, + ImageDecoder* decoder, + size_t index) { + EXPECT_EQ(expected.duration, decoder->FrameDurationAtIndex(index)); + + const auto* frame = decoder->DecodeFrameBufferAtIndex(index); + ASSERT_TRUE(frame); + + EXPECT_EQ(expected.duration, frame->Duration()); + EXPECT_EQ(expected.frame_rect, frame->OriginalFrameRect()); + EXPECT_EQ(expected.disposal_method, frame->GetDisposalMethod()); + EXPECT_EQ(expected.alpha_blend, frame->GetAlphaBlendSource()); +} + +// This function removes |length| bytes at |offset|, and then calls FrameCount. +// It assumes the missing bytes should result in a failed decode because the +// parser jumps |length| bytes too far in the next chunk. +void TestMissingDataBreaksDecoding(const char* png_file, + size_t offset, + size_t length) { + auto decoder = CreatePNGDecoder(); + scoped_refptr<SharedBuffer> data = ReadFile(png_file); + ASSERT_FALSE(data->IsEmpty()); + + scoped_refptr<SharedBuffer> invalid_data = + SharedBuffer::Create(data->Data(), offset); + invalid_data->Append(data->Data() + offset + length, + data->size() - offset - length); + ASSERT_EQ(data->size() - length, invalid_data->size()); + + decoder->SetData(invalid_data, true); + decoder->FrameCount(); + EXPECT_TRUE(decoder->Failed()); +} + +// Verify that a decoder with a parse error converts to a static image. +static void ExpectStatic(ImageDecoder* decoder) { + EXPECT_EQ(1u, decoder->FrameCount()); + EXPECT_FALSE(decoder->Failed()); + + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0); + ASSERT_NE(nullptr, frame); + EXPECT_EQ(ImageFrame::kFrameComplete, frame->GetStatus()); + EXPECT_FALSE(decoder->Failed()); + EXPECT_EQ(kAnimationNone, decoder->RepetitionCount()); +} + +// Decode up to the indicated fcTL offset and then provide an fcTL with the +// wrong chunk size (20 instead of 26). +void TestInvalidFctlSize(const char* png_file, + size_t offset_fctl, + size_t expected_frame_count, + bool should_fail) { + scoped_refptr<SharedBuffer> data = ReadFile(png_file); + ASSERT_FALSE(data->IsEmpty()); + + auto decoder = CreatePNGDecoder(); + scoped_refptr<SharedBuffer> invalid_data = + SharedBuffer::Create(data->Data(), offset_fctl); + + // Test if this gives the correct frame count, before the fcTL is parsed. + decoder->SetData(invalid_data, false); + EXPECT_EQ(expected_frame_count, decoder->FrameCount()); + ASSERT_FALSE(decoder->Failed()); + + // Append the wrong size to the data stream + png_byte size_chunk[4]; + WriteUint32(20, size_chunk); + invalid_data->Append(reinterpret_cast<char*>(size_chunk), 4u); + + // Skip the size in the original data, but provide a truncated fcTL, + // which is 4B of tag, 20B of data and 4B of CRC, totalling 28B. + invalid_data->Append(data->Data() + offset_fctl + 4, 28u); + // Append the rest of the data + const size_t offset_post_fctl = offset_fctl + 38; + invalid_data->Append(data->Data() + offset_post_fctl, + data->size() - offset_post_fctl); + + decoder->SetData(invalid_data, false); + if (should_fail) { + EXPECT_EQ(expected_frame_count, decoder->FrameCount()); + EXPECT_EQ(true, decoder->Failed()); + } else { + ExpectStatic(decoder.get()); + } +} + +// Verify that the decoder can successfully decode the first frame when +// initially only half of the frame data is received, resulting in a partially +// decoded image, and then the rest of the image data is received. Verify that +// the bitmap hashes of the two stages are different. Also verify that the final +// bitmap hash is equivalent to the hash when all data is provided at once. +// +// This verifies that the decoder correctly keeps track of where it stopped +// decoding when the image was not yet fully received. +void TestProgressiveDecodingContinuesAfterFullData( + const char* png_file, + size_t offset_mid_first_frame) { + scoped_refptr<SharedBuffer> full_data = ReadFile(png_file); + ASSERT_FALSE(full_data->IsEmpty()); + + auto decoder_upfront = CreatePNGDecoder(); + decoder_upfront->SetData(full_data.get(), true); + EXPECT_GE(decoder_upfront->FrameCount(), 1u); + const ImageFrame* const frame_upfront = + decoder_upfront->DecodeFrameBufferAtIndex(0); + ASSERT_EQ(ImageFrame::kFrameComplete, frame_upfront->GetStatus()); + const unsigned hash_upfront = HashBitmap(frame_upfront->Bitmap()); + + auto decoder = CreatePNGDecoder(); + scoped_refptr<SharedBuffer> partial_data = + SharedBuffer::Create(full_data->Data(), offset_mid_first_frame); + decoder->SetData(partial_data, false); + + EXPECT_EQ(1u, decoder->FrameCount()); + const ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0); + EXPECT_EQ(frame->GetStatus(), ImageFrame::kFramePartial); + const unsigned hash_partial = HashBitmap(frame->Bitmap()); + + decoder->SetData(full_data.get(), true); + frame = decoder->DecodeFrameBufferAtIndex(0); + EXPECT_EQ(frame->GetStatus(), ImageFrame::kFrameComplete); + const unsigned hash_full = HashBitmap(frame->Bitmap()); + + EXPECT_FALSE(decoder->Failed()); + EXPECT_NE(hash_full, hash_partial); + EXPECT_EQ(hash_full, hash_upfront); +} + +} // Anonymous namespace + +// Animated PNG Tests + +TEST(AnimatedPNGTests, sizeTest) { + TestSize( + "/images/resources/" + "png-animated-idat-part-of-animation.png", + IntSize(5, 5)); + TestSize( + "/images/resources/" + "png-animated-idat-not-part-of-animation.png", + IntSize(227, 35)); +} + +TEST(AnimatedPNGTests, repetitionCountTest) { + TestRepetitionCount( + "/images/resources/" + "png-animated-idat-part-of-animation.png", + 6u); + // This is an "animated" image with only one frame, that is, the IDAT is + // ignored and there is one fdAT frame. so it should be considered + // non-animated. + TestRepetitionCount( + "/images/resources/" + "png-animated-idat-not-part-of-animation.png", + kAnimationNone); +} + +// Test if the decoded metdata corresponds to the defined expectations +TEST(AnimatedPNGTests, MetaDataTest) { + const char* png_file = + "/images/resources/" + "png-animated-idat-part-of-animation.png"; + constexpr size_t kExpectedFrameCount = 4; + + auto decoder = CreatePNGDecoderWithPngData(png_file); + ASSERT_EQ(kExpectedFrameCount, decoder->FrameCount()); + for (size_t i = 0; i < kExpectedFrameCount; i++) { + CompareFrameWithExpectation(g_png_animated_frame_info[i], decoder.get(), i); + } +} + +TEST(AnimatedPNGTests, EmptyFrame) { + const char* png_file = "/images/resources/empty-frame.png"; + auto decoder = CreatePNGDecoderWithPngData(png_file); + // Frame 0 is empty. Ensure that decoding frame 1 (which depends on frame 0) + // fails (rather than crashing). + EXPECT_EQ(2u, decoder->FrameCount()); + EXPECT_FALSE(decoder->Failed()); + + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(1); + EXPECT_TRUE(decoder->Failed()); + ASSERT_NE(nullptr, frame); + EXPECT_EQ(ImageFrame::kFrameEmpty, frame->GetStatus()); +} + +TEST(AnimatedPNGTests, ByteByByteSizeAvailable) { + TestSizeByteByByte( + "/images/resources/" + "png-animated-idat-part-of-animation.png", + 141u, IntSize(5, 5)); + TestSizeByteByByte( + "/images/resources/" + "png-animated-idat-not-part-of-animation.png", + 79u, IntSize(227, 35)); +} + +TEST(AnimatedPNGTests, ByteByByteMetaData) { + const char* png_file = + "/images/resources/" + "png-animated-idat-part-of-animation.png"; + constexpr size_t kExpectedFrameCount = 4; + + // These are the byte offsets where each frame should have been parsed. + // It boils down to the offset of the first fcTL / IEND after the last + // frame data chunk, plus 8 bytes for recognition. The exception on this is + // the first frame, which is reported when its first framedata is seen. + size_t frame_offsets[kExpectedFrameCount] = {141, 249, 322, 430}; + + auto decoder = CreatePNGDecoder(); + scoped_refptr<SharedBuffer> data = ReadFile(png_file); + ASSERT_FALSE(data->IsEmpty()); + size_t frames_parsed = 0; + + const char* source = data->Data(); + scoped_refptr<SharedBuffer> partial_data = SharedBuffer::Create(); + for (size_t length = 1; length <= frame_offsets[kExpectedFrameCount - 1]; + length++) { + partial_data->Append(source++, 1u); + decoder->SetData(partial_data.get(), false); + EXPECT_FALSE(decoder->Failed()); + if (length < frame_offsets[frames_parsed]) { + EXPECT_EQ(frames_parsed, decoder->FrameCount()); + } else { + ASSERT_EQ(frames_parsed + 1, decoder->FrameCount()); + CompareFrameWithExpectation(g_png_animated_frame_info[frames_parsed], + decoder.get(), frames_parsed); + frames_parsed++; + } + } + EXPECT_EQ(kExpectedFrameCount, decoder->FrameCount()); + EXPECT_FALSE(decoder->Failed()); +} + +TEST(AnimatedPNGTests, TestRandomFrameDecode) { + TestRandomFrameDecode(&CreatePNGDecoder, + "/images/resources/" + "png-animated-idat-part-of-animation.png", + 2u); +} + +TEST(AnimatedPNGTests, TestDecodeAfterReallocation) { + TestDecodeAfterReallocatingData(&CreatePNGDecoder, + "/images/resources/" + "png-animated-idat-part-of-animation.png"); +} + +TEST(AnimatedPNGTests, ProgressiveDecode) { + TestProgressiveDecoding(&CreatePNGDecoder, + "/images/resources/" + "png-animated-idat-part-of-animation.png", + 13u); +} + +TEST(AnimatedPNGTests, ParseAndDecodeByteByByte) { + TestByteByByteDecode(&CreatePNGDecoder, + "/images/resources/" + "png-animated-idat-part-of-animation.png", + 4u, 6u); +} + +TEST(AnimatedPNGTests, FailureDuringParsing) { + // Test the first fcTL in the stream. Because no frame data has been set at + // this point, the expected frame count is zero. 95 bytes is just before the + // first fcTL chunk, at which the first frame is detected. This is before the + // IDAT, so it should be treated as a static image. + TestInvalidFctlSize( + "/images/resources/" + "png-animated-idat-part-of-animation.png", + 95u, 0u, false); + + // Test for the third fcTL in the stream. This should see 1 frame before the + // fcTL, and then fail when parsing it. + TestInvalidFctlSize( + "/images/resources/" + "png-animated-idat-part-of-animation.png", + 241u, 1u, true); +} + +TEST(AnimatedPNGTests, ActlErrors) { + const char* png_file = + "/images/resources/" + "png-animated-idat-part-of-animation.png"; + scoped_refptr<SharedBuffer> data = ReadFile(png_file); + ASSERT_FALSE(data->IsEmpty()); + + const size_t kOffsetActl = 33u; + const size_t kAcTLSize = 20u; + { + // Remove the acTL chunk from the stream. This results in a static image. + scoped_refptr<SharedBuffer> no_actl_data = + SharedBuffer::Create(data->Data(), kOffsetActl); + no_actl_data->Append(data->Data() + kOffsetActl + kAcTLSize, + data->size() - kOffsetActl - kAcTLSize); + + auto decoder = CreatePNGDecoder(); + decoder->SetData(no_actl_data, true); + EXPECT_EQ(1u, decoder->FrameCount()); + EXPECT_FALSE(decoder->Failed()); + EXPECT_EQ(kAnimationNone, decoder->RepetitionCount()); + } + + // Store the acTL for more tests. + char ac_tl[kAcTLSize]; + memcpy(ac_tl, data->Data() + kOffsetActl, kAcTLSize); + + // Insert an extra acTL at a couple of different offsets. + // Prior to the IDAT, this should result in a static image. After, this + // should fail. + const struct { + size_t offset; + bool should_fail; + } kGRecs[] = {{8u, false}, + {kOffsetActl, false}, + {133u, false}, + {172u, true}, + {422u, true}}; + for (const auto& rec : kGRecs) { + const size_t offset = rec.offset; + scoped_refptr<SharedBuffer> extra_actl_data = + SharedBuffer::Create(data->Data(), offset); + extra_actl_data->Append(ac_tl, kAcTLSize); + extra_actl_data->Append(data->Data() + offset, data->size() - offset); + auto decoder = CreatePNGDecoder(); + decoder->SetData(extra_actl_data, true); + EXPECT_EQ(rec.should_fail ? 0u : 1u, decoder->FrameCount()); + EXPECT_EQ(rec.should_fail, decoder->Failed()); + } + + // An acTL after IDAT is ignored. + png_file = + "/images/resources/" + "cHRM_color_spin.png"; + { + scoped_refptr<SharedBuffer> data2 = ReadFile(png_file); + ASSERT_FALSE(data2->IsEmpty()); + const size_t kPostIDATOffset = 30971u; + for (size_t times = 0; times < 2; times++) { + scoped_refptr<SharedBuffer> extra_actl_data = + SharedBuffer::Create(data2->Data(), kPostIDATOffset); + for (size_t i = 0; i < times; i++) + extra_actl_data->Append(ac_tl, kAcTLSize); + extra_actl_data->Append(data2->Data() + kPostIDATOffset, + data2->size() - kPostIDATOffset); + + auto decoder = CreatePNGDecoder(); + decoder->SetData(extra_actl_data, true); + EXPECT_EQ(1u, decoder->FrameCount()); + EXPECT_FALSE(decoder->Failed()); + EXPECT_EQ(kAnimationNone, decoder->RepetitionCount()); + EXPECT_NE(nullptr, decoder->DecodeFrameBufferAtIndex(0)); + EXPECT_FALSE(decoder->Failed()); + } + } +} + +TEST(AnimatedPNGTests, fdatBeforeIdat) { + const char* png_file = + "/images/resources/" + "png-animated-idat-not-part-of-animation.png"; + scoped_refptr<SharedBuffer> data = ReadFile(png_file); + ASSERT_FALSE(data->IsEmpty()); + + // Insert fcTL and fdAT prior to the IDAT + const size_t kIdatOffset = 71u; + scoped_refptr<SharedBuffer> modified_data = + SharedBuffer::Create(data->Data(), kIdatOffset); + // Copy fcTL and fdAT + const size_t kFctlPlusFdatSize = 38u + 1566u; + modified_data->Append(data->Data() + 2519u, kFctlPlusFdatSize); + // Copy IDAT + modified_data->Append(data->Data() + kIdatOffset, 2448u); + // Copy the remaining + modified_data->Append(data->Data() + 4123u, 39u + 12u); + // Data has just been rearranged. + ASSERT_EQ(data->size(), modified_data->size()); + + { + // This broken APNG will be treated as a static png. + auto decoder = CreatePNGDecoder(); + decoder->SetData(modified_data.get(), true); + ExpectStatic(decoder.get()); + } + + { + // Remove the acTL from the modified image. It now has fdAT before + // IDAT, but no acTL, so fdAT should be ignored. + const size_t kOffsetActl = 33u; + const size_t kAcTLSize = 20u; + scoped_refptr<SharedBuffer> modified_data2 = + SharedBuffer::Create(modified_data->Data(), kOffsetActl); + modified_data2->Append(modified_data->Data() + kOffsetActl + kAcTLSize, + modified_data->size() - kOffsetActl - kAcTLSize); + auto decoder = CreatePNGDecoder(); + decoder->SetData(modified_data2.get(), true); + ExpectStatic(decoder.get()); + + // Likewise, if an acTL follows the fdAT, it is ignored. + const size_t kInsertionOffset = kIdatOffset + kFctlPlusFdatSize - kAcTLSize; + scoped_refptr<SharedBuffer> modified_data3 = + SharedBuffer::Create(modified_data2->Data(), kInsertionOffset); + modified_data3->Append(data->Data() + kOffsetActl, kAcTLSize); + modified_data3->Append(modified_data2->Data() + kInsertionOffset, + modified_data2->size() - kInsertionOffset); + decoder = CreatePNGDecoder(); + decoder->SetData(modified_data3.get(), true); + ExpectStatic(decoder.get()); + } +} + +TEST(AnimatedPNGTests, IdatSizeMismatch) { + // The default image must fill the image + const char* png_file = + "/images/resources/" + "png-animated-idat-part-of-animation.png"; + scoped_refptr<SharedBuffer> data = ReadFile(png_file); + ASSERT_FALSE(data->IsEmpty()); + + const size_t kFctlOffset = 95u; + scoped_refptr<SharedBuffer> modified_data = + SharedBuffer::Create(data->Data(), kFctlOffset); + const size_t kFctlSize = 38u; + png_byte fctl[kFctlSize]; + memcpy(fctl, data->Data() + kFctlOffset, kFctlSize); + // Set the height to a smaller value, so it does not fill the image. + WriteUint32(3, fctl + 16); + // Correct the crc + WriteUint32(3210324191, fctl + 34); + modified_data->Append((const char*)fctl, kFctlSize); + const size_t kAfterFctl = kFctlOffset + kFctlSize; + modified_data->Append(data->Data() + kAfterFctl, data->size() - kAfterFctl); + + auto decoder = CreatePNGDecoder(); + decoder->SetData(modified_data.get(), true); + ExpectStatic(decoder.get()); +} + +// Originally, the third frame has an offset of (1,2) and a size of (3,2). By +// changing the offset to (4,4), the frame rect is no longer within the image +// size of 5x5. This results in a failure. +TEST(AnimatedPNGTests, VerifyFrameOutsideImageSizeFails) { + const char* png_file = + "/images/resources/" + "png-animated-idat-part-of-animation.png"; + scoped_refptr<SharedBuffer> data = ReadFile(png_file); + auto decoder = CreatePNGDecoder(); + ASSERT_FALSE(data->IsEmpty()); + + const size_t kOffsetThirdFctl = 241; + scoped_refptr<SharedBuffer> modified_data = + SharedBuffer::Create(data->Data(), kOffsetThirdFctl); + const size_t kFctlSize = 38u; + png_byte fctl[kFctlSize]; + memcpy(fctl, data->Data() + kOffsetThirdFctl, kFctlSize); + // Modify offset and crc. + WriteUint32(4, fctl + 20u); + WriteUint32(4, fctl + 24u); + WriteUint32(3700322018, fctl + 34u); + + modified_data->Append(const_cast<const char*>(reinterpret_cast<char*>(fctl)), + kFctlSize); + modified_data->Append(data->Data() + kOffsetThirdFctl + kFctlSize, + data->size() - kOffsetThirdFctl - kFctlSize); + + decoder->SetData(modified_data, true); + + IntSize expected_size(5, 5); + EXPECT_TRUE(decoder->IsSizeAvailable()); + EXPECT_EQ(expected_size, decoder->Size()); + + const size_t kExpectedFrameCount = 0; + EXPECT_EQ(kExpectedFrameCount, decoder->FrameCount()); + EXPECT_TRUE(decoder->Failed()); +} + +TEST(AnimatedPNGTests, ProgressiveDecodingContinuesAfterFullData) { + // 160u is a randomly chosen offset in the IDAT chunk of the first frame. + TestProgressiveDecodingContinuesAfterFullData( + "/images/resources/" + "png-animated-idat-part-of-animation.png", + 160u); +} + +TEST(AnimatedPNGTests, RandomDecodeAfterClearFrameBufferCache) { + TestRandomDecodeAfterClearFrameBufferCache( + &CreatePNGDecoder, + "/images/resources/" + "png-animated-idat-part-of-animation.png", + 2u); +} + +TEST(AnimatedPNGTests, VerifyAlphaBlending) { + TestAlphaBlending(&CreatePNGDecoder, + "/images/resources/" + "png-animated-idat-part-of-animation.png"); +} + +// This tests if the frame count gets set correctly when parsing FrameCount +// fails in one of the parsing queries. +// +// First, enough data is provided such that two frames should be registered. +// The decoder should at this point not be in the failed status. +// +// Then, we provide the rest of the data except for the last IEND chunk, but +// tell the decoder that this is all the data we have. The frame count should +// be three, since one extra frame should be discovered. The fourth frame +// should *not* be registered since the reader should not be able to determine +// where the frame ends. The decoder should *not* be in the failed state since +// there are three frames which can be shown. +// Attempting to decode the third frame should fail, since the file is +// truncated. +TEST(AnimatedPNGTests, FailureMissingIendChunk) { + scoped_refptr<SharedBuffer> full_data = ReadFile( + "/images/resources/" + "png-animated-idat-part-of-animation.png"); + ASSERT_FALSE(full_data->IsEmpty()); + auto decoder = CreatePNGDecoder(); + + const size_t kOffsetTwoFrames = 249; + const size_t kExpectedFramesAfter249Bytes = 2; + scoped_refptr<SharedBuffer> temp_data = + SharedBuffer::Create(full_data->Data(), kOffsetTwoFrames); + decoder->SetData(temp_data.get(), false); + EXPECT_EQ(kExpectedFramesAfter249Bytes, decoder->FrameCount()); + EXPECT_FALSE(decoder->Failed()); + + // Provide the rest of the data except for the last IEND chunk. + const size_t kExpectedFramesAfterAllExcept12Bytes = 3; + temp_data = SharedBuffer::Create(full_data->Data(), full_data->size() - 12); + decoder->SetData(temp_data.get(), true); + ASSERT_EQ(kExpectedFramesAfterAllExcept12Bytes, decoder->FrameCount()); + + for (size_t i = 0; i < kExpectedFramesAfterAllExcept12Bytes; i++) { + EXPECT_FALSE(decoder->Failed()); + decoder->DecodeFrameBufferAtIndex(i); + } + + EXPECT_TRUE(decoder->Failed()); +} + +// Verify that a malformatted PNG, where the IEND appears before any frame data +// (IDAT), invalidates the decoder. +TEST(AnimatedPNGTests, VerifyIENDBeforeIDATInvalidatesDecoder) { + scoped_refptr<SharedBuffer> full_data = ReadFile( + "/images/resources/" + "png-animated-idat-part-of-animation.png"); + ASSERT_FALSE(full_data->IsEmpty()); + auto decoder = CreatePNGDecoder(); + + const size_t kOffsetIDAT = 133; + scoped_refptr<SharedBuffer> data = + SharedBuffer::Create(full_data->Data(), kOffsetIDAT); + data->Append(full_data->Data() + full_data->size() - 12u, 12u); + data->Append(full_data->Data() + kOffsetIDAT, + full_data->size() - kOffsetIDAT); + decoder->SetData(data.get(), true); + + const size_t kExpectedFrameCount = 0u; + EXPECT_EQ(kExpectedFrameCount, decoder->FrameCount()); + EXPECT_TRUE(decoder->Failed()); +} + +// All IDAT chunks must be before all fdAT chunks +TEST(AnimatedPNGTests, MixedDataChunks) { + const char* png_file = + "/images/resources/" + "png-animated-idat-part-of-animation.png"; + scoped_refptr<SharedBuffer> full_data = ReadFile(png_file); + ASSERT_FALSE(full_data->IsEmpty()); + + // Add an extra fdAT after the first IDAT, skipping fcTL. + const size_t kPostIDAT = 172u; + scoped_refptr<SharedBuffer> data = + SharedBuffer::Create(full_data->Data(), kPostIDAT); + const size_t kFcTLSize = 38u; + const size_t kFdATSize = 31u; + png_byte fd_at[kFdATSize]; + memcpy(fd_at, full_data->Data() + kPostIDAT + kFcTLSize, kFdATSize); + // Modify the sequence number + WriteUint32(1u, fd_at + 8); + data->Append((const char*)fd_at, kFdATSize); + const size_t kIENDOffset = 422u; + data->Append(full_data->Data() + kIENDOffset, + full_data->size() - kIENDOffset); + auto decoder = CreatePNGDecoder(); + decoder->SetData(data.get(), true); + decoder->FrameCount(); + EXPECT_TRUE(decoder->Failed()); + + // Insert an IDAT after an fdAT. + const size_t kPostfdAT = kPostIDAT + kFcTLSize + kFdATSize; + data = SharedBuffer::Create(full_data->Data(), kPostfdAT); + const size_t kIDATOffset = 133u; + data->Append(full_data->Data() + kIDATOffset, kPostIDAT - kIDATOffset); + // Append the rest. + data->Append(full_data->Data() + kPostIDAT, full_data->size() - kPostIDAT); + decoder = CreatePNGDecoder(); + decoder->SetData(data.get(), true); + decoder->FrameCount(); + EXPECT_TRUE(decoder->Failed()); +} + +// Verify that erroneous values for the disposal method and alpha blending +// cause the decoder to fail. +TEST(AnimatedPNGTests, VerifyInvalidDisposalAndBlending) { + const char* png_file = + "/images/resources/" + "png-animated-idat-part-of-animation.png"; + scoped_refptr<SharedBuffer> full_data = ReadFile(png_file); + ASSERT_FALSE(full_data->IsEmpty()); + auto decoder = CreatePNGDecoder(); + + // The disposal byte in the frame control chunk is the 24th byte, alpha + // blending the 25th. |kOffsetDisposalOp| is 241 bytes to get to the third + // fctl chunk, 8 bytes to skip the length and tag bytes, and 24 bytes to get + // to the disposal op. + // + // Write invalid values to the disposal and alpha blending byte, correct the + // crc and append the rest of the buffer. + const size_t kOffsetDisposalOp = 241 + 8 + 24; + scoped_refptr<SharedBuffer> data = + SharedBuffer::Create(full_data->Data(), kOffsetDisposalOp); + png_byte disposal_and_blending[6u]; + disposal_and_blending[0] = 7; + disposal_and_blending[1] = 9; + WriteUint32(2408835439u, disposal_and_blending + 2u); + data->Append(reinterpret_cast<char*>(disposal_and_blending), 6u); + data->Append(full_data->Data() + kOffsetDisposalOp + 6u, + full_data->size() - kOffsetDisposalOp - 6u); + + decoder->SetData(data.get(), true); + decoder->FrameCount(); + ASSERT_TRUE(decoder->Failed()); +} + +// This test verifies that the following situation does not invalidate the +// decoder: +// - Frame 0 is decoded progressively, but there's not enough data to fully +// decode it. +// - The rest of the image data is received. +// - Frame X, with X > 0, and X does not depend on frame 0, is decoded. +// - Frame 0 is decoded. +// This is a tricky case since the decoder resets the png struct for each frame, +// and this test verifies that it does not break the decoding of frame 0, even +// though it already started in the first call. +TEST(AnimatedPNGTests, VerifySuccessfulFirstFrameDecodeAfterLaterFrame) { + const char* png_file = + "/images/resources/" + "png-animated-three-independent-frames.png"; + auto decoder = CreatePNGDecoder(); + scoped_refptr<SharedBuffer> full_data = ReadFile(png_file); + ASSERT_FALSE(full_data->IsEmpty()); + + // 160u is a randomly chosen offset in the IDAT chunk of the first frame. + const size_t kMiddleFirstFrame = 160u; + scoped_refptr<SharedBuffer> data = + SharedBuffer::Create(full_data->Data(), kMiddleFirstFrame); + decoder->SetData(data.get(), false); + + ASSERT_EQ(1u, decoder->FrameCount()); + ASSERT_EQ(ImageFrame::kFramePartial, + decoder->DecodeFrameBufferAtIndex(0)->GetStatus()); + + decoder->SetData(full_data.get(), true); + ASSERT_EQ(3u, decoder->FrameCount()); + ASSERT_EQ(ImageFrame::kFrameComplete, + decoder->DecodeFrameBufferAtIndex(1)->GetStatus()); + // The point is that this call does not decode frame 0, which it won't do if + // it does not have it as its required previous frame. + ASSERT_EQ(kNotFound, + decoder->DecodeFrameBufferAtIndex(1)->RequiredPreviousFrameIndex()); + + EXPECT_EQ(ImageFrame::kFrameComplete, + decoder->DecodeFrameBufferAtIndex(0)->GetStatus()); + EXPECT_FALSE(decoder->Failed()); +} + +// If the decoder attempts to decode a non-first frame which is subset and +// independent, it needs to discard its png_struct so it can use a modified +// IHDR. Test this by comparing a decode of frame 1 after frame 0 to a decode +// of frame 1 without decoding frame 0. +TEST(AnimatedPNGTests, DecodeFromIndependentFrame) { + const char* png_file = + "/images/resources/" + "png-animated-idat-part-of-animation.png"; + scoped_refptr<SharedBuffer> original_data = ReadFile(png_file); + ASSERT_FALSE(original_data->IsEmpty()); + + // This file almost fits the bill. Modify it to dispose frame 0, making + // frame 1 independent. + const size_t kDisposeOffset = 127u; + auto data = SharedBuffer::Create(original_data->Data(), kDisposeOffset); + // 1 Corresponds to APNG_DISPOSE_OP_BACKGROUND + const char kOne = '\001'; + data->Append(&kOne, 1u); + // No need to modify the blend op + data->Append(original_data->Data() + kDisposeOffset + 1, 1u); + // Modify the CRC + png_byte crc[4]; + WriteUint32(2226670956, crc); + data->Append(reinterpret_cast<const char*>(crc), 4u); + data->Append(original_data->Data() + data->size(), + original_data->size() - data->size()); + ASSERT_EQ(original_data->size(), data->size()); + + auto decoder = CreatePNGDecoder(); + decoder->SetData(data.get(), true); + + ASSERT_EQ(4u, decoder->FrameCount()); + ASSERT_FALSE(decoder->Failed()); + + auto* frame = decoder->DecodeFrameBufferAtIndex(0); + ASSERT_TRUE(frame); + ASSERT_EQ(ImageFrame::kDisposeOverwriteBgcolor, frame->GetDisposalMethod()); + + frame = decoder->DecodeFrameBufferAtIndex(1); + ASSERT_TRUE(frame); + ASSERT_FALSE(decoder->Failed()); + ASSERT_NE(IntRect({}, decoder->Size()), frame->OriginalFrameRect()); + ASSERT_EQ(kNotFound, frame->RequiredPreviousFrameIndex()); + + const auto hash = HashBitmap(frame->Bitmap()); + + // Now decode starting from frame 1. + decoder = CreatePNGDecoder(); + decoder->SetData(data.get(), true); + + frame = decoder->DecodeFrameBufferAtIndex(1); + ASSERT_TRUE(frame); + EXPECT_EQ(hash, HashBitmap(frame->Bitmap())); +} + +// If the first frame is subset from IHDR (only allowed if the first frame is +// not the default image), the decoder has to destroy the png_struct it used +// for parsing so it can use a modified IHDR. +TEST(AnimatedPNGTests, SubsetFromIHDR) { + const char* png_file = + "/images/resources/" + "png-animated-idat-not-part-of-animation.png"; + scoped_refptr<SharedBuffer> original_data = ReadFile(png_file); + ASSERT_FALSE(original_data->IsEmpty()); + + const size_t kFcTLOffset = 2519u; + auto data = SharedBuffer::Create(original_data->Data(), kFcTLOffset); + + const size_t kFcTLSize = 38u; + png_byte fc_tl[kFcTLSize]; + memcpy(fc_tl, original_data->Data() + kFcTLOffset, kFcTLSize); + // Modify to have a subset frame (yOffset 1, height 34 out of 35). + WriteUint32(34, fc_tl + 16u); + WriteUint32(1, fc_tl + 24u); + WriteUint32(3972842751, fc_tl + 34u); + data->Append(reinterpret_cast<const char*>(fc_tl), kFcTLSize); + + // Append the rest of the data. + // Note: If PNGImageDecoder changes to reject an image with too many + // rows, the fdAT data will need to be modified as well. + data->Append(original_data->Data() + kFcTLOffset + kFcTLSize, + original_data->size() - data->size()); + ASSERT_EQ(original_data->size(), data->size()); + + // This will test both byte by byte and using the full data, and compare. + TestByteByByteDecode(CreatePNGDecoder, data.get(), 1, kAnimationNone); +} + +TEST(AnimatedPNGTests, Offset) { + const char* png_file = "/images/resources/apng18.png"; + scoped_refptr<SharedBuffer> original_data = ReadFile(png_file); + ASSERT_FALSE(original_data->IsEmpty()); + + Vector<unsigned> baseline_hashes; + CreateDecodingBaseline(CreatePNGDecoder, original_data.get(), + &baseline_hashes); + constexpr size_t kExpectedFrameCount = 13; + ASSERT_EQ(kExpectedFrameCount, baseline_hashes.size()); + + constexpr size_t kOffset = 37; + char buffer[kOffset] = {}; + + auto data = SharedBuffer::Create(buffer, kOffset); + data->Append(*original_data.get()); + + // Use the same defaults as CreatePNGDecoder, except use the (arbitrary) + // non-zero offset. + auto decoder = std::make_unique<PNGImageDecoder>( + ImageDecoder::kAlphaNotPremultiplied, ColorBehavior::TransformToSRGB(), + ImageDecoder::kNoDecodedImageByteLimit, kOffset); + decoder->SetData(data, true); + ASSERT_EQ(kExpectedFrameCount, decoder->FrameCount()); + + for (size_t i = 0; i < kExpectedFrameCount; ++i) { + auto* frame = decoder->DecodeFrameBufferAtIndex(i); + EXPECT_EQ(baseline_hashes[i], HashBitmap(frame->Bitmap())); + } +} + +TEST(AnimatedPNGTests, ExtraChunksBeforeIHDR) { + const char* png_file = "/images/resources/apng18.png"; + scoped_refptr<SharedBuffer> original_data = ReadFile(png_file); + ASSERT_FALSE(original_data->IsEmpty()); + + Vector<unsigned> baseline_hashes; + CreateDecodingBaseline(CreatePNGDecoder, original_data.get(), + &baseline_hashes); + constexpr size_t kExpectedFrameCount = 13; + ASSERT_EQ(kExpectedFrameCount, baseline_hashes.size()); + + constexpr size_t kPngSignatureSize = 8; + auto data = SharedBuffer::Create(original_data->Data(), kPngSignatureSize); + + // Arbitrary chunk of data. + constexpr size_t kExtraChunkSize = 13; + constexpr png_byte kExtraChunk[kExtraChunkSize] = { + 0, 0, 0, 1, 't', 'R', 'c', 'N', 68, 82, 0, 87, 10}; + data->Append(reinterpret_cast<const char*>(kExtraChunk), kExtraChunkSize); + + // Append the rest of the data from the original. + data->Append(original_data->Data() + kPngSignatureSize, + original_data->size() - kPngSignatureSize); + ASSERT_EQ(original_data->size() + kExtraChunkSize, data->size()); + + auto decoder = CreatePNGDecoder(); + decoder->SetData(data, true); + ASSERT_EQ(kExpectedFrameCount, decoder->FrameCount()); + + for (size_t i = 0; i < kExpectedFrameCount; ++i) { + auto* frame = decoder->DecodeFrameBufferAtIndex(i); + EXPECT_EQ(baseline_hashes[i], HashBitmap(frame->Bitmap())); + } +} + +// Static PNG tests + +TEST(StaticPNGTests, repetitionCountTest) { + TestRepetitionCount("/images/resources/png-simple.png", kAnimationNone); +} + +TEST(StaticPNGTests, sizeTest) { + TestSize("/images/resources/png-simple.png", IntSize(111, 29)); +} + +TEST(StaticPNGTests, MetaDataTest) { + const size_t kExpectedFrameCount = 1; + const TimeDelta kExpectedDuration; + auto decoder = + CreatePNGDecoderWithPngData("/images/resources/png-simple.png"); + EXPECT_EQ(kExpectedFrameCount, decoder->FrameCount()); + EXPECT_EQ(kExpectedDuration, decoder->FrameDurationAtIndex(0)); +} + +TEST(StaticPNGTests, InvalidIHDRChunk) { + TestMissingDataBreaksDecoding("/images/resources/png-simple.png", 20u, 2u); +} + +TEST(StaticPNGTests, ProgressiveDecoding) { + TestProgressiveDecoding(&CreatePNGDecoder, "/images/resources/png-simple.png", + 11u); +} + +TEST(StaticPNGTests, ProgressiveDecodingContinuesAfterFullData) { + TestProgressiveDecodingContinuesAfterFullData( + "/images/resources/png-simple.png", 1000u); +} + +TEST(PNGTests, VerifyFrameCompleteBehavior) { + struct { + const char* name; + size_t expected_frame_count; + size_t offset_in_first_frame; + } g_recs[] = { + {"/images/resources/" + "png-animated-three-independent-frames.png", + 3u, 150u}, + {"/images/resources/" + "png-animated-idat-part-of-animation.png", + 4u, 160u}, + + {"/images/resources/png-simple.png", 1u, 700u}, + {"/images/resources/lenna.png", 1u, 40000u}, + }; + for (const auto& rec : g_recs) { + scoped_refptr<SharedBuffer> full_data = ReadFile(rec.name); + ASSERT_TRUE(full_data.get()); + + // Create with enough data for part of the first frame. + auto decoder = CreatePNGDecoder(); + auto data = + SharedBuffer::Create(full_data->Data(), rec.offset_in_first_frame); + decoder->SetData(data.get(), false); + + EXPECT_FALSE(decoder->FrameIsReceivedAtIndex(0)); + + // Parsing the size is not enough to mark the frame as complete. + EXPECT_TRUE(decoder->IsSizeAvailable()); + EXPECT_FALSE(decoder->FrameIsReceivedAtIndex(0)); + + const auto partial_frame_count = decoder->FrameCount(); + EXPECT_EQ(1u, partial_frame_count); + + // Frame is not complete, even after decoding partially. + EXPECT_FALSE(decoder->FrameIsReceivedAtIndex(0)); + auto* frame = decoder->DecodeFrameBufferAtIndex(0); + ASSERT_TRUE(frame); + EXPECT_NE(ImageFrame::kFrameComplete, frame->GetStatus()); + EXPECT_FALSE(decoder->FrameIsReceivedAtIndex(0)); + + decoder->SetData(full_data.get(), true); + + // With full data, parsing the size still does not mark a frame as + // complete for animated images. + EXPECT_TRUE(decoder->IsSizeAvailable()); + if (rec.expected_frame_count > 1) + EXPECT_FALSE(decoder->FrameIsReceivedAtIndex(0)); + else + EXPECT_TRUE(decoder->FrameIsReceivedAtIndex(0)); + + const auto frame_count = decoder->FrameCount(); + ASSERT_EQ(rec.expected_frame_count, frame_count); + + // After parsing (the full file), all frames are complete. + for (size_t i = 0; i < frame_count; ++i) + EXPECT_TRUE(decoder->FrameIsReceivedAtIndex(i)); + + frame = decoder->DecodeFrameBufferAtIndex(0); + ASSERT_TRUE(frame); + EXPECT_EQ(ImageFrame::kFrameComplete, frame->GetStatus()); + EXPECT_TRUE(decoder->FrameIsReceivedAtIndex(0)); + } +} + +TEST(PNGTests, sizeMayOverflow) { + auto decoder = + CreatePNGDecoderWithPngData("/images/resources/crbug702934.png"); + EXPECT_FALSE(decoder->IsSizeAvailable()); + EXPECT_TRUE(decoder->Failed()); +} + +TEST(PNGTests, truncated) { + auto decoder = + CreatePNGDecoderWithPngData("/images/resources/crbug807324.png"); + + // An update to libpng (without using the libpng-provided workaround) resulted + // in truncating this image. It has no transparency, so no pixel should be + // transparent. + auto* frame = decoder->DecodeFrameBufferAtIndex(0); + auto size = decoder->Size(); + for (int i = 0; i < size.Width(); ++i) + for (int j = 0; j < size.Height(); ++j) { + ASSERT_NE(SK_ColorTRANSPARENT, *frame->GetAddr(i, j)); + } +} + +}; // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_reader.cc b/chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_reader.cc new file mode 100644 index 00000000000..a0b617fb4d9 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_reader.cc @@ -0,0 +1,701 @@ +/* + * 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_reader.h" + +#include <memory> +#include "third_party/blink/renderer/platform/image-decoders/fast_shared_buffer_reader.h" +#include "third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.h" +#include "third_party/blink/renderer/platform/image-decoders/segment_reader.h" +#include "zlib.h" + +namespace { + +inline blink::PNGImageDecoder* imageDecoder(png_structp png) { + return static_cast<blink::PNGImageDecoder*>(png_get_progressive_ptr(png)); +} + +void PNGAPI pngHeaderAvailable(png_structp png, png_infop) { + imageDecoder(png)->HeaderAvailable(); +} + +void PNGAPI pngRowAvailable(png_structp png, + png_bytep row, + png_uint_32 rowIndex, + int state) { + imageDecoder(png)->RowAvailable(row, rowIndex, state); +} + +void PNGAPI pngFrameComplete(png_structp png, png_infop) { + imageDecoder(png)->FrameComplete(); +} + +void PNGAPI pngFailed(png_structp png, png_const_charp) { + longjmp(JMPBUF(png), 1); +} + +} // namespace + +namespace blink { + +PNGImageReader::PNGImageReader(PNGImageDecoder* decoder, size_t initial_offset) + : width_(0), + height_(0), + decoder_(decoder), + initial_offset_(initial_offset), + read_offset_(initial_offset), + progressive_decode_offset_(0), + ihdr_offset_(0), + idat_offset_(0), + idat_is_part_of_animation_(false), + expect_idats_(true), + is_animated_(false), + parsed_signature_(false), + parsed_ihdr_(false), + parse_completed_(false), + reported_frame_count_(0), + next_sequence_number_(0), + fctl_needs_dat_chunk_(false), + ignore_animation_(false) { + png_ = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, pngFailed, + nullptr); + info_ = png_create_info_struct(png_); + png_set_progressive_read_fn(png_, decoder_, nullptr, pngRowAvailable, + pngFrameComplete); + // This setting ensures that we display images with incorrect CMF bytes. + // See crbug.com/807324. + png_set_option(png_, PNG_MAXIMUM_INFLATE_WINDOW, PNG_OPTION_ON); +} + +PNGImageReader::~PNGImageReader() { + png_destroy_read_struct(png_ ? &png_ : nullptr, info_ ? &info_ : nullptr, + nullptr); + DCHECK(!png_ && !info_); +} + +// This method reads from the FastSharedBufferReader, starting at offset, +// and returns |length| bytes in the form of a pointer to a const png_byte*. +// This function is used to make it easy to access data from the reader in a +// png friendly way, and pass it to libpng for decoding. +// +// Pre-conditions before using this: +// - |reader|.size() >= |read_offset| + |length| +// - |buffer|.size() >= |length| +// - |length| <= |kPngReadBufferSize| +// +// The reason for the last two precondition is that currently the png signature +// plus IHDR chunk (8B + 25B = 33B) is the largest chunk that is read using this +// method. If the data is not consecutive, it is stored in |buffer|, which must +// have the size of (at least) |length|, but there's no need for it to be larger +// than |kPngReadBufferSize|. +static constexpr size_t kPngReadBufferSize = 33; +const png_byte* ReadAsConstPngBytep(const FastSharedBufferReader& reader, + size_t read_offset, + size_t length, + char* buffer) { + DCHECK(length <= kPngReadBufferSize); + return reinterpret_cast<const png_byte*>( + reader.GetConsecutiveData(read_offset, length, buffer)); +} + +bool PNGImageReader::ShouldDecodeWithNewPNG(size_t index) const { + if (!png_) + return true; + const bool first_frame_decode_in_progress = progressive_decode_offset_; + const bool frame_size_matches_ihdr = + frame_info_[index].frame_rect == IntRect(0, 0, width_, height_); + if (index) + return first_frame_decode_in_progress || !frame_size_matches_ihdr; + return !first_frame_decode_in_progress && !frame_size_matches_ihdr; +} + +// Return false on a fatal error. +bool PNGImageReader::Decode(SegmentReader& data, size_t index) { + if (index >= frame_info_.size()) + return true; + + const FastSharedBufferReader reader(&data); + + if (!is_animated_) { + if (setjmp(JMPBUF(png_))) + return false; + DCHECK_EQ(0u, index); + progressive_decode_offset_ += ProcessData( + reader, frame_info_[0].start_offset + progressive_decode_offset_, 0); + return true; + } + + DCHECK(is_animated_); + + const bool decode_with_new_png = ShouldDecodeWithNewPNG(index); + if (decode_with_new_png) { + ClearDecodeState(0); + png_ = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, pngFailed, + nullptr); + info_ = png_create_info_struct(png_); + png_set_progressive_read_fn(png_, decoder_, pngHeaderAvailable, + pngRowAvailable, pngFrameComplete); + } + + if (setjmp(JMPBUF(png_))) + return false; + + if (decode_with_new_png) + StartFrameDecoding(reader, index); + + if (!index && (!FirstFrameFullyReceived() || progressive_decode_offset_)) { + const bool decoded_entire_frame = ProgressivelyDecodeFirstFrame(reader); + if (!decoded_entire_frame) + return true; + progressive_decode_offset_ = 0; + } else { + DecodeFrame(reader, index); + } + + static png_byte iend[12] = {0, 0, 0, 0, 'I', 'E', 'N', 'D', 174, 66, 96, 130}; + png_process_data(png_, info_, iend, 12); + png_destroy_read_struct(&png_, &info_, nullptr); + DCHECK(!png_ && !info_); + + return true; +} + +void PNGImageReader::StartFrameDecoding(const FastSharedBufferReader& reader, + size_t index) { + DCHECK_GT(ihdr_offset_, initial_offset_); + ProcessData(reader, initial_offset_, ihdr_offset_ - initial_offset_); + + const IntRect& frame_rect = frame_info_[index].frame_rect; + if (frame_rect == IntRect(0, 0, width_, height_)) { + DCHECK_GT(idat_offset_, ihdr_offset_); + ProcessData(reader, ihdr_offset_, idat_offset_ - ihdr_offset_); + return; + } + + // Process the IHDR chunk, but change the width and height so it reflects + // the frame's width and height. ImageDecoder will apply the x,y offset. + constexpr size_t kHeaderSize = 25; + char read_buffer[kHeaderSize]; + const png_byte* chunk = + ReadAsConstPngBytep(reader, ihdr_offset_, kHeaderSize, read_buffer); + png_byte* header = reinterpret_cast<png_byte*>(read_buffer); + if (chunk != header) + memcpy(header, chunk, kHeaderSize); + png_save_uint_32(header + 8, frame_rect.Width()); + png_save_uint_32(header + 12, frame_rect.Height()); + // IHDR has been modified, so tell libpng to ignore CRC errors. + png_set_crc_action(png_, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); + png_process_data(png_, info_, header, kHeaderSize); + + // Process the rest of the header chunks. + DCHECK_GE(idat_offset_, ihdr_offset_ + kHeaderSize); + ProcessData(reader, ihdr_offset_ + kHeaderSize, + idat_offset_ - ihdr_offset_ - kHeaderSize); +} + +// Determine if the bytes 4 to 7 of |chunk| indicate that it is a |tag| chunk. +// - The length of |chunk| must be >= 8 +// - The length of |tag| must be = 4 +static inline bool IsChunk(const png_byte* chunk, const char* tag) { + return memcmp(chunk + 4, tag, 4) == 0; +} + +bool PNGImageReader::ProgressivelyDecodeFirstFrame( + const FastSharedBufferReader& reader) { + size_t offset = frame_info_[0].start_offset; + + // Loop while there is enough data to do progressive decoding. + while (reader.size() >= offset + 8) { + char read_buffer[8]; + // At the beginning of each loop, the offset is at the start of a chunk. + const png_byte* chunk = ReadAsConstPngBytep(reader, offset, 8, read_buffer); + const png_uint_32 length = png_get_uint_32(chunk); + DCHECK(length <= PNG_UINT_31_MAX); + + // When an fcTL or IEND chunk is encountered, the frame data has ended. + // Return true, since all frame data is decoded. + if (IsChunk(chunk, "fcTL") || IsChunk(chunk, "IEND")) + return true; + + // If this chunk was already decoded, move on to the next. + if (progressive_decode_offset_ >= offset + length + 12) { + offset += length + 12; + continue; + } + + // Three scenarios are possible here: + // 1) Some bytes of this chunk were already decoded in a previous call. + // Continue from there. + // 2) This is an fdAT chunk. Convert it to an IDAT chunk to decode. + // 3) This is any other chunk. Pass it to libpng for processing. + size_t end_offset_chunk = offset + length + 12; + + if (progressive_decode_offset_ >= offset + 8) { + offset = progressive_decode_offset_; + } else if (IsChunk(chunk, "fdAT")) { + ProcessFdatChunkAsIdat(length); + // Skip the sequence number. + offset += 12; + } else { + png_process_data(png_, info_, const_cast<png_byte*>(chunk), 8); + offset += 8; + } + + size_t bytes_left_in_chunk = end_offset_chunk - offset; + size_t bytes_decoded = ProcessData(reader, offset, bytes_left_in_chunk); + progressive_decode_offset_ = offset + bytes_decoded; + if (bytes_decoded < bytes_left_in_chunk) + return false; + offset += bytes_decoded; + } + + return false; +} + +void PNGImageReader::ProcessFdatChunkAsIdat(png_uint_32 fdat_length) { + // An fdAT chunk is build up as follows: + // - |length| (4B) + // - fdAT tag (4B) + // - sequence number (4B) + // - frame data (|length| - 4B) + // - CRC (4B) + // Thus, to reformat this into an IDAT chunk, do the following: + // - write |length| - 4 as the new length, since the sequence number + // must be removed. + // - change the tag to IDAT. + // - omit the sequence number from the data part of the chunk. + png_byte chunk_idat[] = {0, 0, 0, 0, 'I', 'D', 'A', 'T'}; + png_save_uint_32(chunk_idat, fdat_length - 4); + // The CRC is incorrect when applied to the modified fdAT. + png_set_crc_action(png_, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); + png_process_data(png_, info_, chunk_idat, 8); +} + +void PNGImageReader::DecodeFrame(const FastSharedBufferReader& reader, + size_t index) { + size_t offset = frame_info_[index].start_offset; + size_t end_offset = offset + frame_info_[index].byte_length; + char read_buffer[8]; + + while (offset < end_offset) { + const png_byte* chunk = ReadAsConstPngBytep(reader, offset, 8, read_buffer); + const png_uint_32 length = png_get_uint_32(chunk); + DCHECK(length <= PNG_UINT_31_MAX); + + if (IsChunk(chunk, "fdAT")) { + ProcessFdatChunkAsIdat(length); + // The frame data and the CRC span |length| bytes, so skip the + // sequence number and process |length| bytes to decode the frame. + ProcessData(reader, offset + 12, length); + } else { + png_process_data(png_, info_, const_cast<png_byte*>(chunk), 8); + ProcessData(reader, offset + 8, length + 4); + } + + offset += 12 + length; + } +} + +// Compute the CRC and compare to the stored value. +static bool CheckCrc(const FastSharedBufferReader& reader, + size_t chunk_start, + size_t chunk_length) { + constexpr size_t kSizeNeededForfcTL = 26 + 4; + char read_buffer[kSizeNeededForfcTL]; + DCHECK(chunk_length + 4 <= kSizeNeededForfcTL); + const png_byte* chunk = ReadAsConstPngBytep(reader, chunk_start + 4, + chunk_length + 4, read_buffer); + + char crc_buffer[4]; + const png_byte* crc_position = ReadAsConstPngBytep( + reader, chunk_start + 8 + chunk_length, 4, crc_buffer); + png_uint_32 crc = png_get_uint_32(crc_position); + return crc == crc32(crc32(0, Z_NULL, 0), chunk, chunk_length + 4); +} + +bool PNGImageReader::CheckSequenceNumber(const png_byte* position) { + png_uint_32 sequence = png_get_uint_32(position); + if (sequence != next_sequence_number_ || sequence > PNG_UINT_31_MAX) + return false; + + ++next_sequence_number_; + return true; +} + +// Return false if there was a fatal error; true otherwise. +bool PNGImageReader::Parse(SegmentReader& data, ParseQuery query) { + if (parse_completed_) + return true; + + const FastSharedBufferReader reader(&data); + + if (!ParseSize(reader)) + return false; + + if (!decoder_->IsDecodedSizeAvailable()) + return true; + + // For non animated images (identified by no acTL chunk before the IDAT), + // there is no need to continue parsing. + if (!is_animated_) { + FrameInfo frame; + frame.start_offset = read_offset_; + // This should never be read in this case, but initialize just in case. + frame.byte_length = kFirstFrameIndicator; + frame.duration = 0; + frame.frame_rect = IntRect(0, 0, width_, height_); + frame.disposal_method = ImageFrame::DisposalMethod::kDisposeKeep; + frame.alpha_blend = ImageFrame::AlphaBlendSource::kBlendAtopBgcolor; + DCHECK(frame_info_.IsEmpty()); + frame_info_.push_back(frame); + parse_completed_ = true; + return true; + } + + if (query == ParseQuery::kSize) + return true; + + DCHECK_EQ(ParseQuery::kMetaData, query); + DCHECK(is_animated_); + + // Loop over the data and manually register all frames. Nothing is passed to + // libpng for processing. A frame is registered on the next fcTL chunk or + // when the IEND chunk is found. This ensures that only complete frames are + // reported, unless there is an error in the stream. + char read_buffer[kPngReadBufferSize]; + while (reader.size() >= read_offset_ + 8) { + const png_byte* chunk = + ReadAsConstPngBytep(reader, read_offset_, 8, read_buffer); + const size_t length = png_get_uint_32(chunk); + if (length > PNG_UINT_31_MAX) + return false; + + const bool idat = IsChunk(chunk, "IDAT"); + if (idat && !expect_idats_) + return false; + + const bool fd_at = IsChunk(chunk, "fdAT"); + if (fd_at && expect_idats_) + return false; + + if (fd_at || (idat && idat_is_part_of_animation_)) { + fctl_needs_dat_chunk_ = false; + if (!new_frame_.start_offset) { + // Beginning of a new frame's data. + new_frame_.start_offset = read_offset_; + + if (frame_info_.IsEmpty()) { + // This is the first frame. Report it immediately so it can be + // decoded progressively. + new_frame_.byte_length = kFirstFrameIndicator; + frame_info_.push_back(new_frame_); + } + } + + if (fd_at) { + if (reader.size() < read_offset_ + 8 + 4) + return true; + const png_byte* sequence_position = + ReadAsConstPngBytep(reader, read_offset_ + 8, 4, read_buffer); + if (!CheckSequenceNumber(sequence_position)) + return false; + } + + } else if (IsChunk(chunk, "fcTL") || IsChunk(chunk, "IEND")) { + // This marks the end of the previous frame. + if (new_frame_.start_offset) { + new_frame_.byte_length = read_offset_ - new_frame_.start_offset; + if (frame_info_[0].byte_length == kFirstFrameIndicator) { + frame_info_[0].byte_length = new_frame_.byte_length; + } else { + frame_info_.push_back(new_frame_); + if (IsChunk(chunk, "fcTL")) { + if (frame_info_.size() >= reported_frame_count_) + return false; + } else { // IEND + if (frame_info_.size() != reported_frame_count_) + return false; + } + } + + new_frame_.start_offset = 0; + } + + if (reader.size() < read_offset_ + 12 + length) + return true; + + if (IsChunk(chunk, "IEND")) { + parse_completed_ = true; + return true; + } + + if (length != 26 || !CheckCrc(reader, read_offset_, length)) + return false; + + chunk = + ReadAsConstPngBytep(reader, read_offset_ + 8, length, read_buffer); + if (!ParseFrameInfo(chunk)) + return false; + + expect_idats_ = false; + } else if (IsChunk(chunk, "acTL")) { + // There should only be one acTL chunk, and it should be before the + // IDAT chunk. + return false; + } + + read_offset_ += 12 + length; + } + return true; +} + +// If |length| == 0, read until the stream ends. Return number of bytes +// processed. +size_t PNGImageReader::ProcessData(const FastSharedBufferReader& reader, + size_t offset, + size_t length) { + const char* segment; + size_t total_processed_bytes = 0; + while (reader.size() > offset) { + size_t segment_length = reader.GetSomeData(segment, offset); + if (length > 0 && segment_length + total_processed_bytes > length) + segment_length = length - total_processed_bytes; + + png_process_data(png_, info_, + reinterpret_cast<png_byte*>(const_cast<char*>(segment)), + segment_length); + offset += segment_length; + total_processed_bytes += segment_length; + if (total_processed_bytes == length) + return length; + } + return total_processed_bytes; +} + +// Process up to the start of the IDAT with libpng. +// Return false for a fatal error. True otherwise. +bool PNGImageReader::ParseSize(const FastSharedBufferReader& reader) { + if (decoder_->IsDecodedSizeAvailable()) + return true; + + char read_buffer[kPngReadBufferSize]; + + if (setjmp(JMPBUF(png_))) + return false; + + if (!parsed_signature_) { + if (reader.size() < read_offset_ + 8) + return true; + + const png_byte* chunk = + ReadAsConstPngBytep(reader, read_offset_, 8, read_buffer); + png_process_data(png_, info_, const_cast<png_byte*>(chunk), 8); + read_offset_ += 8; + parsed_signature_ = true; + new_frame_.start_offset = 0; + } + + // Process APNG chunks manually, pass other chunks to libpng. + for (png_uint_32 length = 0; reader.size() >= read_offset_ + 8; + read_offset_ += length + 12) { + const png_byte* chunk = + ReadAsConstPngBytep(reader, read_offset_, 8, read_buffer); + length = png_get_uint_32(chunk); + + if (IsChunk(chunk, "IDAT")) { + // Done with header chunks. + idat_offset_ = read_offset_; + fctl_needs_dat_chunk_ = false; + if (ignore_animation_) + is_animated_ = false; + if (!is_animated_ || 1 == reported_frame_count_) + decoder_->SetRepetitionCount(kAnimationNone); + if (!decoder_->SetSize(width_, height_)) + return false; + decoder_->SetColorSpace(); + decoder_->HeaderAvailable(); + return true; + } + + // Wait until the entire chunk is available for parsing simplicity. + if (reader.size() < read_offset_ + length + 12) + break; + + if (IsChunk(chunk, "acTL")) { + if (ignore_animation_) + continue; + if (is_animated_ || length != 8 || !parsed_ihdr_ || + !CheckCrc(reader, read_offset_, 8)) { + ignore_animation_ = true; + continue; + } + chunk = + ReadAsConstPngBytep(reader, read_offset_ + 8, length, read_buffer); + reported_frame_count_ = png_get_uint_32(chunk); + if (!reported_frame_count_ || reported_frame_count_ > PNG_UINT_31_MAX) { + ignore_animation_ = true; + continue; + } + png_uint_32 repetition_count = png_get_uint_32(chunk + 4); + if (repetition_count > PNG_UINT_31_MAX) { + ignore_animation_ = true; + continue; + } + is_animated_ = true; + decoder_->SetRepetitionCount(static_cast<int>(repetition_count) - 1); + } else if (IsChunk(chunk, "fcTL")) { + if (ignore_animation_) + continue; + if (length != 26 || !parsed_ihdr_ || + !CheckCrc(reader, read_offset_, 26)) { + ignore_animation_ = true; + continue; + } + chunk = + ReadAsConstPngBytep(reader, read_offset_ + 8, length, read_buffer); + if (!ParseFrameInfo(chunk) || + new_frame_.frame_rect != IntRect(0, 0, width_, height_)) { + ignore_animation_ = true; + continue; + } + idat_is_part_of_animation_ = true; + } else if (IsChunk(chunk, "fdAT")) { + ignore_animation_ = true; + } else { + png_process_data(png_, info_, const_cast<png_byte*>(chunk), 8); + ProcessData(reader, read_offset_ + 8, length + 4); + if (IsChunk(chunk, "IHDR")) { + parsed_ihdr_ = true; + ihdr_offset_ = read_offset_; + width_ = png_get_image_width(png_, info_); + height_ = png_get_image_height(png_, info_); + } + } + } + + // Not enough data to call HeaderAvailable. + return true; +} + +void PNGImageReader::ClearDecodeState(size_t index) { + if (index) + return; + png_destroy_read_struct(png_ ? &png_ : nullptr, info_ ? &info_ : nullptr, + nullptr); + DCHECK(!png_ && !info_); + progressive_decode_offset_ = 0; +} + +const PNGImageReader::FrameInfo& PNGImageReader::GetFrameInfo( + size_t index) const { + DCHECK(index < frame_info_.size()); + return frame_info_[index]; +} + +// Extract the fcTL frame control info and store it in new_frame_. The length +// check on the fcTL data has been done by the calling code. +bool PNGImageReader::ParseFrameInfo(const png_byte* data) { + if (fctl_needs_dat_chunk_) + return false; + + png_uint_32 frame_width = png_get_uint_32(data + 4); + png_uint_32 frame_height = png_get_uint_32(data + 8); + png_uint_32 x_offset = png_get_uint_32(data + 12); + png_uint_32 y_offset = png_get_uint_32(data + 16); + png_uint_16 delay_numerator = png_get_uint_16(data + 20); + png_uint_16 delay_denominator = png_get_uint_16(data + 22); + + if (!CheckSequenceNumber(data)) + return false; + if (!frame_width || !frame_height) + return false; + if (x_offset + frame_width > width_ || y_offset + frame_height > height_) + return false; + + new_frame_.frame_rect = + IntRect(x_offset, y_offset, frame_width, frame_height); + + if (delay_denominator) + new_frame_.duration = delay_numerator * 1000 / delay_denominator; + else + new_frame_.duration = delay_numerator * 10; + + enum DisposeOperations : png_byte { + kAPNG_DISPOSE_OP_NONE = 0, + kAPNG_DISPOSE_OP_BACKGROUND = 1, + kAPNG_DISPOSE_OP_PREVIOUS = 2, + }; + const png_byte& dispose_op = data[24]; + switch (dispose_op) { + case kAPNG_DISPOSE_OP_NONE: + new_frame_.disposal_method = ImageFrame::DisposalMethod::kDisposeKeep; + break; + case kAPNG_DISPOSE_OP_BACKGROUND: + new_frame_.disposal_method = + ImageFrame::DisposalMethod::kDisposeOverwriteBgcolor; + break; + case kAPNG_DISPOSE_OP_PREVIOUS: + new_frame_.disposal_method = + ImageFrame::DisposalMethod::kDisposeOverwritePrevious; + break; + default: + return false; + } + + enum BlendOperations : png_byte { + kAPNG_BLEND_OP_SOURCE = 0, + kAPNG_BLEND_OP_OVER = 1, + }; + const png_byte& blend_op = data[25]; + switch (blend_op) { + case kAPNG_BLEND_OP_SOURCE: + new_frame_.alpha_blend = ImageFrame::AlphaBlendSource::kBlendAtopBgcolor; + break; + case kAPNG_BLEND_OP_OVER: + new_frame_.alpha_blend = + ImageFrame::AlphaBlendSource::kBlendAtopPreviousFrame; + break; + default: + return false; + } + + fctl_needs_dat_chunk_ = true; + return true; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_reader.h b/chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_reader.h new file mode 100644 index 00000000000..3826493ed60 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_reader.h @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_PNG_PNG_IMAGE_READER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_PNG_PNG_IMAGE_READER_H_ + +#include "base/macros.h" +#include "third_party/blink/renderer/platform/geometry/int_rect.h" +#include "third_party/blink/renderer/platform/image-decoders/image_frame.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +#define PNG_SET_OPTION_SUPPORTED +#include "png.h" + +#if !defined(PNG_LIBPNG_VER_MAJOR) || !defined(PNG_LIBPNG_VER_MINOR) +#error version error: compile against a versioned libpng. +#endif + +#if PNG_LIBPNG_VER_MAJOR > 1 || \ + (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 4) +#define JMPBUF(png_ptr) png_jmpbuf(png_ptr) +#else +#define JMPBUF(png_ptr) png_ptr->jmpbuf +#endif + +namespace blink { + +class FastSharedBufferReader; +class PNGImageDecoder; +class SegmentReader; + +class PLATFORM_EXPORT PNGImageReader final { + USING_FAST_MALLOC(PNGImageReader); + + public: + PNGImageReader(PNGImageDecoder*, size_t initial_offset); + ~PNGImageReader(); + + struct FrameInfo { + // The offset where the frame data of this frame starts. + size_t start_offset; + // The number of bytes that contain frame data, starting at start_offset. + size_t byte_length; + size_t duration; + IntRect frame_rect; + ImageFrame::DisposalMethod disposal_method; + ImageFrame::AlphaBlendSource alpha_blend; + }; + + enum class ParseQuery { kSize, kMetaData }; + + bool Parse(SegmentReader&, ParseQuery); + + // Returns false on a fatal error. + bool Decode(SegmentReader&, size_t); + const FrameInfo& GetFrameInfo(size_t) const; + + // Number of complete frames parsed so far; includes frame 0 even if partial. + size_t FrameCount() const { return frame_info_.size(); } + + bool ParseCompleted() const { return parse_completed_; }; + + bool FrameIsReceivedAtIndex(size_t index) const { + if (!index) + return FirstFrameFullyReceived(); + return index < FrameCount(); + } + + void ClearDecodeState(size_t); + + png_structp PngPtr() const { return png_; } + png_infop InfoPtr() const { return info_; } + + png_bytep InterlaceBuffer() const { return interlace_buffer_.get(); } + void CreateInterlaceBuffer(int size) { + interlace_buffer_ = std::make_unique<png_byte[]>(size); + } + void ClearInterlaceBuffer() { interlace_buffer_.reset(); } + + private: + png_structp png_; + png_infop info_; + png_uint_32 width_; + png_uint_32 height_; + + PNGImageDecoder* decoder_; + + // The offset in the stream where the PNG image starts. + const size_t initial_offset_; + // How many bytes have been read during parsing. + size_t read_offset_; + size_t progressive_decode_offset_; + size_t ihdr_offset_; + size_t idat_offset_; + + bool idat_is_part_of_animation_; + // All IDAT chunks must precede the first fdAT chunk, and all fdAT chunks + // should be separated from the IDAT chunks by an fcTL chunk. So this is true + // until the first fcTL chunk after an IDAT chunk. After that, only fdAT + // chunks are expected. + bool expect_idats_; + bool is_animated_; + bool parsed_signature_; + bool parsed_ihdr_; + bool parse_completed_; + uint32_t reported_frame_count_; + uint32_t next_sequence_number_; + // True when an fcTL has been parsed but not its corresponding fdAT or IDAT + // chunk. Consecutive fcTLs is an error. + bool fctl_needs_dat_chunk_; + bool ignore_animation_; + + std::unique_ptr<png_byte[]> interlace_buffer_; + + // Value used for the byte_length of a FrameInfo struct to indicate that it is + // the first frame and its byte_length is not yet known. 1 is a safe value + // since the byte_length field of a frame is at least 12. + static constexpr size_t kFirstFrameIndicator = 1; + + // Stores information about a frame until it can be pushed to |frame_info| + // once all the frame data has been read from the stream. + FrameInfo new_frame_; + Vector<FrameInfo, 1> frame_info_; + + size_t ProcessData(const FastSharedBufferReader&, + size_t offset, + size_t length); + // Returns false on a fatal error. + bool ParseSize(const FastSharedBufferReader&); + // Returns false on an error. + bool ParseFrameInfo(const png_byte* data); + bool ShouldDecodeWithNewPNG(size_t) const; + void StartFrameDecoding(const FastSharedBufferReader&, size_t); + // Returns whether the frame was completely decoded. + bool ProgressivelyDecodeFirstFrame(const FastSharedBufferReader&); + void DecodeFrame(const FastSharedBufferReader&, size_t); + void ProcessFdatChunkAsIdat(png_uint_32 fdat_length); + // Returns false on a fatal error. + bool CheckSequenceNumber(const png_byte* position); + bool FirstFrameFullyReceived() const { + return !frame_info_.IsEmpty() && + frame_info_[0].byte_length != kFirstFrameIndicator; + } + + DISALLOW_COPY_AND_ASSIGN(PNGImageReader); +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/segment_reader.cc b/chromium/third_party/blink/renderer/platform/image-decoders/segment_reader.cc new file mode 100644 index 00000000000..7ce0ba3acba --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/segment_reader.cc @@ -0,0 +1,199 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/platform/image-decoders/segment_reader.h" + +#include "base/memory/scoped_refptr.h" +#include "third_party/blink/renderer/platform/shared_buffer.h" +#include "third_party/blink/renderer/platform/wtf/assertions.h" +#include "third_party/blink/renderer/platform/wtf/noncopyable.h" +#include "third_party/blink/renderer/platform/wtf/threading_primitives.h" +#include "third_party/skia/include/core/SkData.h" +#include "third_party/skia/include/core/SkRWBuffer.h" + +namespace blink { + +// SharedBufferSegmentReader --------------------------------------------------- + +// Interface for ImageDecoder to read a SharedBuffer. +class SharedBufferSegmentReader final : public SegmentReader { + WTF_MAKE_NONCOPYABLE(SharedBufferSegmentReader); + + public: + SharedBufferSegmentReader(scoped_refptr<SharedBuffer>); + size_t size() const override; + size_t GetSomeData(const char*& data, size_t position) const override; + sk_sp<SkData> GetAsSkData() const override; + + private: + scoped_refptr<SharedBuffer> shared_buffer_; +}; + +SharedBufferSegmentReader::SharedBufferSegmentReader( + scoped_refptr<SharedBuffer> buffer) + : shared_buffer_(std::move(buffer)) {} + +size_t SharedBufferSegmentReader::size() const { + return shared_buffer_->size(); +} + +size_t SharedBufferSegmentReader::GetSomeData(const char*& data, + size_t position) const { + return shared_buffer_->GetSomeData(data, position); +} + +sk_sp<SkData> SharedBufferSegmentReader::GetAsSkData() const { + return shared_buffer_->GetAsSkData(); +} + +// DataSegmentReader ----------------------------------------------------------- + +// Interface for ImageDecoder to read an SkData. +class DataSegmentReader final : public SegmentReader { + WTF_MAKE_NONCOPYABLE(DataSegmentReader); + + public: + DataSegmentReader(sk_sp<SkData>); + size_t size() const override; + size_t GetSomeData(const char*& data, size_t position) const override; + sk_sp<SkData> GetAsSkData() const override; + + private: + sk_sp<SkData> data_; +}; + +DataSegmentReader::DataSegmentReader(sk_sp<SkData> data) + : data_(std::move(data)) {} + +size_t DataSegmentReader::size() const { + return data_->size(); +} + +size_t DataSegmentReader::GetSomeData(const char*& data, + size_t position) const { + if (position >= data_->size()) + return 0; + + data = reinterpret_cast<const char*>(data_->bytes() + position); + return data_->size() - position; +} + +sk_sp<SkData> DataSegmentReader::GetAsSkData() const { + return data_; +} + +// ROBufferSegmentReader ------------------------------------------------------- + +class ROBufferSegmentReader final : public SegmentReader { + WTF_MAKE_NONCOPYABLE(ROBufferSegmentReader); + + public: + ROBufferSegmentReader(sk_sp<SkROBuffer>); + + size_t size() const override; + size_t GetSomeData(const char*& data, size_t position) const override; + sk_sp<SkData> GetAsSkData() const override; + + private: + sk_sp<SkROBuffer> ro_buffer_; + // Protects access to mutable fields. + mutable Mutex read_mutex_; + // Position of the first char in the current block of iter_. + mutable size_t position_of_block_; + mutable SkROBuffer::Iter iter_; +}; + +ROBufferSegmentReader::ROBufferSegmentReader(sk_sp<SkROBuffer> buffer) + : ro_buffer_(std::move(buffer)), + position_of_block_(0), + iter_(ro_buffer_.get()) {} + +size_t ROBufferSegmentReader::size() const { + return ro_buffer_ ? ro_buffer_->size() : 0; +} + +size_t ROBufferSegmentReader::GetSomeData(const char*& data, + size_t position) const { + if (!ro_buffer_) + return 0; + + MutexLocker lock(read_mutex_); + + if (position < position_of_block_) { + // SkROBuffer::Iter only iterates forwards. Start from the beginning. + iter_.reset(ro_buffer_.get()); + position_of_block_ = 0; + } + + for (size_t size_of_block = iter_.size(); size_of_block != 0; + position_of_block_ += size_of_block, size_of_block = iter_.size()) { + DCHECK_LE(position_of_block_, position); + + if (position_of_block_ + size_of_block > position) { + // |position| is in this block. + const size_t position_in_block = position - position_of_block_; + data = static_cast<const char*>(iter_.data()) + position_in_block; + return size_of_block - position_in_block; + } + + // Move to next block. + if (!iter_.next()) { + // Reset to the beginning, so future calls can succeed. + iter_.reset(ro_buffer_.get()); + position_of_block_ = 0; + return 0; + } + } + + return 0; +} + +static void UnrefROBuffer(const void* ptr, void* context) { + static_cast<SkROBuffer*>(context)->unref(); +} + +sk_sp<SkData> ROBufferSegmentReader::GetAsSkData() const { + if (!ro_buffer_) + return nullptr; + + // Check to see if the data is already contiguous. + SkROBuffer::Iter iter(ro_buffer_.get()); + const bool multiple_blocks = iter.next(); + iter.reset(ro_buffer_.get()); + + if (!multiple_blocks) { + // Contiguous data. No need to copy. + ro_buffer_->ref(); + return SkData::MakeWithProc(iter.data(), iter.size(), &UnrefROBuffer, + ro_buffer_.get()); + } + + sk_sp<SkData> data = SkData::MakeUninitialized(ro_buffer_->size()); + char* dst = static_cast<char*>(data->writable_data()); + do { + size_t size = iter.size(); + memcpy(dst, iter.data(), size); + dst += size; + } while (iter.next()); + return data; +} + +// SegmentReader --------------------------------------------------------------- + +scoped_refptr<SegmentReader> SegmentReader::CreateFromSharedBuffer( + scoped_refptr<SharedBuffer> buffer) { + return base::AdoptRef(new SharedBufferSegmentReader(std::move(buffer))); +} + +scoped_refptr<SegmentReader> SegmentReader::CreateFromSkData( + sk_sp<SkData> data) { + return base::AdoptRef(new DataSegmentReader(std::move(data))); +} + +scoped_refptr<SegmentReader> SegmentReader::CreateFromSkROBuffer( + sk_sp<SkROBuffer> buffer) { + return base::AdoptRef(new ROBufferSegmentReader(std::move(buffer))); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/segment_reader.h b/chromium/third_party/blink/renderer/platform/image-decoders/segment_reader.h new file mode 100644 index 00000000000..acc68b38254 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/segment_reader.h @@ -0,0 +1,52 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_SEGMENT_READER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_SEGMENT_READER_H_ + +#include "base/memory/scoped_refptr.h" +#include "third_party/blink/renderer/platform/shared_buffer.h" +#include "third_party/blink/renderer/platform/wtf/noncopyable.h" +#include "third_party/blink/renderer/platform/wtf/thread_safe_ref_counted.h" + +class SkData; +class SkROBuffer; + +namespace blink { + +// Interface that looks like SharedBuffer. Used by ImageDecoders to use various +// sources of input including: +// - SharedBuffer +// - for when the caller already has a SharedBuffer +// - SkData +// - for when the caller already has an SkData +// - SkROBuffer +// - for when the caller wants to read/write in different threads +// +// Unlike SharedBuffer, this is a read-only interface. There is no way to +// modify the underlying data source. +class PLATFORM_EXPORT SegmentReader + : public ThreadSafeRefCounted<SegmentReader> { + WTF_MAKE_NONCOPYABLE(SegmentReader); + + public: + // This version is thread-safe so long as no thread is modifying the + // underlying SharedBuffer. This class does not modify it, so that would + // mean modifying it in another way. + static scoped_refptr<SegmentReader> CreateFromSharedBuffer( + scoped_refptr<SharedBuffer>); + + // These versions use thread-safe input, so they are always thread-safe. + static scoped_refptr<SegmentReader> CreateFromSkData(sk_sp<SkData>); + static scoped_refptr<SegmentReader> CreateFromSkROBuffer(sk_sp<SkROBuffer>); + + SegmentReader() = default; + virtual ~SegmentReader() = default; + virtual size_t size() const = 0; + virtual size_t GetSomeData(const char*& data, size_t position) const = 0; + virtual sk_sp<SkData> GetAsSkData() const = 0; +}; + +} // namespace blink +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_SEGMENT_READER_H_ diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/testing/bad-code.gif b/chromium/third_party/blink/renderer/platform/image-decoders/testing/bad-code.gif Binary files differnew file mode 100644 index 00000000000..ed519e76794 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/testing/bad-code.gif diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/testing/bad-initial-code.gif b/chromium/third_party/blink/renderer/platform/image-decoders/testing/bad-initial-code.gif Binary files differnew file mode 100644 index 00000000000..3dc27ec4056 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/testing/bad-initial-code.gif diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/testing/broken.gif b/chromium/third_party/blink/renderer/platform/image-decoders/testing/broken.gif Binary files differnew file mode 100644 index 00000000000..e3a7c4ebe7d --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/testing/broken.gif diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/testing/first-frame-has-greater-size-than-screen-size.gif b/chromium/third_party/blink/renderer/platform/image-decoders/testing/first-frame-has-greater-size-than-screen-size.gif Binary files differnew file mode 100644 index 00000000000..fda744aaf76 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/testing/first-frame-has-greater-size-than-screen-size.gif diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/testing/invalid-disposal-method.gif b/chromium/third_party/blink/renderer/platform/image-decoders/testing/invalid-disposal-method.gif Binary files differnew file mode 100644 index 00000000000..158f37677d7 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/testing/invalid-disposal-method.gif diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/testing/many-progressive-scans.jpg b/chromium/third_party/blink/renderer/platform/image-decoders/testing/many-progressive-scans.jpg Binary files differnew file mode 100644 index 00000000000..05a1a00b09b --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/testing/many-progressive-scans.jpg diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/testing/radient-bad-terminator.gif b/chromium/third_party/blink/renderer/platform/image-decoders/testing/radient-bad-terminator.gif Binary files differnew file mode 100644 index 00000000000..b4e9ebdafb7 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/testing/radient-bad-terminator.gif diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/testing/radient.gif b/chromium/third_party/blink/renderer/platform/image-decoders/testing/radient.gif Binary files differnew file mode 100644 index 00000000000..92de286ebbe --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/testing/radient.gif diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.cc b/chromium/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.cc new file mode 100644 index 00000000000..391da54bb27 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.cc @@ -0,0 +1,557 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.h" + +#include "build/build_config.h" +#include "third_party/skia/include/core/SkData.h" + +#if defined(ARCH_CPU_BIG_ENDIAN) +#error Blink assumes a little-endian target. +#endif + +#if SK_B32_SHIFT // Output little-endian RGBA pixels (Android). +inline WEBP_CSP_MODE outputMode(bool hasAlpha) { + return hasAlpha ? MODE_rgbA : MODE_RGBA; +} +#else // Output little-endian BGRA pixels. +inline WEBP_CSP_MODE outputMode(bool hasAlpha) { + return hasAlpha ? MODE_bgrA : MODE_BGRA; +} +#endif + +namespace { + +// Returns two point ranges (<left, width> pairs) at row |canvasY| which belong +// to |src| but not |dst|. A range is empty if its width is 0. +inline void findBlendRangeAtRow(const blink::IntRect& src, + const blink::IntRect& dst, + int canvasY, + int& left1, + int& width1, + int& left2, + int& width2) { + SECURITY_DCHECK(canvasY >= src.Y() && canvasY < src.MaxY()); + left1 = -1; + width1 = 0; + left2 = -1; + width2 = 0; + + if (canvasY < dst.Y() || canvasY >= dst.MaxY() || src.X() >= dst.MaxX() || + src.MaxX() <= dst.X()) { + left1 = src.X(); + width1 = src.Width(); + return; + } + + if (src.X() < dst.X()) { + left1 = src.X(); + width1 = dst.X() - src.X(); + } + + if (src.MaxX() > dst.MaxX()) { + left2 = dst.MaxX(); + width2 = src.MaxX() - dst.MaxX(); + } +} + +// alphaBlendPremultiplied and alphaBlendNonPremultiplied are separate methods, +// even though they only differ by one line. This is done so that the compiler +// can inline BlendSrcOverDstPremultiplied() and BlensSrcOverDstRaw() calls. +// For GIF images, this optimization reduces decoding time by 15% for 3MB +// images. +void alphaBlendPremultiplied(blink::ImageFrame& src, + blink::ImageFrame& dst, + int canvasY, + int left, + int width) { + for (int x = 0; x < width; ++x) { + int canvasX = left + x; + blink::ImageFrame::PixelData* pixel = src.GetAddr(canvasX, canvasY); + if (SkGetPackedA32(*pixel) != 0xff) { + blink::ImageFrame::PixelData prevPixel = *dst.GetAddr(canvasX, canvasY); + blink::ImageFrame::BlendSrcOverDstPremultiplied(pixel, prevPixel); + } + } +} + +void alphaBlendNonPremultiplied(blink::ImageFrame& src, + blink::ImageFrame& dst, + int canvasY, + int left, + int width) { + for (int x = 0; x < width; ++x) { + int canvasX = left + x; + blink::ImageFrame::PixelData* pixel = src.GetAddr(canvasX, canvasY); + if (SkGetPackedA32(*pixel) != 0xff) { + blink::ImageFrame::PixelData prevPixel = *dst.GetAddr(canvasX, canvasY); + blink::ImageFrame::BlendSrcOverDstRaw(pixel, prevPixel); + } + } +} + +} // namespace + +namespace blink { + +WEBPImageDecoder::WEBPImageDecoder(AlphaOption alpha_option, + const ColorBehavior& color_behavior, + size_t max_decoded_bytes) + : ImageDecoder(alpha_option, color_behavior, max_decoded_bytes), + decoder_(nullptr), + format_flags_(0), + frame_background_has_alpha_(false), + demux_(nullptr), + demux_state_(WEBP_DEMUX_PARSING_HEADER), + have_already_parsed_this_data_(false), + repetition_count_(kAnimationLoopOnce), + decoded_height_(0) { + blend_function_ = (alpha_option == kAlphaPremultiplied) + ? alphaBlendPremultiplied + : alphaBlendNonPremultiplied; +} + +WEBPImageDecoder::~WEBPImageDecoder() { + Clear(); +} + +void WEBPImageDecoder::Clear() { + WebPDemuxDelete(demux_); + demux_ = nullptr; + consolidated_data_.reset(); + ClearDecoder(); +} + +void WEBPImageDecoder::ClearDecoder() { + WebPIDelete(decoder_); + decoder_ = nullptr; + decoded_height_ = 0; + frame_background_has_alpha_ = false; +} + +void WEBPImageDecoder::OnSetData(SegmentReader*) { + have_already_parsed_this_data_ = false; +} + +int WEBPImageDecoder::RepetitionCount() const { + return Failed() ? kAnimationLoopOnce : repetition_count_; +} + +bool WEBPImageDecoder::FrameIsReceivedAtIndex(size_t index) const { + if (!demux_ || demux_state_ <= WEBP_DEMUX_PARSING_HEADER) + return false; + if (!(format_flags_ & ANIMATION_FLAG)) + return ImageDecoder::FrameIsReceivedAtIndex(index); + bool frame_is_received_at_index = index < frame_buffer_cache_.size(); + return frame_is_received_at_index; +} + +TimeDelta WEBPImageDecoder::FrameDurationAtIndex(size_t index) const { + return index < frame_buffer_cache_.size() + ? frame_buffer_cache_[index].Duration() + : TimeDelta(); +} + +bool WEBPImageDecoder::UpdateDemuxer() { + if (Failed()) + return false; + + const unsigned kWebpHeaderSize = 30; + if (data_->size() < kWebpHeaderSize) + return IsAllDataReceived() ? SetFailed() : false; + + if (have_already_parsed_this_data_) + return true; + + have_already_parsed_this_data_ = true; + + if (consolidated_data_ && consolidated_data_->size() >= data_->size()) { + // Less data provided than last time. |consolidated_data_| is guaranteed + // to be its own copy of the data, so it is safe to keep it. + return true; + } + + if (IsAllDataReceived() && !consolidated_data_) { + consolidated_data_ = data_->GetAsSkData(); + } else { + buffer_.ReserveCapacity(data_->size()); + while (buffer_.size() < data_->size()) { + const char* segment; + const size_t bytes = data_->GetSomeData(segment, buffer_.size()); + DCHECK(bytes); + buffer_.Append(segment, bytes); + } + DCHECK_EQ(buffer_.size(), data_->size()); + consolidated_data_ = + SkData::MakeWithoutCopy(buffer_.data(), buffer_.size()); + } + + WebPDemuxDelete(demux_); + WebPData input_data = { + reinterpret_cast<const uint8_t*>(consolidated_data_->data()), + consolidated_data_->size()}; + demux_ = WebPDemuxPartial(&input_data, &demux_state_); + if (!demux_ || (IsAllDataReceived() && demux_state_ != WEBP_DEMUX_DONE)) { + if (!demux_) + consolidated_data_.reset(); + return SetFailed(); + } + + DCHECK_GT(demux_state_, WEBP_DEMUX_PARSING_HEADER); + if (!WebPDemuxGetI(demux_, WEBP_FF_FRAME_COUNT)) + return false; // Wait until the encoded image frame data arrives. + + if (!IsDecodedSizeAvailable()) { + int width = WebPDemuxGetI(demux_, WEBP_FF_CANVAS_WIDTH); + int height = WebPDemuxGetI(demux_, WEBP_FF_CANVAS_HEIGHT); + if (!SetSize(width, height)) + return SetFailed(); + + format_flags_ = WebPDemuxGetI(demux_, WEBP_FF_FORMAT_FLAGS); + if (!(format_flags_ & ANIMATION_FLAG)) { + repetition_count_ = kAnimationNone; + } else { + // Since we have parsed at least one frame, even if partially, + // the global animation (ANIM) properties have been read since + // an ANIM chunk must precede the ANMF frame chunks. + repetition_count_ = WebPDemuxGetI(demux_, WEBP_FF_LOOP_COUNT); + // Repetition count is always <= 16 bits. + DCHECK_EQ(repetition_count_, repetition_count_ & 0xffff); + // Repetition count is treated as n + 1 cycles for GIF. WebP defines loop + // count as the number of cycles, with 0 meaning infinite. + repetition_count_ = repetition_count_ == 0 ? kAnimationLoopInfinite + : repetition_count_ - 1; + // FIXME: Implement ICC profile support for animated images. + format_flags_ &= ~ICCP_FLAG; + } + + if ((format_flags_ & ICCP_FLAG) && !IgnoresColorSpace()) + ReadColorProfile(); + } + + DCHECK(IsDecodedSizeAvailable()); + + size_t frame_count = WebPDemuxGetI(demux_, WEBP_FF_FRAME_COUNT); + UpdateAggressivePurging(frame_count); + + return true; +} + +void WEBPImageDecoder::OnInitFrameBuffer(size_t frame_index) { + // ImageDecoder::InitFrameBuffer does a DCHECK if |frame_index| exists. + ImageFrame& buffer = frame_buffer_cache_[frame_index]; + + const size_t required_previous_frame_index = + buffer.RequiredPreviousFrameIndex(); + if (required_previous_frame_index == kNotFound) { + frame_background_has_alpha_ = + !buffer.OriginalFrameRect().Contains(IntRect(IntPoint(), Size())); + } else { + const ImageFrame& prev_buffer = + frame_buffer_cache_[required_previous_frame_index]; + frame_background_has_alpha_ = + prev_buffer.HasAlpha() || (prev_buffer.GetDisposalMethod() == + ImageFrame::kDisposeOverwriteBgcolor); + } + + // The buffer is transparent outside the decoded area while the image is + // loading. The correct alpha value for the frame will be set when it is fully + // decoded. + buffer.SetHasAlpha(true); +} + +bool WEBPImageDecoder::CanReusePreviousFrameBuffer(size_t frame_index) const { + DCHECK(frame_index < frame_buffer_cache_.size()); + return frame_buffer_cache_[frame_index].GetAlphaBlendSource() != + ImageFrame::kBlendAtopPreviousFrame; +} + +void WEBPImageDecoder::ClearFrameBuffer(size_t frame_index) { + if (demux_ && demux_state_ >= WEBP_DEMUX_PARSED_HEADER && + frame_buffer_cache_[frame_index].GetStatus() == + ImageFrame::kFramePartial) { + // Clear the decoder state so that this partial frame can be decoded again + // when requested. + ClearDecoder(); + } + ImageDecoder::ClearFrameBuffer(frame_index); +} + +void WEBPImageDecoder::ReadColorProfile() { + WebPChunkIterator chunk_iterator; + if (!WebPDemuxGetChunk(demux_, "ICCP", 1, &chunk_iterator)) { + WebPDemuxReleaseChunkIterator(&chunk_iterator); + return; + } + + const char* profile_data = + reinterpret_cast<const char*>(chunk_iterator.chunk.bytes); + size_t profile_size = chunk_iterator.chunk.size; + + sk_sp<SkColorSpace> color_space = + SkColorSpace::MakeICC(profile_data, profile_size); + if (color_space) { + if (color_space->type() == SkColorSpace::kRGB_Type) + SetEmbeddedColorSpace(std::move(color_space)); + } else { + DLOG(ERROR) << "Failed to parse image ICC profile"; + } + + WebPDemuxReleaseChunkIterator(&chunk_iterator); +} + +void WEBPImageDecoder::ApplyPostProcessing(size_t frame_index) { + ImageFrame& buffer = frame_buffer_cache_[frame_index]; + int width; + int decoded_height; + if (!WebPIDecGetRGB(decoder_, &decoded_height, &width, nullptr, nullptr)) + return; // See also https://bugs.webkit.org/show_bug.cgi?id=74062 + if (decoded_height <= 0) + return; + + const IntRect& frame_rect = buffer.OriginalFrameRect(); + SECURITY_DCHECK(width == frame_rect.Width()); + SECURITY_DCHECK(decoded_height <= frame_rect.Height()); + const int left = frame_rect.X(); + const int top = frame_rect.Y(); + + // TODO (msarett): + // 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 + // and linear blending. Can we find a way to perform the + // premultiplication and blending in a linear space? + SkColorSpaceXform* xform = ColorTransform(); + if (xform) { + const SkColorSpaceXform::ColorFormat kSrcFormat = + SkColorSpaceXform::kBGRA_8888_ColorFormat; + const SkColorSpaceXform::ColorFormat kDstFormat = + SkColorSpaceXform::kRGBA_8888_ColorFormat; + for (int y = decoded_height_; y < decoded_height; ++y) { + const int canvas_y = top + y; + uint8_t* row = reinterpret_cast<uint8_t*>(buffer.GetAddr(left, canvas_y)); + bool color_converison_successful = xform->apply( + kDstFormat, row, kSrcFormat, row, width, kUnpremul_SkAlphaType); + DCHECK(color_converison_successful); + uint8_t* pixel = row; + for (int x = 0; x < width; ++x, pixel += 4) { + const int canvas_x = left + x; + buffer.SetRGBA(canvas_x, canvas_y, pixel[0], pixel[1], pixel[2], + pixel[3]); + } + } + } + + // During the decoding of the current frame, we may have set some pixels to be + // transparent (i.e. alpha < 255). If the alpha blend source was + // 'BlendAtopPreviousFrame', the values of these pixels should be determined + // by blending them against the pixels of the corresponding previous frame. + // Compute the correct opaque values now. + // FIXME: This could be avoided if libwebp decoder had an API that used the + // previous required frame to do the alpha-blending by itself. + if ((format_flags_ & ANIMATION_FLAG) && frame_index && + buffer.GetAlphaBlendSource() == ImageFrame::kBlendAtopPreviousFrame && + buffer.RequiredPreviousFrameIndex() != kNotFound) { + ImageFrame& prev_buffer = frame_buffer_cache_[frame_index - 1]; + DCHECK_EQ(prev_buffer.GetStatus(), ImageFrame::kFrameComplete); + ImageFrame::DisposalMethod prev_disposal_method = + prev_buffer.GetDisposalMethod(); + if (prev_disposal_method == ImageFrame::kDisposeKeep) { + // Blend transparent pixels with pixels in previous canvas. + for (int y = decoded_height_; y < decoded_height; ++y) { + blend_function_(buffer, prev_buffer, top + y, left, width); + } + } else if (prev_disposal_method == ImageFrame::kDisposeOverwriteBgcolor) { + const IntRect& prev_rect = prev_buffer.OriginalFrameRect(); + // We need to blend a transparent pixel with the starting value (from just + // after the InitFrame() call). If the pixel belongs to prev_rect, the + // starting value was fully transparent, so this is a no-op. Otherwise, we + // need to blend against the pixel from the previous canvas. + for (int y = decoded_height_; y < decoded_height; ++y) { + int canvas_y = top + y; + int left1, width1, left2, width2; + findBlendRangeAtRow(frame_rect, prev_rect, canvas_y, left1, width1, + left2, width2); + if (width1 > 0) + blend_function_(buffer, prev_buffer, canvas_y, left1, width1); + if (width2 > 0) + blend_function_(buffer, prev_buffer, canvas_y, left2, width2); + } + } + } + + decoded_height_ = decoded_height; + buffer.SetPixelsChanged(true); +} + +size_t WEBPImageDecoder::DecodeFrameCount() { + // If UpdateDemuxer() fails, return the existing number of frames. This way + // if we get halfway through the image before decoding fails, we won't + // suddenly start reporting that the image has zero frames. + return UpdateDemuxer() ? WebPDemuxGetI(demux_, WEBP_FF_FRAME_COUNT) + : frame_buffer_cache_.size(); +} + +void WEBPImageDecoder::InitializeNewFrame(size_t index) { + if (!(format_flags_ & ANIMATION_FLAG)) { + DCHECK(!index); + return; + } + WebPIterator animated_frame; + WebPDemuxGetFrame(demux_, index + 1, &animated_frame); + DCHECK_EQ(animated_frame.complete, 1); + ImageFrame* buffer = &frame_buffer_cache_[index]; + IntRect frame_rect(animated_frame.x_offset, animated_frame.y_offset, + animated_frame.width, animated_frame.height); + buffer->SetOriginalFrameRect( + Intersection(frame_rect, IntRect(IntPoint(), Size()))); + buffer->SetDuration(TimeDelta::FromMilliseconds(animated_frame.duration)); + buffer->SetDisposalMethod(animated_frame.dispose_method == + WEBP_MUX_DISPOSE_BACKGROUND + ? ImageFrame::kDisposeOverwriteBgcolor + : ImageFrame::kDisposeKeep); + buffer->SetAlphaBlendSource(animated_frame.blend_method == WEBP_MUX_BLEND + ? ImageFrame::kBlendAtopPreviousFrame + : ImageFrame::kBlendAtopBgcolor); + buffer->SetRequiredPreviousFrameIndex( + FindRequiredPreviousFrame(index, !animated_frame.has_alpha)); + WebPDemuxReleaseIterator(&animated_frame); +} + +void WEBPImageDecoder::Decode(size_t index) { + if (Failed()) + return; + + Vector<size_t> frames_to_decode = FindFramesToDecode(index); + + DCHECK(demux_); + for (auto i = frames_to_decode.rbegin(); i != frames_to_decode.rend(); ++i) { + if ((format_flags_ & ANIMATION_FLAG) && !InitFrameBuffer(*i)) { + SetFailed(); + return; + } + + WebPIterator webp_frame; + if (!WebPDemuxGetFrame(demux_, *i + 1, &webp_frame)) { + SetFailed(); + } else { + DecodeSingleFrame(webp_frame.fragment.bytes, webp_frame.fragment.size, + *i); + WebPDemuxReleaseIterator(&webp_frame); + } + if (Failed()) + 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() && + demux_ && demux_state_ != WEBP_DEMUX_DONE) + SetFailed(); +} + +bool WEBPImageDecoder::DecodeSingleFrame(const uint8_t* data_bytes, + size_t data_size, + size_t frame_index) { + if (Failed()) + return false; + + DCHECK(IsDecodedSizeAvailable()); + + DCHECK_GT(frame_buffer_cache_.size(), frame_index); + ImageFrame& buffer = frame_buffer_cache_[frame_index]; + DCHECK_NE(buffer.GetStatus(), ImageFrame::kFrameComplete); + + if (buffer.GetStatus() == ImageFrame::kFrameEmpty) { + if (!buffer.AllocatePixelData(Size().Width(), Size().Height(), + ColorSpaceForSkImages())) + return SetFailed(); + buffer.ZeroFillPixelData(); + buffer.SetStatus(ImageFrame::kFramePartial); + // The buffer is transparent outside the decoded area while the image is + // loading. The correct alpha value for the frame will be set when it is + // fully decoded. + buffer.SetHasAlpha(true); + buffer.SetOriginalFrameRect(IntRect(IntPoint(), Size())); + } + + const IntRect& frame_rect = buffer.OriginalFrameRect(); + if (!decoder_) { + WEBP_CSP_MODE mode = outputMode(format_flags_ & ALPHA_FLAG); + if (!premultiply_alpha_) + mode = outputMode(false); + if (ColorTransform()) { + // Swizzling between RGBA and BGRA is zero cost in a color transform. + // So when we have a color transform, we should decode to whatever is + // easiest for libwebp, and then let the color transform swizzle if + // necessary. + // Lossy webp is encoded as YUV (so RGBA and BGRA are the same cost). + // Lossless webp is encoded as BGRA. This means decoding to BGRA is + // either faster or the same cost as RGBA. + mode = MODE_BGRA; + } + WebPInitDecBuffer(&decoder_buffer_); + decoder_buffer_.colorspace = mode; + decoder_buffer_.u.RGBA.stride = + Size().Width() * sizeof(ImageFrame::PixelData); + decoder_buffer_.u.RGBA.size = + decoder_buffer_.u.RGBA.stride * frame_rect.Height(); + decoder_buffer_.is_external_memory = 1; + decoder_ = WebPINewDecoder(&decoder_buffer_); + if (!decoder_) + return SetFailed(); + } + + decoder_buffer_.u.RGBA.rgba = reinterpret_cast<uint8_t*>( + buffer.GetAddr(frame_rect.X(), frame_rect.Y())); + + switch (WebPIUpdate(decoder_, data_bytes, data_size)) { + case VP8_STATUS_OK: + ApplyPostProcessing(frame_index); + buffer.SetHasAlpha((format_flags_ & ALPHA_FLAG) || + frame_background_has_alpha_); + buffer.SetStatus(ImageFrame::kFrameComplete); + ClearDecoder(); + return true; + case VP8_STATUS_SUSPENDED: + if (!IsAllDataReceived() && !FrameIsReceivedAtIndex(frame_index)) { + ApplyPostProcessing(frame_index); + return false; + } + FALLTHROUGH; + default: + Clear(); + return SetFailed(); + } +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.h b/chromium/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.h new file mode 100644 index 00000000000..651d35d0534 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.h @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_WEBP_WEBP_IMAGE_DECODER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_WEBP_WEBP_IMAGE_DECODER_H_ + +#include "third_party/blink/renderer/platform/image-decoders/image_decoder.h" +#include "third_party/blink/renderer/platform/wtf/time.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" +#include "webp/decode.h" +#include "webp/demux.h" + +class SkData; + +namespace blink { + +class PLATFORM_EXPORT WEBPImageDecoder final : public ImageDecoder { + WTF_MAKE_NONCOPYABLE(WEBPImageDecoder); + + public: + WEBPImageDecoder(AlphaOption, const ColorBehavior&, size_t max_decoded_bytes); + ~WEBPImageDecoder() override; + + // ImageDecoder: + String FilenameExtension() const override { return "webp"; } + void OnSetData(SegmentReader* data) override; + int RepetitionCount() const override; + bool FrameIsReceivedAtIndex(size_t) const override; + TimeDelta FrameDurationAtIndex(size_t) const override; + + private: + // ImageDecoder: + virtual void DecodeSize() { UpdateDemuxer(); } + size_t DecodeFrameCount() override; + void InitializeNewFrame(size_t) override; + void Decode(size_t) override; + + bool DecodeSingleFrame(const uint8_t* data_bytes, + size_t data_size, + size_t frame_index); + + // For WebP images, the frame status needs to be FrameComplete to decode + // subsequent frames that depend on frame |index|. The reason for this is that + // WebP uses the previous frame for alpha blending, in ApplyPostProcessing(). + // + // Before calling this, verify that frame |index| exists by checking that + // |index| is smaller than |frame_buffer_cache_|.size(). + bool FrameStatusSufficientForSuccessors(size_t index) override { + DCHECK(index < frame_buffer_cache_.size()); + return frame_buffer_cache_[index].GetStatus() == ImageFrame::kFrameComplete; + } + + WebPIDecoder* decoder_; + WebPDecBuffer decoder_buffer_; + int format_flags_; + bool frame_background_has_alpha_; + + void ReadColorProfile(); + bool UpdateDemuxer(); + + // Set |frame_background_has_alpha_| based on this frame's characteristics. + // Before calling this method, the caller must verify that the frame exists. + void OnInitFrameBuffer(size_t frame_index) override; + + // When the blending method of this frame is BlendAtopPreviousFrame, the + // previous frame's buffer is necessary to decode this frame in + // ApplyPostProcessing, so we can't take over the data. Before calling this + // method, the caller must verify that the frame exists. + bool CanReusePreviousFrameBuffer(size_t frame_index) const override; + + void ApplyPostProcessing(size_t frame_index); + void ClearFrameBuffer(size_t frame_index) override; + + WebPDemuxer* demux_; + WebPDemuxState demux_state_; + bool have_already_parsed_this_data_; + int repetition_count_; + int decoded_height_; + + typedef void (*AlphaBlendFunction)(ImageFrame&, ImageFrame&, int, int, int); + AlphaBlendFunction blend_function_; + + void Clear(); + void ClearDecoder(); + + // This will point to one of three things: + // - the SegmentReader's data, if contiguous. + // - its own copy, if not, and all data was received initially. + // - |buffer_|, if streaming. + sk_sp<SkData> consolidated_data_; + Vector<char> buffer_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder_test.cc b/chromium/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder_test.cc new file mode 100644 index 00000000000..975d3d1b84f --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder_test.cc @@ -0,0 +1,539 @@ +/* + * Copyright (C) 2013 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.h" + +#include <memory> +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/platform/web_data.h" +#include "third_party/blink/public/platform/web_size.h" +#include "third_party/blink/renderer/platform/image-decoders/image_decoder_test_helpers.h" +#include "third_party/blink/renderer/platform/shared_buffer.h" +#include "third_party/blink/renderer/platform/wtf/dtoa/utils.h" +#include "third_party/blink/renderer/platform/wtf/time.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { + +namespace { + +struct AnimParam { + int x_offset, y_offset, width, height; + ImageFrame::DisposalMethod disposal_method; + ImageFrame::AlphaBlendSource alpha_blend_source; + TimeDelta duration; + bool has_alpha; +}; + +std::unique_ptr<ImageDecoder> CreateWEBPDecoder( + ImageDecoder::AlphaOption alpha_option) { + return std::make_unique<WEBPImageDecoder>( + alpha_option, ColorBehavior::TransformToSRGB(), + ImageDecoder::kNoDecodedImageByteLimit); +} + +std::unique_ptr<ImageDecoder> CreateWEBPDecoder() { + return CreateWEBPDecoder(ImageDecoder::kAlphaNotPremultiplied); +} + +// If 'parse_error_expected' is true, error is expected during parse +// (FrameCount() call); else error is expected during decode +// (FrameBufferAtIndex() call). +void TestInvalidImage(const char* webp_file, bool parse_error_expected) { + std::unique_ptr<ImageDecoder> decoder = CreateWEBPDecoder(); + + scoped_refptr<SharedBuffer> data = ReadFile(webp_file); + ASSERT_TRUE(data.get()); + decoder->SetData(data.get(), true); + + if (parse_error_expected) { + EXPECT_EQ(0u, decoder->FrameCount()); + EXPECT_FALSE(decoder->DecodeFrameBufferAtIndex(0)); + } else { + EXPECT_GT(decoder->FrameCount(), 0u); + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0); + ASSERT_TRUE(frame); + EXPECT_EQ(ImageFrame::kFramePartial, frame->GetStatus()); + } + EXPECT_EQ(kAnimationLoopOnce, decoder->RepetitionCount()); + EXPECT_TRUE(decoder->Failed()); +} + +} // anonymous namespace + +TEST(AnimatedWebPTests, uniqueGenerationIDs) { + std::unique_ptr<ImageDecoder> decoder = CreateWEBPDecoder(); + + scoped_refptr<SharedBuffer> data = + ReadFile("/images/resources/webp-animated.webp"); + ASSERT_TRUE(data.get()); + decoder->SetData(data.get(), true); + + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0); + uint32_t generation_id0 = frame->Bitmap().getGenerationID(); + frame = decoder->DecodeFrameBufferAtIndex(1); + uint32_t generation_id1 = frame->Bitmap().getGenerationID(); + + EXPECT_TRUE(generation_id0 != generation_id1); +} + +TEST(AnimatedWebPTests, verifyAnimationParametersTransparentImage) { + std::unique_ptr<ImageDecoder> decoder = CreateWEBPDecoder(); + EXPECT_EQ(kAnimationLoopOnce, decoder->RepetitionCount()); + + scoped_refptr<SharedBuffer> data = + ReadFile("/images/resources/webp-animated.webp"); + ASSERT_TRUE(data.get()); + decoder->SetData(data.get(), true); + + const int kCanvasWidth = 11; + const int kCanvasHeight = 29; + const AnimParam kFrameParameters[] = { + {0, 0, 11, 29, ImageFrame::kDisposeKeep, + ImageFrame::kBlendAtopPreviousFrame, TimeDelta::FromMilliseconds(1000), + true}, + {2, 10, 7, 17, ImageFrame::kDisposeKeep, + ImageFrame::kBlendAtopPreviousFrame, TimeDelta::FromMilliseconds(500), + true}, + {2, 2, 7, 16, ImageFrame::kDisposeKeep, + ImageFrame::kBlendAtopPreviousFrame, TimeDelta::FromMilliseconds(1000), + true}, + }; + + for (size_t i = 0; i < WTF_ARRAY_LENGTH(kFrameParameters); ++i) { + const ImageFrame* const frame = decoder->DecodeFrameBufferAtIndex(i); + EXPECT_EQ(ImageFrame::kFrameComplete, frame->GetStatus()); + EXPECT_EQ(kCanvasWidth, frame->Bitmap().width()); + EXPECT_EQ(kCanvasHeight, frame->Bitmap().height()); + EXPECT_EQ(kFrameParameters[i].x_offset, frame->OriginalFrameRect().X()); + EXPECT_EQ(kFrameParameters[i].y_offset, frame->OriginalFrameRect().Y()); + EXPECT_EQ(kFrameParameters[i].width, frame->OriginalFrameRect().Width()); + EXPECT_EQ(kFrameParameters[i].height, frame->OriginalFrameRect().Height()); + EXPECT_EQ(kFrameParameters[i].disposal_method, frame->GetDisposalMethod()); + EXPECT_EQ(kFrameParameters[i].alpha_blend_source, + frame->GetAlphaBlendSource()); + EXPECT_EQ(kFrameParameters[i].duration, frame->Duration()); + EXPECT_EQ(kFrameParameters[i].has_alpha, frame->HasAlpha()); + } + + EXPECT_EQ(WTF_ARRAY_LENGTH(kFrameParameters), decoder->FrameCount()); + EXPECT_EQ(kAnimationLoopInfinite, decoder->RepetitionCount()); +} + +TEST(AnimatedWebPTests, + verifyAnimationParametersOpaqueFramesTransparentBackground) { + std::unique_ptr<ImageDecoder> decoder = CreateWEBPDecoder(); + EXPECT_EQ(kAnimationLoopOnce, decoder->RepetitionCount()); + + scoped_refptr<SharedBuffer> data = + ReadFile("/images/resources/webp-animated-opaque.webp"); + ASSERT_TRUE(data.get()); + decoder->SetData(data.get(), true); + + const int kCanvasWidth = 94; + const int kCanvasHeight = 87; + const AnimParam kFrameParameters[] = { + {4, 10, 33, 32, ImageFrame::kDisposeOverwriteBgcolor, + ImageFrame::kBlendAtopPreviousFrame, TimeDelta::FromMilliseconds(1000), + true}, + {34, 30, 33, 32, ImageFrame::kDisposeOverwriteBgcolor, + ImageFrame::kBlendAtopPreviousFrame, TimeDelta::FromMilliseconds(1000), + true}, + {62, 50, 32, 32, ImageFrame::kDisposeOverwriteBgcolor, + ImageFrame::kBlendAtopPreviousFrame, TimeDelta::FromMilliseconds(1000), + true}, + {10, 54, 32, 33, ImageFrame::kDisposeOverwriteBgcolor, + ImageFrame::kBlendAtopPreviousFrame, TimeDelta::FromMilliseconds(1000), + true}, + }; + + for (size_t i = 0; i < WTF_ARRAY_LENGTH(kFrameParameters); ++i) { + const ImageFrame* const frame = decoder->DecodeFrameBufferAtIndex(i); + EXPECT_EQ(ImageFrame::kFrameComplete, frame->GetStatus()); + EXPECT_EQ(kCanvasWidth, frame->Bitmap().width()); + EXPECT_EQ(kCanvasHeight, frame->Bitmap().height()); + EXPECT_EQ(kFrameParameters[i].x_offset, frame->OriginalFrameRect().X()); + EXPECT_EQ(kFrameParameters[i].y_offset, frame->OriginalFrameRect().Y()); + EXPECT_EQ(kFrameParameters[i].width, frame->OriginalFrameRect().Width()); + EXPECT_EQ(kFrameParameters[i].height, frame->OriginalFrameRect().Height()); + EXPECT_EQ(kFrameParameters[i].disposal_method, frame->GetDisposalMethod()); + EXPECT_EQ(kFrameParameters[i].alpha_blend_source, + frame->GetAlphaBlendSource()); + EXPECT_EQ(kFrameParameters[i].duration, frame->Duration()); + EXPECT_EQ(kFrameParameters[i].has_alpha, frame->HasAlpha()); + } + + EXPECT_EQ(WTF_ARRAY_LENGTH(kFrameParameters), decoder->FrameCount()); + EXPECT_EQ(kAnimationLoopInfinite, decoder->RepetitionCount()); +} + +TEST(AnimatedWebPTests, verifyAnimationParametersBlendOverwrite) { + std::unique_ptr<ImageDecoder> decoder = CreateWEBPDecoder(); + EXPECT_EQ(kAnimationLoopOnce, decoder->RepetitionCount()); + + scoped_refptr<SharedBuffer> data = + ReadFile("/images/resources/webp-animated-no-blend.webp"); + ASSERT_TRUE(data.get()); + decoder->SetData(data.get(), true); + + const int kCanvasWidth = 94; + const int kCanvasHeight = 87; + const AnimParam kFrameParameters[] = { + {4, 10, 33, 32, ImageFrame::kDisposeOverwriteBgcolor, + ImageFrame::kBlendAtopBgcolor, TimeDelta::FromMilliseconds(1000), true}, + {34, 30, 33, 32, ImageFrame::kDisposeOverwriteBgcolor, + ImageFrame::kBlendAtopBgcolor, TimeDelta::FromMilliseconds(1000), true}, + {62, 50, 32, 32, ImageFrame::kDisposeOverwriteBgcolor, + ImageFrame::kBlendAtopBgcolor, TimeDelta::FromMilliseconds(1000), true}, + {10, 54, 32, 33, ImageFrame::kDisposeOverwriteBgcolor, + ImageFrame::kBlendAtopBgcolor, TimeDelta::FromMilliseconds(1000), true}, + }; + + for (size_t i = 0; i < WTF_ARRAY_LENGTH(kFrameParameters); ++i) { + const ImageFrame* const frame = decoder->DecodeFrameBufferAtIndex(i); + EXPECT_EQ(ImageFrame::kFrameComplete, frame->GetStatus()); + EXPECT_EQ(kCanvasWidth, frame->Bitmap().width()); + EXPECT_EQ(kCanvasHeight, frame->Bitmap().height()); + EXPECT_EQ(kFrameParameters[i].x_offset, frame->OriginalFrameRect().X()); + EXPECT_EQ(kFrameParameters[i].y_offset, frame->OriginalFrameRect().Y()); + EXPECT_EQ(kFrameParameters[i].width, frame->OriginalFrameRect().Width()); + EXPECT_EQ(kFrameParameters[i].height, frame->OriginalFrameRect().Height()); + EXPECT_EQ(kFrameParameters[i].disposal_method, frame->GetDisposalMethod()); + EXPECT_EQ(kFrameParameters[i].alpha_blend_source, + frame->GetAlphaBlendSource()); + EXPECT_EQ(kFrameParameters[i].duration, frame->Duration()); + EXPECT_EQ(kFrameParameters[i].has_alpha, frame->HasAlpha()); + } + + EXPECT_EQ(WTF_ARRAY_LENGTH(kFrameParameters), decoder->FrameCount()); + EXPECT_EQ(kAnimationLoopInfinite, decoder->RepetitionCount()); +} + +TEST(AnimatedWebPTests, parseAndDecodeByteByByte) { + TestByteByByteDecode(&CreateWEBPDecoder, + "/images/resources/webp-animated.webp", 3u, + kAnimationLoopInfinite); + TestByteByByteDecode(&CreateWEBPDecoder, + "/images/resources/webp-animated-icc-xmp.webp", 13u, + 31999); +} + +TEST(AnimatedWebPTests, invalidImages) { + // ANMF chunk size is smaller than ANMF header size. + TestInvalidImage("/images/resources/invalid-animated-webp.webp", true); + // One of the frame rectangles extends outside the image boundary. + TestInvalidImage("/images/resources/invalid-animated-webp3.webp", true); +} + +TEST(AnimatedWebPTests, truncatedLastFrame) { + std::unique_ptr<ImageDecoder> decoder = CreateWEBPDecoder(); + + scoped_refptr<SharedBuffer> data = + ReadFile("/images/resources/invalid-animated-webp2.webp"); + ASSERT_TRUE(data.get()); + decoder->SetData(data.get(), true); + + size_t frame_count = 8; + EXPECT_EQ(frame_count, decoder->FrameCount()); + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0); + ASSERT_TRUE(frame); + EXPECT_EQ(ImageFrame::kFrameComplete, frame->GetStatus()); + EXPECT_FALSE(decoder->Failed()); + frame = decoder->DecodeFrameBufferAtIndex(frame_count - 1); + ASSERT_TRUE(frame); + EXPECT_EQ(ImageFrame::kFramePartial, frame->GetStatus()); + EXPECT_TRUE(decoder->Failed()); + frame = decoder->DecodeFrameBufferAtIndex(0); + ASSERT_TRUE(frame); + EXPECT_EQ(ImageFrame::kFrameComplete, frame->GetStatus()); +} + +TEST(AnimatedWebPTests, truncatedInBetweenFrame) { + std::unique_ptr<ImageDecoder> decoder = CreateWEBPDecoder(); + + const Vector<char> full_data = + ReadFile("/images/resources/invalid-animated-webp4.webp")->Copy(); + scoped_refptr<SharedBuffer> data = + SharedBuffer::Create(full_data.data(), full_data.size() - 1); + decoder->SetData(data.get(), false); + + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(1); + ASSERT_TRUE(frame); + EXPECT_EQ(ImageFrame::kFrameComplete, frame->GetStatus()); + frame = decoder->DecodeFrameBufferAtIndex(2); + ASSERT_TRUE(frame); + EXPECT_EQ(ImageFrame::kFramePartial, frame->GetStatus()); + EXPECT_TRUE(decoder->Failed()); +} + +// Tests for a crash that used to happen for a specific file with specific +// sequence of method calls. +TEST(AnimatedWebPTests, reproCrash) { + std::unique_ptr<ImageDecoder> decoder = CreateWEBPDecoder(); + + scoped_refptr<SharedBuffer> full_data_buffer = + ReadFile("/images/resources/invalid_vp8_vp8x.webp"); + ASSERT_TRUE(full_data_buffer.get()); + const Vector<char> full_data = full_data_buffer->Copy(); + + // Parse partial data up to which error in bitstream is not detected. + const size_t kPartialSize = 32768; + ASSERT_GT(full_data.size(), kPartialSize); + scoped_refptr<SharedBuffer> data = + SharedBuffer::Create(full_data.data(), kPartialSize); + decoder->SetData(data.get(), false); + EXPECT_EQ(1u, decoder->FrameCount()); + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0); + ASSERT_TRUE(frame); + EXPECT_EQ(ImageFrame::kFramePartial, frame->GetStatus()); + EXPECT_FALSE(decoder->Failed()); + + // Parse full data now. The error in bitstream should now be detected. + decoder->SetData(full_data_buffer.get(), true); + EXPECT_EQ(1u, decoder->FrameCount()); + frame = decoder->DecodeFrameBufferAtIndex(0); + ASSERT_TRUE(frame); + EXPECT_EQ(ImageFrame::kFramePartial, frame->GetStatus()); + EXPECT_EQ(kAnimationLoopOnce, decoder->RepetitionCount()); + EXPECT_TRUE(decoder->Failed()); +} + +TEST(AnimatedWebPTests, progressiveDecode) { + TestProgressiveDecoding(&CreateWEBPDecoder, + "/images/resources/webp-animated.webp"); +} + +TEST(AnimatedWebPTests, frameIsCompleteAndDuration) { + std::unique_ptr<ImageDecoder> decoder = CreateWEBPDecoder(); + + scoped_refptr<SharedBuffer> data_buffer = + ReadFile("/images/resources/webp-animated.webp"); + ASSERT_TRUE(data_buffer.get()); + const Vector<char> data = data_buffer->Copy(); + + ASSERT_GE(data.size(), 10u); + scoped_refptr<SharedBuffer> temp_data = + SharedBuffer::Create(data.data(), data.size() - 10); + decoder->SetData(temp_data.get(), false); + + EXPECT_EQ(2u, decoder->FrameCount()); + EXPECT_FALSE(decoder->Failed()); + EXPECT_TRUE(decoder->FrameIsReceivedAtIndex(0)); + EXPECT_EQ(TimeDelta::FromMilliseconds(1000), + decoder->FrameDurationAtIndex(0)); + EXPECT_TRUE(decoder->FrameIsReceivedAtIndex(1)); + EXPECT_EQ(TimeDelta::FromMilliseconds(500), decoder->FrameDurationAtIndex(1)); + + decoder->SetData(data_buffer.get(), true); + EXPECT_EQ(3u, decoder->FrameCount()); + EXPECT_TRUE(decoder->FrameIsReceivedAtIndex(0)); + EXPECT_EQ(TimeDelta::FromMilliseconds(1000), + decoder->FrameDurationAtIndex(0)); + EXPECT_TRUE(decoder->FrameIsReceivedAtIndex(1)); + EXPECT_EQ(TimeDelta::FromMilliseconds(500), decoder->FrameDurationAtIndex(1)); + EXPECT_TRUE(decoder->FrameIsReceivedAtIndex(2)); + EXPECT_EQ(TimeDelta::FromMilliseconds(1000), + decoder->FrameDurationAtIndex(2)); +} + +TEST(AnimatedWebPTests, updateRequiredPreviousFrameAfterFirstDecode) { + TestUpdateRequiredPreviousFrameAfterFirstDecode( + &CreateWEBPDecoder, "/images/resources/webp-animated.webp"); +} + +TEST(AnimatedWebPTests, randomFrameDecode) { + TestRandomFrameDecode(&CreateWEBPDecoder, + "/images/resources/webp-animated.webp"); + TestRandomFrameDecode(&CreateWEBPDecoder, + "/images/resources/webp-animated-opaque.webp"); + TestRandomFrameDecode(&CreateWEBPDecoder, + "/images/resources/webp-animated-large.webp"); + TestRandomFrameDecode(&CreateWEBPDecoder, + "/images/resources/webp-animated-icc-xmp.webp"); +} + +TEST(AnimatedWebPTests, randomDecodeAfterClearFrameBufferCache) { + TestRandomDecodeAfterClearFrameBufferCache( + &CreateWEBPDecoder, "/images/resources/webp-animated.webp"); + TestRandomDecodeAfterClearFrameBufferCache( + &CreateWEBPDecoder, "/images/resources/webp-animated-opaque.webp"); + TestRandomDecodeAfterClearFrameBufferCache( + &CreateWEBPDecoder, "/images/resources/webp-animated-large.webp"); + TestRandomDecodeAfterClearFrameBufferCache( + &CreateWEBPDecoder, "/images/resources/webp-animated-icc-xmp.webp"); +} + +// This test is disabled since it timed out on the Windows bot. See +// crrev.com/962853004 +TEST(AnimatedWebPTests, + DISABLED_resumePartialDecodeAfterClearFrameBufferCache) { + TestResumePartialDecodeAfterClearFrameBufferCache( + &CreateWEBPDecoder, "/images/resources/webp-animated-large.webp"); +} + +TEST(AnimatedWebPTests, decodeAfterReallocatingData) { + TestDecodeAfterReallocatingData(&CreateWEBPDecoder, + "/images/resources/webp-animated.webp"); + TestDecodeAfterReallocatingData( + &CreateWEBPDecoder, "/images/resources/webp-animated-icc-xmp.webp"); +} + +TEST(AnimatedWebPTests, alphaBlending) { + TestAlphaBlending(&CreateWEBPDecoder, "/images/resources/webp-animated.webp"); + TestAlphaBlending(&CreateWEBPDecoder, + "/images/resources/webp-animated-semitransparent1.webp"); + TestAlphaBlending(&CreateWEBPDecoder, + "/images/resources/webp-animated-semitransparent2.webp"); + TestAlphaBlending(&CreateWEBPDecoder, + "/images/resources/webp-animated-semitransparent3.webp"); + TestAlphaBlending(&CreateWEBPDecoder, + "/images/resources/webp-animated-semitransparent4.webp"); +} + +TEST(AnimatedWebPTests, isSizeAvailable) { + TestByteByByteSizeAvailable(&CreateWEBPDecoder, + "/images/resources/webp-animated.webp", 142u, + false, kAnimationLoopInfinite); + // FIXME: Add color profile support for animated webp images. + TestByteByByteSizeAvailable(&CreateWEBPDecoder, + "/images/resources/webp-animated-icc-xmp.webp", + 1404u, false, 31999); +} + +TEST(AnimatedWEBPTests, clearCacheExceptFrameWithAncestors) { + std::unique_ptr<ImageDecoder> decoder = CreateWEBPDecoder(); + + scoped_refptr<SharedBuffer> full_data = + ReadFile("/images/resources/webp-animated.webp"); + ASSERT_TRUE(full_data.get()); + decoder->SetData(full_data.get(), true); + + ASSERT_EQ(3u, decoder->FrameCount()); + // We need to store pointers to the image frames, since calling + // FrameBufferAtIndex will decode the frame if it is not FrameComplete, + // and we want to read the status of the frame without decoding it again. + ImageFrame* buffers[3]; + size_t buffer_sizes[3]; + for (size_t i = 0; i < decoder->FrameCount(); i++) { + buffers[i] = decoder->DecodeFrameBufferAtIndex(i); + ASSERT_EQ(ImageFrame::kFrameComplete, buffers[i]->GetStatus()); + buffer_sizes[i] = decoder->FrameBytesAtIndex(i); + } + + // Explicitly set the required previous frame for the frames, since this test + // is designed on this chain. Whether the frames actually depend on each + // other is not important for this test - ClearCacheExceptFrame just looks at + // the frame status and the required previous frame. + buffers[1]->SetRequiredPreviousFrameIndex(0); + buffers[2]->SetRequiredPreviousFrameIndex(1); + + // Clear the cache except for a single frame. All other frames should be + // cleared to FrameEmpty, since this frame is FrameComplete. + EXPECT_EQ(buffer_sizes[0] + buffer_sizes[2], + decoder->ClearCacheExceptFrame(1)); + EXPECT_EQ(ImageFrame::kFrameEmpty, buffers[0]->GetStatus()); + EXPECT_EQ(ImageFrame::kFrameComplete, buffers[1]->GetStatus()); + EXPECT_EQ(ImageFrame::kFrameEmpty, buffers[2]->GetStatus()); + + // Verify that the required previous frame is also preserved if the provided + // frame is not FrameComplete. The simulated situation is: + // + // Frame 0 <--------- Frame 1 <--------- Frame 2 + // FrameComplete depends on FrameComplete depends on FramePartial + // + // The expected outcome is that frame 1 and frame 2 are preserved, since + // frame 1 is necessary to fully decode frame 2. + for (size_t i = 0; i < decoder->FrameCount(); i++) { + ASSERT_EQ(ImageFrame::kFrameComplete, + decoder->DecodeFrameBufferAtIndex(i)->GetStatus()); + } + buffers[2]->SetStatus(ImageFrame::kFramePartial); + EXPECT_EQ(buffer_sizes[0], decoder->ClearCacheExceptFrame(2)); + EXPECT_EQ(ImageFrame::kFrameEmpty, buffers[0]->GetStatus()); + EXPECT_EQ(ImageFrame::kFrameComplete, buffers[1]->GetStatus()); + EXPECT_EQ(ImageFrame::kFramePartial, buffers[2]->GetStatus()); + + // Verify that the nearest FrameComplete required frame is preserved if + // earlier required frames in the ancestor list are not FrameComplete. The + // simulated situation is: + // + // Frame 0 <--------- Frame 1 <--------- Frame 2 + // FrameComplete depends on FrameEmpty depends on FramePartial + // + // The expected outcome is that frame 0 and frame 2 are preserved. Frame 2 + // should be preserved since it is the frame passed to ClearCacheExceptFrame. + // Frame 0 should be preserved since it is the nearest FrameComplete ancestor. + // Thus, since frame 1 is FrameEmpty, no data is cleared in this case. + for (size_t i = 0; i < decoder->FrameCount(); i++) { + ASSERT_EQ(ImageFrame::kFrameComplete, + decoder->DecodeFrameBufferAtIndex(i)->GetStatus()); + } + buffers[1]->SetStatus(ImageFrame::kFrameEmpty); + buffers[2]->SetStatus(ImageFrame::kFramePartial); + EXPECT_EQ(0u, decoder->ClearCacheExceptFrame(2)); + EXPECT_EQ(ImageFrame::kFrameComplete, buffers[0]->GetStatus()); + EXPECT_EQ(ImageFrame::kFrameEmpty, buffers[1]->GetStatus()); + EXPECT_EQ(ImageFrame::kFramePartial, buffers[2]->GetStatus()); +} + +TEST(StaticWebPTests, truncatedImage) { + // VP8 data is truncated. + TestInvalidImage("/images/resources/truncated.webp", false); + // Chunk size in RIFF header doesn't match the file size. + TestInvalidImage("/images/resources/truncated2.webp", true); +} + +// Regression test for a bug where some valid images were failing to decode +// incrementally. +TEST(StaticWebPTests, incrementalDecode) { + TestByteByByteDecode(&CreateWEBPDecoder, + "/images/resources/crbug.364830.webp", 1u, + kAnimationNone); +} + +TEST(StaticWebPTests, isSizeAvailable) { + TestByteByByteSizeAvailable(&CreateWEBPDecoder, + "/images/resources/webp-color-profile-lossy.webp", + 520u, true, kAnimationNone); + TestByteByByteSizeAvailable(&CreateWEBPDecoder, "/images/resources/test.webp", + 30u, false, kAnimationNone); +} + +TEST(StaticWebPTests, notAnimated) { + std::unique_ptr<ImageDecoder> decoder = CreateWEBPDecoder(); + scoped_refptr<SharedBuffer> data = + ReadFile("/images/resources/webp-color-profile-lossy.webp"); + ASSERT_TRUE(data.get()); + decoder->SetData(data.get(), true); + EXPECT_EQ(1u, decoder->FrameCount()); + EXPECT_EQ(kAnimationNone, decoder->RepetitionCount()); +} + +} // namespace blink |