blob: d5c33506980f4e470d8c3615fd01d1fe05b9a03f [file] [log] [blame]
// Copyright 2016 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/modules/shapedetection/shape_detector.h"
#include <utility>
#include "base/numerics/checked_math.h"
#include "skia/ext/skia_utils_base.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/bindings/modules/v8/v8_union_blob_htmlcanvaselement_htmlimageelement_htmlvideoelement_imagebitmap_imagedata_offscreencanvas_svgimageelement_videoframe.h"
#include "third_party/blink/renderer/core/dom/document.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/frame/local_frame.h"
#include "third_party/blink/renderer/core/geometry/dom_rect.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/loader/resource/image_resource_content.h"
#include "third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.h"
#include "third_party/blink/renderer/platform/graphics/image.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkImageInfo.h"
namespace blink {
ScriptPromise ShapeDetector::detect(ScriptState* script_state,
const V8ImageBitmapSource* image_source,
ExceptionState& exception_state) {
DCHECK(image_source);
CanvasImageSource* canvas_image_source = nullptr;
switch (image_source->GetContentType()) {
case V8ImageBitmapSource::ContentType::kHTMLCanvasElement:
canvas_image_source = image_source->GetAsHTMLCanvasElement();
break;
case V8ImageBitmapSource::ContentType::kHTMLImageElement:
canvas_image_source = image_source->GetAsHTMLImageElement();
break;
case V8ImageBitmapSource::ContentType::kHTMLVideoElement:
canvas_image_source = image_source->GetAsHTMLVideoElement();
break;
case V8ImageBitmapSource::ContentType::kImageBitmap:
canvas_image_source = image_source->GetAsImageBitmap();
break;
case V8ImageBitmapSource::ContentType::kImageData:
// ImageData cannot be tainted by definition.
return DetectShapesOnImageData(
script_state, image_source->GetAsImageData(), exception_state);
case V8ImageBitmapSource::ContentType::kOffscreenCanvas:
canvas_image_source = image_source->GetAsOffscreenCanvas();
break;
case V8ImageBitmapSource::ContentType::kBlob:
case V8ImageBitmapSource::ContentType::kSVGImageElement:
case V8ImageBitmapSource::ContentType::kVideoFrame:
exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
"Unsupported source.");
return ScriptPromise();
}
DCHECK(canvas_image_source);
if (canvas_image_source->IsNeutered()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"The image source is detached.");
return ScriptPromise();
}
if (canvas_image_source->WouldTaintOrigin()) {
exception_state.ThrowSecurityError("Source would taint origin.", "");
return ScriptPromise();
}
if (image_source->IsHTMLImageElement()) {
return DetectShapesOnImageElement(
script_state, image_source->GetAsHTMLImageElement(), exception_state);
}
// TODO(mcasas): Check if |video| is actually playing a MediaStream by using
// HTMLMediaElement::isMediaStreamURL(video->currentSrc().getString()); if
// there is a local WebCam associated, there might be sophisticated ways to
// detect faces on it. Until then, treat as a normal <video> element.
const gfx::SizeF size =
canvas_image_source->ElementSize(gfx::SizeF(), kRespectImageOrientation);
SourceImageStatus source_image_status = kInvalidSourceImageStatus;
scoped_refptr<Image> image =
canvas_image_source->GetSourceImageForCanvas(&source_image_status, size);
if (!image || source_image_status != kNormalSourceImageStatus) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"Invalid element or state.");
return ScriptPromise();
}
if (size.IsEmpty()) {
return ScriptPromise::Cast(script_state,
ToV8Traits<IDLSequence<DOMRect>>::ToV8(
script_state, HeapVector<Member<DOMRect>>())
.ToLocalChecked());
}
// GetSwSkImage() will make a raster copy of PaintImageForCurrentFrame()
// if needed, otherwise returning the original SkImage.
const sk_sp<SkImage> sk_image =
image->PaintImageForCurrentFrame().GetSwSkImage();
SkBitmap sk_bitmap;
SkBitmap n32_bitmap;
if (!sk_image->asLegacyBitmap(&sk_bitmap) ||
!skia::SkBitmapToN32OpaqueOrPremul(sk_bitmap, &n32_bitmap)) {
// TODO(mcasas): retrieve the pixels from elsewhere.
NOTREACHED();
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"Failed to get pixels for current frame.");
return ScriptPromise();
}
return DoDetect(script_state, std::move(n32_bitmap), exception_state);
}
ScriptPromise ShapeDetector::DetectShapesOnImageData(
ScriptState* script_state,
ImageData* image_data,
ExceptionState& exception_state) {
if (image_data->Size().IsZero()) {
return ScriptPromise::Cast(script_state,
ToV8Traits<IDLSequence<DOMRect>>::ToV8(
script_state, HeapVector<Member<DOMRect>>())
.ToLocalChecked());
}
if (image_data->IsBufferBaseDetached()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"The image data has been detached.");
return ScriptPromise();
}
SkPixmap image_data_pixmap = image_data->GetSkPixmap();
SkBitmap sk_bitmap;
if (!sk_bitmap.tryAllocPixels(
image_data_pixmap.info().makeColorType(kN32_SkColorType),
image_data_pixmap.rowBytes())) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"Failed to allocate pixels for current frame.");
return ScriptPromise();
}
if (!sk_bitmap.writePixels(image_data_pixmap, 0, 0)) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"Failed to copy pixels for current frame.");
return ScriptPromise();
}
return DoDetect(script_state, std::move(sk_bitmap), exception_state);
}
ScriptPromise ShapeDetector::DetectShapesOnImageElement(
ScriptState* script_state,
const HTMLImageElement* img,
ExceptionState& exception_state) {
ImageResourceContent* const image_content = img->CachedImage();
if (!image_content || !image_content->IsLoaded() ||
image_content->ErrorOccurred()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"Failed to load or decode HTMLImageElement.");
return ScriptPromise();
}
if (!image_content->HasImage()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"Failed to get image from resource.");
return ScriptPromise();
}
Image* const blink_image = image_content->GetImage();
if (blink_image->Size().IsZero()) {
return ScriptPromise::Cast(script_state,
ToV8Traits<IDLSequence<DOMRect>>::ToV8(
script_state, HeapVector<Member<DOMRect>>())
.ToLocalChecked());
}
// The call to asLegacyBitmap() below forces a readback so getting SwSkImage
// here doesn't readback unnecessarily
const sk_sp<SkImage> sk_image =
blink_image->PaintImageForCurrentFrame().GetSwSkImage();
DCHECK_EQ(img->naturalWidth(), static_cast<unsigned>(sk_image->width()));
DCHECK_EQ(img->naturalHeight(), static_cast<unsigned>(sk_image->height()));
SkBitmap sk_bitmap;
if (!sk_image || !sk_image->asLegacyBitmap(&sk_bitmap)) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"Failed to get image from current frame.");
return ScriptPromise();
}
return DoDetect(script_state, std::move(sk_bitmap), exception_state);
}
} // namespace blink