15f2281c18950b3acc9b4eb4c84729664604e66f
[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 "KRenderingDeviceQuartz.h"
28 #include "SVGPaintServerLinearGradient.h"
29 #include "SVGPaintServerRadialGradient.h"
30 #include "RenderPath.h"
31 #include "QuartzSupport.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(KRenderingDeviceContext* context, const RenderObject* object, SVGPaintTargetType type) const
171 {
172     CGShadingRef shading = m_shadingCache;
173     KRenderingDeviceQuartz* quartzDevice = static_cast<KRenderingDeviceQuartz*>(renderingDevice());
174     CGContextRef contextRef = quartzDevice->currentCGContext();
175     RenderStyle* style = object->style();
176     ASSERT(contextRef != NULL);
177
178     if ((type & ApplyToFillTargetType) && style->svgStyle()->hasFill()) {
179         // workaround for filling the entire screen with the shading in the case that no text was intersected with the clip
180         if (!isPaintingText() || (object->width() > 0 && object->height() > 0))
181             CGContextDrawShading(contextRef, shading);
182         CGContextRestoreGState(contextRef);
183     }
184
185     if ((type & ApplyToStrokeTargetType) && style->svgStyle()->hasStroke()) {
186         if (isPaintingText()) {
187             int width  = 2048;
188             int height = 2048; // FIXME??? SEE ABOVE
189             delete quartzDevice->popContext();
190             contextRef = quartzDevice->currentCGContext();
191             void* imageBuffer = fastMalloc(width * height);
192             CGColorSpaceRef grayColorSpace = CGColorSpaceCreateDeviceGray();
193             CGContextRef grayscaleContext = CGBitmapContextCreate(imageBuffer, width, height, 8, width, grayColorSpace, kCGImageAlphaNone);
194             CGColorSpaceRelease(grayColorSpace);
195             CGContextDrawLayerAtPoint(grayscaleContext, CGPointMake(0, 0), m_maskImage->cgLayer());
196             CGImageRef grayscaleImage = CGBitmapContextCreateImage(grayscaleContext);
197             CGContextClipToMask(contextRef, CGRectMake(0, 0, width, height), grayscaleImage);
198             CGContextRelease(grayscaleContext);
199             CGImageRelease(grayscaleImage);
200         }
201         CGContextDrawShading(contextRef, shading);
202         CGContextRestoreGState(contextRef);
203     }
204
205     CGContextRestoreGState(contextRef);
206 }
207
208 void SVGPaintServerGradient::renderPath(KRenderingDeviceContext* context, const RenderPath* path, SVGPaintTargetType type) const
209 {
210     KRenderingDeviceQuartz* quartzDevice = static_cast<KRenderingDeviceQuartz*>(renderingDevice());
211     CGContextRef contextRef = quartzDevice->currentCGContext();
212     RenderStyle* style = path->style();
213     ASSERT(contextRef != NULL);
214
215     CGRect objectBBox;
216     if (boundingBoxMode())
217         objectBBox = CGContextGetPathBoundingBox(contextRef);
218     if ((type & ApplyToFillTargetType) && style->svgStyle()->hasFill())
219         clipToFillPath(contextRef, path);
220     if ((type & ApplyToStrokeTargetType) && style->svgStyle()->hasStroke())
221         clipToStrokePath(contextRef, path);
222     // make the gradient fit in the bbox if necessary.
223     if (boundingBoxMode()) { // no support for bounding boxes around text yet!
224         // get the object bbox
225         CGRect gradientBBox = CGRectMake(0,0,100,100); // FIXME - this is arbitrary no?
226         // generate a transform to map between the two.
227         CGAffineTransform gradientIntoObjectBBox = CGAffineTransformMakeMapBetweenRects(gradientBBox, objectBBox);
228         CGContextConcatCTM(contextRef, gradientIntoObjectBBox);
229     }
230
231     // apply the gradient's own transform
232     CGAffineTransform transform = gradientTransform();
233     CGContextConcatCTM(contextRef, transform);
234 }
235
236 bool SVGPaintServerGradient::setup(KRenderingDeviceContext* context, const RenderObject* object, SVGPaintTargetType type) const
237 {
238     if (listener()) // this seems like bad design to me, should be in a common baseclass. -- ecs 8/6/05
239         listener()->resourceNotification();
240
241     // FIXME: total const HACK!
242     // We need a hook to call this when the gradient gets updated, before drawn.
243     if (!m_shadingCache)
244         const_cast<SVGPaintServerGradient*>(this)->updateQuartzGradientCache(this);
245
246     KRenderingDeviceQuartz* quartzDevice = static_cast<KRenderingDeviceQuartz*>(renderingDevice());
247     CGContextRef contextRef = quartzDevice->currentCGContext();
248     RenderStyle* style = object->style();
249     ASSERT(contextRef != NULL);
250
251     CGContextSaveGState(contextRef);
252     CGContextSetAlpha(contextRef, style->opacity());
253
254     if ((type & ApplyToFillTargetType) && style->svgStyle()->hasFill()) {
255         CGContextSaveGState(contextRef);
256         if (isPaintingText())
257             CGContextSetTextDrawingMode(contextRef, kCGTextClip);
258     }
259
260     if ((type & ApplyToStrokeTargetType) && style->svgStyle()->hasStroke()) {
261         CGContextSaveGState(contextRef);
262         applyStrokeStyleToContext(contextRef, style, object); // FIXME: this seems like the wrong place for this.
263         if (isPaintingText()) {
264             m_maskImage = new SVGResourceImage();
265             int width  = 2048;
266             int height = 2048; // FIXME???
267             IntSize size = IntSize(width, height);
268             m_maskImage->init(size);
269             KRenderingDeviceContext* maskImageContext = quartzDevice->contextForImage(m_maskImage.get());
270             quartzDevice->pushContext(maskImageContext);
271             CGContextRef maskContext = static_cast<KRenderingDeviceContextQuartz*>(maskImageContext)->cgContext();
272             const_cast<RenderObject*>(object)->style()->setColor(Color(255, 255, 255));
273             CGContextSetTextDrawingMode(maskContext, kCGTextStroke);
274         }
275     }
276
277     return true;
278 }
279
280 void SVGPaintServerGradient::invalidate()
281 {
282     invalidateCaches();
283 }
284
285 } // namespace WebCore
286
287 #endif
288
289 // vim:ts=4:noet