blob: f7f6f26d15b618b338bec6373e1b776b8a9d1485 [file]
/*
* Copyright (c) 2013, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h"
#include "base/test/null_task_runner.h"
#include "build/build_config.h"
#include "components/viz/test/test_raster_interface.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/html/canvas/html_canvas_element.h"
#include "third_party/blink/renderer/core/html/canvas/image_data.h"
#include "third_party/blink/renderer/core/html/html_image_element.h"
#include "third_party/blink/renderer/core/html/media/html_video_element.h"
#include "third_party/blink/renderer/core/loader/resource/image_resource_content.h"
#include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
#include "third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.h"
#include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h"
#include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h"
#include "third_party/blink/renderer/platform/graphics/test/fake_gles2_interface.h"
#include "third_party/blink/renderer/platform/graphics/test/fake_web_graphics_context_3d_provider.h"
#include "third_party/blink/renderer/platform/graphics/test/gpu_test_utils.h"
#include "third_party/blink/renderer/platform/graphics/unaccelerated_static_bitmap_image.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/heap/thread_state.h"
#include "third_party/blink/renderer/platform/image-decoders/image_decoder.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h"
#include "third_party/blink/renderer/platform/loader/fetch/memory_cache.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
#include "third_party/blink/renderer/platform/testing/task_environment.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkPixelRef.h"
#include "third_party/skia/include/core/SkSurface.h"
namespace blink {
class ExceptionState;
class ImageBitmapTest : public testing::Test {
protected:
ImageBitmapTest()
: scoped_memory_cache_(MakeGarbageCollected<MemoryCache>(
blink::scheduler::GetSingleThreadTaskRunnerForTesting())) {}
void SetUp() override {
sk_sp<SkSurface> surface =
SkSurfaces::Raster(SkImageInfo::MakeN32Premul(10, 10));
surface->getCanvas()->clear(0xFFFFFFFF);
image_ = surface->makeImageSnapshot();
sk_sp<SkSurface> surface2 =
SkSurfaces::Raster(SkImageInfo::MakeN32Premul(5, 5));
surface2->getCanvas()->clear(0xAAAAAAAA);
image2_ = surface2->makeImageSnapshot();
test_context_provider_ = viz::TestContextProvider::CreateRaster();
InitializeSharedGpuContext(test_context_provider_.get());
}
void TearDown() override {
// Garbage collection is required prior to switching out the
// test's memory cache; image resources are released, evicting
// them from the cache.
ThreadState::Current()->CollectAllGarbageForTesting(
ThreadState::StackState::kNoHeapPointers);
SharedGpuContext::Reset();
}
protected:
test::TaskEnvironment task_environment_;
scoped_refptr<viz::TestContextProvider> test_context_provider_;
sk_sp<SkImage> image_, image2_;
ScopedMemoryCacheForTesting scoped_memory_cache_;
};
TEST_F(ImageBitmapTest, ImageResourceConsistency) {
const ImageBitmapOptions* default_options = ImageBitmapOptions::Create();
auto dummy = std::make_unique<DummyPageHolder>(gfx::Size(800, 600));
auto* image_element =
MakeGarbageCollected<HTMLImageElement>(dummy->GetDocument());
sk_sp<SkColorSpace> src_rgb_color_space = SkColorSpace::MakeSRGB();
SkImageInfo raster_image_info =
SkImageInfo::MakeN32Premul(5, 5, src_rgb_color_space);
sk_sp<SkSurface> surface(SkSurfaces::Raster(raster_image_info));
sk_sp<SkImage> image = surface->makeImageSnapshot();
ImageResourceContent* original_image_content =
ImageResourceContent::CreateLoaded(
UnacceleratedStaticBitmapImage::Create(image).get());
image_element->SetImageForTest(original_image_content);
std::optional<gfx::Rect> crop_rect =
gfx::Rect(0, 0, image_element->width(), image_element->height());
auto* image_bitmap_no_crop = MakeGarbageCollected<ImageBitmap>(
image_element, crop_rect, default_options);
ASSERT_TRUE(image_bitmap_no_crop);
crop_rect =
gfx::Rect(image_element->width() / 2, image_element->height() / 2,
image_element->width() / 2, image_element->height() / 2);
auto* image_bitmap_interior_crop = MakeGarbageCollected<ImageBitmap>(
image_element, crop_rect, default_options);
ASSERT_TRUE(image_bitmap_interior_crop);
crop_rect =
gfx::Rect(-image_element->width() / 2, -image_element->height() / 2,
image_element->width(), image_element->height());
auto* image_bitmap_exterior_crop = MakeGarbageCollected<ImageBitmap>(
image_element, crop_rect, default_options);
ASSERT_TRUE(image_bitmap_exterior_crop);
crop_rect = gfx::Rect(-image_element->width(), -image_element->height(),
image_element->width(), image_element->height());
auto* image_bitmap_outside_crop = MakeGarbageCollected<ImageBitmap>(
image_element, crop_rect, default_options);
ASSERT_TRUE(image_bitmap_outside_crop);
ASSERT_EQ(image_bitmap_no_crop->BitmapImage()
->PaintImageForCurrentFrame()
.GetSwSkImage(),
image_element->CachedImage()
->GetImage()
->PaintImageForCurrentFrame()
.GetSwSkImage());
ASSERT_NE(image_bitmap_interior_crop->BitmapImage()
->PaintImageForCurrentFrame()
.GetSwSkImage(),
image_element->CachedImage()
->GetImage()
->PaintImageForCurrentFrame()
.GetSwSkImage());
ASSERT_NE(image_bitmap_exterior_crop->BitmapImage()
->PaintImageForCurrentFrame()
.GetSwSkImage(),
image_element->CachedImage()
->GetImage()
->PaintImageForCurrentFrame()
.GetSwSkImage());
scoped_refptr<StaticBitmapImage> empty_image =
image_bitmap_outside_crop->BitmapImage();
ASSERT_NE(empty_image->PaintImageForCurrentFrame().GetSwSkImage(),
image_element->CachedImage()
->GetImage()
->PaintImageForCurrentFrame()
.GetSwSkImage());
}
// Verifies that ImageBitmaps constructed from HTMLImageElements hold a
// reference to the original Image if the HTMLImageElement src is changed.
TEST_F(ImageBitmapTest, ImageBitmapSourceChanged) {
auto dummy = std::make_unique<DummyPageHolder>(gfx::Size(800, 600));
auto* image = MakeGarbageCollected<HTMLImageElement>(dummy->GetDocument());
sk_sp<SkColorSpace> src_rgb_color_space = SkColorSpace::MakeSRGB();
SkImageInfo raster_image_info =
SkImageInfo::MakeN32Premul(5, 5, src_rgb_color_space);
sk_sp<SkSurface> raster_surface(SkSurfaces::Raster(raster_image_info));
sk_sp<SkImage> raster_image = raster_surface->makeImageSnapshot();
ImageResourceContent* original_image_content =
ImageResourceContent::CreateLoaded(
UnacceleratedStaticBitmapImage::Create(raster_image).get());
image->SetImageForTest(original_image_content);
const ImageBitmapOptions* default_options = ImageBitmapOptions::Create();
std::optional<gfx::Rect> crop_rect =
gfx::Rect(0, 0, image->width(), image->height());
auto* image_bitmap =
MakeGarbageCollected<ImageBitmap>(image, crop_rect, default_options);
ASSERT_TRUE(image_bitmap);
ASSERT_EQ(
image_bitmap->BitmapImage()->PaintImageForCurrentFrame().GetSwSkImage(),
original_image_content->GetImage()
->PaintImageForCurrentFrame()
.GetSwSkImage());
ImageResourceContent* new_image_content = ImageResourceContent::CreateLoaded(
UnacceleratedStaticBitmapImage::Create(image2_).get());
image->SetImageForTest(new_image_content);
{
ASSERT_EQ(
image_bitmap->BitmapImage()->PaintImageForCurrentFrame().GetSwSkImage(),
original_image_content->GetImage()
->PaintImageForCurrentFrame()
.GetSwSkImage());
SkImage* image1 = image_bitmap->BitmapImage()
->PaintImageForCurrentFrame()
.GetSwSkImage()
.get();
ASSERT_NE(image1, nullptr);
SkImage* image2 = original_image_content->GetImage()
->PaintImageForCurrentFrame()
.GetSwSkImage()
.get();
ASSERT_NE(image2, nullptr);
ASSERT_EQ(image1, image2);
}
{
ASSERT_NE(
image_bitmap->BitmapImage()->PaintImageForCurrentFrame().GetSwSkImage(),
new_image_content->GetImage()
->PaintImageForCurrentFrame()
.GetSwSkImage());
SkImage* image1 = image_bitmap->BitmapImage()
->PaintImageForCurrentFrame()
.GetSwSkImage()
.get();
ASSERT_NE(image1, nullptr);
SkImage* image2 = new_image_content->GetImage()
->PaintImageForCurrentFrame()
.GetSwSkImage()
.get();
ASSERT_NE(image2, nullptr);
ASSERT_NE(image1, image2);
}
}
static void TestImageBitmapTextureBacked(
scoped_refptr<StaticBitmapImage> bitmap,
gfx::Rect& rect,
ImageBitmapOptions* options,
bool is_texture_backed) {
auto* image_bitmap = MakeGarbageCollected<ImageBitmap>(bitmap, rect, options);
EXPECT_TRUE(image_bitmap);
EXPECT_EQ(image_bitmap->BitmapImage()->IsTextureBacked(), is_texture_backed);
}
TEST_F(ImageBitmapTest, AvoidGPUReadback) {
auto client_si = gpu::ClientSharedImage::CreateForTesting(
gpu::SHARED_IMAGE_USAGE_RASTER_READ);
gpu::SyncToken token;
token.Set(gpu::CommandBufferNamespace::GPU_IO,
gpu::CommandBufferId::FromUnsafeValue(64), 100);
scoped_refptr<StaticBitmapImage> bitmap =
AcceleratedStaticBitmapImage::CreateFromCanvasSharedImage(
std::move(client_si), token, kPremul_SkAlphaType, gfx::HDRMetadata(),
SharedGpuContext::ContextProviderWrapper(),
base::PlatformThread::CurrentRef(),
base::MakeRefCounted<base::NullTaskRunner>(), base::DoNothing());
ASSERT_TRUE(bitmap->IsTextureBacked());
auto* image_bitmap = MakeGarbageCollected<ImageBitmap>(bitmap);
EXPECT_TRUE(image_bitmap);
EXPECT_TRUE(image_bitmap->BitmapImage()->IsTextureBacked());
gfx::Rect image_bitmap_rect(25, 25, 50, 50);
{
ImageBitmapOptions* image_bitmap_options = ImageBitmapOptions::Create();
TestImageBitmapTextureBacked(bitmap, image_bitmap_rect,
image_bitmap_options, true);
}
std::list<V8ImageOrientation::Enum> image_orientations = {
V8ImageOrientation::Enum::kNone, V8ImageOrientation::Enum::kFlipY};
std::list<V8PremultiplyAlpha::Enum> premultiply_alphas = {
V8PremultiplyAlpha::Enum::kNone, V8PremultiplyAlpha::Enum::kPremultiply,
V8PremultiplyAlpha::Enum::kDefault};
std::list<V8ColorSpaceConversion::Enum> color_space_conversions = {
V8ColorSpaceConversion::Enum::kNone,
V8ColorSpaceConversion::Enum::kDefault};
std::list<int> resize_widths = {25, 50, 75};
std::list<int> resize_heights = {25, 50, 75};
std::list<V8ResizeQuality::Enum> resize_qualities = {
V8ResizeQuality::Enum::kPixelated, V8ResizeQuality::Enum::kLow,
V8ResizeQuality::Enum::kMedium, V8ResizeQuality::Enum::kHigh};
for (auto image_orientation : image_orientations) {
for (auto premultiply_alpha : premultiply_alphas) {
for (auto color_space_conversion : color_space_conversions) {
for (auto resize_width : resize_widths) {
for (auto resize_height : resize_heights) {
for (auto resize_quality : resize_qualities) {
ImageBitmapOptions* image_bitmap_options =
ImageBitmapOptions::Create();
image_bitmap_options->setImageOrientation(image_orientation);
image_bitmap_options->setPremultiplyAlpha(premultiply_alpha);
image_bitmap_options->setColorSpaceConversion(
color_space_conversion);
image_bitmap_options->setResizeWidth(resize_width);
image_bitmap_options->setResizeHeight(resize_height);
image_bitmap_options->setResizeQuality(resize_quality);
// Setting premuliply_alpha to none will cause a read back.
// Otherwise, we expect to avoid GPU readback when creaing an
// ImageBitmap from a texture-backed source.
TestImageBitmapTextureBacked(
bitmap, image_bitmap_rect, image_bitmap_options,
premultiply_alpha != V8PremultiplyAlpha::Enum::kNone);
}
}
}
}
}
}
}
// This test is failing on asan-clang-phone because memory allocation is
// declined. See <http://crbug.com/782286>.
// This test is failing on fuchsia because memory allocation is
// declined. <http://crbug.com/1090252>.
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_FUCHSIA)
#define MAYBE_CreateImageBitmapFromTooBigImageDataDoesNotCrash \
DISABLED_CreateImageBitmapFromTooBigImageDataDoesNotCrash
#else
#define MAYBE_CreateImageBitmapFromTooBigImageDataDoesNotCrash \
CreateImageBitmapFromTooBigImageDataDoesNotCrash
#endif
// This test verifies if requesting a large ImageData and creating an
// ImageBitmap from that does not crash. crbug.com/780358
TEST_F(ImageBitmapTest,
MAYBE_CreateImageBitmapFromTooBigImageDataDoesNotCrash) {
constexpr int kWidth = 1 << 28; // 256M pixels width, resulting in 1GB data.
ImageData* image_data = ImageData::CreateForTest(gfx::Size(kWidth, 1));
DCHECK(image_data);
ImageBitmapOptions* options = ImageBitmapOptions::Create();
options->setColorSpaceConversion(V8ColorSpaceConversion::Enum::kDefault);
auto* image_bitmap = MakeGarbageCollected<ImageBitmap>(
image_data, gfx::Rect(image_data->Size()), options);
DCHECK(image_bitmap);
}
TEST_F(ImageBitmapTest, ImageAlphaState) {
auto dummy = std::make_unique<DummyPageHolder>(gfx::Size(800, 600));
auto* image_element =
MakeGarbageCollected<HTMLImageElement>(dummy->GetDocument());
// Load a 2x2 png file which has pixels (255, 102, 153, 0). It is a fully
// transparent image.
ResourceRequest resource_request(
"data:image/"
"png;base64,"
"iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAEUlEQVR42mP8nzaTAQQYYQwA"
"LssD/5ca+r8AAAAASUVORK5CYII=");
FetchParameters params =
FetchParameters::CreateForTest(std::move(resource_request));
ImageResourceContent* resource_content =
ImageResourceContent::Fetch(params, dummy->GetDocument().Fetcher());
image_element->SetImageForTest(resource_content);
ImageBitmapOptions* options = ImageBitmapOptions::Create();
// ImageBitmap created from unpremul source image result.
options->setPremultiplyAlpha(V8PremultiplyAlpha::Enum::kNone);
// Additional operation shouldn't affect alpha op.
options->setImageOrientation(V8ImageOrientation::Enum::kFlipY);
std::optional<gfx::Rect> crop_rect =
gfx::Rect(0, 0, image_element->width(), image_element->height());
auto* image_bitmap =
MakeGarbageCollected<ImageBitmap>(image_element, crop_rect, options);
ASSERT_TRUE(image_bitmap);
// Read 1 pixel
sk_sp<SkImage> result =
image_bitmap->BitmapImage()->PaintImageForCurrentFrame().GetSwSkImage();
SkPixmap pixmap;
ASSERT_TRUE(result->peekPixels(&pixmap));
const uint32_t* pixels = pixmap.addr32();
SkColorType result_color_type = result->colorType();
SkColor expected = SkColorSetARGB(0, 0, 0, 0);
switch (result_color_type) {
case SkColorType::kRGBA_8888_SkColorType:
// Set ABGR value as reverse of RGBA
expected =
SkColorSetARGB(/* a */ 0, /* b */ 153, /* g */ 102, /* r */ 255);
break;
case SkColorType::kBGRA_8888_SkColorType:
// Set ARGB value as reverse of BGRA
expected =
SkColorSetARGB(/* a */ 0, /* r */ 255, /* g */ 102, /* b */ 153);
break;
default:
NOTREACHED();
}
ASSERT_EQ(pixels[0], expected);
}
} // namespace blink