/*
 * The Python Imaging Library.
 * $Id$
 *
 * a simple drawing package for the Imaging library
 *
 * history:
 * 1996-04-13 fl  Created.
 * 1996-04-30 fl  Added transforms and polygon support.
 * 1996-08-12 fl  Added filled polygons.
 * 1996-11-05 fl  Fixed float/int confusion in polygon filler
 * 1997-07-04 fl  Support 32-bit images (C++ would have been nice)
 * 1998-09-09 fl  Eliminated qsort casts; improved rectangle clipping
 * 1998-09-10 fl  Fixed fill rectangle to include lower edge (!)
 * 1998-12-29 fl  Added arc, chord, and pieslice primitives
 * 1999-01-10 fl  Added some level 2 ("arrow") stuff (experimental)
 * 1999-02-06 fl  Added bitmap primitive
 * 1999-07-26 fl  Eliminated a compiler warning
 * 1999-07-31 fl  Pass ink as void* instead of int
 * 2002-12-10 fl  Added experimental RGBA-on-RGB drawing
 * 2004-09-04 fl  Support simple wide lines (no joins)
 * 2005-05-25 fl  Fixed line width calculation
 *
 * Copyright (c) 1996-2006 by Fredrik Lundh
 * Copyright (c) 1997-2006 by Secret Labs AB.
 *
 * See the README file for information on usage and redistribution.
 */

/* FIXME: support fill/outline attribute for all filled shapes */
/* FIXME: support zero-winding fill */
/* FIXME: add drawing context, support affine transforms */
/* FIXME: support clip window (and mask?) */

#include "Imaging.h"

#include <math.h>

#define CEIL(v)  (int) ceil(v)
#define FLOOR(v) ((v) >= 0.0 ? (int) (v) : (int) floor(v))

#define INK8(ink) (*(UINT8*)ink)
#define INK32(ink) (*(INT32*)ink)

/* like (a * b + 127) / 255), but much faster on most platforms */
#define MULDIV255(a, b, tmp)\
        (tmp = (a) * (b) + 128, ((((tmp) >> 8) + (tmp)) >> 8))

#define BLEND(mask, in1, in2, tmp1, tmp2)\
        (MULDIV255(in1, 255 - mask, tmp1) + MULDIV255(in2, mask, tmp2))

/* -------------------------------------------------------------------- */
/* Primitives                                                           */
/* -------------------------------------------------------------------- */

typedef struct {
    /* edge descriptor for polygon engine */
    int d;
    int x0, y0;
    int xmin, ymin, xmax, ymax;
    float dx;
} Edge;

static inline void
point8(Imaging im, int x, int y, int ink)
{
    if (x >= 0 && x < im->xsize && y >= 0 && y < im->ysize)
        im->image8[y][x] = (UINT8) ink;
}

static inline void
point32(Imaging im, int x, int y, int ink)
{
    if (x >= 0 && x < im->xsize && y >= 0 && y < im->ysize)
        im->image32[y][x] = ink;
}

static inline void
point32rgba(Imaging im, int x, int y, int ink)
{
    unsigned int tmp1, tmp2;

    if (x >= 0 && x < im->xsize && y >= 0 && y < im->ysize) {
        UINT8* out = (UINT8*) im->image[y]+x*4;
        UINT8* in = (UINT8*) &ink;
        out[0] = BLEND(in[3], out[0], in[0], tmp1, tmp2);
        out[1] = BLEND(in[3], out[1], in[1], tmp1, tmp2);
        out[2] = BLEND(in[3], out[2], in[2], tmp1, tmp2);
    }
}

static inline void
hline8(Imaging im, int x0, int y0, int x1, int ink)
{
    int tmp;

    if (y0 >= 0 && y0 < im->ysize) {
        if (x0 > x1)
            tmp = x0, x0 = x1, x1 = tmp;
        if (x0 < 0)
            x0 = 0;
        else if (x0 >= im->xsize)
            return;
        if (x1 < 0)
            return;
        else if (x1 >= im->xsize)
            x1 = im->xsize-1;
        if (x0 <= x1)
            memset(im->image8[y0] + x0, (UINT8) ink, x1 - x0 + 1);
    }
}

