blob: fafdea7a4a84eeb1430ede6d12dc0e3202c61e0c [file] [log] [blame]
// Copyright 2015 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/CanvasRenderingContext2DState.h"
#include "core/css/CSSFontSelector.h"
#include "core/css/resolver/FilterOperationResolver.h"
#include "core/css/resolver/StyleBuilder.h"
#include "core/css/resolver/StyleResolverState.h"
#include "core/html/HTMLCanvasElement.h"
#include "core/paint/FilterEffectBuilder.h"
#include "core/style/ComputedStyle.h"
#include "core/style/FilterOperation.h"
#include "core/svg/SVGFilterElement.h"
#include "modules/canvas2d/CanvasGradient.h"
#include "modules/canvas2d/CanvasPattern.h"
#include "modules/canvas2d/CanvasRenderingContext2D.h"
#include "modules/canvas2d/CanvasStyle.h"
#include "platform/graphics/DrawLooperBuilder.h"
#include "platform/graphics/filters/FilterEffect.h"
#include "platform/graphics/filters/SkiaImageFilterBuilder.h"
#include "platform/graphics/paint/PaintCanvas.h"
#include "platform/graphics/paint/PaintFlags.h"
#include "platform/graphics/skia/SkiaUtils.h"
#include "third_party/skia/include/effects/SkDashPathEffect.h"
#include "third_party/skia/include/effects/SkDropShadowImageFilter.h"
#include <memory>
static const char defaultFont[] = "10px sans-serif";
static const char defaultFilter[] = "none";
namespace blink {
CanvasRenderingContext2DState::CanvasRenderingContext2DState()
: m_unrealizedSaveCount(0),
m_strokeStyle(CanvasStyle::createFromRGBA(SK_ColorBLACK)),
m_fillStyle(CanvasStyle::createFromRGBA(SK_ColorBLACK)),
m_shadowBlur(0),
m_shadowColor(Color::transparent),
m_globalAlpha(1),
m_lineDashOffset(0),
m_unparsedFont(defaultFont),
m_unparsedFilter(defaultFilter),
m_textAlign(StartTextAlign),
m_textBaseline(AlphabeticTextBaseline),
m_direction(DirectionInherit),
m_realizedFont(false),
m_isTransformInvertible(true),
m_hasClip(false),
m_hasComplexClip(false),
m_fillStyleDirty(true),
m_strokeStyleDirty(true),
m_lineDashDirty(false),
m_imageSmoothingQuality(kLow_SkFilterQuality) {
m_fillFlags.setStyle(PaintFlags::kFill_Style);
m_fillFlags.setAntiAlias(true);
m_imageFlags.setStyle(PaintFlags::kFill_Style);
m_imageFlags.setAntiAlias(true);
m_strokeFlags.setStyle(PaintFlags::kStroke_Style);
m_strokeFlags.setStrokeWidth(1);
m_strokeFlags.setStrokeCap(PaintFlags::kButt_Cap);
m_strokeFlags.setStrokeMiter(10);
m_strokeFlags.setStrokeJoin(PaintFlags::kMiter_Join);
m_strokeFlags.setAntiAlias(true);
setImageSmoothingEnabled(true);
}
CanvasRenderingContext2DState::CanvasRenderingContext2DState(
const CanvasRenderingContext2DState& other,
ClipListCopyMode mode)
: CSSFontSelectorClient(),
m_unrealizedSaveCount(other.m_unrealizedSaveCount),
m_unparsedStrokeColor(other.m_unparsedStrokeColor),
m_unparsedFillColor(other.m_unparsedFillColor),
m_strokeStyle(other.m_strokeStyle),
m_fillStyle(other.m_fillStyle),
m_strokeFlags(other.m_strokeFlags),
m_fillFlags(other.m_fillFlags),
m_imageFlags(other.m_imageFlags),
m_shadowOffset(other.m_shadowOffset),
m_shadowBlur(other.m_shadowBlur),
m_shadowColor(other.m_shadowColor),
m_emptyDrawLooper(other.m_emptyDrawLooper),
m_shadowOnlyDrawLooper(other.m_shadowOnlyDrawLooper),
m_shadowAndForegroundDrawLooper(other.m_shadowAndForegroundDrawLooper),
m_shadowOnlyImageFilter(other.m_shadowOnlyImageFilter),
m_shadowAndForegroundImageFilter(other.m_shadowAndForegroundImageFilter),
m_globalAlpha(other.m_globalAlpha),
m_transform(other.m_transform),
m_lineDash(other.m_lineDash),
m_lineDashOffset(other.m_lineDashOffset),
m_unparsedFont(other.m_unparsedFont),
m_font(other.m_font),
m_fontForFilter(other.m_fontForFilter),
m_unparsedFilter(other.m_unparsedFilter),
m_filterValue(other.m_filterValue),
m_resolvedFilter(other.m_resolvedFilter),
m_textAlign(other.m_textAlign),
m_textBaseline(other.m_textBaseline),
m_direction(other.m_direction),
m_realizedFont(other.m_realizedFont),
m_isTransformInvertible(other.m_isTransformInvertible),
m_hasClip(other.m_hasClip),
m_hasComplexClip(other.m_hasComplexClip),
m_fillStyleDirty(other.m_fillStyleDirty),
m_strokeStyleDirty(other.m_strokeStyleDirty),
m_lineDashDirty(other.m_lineDashDirty),
m_imageSmoothingEnabled(other.m_imageSmoothingEnabled),
m_imageSmoothingQuality(other.m_imageSmoothingQuality) {
if (mode == CopyClipList) {
m_clipList = other.m_clipList;
}
if (m_realizedFont)
static_cast<CSSFontSelector*>(m_font.getFontSelector())
->registerForInvalidationCallbacks(this);
}
CanvasRenderingContext2DState::~CanvasRenderingContext2DState() {}
void CanvasRenderingContext2DState::fontsNeedUpdate(
CSSFontSelector* fontSelector) {
DCHECK_EQ(fontSelector, m_font.getFontSelector());
DCHECK(m_realizedFont);
m_font.update(fontSelector);
// FIXME: We only really need to invalidate the resolved filter if the font
// update above changed anything and the filter uses font-dependent units.
m_resolvedFilter.reset();
}
DEFINE_TRACE(CanvasRenderingContext2DState) {
visitor->trace(m_strokeStyle);
visitor->trace(m_fillStyle);
visitor->trace(m_filterValue);
CSSFontSelectorClient::trace(visitor);
}
void CanvasRenderingContext2DState::setLineDashOffset(double offset) {
m_lineDashOffset = offset;
m_lineDashDirty = true;
}
void CanvasRenderingContext2DState::setLineDash(const Vector<double>& dash) {
m_lineDash = dash;
// Spec requires the concatenation of two copies the dash list when the
// number of elements is odd
if (dash.size() % 2)
m_lineDash.appendVector(dash);
m_lineDashDirty = true;
}
static bool hasANonZeroElement(const Vector<double>& lineDash) {
for (size_t i = 0; i < lineDash.size(); i++) {
if (lineDash[i] != 0.0)
return true;
}
return false;
}
void CanvasRenderingContext2DState::updateLineDash() const {
if (!m_lineDashDirty)
return;
if (!hasANonZeroElement(m_lineDash)) {
m_strokeFlags.setPathEffect(0);
} else {
Vector<float> lineDash(m_lineDash.size());
std::copy(m_lineDash.begin(), m_lineDash.end(), lineDash.begin());
m_strokeFlags.setPathEffect(SkDashPathEffect::Make(
lineDash.data(), lineDash.size(), m_lineDashOffset));
}
m_lineDashDirty = false;
}
void CanvasRenderingContext2DState::setStrokeStyle(CanvasStyle* style) {
m_strokeStyle = style;
m_strokeStyleDirty = true;
}
void CanvasRenderingContext2DState::setFillStyle(CanvasStyle* style) {
m_fillStyle = style;
m_fillStyleDirty = true;
}
void CanvasRenderingContext2DState::updateStrokeStyle() const {
if (!m_strokeStyleDirty)
return;
int clampedAlpha = clampedAlphaForBlending(m_globalAlpha);
DCHECK(m_strokeStyle);
m_strokeStyle->applyToFlags(m_strokeFlags);
m_strokeFlags.setColor(scaleAlpha(m_strokeStyle->paintColor(), clampedAlpha));
m_strokeStyleDirty = false;
}
void CanvasRenderingContext2DState::updateFillStyle() const {
if (!m_fillStyleDirty)
return;
int clampedAlpha = clampedAlphaForBlending(m_globalAlpha);
DCHECK(m_fillStyle);
m_fillStyle->applyToFlags(m_fillFlags);
m_fillFlags.setColor(scaleAlpha(m_fillStyle->paintColor(), clampedAlpha));
m_fillStyleDirty = false;
}
CanvasStyle* CanvasRenderingContext2DState::style(PaintType paintType) const {
switch (paintType) {
case FillPaintType:
return fillStyle();
case StrokePaintType:
return strokeStyle();
case ImagePaintType:
return nullptr;
}
NOTREACHED();
return nullptr;
}
void CanvasRenderingContext2DState::setShouldAntialias(bool shouldAntialias) {
m_fillFlags.setAntiAlias(shouldAntialias);
m_strokeFlags.setAntiAlias(shouldAntialias);
m_imageFlags.setAntiAlias(shouldAntialias);
}
bool CanvasRenderingContext2DState::shouldAntialias() const {
DCHECK(m_fillFlags.isAntiAlias() == m_strokeFlags.isAntiAlias() &&
m_fillFlags.isAntiAlias() == m_imageFlags.isAntiAlias());
return m_fillFlags.isAntiAlias();
}
void CanvasRenderingContext2DState::setGlobalAlpha(double alpha) {
m_globalAlpha = alpha;
m_strokeStyleDirty = true;
m_fillStyleDirty = true;
int imageAlpha = clampedAlphaForBlending(alpha);
m_imageFlags.setAlpha(imageAlpha > 255 ? 255 : imageAlpha);
}
void CanvasRenderingContext2DState::clipPath(
const SkPath& path,
AntiAliasingMode antiAliasingMode) {
m_clipList.clipPath(path, antiAliasingMode,
affineTransformToSkMatrix(m_transform));
m_hasClip = true;
if (!path.isRect(0))
m_hasComplexClip = true;
}
void CanvasRenderingContext2DState::setFont(const Font& font,
CSSFontSelector* selector) {
m_font = font;
m_font.update(selector);
m_realizedFont = true;
selector->registerForInvalidationCallbacks(this);
}
const Font& CanvasRenderingContext2DState::font() const {
DCHECK(m_realizedFont);
return m_font;
}
void CanvasRenderingContext2DState::setTransform(
const AffineTransform& transform) {
m_isTransformInvertible = transform.isInvertible();
m_transform = transform;
}
void CanvasRenderingContext2DState::resetTransform() {
m_transform.makeIdentity();
m_isTransformInvertible = true;
}
sk_sp<SkImageFilter> CanvasRenderingContext2DState::getFilterForOffscreenCanvas(
IntSize canvasSize) const {
if (!m_filterValue)
return nullptr;
if (m_resolvedFilter)
return m_resolvedFilter;
FilterOperations operations =
FilterOperationResolver::createOffscreenFilterOperations(*m_filterValue);
// We can't reuse m_fillFlags and m_strokeFlags for the filter, since these
// incorporate the global alpha, which isn't applicable here.
SkPaint fillFlagsForFilter;
m_fillStyle->applyToFlags(fillFlagsForFilter);
fillFlagsForFilter.setColor(m_fillStyle->paintColor());
SkPaint strokeFlagsForFilter;
m_strokeStyle->applyToFlags(strokeFlagsForFilter);
strokeFlagsForFilter.setColor(m_strokeStyle->paintColor());
FilterEffectBuilder filterEffectBuilder(
FloatRect((FloatPoint()), FloatSize(canvasSize)),
1.0f, // Deliberately ignore zoom on the canvas element.
&fillFlagsForFilter, &strokeFlagsForFilter);
FilterEffect* lastEffect = filterEffectBuilder.buildFilterEffect(operations);
if (lastEffect) {
m_resolvedFilter =
SkiaImageFilterBuilder::build(lastEffect, ColorSpaceDeviceRGB);
}
return m_resolvedFilter;
}
sk_sp<SkImageFilter> CanvasRenderingContext2DState::getFilter(
Element* styleResolutionHost,
IntSize canvasSize,
CanvasRenderingContext2D* context) const {
if (!m_filterValue)
return nullptr;
// StyleResolverState cannot be used in frame-less documents.
if (!styleResolutionHost->document().frame())
return nullptr;
if (!m_resolvedFilter) {
// Update the filter value to the proper base URL if needed.
if (m_filterValue->mayContainUrl())
m_filterValue->reResolveUrl(styleResolutionHost->document());
RefPtr<ComputedStyle> filterStyle = ComputedStyle::create();
// Must set font in case the filter uses any font-relative units (em, ex)
filterStyle->setFont(m_fontForFilter);
StyleResolverState resolverState(styleResolutionHost->document(),
styleResolutionHost, filterStyle.get(),
filterStyle.get());
resolverState.setStyle(filterStyle);
StyleBuilder::applyProperty(CSSPropertyFilter, resolverState,
*m_filterValue);
resolverState.loadPendingResources();
// We can't reuse m_fillFlags and m_strokeFlags for the filter, since these
// incorporate the global alpha, which isn't applicable here.
PaintFlags fillFlagsForFilter;
m_fillStyle->applyToFlags(fillFlagsForFilter);
fillFlagsForFilter.setColor(m_fillStyle->paintColor());
PaintFlags strokeFlagsForFilter;
m_strokeStyle->applyToFlags(strokeFlagsForFilter);
strokeFlagsForFilter.setColor(m_strokeStyle->paintColor());
FilterEffectBuilder filterEffectBuilder(
styleResolutionHost, FloatRect((FloatPoint()), FloatSize(canvasSize)),
1.0f, // Deliberately ignore zoom on the canvas element.
&fillFlagsForFilter, &strokeFlagsForFilter);
if (FilterEffect* lastEffect =
filterEffectBuilder.buildFilterEffect(filterStyle->filter())) {
m_resolvedFilter =
SkiaImageFilterBuilder::build(lastEffect, ColorSpaceDeviceRGB);
if (m_resolvedFilter) {
context->updateFilterReferences(filterStyle->filter());
if (lastEffect->originTainted())
context->setOriginTainted();
}
}
}
return m_resolvedFilter;
}
bool CanvasRenderingContext2DState::hasFilterForOffscreenCanvas(
IntSize canvasSize) const {
// Checking for a non-null m_filterValue isn't sufficient, since this value
// might refer to a non-existent filter.
return !!getFilterForOffscreenCanvas(canvasSize);
}
bool CanvasRenderingContext2DState::hasFilter(
Element* styleResolutionHost,
IntSize canvasSize,
CanvasRenderingContext2D* context) const {
// Checking for a non-null m_filterValue isn't sufficient, since this value
// might refer to a non-existent filter.
return !!getFilter(styleResolutionHost, canvasSize, context);
}
void CanvasRenderingContext2DState::clearResolvedFilter() const {
m_resolvedFilter.reset();
}
SkDrawLooper* CanvasRenderingContext2DState::emptyDrawLooper() const {
if (!m_emptyDrawLooper)
m_emptyDrawLooper = DrawLooperBuilder().detachDrawLooper();
return m_emptyDrawLooper.get();
}
SkDrawLooper* CanvasRenderingContext2DState::shadowOnlyDrawLooper() const {
if (!m_shadowOnlyDrawLooper) {
DrawLooperBuilder drawLooperBuilder;
drawLooperBuilder.addShadow(m_shadowOffset, m_shadowBlur, m_shadowColor,
DrawLooperBuilder::ShadowIgnoresTransforms,
DrawLooperBuilder::ShadowRespectsAlpha);
m_shadowOnlyDrawLooper = drawLooperBuilder.detachDrawLooper();
}
return m_shadowOnlyDrawLooper.get();
}
SkDrawLooper* CanvasRenderingContext2DState::shadowAndForegroundDrawLooper()
const {
if (!m_shadowAndForegroundDrawLooper) {
DrawLooperBuilder drawLooperBuilder;
drawLooperBuilder.addShadow(m_shadowOffset, m_shadowBlur, m_shadowColor,
DrawLooperBuilder::ShadowIgnoresTransforms,
DrawLooperBuilder::ShadowRespectsAlpha);
drawLooperBuilder.addUnmodifiedContent();
m_shadowAndForegroundDrawLooper = drawLooperBuilder.detachDrawLooper();
}
return m_shadowAndForegroundDrawLooper.get();
}
sk_sp<SkImageFilter> CanvasRenderingContext2DState::shadowOnlyImageFilter()
const {
if (!m_shadowOnlyImageFilter) {
double sigma = skBlurRadiusToSigma(m_shadowBlur);
m_shadowOnlyImageFilter = SkDropShadowImageFilter::Make(
m_shadowOffset.width(), m_shadowOffset.height(), sigma, sigma,
m_shadowColor, SkDropShadowImageFilter::kDrawShadowOnly_ShadowMode,
nullptr);
}
return m_shadowOnlyImageFilter;
}
sk_sp<SkImageFilter>
CanvasRenderingContext2DState::shadowAndForegroundImageFilter() const {
if (!m_shadowAndForegroundImageFilter) {
double sigma = skBlurRadiusToSigma(m_shadowBlur);
m_shadowAndForegroundImageFilter = SkDropShadowImageFilter::Make(
m_shadowOffset.width(), m_shadowOffset.height(), sigma, sigma,
m_shadowColor,
SkDropShadowImageFilter::kDrawShadowAndForeground_ShadowMode, nullptr);
}
return m_shadowAndForegroundImageFilter;
}
void CanvasRenderingContext2DState::shadowParameterChanged() {
m_shadowOnlyDrawLooper.reset();
m_shadowAndForegroundDrawLooper.reset();
m_shadowOnlyImageFilter.reset();
m_shadowAndForegroundImageFilter.reset();
}
void CanvasRenderingContext2DState::setShadowOffsetX(double x) {
m_shadowOffset.setWidth(x);
shadowParameterChanged();
}
void CanvasRenderingContext2DState::setShadowOffsetY(double y) {
m_shadowOffset.setHeight(y);
shadowParameterChanged();
}
void CanvasRenderingContext2DState::setShadowBlur(double shadowBlur) {
m_shadowBlur = shadowBlur;
shadowParameterChanged();
}
void CanvasRenderingContext2DState::setShadowColor(SkColor shadowColor) {
m_shadowColor = shadowColor;
shadowParameterChanged();
}
void CanvasRenderingContext2DState::setFilter(const CSSValue* filterValue) {
m_filterValue = filterValue;
m_resolvedFilter.reset();
}
void CanvasRenderingContext2DState::setGlobalComposite(SkBlendMode mode) {
m_strokeFlags.setBlendMode(mode);
m_fillFlags.setBlendMode(mode);
m_imageFlags.setBlendMode(mode);
}
SkBlendMode CanvasRenderingContext2DState::globalComposite() const {
return m_strokeFlags.getBlendMode();
}
void CanvasRenderingContext2DState::setImageSmoothingEnabled(bool enabled) {
m_imageSmoothingEnabled = enabled;
updateFilterQuality();
}
bool CanvasRenderingContext2DState::imageSmoothingEnabled() const {
return m_imageSmoothingEnabled;
}
void CanvasRenderingContext2DState::setImageSmoothingQuality(
const String& qualityString) {
if (qualityString == "low") {
m_imageSmoothingQuality = kLow_SkFilterQuality;
} else if (qualityString == "medium") {
m_imageSmoothingQuality = kMedium_SkFilterQuality;
} else if (qualityString == "high") {
m_imageSmoothingQuality = kHigh_SkFilterQuality;
} else {
return;
}
updateFilterQuality();
}
String CanvasRenderingContext2DState::imageSmoothingQuality() const {
switch (m_imageSmoothingQuality) {
case kLow_SkFilterQuality:
return "low";
case kMedium_SkFilterQuality:
return "medium";
case kHigh_SkFilterQuality:
return "high";
default:
NOTREACHED();
return "low";
}
}
void CanvasRenderingContext2DState::updateFilterQuality() const {
if (!m_imageSmoothingEnabled) {
updateFilterQualityWithSkFilterQuality(kNone_SkFilterQuality);
} else {
updateFilterQualityWithSkFilterQuality(m_imageSmoothingQuality);
}
}
void CanvasRenderingContext2DState::updateFilterQualityWithSkFilterQuality(
const SkFilterQuality& filterQuality) const {
m_strokeFlags.setFilterQuality(filterQuality);
m_fillFlags.setFilterQuality(filterQuality);
m_imageFlags.setFilterQuality(filterQuality);
}
bool CanvasRenderingContext2DState::shouldDrawShadows() const {
return alphaChannel(m_shadowColor) &&
(m_shadowBlur || !m_shadowOffset.isZero());
}
const PaintFlags* CanvasRenderingContext2DState::getFlags(
PaintType paintType,
ShadowMode shadowMode,
ImageType imageType) const {
PaintFlags* flags;
switch (paintType) {
case StrokePaintType:
updateLineDash();
updateStrokeStyle();
flags = &m_strokeFlags;
break;
default:
NOTREACHED();
// no break on purpose: flags needs to be assigned to avoid compiler warning
// about uninitialized variable.
case FillPaintType:
updateFillStyle();
flags = &m_fillFlags;
break;
case ImagePaintType:
flags = &m_imageFlags;
break;
}
if ((!shouldDrawShadows() && shadowMode == DrawShadowAndForeground) ||
shadowMode == DrawForegroundOnly) {
flags->setLooper(0);
flags->setImageFilter(0);
return flags;
}
if (!shouldDrawShadows() && shadowMode == DrawShadowOnly) {
flags->setLooper(sk_ref_sp(emptyDrawLooper())); // draw nothing
flags->setImageFilter(0);
return flags;
}
if (shadowMode == DrawShadowOnly) {
if (imageType == NonOpaqueImage || m_filterValue) {
flags->setLooper(0);
flags->setImageFilter(shadowOnlyImageFilter());
return flags;
}
flags->setLooper(sk_ref_sp(shadowOnlyDrawLooper()));
flags->setImageFilter(0);
return flags;
}
DCHECK(shadowMode == DrawShadowAndForeground);
if (imageType == NonOpaqueImage) {
flags->setLooper(0);
flags->setImageFilter(shadowAndForegroundImageFilter());
return flags;
}
flags->setLooper(sk_ref_sp(shadowAndForegroundDrawLooper()));
flags->setImageFilter(0);
return flags;
}
} // namespace blink