--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/webengine/osswebengine/WebCore/platform/graphics/svg/cg/SVGPaintServerGradientCg.cpp Mon Mar 30 12:54:55 2009 +0300
@@ -0,0 +1,314 @@
+/*
+ Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org>
+
+ This file is part of the KDE project
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ aint with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "config.h"
+
+#if ENABLE(SVG)
+#include "SVGPaintServerGradient.h"
+
+#include "CgSupport.h"
+#include "FloatConversion.h"
+#include "GraphicsContext.h"
+#include "ImageBuffer.h"
+#include "RenderPath.h"
+#include "SVGGradientElement.h"
+#include "SVGPaintServerLinearGradient.h"
+#include "SVGPaintServerRadialGradient.h"
+
+using namespace std;
+
+namespace WebCore {
+
+static void cgGradientCallback(void* info, const CGFloat* inValues, CGFloat* outColor)
+{
+ const SVGPaintServerGradient* server = reinterpret_cast<const SVGPaintServerGradient*>(info);
+ SVGPaintServerGradient::QuartzGradientStop* stops = server->m_stopsCache;
+ int stopsCount = server->m_stopsCount;
+
+ CGFloat inValue = inValues[0];
+
+ if (!stopsCount) {
+ outColor[0] = 0;
+ outColor[1] = 0;
+ outColor[2] = 0;
+ outColor[3] = 0;
+ return;
+ } else if (stopsCount == 1) {
+ memcpy(outColor, stops[0].colorArray, 4 * sizeof(CGFloat));
+ return;
+ }
+
+ if (!(inValue > stops[0].offset))
+ memcpy(outColor, stops[0].colorArray, 4 * sizeof(CGFloat));
+ else if (!(inValue < stops[stopsCount - 1].offset))
+ memcpy(outColor, stops[stopsCount - 1].colorArray, 4 * sizeof(CGFloat));
+ else {
+ int nextStopIndex = 0;
+ while ((nextStopIndex < stopsCount) && (stops[nextStopIndex].offset < inValue))
+ nextStopIndex++;
+
+ CGFloat* nextColorArray = stops[nextStopIndex].colorArray;
+ CGFloat* previousColorArray = stops[nextStopIndex - 1].colorArray;
+ CGFloat diffFromPrevious = inValue - stops[nextStopIndex - 1].offset;
+ CGFloat percent = diffFromPrevious * stops[nextStopIndex].previousDeltaInverse;
+
+ outColor[0] = ((1.0f - percent) * previousColorArray[0] + percent * nextColorArray[0]);
+ outColor[1] = ((1.0f - percent) * previousColorArray[1] + percent * nextColorArray[1]);
+ outColor[2] = ((1.0f - percent) * previousColorArray[2] + percent * nextColorArray[2]);
+ outColor[3] = ((1.0f - percent) * previousColorArray[3] + percent * nextColorArray[3]);
+ }
+ // FIXME: have to handle the spreadMethod()s here SPREADMETHOD_REPEAT, etc.
+}
+
+static CGShadingRef CGShadingRefForLinearGradient(const SVGPaintServerLinearGradient* server)
+{
+ CGPoint start = CGPoint(server->gradientStart());
+ CGPoint end = CGPoint(server->gradientEnd());
+
+ CGFunctionCallbacks callbacks = {0, cgGradientCallback, NULL};
+ CGFloat domainLimits[2] = {0, 1};
+ CGFloat rangeLimits[8] = {0, 1, 0, 1, 0, 1, 0, 1};
+ CGFunctionRef shadingFunction = CGFunctionCreate((void *)server, 1, domainLimits, 4, rangeLimits, &callbacks);
+
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
+ CGShadingRef shading = CGShadingCreateAxial(colorSpace, start, end, shadingFunction, true, true);
+ CGColorSpaceRelease(colorSpace);
+ CGFunctionRelease(shadingFunction);
+ return shading;
+}
+
+static CGShadingRef CGShadingRefForRadialGradient(const SVGPaintServerRadialGradient* server)
+{
+ CGPoint center = CGPoint(server->gradientCenter());
+ CGPoint focus = CGPoint(server->gradientFocal());
+ double radius = server->gradientRadius();
+
+ double fdx = focus.x - center.x;
+ double fdy = focus.y - center.y;
+
+ // Spec: If (fx, fy) lies outside the circle defined by (cx, cy) and r, set (fx, fy)
+ // to the point of intersection of the line through (fx, fy) and the circle.
+ if (sqrt(fdx * fdx + fdy * fdy) > radius) {
+ double angle = atan2(focus.y * 100.0, focus.x * 100.0);
+ focus.x = narrowPrecisionToCGFloat(cos(angle) * radius);
+ focus.y = narrowPrecisionToCGFloat(sin(angle) * radius);
+ }
+
+ CGFunctionCallbacks callbacks = {0, cgGradientCallback, NULL};
+ CGFloat domainLimits[2] = {0, 1};
+ CGFloat rangeLimits[8] = {0, 1, 0, 1, 0, 1, 0, 1};
+ CGFunctionRef shadingFunction = CGFunctionCreate((void *)server, 1, domainLimits, 4, rangeLimits, &callbacks);
+
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
+ CGShadingRef shading = CGShadingCreateRadial(colorSpace, focus, 0, center, narrowPrecisionToCGFloat(radius), shadingFunction, true, true);
+ CGColorSpaceRelease(colorSpace);
+ CGFunctionRelease(shadingFunction);
+ return shading;
+}
+
+void SVGPaintServerGradient::updateQuartzGradientStopsCache(const Vector<SVGGradientStop>& stops)
+{
+ delete m_stopsCache;
+
+ m_stopsCount = stops.size();
+ m_stopsCache = new SVGPaintServerGradient::QuartzGradientStop[m_stopsCount];
+
+ CGFloat previousOffset = 0.0f;
+ for (unsigned i = 0; i < stops.size(); ++i) {
+ CGFloat currOffset = min(max(stops[i].first, previousOffset), static_cast<CGFloat>(1.0));
+ m_stopsCache[i].offset = currOffset;
+ m_stopsCache[i].previousDeltaInverse = 1.0f / (currOffset - previousOffset);
+ previousOffset = currOffset;
+ CGFloat* ca = m_stopsCache[i].colorArray;
+ stops[i].second.getRGBA(ca[0], ca[1], ca[2], ca[3]);
+ }
+}
+
+void SVGPaintServerGradient::updateQuartzGradientCache(const SVGPaintServerGradient* server)
+{
+ // cache our own copy of the stops for faster access.
+ // this is legacy code, probably could be reworked.
+ if (!m_stopsCache)
+ updateQuartzGradientStopsCache(gradientStops());
+
+ CGShadingRelease(m_shadingCache);
+
+ if (type() == RadialGradientPaintServer) {
+ const SVGPaintServerRadialGradient* radial = static_cast<const SVGPaintServerRadialGradient*>(server);
+ m_shadingCache = CGShadingRefForRadialGradient(radial);
+ } else if (type() == LinearGradientPaintServer) {
+ const SVGPaintServerLinearGradient* linear = static_cast<const SVGPaintServerLinearGradient*>(server);
+ m_shadingCache = CGShadingRefForLinearGradient(linear);
+ }
+}
+
+void SVGPaintServerGradient::teardown(GraphicsContext*& context, const RenderObject* object, SVGPaintTargetType type, bool isPaintingText) const
+{
+ CGShadingRef shading = m_shadingCache;
+ CGContextRef contextRef = context->platformContext();
+ RenderStyle* style = object->style();
+ ASSERT(contextRef);
+
+ // As renderPath() is not used when painting text, special logic needed here.
+ if (isPaintingText) {
+ IntRect textBoundary = const_cast<RenderObject*>(object)->absoluteBoundingBoxRect();
+ FloatRect targetRect = object->absoluteTransform().inverse().mapRect(textBoundary);
+ handleBoundingBoxModeAndGradientTransformation(context, targetRect);
+ }
+
+ if ((type & ApplyToFillTargetType) && style->svgStyle()->hasFill()) {
+ // workaround for filling the entire screen with the shading in the case that no text was intersected with the clip
+ if (!isPaintingText || (object->width() > 0 && object->height() > 0))
+ CGContextDrawShading(contextRef, shading);
+
+ context->restore();
+ }
+
+ if ((type & ApplyToStrokeTargetType) && style->svgStyle()->hasStroke()) {
+ if (isPaintingText && m_savedContext) {
+ IntRect maskRect = const_cast<RenderObject*>(object)->absoluteBoundingBoxRect();
+ maskRect = object->absoluteTransform().inverse().mapRect(maskRect);
+
+ // Translate from 0x0 image origin to actual rendering position
+ m_savedContext->translate(maskRect.x(), maskRect.y());
+
+ // Clip current context to mask image (gradient)
+ CGContextClipToMask(m_savedContext->platformContext(), CGRectMake(0, 0, maskRect.width(), maskRect.height()), m_imageBuffer->cgImage());
+ m_savedContext->translate(-maskRect.x(), -maskRect.y());
+
+ // Restore on-screen drawing context, after we got the image of the gradient
+ delete m_imageBuffer;
+ context = m_savedContext;
+ contextRef = context->platformContext();
+ m_savedContext = 0;
+ m_imageBuffer = 0;
+ }
+
+ CGContextDrawShading(contextRef, shading);
+ context->restore();
+ }
+
+ context->restore();
+}
+
+void SVGPaintServerGradient::renderPath(GraphicsContext*& context, const RenderPath* path, SVGPaintTargetType type) const
+{
+ RenderStyle* style = path->style();
+ CGContextRef contextRef = context->platformContext();
+ ASSERT(contextRef);
+
+ // Compute destination object bounding box
+ FloatRect objectBBox;
+ if (boundingBoxMode())
+ objectBBox = CGContextGetPathBoundingBox(contextRef);
+
+ if ((type & ApplyToFillTargetType) && style->svgStyle()->hasFill())
+ clipToFillPath(contextRef, path);
+
+ if ((type & ApplyToStrokeTargetType) && style->svgStyle()->hasStroke())
+ clipToStrokePath(contextRef, path);
+
+ handleBoundingBoxModeAndGradientTransformation(context, objectBBox);
+}
+
+void SVGPaintServerGradient::handleBoundingBoxModeAndGradientTransformation(GraphicsContext* context, const FloatRect& targetRect) const
+{
+ CGContextRef contextRef = context->platformContext();
+
+ if (boundingBoxMode()) {
+ // Choose default gradient bounding box
+ CGRect gradientBBox = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
+
+ // Generate a transform to map between both bounding boxes
+ CGAffineTransform gradientIntoObjectBBox = CGAffineTransformMakeMapBetweenRects(gradientBBox, CGRect(targetRect));
+ CGContextConcatCTM(contextRef, gradientIntoObjectBBox);
+ }
+
+ // Apply the gradient's own transform
+ CGAffineTransform transform = gradientTransform();
+ CGContextConcatCTM(contextRef, transform);
+}
+
+bool SVGPaintServerGradient::setup(GraphicsContext*& context, const RenderObject* object, SVGPaintTargetType type, bool isPaintingText) const
+{
+ m_ownerElement->buildGradient();
+
+ // We need a hook to call this when the gradient gets updated, before drawn.
+ if (!m_shadingCache)
+ const_cast<SVGPaintServerGradient*>(this)->updateQuartzGradientCache(this);
+
+ CGContextRef contextRef = context->platformContext();
+ RenderStyle* style = object->style();
+ ASSERT(contextRef);
+
+ context->save();
+ CGContextSetAlpha(contextRef, style->opacity());
+
+ if ((type & ApplyToFillTargetType) && style->svgStyle()->hasFill()) {
+ context->save();
+
+ if (isPaintingText)
+ context->setTextDrawingMode(cTextClip);
+ }
+
+ if ((type & ApplyToStrokeTargetType) && style->svgStyle()->hasStroke()) {
+ context->save();
+ applyStrokeStyleToContext(contextRef, style, object);
+
+ if (isPaintingText) {
+ IntRect maskRect = const_cast<RenderObject*>(object)->absoluteBoundingBoxRect();
+ maskRect = object->absoluteTransform().inverse().mapRect(maskRect);
+
+ auto_ptr<ImageBuffer> maskImage = ImageBuffer::create(IntSize(maskRect.width(), maskRect.height()), false);
+ // FIXME: maskImage could be NULL
+
+ GraphicsContext* maskImageContext = maskImage->context();
+
+ maskImageContext->save();
+ maskImageContext->translate(-maskRect.x(), -maskRect.y());
+
+ const_cast<RenderObject*>(object)->style()->setColor(Color(255, 255, 255));
+ maskImageContext->setTextDrawingMode(cTextStroke);
+
+ m_imageBuffer = maskImage.release();
+ m_savedContext = context;
+ context = maskImageContext;
+ }
+ }
+
+ return true;
+}
+
+void SVGPaintServerGradient::invalidate()
+{
+ // Invalidate caches
+ delete m_stopsCache;
+ CGShadingRelease(m_shadingCache);
+
+ m_stopsCache = 0;
+ m_shadingCache = 0;
+}
+
+} // namespace WebCore
+
+#endif
+
+// vim:ts=4:noet