static inline void
hline32(Imaging im, int x0, int y0, int x1, int ink)
{
    int tmp;
    INT32* p;

    if (y0 >= 0 && y0 < im->ysize) {
        if (x0 > x1)
            tmp = x0, x0 = x1, x1 = tmp;
        if (x0 < 0)
            x0 = 0;
        else if (x0 >= im->xsize)
            return;
        if (x1 < 0)
            return;
        else if (x1 >= im->xsize)
            x1 = im->xsize-1;
        p = im->image32[y0];
        while (x0 <= x1)
            p[x0++] = ink;
    }
}

static inline void
hline32rgba(Imaging im, int x0, int y0, int x1, int ink)
{
    int tmp;
    unsigned int tmp1, tmp2;

    if (y0 >= 0 && y0 < im->ysize) {
        if (x0 > x1)
            tmp = x0, x0 = x1, x1 = tmp;
        if (x0 < 0)
            x0 = 0;
        else if (x0 >= im->xsize)
            return;
        if (x1 < 0)
            return;
        else if (x1 >= im->xsize)
            x1 = im->xsize-1;
        if (x0 <= x1) {
            UINT8* out = (UINT8*) im->image[y0]+x0*4;
            UINT8* in = (UINT8*) &ink;
            while (x0 <= x1) {
                out[0] = BLEND(in[3], out[0], in[0], tmp1, tmp2);
                out[1] = BLEND(in[3], out[1], in[1], tmp1, tmp2);
                out[2] = BLEND(in[3], out[2], in[2], tmp1, tmp2);
                x0++; out += 4;
            }
        }
    }
}

static inline void
line8(Imaging im, int x0, int y0, int x1, int y1, int ink)
{
    int i, n, e;
    int dx, dy;
    int xs, ys;

    /* normalize coordinates */
    dx = x1-x0;
    if (dx < 0)
        dx = -dx, xs = -1;
    else
        xs = 1;
    dy = y1-y0;
    if (dy < 0)
        dy = -dy, ys = -1;
    else
        ys = 1;

    n = (dx > dy) ? dx : dy;

    if (dx == 0)

        /* vertical */
        for (i = 0; i < dy; i++) {
            point8(im, x0, y0, ink);
            y0 += ys;
        }

    else if (dy == 0)

        /* horizontal */
        for (i = 0; i < dx; i++) {
            point8(im, x0, y0, ink);
            x0 += xs;
        }

    else if (dx > dy) {

        /* bresenham, horizontal slope */
        n = dx;
        dy += dy;
        e = dy - dx;
        dx += dx;

        for (i = 0; i < n; i++) {
            point8(im, x0, y0, ink);
            if (e >= 0) {
                y0 += ys;
                e -= dx;
            }
            e += dy;
            x0 += xs;
        }

    } else {

        /* bresenham, vertical slope */
        n = dy;
        dx += dx;
        e = dx - dy;
        dy += dy;

        for (i = 0; i < n; i++) {
            point8(im, x0, y0, ink);
            if (e >= 0) {
                x0 += xs;
                e -= dy;
            }
            e += dx;
            y0 += ys;
        }

    }
}

