| // Copyright 2013 The Chromium Authors |
| // 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/core/imagebitmap/image_bitmap.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/memory/scoped_refptr.h" |
| #include "base/numerics/checked_math.h" |
| #include "base/numerics/clamped_math.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "gpu/command_buffer/client/shared_image_interface.h" |
| #include "gpu/command_buffer/common/shared_image_usage.h" |
| #include "gpu/config/gpu_feature_info.h" |
| #include "skia/ext/legacy_display_globals.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/public/platform/web_media_player.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" |
| #include "third_party/blink/renderer/bindings/core/v8/to_v8_traits.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_throw_dom_exception.h" |
| #include "third_party/blink/renderer/core/dom/dom_exception.h" |
| #include "third_party/blink/renderer/core/html/canvas/html_canvas_element.h" |
| #include "third_party/blink/renderer/core/html/canvas/image_data.h" |
| #include "third_party/blink/renderer/core/html/canvas/image_element_base.h" |
| #include "third_party/blink/renderer/core/html/media/html_video_element.h" |
| #include "third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.h" |
| #include "third_party/blink/renderer/core/svg/graphics/svg_image_for_container.h" |
| #include "third_party/blink/renderer/platform/bindings/enumeration_base.h" |
| #include "third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.h" |
| #include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_context.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_context_types.h" |
| #include "third_party/blink/renderer/platform/graphics/image.h" |
| #include "third_party/blink/renderer/platform/graphics/static_bitmap_image_transform.h" |
| #include "third_party/blink/renderer/platform/graphics/unaccelerated_static_bitmap_image.h" |
| #include "third_party/blink/renderer/platform/graphics/video_frame_image_util.h" |
| #include "third_party/blink/renderer/platform/heap/cross_thread_handle.h" |
| #include "third_party/blink/renderer/platform/heap/garbage_collected.h" |
| #include "third_party/blink/renderer/platform/image-decoders/image_decoder.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/main_thread.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/worker_pool.h" |
| #include "third_party/blink/renderer/platform/transforms/affine_transform.h" |
| #include "third_party/blink/renderer/platform/wtf/cross_thread_copier_base.h" |
| #include "third_party/blink/renderer/platform/wtf/cross_thread_copier_gfx.h" |
| #include "third_party/blink/renderer/platform/wtf/cross_thread_copier_skia.h" |
| #include "third_party/blink/renderer/platform/wtf/cross_thread_copier_std.h" |
| #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h" |
| #include "third_party/skia/include/core/SkCanvas.h" |
| #include "third_party/skia/include/core/SkImage.h" |
| #include "third_party/skia/include/core/SkImageInfo.h" |
| #include "third_party/skia/include/core/SkSurface.h" |
| #include "third_party/skia/include/core/SkSwizzle.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| gfx::Size ParseDstSize(const ImageBitmapOptions* options, |
| const gfx::Rect& src_rect) { |
| int resize_width = 0; |
| int resize_height = 0; |
| if (!options->hasResizeWidth() && !options->hasResizeHeight()) { |
| resize_width = src_rect.width(); |
| resize_height = src_rect.height(); |
| } else if (options->hasResizeWidth() && options->hasResizeHeight()) { |
| resize_width = options->resizeWidth(); |
| resize_height = options->resizeHeight(); |
| } else if (options->hasResizeWidth() && !options->hasResizeHeight()) { |
| resize_width = options->resizeWidth(); |
| resize_height = |
| ClampTo<unsigned>(ceil(static_cast<float>(options->resizeWidth()) / |
| src_rect.width() * src_rect.height())); |
| } else { |
| resize_height = options->resizeHeight(); |
| resize_width = |
| ClampTo<unsigned>(ceil(static_cast<float>(options->resizeHeight()) / |
| src_rect.height() * src_rect.width())); |
| } |
| return gfx::Size(resize_width, resize_height); |
| } |
| |
| ImageBitmap::ParsedOptions ParseOptions(const ImageBitmapOptions* options, |
| std::optional<gfx::Rect> crop_rect, |
| gfx::Size source_size, |
| ImageOrientation source_orientation) { |
| ImageBitmap::ParsedOptions parsed_options; |
| if (options->imageOrientation() == V8ImageOrientation::Enum::kFlipY) { |
| parsed_options.flip_y = true; |
| parsed_options.orientation_from_image = true; |
| parsed_options.source_orientation = source_orientation; |
| } else { |
| DCHECK(options->imageOrientation() == |
| V8ImageOrientation::Enum::kFromImage || |
| options->imageOrientation() == V8ImageOrientation::Enum::kNone); |
| parsed_options.flip_y = false; |
| parsed_options.orientation_from_image = true; |
| parsed_options.source_orientation = source_orientation; |
| if (base::FeatureList::IsEnabled( |
| features::kCreateImageBitmapOrientationNone) && |
| options->imageOrientation() == V8ImageOrientation::Enum::kNone) { |
| parsed_options.orientation_from_image = false; |
| parsed_options.source_orientation = ImageOrientation(); |
| } |
| } |
| |
| switch (options->premultiplyAlpha().AsEnum()) { |
| case V8PremultiplyAlpha::Enum::kNone: |
| parsed_options.premultiply_alpha = false; |
| break; |
| case V8PremultiplyAlpha::Enum::kDefault: |
| case V8PremultiplyAlpha::Enum::kPremultiply: |
| parsed_options.premultiply_alpha = true; |
| break; |
| } |
| |
| switch (options->colorSpaceConversion().AsEnum()) { |
| case V8ColorSpaceConversion::Enum::kNone: |
| parsed_options.has_color_space_conversion = false; |
| break; |
| case V8ColorSpaceConversion::Enum::kDefault: |
| parsed_options.has_color_space_conversion = true; |
| break; |
| } |
| |
| parsed_options.source_size = |
| parsed_options.source_orientation.UsesWidthAsHeight() |
| ? gfx::TransposeSize(source_size) |
| : source_size; |
| if (!crop_rect) { |
| // TODO(crbug.com/40773069): This should use `parsed_options.source_size`, |
| // because it should be in the same (post-orientation) space. The are |
| // other bugs that depend on this bug, so keep this present, adding |
| // `source_rect` as the future replacement. |
| parsed_options.crop_rect = gfx::Rect(source_size); |
| parsed_options.source_rect = gfx::Rect(parsed_options.source_size); |
| } else { |
| parsed_options.crop_rect = *crop_rect; |
| parsed_options.source_rect = *crop_rect; |
| } |
| // TODO(crbug.com/40773069): The above error propagates into `resize_width` |
| // and `resize_height`. Add `dest_size` as the future replacement. |
| gfx::Size resize = ParseDstSize(options, parsed_options.crop_rect); |
| parsed_options.resize_width = resize.width(); |
| parsed_options.resize_height = resize.height(); |
| parsed_options.dest_size = ParseDstSize(options, parsed_options.source_rect); |
| |
| if (static_cast<int>(parsed_options.resize_width) == |
| parsed_options.crop_rect.width() && |
| static_cast<int>(parsed_options.resize_height) == |
| parsed_options.crop_rect.height()) { |
| parsed_options.should_scale_input = false; |
| return parsed_options; |
| } |
| parsed_options.should_scale_input = true; |
| |
| switch (options->resizeQuality().AsEnum()) { |
| case V8ResizeQuality::Enum::kHigh: |
| parsed_options.resize_quality = cc::PaintFlags::FilterQuality::kHigh; |
| break; |
| case V8ResizeQuality::Enum::kMedium: |
| parsed_options.resize_quality = cc::PaintFlags::FilterQuality::kMedium; |
| break; |
| case V8ResizeQuality::Enum::kLow: |
| parsed_options.resize_quality = cc::PaintFlags::FilterQuality::kLow; |
| break; |
| case V8ResizeQuality::Enum::kPixelated: |
| parsed_options.resize_quality = cc::PaintFlags::FilterQuality::kNone; |
| break; |
| } |
| |
| parsed_options.sampling = cc::PaintFlags::FilterQualityToSkSamplingOptions( |
| parsed_options.resize_quality); |
| return parsed_options; |
| } |
| |
| ImageBitmap::ParsedOptions ParseOptions(const ImageBitmapOptions* options, |
| std::optional<gfx::Rect> crop_rect, |
| scoped_refptr<Image> input) { |
| const auto info = input->PaintImageForCurrentFrame().GetSkImageInfo(); |
| return ParseOptions(options, crop_rect, |
| gfx::Size(info.width(), info.height()), |
| input->Orientation()); |
| } |
| |
| ImageBitmap::ParsedOptions ParseOptions( |
| const ImageBitmapOptions* options, |
| std::optional<gfx::Rect> crop_rect, |
| scoped_refptr<StaticBitmapImage> input) { |
| return ParseOptions(options, crop_rect, input->GetSize(), |
| input->Orientation()); |
| } |
| |
| // The function dstBufferSizeHasOverflow() is being called at the beginning of |
| // each ImageBitmap() constructor, which makes sure that doing |
| // width * height * bytesPerPixel will never overflow unsigned. |
| // This function assumes that the pixel format is N32. |
| bool DstBufferSizeHasOverflow(const ImageBitmap::ParsedOptions& options) { |
| base::CheckedNumeric<unsigned> total_bytes = options.crop_rect.width(); |
| total_bytes *= options.crop_rect.height(); |
| total_bytes *= SkColorTypeBytesPerPixel(kN32_SkColorType); |
| if (!total_bytes.IsValid()) |
| return true; |
| |
| if (!options.should_scale_input) |
| return false; |
| total_bytes = options.resize_width; |
| total_bytes *= options.resize_height; |
| total_bytes *= SkColorTypeBytesPerPixel(kN32_SkColorType); |
| if (!total_bytes.IsValid()) |
| return true; |
| |
| return false; |
| } |
| |
| SkImageInfo GetSkImageInfo(const scoped_refptr<Image>& input) { |
| return input->PaintImageForCurrentFrame().GetSkImageInfo(); |
| } |
| |
| scoped_refptr<StaticBitmapImage> ApplyTransformsFromOptions( |
| scoped_refptr<StaticBitmapImage> source, |
| const ImageBitmap::ParsedOptions& options, |
| bool force_copy = false) { |
| // Early-out for empty transformations. |
| if (options.source_rect.IsEmpty() || options.dest_size.IsEmpty()) { |
| return nullptr; |
| } |
| |
| StaticBitmapImageTransform::Params params; |
| params.force_copy = force_copy; |
| params.flip_y = options.flip_y; |
| params.premultiply_alpha = options.premultiply_alpha; |
| params.reinterpret_as_srgb = !options.has_color_space_conversion; |
| params.orientation_from_image = options.orientation_from_image; |
| params.sampling = options.sampling; |
| params.source_rect = options.source_rect; |
| params.dest_size = options.dest_size; |
| return StaticBitmapImageTransform::Apply(source, params); |
| } |
| |
| scoped_refptr<StaticBitmapImage> MakeBlankImage( |
| const ImageBitmap::ParsedOptions& parsed_options) { |
| SkImageInfo info = SkImageInfo::Make( |
| parsed_options.crop_rect.width(), parsed_options.crop_rect.height(), |
| kN32_SkColorType, kPremul_SkAlphaType, SkColorSpace::MakeSRGB()); |
| if (parsed_options.should_scale_input) { |
| info = |
| info.makeWH(parsed_options.resize_width, parsed_options.resize_height); |
| } |
| sk_sp<SkSurface> surface = SkSurfaces::Raster(info); |
| if (!surface) |
| return nullptr; |
| return UnacceleratedStaticBitmapImage::Create(surface->makeImageSnapshot()); |
| } |
| |
| } // namespace |
| |
| sk_sp<SkImage> ImageBitmap::GetSkImageFromDecoder( |
| std::unique_ptr<ImageDecoder> decoder) { |
| if (!decoder->FrameCount()) |
| return nullptr; |
| ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0); |
| if (!frame || frame->GetStatus() != ImageFrame::kFrameComplete) |
| return nullptr; |
| DCHECK(!frame->Bitmap().isNull() && !frame->Bitmap().empty()); |
| return frame->FinalizePixelsAndGetImage(); |
| } |
| |
| ImageBitmap::ImageBitmap(ImageElementBase* image, |
| std::optional<gfx::Rect> crop_rect, |
| const ImageBitmapOptions* options) { |
| auto* cached = image->CachedImage(); |
| scoped_refptr<Image> input = cached ? cached->GetImage() : Image::NullImage(); |
| DCHECK(!input->IsTextureBacked()); |
| |
| ParsedOptions parsed_options = ParseOptions(options, crop_rect, input); |
| if (DstBufferSizeHasOverflow(parsed_options)) |
| return; |
| |
| cc::PaintImage paint_image = input->PaintImageForCurrentFrame(); |
| if (!paint_image) |
| return; |
| |
| DCHECK(!paint_image.IsTextureBacked()); |
| if (input->IsBitmapImage()) { |
| // A BitmapImage indicates that this is a coded backed image. |
| if (!input->HasData()) |
| return; |
| |
| DCHECK(paint_image.IsLazyGenerated()); |
| const bool data_complete = true; |
| std::unique_ptr<ImageDecoder> decoder(ImageDecoder::Create( |
| input->Data(), data_complete, |
| parsed_options.premultiply_alpha ? ImageDecoder::kAlphaPremultiplied |
| : ImageDecoder::kAlphaNotPremultiplied, |
| paint_image.GetColorType() == kRGBA_F16_SkColorType |
| ? ImageDecoder::kHighBitDepthToHalfFloat |
| : ImageDecoder::kDefaultBitDepth, |
| parsed_options.has_color_space_conversion ? ColorBehavior::kTag |
| : ColorBehavior::kIgnore, |
| cc::AuxImage::kDefault, Platform::GetMaxDecodedImageBytes())); |
| auto skia_image = ImageBitmap::GetSkImageFromDecoder(std::move(decoder)); |
| if (!skia_image) |
| return; |
| |
| paint_image = PaintImageBuilder::WithDefault() |
| .set_id(paint_image.stable_id()) |
| .set_image(std::move(skia_image), |
| paint_image.GetContentIdForFrame(0u)) |
| .TakePaintImage(); |
| } else if (paint_image.IsLazyGenerated()) { |
| // Other Image types can still produce lazy generated images (for example |
| // SVGs). |
| SkBitmap bitmap; |
| SkImageInfo image_info = GetSkImageInfo(input); |
| bitmap.allocPixels(image_info, image_info.minRowBytes()); |
| if (!paint_image.GetSwSkImage()->readPixels(bitmap.pixmap(), 0, 0)) |
| return; |
| |
| paint_image = PaintImageBuilder::WithDefault() |
| .set_id(paint_image.stable_id()) |
| .set_image(SkImages::RasterFromBitmap(bitmap), |
| paint_image.GetContentIdForFrame(0u)) |
| .TakePaintImage(); |
| } |
| |
| auto static_input = UnacceleratedStaticBitmapImage::Create( |
| std::move(paint_image), input->Orientation()); |
| |
| image_ = ApplyTransformsFromOptions(static_input, parsed_options); |
| if (!image_) |
| return; |
| |
| image_->SetOriginClean(!image->WouldTaintOrigin()); |
| UpdateImageBitmapMemoryUsage(); |
| } |
| |
| ImageBitmap::ImageBitmap(HTMLVideoElement* video, |
| std::optional<gfx::Rect> crop_rect, |
| const ImageBitmapOptions* options) { |
| const bool reinterpret_as_srgb = |
| (options->colorSpaceConversion() == V8ColorSpaceConversion::Enum::kNone); |
| auto input = video->CreateStaticBitmapImage(/*size=*/std::nullopt, |
| reinterpret_as_srgb); |
| if (!input) { |
| return; |
| } |
| |
| ParsedOptions parsed_options = ParseOptions(options, crop_rect, input); |
| if (DstBufferSizeHasOverflow(parsed_options)) { |
| return; |
| } |
| |
| image_ = ApplyTransformsFromOptions(input, parsed_options); |
| if (!image_) |
| return; |
| |
| image_->SetOriginClean(!video->WouldTaintOrigin()); |
| UpdateImageBitmapMemoryUsage(); |
| } |
| |
| ImageBitmap::ImageBitmap(HTMLCanvasElement* canvas, |
| std::optional<gfx::Rect> crop_rect, |
| const ImageBitmapOptions* options) { |
| SourceImageStatus status; |
| scoped_refptr<Image> image_input = |
| canvas->GetSourceImageForCanvas(&status, gfx::SizeF()); |
| if (status != kNormalSourceImageStatus) |
| return; |
| scoped_refptr<StaticBitmapImage> input = |
| To<StaticBitmapImage>(image_input.get()); |
| |
| const ParsedOptions parsed_options = ParseOptions(options, crop_rect, input); |
| if (DstBufferSizeHasOverflow(parsed_options)) |
| return; |
| |
| image_ = ApplyTransformsFromOptions(input, parsed_options); |
| if (!image_) |
| return; |
| |
| image_->SetOriginClean(canvas->OriginClean()); |
| UpdateImageBitmapMemoryUsage(); |
| } |
| |
| ImageBitmap::ImageBitmap(OffscreenCanvas* offscreen_canvas, |
| std::optional<gfx::Rect> crop_rect, |
| const ImageBitmapOptions* options) { |
| SourceImageStatus status; |
| scoped_refptr<Image> raw_input = offscreen_canvas->GetSourceImageForCanvas( |
| &status, gfx::SizeF(offscreen_canvas->Size())); |
| scoped_refptr<StaticBitmapImage> input = |
| To<StaticBitmapImage>(raw_input.get()); |
| raw_input = nullptr; |
| |
| if (status != kNormalSourceImageStatus) |
| return; |
| |
| const ParsedOptions parsed_options = ParseOptions(options, crop_rect, input); |
| if (DstBufferSizeHasOverflow(parsed_options)) |
| return; |
| |
| image_ = ApplyTransformsFromOptions(input, parsed_options); |
| if (!image_) |
| return; |
| image_->SetOriginClean(offscreen_canvas->OriginClean()); |
| UpdateImageBitmapMemoryUsage(); |
| } |
| |
| ImageBitmap::ImageBitmap(const SkPixmap& pixmap, |
| bool is_image_bitmap_origin_clean, |
| ImageOrientationEnum image_orientation) { |
| sk_sp<SkImage> raster_copy = SkImages::RasterFromPixmapCopy(pixmap); |
| if (!raster_copy) |
| return; |
| image_ = UnacceleratedStaticBitmapImage::Create(std::move(raster_copy)); |
| if (!image_) |
| return; |
| image_->SetOriginClean(is_image_bitmap_origin_clean); |
| image_->SetOrientation(image_orientation); |
| UpdateImageBitmapMemoryUsage(); |
| } |
| |
| ImageBitmap::ImageBitmap(ImageData* data, |
| std::optional<gfx::Rect> crop_rect, |
| const ImageBitmapOptions* options) { |
| const ParsedOptions parsed_options = ParseOptions( |
| options, crop_rect, data->Size(), ImageOrientationEnum::kOriginTopLeft); |
| if (DstBufferSizeHasOverflow(parsed_options)) |
| return; |
| |
| // Create a StaticBitmapImage that directly references the ImageData pixels. |
| SkPixmap pm = data->GetSkPixmap(); |
| auto sk_data = SkData::MakeWithoutCopy(pm.addr(), pm.computeByteSize()); |
| auto image = StaticBitmapImage::Create(sk_data, pm.info(), gfx::HDRMetadata(), |
| ImageOrientationEnum::kOriginTopLeft); |
| |
| // Force a copy of the data during the transformation (so that we do not |
| // reference ImageData's mutable data). |
| image_ = |
| ApplyTransformsFromOptions(image, parsed_options, /*force_copy=*/true); |
| if (!image_) |
| return; |
| |
| UpdateImageBitmapMemoryUsage(); |
| } |
| |
| ImageBitmap::ImageBitmap(ImageBitmap* bitmap, |
| std::optional<gfx::Rect> crop_rect, |
| const ImageBitmapOptions* options) { |
| scoped_refptr<StaticBitmapImage> input = bitmap->BitmapImage(); |
| if (!input) |
| return; |
| const ParsedOptions parsed_options = ParseOptions(options, crop_rect, input); |
| if (DstBufferSizeHasOverflow(parsed_options)) |
| return; |
| |
| image_ = ApplyTransformsFromOptions(input, parsed_options); |
| if (!image_) |
| return; |
| |
| image_->SetOriginClean(bitmap->OriginClean()); |
| UpdateImageBitmapMemoryUsage(); |
| } |
| |
| ImageBitmap::ImageBitmap(scoped_refptr<StaticBitmapImage> image, |
| std::optional<gfx::Rect> crop_rect, |
| const ImageBitmapOptions* options) { |
| bool origin_clean = image->OriginClean(); |
| const ParsedOptions parsed_options = ParseOptions(options, crop_rect, image); |
| if (DstBufferSizeHasOverflow(parsed_options)) |
| return; |
| |
| image_ = ApplyTransformsFromOptions(image, parsed_options); |
| if (!image_) |
| return; |
| |
| image_->SetOriginClean(origin_clean); |
| UpdateImageBitmapMemoryUsage(); |
| } |
| |
| ImageBitmap::ImageBitmap(scoped_refptr<StaticBitmapImage> image) { |
| image_ = std::move(image); |
| UpdateImageBitmapMemoryUsage(); |
| } |
| |
| scoped_refptr<StaticBitmapImage> ImageBitmap::Transfer() { |
| DCHECK(!IsNeutered()); |
| if (!image_->HasOneRef()) { |
| // For it to be safe to transfer a StaticBitmapImage it must not be |
| // referenced by any other object on this thread. |
| // The first step is to attempt to release other references via |
| // NotifyImageBitmapWillTransfer. |
| const auto content_id = |
| image_->PaintImageForCurrentFrame().GetContentIdForFrame(0); |
| NotifyImageBitmapWillTransfer(content_id); |
| |
| // If will still have other references, the last resort is to make a copy |
| // of the bitmap. This could happen, for example, if another ImageBitmap |
| // or a CanvasPattern object points to the same StaticBitmapImage. |
| // This approach is slow and wateful but it is only to handle extremely |
| // rare edge cases. |
| if (!image_->HasOneRef()) { |
| auto copy = StaticBitmapImageTransform::Clone(image_); |
| if (!copy) { |
| return nullptr; |
| } |
| image_ = std::move(copy); |
| } |
| } |
| |
| DCHECK(image_->HasOneRef()); |
| is_neutered_ = true; |
| image_->Transfer(); |
| UpdateImageBitmapMemoryUsage(); |
| return std::move(image_); |
| } |
| |
| void ImageBitmap::UpdateImageBitmapMemoryUsage() { |
| // TODO(fserb): We should be calling GetCanvasColorParams().BytesPerPixel() |
| // but this is breaking some tests due to the repaint of the image. |
| int bytes_per_pixel = 4; |
| |
| int32_t new_memory_usage = 0; |
| |
| if (!is_neutered_ && image_) { |
| base::CheckedNumeric<int32_t> memory_usage_checked = bytes_per_pixel; |
| memory_usage_checked *= image_->width(); |
| memory_usage_checked *= image_->height(); |
| new_memory_usage = memory_usage_checked.ValueOrDefault( |
| std::numeric_limits<int32_t>::max()); |
| } |
| |
| external_memory_accounter_.Update(v8::Isolate::GetCurrent(), |
| new_memory_usage - memory_usage_); |
| memory_usage_ = new_memory_usage; |
| } |
| |
| ImageBitmap::~ImageBitmap() { |
| external_memory_accounter_.Decrease(v8::Isolate::GetCurrent(), memory_usage_); |
| } |
| |
| void ImageBitmap::ResolvePromiseOnOriginalThread( |
| ScriptPromiseResolver<ImageBitmap>* resolver, |
| bool origin_clean, |
| std::unique_ptr<ParsedOptions> parsed_options, |
| sk_sp<SkImage> skia_image, |
| const ImageOrientationEnum orientation) { |
| if (!skia_image) { |
| resolver->Reject(v8::Null(resolver->GetScriptState()->GetIsolate())); |
| return; |
| } |
| scoped_refptr<StaticBitmapImage> image = |
| UnacceleratedStaticBitmapImage::Create(std::move(skia_image), |
| orientation); |
| DCHECK(IsMainThread()); |
| if (!image) { |
| resolver->Reject(v8::Null(resolver->GetScriptState()->GetIsolate())); |
| return; |
| } |
| ImageBitmap* bitmap = MakeGarbageCollected<ImageBitmap>(image); |
| bitmap->BitmapImage()->SetOriginClean(origin_clean); |
| resolver->Resolve(bitmap); |
| } |
| |
| void ImageBitmap::RasterizeImageOnBackgroundThread( |
| PaintRecord paint_record, |
| const gfx::Rect& dst_rect, |
| scoped_refptr<base::SequencedTaskRunner> task_runner, |
| CrossThreadOnceFunction<void(sk_sp<SkImage>, const ImageOrientationEnum)> |
| callback) { |
| DCHECK(!IsMainThread()); |
| SkImageInfo info = |
| SkImageInfo::MakeN32Premul(dst_rect.width(), dst_rect.height()); |
| SkSurfaceProps props = skia::LegacyDisplayGlobals::GetSkSurfaceProps(); |
| sk_sp<SkSurface> surface = SkSurfaces::Raster(info, &props); |
| sk_sp<SkImage> skia_image; |
| if (surface) { |
| paint_record.Playback(surface->getCanvas()); |
| skia_image = surface->makeImageSnapshot(); |
| } |
| PostCrossThreadTask( |
| *task_runner, FROM_HERE, |
| CrossThreadBindOnce(std::move(callback), std::move(skia_image), |
| ImageOrientationEnum::kDefault)); |
| } |
| |
| ScriptPromise<ImageBitmap> ImageBitmap::CreateAsync( |
| ImageElementBase* image, |
| std::optional<gfx::Rect> crop_rect, |
| ScriptState* script_state, |
| scoped_refptr<base::SequencedTaskRunner> task_runner, |
| mojom::blink::PreferredColorScheme preferred_color_scheme, |
| ExceptionState& exception_state, |
| const ImageBitmapOptions* options) { |
| scoped_refptr<Image> input = image->CachedImage()->GetImage(); |
| DCHECK(input->IsSVGImage()); |
| |
| const ParsedOptions parsed_options = ParseOptions(options, crop_rect, input); |
| if (DstBufferSizeHasOverflow(parsed_options)) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidStateError, |
| "The ImageBitmap could not be allocated."); |
| return EmptyPromise(); |
| } |
| gfx::Rect input_rect(input->Size()); |
| |
| // In the case when |crop_rect| doesn't intersect the source image, we return |
| // a transparent black image, respecting the color_params but ignoring |
| // premultiply_alpha. |
| if (!parsed_options.crop_rect.Intersects(input_rect)) { |
| ImageBitmap* bitmap = |
| MakeGarbageCollected<ImageBitmap>(MakeBlankImage(parsed_options)); |
| if (bitmap->BitmapImage()) { |
| bitmap->BitmapImage()->SetOriginClean(!image->WouldTaintOrigin()); |
| return ToResolvedPromise<ImageBitmap>(script_state, bitmap); |
| } else { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidStateError, |
| "The ImageBitmap could not be allocated."); |
| return EmptyPromise(); |
| } |
| } |
| |
| gfx::Rect draw_src_rect = parsed_options.crop_rect; |
| gfx::Rect draw_dst_rect(0, 0, parsed_options.resize_width, |
| parsed_options.resize_height); |
| PaintRecorder recorder; |
| cc::PaintCanvas* canvas = recorder.beginRecording(); |
| if (parsed_options.flip_y) { |
| canvas->translate(0, draw_dst_rect.height()); |
| canvas->scale(1, -1); |
| } |
| |
| // apply the orientation from EXIF metadata if needed. |
| if (!parsed_options.orientation_from_image && |
| input->Orientation() != ImageOrientationEnum::kOriginTopLeft) { |
| auto affineTransform = input->Orientation().TransformFromDefault( |
| gfx::SizeF(draw_dst_rect.size())); |
| canvas->concat(affineTransform.ToSkM44()); |
| if (input->Orientation().UsesWidthAsHeight()) { |
| draw_dst_rect.set_size(gfx::TransposeSize(draw_dst_rect.size())); |
| } |
| } |
| |
| SVGImageForContainer::Create(To<SVGImage>(*input), |
| gfx::SizeF(input_rect.size()), 1, nullptr, |
| preferred_color_scheme) |
| ->Draw(canvas, cc::PaintFlags(), gfx::RectF(draw_dst_rect), |
| gfx::RectF(draw_src_rect), ImageDrawOptions()); |
| PaintRecord paint_record = recorder.finishRecordingAsPicture(); |
| |
| std::unique_ptr<ParsedOptions> passed_parsed_options = |
| std::make_unique<ParsedOptions>(parsed_options); |
| auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<ImageBitmap>>( |
| script_state, exception_state.GetContext()); |
| auto promise = resolver->Promise(); |
| |
| worker_pool::PostTask( |
| FROM_HERE, |
| CrossThreadBindOnce( |
| &RasterizeImageOnBackgroundThread, std::move(paint_record), |
| draw_dst_rect, std::move(task_runner), |
| CrossThreadBindOnce(&ResolvePromiseOnOriginalThread, |
| MakeUnwrappingCrossThreadHandle(resolver), |
| !image->WouldTaintOrigin(), |
| std::move(passed_parsed_options)))); |
| return promise; |
| } |
| |
| void ImageBitmap::close() { |
| if (!image_ || is_neutered_) |
| return; |
| image_ = nullptr; |
| is_neutered_ = true; |
| UpdateImageBitmapMemoryUsage(); |
| } |
| |
| SkImageInfo ImageBitmap::GetBitmapSkImageInfo() const { |
| return GetSkImageInfo(image_); |
| } |
| |
| Vector<uint8_t> ImageBitmap::CopyBitmapData(const SkImageInfo& info, |
| bool apply_orientation) { |
| return image_->CopyImageData(info, apply_orientation); |
| } |
| |
| unsigned ImageBitmap::width() const { |
| if (!image_) |
| return 0; |
| gfx::Size size = image_->PreferredDisplaySize(); |
| DCHECK_GT(size.width(), 0); |
| return size.width(); |
| } |
| |
| unsigned ImageBitmap::height() const { |
| if (!image_) |
| return 0; |
| gfx::Size size = image_->PreferredDisplaySize(); |
| DCHECK_GT(size.height(), 0); |
| return size.height(); |
| } |
| |
| bool ImageBitmap::IsAccelerated() const { |
| return image_ && image_->IsTextureBacked(); |
| } |
| |
| gfx::Size ImageBitmap::Size() const { |
| if (!image_) |
| return gfx::Size(); |
| DCHECK_GT(image_->width(), 0); |
| DCHECK_GT(image_->height(), 0); |
| return image_->PreferredDisplaySize(); |
| } |
| |
| ImageBitmapSourceStatus ImageBitmap::CheckUsability() const { |
| if (is_neutered_) { |
| return base::unexpected(ImageBitmapSourceError::kInvalid); |
| } |
| return base::ok(); |
| } |
| |
| ScriptPromise<ImageBitmap> ImageBitmap::CreateImageBitmap( |
| ScriptState* script_state, |
| std::optional<gfx::Rect> crop_rect, |
| const ImageBitmapOptions* options, |
| ExceptionState& exception_state) { |
| return ImageBitmapSource::FulfillImageBitmap( |
| script_state, MakeGarbageCollected<ImageBitmap>(this, crop_rect, options), |
| options, exception_state); |
| } |
| |
| scoped_refptr<Image> ImageBitmap::GetSourceImageForCanvas( |
| SourceImageStatus* status, |
| const gfx::SizeF&) { |
| *status = kNormalSourceImageStatus; |
| return image_; |
| } |
| |
| gfx::SizeF ImageBitmap::ElementSize( |
| const gfx::SizeF&, |
| const RespectImageOrientationEnum respect_orientation) const { |
| return gfx::SizeF(image_->Size(respect_orientation)); |
| } |
| |
| } // namespace blink |