| /* |
| * Copyright (c) 2013, Google 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: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * 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. |
| * * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT |
| * OWNER OR 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/modules/canvas/imagebitmap/image_bitmap_factories.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/location.h" |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_image_bitmap_options.h" |
| #include "third_party/blink/renderer/core/dom/dom_exception.h" |
| #include "third_party/blink/renderer/core/execution_context/execution_context.h" |
| #include "third_party/blink/renderer/core/fileapi/blob.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.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/html_image_element.h" |
| #include "third_party/blink/renderer/core/html/media/html_video_element.h" |
| #include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h" |
| #include "third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.h" |
| #include "third_party/blink/renderer/core/svg/svg_image_element.h" |
| #include "third_party/blink/renderer/core/workers/worker_global_scope.h" |
| #include "third_party/blink/renderer/modules/webcodecs/video_frame.h" |
| #include "third_party/blink/renderer/platform/bindings/exception_state.h" |
| #include "third_party/blink/renderer/platform/graphics/unaccelerated_static_bitmap_image.h" |
| #include "third_party/blink/renderer/platform/instrumentation/histogram.h" |
| #include "third_party/blink/renderer/platform/instrumentation/use_counter.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/thread.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/worker_pool.h" |
| #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h" |
| #include "third_party/blink/renderer/platform/wtf/shared_buffer.h" |
| #include "v8/include/v8.h" |
| |
| namespace blink { |
| |
| namespace { |
| // This enum is used in a UMA histogram. |
| enum CreateImageBitmapSource { |
| kCreateImageBitmapSourceBlob = 0, |
| kCreateImageBitmapSourceImageBitmap = 1, |
| kCreateImageBitmapSourceImageData = 2, |
| kCreateImageBitmapSourceHTMLCanvasElement = 3, |
| kCreateImageBitmapSourceHTMLImageElement = 4, |
| kCreateImageBitmapSourceHTMLVideoElement = 5, |
| kCreateImageBitmapSourceOffscreenCanvas = 6, |
| kCreateImageBitmapSourceSVGImageElement = 7, |
| kCreateImageBitmapSourceVideoFrame = 8, |
| kMaxValue = kCreateImageBitmapSourceVideoFrame, |
| }; |
| |
| } // namespace |
| |
| static inline ImageBitmapSource* ToImageBitmapSourceInternal( |
| const ImageBitmapSourceUnion& value, |
| const ImageBitmapOptions* options, |
| bool has_crop_rect) { |
| if (value.IsHTMLVideoElement()) { |
| UMA_HISTOGRAM_ENUMERATION("Blink.Canvas.CreateImageBitmapSource", |
| kCreateImageBitmapSourceHTMLVideoElement); |
| return value.GetAsHTMLVideoElement(); |
| } |
| if (value.IsHTMLImageElement()) { |
| UMA_HISTOGRAM_ENUMERATION("Blink.Canvas.CreateImageBitmapSource", |
| kCreateImageBitmapSourceHTMLImageElement); |
| return value.GetAsHTMLImageElement(); |
| } |
| if (value.IsSVGImageElement()) { |
| UMA_HISTOGRAM_ENUMERATION("Blink.Canvas.CreateImageBitmapSource", |
| kCreateImageBitmapSourceSVGImageElement); |
| return value.GetAsSVGImageElement(); |
| } |
| if (value.IsHTMLCanvasElement()) { |
| UMA_HISTOGRAM_ENUMERATION("Blink.Canvas.CreateImageBitmapSource", |
| kCreateImageBitmapSourceHTMLCanvasElement); |
| return value.GetAsHTMLCanvasElement(); |
| } |
| if (value.IsBlob()) { |
| UMA_HISTOGRAM_ENUMERATION("Blink.Canvas.CreateImageBitmapSource", |
| kCreateImageBitmapSourceBlob); |
| return value.GetAsBlob(); |
| } |
| if (value.IsImageData()) { |
| UMA_HISTOGRAM_ENUMERATION("Blink.Canvas.CreateImageBitmapSource", |
| kCreateImageBitmapSourceImageData); |
| return value.GetAsImageData(); |
| } |
| if (value.IsImageBitmap()) { |
| UMA_HISTOGRAM_ENUMERATION("Blink.Canvas.CreateImageBitmapSource", |
| kCreateImageBitmapSourceImageBitmap); |
| return value.GetAsImageBitmap(); |
| } |
| if (value.IsOffscreenCanvas()) { |
| UMA_HISTOGRAM_ENUMERATION("Blink.Canvas.CreateImageBitmapSource", |
| kCreateImageBitmapSourceOffscreenCanvas); |
| return value.GetAsOffscreenCanvas(); |
| } |
| if (value.IsVideoFrame()) { |
| UMA_HISTOGRAM_ENUMERATION("Blink.Canvas.CreateImageBitmapSource", |
| kCreateImageBitmapSourceVideoFrame); |
| return value.GetAsVideoFrame(); |
| } |
| NOTREACHED(); |
| return nullptr; |
| } |
| |
| ScriptPromise ImageBitmapFactories::CreateImageBitmapFromBlob( |
| ScriptState* script_state, |
| ImageBitmapSource* bitmap_source, |
| base::Optional<IntRect> crop_rect, |
| const ImageBitmapOptions* options) { |
| DCHECK(script_state->ContextIsValid()); |
| ImageBitmapFactories& factory = From(*ExecutionContext::From(script_state)); |
| ImageBitmapLoader* loader = ImageBitmapFactories::ImageBitmapLoader::Create( |
| factory, crop_rect, options, script_state); |
| factory.AddLoader(loader); |
| loader->LoadBlobAsync(static_cast<Blob*>(bitmap_source)); |
| return loader->Promise(); |
| } |
| |
| ScriptPromise ImageBitmapFactories::CreateImageBitmap( |
| ScriptState* script_state, |
| const ImageBitmapSourceUnion& bitmap_source, |
| const ImageBitmapOptions* options, |
| ExceptionState& exception_state) { |
| WebFeature feature = WebFeature::kCreateImageBitmap; |
| UseCounter::Count(ExecutionContext::From(script_state), feature); |
| ImageBitmapSource* bitmap_source_internal = |
| ToImageBitmapSourceInternal(bitmap_source, options, false); |
| if (!bitmap_source_internal) |
| return ScriptPromise(); |
| return CreateImageBitmap(script_state, bitmap_source_internal, |
| base::Optional<IntRect>(), options, exception_state); |
| } |
| |
| ScriptPromise ImageBitmapFactories::CreateImageBitmap( |
| ScriptState* script_state, |
| const ImageBitmapSourceUnion& bitmap_source, |
| int sx, |
| int sy, |
| int sw, |
| int sh, |
| const ImageBitmapOptions* options, |
| ExceptionState& exception_state) { |
| WebFeature feature = WebFeature::kCreateImageBitmap; |
| UseCounter::Count(ExecutionContext::From(script_state), feature); |
| ImageBitmapSource* bitmap_source_internal = |
| ToImageBitmapSourceInternal(bitmap_source, options, true); |
| if (!bitmap_source_internal) |
| return ScriptPromise(); |
| base::Optional<IntRect> crop_rect = IntRect(sx, sy, sw, sh); |
| return CreateImageBitmap(script_state, bitmap_source_internal, crop_rect, |
| options, exception_state); |
| } |
| |
| ScriptPromise ImageBitmapFactories::CreateImageBitmap( |
| ScriptState* script_state, |
| ImageBitmapSource* bitmap_source, |
| base::Optional<IntRect> crop_rect, |
| const ImageBitmapOptions* options, |
| ExceptionState& exception_state) { |
| if (crop_rect && (crop_rect->Width() == 0 || crop_rect->Height() == 0)) { |
| exception_state.ThrowRangeError(String::Format( |
| "The crop rect %s is 0.", crop_rect->Width() ? "height" : "width")); |
| return ScriptPromise(); |
| } |
| |
| if (bitmap_source->IsBlob()) { |
| return CreateImageBitmapFromBlob(script_state, bitmap_source, crop_rect, |
| options); |
| } |
| |
| if (bitmap_source->BitmapSourceSize().Width() == 0 || |
| bitmap_source->BitmapSourceSize().Height() == 0) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidStateError, |
| String::Format( |
| "The source image %s is 0.", |
| bitmap_source->BitmapSourceSize().Width() ? "height" : "width")); |
| return ScriptPromise(); |
| } |
| |
| return bitmap_source->CreateImageBitmap(script_state, crop_rect, options, |
| exception_state); |
| } |
| |
| const char ImageBitmapFactories::kSupplementName[] = "ImageBitmapFactories"; |
| |
| ImageBitmapFactories& ImageBitmapFactories::From(ExecutionContext& context) { |
| ImageBitmapFactories* supplement = |
| Supplement<ExecutionContext>::From<ImageBitmapFactories>(context); |
| if (!supplement) { |
| supplement = MakeGarbageCollected<ImageBitmapFactories>(); |
| Supplement<ExecutionContext>::ProvideTo(context, supplement); |
| } |
| return *supplement; |
| } |
| |
| void ImageBitmapFactories::AddLoader(ImageBitmapLoader* loader) { |
| pending_loaders_.insert(loader); |
| } |
| |
| void ImageBitmapFactories::DidFinishLoading(ImageBitmapLoader* loader) { |
| DCHECK(pending_loaders_.Contains(loader)); |
| pending_loaders_.erase(loader); |
| } |
| |
| void ImageBitmapFactories::Trace(Visitor* visitor) const { |
| visitor->Trace(pending_loaders_); |
| Supplement<ExecutionContext>::Trace(visitor); |
| } |
| |
| ImageBitmapFactories::ImageBitmapLoader::ImageBitmapLoader( |
| ImageBitmapFactories& factory, |
| base::Optional<IntRect> crop_rect, |
| ScriptState* script_state, |
| const ImageBitmapOptions* options) |
| : ExecutionContextLifecycleObserver(ExecutionContext::From(script_state)), |
| loader_(std::make_unique<FileReaderLoader>( |
| FileReaderLoader::kReadAsArrayBuffer, |
| this, |
| GetExecutionContext()->GetTaskRunner(TaskType::kFileReading))), |
| factory_(&factory), |
| resolver_(MakeGarbageCollected<ScriptPromiseResolver>(script_state)), |
| crop_rect_(crop_rect), |
| options_(options) {} |
| |
| void ImageBitmapFactories::ImageBitmapLoader::LoadBlobAsync(Blob* blob) { |
| loader_->Start(blob->GetBlobDataHandle()); |
| } |
| |
| ImageBitmapFactories::ImageBitmapLoader::~ImageBitmapLoader() { |
| DCHECK(!loader_); |
| } |
| |
| void ImageBitmapFactories::ImageBitmapLoader::RejectPromise( |
| ImageBitmapRejectionReason reason) { |
| switch (reason) { |
| case kUndecodableImageBitmapRejectionReason: |
| resolver_->Reject(MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kInvalidStateError, |
| "The source image could not be decoded.")); |
| break; |
| case kAllocationFailureImageBitmapRejectionReason: |
| resolver_->Reject(MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kInvalidStateError, |
| "The ImageBitmap could not be allocated.")); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| loader_.reset(); |
| factory_->DidFinishLoading(this); |
| } |
| |
| void ImageBitmapFactories::ImageBitmapLoader::ContextDestroyed() { |
| if (loader_) |
| factory_->DidFinishLoading(this); |
| loader_.reset(); |
| } |
| |
| void ImageBitmapFactories::ImageBitmapLoader::DidFinishLoading() { |
| auto contents = loader_->TakeContents(); |
| loader_.reset(); |
| if (!contents.IsValid()) { |
| RejectPromise(kAllocationFailureImageBitmapRejectionReason); |
| return; |
| } |
| ScheduleAsyncImageBitmapDecoding(std::move(contents)); |
| } |
| |
| void ImageBitmapFactories::ImageBitmapLoader::DidFail(FileErrorCode) { |
| RejectPromise(kUndecodableImageBitmapRejectionReason); |
| } |
| |
| namespace { |
| void DecodeImageOnDecoderThread( |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner, |
| ArrayBufferContents contents, |
| ImageDecoder::AlphaOption alpha_option, |
| ColorBehavior color_behavior, |
| WTF::CrossThreadOnceFunction< |
| void(sk_sp<SkImage>, const ImageOrientationEnum)> result_callback) { |
| const bool data_complete = true; |
| std::unique_ptr<ImageDecoder> decoder = ImageDecoder::Create( |
| SegmentReader::CreateFromSkData( |
| SkData::MakeWithoutCopy(contents.Data(), contents.DataLength())), |
| data_complete, alpha_option, ImageDecoder::kDefaultBitDepth, |
| color_behavior); |
| sk_sp<SkImage> frame; |
| ImageOrientationEnum orientation = ImageOrientationEnum::kDefault; |
| if (decoder) { |
| orientation = decoder->Orientation().Orientation(); |
| frame = ImageBitmap::GetSkImageFromDecoder(std::move(decoder)); |
| } |
| PostCrossThreadTask(*task_runner, FROM_HERE, |
| CrossThreadBindOnce(std::move(result_callback), |
| std::move(frame), orientation)); |
| } |
| } // namespace |
| |
| void ImageBitmapFactories::ImageBitmapLoader::ScheduleAsyncImageBitmapDecoding( |
| ArrayBufferContents contents) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner = |
| Thread::Current()->GetTaskRunner(); |
| ImageDecoder::AlphaOption alpha_option = |
| options_->premultiplyAlpha() != "none" |
| ? ImageDecoder::AlphaOption::kAlphaPremultiplied |
| : ImageDecoder::AlphaOption::kAlphaNotPremultiplied; |
| ColorBehavior color_behavior = options_->colorSpaceConversion() == "none" |
| ? ColorBehavior::Ignore() |
| : ColorBehavior::Tag(); |
| worker_pool::PostTask( |
| FROM_HERE, |
| CrossThreadBindOnce( |
| DecodeImageOnDecoderThread, std::move(task_runner), |
| std::move(contents), alpha_option, color_behavior, |
| CrossThreadBindOnce(&ImageBitmapFactories::ImageBitmapLoader:: |
| ResolvePromiseOnOriginalThread, |
| WrapCrossThreadWeakPersistent(this)))); |
| } |
| |
| void ImageBitmapFactories::ImageBitmapLoader::ResolvePromiseOnOriginalThread( |
| sk_sp<SkImage> frame, |
| const ImageOrientationEnum orientation) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!frame) { |
| RejectPromise(kUndecodableImageBitmapRejectionReason); |
| return; |
| } |
| DCHECK(frame->width()); |
| DCHECK(frame->height()); |
| scoped_refptr<StaticBitmapImage> image = |
| UnacceleratedStaticBitmapImage::Create(std::move(frame), orientation); |
| |
| image->SetOriginClean(true); |
| auto* image_bitmap = |
| MakeGarbageCollected<ImageBitmap>(image, crop_rect_, options_); |
| if (image_bitmap && image_bitmap->BitmapImage()) { |
| resolver_->Resolve(image_bitmap); |
| } else { |
| RejectPromise(kAllocationFailureImageBitmapRejectionReason); |
| return; |
| } |
| factory_->DidFinishLoading(this); |
| } |
| |
| void ImageBitmapFactories::ImageBitmapLoader::Trace(Visitor* visitor) const { |
| ExecutionContextLifecycleObserver::Trace(visitor); |
| visitor->Trace(factory_); |
| visitor->Trace(resolver_); |
| visitor->Trace(options_); |
| } |
| |
| } // namespace blink |