blob: 828f244d431ba56cc81cec2420e96eea7ab9d0bc [file]
// 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