Commit f1584254 authored by Christopher Cameron's avatar Christopher Cameron Committed by Commit Bot

Canvas: Clean up BaseRenderingContext2D::getImageData

This function used to use the helper functions CopyToByteArray and
ConvertPixelsFromCanvasPixelFormatToImageDataStorageFormat to first
copy the data, then the convert formats.

The function PaintImage::readPixels can do the copy and conversion
simultaneously. Use that instead.

Add the capability to read to uint16 (as well as uint8 and float16),
since getImageData will be changed to allow specifying the type.

Remove a bunch of unused helper functions left behind.

Bug: 1115317
Change-Id: I88e637c2f314385c62b1a450ea2c0c9e7fc29ad9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2533833Reviewed-by: default avatarYi Xu <yiyix@chromium.org>
Reviewed-by: default avatarFernando Serboncini <fserb@chromium.org>
Reviewed-by: default avatarccameron <ccameron@chromium.org>
Commit-Queue: ccameron <ccameron@chromium.org>
Cr-Commit-Position: refs/heads/master@{#827870}
parent 272abd85
......@@ -1656,11 +1656,12 @@ ImageData* BaseRenderingContext2D::getImageData(
return nullptr;
}
IntRect image_data_rect(sx, sy, sw, sh);
bool hasResourceProvider = CanCreateCanvas2dResourceProvider();
const IntRect image_data_rect(sx, sy, sw, sh);
ImageDataColorSettings* color_settings =
GetColorSettingsAsImageDataColorSettings();
if (!hasResourceProvider || isContextLost()) {
const ImageDataStorageFormat storage_format =
ImageData::GetImageDataStorageFormat(color_settings->storageFormat());
if (!CanCreateCanvas2dResourceProvider() || isContextLost()) {
ImageData* result =
ImageData::Create(image_data_rect.Size(), color_settings);
if (!result)
......@@ -1668,7 +1669,6 @@ ImageData* BaseRenderingContext2D::getImageData(
return result;
}
const CanvasColorParams& color_params = GetCanvas2DColorParams();
// Deferred offscreen canvases might have recorded commands, make sure
// that those get drawn here
FinalizeFrame();
......@@ -1688,59 +1688,100 @@ ImageData* BaseRenderingContext2D::getImageData(
}
}
size_t size_in_bytes;
if (!StaticBitmapImage::GetSizeInBytes(image_data_rect, color_params)
.AssignIfValid(&size_in_bytes) ||
size_in_bytes > v8::TypedArray::kMaxLength) {
exception_state.ThrowRangeError("Out of memory at ImageData creation");
return nullptr;
// Compute the ImageData's SkImageInfo;
SkImageInfo image_info;
{
SkColorType color_type = kRGBA_8888_SkColorType;
switch (storage_format) {
case kUint8ClampedArrayStorageFormat:
color_type = kRGBA_8888_SkColorType;
break;
case kUint16ArrayStorageFormat:
color_type = kR16G16B16A16_unorm_SkColorType;
break;
case kFloat32ArrayStorageFormat:
color_type = kRGBA_F32_SkColorType;
break;
default:
NOTREACHED();
}
image_info = SkImageInfo::Make(sw, sh, color_type, kUnpremul_SkAlphaType,
GetCanvas2DColorParams().GetSkColorSpace());
}
bool may_have_stray_area =
IsAccelerated() // GPU readback may fail silently.
|| StaticBitmapImage::MayHaveStrayArea(snapshot, image_data_rect);
ArrayBufferContents::InitializationPolicy initialization_policy =
may_have_stray_area ? ArrayBufferContents::kZeroInitialize
: ArrayBufferContents::kDontInitialize;
ArrayBufferContents contents(
size_in_bytes, 1, ArrayBufferContents::kNotShared, initialization_policy);
if (contents.DataLength() != size_in_bytes) {
exception_state.ThrowRangeError("Out of memory at ImageData creation");
return nullptr;
// Compute the size of and allocate |contents|, the ArrayContentsBuffer.
ArrayBufferContents contents;
const size_t data_size_bytes = image_info.computeMinByteSize();
{
if (data_size_bytes > std::numeric_limits<unsigned int>::max()) {
exception_state.ThrowRangeError(
"Buffer size exceeds maximum heap object size.");
return nullptr;
}
if (SkImageInfo::ByteSizeOverflowed(data_size_bytes) ||
data_size_bytes > v8::TypedArray::kMaxLength) {
exception_state.ThrowRangeError("Out of memory at ImageData creation");
return nullptr;
}
ArrayBufferContents::InitializationPolicy initialization_policy =
ArrayBufferContents::kDontInitialize;
if (IsAccelerated()) {
// GPU readback may fail silently.
initialization_policy = ArrayBufferContents::kZeroInitialize;
} else if (snapshot) {
// Zero-initialize if some of the readback area is out of bounds.
if (image_data_rect.X() < 0 || image_data_rect.Y() < 0 ||
image_data_rect.MaxX() > snapshot->Size().Width() ||
image_data_rect.MaxY() > snapshot->Size().Height()) {
initialization_policy = ArrayBufferContents::kZeroInitialize;
}
}
contents =
ArrayBufferContents(data_size_bytes, 1, ArrayBufferContents::kNotShared,
initialization_policy);
if (contents.DataLength() != data_size_bytes) {
exception_state.ThrowRangeError("Out of memory at ImageData creation");
return nullptr;
}
}
if (!StaticBitmapImage::CopyToByteArray(
snapshot,
base::span<uint8_t>(reinterpret_cast<uint8_t*>(contents.Data()),
contents.DataLength()),
image_data_rect, color_params)) {
exception_state.ThrowRangeError("Failed to copy image data");
return nullptr;
// Read pixels into |contents|.
bool read_pixels_successful =
snapshot->PaintImageForCurrentFrame().readPixels(
image_info, contents.Data(), image_info.minRowBytes(), sx, sy);
if (!read_pixels_successful) {
SkIRect bounds =
snapshot->PaintImageForCurrentFrame().GetSkImageInfo().bounds();
DCHECK(!bounds.intersect(SkIRect::MakeXYWH(sx, sy, sw, sh)));
}
// Convert pixels to proper storage format if needed
if (PixelFormat() != CanvasColorParams::GetNativeCanvasPixelFormat()) {
ImageDataStorageFormat storage_format =
ImageData::GetImageDataStorageFormat(color_settings->storageFormat());
NotShared<DOMArrayBufferView> array_buffer_view =
ImageData::ConvertPixelsFromCanvasPixelFormatToImageDataStorageFormat(
contents, PixelFormat(), storage_format);
return ImageData::Create(image_data_rect.Size(), array_buffer_view,
color_settings);
}
if (size_in_bytes > std::numeric_limits<unsigned int>::max()) {
exception_state.ThrowRangeError(
"Buffer size exceeds maximum heap object size.");
return nullptr;
}
// Wrap |contents| in an ImageData.
DOMArrayBuffer* array_buffer = DOMArrayBuffer::Create(std::move(contents));
ImageData* imageData = ImageData::Create(
image_data_rect.Size(),
NotShared<DOMUint8ClampedArray>(DOMUint8ClampedArray::Create(
array_buffer, 0, static_cast<unsigned int>(size_in_bytes))),
color_settings);
NotShared<DOMArrayBufferView> data_array;
switch (storage_format) {
case kUint8ClampedArrayStorageFormat: {
size_t num_elements = data_size_bytes;
data_array = NotShared<DOMArrayBufferView>(
DOMUint8ClampedArray::Create(array_buffer, 0, num_elements));
break;
}
case kUint16ArrayStorageFormat: {
size_t num_elements = data_size_bytes / 2;
data_array = NotShared<DOMArrayBufferView>(
DOMUint16Array::Create(array_buffer, 0, num_elements));
break;
}
case kFloat32ArrayStorageFormat: {
size_t num_elements = data_size_bytes / 4;
data_array = NotShared<DOMArrayBufferView>(
DOMFloat32Array::Create(array_buffer, 0, num_elements));
break;
}
default:
NOTREACHED();
}
ImageData* image_data = ImageData::Create(
image_data_rect.Size(), std::move(data_array), color_settings);
if (!IsPaint2D()) {
int scaled_time = getScaledElapsedTime(
......@@ -1753,8 +1794,7 @@ ImageData* BaseRenderingContext2D::getImageData(
"Blink.Canvas.GetImageDataScaledDuration.CPU", scaled_time);
}
}
return imageData;
return image_data;
}
int BaseRenderingContext2D::getScaledElapsedTime(float width,
......
......@@ -2013,7 +2013,6 @@ source_set("blink_platform_unittests_sources") {
"graphics/path_test.cc",
"graphics/placeholder_image_test.cc",
"graphics/raster_dark_mode_filter_impl_test.cc",
"graphics/static_bitmap_image_test.cc",
"graphics/video_frame_submitter_test.cc",
"heap_observer_set_test.cc",
"image-decoders/bmp/bmp_image_decoder_test.cc",
......
......@@ -79,56 +79,4 @@ void StaticBitmapImage::DrawHelper(
WebCoreClampingModeToSkiaRectConstraint(clamp_mode));
}
base::CheckedNumeric<size_t> StaticBitmapImage::GetSizeInBytes(
const IntRect& rect,
const CanvasColorParams& color_params) {
uint8_t bytes_per_pixel = color_params.BytesPerPixel();
base::CheckedNumeric<size_t> data_size = bytes_per_pixel;
data_size *= rect.Size().Area();
return data_size;
}
bool StaticBitmapImage::MayHaveStrayArea(
scoped_refptr<StaticBitmapImage> src_image,
const IntRect& rect) {
if (!src_image)
return false;
return rect.X() < 0 || rect.Y() < 0 ||
rect.MaxX() > src_image->Size().Width() ||
rect.MaxY() > src_image->Size().Height();
}
bool StaticBitmapImage::CopyToByteArray(
scoped_refptr<StaticBitmapImage> src_image,
base::span<uint8_t> dst,
const IntRect& rect,
const CanvasColorParams& color_params) {
DCHECK_EQ(dst.size(), GetSizeInBytes(rect, color_params).ValueOrDie());
if (!src_image)
return true;
if (dst.size() == 0)
return true;
SkColorType color_type =
(color_params.GetSkColorType() == kRGBA_F16_SkColorType)
? kRGBA_F16_SkColorType
: kRGBA_8888_SkColorType;
SkImageInfo info =
SkImageInfo::Make(rect.Width(), rect.Height(), color_type,
kUnpremul_SkAlphaType, color_params.GetSkColorSpace());
bool read_pixels_successful =
src_image->PaintImageForCurrentFrame().readPixels(
info, dst.data(), info.minRowBytes(), rect.X(), rect.Y());
DCHECK(read_pixels_successful ||
!src_image->PaintImageForCurrentFrame()
.GetSkImageInfo()
.bounds()
.intersect(SkIRect::MakeXYWH(rect.X(), rect.Y(), info.width(),
info.height())));
return true;
}
} // namespace blink
......@@ -96,18 +96,6 @@ class PLATFORM_EXPORT StaticBitmapImage : public Image {
return orientation_ == ImageOrientationEnum::kDefault;
}
static base::CheckedNumeric<size_t> GetSizeInBytes(
const IntRect& rect,
const CanvasColorParams& color_params);
static bool MayHaveStrayArea(scoped_refptr<StaticBitmapImage> src_image,
const IntRect& rect);
static bool CopyToByteArray(scoped_refptr<StaticBitmapImage> src_image,
base::span<uint8_t> dst,
const IntRect&,
const CanvasColorParams&);
protected:
// Helper for sub-classes
void DrawHelper(cc::PaintCanvas*,
......
// Copyright 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 "third_party/blink/renderer/platform/graphics/static_bitmap_image.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/platform/graphics/unaccelerated_static_bitmap_image.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "v8/include/v8.h"
namespace blink {
class StaticBitmapImageTest : public testing::Test {};
// This test verifies if requesting a large ImageData that cannot be handled by
// V8 is denied by StaticBitmapImage. This prevents V8 from crashing the
// renderer if the user asks to get back the ImageData.
TEST_F(StaticBitmapImageTest,
ConvertArrayBufferContentsTooBigToAllocateDoesNotCrash) {
SkImageInfo info = SkImageInfo::MakeN32Premul(1, 1);
sk_sp<SkSurface> surface = SkSurface::MakeRaster(info);
EXPECT_TRUE(!!surface);
scoped_refptr<StaticBitmapImage> image =
UnacceleratedStaticBitmapImage::Create(surface->makeImageSnapshot());
IntRect too_big_rect(IntPoint(0, 0),
IntSize(1, (v8::TypedArray::kMaxLength / 4) + 1));
EXPECT_GT(StaticBitmapImage::GetSizeInBytes(too_big_rect, CanvasColorParams())
.ValueOrDie(),
v8::TypedArray::kMaxLength);
}
} // namespace blink
......@@ -27,7 +27,7 @@ function testPixels(actualPixels, refPixels, testScenario)
for (let i = 0; i < actualPixels.length; i++) {
// Alpha channel
if (i % 4 == 3)
assert_equals(actualPixels[i], refPixels[i]);
assert_approx_equals(actualPixels[i], refPixels[i], tolerance_color);
// Red channel of the green pixel
else if (i == 4)
assert_approx_equals(actualPixels[i], refPixels[i], tolerance_r_green);
......@@ -41,7 +41,7 @@ function testPixels(actualPixels, refPixels, testScenario)
for (let i = 0; i < actualPixels.length; i++) {
// Alpha channel
if (i % 4 == 3)
assert_equals(actualPixels[i], refPixels[i]);
assert_approx_equals(actualPixels[i], refPixels[i], tolerance_color);
else if (i == 4)
assert_approx_equals(actualPixels[i], refPixels[i], tolerance_r_green);
else
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment