/* Copyright (C) 2006 Nikolas Zimmermann 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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(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& 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(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(server); m_shadingCache = CGShadingRefForRadialGradient(radial); } else if (type() == LinearGradientPaintServer) { const SVGPaintServerLinearGradient* linear = static_cast(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(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(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(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(object)->absoluteBoundingBoxRect(); maskRect = object->absoluteTransform().inverse().mapRect(maskRect); auto_ptr 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(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