KRenderingPaintServerGradientQuartz.mm [plain text]
/*
* Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
* 2006 Alexander Kellett <lypanov@kde.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#if SVG_SUPPORT
#import "KRenderingPaintServerQuartz.h"
#import "QuartzSupport.h"
#import "RenderObject.h"
#import "KCanvasRenderingStyle.h"
#import "KRenderingPaintServer.h"
#import "KRenderingFillPainter.h"
#import "KRenderingStrokePainter.h"
#import "KCanvasMatrix.h"
#import "KRenderingDeviceQuartz.h"
#import "KCanvasResourcesQuartz.h"
#import "KCanvasImage.h"
#import <wtf/Assertions.h>
namespace WebCore {
static void cgGradientCallback(void* info, const CGFloat* inValues, CGFloat* outColor)
{
const KRenderingPaintServerGradientQuartz *server = (const KRenderingPaintServerGradientQuartz *)info;
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] = 1;
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++;
}
//float nextOffset = stops[nextStopIndex].offset;
CGFloat *nextColorArray = stops[nextStopIndex].colorArray;
CGFloat *previousColorArray = stops[nextStopIndex-1].colorArray;
//float totalDelta = nextOffset - previousOffset;
CGFloat diffFromPrevious = inValue - stops[nextStopIndex-1].offset;
//float percent = diffFromPrevious / totalDelta;
CGFloat percent = diffFromPrevious * stops[nextStopIndex].previousDeltaInverse;
outColor[0] = ((1.0 - percent) * previousColorArray[0] + percent * nextColorArray[0]);
outColor[1] = ((1.0 - percent) * previousColorArray[1] + percent * nextColorArray[1]);
outColor[2] = ((1.0 - percent) * previousColorArray[2] + percent * nextColorArray[2]);
outColor[3] = ((1.0 - percent) * previousColorArray[3] + percent * nextColorArray[3]);
}
// FIXME: have to handle the spreadMethod()s here SPREADMETHOD_REPEAT, etc.
}
static CGShadingRef CGShadingRefForLinearGradient(const KRenderingPaintServerLinearGradientQuartz *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};
const KRenderingPaintServerGradientQuartz *castServer = static_cast<const KRenderingPaintServerGradientQuartz *>(server);
CGFunctionRef shadingFunction = CGFunctionCreate((void *)castServer, 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 KRenderingPaintServerRadialGradientQuartz *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, focus.x);
focus.x = int(cos(angle) * radius) - 1;
focus.y = int(sin(angle) * radius) - 1;
}
CGFunctionCallbacks callbacks = {0, cgGradientCallback, NULL};
CGFloat domainLimits[2] = {0, 1};
CGFloat rangeLimits[8] = {0, 1, 0, 1, 0, 1, 0, 1};
const KRenderingPaintServerGradientQuartz *castServer = static_cast<const KRenderingPaintServerGradientQuartz *>(server);
CGFunctionRef shadingFunction = CGFunctionCreate((void *)castServer, 1, domainLimits, 4, rangeLimits, &callbacks);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGShadingRef shading = CGShadingCreateRadial(colorSpace, focus, 0, center, radius, shadingFunction, true, true);
CGColorSpaceRelease(colorSpace);
CGFunctionRelease(shadingFunction);
return shading;
}
KRenderingPaintServerGradientQuartz::KRenderingPaintServerGradientQuartz() :
m_stopsCache(0), m_stopsCount(0), m_shadingCache(0), m_maskImage(0)
{
}
KRenderingPaintServerGradientQuartz::~KRenderingPaintServerGradientQuartz()
{
delete m_stopsCache;
CGShadingRelease(m_shadingCache);
delete m_maskImage;
}
void KRenderingPaintServerGradientQuartz::updateQuartzGradientCache(const KRenderingPaintServerGradient *server)
{
// cache our own copy of the stops for faster access.
// this is legacy code, probably could be reworked.
if (!m_stopsCache)
updateQuartzGradientStopsCache(server->gradientStops());
if (!m_stopsCount)
NSLog(@"Warning, no gradient stops, gradient (%p) will be all black!", this);
if (m_shadingCache)
CGShadingRelease(m_shadingCache);
if (server->type() == PS_RADIAL_GRADIENT) {
const KRenderingPaintServerRadialGradientQuartz *radial = static_cast<const KRenderingPaintServerRadialGradientQuartz *>(server);
m_shadingCache = CGShadingRefForRadialGradient(radial);
} else if (server->type() == PS_LINEAR_GRADIENT) {
const KRenderingPaintServerLinearGradientQuartz *linear = static_cast<const KRenderingPaintServerLinearGradientQuartz *>(server);
m_shadingCache = CGShadingRefForLinearGradient(linear);
}
}
void KRenderingPaintServerGradientQuartz::updateQuartzGradientStopsCache(const Vector<KCGradientStop>& stops)
{
delete m_stopsCache;
m_stopsCount = stops.size();
m_stopsCache = new QuartzGradientStop[m_stopsCount];
CGFloat previousOffset = 0.0;
for (unsigned i = 0; i < stops.size(); ++i) {
m_stopsCache[i].offset = stops[i].first;
m_stopsCache[i].previousDeltaInverse = 1.0 / (stops[i].first - previousOffset);
previousOffset = stops[i].first;
CGFloat *ca = m_stopsCache[i].colorArray;
stops[i].second.getRGBA(ca[0], ca[1], ca[2], ca[3]);
}
}
void KRenderingPaintServerGradientQuartz::invalidateCaches()
{
delete m_stopsCache;
CGShadingRelease(m_shadingCache);
m_stopsCache = 0;
m_shadingCache = 0;
}
void KRenderingPaintServerLinearGradientQuartz::invalidate()
{
invalidateCaches();
KRenderingPaintServerLinearGradient::invalidate();
}
void KRenderingPaintServerRadialGradientQuartz::invalidate()
{
invalidateCaches();
KRenderingPaintServerRadialGradient::invalidate();
}
void KRenderingPaintServerGradientQuartz::draw(const KRenderingPaintServerGradient* server, KRenderingDeviceContext* renderingContext, const RenderPath* path, KCPaintTargetType type) const
{
if (!setup(server, renderingContext, path, type))
return;
renderPath(server, renderingContext, path, type);
teardown(server, renderingContext, path, type);
}
bool KRenderingPaintServerGradientQuartz::setup(const KRenderingPaintServerGradient* server, KRenderingDeviceContext* renderingContext, const RenderObject* renderObject, KCPaintTargetType type) const
{
if (server->listener()) // this seems like bad design to me, should be in a common baseclass. -- ecs 8/6/05
server->listener()->resourceNotification();
delete m_maskImage;
m_maskImage = 0;
// FIXME: total const HACK!
// We need a hook to call this when the gradient gets updated, before drawn.
if (!m_shadingCache)
const_cast<KRenderingPaintServerGradientQuartz*>(this)->updateQuartzGradientCache(server);
KRenderingDeviceQuartz* quartzDevice = static_cast<KRenderingDeviceQuartz*>(renderingDevice());
CGContextRef context = quartzDevice->currentCGContext();
RenderStyle* renderStyle = renderObject->style();
ASSERT(context != NULL);
CGContextSaveGState(context);
CGContextSetAlpha(context, renderStyle->opacity());
if ((type & APPLY_TO_FILL) && KSVGPainterFactory::isFilled(renderStyle)) {
CGContextSaveGState(context);
if (server->isPaintingText())
CGContextSetTextDrawingMode(context, kCGTextClip);
}
if ((type & APPLY_TO_STROKE) && KSVGPainterFactory::isStroked(renderStyle)) {
CGContextSaveGState(context);
applyStrokeStyleToContext(context, renderStyle, renderObject); // FIXME: this seems like the wrong place for this.
if (server->isPaintingText()) {
m_maskImage = static_cast<KCanvasImage *>(quartzDevice->createResource(RS_IMAGE));
int width = 2048;
int height = 2048; // FIXME???
IntSize size = IntSize(width, height);
m_maskImage->init(size);
KRenderingDeviceContext* maskImageContext = quartzDevice->contextForImage(m_maskImage);
quartzDevice->pushContext(maskImageContext);
CGContextRef maskContext = static_cast<KRenderingDeviceContextQuartz*>(maskImageContext)->cgContext();
const_cast<RenderObject*>(renderObject)->style()->setColor(Color(255, 255, 255));
CGContextSetTextDrawingMode(maskContext, kCGTextStroke);
}
}
return true;
}
void KRenderingPaintServerGradientQuartz::renderPath(const KRenderingPaintServerGradient* server, KRenderingDeviceContext* renderingContext, const RenderPath* path, KCPaintTargetType type) const
{
KRenderingDeviceQuartz* quartzDevice = static_cast<KRenderingDeviceQuartz*>(renderingDevice());
CGContextRef context = quartzDevice->currentCGContext();
RenderStyle* renderStyle = path->style();
ASSERT(context != NULL);
CGRect objectBBox;
if (server->boundingBoxMode())
objectBBox = CGContextGetPathBoundingBox(context);
if ((type & APPLY_TO_FILL) && KSVGPainterFactory::isFilled(renderStyle))
KRenderingPaintServerQuartzHelper::clipToFillPath(context, path);
if ((type & APPLY_TO_STROKE) && KSVGPainterFactory::isStroked(renderStyle))
KRenderingPaintServerQuartzHelper::clipToStrokePath(context, path);
// make the gradient fit in the bbox if necessary.
if (server->boundingBoxMode()) { // no support for bounding boxes around text yet!
// get the object bbox
CGRect gradientBBox = CGRectMake(0,0,100,100); // FIXME - this is arbitrary no?
// generate a transform to map between the two.
CGAffineTransform gradientIntoObjectBBox = CGAffineTransformMakeMapBetweenRects(gradientBBox, objectBBox);
CGContextConcatCTM(context, gradientIntoObjectBBox);
}
// apply the gradient's own transform
CGAffineTransform gradientTransform = CGAffineTransform(server->gradientTransform().matrix());
CGContextConcatCTM(context, gradientTransform);
}
void KRenderingPaintServerGradientQuartz::teardown(const KRenderingPaintServerGradient *server, KRenderingDeviceContext* renderingContext, const RenderObject* renderObject, KCPaintTargetType type) const
{
CGShadingRef shading = m_shadingCache;
KCanvasImage* maskImage = m_maskImage;
KRenderingDeviceQuartz* quartzDevice = static_cast<KRenderingDeviceQuartz*>(renderingDevice());
CGContextRef context = quartzDevice->currentCGContext();
RenderStyle* renderStyle = renderObject->style();
ASSERT(context != NULL);
if ((type & APPLY_TO_FILL) && KSVGPainterFactory::isFilled(renderStyle)) {
// workaround for filling the entire screen with the shading in the case that no text was intersected with the clip
if (!server->isPaintingText() || (renderObject->width() > 0 && renderObject->height() > 0))
CGContextDrawShading(context, shading);
CGContextRestoreGState(context);
}
if ((type & APPLY_TO_STROKE) && KSVGPainterFactory::isStroked(renderStyle)) {
if (server->isPaintingText()) {
int width = 2048;
int height = 2048; // FIXME??? SEE ABOVE
delete quartzDevice->popContext();
context = quartzDevice->currentCGContext();
void* imageBuffer = fastMalloc(width * height);
CGColorSpaceRef grayColorSpace = CGColorSpaceCreateDeviceGray();
CGContextRef grayscaleContext = CGBitmapContextCreate(imageBuffer, width, height, 8, width, grayColorSpace, kCGImageAlphaNone);
CGColorSpaceRelease(grayColorSpace);
KCanvasImageQuartz* qMaskImage = static_cast<KCanvasImageQuartz *>(maskImage);
CGContextDrawLayerAtPoint(grayscaleContext, CGPointMake(0, 0), qMaskImage->cgLayer());
CGImageRef grayscaleImage = CGBitmapContextCreateImage(grayscaleContext);
CGContextClipToMask(context, CGRectMake(0, 0, width, height), grayscaleImage);
CGContextRelease(grayscaleContext);
CGImageRelease(grayscaleImage);
}
CGContextDrawShading(context, shading);
CGContextRestoreGState(context);
}
CGContextRestoreGState(context);
}
}
#endif // SVG_SUPPORT