blob: 734fc33609ab6b27f0c51c68914b383ff5773e7f [file] [log] [blame]
/*
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