KCanvasFilterQuartz.mm   [plain text]


/*
 * Copyright (C) 2005, 2006 Apple Computer, Inc.  All rights reserved.
 *
 * 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 "KCanvasFilterQuartz.h"

#import "BlockExceptions.h"
#import "CachedImage.h"
#import "FoundationExtras.h"
#import "Image.h"
#import "KRenderingDeviceQuartz.h"
#import "QuartzSupport.h"
#import "WKArithmeticFilter.h"
#import "WKDiffuseLightingFilter.h"
#import "WKDisplacementMapFilter.h"
#import "WKDistantLightFilter.h"
#import "WKNormalMapFilter.h"
#import "WKArithmeticFilter.h"
#import "WKComponentMergeFilter.h"
#import "WKIdentityTransferFilter.h"
#import "WKTableTransferFilter.h"
#import "WKDiscreteTransferFilter.h"
#import "WKLinearTransferFilter.h"
#import "WKGammaTransferFilter.h"
#import "WKPointLightFilter.h"
#import "WKSpecularLightingFilter.h"
#import "WKSpotLightFilter.h"
#import <QuartzCore/QuartzCore.h>

namespace WebCore {

static const char* const KCPreviousFilterOutputName = "__previousOutput__";

static inline CIColor *ciColor(const Color &c)
{
    CGColorRef colorCG = cgColor(c);
    CIColor *colorCI = [CIColor colorWithCGColor:colorCG];
    CGColorRelease(colorCG);
    return colorCI;
}

static inline CIVector *ciVector(KCanvasPoint3F point)
{
    return [CIVector vectorWithX:point.x() Y:point.y() Z:point.z()];
}

static inline CIVector *ciVector(FloatPoint point)
{
    return [CIVector vectorWithX:point.x() Y:point.y()];
}

static inline CIVector *getVectorForChannel(KCChannelSelectorType channel)
{
    switch (channel) {
        case CS_RED:
            return [CIVector vectorWithX:1.0 Y:0.0 Z:0.0 W:0.0];
        case CS_GREEN:
            return [CIVector vectorWithX:0.0 Y:1.0 Z:0.0 W:0.0];            
        case CS_BLUE:
            return [CIVector vectorWithX:0.0 Y:0.0 Z:1.0 W:0.0];
        case CS_ALPHA:
            return [CIVector vectorWithX:0.0 Y:0.0 Z:0.0 W:1.0];
        default:
            return [CIVector vectorWithX:0.0 Y:0.0 Z:0.0 W:0.0];
    }
}

KCanvasFilterQuartz::KCanvasFilterQuartz() : m_filterCIContext(0), m_filterCGLayer(0)
{
    m_imagesByName = HardRetainWithNSRelease([[NSMutableDictionary alloc] init]);
}

KCanvasFilterQuartz::~KCanvasFilterQuartz()
{
    ASSERT(!m_filterCGLayer);
    ASSERT(!m_filterCIContext);
    HardRelease(m_imagesByName);
}

void KCanvasFilterQuartz::prepareFilter(const FloatRect &bbox)
{
    if (bbox.isEmpty() || !KRenderingDeviceQuartz::filtersEnabled() || m_effects.isEmpty())
        return;

    CGContextRef cgContext = static_cast<KRenderingDeviceQuartz*>(renderingDevice())->currentCGContext();
    
    // get a CIContext, and CGLayer for drawing in.
    bool useSoftware = ! KRenderingDeviceQuartz::hardwareRenderingEnabled();
    NSDictionary *contextOptions = nil;
    
    if (useSoftware)
        contextOptions = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], kCIContextUseSoftwareRenderer, nil];
    
    m_filterCIContext = HardRetain([CIContext contextWithCGContext:cgContext options:contextOptions]);
    m_filterCGLayer = [m_filterCIContext createCGLayerWithSize:CGRect(bbox).size info:NULL];
    
    KRenderingDeviceContext *filterContext = new KRenderingDeviceContextQuartz(CGLayerGetContext(m_filterCGLayer));
    renderingDevice()->pushContext(filterContext);
    
    filterContext->concatCTM(AffineTransform().translate(-1.0f * bbox.x(), -1.0f * bbox.y()));
}

void KCanvasFilterQuartz::applyFilter(const FloatRect &bbox)
{
    if (bbox.isEmpty() || !KRenderingDeviceQuartz::filtersEnabled() || m_effects.isEmpty())
        return;

    // restore the previous context, delete the filter context.
    delete (renderingDevice()->popContext());

    // actually apply the filter effects
    CIImage *inputImage = [CIImage imageWithCGLayer:m_filterCGLayer];
    NSArray *filterStack = getCIFilterStack(inputImage);
    if ([filterStack count]) {
        CIImage *outputImage = [[filterStack lastObject] valueForKey:@"outputImage"];
        if (outputImage) {
            CGRect filterRect = CGRect(filterBBoxForItemBBox(bbox));
            CGRect translated = filterRect;
            CGPoint bboxOrigin = CGRect(bbox).origin;
            CGRect sourceRect = CGRectIntersection(translated,[outputImage extent]);
            
            CGPoint destOrigin = sourceRect.origin;
            destOrigin.x += bboxOrigin.x;
            destOrigin.y += bboxOrigin.y;
            
            [m_filterCIContext drawImage:outputImage atPoint:destOrigin fromRect:sourceRect];
        }
    }
    
    CGLayerRelease(m_filterCGLayer);
    m_filterCGLayer = 0;

    HardRelease(m_filterCIContext);
    m_filterCIContext = 0;
}

NSArray *KCanvasFilterQuartz::getCIFilterStack(CIImage *inputImage)
{
    NSMutableArray *filterEffects = [NSMutableArray array];

    DeprecatedValueListIterator<KCanvasFilterEffect *> it = m_effects.begin();
    DeprecatedValueListIterator<KCanvasFilterEffect *> end = m_effects.end();

    setImageForName(inputImage, "SourceGraphic"); // input
    for (;it != end; it++) {
        CIFilter *filter = (*it)->getCIFilter(this);
        if (filter)
            [filterEffects addObject:filter];
    }
    [m_imagesByName removeAllObjects]; // clean up before next time.

    return filterEffects;
}

CIImage *KCanvasFilterQuartz::imageForName(const DeprecatedString& name) const
{
    return [m_imagesByName objectForKey:name.getNSString()];
}

void KCanvasFilterQuartz::setImageForName(CIImage *image, const DeprecatedString &name)
{
    [m_imagesByName setValue:image forKey:name.getNSString()];
}

void KCanvasFilterQuartz::setOutputImage(const KCanvasFilterEffect *filterEffect, CIImage *output)
{
    if (!filterEffect->result().isEmpty())
        setImageForName(output, filterEffect->result());
    setImageForName(output, KCPreviousFilterOutputName);
}

static inline CIImage *alphaImageForImage(CIImage *image)
{
    CIFilter *onlyAlpha = [CIFilter filterWithName:@"CIColorMatrix"];
    CGFloat zero[4] = {0, 0, 0, 0};
    [onlyAlpha setDefaults];
    [onlyAlpha setValue:image forKey:@"inputImage"];
    [onlyAlpha setValue:[CIVector vectorWithValues:zero count:4] forKey:@"inputRVector"];
    [onlyAlpha setValue:[CIVector vectorWithValues:zero count:4] forKey:@"inputGVector"];
    [onlyAlpha setValue:[CIVector vectorWithValues:zero count:4] forKey:@"inputBVector"];
    return [onlyAlpha valueForKey:@"outputImage"];
}

CIImage *KCanvasFilterQuartz::inputImage(const KCanvasFilterEffect *filterEffect)
{
    if (filterEffect->in().isEmpty()) {
        CIImage *inImage = imageForName(KCPreviousFilterOutputName);
        if (!inImage)
            inImage = imageForName("SourceGraphic");
        return inImage;
    } else if (filterEffect->in() == "SourceAlpha") {
        CIImage *sourceAlpha = imageForName(filterEffect->in());
        if (!sourceAlpha) {
            CIImage *sourceGraphic = imageForName("SourceGraphic");
            if (!sourceGraphic)
                return nil;
            sourceAlpha = alphaImageForImage(sourceGraphic);
            setImageForName(sourceAlpha, "SourceAlpha");
        }
        return sourceAlpha;
    }

    return imageForName(filterEffect->in());
}

#pragma mark -
#pragma mark Filter Elements

#define FE_QUARTZ_SETUP_INPUT(name) \
    CIImage *inputImage = quartzFilter->inputImage(this); \
    FE_QUARTZ_CHECK_INPUT(inputImage) \
    CIFilter *filter; \
    BEGIN_BLOCK_OBJC_EXCEPTIONS; \
    filter = [CIFilter filterWithName:name]; \
    [filter setDefaults]; \
    [filter setValue:inputImage forKey:@"inputImage"];

#define FE_QUARTZ_CHECK_INPUT(input) \
    if (!input) \
        return nil;

#define FE_QUARTZ_OUTPUT_RETURN \
    quartzFilter->setOutputImage(this, [filter valueForKey:@"outputImage"]); \
    return filter; \
    END_BLOCK_OBJC_EXCEPTIONS; \
    return nil;

#define FE_QUARTZ_CROP_TO_RECT(rect) \
    { \
        CIFilter *crop = [CIFilter filterWithName:@"CICrop"]; \
        [crop setDefaults]; \
        [crop setValue:[filter valueForKey:@"outputImage"] forKey:@"inputImage"]; \
        [crop setValue:[CIVector vectorWithX:rect.origin.x Y:rect.origin.y Z:rect.size.width W:rect.size.height] forKey:@"inputRectangle"]; \
        filter = crop; \
    }

CIFilter *KCanvasFEBlendQuartz::getCIFilter(KCanvasFilterQuartz *quartzFilter) const
{
    CIFilter *filter = nil;
    BEGIN_BLOCK_OBJC_EXCEPTIONS;

    switch (blendMode()) {
    case BM_NORMAL:
        // FIXME: I think this is correct....
        filter = [CIFilter filterWithName:@"CISourceOverCompositing"];
        break;
    case BM_MULTIPLY:
        filter = [CIFilter filterWithName:@"CIMultiplyBlendMode"];
        break;
    case BM_SCREEN:
        filter = [CIFilter filterWithName:@"CIScreenBlendMode"];
        break;
    case BM_DARKEN:
        filter = [CIFilter filterWithName:@"CIDarkenBlendMode"];
        break;
    case BM_LIGHTEN:
        filter = [CIFilter filterWithName:@"CILightenBlendMode"];
        break;
    default:
        LOG_ERROR("Unhandled blend mode: %i", blendMode());
        return nil;
    }

    [filter setDefaults];
    CIImage *inputImage = quartzFilter->inputImage(this);
    FE_QUARTZ_CHECK_INPUT(inputImage);
    [filter setValue:inputImage forKey:@"inputImage"];
    CIImage *backgroundImage = quartzFilter->imageForName(in2());
    FE_QUARTZ_CHECK_INPUT(backgroundImage);
    [filter setValue:backgroundImage forKey:@"inputBackgroundImage"];

    FE_QUARTZ_OUTPUT_RETURN;
}

#define deg2rad(d) ((d * (2.0 * M_PI))/360.0)

#define CMValuesCheck(expected, type) \
    if (values().count() != expected) { \
        NSLog(@"Error, incorrect number of values in ColorMatrix for type \"%s\", expected: %i actual: %i, ignoring filter.  Values:", type, expected, values().count()); \
        for (unsigned int x=0; x < values().count(); x++) fprintf(stderr, " %f", values()[x]); \
        fprintf(stderr, "\n"); \
        return nil; \
    }

CIFilter *KCanvasFEColorMatrixQuartz::getCIFilter(KCanvasFilterQuartz *quartzFilter) const
{
    CIFilter *filter = nil;
    BEGIN_BLOCK_OBJC_EXCEPTIONS;
    switch (type()) {
    case CMT_MATRIX:
    {
        CMValuesCheck(20, "matrix");
        filter = [CIFilter filterWithName:@"CIColorMatrix"];
        [filter setDefaults];
        DeprecatedValueList<float> v = values();
        [filter setValue:[CIVector vectorWithX:v[0] Y:v[1] Z:v[2] W:v[3]] forKey:@"inputRVector"];
        [filter setValue:[CIVector vectorWithX:v[5] Y:v[6] Z:v[7] W:v[8]] forKey:@"inputGVector"];
        [filter setValue:[CIVector vectorWithX:v[10] Y:v[11] Z:v[12] W:v[13]] forKey:@"inputBVector"];
        [filter setValue:[CIVector vectorWithX:v[15] Y:v[16] Z:v[17] W:v[18]] forKey:@"inputAVector"];
        [filter setValue:[CIVector vectorWithX:v[4] Y:v[9] Z:v[14] W:v[19]] forKey:@"inputBiasVector"];
        break;
    }
    case CMT_SATURATE:
    {
        CMValuesCheck(1, "saturate");
        filter = [CIFilter filterWithName:@"CIColorControls"];
        [filter setDefaults];
        float saturation = values()[0];
        if ((saturation < 0.0) || (saturation > 3.0))
                NSLog(@"WARNING: Saturation adjustment: %f outside supported range.");
        [filter setValue:[NSNumber numberWithFloat:saturation] forKey:@"inputSaturation"];
        break;
    }
    case CMT_HUE_ROTATE:
    {
        CMValuesCheck(1, "hueRotate");
        filter = [CIFilter filterWithName:@"CIHueAdjust"];
        [filter setDefaults];
        float radians = deg2rad(values()[0]);
        [filter setValue:[NSNumber numberWithFloat:radians] forKey:@"inputAngle"];
        break;
    }
    case CMT_LUMINANCE_TO_ALPHA:
    {
        CMValuesCheck(0, "luminanceToAlpha");
        // FIXME: I bet there is an easy filter to do this.
        filter = [CIFilter filterWithName:@"CIColorMatrix"];
        [filter setDefaults];
        CGFloat zero[4] = {0, 0, 0, 0};
        CGFloat alpha[4] = {0.2125, 0.7154, 0.0721, 0};
        [filter setValue:[CIVector vectorWithValues:zero count:4] forKey:@"inputRVector"];
        [filter setValue:[CIVector vectorWithValues:zero count:4] forKey:@"inputGVector"];
        [filter setValue:[CIVector vectorWithValues:zero count:4] forKey:@"inputBVector"];
        [filter setValue:[CIVector vectorWithValues:alpha count:4] forKey:@"inputAVector"];
        [filter setValue:[CIVector vectorWithValues:zero count:4] forKey:@"inputBiasVector"];
        break;
    }
    default:
        LOG_ERROR("Unhandled ColorMatrix type: %i", type());
        return nil;
    }
    CIImage *inputImage = quartzFilter->inputImage(this);
    FE_QUARTZ_CHECK_INPUT(inputImage);
    [filter setValue:inputImage forKey:@"inputImage"];

    FE_QUARTZ_OUTPUT_RETURN;
}

static CIImage *genImageFromTable(const Vector<float>& table)
{
    int length = table.size();
    int nBytes = length*4*sizeof(float);
    float *tableStore = (float *)malloc(nBytes);
    NSData *bitmapData = [NSData dataWithBytesNoCopy:tableStore length:nBytes];
    for (Vector<float>::const_iterator it = table.begin(); it != table.end(); it++) {
        const float value = *it;
        *tableStore++ = value;
        *tableStore++ = value;
        *tableStore++ = value;
        *tableStore++ = value;
    }
    return [CIImage imageWithBitmapData:bitmapData bytesPerRow:nBytes size:CGSizeMake(length, 1) format:kCIFormatRGBAf colorSpace:nil];
}

static CIFilter *filterForComponentFunc(const KCComponentTransferFunction& func)
{
    CIFilter *filter;
    switch (func.type) {
        case CT_IDENTITY:
            filter = [CIFilter filterWithName:@"WKIdentityTransfer"];
            break;
        case CT_TABLE:
            filter = [CIFilter filterWithName:@"WKTableTransferFilter"];
            break;
        case CT_DISCRETE:
            filter = [CIFilter filterWithName:@"WKDiscreteTransferFilter"];
            break;
        case CT_LINEAR:
            filter = [CIFilter filterWithName:@"WKLinearTransfer"];            
            break;
        case CT_GAMMA:
            filter = [CIFilter filterWithName:@"WKGammaTransfer"];
            break;
        default:
            NSLog(@"WARNING: Unknown function type for feComponentTransfer");
            //and to prevent the entire svg from failing as a result
            filter = [CIFilter filterWithName:@"WKIdentityTransfer"];
            break;
    }
    return filter;
}

static void setParametersForComponentFunc(CIFilter *filter, const KCComponentTransferFunction& func, CIVector *channelSelector)
{
    switch (func.type) {
        case CT_TABLE:
            [filter setValue:genImageFromTable(func.tableValues) forKey:@"inputTable"];
            [filter setValue:channelSelector forKey:@"inputSelector"];
            break;
        case CT_DISCRETE:
            [filter setValue:genImageFromTable(func.tableValues) forKey:@"inputTable"];
            [filter setValue:channelSelector forKey:@"inputSelector"];
            break;
        case CT_LINEAR:
            [filter setValue:[NSNumber numberWithFloat:func.slope] forKey:@"inputSlope"];
            [filter setValue:[NSNumber numberWithFloat:func.intercept] forKey:@"inputIntercept"];          
            break;
        case CT_GAMMA:
            [filter setValue:[NSNumber numberWithFloat:func.amplitude] forKey:@"inputAmplitude"];
            [filter setValue:[NSNumber numberWithFloat:func.exponent] forKey:@"inputExponent"];
            [filter setValue:[NSNumber numberWithFloat:func.offset] forKey:@"inputOffset"];
            break;
        default:
            //identity has no args
            break;
    }
}

static CIFilter *getFilterForFunc(const KCComponentTransferFunction& func, CIImage *inputImage, CIVector *channelSelector) 
{
    CIFilter *filter = filterForComponentFunc(func);
    [filter setDefaults];
    
    setParametersForComponentFunc(filter, func, channelSelector);
    [filter setValue:inputImage forKey:@"inputImage"];
    return filter;
}

CIFilter *KCanvasFEComponentTransferQuartz::getFunctionFilter(KCChannelSelectorType channel, CIImage *inputImage) const
{
    switch (channel) {
        case CS_RED:
            return [getFilterForFunc(redFunction(), inputImage, getVectorForChannel(channel)) valueForKey:@"outputImage"];
        case CS_GREEN: 
            return [getFilterForFunc(greenFunction(), inputImage, getVectorForChannel(channel)) valueForKey:@"outputImage"];
        case CS_BLUE:
            return [getFilterForFunc(blueFunction(), inputImage, getVectorForChannel(channel)) valueForKey:@"outputImage"];
        case CS_ALPHA:
            return [getFilterForFunc(alphaFunction(), inputImage, getVectorForChannel(channel)) valueForKey:@"outputImage"];
        default:
            return nil;
    }
    
}

CIFilter *KCanvasFEComponentTransferQuartz::getCIFilter(KCanvasFilterQuartz *quartzFilter) const
{
    [WKComponentMergeFilter class];
    [WKIdentityTransferFilter class];
    [WKTableTransferFilter class];
    [WKDiscreteTransferFilter class];
    [WKLinearTransferFilter class];
    [WKGammaTransferFilter class];
    
    CIFilter *filter = nil;
    BEGIN_BLOCK_OBJC_EXCEPTIONS;
    filter = [CIFilter filterWithName:@"WKComponentMerge"];
    if (!filter)
        return nil;
    [filter setDefaults];
    CIImage *inputImage = quartzFilter->inputImage(this);
    FE_QUARTZ_CHECK_INPUT(inputImage);    
    
    [filter setValue:getFunctionFilter(CS_RED, inputImage) forKey:@"inputFuncR"];
    [filter setValue:getFunctionFilter(CS_GREEN, inputImage) forKey:@"inputFuncG"];
    [filter setValue:getFunctionFilter(CS_BLUE, inputImage) forKey:@"inputFuncB"];
    [filter setValue:getFunctionFilter(CS_ALPHA, inputImage) forKey:@"inputFuncA"];
    
    FE_QUARTZ_OUTPUT_RETURN;
    return nil;
}

CIFilter *KCanvasFECompositeQuartz::getCIFilter(KCanvasFilterQuartz *quartzFilter) const
{
    CIFilter *filter = nil;
    BEGIN_BLOCK_OBJC_EXCEPTIONS;

    switch (operation()) {
    case CO_OVER:
        filter = [CIFilter filterWithName:@"CISourceOverCompositing"];
        break;
    case CO_IN:
        filter = [CIFilter filterWithName:@"CISourceInCompositing"];
        break;
    case CO_OUT:
        filter = [CIFilter filterWithName:@"CISourceOutCompositing"];
        break;
    case CO_ATOP:
        filter = [CIFilter filterWithName:@"CISourceAtopCompositing"];
        break;
    case CO_XOR:
        //FIXME: I'm not sure this is right...
        filter = [CIFilter filterWithName:@"CIExclusionBlendMode"];
        break;
    case CO_ARITHMETIC:
        [WKArithmeticFilter class];
        filter = [CIFilter filterWithName:@"WKArithmeticFilter"];
        break;
    }
    
    [filter setDefaults];
    CIImage *inputImage = quartzFilter->inputImage(this);
    CIImage *backgroundImage = quartzFilter->imageForName(in2());
    FE_QUARTZ_CHECK_INPUT(inputImage);
    FE_QUARTZ_CHECK_INPUT(backgroundImage);
    [filter setValue:inputImage forKey:@"inputImage"];
    [filter setValue:backgroundImage forKey:@"inputBackgroundImage"];
    //FIXME: this seems ugly
    if (operation() == CO_ARITHMETIC) {
        [filter setValue:[NSNumber numberWithFloat:k1()] forKey:@"inputK1"];
        [filter setValue:[NSNumber numberWithFloat:k2()] forKey:@"inputK2"];
        [filter setValue:[NSNumber numberWithFloat:k3()] forKey:@"inputK3"];
        [filter setValue:[NSNumber numberWithFloat:k4()] forKey:@"inputK4"];
    }
    FE_QUARTZ_OUTPUT_RETURN;
}

CIFilter *KCanvasFEDisplacementMapQuartz::getCIFilter(KCanvasFilterQuartz *quartzFilter) const
{
    CIFilter *filter = nil;
    BEGIN_BLOCK_OBJC_EXCEPTIONS;
    [WKDisplacementMapFilter class];
    filter = [CIFilter filterWithName:@"WKDisplacementMapFilter"];    
    [filter setDefaults];
    CIImage *inputImage = quartzFilter->inputImage(this);
    CIImage *displacementMap = quartzFilter->imageForName(in2());
    FE_QUARTZ_CHECK_INPUT(inputImage);
    FE_QUARTZ_CHECK_INPUT(displacementMap);
    [filter setValue:inputImage forKey:@"inputImage"];
    [filter setValue:displacementMap forKey:@"inputDisplacementMap"];
    [filter setValue:getVectorForChannel(xChannelSelector()) forKey:@"inputXChannelSelector"];
    [filter setValue:getVectorForChannel(yChannelSelector()) forKey:@"inputYChannelSelector"];
    [filter setValue:[NSNumber numberWithFloat:scale()] forKey:@"inputScale"];
    FE_QUARTZ_OUTPUT_RETURN;
}

static inline CIFilter *getPointLightVectors(CIFilter * normals, CIVector * lightPosition, float surfaceScale)
{
    CIFilter *filter;
    BEGIN_BLOCK_OBJC_EXCEPTIONS;
    filter = [CIFilter filterWithName:@"WKPointLight"];
    if (!filter)
        return nil;
    [filter setDefaults];
    [filter setValue:[normals valueForKey:@"outputImage"] forKey:@"inputNormalMap"];
    [filter setValue:lightPosition forKey:@"inputLightPosition"];    
    [filter setValue:[NSNumber numberWithFloat:surfaceScale] forKey:@"inputSurfaceScale"];
    return filter; 
    END_BLOCK_OBJC_EXCEPTIONS;
    return nil;
}

static CIFilter *getLightVectors(CIFilter * normals, const KCLightSource * light, float surfaceScale)
{
    [WKDistantLightFilter class];
    [WKPointLightFilter class];
    [WKSpotLightFilter class];

    CIFilter *filter = nil;    
    BEGIN_BLOCK_OBJC_EXCEPTIONS;
    
    switch (light->type()) {
    case LS_DISTANT:
    {
        const KCDistantLightSource *dlight = static_cast<const KCDistantLightSource *>(light);
        
        filter = [CIFilter filterWithName:@"WKDistantLight"];
        if (!filter)
            return nil;
        [filter setDefaults];
        
        float azimuth = dlight->azimuth();
        float elevation = dlight->elevation();
        azimuth=deg2rad(azimuth);
        elevation=deg2rad(elevation);
        float Lx = cos(azimuth)*cos(elevation);
        float Ly = sin(azimuth)*cos(elevation);
        float Lz = sin(elevation);
        
        [filter setValue:[normals valueForKey:@"outputImage"] forKey:@"inputNormalMap"];
        [filter setValue:[CIVector vectorWithX:Lx Y:Ly Z:Lz] forKey:@"inputLightDirection"];
        return filter;
    }
    case LS_POINT:
    {
        const KCPointLightSource *plight = static_cast<const KCPointLightSource *>(light);
        return getPointLightVectors(normals, [CIVector vectorWithX:plight->position().x() Y:plight->position().y() Z:plight->position().z()], surfaceScale);
    }
    case LS_SPOT:
    {
        const KCSpotLightSource *slight = static_cast<const KCSpotLightSource *>(light);
        filter = [CIFilter filterWithName:@"WKSpotLight"];
        if (!filter)
            return nil;
        
        CIFilter * pointLightFilter = getPointLightVectors(normals, [CIVector vectorWithX:slight->position().x() Y:slight->position().y() Z:slight->position().z()], surfaceScale);
        if (!pointLightFilter)
            return nil;
        [filter setDefaults];
        
        [filter setValue:[pointLightFilter valueForKey:@"outputImage"] forKey:@"inputLightVectors"];
        [filter setValue:[CIVector vectorWithX:slight->direction().x() Y:slight->direction().y() Z:slight->direction().z()] forKey:@"inputLightDirection"];
        [filter setValue:[NSNumber numberWithFloat:slight->specularExponent()] forKey:@"inputSpecularExponent"];
        [filter setValue:[NSNumber numberWithFloat:deg2rad(slight->limitingConeAngle())] forKey:@"inputLimitingConeAngle"];
        return filter;
    }
    }
    END_BLOCK_OBJC_EXCEPTIONS;
    return nil;
}

static CIFilter *getNormalMap(CIImage *bumpMap, float scale)
{
    [WKNormalMapFilter class];
    CIFilter *filter;
    BEGIN_BLOCK_OBJC_EXCEPTIONS;
    filter = [CIFilter filterWithName:@"WKNormalMap"];   
    [filter setDefaults];
    
    [filter setValue:bumpMap forKey:@"inputImage"];  
    [filter setValue:[NSNumber numberWithFloat:scale] forKey:@"inputSurfaceScale"];
    return filter;
    END_BLOCK_OBJC_EXCEPTIONS;
    return nil;
}

CIFilter *KCanvasFEDiffuseLightingQuartz::getCIFilter(KCanvasFilterQuartz *quartzFilter) const
{
    const KCLightSource *light = lightSource();
    if (!light)
        return nil;
    
    [WKDiffuseLightingFilter class];
    
    CIFilter *filter;
    BEGIN_BLOCK_OBJC_EXCEPTIONS;
    filter = [CIFilter filterWithName:@"WKDiffuseLighting"];
    if (!filter)
        return nil;
    
    [filter setDefaults];
    CIImage *inputImage = quartzFilter->inputImage(this);
    FE_QUARTZ_CHECK_INPUT(inputImage);
    CIFilter *normals = getNormalMap(inputImage, surfaceScale());
    if (!normals) 
        return nil;
    
    CIFilter *lightVectors = getLightVectors(normals, light, surfaceScale());
    if (!lightVectors) 
        return nil;
    
    [filter setValue:[normals valueForKey:@"outputImage"] forKey:@"inputNormalMap"];
    [filter setValue:[lightVectors valueForKey:@"outputImage"] forKey:@"inputLightVectors"];
    [filter setValue:ciColor(lightingColor()) forKey:@"inputLightingColor"];
    [filter setValue:[NSNumber numberWithFloat:surfaceScale()] forKey:@"inputSurfaceScale"];
    [filter setValue:[NSNumber numberWithFloat:diffuseConstant()] forKey:@"inputDiffuseConstant"];
    [filter setValue:[NSNumber numberWithFloat:kernelUnitLengthX()] forKey:@"inputKernelUnitLengthX"];
    [filter setValue:[NSNumber numberWithFloat:kernelUnitLengthY()] forKey:@"inputKernelUnitLengthY"];
    
    FE_QUARTZ_OUTPUT_RETURN;
}

CIFilter *KCanvasFEFloodQuartz::getCIFilter(KCanvasFilterQuartz *quartzFilter) const
{
    CIFilter *filter;
    BEGIN_BLOCK_OBJC_EXCEPTIONS;
    filter = [CIFilter filterWithName:@"CIConstantColorGenerator"];
    [filter setDefaults];
    CGColorRef color = cgColor(floodColor());
    CGColorRef withAlpha = CGColorCreateCopyWithAlpha(color,CGColorGetAlpha(color) * floodOpacity());
    CIColor *inputColor = [CIColor colorWithCGColor:withAlpha];
    CGColorRelease(color);
    CGColorRelease(withAlpha);
    [filter setValue:inputColor forKey:@"inputColor"];
    
    CGRect cropRect = CGRectMake(-100,-100,1000,1000); // HACK
    if (!subRegion().isEmpty())
        cropRect = subRegion();
    FE_QUARTZ_CROP_TO_RECT(cropRect);
    
    FE_QUARTZ_OUTPUT_RETURN;
}

CIFilter *KCanvasFEImageQuartz::getCIFilter(KCanvasFilterQuartz *quartzFilter) const
{
    if (!cachedImage())
        return nil;

    CIFilter *filter;
    BEGIN_BLOCK_OBJC_EXCEPTIONS;
    // FIXME: This is only partially implemented (only supports images)
    CIImage *ciImage = [CIImage imageWithCGImage:cachedImage()->image()->getCGImageRef()];
    
    // FIXME: There is probably a nicer way to perform both of these transforms.
    filter = [CIFilter filterWithName:@"CIAffineTransform"];
    [filter setDefaults];
    [filter setValue:ciImage forKey:@"inputImage"];
    
    CGAffineTransform cgTransform = CGAffineTransformMake(1,0,0,-1,0,cachedImage()->image()->rect().bottom());
    NSAffineTransform *nsTransform = [NSAffineTransform transform];
    [nsTransform setTransformStruct:*((NSAffineTransformStruct *)&cgTransform)];
    [filter setValue:nsTransform forKey:@"inputTransform"];
    
    if (!subRegion().isEmpty()) {
        CIFilter *scaleImage = [CIFilter filterWithName:@"CIAffineTransform"];
        [scaleImage setDefaults];
        [scaleImage setValue:[filter valueForKey:@"outputImage"] forKey:@"inputImage"];
        
        cgTransform = CGAffineTransformMakeMapBetweenRects(CGRect(cachedImage()->image()->rect()), subRegion());
        [nsTransform setTransformStruct:*((NSAffineTransformStruct *)&cgTransform)];
        [scaleImage setValue:nsTransform forKey:@"inputTransform"];
        filter = scaleImage;
    }
    
    FE_QUARTZ_OUTPUT_RETURN;
}

CIFilter *KCanvasFEGaussianBlurQuartz::getCIFilter(KCanvasFilterQuartz *quartzFilter) const
{
    FE_QUARTZ_SETUP_INPUT(@"CIGaussianPyramid");

    float inputRadius = stdDeviationX();
    if (inputRadius != stdDeviationY()) {
        float inputAspectRatio = stdDeviationX()/stdDeviationY();
        // FIXME: inputAspectRatio only support the range .5 to 2.0!
        [filter setValue:[NSNumber numberWithFloat:inputAspectRatio] forKey:@"inputAspectRatio"];
    }
    [filter setValue:[NSNumber numberWithFloat:inputRadius] forKey:@"inputRadius"];

    FE_QUARTZ_OUTPUT_RETURN;
}

CIFilter *KCanvasFEMergeQuartz::getCIFilter(KCanvasFilterQuartz *quartzFilter) const
{
    CIFilter *filter = nil;
    BEGIN_BLOCK_OBJC_EXCEPTIONS;
    DeprecatedStringList inputs = mergeInputs();
    DeprecatedValueListIterator<DeprecatedString> it = inputs.begin();
    DeprecatedValueListIterator<DeprecatedString> end = inputs.end();

    CIImage *previousOutput = quartzFilter->inputImage(this);
    for (;it != end; it++) {
        CIImage *inputImage = quartzFilter->imageForName(*it);
    FE_QUARTZ_CHECK_INPUT(inputImage);
    FE_QUARTZ_CHECK_INPUT(previousOutput);
        filter = [CIFilter filterWithName:@"CISourceOverCompositing"];
        [filter setDefaults];
        [filter setValue:inputImage forKey:@"inputImage"];
        [filter setValue:previousOutput forKey:@"inputBackgroundImage"];
        previousOutput = [filter valueForKey:@"outputImage"];
    }
    FE_QUARTZ_OUTPUT_RETURN;
}

CIFilter *KCanvasFEOffsetQuartz::getCIFilter(KCanvasFilterQuartz *quartzFilter) const
{
    FE_QUARTZ_SETUP_INPUT(@"CIAffineTransform");
    NSAffineTransform *offsetTransform = [NSAffineTransform transform];
    [offsetTransform translateXBy:dx() yBy:dy()];
    [filter setValue:offsetTransform  forKey:@"inputTransform"];
    FE_QUARTZ_OUTPUT_RETURN;
}

CIFilter *KCanvasFESpecularLightingQuartz::getCIFilter(KCanvasFilterQuartz *quartzFilter) const
{  
    const KCLightSource *light = lightSource();
    if(!light)
        return nil;
    
    [WKSpecularLightingFilter class];  
    
    CIFilter *filter;
    BEGIN_BLOCK_OBJC_EXCEPTIONS;
    filter = [CIFilter filterWithName:@"WKSpecularLighting"];
    [filter setDefaults];
    CIImage *inputImage = quartzFilter->inputImage(this);
    FE_QUARTZ_CHECK_INPUT(inputImage);
    CIFilter *normals = getNormalMap(inputImage, surfaceScale());
    if (!normals) 
        return nil;
    CIFilter *lightVectors = getLightVectors(normals, light, surfaceScale());
    if (!lightVectors) 
        return nil;
    [filter setValue:[normals valueForKey:@"outputImage"] forKey:@"inputNormalMap"];
    [filter setValue:[lightVectors valueForKey:@"outputImage"] forKey:@"inputLightVectors"];
    [filter setValue:ciColor(lightingColor()) forKey:@"inputLightingColor"];
    [filter setValue:[NSNumber numberWithFloat:surfaceScale()] forKey:@"inputSurfaceScale"];
    [filter setValue:[NSNumber numberWithFloat:specularConstant()] forKey:@"inputSpecularConstant"];
    [filter setValue:[NSNumber numberWithFloat:specularExponent()] forKey:@"inputSpecularExponent"];
    [filter setValue:[NSNumber numberWithFloat:kernelUnitLengthX()] forKey:@"inputKernelUnitLengthX"];
    [filter setValue:[NSNumber numberWithFloat:kernelUnitLengthY()] forKey:@"inputKernelUnitLengthY"];
    
    FE_QUARTZ_OUTPUT_RETURN;
}

CIFilter *KCanvasFETileQuartz::getCIFilter(KCanvasFilterQuartz *quartzFilter) const
{
    FE_QUARTZ_SETUP_INPUT(@"CIAffineTile");
    FE_QUARTZ_OUTPUT_RETURN;
}

}

#endif // SVG_SUPPORT