2 * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
3 * 2006 Alexander Kellett <lypanov@kde.org>
4 * 2006 Rob Buis <buis@kde.org>
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
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.
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.
31 #import "KRenderingPaintServerQuartz.h"
33 #import "KCanvasImage.h"
34 #import "KCanvasResourcesQuartz.h"
35 #import "KRenderingDeviceQuartz.h"
36 #import "KRenderingPaintServer.h"
37 #import "QuartzSupport.h"
38 #import "RenderObject.h"
40 #import <wtf/Assertions.h>
44 static void cgGradientCallback(void* info, const CGFloat* inValues, CGFloat* outColor)
46 const KRenderingPaintServerGradientQuartz* server = (const KRenderingPaintServerGradientQuartz*)info;
47 QuartzGradientStop *stops = server->m_stopsCache;
48 int stopsCount = server->m_stopsCount;
50 CGFloat inValue = inValues[0];
58 } else if (stopsCount == 1) {
59 memcpy(outColor, stops[0].colorArray, 4 * sizeof(CGFloat));
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));
68 int nextStopIndex = 0;
69 while ((nextStopIndex < stopsCount) && (stops[nextStopIndex].offset < inValue))
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;
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]);
85 // FIXME: have to handle the spreadMethod()s here SPREADMETHOD_REPEAT, etc.
88 static CGShadingRef CGShadingRefForLinearGradient(const KRenderingPaintServerLinearGradientQuartz* server)
90 CGPoint start = CGPoint(server->gradientStart());
91 CGPoint end = CGPoint(server->gradientEnd());
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);
99 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
100 CGShadingRef shading = CGShadingCreateAxial(colorSpace, start, end, shadingFunction, true, true);
101 CGColorSpaceRelease(colorSpace);
102 CGFunctionRelease(shadingFunction);
106 static CGShadingRef CGShadingRefForRadialGradient(const KRenderingPaintServerRadialGradientQuartz* server)
108 CGPoint center = CGPoint(server->gradientCenter());
109 CGPoint focus = CGPoint(server->gradientFocal());
110 double radius = server->gradientRadius();
112 double fdx = focus.x - center.x;
113 double fdy = focus.y - center.y;
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;
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);
129 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
130 CGShadingRef shading = CGShadingCreateRadial(colorSpace, focus, 0, center, radius, shadingFunction, true, true);
131 CGColorSpaceRelease(colorSpace);
132 CGFunctionRelease(shadingFunction);
136 KRenderingPaintServerGradientQuartz::KRenderingPaintServerGradientQuartz()
144 KRenderingPaintServerGradientQuartz::~KRenderingPaintServerGradientQuartz()
147 CGShadingRelease(m_shadingCache);
151 void KRenderingPaintServerGradientQuartz::updateQuartzGradientCache(const KRenderingPaintServerGradient *server)
153 // cache our own copy of the stops for faster access.
154 // this is legacy code, probably could be reworked.
156 updateQuartzGradientStopsCache(server->gradientStops());
159 NSLog(@"Warning, no gradient stops, gradient (%p) will be all black!", this);
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);
172 void KRenderingPaintServerGradientQuartz::updateQuartzGradientStopsCache(const Vector<KCGradientStop>& stops)
176 m_stopsCount = stops.size();
177 m_stopsCache = new QuartzGradientStop[m_stopsCount];
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]);
189 void KRenderingPaintServerGradientQuartz::invalidateCaches()
192 CGShadingRelease(m_shadingCache);
198 void KRenderingPaintServerLinearGradientQuartz::invalidate()
201 KRenderingPaintServerLinearGradient::invalidate();
204 void KRenderingPaintServerRadialGradientQuartz::invalidate()
207 KRenderingPaintServerRadialGradient::invalidate();
210 void KRenderingPaintServerGradientQuartz::draw(const KRenderingPaintServerGradient* server, KRenderingDeviceContext* renderingContext, const RenderPath* path, KCPaintTargetType type) const
212 if (!setup(server, renderingContext, path, type))
214 renderPath(server, renderingContext, path, type);
215 teardown(server, renderingContext, path, type);
218 bool KRenderingPaintServerGradientQuartz::setup(const KRenderingPaintServerGradient* server, KRenderingDeviceContext* renderingContext, const RenderObject* renderObject, KCPaintTargetType type) const
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();
226 // FIXME: total const HACK!
227 // We need a hook to call this when the gradient gets updated, before drawn.
229 const_cast<KRenderingPaintServerGradientQuartz*>(this)->updateQuartzGradientCache(server);
231 KRenderingDeviceQuartz* quartzDevice = static_cast<KRenderingDeviceQuartz*>(renderingDevice());
232 CGContextRef context = quartzDevice->currentCGContext();
233 RenderStyle* renderStyle = renderObject->style();
234 ASSERT(context != NULL);
236 CGContextSaveGState(context);
237 CGContextSetAlpha(context, renderStyle->opacity());
239 if ((type & APPLY_TO_FILL) && renderStyle->svgStyle()->hasFill()) {
240 CGContextSaveGState(context);
241 if (server->isPaintingText())
242 CGContextSetTextDrawingMode(context, kCGTextClip);
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));
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);
264 void KRenderingPaintServerGradientQuartz::renderPath(const KRenderingPaintServerGradient* server, KRenderingDeviceContext* renderingContext, const RenderPath* path, KCPaintTargetType type) const
266 KRenderingDeviceQuartz* quartzDevice = static_cast<KRenderingDeviceQuartz*>(renderingDevice());
267 CGContextRef context = quartzDevice->currentCGContext();
268 RenderStyle* renderStyle = path->style();
269 ASSERT(context != NULL);
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);
287 // apply the gradient's own transform
288 CGAffineTransform gradientTransform = server->gradientTransform();
289 CGContextConcatCTM(context, gradientTransform);
292 void KRenderingPaintServerGradientQuartz::teardown(const KRenderingPaintServerGradient *server, KRenderingDeviceContext* renderingContext, const RenderObject* renderObject, KCPaintTargetType type) const
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);
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);
308 if ((type & APPLY_TO_STROKE) && renderStyle->svgStyle()->hasStroke()) {
309 if (server->isPaintingText()) {
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);
325 CGContextDrawShading(context, shading);
326 CGContextRestoreGState(context);
329 CGContextRestoreGState(context);
334 #endif // SVG_SUPPORT