2006-10-05 Eric Seidel <eric@eseidel.com>
[WebKit-https.git] / WebCore / kcanvas / device / quartz / KRenderingPaintServerGradientQuartz.mm
1 /*
2  * Copyright (C) 2005 Apple Computer, Inc.  All rights reserved.
3  *               2006 Alexander Kellett <lypanov@kde.org>
4  *               2006 Rob Buis <buis@kde.org>
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
16  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
26  */
27
28
29 #include "config.h"
30 #ifdef SVG_SUPPORT
31 #import "KRenderingPaintServerQuartz.h"
32
33 #import "KCanvasImage.h"
34 #import "KCanvasResourcesQuartz.h"
35 #import "KRenderingDeviceQuartz.h"
36 #import "KRenderingFillPainter.h"
37 #import "KRenderingPaintServer.h"
38 #import "KRenderingStrokePainter.h"
39 #import "QuartzSupport.h"
40 #import "RenderObject.h"
41
42 #import <wtf/Assertions.h>
43
44 namespace WebCore {
45     
46 static void cgGradientCallback(void* info, const CGFloat* inValues, CGFloat* outColor)
47 {
48     const KRenderingPaintServerGradientQuartz* server = (const KRenderingPaintServerGradientQuartz*)info;
49     QuartzGradientStop *stops = server->m_stopsCache;
50     int stopsCount = server->m_stopsCount;
51     
52     CGFloat inValue = inValues[0];
53     
54     if (!stopsCount) {
55         outColor[0] = 0;
56         outColor[1] = 0;
57         outColor[2] = 0;
58         outColor[3] = 1;
59         return;
60     } else if (stopsCount == 1) {
61         memcpy(outColor, stops[0].colorArray, 4 * sizeof(CGFloat));
62         return;
63     }
64     
65     if (!(inValue > stops[0].offset))
66         memcpy(outColor, stops[0].colorArray, 4 * sizeof(CGFloat));
67     else if (!(inValue < stops[stopsCount-1].offset))
68         memcpy(outColor, stops[stopsCount-1].colorArray, 4 * sizeof(CGFloat));
69     else {
70         int nextStopIndex = 0;
71         while ((nextStopIndex < stopsCount) && (stops[nextStopIndex].offset < inValue))
72             nextStopIndex++;
73         
74         //float nextOffset = stops[nextStopIndex].offset;
75         CGFloat *nextColorArray = stops[nextStopIndex].colorArray;
76         CGFloat *previousColorArray = stops[nextStopIndex-1].colorArray;
77         //float totalDelta = nextOffset - previousOffset;
78         CGFloat diffFromPrevious = inValue - stops[nextStopIndex-1].offset;
79         //float percent = diffFromPrevious / totalDelta;
80         CGFloat percent = diffFromPrevious * stops[nextStopIndex].previousDeltaInverse;
81         
82         outColor[0] = ((1.0 - percent) * previousColorArray[0] + percent * nextColorArray[0]);
83         outColor[1] = ((1.0 - percent) * previousColorArray[1] + percent * nextColorArray[1]);
84         outColor[2] = ((1.0 - percent) * previousColorArray[2] + percent * nextColorArray[2]);
85         outColor[3] = ((1.0 - percent) * previousColorArray[3] + percent * nextColorArray[3]);
86     }
87     // FIXME: have to handle the spreadMethod()s here SPREADMETHOD_REPEAT, etc.
88 }
89
90 static CGShadingRef CGShadingRefForLinearGradient(const KRenderingPaintServerLinearGradientQuartz* server)
91 {
92     CGPoint start = CGPoint(server->gradientStart());
93     CGPoint end = CGPoint(server->gradientEnd());
94     
95     CGFunctionCallbacks callbacks = {0, cgGradientCallback, NULL};
96     CGFloat domainLimits[2] = {0, 1};
97     CGFloat rangeLimits[8] = {0, 1, 0, 1, 0, 1, 0, 1};
98     const KRenderingPaintServerGradientQuartz* castServer = static_cast<const KRenderingPaintServerGradientQuartz*>(server);
99     CGFunctionRef shadingFunction = CGFunctionCreate((void *)castServer, 1, domainLimits, 4, rangeLimits, &callbacks);
100     
101     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
102     CGShadingRef shading = CGShadingCreateAxial(colorSpace, start, end, shadingFunction, true, true);
103     CGColorSpaceRelease(colorSpace);
104     CGFunctionRelease(shadingFunction);
105     return shading;
106 }
107
108 static CGShadingRef CGShadingRefForRadialGradient(const KRenderingPaintServerRadialGradientQuartz* server)
109 {
110     CGPoint center = CGPoint(server->gradientCenter());
111     CGPoint focus = CGPoint(server->gradientFocal());
112     double radius = server->gradientRadius();
113     
114     double fdx = focus.x - center.x;
115     double fdy = focus.y - center.y;
116     
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, focus.x);
121         focus.x = int(cos(angle) * radius) - 1;
122         focus.y = int(sin(angle) * radius) - 1;
123     }
124     
125     CGFunctionCallbacks callbacks = {0, cgGradientCallback, NULL};
126     CGFloat domainLimits[2] = {0, 1};
127     CGFloat rangeLimits[8] = {0, 1, 0, 1, 0, 1, 0, 1};
128     const KRenderingPaintServerGradientQuartz* castServer = static_cast<const KRenderingPaintServerGradientQuartz*>(server);
129     CGFunctionRef shadingFunction = CGFunctionCreate((void *)castServer, 1, domainLimits, 4, rangeLimits, &callbacks);
130     
131     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
132     CGShadingRef shading = CGShadingCreateRadial(colorSpace, focus, 0, center, radius, shadingFunction, true, true);
133     CGColorSpaceRelease(colorSpace);
134     CGFunctionRelease(shadingFunction);
135     return shading;
136 }
137
138 KRenderingPaintServerGradientQuartz::KRenderingPaintServerGradientQuartz()
139     : m_stopsCache(0)
140     , m_stopsCount(0)
141     , m_shadingCache(0)
142     , m_maskImage(0)
143 {
144 }
145
146 KRenderingPaintServerGradientQuartz::~KRenderingPaintServerGradientQuartz()
147 {
148     delete m_stopsCache;
149     CGShadingRelease(m_shadingCache);
150     delete m_maskImage;
151 }
152
153 void KRenderingPaintServerGradientQuartz::updateQuartzGradientCache(const KRenderingPaintServerGradient *server)
154 {
155     // cache our own copy of the stops for faster access.
156     // this is legacy code, probably could be reworked.
157     if (!m_stopsCache)
158         updateQuartzGradientStopsCache(server->gradientStops());
159     
160     if (!m_stopsCount)
161         NSLog(@"Warning, no gradient stops, gradient (%p) will be all black!", this);
162     
163     if (m_shadingCache)
164         CGShadingRelease(m_shadingCache);
165     if (server->type() == PS_RADIAL_GRADIENT) {
166         const KRenderingPaintServerRadialGradientQuartz* radial = static_cast<const KRenderingPaintServerRadialGradientQuartz*>(server);
167         m_shadingCache = CGShadingRefForRadialGradient(radial);
168     } else if (server->type() == PS_LINEAR_GRADIENT) {
169         const KRenderingPaintServerLinearGradientQuartz* linear = static_cast<const KRenderingPaintServerLinearGradientQuartz*>(server);
170         m_shadingCache = CGShadingRefForLinearGradient(linear);
171     }
172 }
173
174 void KRenderingPaintServerGradientQuartz::updateQuartzGradientStopsCache(const Vector<KCGradientStop>& stops)
175 {
176     delete m_stopsCache;
177
178     m_stopsCount = stops.size();
179     m_stopsCache = new QuartzGradientStop[m_stopsCount];
180     
181     CGFloat previousOffset = 0.0;
182     for (unsigned i = 0; i < stops.size(); ++i) {
183         m_stopsCache[i].offset = stops[i].first;
184         m_stopsCache[i].previousDeltaInverse = 1.0 / (stops[i].first - previousOffset);
185         previousOffset = stops[i].first;
186         CGFloat *ca = m_stopsCache[i].colorArray;
187         stops[i].second.getRGBA(ca[0], ca[1], ca[2], ca[3]);
188     }
189 }
190
191 void KRenderingPaintServerGradientQuartz::invalidateCaches()
192 {
193     delete m_stopsCache;
194     CGShadingRelease(m_shadingCache);
195     
196     m_stopsCache = 0;
197     m_shadingCache = 0;
198 }
199
200 void KRenderingPaintServerLinearGradientQuartz::invalidate()
201 {
202     invalidateCaches();
203     KRenderingPaintServerLinearGradient::invalidate();
204 }
205
206 void KRenderingPaintServerRadialGradientQuartz::invalidate()
207 {
208     invalidateCaches();
209     KRenderingPaintServerRadialGradient::invalidate();
210 }
211
212 void KRenderingPaintServerGradientQuartz::draw(const KRenderingPaintServerGradient* server, KRenderingDeviceContext* renderingContext, const RenderPath* path, KCPaintTargetType type) const
213 {
214     if (!setup(server, renderingContext, path, type))
215         return;
216     renderPath(server, renderingContext, path, type);
217     teardown(server, renderingContext, path, type);
218 }
219
220 bool KRenderingPaintServerGradientQuartz::setup(const KRenderingPaintServerGradient* server, KRenderingDeviceContext* renderingContext, const RenderObject* renderObject, KCPaintTargetType type) const
221 {
222     if (server->listener()) // this seems like bad design to me, should be in a common baseclass. -- ecs 8/6/05
223         server->listener()->resourceNotification();
224     
225     delete m_maskImage;
226     m_maskImage = 0;
227
228     // FIXME: total const HACK!
229     // We need a hook to call this when the gradient gets updated, before drawn.
230     if (!m_shadingCache)
231         const_cast<KRenderingPaintServerGradientQuartz*>(this)->updateQuartzGradientCache(server);
232     
233     KRenderingDeviceQuartz* quartzDevice = static_cast<KRenderingDeviceQuartz*>(renderingDevice());
234     CGContextRef context = quartzDevice->currentCGContext();
235     RenderStyle* renderStyle = renderObject->style();
236     ASSERT(context != NULL);
237     
238     CGContextSaveGState(context);
239     CGContextSetAlpha(context, renderStyle->opacity());
240     
241     if ((type & APPLY_TO_FILL) && renderStyle->svgStyle()->hasFill()) {
242         CGContextSaveGState(context);
243         if (server->isPaintingText())
244             CGContextSetTextDrawingMode(context, kCGTextClip);
245     }
246
247     if ((type & APPLY_TO_STROKE) && renderStyle->svgStyle()->hasStroke()) {
248         CGContextSaveGState(context);
249         applyStrokeStyleToContext(context, renderStyle, renderObject); // FIXME: this seems like the wrong place for this.
250         if (server->isPaintingText()) {
251             m_maskImage = static_cast<KCanvasImage*>(quartzDevice->createResource(RS_IMAGE));
252             int width  = 2048;
253             int height = 2048; // FIXME???
254             IntSize size = IntSize(width, height);
255             m_maskImage->init(size);
256             KRenderingDeviceContext* maskImageContext = quartzDevice->contextForImage(m_maskImage);
257             quartzDevice->pushContext(maskImageContext);
258             CGContextRef maskContext = static_cast<KRenderingDeviceContextQuartz*>(maskImageContext)->cgContext();
259             const_cast<RenderObject*>(renderObject)->style()->setColor(Color(255, 255, 255));
260             CGContextSetTextDrawingMode(maskContext, kCGTextStroke);
261         }
262     }
263     return true;
264 }
265
266 void KRenderingPaintServerGradientQuartz::renderPath(const KRenderingPaintServerGradient* server, KRenderingDeviceContext* renderingContext, const RenderPath* path, KCPaintTargetType type) const
267 {    
268     KRenderingDeviceQuartz* quartzDevice = static_cast<KRenderingDeviceQuartz*>(renderingDevice());
269     CGContextRef context = quartzDevice->currentCGContext();
270     RenderStyle* renderStyle = path->style();
271     ASSERT(context != NULL);
272     
273     CGRect objectBBox;
274     if (server->boundingBoxMode())
275         objectBBox = CGContextGetPathBoundingBox(context);
276     if ((type & APPLY_TO_FILL) && renderStyle->svgStyle()->hasFill())
277         KRenderingPaintServerQuartzHelper::clipToFillPath(context, path);
278     if ((type & APPLY_TO_STROKE) && renderStyle->svgStyle()->hasStroke())
279         KRenderingPaintServerQuartzHelper::clipToStrokePath(context, path);
280     // make the gradient fit in the bbox if necessary.
281     if (server->boundingBoxMode()) { // no support for bounding boxes around text yet!
282         // get the object bbox
283         CGRect gradientBBox = CGRectMake(0,0,100,100); // FIXME - this is arbitrary no?
284         // generate a transform to map between the two.
285         CGAffineTransform gradientIntoObjectBBox = CGAffineTransformMakeMapBetweenRects(gradientBBox, objectBBox);
286         CGContextConcatCTM(context, gradientIntoObjectBBox);
287     }
288     
289     // apply the gradient's own transform
290     CGAffineTransform gradientTransform = server->gradientTransform();
291     CGContextConcatCTM(context, gradientTransform);
292 }
293
294 void KRenderingPaintServerGradientQuartz::teardown(const KRenderingPaintServerGradient *server, KRenderingDeviceContext* renderingContext, const RenderObject* renderObject, KCPaintTargetType type) const
295
296     CGShadingRef shading = m_shadingCache;
297     KCanvasImage* maskImage = m_maskImage;
298     KRenderingDeviceQuartz* quartzDevice = static_cast<KRenderingDeviceQuartz*>(renderingDevice());
299     CGContextRef context = quartzDevice->currentCGContext();
300     RenderStyle* renderStyle = renderObject->style();
301     ASSERT(context != NULL);
302     
303     if ((type & APPLY_TO_FILL) && renderStyle->svgStyle()->hasFill()) {
304         // workaround for filling the entire screen with the shading in the case that no text was intersected with the clip
305         if (!server->isPaintingText() || (renderObject->width() > 0 && renderObject->height() > 0))
306             CGContextDrawShading(context, shading);
307         CGContextRestoreGState(context);
308     }
309     
310     if ((type & APPLY_TO_STROKE) && renderStyle->svgStyle()->hasStroke()) {
311         if (server->isPaintingText()) {
312             int width  = 2048;
313             int height = 2048; // FIXME??? SEE ABOVE
314             delete quartzDevice->popContext();
315             context = quartzDevice->currentCGContext();
316             void* imageBuffer = fastMalloc(width * height);
317             CGColorSpaceRef grayColorSpace = CGColorSpaceCreateDeviceGray();
318             CGContextRef grayscaleContext = CGBitmapContextCreate(imageBuffer, width, height, 8, width, grayColorSpace, kCGImageAlphaNone);
319             CGColorSpaceRelease(grayColorSpace);
320             KCanvasImageQuartz* qMaskImage = static_cast<KCanvasImageQuartz*>(maskImage);
321             CGContextDrawLayerAtPoint(grayscaleContext, CGPointMake(0, 0), qMaskImage->cgLayer());
322             CGImageRef grayscaleImage = CGBitmapContextCreateImage(grayscaleContext);
323             CGContextClipToMask(context, CGRectMake(0, 0, width, height), grayscaleImage);
324             CGContextRelease(grayscaleContext);
325             CGImageRelease(grayscaleImage);
326         }
327         CGContextDrawShading(context, shading);
328         CGContextRestoreGState(context);
329     }
330     
331     CGContextRestoreGState(context);
332 }
333
334 }
335
336 #endif // SVG_SUPPORT