webengine/osswebengine/WebCore/platform/graphics/svg/cg/SVGPaintServerGradientCg.cpp
changeset 0 dd21522fd290
equal deleted inserted replaced
-1:000000000000 0:dd21522fd290
       
     1 /*
       
     2     Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org>
       
     3 
       
     4     This file is part of the KDE project
       
     5 
       
     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.
       
    10 
       
    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.
       
    15 
       
    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.
       
    20 */
       
    21 
       
    22 #include "config.h"
       
    23 
       
    24 #if ENABLE(SVG)
       
    25 #include "SVGPaintServerGradient.h"
       
    26 
       
    27 #include "CgSupport.h"
       
    28 #include "FloatConversion.h"
       
    29 #include "GraphicsContext.h"
       
    30 #include "ImageBuffer.h"
       
    31 #include "RenderPath.h"
       
    32 #include "SVGGradientElement.h"
       
    33 #include "SVGPaintServerLinearGradient.h"
       
    34 #include "SVGPaintServerRadialGradient.h"
       
    35 
       
    36 using namespace std;
       
    37 
       
    38 namespace WebCore {
       
    39 
       
    40 static void cgGradientCallback(void* info, const CGFloat* inValues, CGFloat* outColor)
       
    41 {
       
    42     const SVGPaintServerGradient* server = reinterpret_cast<const SVGPaintServerGradient*>(info);
       
    43     SVGPaintServerGradient::QuartzGradientStop* stops = server->m_stopsCache;
       
    44     int stopsCount = server->m_stopsCount;
       
    45 
       
    46     CGFloat inValue = inValues[0];
       
    47 
       
    48     if (!stopsCount) {
       
    49         outColor[0] = 0;
       
    50         outColor[1] = 0;
       
    51         outColor[2] = 0;
       
    52         outColor[3] = 0;
       
    53         return;
       
    54     } else if (stopsCount == 1) {
       
    55         memcpy(outColor, stops[0].colorArray, 4 * sizeof(CGFloat));
       
    56         return;
       
    57     }
       
    58 
       
    59     if (!(inValue > stops[0].offset))
       
    60         memcpy(outColor, stops[0].colorArray, 4 * sizeof(CGFloat));
       
    61     else if (!(inValue < stops[stopsCount - 1].offset))
       
    62         memcpy(outColor, stops[stopsCount - 1].colorArray, 4 * sizeof(CGFloat));
       
    63     else {
       
    64         int nextStopIndex = 0;
       
    65         while ((nextStopIndex < stopsCount) && (stops[nextStopIndex].offset < inValue))
       
    66             nextStopIndex++;
       
    67 
       
    68         CGFloat* nextColorArray = stops[nextStopIndex].colorArray;
       
    69         CGFloat* previousColorArray = stops[nextStopIndex - 1].colorArray;
       
    70         CGFloat diffFromPrevious = inValue - stops[nextStopIndex - 1].offset;
       
    71         CGFloat percent = diffFromPrevious * stops[nextStopIndex].previousDeltaInverse;
       
    72 
       
    73         outColor[0] = ((1.0f - percent) * previousColorArray[0] + percent * nextColorArray[0]);
       
    74         outColor[1] = ((1.0f - percent) * previousColorArray[1] + percent * nextColorArray[1]);
       
    75         outColor[2] = ((1.0f - percent) * previousColorArray[2] + percent * nextColorArray[2]);
       
    76         outColor[3] = ((1.0f - percent) * previousColorArray[3] + percent * nextColorArray[3]);
       
    77     }
       
    78     // FIXME: have to handle the spreadMethod()s here SPREADMETHOD_REPEAT, etc.
       
    79 }
       
    80 
       
    81 static CGShadingRef CGShadingRefForLinearGradient(const SVGPaintServerLinearGradient* server)
       
    82 {
       
    83     CGPoint start = CGPoint(server->gradientStart());
       
    84     CGPoint end = CGPoint(server->gradientEnd());
       
    85 
       
    86     CGFunctionCallbacks callbacks = {0, cgGradientCallback, NULL};
       
    87     CGFloat domainLimits[2] = {0, 1};
       
    88     CGFloat rangeLimits[8] = {0, 1, 0, 1, 0, 1, 0, 1};
       
    89     CGFunctionRef shadingFunction = CGFunctionCreate((void *)server, 1, domainLimits, 4, rangeLimits, &callbacks);
       
    90 
       
    91     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
       
    92     CGShadingRef shading = CGShadingCreateAxial(colorSpace, start, end, shadingFunction, true, true);
       
    93     CGColorSpaceRelease(colorSpace);
       
    94     CGFunctionRelease(shadingFunction);
       
    95     return shading;
       
    96 }
       
    97 
       
    98 static CGShadingRef CGShadingRefForRadialGradient(const SVGPaintServerRadialGradient* server)
       
    99 {
       
   100     CGPoint center = CGPoint(server->gradientCenter());
       
   101     CGPoint focus = CGPoint(server->gradientFocal());
       
   102     double radius = server->gradientRadius();
       
   103 
       
   104     double fdx = focus.x - center.x;
       
   105     double fdy = focus.y - center.y;
       
   106 
       
   107     // Spec: If (fx, fy) lies outside the circle defined by (cx, cy) and r, set (fx, fy)
       
   108     // to the point of intersection of the line through (fx, fy) and the circle.
       
   109     if (sqrt(fdx * fdx + fdy * fdy) > radius) { 
       
   110         double angle = atan2(focus.y * 100.0, focus.x * 100.0);
       
   111         focus.x = narrowPrecisionToCGFloat(cos(angle) * radius);
       
   112         focus.y = narrowPrecisionToCGFloat(sin(angle) * radius);
       
   113     }
       
   114 
       
   115     CGFunctionCallbacks callbacks = {0, cgGradientCallback, NULL};
       
   116     CGFloat domainLimits[2] = {0, 1};
       
   117     CGFloat rangeLimits[8] = {0, 1, 0, 1, 0, 1, 0, 1};
       
   118     CGFunctionRef shadingFunction = CGFunctionCreate((void *)server, 1, domainLimits, 4, rangeLimits, &callbacks);
       
   119 
       
   120     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
       
   121     CGShadingRef shading = CGShadingCreateRadial(colorSpace, focus, 0, center, narrowPrecisionToCGFloat(radius), shadingFunction, true, true);
       
   122     CGColorSpaceRelease(colorSpace);
       
   123     CGFunctionRelease(shadingFunction);
       
   124     return shading;
       
   125 }
       
   126 
       
   127 void SVGPaintServerGradient::updateQuartzGradientStopsCache(const Vector<SVGGradientStop>& stops)
       
   128 {
       
   129     delete m_stopsCache;
       
   130 
       
   131     m_stopsCount = stops.size();
       
   132     m_stopsCache = new SVGPaintServerGradient::QuartzGradientStop[m_stopsCount];
       
   133 
       
   134     CGFloat previousOffset = 0.0f;
       
   135     for (unsigned i = 0; i < stops.size(); ++i) {
       
   136         CGFloat currOffset = min(max(stops[i].first, previousOffset), static_cast<CGFloat>(1.0));
       
   137         m_stopsCache[i].offset = currOffset;
       
   138         m_stopsCache[i].previousDeltaInverse = 1.0f / (currOffset - previousOffset);
       
   139         previousOffset = currOffset;
       
   140         CGFloat* ca = m_stopsCache[i].colorArray;
       
   141         stops[i].second.getRGBA(ca[0], ca[1], ca[2], ca[3]);
       
   142     }
       
   143 }
       
   144 
       
   145 void SVGPaintServerGradient::updateQuartzGradientCache(const SVGPaintServerGradient* server)
       
   146 {
       
   147     // cache our own copy of the stops for faster access.
       
   148     // this is legacy code, probably could be reworked.
       
   149     if (!m_stopsCache)
       
   150         updateQuartzGradientStopsCache(gradientStops());
       
   151 
       
   152     CGShadingRelease(m_shadingCache);
       
   153 
       
   154     if (type() == RadialGradientPaintServer) {
       
   155         const SVGPaintServerRadialGradient* radial = static_cast<const SVGPaintServerRadialGradient*>(server);
       
   156         m_shadingCache = CGShadingRefForRadialGradient(radial);
       
   157     } else if (type() == LinearGradientPaintServer) {
       
   158         const SVGPaintServerLinearGradient* linear = static_cast<const SVGPaintServerLinearGradient*>(server);
       
   159         m_shadingCache = CGShadingRefForLinearGradient(linear);
       
   160     }
       
   161 }
       
   162 
       
   163 void SVGPaintServerGradient::teardown(GraphicsContext*& context, const RenderObject* object, SVGPaintTargetType type, bool isPaintingText) const
       
   164 {
       
   165     CGShadingRef shading = m_shadingCache;
       
   166     CGContextRef contextRef = context->platformContext();
       
   167     RenderStyle* style = object->style();
       
   168     ASSERT(contextRef);
       
   169 
       
   170     // As renderPath() is not used when painting text, special logic needed here.
       
   171     if (isPaintingText) {
       
   172         IntRect textBoundary = const_cast<RenderObject*>(object)->absoluteBoundingBoxRect();
       
   173         FloatRect targetRect = object->absoluteTransform().inverse().mapRect(textBoundary);
       
   174         handleBoundingBoxModeAndGradientTransformation(context, targetRect);
       
   175     }
       
   176 
       
   177     if ((type & ApplyToFillTargetType) && style->svgStyle()->hasFill()) {
       
   178         // workaround for filling the entire screen with the shading in the case that no text was intersected with the clip
       
   179         if (!isPaintingText || (object->width() > 0 && object->height() > 0))
       
   180             CGContextDrawShading(contextRef, shading);
       
   181 
       
   182         context->restore();
       
   183     }
       
   184 
       
   185     if ((type & ApplyToStrokeTargetType) && style->svgStyle()->hasStroke()) {
       
   186         if (isPaintingText && m_savedContext) {
       
   187             IntRect maskRect = const_cast<RenderObject*>(object)->absoluteBoundingBoxRect();
       
   188             maskRect = object->absoluteTransform().inverse().mapRect(maskRect);
       
   189 
       
   190             // Translate from 0x0 image origin to actual rendering position
       
   191             m_savedContext->translate(maskRect.x(), maskRect.y());
       
   192 
       
   193             // Clip current context to mask image (gradient)
       
   194             CGContextClipToMask(m_savedContext->platformContext(), CGRectMake(0, 0, maskRect.width(), maskRect.height()), m_imageBuffer->cgImage());
       
   195             m_savedContext->translate(-maskRect.x(), -maskRect.y());
       
   196 
       
   197             // Restore on-screen drawing context, after we got the image of the gradient
       
   198             delete m_imageBuffer;
       
   199             context = m_savedContext;
       
   200             contextRef = context->platformContext();
       
   201             m_savedContext = 0;
       
   202             m_imageBuffer = 0;
       
   203         }
       
   204 
       
   205         CGContextDrawShading(contextRef, shading);
       
   206         context->restore();
       
   207     }
       
   208 
       
   209     context->restore();
       
   210 }
       
   211 
       
   212 void SVGPaintServerGradient::renderPath(GraphicsContext*& context, const RenderPath* path, SVGPaintTargetType type) const
       
   213 {
       
   214     RenderStyle* style = path->style(); 
       
   215     CGContextRef contextRef = context->platformContext();
       
   216     ASSERT(contextRef);
       
   217 
       
   218     // Compute destination object bounding box
       
   219     FloatRect objectBBox;
       
   220     if (boundingBoxMode())
       
   221         objectBBox = CGContextGetPathBoundingBox(contextRef);
       
   222 
       
   223     if ((type & ApplyToFillTargetType) && style->svgStyle()->hasFill())
       
   224         clipToFillPath(contextRef, path);
       
   225 
       
   226     if ((type & ApplyToStrokeTargetType) && style->svgStyle()->hasStroke())
       
   227         clipToStrokePath(contextRef, path);
       
   228 
       
   229     handleBoundingBoxModeAndGradientTransformation(context, objectBBox);
       
   230 }
       
   231 
       
   232 void SVGPaintServerGradient::handleBoundingBoxModeAndGradientTransformation(GraphicsContext* context, const FloatRect& targetRect) const
       
   233 {
       
   234     CGContextRef contextRef = context->platformContext(); 
       
   235 
       
   236     if (boundingBoxMode()) {
       
   237         // Choose default gradient bounding box
       
   238         CGRect gradientBBox = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
       
   239 
       
   240         // Generate a transform to map between both bounding boxes
       
   241         CGAffineTransform gradientIntoObjectBBox = CGAffineTransformMakeMapBetweenRects(gradientBBox, CGRect(targetRect));
       
   242         CGContextConcatCTM(contextRef, gradientIntoObjectBBox);
       
   243     }
       
   244 
       
   245     // Apply the gradient's own transform
       
   246     CGAffineTransform transform = gradientTransform();
       
   247     CGContextConcatCTM(contextRef, transform);
       
   248 }
       
   249 
       
   250 bool SVGPaintServerGradient::setup(GraphicsContext*& context, const RenderObject* object, SVGPaintTargetType type, bool isPaintingText) const
       
   251 {
       
   252     m_ownerElement->buildGradient();
       
   253 
       
   254     // We need a hook to call this when the gradient gets updated, before drawn.
       
   255     if (!m_shadingCache)
       
   256         const_cast<SVGPaintServerGradient*>(this)->updateQuartzGradientCache(this);
       
   257 
       
   258     CGContextRef contextRef = context->platformContext(); 
       
   259     RenderStyle* style = object->style();
       
   260     ASSERT(contextRef);
       
   261 
       
   262     context->save();
       
   263     CGContextSetAlpha(contextRef, style->opacity());
       
   264 
       
   265     if ((type & ApplyToFillTargetType) && style->svgStyle()->hasFill()) {
       
   266         context->save();      
       
   267 
       
   268         if (isPaintingText)
       
   269             context->setTextDrawingMode(cTextClip);
       
   270     }
       
   271 
       
   272     if ((type & ApplyToStrokeTargetType) && style->svgStyle()->hasStroke()) {
       
   273         context->save();
       
   274         applyStrokeStyleToContext(contextRef, style, object);
       
   275 
       
   276         if (isPaintingText) {
       
   277             IntRect maskRect = const_cast<RenderObject*>(object)->absoluteBoundingBoxRect();
       
   278             maskRect = object->absoluteTransform().inverse().mapRect(maskRect);
       
   279 
       
   280             auto_ptr<ImageBuffer> maskImage = ImageBuffer::create(IntSize(maskRect.width(), maskRect.height()), false);
       
   281             // FIXME: maskImage could be NULL
       
   282 
       
   283             GraphicsContext* maskImageContext = maskImage->context();
       
   284 
       
   285             maskImageContext->save();
       
   286             maskImageContext->translate(-maskRect.x(), -maskRect.y());
       
   287 
       
   288             const_cast<RenderObject*>(object)->style()->setColor(Color(255, 255, 255));
       
   289             maskImageContext->setTextDrawingMode(cTextStroke);
       
   290 
       
   291             m_imageBuffer = maskImage.release();
       
   292             m_savedContext = context;
       
   293             context = maskImageContext;
       
   294         }
       
   295     }
       
   296 
       
   297     return true;
       
   298 }
       
   299 
       
   300 void SVGPaintServerGradient::invalidate()
       
   301 {
       
   302     // Invalidate caches
       
   303     delete m_stopsCache;
       
   304     CGShadingRelease(m_shadingCache);
       
   305 
       
   306     m_stopsCache = 0;
       
   307     m_shadingCache = 0;
       
   308 }
       
   309 
       
   310 } // namespace WebCore
       
   311 
       
   312 #endif
       
   313 
       
   314 // vim:ts=4:noet