static inline void
line32(Imaging im, int x0, int y0, int x1, int y1, int ink)
{
    int i, n, e;
    int dx, dy;
    int xs, ys;

    /* normalize coordinates */
    dx = x1-x0;
    if (dx < 0)
        dx = -dx, xs = -1;
    else
        xs = 1;
    dy = y1-y0;
    if (dy < 0)
        dy = -dy, ys = -1;
    else
        ys = 1;

    n = (dx > dy) ? dx : dy;

    if (dx == 0)

        /* vertical */
        for (i = 0; i < dy; i++) {
            point32(im, x0, y0, ink);
            y0 += ys;
        }

    else if (dy == 0)

        /* horizontal */
        for (i = 0; i < dx; i++) {
            point32(im, x0, y0, ink);
            x0 += xs;
        }

    else if (dx > dy) {

        /* bresenham, horizontal slope */
        n = dx;
        dy += dy;
        e = dy - dx;
        dx += dx;

        for (i = 0; i < n; i++) {
            point32(im, x0, y0, ink);
            if (e >= 0) {
                y0 += ys;
                e -= dx;
            }
            e += dy;
            x0 += xs;
        }

    } else {

        /* bresenham, vertical slope */
        n = dy;
        dx += dx;
        e = dx - dy;
        dy += dy;

        for (i = 0; i < n; i++) {
            point32(im, x0, y0, ink);
            if (e >= 0) {
                x0 += xs;
                e -= dy;
            }
            e += dx;
            y0 += ys;
        }

    }
}

static inline void
line32rgba(Imaging im, int x0, int y0, int x1, int y1, int ink)
{
    int i, n, e;
    int dx, dy;
    int xs, ys;

    /* normalize coordinates */
    dx = x1-x0;
    if (dx < 0)
        dx = -dx, xs = -1;
    else
        xs = 1;
    dy = y1-y0;
    if (dy < 0)
        dy = -dy, ys = -1;
    else
        ys = 1;

    n = (dx > dy) ? dx : dy;

    if (dx == 0)

        /* vertical */
        for (i = 0; i < dy; i++) {
            point32rgba(im, x0, y0, ink);
            y0 += ys;
        }

    else if (dy == 0)

        /* horizontal */
        for (i = 0; i < dx; i++) {
            point32rgba(im, x0, y0, ink);
            x0 += xs;
        }

    else if (dx > dy) {

        /* bresenham, horizontal slope */
        n = dx;
        dy += dy;
        e = dy - dx;
        dx += dx;

        for (i = 0; i < n; i++) {
            point32rgba(im, x0, y0, ink);
            if (e >= 0) {
                y0 += ys;
                e -= dx;
            }
            e += dy;
            x0 += xs;
        }

    } else {

        /* bresenham, vertical slope */
        n = dy;
        dx += dx;
        e = dx - dy;
        dy += dy;

        for (i = 0; i < n; i++) {
            point32rgba(im, x0, y0, ink);
            if (e >= 0) {
                x0 += xs;
                e -= dy;
            }
            e += dx;
            y0 += ys;
        }

    }
}

static int
x_cmp(const void *x0, const void *x1)
{
    float diff = *((float*)x0) - *((float*)x1);
    if (diff < 0)
        return -1;
    else if (diff > 0)
        return 1;
    else
        return 0;
}

static inline int
polygon8(Imaging im, int n, Edge *e, int ink, int eofill)
{
    int i, j;
    float *xx;
    int ymin, ymax;
    float y;

    if (n <= 0)
        return 0;

    /* Find upper and lower polygon boundary (within image) */

    ymin = e[0].ymin;
    ymax = e[0].ymax;
    for (i = 1; i < n; i++) {
        if (e[i].ymin < ymin) ymin = e[i].ymin;
        if (e[i].ymax > ymax) ymax = e[i].ymax;
    }

    if (ymin < 0)
        ymin = 0;
    if (ymax >= im->ysize)
        ymax = im->ysize-1;

    /* Process polygon edges */

    xx = malloc(n * sizeof(float));
    if (!xx)
        return -1;

    for (;ymin <= ymax; ymin++) {
        y = ymin+0.5F;
        for (i = j = 0; i < n; i++)
            if (y >= e[i].ymin && y <= e[i].ymax) {
                if (e[i].d == 0)
                    hline8(im, e[i].xmin, ymin, e[i].xmax, ink);
                else
                    xx[j++] = (y-e[i].y0) * e[i].dx + e[i].x0;
            }
        if (j == 2) {
            if (xx[0] < xx[1])
                hline8(im, CEIL(xx[0]-0.5), ymin, FLOOR(xx[1]+0.5), ink);
            else
                hline8(im, CEIL(xx[1]-0.5), ymin, FLOOR(xx[0]+0.5), ink);
        } else {
            qsort(xx, j, sizeof(float), x_cmp);
            for (i = 0; i < j-1 ; i += 2)
                hline8(im, CEIL(xx[i]-0.5), ymin, FLOOR(xx[i+1]+0.5), ink);
        }
    }

    free(xx);

    return 0;
}

