// Morpher.m
//
// created by Martin Wennerberg on Sun 08-Sep-1996
//
// when		who	modification

#import "Morpher.h"
#import "MorphLine.h"
#import <AppKit/AppKit.h>
#import "algebra.h"
#import "NSBitmapImageRep_editing.h"

#define SMALL_FLOAT	0.00001

#define CONTROL	0.001
#define RANGE	1.25
#define SCALE	1000

static struct RGBA mixRGBA(struct RGBA a, struct RGBA b, float d)
{
    struct RGBA	res;

    d = MIN(d, 1);
    d = MAX(d, 0);
    
    res.r = a.r + (b.r - a.r) * d;
    res.g = a.g + (b.g - a.g) * d;
    res.b = a.b + (b.b - a.b) * d;
    res.a = a.a + (b.a - a.a) * d;

    return res;
}

static inline NSPoint transform (NSPoint p, NSAffineTransformStruct m)
{
    NSPoint res;

    res.x = p.x * m.m11 + p.y * m.m12 + m.tX;
    res.y = p.x * m.m21 + p.y * m.m22 + m.tY;

    return res;
}

static NSArray *copyObjectsInArray(NSArray *source)
{
    NSMutableArray *dest = [NSMutableArray arrayWithCapacity:[source count]];
    NSEnumerator   *sourceEnum = [source objectEnumerator];
    MorphLine 	   *sourceObj;

    while ((sourceObj = [sourceEnum nextObject]))
        [dest addObject:[[sourceObj copy] autorelease]];
    return dest;
}

@implementation Morpher
- initWithFirstBitmap:(NSBitmapImageRep *)im0
           lastBitmap:(NSBitmapImageRep *)im1
           morphLines:(NSArray *) lines
{
    self = [super init];
    if (!self)
        return nil;

    firstBitmap = [im0 retain];
    lastBitmap  = [im1 retain];

    // We currently only support 8 bit per color images
    if ([firstBitmap bitsPerPixel] / [firstBitmap samplesPerPixel] != 8)
        NSLog(@"Unsupported image format. Convert first image to 8 bit per color.");
    if ([lastBitmap bitsPerPixel] / [lastBitmap samplesPerPixel] != 8)
        NSLog(@"Unsupported image format. Convert last image to 8 bit per color.");
    
    morphLines = copyObjectsInArray(lines);

    return self;
}

- (NSAffineTransformStruct) transformFromDelta:(float)beforeDelta toDelta:(float)afterDelta control:(MorphLine *)line
{
    NSAffineTransformStruct m;
    float	a1, a2;
    float	cosa1, cosa2, sina1, sina2;
    float	w1, h1, w2, h2;
    NSPoint	P1, P2;
    float	s;			/* scale */

    memset (&m, 0, sizeof(m));

    P1 = [line startPointAtDelta:beforeDelta];
    P2 = [line startPointAtDelta:afterDelta];

    w1 = [line endPointAtDelta:beforeDelta].x - P1.x;
    h1 = [line endPointAtDelta:beforeDelta].y - P1.y;
    w2 = [line endPointAtDelta:afterDelta].x - P2.x;
    h2 = [line endPointAtDelta:afterDelta].y - P2.y;

    s =  sqrt(w2 * w2 + h2 * h2) / sqrt(w1 * w1 + h1 * h1);

    a1 = atan2(h1, w1);
    a2 = atan2(h2, w2);

    cosa1 = cos(a1);
    cosa2 = cos(a2);
    sina1 = sin(a1);
    sina2 = sin(a2);

    m.m11 = s * cosa2 * cosa1 + sina2 * sina1;
    m.m12 = s * cosa2 * sina1 - sina2 * cosa1;
    m.m21 = s * sina2 * cosa1 - cosa2 * sina1;
    m.m22 = s * sina2 * sina1 + cosa2 * cosa1;

    m.tX = m.m11 * (-P1.x) + m.m12 * (-P1.y) + P2.x;
    m.tY = m.m21 * (-P1.x) + m.m22 * (-P1.y) + P2.y;

    return m;
}

