webengine/osswebengine/WebCore/platform/graphics/svg/cg/SVGPaintServerGradientCg.cpp
changeset 0 dd21522fd290
--- /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