| Index: third_party/WebKit/Source/modules/canvas2d/BaseRenderingContext2D.cpp
|
| diff --git a/third_party/WebKit/Source/modules/canvas2d/BaseRenderingContext2D.cpp b/third_party/WebKit/Source/modules/canvas2d/BaseRenderingContext2D.cpp
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..37810e2b7347bffc04d3d42e82e3a297615a7967
|
| --- /dev/null
|
| +++ b/third_party/WebKit/Source/modules/canvas2d/BaseRenderingContext2D.cpp
|
| @@ -0,0 +1,1350 @@
|
| +// 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 "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 "modules/canvas2d/CanvasGradient.h"
|
| +#include "modules/canvas2d/CanvasPattern.h"
|
| +#include "modules/canvas2d/CanvasStyle.h"
|
| +#include "modules/canvas2d/Path2D.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"
|
| +#include "third_party/skia/include/core/SkImageFilter.h"
|
| +
|
| +namespace blink {
|
| +
|
| +static const double cDeviceScaleFactor = 1.0; // Canvas is device independent
|
| +
|
| +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();
|
| +}
|
| +
|
| +static inline void convertCanvasStyleToUnionType(CanvasStyle* style, StringOrCanvasGradientOrCanvasPattern& returnValue)
|
| +{
|
| + if (CanvasGradient* gradient = style->canvasGradient()) {
|
| + returnValue.setCanvasGradient(gradient);
|
| + return;
|
| + }
|
| + if (CanvasPattern* pattern = style->canvasPattern()) {
|
| + 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);
|
| +}
|
| +
|
| +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->pattern()->isTextureBacked())
|
| + disableDeferral(DisableDeferralReasonUsingTextureBackedPattern);
|
| + canvasStyle = CanvasStyle::createFromPattern(canvasPattern);
|
| + }
|
| +
|
| + ASSERT(canvasStyle);
|
| + modifiableState().setFillStyle(canvasStyle);
|
| + modifiableState().setUnparsedFillColor(colorString);
|
| +}
|
| +
|
| +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().lineCap());
|
| +}
|
| +
|
| +void BaseRenderingContext2D::setLineCap(const String& s)
|
| +{
|
| + LineCap cap;
|
| + if (!parseLineCap(s, cap))
|
| + return;
|
| + if (state().lineCap() == cap)
|
| + return;
|
| + modifiableState().setLineCap(cap);
|
| +}
|
| +
|
| +String BaseRenderingContext2D::lineJoin() const
|
| +{
|
| + return lineJoinName(state().lineJoin());
|
| +}
|
| +
|
| +void BaseRenderingContext2D::setLineJoin(const String& s)
|
| +{
|
| + LineJoin join;
|
| + if (!parseLineJoin(s, join))
|
| + return;
|
| + if (state().lineJoin() == 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;
|
| +
|
| + RefPtrWillBeRawPtr<CSSValue> filterValue = CSSParser::parseSingleValue(CSSPropertyWebkitFilter, filterString, CSSParserContext(HTMLStandardMode, 0));
|
| +
|
| + if (!filterValue || filterValue->isInitialValue() || filterValue->isInheritedValue())
|
| + return;
|
| +
|
| + modifiableState().setUnparsedFilter(filterString);
|
| + modifiableState().setFilter(filterValue.release());
|
| +}
|
| +
|
| +PassRefPtrWillBeRawPtr<SVGMatrixTearOff> BaseRenderingContext2D::currentTransform() const
|
| +{
|
| + return SVGMatrixTearOff::create(state().transform());
|
| +}
|
| +
|
| +void BaseRenderingContext2D::setCurrentTransform(PassRefPtrWillBeRawPtr<SVGMatrixTearOff> passMatrixTearOff)
|
| +{
|
| + RefPtrWillBeRawPtr<SVGMatrixTearOff> matrixTearOff = passMatrixTearOff;
|
| + 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;
|
| +}
|
| +
|
| +void BaseRenderingContext2D::drawForText(const Font& font, const TextRunPaintInfo& textRunPaintInfo, const FloatPoint& location, CanvasRenderingContext2DState::PaintType paintType)
|
| +{
|
| + draw(
|
| + [&font, this, &textRunPaintInfo, &location](SkCanvas* c, const SkPaint* paint) // draw lambda
|
| + {
|
| + font.drawBidiText(c, textRunPaintInfo, location, Font::UseFallbackIfFontNotReady, cDeviceScaleFactor, *paint);
|
| + },
|
| + [](const SkIRect& rect) // overdraw test lambda
|
| + {
|
| + return false;
|
| + },
|
| + textRunPaintInfo.bounds, paintType);
|
| +}
|
| +
|
| +static bool 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;
|
| +}
|
| +
|
| +template<typename DrawFunc>
|
| +void BaseRenderingContext2D::compositedDraw(const DrawFunc& drawFunc, SkCanvas* c, CanvasRenderingContext2DState::PaintType paintType, CanvasRenderingContext2DState::ImageType imageType)
|
| +{
|
| + SkImageFilter* filter = stateGetFilter();
|
| + ASSERT(isFullCanvasCompositeMode(state().globalComposite()) || filter);
|
| + SkMatrix ctm = c->getTotalMatrix();
|
| + c->resetMatrix();
|
| + SkPaint compositePaint;
|
| + compositePaint.setXfermodeMode(state().globalComposite());
|
| + if (state().shouldDrawShadows()) {
|
| + // unroll into two independently composited passes if drawing shadows
|
| + SkPaint shadowPaint = *state().getPaint(paintType, DrawShadowOnly, imageType);
|
| + int saveCount = c->getSaveCount();
|
| + if (filter) {
|
| + SkPaint filterPaint;
|
| + filterPaint.setImageFilter(filter);
|
| + // TODO(junov): crbug.com/502921 We could use primitive bounds if we knew that the filter
|
| + // does not affect transparent black regions.
|
| + c->saveLayer(nullptr, &shadowPaint);
|
| + c->saveLayer(nullptr, &filterPaint);
|
| + SkPaint foregroundPaint = *state().getPaint(paintType, DrawForegroundOnly, imageType);
|
| + c->setMatrix(ctm);
|
| + drawFunc(c, &foregroundPaint);
|
| + } else {
|
| + ASSERT(isFullCanvasCompositeMode(state().globalComposite()));
|
| + c->saveLayer(nullptr, &compositePaint);
|
| + shadowPaint.setXfermodeMode(SkXfermode::kSrcOver_Mode);
|
| + c->setMatrix(ctm);
|
| + drawFunc(c, &shadowPaint);
|
| + }
|
| + c->restoreToCount(saveCount);
|
| + }
|
| +
|
| + compositePaint.setImageFilter(filter);
|
| + // TODO(junov): crbug.com/502921 We could use primitive bounds if we knew that the filter
|
| + // does not affect transparent black regions *and* !isFullCanvasCompositeMode
|
| + c->saveLayer(nullptr, &compositePaint);
|
| + SkPaint foregroundPaint = *state().getPaint(paintType, DrawForegroundOnly, imageType);
|
| + foregroundPaint.setXfermodeMode(SkXfermode::kSrcOver_Mode);
|
| + c->setMatrix(ctm);
|
| + drawFunc(c, &foregroundPaint);
|
| + c->restore();
|
| + c->setMatrix(ctm);
|
| +}
|
| +
|
| +template<typename DrawFunc, typename ContainsFunc>
|
| +bool BaseRenderingContext2D::draw(const DrawFunc& drawFunc, const ContainsFunc& drawCoversClipBounds, const SkRect& bounds, CanvasRenderingContext2DState::PaintType paintType, CanvasRenderingContext2DState::ImageType imageType)
|
| +{
|
| + if (!state().isTransformInvertible())
|
| + return false;
|
| +
|
| + SkIRect clipBounds;
|
| + if (!drawingCanvas() || !drawingCanvas()->getClipDeviceBounds(&clipBounds))
|
| + return false;
|
| +
|
| + // If gradient size is zero, then paint nothing.
|
| + CanvasStyle* style = state().style(paintType);
|
| + if (style) {
|
| + CanvasGradient* gradient = style->canvasGradient();
|
| + if (gradient && gradient->gradient()->isZeroSize())
|
| + return false;
|
| + }
|
| +
|
| + if (isFullCanvasCompositeMode(state().globalComposite()) || stateHasFilter()) {
|
| + compositedDraw(drawFunc, drawingCanvas(), paintType, imageType);
|
| + didDraw(clipBounds);
|
| + } else if (state().globalComposite() == SkXfermode::kSrc_Mode) {
|
| + clearCanvas(); // takes care of checkOverdraw()
|
| + const SkPaint* paint = state().getPaint(paintType, DrawForegroundOnly, imageType);
|
| + drawFunc(drawingCanvas(), paint);
|
| + didDraw(clipBounds);
|
| + } else {
|
| + SkIRect dirtyRect;
|
| + if (computeDirtyRect(bounds, clipBounds, &dirtyRect)) {
|
| + const SkPaint* paint = state().getPaint(paintType, DrawShadowAndForeground, imageType);
|
| + if (paintType != CanvasRenderingContext2DState::StrokePaintType && drawCoversClipBounds(clipBounds))
|
| + checkOverdraw(bounds, paint, imageType, ClipFill);
|
| + drawFunc(drawingCanvas(), paint);
|
| + didDraw(dirtyRect);
|
| + }
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +static bool isPathExpensive(const Path& path)
|
| +{
|
| + const SkPath& skPath = path.skPath();
|
| + 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.skPath();
|
| + 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)
|
| +{
|
| + drawPathInternal(m_path, CanvasRenderingContext2DState::FillPaintType, parseWinding(windingRuleString));
|
| +}
|
| +
|
| +void BaseRenderingContext2D::fill(Path2D* domPath, const String& windingRuleString)
|
| +{
|
| + drawPathInternal(domPath->path(), CanvasRenderingContext2DState::FillPaintType, parseWinding(windingRuleString));
|
| +}
|
| +
|
| +void BaseRenderingContext2D::stroke()
|
| +{
|
| + drawPathInternal(m_path, CanvasRenderingContext2DState::StrokePaintType);
|
| +}
|
| +
|
| +void BaseRenderingContext2D::stroke(Path2D* domPath)
|
| +{
|
| + drawPathInternal(domPath->path(), CanvasRenderingContext2DState::StrokePaintType);
|
| +}
|
| +
|
| +void BaseRenderingContext2D::fillRect(double x, double y, double width, double 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)
|
| +{
|
| + 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.skPath();
|
| + 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().lineCap());
|
| + strokeData.setLineJoin(state().lineJoin());
|
| + 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)
|
| +{
|
| + 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)
|
| +{
|
| + if (value.isHTMLImageElement())
|
| + return value.getAsHTMLImageElement().get();
|
| + if (value.isHTMLVideoElement())
|
| + return value.getAsHTMLVideoElement().get();
|
| + if (value.isHTMLCanvasElement())
|
| + return value.getAsHTMLCanvasElement().get();
|
| + if (value.isImageBitmap())
|
| + return value.getAsImageBitmap().get();
|
| + ASSERT_NOT_REACHED();
|
| + return nullptr;
|
| +}
|
| +
|
| +void BaseRenderingContext2D::drawImage(const CanvasImageSourceUnion& imageSource, double x, double y, ExceptionState& exceptionState)
|
| +{
|
| + CanvasImageSource* imageSourceInternal = toImageSourceInternal(imageSource);
|
| + FloatSize sourceRectSize = imageSourceInternal->elementSize();
|
| + FloatSize destRectSize = imageSourceInternal->defaultDestinationSize();
|
| + drawImage(imageSourceInternal, 0, 0, sourceRectSize.width(), sourceRectSize.height(), x, y, destRectSize.width(), destRectSize.height(), exceptionState);
|
| +}
|
| +
|
| +void BaseRenderingContext2D::drawImage(const CanvasImageSourceUnion& imageSource,
|
| + double x, double y, double width, double height, ExceptionState& exceptionState)
|
| +{
|
| + CanvasImageSource* imageSourceInternal = toImageSourceInternal(imageSource);
|
| + FloatSize sourceRectSize = imageSourceInternal->elementSize();
|
| + drawImage(imageSourceInternal, 0, 0, sourceRectSize.width(), sourceRectSize.height(), x, y, width, height, exceptionState);
|
| +}
|
| +
|
| +void BaseRenderingContext2D::drawImage(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);
|
| + drawImage(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;
|
| +}
|
| +
|
| +void BaseRenderingContext2D::drawImageInternal(SkCanvas* c, CanvasImageSource* imageSource, Image* image, const FloatRect& srcRect, const FloatRect& dstRect, const SkPaint* paint)
|
| +{
|
| + int initialSaveCount = c->getSaveCount();
|
| + SkPaint imagePaint = *paint;
|
| +
|
| + if (paint->getImageFilter()) {
|
| + SkMatrix invCtm;
|
| + if (!c->getTotalMatrix().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;
|
| + }
|
| + SkRect bounds = dstRect;
|
| + SkPaint layerPaint;
|
| + layerPaint.setXfermode(paint->getXfermode());
|
| + SkAutoTUnref<SkImageFilter> localFilter(paint->getImageFilter()->newWithLocalMatrix(invCtm));
|
| + layerPaint.setImageFilter(localFilter);
|
| + c->saveLayer(&bounds, &layerPaint);
|
| + imagePaint.setXfermodeMode(SkXfermode::kSrcOver_Mode);
|
| + imagePaint.setImageFilter(nullptr);
|
| + }
|
| +
|
| + 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(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;
|
| + SourceImageStatus sourceImageStatus = InvalidSourceImageStatus;
|
| + if (!imageSource->isVideoElement()) {
|
| + AccelerationHint hint = imageBuffer()->isAccelerated() ? PreferAcceleration : PreferNoAcceleration;
|
| + image = imageSource->getSourceImageForCanvas(&sourceImageStatus, hint, SnapshotReasonDrawImage);
|
| + 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));
|
| +
|
| + clipRectsToImageRect(FloatRect(FloatPoint(), imageSource->elementSize()), &srcRect, &dstRect);
|
| +
|
| + imageSource->adjustDrawRects(&srcRect, &dstRect);
|
| +
|
| + if (srcRect.isEmpty())
|
| + return;
|
| +
|
| + DisableDeferralReason reason = DisableDeferralReasonUnknown;
|
| + if (shouldDisableDeferral(imageSource, &reason) || image->isTextureBacked())
|
| + disableDeferral(reason);
|
| +
|
| + validateStateStack();
|
| +
|
| + 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 (imageSource->elementSize().width() * imageSource->elementSize().height() > width() * height() * ExpensiveCanvasHeuristicParameters::ExpensiveImageSizeRatio)
|
| + isExpensive = true;
|
| +
|
| + if (isExpensive) {
|
| + ImageBuffer* buffer = imageBuffer();
|
| + if (buffer)
|
| + buffer->setHasExpensiveOp();
|
| + }
|
| +
|
| + if (imageSource->isCanvasElement() && static_cast<HTMLCanvasElement*>(imageSource)->is3D()) {
|
| + // WebGL to 2D canvas: must flush graphics context to prevent a race
|
| + // FIXME: crbug.com/516331 Fix the underlying synchronization issue so this flush can be eliminated.
|
| + imageBuffer()->flushGpu(FlushReasonDrawImageOfWebGL);
|
| + }
|
| +
|
| + if (originClean() && wouldTaintOrigin(imageSource))
|
| + 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(const CanvasImageSourceUnion& imageSource, const String& repetitionType, ExceptionState& exceptionState)
|
| +{
|
| + Pattern::RepeatMode repeatMode = CanvasPattern::parseRepetitionType(repetitionType, exceptionState);
|
| + if (exceptionState.hadException())
|
| + return nullptr;
|
| +
|
| + SourceImageStatus status;
|
| + CanvasImageSource* imageSourceInternal = toImageSourceInternal(imageSource);
|
| + RefPtr<Image> imageForRendering = imageSourceInternal->getSourceImageForCanvas(&status, PreferNoAcceleration, SnapshotReasonCreatePattern);
|
| +
|
| + switch (status) {
|
| + case NormalSourceImageStatus:
|
| + break;
|
| + case ZeroSizeCanvasSourceImageStatus:
|
| + exceptionState.throwDOMException(InvalidStateError, String::format("The canvas %s is 0.", imageSourceInternal->elementSize().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(imageSourceInternal);
|
| +
|
| + 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;
|
| +}
|
| +
|
| +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().lineJoin() == MiterJoin)
|
| + delta *= state().miterLimit();
|
| + else if (state().lineCap() == 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();
|
| +}
|
| +
|
| +DEFINE_TRACE(BaseRenderingContext2D)
|
| +{
|
| + visitor->trace(m_stateStack);
|
| +}
|
| +
|
| +} // namespace blink
|
|
|