- (void)calcMorphBitmap:(NSBitmapImageRep *) bitmap
                atDelta:(float) delta
{
    MorphLine	*morphLine;
    NSAffineTransformStruct	*trans0;	// transformation from 0 to delta
    NSAffineTransformStruct *trans1;	// transformation from 1 to delta
    NSPoint		 dp;					// destination point
    NSPoint		 p0;					// source point at 0
    NSPoint		 p1;					// source point at 1
    struct RGBA		 RGBA0, RGBA1;		// color at 0 and 1
    struct RGBA		 rgba;				// destination color (this is what we're trying to calculate)
    int			 lineCount;
    int			 i;
    float		 dissolveDelta;
    struct LineCoords  	*PdQd;
    struct LineCoords  	*destLineCoordinates;
    NSPoint 		 D;		/* Displacement */
    float		 weight, weightsum;
    NSPoint		 DSUM0, DSUM1;	/* Displacement sum for the to images */
    float		 dissolveStartSum, dissolveEndSum;
    float		 dissolveStartDelta, dissolveEndDelta;

    progress = 0;
    
    lineCount = [morphLines count];

    trans0 = (NSAffineTransformStruct *) alloca (lineCount * sizeof(NSAffineTransformStruct));
    trans1 = (NSAffineTransformStruct *) alloca (lineCount * sizeof(NSAffineTransformStruct));
    destLineCoordinates = (struct LineCoords *) alloca (lineCount * sizeof(struct LineCoords));

    [self fillTransMatrixes:trans0 withDeltaFrom:delta to:0];
    [self fillTransMatrixes:trans1 withDeltaFrom:delta to:1];
    [self fillDestLineCoordinates:destLineCoordinates delta:delta];

    // Loop through all pixels in the destination bitmap and set them to the morphed colors
    for (dp.y = 0; dp.y < 1; dp.y += 1.0 / [bitmap size].height)
    {
        progress = dp.y / [bitmap size].height;

        for (dp.x = 0; dp.x < 1; dp.x += 1.0 / [bitmap size].width)
        {
            /* Calc the SourcePoints p0 and p1 */
            D = NSZeroPoint;
            weightsum = 0.0;
            DSUM0 = NSZeroPoint;
            DSUM1 = NSZeroPoint;
            dissolveStartSum = 0.0;
            dissolveEndSum = 0.0;

            for (i = 0; i < lineCount; ++i)
            {
                morphLine = [morphLines objectAtIndex:i];
                PdQd = &destLineCoordinates[i];

                /* calculate the source points to sample */
                p0 = transform (dp, trans0[i]);
                p1 = transform (dp, trans1[i]);

                /* calculate the weight */
                D = pt_sub (p0, dp);
                weight = 1.0 / line_dist2_to_point (PdQd->start, PdQd->end, dp);
                DSUM0 = pt_sum (DSUM0, pt_scale (D, weight, weight));
                weightsum += weight;

                D = pt_sub (p1, dp);
                DSUM1 = pt_sum (DSUM1, pt_scale (D, weight, weight));

                dissolveStartSum += morphLine->dissolveStartDelta * weight;
                dissolveEndSum += morphLine->dissolveEndDelta * weight;
            }
            /* average (by weight) the source points */
            p0 = pt_sum(dp, pt_scale(DSUM0, 1.0 / weightsum, 1.0 / weightsum));
            p1 = pt_sum(dp, pt_scale(DSUM1, 1.0 / weightsum, 1.0 / weightsum));

            dissolveStartDelta = dissolveStartSum / weightsum;
            dissolveEndDelta = dissolveEndSum / weightsum;

            /* make sure 0 <= dissolveDelta <= 1 */
            if (ABS(dissolveEndDelta - dissolveStartDelta) <= SMALL_FLOAT )
                    if (dissolveStartDelta == 0.0)
                            dissolveDelta = 1.0;
                    else if (delta <= dissolveStartDelta)
                            dissolveDelta = 0.0;
                    else dissolveDelta = 11.0;
            else if (delta <= dissolveStartDelta)
                    dissolveDelta = 0.0;
            else
            {
                    dissolveDelta = (delta - dissolveStartDelta) / (dissolveEndDelta - dissolveStartDelta);
                    dissolveDelta = MIN (dissolveDelta, 1.0);
                    dissolveDelta = MAX (dissolveDelta, 0.0);
            }

            RGBA0 = [firstBitmap RGBAAtPoint:pt_scale (p0, [firstBitmap size].width, [firstBitmap size].height)];
            RGBA1  = [lastBitmap  RGBAAtPoint:pt_scale ( p1,  [lastBitmap size].width, [lastBitmap size].height)];
            rgba = mixRGBA (RGBA0, RGBA1, dissolveDelta);
            [bitmap setRGBA:rgba atPoint:pt_scale ( dp,  [bitmap size].width, [bitmap size].height)];
        }
    }
    progress = 1.0;
}

- (void) fillTransMatrixes:(NSAffineTransformStruct *) matrixes
             withDeltaFrom:(float) beforeDelta
                        to:(float) afterDelta
{
    int						 i;
    MorphLine				*line;

    for (i = 0; i < [morphLines count]; ++i)
    {
        line = [morphLines objectAtIndex:i];
        matrixes[i] = [self transformFromDelta:beforeDelta toDelta:afterDelta control:line];
//        matrixes[i] = [[line transformFromDelta:beforeDelta toDelta:afterDelta] transformStruct];
    }
}

- (void) fillDestLineCoordinates:(struct LineCoords *)lines
                       delta:(float)delta
{
    int	i;
    MorphLine	*morphLine;

    for (i = 0; i < [morphLines count]; ++i)
    {
        morphLine = [morphLines objectAtIndex:i];
        lines[i].start = [morphLine startPointAtDelta:delta];
        lines[i].end = [morphLine endPointAtDelta:delta];
    }
}

- (float) progress
{
    return progress;
}

@end
