| /* |
| * 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_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/core/imagebitmap/image_bitmap_options.h" |
| #include "third_party/blink/renderer/platform/graphics/color_behavior.h" |
| #include "third_party/skia/include/third_party/skcms/skcms.h" |
| #include "v8/include/v8.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| bool RaiseDOMExceptionAndReturnFalse(ExceptionState* exception_state, |
| DOMExceptionCode exception_code, |
| const char* message) { |
| if (exception_state) |
| exception_state->ThrowDOMException(exception_code, message); |
| return false; |
| } |
| |
| } // namespace |
| |
| bool ImageData::ValidateConstructorArguments( |
| const unsigned& param_flags, |
| const IntSize* size, |
| const unsigned& width, |
| const unsigned& height, |
| const DOMArrayBufferView* data, |
| const ImageDataColorSettings* color_settings, |
| ExceptionState* exception_state) { |
| // We accept all the combinations of colorSpace and storageFormat in an |
| // ImageDataColorSettings to be stored in an ImageData. Therefore, we don't |
| // check the color settings in this function. |
| |
| if ((param_flags & kParamWidth) && !width) { |
| return RaiseDOMExceptionAndReturnFalse( |
| exception_state, DOMExceptionCode::kIndexSizeError, |
| "The source width is zero or not a number."); |
| } |
| |
| if ((param_flags & kParamHeight) && !height) { |
| return RaiseDOMExceptionAndReturnFalse( |
| exception_state, DOMExceptionCode::kIndexSizeError, |
| "The source height is zero or not a number."); |
| } |
| |
| if (param_flags & (kParamWidth | kParamHeight)) { |
| base::CheckedNumeric<unsigned> data_size = 4; |
| if (color_settings) { |
| data_size *= |
| ImageData::StorageFormatDataSize(color_settings->storageFormat()); |
| } |
| data_size *= width; |
| data_size *= height; |
| if (!data_size.IsValid()) { |
| return RaiseDOMExceptionAndReturnFalse( |
| exception_state, DOMExceptionCode::kIndexSizeError, |
| "The requested image size exceeds the supported range."); |
| } |
| |
| if (data_size.ValueOrDie() > v8::TypedArray::kMaxLength) { |
| if (exception_state) { |
| exception_state->ThrowRangeError( |
| "Out of memory at ImageData creation."); |
| } |
| return false; |
| } |
| } |
| |
| unsigned data_length = 0; |
| if (param_flags & kParamData) { |
| DCHECK(data); |
| if (data->GetType() != DOMArrayBufferView::ViewType::kTypeUint8Clamped && |
| data->GetType() != DOMArrayBufferView::ViewType::kTypeUint16 && |
| data->GetType() != DOMArrayBufferView::ViewType::kTypeFloat32) { |
| return RaiseDOMExceptionAndReturnFalse( |
| exception_state, DOMExceptionCode::kNotSupportedError, |
| "The input data type is not supported."); |
| } |
| |
| if (!data->byteLength()) { |
| return RaiseDOMExceptionAndReturnFalse( |
| exception_state, DOMExceptionCode::kInvalidStateError, |
| "The input data has zero elements."); |
| } |
| |
| data_length = data->byteLength() / data->TypeSize(); |
| if (data_length % 4) { |
| return RaiseDOMExceptionAndReturnFalse( |
| exception_state, DOMExceptionCode::kInvalidStateError, |
| "The input data length is not a multiple of 4."); |
| } |
| |
| if ((param_flags & kParamWidth) && (data_length / 4) % width) { |
| return RaiseDOMExceptionAndReturnFalse( |
| exception_state, DOMExceptionCode::kIndexSizeError, |
| "The input data length is not a multiple of (4 * width)."); |
| } |
| |
| if ((param_flags & kParamWidth) && (param_flags & kParamHeight) && |
| height != data_length / (4 * width)) |
| return RaiseDOMExceptionAndReturnFalse( |
| exception_state, DOMExceptionCode::kIndexSizeError, |
| "The input data length is not equal to (4 * width * height)."); |
| } |
| |
| if (param_flags & kParamSize) { |
| if (size->Width() <= 0 || size->Height() <= 0) |
| return false; |
| base::CheckedNumeric<unsigned> data_size = 4; |
| data_size *= size->Width(); |
| data_size *= size->Height(); |
| if (!data_size.IsValid() || |
| data_size.ValueOrDie() > v8::TypedArray::kMaxLength) |
| return false; |
| if (param_flags & kParamData) { |
| if (data_size.ValueOrDie() > data_length) |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| DOMArrayBufferView* ImageData::AllocateAndValidateDataArray( |
| const unsigned& length, |
| ImageDataStorageFormat storage_format, |
| ExceptionState* exception_state) { |
| if (!length) |
| return nullptr; |
| |
| DOMArrayBufferView* data_array = nullptr; |
| switch (storage_format) { |
| case kUint8ClampedArrayStorageFormat: |
| data_array = DOMUint8ClampedArray::CreateOrNull(length); |
| break; |
| case kUint16ArrayStorageFormat: |
| data_array = DOMUint16Array::CreateOrNull(length); |
| break; |
| case kFloat32ArrayStorageFormat: |
| data_array = DOMFloat32Array::CreateOrNull(length); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| |
| if (!data_array || |
| length != data_array->byteLength() / data_array->TypeSize()) { |
| if (exception_state) |
| exception_state->ThrowRangeError("Out of memory at ImageData creation"); |
| return nullptr; |
| } |
| |
| return data_array; |
| } |
| |
| DOMUint8ClampedArray* ImageData::AllocateAndValidateUint8ClampedArray( |
| const unsigned& length, |
| ExceptionState* exception_state) { |
| DOMArrayBufferView* buffer_view = AllocateAndValidateDataArray( |
| length, kUint8ClampedArrayStorageFormat, exception_state); |
| if (!buffer_view) |
| return nullptr; |
| DOMUint8ClampedArray* u8_array = const_cast<DOMUint8ClampedArray*>( |
| static_cast<const DOMUint8ClampedArray*>(buffer_view)); |
| DCHECK(u8_array); |
| return u8_array; |
| } |
| |
| DOMUint16Array* ImageData::AllocateAndValidateUint16Array( |
| const unsigned& length, |
| ExceptionState* exception_state) { |
| DOMArrayBufferView* buffer_view = AllocateAndValidateDataArray( |
| length, kUint16ArrayStorageFormat, exception_state); |
| if (!buffer_view) |
| return nullptr; |
| DOMUint16Array* u16_array = const_cast<DOMUint16Array*>( |
| static_cast<const DOMUint16Array*>(buffer_view)); |
| DCHECK(u16_array); |
| return u16_array; |
| } |
| |
| DOMFloat32Array* ImageData::AllocateAndValidateFloat32Array( |
| const unsigned& length, |
| ExceptionState* exception_state) { |
| DOMArrayBufferView* buffer_view = AllocateAndValidateDataArray( |
| length, kFloat32ArrayStorageFormat, exception_state); |
| if (!buffer_view) |
| return nullptr; |
| DOMFloat32Array* f32_array = const_cast<DOMFloat32Array*>( |
| static_cast<const DOMFloat32Array*>(buffer_view)); |
| DCHECK(f32_array); |
| return f32_array; |
| } |
| |
| ImageData* ImageData::Create(const IntSize& size, |
| const ImageDataColorSettings* color_settings) { |
| if (!ImageData::ValidateConstructorArguments(kParamSize, &size, 0, 0, nullptr, |
| color_settings)) |
| return nullptr; |
| ImageDataStorageFormat storage_format = kUint8ClampedArrayStorageFormat; |
| if (color_settings) { |
| storage_format = |
| ImageData::GetImageDataStorageFormat(color_settings->storageFormat()); |
| } |
| DOMArrayBufferView* data_array = |
| AllocateAndValidateDataArray(4 * static_cast<unsigned>(size.Width()) * |
| static_cast<unsigned>(size.Height()), |
| storage_format); |
| return data_array |
| ? MakeGarbageCollected<ImageData>(size, data_array, color_settings) |
| : nullptr; |
| } |
| |
| ImageDataColorSettings* CanvasColorParamsToImageDataColorSettings( |
| const CanvasColorParams& color_params) { |
| ImageDataColorSettings* color_settings = ImageDataColorSettings::Create(); |
| switch (color_params.ColorSpace()) { |
| case kSRGBCanvasColorSpace: |
| color_settings->setColorSpace(kSRGBCanvasColorSpaceName); |
| break; |
| case kLinearRGBCanvasColorSpace: |
| color_settings->setColorSpace(kLinearRGBCanvasColorSpaceName); |
| break; |
| case kRec2020CanvasColorSpace: |
| color_settings->setColorSpace(kRec2020CanvasColorSpaceName); |
| break; |
| case kP3CanvasColorSpace: |
| color_settings->setColorSpace(kP3CanvasColorSpaceName); |
| break; |
| } |
| color_settings->setStorageFormat(kUint8ClampedArrayStorageFormatName); |
| if (color_params.PixelFormat() == kF16CanvasPixelFormat) |
| color_settings->setStorageFormat(kFloat32ArrayStorageFormatName); |
| return color_settings; |
| } |
| |
| ImageData* ImageData::Create(const IntSize& size, |
| const CanvasColorParams& color_params) { |
| ImageDataColorSettings* color_settings = |
| CanvasColorParamsToImageDataColorSettings(color_params); |
| return ImageData::Create(size, color_settings); |
| } |
| |
| ImageData* ImageData::Create(const IntSize& size, |
| CanvasColorSpace color_space, |
| ImageDataStorageFormat storage_format) { |
| ImageDataColorSettings* color_settings = ImageDataColorSettings::Create(); |
| switch (color_space) { |
| case kSRGBCanvasColorSpace: |
| color_settings->setColorSpace(kSRGBCanvasColorSpaceName); |
| break; |
| case kLinearRGBCanvasColorSpace: |
| color_settings->setColorSpace(kLinearRGBCanvasColorSpaceName); |
| break; |
| case kRec2020CanvasColorSpace: |
| color_settings->setColorSpace(kRec2020CanvasColorSpaceName); |
| break; |
| case kP3CanvasColorSpace: |
| color_settings->setColorSpace(kP3CanvasColorSpaceName); |
| break; |
| } |
| |
| switch (storage_format) { |
| case kUint8ClampedArrayStorageFormat: |
| color_settings->setStorageFormat(kUint8ClampedArrayStorageFormatName); |
| break; |
| case kUint16ArrayStorageFormat: |
| color_settings->setStorageFormat(kUint16ArrayStorageFormatName); |
| break; |
| case kFloat32ArrayStorageFormat: |
| color_settings->setStorageFormat(kFloat32ArrayStorageFormatName); |
| break; |
| } |
| |
| return ImageData::Create(size, color_settings); |
| } |
| |
| ImageData* ImageData::Create(const IntSize& size, |
| NotShared<DOMArrayBufferView> data_array, |
| const ImageDataColorSettings* color_settings) { |
| if (!ImageData::ValidateConstructorArguments(kParamSize | kParamData, &size, |
| 0, 0, data_array.View(), |
| color_settings)) |
| return nullptr; |
| return MakeGarbageCollected<ImageData>(size, data_array.View(), |
| color_settings); |
| } |
| |
| static SkImageInfo GetImageInfo(scoped_refptr<StaticBitmapImage> image) { |
| sk_sp<SkImage> skia_image = image->PaintImageForCurrentFrame().GetSkImage(); |
| return SkImageInfo::Make(skia_image->width(), skia_image->height(), |
| skia_image->colorType(), skia_image->alphaType(), |
| skia_image->refColorSpace()); |
| } |
| |
| ImageData* ImageData::Create(scoped_refptr<StaticBitmapImage> image, |
| AlphaDisposition alpha_disposition) { |
| sk_sp<SkImage> skia_image = image->PaintImageForCurrentFrame().GetSkImage(); |
| DCHECK(skia_image); |
| SkImageInfo image_info = GetImageInfo(image); |
| CanvasColorParams color_params(image_info); |
| if (image_info.alphaType() != kOpaque_SkAlphaType) { |
| if (alpha_disposition == kPremultiplyAlpha) { |
| image_info = image_info.makeAlphaType(kPremul_SkAlphaType); |
| } else if (alpha_disposition == kUnpremultiplyAlpha) { |
| image_info = image_info.makeAlphaType(kUnpremul_SkAlphaType); |
| } |
| } |
| |
| SkColorType color_type = image_info.colorType(); |
| bool create_f32_image_data = (color_type == kRGBA_1010102_SkColorType || |
| color_type == kRGB_101010x_SkColorType || |
| color_type == kRGBA_F16_SkColorType || |
| color_type == kRGBA_F32_SkColorType); |
| |
| if (!create_f32_image_data) { |
| ImageData* image_data = Create(image->Size(), color_params); |
| if (!image_data) |
| return nullptr; |
| image_info = image_info.makeColorType(kRGBA_8888_SkColorType); |
| skia_image->readPixels(image_info, image_data->data()->Data(), |
| image_info.minRowBytes(), 0, 0); |
| return image_data; |
| } |
| |
| base::CheckedNumeric<uint32_t> area = image->Size().Area(); |
| area *= 4; |
| |
| if (!area.IsValid()) |
| return nullptr; |
| // Create image data with f32 storage |
| DOMFloat32Array* f32_array = |
| ImageData::AllocateAndValidateFloat32Array(area.ValueOrDie(), nullptr); |
| if (!f32_array) |
| return nullptr; |
| image_info = image_info.makeColorType(kRGBA_F32_SkColorType); |
| skia_image->readPixels(image_info, f32_array->Data(), |
| image_info.minRowBytes(), 0, 0); |
| ImageDataColorSettings* color_settings = |
| CanvasColorParamsToImageDataColorSettings(color_params); |
| return Create(image->Size(), NotShared<blink::DOMArrayBufferView>(f32_array), |
| color_settings); |
| } |
| |
| ImageData* ImageData::Create(unsigned width, |
| unsigned height, |
| ExceptionState& exception_state) { |
| if (!ImageData::ValidateConstructorArguments(kParamWidth | kParamHeight, |
| nullptr, width, height, nullptr, |
| nullptr, &exception_state)) |
| return nullptr; |
| |
| DOMArrayBufferView* byte_array = AllocateAndValidateDataArray( |
| 4 * width * height, kUint8ClampedArrayStorageFormat, &exception_state); |
| return byte_array ? MakeGarbageCollected<ImageData>(IntSize(width, height), |
| byte_array) |
| : nullptr; |
| } |
| |
| ImageData* ImageData::Create(NotShared<DOMUint8ClampedArray> data, |
| unsigned width, |
| ExceptionState& exception_state) { |
| if (!ImageData::ValidateConstructorArguments(kParamData | kParamWidth, |
| nullptr, width, 0, data.View(), |
| nullptr, &exception_state)) |
| return nullptr; |
| |
| unsigned height = data.View()->length() / (width * 4); |
| return MakeGarbageCollected<ImageData>(IntSize(width, height), data.View()); |
| } |
| |
| ImageData* ImageData::Create(NotShared<DOMUint8ClampedArray> data, |
| unsigned width, |
| unsigned height, |
| ExceptionState& exception_state) { |
| if (!ImageData::ValidateConstructorArguments( |
| kParamData | kParamWidth | kParamHeight, nullptr, width, height, |
| data.View(), nullptr, &exception_state)) |
| return nullptr; |
| |
| return MakeGarbageCollected<ImageData>(IntSize(width, height), data.View()); |
| } |
| |
| ImageData* ImageData::CreateImageData( |
| unsigned width, |
| unsigned height, |
| const ImageDataColorSettings* color_settings, |
| ExceptionState& exception_state) { |
| if (!ImageData::ValidateConstructorArguments( |
| kParamWidth | kParamHeight, nullptr, width, height, nullptr, |
| color_settings, &exception_state)) |
| return nullptr; |
| |
| ImageDataStorageFormat storage_format = |
| ImageData::GetImageDataStorageFormat(color_settings->storageFormat()); |
| DOMArrayBufferView* buffer_view = AllocateAndValidateDataArray( |
| 4 * width * height, storage_format, &exception_state); |
| |
| if (!buffer_view) |
| return nullptr; |
| |
| return MakeGarbageCollected<ImageData>(IntSize(width, height), buffer_view, |
| color_settings); |
| } |
| |
| ImageData* ImageData::CreateImageData(ImageDataArray& data, |
| unsigned width, |
| unsigned height, |
| ImageDataColorSettings* color_settings, |
| ExceptionState& exception_state) { |
| DOMArrayBufferView* buffer_view = nullptr; |
| |
| // When pixels data is provided, we need to override the storage format of |
| // ImageDataColorSettings with the one that matches the data type of the |
| // pixels. |
| String storage_format_name; |
| |
| if (data.IsUint8ClampedArray()) { |
| buffer_view = data.GetAsUint8ClampedArray().View(); |
| storage_format_name = kUint8ClampedArrayStorageFormatName; |
| } else if (data.IsUint16Array()) { |
| buffer_view = data.GetAsUint16Array().View(); |
| storage_format_name = kUint16ArrayStorageFormatName; |
| } else if (data.IsFloat32Array()) { |
| buffer_view = data.GetAsFloat32Array().View(); |
| storage_format_name = kFloat32ArrayStorageFormatName; |
| } else { |
| NOTREACHED(); |
| } |
| |
| if (storage_format_name != color_settings->storageFormat()) |
| color_settings->setStorageFormat(storage_format_name); |
| |
| if (!ImageData::ValidateConstructorArguments( |
| kParamData | kParamWidth | kParamHeight, nullptr, width, height, |
| buffer_view, color_settings, &exception_state)) |
| return nullptr; |
| |
| return MakeGarbageCollected<ImageData>(IntSize(width, height), buffer_view, |
| color_settings); |
| } |
| |
| // 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 = 4; |
| data_size *= size.Width(); |
| data_size *= size.Height(); |
| if (!data_size.IsValid() || |
| data_size.ValueOrDie() > v8::TypedArray::kMaxLength) |
| return nullptr; |
| |
| 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, |
| DOMArrayBufferView* buffer_view, |
| const ImageDataColorSettings* color_settings) { |
| return MakeGarbageCollected<ImageData>(size, buffer_view, color_settings); |
| } |
| |
| // Crops ImageData to the intersect of its size and the given rectangle. If the |
| // intersection is empty or it cannot create the cropped ImageData it returns |
| // nullptr. This function leaves the source ImageData intact. When crop_rect |
| // covers all the ImageData, a copy of the ImageData is returned. |
| // TODO (zakerinasab): crbug.com/774484: As a rule of thumb ImageData belongs to |
| // the user and its state should not change unless directly modified by the |
| // user. Therefore, we should be able to remove the extra copy and return a |
| // "cropped view" on the source ImageData object. |
| ImageData* ImageData::CropRect(const IntRect& crop_rect, bool flip_y) { |
| IntRect src_rect(IntPoint(), size_); |
| const IntRect dst_rect = Intersection(src_rect, crop_rect); |
| if (dst_rect.IsEmpty()) |
| return nullptr; |
| |
| unsigned data_size = 4 * dst_rect.Width() * dst_rect.Height(); |
| DOMArrayBufferView* buffer_view = AllocateAndValidateDataArray( |
| data_size, |
| ImageData::GetImageDataStorageFormat(color_settings_->storageFormat())); |
| if (!buffer_view) |
| return nullptr; |
| |
| if (src_rect == dst_rect && !flip_y) { |
| std::memcpy(buffer_view->BufferBase()->Data(), BufferBase()->Data(), |
| data_size * buffer_view->TypeSize()); |
| } else { |
| unsigned data_type_size = |
| ImageData::StorageFormatDataSize(color_settings_->storageFormat()); |
| int src_index = (dst_rect.X() + dst_rect.Y() * src_rect.Width()) * 4; |
| int dst_index = 0; |
| if (flip_y) |
| dst_index = (dst_rect.Height() - 1) * dst_rect.Width() * 4; |
| int src_row_stride = src_rect.Width() * 4; |
| int dst_row_stride = flip_y ? -dst_rect.Width() * 4 : dst_rect.Width() * 4; |
| for (int i = 0; i < dst_rect.Height(); i++) { |
| std::memcpy( |
| static_cast<char*>(buffer_view->BufferBase()->Data()) + |
| dst_index * data_type_size, |
| static_cast<char*>(BufferBase()->Data()) + src_index * data_type_size, |
| dst_rect.Width() * 4 * data_type_size); |
| src_index += src_row_stride; |
| dst_index += dst_row_stride; |
| } |
| } |
| return MakeGarbageCollected<ImageData>(dst_rect.Size(), buffer_view, |
| color_settings_); |
| } |
| |
| ScriptPromise ImageData::CreateImageBitmap(ScriptState* script_state, |
| EventTarget& event_target, |
| base::Optional<IntRect> crop_rect, |
| const ImageBitmapOptions* options) { |
| if (BufferBase()->IsNeutered()) { |
| return ScriptPromise::RejectWithDOMException( |
| script_state, MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kInvalidStateError, |
| "The source data has been detached.")); |
| } |
| return ImageBitmapSource::FulfillImageBitmap( |
| script_state, ImageBitmap::Create(this, crop_rect, options)); |
| } |
| |
| 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_) { |
| // Create a V8 Uint8ClampedArray object 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_.Get(), 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; |
| } |
| |
| const DOMUint8ClampedArray* ImageData::data() const { |
| if (color_settings_->storageFormat() == kUint8ClampedArrayStorageFormatName) |
| return data_.Get(); |
| return nullptr; |
| } |
| |
| DOMUint8ClampedArray* ImageData::data() { |
| if (color_settings_->storageFormat() == kUint8ClampedArrayStorageFormatName) |
| return data_.Get(); |
| return nullptr; |
| } |
| |
| CanvasColorSpace ImageData::GetCanvasColorSpace( |
| const String& color_space_name) { |
| if (color_space_name == kSRGBCanvasColorSpaceName) |
| return kSRGBCanvasColorSpace; |
| if (color_space_name == kLinearRGBCanvasColorSpaceName) |
| return kLinearRGBCanvasColorSpace; |
| if (color_space_name == kRec2020CanvasColorSpaceName) |
| return kRec2020CanvasColorSpace; |
| if (color_space_name == kP3CanvasColorSpaceName) |
| return kP3CanvasColorSpace; |
| NOTREACHED(); |
| return kSRGBCanvasColorSpace; |
| } |
| |
| String ImageData::CanvasColorSpaceName(CanvasColorSpace color_space) { |
| switch (color_space) { |
| case kSRGBCanvasColorSpace: |
| return kSRGBCanvasColorSpaceName; |
| case kLinearRGBCanvasColorSpace: |
| return kLinearRGBCanvasColorSpaceName; |
| case kRec2020CanvasColorSpace: |
| return kRec2020CanvasColorSpaceName; |
| case kP3CanvasColorSpace: |
| 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; |
| } |
| |
| ImageDataStorageFormat ImageData::GetImageDataStorageFormat() { |
| if (data_u16_) |
| return kUint16ArrayStorageFormat; |
| if (data_f32_) |
| return kFloat32ArrayStorageFormat; |
| return kUint8ClampedArrayStorageFormat; |
| } |
| |
| unsigned ImageData::StorageFormatDataSize(const String& storage_format_name) { |
| if (storage_format_name == kUint8ClampedArrayStorageFormatName) |
| return 1; |
| if (storage_format_name == kUint16ArrayStorageFormatName) |
| return 2; |
| if (storage_format_name == kFloat32ArrayStorageFormatName) |
| return 4; |
| NOTREACHED(); |
| return 1; |
| } |
| |
| unsigned ImageData::StorageFormatDataSize( |
| ImageDataStorageFormat storage_format) { |
| switch (storage_format) { |
| case kUint8ClampedArrayStorageFormat: |
| return 1; |
| case kUint16ArrayStorageFormat: |
| return 2; |
| case kFloat32ArrayStorageFormat: |
| return 4; |
| } |
| NOTREACHED(); |
| return 1; |
| } |
| |
| DOMArrayBufferView* |
| ImageData::ConvertPixelsFromCanvasPixelFormatToImageDataStorageFormat( |
| WTF::ArrayBufferContents& content, |
| CanvasPixelFormat pixel_format, |
| ImageDataStorageFormat storage_format) { |
| if (!content.DataLength()) |
| return nullptr; |
| |
| if (pixel_format == kRGBA8CanvasPixelFormat && |
| storage_format == kUint8ClampedArrayStorageFormat) { |
| DOMArrayBuffer* array_buffer = DOMArrayBuffer::Create(content); |
| return DOMUint8ClampedArray::Create(array_buffer, 0, |
| array_buffer->ByteLength()); |
| } |
| |
| skcms_PixelFormat src_format = skcms_PixelFormat_RGBA_8888; |
| unsigned num_pixels = content.DataLength() / 4; |
| if (pixel_format == kF16CanvasPixelFormat) { |
| src_format = skcms_PixelFormat_RGBA_hhhh; |
| num_pixels /= 2; |
| } |
| skcms_AlphaFormat alpha_format = skcms_AlphaFormat_Unpremul; |
| |
| if (storage_format == kUint8ClampedArrayStorageFormat) { |
| DOMUint8ClampedArray* u8_array = |
| AllocateAndValidateUint8ClampedArray(num_pixels * 4); |
| if (!u8_array) |
| return nullptr; |
| bool data_transform_successful = skcms_Transform( |
| content.Data(), src_format, alpha_format, nullptr, u8_array->Data(), |
| skcms_PixelFormat_RGBA_8888, alpha_format, nullptr, num_pixels); |
| DCHECK(data_transform_successful); |
| return u8_array; |
| } |
| |
| if (storage_format == kUint16ArrayStorageFormat) { |
| DOMUint16Array* u16_array = AllocateAndValidateUint16Array(num_pixels * 4); |
| if (!u16_array) |
| return nullptr; |
| bool data_transform_successful = skcms_Transform( |
| content.Data(), src_format, alpha_format, nullptr, u16_array->Data(), |
| skcms_PixelFormat_RGBA_16161616LE, alpha_format, nullptr, num_pixels); |
| DCHECK(data_transform_successful); |
| return u16_array; |
| } |
| |
| DOMFloat32Array* f32_array = AllocateAndValidateFloat32Array(num_pixels * 4); |
| if (!f32_array) |
| return nullptr; |
| bool data_transform_successful = skcms_Transform( |
| content.Data(), src_format, alpha_format, nullptr, f32_array->Data(), |
| skcms_PixelFormat_RGBA_ffff, alpha_format, nullptr, num_pixels); |
| DCHECK(data_transform_successful); |
| return f32_array; |
| } |
| |
| DOMArrayBufferBase* ImageData::BufferBase() const { |
| if (data_) |
| return data_->BufferBase(); |
| if (data_u16_) |
| return data_u16_->BufferBase(); |
| if (data_f32_) |
| return data_f32_->BufferBase(); |
| return nullptr; |
| } |
| |
| CanvasColorParams ImageData::GetCanvasColorParams() { |
| if (!RuntimeEnabledFeatures::CanvasColorManagementEnabled()) |
| return CanvasColorParams(); |
| CanvasColorSpace color_space = |
| ImageData::GetCanvasColorSpace(color_settings_->colorSpace()); |
| CanvasPixelFormat pixel_format = kRGBA8CanvasPixelFormat; |
| if (color_settings_->storageFormat() != kUint8ClampedArrayStorageFormatName) |
| pixel_format = kF16CanvasPixelFormat; |
| return CanvasColorParams(color_space, pixel_format, kNonOpaque); |
| } |
| |
| bool ImageData::ImageDataInCanvasColorSettings( |
| CanvasColorSpace canvas_color_space, |
| CanvasPixelFormat canvas_pixel_format, |
| unsigned char* converted_pixels, |
| DataU8ColorType u8_color_type, |
| const IntRect* src_rect, |
| const AlphaDisposition alpha_disposition) { |
| if (!data_ && !data_u16_ && !data_f32_) |
| return false; |
| |
| CanvasColorParams canvas_color_params = |
| CanvasColorParams(canvas_color_space, canvas_pixel_format, kNonOpaque); |
| |
| unsigned char* src_data = static_cast<unsigned char*>(BufferBase()->Data()); |
| |
| ImageDataStorageFormat storage_format = GetImageDataStorageFormat(); |
| |
| skcms_PixelFormat src_pixel_format = skcms_PixelFormat_RGBA_8888; |
| if (data_u16_) |
| src_pixel_format = skcms_PixelFormat_RGBA_16161616LE; |
| else if (data_f32_) |
| src_pixel_format = skcms_PixelFormat_RGBA_ffff; |
| |
| skcms_PixelFormat dst_pixel_format = skcms_PixelFormat_RGBA_8888; |
| if (canvas_pixel_format == kRGBA8CanvasPixelFormat && |
| u8_color_type == kN32ColorType && |
| kN32_SkColorType == kBGRA_8888_SkColorType) { |
| dst_pixel_format = skcms_PixelFormat_BGRA_8888; |
| } else if (canvas_pixel_format == kF16CanvasPixelFormat) { |
| dst_pixel_format = skcms_PixelFormat_RGBA_hhhh; |
| } |
| |
| skcms_AlphaFormat src_alpha_format = skcms_AlphaFormat_Unpremul; |
| skcms_AlphaFormat dst_alpha_format = skcms_AlphaFormat_Unpremul; |
| if (alpha_disposition == kPremultiplyAlpha) |
| dst_alpha_format = skcms_AlphaFormat_PremulAsEncoded; |
| |
| skcms_ICCProfile* src_profile_ptr = nullptr; |
| skcms_ICCProfile* dst_profile_ptr = nullptr; |
| skcms_ICCProfile src_profile, dst_profile; |
| GetCanvasColorParams().GetSkColorSpace()->toProfile(&src_profile); |
| canvas_color_params.GetSkColorSpace()->toProfile(&dst_profile); |
| // If the profiles are similar, we better leave them as nullptr, since |
| // skcms_Transform() only checks for profile pointer equality for the fast |
| // path. |
| if (!skcms_ApproximatelyEqualProfiles(&src_profile, &dst_profile)) { |
| src_profile_ptr = &src_profile; |
| dst_profile_ptr = &dst_profile; |
| } |
| |
| const IntRect* crop_rect = nullptr; |
| if (src_rect && *src_rect != IntRect(IntPoint(), Size())) |
| crop_rect = src_rect; |
| |
| // If only a portion of ImageData is required for canvas, we run the transform |
| // for every line. |
| if (crop_rect) { |
| unsigned bytes_per_pixel = |
| ImageData::StorageFormatDataSize(storage_format) * 4; |
| unsigned src_index = |
| (crop_rect->X() + crop_rect->Y() * width()) * bytes_per_pixel; |
| unsigned dst_index = 0; |
| unsigned src_row_stride = width() * bytes_per_pixel; |
| unsigned dst_row_stride = |
| crop_rect->Width() * canvas_color_params.BytesPerPixel(); |
| bool data_transform_successful = true; |
| |
| for (int i = 0; data_transform_successful && i < crop_rect->Height(); i++) { |
| data_transform_successful = skcms_Transform( |
| src_data + src_index, src_pixel_format, src_alpha_format, |
| src_profile_ptr, converted_pixels + dst_index, dst_pixel_format, |
| dst_alpha_format, dst_profile_ptr, crop_rect->Width()); |
| DCHECK(data_transform_successful); |
| src_index += src_row_stride; |
| dst_index += dst_row_stride; |
| } |
| return data_transform_successful; |
| } |
| |
| base::CheckedNumeric<uint32_t> area = size_.Area(); |
| if (!area.IsValid()) |
| return false; |
| bool data_transform_successful = |
| skcms_Transform(src_data, src_pixel_format, src_alpha_format, |
| src_profile_ptr, converted_pixels, dst_pixel_format, |
| dst_alpha_format, dst_profile_ptr, area.ValueOrDie()); |
| return data_transform_successful; |
| } |
| |
| void ImageData::Trace(Visitor* visitor) { |
| visitor->Trace(color_settings_); |
| visitor->Trace(data_); |
| visitor->Trace(data_u16_); |
| visitor->Trace(data_f32_); |
| visitor->Trace(data_union_); |
| ScriptWrappable::Trace(visitor); |
| } |
| |
| ImageData::ImageData(const IntSize& size, |
| DOMArrayBufferView* data, |
| const ImageDataColorSettings* color_settings) |
| : size_(size), color_settings_(ImageDataColorSettings::Create()) { |
| DCHECK_GE(size.Width(), 0); |
| DCHECK_GE(size.Height(), 0); |
| DCHECK(data); |
| |
| data_ = nullptr; |
| data_u16_ = nullptr; |
| data_f32_ = nullptr; |
| |
| if (color_settings) { |
| color_settings_->setColorSpace(color_settings->colorSpace()); |
| color_settings_->setStorageFormat(color_settings->storageFormat()); |
| } |
| |
| ImageDataStorageFormat storage_format = |
| GetImageDataStorageFormat(color_settings_->storageFormat()); |
| |
| // TODO (zakerinasab): crbug.com/779570 |
| // The default color space for ImageData with U16/F32 data should be |
| // extended-srgb color space. It is temporarily set to linear-rgb, which is |
| // not correct, but fixes crbug.com/779419. |
| |
| switch (storage_format) { |
| case kUint8ClampedArrayStorageFormat: |
| DCHECK(data->GetType() == |
| DOMArrayBufferView::ViewType::kTypeUint8Clamped); |
| data_ = const_cast<DOMUint8ClampedArray*>( |
| static_cast<const DOMUint8ClampedArray*>(data)); |
| DCHECK(data_); |
| data_union_.SetUint8ClampedArray(data_); |
| SECURITY_CHECK(static_cast<unsigned>(size.Width() * size.Height() * 4) <= |
| data_->length()); |
| break; |
| |
| case kUint16ArrayStorageFormat: |
| DCHECK(data->GetType() == DOMArrayBufferView::ViewType::kTypeUint16); |
| data_u16_ = |
| const_cast<DOMUint16Array*>(static_cast<const DOMUint16Array*>(data)); |
| DCHECK(data_u16_); |
| data_union_.SetUint16Array(data_u16_); |
| SECURITY_CHECK(static_cast<unsigned>(size.Width() * size.Height() * 4) <= |
| data_u16_->length()); |
| break; |
| |
| case kFloat32ArrayStorageFormat: |
| DCHECK(data->GetType() == DOMArrayBufferView::ViewType::kTypeFloat32); |
| data_f32_ = const_cast<DOMFloat32Array*>( |
| static_cast<const DOMFloat32Array*>(data)); |
| DCHECK(data_f32_); |
| data_union_.SetFloat32Array(data_f32_); |
| SECURITY_CHECK(static_cast<unsigned>(size.Width() * size.Height() * 4) <= |
| data_f32_->length()); |
| break; |
| |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| } // namespace blink |