/* Copyright (C) 2006, 2007, 2008 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "config.h" #if ENABLE(SVG) #include "SVGPaintServerGradient.h" #include "FloatConversion.h" #include "GraphicsContext.h" #include "ImageBuffer.h" #include "RenderObject.h" #include "SVGGradientElement.h" #include "SVGPaintServerLinearGradient.h" #include "SVGPaintServerRadialGradient.h" #include "SVGRenderSupport.h" #include using namespace std; namespace WebCore { static void releaseCachedStops(void* info) { static_cast(info)->deref(); } static void cgGradientCallback(void* info, const CGFloat* inValues, CGFloat* outColor) { SVGPaintServerGradient::SharedStopCache* stopsCache = static_cast(info); SVGPaintServerGradient::QuartzGradientStop* stops = stopsCache->m_stops.data(); int stopsCount = stopsCache->m_stops.size(); 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, releaseCachedStops}; CGFloat domainLimits[2] = {0, 1}; CGFloat rangeLimits[8] = {0, 1, 0, 1, 0, 1, 0, 1}; server->m_stopsCache->ref(); CGFunctionRef shadingFunction = CGFunctionCreate(server->m_stopsCache.get(), 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, releaseCachedStops}; CGFloat domainLimits[2] = {0, 1}; CGFloat rangeLimits[8] = {0, 1, 0, 1, 0, 1, 0, 1}; server->m_stopsCache->ref(); CGFunctionRef shadingFunction = CGFunctionCreate(server->m_stopsCache.get(), 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) { m_stopsCache = SharedStopCache::create(); Vector& stopsCache = m_stopsCache->m_stops; stopsCache.resize(stops.size()); CGFloat previousOffset = 0.0f; for (unsigned i = 0; i < stops.size(); ++i) { CGFloat currOffset = min(max(stops[i].first, previousOffset), static_cast(1.0)); stopsCache[i].offset = currOffset; stopsCache[i].previousDeltaInverse = 1.0f / (currOffset - previousOffset); previousOffset = currOffset; CGFloat* ca = 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); } } // Helper function for text painting static inline const RenderObject* findTextRootObject(const RenderObject* start) { while (start && !start->isSVGText()) start = start->parent(); ASSERT(start); ASSERT(start->isSVGText()); return start; } void SVGPaintServerGradient::teardown(GraphicsContext*& context, const RenderObject* object, SVGPaintTargetType type, bool isPaintingText) const { CGShadingRef shading = m_shadingCache; CGContextRef contextRef = context->platformContext(); ASSERT(contextRef); // As renderPath() is not used when painting text, special logic needed here. if (isPaintingText) { if (m_savedContext) { FloatRect maskBBox = const_cast(findTextRootObject(object))->relativeBBox(false); // Fixup transformations to be able to clip to mask AffineTransform transform = object->absoluteTransform(); FloatRect textBoundary = transform.mapRect(maskBBox); IntSize maskSize(lroundf(textBoundary.width()), lroundf(textBoundary.height())); clampImageBufferSizeToViewport(object->document()->renderer(), maskSize); if (maskSize.width() < static_cast(textBoundary.width())) textBoundary.setWidth(maskSize.width()); if (maskSize.height() < static_cast(textBoundary.height())) textBoundary.setHeight(maskSize.height()); // Clip current context to mask image (gradient) m_savedContext->concatCTM(transform.inverse()); m_savedContext->clipToImageBuffer(textBoundary, m_imageBuffer); m_savedContext->concatCTM(transform); handleBoundingBoxModeAndGradientTransformation(m_savedContext, maskBBox); // 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(); } void SVGPaintServerGradient::renderPath(GraphicsContext*& context, const RenderObject* path, SVGPaintTargetType type) const { RenderStyle* style = path->style(); CGContextRef contextRef = context->platformContext(); ASSERT(contextRef); bool isFilled = (type & ApplyToFillTargetType) && style->svgStyle()->hasFill(); // Compute destination object bounding box FloatRect objectBBox; if (boundingBoxMode()) { FloatRect bbox = path->relativeBBox(false); if (bbox.width() > 0 && bbox.height() > 0) objectBBox = bbox; } if (isFilled) clipToFillPath(contextRef, path); else clipToStrokePath(contextRef, path); handleBoundingBoxModeAndGradientTransformation(context, objectBBox); } void SVGPaintServerGradient::handleBoundingBoxModeAndGradientTransformation(GraphicsContext* context, const FloatRect& targetRect) const { if (boundingBoxMode()) { // Choose default gradient bounding box FloatRect gradientBBox(0.0f, 0.0f, 1.0f, 1.0f); // Generate a transform to map between both bounding boxes context->concatCTM(makeMapBetweenRects(gradientBBox, targetRect)); } // Apply the gradient's own transform context->concatCTM(gradientTransform()); } 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(); ASSERT(contextRef); RenderStyle* style = object->style(); bool isFilled = (type & ApplyToFillTargetType) && style->svgStyle()->hasFill(); bool isStroked = (type & ApplyToStrokeTargetType) && style->svgStyle()->hasStroke(); ASSERT(isFilled && !isStroked || !isFilled && isStroked); context->save(); CGContextSetAlpha(contextRef, isFilled ? style->svgStyle()->fillOpacity() : style->svgStyle()->strokeOpacity()); if (isPaintingText) { FloatRect maskBBox = const_cast(findTextRootObject(object))->relativeBBox(false); IntRect maskRect = enclosingIntRect(object->absoluteTransform().mapRect(maskBBox)); IntSize maskSize(maskRect.width(), maskRect.height()); clampImageBufferSizeToViewport(object->document()->renderer(), maskSize); auto_ptr maskImage = ImageBuffer::create(maskSize, false); if (!maskImage.get()) { context->restore(); return false; } GraphicsContext* maskImageContext = maskImage->context(); maskImageContext->save(); maskImageContext->setTextDrawingMode(isFilled ? cTextFill : cTextStroke); maskImageContext->translate(-maskRect.x(), -maskRect.y()); maskImageContext->concatCTM(object->absoluteTransform()); m_imageBuffer = maskImage.release(); m_savedContext = context; context = maskImageContext; contextRef = context->platformContext(); } if (isStroked) applyStrokeStyleToContext(context, style, object); return true; } void SVGPaintServerGradient::invalidate() { SVGPaintServer::invalidate(); // Invalidate caches CGShadingRelease(m_shadingCache); m_stopsCache = 0; m_shadingCache = 0; } } // namespace WebCore #endif