2 Copyright (C) 2006, 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org>
4 This file is part of the KDE project
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public
8 License as published by the Free Software Foundation; either
9 version 2 of the License, or (at your option) any later version.
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Library General Public License for more details.
16 You should have received a copy of the GNU Library General Public License
17 aint with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
25 #include "SVGPaintServerGradient.h"
27 #include "FloatConversion.h"
28 #include "GraphicsContext.h"
29 #include "ImageBuffer.h"
30 #include "RenderObject.h"
31 #include "SVGGradientElement.h"
32 #include "SVGPaintServerLinearGradient.h"
33 #include "SVGPaintServerRadialGradient.h"
34 #include "SVGRenderSupport.h"
36 #include <wtf/MathExtras.h>
42 static void releaseCachedStops(void* info
)
44 static_cast<SVGPaintServerGradient::SharedStopCache
*>(info
)->deref();
47 static void cgGradientCallback(void* info
, const CGFloat
* inValues
, CGFloat
* outColor
)
49 SVGPaintServerGradient::SharedStopCache
* stopsCache
= static_cast<SVGPaintServerGradient::SharedStopCache
*>(info
);
51 SVGPaintServerGradient::QuartzGradientStop
* stops
= stopsCache
->m_stops
.data();
53 int stopsCount
= stopsCache
->m_stops
.size();
55 CGFloat inValue
= inValues
[0];
63 } else if (stopsCount
== 1) {
64 memcpy(outColor
, stops
[0].colorArray
, 4 * sizeof(CGFloat
));
68 if (!(inValue
> stops
[0].offset
))
69 memcpy(outColor
, stops
[0].colorArray
, 4 * sizeof(CGFloat
));
70 else if (!(inValue
< stops
[stopsCount
- 1].offset
))
71 memcpy(outColor
, stops
[stopsCount
- 1].colorArray
, 4 * sizeof(CGFloat
));
73 int nextStopIndex
= 0;
74 while ((nextStopIndex
< stopsCount
) && (stops
[nextStopIndex
].offset
< inValue
))
77 CGFloat
* nextColorArray
= stops
[nextStopIndex
].colorArray
;
78 CGFloat
* previousColorArray
= stops
[nextStopIndex
- 1].colorArray
;
79 CGFloat diffFromPrevious
= inValue
- stops
[nextStopIndex
- 1].offset
;
80 CGFloat percent
= diffFromPrevious
* stops
[nextStopIndex
].previousDeltaInverse
;
82 outColor
[0] = ((1.0f
- percent
) * previousColorArray
[0] + percent
* nextColorArray
[0]);
83 outColor
[1] = ((1.0f
- percent
) * previousColorArray
[1] + percent
* nextColorArray
[1]);
84 outColor
[2] = ((1.0f
- percent
) * previousColorArray
[2] + percent
* nextColorArray
[2]);
85 outColor
[3] = ((1.0f
- percent
) * previousColorArray
[3] + percent
* nextColorArray
[3]);
87 // FIXME: have to handle the spreadMethod()s here SPREADMETHOD_REPEAT, etc.
90 static CGShadingRef
CGShadingRefForLinearGradient(const SVGPaintServerLinearGradient
* server
)
92 CGPoint start
= CGPoint(server
->gradientStart());
93 CGPoint end
= CGPoint(server
->gradientEnd());
95 CGFunctionCallbacks callbacks
= {0, cgGradientCallback
, releaseCachedStops
};
96 CGFloat domainLimits
[2] = {0, 1};
97 CGFloat rangeLimits
[8] = {0, 1, 0, 1, 0, 1, 0, 1};
98 server
->m_stopsCache
->ref();
99 CGFunctionRef shadingFunction
= CGFunctionCreate(server
->m_stopsCache
.get(), 1, domainLimits
, 4, rangeLimits
, &callbacks
);
101 CGColorSpaceRef colorSpace
= CGColorSpaceCreateDeviceRGB();
102 CGShadingRef shading
= CGShadingCreateAxial(colorSpace
, start
, end
, shadingFunction
, true, true);
103 CGColorSpaceRelease(colorSpace
);
104 CGFunctionRelease(shadingFunction
);
108 static CGShadingRef
CGShadingRefForRadialGradient(const SVGPaintServerRadialGradient
* server
)
110 CGPoint center
= CGPoint(server
->gradientCenter());
111 CGPoint focus
= CGPoint(server
->gradientFocal());
112 double radius
= server
->gradientRadius();
114 double fdx
= focus
.x
- center
.x
;
115 double fdy
= focus
.y
- center
.y
;
117 // Spec: If (fx, fy) lies outside the circle defined by (cx, cy) and r, set (fx, fy)
118 // to the point of intersection of the line through (fx, fy) and the circle.
119 if (sqrt(fdx
* fdx
+ fdy
* fdy
) > radius
) {
120 double angle
= atan2(focus
.y
* 100.0, focus
.x
* 100.0);
121 focus
.x
= narrowPrecisionToCGFloat(cos(angle
) * radius
);
122 focus
.y
= narrowPrecisionToCGFloat(sin(angle
) * radius
);
125 CGFunctionCallbacks callbacks
= {0, cgGradientCallback
, releaseCachedStops
};
126 CGFloat domainLimits
[2] = {0, 1};
127 CGFloat rangeLimits
[8] = {0, 1, 0, 1, 0, 1, 0, 1};
128 server
->m_stopsCache
->ref();
129 CGFunctionRef shadingFunction
= CGFunctionCreate(server
->m_stopsCache
.get(), 1, domainLimits
, 4, rangeLimits
, &callbacks
);
131 CGColorSpaceRef colorSpace
= CGColorSpaceCreateDeviceRGB();
132 CGShadingRef shading
= CGShadingCreateRadial(colorSpace
, focus
, 0, center
, narrowPrecisionToCGFloat(radius
), shadingFunction
, true, true);
133 CGColorSpaceRelease(colorSpace
);
134 CGFunctionRelease(shadingFunction
);
138 void SVGPaintServerGradient::updateQuartzGradientStopsCache(const Vector
<SVGGradientStop
>& stops
)
140 m_stopsCache
= SharedStopCache::create();
141 Vector
<QuartzGradientStop
>& stopsCache
= m_stopsCache
->m_stops
;
142 stopsCache
.resize(stops
.size());
143 CGFloat previousOffset
= 0.0f
;
144 for (unsigned i
= 0; i
< stops
.size(); ++i
) {
145 CGFloat currOffset
= min(max(stops
[i
].first
, previousOffset
), static_cast<CGFloat
>(1.0));
146 stopsCache
[i
].offset
= currOffset
;
147 stopsCache
[i
].previousDeltaInverse
= 1.0f
/ (currOffset
- previousOffset
);
148 previousOffset
= currOffset
;
149 CGFloat
* ca
= stopsCache
[i
].colorArray
;
150 stops
[i
].second
.getRGBA(ca
[0], ca
[1], ca
[2], ca
[3]);
154 void SVGPaintServerGradient::updateQuartzGradientCache(const SVGPaintServerGradient
* server
)
156 // cache our own copy of the stops for faster access.
157 // this is legacy code, probably could be reworked.
159 updateQuartzGradientStopsCache(gradientStops());
161 CGShadingRelease(m_shadingCache
);
163 if (type() == RadialGradientPaintServer
) {
164 const SVGPaintServerRadialGradient
* radial
= static_cast<const SVGPaintServerRadialGradient
*>(server
);
165 m_shadingCache
= CGShadingRefForRadialGradient(radial
);
166 } else if (type() == LinearGradientPaintServer
) {
167 const SVGPaintServerLinearGradient
* linear
= static_cast<const SVGPaintServerLinearGradient
*>(server
);
168 m_shadingCache
= CGShadingRefForLinearGradient(linear
);
172 // Helper function for text painting
173 static inline const RenderObject
* findTextRootObject(const RenderObject
* start
)
175 while (start
&& !start
->isSVGText())
176 start
= start
->parent();
179 ASSERT(start
->isSVGText());
184 void SVGPaintServerGradient::teardown(GraphicsContext
*& context
, const RenderObject
* object
, SVGPaintTargetType type
, bool isPaintingText
) const
186 CGShadingRef shading
= m_shadingCache
;
187 CGContextRef contextRef
= context
->platformContext();
190 // As renderPath() is not used when painting text, special logic needed here.
191 if (isPaintingText
) {
192 if (m_savedContext
) {
193 FloatRect maskBBox
= const_cast<RenderObject
*>(findTextRootObject(object
))->relativeBBox(false);
195 // Fixup transformations to be able to clip to mask
196 AffineTransform transform
= object
->absoluteTransform();
197 FloatRect textBoundary
= transform
.mapRect(maskBBox
);
199 IntSize
maskSize(lroundf(textBoundary
.width()), lroundf(textBoundary
.height()));
200 clampImageBufferSizeToViewport(object
->document()->renderer(), maskSize
);
202 if (maskSize
.width() < static_cast<int>(textBoundary
.width()))
203 textBoundary
.setWidth(maskSize
.width());
205 if (maskSize
.height() < static_cast<int>(textBoundary
.height()))
206 textBoundary
.setHeight(maskSize
.height());
208 // Clip current context to mask image (gradient)
209 m_savedContext
->concatCTM(transform
.inverse());
210 m_savedContext
->clipToImageBuffer(textBoundary
, m_imageBuffer
);
211 m_savedContext
->concatCTM(transform
);
213 handleBoundingBoxModeAndGradientTransformation(m_savedContext
, maskBBox
);
215 // Restore on-screen drawing context, after we got the image of the gradient
216 delete m_imageBuffer
;
218 context
= m_savedContext
;
219 contextRef
= context
->platformContext();
226 CGContextDrawShading(contextRef
, shading
);
230 void SVGPaintServerGradient::renderPath(GraphicsContext
*& context
, const RenderObject
* path
, SVGPaintTargetType type
) const
232 RenderStyle
* style
= path
->style();
233 CGContextRef contextRef
= context
->platformContext();
236 bool isFilled
= (type
& ApplyToFillTargetType
) && style
->svgStyle()->hasFill();
238 // Compute destination object bounding box
239 FloatRect objectBBox
;
240 if (boundingBoxMode()) {
241 FloatRect bbox
= path
->relativeBBox(false);
242 if (bbox
.width() > 0 && bbox
.height() > 0)
247 clipToFillPath(contextRef
, path
);
249 clipToStrokePath(contextRef
, path
);
251 handleBoundingBoxModeAndGradientTransformation(context
, objectBBox
);
254 void SVGPaintServerGradient::handleBoundingBoxModeAndGradientTransformation(GraphicsContext
* context
, const FloatRect
& targetRect
) const
256 if (boundingBoxMode()) {
257 // Choose default gradient bounding box
258 FloatRect
gradientBBox(0.0f
, 0.0f
, 1.0f
, 1.0f
);
260 // Generate a transform to map between both bounding boxes
261 context
->concatCTM(makeMapBetweenRects(gradientBBox
, targetRect
));
264 // Apply the gradient's own transform
265 context
->concatCTM(gradientTransform());
268 bool SVGPaintServerGradient::setup(GraphicsContext
*& context
, const RenderObject
* object
, SVGPaintTargetType type
, bool isPaintingText
) const
270 m_ownerElement
->buildGradient();
272 // We need a hook to call this when the gradient gets updated, before drawn.
274 const_cast<SVGPaintServerGradient
*>(this)->updateQuartzGradientCache(this);
276 CGContextRef contextRef
= context
->platformContext();
279 RenderStyle
* style
= object
->style();
281 bool isFilled
= (type
& ApplyToFillTargetType
) && style
->svgStyle()->hasFill();
282 bool isStroked
= (type
& ApplyToStrokeTargetType
) && style
->svgStyle()->hasStroke();
284 ASSERT(isFilled
&& !isStroked
|| !isFilled
&& isStroked
);
287 CGContextSetAlpha(contextRef
, isFilled
? style
->svgStyle()->fillOpacity() : style
->svgStyle()->strokeOpacity());
289 if (isPaintingText
) {
290 FloatRect maskBBox
= const_cast<RenderObject
*>(findTextRootObject(object
))->relativeBBox(false);
291 IntRect maskRect
= enclosingIntRect(object
->absoluteTransform().mapRect(maskBBox
));
293 IntSize
maskSize(maskRect
.width(), maskRect
.height());
294 clampImageBufferSizeToViewport(object
->document()->renderer(), maskSize
);
296 auto_ptr
<ImageBuffer
> maskImage
= ImageBuffer::create(maskSize
, false);
298 if (!maskImage
.get()) {
303 GraphicsContext
* maskImageContext
= maskImage
->context();
304 maskImageContext
->save();
306 maskImageContext
->setTextDrawingMode(isFilled
? cTextFill
: cTextStroke
);
307 maskImageContext
->translate(-maskRect
.x(), -maskRect
.y());
308 maskImageContext
->concatCTM(object
->absoluteTransform());
310 m_imageBuffer
= maskImage
.release();
311 m_savedContext
= context
;
313 context
= maskImageContext
;
314 contextRef
= context
->platformContext();
318 applyStrokeStyleToContext(context
, style
, object
);
323 void SVGPaintServerGradient::invalidate()
325 SVGPaintServer::invalidate();
328 CGShadingRelease(m_shadingCache
);
334 } // namespace WebCore