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