RenderSVGResourcePattern.cpp revision ab9e7a118cf1ea2e3a93dce683b2ded3e7291ddb
1/*
2 * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org>
3 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB.  If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21#include "config.h"
22
23#if ENABLE(SVG)
24#include "RenderSVGResourcePattern.h"
25
26#include "FrameView.h"
27#include "GraphicsContext.h"
28#include "PatternAttributes.h"
29#include "RenderSVGRoot.h"
30#include "SVGImageBufferTools.h"
31#include "SVGRenderSupport.h"
32
33namespace WebCore {
34
35RenderSVGResourceType RenderSVGResourcePattern::s_resourceType = PatternResourceType;
36
37RenderSVGResourcePattern::RenderSVGResourcePattern(SVGPatternElement* node)
38    : RenderSVGResourceContainer(node)
39    , m_shouldCollectPatternAttributes(true)
40{
41}
42
43RenderSVGResourcePattern::~RenderSVGResourcePattern()
44{
45    if (m_pattern.isEmpty())
46        return;
47
48    deleteAllValues(m_pattern);
49    m_pattern.clear();
50}
51
52void RenderSVGResourcePattern::removeAllClientsFromCache(bool markForInvalidation)
53{
54    if (!m_pattern.isEmpty()) {
55        deleteAllValues(m_pattern);
56        m_pattern.clear();
57    }
58
59    m_shouldCollectPatternAttributes = true;
60    markAllClientsForInvalidation(markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation);
61}
62
63void RenderSVGResourcePattern::removeClientFromCache(RenderObject* client, bool markForInvalidation)
64{
65    ASSERT(client);
66
67    if (m_pattern.contains(client))
68        delete m_pattern.take(client);
69
70    markClientForInvalidation(client, markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation);
71}
72
73bool RenderSVGResourcePattern::applyResource(RenderObject* object, RenderStyle* style, GraphicsContext*& context, unsigned short resourceMode)
74{
75    ASSERT(object);
76    ASSERT(style);
77    ASSERT(context);
78    ASSERT(resourceMode != ApplyToDefaultMode);
79
80    // Be sure to synchronize all SVG properties on the patternElement _before_ processing any further.
81    // Otherwhise the call to collectPatternAttributes() below, may cause the SVG DOM property
82    // synchronization to kick in, which causes removeAllClientsFromCache() to be called, which in turn deletes our
83    // PatternData object! Leaving out the line below will cause svg/dynamic-updates/SVGPatternElement-svgdom* to crash.
84    SVGPatternElement* patternElement = static_cast<SVGPatternElement*>(node());
85    if (!patternElement)
86        return false;
87
88    if (m_shouldCollectPatternAttributes) {
89        patternElement->updateAnimatedSVGAttribute(anyQName());
90
91        m_attributes = PatternAttributes();
92        patternElement->collectPatternAttributes(m_attributes);
93        m_shouldCollectPatternAttributes = false;
94    }
95
96    // Spec: When the geometry of the applicable element has no width or height and objectBoundingBox is specified,
97    // then the given effect (e.g. a gradient or a filter) will be ignored.
98    FloatRect objectBoundingBox = object->objectBoundingBox();
99    if (m_attributes.boundingBoxMode() && objectBoundingBox.isEmpty())
100        return false;
101
102    if (!m_pattern.contains(object))
103        m_pattern.set(object, new PatternData);
104
105    PatternData* patternData = m_pattern.get(object);
106    if (!patternData->pattern) {
107        // If we couldn't determine the pattern content element root, stop here.
108        if (!m_attributes.patternContentElement())
109            return false;
110
111        // Compute all necessary transformations to build the tile image & the pattern.
112        FloatRect tileBoundaries;
113        AffineTransform tileImageTransform;
114        if (!buildTileImageTransform(object, m_attributes, patternElement, tileBoundaries, tileImageTransform))
115            return false;
116
117        AffineTransform absoluteTransform;
118        SVGImageBufferTools::calculateTransformationToOutermostSVGCoordinateSystem(object, absoluteTransform);
119
120        FloatRect absoluteTileBoundaries = absoluteTransform.mapRect(tileBoundaries);
121
122        // Build tile image.
123        OwnPtr<ImageBuffer> tileImage = createTileImage(object, m_attributes, tileBoundaries, absoluteTileBoundaries, tileImageTransform);
124        if (!tileImage)
125            return false;
126
127        RefPtr<Image> copiedImage = tileImage->copyImage();
128        if (!copiedImage)
129            return false;
130
131        // Build pattern.
132        patternData->pattern = Pattern::create(copiedImage, true, true);
133        if (!patternData->pattern)
134            return false;
135
136        // Compute pattern space transformation.
137        patternData->transform.translate(tileBoundaries.x(), tileBoundaries.y());
138        patternData->transform.scale(tileBoundaries.width() / absoluteTileBoundaries.width(), tileBoundaries.height() / absoluteTileBoundaries.height());
139
140        AffineTransform patternTransform = m_attributes.patternTransform();
141        if (!patternTransform.isIdentity())
142            patternData->transform = patternTransform * patternData->transform;
143
144        patternData->pattern->setPatternSpaceTransform(patternData->transform);
145    }
146
147    // Draw pattern
148    context->save();
149
150    const SVGRenderStyle* svgStyle = style->svgStyle();
151    ASSERT(svgStyle);
152
153    if (resourceMode & ApplyToFillMode) {
154        context->setAlpha(svgStyle->fillOpacity());
155        context->setFillPattern(patternData->pattern);
156        context->setFillRule(svgStyle->fillRule());
157    } else if (resourceMode & ApplyToStrokeMode) {
158        if (svgStyle->vectorEffect() == VE_NON_SCALING_STROKE)
159            patternData->pattern->setPatternSpaceTransform(transformOnNonScalingStroke(object, patternData->transform));
160        context->setAlpha(svgStyle->strokeOpacity());
161        context->setStrokePattern(patternData->pattern);
162        SVGRenderSupport::applyStrokeStyleToContext(context, style, object);
163    }
164
165    if (resourceMode & ApplyToTextMode) {
166        if (resourceMode & ApplyToFillMode) {
167            context->setTextDrawingMode(TextModeFill);
168
169#if PLATFORM(CG)
170            context->applyFillPattern();
171#endif
172        } else if (resourceMode & ApplyToStrokeMode) {
173            context->setTextDrawingMode(TextModeStroke);
174
175#if PLATFORM(CG)
176            context->applyStrokePattern();
177#endif
178        }
179    }
180
181    return true;
182}
183
184void RenderSVGResourcePattern::postApplyResource(RenderObject*, GraphicsContext*& context, unsigned short resourceMode, const Path* path)
185{
186    ASSERT(context);
187    ASSERT(resourceMode != ApplyToDefaultMode);
188
189    if (path && !(resourceMode & ApplyToTextMode)) {
190        if (resourceMode & ApplyToFillMode)
191            context->fillPath(*path);
192        else if (resourceMode & ApplyToStrokeMode)
193            context->strokePath(*path);
194    }
195
196    context->restore();
197}
198
199static inline FloatRect calculatePatternBoundaries(const PatternAttributes& attributes,
200                                                   const FloatRect& objectBoundingBox,
201                                                   const SVGPatternElement* patternElement)
202{
203    ASSERT(patternElement);
204
205    if (attributes.boundingBoxMode())
206        return FloatRect(attributes.x().valueAsPercentage() * objectBoundingBox.width() + objectBoundingBox.x(),
207                         attributes.y().valueAsPercentage() * objectBoundingBox.height() + objectBoundingBox.y(),
208                         attributes.width().valueAsPercentage() * objectBoundingBox.width(),
209                         attributes.height().valueAsPercentage() * objectBoundingBox.height());
210
211    return FloatRect(attributes.x().value(patternElement),
212                     attributes.y().value(patternElement),
213                     attributes.width().value(patternElement),
214                     attributes.height().value(patternElement));
215}
216
217bool RenderSVGResourcePattern::buildTileImageTransform(RenderObject* renderer,
218                                                       const PatternAttributes& attributes,
219                                                       const SVGPatternElement* patternElement,
220                                                       FloatRect& patternBoundaries,
221                                                       AffineTransform& tileImageTransform) const
222{
223    ASSERT(renderer);
224    ASSERT(patternElement);
225
226    FloatRect objectBoundingBox = renderer->objectBoundingBox();
227    patternBoundaries = calculatePatternBoundaries(attributes, objectBoundingBox, patternElement);
228    if (patternBoundaries.width() <= 0 || patternBoundaries.height() <= 0)
229        return false;
230
231    AffineTransform viewBoxCTM = patternElement->viewBoxToViewTransform(attributes.viewBox(), attributes.preserveAspectRatio(), patternBoundaries.width(), patternBoundaries.height());
232
233    // Apply viewBox/objectBoundingBox transformations.
234    if (!viewBoxCTM.isIdentity())
235        tileImageTransform = viewBoxCTM;
236    else if (attributes.boundingBoxModeContent()) {
237        tileImageTransform.translate(objectBoundingBox.x(), objectBoundingBox.y());
238        tileImageTransform.scale(objectBoundingBox.width(), objectBoundingBox.height());
239    }
240
241    return true;
242}
243
244PassOwnPtr<ImageBuffer> RenderSVGResourcePattern::createTileImage(RenderObject* object,
245                                                                  const PatternAttributes& attributes,
246                                                                  const FloatRect& tileBoundaries,
247                                                                  const FloatRect& absoluteTileBoundaries,
248                                                                  const AffineTransform& tileImageTransform) const
249{
250    ASSERT(object);
251
252    // Clamp tile image size against SVG viewport size, as last resort, to avoid allocating huge image buffers.
253    FloatRect contentBoxRect = SVGRenderSupport::findTreeRootObject(object)->contentBoxRect();
254
255    FloatRect clampedAbsoluteTileBoundaries = absoluteTileBoundaries;
256    if (clampedAbsoluteTileBoundaries.width() > contentBoxRect.width())
257        clampedAbsoluteTileBoundaries.setWidth(contentBoxRect.width());
258
259    if (clampedAbsoluteTileBoundaries.height() > contentBoxRect.height())
260        clampedAbsoluteTileBoundaries.setHeight(contentBoxRect.height());
261
262    OwnPtr<ImageBuffer> tileImage;
263
264    if (!SVGImageBufferTools::createImageBuffer(absoluteTileBoundaries, clampedAbsoluteTileBoundaries, tileImage, ColorSpaceDeviceRGB))
265        return PassOwnPtr<ImageBuffer>();
266
267    GraphicsContext* tileImageContext = tileImage->context();
268    ASSERT(tileImageContext);
269
270    // The image buffer represents the final rendered size, so the content has to be scaled (to avoid pixelation).
271    tileImageContext->scale(FloatSize(absoluteTileBoundaries.width() / tileBoundaries.width(),
272                                      absoluteTileBoundaries.height() / tileBoundaries.height()));
273
274    // Apply tile image transformations.
275    if (!tileImageTransform.isIdentity())
276        tileImageContext->concatCTM(tileImageTransform);
277
278    AffineTransform contentTransformation;
279
280    // Draw the content into the ImageBuffer.
281    for (Node* node = attributes.patternContentElement()->firstChild(); node; node = node->nextSibling()) {
282        if (!node->isSVGElement() || !static_cast<SVGElement*>(node)->isStyled() || !node->renderer())
283            continue;
284        SVGImageBufferTools::renderSubtreeToImageBuffer(tileImage.get(), node->renderer(), contentTransformation);
285    }
286
287    return tileImage.release();
288}
289
290}
291
292#endif
293