blob: a0861fa8de0dc26de24867896e1966f26a1d76f0 [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 "modules/canvas/offscreencanvas2d/OffscreenCanvasRenderingContext2D.h"
#include "bindings/modules/v8/offscreen_rendering_context.h"
#include "core/css/OffscreenFontSelector.h"
#include "core/css/parser/CSSParser.h"
#include "core/css/resolver/FontStyleResolver.h"
#include "core/dom/ExecutionContext.h"
#include "core/frame/Settings.h"
#include "core/html/TextMetrics.h"
#include "core/imagebitmap/ImageBitmap.h"
#include "core/workers/WorkerGlobalScope.h"
#include "core/workers/WorkerSettings.h"
#include "platform/graphics/GraphicsTypes.h"
#include "platform/graphics/ImageBuffer.h"
#include "platform/graphics/StaticBitmapImage.h"
#include "platform/graphics/paint/PaintCanvas.h"
#include "platform/text/BidiTextRun.h"
#include "platform/wtf/Assertions.h"
#include "platform/wtf/Time.h"
namespace blink {
OffscreenCanvasRenderingContext2D::~OffscreenCanvasRenderingContext2D() {}
OffscreenCanvasRenderingContext2D::OffscreenCanvasRenderingContext2D(
OffscreenCanvas* canvas,
const CanvasContextCreationAttributes& attrs)
: CanvasRenderingContext(canvas, attrs) {
ExecutionContext* execution_context = canvas->GetTopExecutionContext();
if (execution_context->IsDocument()) {
Settings* settings = ToDocument(execution_context)->GetSettings();
if (settings->GetDisableReadingFromCanvas())
canvas->SetDisableReadingFromCanvasTrue();
return;
}
dirty_rect_for_commit_.setEmpty();
WorkerSettings* worker_settings =
ToWorkerGlobalScope(execution_context)->GetWorkerSettings();
if (worker_settings && worker_settings->DisableReadingFromCanvas())
canvas->SetDisableReadingFromCanvasTrue();
}
void OffscreenCanvasRenderingContext2D::Trace(blink::Visitor* visitor) {
CanvasRenderingContext::Trace(visitor);
BaseRenderingContext2D::Trace(visitor);
}
ScriptPromise OffscreenCanvasRenderingContext2D::commit(
ScriptState* script_state,
ExceptionState& exception_state) {
WebFeature feature = WebFeature::kOffscreenCanvasCommit2D;
UseCounter::Count(ExecutionContext::From(script_state), feature);
SkIRect damage_rect(dirty_rect_for_commit_);
dirty_rect_for_commit_.setEmpty();
return Host()->Commit(TransferToStaticBitmapImage(), damage_rect,
script_state, exception_state);
}
// BaseRenderingContext2D implementation
bool OffscreenCanvasRenderingContext2D::OriginClean() const {
return Host()->OriginClean();
}
void OffscreenCanvasRenderingContext2D::SetOriginTainted() {
Host()->SetOriginTainted();
}
bool OffscreenCanvasRenderingContext2D::WouldTaintOrigin(
CanvasImageSource* source,
ExecutionContext* execution_context) {
if (execution_context->IsWorkerGlobalScope()) {
// We only support passing in ImageBitmap and OffscreenCanvases as
// source images in drawImage() or createPattern() in a
// OffscreenCanvas2d in worker.
DCHECK(source->IsImageBitmap() || source->IsOffscreenCanvas());
}
return CanvasRenderingContext::WouldTaintOrigin(
source, execution_context->GetSecurityOrigin());
}
int OffscreenCanvasRenderingContext2D::Width() const {
return Host()->Size().Width();
}
int OffscreenCanvasRenderingContext2D::Height() const {
return Host()->Size().Height();
}
bool OffscreenCanvasRenderingContext2D::HasImageBuffer() const {
return Host()->GetImageBuffer();
}
void OffscreenCanvasRenderingContext2D::Reset() {
Host()->DiscardImageBuffer();
BaseRenderingContext2D::Reset();
}
ImageBuffer* OffscreenCanvasRenderingContext2D::GetImageBuffer() const {
return const_cast<CanvasRenderingContextHost*>(Host())
->GetOrCreateImageBuffer();
}
scoped_refptr<StaticBitmapImage>
OffscreenCanvasRenderingContext2D::TransferToStaticBitmapImage() {
if (!GetImageBuffer())
return nullptr;
scoped_refptr<StaticBitmapImage> image = GetImageBuffer()->NewImageSnapshot(
kPreferAcceleration, kSnapshotReasonTransferToImageBitmap);
image->SetOriginClean(this->OriginClean());
return image;
}
ImageBitmap* OffscreenCanvasRenderingContext2D::TransferToImageBitmap(
ScriptState* script_state) {
WebFeature feature = WebFeature::kOffscreenCanvasTransferToImageBitmap2D;
UseCounter::Count(ExecutionContext::From(script_state), feature);
scoped_refptr<StaticBitmapImage> image = TransferToStaticBitmapImage();
if (!image)
return nullptr;
if (image->IsTextureBacked()) {
// Before discarding the ImageBuffer, we need to flush pending render ops
// to fully resolve the snapshot.
image->PaintImageForCurrentFrame().GetSkImage()->getTextureHandle(
true); // Flush pending ops.
}
Host()->DiscardImageBuffer(); // "Transfer" means no retained buffer.
return ImageBitmap::Create(std::move(image));
}
scoped_refptr<StaticBitmapImage> OffscreenCanvasRenderingContext2D::GetImage(
AccelerationHint hint,
SnapshotReason reason) const {
if (!GetImageBuffer())
return nullptr;
scoped_refptr<StaticBitmapImage> image =
GetImageBuffer()->NewImageSnapshot(hint, reason);
return image;
}
// TODO(xlai): Handle error cases when, by any reason,
// OffscreenCanvasRenderingContext2D fails to get ImageData.
ImageData* OffscreenCanvasRenderingContext2D::ToImageData(
SnapshotReason reason) {
if (!GetImageBuffer())
return nullptr;
if (Host()->Size().IsEmpty())
return nullptr;
scoped_refptr<StaticBitmapImage> snapshot =
GetImageBuffer()->NewImageSnapshot(kPreferNoAcceleration, reason);
ImageData* image_data = nullptr;
if (snapshot) {
image_data = ImageData::Create(Host()->Size());
SkImageInfo image_info =
SkImageInfo::Make(this->Width(), this->Height(), kRGBA_8888_SkColorType,
kUnpremul_SkAlphaType);
sk_sp<SkImage> sk_image =
snapshot->PaintImageForCurrentFrame().GetSkImage();
bool read_pixels_successful = sk_image->readPixels(
image_info, image_data->data()->Data(), image_info.minRowBytes(), 0, 0);
DCHECK(read_pixels_successful);
if (!read_pixels_successful)
return nullptr;
}
return image_data;
}
void OffscreenCanvasRenderingContext2D::SetOffscreenCanvasGetContextResult(
OffscreenRenderingContext& result) {
result.SetOffscreenCanvasRenderingContext2D(this);
}
bool OffscreenCanvasRenderingContext2D::ParseColorOrCurrentColor(
Color& color,
const String& color_string) const {
return ::blink::ParseColorOrCurrentColor(color, color_string, nullptr);
}
PaintCanvas* OffscreenCanvasRenderingContext2D::DrawingCanvas() const {
ImageBuffer* buffer = GetImageBuffer();
if (!buffer)
return nullptr;
return GetImageBuffer()->Canvas();
}
PaintCanvas* OffscreenCanvasRenderingContext2D::ExistingDrawingCanvas() const {
if (!HasImageBuffer())
return nullptr;
return GetImageBuffer()->Canvas();
}
void OffscreenCanvasRenderingContext2D::DisableDeferral(DisableDeferralReason) {
}
void OffscreenCanvasRenderingContext2D::DidDraw(const SkIRect& dirty_rect) {
dirty_rect_for_commit_.join(dirty_rect);
}
bool OffscreenCanvasRenderingContext2D::StateHasFilter() {
return GetState().HasFilterForOffscreenCanvas(Host()->Size(), this);
}
sk_sp<PaintFilter> OffscreenCanvasRenderingContext2D::StateGetFilter() {
return GetState().GetFilterForOffscreenCanvas(Host()->Size(), this);
}
void OffscreenCanvasRenderingContext2D::ValidateStateStack() const {
#if DCHECK_IS_ON()
if (PaintCanvas* sk_canvas = ExistingDrawingCanvas()) {
DCHECK_EQ(static_cast<size_t>(sk_canvas->getSaveCount()),
state_stack_.size() + 1);
}
#endif
}
bool OffscreenCanvasRenderingContext2D::isContextLost() const {
return false;
}
bool OffscreenCanvasRenderingContext2D::IsPaintable() const {
return GetImageBuffer();
}
CanvasColorSpace OffscreenCanvasRenderingContext2D::ColorSpace() const {
return ColorParams().ColorSpace();
}
String OffscreenCanvasRenderingContext2D::ColorSpaceAsString() const {
return CanvasRenderingContext::ColorSpaceAsString();
}
CanvasPixelFormat OffscreenCanvasRenderingContext2D::PixelFormat() const {
return ColorParams().PixelFormat();
}
bool OffscreenCanvasRenderingContext2D::IsAccelerated() const {
return HasImageBuffer() && GetImageBuffer()->IsAccelerated();
}
String OffscreenCanvasRenderingContext2D::font() const {
if (!GetState().HasRealizedFont())
return kDefaultFont;
StringBuilder serialized_font;
const FontDescription& font_description =
GetState().GetFont().GetFontDescription();
if (font_description.Style() == ItalicSlopeValue())
serialized_font.Append("italic ");
if (font_description.Weight() == BoldWeightValue())
serialized_font.Append("bold ");
if (font_description.VariantCaps() == FontDescription::kSmallCaps)
serialized_font.Append("small-caps ");
serialized_font.AppendNumber(font_description.ComputedPixelSize());
serialized_font.Append("px");
const FontFamily& first_font_family = font_description.Family();
for (const FontFamily* font_family = &first_font_family; font_family;
font_family = font_family->Next()) {
if (font_family != &first_font_family)
serialized_font.Append(',');
// FIXME: We should append family directly to serializedFont rather than
// building a temporary string.
String family = font_family->Family();
if (family.StartsWith("-webkit-"))
family = family.Substring(8);
if (family.Contains(' '))
family = "\"" + family + "\"";
serialized_font.Append(' ');
serialized_font.Append(family);
}
return serialized_font.ToString();
}
void OffscreenCanvasRenderingContext2D::setFont(const String& new_font) {
if (new_font == GetState().UnparsedFont() && GetState().HasRealizedFont())
return;
MutableCSSPropertyValueSet* style =
MutableCSSPropertyValueSet::Create(kHTMLStandardMode);
if (!style)
return;
if (EqualIgnoringASCIICase(new_font, "inherit")) {
return;
}
CSSParser::ParseValue(
style, CSSPropertyFont, new_font, true,
Host()->GetTopExecutionContext()->GetSecureContextMode());
FontDescription desc =
FontStyleResolver::ComputeFont(*style, Host()->GetFontSelector());
Font font = Font(desc);
ModifiableState().SetFont(font, Host()->GetFontSelector());
ModifiableState().SetUnparsedFont(new_font);
}
static inline TextDirection ToTextDirection(
CanvasRenderingContext2DState::Direction direction) {
switch (direction) {
case CanvasRenderingContext2DState::kDirectionInherit:
return TextDirection::kLtr;
case CanvasRenderingContext2DState::kDirectionRTL:
return TextDirection::kRtl;
case CanvasRenderingContext2DState::kDirectionLTR:
return TextDirection::kLtr;
}
NOTREACHED();
return TextDirection::kLtr;
}
String OffscreenCanvasRenderingContext2D::direction() const {
return ToTextDirection(GetState().GetDirection()) == TextDirection::kRtl
? kRtlDirectionString
: kLtrDirectionString;
}
void OffscreenCanvasRenderingContext2D::setDirection(
const String& direction_string) {
CanvasRenderingContext2DState::Direction direction;
if (direction_string == kInheritDirectionString)
direction = CanvasRenderingContext2DState::kDirectionInherit;
else if (direction_string == kRtlDirectionString)
direction = CanvasRenderingContext2DState::kDirectionRTL;
else if (direction_string == kLtrDirectionString)
direction = CanvasRenderingContext2DState::kDirectionLTR;
else
return;
if (GetState().GetDirection() != direction)
ModifiableState().SetDirection(direction);
}
void OffscreenCanvasRenderingContext2D::fillText(const String& text,
double x,
double y) {
DrawTextInternal(text, x, y, CanvasRenderingContext2DState::kFillPaintType);
}
void OffscreenCanvasRenderingContext2D::fillText(const String& text,
double x,
double y,
double max_width) {
DrawTextInternal(text, x, y, CanvasRenderingContext2DState::kFillPaintType,
&max_width);
}
void OffscreenCanvasRenderingContext2D::strokeText(const String& text,
double x,
double y) {
DrawTextInternal(text, x, y, CanvasRenderingContext2DState::kStrokePaintType);
}
void OffscreenCanvasRenderingContext2D::strokeText(const String& text,
double x,
double y,
double max_width) {
DrawTextInternal(text, x, y, CanvasRenderingContext2DState::kStrokePaintType,
&max_width);
}
void OffscreenCanvasRenderingContext2D::DrawTextInternal(
const String& text,
double x,
double y,
CanvasRenderingContext2DState::PaintType paint_type,
double* max_width) {
PaintCanvas* c = DrawingCanvas();
if (!c)
return;
if (!std::isfinite(x) || !std::isfinite(y))
return;
if (max_width && (!std::isfinite(*max_width) || *max_width <= 0))
return;
// Currently, SkPictureImageFilter does not support subpixel text
// anti-aliasing, which is expected when !creationAttributes().alpha(), so we
// need to fall out of display list mode when drawing text to an opaque
// canvas. crbug.com/583809
if (!IsComposited()) {
DisableDeferral(kDisableDeferralReasonSubPixelTextAntiAliasingSupport);
}
const Font& font = AccessFont();
font.GetFontDescription().SetSubpixelAscentDescent(true);
const SimpleFontData* font_data = font.PrimaryFont();
DCHECK(font_data);
if (!font_data)
return;
const FontMetrics& font_metrics = font_data->GetFontMetrics();
// FIXME: Need to turn off font smoothing.
TextDirection direction = ToTextDirection(GetState().GetDirection());
bool is_rtl = direction == TextDirection::kRtl;
TextRun text_run(text, 0, 0, TextRun::kAllowTrailingExpansion, direction,
false);
text_run.SetNormalizeSpace(true);
// Draw the item text at the correct point.
FloatPoint location(x, y + GetFontBaseline(font_metrics));
double font_width = font.Width(text_run);
bool use_max_width = (max_width && *max_width < font_width);
double width = use_max_width ? *max_width : font_width;
TextAlign align = GetState().GetTextAlign();
if (align == kStartTextAlign)
align = is_rtl ? kRightTextAlign : kLeftTextAlign;
else if (align == kEndTextAlign)
align = is_rtl ? kLeftTextAlign : kRightTextAlign;
switch (align) {
case kCenterTextAlign:
location.SetX(location.X() - width / 2);
break;
case kRightTextAlign:
location.SetX(location.X() - width);
break;
default:
break;
}
// The slop built in to this mask rect matches the heuristic used in
// FontCGWin.cpp for GDI text.
TextRunPaintInfo text_run_paint_info(text_run);
text_run_paint_info.bounds =
FloatRect(location.X() - font_metrics.Height() / 2,
location.Y() - font_metrics.Ascent() - font_metrics.LineGap(),
width + font_metrics.Height(), font_metrics.LineSpacing());
if (paint_type == CanvasRenderingContext2DState::kStrokePaintType)
InflateStrokeRect(text_run_paint_info.bounds);
int save_count = c->getSaveCount();
if (use_max_width) {
DrawingCanvas()->save();
DrawingCanvas()->translate(location.X(), location.Y());
// We draw when fontWidth is 0 so compositing operations (eg, a "copy" op)
// still work.
DrawingCanvas()->scale((font_width > 0 ? (width / font_width) : 0), 1);
location = FloatPoint();
}
Draw(
[&font, &text_run_paint_info, &location](
PaintCanvas* c, const PaintFlags* flags) // draw lambda
{
font.DrawBidiText(c, text_run_paint_info, location,
Font::kUseFallbackIfFontNotReady, kCDeviceScaleFactor,
*flags);
},
[](const SkIRect& rect) // overdraw test lambda
{ return false; },
text_run_paint_info.bounds, paint_type);
c->restoreToCount(save_count);
ValidateStateStack();
}
TextMetrics* OffscreenCanvasRenderingContext2D::measureText(
const String& text) {
const Font& font = AccessFont();
TextDirection direction;
if (GetState().GetDirection() ==
CanvasRenderingContext2DState::kDirectionInherit)
direction = DetermineDirectionality(text);
else
direction = ToTextDirection(GetState().GetDirection());
return TextMetrics::Create(font, direction, GetState().GetTextBaseline(),
GetState().GetTextAlign(), text);
}
const Font& OffscreenCanvasRenderingContext2D::AccessFont() {
if (!GetState().HasRealizedFont())
setFont(GetState().UnparsedFont());
return GetState().GetFont();
}
} // namespace blink