static inline int
polygon32(Imaging im, int n, Edge *e, int ink, int eofill)
{
    int i, j;
    float *xx;
    int ymin, ymax;
    float y;

    if (n <= 0)
        return 0;

    /* Find upper and lower polygon boundary (within image) */

    ymin = e[0].ymin;
    ymax = e[0].ymax;
    for (i = 1; i < n; i++) {
        if (e[i].ymin < ymin) ymin = e[i].ymin;
        if (e[i].ymax > ymax) ymax = e[i].ymax;
    }

    if (ymin < 0)
        ymin = 0;
    if (ymax >= im->ysize)
        ymax = im->ysize-1;

    /* Process polygon edges */

    xx = malloc(n * sizeof(float));
    if (!xx)
        return -1;

    for (;ymin <= ymax; ymin++) {
        y = ymin+0.5F;
        for (i = j = 0; i < n; i++) {
            if (y >= e[i].ymin && y <= e[i].ymax) {
                if (e[i].d == 0)
                    hline32(im, e[i].xmin, ymin, e[i].xmax, ink);
                else
                    xx[j++] = (y-e[i].y0) * e[i].dx + e[i].x0;
            }
        }
        if (j == 2) {
            if (xx[0] < xx[1])
                hline32(im, CEIL(xx[0]-0.5), ymin, FLOOR(xx[1]+0.5), ink);
            else
                hline32(im, CEIL(xx[1]-0.5), ymin, FLOOR(xx[0]+0.5), ink);
        } else {
            qsort(xx, j, sizeof(float), x_cmp);
            for (i = 0; i < j-1 ; i += 2)
                hline32(im, CEIL(xx[i]-0.5), ymin, FLOOR(xx[i+1]+0.5), ink);
        }
    }

    free(xx);

    return 0;
}

static inline int
polygon32rgba(Imaging im, int n, Edge *e, int ink, int eofill)
{
    int i, j;
    float *xx;
    int ymin, ymax;
    float y;

    if (n <= 0)
        return 0;

    /* Find upper and lower polygon boundary (within image) */

    ymin = e[0].ymin;
    ymax = e[0].ymax;
    for (i = 1; i < n; i++) {
        if (e[i].ymin < ymin) ymin = e[i].ymin;
        if (e[i].ymax > ymax) ymax = e[i].ymax;
    }

    if (ymin < 0)
        ymin = 0;
    if (ymax >= im->ysize)
        ymax = im->ysize-1;

    /* Process polygon edges */

    xx = malloc(n * sizeof(float));
    if (!xx)
        return -1;

    for (;ymin <= ymax; ymin++) {
        y = ymin+0.5F;
        for (i = j = 0; i < n; i++) {
            if (y >= e[i].ymin && y <= e[i].ymax) {
                if (e[i].d == 0)
                    hline32rgba(im, e[i].xmin, ymin, e[i].xmax, ink);
                else
                    xx[j++] = (y-e[i].y0) * e[i].dx + e[i].x0;
            }
        }
        if (j == 2) {
            if (xx[0] < xx[1])
                hline32rgba(im, CEIL(xx[0]-0.5), ymin, FLOOR(xx[1]+0.5), ink);
            else
                hline32rgba(im, CEIL(xx[1]-0.5), ymin, FLOOR(xx[0]+0.5), ink);
        } else {
            qsort(xx, j, sizeof(float), x_cmp);
            for (i = 0; i < j-1 ; i += 2)
                hline32rgba(im, CEIL(xx[i]-0.5), ymin, FLOOR(xx[i+1]+0.5), ink);
        }
    }

    free(xx);

    return 0;
}

