Cherry-pick 'Reland "Stop building SkCodec"'
Bug: 779261
This is a reland of fd493b4bc80363d51a4c142ddf287d38e86fc1f5
Original change's description:
> Stop building SkCodec
>
> Bug: 768878
>
> Building SkCodec seems to have caused a paint regression on a webpage
> without any images. This leads us to suspect "some minor compiler
> optimization tickling". Stop building it to confirm. Two CLs rely on
> SkCodec:
>
> "Use SkCodec internally in GIFImageDecoder"
> 4fed3346549a90c0de40c02f6388e19e8151e92a. This introduced building
> SkCodec.
>
> "Enable Skia's SkImageGenerator implementation"
> f5eb27c2b897f206b275fd862e874b64159cc15e. This used SkCodec to fix
> crbug.com/758459, but that seems to have been fixed in another way.
>
> In addition, this corrects some formatting in the old code (as
> commanded by presubmit), and makes some other minor changes (no more
> PassRefPtr, FrameDurationAtIndex now returns a TimeDelta).
>
> Change-Id: Ic2bdd87740da0232c9c07e27eed6049efc26d76c
> Reviewed-on: https://chromium-review.googlesource.com/718918
> Commit-Queue: Leon Scroggins <[email protected]>
> Reviewed-by: Chris Blume <[email protected]>
> Reviewed-by: Fredrik Söderquist <[email protected]>
> Reviewed-by: Leon Scroggins <[email protected]>
> Reviewed-by: Philip Rogers <[email protected]>
> Cr-Commit-Position: refs/heads/master@{#509570}
[email protected]
(cherry picked from commit 8bf6a886aad1c42210fd16f372097236db34c162)
Bug: 768878
Change-Id: I18ce8032a1154f222d7392ac5a48b4cd5ec31672
Reviewed-on: https://chromium-review.googlesource.com/730484
Reviewed-by: Philip Rogers <[email protected]>
Reviewed-by: Leon Scroggins <[email protected]>
Commit-Queue: Leon Scroggins <[email protected]>
Cr-Original-Commit-Position: refs/heads/master@{#510847}
Reviewed-on: https://chromium-review.googlesource.com/743691
Cr-Commit-Position: refs/branch-heads/3239@{#332}
Cr-Branched-From: adb61db19020ed8ecee5e91b1a0ea4c924ae2988-refs/heads/master@{#508578}
diff --git a/skia/BUILD.gn b/skia/BUILD.gn
index 1e482fa6..c0477a6 100644
--- a/skia/BUILD.gn
+++ b/skia/BUILD.gn
@@ -45,10 +45,6 @@
"//third_party/skia/third_party/vulkan",
]
- if (!is_ios) {
- include_dirs += [ "//third_party/skia/include/codec" ]
- }
-
defines = skia_for_chromium_defines
defines += [
"SK_HAS_PNG_LIBRARY",
@@ -285,39 +281,16 @@
"//third_party/skia/src/images/SkPngEncoder.cpp",
"//third_party/skia/src/images/SkWebpEncoder.cpp",
"//third_party/skia/src/ports/SkGlobalInitialization_default.cpp",
+ "//third_party/skia/src/ports/SkImageGenerator_none.cpp",
"//third_party/skia/src/ports/SkOSFile_stdio.cpp",
"//third_party/skia/src/sfnt/SkOTTable_name.cpp",
"//third_party/skia/src/sfnt/SkOTUtils.cpp",
]
if (!is_ios) {
sources += [
- "//third_party/skia/src/codec/SkBmpBaseCodec.cpp",
- "//third_party/skia/src/codec/SkBmpCodec.cpp",
- "//third_party/skia/src/codec/SkBmpMaskCodec.cpp",
- "//third_party/skia/src/codec/SkBmpRLECodec.cpp",
- "//third_party/skia/src/codec/SkBmpStandardCodec.cpp",
- "//third_party/skia/src/codec/SkCodec.cpp",
- "//third_party/skia/src/codec/SkCodecImageGenerator.cpp",
- "//third_party/skia/src/codec/SkGifCodec.cpp",
- "//third_party/skia/src/codec/SkIcoCodec.cpp",
- "//third_party/skia/src/codec/SkJpegCodec.cpp",
- "//third_party/skia/src/codec/SkJpegDecoderMgr.cpp",
- "//third_party/skia/src/codec/SkJpegUtility.cpp",
- "//third_party/skia/src/codec/SkMaskSwizzler.cpp",
- "//third_party/skia/src/codec/SkMasks.cpp",
- "//third_party/skia/src/codec/SkPngCodec.cpp",
- "//third_party/skia/src/codec/SkSampler.cpp",
- "//third_party/skia/src/codec/SkStreamBuffer.cpp",
- "//third_party/skia/src/codec/SkSwizzler.cpp",
- "//third_party/skia/src/codec/SkWbmpCodec.cpp",
- "//third_party/skia/src/codec/SkWebpCodec.cpp",
"//third_party/skia/src/images/SkJPEGWriteUtility.cpp",
"//third_party/skia/src/images/SkJpegEncoder.cpp",
- "//third_party/skia/src/ports/SkImageGenerator_skia.cpp",
- "//third_party/skia/third_party/gif/SkGifImageReader.cpp",
]
- } else {
- sources += [ "//third_party/skia/src/ports/SkImageGenerator_none.cpp" ]
}
# This and skia_opts are really the same conceptual target so share headers.
diff --git a/third_party/WebKit/Source/platform/BUILD.gn b/third_party/WebKit/Source/platform/BUILD.gn
index dcd384e..de62e8f 100644
--- a/third_party/WebKit/Source/platform/BUILD.gn
+++ b/third_party/WebKit/Source/platform/BUILD.gn
@@ -1180,14 +1180,14 @@
"image-decoders/ImageFrame.h",
"image-decoders/SegmentReader.cpp",
"image-decoders/SegmentReader.h",
- "image-decoders/SegmentStream.cpp",
- "image-decoders/SegmentStream.h",
"image-decoders/bmp/BMPImageDecoder.cpp",
"image-decoders/bmp/BMPImageDecoder.h",
"image-decoders/bmp/BMPImageReader.cpp",
"image-decoders/bmp/BMPImageReader.h",
"image-decoders/gif/GIFImageDecoder.cpp",
"image-decoders/gif/GIFImageDecoder.h",
+ "image-decoders/gif/GIFImageReader.cpp",
+ "image-decoders/gif/GIFImageReader.h",
"image-decoders/ico/ICOImageDecoder.cpp",
"image-decoders/ico/ICOImageDecoder.h",
"image-decoders/jpeg/JPEGImageDecoder.cpp",
@@ -1850,7 +1850,6 @@
"image-decoders/ImageDecoderTest.cpp",
"image-decoders/ImageDecoderTestHelpers.cpp",
"image-decoders/ImageDecoderTestHelpers.h",
- "image-decoders/SegmentStreamTest.cpp",
"image-decoders/bmp/BMPImageDecoderTest.cpp",
"image-decoders/gif/GIFImageDecoderTest.cpp",
"image-decoders/ico/ICOImageDecoderTest.cpp",
diff --git a/third_party/WebKit/Source/platform/image-decoders/ImageDecoder.h b/third_party/WebKit/Source/platform/image-decoders/ImageDecoder.h
index e440c80..dc40551a 100644
--- a/third_party/WebKit/Source/platform/image-decoders/ImageDecoder.h
+++ b/third_party/WebKit/Source/platform/image-decoders/ImageDecoder.h
@@ -253,6 +253,15 @@
// 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
@@ -392,9 +401,7 @@
// |index| is smaller than |frame_buffer_cache_|.size().
virtual bool FrameStatusSufficientForSuccessors(size_t index) {
DCHECK(index < frame_buffer_cache_.size());
- ImageFrame::Status frame_status = frame_buffer_cache_[index].GetStatus();
- return frame_status == ImageFrame::kFramePartial ||
- frame_status == ImageFrame::kFrameComplete;
+ return frame_buffer_cache_[index].GetStatus() != ImageFrame::kFrameEmpty;
}
private:
diff --git a/third_party/WebKit/Source/platform/image-decoders/ImageFrame.cpp b/third_party/WebKit/Source/platform/image-decoders/ImageFrame.cpp
index cfda667c..99c7821b 100644
--- a/third_party/WebKit/Source/platform/image-decoders/ImageFrame.cpp
+++ b/third_party/WebKit/Source/platform/image-decoders/ImageFrame.cpp
@@ -82,12 +82,8 @@
has_alpha_ = other.has_alpha_;
bitmap_.reset();
SkImageInfo info = other.bitmap_.info();
- if (!bitmap_.tryAllocPixels(info)) {
- return false;
- }
-
- status_ = kFrameAllocated;
- return other.bitmap_.readPixels(info, bitmap_.getPixels(), bitmap_.rowBytes(),
+ return bitmap_.tryAllocPixels(info) &&
+ other.bitmap_.readPixels(info, bitmap_.getPixels(), bitmap_.rowBytes(),
0, 0);
}
@@ -102,7 +98,6 @@
bitmap_.reset();
bitmap_.swap(other->bitmap_);
other->status_ = kFrameEmpty;
- status_ = kFrameAllocated;
return true;
}
@@ -116,11 +111,7 @@
new_width, new_height,
premultiply_alpha_ ? kPremul_SkAlphaType : kUnpremul_SkAlphaType,
std::move(color_space)));
- bool allocated = bitmap_.tryAllocPixels(allocator_);
- if (allocated)
- status_ = kFrameAllocated;
-
- return allocated;
+ return bitmap_.tryAllocPixels(allocator_);
}
bool ImageFrame::HasAlpha() const {
diff --git a/third_party/WebKit/Source/platform/image-decoders/ImageFrame.h b/third_party/WebKit/Source/platform/image-decoders/ImageFrame.h
index 2af8752..ba6fb6f 100644
--- a/third_party/WebKit/Source/platform/image-decoders/ImageFrame.h
+++ b/third_party/WebKit/Source/platform/image-decoders/ImageFrame.h
@@ -46,7 +46,7 @@
DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
public:
- enum Status { kFrameEmpty, kFrameAllocated, kFramePartial, kFrameComplete };
+ 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
diff --git a/third_party/WebKit/Source/platform/image-decoders/SegmentStream.cpp b/third_party/WebKit/Source/platform/image-decoders/SegmentStream.cpp
deleted file mode 100644
index 3ac63a1..0000000
--- a/third_party/WebKit/Source/platform/image-decoders/SegmentStream.cpp
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright (c) 2017 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 "platform/image-decoders/SegmentStream.h"
-#include <utility>
-#include "platform/image-decoders/SegmentReader.h"
-
-namespace blink {
-
-SegmentStream::SegmentStream() = default;
-
-SegmentStream::SegmentStream(SegmentStream&& rhs)
- : reader_(std::move(rhs.reader_)), position_(rhs.position_) {}
-
-SegmentStream& SegmentStream::operator=(SegmentStream&& rhs) {
- reader_ = std::move(rhs.reader_);
- position_ = rhs.position_;
-
- return *this;
-}
-
-SegmentStream::~SegmentStream() = default;
-
-void SegmentStream::SetReader(WTF::RefPtr<SegmentReader> reader) {
- reader_ = std::move(reader);
-}
-
-bool SegmentStream::IsCleared() const {
- return !reader_ || position_ > reader_->size();
-}
-
-size_t SegmentStream::read(void* buffer, size_t size) {
- DCHECK(!IsCleared());
-
- size = std::min(size, reader_->size() - position_);
-
- size_t bytes_advanced = 0;
- if (!buffer) { // skipping, not reading
- bytes_advanced = size;
- } else {
- bytes_advanced = peek(buffer, size);
- }
-
- position_ += bytes_advanced;
-
- return bytes_advanced;
-}
-
-size_t SegmentStream::peek(void* buffer, size_t size) const {
- DCHECK(!IsCleared());
-
- size = std::min(size, reader_->size() - position_);
-
- size_t total_bytes_peeked = 0;
- char* buffer_as_char_ptr = reinterpret_cast<char*>(buffer);
- while (size) {
- const char* segment = nullptr;
- size_t bytes_peeked =
- reader_->GetSomeData(segment, position_ + total_bytes_peeked);
- if (!bytes_peeked)
- break;
- if (bytes_peeked > size)
- bytes_peeked = size;
-
- memcpy(buffer_as_char_ptr, segment, bytes_peeked);
- buffer_as_char_ptr += bytes_peeked;
- size -= bytes_peeked;
- total_bytes_peeked += bytes_peeked;
- }
-
- return total_bytes_peeked;
-}
-
-bool SegmentStream::isAtEnd() const {
- return !reader_ || position_ >= reader_->size();
-}
-
-bool SegmentStream::rewind() {
- position_ = 0;
- return true;
-}
-
-bool SegmentStream::seek(size_t position) {
- position_ = position;
- return true;
-}
-
-bool SegmentStream::move(long offset) {
- DCHECK_GT(offset, 0);
- position_ += offset;
- return true;
-}
-
-size_t SegmentStream::getLength() const {
- if (reader_)
- return reader_->size();
-
- return 0;
-}
-
-} // namespace blink
diff --git a/third_party/WebKit/Source/platform/image-decoders/SegmentStream.h b/third_party/WebKit/Source/platform/image-decoders/SegmentStream.h
deleted file mode 100644
index 37359e6..0000000
--- a/third_party/WebKit/Source/platform/image-decoders/SegmentStream.h
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright (c) 2017 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 SegmentStream_h
-#define SegmentStream_h
-
-#include <algorithm>
-#include "platform/PlatformExport.h"
-#include "platform/wtf/RefPtr.h"
-#include "third_party/skia/include/core/SkStream.h"
-
-namespace blink {
-
-class SegmentReader;
-
-class PLATFORM_EXPORT SegmentStream : public SkStream {
- public:
- SegmentStream();
- SegmentStream(const SegmentStream&) = delete;
- SegmentStream& operator=(const SegmentStream&) = delete;
- SegmentStream(SegmentStream&&);
- SegmentStream& operator=(SegmentStream&&);
-
- ~SegmentStream() override;
-
- void SetReader(WTF::RefPtr<SegmentReader>);
- // If a buffer has shrunk beyond the point we have read, it has been cleared.
- // This allows clients to be aware of when data suddenly disappears.
- bool IsCleared() const;
-
- // From SkStream:
- size_t read(void* buffer, size_t) override;
- size_t peek(void* buffer, size_t) const override;
- bool isAtEnd() const override;
- bool rewind() override;
- bool hasPosition() const override { return true; }
- size_t getPosition() const override { return position_; }
- bool seek(size_t position) override;
- bool move(long offset) override;
- bool hasLength() const override { return true; }
- size_t getLength() const override;
-
- private:
- WTF::RefPtr<SegmentReader> reader_;
- size_t position_ = 0;
-};
-
-} // namespace blink
-
-#endif
diff --git a/third_party/WebKit/Source/platform/image-decoders/SegmentStreamTest.cpp b/third_party/WebKit/Source/platform/image-decoders/SegmentStreamTest.cpp
deleted file mode 100644
index 180a728e..0000000
--- a/third_party/WebKit/Source/platform/image-decoders/SegmentStreamTest.cpp
+++ /dev/null
@@ -1,702 +0,0 @@
-// Copyright (c) 2017 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 "platform/image-decoders/SegmentStream.h"
-
-#include <array>
-#include <memory>
-#include "platform/SharedBuffer.h"
-#include "platform/image-decoders/SegmentReader.h"
-#include "platform/wtf/PtrUtil.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-// SegmentStream has 4 accessors which do not alter state:
-// - isCleared()
-// - isAtEnd()
-// - getPosition()
-// - getLength()
-//
-// For every operation which changes state we can test:
-// - the operation completed as expected,
-// - the accessors did not change, and/or
-// - the accessors changed in the way we expected.
-//
-// There are actually 2 more accessors:
-// - hasPosition()
-// - hasLength()
-// but these should always return true to indicate that we can call getLength()
-// for example. So let's not add them to every state changing operation and add
-// needless complexity.
-
-namespace {
-
-constexpr size_t kBufferAllocationSize = 20;
-constexpr size_t kInsideBufferPosition = 10;
-constexpr size_t kPastEndOfBufferPosition = 30;
-
-testing::AssertionResult IsCleared(const blink::SegmentStream&);
-testing::AssertionResult IsAtEnd(const blink::SegmentStream&);
-testing::AssertionResult PositionIsZero(const blink::SegmentStream&);
-testing::AssertionResult PositionIsInsideBuffer(const blink::SegmentStream&);
-testing::AssertionResult PositionIsAtEndOfBuffer(const blink::SegmentStream&);
-testing::AssertionResult LengthIsZero(const blink::SegmentStream&);
-testing::AssertionResult LengthIsAllocationSize(const blink::SegmentStream&);
-
-// Many of these tests require a SegmentStream with populated data.
-//
-// This function creates a buffer of size |kBufferAllocationSize| and prepares
-// a SegmentStream with that buffer.
-// This also populates other properties such as the length, cleared state, etc.
-blink::SegmentStream CreatePopulatedSegmentStream();
-
-// This function creates a buffer of size |kBufferAllocationSize| to be used
-// when populating a SegmentStream.
-WTF::RefPtr<blink::SegmentReader> CreateSegmentReader();
-
-size_t ReadFromSegmentStream(blink::SegmentStream&,
- size_t amount_to_read = kInsideBufferPosition);
-size_t PeekIntoSegmentStream(blink::SegmentStream&,
- size_t amount_to_peek = kInsideBufferPosition);
-
-} // namespace
-
-namespace blink {
-
-TEST(SegmentStreamTest, DefaultConstructorShouldSetIsCleared) {
- SegmentStream segment_stream;
-
- ASSERT_TRUE(IsCleared(segment_stream));
-}
-
-TEST(SegmentStreamTest, DefaultConstructorShouldSetIsAtEnd) {
- SegmentStream segment_stream;
-
- ASSERT_TRUE(IsAtEnd(segment_stream));
-}
-
-TEST(SegmentStreamTest, DefaultContructorShouldClearPosition) {
- SegmentStream segment_stream;
-
- ASSERT_TRUE(PositionIsZero(segment_stream));
-}
-
-TEST(SegmentStreamTest, DefaultConstructorShouldHaveZeroLength) {
- SegmentStream segment_stream;
-
- ASSERT_TRUE(LengthIsZero(segment_stream));
-}
-
-TEST(SegmentStreamTest, MoveConstructorShouldSetIsClearedWhenRhsIsCleared) {
- SegmentStream cleared_segment_stream;
- ASSERT_TRUE(IsCleared(cleared_segment_stream));
-
- SegmentStream move_constructed_segment_stream =
- std::move(cleared_segment_stream);
-
- ASSERT_TRUE(IsCleared(move_constructed_segment_stream));
-}
-
-TEST(SegmentStreamTest,
- MoveConstructorShouldUnsetIsClearedWhenRhsIsNotCleared) {
- SegmentStream uncleared_segment_stream = CreatePopulatedSegmentStream();
- ASSERT_FALSE(IsCleared(uncleared_segment_stream));
-
- SegmentStream move_constructed_segment_stream =
- std::move(uncleared_segment_stream);
-
- ASSERT_FALSE(IsCleared(move_constructed_segment_stream));
-}
-
-TEST(SegmentStreamTest, MoveConstructorShouldSetIsAtEndWhenRhsIsAtEnd) {
- SegmentStream at_end_segment_stream;
- ASSERT_TRUE(IsAtEnd(at_end_segment_stream));
-
- SegmentStream move_constructed_segment_stream =
- std::move(at_end_segment_stream);
-
- ASSERT_TRUE(IsAtEnd(move_constructed_segment_stream));
-}
-
-TEST(SegmentStreamTest, MoveConstructorShouldUnsetIsAtEndWhenRhsIsNotAtEnd) {
- SegmentStream not_at_end_segment_stream = CreatePopulatedSegmentStream();
- ASSERT_FALSE(IsAtEnd(not_at_end_segment_stream));
-
- SegmentStream move_constructed_segment_stream =
- std::move(not_at_end_segment_stream);
-
- ASSERT_FALSE(IsAtEnd(move_constructed_segment_stream));
-}
-
-TEST(SegmentStreamTest, MoveContructorShouldCopyRhsPosition) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- segment_stream.seek(kInsideBufferPosition);
- ASSERT_EQ(kInsideBufferPosition, segment_stream.getPosition());
-
- SegmentStream move_constructed_segment_stream = std::move(segment_stream);
-
- ASSERT_EQ(kInsideBufferPosition,
- move_constructed_segment_stream.getPosition());
-}
-
-TEST(SegmentStreamTest, MoveConstructorShouldCopyRhsLength) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ASSERT_EQ(kBufferAllocationSize, segment_stream.getLength());
-
- SegmentStream move_constructed_segment_stream = std::move(segment_stream);
-
- ASSERT_EQ(kBufferAllocationSize, move_constructed_segment_stream.getLength());
-}
-
-TEST(SegmentStreamTest,
- MoveAssignmentOperatorShouldSetIsClearedWhenRhsIsCleared) {
- SegmentStream cleared_segment_stream;
- ASSERT_TRUE(IsCleared(cleared_segment_stream));
-
- SegmentStream move_assigned_segment_stream;
- move_assigned_segment_stream = std::move(cleared_segment_stream);
-
- ASSERT_TRUE(IsCleared(move_assigned_segment_stream));
-}
-
-TEST(SegmentStreamTest,
- MoveAssignmentOperatorShouldUnsetIsClearedWhenRhsIsNotCleared) {
- SegmentStream uncleared_segment_stream = CreatePopulatedSegmentStream();
- ASSERT_FALSE(IsCleared(uncleared_segment_stream));
-
- SegmentStream move_assigned_segment_stream;
- move_assigned_segment_stream = std::move(uncleared_segment_stream);
-
- ASSERT_FALSE(IsCleared(move_assigned_segment_stream));
-}
-
-TEST(SegmentStreamTest, MoveAssignmentOperatorShouldSetIsAtEndWhenRhsIsAtEnd) {
- SegmentStream at_end_segment_stream;
- ASSERT_TRUE(IsAtEnd(at_end_segment_stream));
-
- SegmentStream move_assigned_segment_stream;
- move_assigned_segment_stream = std::move(at_end_segment_stream);
-
- ASSERT_TRUE(IsAtEnd(move_assigned_segment_stream));
-}
-
-TEST(SegmentStreamTest,
- MoveAssignmentOperatorShouldUnsetIsAtEndWhenRhsIsNotAtEnd) {
- SegmentStream not_at_end_segment_stream = CreatePopulatedSegmentStream();
- ASSERT_FALSE(IsAtEnd(not_at_end_segment_stream));
-
- SegmentStream move_assigned_segment_stream;
- move_assigned_segment_stream = std::move(not_at_end_segment_stream);
-
- ASSERT_FALSE(IsAtEnd(move_assigned_segment_stream));
-}
-
-TEST(SegmentStreamTest, MoveAssignmentOperatorShouldCopyRhsPosition) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- segment_stream.seek(kInsideBufferPosition);
- ASSERT_EQ(kInsideBufferPosition, segment_stream.getPosition());
-
- SegmentStream move_assigned_segment_stream;
- move_assigned_segment_stream = std::move(segment_stream);
-
- ASSERT_EQ(kInsideBufferPosition, move_assigned_segment_stream.getPosition());
-}
-
-TEST(SegmentStreamTest, MoveAssignmentOperatorShouldCopyRhsLength) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ASSERT_EQ(kBufferAllocationSize, segment_stream.getLength());
-
- SegmentStream move_assigned_segment_stream;
- move_assigned_segment_stream = std::move(segment_stream);
-
- ASSERT_EQ(kBufferAllocationSize, move_assigned_segment_stream.getLength());
-}
-
-TEST(SegmentStreamTest, SetReaderShouldUnsetIsCleared) {
- SegmentStream segment_stream;
- WTF::RefPtr<SegmentReader> segment_reader = CreateSegmentReader();
- ASSERT_TRUE(IsCleared(segment_stream));
-
- segment_stream.SetReader(segment_reader);
-
- ASSERT_FALSE(IsCleared(segment_stream));
-}
-
-TEST(SegmentStreamTest, SetReaderShouldUnsetIsAtEnd) {
- SegmentStream segment_stream;
- WTF::RefPtr<SegmentReader> segment_reader = CreateSegmentReader();
- ASSERT_TRUE(IsAtEnd(segment_stream));
-
- segment_stream.SetReader(segment_reader);
-
- ASSERT_FALSE(IsAtEnd(segment_stream));
-}
-
-TEST(SegmentStreamTest, SetReaderShouldNotChangePosition) {
- SegmentStream segment_stream;
- WTF::RefPtr<SegmentReader> segment_reader = CreateSegmentReader();
- ASSERT_TRUE(PositionIsZero(segment_stream));
-
- segment_stream.SetReader(segment_reader);
-
- ASSERT_TRUE(PositionIsZero(segment_stream));
-}
-
-TEST(SegmentStreamTest, SetReaderShouldUpdateLength) {
- SegmentStream segment_stream;
- WTF::RefPtr<SegmentReader> segment_reader = CreateSegmentReader();
- ASSERT_FALSE(LengthIsAllocationSize(segment_stream));
-
- segment_stream.SetReader(segment_reader);
-
- ASSERT_TRUE(LengthIsAllocationSize(segment_stream));
-}
-
-TEST(SegmentStreamTest, SetReaderShouldSetIsClearedWhenSetToNull) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ASSERT_FALSE(IsCleared(segment_stream));
-
- segment_stream.SetReader(nullptr);
-
- ASSERT_TRUE(IsCleared(segment_stream));
-}
-
-TEST(SegmentStreamTest, SetReaderShouldSetIsClearedWhenReaderSizeNotBigEnough) {
- SegmentStream segment_stream;
- segment_stream.seek(kPastEndOfBufferPosition);
- RefPtr<blink::SegmentReader> segment_reader = CreateSegmentReader();
-
- segment_stream.SetReader(segment_reader);
-
- ASSERT_TRUE(IsCleared(segment_stream));
-}
-
-TEST(SegmentStreamTest, SetReaderShouldSetIsAtEndWhenReaderSizeNotBigEnough) {
- SegmentStream segment_stream;
- segment_stream.seek(kPastEndOfBufferPosition);
- RefPtr<blink::SegmentReader> segment_reader = CreateSegmentReader();
-
- segment_stream.SetReader(segment_reader);
-
- ASSERT_TRUE(IsAtEnd(segment_stream));
-}
-
-TEST(SegmentStreamTest,
- SetReaderShouldNotChangePositionWhenReaderSizeNotBigEnough) {
- SegmentStream segment_stream;
- segment_stream.seek(kPastEndOfBufferPosition);
- RefPtr<blink::SegmentReader> segment_reader = CreateSegmentReader();
-
- segment_stream.SetReader(segment_reader);
-
- ASSERT_EQ(kPastEndOfBufferPosition, segment_stream.getPosition());
-}
-
-TEST(SegmentStreamTest, SetReaderShouldChangeLengthWhenReaderSizeNotBigEnough) {
- SegmentStream segment_stream;
- segment_stream.seek(kPastEndOfBufferPosition);
- RefPtr<blink::SegmentReader> segment_reader = CreateSegmentReader();
-
- segment_stream.SetReader(segment_reader);
-
- ASSERT_TRUE(LengthIsAllocationSize(segment_stream));
-}
-
-TEST(SegmentStreamTest, SetReaderShouldSetIsAtEndWhenSetToNull) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ASSERT_FALSE(IsAtEnd(segment_stream));
-
- segment_stream.SetReader(nullptr);
- ASSERT_TRUE(IsAtEnd(segment_stream));
-}
-
-TEST(SegmentStreamTest, SetReaderShouldNotChangePositionWhenSetToNull) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- const size_t amount_read = ReadFromSegmentStream(segment_stream);
- ASSERT_EQ(kInsideBufferPosition, amount_read);
- const size_t pre_nulled_position = segment_stream.getPosition();
- ASSERT_EQ(kInsideBufferPosition, pre_nulled_position);
-
- segment_stream.SetReader(nullptr);
-
- ASSERT_EQ(kInsideBufferPosition, segment_stream.getPosition());
-}
-
-TEST(SegmentStreamTest, SetReaderShouldClearLengthWhenSetToNull) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ASSERT_FALSE(LengthIsZero(segment_stream));
-
- segment_stream.SetReader(nullptr);
-
- ASSERT_TRUE(LengthIsZero(segment_stream));
-}
-
-TEST(SegmentStreamTest, ReadShouldConsumeBuffer) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
-
- const size_t amount_read = ReadFromSegmentStream(segment_stream);
-
- ASSERT_EQ(kInsideBufferPosition, amount_read);
-}
-
-TEST(SegmentStreamTest, ReadShouldNotClear) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
-
- ReadFromSegmentStream(segment_stream);
-
- ASSERT_FALSE(IsCleared(segment_stream));
-}
-
-TEST(SegmentStreamTest, ReadShouldUpdateIsAtEnd) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ASSERT_FALSE(IsAtEnd(segment_stream));
-
- ReadFromSegmentStream(segment_stream, kBufferAllocationSize);
-
- ASSERT_TRUE(IsAtEnd(segment_stream));
-}
-
-TEST(SegmentStreamTest, ReadShouldUpdatePosition) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ASSERT_TRUE(PositionIsZero(segment_stream));
-
- ReadFromSegmentStream(segment_stream);
-
- ASSERT_TRUE(PositionIsInsideBuffer(segment_stream));
-}
-
-TEST(SegmentStreamTest, ReadShouldNotChangeLength) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ASSERT_EQ(kBufferAllocationSize, segment_stream.getLength());
-
- ReadFromSegmentStream(segment_stream);
-
- ASSERT_EQ(kBufferAllocationSize, segment_stream.getLength());
-}
-
-TEST(SegmentStreamTest, ReadShouldConsumeBufferWithoutGoingPastTheEnd) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
-
- const size_t amount_read =
- ReadFromSegmentStream(segment_stream, kPastEndOfBufferPosition);
-
- ASSERT_EQ(kBufferAllocationSize, amount_read);
-}
-
-TEST(SegmentStreamTest, ReadShouldSetIsAtEndWhenPastEnd) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ASSERT_FALSE(IsAtEnd(segment_stream));
-
- ReadFromSegmentStream(segment_stream, kPastEndOfBufferPosition);
-
- ASSERT_TRUE(IsAtEnd(segment_stream));
-}
-
-TEST(SegmentStreamTest, ReadShouldTruncatePositionWhenPastEnd) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ASSERT_TRUE(PositionIsZero(segment_stream));
-
- ReadFromSegmentStream(segment_stream, kPastEndOfBufferPosition);
-
- ASSERT_TRUE(PositionIsAtEndOfBuffer(segment_stream));
-}
-
-TEST(SegmentStreamTest, PeekShouldConsumeBuffer) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
-
- const size_t amount_peeked = PeekIntoSegmentStream(segment_stream);
-
- ASSERT_EQ(kInsideBufferPosition, amount_peeked);
-}
-
-TEST(SegmentStreamTest, PeekShouldNotClear) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
-
- PeekIntoSegmentStream(segment_stream);
-
- ASSERT_FALSE(IsCleared(segment_stream));
-}
-
-TEST(SegmentStreamTest, PeekShouldNotUpdateIsAtEnd) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ASSERT_FALSE(IsAtEnd(segment_stream));
-
- PeekIntoSegmentStream(segment_stream, kBufferAllocationSize);
-
- ASSERT_FALSE(IsAtEnd(segment_stream));
-}
-
-TEST(SegmentStreamTest, PeekShouldNotUpdatePosition) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ASSERT_TRUE(PositionIsZero(segment_stream));
-
- PeekIntoSegmentStream(segment_stream);
-
- ASSERT_TRUE(PositionIsZero(segment_stream));
-}
-
-TEST(SegmentStreamTest, PeekShouldNotChangeLength) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
-
- PeekIntoSegmentStream(segment_stream);
-
- ASSERT_EQ(kBufferAllocationSize, segment_stream.getLength());
-}
-
-TEST(SegmentStreamTest, PeekShouldConsumeBufferWithoutGoingPastTheEnd) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
-
- const size_t amount_peeked =
- PeekIntoSegmentStream(segment_stream, kPastEndOfBufferPosition);
-
- ASSERT_EQ(kBufferAllocationSize, amount_peeked);
-}
-
-TEST(SegmentStreamTest, PeekShouldNotSetIsAtEndWhenPastEnd) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ASSERT_FALSE(IsAtEnd(segment_stream));
-
- PeekIntoSegmentStream(segment_stream, kPastEndOfBufferPosition);
-
- ASSERT_FALSE(IsAtEnd(segment_stream));
-}
-
-TEST(SegmentStreamTest, PeekShouldNotTruncatePositionWhenPastEnd) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ASSERT_TRUE(PositionIsZero(segment_stream));
-
- PeekIntoSegmentStream(segment_stream, kPastEndOfBufferPosition);
-
- ASSERT_TRUE(PositionIsZero(segment_stream));
-}
-
-TEST(SegmentStreamTest, RewindShouldNotClear) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ReadFromSegmentStream(segment_stream);
- ASSERT_FALSE(IsCleared(segment_stream));
-
- segment_stream.rewind();
-
- ASSERT_FALSE(IsCleared(segment_stream));
-}
-
-TEST(SegmentStreamTest, RewindShouldNotSetAtEnd) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ReadFromSegmentStream(segment_stream);
- ASSERT_FALSE(IsAtEnd(segment_stream));
-
- segment_stream.rewind();
-
- ASSERT_FALSE(IsAtEnd(segment_stream));
-}
-
-TEST(SegmentStreamTest, RewindShouldResetPosition) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ReadFromSegmentStream(segment_stream);
- ASSERT_TRUE(PositionIsInsideBuffer(segment_stream));
-
- segment_stream.rewind();
-
- ASSERT_TRUE(PositionIsZero(segment_stream));
-}
-
-TEST(SegmentStreamTest, RewindShouldNotChangeLength) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ReadFromSegmentStream(segment_stream);
-
- segment_stream.rewind();
-
- ASSERT_EQ(kBufferAllocationSize, segment_stream.getLength());
-}
-
-TEST(SegmentStreamTest, HasPositionShouldBeSupported) {
- blink::SegmentStream segment_stream;
-
- ASSERT_TRUE(segment_stream.hasPosition());
-}
-
-TEST(SegmentStreamTest, SeekShouldNotSetIsCleared) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ASSERT_FALSE(IsCleared(segment_stream));
-
- segment_stream.seek(kInsideBufferPosition);
-
- ASSERT_FALSE(IsCleared(segment_stream));
-}
-
-TEST(SegmentStreamTest, SeekShouldNotSetIsAtEndWhenSeekingInsideTheBuffer) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ASSERT_FALSE(IsAtEnd(segment_stream));
-
- segment_stream.seek(kInsideBufferPosition);
-
- ASSERT_FALSE(IsAtEnd(segment_stream));
-}
-
-TEST(SegmentStreamTest, SeekShouldSetIsAtEndWhenSeekingToTheEndOfTheBuffer) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ASSERT_FALSE(IsAtEnd(segment_stream));
-
- segment_stream.seek(kBufferAllocationSize);
-
- ASSERT_TRUE(IsAtEnd(segment_stream));
-}
-
-TEST(SegmentStreamTest, SeekShouldUpdatePosition) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ASSERT_TRUE(PositionIsZero(segment_stream));
-
- segment_stream.seek(kInsideBufferPosition);
-
- ASSERT_EQ(kInsideBufferPosition, segment_stream.getPosition());
-}
-
-TEST(SegmentStreamTest, SeekShouldNotTruncatePositionWhenPastEnd) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ASSERT_TRUE(PositionIsZero(segment_stream));
-
- segment_stream.seek(kPastEndOfBufferPosition);
-
- ASSERT_EQ(kPastEndOfBufferPosition, segment_stream.getPosition());
-}
-
-TEST(SegmentStreamTest, SeekShouldNotUpdateLength) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
-
- segment_stream.seek(kInsideBufferPosition);
-
- ASSERT_EQ(kBufferAllocationSize, segment_stream.getLength());
-}
-
-TEST(SegmentStreamTest, MoveShouldNotSetCleared) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
-
- segment_stream.move(kInsideBufferPosition);
-
- ASSERT_FALSE(IsCleared(segment_stream));
-}
-
-TEST(SegmentStreamTest, MoveShouldUpdatePosition) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ASSERT_TRUE(PositionIsZero(segment_stream));
-
- segment_stream.move(kInsideBufferPosition);
-
- ASSERT_TRUE(PositionIsInsideBuffer(segment_stream));
-}
-
-TEST(SegmentStreamTest, MoveShouldNotTruncatePositionWhenPastEnd) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ASSERT_TRUE(PositionIsZero(segment_stream));
-
- segment_stream.move(kPastEndOfBufferPosition);
-
- ASSERT_EQ(kPastEndOfBufferPosition, segment_stream.getPosition());
-}
-
-TEST(SegmentStreamTest, MoveShouldNotChangeLength) {
- SegmentStream segment_stream = CreatePopulatedSegmentStream();
- ASSERT_TRUE(LengthIsAllocationSize(segment_stream));
-
- segment_stream.move(kInsideBufferPosition);
-
- ASSERT_TRUE(LengthIsAllocationSize(segment_stream));
-}
-
-TEST(SegmentStreamTest, HasLengthShouldBeSupported) {
- blink::SegmentStream segment_stream;
- ASSERT_TRUE(segment_stream.hasLength());
-}
-
-} // namespace blink
-
-namespace {
-
-testing::AssertionResult IsCleared(const blink::SegmentStream& segment_stream) {
- if (segment_stream.IsCleared())
- return testing::AssertionSuccess();
-
- return testing::AssertionFailure() << "SegmentStream is not clear";
-}
-
-testing::AssertionResult IsAtEnd(const blink::SegmentStream& segment_stream) {
- if (segment_stream.isAtEnd())
- return testing::AssertionSuccess();
-
- return testing::AssertionFailure() << "SegmentStream is not at the end";
-}
-
-testing::AssertionResult PositionIsZero(
- const blink::SegmentStream& segment_stream) {
- if (segment_stream.getPosition() == 0ul)
- return testing::AssertionSuccess();
-
- return testing::AssertionFailure() << "SegmentStream position is not 0";
-}
-
-testing::AssertionResult PositionIsInsideBuffer(
- const blink::SegmentStream& segment_stream) {
- if (segment_stream.getPosition() == kInsideBufferPosition)
- return testing::AssertionSuccess();
-
- return testing::AssertionFailure()
- << "SegmentStream position is not inside the buffer";
-}
-
-testing::AssertionResult PositionIsAtEndOfBuffer(
- const blink::SegmentStream& segment_stream) {
- if (segment_stream.getPosition() == kBufferAllocationSize)
- return testing::AssertionSuccess();
-
- return testing::AssertionFailure()
- << "SegmentStream position is not at the end of the buffer";
-}
-
-testing::AssertionResult LengthIsZero(
- const blink::SegmentStream& segment_stream) {
- if (segment_stream.getLength() == 0ul)
- return testing::AssertionSuccess();
-
- return testing::AssertionFailure() << "SegmentStream length is not 0";
-}
-
-testing::AssertionResult LengthIsAllocationSize(
- const blink::SegmentStream& segment_stream) {
- if (segment_stream.getLength() == kBufferAllocationSize)
- return testing::AssertionSuccess();
-
- return testing::AssertionFailure()
- << "SegmentStream length is not the allocation size";
-}
-
-blink::SegmentStream CreatePopulatedSegmentStream() {
- blink::SegmentStream segment_stream;
- segment_stream.SetReader(CreateSegmentReader());
- return segment_stream;
-}
-
-WTF::RefPtr<blink::SegmentReader> CreateSegmentReader() {
- std::array<char, kBufferAllocationSize> raw_buffer;
-
- WTF::RefPtr<blink::SharedBuffer> shared_buffer =
- blink::SharedBuffer::Create(raw_buffer.data(), kBufferAllocationSize);
-
- WTF::RefPtr<blink::SegmentReader> segment_reader =
- blink::SegmentReader::CreateFromSharedBuffer(std::move(shared_buffer));
-
- return segment_reader;
-}
-
-size_t ReadFromSegmentStream(blink::SegmentStream& segment_stream,
- size_t amount_to_read) {
- std::array<char, kBufferAllocationSize> read_buffer;
- return segment_stream.read(read_buffer.data(), amount_to_read);
-}
-
-size_t PeekIntoSegmentStream(blink::SegmentStream& segment_stream,
- size_t amount_to_peek) {
- std::array<char, kBufferAllocationSize> peek_buffer;
- return segment_stream.peek(peek_buffer.data(), amount_to_peek);
-}
-
-} // namespace
diff --git a/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageDecoder.cpp b/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageDecoder.cpp
index a765a782a..1879171 100644
--- a/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageDecoder.cpp
+++ b/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageDecoder.cpp
@@ -27,9 +27,8 @@
#include <limits>
#include <memory>
-#include "platform/image-decoders/SegmentStream.h"
+#include "platform/image-decoders/gif/GIFImageReader.h"
#include "platform/wtf/NotFound.h"
-#include "third_party/skia/include/core/SkImageInfo.h"
namespace blink {
@@ -37,306 +36,248 @@
const ColorBehavior& color_behavior,
size_t max_decoded_bytes)
: ImageDecoder(alpha_option, color_behavior, max_decoded_bytes),
- codec_(),
- segment_stream_(nullptr),
- prior_frame_(SkCodec::kNone) {}
+ repetition_count_(kAnimationLoopOnce) {}
-GIFImageDecoder::~GIFImageDecoder() = default;
+GIFImageDecoder::~GIFImageDecoder() {}
void GIFImageDecoder::OnSetData(SegmentReader* data) {
- if (!data) {
- if (segment_stream_)
- segment_stream_->SetReader(nullptr);
- return;
- }
-
- std::unique_ptr<SegmentStream> segment_stream;
- if (!segment_stream_) {
- segment_stream = std::make_unique<SegmentStream>();
- segment_stream_ = segment_stream.get();
- }
-
- segment_stream_->SetReader(std::move(data));
-
- if (!codec_) {
- SkCodec::Result codec_creation_result;
- codec_ = SkCodec::MakeFromStream(std::move(segment_stream),
- &codec_creation_result, nullptr);
- switch (codec_creation_result) {
- case SkCodec::kSuccess: {
- // SkCodec::MakeFromStream will read enough of the image to get the
- // image size.
- SkImageInfo image_info = codec_->getInfo();
- SetSize(image_info.width(), image_info.height());
- return;
- }
- case SkCodec::kIncompleteInput:
- // |segment_stream_|'s ownership is passed into MakeFromStream.
- // It is deleted if MakeFromStream fails.
- // If MakeFromStream fails, we set |segment_stream_| to null so
- // we aren't pointing to reclaimed memory.
- segment_stream_ = nullptr;
- return;
- default:
- SetFailed();
- return;
- }
- }
+ if (reader_)
+ reader_->SetData(data);
}
int GIFImageDecoder::RepetitionCount() const {
- if (!codec_ || segment_stream_->IsCleared())
- return repetition_count_;
-
- DCHECK(!Failed());
-
// 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.
+ // 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_|).
//
- // SkCodec will parse forward in the file if the repetition count has not
- // been seen yet.
- int repetition_count = codec_->getRepetitionCount();
-
- switch (repetition_count) {
- case 0: {
- // SkCodec returns 0 for both still images and animated images which
- // only play once.
- if (IsAllDataReceived() && codec_->getFrameCount() == 1) {
- repetition_count_ = kAnimationNone;
- break;
- }
-
- repetition_count_ = kAnimationLoopOnce;
- break;
- }
- case SkCodec::kRepetitionCountInfinite:
- repetition_count_ = kAnimationLoopInfinite;
- break;
- default:
- repetition_count_ = repetition_count;
- break;
- }
-
+ // 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 {
- SkCodec::FrameInfo frame_info;
- if (!codec_ || !codec_->getFrameInfo(index, &frame_info))
- return false;
- return frame_info.fFullyReceived;
+ return reader_ && (index < reader_->ImagesCount()) &&
+ reader_->FrameContext(index)->IsComplete();
}
TimeDelta GIFImageDecoder::FrameDurationAtIndex(size_t index) const {
- if (index < frame_buffer_cache_.size())
- return frame_buffer_cache_[index].Duration();
- return TimeDelta();
+ return (reader_ && (index < reader_->ImagesCount()) &&
+ reader_->FrameContext(index)->IsHeaderDefined())
+ ? TimeDelta::FromMilliseconds(
+ reader_->FrameContext(index)->DelayTime())
+ : TimeDelta();
}
bool GIFImageDecoder::SetFailed() {
- segment_stream_ = nullptr;
- codec_.reset();
+ reader_.reset();
return ImageDecoder::SetFailed();
}
-size_t GIFImageDecoder::ClearCacheExceptFrame(size_t index) {
- // SkCodec attempts to report the earliest possible required frame, but it is
- // possible that frame has been evicted, while a later frame (which could also
- // be used as the required frame) is still cached. Try to preserve a frame
- // that is still cached.
- if (frame_buffer_cache_.size() <= 1)
- return 0;
+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;
- size_t index2 = kNotFound;
- if (index < frame_buffer_cache_.size()) {
- const ImageFrame& frame = frame_buffer_cache_[index];
- if (frame.RequiredPreviousFrameIndex() != kNotFound &&
- (!FrameStatusSufficientForSuccessors(index) ||
- frame.GetDisposalMethod() == ImageFrame::kDisposeOverwritePrevious)) {
- index2 = GetViableReferenceFrameIndex(index);
+ 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;
}
}
- return ClearCacheExceptTwoFrames(index, index2);
+ // 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() {
- if (!codec_ || segment_stream_->IsCleared())
- return frame_buffer_cache_.size();
-
- return codec_->getFrameCount();
+ 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) {
- DCHECK(codec_);
-
- ImageFrame& frame = frame_buffer_cache_[index];
- // SkCodec does not inform us if only a portion of the image was updated
- // in the current frame. Because of this, rather than correctly filling in
- // the frame rect, we set the frame rect to be the image's full size.
- // The original frame rect is not used, anyway.
- IntSize full_image_size = Size();
- frame.SetOriginalFrameRect(IntRect(IntPoint(), full_image_size));
-
- SkCodec::FrameInfo frame_info;
- bool frame_info_received = codec_->getFrameInfo(index, &frame_info);
- DCHECK(frame_info_received);
- frame.SetDuration(TimeDelta::FromMilliseconds(frame_info.fDuration));
- size_t required_previous_frame_index;
- if (frame_info.fRequiredFrame == SkCodec::kNone) {
- required_previous_frame_index = kNotFound;
- } else {
- required_previous_frame_index =
- static_cast<size_t>(frame_info.fRequiredFrame);
- }
- frame.SetRequiredPreviousFrameIndex(required_previous_frame_index);
-
- ImageFrame::DisposalMethod disposal_method = ImageFrame::kDisposeNotSpecified;
- switch (frame_info.fDisposalMethod) {
- case SkCodecAnimation::DisposalMethod::kKeep:
- disposal_method = ImageFrame::kDisposeKeep;
- break;
- case SkCodecAnimation::DisposalMethod::kRestoreBGColor:
- disposal_method = ImageFrame::kDisposeOverwriteBgcolor;
- break;
- case SkCodecAnimation::DisposalMethod::kRestorePrevious:
- disposal_method = ImageFrame::kDisposeOverwritePrevious;
- break;
- }
- frame.SetDisposalMethod(disposal_method);
+ 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) {
- if (!codec_ || segment_stream_->IsCleared())
+ Parse(kGIFFrameCountQuery);
+
+ if (Failed())
return;
- DCHECK(!Failed());
-
- DCHECK_LT(index, frame_buffer_cache_.size());
-
UpdateAggressivePurging(index);
- ImageFrame& frame = frame_buffer_cache_[index];
- if (frame.GetStatus() == ImageFrame::kFrameEmpty) {
- size_t required_previous_frame_index = frame.RequiredPreviousFrameIndex();
- if (required_previous_frame_index == kNotFound) {
- frame.AllocatePixelData(Size().Width(), Size().Height(),
- ColorSpaceForSkImages());
- frame.ZeroFillPixelData();
- prior_frame_ = SkCodec::kNone;
- } else {
- size_t previous_frame_index = GetViableReferenceFrameIndex(index);
- if (previous_frame_index == kNotFound) {
- previous_frame_index = required_previous_frame_index;
- Decode(previous_frame_index);
- if (Failed()) {
- return;
- }
- }
-
- // We try to reuse |previous_frame| as starting state to avoid copying.
- // If CanReusePreviousFrameBuffer returns false, we must copy the data
- // since |previous_frame| is necessary to decode this or later frames.
- // In that case copy the data instead.
- ImageFrame& previous_frame = frame_buffer_cache_[previous_frame_index];
- if ((!CanReusePreviousFrameBuffer(index) ||
- !frame.TakeBitmapDataIfWritable(&previous_frame)) &&
- !frame.CopyBitmapData(previous_frame)) {
- SetFailed();
- return;
- }
- prior_frame_ = previous_frame_index;
- }
- }
-
- if (frame.GetStatus() == ImageFrame::kFrameAllocated) {
- SkImageInfo image_info = codec_->getInfo()
- .makeColorType(kN32_SkColorType)
- .makeColorSpace(ColorSpaceForSkImages());
-
- SkCodec::Options options;
- options.fFrameIndex = index;
- options.fPriorFrame = prior_frame_;
- options.fZeroInitialized = SkCodec::kNo_ZeroInitialized;
-
- SkCodec::Result start_incremental_decode_result =
- codec_->startIncrementalDecode(image_info, frame.Bitmap().getPixels(),
- frame.Bitmap().rowBytes(), &options);
- switch (start_incremental_decode_result) {
- case SkCodec::kSuccess:
- break;
- case SkCodec::kIncompleteInput:
- return;
- default:
- SetFailed();
- return;
- }
- frame.SetStatus(ImageFrame::kFramePartial);
- }
-
- SkCodec::Result incremental_decode_result = codec_->incrementalDecode();
- switch (incremental_decode_result) {
- case SkCodec::kSuccess: {
- SkCodec::FrameInfo frame_info;
- bool frame_info_received = codec_->getFrameInfo(index, &frame_info);
- DCHECK(frame_info_received);
- frame.SetHasAlpha(frame_info.fAlpha != SkEncodedInfo::kOpaque_Alpha);
- frame.SetPixelsChanged(true);
- frame.SetStatus(ImageFrame::kFrameComplete);
- PostDecodeProcessing(index);
- break;
- }
- case SkCodec::kIncompleteInput:
- frame.SetPixelsChanged(true);
- if (FrameIsReceivedAtIndex(index) || IsAllDataReceived()) {
- SetFailed();
- }
- break;
- default:
+ 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();
}
-bool GIFImageDecoder::CanReusePreviousFrameBuffer(size_t index) const {
- DCHECK_LT(index, frame_buffer_cache_.size());
- return frame_buffer_cache_[index].GetDisposalMethod() !=
- ImageFrame::kDisposeOverwritePrevious;
-}
+void GIFImageDecoder::Parse(GIFParseQuery query) {
+ if (Failed())
+ return;
-size_t GIFImageDecoder::GetViableReferenceFrameIndex(
- size_t dependent_index) const {
- DCHECK_LT(dependent_index, frame_buffer_cache_.size());
-
- size_t required_previous_frame_index =
- frame_buffer_cache_[dependent_index].RequiredPreviousFrameIndex();
-
- // Any frame in the range [|required_previous_frame_index|, |dependent_index|)
- // which has a disposal method other than kRestorePrevious can be provided as
- // the prior frame to SkCodec.
- //
- // SkCodec sets SkCodec::FrameInfo::fRequiredFrame to the earliest frame which
- // can be used. This might come up when several frames update the same
- // subregion. If that same subregion is about to be overwritten, it doesn't
- // matter which frame in that chain is provided.
- DCHECK_NE(required_previous_frame_index, kNotFound);
- // Loop backwards because the frames most likely to be in cache are the most
- // recent.
- for (size_t i = dependent_index - 1; i != required_previous_frame_index;
- i--) {
- const ImageFrame& frame = frame_buffer_cache_[i];
-
- if (frame.GetDisposalMethod() == ImageFrame::kDisposeOverwritePrevious)
- continue;
-
- if (frame.GetStatus() == ImageFrame::kFrameComplete) {
- return i;
- }
+ if (!reader_) {
+ reader_ = WTF::MakeUnique<GIFImageReader>(this);
+ reader_->SetData(data_);
}
- return kNotFound;
+ 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/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageDecoder.h b/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageDecoder.h
index 505f11db..44523dcf 100644
--- a/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageDecoder.h
+++ b/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageDecoder.h
@@ -29,13 +29,13 @@
#include <memory>
#include "platform/image-decoders/ImageDecoder.h"
#include "platform/wtf/Noncopyable.h"
-#include "platform/wtf/RefPtr.h"
#include "platform/wtf/Time.h"
-#include "third_party/skia/include/codec/SkCodec.h"
namespace blink {
-class SegmentStream;
+class GIFImageReader;
+
+using GIFRow = Vector<unsigned char>;
// This class decodes the GIF image format.
class PLATFORM_EXPORT GIFImageDecoder final : public ImageDecoder {
@@ -45,43 +45,56 @@
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 |codec_|. Be careful to avoid
- // accessing deleted memory.
+ // CAUTION: SetFailed() deletes |reader_|. Be careful to avoid
+ // accessing deleted memory, especially when calling this from inside
+ // GIFImageReader!
bool SetFailed() override;
- size_t ClearCacheExceptFrame(size_t) 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 DecodeSize() override {}
+ 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 a previous frame's buffer as its starting state, so
+ // 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;
- // When a frame depends on a previous frame's content, there is a list of
- // candidate reference frames. This function will find a previous frame from
- // that list which satisfies the requirements of being a reference frame
- // (kFrameComplete, not kDisposeOverwritePrevious).
- // If no frame is found, it returns kNotFound.
- size_t GetViableReferenceFrameIndex(size_t) const;
-
- std::unique_ptr<SkCodec> codec_;
- // |codec_| owns the SegmentStream, but we need access to it to append more
- // data as it arrives.
- SegmentStream* segment_stream_;
- mutable int repetition_count_ = kAnimationLoopOnce;
- int prior_frame_;
+ bool current_buffer_saw_alpha_;
+ mutable int repetition_count_;
+ std::unique_ptr<GIFImageReader> reader_;
};
} // namespace blink
diff --git a/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageDecoderTest.cpp b/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageDecoderTest.cpp
index 7ad0428..55f71425 100644
--- a/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageDecoderTest.cpp
+++ b/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageDecoderTest.cpp
@@ -58,8 +58,16 @@
RefPtr<SharedBuffer> data = ReadFile(dir, file);
ASSERT_TRUE(data.get());
decoder->SetData(data.get(), true);
+ EXPECT_EQ(kAnimationLoopOnce,
+ decoder->RepetitionCount()); // Default value before decode.
- EXPECT_EQ(expected_repetition_count, decoder->RepetitionCount());
+ 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
@@ -70,6 +78,7 @@
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();
@@ -94,6 +103,10 @@
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());
@@ -307,13 +320,10 @@
EXPECT_EQ(2u, decoder->FrameCount());
// Disposal method 4 is converted to ImageFrame::DisposeOverwritePrevious.
- // This is because some specs say method 3 is "overwrite previous", while
- // others say setting the third bit (i.e. method 4) is.
EXPECT_EQ(ImageFrame::kDisposeOverwritePrevious,
decoder->DecodeFrameBufferAtIndex(0)->GetDisposalMethod());
- // Unknown disposal methods (5 in this case) are converted to
- // ImageFrame::DisposeKeep.
- EXPECT_EQ(ImageFrame::kDisposeKeep,
+ // Disposal method 5 is ignored.
+ EXPECT_EQ(ImageFrame::kDisposeNotSpecified,
decoder->DecodeFrameBufferAtIndex(1)->GetDisposalMethod());
}
@@ -380,11 +390,11 @@
ImageFrame* premul_frame = premul_decoder->DecodeFrameBufferAtIndex(0);
EXPECT_TRUE(premul_frame &&
premul_frame->GetStatus() != ImageFrame::kFrameComplete);
- EXPECT_EQ(kPremul_SkAlphaType, premul_frame->Bitmap().alphaType());
+ 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(kUnpremul_SkAlphaType, unpremul_frame->Bitmap().alphaType());
+ 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);
@@ -394,11 +404,11 @@
premul_frame = premul_decoder->DecodeFrameBufferAtIndex(0);
EXPECT_TRUE(premul_frame &&
premul_frame->GetStatus() == ImageFrame::kFrameComplete);
- EXPECT_EQ(kOpaque_SkAlphaType, premul_frame->Bitmap().alphaType());
+ 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(kOpaque_SkAlphaType, unpremul_frame->Bitmap().alphaType());
+ EXPECT_EQ(unpremul_frame->Bitmap().alphaType(), kOpaque_SkAlphaType);
}
namespace {
diff --git a/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageReader.cpp b/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageReader.cpp
new file mode 100644
index 0000000..3f5ad37
--- /dev/null
+++ b/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageReader.cpp
@@ -0,0 +1,900 @@
+/* -*- 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 <[email protected]>
+ * 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 "platform/image-decoders/gif/GIFImageReader.h"
+
+#include <string.h>
+#include "platform/image-decoders/FastSharedBufferReader.h"
+#include "platform/wtf/PtrUtil.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_ = WTF::MakeUnique<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 = 0;
+ 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(WTF::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/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageReader.h b/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageReader.h
new file mode 100644
index 0000000..1ebaca5
--- /dev/null
+++ b/third_party/WebKit/Source/platform/image-decoders/gif/GIFImageReader.h
@@ -0,0 +1,369 @@
+/* -*- 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 GIFImageReader_h
+#define GIFImageReader_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 "platform/image-decoders/gif/GIFImageDecoder.h"
+#include "platform/wtf/Allocator.h"
+#include "platform/wtf/Noncopyable.h"
+#include "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(0),
+ 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() {}
+
+ 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 = 0)
+ : 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() {}
+
+ void SetData(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() : 0;
+ }
+
+ 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_;
+
+ RefPtr<blink::SegmentReader> data_;
+ bool parse_completed_;
+};
+
+} // namespace blink
+
+#endif