| /* |
| * 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 "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" |
| #include "wtf/PtrUtil.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. |
| m_filterPaintInfo = nullptr; |
| } |
| |
| if (m_masker) { |
| ASSERT(SVGResourcesCache::cachedResourcesForLayoutObject(&m_object)); |
| ASSERT(SVGResourcesCache::cachedResourcesForLayoutObject(&m_object)->masker() == m_masker); |
| SVGMaskPainter(*m_masker).finishEffect(m_object, paintInfo().context); |
| } |
| |
| if (m_clipper) { |
| ASSERT(SVGResourcesCache::cachedResourcesForLayoutObject(&m_object)); |
| ASSERT(SVGResourcesCache::cachedResourcesForLayoutObject(&m_object)->clipper() == m_clipper); |
| SVGClipPainter(*m_clipper).finishEffect(m_object, 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 (paintInfo().isRenderingClipPathAsMaskImage()) { |
| if (!applyClipIfNecessary(resources)) |
| return false; |
| return true; |
| } |
| |
| bool isSVGRoot = m_object.isSVGRoot(); |
| |
| // Layer takes care of root opacity and blend mode. |
| if (isSVGRoot) { |
| DCHECK(!(m_object.isTransparent() || m_object.styleRef().hasBlendMode()) || m_object.hasLayer()); |
| } else { |
| applyCompositingIfNecessary(); |
| } |
| |
| if (!applyClipIfNecessary(resources)) |
| return false; |
| |
| if (!applyMaskIfNecessary(resources)) |
| return false; |
| |
| if (isSVGRoot) { |
| DCHECK(!m_object.styleRef().hasFilter() || m_object.hasLayer()); |
| } else if (!applyFilterIfNecessary(resources)) { |
| return false; |
| } |
| |
| if (!isIsolationInstalled() && SVGLayoutSupport::isIsolationRequired(&m_object)) |
| m_compositingRecorder = wrapUnique(new CompositingRecorder(paintInfo().context, m_object, SkXfermode::kSrcOver_Mode, 1)); |
| |
| return true; |
| } |
| |
| void SVGPaintContext::applyCompositingIfNecessary() |
| { |
| ASSERT(!paintInfo().isRenderingClipPathAsMaskImage()); |
| |
| 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) { |
| const FloatRect compositingBounds = m_object.paintInvalidationRectInLocalSVGCoordinates(); |
| m_compositingRecorder = wrapUnique(new CompositingRecorder(paintInfo().context, m_object, |
| WebCoreCompositeToSkiaComposite(CompositeSourceOver, blendMode), opacity, &compositingBounds)); |
| } |
| } |
| |
| 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).prepareEffect(m_object, m_object.objectBoundingBox(), m_object.paintInvalidationRectInLocalSVGCoordinates(), 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 = wrapUnique(new ClipPathRecorder(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, paintInfo().context)) |
| return false; |
| m_masker = masker; |
| } |
| return true; |
| } |
| |
| bool SVGPaintContext::applyFilterIfNecessary(SVGResources* resources) |
| { |
| if (!resources) { |
| if (m_object.style()->hasFilter()) |
| return false; |
| } else if (LayoutSVGResourceFilter* filter = resources->filter()) { |
| m_filterRecordingContext = wrapUnique(new SVGFilterRecordingContext(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_filterPaintInfo = wrapUnique(new PaintInfo(*filterContext, m_paintInfo)); |
| |
| // 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_filterPaintInfo->m_cullRect.m_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, const LayoutObject* item) |
| { |
| ASSERT(item); |
| ASSERT(!item->needsLayout()); |
| |
| PaintInfo info(context, LayoutRect::infiniteIntRect(), PaintPhaseForeground, GlobalPaintNormalPhase, PaintLayerNoFlag); |
| item->paint(info, IntPoint()); |
| } |
| |
| bool SVGPaintContext::paintForLayoutObject(const PaintInfo& paintInfo, const ComputedStyle& style, const 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(sk_ref_sp(paintInfo.context.colorFilter())); |
| return true; |
| } |
| |
| } // namespace blink |