static inline void
add_edge(Edge *e, int x0, int y0, int x1, int y1)
{
    /* printf("edge %d %d %d %d\n", x0, y0, x1, y1); */

    if (x0 <= x1)
        e->xmin = x0, e->xmax = x1;
    else
        e->xmin = x1, e->xmax = x0;

    if (y0 <= y1)
        e->ymin = y0, e->ymax = y1;
    else
        e->ymin = y1, e->ymax = y0;

    if (y0 == y1) {
        e->d = 0;
        e->dx = 0.0;
    } else {
        e->dx = ((float)(x1-x0)) / (y1-y0);
        if (y0 == e->ymin)
            e->d = 1;
        else
            e->d = -1;
    }

    e->x0 = x0;
    e->y0 = y0;
}

typedef struct {
    void (*point)(Imaging im, int x, int y, int ink);
    void (*hline)(Imaging im, int x0, int y0, int x1, int ink);
    void (*line)(Imaging im, int x0, int y0, int x1, int y1, int ink);
    int (*polygon)(Imaging im, int n, Edge *e, int ink, int eofill);
} DRAW;

DRAW draw8 = { point8,  hline8,  line8,  polygon8 };
DRAW draw32 = { point32, hline32, line32, polygon32 };
DRAW draw32rgba = { point32rgba, hline32rgba, line32rgba, polygon32rgba };

/* -------------------------------------------------------------------- */
/* Interface                                                            */
/* -------------------------------------------------------------------- */

#define DRAWINIT()\
    if (im->image8) {\
        draw = &draw8;\
        ink = INK8(ink_);\
    } else {\
        draw = (op) ? &draw32rgba : &draw32;    \
        ink = INK32(ink_);\
    }

int
ImagingDrawPoint(Imaging im, int x0, int y0, const void* ink_, int op)
{
    DRAW* draw;
    INT32 ink;

    DRAWINIT();

    draw->point(im, x0, y0, ink);

    return 0;
}

int
ImagingDrawLine(Imaging im, int x0, int y0, int x1, int y1, const void* ink_,
                int op)
{
    DRAW* draw;
    INT32 ink;

    DRAWINIT();

    draw->line(im, x0, y0, x1, y1, ink);

    return 0;
}

int
ImagingDrawWideLine(Imaging im, int x0, int y0, int x1, int y1,
                    const void* ink_, int width, int op)
{
    DRAW* draw;
    INT32 ink;

    Edge e[4];

    int dx, dy;
    double d;

    DRAWINIT();

    if (width <= 1) {
        draw->line(im, x0, y0, x1, y1, ink);
        return 0;
    }

    dx = x1-x0;
    dy = y1-y0;

    if (dx == 0 && dy == 0) {
        draw->point(im, x0, y0, ink);
        return 0;
    }

    d = width / sqrt((float) (dx*dx + dy*dy)) / 2.0;

    dx = (int) floor(d * (y1-y0) + 0.5);
    dy = (int) floor(d * (x1-x0) + 0.5);

    add_edge(e+0, x0 - dx,  y0 + dy, x1 - dx,  y1 + dy);
    add_edge(e+1, x1 - dx,  y1 + dy, x1 + dx,  y1 - dy);
    add_edge(e+2, x1 + dx,  y1 - dy, x0 + dx,  y0 - dy);
    add_edge(e+3, x0 + dx,  y0 - dy, x0 - dx,  y0 + dy);

    draw->polygon(im, 4, e, ink, 0);

    return 0;
}

