2008-11-04 Anders Carlsson <andersca@apple.com>
[webkit/qt.git] / WebCore / svg / graphics / cg / SVGPaintServerGradientCg.cpp
blobbfa50177ad689ccb51b51b266c0b68499587edc9
1 /*
2 Copyright (C) 2006, 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org>
4 This file is part of the KDE project
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.
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.
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., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
22 #include "config.h"
24 #if ENABLE(SVG)
25 #include "SVGPaintServerGradient.h"
27 #include "FloatConversion.h"
28 #include "GraphicsContext.h"
29 #include "ImageBuffer.h"
30 #include "RenderObject.h"
31 #include "SVGGradientElement.h"
32 #include "SVGPaintServerLinearGradient.h"
33 #include "SVGPaintServerRadialGradient.h"
34 #include "SVGRenderSupport.h"
36 #include <wtf/MathExtras.h>
38 using namespace std;
40 namespace WebCore {
42 static void releaseCachedStops(void* info)
44 static_cast<SVGPaintServerGradient::SharedStopCache*>(info)->deref();
47 static void cgGradientCallback(void* info, const CGFloat* inValues, CGFloat* outColor)
49 SVGPaintServerGradient::SharedStopCache* stopsCache = static_cast<SVGPaintServerGradient::SharedStopCache*>(info);
51 SVGPaintServerGradient::QuartzGradientStop* stops = stopsCache->m_stops.data();
53 int stopsCount = stopsCache->m_stops.size();
55 CGFloat inValue = inValues[0];
57 if (!stopsCount) {
58 outColor[0] = 0;
59 outColor[1] = 0;
60 outColor[2] = 0;
61 outColor[3] = 0;
62 return;
63 } else if (stopsCount == 1) {
64 memcpy(outColor, stops[0].colorArray, 4 * sizeof(CGFloat));
65 return;
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++;
77 CGFloat* nextColorArray = stops[nextStopIndex].colorArray;
78 CGFloat* previousColorArray = stops[nextStopIndex - 1].colorArray;
79 CGFloat diffFromPrevious = inValue - stops[nextStopIndex - 1].offset;
80 CGFloat percent = diffFromPrevious * stops[nextStopIndex].previousDeltaInverse;
82 outColor[0] = ((1.0f - percent) * previousColorArray[0] + percent * nextColorArray[0]);
83 outColor[1] = ((1.0f - percent) * previousColorArray[1] + percent * nextColorArray[1]);
84 outColor[2] = ((1.0f - percent) * previousColorArray[2] + percent * nextColorArray[2]);
85 outColor[3] = ((1.0f - percent) * previousColorArray[3] + percent * nextColorArray[3]);
87 // FIXME: have to handle the spreadMethod()s here SPREADMETHOD_REPEAT, etc.
90 static CGShadingRef CGShadingRefForLinearGradient(const SVGPaintServerLinearGradient* server)
92 CGPoint start = CGPoint(server->gradientStart());
93 CGPoint end = CGPoint(server->gradientEnd());
95 CGFunctionCallbacks callbacks = {0, cgGradientCallback, releaseCachedStops};
96 CGFloat domainLimits[2] = {0, 1};
97 CGFloat rangeLimits[8] = {0, 1, 0, 1, 0, 1, 0, 1};
98 server->m_stopsCache->ref();
99 CGFunctionRef shadingFunction = CGFunctionCreate(server->m_stopsCache.get(), 1, domainLimits, 4, rangeLimits, &callbacks);
101 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
102 CGShadingRef shading = CGShadingCreateAxial(colorSpace, start, end, shadingFunction, true, true);
103 CGColorSpaceRelease(colorSpace);
104 CGFunctionRelease(shadingFunction);
105 return shading;
108 static CGShadingRef CGShadingRefForRadialGradient(const SVGPaintServerRadialGradient* server)
110 CGPoint center = CGPoint(server->gradientCenter());
111 CGPoint focus = CGPoint(server->gradientFocal());
112 double radius = server->gradientRadius();
114 double fdx = focus.x - center.x;
115 double fdy = focus.y - center.y;
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 * 100.0, focus.x * 100.0);
121 focus.x = narrowPrecisionToCGFloat(cos(angle) * radius);
122 focus.y = narrowPrecisionToCGFloat(sin(angle) * radius);
125 CGFunctionCallbacks callbacks = {0, cgGradientCallback, releaseCachedStops};
126 CGFloat domainLimits[2] = {0, 1};
127 CGFloat rangeLimits[8] = {0, 1, 0, 1, 0, 1, 0, 1};
128 server->m_stopsCache->ref();
129 CGFunctionRef shadingFunction = CGFunctionCreate(server->m_stopsCache.get(), 1, domainLimits, 4, rangeLimits, &callbacks);
131 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
132 CGShadingRef shading = CGShadingCreateRadial(colorSpace, focus, 0, center, narrowPrecisionToCGFloat(radius), shadingFunction, true, true);
133 CGColorSpaceRelease(colorSpace);
134 CGFunctionRelease(shadingFunction);
135 return shading;
138 void SVGPaintServerGradient::updateQuartzGradientStopsCache(const Vector<SVGGradientStop>& stops)
140 m_stopsCache = SharedStopCache::create();
141 Vector<QuartzGradientStop>& stopsCache = m_stopsCache->m_stops;
142 stopsCache.resize(stops.size());
143 CGFloat previousOffset = 0.0f;
144 for (unsigned i = 0; i < stops.size(); ++i) {
145 CGFloat currOffset = min(max(stops[i].first, previousOffset), static_cast<CGFloat>(1.0));
146 stopsCache[i].offset = currOffset;
147 stopsCache[i].previousDeltaInverse = 1.0f / (currOffset - previousOffset);
148 previousOffset = currOffset;
149 CGFloat* ca = stopsCache[i].colorArray;
150 stops[i].second.getRGBA(ca[0], ca[1], ca[2], ca[3]);
154 void SVGPaintServerGradient::updateQuartzGradientCache(const SVGPaintServerGradient* server)
156 // cache our own copy of the stops for faster access.
157 // this is legacy code, probably could be reworked.
158 if (!m_stopsCache)
159 updateQuartzGradientStopsCache(gradientStops());
161 CGShadingRelease(m_shadingCache);
163 if (type() == RadialGradientPaintServer) {
164 const SVGPaintServerRadialGradient* radial = static_cast<const SVGPaintServerRadialGradient*>(server);
165 m_shadingCache = CGShadingRefForRadialGradient(radial);
166 } else if (type() == LinearGradientPaintServer) {
167 const SVGPaintServerLinearGradient* linear = static_cast<const SVGPaintServerLinearGradient*>(server);
168 m_shadingCache = CGShadingRefForLinearGradient(linear);
172 // Helper function for text painting
173 static inline const RenderObject* findTextRootObject(const RenderObject* start)
175 while (start && !start->isSVGText())
176 start = start->parent();
178 ASSERT(start);
179 ASSERT(start->isSVGText());
181 return start;
184 void SVGPaintServerGradient::teardown(GraphicsContext*& context, const RenderObject* object, SVGPaintTargetType type, bool isPaintingText) const
186 CGShadingRef shading = m_shadingCache;
187 CGContextRef contextRef = context->platformContext();
188 ASSERT(contextRef);
190 // As renderPath() is not used when painting text, special logic needed here.
191 if (isPaintingText) {
192 if (m_savedContext) {
193 FloatRect maskBBox = const_cast<RenderObject*>(findTextRootObject(object))->relativeBBox(false);
195 // Fixup transformations to be able to clip to mask
196 AffineTransform transform = object->absoluteTransform();
197 FloatRect textBoundary = transform.mapRect(maskBBox);
199 IntSize maskSize(lroundf(textBoundary.width()), lroundf(textBoundary.height()));
200 clampImageBufferSizeToViewport(object->document()->renderer(), maskSize);
202 if (maskSize.width() < static_cast<int>(textBoundary.width()))
203 textBoundary.setWidth(maskSize.width());
205 if (maskSize.height() < static_cast<int>(textBoundary.height()))
206 textBoundary.setHeight(maskSize.height());
208 // Clip current context to mask image (gradient)
209 m_savedContext->concatCTM(transform.inverse());
210 m_savedContext->clipToImageBuffer(textBoundary, m_imageBuffer);
211 m_savedContext->concatCTM(transform);
213 handleBoundingBoxModeAndGradientTransformation(m_savedContext, maskBBox);
215 // Restore on-screen drawing context, after we got the image of the gradient
216 delete m_imageBuffer;
218 context = m_savedContext;
219 contextRef = context->platformContext();
221 m_savedContext = 0;
222 m_imageBuffer = 0;
226 CGContextDrawShading(contextRef, shading);
227 context->restore();
230 void SVGPaintServerGradient::renderPath(GraphicsContext*& context, const RenderObject* path, SVGPaintTargetType type) const
232 RenderStyle* style = path->style();
233 CGContextRef contextRef = context->platformContext();
234 ASSERT(contextRef);
236 bool isFilled = (type & ApplyToFillTargetType) && style->svgStyle()->hasFill();
238 // Compute destination object bounding box
239 FloatRect objectBBox;
240 if (boundingBoxMode()) {
241 FloatRect bbox = path->relativeBBox(false);
242 if (bbox.width() > 0 && bbox.height() > 0)
243 objectBBox = bbox;
246 if (isFilled)
247 clipToFillPath(contextRef, path);
248 else
249 clipToStrokePath(contextRef, path);
251 handleBoundingBoxModeAndGradientTransformation(context, objectBBox);
254 void SVGPaintServerGradient::handleBoundingBoxModeAndGradientTransformation(GraphicsContext* context, const FloatRect& targetRect) const
256 if (boundingBoxMode()) {
257 // Choose default gradient bounding box
258 FloatRect gradientBBox(0.0f, 0.0f, 1.0f, 1.0f);
260 // Generate a transform to map between both bounding boxes
261 context->concatCTM(makeMapBetweenRects(gradientBBox, targetRect));
264 // Apply the gradient's own transform
265 context->concatCTM(gradientTransform());
268 bool SVGPaintServerGradient::setup(GraphicsContext*& context, const RenderObject* object, SVGPaintTargetType type, bool isPaintingText) const
270 m_ownerElement->buildGradient();
272 // We need a hook to call this when the gradient gets updated, before drawn.
273 if (!m_shadingCache)
274 const_cast<SVGPaintServerGradient*>(this)->updateQuartzGradientCache(this);
276 CGContextRef contextRef = context->platformContext();
277 ASSERT(contextRef);
279 RenderStyle* style = object->style();
281 bool isFilled = (type & ApplyToFillTargetType) && style->svgStyle()->hasFill();
282 bool isStroked = (type & ApplyToStrokeTargetType) && style->svgStyle()->hasStroke();
284 ASSERT(isFilled && !isStroked || !isFilled && isStroked);
286 context->save();
287 CGContextSetAlpha(contextRef, isFilled ? style->svgStyle()->fillOpacity() : style->svgStyle()->strokeOpacity());
289 if (isPaintingText) {
290 FloatRect maskBBox = const_cast<RenderObject*>(findTextRootObject(object))->relativeBBox(false);
291 IntRect maskRect = enclosingIntRect(object->absoluteTransform().mapRect(maskBBox));
293 IntSize maskSize(maskRect.width(), maskRect.height());
294 clampImageBufferSizeToViewport(object->document()->renderer(), maskSize);
296 auto_ptr<ImageBuffer> maskImage = ImageBuffer::create(maskSize, false);
298 if (!maskImage.get()) {
299 context->restore();
300 return false;
303 GraphicsContext* maskImageContext = maskImage->context();
304 maskImageContext->save();
306 maskImageContext->setTextDrawingMode(isFilled ? cTextFill : cTextStroke);
307 maskImageContext->translate(-maskRect.x(), -maskRect.y());
308 maskImageContext->concatCTM(object->absoluteTransform());
310 m_imageBuffer = maskImage.release();
311 m_savedContext = context;
313 context = maskImageContext;
314 contextRef = context->platformContext();
317 if (isStroked)
318 applyStrokeStyleToContext(context, style, object);
320 return true;
323 void SVGPaintServerGradient::invalidate()
325 SVGPaintServer::invalidate();
327 // Invalidate caches
328 CGShadingRelease(m_shadingCache);
330 m_stopsCache = 0;
331 m_shadingCache = 0;
334 } // namespace WebCore
336 #endif