|
1 /* |
|
2 Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org> |
|
3 |
|
4 This file is part of the KDE project |
|
5 |
|
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. |
|
10 |
|
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. |
|
15 |
|
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. |
|
20 */ |
|
21 |
|
22 #include "config.h" |
|
23 |
|
24 #if ENABLE(SVG) |
|
25 #include "SVGPaintServerGradient.h" |
|
26 |
|
27 #include "CgSupport.h" |
|
28 #include "FloatConversion.h" |
|
29 #include "GraphicsContext.h" |
|
30 #include "ImageBuffer.h" |
|
31 #include "RenderPath.h" |
|
32 #include "SVGGradientElement.h" |
|
33 #include "SVGPaintServerLinearGradient.h" |
|
34 #include "SVGPaintServerRadialGradient.h" |
|
35 |
|
36 using namespace std; |
|
37 |
|
38 namespace WebCore { |
|
39 |
|
40 static void cgGradientCallback(void* info, const CGFloat* inValues, CGFloat* outColor) |
|
41 { |
|
42 const SVGPaintServerGradient* server = reinterpret_cast<const SVGPaintServerGradient*>(info); |
|
43 SVGPaintServerGradient::QuartzGradientStop* stops = server->m_stopsCache; |
|
44 int stopsCount = server->m_stopsCount; |
|
45 |
|
46 CGFloat inValue = inValues[0]; |
|
47 |
|
48 if (!stopsCount) { |
|
49 outColor[0] = 0; |
|
50 outColor[1] = 0; |
|
51 outColor[2] = 0; |
|
52 outColor[3] = 0; |
|
53 return; |
|
54 } else if (stopsCount == 1) { |
|
55 memcpy(outColor, stops[0].colorArray, 4 * sizeof(CGFloat)); |
|
56 return; |
|
57 } |
|
58 |
|
59 if (!(inValue > stops[0].offset)) |
|
60 memcpy(outColor, stops[0].colorArray, 4 * sizeof(CGFloat)); |
|
61 else if (!(inValue < stops[stopsCount - 1].offset)) |
|
62 memcpy(outColor, stops[stopsCount - 1].colorArray, 4 * sizeof(CGFloat)); |
|
63 else { |
|
64 int nextStopIndex = 0; |
|
65 while ((nextStopIndex < stopsCount) && (stops[nextStopIndex].offset < inValue)) |
|
66 nextStopIndex++; |
|
67 |
|
68 CGFloat* nextColorArray = stops[nextStopIndex].colorArray; |
|
69 CGFloat* previousColorArray = stops[nextStopIndex - 1].colorArray; |
|
70 CGFloat diffFromPrevious = inValue - stops[nextStopIndex - 1].offset; |
|
71 CGFloat percent = diffFromPrevious * stops[nextStopIndex].previousDeltaInverse; |
|
72 |
|
73 outColor[0] = ((1.0f - percent) * previousColorArray[0] + percent * nextColorArray[0]); |
|
74 outColor[1] = ((1.0f - percent) * previousColorArray[1] + percent * nextColorArray[1]); |
|
75 outColor[2] = ((1.0f - percent) * previousColorArray[2] + percent * nextColorArray[2]); |
|
76 outColor[3] = ((1.0f - percent) * previousColorArray[3] + percent * nextColorArray[3]); |
|
77 } |
|
78 // FIXME: have to handle the spreadMethod()s here SPREADMETHOD_REPEAT, etc. |
|
79 } |
|
80 |
|
81 static CGShadingRef CGShadingRefForLinearGradient(const SVGPaintServerLinearGradient* server) |
|
82 { |
|
83 CGPoint start = CGPoint(server->gradientStart()); |
|
84 CGPoint end = CGPoint(server->gradientEnd()); |
|
85 |
|
86 CGFunctionCallbacks callbacks = {0, cgGradientCallback, NULL}; |
|
87 CGFloat domainLimits[2] = {0, 1}; |
|
88 CGFloat rangeLimits[8] = {0, 1, 0, 1, 0, 1, 0, 1}; |
|
89 CGFunctionRef shadingFunction = CGFunctionCreate((void *)server, 1, domainLimits, 4, rangeLimits, &callbacks); |
|
90 |
|
91 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); |
|
92 CGShadingRef shading = CGShadingCreateAxial(colorSpace, start, end, shadingFunction, true, true); |
|
93 CGColorSpaceRelease(colorSpace); |
|
94 CGFunctionRelease(shadingFunction); |
|
95 return shading; |
|
96 } |
|
97 |
|
98 static CGShadingRef CGShadingRefForRadialGradient(const SVGPaintServerRadialGradient* server) |
|
99 { |
|
100 CGPoint center = CGPoint(server->gradientCenter()); |
|
101 CGPoint focus = CGPoint(server->gradientFocal()); |
|
102 double radius = server->gradientRadius(); |
|
103 |
|
104 double fdx = focus.x - center.x; |
|
105 double fdy = focus.y - center.y; |
|
106 |
|
107 // Spec: If (fx, fy) lies outside the circle defined by (cx, cy) and r, set (fx, fy) |
|
108 // to the point of intersection of the line through (fx, fy) and the circle. |
|
109 if (sqrt(fdx * fdx + fdy * fdy) > radius) { |
|
110 double angle = atan2(focus.y * 100.0, focus.x * 100.0); |
|
111 focus.x = narrowPrecisionToCGFloat(cos(angle) * radius); |
|
112 focus.y = narrowPrecisionToCGFloat(sin(angle) * radius); |
|
113 } |
|
114 |
|
115 CGFunctionCallbacks callbacks = {0, cgGradientCallback, NULL}; |
|
116 CGFloat domainLimits[2] = {0, 1}; |
|
117 CGFloat rangeLimits[8] = {0, 1, 0, 1, 0, 1, 0, 1}; |
|
118 CGFunctionRef shadingFunction = CGFunctionCreate((void *)server, 1, domainLimits, 4, rangeLimits, &callbacks); |
|
119 |
|
120 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); |
|
121 CGShadingRef shading = CGShadingCreateRadial(colorSpace, focus, 0, center, narrowPrecisionToCGFloat(radius), shadingFunction, true, true); |
|
122 CGColorSpaceRelease(colorSpace); |
|
123 CGFunctionRelease(shadingFunction); |
|
124 return shading; |
|
125 } |
|
126 |
|
127 void SVGPaintServerGradient::updateQuartzGradientStopsCache(const Vector<SVGGradientStop>& stops) |
|
128 { |
|
129 delete m_stopsCache; |
|
130 |
|
131 m_stopsCount = stops.size(); |
|
132 m_stopsCache = new SVGPaintServerGradient::QuartzGradientStop[m_stopsCount]; |
|
133 |
|
134 CGFloat previousOffset = 0.0f; |
|
135 for (unsigned i = 0; i < stops.size(); ++i) { |
|
136 CGFloat currOffset = min(max(stops[i].first, previousOffset), static_cast<CGFloat>(1.0)); |
|
137 m_stopsCache[i].offset = currOffset; |
|
138 m_stopsCache[i].previousDeltaInverse = 1.0f / (currOffset - previousOffset); |
|
139 previousOffset = currOffset; |
|
140 CGFloat* ca = m_stopsCache[i].colorArray; |
|
141 stops[i].second.getRGBA(ca[0], ca[1], ca[2], ca[3]); |
|
142 } |
|
143 } |
|
144 |
|
145 void SVGPaintServerGradient::updateQuartzGradientCache(const SVGPaintServerGradient* server) |
|
146 { |
|
147 // cache our own copy of the stops for faster access. |
|
148 // this is legacy code, probably could be reworked. |
|
149 if (!m_stopsCache) |
|
150 updateQuartzGradientStopsCache(gradientStops()); |
|
151 |
|
152 CGShadingRelease(m_shadingCache); |
|
153 |
|
154 if (type() == RadialGradientPaintServer) { |
|
155 const SVGPaintServerRadialGradient* radial = static_cast<const SVGPaintServerRadialGradient*>(server); |
|
156 m_shadingCache = CGShadingRefForRadialGradient(radial); |
|
157 } else if (type() == LinearGradientPaintServer) { |
|
158 const SVGPaintServerLinearGradient* linear = static_cast<const SVGPaintServerLinearGradient*>(server); |
|
159 m_shadingCache = CGShadingRefForLinearGradient(linear); |
|
160 } |
|
161 } |
|
162 |
|
163 void SVGPaintServerGradient::teardown(GraphicsContext*& context, const RenderObject* object, SVGPaintTargetType type, bool isPaintingText) const |
|
164 { |
|
165 CGShadingRef shading = m_shadingCache; |
|
166 CGContextRef contextRef = context->platformContext(); |
|
167 RenderStyle* style = object->style(); |
|
168 ASSERT(contextRef); |
|
169 |
|
170 // As renderPath() is not used when painting text, special logic needed here. |
|
171 if (isPaintingText) { |
|
172 IntRect textBoundary = const_cast<RenderObject*>(object)->absoluteBoundingBoxRect(); |
|
173 FloatRect targetRect = object->absoluteTransform().inverse().mapRect(textBoundary); |
|
174 handleBoundingBoxModeAndGradientTransformation(context, targetRect); |
|
175 } |
|
176 |
|
177 if ((type & ApplyToFillTargetType) && style->svgStyle()->hasFill()) { |
|
178 // workaround for filling the entire screen with the shading in the case that no text was intersected with the clip |
|
179 if (!isPaintingText || (object->width() > 0 && object->height() > 0)) |
|
180 CGContextDrawShading(contextRef, shading); |
|
181 |
|
182 context->restore(); |
|
183 } |
|
184 |
|
185 if ((type & ApplyToStrokeTargetType) && style->svgStyle()->hasStroke()) { |
|
186 if (isPaintingText && m_savedContext) { |
|
187 IntRect maskRect = const_cast<RenderObject*>(object)->absoluteBoundingBoxRect(); |
|
188 maskRect = object->absoluteTransform().inverse().mapRect(maskRect); |
|
189 |
|
190 // Translate from 0x0 image origin to actual rendering position |
|
191 m_savedContext->translate(maskRect.x(), maskRect.y()); |
|
192 |
|
193 // Clip current context to mask image (gradient) |
|
194 CGContextClipToMask(m_savedContext->platformContext(), CGRectMake(0, 0, maskRect.width(), maskRect.height()), m_imageBuffer->cgImage()); |
|
195 m_savedContext->translate(-maskRect.x(), -maskRect.y()); |
|
196 |
|
197 // Restore on-screen drawing context, after we got the image of the gradient |
|
198 delete m_imageBuffer; |
|
199 context = m_savedContext; |
|
200 contextRef = context->platformContext(); |
|
201 m_savedContext = 0; |
|
202 m_imageBuffer = 0; |
|
203 } |
|
204 |
|
205 CGContextDrawShading(contextRef, shading); |
|
206 context->restore(); |
|
207 } |
|
208 |
|
209 context->restore(); |
|
210 } |
|
211 |
|
212 void SVGPaintServerGradient::renderPath(GraphicsContext*& context, const RenderPath* path, SVGPaintTargetType type) const |
|
213 { |
|
214 RenderStyle* style = path->style(); |
|
215 CGContextRef contextRef = context->platformContext(); |
|
216 ASSERT(contextRef); |
|
217 |
|
218 // Compute destination object bounding box |
|
219 FloatRect objectBBox; |
|
220 if (boundingBoxMode()) |
|
221 objectBBox = CGContextGetPathBoundingBox(contextRef); |
|
222 |
|
223 if ((type & ApplyToFillTargetType) && style->svgStyle()->hasFill()) |
|
224 clipToFillPath(contextRef, path); |
|
225 |
|
226 if ((type & ApplyToStrokeTargetType) && style->svgStyle()->hasStroke()) |
|
227 clipToStrokePath(contextRef, path); |
|
228 |
|
229 handleBoundingBoxModeAndGradientTransformation(context, objectBBox); |
|
230 } |
|
231 |
|
232 void SVGPaintServerGradient::handleBoundingBoxModeAndGradientTransformation(GraphicsContext* context, const FloatRect& targetRect) const |
|
233 { |
|
234 CGContextRef contextRef = context->platformContext(); |
|
235 |
|
236 if (boundingBoxMode()) { |
|
237 // Choose default gradient bounding box |
|
238 CGRect gradientBBox = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f); |
|
239 |
|
240 // Generate a transform to map between both bounding boxes |
|
241 CGAffineTransform gradientIntoObjectBBox = CGAffineTransformMakeMapBetweenRects(gradientBBox, CGRect(targetRect)); |
|
242 CGContextConcatCTM(contextRef, gradientIntoObjectBBox); |
|
243 } |
|
244 |
|
245 // Apply the gradient's own transform |
|
246 CGAffineTransform transform = gradientTransform(); |
|
247 CGContextConcatCTM(contextRef, transform); |
|
248 } |
|
249 |
|
250 bool SVGPaintServerGradient::setup(GraphicsContext*& context, const RenderObject* object, SVGPaintTargetType type, bool isPaintingText) const |
|
251 { |
|
252 m_ownerElement->buildGradient(); |
|
253 |
|
254 // We need a hook to call this when the gradient gets updated, before drawn. |
|
255 if (!m_shadingCache) |
|
256 const_cast<SVGPaintServerGradient*>(this)->updateQuartzGradientCache(this); |
|
257 |
|
258 CGContextRef contextRef = context->platformContext(); |
|
259 RenderStyle* style = object->style(); |
|
260 ASSERT(contextRef); |
|
261 |
|
262 context->save(); |
|
263 CGContextSetAlpha(contextRef, style->opacity()); |
|
264 |
|
265 if ((type & ApplyToFillTargetType) && style->svgStyle()->hasFill()) { |
|
266 context->save(); |
|
267 |
|
268 if (isPaintingText) |
|
269 context->setTextDrawingMode(cTextClip); |
|
270 } |
|
271 |
|
272 if ((type & ApplyToStrokeTargetType) && style->svgStyle()->hasStroke()) { |
|
273 context->save(); |
|
274 applyStrokeStyleToContext(contextRef, style, object); |
|
275 |
|
276 if (isPaintingText) { |
|
277 IntRect maskRect = const_cast<RenderObject*>(object)->absoluteBoundingBoxRect(); |
|
278 maskRect = object->absoluteTransform().inverse().mapRect(maskRect); |
|
279 |
|
280 auto_ptr<ImageBuffer> maskImage = ImageBuffer::create(IntSize(maskRect.width(), maskRect.height()), false); |
|
281 // FIXME: maskImage could be NULL |
|
282 |
|
283 GraphicsContext* maskImageContext = maskImage->context(); |
|
284 |
|
285 maskImageContext->save(); |
|
286 maskImageContext->translate(-maskRect.x(), -maskRect.y()); |
|
287 |
|
288 const_cast<RenderObject*>(object)->style()->setColor(Color(255, 255, 255)); |
|
289 maskImageContext->setTextDrawingMode(cTextStroke); |
|
290 |
|
291 m_imageBuffer = maskImage.release(); |
|
292 m_savedContext = context; |
|
293 context = maskImageContext; |
|
294 } |
|
295 } |
|
296 |
|
297 return true; |
|
298 } |
|
299 |
|
300 void SVGPaintServerGradient::invalidate() |
|
301 { |
|
302 // Invalidate caches |
|
303 delete m_stopsCache; |
|
304 CGShadingRelease(m_shadingCache); |
|
305 |
|
306 m_stopsCache = 0; |
|
307 m_shadingCache = 0; |
|
308 } |
|
309 |
|
310 } // namespace WebCore |
|
311 |
|
312 #endif |
|
313 |
|
314 // vim:ts=4:noet |