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