blob: 087e12540e70883e867be78acdaa180b785f9b4e [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// 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/single_thread_task_runner.h"
#include "gpu/command_buffer/client/shared_image_interface.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/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/canvas_color_params.h"
#include "third_party/blink/renderer/platform/graphics/canvas_resource_provider.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/skia/skia_utils.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/garbage_collected.h"
#include "third_party/blink/renderer/platform/image-decoders/image_decoder.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/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/SkImageInfo.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/core/SkSwizzle.h"
namespace blink {
constexpr const char* kImageOrientationFlipY = "flipY";
constexpr const char* kImageBitmapOptionNone = "none";
constexpr const char* kImageBitmapOptionDefault = "default";
constexpr const char* kImageBitmapOptionPremultiply = "premultiply";
constexpr const char* kImageBitmapOptionResizeQualityHigh = "high";
constexpr const char* kImageBitmapOptionResizeQualityMedium = "medium";
constexpr const char* kImageBitmapOptionResizeQualityPixelated = "pixelated";
namespace {
ImageBitmap::ParsedOptions ParseOptions(const ImageBitmapOptions* options,
absl::optional<gfx::Rect> crop_rect,
gfx::Size source_size) {
ImageBitmap::ParsedOptions parsed_options;
if (options->imageOrientation() == kImageOrientationFlipY) {
parsed_options.flip_y = true;
} else {
parsed_options.flip_y = false;
DCHECK(options->imageOrientation() == kImageBitmapOptionNone);
}
if (options->premultiplyAlpha() == kImageBitmapOptionNone) {
parsed_options.premultiply_alpha = false;
} else {
parsed_options.premultiply_alpha = true;
DCHECK(options->premultiplyAlpha() == kImageBitmapOptionDefault ||
options->premultiplyAlpha() == kImageBitmapOptionPremultiply);
}
parsed_options.has_color_space_conversion =
(options->colorSpaceConversion() != kImageBitmapOptionNone);
if (options->colorSpaceConversion() != kImageBitmapOptionNone &&
options->colorSpaceConversion() != kImageBitmapOptionDefault) {
NOTREACHED()
<< "Invalid ImageBitmap creation attribute colorSpaceConversion: "
<< IDLEnumAsString(options->colorSpaceConversion());
}
int source_width = source_size.width();
int source_height = source_size.height();
if (!crop_rect) {
parsed_options.crop_rect = gfx::Rect(0, 0, source_width, source_height);
} else {
parsed_options.crop_rect = *crop_rect;
}
if (!options->hasResizeWidth() && !options->hasResizeHeight()) {
parsed_options.resize_width = parsed_options.crop_rect.width();
parsed_options.resize_height = parsed_options.crop_rect.height();
} else if (options->hasResizeWidth() && options->hasResizeHeight()) {
parsed_options.resize_width = options->resizeWidth();
parsed_options.resize_height = options->resizeHeight();
} else if (options->hasResizeWidth() && !options->hasResizeHeight()) {
parsed_options.resize_width = options->resizeWidth();
parsed_options.resize_height = ClampTo<unsigned>(ceil(
static_cast<float>(options->resizeWidth()) /
parsed_options.crop_rect.width() * parsed_options.crop_rect.height()));
} else {
parsed_options.resize_height = options->resizeHeight();
parsed_options.resize_width = ClampTo<unsigned>(ceil(
static_cast<float>(options->resizeHeight()) /
parsed_options.crop_rect.height() * parsed_options.crop_rect.width()));
}
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;
if (options->resizeQuality() == kImageBitmapOptionResizeQualityHigh)
parsed_options.resize_quality = cc::PaintFlags::FilterQuality::kHigh;
else if (options->resizeQuality() == kImageBitmapOptionResizeQualityMedium)
parsed_options.resize_quality = cc::PaintFlags::FilterQuality::kMedium;
else if (options->resizeQuality() == kImageBitmapOptionResizeQualityPixelated)
parsed_options.resize_quality = cc::PaintFlags::FilterQuality::kNone;
else
parsed_options.resize_quality = cc::PaintFlags::FilterQuality::kLow;
return parsed_options;
}
// 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();
}
static inline bool ShouldAvoidPremul(
const ImageBitmap::ParsedOptions& options) {
return options.source_is_unpremul && !options.premultiply_alpha;
}
std::unique_ptr<CanvasResourceProvider> CreateProvider(
base::WeakPtr<WebGraphicsContext3DProviderWrapper> context_provider,
const SkImageInfo& info,
const scoped_refptr<StaticBitmapImage>& source_image,
bool fallback_to_software) {
const cc::PaintFlags::FilterQuality filter_quality =
cc::PaintFlags::FilterQuality::kLow;
if (context_provider) {
uint32_t usage_flags =
context_provider->ContextProvider()
->SharedImageInterface()
->UsageForMailbox(source_image->GetMailboxHolder().mailbox);
auto resource_provider = CanvasResourceProvider::CreateSharedImageProvider(
info, filter_quality, CanvasResourceProvider::ShouldInitialize::kNo,
context_provider, RasterMode::kGPU, source_image->IsOriginTopLeft(),
usage_flags);
if (resource_provider)
return resource_provider;
if (!fallback_to_software)
return nullptr;
}
return CanvasResourceProvider::CreateBitmapProvider(
info, filter_quality, CanvasResourceProvider::ShouldInitialize::kNo);
}
scoped_refptr<StaticBitmapImage> FlipImageVertically(
scoped_refptr<StaticBitmapImage> input,
const ImageBitmap::ParsedOptions& parsed_options) {
SkImageInfo info = GetSkImageInfo(input);
if (info.isEmpty())
return nullptr;
PaintImage paint_image = input->PaintImageForCurrentFrame();
if (ShouldAvoidPremul(parsed_options)) {
// Unpremul code path results in a GPU readback if |input| is texture
// backed since CopyImageData() uses SkImage::readPixels() to extract the
// pixels from SkImage.
sk_sp<SkData> image_pixels = TryAllocateSkData(info.computeMinByteSize());
if (!image_pixels)
return nullptr;
uint8_t* writable_pixels =
static_cast<uint8_t*>(image_pixels->writable_data());
size_t image_row_bytes = static_cast<size_t>(info.minRowBytes64());
bool read_successful =
paint_image.readPixels(info, writable_pixels, image_row_bytes, 0, 0);
DCHECK(read_successful);
for (int i = 0; i < info.height() / 2; i++) {
size_t top_first_element = i * image_row_bytes;
size_t top_last_element = (i + 1) * image_row_bytes;
size_t bottom_first_element = (info.height() - 1 - i) * image_row_bytes;
std::swap_ranges(&writable_pixels[top_first_element],
&writable_pixels[top_last_element],
&writable_pixels[bottom_first_element]);
}
return StaticBitmapImage::Create(std::move(image_pixels), info,
input->CurrentFrameOrientation());
}
// Since we are allowed to premul the input image if needed, we can use Skia
// to flip the image by drawing it on a surface. If the image is premul, we
// can use both accelerated and software surfaces. If the image is unpremul,
// we have to use software surfaces.
bool use_accelerated =
paint_image.IsTextureBacked() && info.alphaType() == kPremul_SkAlphaType;
auto resource_provider = CreateProvider(
use_accelerated ? input->ContextProviderWrapper() : nullptr, info, input,
true /* fallback_to_software */);
if (!resource_provider)
return nullptr;
auto* canvas = resource_provider->Canvas();
canvas->scale(1, -1);
canvas->translate(0, -input->height());
cc::PaintFlags paint;
paint.setBlendMode(SkBlendMode::kSrc);
canvas->drawImage(input->PaintImageForCurrentFrame(), 0, 0,
SkSamplingOptions(), &paint);
return resource_provider->Snapshot(input->CurrentFrameOrientation());
}
scoped_refptr<StaticBitmapImage> ScaleImage(
scoped_refptr<StaticBitmapImage>&& image,
const ImageBitmap::ParsedOptions& parsed_options) {
auto src_image_info = image->PaintImageForCurrentFrame().GetSkImageInfo();
auto image_info = GetSkImageInfo(image).makeWH(parsed_options.resize_width,
parsed_options.resize_height);
// Try to avoid GPU read back by drawing accelerated premul image on an
// accelerated surface.
if (!ShouldAvoidPremul(parsed_options) && image->IsTextureBacked() &&
src_image_info.alphaType() == kPremul_SkAlphaType) {
auto resource_provider =
CreateProvider(image->ContextProviderWrapper(), image_info, image,
false /* fallback_to_software */);
if (resource_provider) {
SkSamplingOptions sampling =
cc::PaintFlags::FilterQualityToSkSamplingOptions(
parsed_options.resize_quality);
cc::PaintFlags paint;
paint.setBlendMode(SkBlendMode::kSrc);
resource_provider->Canvas()->drawImageRect(
image->PaintImageForCurrentFrame(),
SkRect::MakeWH(src_image_info.width(), src_image_info.height()),
SkRect::MakeWH(parsed_options.resize_width,
parsed_options.resize_height),
sampling, &paint, SkCanvas::kStrict_SrcRectConstraint);
return resource_provider->Snapshot(image->CurrentFrameOrientation());
}
}
// Avoid sRGB transfer function by setting the color space to nullptr.
if (image_info.colorSpace()->isSRGB())
image_info = image_info.makeColorSpace(nullptr);
sk_sp<SkData> image_pixels =
TryAllocateSkData(image_info.computeMinByteSize());
if (!image_pixels) {
return nullptr;
}
SkPixmap resized_pixmap(image_info, image_pixels->data(),
image_info.minRowBytes());
auto sk_image = image->PaintImageForCurrentFrame().GetSwSkImage();
if (!sk_image)
return nullptr;
sk_image->scalePixels(resized_pixmap,
cc::PaintFlags::FilterQualityToSkSamplingOptions(
parsed_options.resize_quality));
// Tag the resized Pixmap with the correct color space.
resized_pixmap.setColorSpace(GetSkImageInfo(image).refColorSpace());
auto resized_sk_image =
SkImage::MakeRasterData(resized_pixmap.info(), std::move(image_pixels),
resized_pixmap.rowBytes());
if (!resized_sk_image)
return nullptr;
return UnacceleratedStaticBitmapImage::Create(
resized_sk_image, image->CurrentFrameOrientation());
}
scoped_refptr<StaticBitmapImage> ApplyColorSpaceConversion(
scoped_refptr<StaticBitmapImage>&& image,
ImageBitmap::ParsedOptions& options) {
sk_sp<SkColorSpace> color_space = SkColorSpace::MakeSRGB();
SkColorType color_type =
image->IsTextureBacked() ? kRGBA_8888_SkColorType : kN32_SkColorType;
SkImageInfo src_image_info =
image->PaintImageForCurrentFrame().GetSkImageInfo();
if (src_image_info.isEmpty())
return nullptr;
// This will always convert to 8-bit sRGB.
return image->ConvertToColorSpace(color_space, color_type);
}
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 = SkSurface::MakeRaster(info);
if (!surface)
return nullptr;
return UnacceleratedStaticBitmapImage::Create(surface->makeImageSnapshot());
}
void SwapYOrientation(scoped_refptr<StaticBitmapImage> image) {
switch (image->CurrentFrameOrientation().Orientation()) {
case ImageOrientationEnum::kOriginTopLeft:
image->SetOrientation(ImageOrientationEnum::kOriginBottomLeft);
break;
case ImageOrientationEnum::kOriginBottomLeft:
image->SetOrientation(ImageOrientationEnum::kOriginTopLeft);
break;
case ImageOrientationEnum::kOriginTopRight:
image->SetOrientation(ImageOrientationEnum::kOriginBottomRight);
break;
case ImageOrientationEnum::kOriginBottomRight:
image->SetOrientation(ImageOrientationEnum::kOriginTopRight);
break;
case ImageOrientationEnum::kOriginLeftTop:
image->SetOrientation(ImageOrientationEnum::kOriginLeftBottom);
break;
case ImageOrientationEnum::kOriginRightTop:
image->SetOrientation(ImageOrientationEnum::kOriginRightBottom);
break;
case ImageOrientationEnum::kOriginRightBottom:
image->SetOrientation(ImageOrientationEnum::kOriginRightTop);
break;
case ImageOrientationEnum::kOriginLeftBottom:
image->SetOrientation(ImageOrientationEnum::kOriginLeftTop);
break;
}
}
static scoped_refptr<StaticBitmapImage> CropImageAndApplyColorSpaceConversion(
scoped_refptr<StaticBitmapImage>&& image,
ImageBitmap::ParsedOptions& parsed_options) {
DCHECK(image);
DCHECK(!image->HasData());
gfx::Rect img_rect(image->width(), image->height());
const gfx::Rect& src_rect = parsed_options.crop_rect;
const gfx::Rect intersect_rect = IntersectRects(img_rect, src_rect);
// If cropRect doesn't intersect the source image, return a transparent black
// image.
if (intersect_rect.IsEmpty())
return MakeBlankImage(parsed_options);
scoped_refptr<StaticBitmapImage> result = image;
if (src_rect != img_rect) {
auto paint_image = result->PaintImageForCurrentFrame();
auto image_info = paint_image.GetSkImageInfo().makeWH(src_rect.width(),
src_rect.height());
auto resource_provider =
CreateProvider(image->ContextProviderWrapper(), image_info, result,
true /* fallback_to_software*/);
if (!resource_provider)
return nullptr;
cc::PaintCanvas* canvas = resource_provider->Canvas();
cc::PaintFlags paint;
paint.setBlendMode(SkBlendMode::kSrc);
canvas->drawImageRect(paint_image,
SkRect::MakeXYWH(src_rect.x(), src_rect.y(),
src_rect.width(), src_rect.height()),
SkRect::MakeWH(src_rect.width(), src_rect.height()),
SkSamplingOptions(), &paint,
SkCanvas::kStrict_SrcRectConstraint);
result = resource_provider->Snapshot(image->CurrentFrameOrientation());
}
// down-scaling has higher priority than other tasks, up-scaling has lower.
bool down_scaling = parsed_options.should_scale_input &&
(static_cast<uint64_t>(parsed_options.resize_width) *
parsed_options.resize_height <
result->Size().Area64());
bool up_scaling = parsed_options.should_scale_input && !down_scaling;
// resize if down-scaling
if (down_scaling) {
result = ScaleImage(std::move(result), parsed_options);
if (!result)
return nullptr;
}
// flip if needed
if (parsed_options.flip_y) {
result = FlipImageVertically(std::move(result), parsed_options);
if (!result)
return nullptr;
}
// color convert if needed
if (parsed_options.has_color_space_conversion) {
result = ApplyColorSpaceConversion(std::move(result), parsed_options);
if (!result)
return nullptr;
}
// premultiply / unpremultiply if needed
result = GetImageWithAlphaDisposition(std::move(result),
parsed_options.premultiply_alpha
? kPremultiplyAlpha
: kUnpremultiplyAlpha);
if (!result)
return nullptr;
// resize if up-scaling
if (up_scaling) {
result = ScaleImage(std::move(result), parsed_options);
if (!result)
return nullptr;
}
return result;
}
} // 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,
absl::optional<gfx::Rect> crop_rect,
const ImageBitmapOptions* options) {
scoped_refptr<Image> input = image->CachedImage()->GetImage();
DCHECK(!input->IsTextureBacked());
ParsedOptions parsed_options =
ParseOptions(options, crop_rect, image->BitmapSourceSize());
parsed_options.source_is_unpremul =
(input->PaintImageForCurrentFrame().GetAlphaType() ==
kUnpremul_SkAlphaType);
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,
ImageDecoder::kDefaultBitDepth,
parsed_options.has_color_space_conversion ? ColorBehavior::Tag()
: ColorBehavior::Ignore()));
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(SkImage::MakeFromBitmap(bitmap),
paint_image.GetContentIdForFrame(0u))
.TakePaintImage();
}
auto static_input = UnacceleratedStaticBitmapImage::Create(
std::move(paint_image), input->CurrentFrameOrientation());
image_ = CropImageAndApplyColorSpaceConversion(std::move(static_input),
parsed_options);
if (!image_)
return;
image_->SetOriginClean(!image->WouldTaintOrigin());
UpdateImageBitmapMemoryUsage();
}
ImageBitmap::ImageBitmap(HTMLVideoElement* video,
absl::optional<gfx::Rect> crop_rect,
const ImageBitmapOptions* options) {
ParsedOptions parsed_options =
ParseOptions(options, crop_rect, video->BitmapSourceSize());
if (DstBufferSizeHasOverflow(parsed_options))
return;
// TODO(crbug.com/1181329): ImageBitmap resize test case failed when
// quality equals to "low" and "medium". Need further investigate to
// enable gpu backed imageBitmap with resize options.
const bool allow_accelerated_images =
!options->hasResizeWidth() && !options->hasResizeHeight();
auto input = video->CreateStaticBitmapImage(allow_accelerated_images);
if (!input)
return;
image_ =
CropImageAndApplyColorSpaceConversion(std::move(input), parsed_options);
if (!image_)
return;
image_->SetOriginClean(!video->WouldTaintOrigin());
UpdateImageBitmapMemoryUsage();
}
ImageBitmap::ImageBitmap(HTMLCanvasElement* canvas,
absl::optional<gfx::Rect> crop_rect,
const ImageBitmapOptions* options) {
SourceImageStatus status;
scoped_refptr<Image> image_input =
canvas->GetSourceImageForCanvas(&status, gfx::SizeF());
if (status != kNormalSourceImageStatus)
return;
DCHECK(IsA<StaticBitmapImage>(image_input.get()));
scoped_refptr<StaticBitmapImage> input =
static_cast<StaticBitmapImage*>(image_input.get());
ParsedOptions parsed_options = ParseOptions(
options, crop_rect, gfx::Size(input->width(), input->height()));
if (DstBufferSizeHasOverflow(parsed_options))
return;
image_ =
CropImageAndApplyColorSpaceConversion(std::move(input), parsed_options);
if (!image_)
return;
image_->SetOriginClean(canvas->OriginClean());
UpdateImageBitmapMemoryUsage();
}
ImageBitmap::ImageBitmap(OffscreenCanvas* offscreen_canvas,
absl::optional<gfx::Rect> crop_rect,
const ImageBitmapOptions* options) {
SourceImageStatus status;
scoped_refptr<Image> raw_input = offscreen_canvas->GetSourceImageForCanvas(
&status, gfx::SizeF(offscreen_canvas->Size()));
DCHECK(IsA<StaticBitmapImage>(raw_input.get()));
scoped_refptr<StaticBitmapImage> input =
static_cast<StaticBitmapImage*>(raw_input.get());
raw_input = nullptr;
if (status != kNormalSourceImageStatus)
return;
ParsedOptions parsed_options = ParseOptions(
options, crop_rect, gfx::Size(input->width(), input->height()));
if (DstBufferSizeHasOverflow(parsed_options))
return;
image_ =
CropImageAndApplyColorSpaceConversion(std::move(input), parsed_options);
if (!image_)
return;
image_->SetOriginClean(offscreen_canvas->OriginClean());
UpdateImageBitmapMemoryUsage();
}
ImageBitmap::ImageBitmap(const SkPixmap& pixmap,
bool is_image_bitmap_origin_clean) {
sk_sp<SkImage> raster_copy = SkImage::MakeRasterCopy(pixmap);
if (!raster_copy)
return;
image_ = UnacceleratedStaticBitmapImage::Create(std::move(raster_copy));
if (!image_)
return;
image_->SetOriginClean(is_image_bitmap_origin_clean);
UpdateImageBitmapMemoryUsage();
}
ImageBitmap::ImageBitmap(ImageData* data,
absl::optional<gfx::Rect> crop_rect,
const ImageBitmapOptions* options) {
ParsedOptions parsed_options =
ParseOptions(options, crop_rect, data->BitmapSourceSize());
// ImageData is always unpremul.
parsed_options.source_is_unpremul = true;
if (DstBufferSizeHasOverflow(parsed_options))
return;
const gfx::Rect& src_rect = parsed_options.crop_rect;
const gfx::Rect data_src_rect(data->Size());
const gfx::Rect intersect_rect =
crop_rect ? IntersectRects(src_rect, data_src_rect) : data_src_rect;
// If cropRect doesn't intersect the source image, return a transparent black
// image.
if (intersect_rect.IsEmpty()) {
image_ = MakeBlankImage(parsed_options);
return;
}
// Copy / color convert the pixels
SkImageInfo info = SkImageInfo::Make(
src_rect.width(), src_rect.height(), kN32_SkColorType,
parsed_options.premultiply_alpha ? kPremul_SkAlphaType
: kUnpremul_SkAlphaType,
SkColorSpace::MakeSRGB());
size_t image_pixels_size = info.computeMinByteSize();
if (SkImageInfo::ByteSizeOverflowed(image_pixels_size))
return;
sk_sp<SkData> image_pixels = TryAllocateSkData(image_pixels_size);
if (!image_pixels)
return;
if (!data->GetSkPixmap().readPixels(info, image_pixels->writable_data(),
info.minRowBytes(), src_rect.x(),
src_rect.y())) {
return;
}
// Create Image object
image_ = StaticBitmapImage::Create(std::move(image_pixels), info);
if (!image_)
return;
// down-scaling has higher priority than other tasks, up-scaling has lower.
bool down_scaling = parsed_options.should_scale_input &&
(static_cast<uint64_t>(parsed_options.resize_width) *
parsed_options.resize_height <
image_->Size().Area64());
bool up_scaling = parsed_options.should_scale_input && !down_scaling;
// resize if down-scaling
if (down_scaling)
image_ = ScaleImage(std::move(image_), parsed_options);
if (!image_)
return;
// flip if needed
if (parsed_options.flip_y) {
// Since |accelerated_static_bitmap_image| always has preMultiplied alpha
// and some images should avoid premul alpha, simply flip the image
// vertically can't solve these cases. So swap orientation Y of |image_| for
// these simple cases and flip the image in place with corrected alpha
// values for other cases.
if (IsPremultiplied() && !ShouldAvoidPremul(parsed_options)) {
SwapYOrientation(image_);
} else {
image_ = FlipImageVertically(std::move(image_), parsed_options);
}
if (!image_)
return;
}
// resize if up-scaling
if (up_scaling)
image_ = ScaleImage(std::move(image_), parsed_options);
UpdateImageBitmapMemoryUsage();
}
ImageBitmap::ImageBitmap(ImageBitmap* bitmap,
absl::optional<gfx::Rect> crop_rect,
const ImageBitmapOptions* options) {
scoped_refptr<StaticBitmapImage> input = bitmap->BitmapImage();
if (!input)
return;
ParsedOptions parsed_options =
ParseOptions(options, crop_rect, input->Size());
parsed_options.source_is_unpremul =
(input->PaintImageForCurrentFrame().GetAlphaType() ==
kUnpremul_SkAlphaType);
if (DstBufferSizeHasOverflow(parsed_options))
return;
image_ =
CropImageAndApplyColorSpaceConversion(std::move(input), parsed_options);
if (!image_)
return;
image_->SetOriginClean(bitmap->OriginClean());
UpdateImageBitmapMemoryUsage();
}
ImageBitmap::ImageBitmap(scoped_refptr<StaticBitmapImage> image,
absl::optional<gfx::Rect> crop_rect,
const ImageBitmapOptions* options) {
bool origin_clean = image->OriginClean();
ParsedOptions parsed_options =
ParseOptions(options, crop_rect, image->Size());
parsed_options.source_is_unpremul =
(image->PaintImageForCurrentFrame().GetAlphaType() ==
kUnpremul_SkAlphaType);
if (DstBufferSizeHasOverflow(parsed_options))
return;
image_ =
CropImageAndApplyColorSpaceConversion(std::move(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
// NotifyWillTransfer
const auto content_id =
image_->PaintImageForCurrentFrame().GetContentIdForFrame(0);
CanvasResourceProvider::NotifyWillTransfer(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()) {
SkImageInfo info = GetSkImageInfo(image_);
if (info.isEmpty())
return nullptr;
PaintImage paint_image = image_->PaintImageForCurrentFrame();
bool use_accelerated = paint_image.IsTextureBacked() &&
info.alphaType() == kPremul_SkAlphaType;
auto resource_provider = CreateProvider(
use_accelerated ? image_->ContextProviderWrapper() : nullptr, info,
image_, true /* fallback_to_software */);
if (!resource_provider)
return nullptr;
auto* canvas = resource_provider->Canvas();
cc::PaintFlags paint;
paint.setBlendMode(SkBlendMode::kSrc);
canvas->drawImage(image_->PaintImageForCurrentFrame(), 0, 0,
SkSamplingOptions(), &paint);
image_ = resource_provider->Snapshot(image_->CurrentFrameOrientation());
}
}
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());
}
v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory(
new_memory_usage - memory_usage_);
memory_usage_ = new_memory_usage;
}
ImageBitmap::~ImageBitmap() {
v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory(
-memory_usage_);
}
void ImageBitmap::ResolvePromiseOnOriginalThread(
ScriptPromiseResolver* resolver,
bool origin_clean,
std::unique_ptr<ParsedOptions> parsed_options,
sk_sp<SkImage> skia_image,
const ImageOrientationEnum orientation) {
if (!skia_image) {
resolver->Reject(
ScriptValue(resolver->GetScriptState()->GetIsolate(),
v8::Null(resolver->GetScriptState()->GetIsolate())));
return;
}
scoped_refptr<StaticBitmapImage> image =
UnacceleratedStaticBitmapImage::Create(std::move(skia_image),
orientation);
DCHECK(IsMainThread());
if (!parsed_options->premultiply_alpha) {
image = GetImageWithAlphaDisposition(std::move(image), kUnpremultiplyAlpha);
}
if (!image) {
resolver->Reject(
ScriptValue(resolver->GetScriptState()->GetIsolate(),
v8::Null(resolver->GetScriptState()->GetIsolate())));
return;
}
image = ApplyColorSpaceConversion(std::move(image), *(parsed_options.get()));
if (!image) {
resolver->Reject(
ScriptValue(resolver->GetScriptState()->GetIsolate(),
v8::Null(resolver->GetScriptState()->GetIsolate())));
return;
}
ImageBitmap* bitmap = MakeGarbageCollected<ImageBitmap>(image);
bitmap->BitmapImage()->SetOriginClean(origin_clean);
resolver->Resolve(bitmap);
}
void ImageBitmap::RasterizeImageOnBackgroundThread(
sk_sp<PaintRecord> paint_record,
const gfx::Rect& dst_rect,
scoped_refptr<base::SequencedTaskRunner> task_runner,
WTF::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 = SkSurface::MakeRaster(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::CreateAsync(
ImageElementBase* image,
absl::optional<gfx::Rect> crop_rect,
ScriptState* script_state,
mojom::blink::PreferredColorScheme preferred_color_scheme,
ExceptionState& exception_state,
const ImageBitmapOptions* options) {
ParsedOptions parsed_options =
ParseOptions(options, crop_rect, image->BitmapSourceSize());
if (DstBufferSizeHasOverflow(parsed_options)) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"The ImageBitmap could not be allocated.");
return ScriptPromise();
}
scoped_refptr<Image> input = image->CachedImage()->GetImage();
DCHECK(input->IsSVGImage());
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 ScriptPromise::Cast(
script_state,
ToV8Traits<ImageBitmap>::ToV8(script_state, bitmap).ToLocalChecked());
} else {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"The ImageBitmap could not be allocated.");
return ScriptPromise();
}
}
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(gfx::RectToSkRect(draw_src_rect));
if (parsed_options.flip_y) {
canvas->translate(0, draw_dst_rect.height());
canvas->scale(1, -1);
}
SVGImageForContainer::Create(To<SVGImage>(input.get()),
gfx::SizeF(input_rect.size()), 1, NullURL(),
preferred_color_scheme)
->Draw(canvas, cc::PaintFlags(), gfx::RectF(draw_dst_rect),
gfx::RectF(draw_src_rect), ImageDrawOptions());
sk_sp<PaintRecord> paint_record = recorder.finishRecordingAsPicture();
std::unique_ptr<ParsedOptions> passed_parsed_options =
std::make_unique<ParsedOptions>(parsed_options);
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
worker_pool::PostTask(
FROM_HERE, CrossThreadBindOnce(
&RasterizeImageOnBackgroundThread, std::move(paint_record),
draw_dst_rect, Thread::MainThread()->GetTaskRunner(),
CrossThreadBindOnce(&ResolvePromiseOnOriginalThread,
WrapCrossThreadPersistent(resolver),
!image->WouldTaintOrigin(),
std::move(passed_parsed_options))));
return promise;
}
void ImageBitmap::close() {
if (!image_ || is_neutered_)
return;
image_ = nullptr;
is_neutered_ = true;
UpdateImageBitmapMemoryUsage();
}
// static
ImageBitmap* ImageBitmap::Take(ScriptPromiseResolver*, sk_sp<SkImage> image) {
return MakeGarbageCollected<ImageBitmap>(
UnacceleratedStaticBitmapImage::Create(std::move(image)));
}
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();
}
ScriptPromise ImageBitmap::CreateImageBitmap(
ScriptState* script_state,
absl::optional<gfx::Rect> crop_rect,
const ImageBitmapOptions* options,
ExceptionState& exception_state) {
return ImageBitmapSource::FulfillImageBitmap(
script_state, MakeGarbageCollected<ImageBitmap>(this, crop_rect, options),
exception_state);
}
scoped_refptr<Image> ImageBitmap::GetSourceImageForCanvas(
SourceImageStatus* status,
const gfx::SizeF&,
const AlphaDisposition alpha_disposition) {
*status = kNormalSourceImageStatus;
if (!image_)
return nullptr;
scoped_refptr<StaticBitmapImage> image = image_;
// If the alpha_disposition is already correct, or the image is opaque, this
// is a no-op.
return GetImageWithAlphaDisposition(std::move(image), alpha_disposition);
}
gfx::SizeF ImageBitmap::ElementSize(
const gfx::SizeF&,
const RespectImageOrientationEnum respect_orientation) const {
return gfx::SizeF(image_->Size(respect_orientation));
}
} // namespace blink