blob: c2f19d381c4b05ea4394fe63e79964027eb72488 [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/canvas2d/BaseRenderingContext2D.h"
#include "bindings/core/v8/ExceptionMessages.h"
#include "bindings/core/v8/ExceptionState.h"
#include "bindings/core/v8/ExceptionStatePlaceholder.h"
#include "core/css/cssom/CSSURLImageValue.h"
#include "core/css/parser/CSSParser.h"
#include "core/frame/ImageBitmap.h"
#include "core/html/HTMLCanvasElement.h"
#include "core/html/HTMLImageElement.h"
#include "core/html/HTMLVideoElement.h"
#include "core/html/ImageData.h"
#include "core/offscreencanvas/OffscreenCanvas.h"
#include "modules/canvas2d/CanvasGradient.h"
#include "modules/canvas2d/CanvasPattern.h"
#include "modules/canvas2d/CanvasStyle.h"
#include "modules/canvas2d/Path2D.h"
#include "platform/Histogram.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/geometry/FloatQuad.h"
#include "platform/graphics/Color.h"
#include "platform/graphics/ExpensiveCanvasHeuristicParameters.h"
#include "platform/graphics/Image.h"
#include "platform/graphics/ImageBuffer.h"
#include "platform/graphics/StrokeData.h"
#include "platform/graphics/skia/SkiaUtils.h"
namespace blink {
BaseRenderingContext2D::BaseRenderingContext2D()
: m_clipAntialiasing(NotAntiAliased) {
m_stateStack.append(CanvasRenderingContext2DState::create());
}
BaseRenderingContext2D::~BaseRenderingContext2D() {}
CanvasRenderingContext2DState& BaseRenderingContext2D::modifiableState() {
realizeSaves();
return *m_stateStack.last();
}
void BaseRenderingContext2D::realizeSaves() {
validateStateStack();
if (state().hasUnrealizedSaves()) {
ASSERT(m_stateStack.size() >= 1);
// Reduce the current state's unrealized count by one now,
// to reflect the fact we are saving one state.
m_stateStack.last()->restore();
m_stateStack.append(CanvasRenderingContext2DState::create(
state(), CanvasRenderingContext2DState::DontCopyClipList));
// 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).
m_stateStack.last()->resetUnrealizedSaveCount();
SkCanvas* canvas = drawingCanvas();
if (canvas)
canvas->save();
validateStateStack();
}
}
void BaseRenderingContext2D::save() {
m_stateStack.last()->save();
}
void BaseRenderingContext2D::restore() {
validateStateStack();
if (state().hasUnrealizedSaves()) {
// We never realized the save, so just record that it was unnecessary.
m_stateStack.last()->restore();
return;
}
ASSERT(m_stateStack.size() >= 1);
if (m_stateStack.size() <= 1)
return;
m_path.transform(state().transform());
m_stateStack.removeLast();
m_stateStack.last()->clearResolvedFilter();
m_path.transform(state().transform().inverse());
SkCanvas* c = drawingCanvas();
if (c)
c->restore();
validateStateStack();
}
void BaseRenderingContext2D::restoreMatrixClipStack(SkCanvas* c) const {
if (!c)
return;
HeapVector<Member<CanvasRenderingContext2DState>>::const_iterator currState;
DCHECK(m_stateStack.begin() < m_stateStack.end());
for (currState = m_stateStack.begin(); currState < m_stateStack.end();
currState++) {
CHECK(currState->get());
c->setMatrix(SkMatrix::I());
currState->get()->playbackClips(c);
c->setMatrix(affineTransformToSkMatrix(currState->get()->transform()));
c->save();
}
c->restore();
validateStateStack();
}
static inline void convertCanvasStyleToUnionType(
CanvasStyle* style,
StringOrCanvasGradientOrCanvasPattern& returnValue) {
if (CanvasGradient* gradient = style->getCanvasGradient()) {
returnValue.setCanvasGradient(gradient);
return;
}
if (CanvasPattern* pattern = style->getCanvasPattern()) {
returnValue.setCanvasPattern(pattern);
return;
}
returnValue.setString(style->color());
}
void BaseRenderingContext2D::strokeStyle(
StringOrCanvasGradientOrCanvasPattern& returnValue) const {
convertCanvasStyleToUnionType(state().strokeStyle(), returnValue);
}
void BaseRenderingContext2D::setStrokeStyle(
const StringOrCanvasGradientOrCanvasPattern& style) {
ASSERT(!style.isNull());
String colorString;
CanvasStyle* canvasStyle = nullptr;
if (style.isString()) {
colorString = style.getAsString();
if (colorString == state().unparsedStrokeColor())
return;
Color parsedColor = 0;
if (!parseColorOrCurrentColor(parsedColor, colorString))
return;
if (state().strokeStyle()->isEquivalentRGBA(parsedColor.rgb())) {
modifiableState().setUnparsedStrokeColor(colorString);
return;
}
canvasStyle = CanvasStyle::createFromRGBA(parsedColor.rgb());
} else if (style.isCanvasGradient()) {
canvasStyle = CanvasStyle::createFromGradient(style.getAsCanvasGradient());
} else if (style.isCanvasPattern()) {
CanvasPattern* canvasPattern = style.getAsCanvasPattern();
if (originClean() && !canvasPattern->originClean())
setOriginTainted();
canvasStyle = CanvasStyle::createFromPattern(canvasPattern);
}
ASSERT(canvasStyle);
modifiableState().setStrokeStyle(canvasStyle);
modifiableState().setUnparsedStrokeColor(colorString);
modifiableState().clearResolvedFilter();
}
void BaseRenderingContext2D::fillStyle(
StringOrCanvasGradientOrCanvasPattern& returnValue) const {
convertCanvasStyleToUnionType(state().fillStyle(), returnValue);
}
void BaseRenderingContext2D::setFillStyle(
const StringOrCanvasGradientOrCanvasPattern& style) {
ASSERT(!style.isNull());
validateStateStack();
String colorString;
CanvasStyle* canvasStyle = nullptr;
if (style.isString()) {
colorString = style.getAsString();
if (colorString == state().unparsedFillColor())
return;
Color parsedColor = 0;
if (!parseColorOrCurrentColor(parsedColor, colorString))
return;
if (state().fillStyle()->isEquivalentRGBA(parsedColor.rgb())) {
modifiableState().setUnparsedFillColor(colorString);
return;
}
canvasStyle = CanvasStyle::createFromRGBA(parsedColor.rgb());
} else if (style.isCanvasGradient()) {
canvasStyle = CanvasStyle::createFromGradient(style.getAsCanvasGradient());
} else if (style.isCanvasPattern()) {
CanvasPattern* canvasPattern = style.getAsCanvasPattern();
if (originClean() && !canvasPattern->originClean())
setOriginTainted();
if (canvasPattern->getPattern()->isTextureBacked())
disableDeferral(DisableDeferralReasonUsingTextureBackedPattern);
canvasStyle = CanvasStyle::createFromPattern(canvasPattern);
}
ASSERT(canvasStyle);
modifiableState().setFillStyle(canvasStyle);
modifiableState().setUnparsedFillColor(colorString);
modifiableState().clearResolvedFilter();
}
double BaseRenderingContext2D::lineWidth() const {
return state().lineWidth();
}
void BaseRenderingContext2D::setLineWidth(double width) {
if (!std::isfinite(width) || width <= 0)
return;
if (state().lineWidth() == width)
return;
modifiableState().setLineWidth(width);
}
String BaseRenderingContext2D::lineCap() const {
return lineCapName(state().getLineCap());
}
void BaseRenderingContext2D::setLineCap(const String& s) {
LineCap cap;
if (!parseLineCap(s, cap))
return;
if (state().getLineCap() == cap)
return;
modifiableState().setLineCap(cap);
}
String BaseRenderingContext2D::lineJoin() const {
return lineJoinName(state().getLineJoin());
}
void BaseRenderingContext2D::setLineJoin(const String& s) {
LineJoin join;
if (!parseLineJoin(s, join))
return;
if (state().getLineJoin() == join)
return;
modifiableState().setLineJoin(join);
}
double BaseRenderingContext2D::miterLimit() const {
return state().miterLimit();
}
void BaseRenderingContext2D::setMiterLimit(double limit) {
if (!std::isfinite(limit) || limit <= 0)
return;
if (state().miterLimit() == limit)
return;
modifiableState().setMiterLimit(limit);
}
double BaseRenderingContext2D::shadowOffsetX() const {
return state().shadowOffset().width();
}
void BaseRenderingContext2D::setShadowOffsetX(double x) {
if (!std::isfinite(x))
return;
if (state().shadowOffset().width() == x)
return;
modifiableState().setShadowOffsetX(x);
}
double BaseRenderingContext2D::shadowOffsetY() const {
return state().shadowOffset().height();
}
void BaseRenderingContext2D::setShadowOffsetY(double y) {
if (!std::isfinite(y))
return;
if (state().shadowOffset().height() == y)
return;
modifiableState().setShadowOffsetY(y);
}
double BaseRenderingContext2D::shadowBlur() const {
return state().shadowBlur();
}
void BaseRenderingContext2D::setShadowBlur(double blur) {
if (!std::isfinite(blur) || blur < 0)
return;
if (state().shadowBlur() == blur)
return;
modifiableState().setShadowBlur(blur);
}
String BaseRenderingContext2D::shadowColor() const {
return Color(state().shadowColor()).serialized();
}
void BaseRenderingContext2D::setShadowColor(const String& colorString) {
Color color;
if (!parseColorOrCurrentColor(color, colorString))
return;
if (state().shadowColor() == color)
return;
modifiableState().setShadowColor(color.rgb());
}
const Vector<double>& BaseRenderingContext2D::getLineDash() const {
return state().lineDash();
}
static bool lineDashSequenceIsValid(const Vector<double>& dash) {
for (size_t i = 0; i < dash.size(); i++) {
if (!std::isfinite(dash[i]) || dash[i] < 0)
return false;
}
return true;
}
void BaseRenderingContext2D::setLineDash(const Vector<double>& dash) {
if (!lineDashSequenceIsValid(dash))
return;
modifiableState().setLineDash(dash);
}
double BaseRenderingContext2D::lineDashOffset() const {
return state().lineDashOffset();
}
void BaseRenderingContext2D::setLineDashOffset(double offset) {
if (!std::isfinite(offset) || state().lineDashOffset() == offset)
return;
modifiableState().setLineDashOffset(offset);
}
double BaseRenderingContext2D::globalAlpha() const {
return state().globalAlpha();
}
void BaseRenderingContext2D::setGlobalAlpha(double alpha) {
if (!(alpha >= 0 && alpha <= 1))
return;
if (state().globalAlpha() == alpha)
return;
modifiableState().setGlobalAlpha(alpha);
}
String BaseRenderingContext2D::globalCompositeOperation() const {
return compositeOperatorName(
compositeOperatorFromSkia(state().globalComposite()),
blendModeFromSkia(state().globalComposite()));
}
void BaseRenderingContext2D::setGlobalCompositeOperation(
const String& operation) {
CompositeOperator op = CompositeSourceOver;
WebBlendMode blendMode = WebBlendModeNormal;
if (!parseCompositeAndBlendOperator(operation, op, blendMode))
return;
SkXfermode::Mode xfermode = WebCoreCompositeToSkiaComposite(op, blendMode);
if (state().globalComposite() == xfermode)
return;
modifiableState().setGlobalComposite(xfermode);
}
String BaseRenderingContext2D::filter() const {
return state().unparsedFilter();
}
void BaseRenderingContext2D::setFilter(const String& filterString) {
if (filterString == state().unparsedFilter())
return;
const CSSValue* filterValue =
CSSParser::parseSingleValue(CSSPropertyFilter, filterString,
CSSParserContext(HTMLStandardMode, nullptr));
if (!filterValue || filterValue->isInitialValue() ||
filterValue->isInheritedValue())
return;
modifiableState().setUnparsedFilter(filterString);
modifiableState().setFilter(filterValue);
snapshotStateForFilter();
}
SVGMatrixTearOff* BaseRenderingContext2D::currentTransform() const {
return SVGMatrixTearOff::create(state().transform());
}
void BaseRenderingContext2D::setCurrentTransform(
SVGMatrixTearOff* matrixTearOff) {
const AffineTransform& transform = matrixTearOff->value();
setTransform(transform.a(), transform.b(), transform.c(), transform.d(),
transform.e(), transform.f());
}
void BaseRenderingContext2D::scale(double sx, double sy) {
SkCanvas* c = drawingCanvas();
if (!c)
return;
if (!std::isfinite(sx) || !std::isfinite(sy))
return;
AffineTransform newTransform = state().transform();
newTransform.scaleNonUniform(sx, sy);
if (state().transform() == newTransform)
return;
modifiableState().setTransform(newTransform);
if (!state().isTransformInvertible())
return;
c->scale(sx, sy);
m_path.transform(AffineTransform().scaleNonUniform(1.0 / sx, 1.0 / sy));
}
void BaseRenderingContext2D::rotate(double angleInRadians) {
SkCanvas* c = drawingCanvas();
if (!c)
return;
if (!std::isfinite(angleInRadians))
return;
AffineTransform newTransform = state().transform();
newTransform.rotateRadians(angleInRadians);
if (state().transform() == newTransform)
return;
modifiableState().setTransform(newTransform);
if (!state().isTransformInvertible())
return;
c->rotate(angleInRadians * (180.0 / piFloat));
m_path.transform(AffineTransform().rotateRadians(-angleInRadians));
}
void BaseRenderingContext2D::translate(double tx, double ty) {
SkCanvas* c = drawingCanvas();
if (!c)
return;
if (!state().isTransformInvertible())
return;
if (!std::isfinite(tx) || !std::isfinite(ty))
return;
AffineTransform newTransform = state().transform();
newTransform.translate(tx, ty);
if (state().transform() == newTransform)
return;
modifiableState().setTransform(newTransform);
if (!state().isTransformInvertible())
return;
c->translate(tx, ty);
m_path.transform(AffineTransform().translate(-tx, -ty));
}
void BaseRenderingContext2D::transform(double m11,
double m12,
double m21,
double m22,
double dx,
double dy) {
SkCanvas* 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;
AffineTransform transform(m11, m12, m21, m22, dx, dy);
AffineTransform newTransform = state().transform() * transform;
if (state().transform() == newTransform)
return;
modifiableState().setTransform(newTransform);
if (!state().isTransformInvertible())
return;
c->concat(affineTransformToSkMatrix(transform));
m_path.transform(transform.inverse());
}
void BaseRenderingContext2D::resetTransform() {
SkCanvas* c = drawingCanvas();
if (!c)
return;
AffineTransform ctm = state().transform();
bool invertibleCTM = state().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() && invertibleCTM)
return;
// resetTransform() resolves the non-invertible CTM state.
modifiableState().resetTransform();
c->setMatrix(affineTransformToSkMatrix(baseTransform()));
if (invertibleCTM)
m_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) {
SkCanvas* 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;
resetTransform();
transform(m11, m12, m21, m22, dx, dy);
}
void BaseRenderingContext2D::beginPath() {
m_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(SkXfermode::Mode 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 == SkXfermode::kSrcIn_Mode || op == SkXfermode::kSrcOut_Mode ||
op == SkXfermode::kDstIn_Mode || op == SkXfermode::kDstATop_Mode;
}
static bool isPathExpensive(const Path& path) {
const SkPath& skPath = path.getSkPath();
if (ExpensiveCanvasHeuristicParameters::ConcavePathsAreExpensive &&
!skPath.isConvex())
return true;
if (skPath.countPoints() >
ExpensiveCanvasHeuristicParameters::ExpensivePathPointCount)
return true;
return false;
}
void BaseRenderingContext2D::drawPathInternal(
const Path& path,
CanvasRenderingContext2DState::PaintType paintType,
SkPath::FillType fillType) {
if (path.isEmpty())
return;
SkPath skPath = path.getSkPath();
FloatRect bounds = path.boundingRect();
skPath.setFillType(fillType);
if (paintType == CanvasRenderingContext2DState::StrokePaintType)
inflateStrokeRect(bounds);
if (!drawingCanvas())
return;
if (draw([&skPath, this](SkCanvas* c, const SkPaint* paint) // draw lambda
{ c->drawPath(skPath, *paint); },
[](const SkIRect& rect) // overdraw test lambda
{ return false; },
bounds, paintType)) {
if (isPathExpensive(path)) {
ImageBuffer* buffer = imageBuffer();
if (buffer)
buffer->setHasExpensiveOp();
}
}
}
static SkPath::FillType parseWinding(const String& windingRuleString) {
if (windingRuleString == "nonzero")
return SkPath::kWinding_FillType;
if (windingRuleString == "evenodd")
return SkPath::kEvenOdd_FillType;
ASSERT_NOT_REACHED();
return SkPath::kEvenOdd_FillType;
}
void BaseRenderingContext2D::fill(const String& windingRuleString) {
trackDrawCall(FillPath);
drawPathInternal(m_path, CanvasRenderingContext2DState::FillPaintType,
parseWinding(windingRuleString));
}
void BaseRenderingContext2D::fill(Path2D* domPath,
const String& windingRuleString) {
trackDrawCall(FillPath, domPath);
drawPathInternal(domPath->path(),
CanvasRenderingContext2DState::FillPaintType,
parseWinding(windingRuleString));
}
void BaseRenderingContext2D::stroke() {
trackDrawCall(StrokePath);
drawPathInternal(m_path, CanvasRenderingContext2DState::StrokePaintType);
}
void BaseRenderingContext2D::stroke(Path2D* domPath) {
trackDrawCall(StrokePath, domPath);
drawPathInternal(domPath->path(),
CanvasRenderingContext2DState::StrokePaintType);
}
void BaseRenderingContext2D::fillRect(double x,
double y,
double width,
double height) {
trackDrawCall(FillRect, nullptr, width, height);
if (!validateRectForCanvas(x, y, width, height))
return;
if (!drawingCanvas())
return;
SkRect rect = SkRect::MakeXYWH(x, y, width, height);
draw([&rect, this](SkCanvas* c, const SkPaint* paint) // draw lambda
{ c->drawRect(rect, *paint); },
[&rect, this](const SkIRect& clipBounds) // overdraw test lambda
{ return rectContainsTransformedRect(rect, clipBounds); },
rect, CanvasRenderingContext2DState::FillPaintType);
}
static void strokeRectOnCanvas(const FloatRect& rect,
SkCanvas* canvas,
const SkPaint* paint) {
ASSERT(paint->getStyle() == SkPaint::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, *paint);
return;
}
canvas->drawRect(rect, *paint);
}
void BaseRenderingContext2D::strokeRect(double x,
double y,
double width,
double height) {
trackDrawCall(StrokeRect, nullptr, width, height);
if (!validateRectForCanvas(x, y, width, height))
return;
if (!drawingCanvas())
return;
SkRect rect = SkRect::MakeXYWH(x, y, width, height);
FloatRect bounds = rect;
inflateStrokeRect(bounds);
draw([&rect, this](SkCanvas* c, const SkPaint* paint) // draw lambda
{ strokeRectOnCanvas(rect, c, paint); },
[](const SkIRect& clipBounds) // overdraw test lambda
{ return false; },
bounds, CanvasRenderingContext2DState::StrokePaintType);
}
void BaseRenderingContext2D::clipInternal(const Path& path,
const String& windingRuleString) {
SkCanvas* c = drawingCanvas();
if (!c) {
return;
}
if (!state().isTransformInvertible()) {
return;
}
SkPath skPath = path.getSkPath();
skPath.setFillType(parseWinding(windingRuleString));
modifiableState().clipPath(skPath, m_clipAntialiasing);
c->clipPath(skPath, SkRegion::kIntersect_Op,
m_clipAntialiasing == AntiAliased);
if (ExpensiveCanvasHeuristicParameters::ComplexClipsAreExpensive &&
!skPath.isRect(0) && hasImageBuffer()) {
imageBuffer()->setHasExpensiveOp();
}
}
void BaseRenderingContext2D::clip(const String& windingRuleString) {
clipInternal(m_path, windingRuleString);
}
void BaseRenderingContext2D::clip(Path2D* domPath,
const String& windingRuleString) {
clipInternal(domPath->path(), windingRuleString);
}
bool BaseRenderingContext2D::isPointInPath(const double x,
const double y,
const String& windingRuleString) {
return isPointInPathInternal(m_path, x, y, windingRuleString);
}
bool BaseRenderingContext2D::isPointInPath(Path2D* domPath,
const double x,
const double y,
const String& windingRuleString) {
return isPointInPathInternal(domPath->path(), x, y, windingRuleString);
}
bool BaseRenderingContext2D::isPointInPathInternal(
const Path& path,
const double x,
const double y,
const String& windingRuleString) {
SkCanvas* c = drawingCanvas();
if (!c)
return false;
if (!state().isTransformInvertible())
return false;
FloatPoint point(x, y);
if (!std::isfinite(point.x()) || !std::isfinite(point.y()))
return false;
AffineTransform ctm = state().transform();
FloatPoint transformedPoint = ctm.inverse().mapPoint(point);
return path.contains(transformedPoint,
SkFillTypeToWindRule(parseWinding(windingRuleString)));
}
bool BaseRenderingContext2D::isPointInStroke(const double x, const double y) {
return isPointInStrokeInternal(m_path, x, y);
}
bool BaseRenderingContext2D::isPointInStroke(Path2D* domPath,
const double x,
const double y) {
return isPointInStrokeInternal(domPath->path(), x, y);
}
bool BaseRenderingContext2D::isPointInStrokeInternal(const Path& path,
const double x,
const double y) {
SkCanvas* c = drawingCanvas();
if (!c)
return false;
if (!state().isTransformInvertible())
return false;
FloatPoint point(x, y);
if (!std::isfinite(point.x()) || !std::isfinite(point.y()))
return false;
AffineTransform ctm = state().transform();
FloatPoint transformedPoint = ctm.inverse().mapPoint(point);
StrokeData strokeData;
strokeData.setThickness(state().lineWidth());
strokeData.setLineCap(state().getLineCap());
strokeData.setLineJoin(state().getLineJoin());
strokeData.setMiterLimit(state().miterLimit());
Vector<float> lineDash(state().lineDash().size());
std::copy(state().lineDash().begin(), state().lineDash().end(),
lineDash.begin());
strokeData.setLineDash(lineDash, state().lineDashOffset());
return path.strokeContains(transformedPoint, strokeData);
}
void BaseRenderingContext2D::clearRect(double x,
double y,
double width,
double height) {
m_usageCounters.numClearRectCalls++;
if (!validateRectForCanvas(x, y, width, height))
return;
SkCanvas* c = drawingCanvas();
if (!c)
return;
if (!state().isTransformInvertible())
return;
SkIRect clipBounds;
if (!c->getClipDeviceBounds(&clipBounds))
return;
SkPaint clearPaint;
clearPaint.setXfermodeMode(SkXfermode::kClear_Mode);
clearPaint.setStyle(SkPaint::kFill_Style);
FloatRect rect(x, y, width, height);
if (rectContainsTransformedRect(rect, clipBounds)) {
checkOverdraw(rect, &clearPaint, CanvasRenderingContext2DState::NoImage,
ClipFill);
if (drawingCanvas())
drawingCanvas()->drawRect(rect, clearPaint);
didDraw(clipBounds);
} else {
SkIRect dirtyRect;
if (computeDirtyRect(rect, clipBounds, &dirtyRect)) {
c->drawRect(rect, clearPaint);
didDraw(dirtyRect);
}
}
}
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& imageRect,
FloatRect* srcRect,
FloatRect* dstRect) {
if (imageRect.contains(*srcRect))
return;
// Compute the src to dst transform
FloatSize scale(dstRect->size().width() / srcRect->size().width(),
dstRect->size().height() / srcRect->size().height());
FloatPoint scaledSrcLocation = srcRect->location();
scaledSrcLocation.scale(scale.width(), scale.height());
FloatSize offset = dstRect->location() - scaledSrcLocation;
srcRect->intersect(imageRect);
// To clip the destination rectangle in the same proportion, transform the
// clipped src rect
*dstRect = *srcRect;
dstRect->scale(scale.width(), scale.height());
dstRect->move(offset);
}
static inline CanvasImageSource* toImageSourceInternal(
const CanvasImageSourceUnion& value,
ExceptionState& exceptionState) {
if (value.isCSSImageValue()) {
if (RuntimeEnabledFeatures::cssPaintAPIEnabled())
return value.getAsCSSImageValue();
exceptionState.throwTypeError("CSSImageValue is not yet supported");
return nullptr;
}
if (value.isHTMLImageElement())
return value.getAsHTMLImageElement();
if (value.isHTMLVideoElement())
return value.getAsHTMLVideoElement();
if (value.isHTMLCanvasElement())
return value.getAsHTMLCanvasElement();
if (value.isImageBitmap()) {
if (static_cast<ImageBitmap*>(value.getAsImageBitmap())->isNeutered()) {
exceptionState.throwDOMException(
InvalidStateError, String::format("The image source is detached"));
return nullptr;
}
return value.getAsImageBitmap();
}
if (value.isOffscreenCanvas()) {
if (static_cast<OffscreenCanvas*>(value.getAsOffscreenCanvas())
->isNeutered()) {
exceptionState.throwDOMException(
InvalidStateError, String::format("The image source is detached"));
return nullptr;
}
return value.getAsOffscreenCanvas();
}
ASSERT_NOT_REACHED();
return nullptr;
}
void BaseRenderingContext2D::drawImage(
ExecutionContext* executionContext,
const CanvasImageSourceUnion& imageSource,
double x,
double y,
ExceptionState& exceptionState) {
CanvasImageSource* imageSourceInternal =
toImageSourceInternal(imageSource, exceptionState);
if (!imageSourceInternal)
return;
FloatSize defaultObjectSize(width(), height());
FloatSize sourceRectSize =
imageSourceInternal->elementSize(defaultObjectSize);
FloatSize destRectSize =
imageSourceInternal->defaultDestinationSize(defaultObjectSize);
drawImage(executionContext, imageSourceInternal, 0, 0, sourceRectSize.width(),
sourceRectSize.height(), x, y, destRectSize.width(),
destRectSize.height(), exceptionState);
}
void BaseRenderingContext2D::drawImage(
ExecutionContext* executionContext,
const CanvasImageSourceUnion& imageSource,
double x,
double y,
double width,
double height,
ExceptionState& exceptionState) {
CanvasImageSource* imageSourceInternal =
toImageSourceInternal(imageSource, exceptionState);
if (!imageSourceInternal)
return;
FloatSize defaultObjectSize(this->width(), this->height());
FloatSize sourceRectSize =
imageSourceInternal->elementSize(defaultObjectSize);
drawImage(executionContext, imageSourceInternal, 0, 0, sourceRectSize.width(),
sourceRectSize.height(), x, y, width, height, exceptionState);
}
void BaseRenderingContext2D::drawImage(
ExecutionContext* executionContext,
const CanvasImageSourceUnion& imageSource,
double sx,
double sy,
double sw,
double sh,
double dx,
double dy,
double dw,
double dh,
ExceptionState& exceptionState) {
CanvasImageSource* imageSourceInternal =
toImageSourceInternal(imageSource, exceptionState);
if (!imageSourceInternal)
return;
drawImage(executionContext, imageSourceInternal, sx, sy, sw, sh, dx, dy, dw,
dh, exceptionState);
}
bool BaseRenderingContext2D::shouldDrawImageAntialiased(
const FloatRect& destRect) const {
if (!state().shouldAntialias())
return false;
SkCanvas* c = drawingCanvas();
ASSERT(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 widthExpansion, heightExpansion;
if (ctm.getType() & SkMatrix::kAffine_Mask)
widthExpansion = ctm[SkMatrix::kMSkewY],
heightExpansion = ctm[SkMatrix::kMSkewX];
else
widthExpansion = ctm[SkMatrix::kMScaleX],
heightExpansion = ctm[SkMatrix::kMScaleY];
return destRect.width() * fabs(widthExpansion) < 1 ||
destRect.height() * fabs(heightExpansion) < 1;
}
static bool isDrawScalingDown(const FloatRect& srcRect,
const FloatRect& dstRect,
float xScaleSquared,
float yScaleSquared) {
return dstRect.width() * dstRect.width() * xScaleSquared <
srcRect.width() * srcRect.width() &&
dstRect.height() * dstRect.height() * yScaleSquared <
srcRect.height() * srcRect.height();
}
void BaseRenderingContext2D::drawImageInternal(SkCanvas* c,
CanvasImageSource* imageSource,
Image* image,
const FloatRect& srcRect,
const FloatRect& dstRect,
const SkPaint* paint) {
if (imageSource->isSVGSource()) {
trackDrawCall(DrawVectorImage, nullptr, dstRect.width(), dstRect.height());
} else {
trackDrawCall(DrawBitmapImage, nullptr, dstRect.width(), dstRect.height());
}
int initialSaveCount = c->getSaveCount();
SkPaint imagePaint = *paint;
if (paint->getImageFilter()) {
SkMatrix ctm = c->getTotalMatrix();
SkMatrix invCtm;
if (!ctm.invert(&invCtm)) {
// 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(invCtm);
SkRect bounds = dstRect;
ctm.mapRect(&bounds);
SkPaint layerPaint;
layerPaint.setXfermode(sk_ref_sp(paint->getXfermode()));
layerPaint.setImageFilter(paint->getImageFilter());
c->saveLayer(&bounds, &layerPaint);
c->concat(ctm);
imagePaint.setXfermodeMode(SkXfermode::kSrcOver_Mode);
imagePaint.setImageFilter(nullptr);
}
if (!imageSmoothingEnabled() &&
isDrawScalingDown(srcRect, dstRect, state().transform().xScaleSquared(),
state().transform().yScaleSquared()))
imagePaint.setFilterQuality(kLow_SkFilterQuality);
if (!imageSource->isVideoElement()) {
imagePaint.setAntiAlias(shouldDrawImageAntialiased(dstRect));
image->draw(c, imagePaint, dstRect, srcRect, DoNotRespectImageOrientation,
Image::DoNotClampImageToSourceRect);
} else {
c->save();
c->clipRect(dstRect);
c->translate(dstRect.x(), dstRect.y());
c->scale(dstRect.width() / srcRect.width(),
dstRect.height() / srcRect.height());
c->translate(-srcRect.x(), -srcRect.y());
HTMLVideoElement* video = static_cast<HTMLVideoElement*>(imageSource);
video->paintCurrentFrame(
c,
IntRect(IntPoint(), IntSize(video->videoWidth(), video->videoHeight())),
&imagePaint);
}
c->restoreToCount(initialSaveCount);
}
bool shouldDisableDeferral(CanvasImageSource* imageSource,
DisableDeferralReason* reason) {
ASSERT(reason);
ASSERT(*reason == DisableDeferralReasonUnknown);
if (imageSource->isVideoElement()) {
*reason = DisableDeferralReasonDrawImageOfVideo;
return true;
}
if (imageSource->isCanvasElement()) {
HTMLCanvasElement* canvas = static_cast<HTMLCanvasElement*>(imageSource);
if (canvas->isAnimated2D()) {
*reason = DisableDeferralReasonDrawImageOfAnimated2dCanvas;
return true;
}
}
return false;
}
void BaseRenderingContext2D::drawImage(ExecutionContext* executionContext,
CanvasImageSource* imageSource,
double sx,
double sy,
double sw,
double sh,
double dx,
double dy,
double dw,
double dh,
ExceptionState& exceptionState) {
if (!drawingCanvas())
return;
RefPtr<Image> image;
FloatSize defaultObjectSize(width(), height());
SourceImageStatus sourceImageStatus = InvalidSourceImageStatus;
if (!imageSource->isVideoElement()) {
AccelerationHint hint = imageBuffer()->isAccelerated()
? PreferAcceleration
: PreferNoAcceleration;
image = imageSource->getSourceImageForCanvas(
&sourceImageStatus, hint, SnapshotReasonDrawImage, defaultObjectSize);
if (sourceImageStatus == UndecodableSourceImageStatus)
exceptionState.throwDOMException(
InvalidStateError,
"The HTMLImageElement provided is in the 'broken' state.");
if (!image || !image->width() || !image->height())
return;
} else {
if (!static_cast<HTMLVideoElement*>(imageSource)->hasAvailableVideoFrame())
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;
FloatRect srcRect = normalizeRect(FloatRect(sx, sy, sw, sh));
FloatRect dstRect = normalizeRect(FloatRect(dx, dy, dw, dh));
FloatSize imageSize = imageSource->elementSize(defaultObjectSize);
clipRectsToImageRect(FloatRect(FloatPoint(), imageSize), &srcRect, &dstRect);
imageSource->adjustDrawRects(&srcRect, &dstRect);
if (srcRect.isEmpty())
return;
DisableDeferralReason reason = DisableDeferralReasonUnknown;
if (shouldDisableDeferral(imageSource, &reason))
disableDeferral(reason);
else if (image->isTextureBacked())
disableDeferral(DisableDeferralDrawImageWithTextureBackedSourceImage);
validateStateStack();
// Heuristic for disabling acceleration based on anticipated texture upload
// overhead.
// See comments in ExpensiveCanvasHeuristicParameters.h for explanation.
ImageBuffer* buffer = imageBuffer();
if (buffer && buffer->isAccelerated() && !imageSource->isAccelerated()) {
float srcArea = srcRect.width() * srcRect.height();
if (srcArea > ExpensiveCanvasHeuristicParameters::
DrawImageTextureUploadHardSizeLimit) {
buffer->disableAcceleration();
} else if (srcArea > ExpensiveCanvasHeuristicParameters::
DrawImageTextureUploadSoftSizeLimit) {
SkRect bounds = dstRect;
SkMatrix ctm = drawingCanvas()->getTotalMatrix();
ctm.mapRect(&bounds);
float dstArea = dstRect.width() * dstRect.height();
if (srcArea > dstArea *
ExpensiveCanvasHeuristicParameters::
DrawImageTextureUploadSoftSizeLimitScaleThreshold) {
buffer->disableAcceleration();
}
}
}
validateStateStack();
// TODO(xidachen): After collecting some data, come back and prune off
// the ones that is not needed.
Optional<ScopedUsHistogramTimer> timer;
if (imageBuffer() && imageBuffer()->isAccelerated()) {
if (imageSource->isVideoElement()) {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, scopedUsCounterVideoGPU,
new CustomCountHistogram("Blink.Canvas.DrawImage.Video.GPU", 0,
10000000, 50));
timer.emplace(scopedUsCounterVideoGPU);
} else if (imageSource->isCanvasElement()) {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, scopedUsCounterCanvasGPU,
new CustomCountHistogram("Blink.Canvas.DrawImage.Canvas.GPU", 0,
10000000, 50));
timer.emplace(scopedUsCounterCanvasGPU);
} else if (imageSource->isSVGSource()) {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, scopedUsCounterSVGGPU,
new CustomCountHistogram("Blink.Canvas.DrawImage.SVG.GPU", 0,
10000000, 50));
timer.emplace(scopedUsCounterSVGGPU);
} else if (imageSource->isImageBitmap()) {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, scopedUsCounterImageBitmapGPU,
new CustomCountHistogram("Blink.Canvas.DrawImage.ImageBitmap.GPU", 0,
10000000, 50));
timer.emplace(scopedUsCounterImageBitmapGPU);
} else {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, scopedUsCounterOthersGPU,
new CustomCountHistogram("Blink.Canvas.DrawImage.Others.GPU", 0,
10000000, 50));
timer.emplace(scopedUsCounterOthersGPU);
}
} else if (imageBuffer() && imageBuffer()->isRecording()) {
if (imageSource->isVideoElement()) {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, scopedUsCounterVideoDisplayList,
new CustomCountHistogram("Blink.Canvas.DrawImage.Video.DisplayList",
0, 10000000, 50));
timer.emplace(scopedUsCounterVideoDisplayList);
} else if (imageSource->isCanvasElement()) {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, scopedUsCounterCanvasDisplayList,
new CustomCountHistogram("Blink.Canvas.DrawImage.Canvas.DisplayList",
0, 10000000, 50));
timer.emplace(scopedUsCounterCanvasDisplayList);
} else if (imageSource->isSVGSource()) {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, scopedUsCounterSVGDisplayList,
new CustomCountHistogram("Blink.Canvas.DrawImage.SVG.DisplayList", 0,
10000000, 50));
timer.emplace(scopedUsCounterSVGDisplayList);
} else if (imageSource->isImageBitmap()) {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, scopedUsCounterImageBitmapDisplayList,
new CustomCountHistogram(
"Blink.Canvas.DrawImage.ImageBitmap.DisplayList", 0, 10000000,
50));
timer.emplace(scopedUsCounterImageBitmapDisplayList);
} else {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, scopedUsCounterOthersDisplayList,
new CustomCountHistogram("Blink.Canvas.DrawImage.Others.DisplayList",
0, 10000000, 50));
timer.emplace(scopedUsCounterOthersDisplayList);
}
} else {
if (imageSource->isVideoElement()) {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, scopedUsCounterVideoCPU,
new CustomCountHistogram("Blink.Canvas.DrawImage.Video.CPU", 0,
10000000, 50));
timer.emplace(scopedUsCounterVideoCPU);
} else if (imageSource->isCanvasElement()) {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, scopedUsCounterCanvasCPU,
new CustomCountHistogram("Blink.Canvas.DrawImage.Canvas.CPU", 0,
10000000, 50));
timer.emplace(scopedUsCounterCanvasCPU);
} else if (imageSource->isSVGSource()) {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, scopedUsCounterSVGCPU,
new CustomCountHistogram("Blink.Canvas.DrawImage.SVG.CPU", 0,
10000000, 50));
timer.emplace(scopedUsCounterSVGCPU);
} else if (imageSource->isImageBitmap()) {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, scopedUsCounterImageBitmapCPU,
new CustomCountHistogram("Blink.Canvas.DrawImage.ImageBitmap.CPU", 0,
10000000, 50));
timer.emplace(scopedUsCounterImageBitmapCPU);
} else {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, scopedUsCounterOthersCPU,
new CustomCountHistogram("Blink.Canvas.DrawImage.Others.CPU", 0,
10000000, 50));
timer.emplace(scopedUsCounterOthersCPU);
}
}
draw(
[this, &imageSource, &image, &srcRect, dstRect](
SkCanvas* c, const SkPaint* paint) // draw lambda
{
drawImageInternal(c, imageSource, image.get(), srcRect, dstRect, paint);
},
[this, &dstRect](const SkIRect& clipBounds) // overdraw test lambda
{ return rectContainsTransformedRect(dstRect, clipBounds); },
dstRect, CanvasRenderingContext2DState::ImagePaintType,
imageSource->isOpaque() ? CanvasRenderingContext2DState::OpaqueImage
: CanvasRenderingContext2DState::NonOpaqueImage);
validateStateStack();
bool isExpensive = false;
if (ExpensiveCanvasHeuristicParameters::SVGImageSourcesAreExpensive &&
imageSource->isSVGSource())
isExpensive = true;
if (imageSize.width() * imageSize.height() >
width() * height() *
ExpensiveCanvasHeuristicParameters::ExpensiveImageSizeRatio)
isExpensive = true;
if (isExpensive) {
ImageBuffer* buffer = imageBuffer();
if (buffer)
buffer->setHasExpensiveOp();
}
if (originClean() && wouldTaintOrigin(imageSource, executionContext))
setOriginTainted();
}
void BaseRenderingContext2D::clearCanvas() {
FloatRect canvasRect(0, 0, width(), height());
checkOverdraw(canvasRect, 0, CanvasRenderingContext2DState::NoImage,
ClipFill);
SkCanvas* c = drawingCanvas();
if (c)
c->clear(hasAlpha() ? SK_ColorTRANSPARENT : SK_ColorBLACK);
}
bool BaseRenderingContext2D::rectContainsTransformedRect(
const FloatRect& rect,
const SkIRect& transformedRect) const {
FloatQuad quad(rect);
FloatQuad transformedQuad(FloatRect(transformedRect.x(), transformedRect.y(),
transformedRect.width(),
transformedRect.height()));
return state().transform().mapQuad(quad).containsQuad(transformedQuad);
}
CanvasGradient* BaseRenderingContext2D::createLinearGradient(double x0,
double y0,
double x1,
double y1) {
CanvasGradient* gradient =
CanvasGradient::create(FloatPoint(x0, y0), FloatPoint(x1, y1));
return gradient;
}
CanvasGradient* BaseRenderingContext2D::createRadialGradient(
double x0,
double y0,
double r0,
double x1,
double y1,
double r1,
ExceptionState& exceptionState) {
if (r0 < 0 || r1 < 0) {
exceptionState.throwDOMException(
IndexSizeError, String::format("The %s provided is less than 0.",
r0 < 0 ? "r0" : "r1"));
return nullptr;
}
CanvasGradient* gradient =
CanvasGradient::create(FloatPoint(x0, y0), r0, FloatPoint(x1, y1), r1);
return gradient;
}
CanvasPattern* BaseRenderingContext2D::createPattern(
ExecutionContext* executionContext,
const CanvasImageSourceUnion& imageSource,
const String& repetitionType,
ExceptionState& exceptionState) {
CanvasImageSource* imageSourceInternal =
toImageSourceInternal(imageSource, exceptionState);
if (!imageSourceInternal) {
return nullptr;
}
return createPattern(executionContext, imageSourceInternal, repetitionType,
exceptionState);
}
CanvasPattern* BaseRenderingContext2D::createPattern(
ExecutionContext* executionContext,
CanvasImageSource* imageSource,
const String& repetitionType,
ExceptionState& exceptionState) {
if (!imageSource) {
return nullptr;
}
Pattern::RepeatMode repeatMode =
CanvasPattern::parseRepetitionType(repetitionType, exceptionState);
if (exceptionState.hadException())
return nullptr;
SourceImageStatus status;
FloatSize defaultObjectSize(width(), height());
RefPtr<Image> imageForRendering = imageSource->getSourceImageForCanvas(
&status, PreferNoAcceleration, SnapshotReasonCreatePattern,
defaultObjectSize);
switch (status) {
case NormalSourceImageStatus:
break;
case ZeroSizeCanvasSourceImageStatus:
exceptionState.throwDOMException(
InvalidStateError,
String::format("The canvas %s is 0.",
imageSource->elementSize(defaultObjectSize).width()
? "height"
: "width"));
return nullptr;
case UndecodableSourceImageStatus:
exceptionState.throwDOMException(
InvalidStateError, "Source image is in the 'broken' state.");
return nullptr;
case InvalidSourceImageStatus:
imageForRendering = Image::nullImage();
break;
case IncompleteSourceImageStatus:
return nullptr;
default:
ASSERT_NOT_REACHED();
return nullptr;
}
ASSERT(imageForRendering);
bool originClean = !wouldTaintOrigin(imageSource, executionContext);
return CanvasPattern::create(imageForRendering.release(), repeatMode,
originClean);
}
bool BaseRenderingContext2D::computeDirtyRect(const FloatRect& localRect,
SkIRect* dirtyRect) {
SkIRect clipBounds;
if (!drawingCanvas()->getClipDeviceBounds(&clipBounds))
return false;
return computeDirtyRect(localRect, clipBounds, dirtyRect);
}
bool BaseRenderingContext2D::computeDirtyRect(
const FloatRect& localRect,
const SkIRect& transformedClipBounds,
SkIRect* dirtyRect) {
FloatRect canvasRect = state().transform().mapRect(localRect);
if (alphaChannel(state().shadowColor())) {
FloatRect shadowRect(canvasRect);
shadowRect.move(state().shadowOffset());
shadowRect.inflate(state().shadowBlur());
canvasRect.unite(shadowRect);
}
SkIRect canvasIRect;
static_cast<SkRect>(canvasRect).roundOut(&canvasIRect);
if (!canvasIRect.intersect(transformedClipBounds))
return false;
if (dirtyRect)
*dirtyRect = canvasIRect;
return true;
}
ImageData* BaseRenderingContext2D::createImageData(
ImageData* imageData,
ExceptionState& exceptionState) const {
ImageData* result = ImageData::create(imageData->size());
if (!result)
exceptionState.throwRangeError("Out of memory at ImageData creation");
return result;
}
ImageData* BaseRenderingContext2D::createImageData(
double sw,
double sh,
ExceptionState& exceptionState) const {
if (!sw || !sh) {
exceptionState.throwDOMException(
IndexSizeError,
String::format("The source %s is 0.", sw ? "height" : "width"));
return nullptr;
}
FloatSize logicalSize(fabs(sw), fabs(sh));
if (!logicalSize.isExpressibleAsIntSize())
return nullptr;
IntSize size = expandedIntSize(logicalSize);
if (size.width() < 1)
size.setWidth(1);
if (size.height() < 1)
size.setHeight(1);
ImageData* result = ImageData::create(size);
if (!result)
exceptionState.throwRangeError("Out of memory at ImageData creation");
return result;
}
ImageData* BaseRenderingContext2D::getImageData(
double sx,
double sy,
double sw,
double sh,
ExceptionState& exceptionState) const {
m_usageCounters.numGetImageDataCalls++;
m_usageCounters.areaGetImageDataCalls += sw * sh;
if (!originClean())
exceptionState.throwSecurityError(
"The canvas has been tainted by cross-origin data.");
else if (!sw || !sh)
exceptionState.throwDOMException(
IndexSizeError,
String::format("The source %s is 0.", sw ? "height" : "width"));
if (exceptionState.hadException())
return nullptr;
if (sw < 0) {
sx += sw;
sw = -sw;
}
if (sh < 0) {
sy += sh;
sh = -sh;
}
FloatRect logicalRect(sx, sy, sw, sh);
if (logicalRect.width() < 1)
logicalRect.setWidth(1);
if (logicalRect.height() < 1)
logicalRect.setHeight(1);
if (!logicalRect.isExpressibleAsIntRect())
return nullptr;
Optional<ScopedUsHistogramTimer> timer;
if (imageBuffer() && imageBuffer()->isAccelerated()) {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, scopedUsCounterGPU,
new CustomCountHistogram("Blink.Canvas.GetImageData.GPU", 0, 10000000,
50));
timer.emplace(scopedUsCounterGPU);
} else if (imageBuffer() && imageBuffer()->isRecording()) {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, scopedUsCounterDisplayList,
new CustomCountHistogram("Blink.Canvas.GetImageData.DisplayList", 0,
10000000, 50));
timer.emplace(scopedUsCounterDisplayList);
} else {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, scopedUsCounterCPU,
new CustomCountHistogram("Blink.Canvas.GetImageData.CPU", 0, 10000000,
50));
timer.emplace(scopedUsCounterCPU);
}
IntRect imageDataRect = enclosingIntRect(logicalRect);
ImageBuffer* buffer = imageBuffer();
if (!buffer || isContextLost()) {
ImageData* result = ImageData::create(imageDataRect.size());
if (!result)
exceptionState.throwRangeError("Out of memory at ImageData creation");
return result;
}
WTF::ArrayBufferContents contents;
if (!buffer->getImageData(Unmultiplied, imageDataRect, contents)) {
exceptionState.throwRangeError("Out of memory at ImageData creation");
return nullptr;
}
DOMArrayBuffer* arrayBuffer = DOMArrayBuffer::create(contents);
return ImageData::create(
imageDataRect.size(),
DOMUint8ClampedArray::create(arrayBuffer, 0, arrayBuffer->byteLength()));
}
void BaseRenderingContext2D::putImageData(ImageData* data,
double dx,
double dy,
ExceptionState& exceptionState) {
putImageData(data, dx, dy, 0, 0, data->width(), data->height(),
exceptionState);
}
void BaseRenderingContext2D::putImageData(ImageData* data,
double dx,
double dy,
double dirtyX,
double dirtyY,
double dirtyWidth,
double dirtyHeight,
ExceptionState& exceptionState) {
m_usageCounters.numPutImageDataCalls++;
m_usageCounters.areaPutImageDataCalls += dirtyWidth * dirtyHeight;
if (data->data()->bufferBase()->isNeutered()) {
exceptionState.throwDOMException(InvalidStateError,
"The source data has been neutered.");
return;
}
ImageBuffer* buffer = imageBuffer();
if (!buffer)
return;
if (dirtyWidth < 0) {
dirtyX += dirtyWidth;
dirtyWidth = -dirtyWidth;
}
if (dirtyHeight < 0) {
dirtyY += dirtyHeight;
dirtyHeight = -dirtyHeight;
}
FloatRect clipRect(dirtyX, dirtyY, dirtyWidth, dirtyHeight);
clipRect.intersect(IntRect(0, 0, data->width(), data->height()));
IntSize destOffset(static_cast<int>(dx), static_cast<int>(dy));
IntRect destRect = enclosingIntRect(clipRect);
destRect.move(destOffset);
destRect.intersect(IntRect(IntPoint(), buffer->size()));
if (destRect.isEmpty())
return;
Optional<ScopedUsHistogramTimer> timer;
if (imageBuffer() && imageBuffer()->isAccelerated()) {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, scopedUsCounterGPU,
new CustomCountHistogram("Blink.Canvas.PutImageData.GPU", 0, 10000000,
50));
timer.emplace(scopedUsCounterGPU);
} else if (imageBuffer() && imageBuffer()->isRecording()) {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, scopedUsCounterDisplayList,
new CustomCountHistogram("Blink.Canvas.PutImageData.DisplayList", 0,
10000000, 50));
timer.emplace(scopedUsCounterDisplayList);
} else {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, scopedUsCounterCPU,
new CustomCountHistogram("Blink.Canvas.PutImageData.CPU", 0, 10000000,
50));
timer.emplace(scopedUsCounterCPU);
}
IntRect sourceRect(destRect);
sourceRect.move(-destOffset);
checkOverdraw(destRect, 0, CanvasRenderingContext2DState::NoImage,
UntransformedUnclippedFill);
buffer->putByteArray(Unmultiplied, data->data()->data(),
IntSize(data->width(), data->height()), sourceRect,
IntPoint(destOffset));
didDraw(destRect);
}
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 root2 = sqrtf(2);
double delta = state().lineWidth() / 2;
if (state().getLineJoin() == MiterJoin)
delta *= state().miterLimit();
else if (state().getLineCap() == SquareCap)
delta *= root2;
rect.inflate(delta);
}
bool BaseRenderingContext2D::imageSmoothingEnabled() const {
return state().imageSmoothingEnabled();
}
void BaseRenderingContext2D::setImageSmoothingEnabled(bool enabled) {
if (enabled == state().imageSmoothingEnabled())
return;
modifiableState().setImageSmoothingEnabled(enabled);
}
String BaseRenderingContext2D::imageSmoothingQuality() const {
return state().imageSmoothingQuality();
}
void BaseRenderingContext2D::setImageSmoothingQuality(const String& quality) {
if (quality == state().imageSmoothingQuality())
return;
modifiableState().setImageSmoothingQuality(quality);
}
void BaseRenderingContext2D::checkOverdraw(
const SkRect& rect,
const SkPaint* paint,
CanvasRenderingContext2DState::ImageType imageType,
DrawType drawType) {
SkCanvas* c = drawingCanvas();
if (!c || !imageBuffer()->isRecording())
return;
SkRect deviceRect;
if (drawType == UntransformedUnclippedFill) {
deviceRect = rect;
} else {
ASSERT(drawType == ClipFill);
if (state().hasComplexClip())
return;
SkIRect skIBounds;
if (!c->getClipDeviceBounds(&skIBounds))
return;
deviceRect = SkRect::Make(skIBounds);
}
const SkImageInfo& imageInfo = c->imageInfo();
if (!deviceRect.contains(
SkRect::MakeWH(imageInfo.width(), imageInfo.height())))
return;
bool isSourceOver = true;
unsigned alpha = 0xFF;
if (paint) {
if (paint->getLooper() || paint->getImageFilter() || paint->getMaskFilter())
return;
SkXfermode* xfermode = paint->getXfermode();
if (xfermode) {
SkXfermode::Mode mode;
if (xfermode->asMode(&mode)) {
isSourceOver = mode == SkXfermode::kSrcOver_Mode;
if (!isSourceOver && mode != SkXfermode::kSrc_Mode &&
mode != SkXfermode::kClear_Mode)
return; // The code below only knows how to handle Src, SrcOver, and
// Clear
} else {
// unknown xfermode
ASSERT_NOT_REACHED();
return;
}
}
alpha = paint->getAlpha();
if (isSourceOver && imageType == CanvasRenderingContext2DState::NoImage) {
SkShader* shader = paint->getShader();
if (shader) {
if (shader->isOpaque() && alpha == 0xFF)
imageBuffer()->willOverwriteCanvas();
return;
}
}
}
if (isSourceOver) {
// With source over, we need to certify that alpha == 0xFF for all pixels
if (imageType == CanvasRenderingContext2DState::NonOpaqueImage)
return;
if (alpha < 0xFF)
return;
}
imageBuffer()->willOverwriteCanvas();
}
void BaseRenderingContext2D::trackDrawCall(DrawCallType callType,
Path2D* path2d,
int width,
int height) {
if (!RuntimeEnabledFeatures::
enableCanvas2dDynamicRenderingModeSwitchingEnabled()) {
// Rendering mode switching is disabled so no need to track the usage
return;
}
m_usageCounters.numDrawCalls[callType]++;
float boundingRectWidth = static_cast<float>(width);
float boundingRectHeight = static_cast<float>(height);
float boundingRectArea = boundingRectWidth * boundingRectHeight;
float boundingRectPerimeter =
(2.0 * boundingRectWidth) + (2.0 * boundingRectHeight);
if (callType == FillText || callType == FillPath || callType == StrokeText ||
callType == StrokePath || callType == FillRect ||
callType == StrokeRect) {
SkPath skPath;
if (path2d) {
skPath = path2d->path().getSkPath();
} else {
skPath = m_path.getSkPath();
}
if (!(callType == FillRect || callType == StrokeRect ||
callType == DrawVectorImage || callType == DrawBitmapImage)) {
// The correct width and height were not passed as parameters
const SkRect& boundingRect = skPath.getBounds();
boundingRectWidth = static_cast<float>(std::abs(boundingRect.width()));
boundingRectHeight = static_cast<float>(std::abs(boundingRect.height()));
boundingRectArea = boundingRectWidth * boundingRectHeight;
boundingRectPerimeter =
2.0 * boundingRectWidth + 2.0 * boundingRectHeight;
}
if (callType == FillPath &&
skPath.getConvexity() != SkPath::kConvex_Convexity) {
m_usageCounters.numNonConvexFillPathCalls++;
m_usageCounters.nonConvexFillPathArea += boundingRectArea;
}
m_usageCounters.boundingBoxPerimeterDrawCalls[callType] +=
boundingRectPerimeter;
m_usageCounters.boundingBoxAreaDrawCalls[callType] += boundingRectArea;
CanvasStyle* canvasStyle;
if (callType == FillText || callType == FillPath || callType == FillRect) {
canvasStyle = state().fillStyle();
} else {
canvasStyle = state().strokeStyle();
}
CanvasGradient* gradient = canvasStyle->getCanvasGradient();
if (gradient) {
if (gradient->getGradient()->isRadial()) {
m_usageCounters.numRadialGradients++;
m_usageCounters.boundingBoxAreaFillType
[BaseRenderingContext2D::RadialGradientFillType] +=
boundingRectArea;
} else {
m_usageCounters.numLinearGradients++;
m_usageCounters.boundingBoxAreaFillType
[BaseRenderingContext2D::LinearGradientFillType] +=
boundingRectArea;
}
} else if (canvasStyle->getCanvasPattern()) {
m_usageCounters.numPatterns++;
m_usageCounters
.boundingBoxAreaFillType[BaseRenderingContext2D::PatternFillType] +=
boundingRectArea;
} else {
m_usageCounters
.boundingBoxAreaFillType[BaseRenderingContext2D::ColorFillType] +=
boundingRectArea;
}
}
if (callType == DrawVectorImage || callType == DrawBitmapImage) {
m_usageCounters.boundingBoxPerimeterDrawCalls[callType] +=
boundingRectPerimeter;
m_usageCounters.boundingBoxAreaDrawCalls[callType] += boundingRectArea;
}
if (callType == FillText || callType == FillPath || callType == StrokeText ||
callType == StrokePath || callType == FillRect ||
callType == StrokeRect || callType == DrawVectorImage ||
callType == DrawBitmapImage) {
if (state().shadowBlur() > 0.0 && SkColorGetA(state().shadowColor()) > 0) {
m_usageCounters.numBlurredShadows++;
m_usageCounters.boundingBoxAreaTimesShadowBlurSquared +=
boundingRectArea * state().shadowBlur() * state().shadowBlur();
m_usageCounters.boundingBoxPerimeterTimesShadowBlurSquared +=
boundingRectPerimeter * state().shadowBlur() * state().shadowBlur();
}
}
if (state().hasComplexClip()) {
m_usageCounters.numDrawWithComplexClips++;
}
if (stateHasFilter()) {
m_usageCounters.numFilters++;
}
}
const BaseRenderingContext2D::UsageCounters&
BaseRenderingContext2D::getUsage() {
return m_usageCounters;
}
DEFINE_TRACE(BaseRenderingContext2D) {
visitor->trace(m_stateStack);
}
BaseRenderingContext2D::UsageCounters::UsageCounters()
: numDrawCalls{0, 0, 0, 0, 0, 0, 0},
boundingBoxPerimeterDrawCalls{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
boundingBoxAreaDrawCalls{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
boundingBoxAreaFillType{0.0f, 0.0f, 0.0f, 0.0f},
numNonConvexFillPathCalls(0),
nonConvexFillPathArea(0.0f),
numRadialGradients(0),
numLinearGradients(0),
numPatterns(0),
numDrawWithComplexClips(0),
numBlurredShadows(0),
boundingBoxAreaTimesShadowBlurSquared(0.0f),
boundingBoxPerimeterTimesShadowBlurSquared(0.0f),
numFilters(0),
numGetImageDataCalls(0),
areaGetImageDataCalls(0.0),
numPutImageDataCalls(0),
areaPutImageDataCalls(0.0),
numClearRectCalls(0),
numDrawFocusCalls(0),
numFramesSinceReset(0) {}
float BaseRenderingContext2D::estimateRenderingCost(
ExpensiveCanvasHeuristicParameters::RenderingModeCostIndex index) const {
float basicCostOfDrawCalls =
ExpensiveCanvasHeuristicParameters::FillRectFixedCost[index] *
m_usageCounters.numDrawCalls[BaseRenderingContext2D::FillRect] +
ExpensiveCanvasHeuristicParameters::FillConvexPathFixedCost[index] *
(m_usageCounters.numDrawCalls[BaseRenderingContext2D::FillPath] -
m_usageCounters.numNonConvexFillPathCalls) +
ExpensiveCanvasHeuristicParameters::FillNonConvexPathFixedCost[index] *
m_usageCounters.numNonConvexFillPathCalls +
ExpensiveCanvasHeuristicParameters::FillTextFixedCost[index] *
m_usageCounters.numDrawCalls[BaseRenderingContext2D::FillText] +
ExpensiveCanvasHeuristicParameters::StrokeRectFixedCost[index] *
m_usageCounters.numDrawCalls[BaseRenderingContext2D::StrokeRect] +
ExpensiveCanvasHeuristicParameters::StrokePathFixedCost[index] *
m_usageCounters.numDrawCalls[BaseRenderingContext2D::StrokePath] +
ExpensiveCanvasHeuristicParameters::StrokeTextFixedCost[index] *
m_usageCounters.numDrawCalls[BaseRenderingContext2D::StrokeText] +
ExpensiveCanvasHeuristicParameters::FillRectVariableCostPerArea[index] *
m_usageCounters
.boundingBoxAreaDrawCalls[BaseRenderingContext2D::FillRect] +
ExpensiveCanvasHeuristicParameters::FillConvexPathVariableCostPerArea
[index] *
(m_usageCounters
.boundingBoxAreaDrawCalls[BaseRenderingContext2D::FillPath] -
m_usageCounters.nonConvexFillPathArea) +
ExpensiveCanvasHeuristicParameters::FillNonConvexPathVariableCostPerArea
[index] *
m_usageCounters.nonConvexFillPathArea +
ExpensiveCanvasHeuristicParameters::FillTextVariableCostPerArea[index] *
m_usageCounters
.boundingBoxAreaDrawCalls[BaseRenderingContext2D::FillText] +
ExpensiveCanvasHeuristicParameters::StrokeRectVariableCostPerArea[index] *
m_usageCounters
.boundingBoxAreaDrawCalls[BaseRenderingContext2D::StrokeRect] +
ExpensiveCanvasHeuristicParameters::StrokePathVariableCostPerArea[index] *
m_usageCounters
.boundingBoxAreaDrawCalls[BaseRenderingContext2D::StrokePath] +
ExpensiveCanvasHeuristicParameters::StrokeTextVariableCostPerArea[index] *
m_usageCounters
.boundingBoxAreaDrawCalls[BaseRenderingContext2D::StrokeText] +
ExpensiveCanvasHeuristicParameters::PutImageDataFixedCost[index] *
m_usageCounters.numPutImageDataCalls +
ExpensiveCanvasHeuristicParameters::PutImageDataVariableCostPerArea
[index] *
m_usageCounters.areaPutImageDataCalls +
ExpensiveCanvasHeuristicParameters::DrawSVGImageFixedCost[index] *
m_usageCounters
.numDrawCalls[BaseRenderingContext2D::DrawVectorImage] +
ExpensiveCanvasHeuristicParameters::DrawPNGImageFixedCost[index] *
m_usageCounters
.numDrawCalls[BaseRenderingContext2D::DrawBitmapImage] +
ExpensiveCanvasHeuristicParameters::DrawSVGImageVariableCostPerArea
[index] *
m_usageCounters.boundingBoxAreaDrawCalls
[BaseRenderingContext2D::DrawVectorImage] +
ExpensiveCanvasHeuristicParameters::DrawPNGImageVariableCostPerArea
[index] *
m_usageCounters.boundingBoxAreaDrawCalls
[BaseRenderingContext2D::DrawBitmapImage];
float fillTypeAdjustment =
ExpensiveCanvasHeuristicParameters::PatternFillTypeFixedCost[index] *
m_usageCounters.numPatterns +
ExpensiveCanvasHeuristicParameters::LinearGradientFillTypeFixedCost
[index] *
m_usageCounters.numLinearGradients +
ExpensiveCanvasHeuristicParameters::RadialGradientFillTypeFixedCost
[index] *
m_usageCounters.numRadialGradients +
ExpensiveCanvasHeuristicParameters::PatternFillTypeVariableCostPerArea
[index] *
m_usageCounters.boundingBoxAreaFillType
[BaseRenderingContext2D::PatternFillType] +
ExpensiveCanvasHeuristicParameters::LinearGradientFillVariableCostPerArea
[index] *
m_usageCounters.boundingBoxAreaFillType
[BaseRenderingContext2D::LinearGradientFillType] +
ExpensiveCanvasHeuristicParameters::RadialGradientFillVariableCostPerArea
[index] *
m_usageCounters.boundingBoxAreaFillType
[BaseRenderingContext2D::RadialGradientFillType];
float shadowAdjustment =
ExpensiveCanvasHeuristicParameters::ShadowFixedCost[index] *
m_usageCounters.numBlurredShadows +
ExpensiveCanvasHeuristicParameters::
ShadowVariableCostPerAreaTimesShadowBlurSquared[index] *
m_usageCounters.boundingBoxAreaTimesShadowBlurSquared;
return basicCostOfDrawCalls + fillTypeAdjustment + shadowAdjustment;
}
} // namespace blink