diff options
Diffstat (limited to 'WebCore/svg/graphics/cg/SVGPaintServerGradientCg.cpp')
-rw-r--r-- | WebCore/svg/graphics/cg/SVGPaintServerGradientCg.cpp | 341 |
1 files changed, 341 insertions, 0 deletions
diff --git a/WebCore/svg/graphics/cg/SVGPaintServerGradientCg.cpp b/WebCore/svg/graphics/cg/SVGPaintServerGradientCg.cpp new file mode 100644 index 0000000..4d41d88 --- /dev/null +++ b/WebCore/svg/graphics/cg/SVGPaintServerGradientCg.cpp @@ -0,0 +1,341 @@ +/* + Copyright (C) 2006, 2007, 2008 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 "RenderObject.h" +#include "SVGGradientElement.h" +#include "SVGPaintServerLinearGradient.h" +#include "SVGPaintServerRadialGradient.h" +#include "SVGRenderSupport.h" + +#include <wtf/MathExtras.h> + +using namespace std; + +namespace WebCore { + +static void releaseCachedStops(void* info) +{ + static_cast<SVGPaintServerGradient::SharedStopCache*>(info)->deref(); +} + +static void cgGradientCallback(void* info, const CGFloat* inValues, CGFloat* outColor) +{ + SVGPaintServerGradient::SharedStopCache* stopsCache = static_cast<SVGPaintServerGradient::SharedStopCache*>(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<SVGGradientStop>& stops) +{ + m_stopsCache = SharedStopCache::create(); + Vector<QuartzGradientStop>& 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<CGFloat>(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<const SVGPaintServerRadialGradient*>(server); + m_shadingCache = CGShadingRefForRadialGradient(radial); + } else if (type() == LinearGradientPaintServer) { + const SVGPaintServerLinearGradient* linear = static_cast<const SVGPaintServerLinearGradient*>(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<RenderObject*>(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<int>(textBoundary.width())) + textBoundary.setWidth(maskSize.width()); + + if (maskSize.height() < static_cast<int>(textBoundary.height())) + textBoundary.setHeight(maskSize.height()); + + // Clip current context to mask image (gradient) + m_savedContext->concatCTM(transform.inverse()); + CGContextClipToMask(m_savedContext->platformContext(), CGRect(textBoundary), m_imageBuffer->cgImage()); + 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 +{ + 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(); + 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<RenderObject*>(findTextRootObject(object))->relativeBBox(false); + IntRect maskRect = enclosingIntRect(object->absoluteTransform().mapRect(maskBBox)); + + IntSize maskSize(maskRect.width(), maskRect.height()); + clampImageBufferSizeToViewport(object->document()->renderer(), maskSize); + + auto_ptr<ImageBuffer> 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 |