int
ImagingDrawRectangle(Imaging im, int x0, int y0, int x1, int y1,
                     const void* ink_, int fill, int op)
{
    int y;
    int tmp;
    DRAW* draw;
    INT32 ink;

    DRAWINIT();

    if (y0 > y1)
        tmp = y0, y0 = y1, y1 = tmp;

    if (fill) {

        if (y0 < 0)
            y0 = 0;
        else if (y0 >= im->ysize)
            return 0;

        if (y1 < 0)
            return 0;
        else if (y1 > im->ysize)
            y1 = im->ysize;

        for (y = y0; y <= y1; y++)
            draw->hline(im, x0, y, x1, ink);

    } else {

        /* outline */
        draw->line(im, x0, y0, x1, y0, ink);
        draw->line(im, x1, y0, x1, y1, ink);
        draw->line(im, x1, y1, x0, y1, ink);
        draw->line(im, x0, y1, x0, y0, ink);

    }

    return 0;
}

int
ImagingDrawPolygon(Imaging im, int count, int* xy, const void* ink_,
                   int fill, int op)
{
    int i, n;
    DRAW* draw;
    INT32 ink;

    if (count <= 0)
        return 0;

    DRAWINIT();

    if (fill) {

        /* Build edge list */
        Edge* e = malloc(count * sizeof(Edge));
        if (!e) {
            (void) ImagingError_MemoryError();
            return -1;
        }
        for (i = n = 0; i < count-1; i++)
            add_edge(&e[n++], xy[i+i], xy[i+i+1], xy[i+i+2], xy[i+i+3]);
        if (xy[i+i] != xy[0] || xy[i+i+1] != xy[1])
            add_edge(&e[n++], xy[i+i], xy[i+i+1], xy[0], xy[1]);
        draw->polygon(im, n, e, ink, 0);
        free(e);

    } else {

        /* Outline */
        for (i = 0; i < count-1; i++)
            draw->line(im, xy[i+i], xy[i+i+1], xy[i+i+2], xy[i+i+3], ink);
        draw->line(im, xy[i+i], xy[i+i+1], xy[0], xy[1], ink);

    }

    return 0;
}

int
ImagingDrawBitmap(Imaging im, int x0, int y0, Imaging bitmap, const void* ink,
                  int op)
{
    return ImagingFill2(
        im, ink, bitmap,
        x0, y0, x0 + bitmap->xsize, y0 + bitmap->ysize
        );
}

/* -------------------------------------------------------------------- */
/* standard shapes */

#define ARC 0
#define CHORD 1
#define PIESLICE 2

static int
ellipse(Imaging im, int x0, int y0, int x1, int y1,
        int start, int end, const void* ink_, int fill,
        int mode, int op)
{
    int i, n;
    int cx, cy;
    int w, h;
    int x = 0, y = 0;
    int lx = 0, ly = 0;
    int sx = 0, sy = 0;
    DRAW* draw;
    INT32 ink;

    w = x1 - x0;
    h = y1 - y0;
    if (w < 0 || h < 0)
        return 0;

    DRAWINIT();

    cx = (x0 + x1) / 2;
    cy = (y0 + y1) / 2;

    while (end < start)
        end += 360;

    if (mode != ARC && fill) {

        /* Build edge list */
        Edge* e = malloc((end - start + 3) * sizeof(Edge));
        if (!e) {
            ImagingError_MemoryError();
            return -1;
        }

        n = 0;

        for (i = start; i <= end; i++) {
            x = FLOOR((cos(i*M_PI/180) * w/2) + cx + 0.5);
            y = FLOOR((sin(i*M_PI/180) * h/2) + cy + 0.5);
            if (i != start)
                add_edge(&e[n++], lx, ly, x, y);
            else
                sx = x, sy = y;
            lx = x, ly = y;
        }

        if (n > 0) {
            /* close and draw polygon */
            if (mode == PIESLICE) {
                if (x != cx || y != cy) {
                    add_edge(&e[n++], x, y, cx, cy);
                    add_edge(&e[n++], cx, cy, sx, sy);
                }
            } else {
                if (x != sx || y != sy)
                    add_edge(&e[n++], x, y, sx, sy);
            }
            draw->polygon(im, n, e, ink, 0);
        }

        free(e);

    } else {

        for (i = start; i <= end; i++) {
            x = FLOOR((cos(i*M_PI/180) * w/2) + cx + 0.5);
            y = FLOOR((sin(i*M_PI/180) * h/2) + cy + 0.5);
            if (i != start)
                draw->line(im, lx, ly, x, y, ink);
            else
                sx = x, sy = y;
            lx = x, ly = y;
        }

        if (i != start) {
            if (mode == PIESLICE) {
                if (x != cx || y != cy) {
                    draw->line(im, x, y, cx, cy, ink);
                    draw->line(im, cx, cy, sx, sy, ink);
                }
            } else if (mode == CHORD) {
                if (x != sx || y != sy)
                    draw->line(im, x, y, sx, sy, ink);
            }
        }
    }

    return 0;
}

