blob: e672efd8addad979ac23764d2adfe3e762d6a867 [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/canvas/canvas2d/base_rendering_context_2d.h"
#include <algorithm>
#include <cmath>
#include <memory>
#include "base/metrics/histogram_functions.h"
#include "base/numerics/checked_math.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/renderer/core/css/cssom/css_url_image_value.h"
#include "third_party/blink/renderer/core/css/parser/css_parser.h"
#include "third_party/blink/renderer/core/html/canvas/text_metrics.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/svg/svg_image_element.h"
#include "third_party/blink/renderer/modules/canvas/canvas2d/canvas_pattern.h"
#include "third_party/blink/renderer/modules/canvas/canvas2d/path_2d.h"
#include "third_party/blink/renderer/platform/geometry/float_quad.h"
#include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h"
#include "third_party/blink/renderer/platform/graphics/stroke_data.h"
namespace blink {
const char BaseRenderingContext2D::kDefaultFont[] = "10px sans-serif";
const char BaseRenderingContext2D::kInheritDirectionString[] = "inherit";
const char BaseRenderingContext2D::kRtlDirectionString[] = "rtl";
const char BaseRenderingContext2D::kLtrDirectionString[] = "ltr";
const double BaseRenderingContext2D::kCDeviceScaleFactor = 1.0;
BaseRenderingContext2D::BaseRenderingContext2D()
: clip_antialiasing_(kNotAntiAliased), origin_tainted_by_content_(false) {
state_stack_.push_back(MakeGarbageCollected<CanvasRenderingContext2DState>());
}
BaseRenderingContext2D::~BaseRenderingContext2D() = default;
CanvasRenderingContext2DState& BaseRenderingContext2D::ModifiableState() {
RealizeSaves();
return *state_stack_.back();
}
void BaseRenderingContext2D::RealizeSaves() {
ValidateStateStack();
if (GetState().HasUnrealizedSaves()) {
DCHECK_GE(state_stack_.size(), 1u);
// Reduce the current state's unrealized count by one now,
// to reflect the fact we are saving one state.
state_stack_.back()->Restore();
state_stack_.push_back(MakeGarbageCollected<CanvasRenderingContext2DState>(
GetState(), CanvasRenderingContext2DState::kDontCopyClipList));
// Set the new state's unrealized count to 0, because it has no outstanding
// saves.
// We need to do this explicitly because the copy constructor and operator=
// used by the Vector operations copy the unrealized count from the previous
// state (in turn necessary to support correct resizing and unwinding of the
// stack).
state_stack_.back()->ResetUnrealizedSaveCount();
cc::PaintCanvas* canvas = DrawingCanvas();
if (canvas)
canvas->save();
ValidateStateStack();
}
}
void BaseRenderingContext2D::save() {
state_stack_.back()->Save();
}
void BaseRenderingContext2D::restore() {
ValidateStateStack();
if (GetState().HasUnrealizedSaves()) {
// We never realized the save, so just record that it was unnecessary.
state_stack_.back()->Restore();
return;
}
DCHECK_GE(state_stack_.size(), 1u);
if (state_stack_.size() <= 1)
return;
// Verify that the current state's transform is invertible.
if (GetState().IsTransformInvertible())
path_.Transform(GetState().Transform());
state_stack_.pop_back();
state_stack_.back()->ClearResolvedFilter();
if (GetState().IsTransformInvertible())
path_.Transform(GetState().Transform().Inverse());
cc::PaintCanvas* c = DrawingCanvas();
if (c)
c->restore();
ValidateStateStack();
}
void BaseRenderingContext2D::RestoreMatrixClipStack(cc::PaintCanvas* c) const {
if (!c)
return;
HeapVector<Member<CanvasRenderingContext2DState>>::const_iterator curr_state;
DCHECK(state_stack_.begin() < state_stack_.end());
for (curr_state = state_stack_.begin(); curr_state < state_stack_.end();
curr_state++) {
c->setMatrix(SkMatrix::I());
if (curr_state->Get()) {
curr_state->Get()->PlaybackClips(c);
c->setMatrix(AffineTransformToSkMatrix(curr_state->Get()->Transform()));
}
c->save();
}
c->restore();
ValidateStateStack();
}
void BaseRenderingContext2D::UnwindStateStack() {
if (size_t stack_size = state_stack_.size()) {
if (cc::PaintCanvas* sk_canvas = ExistingDrawingCanvas()) {
while (--stack_size)
sk_canvas->restore();
}
}
}
void BaseRenderingContext2D::Reset() {
ValidateStateStack();
UnwindStateStack();
state_stack_.resize(1);
state_stack_.front() = MakeGarbageCollected<CanvasRenderingContext2DState>();
path_.Clear();
if (cc::PaintCanvas* c = ExistingDrawingCanvas()) {
// The canvas should always have an initial/unbalanced save frame, which
// we use to reset the top level matrix and clip here.
DCHECK_EQ(c->getSaveCount(), 2);
c->restore();
c->save();
DCHECK(c->getTotalMatrix().isIdentity());
#if DCHECK_IS_ON()
SkIRect clip_bounds;
DCHECK(c->getDeviceClipBounds(&clip_bounds));
DCHECK(clip_bounds == c->imageInfo().bounds());
#endif
}
ValidateStateStack();
origin_tainted_by_content_ = false;
}
static inline void ConvertCanvasStyleToUnionType(
CanvasStyle* style,
StringOrCanvasGradientOrCanvasPattern& return_value) {
if (CanvasGradient* gradient = style->GetCanvasGradient()) {
return_value.SetCanvasGradient(gradient);
return;
}
if (CanvasPattern* pattern = style->GetCanvasPattern()) {
return_value.SetCanvasPattern(pattern);
return;
}
return_value.SetString(style->GetColor());
}
void BaseRenderingContext2D::strokeStyle(
StringOrCanvasGradientOrCanvasPattern& return_value) const {
ConvertCanvasStyleToUnionType(GetState().StrokeStyle(), return_value);
}
void BaseRenderingContext2D::setStrokeStyle(
const StringOrCanvasGradientOrCanvasPattern& style) {
DCHECK(!style.IsNull());
String color_string;
CanvasStyle* canvas_style = nullptr;
if (style.IsString()) {
color_string = style.GetAsString();
if (color_string == GetState().UnparsedStrokeColor())
return;
Color parsed_color = 0;
if (!ParseColorOrCurrentColor(parsed_color, color_string))
return;
if (GetState().StrokeStyle()->IsEquivalentRGBA(parsed_color.Rgb())) {
ModifiableState().SetUnparsedStrokeColor(color_string);
return;
}
canvas_style = CanvasStyle::CreateFromRGBA(parsed_color.Rgb());
} else if (style.IsCanvasGradient()) {
canvas_style = CanvasStyle::CreateFromGradient(style.GetAsCanvasGradient());
} else if (style.IsCanvasPattern()) {
CanvasPattern* canvas_pattern = style.GetAsCanvasPattern();
if (!origin_tainted_by_content_ && !canvas_pattern->OriginClean())
SetOriginTaintedByContent();
canvas_style = CanvasStyle::CreateFromPattern(canvas_pattern);
}
DCHECK(canvas_style);
ModifiableState().SetStrokeStyle(canvas_style);
ModifiableState().SetUnparsedStrokeColor(color_string);
ModifiableState().ClearResolvedFilter();
}
void BaseRenderingContext2D::fillStyle(
StringOrCanvasGradientOrCanvasPattern& return_value) const {
ConvertCanvasStyleToUnionType(GetState().FillStyle(), return_value);
}
void BaseRenderingContext2D::setFillStyle(
const StringOrCanvasGradientOrCanvasPattern& style) {
DCHECK(!style.IsNull());
ValidateStateStack();
String color_string;
CanvasStyle* canvas_style = nullptr;
if (style.IsString()) {
color_string = style.GetAsString();
if (color_string == GetState().UnparsedFillColor())
return;
Color parsed_color = 0;
if (!ParseColorOrCurrentColor(parsed_color, color_string))
return;
if (GetState().FillStyle()->IsEquivalentRGBA(parsed_color.Rgb())) {
ModifiableState().SetUnparsedFillColor(color_string);
return;
}
canvas_style = CanvasStyle::CreateFromRGBA(parsed_color.Rgb());
} else if (style.IsCanvasGradient()) {
canvas_style = CanvasStyle::CreateFromGradient(style.GetAsCanvasGradient());
} else if (style.IsCanvasPattern()) {
CanvasPattern* canvas_pattern = style.GetAsCanvasPattern();
if (!origin_tainted_by_content_ && !canvas_pattern->OriginClean()) {
SetOriginTaintedByContent();
}
if (canvas_pattern->GetPattern()->IsTextureBacked())
DisableDeferral(kDisableDeferralReasonUsingTextureBackedPattern);
canvas_style = CanvasStyle::CreateFromPattern(canvas_pattern);
}
DCHECK(canvas_style);
ModifiableState().SetFillStyle(canvas_style);
ModifiableState().SetUnparsedFillColor(color_string);
ModifiableState().ClearResolvedFilter();
}
double BaseRenderingContext2D::lineWidth() const {
return GetState().LineWidth();
}
void BaseRenderingContext2D::setLineWidth(double width) {
if (!std::isfinite(width) || width <= 0)
return;
if (GetState().LineWidth() == width)
return;
ModifiableState().SetLineWidth(clampTo<float>(width));
}
String BaseRenderingContext2D::lineCap() const {
return LineCapName(GetState().GetLineCap());
}
void BaseRenderingContext2D::setLineCap(const String& s) {
LineCap cap;
if (!ParseLineCap(s, cap))
return;
if (GetState().GetLineCap() == cap)
return;
ModifiableState().SetLineCap(cap);
}
String BaseRenderingContext2D::lineJoin() const {
return LineJoinName(GetState().GetLineJoin());
}
void BaseRenderingContext2D::setLineJoin(const String& s) {
LineJoin join;
if (!ParseLineJoin(s, join))
return;
if (GetState().GetLineJoin() == join)
return;
ModifiableState().SetLineJoin(join);
}
double BaseRenderingContext2D::miterLimit() const {
return GetState().MiterLimit();
}
void BaseRenderingContext2D::setMiterLimit(double limit) {
if (!std::isfinite(limit) || limit <= 0)
return;
if (GetState().MiterLimit() == limit)
return;
ModifiableState().SetMiterLimit(clampTo<float>(limit));
}
double BaseRenderingContext2D::shadowOffsetX() const {
return GetState().ShadowOffset().Width();
}
void BaseRenderingContext2D::setShadowOffsetX(double x) {
if (!std::isfinite(x))
return;
if (GetState().ShadowOffset().Width() == x)
return;
ModifiableState().SetShadowOffsetX(clampTo<float>(x));
}
double BaseRenderingContext2D::shadowOffsetY() const {
return GetState().ShadowOffset().Height();
}
void BaseRenderingContext2D::setShadowOffsetY(double y) {
if (!std::isfinite(y))
return;
if (GetState().ShadowOffset().Height() == y)
return;
ModifiableState().SetShadowOffsetY(clampTo<float>(y));
}
double BaseRenderingContext2D::shadowBlur() const {
return GetState().ShadowBlur();
}
void BaseRenderingContext2D::setShadowBlur(double blur) {
if (!std::isfinite(blur) || blur < 0)
return;
if (GetState().ShadowBlur() == blur)
return;
ModifiableState().SetShadowBlur(clampTo<float>(blur));
}
String BaseRenderingContext2D::shadowColor() const {
return Color(GetState().ShadowColor()).Serialized();
}
void BaseRenderingContext2D::setShadowColor(const String& color_string) {
Color color;
if (!ParseColorOrCurrentColor(color, color_string))
return;
if (GetState().ShadowColor() == color)
return;
ModifiableState().SetShadowColor(color.Rgb());
}
const Vector<double>& BaseRenderingContext2D::getLineDash() const {
return GetState().LineDash();
}
static bool LineDashSequenceIsValid(const Vector<double>& dash) {
return std::all_of(dash.begin(), dash.end(),
[](double d) { return std::isfinite(d) && d >= 0; });
}
void BaseRenderingContext2D::setLineDash(const Vector<double>& dash) {
if (!LineDashSequenceIsValid(dash))
return;
ModifiableState().SetLineDash(dash);
}
double BaseRenderingContext2D::lineDashOffset() const {
return GetState().LineDashOffset();
}
void BaseRenderingContext2D::setLineDashOffset(double offset) {
if (!std::isfinite(offset) || GetState().LineDashOffset() == offset)
return;
ModifiableState().SetLineDashOffset(clampTo<float>(offset));
}
double BaseRenderingContext2D::globalAlpha() const {
return GetState().GlobalAlpha();
}
void BaseRenderingContext2D::setGlobalAlpha(double alpha) {
if (!(alpha >= 0 && alpha <= 1))
return;
if (GetState().GlobalAlpha() == alpha)
return;
ModifiableState().SetGlobalAlpha(alpha);
}
String BaseRenderingContext2D::globalCompositeOperation() const {
return CompositeOperatorName(
CompositeOperatorFromSkBlendMode(GetState().GlobalComposite()),
BlendModeFromSkBlendMode(GetState().GlobalComposite()));
}
void BaseRenderingContext2D::setGlobalCompositeOperation(
const String& operation) {
CompositeOperator op = kCompositeSourceOver;
BlendMode blend_mode = BlendMode::kNormal;
if (!ParseCompositeAndBlendMode(operation, op, blend_mode))
return;
SkBlendMode sk_blend_mode = WebCoreCompositeToSkiaComposite(op, blend_mode);
if (GetState().GlobalComposite() == sk_blend_mode)
return;
ModifiableState().SetGlobalComposite(sk_blend_mode);
}
String BaseRenderingContext2D::filter() const {
return GetState().UnparsedFilter();
}
void BaseRenderingContext2D::setFilter(
const ExecutionContext* execution_context,
const String& filter_string) {
if (filter_string == GetState().UnparsedFilter())
return;
const CSSValue* filter_value = CSSParser::ParseSingleValue(
CSSPropertyID::kFilter, filter_string,
MakeGarbageCollected<CSSParserContext>(
kHTMLStandardMode, execution_context->GetSecureContextMode()));
if (!filter_value || filter_value->IsCSSWideKeyword())
return;
ModifiableState().SetUnparsedFilter(filter_string);
ModifiableState().SetFilter(filter_value);
SnapshotStateForFilter();
}
void BaseRenderingContext2D::scale(double sx, double sy) {
cc::PaintCanvas* c = DrawingCanvas();
if (!c)
return;
if (!std::isfinite(sx) || !std::isfinite(sy))
return;
AffineTransform new_transform = GetState().Transform();
float fsx = clampTo<float>(sx);
float fsy = clampTo<float>(sy);
new_transform.ScaleNonUniform(fsx, fsy);
if (GetState().Transform() == new_transform)
return;
ModifiableState().SetTransform(new_transform);
if (!GetState().IsTransformInvertible())
return;
c->scale(fsx, fsy);
path_.Transform(AffineTransform().ScaleNonUniform(1.0 / fsx, 1.0 / fsy));
}
void BaseRenderingContext2D::rotate(double angle_in_radians) {
cc::PaintCanvas* c = DrawingCanvas();
if (!c)
return;
if (!std::isfinite(angle_in_radians))
return;
AffineTransform new_transform = GetState().Transform();
new_transform.RotateRadians(angle_in_radians);
if (GetState().Transform() == new_transform)
return;
ModifiableState().SetTransform(new_transform);
if (!GetState().IsTransformInvertible())
return;
c->rotate(clampTo<float>(angle_in_radians * (180.0 / kPiFloat)));
path_.Transform(AffineTransform().RotateRadians(-angle_in_radians));
}
void BaseRenderingContext2D::translate(double tx, double ty) {
cc::PaintCanvas* c = DrawingCanvas();
if (!c)
return;
if (!GetState().IsTransformInvertible())
return;
if (!std::isfinite(tx) || !std::isfinite(ty))
return;
AffineTransform new_transform = GetState().Transform();
// clamp to float to avoid float cast overflow when used as SkScalar
float ftx = clampTo<float>(tx);
float fty = clampTo<float>(ty);
new_transform.Translate(ftx, fty);
if (GetState().Transform() == new_transform)
return;
ModifiableState().SetTransform(new_transform);
if (!GetState().IsTransformInvertible())
return;
c->translate(ftx, fty);
path_.Transform(AffineTransform().Translate(-ftx, -fty));
}
void BaseRenderingContext2D::transform(double m11,
double m12,
double m21,
double m22,
double dx,
double dy) {
cc::PaintCanvas* c = DrawingCanvas();
if (!c)
return;
if (!std::isfinite(m11) || !std::isfinite(m21) || !std::isfinite(dx) ||
!std::isfinite(m12) || !std::isfinite(m22) || !std::isfinite(dy))
return;
// clamp to float to avoid float cast overflow when used as SkScalar
float fm11 = clampTo<float>(m11);
float fm12 = clampTo<float>(m12);
float fm21 = clampTo<float>(m21);
float fm22 = clampTo<float>(m22);
float fdx = clampTo<float>(dx);
float fdy = clampTo<float>(dy);
AffineTransform transform(fm11, fm12, fm21, fm22, fdx, fdy);
AffineTransform new_transform = GetState().Transform() * transform;
if (GetState().Transform() == new_transform)
return;
ModifiableState().SetTransform(new_transform);
if (!GetState().IsTransformInvertible())
return;
c->concat(AffineTransformToSkMatrix(transform));
path_.Transform(transform.Inverse());
}
void BaseRenderingContext2D::resetTransform() {
cc::PaintCanvas* c = DrawingCanvas();
if (!c)
return;
AffineTransform ctm = GetState().Transform();
bool invertible_ctm = GetState().IsTransformInvertible();
// It is possible that CTM is identity while CTM is not invertible.
// When CTM becomes non-invertible, realizeSaves() can make CTM identity.
if (ctm.IsIdentity() && invertible_ctm)
return;
// resetTransform() resolves the non-invertible CTM state.
ModifiableState().ResetTransform();
c->setMatrix(AffineTransformToSkMatrix(AffineTransform()));
if (invertible_ctm)
path_.Transform(ctm);
// When else, do nothing because all transform methods didn't update m_path
// when CTM became non-invertible.
// It means that resetTransform() restores m_path just before CTM became
// non-invertible.
}
void BaseRenderingContext2D::setTransform(double m11,
double m12,
double m21,
double m22,
double dx,
double dy) {
if (!std::isfinite(m11) || !std::isfinite(m21) || !std::isfinite(dx) ||
!std::isfinite(m12) || !std::isfinite(m22) || !std::isfinite(dy))
return;
resetTransform();
// clamp to float to avoid float cast overflow when used as SkScalar
float fm11 = clampTo<float>(m11);
float fm12 = clampTo<float>(m12);
float fm21 = clampTo<float>(m21);
float fm22 = clampTo<float>(m22);
float fdx = clampTo<float>(dx);
float fdy = clampTo<float>(dy);
transform(fm11, fm12, fm21, fm22, fdx, fdy);
}
void BaseRenderingContext2D::setTransform(DOMMatrix2DInit* transform,
ExceptionState& exception_state) {
DOMMatrixReadOnly* m =
DOMMatrixReadOnly::fromMatrix2D(transform, exception_state);
if (!m)
return;
setTransform(m->m11(), m->m12(), m->m21(), m->m22(), m->m41(), m->m42());
}
DOMMatrix* BaseRenderingContext2D::getTransform() {
const AffineTransform& t = GetState().Transform();
DOMMatrix* m = DOMMatrix::Create();
m->setA(t.A());
m->setB(t.B());
m->setC(t.C());
m->setD(t.D());
m->setE(t.E());
m->setF(t.F());
return m;
}
void BaseRenderingContext2D::beginPath() {
path_.Clear();
}
static bool ValidateRectForCanvas(double& x,
double& y,
double& width,
double& height) {
if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(width) ||
!std::isfinite(height))
return false;
if (!width && !height)
return false;
if (width < 0) {
width = -width;
x -= width;
}
if (height < 0) {
height = -height;
y -= height;
}
return true;
}
bool BaseRenderingContext2D::IsFullCanvasCompositeMode(SkBlendMode op) {
// See 4.8.11.1.3 Compositing
// CompositeSourceAtop and CompositeDestinationOut are not listed here as the
// platforms already implement the specification's behavior.
return op == SkBlendMode::kSrcIn || op == SkBlendMode::kSrcOut ||
op == SkBlendMode::kDstIn || op == SkBlendMode::kDstATop;
}
void BaseRenderingContext2D::DrawPathInternal(
const Path& path,
CanvasRenderingContext2DState::PaintType paint_type,
SkPath::FillType fill_type) {
if (path.IsEmpty())
return;
SkPath sk_path = path.GetSkPath();
FloatRect bounds = path.BoundingRect();
if (std::isnan(bounds.X()) || std::isnan(bounds.Y()) ||
std::isnan(bounds.Width()) || std::isnan(bounds.Height()))
return;
sk_path.setFillType(fill_type);
if (paint_type == CanvasRenderingContext2DState::kStrokePaintType)
InflateStrokeRect(bounds);
if (!DrawingCanvas())
return;
Draw([&sk_path](cc::PaintCanvas* c, const PaintFlags* flags) // draw lambda
{ c->drawPath(sk_path, *flags); },
[](const SkIRect& rect) // overdraw test lambda
{ return false; },
bounds, paint_type);
}
static SkPath::FillType ParseWinding(const String& winding_rule_string) {
if (winding_rule_string == "nonzero")
return SkPath::kWinding_FillType;
if (winding_rule_string == "evenodd")
return SkPath::kEvenOdd_FillType;
NOTREACHED();
return SkPath::kEvenOdd_FillType;
}
void BaseRenderingContext2D::fill(const String& winding_rule_string) {
DrawPathInternal(path_, CanvasRenderingContext2DState::kFillPaintType,
ParseWinding(winding_rule_string));
}
void BaseRenderingContext2D::fill(Path2D* dom_path,
const String& winding_rule_string) {
DrawPathInternal(dom_path->GetPath(),
CanvasRenderingContext2DState::kFillPaintType,
ParseWinding(winding_rule_string));
}
void BaseRenderingContext2D::stroke() {
DrawPathInternal(path_, CanvasRenderingContext2DState::kStrokePaintType);
}
void BaseRenderingContext2D::stroke(Path2D* dom_path) {
DrawPathInternal(dom_path->GetPath(),
CanvasRenderingContext2DState::kStrokePaintType);
}
void BaseRenderingContext2D::fillRect(double x,
double y,
double width,
double height) {
if (!ValidateRectForCanvas(x, y, width, height))
return;
if (!DrawingCanvas())
return;
// clamp to float to avoid float cast overflow when used as SkScalar
float fx = clampTo<float>(x);
float fy = clampTo<float>(y);
float fwidth = clampTo<float>(width);
float fheight = clampTo<float>(height);
// We are assuming that if the pattern is not accelerated and the current
// canvas is accelerated, we will not be able to hold the pattern into the
// canvas, probably because it does not fit. That's why we disable the
// acceleration to be sure that it will work
if (IsAccelerated() && GetState().HasPattern() &&
!GetState().PatternIsAccelerated())
DisableAcceleration();
SkRect rect = SkRect::MakeXYWH(fx, fy, fwidth, fheight);
Draw([&rect](cc::PaintCanvas* c, const PaintFlags* flags) // draw lambda
{ c->drawRect(rect, *flags); },
[&rect, this](const SkIRect& clip_bounds) // overdraw test lambda
{ return RectContainsTransformedRect(rect, clip_bounds); },
rect, CanvasRenderingContext2DState::kFillPaintType);
}
static void StrokeRectOnCanvas(const FloatRect& rect,
cc::PaintCanvas* canvas,
const PaintFlags* flags) {
DCHECK_EQ(flags->getStyle(), PaintFlags::kStroke_Style);
if ((rect.Width() > 0) != (rect.Height() > 0)) {
// When stroking, we must skip the zero-dimension segments
SkPath path;
path.moveTo(rect.X(), rect.Y());
path.lineTo(rect.MaxX(), rect.MaxY());
path.close();
canvas->drawPath(path, *flags);
return;
}
canvas->drawRect(rect, *flags);
}
void BaseRenderingContext2D::strokeRect(double x,
double y,
double width,
double height) {
if (!ValidateRectForCanvas(x, y, width, height))
return;
if (!DrawingCanvas())
return;
// clamp to float to avoid float cast overflow when used as SkScalar
float fx = clampTo<float>(x);
float fy = clampTo<float>(y);
float fwidth = clampTo<float>(width);
float fheight = clampTo<float>(height);
SkRect rect = SkRect::MakeXYWH(fx, fy, fwidth, fheight);
FloatRect bounds = rect;
InflateStrokeRect(bounds);
Draw([&rect](cc::PaintCanvas* c, const PaintFlags* flags) // draw lambda
{ StrokeRectOnCanvas(rect, c, flags); },
[](const SkIRect& clip_bounds) // overdraw test lambda
{ return false; },
bounds, CanvasRenderingContext2DState::kStrokePaintType);
}
void BaseRenderingContext2D::ClipInternal(const Path& path,
const String& winding_rule_string) {
cc::PaintCanvas* c = DrawingCanvas();
if (!c) {
return;
}
if (!GetState().IsTransformInvertible()) {
return;
}
SkPath sk_path = path.GetSkPath();
sk_path.setFillType(ParseWinding(winding_rule_string));
ModifiableState().ClipPath(sk_path, clip_antialiasing_);
c->clipPath(sk_path, SkClipOp::kIntersect,
clip_antialiasing_ == kAntiAliased);
}
void BaseRenderingContext2D::clip(const String& winding_rule_string) {
ClipInternal(path_, winding_rule_string);
}
void BaseRenderingContext2D::clip(Path2D* dom_path,
const String& winding_rule_string) {
ClipInternal(dom_path->GetPath(), winding_rule_string);
}
bool BaseRenderingContext2D::isPointInPath(const double x,
const double y,
const String& winding_rule_string) {
return IsPointInPathInternal(path_, x, y, winding_rule_string);
}
bool BaseRenderingContext2D::isPointInPath(Path2D* dom_path,
const double x,
const double y,
const String& winding_rule_string) {
return IsPointInPathInternal(dom_path->GetPath(), x, y, winding_rule_string);
}
bool BaseRenderingContext2D::IsPointInPathInternal(
const Path& path,
const double x,
const double y,
const String& winding_rule_string) {
cc::PaintCanvas* c = DrawingCanvas();
if (!c)
return false;
if (!GetState().IsTransformInvertible())
return false;
if (!std::isfinite(x) || !std::isfinite(y))
return false;
FloatPoint point(clampTo<float>(x), clampTo<float>(y));
AffineTransform ctm = GetState().Transform();
FloatPoint transformed_point = ctm.Inverse().MapPoint(point);
return path.Contains(transformed_point,
SkFillTypeToWindRule(ParseWinding(winding_rule_string)));
}
bool BaseRenderingContext2D::isPointInStroke(const double x, const double y) {
return IsPointInStrokeInternal(path_, x, y);
}
bool BaseRenderingContext2D::isPointInStroke(Path2D* dom_path,
const double x,
const double y) {
return IsPointInStrokeInternal(dom_path->GetPath(), x, y);
}
bool BaseRenderingContext2D::IsPointInStrokeInternal(const Path& path,
const double x,
const double y) {
cc::PaintCanvas* c = DrawingCanvas();
if (!c)
return false;
if (!GetState().IsTransformInvertible())
return false;
if (!std::isfinite(x) || !std::isfinite(y))
return false;
FloatPoint point(clampTo<float>(x), clampTo<float>(y));
AffineTransform ctm = GetState().Transform();
FloatPoint transformed_point = ctm.Inverse().MapPoint(point);
StrokeData stroke_data;
stroke_data.SetThickness(GetState().LineWidth());
stroke_data.SetLineCap(GetState().GetLineCap());
stroke_data.SetLineJoin(GetState().GetLineJoin());
stroke_data.SetMiterLimit(GetState().MiterLimit());
Vector<float> line_dash(GetState().LineDash().size());
std::copy(GetState().LineDash().begin(), GetState().LineDash().end(),
line_dash.begin());
stroke_data.SetLineDash(line_dash, GetState().LineDashOffset());
return path.StrokeContains(transformed_point, stroke_data);
}
void BaseRenderingContext2D::clearRect(double x,
double y,
double width,
double height) {
usage_counters_.num_clear_rect_calls++;
if (!ValidateRectForCanvas(x, y, width, height))
return;
cc::PaintCanvas* c = DrawingCanvas();
if (!c)
return;
if (!GetState().IsTransformInvertible())
return;
SkIRect clip_bounds;
if (!c->getDeviceClipBounds(&clip_bounds))
return;
PaintFlags clear_flags;
clear_flags.setBlendMode(SkBlendMode::kClear);
clear_flags.setStyle(PaintFlags::kFill_Style);
// clamp to float to avoid float cast overflow when used as SkScalar
float fx = clampTo<float>(x);
float fy = clampTo<float>(y);
float fwidth = clampTo<float>(width);
float fheight = clampTo<float>(height);
FloatRect rect(fx, fy, fwidth, fheight);
if (RectContainsTransformedRect(rect, clip_bounds)) {
CheckOverdraw(rect, &clear_flags, CanvasRenderingContext2DState::kNoImage,
kClipFill);
if (DrawingCanvas())
DrawingCanvas()->drawRect(rect, clear_flags);
DidDraw(clip_bounds);
} else {
SkIRect dirty_rect;
if (ComputeDirtyRect(rect, clip_bounds, &dirty_rect)) {
c->drawRect(rect, clear_flags);
DidDraw(dirty_rect);
}
}
}
static inline FloatRect NormalizeRect(const FloatRect& rect) {
return FloatRect(std::min(rect.X(), rect.MaxX()),
std::min(rect.Y(), rect.MaxY()),
std::max(rect.Width(), -rect.Width()),
std::max(rect.Height(), -rect.Height()));
}
static inline void ClipRectsToImageRect(const FloatRect& image_rect,
FloatRect* src_rect,
FloatRect* dst_rect) {
if (image_rect.Contains(*src_rect))
return;
// Compute the src to dst transform
FloatSize scale(dst_rect->Size().Width() / src_rect->Size().Width(),
dst_rect->Size().Height() / src_rect->Size().Height());
FloatPoint scaled_src_location = src_rect->Location();
scaled_src_location.Scale(scale.Width(), scale.Height());
FloatSize offset = dst_rect->Location() - scaled_src_location;
src_rect->Intersect(image_rect);
// To clip the destination rectangle in the same proportion, transform the
// clipped src rect
*dst_rect = *src_rect;
dst_rect->Scale(scale.Width(), scale.Height());
dst_rect->Move(offset);
}
static inline CanvasImageSource* ToImageSourceInternal(
const CanvasImageSourceUnion& value,
ExceptionState& exception_state) {
if (value.IsCSSImageValue()) {
return value.GetAsCSSImageValue();
}
if (value.IsHTMLImageElement())
return value.GetAsHTMLImageElement();
if (value.IsHTMLVideoElement()) {
HTMLVideoElement* video = value.GetAsHTMLVideoElement();
video->VideoWillBeDrawnToCanvas();
return video;
}
if (value.IsSVGImageElement())
return value.GetAsSVGImageElement();
if (value.IsHTMLCanvasElement()) {
if (static_cast<HTMLCanvasElement*>(value.GetAsHTMLCanvasElement())
->Size()
.IsEmpty()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
String::Format("The image argument is a canvas element with a width "
"or height of 0."));
return nullptr;
}
return value.GetAsHTMLCanvasElement();
}
if (value.IsImageBitmap()) {
if (static_cast<ImageBitmap*>(value.GetAsImageBitmap())->IsNeutered()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
String::Format("The image source is detached"));
return nullptr;
}
return value.GetAsImageBitmap();
}
if (value.IsOffscreenCanvas()) {
if (static_cast<OffscreenCanvas*>(value.GetAsOffscreenCanvas())
->IsNeutered()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
String::Format("The image source is detached"));
return nullptr;
}
if (static_cast<OffscreenCanvas*>(value.GetAsOffscreenCanvas())
->Size()
.IsEmpty()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
String::Format("The image argument is an OffscreenCanvas element "
"with a width or height of 0."));
return nullptr;
}
return value.GetAsOffscreenCanvas();
}
NOTREACHED();
return nullptr;
}
void BaseRenderingContext2D::drawImage(
ScriptState* script_state,
const CanvasImageSourceUnion& image_source,
double x,
double y,
ExceptionState& exception_state) {
CanvasImageSource* image_source_internal =
ToImageSourceInternal(image_source, exception_state);
if (!image_source_internal)
return;
FloatSize default_object_size(Width(), Height());
FloatSize source_rect_size =
image_source_internal->ElementSize(default_object_size);
FloatSize dest_rect_size =
image_source_internal->DefaultDestinationSize(default_object_size);
drawImage(script_state, image_source_internal, 0, 0, source_rect_size.Width(),
source_rect_size.Height(), x, y, dest_rect_size.Width(),
dest_rect_size.Height(), exception_state);
}
void BaseRenderingContext2D::drawImage(
ScriptState* script_state,
const CanvasImageSourceUnion& image_source,
double x,
double y,
double width,
double height,
ExceptionState& exception_state) {
CanvasImageSource* image_source_internal =
ToImageSourceInternal(image_source, exception_state);
if (!image_source_internal)
return;
FloatSize default_object_size(this->Width(), this->Height());
FloatSize source_rect_size =
image_source_internal->ElementSize(default_object_size);
drawImage(script_state, image_source_internal, 0, 0, source_rect_size.Width(),
source_rect_size.Height(), x, y, width, height, exception_state);
}
void BaseRenderingContext2D::drawImage(
ScriptState* script_state,
const CanvasImageSourceUnion& image_source,
double sx,
double sy,
double sw,
double sh,
double dx,
double dy,
double dw,
double dh,
ExceptionState& exception_state) {
CanvasImageSource* image_source_internal =
ToImageSourceInternal(image_source, exception_state);
if (!image_source_internal)
return;
drawImage(script_state, image_source_internal, sx, sy, sw, sh, dx, dy, dw, dh,
exception_state);
}
bool BaseRenderingContext2D::ShouldDrawImageAntialiased(
const FloatRect& dest_rect) const {
if (!GetState().ShouldAntialias())
return false;
cc::PaintCanvas* c = DrawingCanvas();
DCHECK(c);
const SkMatrix& ctm = c->getTotalMatrix();
// Don't disable anti-aliasing if we're rotated or skewed.
if (!ctm.rectStaysRect())
return true;
// Check if the dimensions of the destination are "small" (less than one
// device pixel). To prevent sudden drop-outs. Since we know that
// kRectStaysRect_Mask is set, the matrix either has scale and no skew or
// vice versa. We can query the kAffine_Mask flag to determine which case
// it is.
// FIXME: This queries the CTM while drawing, which is generally
// discouraged. Always drawing with AA can negatively impact performance
// though - that's why it's not always on.
SkScalar width_expansion, height_expansion;
if (ctm.getType() & SkMatrix::kAffine_Mask) {
width_expansion = ctm[SkMatrix::kMSkewY];
height_expansion = ctm[SkMatrix::kMSkewX];
} else {
width_expansion = ctm[SkMatrix::kMScaleX];
height_expansion = ctm[SkMatrix::kMScaleY];
}
return dest_rect.Width() * fabs(width_expansion) < 1 ||
dest_rect.Height() * fabs(height_expansion) < 1;
}
void BaseRenderingContext2D::DrawImageInternal(cc::PaintCanvas* c,
CanvasImageSource* image_source,
Image* image,
const FloatRect& src_rect,
const FloatRect& dst_rect,
const PaintFlags* flags) {
int initial_save_count = c->getSaveCount();
PaintFlags image_flags = *flags;
if (flags->getImageFilter()) {
SkMatrix ctm = c->getTotalMatrix();
SkMatrix inv_ctm;
if (!ctm.invert(&inv_ctm)) {
// There is an earlier check for invertibility, but the arithmetic
// in AffineTransform is not exactly identical, so it is possible
// for SkMatrix to find the transform to be non-invertible at this stage.
// crbug.com/504687
return;
}
c->save();
c->concat(inv_ctm);
SkRect bounds = dst_rect;
ctm.mapRect(&bounds);
PaintFlags layer_flags;
layer_flags.setBlendMode(flags->getBlendMode());
layer_flags.setImageFilter(flags->getImageFilter());
c->saveLayer(&bounds, &layer_flags);
c->concat(ctm);
image_flags.setBlendMode(SkBlendMode::kSrcOver);
image_flags.setImageFilter(nullptr);
}
image_flags.setAntiAlias(ShouldDrawImageAntialiased(dst_rect));
image->Draw(c, image_flags, dst_rect, src_rect, kDoNotRespectImageOrientation,
Image::kDoNotClampImageToSourceRect, Image::kSyncDecode);
c->restoreToCount(initial_save_count);
}
void BaseRenderingContext2D::SetOriginTaintedByContent() {
SetOriginTainted();
origin_tainted_by_content_ = true;
for (auto& state : state_stack_)
state->ClearResolvedFilter();
}
void BaseRenderingContext2D::drawImage(ScriptState* script_state,
CanvasImageSource* image_source,
double sx,
double sy,
double sw,
double sh,
double dx,
double dy,
double dw,
double dh,
ExceptionState& exception_state) {
if (!DrawingCanvas())
return;
base::TimeTicks start_time = base::TimeTicks::Now();
scoped_refptr<Image> image;
FloatSize default_object_size(Width(), Height());
SourceImageStatus source_image_status = kInvalidSourceImageStatus;
AccelerationHint hint =
IsAccelerated() ? kPreferAcceleration : kPreferNoAcceleration;
image = image_source->GetSourceImageForCanvas(&source_image_status, hint,
default_object_size);
if (source_image_status == kUndecodableSourceImageStatus) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"The HTMLImageElement provided is in the 'broken' state.");
}
if (!image || !image->width() || !image->height())
return;
if (!std::isfinite(dx) || !std::isfinite(dy) || !std::isfinite(dw) ||
!std::isfinite(dh) || !std::isfinite(sx) || !std::isfinite(sy) ||
!std::isfinite(sw) || !std::isfinite(sh) || !dw || !dh || !sw || !sh)
return;
// clamp to float to avoid float cast overflow when used as SkScalar
float fsx = clampTo<float>(sx);
float fsy = clampTo<float>(sy);
float fsw = clampTo<float>(sw);
float fsh = clampTo<float>(sh);
float fdx = clampTo<float>(dx);
float fdy = clampTo<float>(dy);
float fdw = clampTo<float>(dw);
float fdh = clampTo<float>(dh);
FloatRect src_rect = NormalizeRect(FloatRect(fsx, fsy, fsw, fsh));
FloatRect dst_rect = NormalizeRect(FloatRect(fdx, fdy, fdw, fdh));
FloatSize image_size = image_source->ElementSize(default_object_size);
ClipRectsToImageRect(FloatRect(FloatPoint(), image_size), &src_rect,
&dst_rect);
image_source->AdjustDrawRects(&src_rect, &dst_rect);
if (src_rect.IsEmpty())
return;
ValidateStateStack();
WillDrawImage(image_source);
ValidateStateStack();
// Heuristic for disabling acceleration based on anticipated texture upload
// overhead.
// See comments in canvas_heuristic_parameters.h for explanation.
if (CanCreateCanvas2dResourceProvider() && IsAccelerated() &&
!image_source->IsAccelerated() &&
!base::FeatureList::IsEnabled(features::kAlwaysAccelerateCanvas)) {
float src_area = src_rect.Width() * src_rect.Height();
if (src_area >
canvas_heuristic_parameters::kDrawImageTextureUploadHardSizeLimit) {
this->DisableAcceleration();
} else if (src_area > canvas_heuristic_parameters::
kDrawImageTextureUploadSoftSizeLimit) {
SkRect bounds = dst_rect;
SkMatrix ctm = DrawingCanvas()->getTotalMatrix();
ctm.mapRect(&bounds);
float dst_area = dst_rect.Width() * dst_rect.Height();
if (src_area >
dst_area * canvas_heuristic_parameters::
kDrawImageTextureUploadSoftSizeLimitScaleThreshold) {
this->DisableAcceleration();
}
}
}
ValidateStateStack();
if (!origin_tainted_by_content_ && WouldTaintOrigin(image_source))
SetOriginTaintedByContent();
Draw(
[this, &image_source, &image, &src_rect, dst_rect](
cc::PaintCanvas* c, const PaintFlags* flags) // draw lambda
{
DrawImageInternal(c, image_source, image.get(), src_rect, dst_rect,
flags);
},
[this, &dst_rect](const SkIRect& clip_bounds) // overdraw test lambda
{ return RectContainsTransformedRect(dst_rect, clip_bounds); },
dst_rect, CanvasRenderingContext2DState::kImagePaintType,
image_source->IsOpaque()
? CanvasRenderingContext2DState::kOpaqueImage
: CanvasRenderingContext2DState::kNonOpaqueImage);
ValidateStateStack();
bool source_is_canvas = false;
if (!IsPaint2D()) {
std::string image_source_name;
if (image_source->IsCanvasElement()) {
image_source_name = "Canvas";
source_is_canvas = true;
} else if (image_source->IsCSSImageValue()) {
image_source_name = "CssImage";
} else if (image_source->IsImageElement()) {
image_source_name = "ImageElement";
} else if (image_source->IsImageBitmap()) {
image_source_name = "ImageBitmap";
} else if (image_source->IsOffscreenCanvas()) {
image_source_name = "OffscreenCanvas";
source_is_canvas = true;
} else if (image_source->IsSVGSource()) {
image_source_name = "SVG";
} else if (image_source->IsVideoElement()) {
image_source_name = "Video";
} else { // Unknown source.
image_source_name = "Unknown";
}
std::string duration_histogram_name =
"Blink.Canvas.DrawImage.Duration." + image_source_name;
std::string size_histogram_name =
"Blink.Canvas.DrawImage.SqrtNumberOfPixels." + image_source_name;
if (CanCreateCanvas2dResourceProvider() && IsAccelerated()) {
if (source_is_canvas)
size_histogram_name.append(".GPU");
duration_histogram_name.append(".GPU");
} else {
if (source_is_canvas)
size_histogram_name.append(".CPU");
duration_histogram_name.append(".CPU");
}
base::TimeDelta elapsed = base::TimeTicks::Now() - start_time;
base::UmaHistogramMicrosecondsTimes(duration_histogram_name, elapsed);
float sqrt_pixels_float =
std::sqrt(dst_rect.Width()) * std::sqrt(dst_rect.Height());
// If sqrt_pixels_float overflows as int CheckedNumeric will store it
// as invalid, then ValueOrDefault will return the maximum int.
base::CheckedNumeric<int> sqrt_pixels = sqrt_pixels_float;
base::UmaHistogramCustomCounts(
size_histogram_name,
sqrt_pixels.ValueOrDefault(std::numeric_limits<int>::max()), 1, 5000,
50);
}
}
void BaseRenderingContext2D::ClearCanvas() {
FloatRect canvas_rect(0, 0, Width(), Height());
CheckOverdraw(canvas_rect, nullptr, CanvasRenderingContext2DState::kNoImage,
kClipFill);
cc::PaintCanvas* c = DrawingCanvas();
if (c)
c->clear(HasAlpha() ? SK_ColorTRANSPARENT : SK_ColorBLACK);
}
bool BaseRenderingContext2D::RectContainsTransformedRect(
const FloatRect& rect,
const SkIRect& transformed_rect) const {
FloatQuad quad(rect);
FloatQuad transformed_quad(
FloatRect(transformed_rect.x(), transformed_rect.y(),
transformed_rect.width(), transformed_rect.height()));
return GetState().Transform().MapQuad(quad).ContainsQuad(transformed_quad);
}
CanvasGradient* BaseRenderingContext2D::createLinearGradient(double x0,
double y0,
double x1,
double y1) {
if (!std::isfinite(x0) || !std::isfinite(y0) || !std::isfinite(x1) ||
!std::isfinite(y1))
return nullptr;
// clamp to float to avoid float cast overflow
float fx0 = clampTo<float>(x0);
float fy0 = clampTo<float>(y0);
float fx1 = clampTo<float>(x1);
float fy1 = clampTo<float>(y1);
auto* gradient = MakeGarbageCollected<CanvasGradient>(FloatPoint(fx0, fy0),
FloatPoint(fx1, fy1));
return gradient;
}
CanvasGradient* BaseRenderingContext2D::createRadialGradient(
double x0,
double y0,
double r0,
double x1,
double y1,
double r1,
ExceptionState& exception_state) {
if (r0 < 0 || r1 < 0) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
String::Format("The %s provided is less than 0.",
r0 < 0 ? "r0" : "r1"));
return nullptr;
}
if (!std::isfinite(x0) || !std::isfinite(y0) || !std::isfinite(r0) ||
!std::isfinite(x1) || !std::isfinite(y1) || !std::isfinite(r1))
return nullptr;
// clamp to float to avoid float cast overflow
float fx0 = clampTo<float>(x0);
float fy0 = clampTo<float>(y0);
float fr0 = clampTo<float>(r0);
float fx1 = clampTo<float>(x1);
float fy1 = clampTo<float>(y1);
float fr1 = clampTo<float>(r1);
auto* gradient = MakeGarbageCollected<CanvasGradient>(
FloatPoint(fx0, fy0), fr0, FloatPoint(fx1, fy1), fr1);
return gradient;
}
CanvasPattern* BaseRenderingContext2D::createPattern(
ScriptState* script_state,
const CanvasImageSourceUnion& image_source,
const String& repetition_type,
ExceptionState& exception_state) {
CanvasImageSource* image_source_internal =
ToImageSourceInternal(image_source, exception_state);
if (!image_source_internal) {
return nullptr;
}
return createPattern(script_state, image_source_internal, repetition_type,
exception_state);
}
CanvasPattern* BaseRenderingContext2D::createPattern(
ScriptState* script_state,
CanvasImageSource* image_source,
const String& repetition_type,
ExceptionState& exception_state) {
if (!image_source) {
return nullptr;
}
Pattern::RepeatMode repeat_mode =
CanvasPattern::ParseRepetitionType(repetition_type, exception_state);
if (exception_state.HadException())
return nullptr;
SourceImageStatus status;
FloatSize default_object_size(Width(), Height());
scoped_refptr<Image> image_for_rendering =
image_source->GetSourceImageForCanvas(&status, kPreferNoAcceleration,
default_object_size);
switch (status) {
case kNormalSourceImageStatus:
break;
case kZeroSizeCanvasSourceImageStatus:
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
String::Format("The canvas %s is 0.",
image_source->ElementSize(default_object_size).Width()
? "height"
: "width"));
return nullptr;
case kUndecodableSourceImageStatus:
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"Source image is in the 'broken' state.");
return nullptr;
case kInvalidSourceImageStatus:
image_for_rendering = Image::NullImage();
break;
case kIncompleteSourceImageStatus:
return nullptr;
default:
NOTREACHED();
return nullptr;
}
DCHECK(image_for_rendering);
bool origin_clean = !WouldTaintOrigin(image_source);
return MakeGarbageCollected<CanvasPattern>(std::move(image_for_rendering),
repeat_mode, origin_clean);
}
bool BaseRenderingContext2D::ComputeDirtyRect(const FloatRect& local_rect,
SkIRect* dirty_rect) {
SkIRect clip_bounds;
if (!DrawingCanvas()->getDeviceClipBounds(&clip_bounds))
return false;
return ComputeDirtyRect(local_rect, clip_bounds, dirty_rect);
}
bool BaseRenderingContext2D::ComputeDirtyRect(
const FloatRect& local_rect,
const SkIRect& transformed_clip_bounds,
SkIRect* dirty_rect) {
FloatRect canvas_rect = GetState().Transform().MapRect(local_rect);
if (AlphaChannel(GetState().ShadowColor())) {
FloatRect shadow_rect(canvas_rect);
shadow_rect.Move(GetState().ShadowOffset());
shadow_rect.Inflate(clampTo<float>(GetState().ShadowBlur()));
canvas_rect.Unite(shadow_rect);
}
SkIRect canvas_i_rect;
static_cast<SkRect>(canvas_rect).roundOut(&canvas_i_rect);
if (!canvas_i_rect.intersect(transformed_clip_bounds))
return false;
if (dirty_rect)
*dirty_rect = canvas_i_rect;
return true;
}
ImageDataColorSettings*
BaseRenderingContext2D::GetColorSettingsAsImageDataColorSettings() const {
ImageDataColorSettings* color_settings = ImageDataColorSettings::Create();
color_settings->setColorSpace(ColorSpaceAsString());
if (PixelFormat() == kF16CanvasPixelFormat)
color_settings->setStorageFormat(kFloat32ArrayStorageFormatName);
return color_settings;
}
ImageData* BaseRenderingContext2D::createImageData(
ImageData* image_data,
ExceptionState& exception_state) const {
ImageData* result = nullptr;
ImageDataColorSettings* color_settings =
GetColorSettingsAsImageDataColorSettings();
result = ImageData::Create(image_data->Size(), color_settings);
if (!result)
exception_state.ThrowRangeError("Out of memory at ImageData creation");
return result;
}
ImageData* BaseRenderingContext2D::createImageData(
int sw,
int sh,
ExceptionState& exception_state) const {
if (!sw || !sh) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
String::Format("The source %s is 0.", sw ? "height" : "width"));
return nullptr;
}
IntSize size(abs(sw), abs(sh));
ImageData* result = nullptr;
ImageDataColorSettings* color_settings =
GetColorSettingsAsImageDataColorSettings();
result = ImageData::Create(size, color_settings);
if (!result)
exception_state.ThrowRangeError("Out of memory at ImageData creation");
return result;
}
ImageData* BaseRenderingContext2D::createImageData(
unsigned width,
unsigned height,
ImageDataColorSettings* color_settings,
ExceptionState& exception_state) const {
return ImageData::CreateImageData(width, height, color_settings,
exception_state);
}
ImageData* BaseRenderingContext2D::createImageData(
ImageDataArray& data_array,
unsigned width,
unsigned height,
ExceptionState& exception_state) const {
return ImageData::CreateImageData(data_array, width, height,
ImageDataColorSettings::Create(),
exception_state);
}
ImageData* BaseRenderingContext2D::createImageData(
ImageDataArray& data_array,
unsigned width,
unsigned height,
ImageDataColorSettings* color_settings,
ExceptionState& exception_state) const {
return ImageData::CreateImageData(data_array, width, height, color_settings,
exception_state);
}
ImageData* BaseRenderingContext2D::getImageData(
int sx,
int sy,
int sw,
int sh,
ExceptionState& exception_state) {
if (!base::CheckMul(sw, sh).IsValid<int>()) {
exception_state.ThrowRangeError("Out of memory at ImageData creation");
return nullptr;
}
base::TimeTicks start_time = base::TimeTicks::Now();
usage_counters_.num_get_image_data_calls++;
usage_counters_.area_get_image_data_calls += sw * sh;
if (!OriginClean()) {
exception_state.ThrowSecurityError(
"The canvas has been tainted by cross-origin data.");
} else if (!sw || !sh) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
String::Format("The source %s is 0.", sw ? "height" : "width"));
}
if (exception_state.HadException())
return nullptr;
if (sw < 0) {
if (!base::CheckAdd(sx, sw).IsValid<int>()) {
exception_state.ThrowRangeError("Out of memory at ImageData creation");
return nullptr;
}
sx += sw;
sw = base::saturated_cast<int>(base::SafeUnsignedAbs(sw));
}
if (sh < 0) {
if (!base::CheckAdd(sy, sh).IsValid<int>()) {
exception_state.ThrowRangeError("Out of memory at ImageData creation");
return nullptr;
}
sy += sh;
sh = base::saturated_cast<int>(base::SafeUnsignedAbs(sh));
}
if (!base::CheckAdd(sx, sw).IsValid<int>() ||
!base::CheckAdd(sy, sh).IsValid<int>()) {
exception_state.ThrowRangeError("Out of memory at ImageData creation");
return nullptr;
}
IntRect image_data_rect(sx, sy, sw, sh);
bool hasResourceProvider = CanCreateCanvas2dResourceProvider();
ImageDataColorSettings* color_settings =
GetColorSettingsAsImageDataColorSettings();
if (!hasResourceProvider || isContextLost()) {
ImageData* result =
ImageData::Create(image_data_rect.Size(), color_settings);
if (!result)
exception_state.ThrowRangeError("Out of memory at ImageData creation");
return result;
}
WTF::ArrayBufferContents contents;
const CanvasColorParams& color_params = ColorParams();
scoped_refptr<StaticBitmapImage> snapshot = GetImage(kPreferNoAcceleration);
if (!StaticBitmapImage::ConvertToArrayBufferContents(
snapshot, contents, image_data_rect, color_params, IsAccelerated())) {
exception_state.ThrowRangeError("Out of memory at ImageData creation");
return nullptr;
}
if (!!snapshot) {
// If source image is not null, the ConvertToArrayBufferContents function
// must have invoked SkImage::readPixels.
DidInvokeGPUReadbackInCurrentFrame();
}
NeedsFinalizeFrame();
// Convert pixels to proper storage format if needed
if (PixelFormat() != kRGBA8CanvasPixelFormat) {
ImageDataStorageFormat storage_format =
ImageData::GetImageDataStorageFormat(color_settings->storageFormat());
DOMArrayBufferView* array_buffer_view =
ImageData::ConvertPixelsFromCanvasPixelFormatToImageDataStorageFormat(
contents, PixelFormat(), storage_format);
return ImageData::Create(image_data_rect.Size(),
NotShared<DOMArrayBufferView>(array_buffer_view),
color_settings);
}
DOMArrayBuffer* array_buffer = DOMArrayBuffer::Create(contents);
ImageData* imageData = ImageData::Create(
image_data_rect.Size(),
NotShared<DOMUint8ClampedArray>(DOMUint8ClampedArray::Create(
array_buffer, 0, array_buffer->ByteLength())),
color_settings);
if (!IsPaint2D()) {
int scaled_time = getScaledElapsedTime(
image_data_rect.Width(), image_data_rect.Height(), start_time);
if (CanCreateCanvas2dResourceProvider() && IsAccelerated()) {
base::UmaHistogramCounts1000(
"Blink.Canvas.GetImageDataScaledDuration.GPU", scaled_time);
} else {
base::UmaHistogramCounts1000(
"Blink.Canvas.GetImageDataScaledDuration.CPU", scaled_time);
}
}
return imageData;
}
int BaseRenderingContext2D::getScaledElapsedTime(float width,
float height,
base::TimeTicks start_time) {
base::TimeDelta elapsed_time = base::TimeTicks::Now() - start_time;
float sqrt_pixels = std::sqrt(width) * std::sqrt(height);
float scaled_time_float = elapsed_time.InMicrosecondsF() * 10.0f /
(sqrt_pixels == 0 ? 1.0f : sqrt_pixels);
// If scaled_time_float overflows as integer, CheckedNumeric will store it
// as invalid, then ValueOrDefault will return the maximum int.
base::CheckedNumeric<int> checked_scaled_time = scaled_time_float;
return checked_scaled_time.ValueOrDefault(std::numeric_limits<int>::max());
}
void BaseRenderingContext2D::putImageData(ImageData* data,
int dx,
int dy,
ExceptionState& exception_state) {
putImageData(data, dx, dy, 0, 0, data->width(), data->height(),
exception_state);
}
void BaseRenderingContext2D::putImageData(ImageData* data,
int dx,
int dy,
int dirty_x,
int dirty_y,
int dirty_width,
int dirty_height,
ExceptionState& exception_state) {
if (!base::CheckMul(dirty_width, dirty_height).IsValid<int>()) {
return;
}
base::TimeTicks start_time = base::TimeTicks::Now();
usage_counters_.num_put_image_data_calls++;
usage_counters_.area_put_image_data_calls += dirty_width * dirty_height;
if (data->BufferBase()->IsNeutered()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"The source data has been neutered.");
return;
}
bool hasResourceProvider = CanCreateCanvas2dResourceProvider();
if (!hasResourceProvider)
return;
if (dirty_width < 0) {
if (dirty_x < 0) {
dirty_x = dirty_width = 0;
} else {
dirty_x += dirty_width;
dirty_width =
base::saturated_cast<int>(base::SafeUnsignedAbs(dirty_width));
}
}
if (dirty_height < 0) {
if (dirty_y < 0) {
dirty_y = dirty_height = 0;
} else {
dirty_y += dirty_height;
dirty_height =
base::saturated_cast<int>(base::SafeUnsignedAbs(dirty_height));
}
}
IntRect dest_rect(dirty_x, dirty_y, dirty_width, dirty_height);
dest_rect.Intersect(IntRect(0, 0, data->width(), data->height()));
IntSize dest_offset(static_cast<int>(dx), static_cast<int>(dy));
dest_rect.Move(dest_offset);
dest_rect.Intersect(IntRect(0, 0, Width(), Height()));
if (dest_rect.IsEmpty())
return;
IntRect source_rect(dest_rect);
source_rect.Move(-dest_offset);
CheckOverdraw(dest_rect, nullptr, CanvasRenderingContext2DState::kNoImage,
kUntransformedUnclippedFill);
// Color / format convert ImageData to context 2D settings if needed. Color /
// format conversion is not needed only if context 2D and ImageData are both
// in sRGB color space and use uint8 pixel storage format. We use RGBA pixel
// order for both ImageData and CanvasResourceProvider, therefore no
// additional swizzling is needed.
CanvasColorParams data_color_params = data->GetCanvasColorParams();
CanvasColorParams context_color_params =
CanvasColorParams(ColorParams().ColorSpace(), PixelFormat(), kNonOpaque);
if (data_color_params.NeedsColorConversion(context_color_params) ||
PixelFormat() == kF16CanvasPixelFormat) {
size_t data_length;
if (!base::CheckMul(data->Size().Area(),
context_color_params.BytesPerPixel())
.AssignIfValid(&data_length))
return;
std::unique_ptr<uint8_t[]> converted_pixels(new uint8_t[data_length]);
if (data->ImageDataInCanvasColorSettings(
ColorParams().ColorSpace(), PixelFormat(), converted_pixels.get(),
kRGBAColorType)) {
PutByteArray(converted_pixels.get(),
IntSize(data->width(), data->height()), source_rect,
IntPoint(dest_offset));
}
} else {
PutByteArray(data->data()->Data(), IntSize(data->width(), data->height()),
source_rect, IntPoint(dest_offset));
}
if (!IsPaint2D()) {
int scaled_time =
getScaledElapsedTime(dest_rect.Width(), dest_rect.Height(), start_time);
if (CanCreateCanvas2dResourceProvider() && IsAccelerated()) {
base::UmaHistogramCounts1000(
"Blink.Canvas.PutImageDataScaledDuration.GPU", scaled_time);
} else {
base::UmaHistogramCounts1000(
"Blink.Canvas.PutImageDataScaledDuration.CPU", scaled_time);
}
}
DidDraw(dest_rect);
}
void BaseRenderingContext2D::PutByteArray(const unsigned char* source,
const IntSize& source_size,
const IntRect& source_rect,
const IntPoint& dest_point) {
if (!IsCanvas2DBufferValid())
return;
uint8_t bytes_per_pixel = ColorParams().BytesPerPixel();
DCHECK_GT(source_rect.Width(), 0);
DCHECK_GT(source_rect.Height(), 0);
int origin_x = source_rect.X();
int dest_x = dest_point.X() + source_rect.X();
DCHECK_GE(dest_x, 0);
DCHECK_LT(dest_x, Width());
DCHECK_GE(origin_x, 0);
DCHECK_LT(origin_x, source_rect.MaxX());
int origin_y = source_rect.Y();
int dest_y = dest_point.Y() + source_rect.Y();
DCHECK_GE(dest_y, 0);
DCHECK_LT(dest_y, Height());
DCHECK_GE(origin_y, 0);
DCHECK_LT(origin_y, source_rect.MaxY());
const size_t src_bytes_per_row = bytes_per_pixel * source_size.Width();
const void* src_addr =
source + origin_y * src_bytes_per_row + origin_x * bytes_per_pixel;
SkAlphaType alpha_type;
if (kOpaque == ColorParams().GetOpacityMode()) {
// If the surface is opaque, tell it that we are writing opaque
// pixels. Writing non-opaque pixels to opaque is undefined in
// Skia. There is some discussion about whether it should be
// defined in skbug.com/6157. For now, we can get the desired
// behavior (memcpy) by pretending the write is opaque.
alpha_type = kOpaque_SkAlphaType;
} else {
alpha_type = kUnpremul_SkAlphaType;
}
SkImageInfo info;
if (ColorParams().GetSkColorSpaceForSkSurfaces()) {
info = SkImageInfo::Make(source_rect.Width(), source_rect.Height(),
ColorParams().GetSkColorType(), alpha_type,
ColorParams().GetSkColorSpaceForSkSurfaces());
if (info.colorType() == kN32_SkColorType)
info = info.makeColorType(kRGBA_8888_SkColorType);
} else {
info = SkImageInfo::Make(source_rect.Width(), source_rect.Height(),
kRGBA_8888_SkColorType, alpha_type);
}
WritePixels(info, src_addr, src_bytes_per_row, dest_x, dest_y);
}
void BaseRenderingContext2D::InflateStrokeRect(FloatRect& rect) const {
// Fast approximation of the stroke's bounding rect.
// This yields a slightly oversized rect but is very fast
// compared to Path::strokeBoundingRect().
static const double kRoot2 = sqrtf(2);
double delta = GetState().LineWidth() / 2;
if (GetState().GetLineJoin() == kMiterJoin)
delta *= GetState().MiterLimit();
else if (GetState().GetLineCap() == kSquareCap)
delta *= kRoot2;
rect.Inflate(clampTo<float>(delta));
}
bool BaseRenderingContext2D::imageSmoothingEnabled() const {
return GetState().ImageSmoothingEnabled();
}
void BaseRenderingContext2D::setImageSmoothingEnabled(bool enabled) {
if (enabled == GetState().ImageSmoothingEnabled())
return;
ModifiableState().SetImageSmoothingEnabled(enabled);
}
String BaseRenderingContext2D::imageSmoothingQuality() const {
return GetState().ImageSmoothingQuality();
}
void BaseRenderingContext2D::setImageSmoothingQuality(const String& quality) {
if (quality == GetState().ImageSmoothingQuality())
return;
ModifiableState().SetImageSmoothingQuality(quality);
}
void BaseRenderingContext2D::CheckOverdraw(
const SkRect& rect,
const PaintFlags* flags,
CanvasRenderingContext2DState::ImageType image_type,
DrawType draw_type) {
cc::PaintCanvas* c = DrawingCanvas();
if (!c)
return;
SkRect device_rect;
if (draw_type == kUntransformedUnclippedFill) {
device_rect = rect;
} else {
DCHECK_EQ(draw_type, kClipFill);
if (GetState().HasComplexClip())
return;
SkIRect sk_i_bounds;
if (!c->getDeviceClipBounds(&sk_i_bounds))
return;
device_rect = SkRect::Make(sk_i_bounds);
}
const SkImageInfo& image_info = c->imageInfo();
if (!device_rect.contains(
SkRect::MakeWH(image_info.width(), image_info.height())))
return;
bool is_source_over = true;
unsigned alpha = 0xFF;
if (flags) {
if (flags->getLooper() || flags->getImageFilter() || flags->getMaskFilter())
return;
SkBlendMode mode = flags->getBlendMode();
is_source_over = mode == SkBlendMode::kSrcOver;
if (!is_source_over && mode != SkBlendMode::kSrc &&
mode != SkBlendMode::kClear)
return; // The code below only knows how to handle Src, SrcOver, and
// Clear
alpha = flags->getAlpha();
if (is_source_over &&
image_type == CanvasRenderingContext2DState::kNoImage) {
if (flags->HasShader()) {
if (flags->ShaderIsOpaque() && alpha == 0xFF)
WillOverwriteCanvas();
return;
}
}
}
if (is_source_over) {
// With source over, we need to certify that alpha == 0xFF for all pixels
if (image_type == CanvasRenderingContext2DState::kNonOpaqueImage)
return;
if (alpha < 0xFF)
return;
}
WillOverwriteCanvas();
}
float BaseRenderingContext2D::GetFontBaseline(
const SimpleFontData& font_data) const {
return TextMetrics::GetFontBaseline(GetState().GetTextBaseline(), font_data);
}
String BaseRenderingContext2D::textAlign() const {
return TextAlignName(GetState().GetTextAlign());
}
void BaseRenderingContext2D::setTextAlign(const String& s) {
TextAlign align;
if (!ParseTextAlign(s, align))
return;
if (GetState().GetTextAlign() == align)
return;
ModifiableState().SetTextAlign(align);
}
String BaseRenderingContext2D::textBaseline() const {
return TextBaselineName(GetState().GetTextBaseline());
}
void BaseRenderingContext2D::setTextBaseline(const String& s) {
TextBaseline baseline;
if (!ParseTextBaseline(s, baseline))
return;
if (GetState().GetTextBaseline() == baseline)
return;
ModifiableState().SetTextBaseline(baseline);
}
const BaseRenderingContext2D::UsageCounters&
BaseRenderingContext2D::GetUsage() {
return usage_counters_;
}
void BaseRenderingContext2D::Trace(blink::Visitor* visitor) {
visitor->Trace(state_stack_);
}
BaseRenderingContext2D::UsageCounters::UsageCounters()
: num_draw_calls{0, 0, 0, 0, 0, 0, 0},
bounding_box_perimeter_draw_calls{0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f},
bounding_box_area_draw_calls{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
bounding_box_area_fill_type{0.0f, 0.0f, 0.0f, 0.0f},
num_non_convex_fill_path_calls(0),
non_convex_fill_path_area(0.0f),
num_radial_gradients(0),
num_linear_gradients(0),
num_patterns(0),
num_draw_with_complex_clips(0),
num_blurred_shadows(0),
bounding_box_area_times_shadow_blur_squared(0.0f),
bounding_box_perimeter_times_shadow_blur_squared(0.0f),
num_filters(0),
num_get_image_data_calls(0),
area_get_image_data_calls(0.0),
num_put_image_data_calls(0),
area_put_image_data_calls(0.0),
num_clear_rect_calls(0),
num_draw_focus_calls(0),
num_frames_since_reset(0) {}
} // namespace blink