Chromium Code Reviews| Index: Source/core/rendering/svg/RenderSVGResourcePattern.cpp |
| diff --git a/Source/core/rendering/svg/RenderSVGResourcePattern.cpp b/Source/core/rendering/svg/RenderSVGResourcePattern.cpp |
| index 738815598fcdf20d9a0c02d51a844d00ac3701ba..de6ba36936fce337aa914a8052190be0d3131a6e 100644 |
| --- a/Source/core/rendering/svg/RenderSVGResourcePattern.cpp |
| +++ b/Source/core/rendering/svg/RenderSVGResourcePattern.cpp |
| @@ -1,6 +1,7 @@ |
| /* |
| * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org> |
| * Copyright (C) Research In Motion Limited 2010. All rights reserved. |
| + * Copyright (C) 2014 Google Inc. 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 |
| @@ -26,10 +27,24 @@ |
| #include "core/rendering/svg/SVGRenderSupport.h" |
| #include "core/rendering/svg/SVGRenderingContext.h" |
| #include "core/svg/SVGFitToViewBox.h" |
| +#include "core/svg/SVGPatternElement.h" |
| +#include "core/svg/SVGUnitTypes.h" |
| +#include "platform/geometry/FloatRect.h" |
| +#include "platform/graphics/DisplayList.h" |
| #include "platform/graphics/GraphicsContext.h" |
| +#include "platform/graphics/ImageBuffer.h" |
| +#include "platform/graphics/Pattern.h" |
| +#include "platform/transforms/AffineTransform.h" |
| namespace blink { |
| +struct PatternData { |
| + WTF_MAKE_FAST_ALLOCATED; |
| +public: |
| + RefPtr<Pattern> pattern; |
| + AffineTransform transform; |
| +}; |
| + |
| const RenderSVGResourceType RenderSVGResourcePattern::s_resourceType = PatternResourceType; |
| RenderSVGResourcePattern::RenderSVGResourcePattern(SVGPatternElement* node) |
| @@ -52,16 +67,49 @@ void RenderSVGResourcePattern::removeClientFromCache(RenderObject* client, bool |
| markClientForInvalidation(client, markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation); |
| } |
| -PatternData* RenderSVGResourcePattern::buildPattern(RenderObject* object, unsigned short resourceMode) |
| +PatternData* RenderSVGResourcePattern::patternForRenderer(GraphicsContext& context, |
| + RenderObject& renderer) |
| { |
| - ASSERT(object); |
| - PatternData* currentData = m_patternMap.get(object); |
| - if (currentData && currentData->pattern) |
| - return currentData; |
| + OwnPtr<PatternData>& patternData = m_patternMap.add(&renderer, nullptr).storedValue->value; |
| + if (!patternData) |
| + patternData = buildPatternData(context, renderer); |
| + |
| + ASSERT(!m_shouldCollectPatternAttributes); |
| + return patternData.get(); |
| +} |
| + |
| +bool RenderSVGResourcePattern::computeTileMetrics(const RenderObject& renderer, |
| + const SVGPatternElement& pattern, FloatRect& bounds, AffineTransform& transform) const |
|
Stephen White
2014/08/08 14:00:48
I prefer pointers over non-const refs for outparam
f(malita)
2014/08/08 15:30:35
The only reason I prefer refs is to avoid assert-n
|
| +{ |
| + FloatRect clientBoundingBox = renderer.objectBoundingBox(); |
|
Erik Dahlström (inactive)
2014/08/08 08:01:02
I've been working a bit in this area too recently,
f(malita)
2014/08/08 15:30:36
Thanks.
|
| + |
| + bounds = SVGLengthContext::resolveRectangle(&pattern, m_attributes.patternUnits(), |
| + clientBoundingBox, m_attributes.x(), m_attributes.y(), m_attributes.width(), |
| + m_attributes.height()); |
| + |
| + if (bounds.width() <= 0 || bounds.height() <= 0) |
|
Stephen White
2014/08/08 14:00:48
Do we have a layout test that exercises this case?
f(malita)
2014/08/08 15:30:36
Yes, apparently svg/W3C-SVG-1.1-SE/pservers-patter
|
| + return false; |
| + // Apply viewBox/objectBoundingBox transformations. |
| + AffineTransform viewBoxCTM = SVGFitToViewBox::viewBoxToViewTransform(m_attributes.viewBox(), |
| + m_attributes.preserveAspectRatio(), bounds.width(), bounds.height()); |
| + |
| + if (!viewBoxCTM.isIdentity()) { |
| + transform = viewBoxCTM; |
| + } else { |
| + if (m_attributes.patternContentUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) |
| + transform.scale(clientBoundingBox.width(), clientBoundingBox.height()); |
|
Stephen White
2014/08/08 14:00:48
I'm guessing this assumes the passed-in transform
f(malita)
2014/08/08 15:30:36
This should be addressed by refactoring.
|
| + } |
| + |
| + return true; |
| +} |
| + |
| +PassOwnPtr<PatternData> RenderSVGResourcePattern::buildPatternData(GraphicsContext& context, |
| + RenderObject& renderer) |
| +{ |
| SVGPatternElement* patternElement = toSVGPatternElement(element()); |
| if (!patternElement) |
| - return 0; |
| + return nullptr; |
| if (m_shouldCollectPatternAttributes) { |
| patternElement->synchronizeAnimatedSVGAttribute(anyQName()); |
| @@ -73,55 +121,28 @@ PatternData* RenderSVGResourcePattern::buildPattern(RenderObject* object, unsign |
| // If we couldn't determine the pattern content element root, stop here. |
| if (!m_attributes.patternContentElement()) |
| - return 0; |
| + return nullptr; |
| // An empty viewBox disables rendering. |
| if (m_attributes.hasViewBox() && m_attributes.viewBox().isEmpty()) |
| - return 0; |
| - |
| - // Compute all necessary transformations to build the tile image & the pattern. |
| - FloatRect tileBoundaries; |
| - AffineTransform tileImageTransform; |
| - if (!buildTileImageTransform(object, m_attributes, patternElement, tileBoundaries, tileImageTransform)) |
| - return 0; |
| - |
| - AffineTransform absoluteTransformIgnoringRotation; |
| - SVGRenderingContext::calculateDeviceSpaceTransformation(object, absoluteTransformIgnoringRotation); |
| - |
| - // Ignore 2D rotation, as it doesn't affect the size of the tile. |
| - SVGRenderingContext::clear2DRotation(absoluteTransformIgnoringRotation); |
| - FloatRect absoluteTileBoundaries = absoluteTransformIgnoringRotation.mapRect(tileBoundaries); |
| - |
| - // Scale the tile size to match the scale level of the patternTransform. |
| - absoluteTileBoundaries.scale(static_cast<float>(m_attributes.patternTransform().xScale()), |
| - static_cast<float>(m_attributes.patternTransform().yScale())); |
| - |
| - // Build tile image. |
| - OwnPtr<ImageBuffer> tileImage = createTileImage(m_attributes, tileBoundaries, absoluteTileBoundaries, tileImageTransform); |
| - if (!tileImage) |
| - return 0; |
| + return nullptr; |
| - RefPtr<Image> copiedImage = tileImage->copyImage(CopyBackingStore); |
| - if (!copiedImage) |
| - return 0; |
| + FloatRect tileBounds; |
| + AffineTransform tileTransform; |
| + if (!computeTileMetrics(renderer, *patternElement, tileBounds, tileTransform)) |
| + return nullptr; |
| - // Build pattern. |
| OwnPtr<PatternData> patternData = adoptPtr(new PatternData); |
| - patternData->pattern = Pattern::createBitmapPattern(copiedImage); |
| + patternData->pattern = Pattern::createDisplayListPattern(asDisplayList(context, tileBounds, |
| + tileTransform)); |
| // Compute pattern space transformation. |
| - const IntSize tileImageSize = tileImage->size(); |
| - patternData->transform.translate(tileBoundaries.x(), tileBoundaries.y()); |
| - patternData->transform.scale(tileBoundaries.width() / tileImageSize.width(), tileBoundaries.height() / tileImageSize.height()); |
| - |
| + patternData->transform.translate(tileBounds.x(), tileBounds.y()); |
| AffineTransform patternTransform = m_attributes.patternTransform(); |
| if (!patternTransform.isIdentity()) |
| patternData->transform = patternTransform * patternData->transform; |
| - // Various calls above may trigger invalidations in some fringe cases (ImageBuffer allocation |
| - // failures in the SVG image cache for example). To avoid having our PatternData deleted by |
| - // removeAllClientsFromCache(), we only make it visible in the cache at the very end. |
| - return m_patternMap.set(object, patternData.release()).storedValue->value.get(); |
| + return patternData.release(); |
| } |
| bool RenderSVGResourcePattern::applyResource(RenderObject* object, RenderStyle* style, GraphicsContext*& context, unsigned short resourceMode) |
| @@ -139,7 +160,7 @@ bool RenderSVGResourcePattern::applyResource(RenderObject* object, RenderStyle* |
| if (m_attributes.patternUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX && objectBoundingBox.isEmpty()) |
| return false; |
| - PatternData* patternData = buildPattern(object, resourceMode); |
| + PatternData* patternData = patternForRenderer(*context, *object); |
| if (!patternData) |
| return false; |
| @@ -192,81 +213,33 @@ void RenderSVGResourcePattern::postApplyResource(RenderObject*, GraphicsContext* |
| context->restore(); |
| } |
| -static inline FloatRect calculatePatternBoundaries(const PatternAttributes& attributes, |
| - const FloatRect& objectBoundingBox, |
| - const SVGPatternElement* patternElement) |
| +PassRefPtr<DisplayList> RenderSVGResourcePattern::asDisplayList(GraphicsContext& context, |
| + const FloatRect& tileBounds, const AffineTransform& tileTransform) const |
| { |
| - ASSERT(patternElement); |
| - return SVGLengthContext::resolveRectangle(patternElement, attributes.patternUnits(), objectBoundingBox, attributes.x(), attributes.y(), attributes.width(), attributes.height()); |
| -} |
| + ASSERT(!m_shouldCollectPatternAttributes); |
| -bool RenderSVGResourcePattern::buildTileImageTransform(RenderObject* renderer, |
| - const PatternAttributes& attributes, |
| - const SVGPatternElement* patternElement, |
| - FloatRect& patternBoundaries, |
| - AffineTransform& tileImageTransform) const |
| -{ |
| - ASSERT(renderer); |
| - ASSERT(patternElement); |
| + AffineTransform contentTransform; |
| + if (m_attributes.patternContentUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) |
| + contentTransform = tileTransform; |
| - FloatRect objectBoundingBox = renderer->objectBoundingBox(); |
| - patternBoundaries = calculatePatternBoundaries(attributes, objectBoundingBox, patternElement); |
| - if (patternBoundaries.width() <= 0 || patternBoundaries.height() <= 0) |
| - return false; |
| + FloatRect pictureBounds(FloatPoint(), tileBounds.size()); |
| - AffineTransform viewBoxCTM = SVGFitToViewBox::viewBoxToViewTransform(attributes.viewBox(), attributes.preserveAspectRatio(), patternBoundaries.width(), patternBoundaries.height()); |
| + // Draw the content into a DisplayList. |
| + context.beginRecording(pictureBounds); |
| + context.concatCTM(tileTransform); |
|
Stephen White
2014/08/08 14:00:48
Just wondering: could the tile transform (e.g., vi
f(malita)
2014/08/08 15:30:36
I don't think so (or if it does, it should be OK t
|
| - // Apply viewBox/objectBoundingBox transformations. |
| - if (!viewBoxCTM.isIdentity()) |
| - tileImageTransform = viewBoxCTM; |
| - else if (attributes.patternContentUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) |
| - tileImageTransform.scale(objectBoundingBox.width(), objectBoundingBox.height()); |
| - |
| - return true; |
| -} |
| - |
| -PassOwnPtr<ImageBuffer> RenderSVGResourcePattern::createTileImage(const PatternAttributes& attributes, |
| - const FloatRect& tileBoundaries, |
| - const FloatRect& absoluteTileBoundaries, |
| - const AffineTransform& tileImageTransform) const |
| -{ |
| - FloatRect clampedAbsoluteTileBoundaries = SVGRenderingContext::clampedAbsoluteTargetRect(absoluteTileBoundaries); |
| - |
| - IntSize imageSize(roundedIntSize(clampedAbsoluteTileBoundaries.size())); |
| - if (imageSize.isEmpty()) |
| - return nullptr; |
| - OwnPtr<ImageBuffer> tileImage = ImageBuffer::create(imageSize); |
| - if (!tileImage) |
| - return nullptr; |
| + ASSERT(m_attributes.patternContentElement()); |
| + RenderSVGResourceContainer* patternRenderer = |
| + toRenderSVGResourceContainer(m_attributes.patternContentElement()->renderer()); |
| + ASSERT(patternRenderer); |
| + ASSERT(!patternRenderer->needsLayout()); |
| - GraphicsContext* tileImageContext = tileImage->context(); |
| - ASSERT(tileImageContext); |
| - IntSize unclampedImageSize(roundedIntSize(absoluteTileBoundaries.size())); |
| - tileImageContext->scale(unclampedImageSize.width() / absoluteTileBoundaries.width(), unclampedImageSize.height() / absoluteTileBoundaries.height()); |
| - |
| - // The image buffer represents the final rendered size, so the content has to be scaled (to avoid pixelation). |
| - tileImageContext->scale( |
| - clampedAbsoluteTileBoundaries.width() / tileBoundaries.width(), |
| - clampedAbsoluteTileBoundaries.height() / tileBoundaries.height()); |
| - |
| - // Apply tile image transformations. |
| - if (!tileImageTransform.isIdentity()) |
| - tileImageContext->concatCTM(tileImageTransform); |
| - |
| - AffineTransform contentTransformation; |
| - if (attributes.patternContentUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) |
| - contentTransformation = tileImageTransform; |
| - |
| - // Draw the content into the ImageBuffer. |
| - for (SVGElement* element = Traversal<SVGElement>::firstChild(*attributes.patternContentElement()); element; element = Traversal<SVGElement>::nextSibling(*element)) { |
| - if (!element->renderer()) |
| - continue; |
| - if (element->renderer()->needsLayout()) |
| - return nullptr; |
| - SVGRenderingContext::renderSubtree(tileImage->context(), element->renderer(), contentTransformation); |
| + for (RenderObject* child = patternRenderer->firstChild(); child; child = child->nextSibling()) { |
| + ASSERT(!child->needsLayout()); |
| + SVGRenderingContext::renderSubtree(&context, child, contentTransform); |
| } |
| - return tileImage.release(); |
| + return context.endRecording(); |
| } |
| } |