int
ImagingDrawArc(Imaging im, int x0, int y0, int x1, int y1,
               int start, int end, const void* ink, int op)
{
    return ellipse(im, x0, y0, x1, y1, start, end, ink, 0, ARC, op);
}

int
ImagingDrawChord(Imaging im, int x0, int y0, int x1, int y1,
               int start, int end, const void* ink, int fill, int op)
{
    return ellipse(im, x0, y0, x1, y1, start, end, ink, fill, CHORD, op);
}

int
ImagingDrawEllipse(Imaging im, int x0, int y0, int x1, int y1,
                   const void* ink, int fill, int op)
{
    return ellipse(im, x0, y0, x1, y1, 0, 360, ink, fill, CHORD, op);
}

int
ImagingDrawPieslice(Imaging im, int x0, int y0, int x1, int y1,
                    int start, int end, const void* ink, int fill, int op)
{
    return ellipse(im, x0, y0, x1, y1, start, end, ink, fill, PIESLICE, op);
}

/* -------------------------------------------------------------------- */

/* experimental level 2 ("arrow") graphics stuff.  this implements
   portions of the arrow api on top of the Edge structure.  the
   semantics are ok, except that "curve" flattens the bezier curves by
   itself */

#if 1 /* ARROW_GRAPHICS */

struct ImagingOutlineInstance {

    float x0, y0;

    float x, y;

    int count;
    Edge *edges;

    int size;

};


ImagingOutline
ImagingOutlineNew(void)
{
    ImagingOutline outline;

    outline = calloc(1, sizeof(struct ImagingOutlineInstance));
    if (!outline)
        return (ImagingOutline) ImagingError_MemoryError();

    outline->edges = NULL;
    outline->count = outline->size = 0;

    ImagingOutlineMove(outline, 0, 0);

    return outline;
}

void
ImagingOutlineDelete(ImagingOutline outline)
{
    if (!outline)
        return;

    if (outline->edges)
        free(outline->edges);

    free(outline);
}


static Edge*
allocate(ImagingOutline outline, int extra)
{
    Edge* e;

    if (outline->count + extra > outline->size) {
        /* expand outline buffer */
        outline->size += extra + 25;
        if (!outline->edges)
            e = malloc(outline->size * sizeof(Edge));
        else
            e = realloc(outline->edges, outline->size * sizeof(Edge));
        if (!e)
            return NULL;
        outline->edges = e;
    }

    e = outline->edges + outline->count;

    outline->count += extra;

    return e;
}

int
ImagingOutlineMove(ImagingOutline outline, float x0, float y0)
{
    outline->x = outline->x0 = x0;
    outline->y = outline->y0 = y0;

    return 0;
}

