blob: 45e08c1d2b16a92beaaa25b1a26b271983d9cd6c [file] [log] [blame]
/*
* 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"
#import "KRenderingPaintServerQuartz.h"
#import "QuartzSupport.h"
#import "render_object.h"
#import "kcanvas/KCanvas.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 <kxmlcore/Assertions.h>
// Maybe this should be in a base class instead...
static KCanvasImage* setupShadingWithStyle(const KRenderingPaintServerGradient *server, CGShadingRef shading, const khtml::RenderObject* renderObject, KCPaintTargetType type, bool paintingText)
{
KRenderingDeviceQuartz *quartzDevice = static_cast<KRenderingDeviceQuartz *>(QPainter::renderingDevice());
CGContextRef context = quartzDevice->currentCGContext();
khtml::RenderStyle *renderStyle = renderObject->style();
ASSERT(context != NULL);
CGContextSaveGState(context);
// make the gradient fit in the bbox if necessary.
if (server->boundingBoxMode() && renderObject->isRenderPath()) { // no support for bounding boxes around text yet!
// get the object bbox
CGRect objectBBox = CGContextGetPathBoundingBox(context);
CGRect gradientBBox = CGRectMake(0,0,100,100);
// 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().qmatrix());
CGContextConcatCTM(context, gradientTransform);
CGContextSetAlpha(context, renderStyle->opacity());
if ((type & APPLY_TO_FILL) && KSVG::KSVGPainterFactory::isFilled(renderStyle)) {
CGContextSaveGState(context);
if (paintingText)
CGContextSetTextDrawingMode(context, kCGTextClip);
}
KCanvasImage *maskImage = 0;
if ((type & APPLY_TO_STROKE) && KSVG::KSVGPainterFactory::isStroked(renderStyle)) {
CGContextSaveGState(context);
applyStrokeStyleToContext(context, renderStyle, renderObject); // FIXME: this seems like the wrong place for this.
if (paintingText) {
maskImage = static_cast<KCanvasImage *>(quartzDevice->createResource(RS_IMAGE));
int width = 2048;
int height = 2048; // FIXME???
IntSize size = IntSize(width, height);
maskImage->init(size);
KRenderingDeviceContext* maskImageContext = quartzDevice->contextForImage(maskImage);
quartzDevice->pushContext(maskImageContext);
CGContextRef maskContext = static_cast<KRenderingDeviceContextQuartz*>(maskImageContext)->cgContext();
const_cast<khtml::RenderObject *>(renderObject)->style()->setColor(QColor(255, 255, 255));
CGContextSetTextDrawingMode(maskContext, kCGTextStroke);
}
}
return maskImage;
}
static void renderShadingWithStyle(const KRenderingPaintServerGradient *server, CGShadingRef shading, const RenderPath* path, KCPaintTargetType type)
{
KRenderingDeviceQuartz *quartzDevice = static_cast<KRenderingDeviceQuartz *>(QPainter::renderingDevice());
CGContextRef context = quartzDevice->currentCGContext();
khtml::RenderStyle *renderStyle = path->style();
ASSERT(context != NULL);
if ((type & APPLY_TO_FILL) && KSVG::KSVGPainterFactory::isFilled(renderStyle))
KRenderingPaintServerQuartzHelper::clipToFillPath(context, path);
if ((type & APPLY_TO_STROKE) && KSVG::KSVGPainterFactory::isStroked(renderStyle))
KRenderingPaintServerQuartzHelper::clipToStrokePath(context, path);
}
static void teardownShadingWithStyle(const KRenderingPaintServerGradient *server, CGShadingRef shading, const khtml::RenderObject *renderObject, KCPaintTargetType type, bool paintingText, KCanvasImage *maskImage)
{
KRenderingDeviceQuartz *quartzDevice = static_cast<KRenderingDeviceQuartz *>(QPainter::renderingDevice());
CGContextRef context = quartzDevice->currentCGContext();
khtml::RenderStyle *renderStyle = renderObject->style();
ASSERT(context != NULL);
if ((type & APPLY_TO_FILL) && KSVG::KSVGPainterFactory::isFilled(renderStyle)) {
CGContextDrawShading(context, shading);
CGContextRestoreGState(context);
}
if ((type & APPLY_TO_STROKE) && KSVG::KSVGPainterFactory::isStroked(renderStyle)) {
if (paintingText) {
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);
}
CGContextDrawShading(context, shading);
CGContextRestoreGState(context);
}
CGContextRestoreGState(context);
}
//typedef vector unsigned char vUInt8;
//vector float vec_loadAndSplatScalar( float *scalarPtr )
//{
// vUInt8 splatMap = vec_lvsl( 0, scalarPtr );
// vector float result = vec_lde( 0, scalarPtr );
// splatMap = (vUInt8) vec_splat( (vector float) splatMap, 0 );
// return vec_perm( result, result, splatMap );
//}
static void cgGradientCallback(void *info, const float *inValues, float *outColor)
{
const KRenderingPaintServerGradientQuartz *server = (const KRenderingPaintServerGradientQuartz *)info;
QuartzGradientStop *stops = server->m_stopsCache;
int stopsCount = server->m_stopsCount;
float 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(float));
return;
}
if (!(inValue > stops[0].offset)) {
memcpy(outColor, stops[0].colorArray, 4 * sizeof(float));
} else if (!(inValue < stops[stopsCount-1].offset)) {
memcpy(outColor, stops[stopsCount-1].colorArray, 4 * sizeof(float));
} else {
int nextStopIndex = 0;
while ( (nextStopIndex < stopsCount) && (stops[nextStopIndex].offset < inValue) ) {
nextStopIndex++;
}
//float nextOffset = stops[nextStopIndex].offset;
float *nextColorArray = stops[nextStopIndex].colorArray;
float *previousColorArray = stops[nextStopIndex-1].colorArray;
//float totalDelta = nextOffset - previousOffset;
float diffFromPrevious = inValue - stops[nextStopIndex-1].offset;
//float percent = diffFromPrevious / totalDelta;
float percent = diffFromPrevious * stops[nextStopIndex].previousDeltaInverse;
#if 1
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]);
#else
// load up vPercent = {percent, percent, percent, percent}
vector float vPercent = vec_loadAndSplatScalar(percent);
// result = -( arg1 * arg2 - arg3 ) = arg3 - arg2 * arg1
vector float vPrevResult = vec_nmsub(vPrevColor, vPercent, vPrevColor);
// result = arg1 * arg2 + arg3
vector float vResult = vec_madd(vPercent, vNextColor, vPrevResult);
vec_st(vResult, 0, outColor);
#endif
}
// 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 };
float domainLimits[2] = { 0.0f, 1.0f };
float 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 };
float domainLimits[2] = { 0.0f, 1.0f };
float 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)
{
}
KRenderingPaintServerGradientQuartz::~KRenderingPaintServerGradientQuartz()
{
if (m_stopsCache)
free(m_stopsCache);
CGShadingRelease(m_shadingCache);
}
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 (server->type() == PS_RADIAL_GRADIENT)
{
const KRenderingPaintServerRadialGradientQuartz *radial = static_cast<const KRenderingPaintServerRadialGradientQuartz *>(server);
if (m_shadingCache)
CGShadingRelease(m_shadingCache);
// actually make the gradient
m_shadingCache = CGShadingRefForRadialGradient(radial);
}
else if (server->type() == PS_LINEAR_GRADIENT)
{
const KRenderingPaintServerLinearGradientQuartz *linear = static_cast<const KRenderingPaintServerLinearGradientQuartz *>(server);
if (m_shadingCache)
CGShadingRelease(m_shadingCache);
// actually make the gradient
m_shadingCache = CGShadingRefForLinearGradient(linear);
}
}
void KRenderingPaintServerGradientQuartz::updateQuartzGradientStopsCache(const KCSortedGradientStopList &stops)
{
if (m_stopsCache) {
free(m_stopsCache);
}
m_stopsCount = stops.count();
m_stopsCache = (QuartzGradientStop *)malloc(m_stopsCount * sizeof(QuartzGradientStop));
KCSortedGradientStopList::Iterator it = stops.begin();
KCSortedGradientStopList::Iterator end = stops.end();
int index = 0;
float previousOffset = 0.0f;
for(; it.current(); ++it) {
KCGradientOffsetPair *stop = (*it);
m_stopsCache[index].offset = stop->offset;
m_stopsCache[index].previousDeltaInverse = 1.0f/(stop->offset - previousOffset);
previousOffset = stop->offset;
float *ca = m_stopsCache[index].colorArray;
stop->color.getRgbaF(ca, ca+1, ca+2, ca+3);
index++;
}
}
void KRenderingPaintServerGradientQuartz::invalidateCaches()
{
if (m_stopsCache)
free(m_stopsCache);
CGShadingRelease(m_shadingCache);
m_stopsCache = NULL;
m_shadingCache = NULL;
}
void KRenderingPaintServerLinearGradientQuartz::invalidate()
{
invalidateCaches();
KRenderingPaintServerLinearGradient::invalidate();
}
void KRenderingPaintServerRadialGradientQuartz::invalidate()
{
invalidateCaches();
KRenderingPaintServerRadialGradient::invalidate();
}
void KRenderingPaintServerLinearGradientQuartz::draw(KRenderingDeviceContext* renderingContext, const RenderPath* path, KCPaintTargetType type) const
{
if (!setup(renderingContext, path, type))
return;
renderPath(renderingContext, path, type);
teardown(renderingContext, path, type);
}
bool KRenderingPaintServerLinearGradientQuartz::setup(KRenderingDeviceContext* renderingContext, const khtml::RenderObject* renderObject, KCPaintTargetType type) const
{
if(listener()) // this seems like bad design to me, should be in a common baseclass. -- ecs 8/6/05
listener()->resourceNotification();
// FIXME: total const HACK!
// We need a hook to call this when the gradient gets updated, before drawn.
if (!m_shadingCache)
const_cast<KRenderingPaintServerLinearGradientQuartz *>(this)->updateQuartzGradientCache(this);
m_maskImage = setupShadingWithStyle(this, m_shadingCache, renderObject, type, isPaintingText());
return true;
}
void KRenderingPaintServerLinearGradientQuartz::renderPath(KRenderingDeviceContext* renderingContext, const RenderPath* path, KCPaintTargetType type) const
{
renderShadingWithStyle(this, m_shadingCache, path, type);
}
void KRenderingPaintServerLinearGradientQuartz::teardown(KRenderingDeviceContext* renderingContext, const khtml::RenderObject* renderObject, KCPaintTargetType type) const
{
teardownShadingWithStyle(this, m_shadingCache, renderObject, type, isPaintingText(), m_maskImage);
}
void KRenderingPaintServerRadialGradientQuartz::draw(KRenderingDeviceContext* renderingContext, const RenderPath* path, KCPaintTargetType type) const
{
if (!setup(renderingContext, path, type))
return;
renderPath(renderingContext, path, type);
teardown(renderingContext, path, type);
}
bool KRenderingPaintServerRadialGradientQuartz::setup(KRenderingDeviceContext* renderingContext, const khtml::RenderObject* renderObject, KCPaintTargetType type) const
{
if(listener()) // this seems like bad design to me, should be in a common baseclass. -- ecs 8/6/05
listener()->resourceNotification();
// FIXME: total const HACK!
// We need a hook to call this when the gradient gets updated, before drawn.
if (!m_shadingCache)
const_cast<KRenderingPaintServerRadialGradientQuartz *>(this)->updateQuartzGradientCache(this);
m_maskImage = setupShadingWithStyle(this, m_shadingCache, renderObject, type, isPaintingText());
return true;
}
void KRenderingPaintServerRadialGradientQuartz::renderPath(KRenderingDeviceContext* renderingContext, const RenderPath* path, KCPaintTargetType type) const
{
renderShadingWithStyle(this, m_shadingCache, path, type);
}
void KRenderingPaintServerRadialGradientQuartz::teardown(KRenderingDeviceContext* renderingContext, const khtml::RenderObject* renderObject, KCPaintTargetType type) const
{
teardownShadingWithStyle(this, m_shadingCache, renderObject, type, isPaintingText(), m_maskImage);
}