blob: 5ef118913cd62ea8f1102739a97216aee081e69f [file] [log] [blame]
/*
* Copyright (C) 2007, 2008 Rob Buis <buis@kde.org>
* Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
* Copyright (C) 2007 Eric Seidel <eric@webkit.org>
* Copyright (C) 2009 Google, Inc. All rights reserved.
* Copyright (C) 2009 Dirk Schulze <krit@webkit.org>
* Copyright (C) Research In Motion Limited 2009-2010. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "core/paint/SVGPaintContext.h"
#include "core/frame/FrameHost.h"
#include "core/layout/svg/LayoutSVGResourceFilter.h"
#include "core/layout/svg/LayoutSVGResourceMasker.h"
#include "core/layout/svg/SVGLayoutSupport.h"
#include "core/layout/svg/SVGResources.h"
#include "core/layout/svg/SVGResourcesCache.h"
#include "core/paint/SVGMaskPainter.h"
#include "platform/FloatConversion.h"
namespace blink {
SVGPaintContext::~SVGPaintContext()
{
if (m_filter) {
ASSERT(SVGResourcesCache::cachedResourcesForLayoutObject(m_object));
ASSERT(SVGResourcesCache::cachedResourcesForLayoutObject(m_object)->filter() == m_filter);
ASSERT(m_filterRecordingContext);
SVGFilterPainter(*m_filter).finishEffect(*m_object, *m_filterRecordingContext);
// Reset the paint info after the filter effect has been completed.
// This isn't strictly required (e.g., m_paintInfo.rect is not used
// after this).
m_paintInfo.context = m_originalPaintInfo->context;
m_paintInfo.rect = m_originalPaintInfo->rect;
}
if (m_masker) {
ASSERT(SVGResourcesCache::cachedResourcesForLayoutObject(m_object));
ASSERT(SVGResourcesCache::cachedResourcesForLayoutObject(m_object)->masker() == m_masker);
SVGMaskPainter(*m_masker).finishEffect(*m_object, m_paintInfo.context);
}
if (m_clipper) {
ASSERT(SVGResourcesCache::cachedResourcesForLayoutObject(m_object));
ASSERT(SVGResourcesCache::cachedResourcesForLayoutObject(m_object)->clipper() == m_clipper);
SVGClipPainter(*m_clipper).postApplyStatefulResource(*m_object, m_paintInfo.context, m_clipperState);
}
}
bool SVGPaintContext::applyClipMaskAndFilterIfNecessary()
{
#if ENABLE(ASSERT)
ASSERT(!m_applyClipMaskAndFilterIfNecessaryCalled);
m_applyClipMaskAndFilterIfNecessaryCalled = true;
#endif
SVGResources* resources = SVGResourcesCache::cachedResourcesForLayoutObject(m_object);
// When rendering clip paths as masks, only geometric operations should be included so skip
// non-geometric operations such as compositing, masking, and filtering.
if (m_paintInfo.isRenderingClipPathAsMaskImage()) {
if (!applyClipIfNecessary(resources))
return false;
return true;
}
applyCompositingIfNecessary();
if (!applyClipIfNecessary(resources))
return false;
if (!applyMaskIfNecessary(resources))
return false;
if (!applyFilterIfNecessary(resources))
return false;
if (!isIsolationInstalled() && SVGLayoutSupport::isIsolationRequired(m_object))
m_compositingRecorder = adoptPtr(new CompositingRecorder(*m_paintInfo.context, *m_object, SkXfermode::kSrcOver_Mode, 1));
return true;
}
void SVGPaintContext::applyCompositingIfNecessary()
{
ASSERT(!m_paintInfo.isRenderingClipPathAsMaskImage());
// Layer takes care of root opacity and blend mode.
if (m_object->isSVGRoot())
return;
const ComputedStyle& style = m_object->styleRef();
float opacity = style.opacity();
WebBlendMode blendMode = style.hasBlendMode() && m_object->isBlendingAllowed() ?
style.blendMode() : WebBlendModeNormal;
if (opacity < 1 || blendMode != WebBlendModeNormal) {
m_clipRecorder = adoptPtr(new FloatClipRecorder(*m_paintInfo.context, *m_object, m_paintInfo.phase, m_object->paintInvalidationRectInLocalCoordinates()));
m_compositingRecorder = adoptPtr(new CompositingRecorder(*m_paintInfo.context, *m_object,
WebCoreCompositeToSkiaComposite(CompositeSourceOver, blendMode), opacity));
}
}
bool SVGPaintContext::applyClipIfNecessary(SVGResources* resources)
{
// resources->clipper() corresponds to the non-prefixed 'clip-path' whereas
// m_object->style()->clipPath() corresponds to '-webkit-clip-path'.
// FIXME: We should unify the clip-path and -webkit-clip-path codepaths.
if (LayoutSVGResourceClipper* clipper = resources ? resources->clipper() : nullptr) {
if (!SVGClipPainter(*clipper).applyStatefulResource(*m_object, m_paintInfo.context, m_clipperState))
return false;
m_clipper = clipper;
} else {
ClipPathOperation* clipPathOperation = m_object->style()->clipPath();
if (clipPathOperation && clipPathOperation->type() == ClipPathOperation::SHAPE) {
ShapeClipPathOperation* clipPath = toShapeClipPathOperation(clipPathOperation);
if (!clipPath->isValid())
return false;
m_clipPathRecorder = adoptPtr(new ClipPathRecorder(*m_paintInfo.context, *m_object, clipPath->path(m_object->objectBoundingBox())));
}
}
return true;
}
bool SVGPaintContext::applyMaskIfNecessary(SVGResources* resources)
{
if (LayoutSVGResourceMasker* masker = resources ? resources->masker() : nullptr) {
if (!SVGMaskPainter(*masker).prepareEffect(*m_object, m_paintInfo.context))
return false;
m_masker = masker;
}
return true;
}
bool SVGPaintContext::applyFilterIfNecessary(SVGResources* resources)
{
if (!resources) {
if (m_object->style()->svgStyle().hasFilter())
return false;
} else if (LayoutSVGResourceFilter* filter = resources->filter()) {
m_filterRecordingContext = adoptPtr(new SVGFilterRecordingContext(m_paintInfo.context));
m_filter = filter;
GraphicsContext* filterContext = SVGFilterPainter(*filter).prepareEffect(*m_object, *m_filterRecordingContext);
if (!filterContext)
return false;
// Because the filter needs to cache its contents we replace the context
// during filtering with the filter's context.
m_paintInfo.context = filterContext;
// Because we cache the filter contents and do not invalidate on paint
// invalidation rect changes, we need to paint the entire filter region
// so elements outside the initial paint (due to scrolling, etc) paint.
m_paintInfo.rect = LayoutRect::infiniteIntRect();
}
return true;
}
bool SVGPaintContext::isIsolationInstalled() const
{
if (m_compositingRecorder)
return true;
if (m_masker || m_filter)
return true;
if (m_clipper && m_clipperState == SVGClipPainter::ClipperAppliedMask)
return true;
return false;
}
void SVGPaintContext::paintSubtree(GraphicsContext* context, LayoutObject* item)
{
ASSERT(context);
ASSERT(item);
ASSERT(!item->needsLayout());
PaintInfo info(context, LayoutRect::infiniteIntRect(), PaintPhaseForeground, PaintBehaviorNormal, PaintLayerNoFlag);
item->paint(info, IntPoint());
}
bool SVGPaintContext::paintForLayoutObject(const PaintInfo& paintInfo, const ComputedStyle& style, LayoutObject& layoutObject, LayoutSVGResourceMode resourceMode, SkPaint& paint, const AffineTransform* additionalPaintServerTransform)
{
if (paintInfo.isRenderingClipPathAsMaskImage()) {
if (resourceMode == ApplyToStrokeMode)
return false;
paint.setColor(SVGComputedStyle::initialFillPaintColor().rgb());
paint.setShader(nullptr);
return true;
}
SVGPaintServer paintServer = SVGPaintServer::requestForLayoutObject(layoutObject, style, resourceMode);
if (!paintServer.isValid())
return false;
if (additionalPaintServerTransform && paintServer.isTransformDependent())
paintServer.prependTransform(*additionalPaintServerTransform);
const SVGComputedStyle& svgStyle = style.svgStyle();
float paintAlpha = resourceMode == ApplyToFillMode ? svgStyle.fillOpacity() : svgStyle.strokeOpacity();
paintServer.applyToSkPaint(paint, paintAlpha);
// We always set filter quality to 'low' here. This value will only have an
// effect for patterns, which are SkPictures, so using high-order filter
// should have little effect on the overall quality.
paint.setFilterQuality(kLow_SkFilterQuality);
// TODO(fs): The color filter can set when generating a picture for a mask -
// due to color-interpolation. We could also just apply the
// color-interpolation property from the the shape itself (which could mean
// the paintserver if it has it specified), since that would be more in line
// with the spec for color-interpolation. For now, just steal it from the GC
// though.
// Additionally, it's not really safe/guaranteed to be correct, as
// something down the paint pipe may want to farther tweak the color
// filter, which could yield incorrect results. (Consider just using
// saveLayer() w/ this color filter explicitly instead.)
paint.setColorFilter(paintInfo.context->colorFilter());
return true;
}
} // namespace blink