int
ImagingOutlineLine(ImagingOutline outline, float x1, float y1)
{
    Edge* e;

    e = allocate(outline, 1);
    if (!e)
        return -1; /* out of memory */

    add_edge(e, (int) outline->x, (int) outline->y, (int) x1, (int) y1);

    outline->x = x1;
    outline->y = y1;

    return 0;
}

int
ImagingOutlineCurve(ImagingOutline outline, float x1, float y1,
                    float x2, float y2, float x3, float y3)
{
    Edge* e;
    int i;
    float xo, yo;

#define STEPS 32

    e = allocate(outline, STEPS);
    if (!e)
        return -1; /* out of memory */

    xo = outline->x;
    yo = outline->y;

    /* flatten the bezier segment */

    for (i = 1; i <= STEPS; i++) {

        float t = ((float) i) / STEPS;
        float t2 = t*t;
        float t3 = t2*t;

        float u = 1.0F - t;
        float u2 = u*u;
        float u3 = u2*u;

        float x = outline->x*u3 + 3*(x1*t*u2 + x2*t2*u) + x3*t3 + 0.5;
        float y = outline->y*u3 + 3*(y1*t*u2 + y2*t2*u) + y3*t3 + 0.5;

        add_edge(e++, xo, yo, (int) x, (int) y);

        xo = x, yo = y;

    }

    outline->x = xo;
    outline->y = yo;

    return 0;
}

int
ImagingOutlineCurve2(ImagingOutline outline, float cx, float cy,
                     float x3, float y3)
{
    /* add bezier curve based on three control points (as
       in the Flash file format) */

    return ImagingOutlineCurve(
        outline,
        (outline->x + cx + cx)/3, (outline->y + cy + cy)/3,
        (cx + cx + x3)/3, (cy + cy + y3)/3,
        x3, y3);
}

int
ImagingOutlineClose(ImagingOutline outline)
{
    if (outline->x == outline->x0 && outline->y == outline->y0)
        return 0;
    return ImagingOutlineLine(outline, outline->x0, outline->y0);
}

int
ImagingOutlineTransform(ImagingOutline outline, double a[6])
{
    Edge *eIn;
    Edge *eOut;
    int i, n;
    int x0, y0, x1, y1;
    int X0, Y0, X1, Y1;

    double a0 = a[0]; double a1 = a[1]; double a2 = a[2];
    double a3 = a[3]; double a4 = a[4]; double a5 = a[5];

    eIn = outline->edges;
    n = outline->count;

    /* FIXME: ugly! */
    outline->edges = NULL;
    outline->count = outline->size = 0;

    eOut = allocate(outline, n);
    if (!eOut) {
        outline->edges = eIn;
        outline->count = outline->size = n;
        ImagingError_MemoryError();
        return -1;
    }

    for (i = 0; i < n; i++) {

        x0 = eIn->x0;
        y0 = eIn->y0;

        /* FIXME: ouch! */
        if (eIn->x0 == eIn->xmin)
            x1 = eIn->xmax;
        else
            x1 = eIn->xmin;
        if (eIn->y0 == eIn->ymin)
            y1 = eIn->ymax;
        else
            y1 = eIn->ymin;

        /* full moon tonight!  if this doesn't work, you may need to
           upgrade your compiler (make sure you have the right service
           pack) */

        X0 = (int) (a0*x0 + a1*y0 + a2);
        Y0 = (int) (a3*x0 + a4*y0 + a5);
        X1 = (int) (a0*x1 + a1*y1 + a2);
        Y1 = (int) (a3*x1 + a4*y1 + a5);

        add_edge(eOut, X0, Y0, X1, Y1);

        eIn++;
        eOut++;

    }

    free(eIn);

    return 0;
}

int
ImagingDrawOutline(Imaging im, ImagingOutline outline, const void* ink_,
                   int fill, int op)
{
    DRAW* draw;
    INT32 ink;

    DRAWINIT();

    draw->polygon(im, outline->count, outline->edges, ink, 0);

    return 0;
}

#endif
