blob: fd85381f277e3dc80f1445b48902907e60d0c80a [file] [log] [blame]
/*
* Copyright (C) 2012 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/platform/graphics/deferred_image_decoder.h"
#include <memory>
#include "base/location.h"
#include "base/memory/scoped_refptr.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/platform/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/graphics/image_decoding_store.h"
#include "third_party/blink/renderer/platform/graphics/image_frame_generator.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_canvas.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_image.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_record.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_recorder.h"
#include "third_party/blink/renderer/platform/graphics/test/mock_image_decoder.h"
#include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread.h"
#include "third_party/blink/renderer/platform/shared_buffer.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkPixmap.h"
#include "third_party/skia/include/core/SkSurface.h"
namespace blink {
namespace {
// Raw data for a PNG file with 1x1 white pixels.
const unsigned char kWhitePNG[] = {
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53, 0xde, 0x00, 0x00, 0x00,
0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00,
0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0b, 0x13, 0x00, 0x00,
0x0b, 0x13, 0x01, 0x00, 0x9a, 0x9c, 0x18, 0x00, 0x00, 0x00, 0x0c, 0x49,
0x44, 0x41, 0x54, 0x08, 0xd7, 0x63, 0xf8, 0xff, 0xff, 0x3f, 0x00, 0x05,
0xfe, 0x02, 0xfe, 0xdc, 0xcc, 0x59, 0xe7, 0x00, 0x00, 0x00, 0x00, 0x49,
0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
};
// Raw data for a GIF file with 1x1 white pixels. Modified from animatedGIF.
const unsigned char kWhiteGIF[] = {
0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x01, 0x00, 0x01, 0x00, 0xf0, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x21, 0xff, 0x0b, 0x4e, 0x45,
0x54, 0x53, 0x43, 0x41, 0x50, 0x45, 0x32, 0x2e, 0x30, 0x03, 0x01, 0x00,
0x00, 0x00, 0x21, 0xff, 0x0b, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x4d, 0x61,
0x67, 0x69, 0x63, 0x6b, 0x0d, 0x67, 0x61, 0x6d, 0x6d, 0x61, 0x3d, 0x30,
0x2e, 0x34, 0x35, 0x34, 0x35, 0x35, 0x00, 0x21, 0xff, 0x0b, 0x49, 0x6d,
0x61, 0x67, 0x65, 0x4d, 0x61, 0x67, 0x69, 0x63, 0x6b, 0x0d, 0x67, 0x61,
0x6d, 0x6d, 0x61, 0x3d, 0x30, 0x2e, 0x34, 0x35, 0x34, 0x35, 0x35, 0x00,
0x21, 0xf9, 0x04, 0x00, 0x00, 0x00, 0xff, 0x00, 0x2c, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x4c, 0x01, 0x00, 0x3b};
} // namespace
class DeferredImageDecoderTest : public testing::Test,
public MockImageDecoderClient {
public:
void SetUp() override {
paint_image_id_ = PaintImage::GetNextId();
ImageDecodingStore::Instance().SetCacheLimitInBytes(1024 * 1024);
data_ = SharedBuffer::Create(kWhitePNG, sizeof(kWhitePNG));
frame_count_ = 1;
std::unique_ptr<MockImageDecoder> decoder = MockImageDecoder::Create(this);
actual_decoder_ = decoder.get();
actual_decoder_->SetSize(1, 1);
lazy_decoder_ = DeferredImageDecoder::CreateForTesting(std::move(decoder));
bitmap_.allocPixels(SkImageInfo::MakeN32Premul(100, 100));
canvas_ = std::make_unique<cc::SkiaPaintCanvas>(bitmap_);
decode_request_count_ = 0;
repetition_count_ = kAnimationNone;
status_ = ImageFrame::kFrameComplete;
frame_duration_ = TimeDelta();
decoded_size_ = actual_decoder_->Size();
}
void TearDown() override { ImageDecodingStore::Instance().Clear(); }
void DecoderBeingDestroyed() override { actual_decoder_ = nullptr; }
void DecodeRequested() override { ++decode_request_count_; }
size_t FrameCount() override { return frame_count_; }
int RepetitionCount() const override { return repetition_count_; }
ImageFrame::Status GetStatus(size_t index) override { return status_; }
TimeDelta FrameDuration() const override { return frame_duration_; }
IntSize DecodedSize() const override { return decoded_size_; }
PaintImage CreatePaintImage(
PaintImage::CompletionState state = PaintImage::CompletionState::DONE) {
return CreatePaintImage(lazy_decoder_.get(), state);
}
PaintImage CreatePaintImage(
DeferredImageDecoder* decoder,
PaintImage::CompletionState state = PaintImage::CompletionState::DONE) {
PaintImage::AnimationType type = FrameCount() > 1
? PaintImage::AnimationType::ANIMATED
: PaintImage::AnimationType::STATIC;
return PaintImageBuilder::WithDefault()
.set_id(paint_image_id_)
.set_animation_type(type)
.set_completion_state(state)
.set_paint_image_generator(
decoder->CreateGenerator(PaintImage::kDefaultFrameIndex))
.TakePaintImage();
}
protected:
void UseMockImageDecoderFactory() {
lazy_decoder_->FrameGenerator()->SetImageDecoderFactory(
MockImageDecoderFactory::Create(this, decoded_size_));
}
// Don't own this but saves the pointer to query states.
PaintImage::Id paint_image_id_;
MockImageDecoder* actual_decoder_;
std::unique_ptr<DeferredImageDecoder> lazy_decoder_;
SkBitmap bitmap_;
std::unique_ptr<cc::PaintCanvas> canvas_;
int decode_request_count_;
scoped_refptr<SharedBuffer> data_;
size_t frame_count_;
int repetition_count_;
ImageFrame::Status status_;
TimeDelta frame_duration_;
IntSize decoded_size_;
};
TEST_F(DeferredImageDecoderTest, drawIntoPaintRecord) {
lazy_decoder_->SetData(data_, true);
PaintImage image = CreatePaintImage();
ASSERT_TRUE(image);
EXPECT_EQ(1, image.width());
EXPECT_EQ(1, image.height());
PaintRecorder recorder;
cc::PaintCanvas* temp_canvas = recorder.beginRecording(100, 100);
temp_canvas->drawImage(image, 0, 0);
sk_sp<PaintRecord> record = recorder.finishRecordingAsPicture();
EXPECT_EQ(0, decode_request_count_);
canvas_->drawPicture(record);
EXPECT_EQ(0, decode_request_count_);
EXPECT_EQ(SkColorSetARGB(255, 255, 255, 255), bitmap_.getColor(0, 0));
}
TEST_F(DeferredImageDecoderTest, drawIntoPaintRecordProgressive) {
scoped_refptr<SharedBuffer> partial_data =
SharedBuffer::Create(data_->Data(), data_->size() - 10);
// Received only half the file.
lazy_decoder_->SetData(partial_data, false);
PaintRecorder recorder;
cc::PaintCanvas* temp_canvas = recorder.beginRecording(100, 100);
PaintImage image =
CreatePaintImage(PaintImage::CompletionState::PARTIALLY_DONE);
temp_canvas->drawImage(image, 0, 0);
canvas_->drawPicture(recorder.finishRecordingAsPicture());
// Fully received the file and draw the PaintRecord again.
lazy_decoder_->SetData(data_, true);
image = CreatePaintImage();
ASSERT_TRUE(image);
temp_canvas = recorder.beginRecording(100, 100);
temp_canvas->drawImage(image, 0, 0);
canvas_->drawPicture(recorder.finishRecordingAsPicture());
EXPECT_EQ(SkColorSetARGB(255, 255, 255, 255), bitmap_.getColor(0, 0));
}
static void RasterizeMain(cc::PaintCanvas* canvas, sk_sp<PaintRecord> record) {
canvas->drawPicture(record);
}
// Flaky on Mac. crbug.com/792540.
#if defined(OS_MACOSX)
#define MAYBE_decodeOnOtherThread DISABLED_decodeOnOtherThread
#else
#define MAYBE_decodeOnOtherThread decodeOnOtherThread
#endif
TEST_F(DeferredImageDecoderTest, MAYBE_decodeOnOtherThread) {
lazy_decoder_->SetData(data_, true);
PaintImage image = CreatePaintImage();
ASSERT_TRUE(image);
EXPECT_EQ(1, image.width());
EXPECT_EQ(1, image.height());
PaintRecorder recorder;
cc::PaintCanvas* temp_canvas = recorder.beginRecording(100, 100);
temp_canvas->drawImage(image, 0, 0);
sk_sp<PaintRecord> record = recorder.finishRecordingAsPicture();
EXPECT_EQ(0, decode_request_count_);
// Create a thread to rasterize PaintRecord.
std::unique_ptr<Thread> thread = Platform::Current()->CreateThread(
ThreadCreationParams(WebThreadType::kTestThread)
.SetThreadNameForTest("RasterThread"));
PostCrossThreadTask(
*thread->GetTaskRunner(), FROM_HERE,
CrossThreadBind(&RasterizeMain, CrossThreadUnretained(canvas_.get()),
record));
thread.reset();
EXPECT_EQ(0, decode_request_count_);
EXPECT_EQ(SkColorSetARGB(255, 255, 255, 255), bitmap_.getColor(0, 0));
}
TEST_F(DeferredImageDecoderTest, singleFrameImageLoading) {
status_ = ImageFrame::kFramePartial;
lazy_decoder_->SetData(data_, false);
EXPECT_FALSE(lazy_decoder_->FrameIsReceivedAtIndex(0));
PaintImage image = CreatePaintImage();
ASSERT_TRUE(image);
EXPECT_FALSE(lazy_decoder_->FrameIsReceivedAtIndex(0));
EXPECT_TRUE(actual_decoder_);
status_ = ImageFrame::kFrameComplete;
data_->Append(" ", 1u);
lazy_decoder_->SetData(data_, true);
EXPECT_FALSE(actual_decoder_);
EXPECT_TRUE(lazy_decoder_->FrameIsReceivedAtIndex(0));
image = CreatePaintImage();
ASSERT_TRUE(image);
EXPECT_FALSE(decode_request_count_);
}
TEST_F(DeferredImageDecoderTest, multiFrameImageLoading) {
repetition_count_ = 10;
frame_count_ = 1;
frame_duration_ = TimeDelta::FromMilliseconds(10);
status_ = ImageFrame::kFramePartial;
lazy_decoder_->SetData(data_, false);
PaintImage image = CreatePaintImage();
ASSERT_TRUE(image);
EXPECT_FALSE(lazy_decoder_->FrameIsReceivedAtIndex(0));
// Anything <= 10ms is clamped to 100ms. See the implementaiton for details.
EXPECT_EQ(TimeDelta::FromMilliseconds(100),
lazy_decoder_->FrameDurationAtIndex(0));
frame_count_ = 2;
frame_duration_ = TimeDelta::FromMilliseconds(20);
status_ = ImageFrame::kFrameComplete;
data_->Append(" ", 1u);
lazy_decoder_->SetData(data_, false);
image = CreatePaintImage();
ASSERT_TRUE(image);
EXPECT_TRUE(lazy_decoder_->FrameIsReceivedAtIndex(0));
EXPECT_TRUE(lazy_decoder_->FrameIsReceivedAtIndex(1));
EXPECT_EQ(TimeDelta::FromMilliseconds(20),
lazy_decoder_->FrameDurationAtIndex(1));
EXPECT_TRUE(actual_decoder_);
frame_count_ = 3;
frame_duration_ = TimeDelta::FromMilliseconds(30);
status_ = ImageFrame::kFrameComplete;
lazy_decoder_->SetData(data_, true);
EXPECT_FALSE(actual_decoder_);
EXPECT_TRUE(lazy_decoder_->FrameIsReceivedAtIndex(0));
EXPECT_TRUE(lazy_decoder_->FrameIsReceivedAtIndex(1));
EXPECT_TRUE(lazy_decoder_->FrameIsReceivedAtIndex(2));
EXPECT_EQ(TimeDelta::FromMilliseconds(100),
lazy_decoder_->FrameDurationAtIndex(0));
EXPECT_EQ(TimeDelta::FromMilliseconds(20),
lazy_decoder_->FrameDurationAtIndex(1));
EXPECT_EQ(TimeDelta::FromMilliseconds(30),
lazy_decoder_->FrameDurationAtIndex(2));
EXPECT_EQ(10, lazy_decoder_->RepetitionCount());
}
TEST_F(DeferredImageDecoderTest, decodedSize) {
decoded_size_ = IntSize(22, 33);
lazy_decoder_->SetData(data_, true);
PaintImage image = CreatePaintImage();
ASSERT_TRUE(image);
EXPECT_EQ(decoded_size_.Width(), image.width());
EXPECT_EQ(decoded_size_.Height(), image.height());
UseMockImageDecoderFactory();
// The following code should not fail any assert.
PaintRecorder recorder;
cc::PaintCanvas* temp_canvas = recorder.beginRecording(100, 100);
temp_canvas->drawImage(image, 0, 0);
sk_sp<PaintRecord> record = recorder.finishRecordingAsPicture();
EXPECT_EQ(0, decode_request_count_);
canvas_->drawPicture(record);
EXPECT_EQ(1, decode_request_count_);
}
TEST_F(DeferredImageDecoderTest, smallerFrameCount) {
frame_count_ = 1;
lazy_decoder_->SetData(data_, false);
EXPECT_EQ(frame_count_, lazy_decoder_->FrameCount());
frame_count_ = 2;
lazy_decoder_->SetData(data_, false);
EXPECT_EQ(frame_count_, lazy_decoder_->FrameCount());
frame_count_ = 0;
lazy_decoder_->SetData(data_, true);
EXPECT_EQ(frame_count_, lazy_decoder_->FrameCount());
}
TEST_F(DeferredImageDecoderTest, frameOpacity) {
for (bool test_gif : {false, true}) {
if (test_gif)
data_ = SharedBuffer::Create(kWhiteGIF, sizeof(kWhiteGIF));
std::unique_ptr<DeferredImageDecoder> decoder =
DeferredImageDecoder::Create(data_, true,
ImageDecoder::kAlphaPremultiplied,
ColorBehavior::TransformToSRGB());
SkImageInfo pix_info = SkImageInfo::MakeN32Premul(1, 1);
size_t row_bytes = pix_info.minRowBytes();
size_t size = pix_info.computeByteSize(row_bytes);
Vector<char> storage(size);
SkPixmap pixmap(pix_info, storage.data(), row_bytes);
// Before decoding, the frame is not known to be opaque.
sk_sp<SkImage> frame = CreatePaintImage(decoder.get()).GetSkImage();
ASSERT_TRUE(frame);
EXPECT_FALSE(frame->isOpaque());
EXPECT_TRUE(decoder->FrameHasAlphaAtIndex(0));
// Force a lazy decode by reading pixels.
EXPECT_TRUE(frame->readPixels(pixmap, 0, 0));
// After decoding, the frame is known to be opaque.
EXPECT_FALSE(decoder->FrameHasAlphaAtIndex(0));
frame = CreatePaintImage(decoder.get()).GetSkImage();
ASSERT_TRUE(frame);
EXPECT_TRUE(frame->isOpaque());
// Re-generating the opaque-marked frame should not fail.
EXPECT_TRUE(frame->readPixels(pixmap, 0, 0));
}
}
TEST_F(DeferredImageDecoderTest, data) {
scoped_refptr<SharedBuffer> original_buffer =
SharedBuffer::Create(data_->Data(), data_->size());
EXPECT_EQ(original_buffer->size(), data_->size());
lazy_decoder_->SetData(original_buffer, false);
scoped_refptr<SharedBuffer> new_buffer = lazy_decoder_->Data();
EXPECT_EQ(original_buffer->size(), new_buffer->size());
const Vector<char> original_data = original_buffer->CopyAs<Vector<char>>();
const Vector<char> new_data = new_buffer->CopyAs<Vector<char>>();
EXPECT_EQ(0, std::memcmp(original_data.data(), new_data.data(),
new_buffer->size()));
}
class MultiFrameDeferredImageDecoderTest : public DeferredImageDecoderTest {
public:
ImageFrame::Status GetStatus(size_t index) override {
return index > last_complete_frame_ ? ImageFrame::Status::kFramePartial
: ImageFrame::Status::kFrameComplete;
}
size_t last_complete_frame_ = 0u;
};
TEST_F(MultiFrameDeferredImageDecoderTest, PaintImage) {
frame_count_ = 2;
frame_duration_ = TimeDelta::FromMilliseconds(20);
last_complete_frame_ = 0u;
lazy_decoder_->SetData(data_, false);
// Only the first frame is complete.
PaintImage image = CreatePaintImage();
ASSERT_TRUE(image);
EXPECT_EQ(image.GetFrameMetadata().size(), 2u);
EXPECT_TRUE(image.GetFrameMetadata()[0].complete);
EXPECT_FALSE(image.GetFrameMetadata()[1].complete);
EXPECT_EQ(image.GetFrameMetadata()[0].duration, frame_duration_);
EXPECT_EQ(image.GetFrameMetadata()[1].duration, frame_duration_);
auto frame0_key = image.GetKeyForFrame(0);
auto frame1_key = image.GetKeyForFrame(1);
EXPECT_NE(frame0_key, frame1_key);
// Send some more data but the frame status remains the same.
last_complete_frame_ = 0u;
lazy_decoder_->SetData(data_, false);
PaintImage updated_image = CreatePaintImage();
ASSERT_TRUE(updated_image);
EXPECT_EQ(updated_image.GetFrameMetadata().size(), 2u);
EXPECT_TRUE(updated_image.GetFrameMetadata()[0].complete);
EXPECT_FALSE(updated_image.GetFrameMetadata()[1].complete);
// Since the first frame was complete, the key remains constant. While the
// second frame generates a new key after it is updated.
auto updated_frame0_key = updated_image.GetKeyForFrame(0);
auto updated_frame1_key = updated_image.GetKeyForFrame(1);
EXPECT_NE(updated_frame0_key, updated_frame1_key);
EXPECT_EQ(updated_frame0_key, frame0_key);
EXPECT_NE(updated_frame1_key, frame1_key);
// Mark all frames complete.
last_complete_frame_ = 1u;
lazy_decoder_->SetData(data_, true);
PaintImage complete_image = CreatePaintImage();
ASSERT_TRUE(complete_image);
EXPECT_EQ(complete_image.GetFrameMetadata().size(), 2u);
EXPECT_TRUE(complete_image.GetFrameMetadata()[0].complete);
EXPECT_TRUE(complete_image.GetFrameMetadata()[1].complete);
auto complete_frame0_key = complete_image.GetKeyForFrame(0);
auto complete_frame1_key = complete_image.GetKeyForFrame(1);
EXPECT_NE(complete_frame0_key, complete_frame1_key);
EXPECT_EQ(updated_frame0_key, complete_frame0_key);
EXPECT_NE(updated_frame1_key, complete_frame1_key);
}
TEST_F(MultiFrameDeferredImageDecoderTest, FrameDurationOverride) {
frame_count_ = 2;
frame_duration_ = TimeDelta::FromMilliseconds(5);
last_complete_frame_ = 1u;
lazy_decoder_->SetData(data_, true);
// If the frame duration is below a threshold, we override it to a constant
// value of 100 ms.
PaintImage image = CreatePaintImage();
EXPECT_EQ(image.GetFrameMetadata()[0].duration,
base::TimeDelta::FromMilliseconds(100));
EXPECT_EQ(image.GetFrameMetadata()[1].duration,
base::TimeDelta::FromMilliseconds(100));
}
} // namespace blink