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( ...@@ -1656,11 +1656,12 @@ ImageData* BaseRenderingContext2D::getImageData(
return nullptr; return nullptr;
} }
IntRect image_data_rect(sx, sy, sw, sh); const IntRect image_data_rect(sx, sy, sw, sh);
bool hasResourceProvider = CanCreateCanvas2dResourceProvider();
ImageDataColorSettings* color_settings = ImageDataColorSettings* color_settings =
GetColorSettingsAsImageDataColorSettings(); GetColorSettingsAsImageDataColorSettings();
if (!hasResourceProvider || isContextLost()) { const ImageDataStorageFormat storage_format =
ImageData::GetImageDataStorageFormat(color_settings->storageFormat());
if (!CanCreateCanvas2dResourceProvider() || isContextLost()) {
ImageData* result = ImageData* result =
ImageData::Create(image_data_rect.Size(), color_settings); ImageData::Create(image_data_rect.Size(), color_settings);
if (!result) if (!result)
...@@ -1668,7 +1669,6 @@ ImageData* BaseRenderingContext2D::getImageData( ...@@ -1668,7 +1669,6 @@ ImageData* BaseRenderingContext2D::getImageData(
return result; return result;
} }
const CanvasColorParams& color_params = GetCanvas2DColorParams();
// Deferred offscreen canvases might have recorded commands, make sure // Deferred offscreen canvases might have recorded commands, make sure
// that those get drawn here // that those get drawn here
FinalizeFrame(); FinalizeFrame();
...@@ -1688,59 +1688,100 @@ ImageData* BaseRenderingContext2D::getImageData( ...@@ -1688,59 +1688,100 @@ ImageData* BaseRenderingContext2D::getImageData(
} }
} }
size_t size_in_bytes; // Compute the ImageData's SkImageInfo;
if (!StaticBitmapImage::GetSizeInBytes(image_data_rect, color_params) SkImageInfo image_info;
.AssignIfValid(&size_in_bytes) || {
size_in_bytes > v8::TypedArray::kMaxLength) { SkColorType color_type = kRGBA_8888_SkColorType;
exception_state.ThrowRangeError("Out of memory at ImageData creation"); switch (storage_format) {
return nullptr; 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 = // Compute the size of and allocate |contents|, the ArrayContentsBuffer.
IsAccelerated() // GPU readback may fail silently. ArrayBufferContents contents;
|| StaticBitmapImage::MayHaveStrayArea(snapshot, image_data_rect); const size_t data_size_bytes = image_info.computeMinByteSize();
ArrayBufferContents::InitializationPolicy initialization_policy = {
may_have_stray_area ? ArrayBufferContents::kZeroInitialize if (data_size_bytes > std::numeric_limits<unsigned int>::max()) {
: ArrayBufferContents::kDontInitialize; exception_state.ThrowRangeError(
"Buffer size exceeds maximum heap object size.");
ArrayBufferContents contents( return nullptr;
size_in_bytes, 1, ArrayBufferContents::kNotShared, initialization_policy); }
if (contents.DataLength() != size_in_bytes) { if (SkImageInfo::ByteSizeOverflowed(data_size_bytes) ||
exception_state.ThrowRangeError("Out of memory at ImageData creation"); data_size_bytes > v8::TypedArray::kMaxLength) {
return nullptr; 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( // Read pixels into |contents|.
snapshot, bool read_pixels_successful =
base::span<uint8_t>(reinterpret_cast<uint8_t*>(contents.Data()), snapshot->PaintImageForCurrentFrame().readPixels(
contents.DataLength()), image_info, contents.Data(), image_info.minRowBytes(), sx, sy);
image_data_rect, color_params)) { if (!read_pixels_successful) {
exception_state.ThrowRangeError("Failed to copy image data"); SkIRect bounds =
return nullptr; snapshot->PaintImageForCurrentFrame().GetSkImageInfo().bounds();
DCHECK(!bounds.intersect(SkIRect::MakeXYWH(sx, sy, sw, sh)));
} }
// Convert pixels to proper storage format if needed // Wrap |contents| in an ImageData.
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;
}
DOMArrayBuffer* array_buffer = DOMArrayBuffer::Create(std::move(contents)); DOMArrayBuffer* array_buffer = DOMArrayBuffer::Create(std::move(contents));
NotShared<DOMArrayBufferView> data_array;
ImageData* imageData = ImageData::Create( switch (storage_format) {
image_data_rect.Size(), case kUint8ClampedArrayStorageFormat: {
NotShared<DOMUint8ClampedArray>(DOMUint8ClampedArray::Create( size_t num_elements = data_size_bytes;
array_buffer, 0, static_cast<unsigned int>(size_in_bytes))), data_array = NotShared<DOMArrayBufferView>(
color_settings); 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()) { if (!IsPaint2D()) {
int scaled_time = getScaledElapsedTime( int scaled_time = getScaledElapsedTime(
...@@ -1753,8 +1794,7 @@ ImageData* BaseRenderingContext2D::getImageData( ...@@ -1753,8 +1794,7 @@ ImageData* BaseRenderingContext2D::getImageData(
"Blink.Canvas.GetImageDataScaledDuration.CPU", scaled_time); "Blink.Canvas.GetImageDataScaledDuration.CPU", scaled_time);
} }
} }
return image_data;
return imageData;
} }
int BaseRenderingContext2D::getScaledElapsedTime(float width, int BaseRenderingContext2D::getScaledElapsedTime(float width,
......
...@@ -2013,7 +2013,6 @@ source_set("blink_platform_unittests_sources") { ...@@ -2013,7 +2013,6 @@ source_set("blink_platform_unittests_sources") {
"graphics/path_test.cc", "graphics/path_test.cc",
"graphics/placeholder_image_test.cc", "graphics/placeholder_image_test.cc",
"graphics/raster_dark_mode_filter_impl_test.cc", "graphics/raster_dark_mode_filter_impl_test.cc",
"graphics/static_bitmap_image_test.cc",
"graphics/video_frame_submitter_test.cc", "graphics/video_frame_submitter_test.cc",
"heap_observer_set_test.cc", "heap_observer_set_test.cc",
"image-decoders/bmp/bmp_image_decoder_test.cc", "image-decoders/bmp/bmp_image_decoder_test.cc",
......
...@@ -79,56 +79,4 @@ void StaticBitmapImage::DrawHelper( ...@@ -79,56 +79,4 @@ void StaticBitmapImage::DrawHelper(
WebCoreClampingModeToSkiaRectConstraint(clamp_mode)); 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 } // namespace blink
...@@ -96,18 +96,6 @@ class PLATFORM_EXPORT StaticBitmapImage : public Image { ...@@ -96,18 +96,6 @@ class PLATFORM_EXPORT StaticBitmapImage : public Image {
return orientation_ == ImageOrientationEnum::kDefault; 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: protected:
// Helper for sub-classes // Helper for sub-classes
void DrawHelper(cc::PaintCanvas*, 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) ...@@ -27,7 +27,7 @@ function testPixels(actualPixels, refPixels, testScenario)
for (let i = 0; i < actualPixels.length; i++) { for (let i = 0; i < actualPixels.length; i++) {
// Alpha channel // Alpha channel
if (i % 4 == 3) 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 // Red channel of the green pixel
else if (i == 4) else if (i == 4)
assert_approx_equals(actualPixels[i], refPixels[i], tolerance_r_green); assert_approx_equals(actualPixels[i], refPixels[i], tolerance_r_green);
...@@ -41,7 +41,7 @@ function testPixels(actualPixels, refPixels, testScenario) ...@@ -41,7 +41,7 @@ function testPixels(actualPixels, refPixels, testScenario)
for (let i = 0; i < actualPixels.length; i++) { for (let i = 0; i < actualPixels.length; i++) {
// Alpha channel // Alpha channel
if (i % 4 == 3) if (i % 4 == 3)
assert_equals(actualPixels[i], refPixels[i]); assert_approx_equals(actualPixels[i], refPixels[i], tolerance_color);
else if (i == 4) else if (i == 4)
assert_approx_equals(actualPixels[i], refPixels[i], tolerance_r_green); assert_approx_equals(actualPixels[i], refPixels[i], tolerance_r_green);
else 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