text: /*
   * 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
  