summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/platform/image-decoders
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/blink/renderer/platform/image-decoders')
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/DEPS3
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/OWNERS6
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder.cc127
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder.h86
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder_test.cc98
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_reader.cc857
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_reader.h367
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/fast_shared_buffer_reader.cc96
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/fast_shared_buffer_reader.h97
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/fast_shared_buffer_reader_test.cc238
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder.cc283
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder.h102
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder_test.cc507
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_reader.cc902
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_reader.h371
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/ico/ico_image_decoder.cc344
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/ico/ico_image_decoder.h186
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/ico/ico_image_decoder_test.cc110
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/image_animation.h37
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/image_decoder.cc564
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/image_decoder.h437
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/image_decoder_test.cc266
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/image_decoder_test_helpers.cc598
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/image_decoder_test_helpers.h122
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/image_frame.cc209
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/image_frame.h304
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder.cc1074
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder.h86
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder_test.cc387
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.cc706
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.h84
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder_test.cc1084
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_reader.cc701
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_reader.h174
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/segment_reader.cc199
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/segment_reader.h52
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/testing/bad-code.gifbin0 -> 1034 bytes
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/testing/bad-initial-code.gifbin0 -> 362 bytes
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/testing/broken.gifbin0 -> 906 bytes
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/testing/first-frame-has-greater-size-than-screen-size.gifbin0 -> 2113 bytes
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/testing/invalid-disposal-method.gifbin0 -> 901 bytes
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/testing/many-progressive-scans.jpgbin0 -> 4477 bytes
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/testing/radient-bad-terminator.gifbin0 -> 1034 bytes
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/testing/radient.gifbin0 -> 1034 bytes
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.cc557
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.h121
-rw-r--r--chromium/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder_test.cc539
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
new file mode 100644
index 00000000000..ed519e76794
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/image-decoders/testing/bad-code.gif
Binary files differ
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
new file mode 100644
index 00000000000..3dc27ec4056
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/image-decoders/testing/bad-initial-code.gif
Binary files differ
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
new file mode 100644
index 00000000000..e3a7c4ebe7d
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/image-decoders/testing/broken.gif
Binary files differ
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
new 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
Binary files differ
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
new file mode 100644
index 00000000000..158f37677d7
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/image-decoders/testing/invalid-disposal-method.gif
Binary files differ
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
new file mode 100644
index 00000000000..05a1a00b09b
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/image-decoders/testing/many-progressive-scans.jpg
Binary files differ
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
new file mode 100644
index 00000000000..b4e9ebdafb7
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/image-decoders/testing/radient-bad-terminator.gif
Binary files differ
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
new file mode 100644
index 00000000000..92de286ebbe
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/image-decoders/testing/radient.gif
Binary files differ
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