blob: d2bbbae8bf85e5fd245da430cc2e0802eaf0430b [file] [log] [blame]
/*
* Copyright (C) 2008 Apple 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.
* 3. Neither the name of Apple Computer, Inc. ("Apple") 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 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/core/html/canvas/image_data.h"
#include "base/sys_byteorder.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_image_bitmap_options.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_uint8_clamped_array.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h"
#include "third_party/blink/renderer/platform/graphics/color_behavior.h"
#include "v8/include/v8.h"
namespace blink {
ImageData* ImageData::ValidateAndCreate(
unsigned width,
base::Optional<unsigned> height,
base::Optional<NotShared<DOMArrayBufferView>> data,
const ImageDataSettings* input_settings,
ExceptionState& exception_state,
uint32_t flags) {
IntSize size;
if ((flags & RequireCanvasColorManagement &&
!RuntimeEnabledFeatures::CanvasColorManagementEnabled())) {
exception_state.ThrowTypeError("Overload resolution failed.");
return nullptr;
}
if (!width) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
"The source width is zero or not a number.");
return nullptr;
}
size.SetWidth(width);
if (height) {
if (!*height) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
"The source height is zero or not a number.");
return nullptr;
}
size.SetHeight(*height);
}
// Populate the ImageDataSettings to use based on |input_settings|.
ImageDataSettings* settings = ImageDataSettings::Create();
if (input_settings) {
settings->setColorSpace(input_settings->colorSpace());
settings->setStorageFormat(input_settings->storageFormat());
}
// Ensure the size does not overflow.
unsigned size_in_elements = 0;
{
// Please note that the number "4" in the means number of channels required
// to describe a pixel, namely, red, green, blue and alpha.
base::CheckedNumeric<unsigned> size_in_elements_checked = 4;
size_in_elements_checked *= size.Width();
size_in_elements_checked *= size.Height();
if (!(flags & ValidateAndCreateFlags::Context2DErrorMode)) {
if (!size_in_elements_checked.IsValid()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
"The requested image size exceeds the supported range.");
return nullptr;
}
}
if (!size_in_elements_checked.IsValid() ||
size_in_elements_checked.ValueOrDie() > v8::TypedArray::kMaxLength) {
exception_state.ThrowRangeError("Out of memory at ImageData creation.");
return nullptr;
}
size_in_elements = size_in_elements_checked.ValueOrDie();
}
// If |data| is provided, ensure it is a reasonable format, and that it can
// work with |size|. Update |settings| to reflect |data|'s format.
if (data) {
DCHECK(data);
switch ((*data)->GetType()) {
case DOMArrayBufferView::ViewType::kTypeUint8Clamped:
settings->setStorageFormat(kUint8ClampedArrayStorageFormatName);
break;
case DOMArrayBufferView::ViewType::kTypeUint16:
settings->setStorageFormat(kUint16ArrayStorageFormatName);
break;
case DOMArrayBufferView::ViewType::kTypeFloat32:
settings->setStorageFormat(kFloat32ArrayStorageFormatName);
break;
default:
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
"The input data type is not supported.");
return nullptr;
}
static_assert(
std::numeric_limits<unsigned>::max() >=
std::numeric_limits<uint32_t>::max(),
"We use UINT32_MAX as the upper bound of the input size and expect "
"that the result fits into an `unsigned`.");
unsigned data_length_in_bytes = 0;
if (!base::CheckedNumeric<uint32_t>((*data)->byteLength())
.AssignIfValid(&data_length_in_bytes)) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
"The input data is too large. The maximum size is 4294967295.");
return nullptr;
}
if (!data_length_in_bytes) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"The input data has zero elements.");
return nullptr;
}
const unsigned data_length_in_elements =
data_length_in_bytes / (*data)->TypeSize();
if (data_length_in_elements % 4) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"The input data length is not a multiple of 4.");
return nullptr;
}
const unsigned data_length_in_pixels = data_length_in_elements / 4;
if (data_length_in_pixels % width) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
"The input data length is not a multiple of (4 * width).");
return nullptr;
}
const unsigned expected_height = data_length_in_pixels / width;
if (height) {
if (*height != expected_height) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
"The input data length is not equal to (4 * width * height).");
return nullptr;
}
} else {
size.SetHeight(expected_height);
}
}
NotShared<DOMArrayBufferView> allocated_data;
if (!data) {
ImageDataStorageFormat storage_format =
GetImageDataStorageFormat(settings->storageFormat());
allocated_data = AllocateAndValidateDataArray(
size_in_elements, storage_format, &exception_state);
if (!allocated_data)
return nullptr;
}
return MakeGarbageCollected<ImageData>(size, data ? *data : allocated_data,
settings);
}
NotShared<DOMArrayBufferView> ImageData::AllocateAndValidateDataArray(
const unsigned& length,
ImageDataStorageFormat storage_format,
ExceptionState* exception_state) {
if (!length)
return NotShared<DOMArrayBufferView>();
NotShared<DOMArrayBufferView> data_array;
switch (storage_format) {
case kUint8ClampedArrayStorageFormat:
data_array = NotShared<DOMArrayBufferView>(
DOMUint8ClampedArray::CreateOrNull(length));
break;
case kUint16ArrayStorageFormat:
data_array =
NotShared<DOMArrayBufferView>(DOMUint16Array::CreateOrNull(length));
break;
case kFloat32ArrayStorageFormat:
data_array =
NotShared<DOMArrayBufferView>(DOMFloat32Array::CreateOrNull(length));
break;
default:
NOTREACHED();
}
size_t expected_size;
if (!data_array || (!base::CheckMul(length, data_array->TypeSize())
.AssignIfValid(&expected_size) &&
expected_size != data_array->byteLength())) {
if (exception_state)
exception_state->ThrowRangeError("Out of memory at ImageData creation");
return NotShared<DOMArrayBufferView>();
}
return data_array;
}
// This function accepts size (0, 0) and always returns the ImageData in
// "srgb" color space and "uint8" storage format.
ImageData* ImageData::CreateForTest(const IntSize& size) {
base::CheckedNumeric<unsigned> data_size =
StorageFormatBytesPerPixel(kUint8ClampedArrayStorageFormat);
data_size *= size.Width();
data_size *= size.Height();
if (!data_size.IsValid() ||
data_size.ValueOrDie() > v8::TypedArray::kMaxLength)
return nullptr;
NotShared<DOMUint8ClampedArray> byte_array(
DOMUint8ClampedArray::CreateOrNull(data_size.ValueOrDie()));
if (!byte_array)
return nullptr;
return MakeGarbageCollected<ImageData>(size, byte_array);
}
// This function is called from unit tests, and all the parameters are supposed
// to be validated on the call site.
ImageData* ImageData::CreateForTest(const IntSize& size,
NotShared<DOMArrayBufferView> buffer_view,
const ImageDataSettings* settings) {
return MakeGarbageCollected<ImageData>(size, buffer_view, settings);
}
ScriptPromise ImageData::CreateImageBitmap(ScriptState* script_state,
base::Optional<IntRect> crop_rect,
const ImageBitmapOptions* options,
ExceptionState& exception_state) {
if (IsBufferBaseDetached()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"The source data has been detached.");
return ScriptPromise();
}
return ImageBitmapSource::FulfillImageBitmap(
script_state, MakeGarbageCollected<ImageBitmap>(this, crop_rect, options),
exception_state);
}
v8::Local<v8::Object> ImageData::AssociateWithWrapper(
v8::Isolate* isolate,
const WrapperTypeInfo* wrapper_type,
v8::Local<v8::Object> wrapper) {
wrapper =
ScriptWrappable::AssociateWithWrapper(isolate, wrapper_type, wrapper);
if (!wrapper.IsEmpty() && data_.IsUint8ClampedArray()) {
// Create a V8 object with |data_| and set the "data" property
// of the ImageData object to the created v8 object, eliminating the
// C++ callback when accessing the "data" property.
v8::Local<v8::Value> pixel_array = ToV8(data_, wrapper, isolate);
bool defined_property;
if (pixel_array.IsEmpty() ||
!wrapper
->DefineOwnProperty(isolate->GetCurrentContext(),
V8AtomicString(isolate, "data"), pixel_array,
v8::ReadOnly)
.To(&defined_property) ||
!defined_property)
return v8::Local<v8::Object>();
}
return wrapper;
}
String ImageData::CanvasColorSpaceName(CanvasColorSpace color_space) {
switch (color_space) {
case CanvasColorSpace::kSRGB:
return kSRGBCanvasColorSpaceName;
case CanvasColorSpace::kRec2020:
return kRec2020CanvasColorSpaceName;
case CanvasColorSpace::kP3:
return kP3CanvasColorSpaceName;
default:
NOTREACHED();
}
return kSRGBCanvasColorSpaceName;
}
ImageDataStorageFormat ImageData::GetImageDataStorageFormat(
const String& storage_format_name) {
if (storage_format_name == kUint8ClampedArrayStorageFormatName)
return kUint8ClampedArrayStorageFormat;
if (storage_format_name == kUint16ArrayStorageFormatName)
return kUint16ArrayStorageFormat;
if (storage_format_name == kFloat32ArrayStorageFormatName)
return kFloat32ArrayStorageFormat;
NOTREACHED();
return kUint8ClampedArrayStorageFormat;
}
CanvasColorSpace ImageData::GetCanvasColorSpace() const {
if (!RuntimeEnabledFeatures::CanvasColorManagementEnabled())
return CanvasColorSpace::kSRGB;
return CanvasColorSpaceFromName(settings_->colorSpace());
}
ImageDataStorageFormat ImageData::GetImageDataStorageFormat() const {
if (data_u16_)
return kUint16ArrayStorageFormat;
if (data_f32_)
return kFloat32ArrayStorageFormat;
return kUint8ClampedArrayStorageFormat;
}
unsigned ImageData::StorageFormatBytesPerPixel(
const String& storage_format_name) {
if (storage_format_name == kUint8ClampedArrayStorageFormatName)
return 4;
if (storage_format_name == kUint16ArrayStorageFormatName)
return 8;
if (storage_format_name == kFloat32ArrayStorageFormatName)
return 16;
NOTREACHED();
return 1;
}
unsigned ImageData::StorageFormatBytesPerPixel(
ImageDataStorageFormat storage_format) {
switch (storage_format) {
case kUint8ClampedArrayStorageFormat:
return 4;
case kUint16ArrayStorageFormat:
return 8;
case kFloat32ArrayStorageFormat:
return 16;
}
NOTREACHED();
return 1;
}
bool ImageData::IsBufferBaseDetached() const {
if (data_.IsUint8ClampedArray())
return data_.GetAsUint8ClampedArray()->BufferBase()->IsDetached();
if (data_.IsUint16Array())
return data_.GetAsUint16Array()->BufferBase()->IsDetached();
if (data_.IsFloat32Array())
return data_.GetAsFloat32Array()->BufferBase()->IsDetached();
return false;
}
SkPixmap ImageData::GetSkPixmap() const {
CHECK(!IsBufferBaseDetached());
SkColorType color_type = kRGBA_8888_SkColorType;
const void* data = nullptr;
if (data_.IsUint8ClampedArray()) {
color_type = kRGBA_8888_SkColorType;
data = data_.GetAsUint8ClampedArray()->Data();
} else if (data_.IsUint16Array()) {
color_type = kR16G16B16A16_unorm_SkColorType;
data = data_.GetAsUint16Array()->Data();
} else if (data_.IsFloat32Array()) {
color_type = kRGBA_F32_SkColorType;
data = data_.GetAsFloat32Array()->Data();
}
SkImageInfo info =
SkImageInfo::Make(width(), height(), color_type, kUnpremul_SkAlphaType,
CanvasColorSpaceToSkColorSpace(GetCanvasColorSpace()));
return SkPixmap(info, data, info.minRowBytes());
}
void ImageData::Trace(Visitor* visitor) const {
visitor->Trace(settings_);
visitor->Trace(data_);
visitor->Trace(data_u8_);
visitor->Trace(data_u16_);
visitor->Trace(data_f32_);
ScriptWrappable::Trace(visitor);
}
ImageData::ImageData(const IntSize& size,
NotShared<DOMArrayBufferView> data,
const ImageDataSettings* settings)
: size_(size), settings_(ImageDataSettings::Create()) {
DCHECK_GE(size.Width(), 0);
DCHECK_GE(size.Height(), 0);
DCHECK(data);
data_u8_.Clear();
data_u16_.Clear();
data_f32_.Clear();
if (settings) {
settings_->setColorSpace(settings->colorSpace());
settings_->setStorageFormat(settings->storageFormat());
}
ImageDataStorageFormat storage_format =
GetImageDataStorageFormat(settings_->storageFormat());
switch (storage_format) {
case kUint8ClampedArrayStorageFormat:
DCHECK_EQ(data->GetType(),
DOMArrayBufferView::ViewType::kTypeUint8Clamped);
data_u8_ = data;
DCHECK(data_u8_);
data_.SetUint8ClampedArray(data_u8_);
SECURITY_CHECK(
(base::CheckedNumeric<size_t>(size.Width()) * size.Height() * 4)
.ValueOrDie() <= data_.GetAsUint8ClampedArray()->length());
break;
case kUint16ArrayStorageFormat:
DCHECK_EQ(data->GetType(), DOMArrayBufferView::ViewType::kTypeUint16);
data_u16_ = data;
DCHECK(data_u16_);
data_.SetUint16Array(data_u16_);
SECURITY_CHECK(
(base::CheckedNumeric<size_t>(size.Width()) * size.Height() * 4)
.ValueOrDie() <= data_.GetAsUint16Array()->length());
break;
case kFloat32ArrayStorageFormat:
DCHECK_EQ(data->GetType(), DOMArrayBufferView::ViewType::kTypeFloat32);
data_f32_ = data;
DCHECK(data_f32_);
data_.SetFloat32Array(data_f32_);
SECURITY_CHECK(
(base::CheckedNumeric<size_t>(size.Width()) * size.Height() * 4)
.ValueOrDie() <= data_.GetAsFloat32Array()->length());
break;
default:
NOTREACHED();
}
}
} // namespace blink