Reviewed by Eric. Rubber stamped by Oliver.
[WebKit-https.git] / WebCore / platform / graphics / svg / cg / SVGPaintServerGradientCg.cpp
1 /*
2     Copyright (C) 2006 Nikolas Zimmermann <wildfox@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 #ifdef SVG_SUPPORT
25 #include "SVGPaintServerGradient.h"
26
27 #include "GraphicsContext.h"
28 #include "SVGPaintServerLinearGradient.h"
29 #include "SVGPaintServerRadialGradient.h"
30 #include "RenderPath.h"
31 #include "CgSupport.h"
32
33 namespace WebCore {
34
35 static void cgGradientCallback(void* info, const CGFloat* inValues, CGFloat* outColor)
36 {
37     const SVGPaintServerGradient* server = (const SVGPaintServerGradient*)info;
38     SVGPaintServerGradient::QuartzGradientStop* stops = server->m_stopsCache;
39     int stopsCount = server->m_stopsCount;
40
41     CGFloat inValue = inValues[0];
42
43     if (!stopsCount) {
44         outColor[0] = 0;
45         outColor[1] = 0;
46         outColor[2] = 0;
47         outColor[3] = 1;
48         return;
49     } else if (stopsCount == 1) {
50         memcpy(outColor, stops[0].colorArray, 4 * sizeof(CGFloat));
51         return;
52     }
53
54     if (!(inValue > stops[0].offset))
55         memcpy(outColor, stops[0].colorArray, 4 * sizeof(CGFloat));
56     else if (!(inValue < stops[stopsCount-1].offset))
57         memcpy(outColor, stops[stopsCount-1].colorArray, 4 * sizeof(CGFloat));
58     else {
59         int nextStopIndex = 0;
60         while ((nextStopIndex < stopsCount) && (stops[nextStopIndex].offset < inValue))
61             nextStopIndex++;
62
63         //float nextOffset = stops[nextStopIndex].offset;
64         CGFloat *nextColorArray = stops[nextStopIndex].colorArray;
65         CGFloat *previousColorArray = stops[nextStopIndex-1].colorArray;
66         //float totalDelta = nextOffset - previousOffset;
67         CGFloat diffFromPrevious = inValue - stops[nextStopIndex-1].offset;
68         //float percent = diffFromPrevious / totalDelta;
69         CGFloat percent = diffFromPrevious * stops[nextStopIndex].previousDeltaInverse;
70
71         outColor[0] = ((1.0 - percent) * previousColorArray[0] + percent * nextColorArray[0]);
72         outColor[1] = ((1.0 - percent) * previousColorArray[1] + percent * nextColorArray[1]);
73         outColor[2] = ((1.0 - percent) * previousColorArray[2] + percent * nextColorArray[2]);
74         outColor[3] = ((1.0 - percent) * previousColorArray[3] + percent * nextColorArray[3]);
75     }
76     // FIXME: have to handle the spreadMethod()s here SPREADMETHOD_REPEAT, etc.
77 }
78
79 static CGShadingRef CGShadingRefForLinearGradient(const SVGPaintServerLinearGradient* server)
80 {
81     CGPoint start = CGPoint(server->gradientStart());
82     CGPoint end = CGPoint(server->gradientEnd());
83
84     CGFunctionCallbacks callbacks = {0, cgGradientCallback, NULL};
85     CGFloat domainLimits[2] = {0, 1};
86     CGFloat rangeLimits[8] = {0, 1, 0, 1, 0, 1, 0, 1};
87     CGFunctionRef shadingFunction = CGFunctionCreate((void *)server, 1, domainLimits, 4, rangeLimits, &callbacks);
88
89     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
90     CGShadingRef shading = CGShadingCreateAxial(colorSpace, start, end, shadingFunction, true, true);
91     CGColorSpaceRelease(colorSpace);
92     CGFunctionRelease(shadingFunction);
93     return shading;
94 }
95
96 static CGShadingRef CGShadingRefForRadialGradient(const SVGPaintServerRadialGradient* server)
97 {
98     CGPoint center = CGPoint(server->gradientCenter());
99     CGPoint focus = CGPoint(server->gradientFocal());
100     double radius = server->gradientRadius();
101
102     double fdx = focus.x - center.x;
103     double fdy = focus.y - center.y;
104
105     // Spec: If (fx, fy) lies outside the circle defined by (cx, cy) and r, set (fx, fy)
106     // to the point of intersection of the line through (fx, fy) and the circle.
107     if (sqrt(fdx*fdx + fdy*fdy) > radius) {
108         double angle = atan2(focus.y, focus.x);
109         focus.x = int(cos(angle) * radius) - 1;
110         focus.y = int(sin(angle) * radius) - 1;
111     }
112
113     CGFunctionCallbacks callbacks = {0, cgGradientCallback, NULL};
114     CGFloat domainLimits[2] = {0, 1};
115     CGFloat rangeLimits[8] = {0, 1, 0, 1, 0, 1, 0, 1};
116     CGFunctionRef shadingFunction = CGFunctionCreate((void *)server, 1, domainLimits, 4, rangeLimits, &callbacks);
117
118     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
119     CGShadingRef shading = CGShadingCreateRadial(colorSpace, focus, 0, center, radius, shadingFunction, true, true);
120     CGColorSpaceRelease(colorSpace);
121     CGFunctionRelease(shadingFunction);
122     return shading;
123 }
124
125 void SVGPaintServerGradient::invalidateCaches()
126 {
127     delete m_stopsCache;
128     CGShadingRelease(m_shadingCache);
129
130     m_stopsCache = 0;
131     m_shadingCache = 0;
132 }
133
134 void SVGPaintServerGradient::updateQuartzGradientStopsCache(const Vector<SVGGradientStop>& stops)
135 {
136     delete m_stopsCache;
137
138     m_stopsCount = stops.size();
139     m_stopsCache = new SVGPaintServerGradient::QuartzGradientStop[m_stopsCount];
140
141     CGFloat previousOffset = 0.0;
142     for (unsigned i = 0; i < stops.size(); ++i) {
143         m_stopsCache[i].offset = stops[i].first;
144         m_stopsCache[i].previousDeltaInverse = 1.0 / (stops[i].first - previousOffset);
145         previousOffset = stops[i].first;
146         CGFloat *ca = m_stopsCache[i].colorArray;
147         stops[i].second.getRGBA(ca[0], ca[1], ca[2], ca[3]);
148     }
149 }
150
151 void SVGPaintServerGradient::updateQuartzGradientCache(const SVGPaintServerGradient* server)
152 {
153     // cache our own copy of the stops for faster access.
154     // this is legacy code, probably could be reworked.
155     if (!m_stopsCache)
156         updateQuartzGradientStopsCache(gradientStops());
157
158     if (m_shadingCache)
159         CGShadingRelease(m_shadingCache);
160
161     if (type() == RadialGradientPaintServer) {
162         const SVGPaintServerRadialGradient* radial = static_cast<const SVGPaintServerRadialGradient*>(server);
163         m_shadingCache = CGShadingRefForRadialGradient(radial);
164     } else if (type() == LinearGradientPaintServer) {
165         const SVGPaintServerLinearGradient* linear = static_cast<const SVGPaintServerLinearGradient*>(server);
166         m_shadingCache = CGShadingRefForLinearGradient(linear);
167     }
168 }
169
170 void SVGPaintServerGradient::teardown(GraphicsContext*& context, const RenderObject* object, SVGPaintTargetType type) const
171 {
172     CGShadingRef shading = m_shadingCache;
173     CGContextRef contextRef = context->platformContext();
174     RenderStyle* style = object->style();
175     ASSERT(contextRef != NULL);
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         CGContextRestoreGState(contextRef);        
182     }
183
184     if ((type & ApplyToStrokeTargetType) && style->svgStyle()->hasStroke()) {
185         if (isPaintingText()) {
186             int width  = 2048;
187             int height = 2048; // FIXME??? SEE ABOVE
188
189             delete context;
190             context = m_savedContext;
191             contextRef = context->platformContext();
192             m_savedContext = 0;
193
194             void* imageBuffer = fastMalloc(width * height);
195             CGColorSpaceRef grayColorSpace = CGColorSpaceCreateDeviceGray();
196             CGContextRef grayscaleContext = CGBitmapContextCreate(imageBuffer, width, height, 8, width, grayColorSpace, kCGImageAlphaNone);
197             CGColorSpaceRelease(grayColorSpace);
198             CGContextDrawLayerAtPoint(grayscaleContext, CGPointMake(0, 0), m_maskImage->cgLayer());
199             CGImageRef grayscaleImage = CGBitmapContextCreateImage(grayscaleContext);
200             CGContextClipToMask(contextRef, CGRectMake(0, 0, width, height), grayscaleImage);
201             CGContextRelease(grayscaleContext);
202             CGImageRelease(grayscaleImage);
203         }
204         CGContextDrawShading(contextRef, shading);
205         CGContextRestoreGState(contextRef);        
206     }
207
208     CGContextRestoreGState(contextRef);
209 }
210
211 void SVGPaintServerGradient::renderPath(GraphicsContext*& context, const RenderPath* path, SVGPaintTargetType type) const
212 {
213     CGContextRef contextRef = context->platformContext();
214     RenderStyle* style = path->style();
215     ASSERT(contextRef != NULL);
216
217     CGRect objectBBox;
218     if (boundingBoxMode())
219         objectBBox = CGContextGetPathBoundingBox(contextRef);
220     if ((type & ApplyToFillTargetType) && style->svgStyle()->hasFill())
221         clipToFillPath(contextRef, path);
222     if ((type & ApplyToStrokeTargetType) && style->svgStyle()->hasStroke())
223         clipToStrokePath(contextRef, path);
224     // make the gradient fit in the bbox if necessary.
225     if (boundingBoxMode()) { // no support for bounding boxes around text yet!
226         // get the object bbox
227         CGRect gradientBBox = CGRectMake(0,0,100,100); // FIXME - this is arbitrary no?
228         // generate a transform to map between the two.
229         CGAffineTransform gradientIntoObjectBBox = CGAffineTransformMakeMapBetweenRects(gradientBBox, objectBBox);
230         CGContextConcatCTM(contextRef, gradientIntoObjectBBox);
231     }
232
233     // apply the gradient's own transform
234     CGAffineTransform transform = gradientTransform();
235     CGContextConcatCTM(contextRef, transform);
236 }
237
238 bool SVGPaintServerGradient::setup(GraphicsContext*& context, const RenderObject* object, SVGPaintTargetType type) const
239 {
240     if (listener()) // this seems like bad design to me, should be in a common baseclass. -- ecs 8/6/05
241         listener()->resourceNotification();
242
243     // FIXME: total const HACK!
244     // We need a hook to call this when the gradient gets updated, before drawn.
245     if (!m_shadingCache)
246         const_cast<SVGPaintServerGradient*>(this)->updateQuartzGradientCache(this);
247
248     CGContextRef contextRef = context->platformContext();
249     RenderStyle* style = object->style();
250     ASSERT(contextRef != NULL);
251
252     CGContextSaveGState(contextRef);
253     CGContextSetAlpha(contextRef, style->opacity());
254
255     if ((type & ApplyToFillTargetType) && style->svgStyle()->hasFill()) {
256         CGContextSaveGState(contextRef);        
257         if (isPaintingText())
258             CGContextSetTextDrawingMode(contextRef, kCGTextClip);
259     }
260
261     if ((type & ApplyToStrokeTargetType) && style->svgStyle()->hasStroke()) {
262         CGContextSaveGState(contextRef);        
263         applyStrokeStyleToContext(contextRef, style, object); // FIXME: this seems like the wrong place for this.
264         if (isPaintingText()) {
265             m_maskImage = new SVGResourceImage();
266             int width  = 2048;
267             int height = 2048; // FIXME???
268             IntSize size = IntSize(width, height);
269             m_maskImage->init(size);
270
271             GraphicsContext* maskImageContext = contextForImage(m_maskImage.get());
272             CGContextRef maskContext = maskImageContext->platformContext();
273             const_cast<RenderObject*>(object)->style()->setColor(Color(255, 255, 255));
274             CGContextSetTextDrawingMode(maskContext, kCGTextStroke);
275
276             m_savedContext = context;
277             context = maskImageContext;
278         }
279     }
280
281     return true;
282 }
283
284 void SVGPaintServerGradient::invalidate()
285 {
286     invalidateCaches();
287 }
288
289 } // namespace WebCore
290
291 #endif
292
293 // vim:ts=4:noet