09713af0367943e25c33edf53ce936881a73b78d
[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 #import "QuartzSupport.h"
33
34 #import "RenderObject.h"
35
36 #import "KCanvasRenderingStyle.h"
37 #import "KRenderingPaintServer.h"
38 #import "KRenderingFillPainter.h"
39 #import "KRenderingStrokePainter.h"
40 #import "KRenderingDeviceQuartz.h"
41
42 #import "KCanvasResourcesQuartz.h"
43 #import "KCanvasImage.h"
44
45 #import <wtf/Assertions.h>
46
47 namespace WebCore {
48     
49 static void cgGradientCallback(void* info, const CGFloat* inValues, CGFloat* outColor)
50 {
51     const KRenderingPaintServerGradientQuartz* server = (const KRenderingPaintServerGradientQuartz*)info;
52     QuartzGradientStop *stops = server->m_stopsCache;
53     int stopsCount = server->m_stopsCount;
54     
55     CGFloat inValue = inValues[0];
56     
57     if (!stopsCount) {
58         outColor[0] = 0;
59         outColor[1] = 0;
60         outColor[2] = 0;
61         outColor[3] = 1;
62         return;
63     } else if (stopsCount == 1) {
64         memcpy(outColor, stops[0].colorArray, 4 * sizeof(CGFloat));
65         return;
66     }
67     
68     if (!(inValue > stops[0].offset))
69         memcpy(outColor, stops[0].colorArray, 4 * sizeof(CGFloat));
70     else if (!(inValue < stops[stopsCount-1].offset))
71         memcpy(outColor, stops[stopsCount-1].colorArray, 4 * sizeof(CGFloat));
72     else {
73         int nextStopIndex = 0;
74         while ((nextStopIndex < stopsCount) && (stops[nextStopIndex].offset < inValue))
75             nextStopIndex++;
76         
77         //float nextOffset = stops[nextStopIndex].offset;
78         CGFloat *nextColorArray = stops[nextStopIndex].colorArray;
79         CGFloat *previousColorArray = stops[nextStopIndex-1].colorArray;
80         //float totalDelta = nextOffset - previousOffset;
81         CGFloat diffFromPrevious = inValue - stops[nextStopIndex-1].offset;
82         //float percent = diffFromPrevious / totalDelta;
83         CGFloat percent = diffFromPrevious * stops[nextStopIndex].previousDeltaInverse;
84         
85         outColor[0] = ((1.0 - percent) * previousColorArray[0] + percent * nextColorArray[0]);
86         outColor[1] = ((1.0 - percent) * previousColorArray[1] + percent * nextColorArray[1]);
87         outColor[2] = ((1.0 - percent) * previousColorArray[2] + percent * nextColorArray[2]);
88         outColor[3] = ((1.0 - percent) * previousColorArray[3] + percent * nextColorArray[3]);
89     }
90     // FIXME: have to handle the spreadMethod()s here SPREADMETHOD_REPEAT, etc.
91 }
92
93 static CGShadingRef CGShadingRefForLinearGradient(const KRenderingPaintServerLinearGradientQuartz* server)
94 {
95     CGPoint start = CGPoint(server->gradientStart());
96     CGPoint end = CGPoint(server->gradientEnd());
97     
98     CGFunctionCallbacks callbacks = {0, cgGradientCallback, NULL};
99     CGFloat domainLimits[2] = {0, 1};
100     CGFloat rangeLimits[8] = {0, 1, 0, 1, 0, 1, 0, 1};
101     const KRenderingPaintServerGradientQuartz* castServer = static_cast<const KRenderingPaintServerGradientQuartz*>(server);
102     CGFunctionRef shadingFunction = CGFunctionCreate((void *)castServer, 1, domainLimits, 4, rangeLimits, &callbacks);
103     
104     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
105     CGShadingRef shading = CGShadingCreateAxial(colorSpace, start, end, shadingFunction, true, true);
106     CGColorSpaceRelease(colorSpace);
107     CGFunctionRelease(shadingFunction);
108     return shading;
109 }
110
111 static CGShadingRef CGShadingRefForRadialGradient(const KRenderingPaintServerRadialGradientQuartz* server)
112 {
113     CGPoint center = CGPoint(server->gradientCenter());
114     CGPoint focus = CGPoint(server->gradientFocal());
115     double radius = server->gradientRadius();
116     
117     double fdx = focus.x - center.x;
118     double fdy = focus.y - center.y;
119     
120     // Spec: If (fx, fy) lies outside the circle defined by (cx, cy) and r, set (fx, fy)
121     // to the point of intersection of the line through (fx, fy) and the circle.
122     if (sqrt(fdx*fdx + fdy*fdy) > radius) {
123         double angle = atan2(focus.y, focus.x);
124         focus.x = int(cos(angle) * radius) - 1;
125         focus.y = int(sin(angle) * radius) - 1;
126     }
127     
128     CGFunctionCallbacks callbacks = {0, cgGradientCallback, NULL};
129     CGFloat domainLimits[2] = {0, 1};
130     CGFloat rangeLimits[8] = {0, 1, 0, 1, 0, 1, 0, 1};
131     const KRenderingPaintServerGradientQuartz* castServer = static_cast<const KRenderingPaintServerGradientQuartz*>(server);
132     CGFunctionRef shadingFunction = CGFunctionCreate((void *)castServer, 1, domainLimits, 4, rangeLimits, &callbacks);
133     
134     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
135     CGShadingRef shading = CGShadingCreateRadial(colorSpace, focus, 0, center, radius, shadingFunction, true, true);
136     CGColorSpaceRelease(colorSpace);
137     CGFunctionRelease(shadingFunction);
138     return shading;
139 }
140
141 KRenderingPaintServerGradientQuartz::KRenderingPaintServerGradientQuartz()
142     : m_stopsCache(0)
143     , m_stopsCount(0)
144     , m_shadingCache(0)
145     , m_maskImage(0)
146 {
147 }
148
149 KRenderingPaintServerGradientQuartz::~KRenderingPaintServerGradientQuartz()
150 {
151     delete m_stopsCache;
152     CGShadingRelease(m_shadingCache);
153     delete m_maskImage;
154 }
155
156 void KRenderingPaintServerGradientQuartz::updateQuartzGradientCache(const KRenderingPaintServerGradient *server)
157 {
158     // cache our own copy of the stops for faster access.
159     // this is legacy code, probably could be reworked.
160     if (!m_stopsCache)
161         updateQuartzGradientStopsCache(server->gradientStops());
162     
163     if (!m_stopsCount)
164         NSLog(@"Warning, no gradient stops, gradient (%p) will be all black!", this);
165     
166     if (m_shadingCache)
167         CGShadingRelease(m_shadingCache);
168     if (server->type() == PS_RADIAL_GRADIENT) {
169         const KRenderingPaintServerRadialGradientQuartz* radial = static_cast<const KRenderingPaintServerRadialGradientQuartz*>(server);
170         m_shadingCache = CGShadingRefForRadialGradient(radial);
171     } else if (server->type() == PS_LINEAR_GRADIENT) {
172         const KRenderingPaintServerLinearGradientQuartz* linear = static_cast<const KRenderingPaintServerLinearGradientQuartz*>(server);
173         m_shadingCache = CGShadingRefForLinearGradient(linear);
174     }
175 }
176
177 void KRenderingPaintServerGradientQuartz::updateQuartzGradientStopsCache(const Vector<KCGradientStop>& stops)
178 {
179     delete m_stopsCache;
180
181     m_stopsCount = stops.size();
182     m_stopsCache = new QuartzGradientStop[m_stopsCount];
183     
184     CGFloat previousOffset = 0.0;
185     for (unsigned i = 0; i < stops.size(); ++i) {
186         m_stopsCache[i].offset = stops[i].first;
187         m_stopsCache[i].previousDeltaInverse = 1.0 / (stops[i].first - previousOffset);
188         previousOffset = stops[i].first;
189         CGFloat *ca = m_stopsCache[i].colorArray;
190         stops[i].second.getRGBA(ca[0], ca[1], ca[2], ca[3]);
191     }
192 }
193
194 void KRenderingPaintServerGradientQuartz::invalidateCaches()
195 {
196     delete m_stopsCache;
197     CGShadingRelease(m_shadingCache);
198     
199     m_stopsCache = 0;
200     m_shadingCache = 0;
201 }
202
203 void KRenderingPaintServerLinearGradientQuartz::invalidate()
204 {
205     invalidateCaches();
206     KRenderingPaintServerLinearGradient::invalidate();
207 }
208
209 void KRenderingPaintServerRadialGradientQuartz::invalidate()
210 {
211     invalidateCaches();
212     KRenderingPaintServerRadialGradient::invalidate();
213 }
214
215 void KRenderingPaintServerGradientQuartz::draw(const KRenderingPaintServerGradient* server, KRenderingDeviceContext* renderingContext, const RenderPath* path, KCPaintTargetType type) const
216 {
217     if (!setup(server, renderingContext, path, type))
218         return;
219     renderPath(server, renderingContext, path, type);
220     teardown(server, renderingContext, path, type);
221 }
222
223 bool KRenderingPaintServerGradientQuartz::setup(const KRenderingPaintServerGradient* server, KRenderingDeviceContext* renderingContext, const RenderObject* renderObject, KCPaintTargetType type) const
224 {
225     if (server->listener()) // this seems like bad design to me, should be in a common baseclass. -- ecs 8/6/05
226         server->listener()->resourceNotification();
227     
228     delete m_maskImage;
229     m_maskImage = 0;
230
231     // FIXME: total const HACK!
232     // We need a hook to call this when the gradient gets updated, before drawn.
233     if (!m_shadingCache)
234         const_cast<KRenderingPaintServerGradientQuartz*>(this)->updateQuartzGradientCache(server);
235     
236     KRenderingDeviceQuartz* quartzDevice = static_cast<KRenderingDeviceQuartz*>(renderingDevice());
237     CGContextRef context = quartzDevice->currentCGContext();
238     RenderStyle* renderStyle = renderObject->style();
239     ASSERT(context != NULL);
240     
241     CGContextSaveGState(context);
242     CGContextSetAlpha(context, renderStyle->opacity());
243     
244     if ((type & APPLY_TO_FILL) && KSVGPainterFactory::isFilled(renderStyle)) {
245         CGContextSaveGState(context);
246         if (server->isPaintingText())
247             CGContextSetTextDrawingMode(context, kCGTextClip);
248     }
249
250     if ((type & APPLY_TO_STROKE) && KSVGPainterFactory::isStroked(renderStyle)) {
251         CGContextSaveGState(context);
252         applyStrokeStyleToContext(context, renderStyle, renderObject); // FIXME: this seems like the wrong place for this.
253         if (server->isPaintingText()) {
254             m_maskImage = static_cast<KCanvasImage*>(quartzDevice->createResource(RS_IMAGE));
255             int width  = 2048;
256             int height = 2048; // FIXME???
257             IntSize size = IntSize(width, height);
258             m_maskImage->init(size);
259             KRenderingDeviceContext* maskImageContext = quartzDevice->contextForImage(m_maskImage);
260             quartzDevice->pushContext(maskImageContext);
261             CGContextRef maskContext = static_cast<KRenderingDeviceContextQuartz*>(maskImageContext)->cgContext();
262             const_cast<RenderObject*>(renderObject)->style()->setColor(Color(255, 255, 255));
263             CGContextSetTextDrawingMode(maskContext, kCGTextStroke);
264         }
265     }
266     return true;
267 }
268
269 void KRenderingPaintServerGradientQuartz::renderPath(const KRenderingPaintServerGradient* server, KRenderingDeviceContext* renderingContext, const RenderPath* path, KCPaintTargetType type) const
270 {    
271     KRenderingDeviceQuartz* quartzDevice = static_cast<KRenderingDeviceQuartz*>(renderingDevice());
272     CGContextRef context = quartzDevice->currentCGContext();
273     RenderStyle* renderStyle = path->style();
274     ASSERT(context != NULL);
275     
276     CGRect objectBBox;
277     if (server->boundingBoxMode())
278         objectBBox = CGContextGetPathBoundingBox(context);
279     if ((type & APPLY_TO_FILL) && KSVGPainterFactory::isFilled(renderStyle))
280         KRenderingPaintServerQuartzHelper::clipToFillPath(context, path);
281     if ((type & APPLY_TO_STROKE) && KSVGPainterFactory::isStroked(renderStyle))
282         KRenderingPaintServerQuartzHelper::clipToStrokePath(context, path);
283     // make the gradient fit in the bbox if necessary.
284     if (server->boundingBoxMode()) { // no support for bounding boxes around text yet!
285         // get the object bbox
286         CGRect gradientBBox = CGRectMake(0,0,100,100); // FIXME - this is arbitrary no?
287         // generate a transform to map between the two.
288         CGAffineTransform gradientIntoObjectBBox = CGAffineTransformMakeMapBetweenRects(gradientBBox, objectBBox);
289         CGContextConcatCTM(context, gradientIntoObjectBBox);
290     }
291     
292     // apply the gradient's own transform
293     CGAffineTransform gradientTransform = server->gradientTransform();
294     CGContextConcatCTM(context, gradientTransform);
295 }
296
297 void KRenderingPaintServerGradientQuartz::teardown(const KRenderingPaintServerGradient *server, KRenderingDeviceContext* renderingContext, const RenderObject* renderObject, KCPaintTargetType type) const
298
299     CGShadingRef shading = m_shadingCache;
300     KCanvasImage* maskImage = m_maskImage;
301     KRenderingDeviceQuartz* quartzDevice = static_cast<KRenderingDeviceQuartz*>(renderingDevice());
302     CGContextRef context = quartzDevice->currentCGContext();
303     RenderStyle* renderStyle = renderObject->style();
304     ASSERT(context != NULL);
305     
306     if ((type & APPLY_TO_FILL) && KSVGPainterFactory::isFilled(renderStyle)) {
307         // workaround for filling the entire screen with the shading in the case that no text was intersected with the clip
308         if (!server->isPaintingText() || (renderObject->width() > 0 && renderObject->height() > 0))
309             CGContextDrawShading(context, shading);
310         CGContextRestoreGState(context);
311     }
312     
313     if ((type & APPLY_TO_STROKE) && KSVGPainterFactory::isStroked(renderStyle)) {
314         if (server->isPaintingText()) {
315             int width  = 2048;
316             int height = 2048; // FIXME??? SEE ABOVE
317             delete quartzDevice->popContext();
318             context = quartzDevice->currentCGContext();
319             void* imageBuffer = fastMalloc(width * height);
320             CGColorSpaceRef grayColorSpace = CGColorSpaceCreateDeviceGray();
321             CGContextRef grayscaleContext = CGBitmapContextCreate(imageBuffer, width, height, 8, width, grayColorSpace, kCGImageAlphaNone);
322             CGColorSpaceRelease(grayColorSpace);
323             KCanvasImageQuartz* qMaskImage = static_cast<KCanvasImageQuartz*>(maskImage);
324             CGContextDrawLayerAtPoint(grayscaleContext, CGPointMake(0, 0), qMaskImage->cgLayer());
325             CGImageRef grayscaleImage = CGBitmapContextCreateImage(grayscaleContext);
326             CGContextClipToMask(context, CGRectMake(0, 0, width, height), grayscaleImage);
327             CGContextRelease(grayscaleContext);
328             CGImageRelease(grayscaleImage);
329         }
330         CGContextDrawShading(context, shading);
331         CGContextRestoreGState(context);
332     }
333     
334     CGContextRestoreGState(context);
335 }
336
337 }
338
339 #endif // SVG_SUPPORT