/*
 *  libpipi       Proper image processing implementation library
 *  Copyright (c) 2004-2008 Sam Hocevar <sam@zoy.org>
 *                2008 Jean-Yves Lamoureux <jylam@lnxscene.org
 *                All Rights Reserved
 *
 *  $Id$
 *
 *  This library is free software. It comes without any warranty, to
 *  the extent permitted by applicable law. You can redistribute it
 *  and/or modify it under the terms of the Do What The Fuck You Want
 *  To Public License, Version 2, as published by Sam Hocevar. See
 *  http://sam.zoy.org/wtfpl/COPYING for more details.
 */

/*
 * line.c: line rendering functions
 */

#include "config.h"
#include "common.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "pipi.h"
#include "pipi_internals.h"

struct line
{
    int x1, y1;
    int x2, y2;
    void (*draw) (pipi_image_t*, struct line*);
    union {
        uint32_t color32;
        float    colorf[3];
    };

    union {
        uint32_t *buf_u32;
        float    *buf_f;
    };

};
static void clip_line(pipi_image_t*, struct line*);
static uint8_t clip_bits(pipi_image_t*, int, int);
static void draw_aliased_line_u32(pipi_image_t*, struct line*);
static void draw_aliased_line_gray(pipi_image_t *img, struct line* s);
static void draw_aliased_line_float(pipi_image_t *img, struct line* s);
static void draw_antialiased_line_float(pipi_image_t *img, struct line* s);
static void draw_antialiased_line_gray(pipi_image_t *img, struct line* s);



int pipi_draw_line(pipi_image_t *img , int x1, int y1, int x2, int y2, uint32_t c, int aa)
{
    struct line s;
    s.x1 = x1;
    s.y1 = y1;
    s.x2 = x2;
    s.y2 = y2;

    /* No Transparency routine for u32 yet, fallback to float version */
    if(img->last_modified == PIPI_PIXELS_RGBA_C)
    {
        if(!aa)
        {
            uint32_t  *dstdata;
            dstdata = (uint32_t *)pipi_getpixels(img, PIPI_PIXELS_RGBA_C)->pixels;
            s.color32 = c;
            s.buf_u32 = dstdata;
            s.draw = draw_aliased_line_u32;
        }
        else
        {
            float  *dstdata;
            dstdata = (float *)pipi_getpixels(img, PIPI_PIXELS_RGBA_F)->pixels;
            s.colorf[2] = ((c&0x00FF0000)>>16)/255.0f; /* XXX FIXME */
            s.colorf[1] = ((c&0x0000FF00)>>8)/255.0f;  /* XXX FIXME */
            s.colorf[0] = (c&0x000000FF)/255.0f;       /* XXX FIXME */
            s.buf_f = dstdata;
            s.draw = draw_antialiased_line_float;
        }
    }
    else if(img->last_modified == PIPI_PIXELS_Y_F)
    {
        float  *dstdata;
        dstdata = (float *)pipi_getpixels(img, PIPI_PIXELS_Y_F)->pixels;
        s.colorf[0] = c/255.0f; /* XXX FIXME */
        s.buf_f = dstdata;
        s.draw = aa==0?draw_aliased_line_gray:draw_antialiased_line_gray;
    }
    else
    {
        float  *dstdata;
        dstdata = (float *)pipi_getpixels(img, PIPI_PIXELS_RGBA_F)->pixels;
        s.colorf[2] = ((c&0x00FF0000)>>16)/255.0f; /* XXX FIXME */
        s.colorf[1] = ((c&0x0000FF00)>>8)/255.0f;  /* XXX FIXME */
        s.colorf[0] = (c&0x000000FF)/255.0f;       /* XXX FIXME */
        s.buf_f = dstdata;
        s.draw = aa==0?draw_aliased_line_float:draw_antialiased_line_float;
    }

    clip_line(img, &s);
    return 0;
}


int pipi_draw_polyline(pipi_image_t *img, int const x[], int const y[],
                       int n, uint32_t c, int aa)
{
    int i;
    struct line s;

    if(img->last_modified == PIPI_PIXELS_RGBA_C)
    {
        if(!aa)
        {
            uint32_t  *dstdata;
            dstdata = (uint32_t *)pipi_getpixels(img, PIPI_PIXELS_RGBA_C)->pixels;
            s.color32 = c;
            s.buf_u32 = dstdata;
            s.draw = draw_aliased_line_u32;
        }
        else
        {
            float  *dstdata;
            dstdata = (float *)pipi_getpixels(img, PIPI_PIXELS_RGBA_F)->pixels;
            s.colorf[2] = ((c&0x00FF0000)>>16)/255.0f; /* XXX FIXME */
            s.colorf[1] = ((c&0x0000FF00)>>8)/255.0f;  /* XXX FIXME */
            s.colorf[0] = (c&0x000000FF)/255.0f;       /* XXX FIXME */
            s.buf_f = dstdata;
            s.draw = draw_antialiased_line_float;
        }
    }
    else if(img->last_modified == PIPI_PIXELS_Y_F)
    {
        float  *dstdata;
        dstdata = (float *)pipi_getpixels(img, PIPI_PIXELS_Y_F)->pixels;
        s.colorf[0] = c/255.0f; /* XXX FIXME */
        s.buf_f = dstdata;
        s.draw = aa==0?draw_aliased_line_gray:draw_antialiased_line_gray;
    }
    else
    {
        float  *dstdata;
        dstdata = (float *)pipi_getpixels(img, PIPI_PIXELS_RGBA_F)->pixels;
        s.colorf[0] = (c&0x00FF0000)/255.0f; /* XXX FIXME */
        s.colorf[1] = (c&0x0000FF00)/255.0f; /* XXX FIXME */
        s.colorf[2] = (c&0x000000FF)/255.0f; /* XXX FIXME */
        s.buf_f = dstdata;
        s.draw = aa==0?draw_aliased_line_float:draw_antialiased_line_float;
        img->last_modified = PIPI_PIXELS_RGBA_F;
    }

    for(i = 0; i < n; i++)
    {
        s.x1 = x[i];
        s.y1 = y[i];
        s.x2 = x[i+1];
        s.y2 = y[i+1];
        clip_line(img, &s);
    }
    return 0;
}





/*
 * XXX: The following functions are local.
 */
/* Generic Cohen-Sutherland line clipping function. */
static void clip_line(pipi_image_t *img, struct line* s)
{
    uint8_t bits1, bits2;

    bits1 = clip_bits(img, s->x1, s->y1);
    bits2 = clip_bits(img, s->x2, s->y2);

    if(bits1 & bits2)
        return;

    if(bits1 == 0)
    {
        if(bits2 == 0)
            s->draw(img, s);
        else
        {
            int tmp;
            tmp = s->x1; s->x1 = s->x2; s->x2 = tmp;
            tmp = s->y1; s->y1 = s->y2; s->y2 = tmp;
            clip_line(img, s);
        }
        return;
    }

    if(bits1 & (1<<0))
    {
        s->y1 = s->y2 - (s->x2 - 0) * (s->y2 - s->y1) / (s->x2 - s->x1);
        s->x1 = 0;
    }
    else if(bits1 & (1<<1))
    {
        int xmax = img->w - 1;
        s->y1 = s->y2 - (s->x2 - xmax) * (s->y2 - s->y1) / (s->x2 - s->x1);
        s->x1 = xmax;
    }
    else if(bits1 & (1<<2))
    {
        s->x1 = s->x2 - (s->y2 - 0) * (s->x2 - s->x1) / (s->y2 - s->y1);
        s->y1 = 0;
    }
    else if(bits1 & (1<<3))
    {
        int ymax = img->h - 1;
        s->x1 = s->x2 - (s->y2 - ymax) * (s->x2 - s->x1) / (s->y2 - s->y1);
        s->y1 = ymax;
    }

    clip_line(img, s);
}

/* Helper function for clip_line(). */
static uint8_t clip_bits(pipi_image_t *img, int x, int y)
{
    uint8_t b = 0;

    if(x < 0)
        b |= (1<<0);
    else if(x >= (int)img->w)
        b |= (1<<1);

    if(y < 0)
        b |= (1<<2);
    else if(y >= (int)img->h)
        b |= (1<<3);

    return b;
}



/* Solid line drawing function, using Bresenham's mid-point line
 * scan-conversion algorithm. */
static void draw_aliased_line_u32(pipi_image_t *img, struct line* s)
{
#undef  ASSIGN
#define ASSIGN(x, y, w) s->buf_u32[x+y*w] = s->color32;
#include "line_template.h"
}
static void draw_aliased_line_float(pipi_image_t *img, struct line* s)
{
#undef  ASSIGN
#define ASSIGN(x, y, w) s->buf_f[(x*4)+y*(w*4)]     = s->colorf[0]; \
                         s->buf_f[1 + (x*4)+y*(w*4)] = s->colorf[1]; \
                         s->buf_f[2 + (x*4)+y*(w*4)] = s->colorf[2];
#include "line_template.h"
}
static void draw_aliased_line_gray(pipi_image_t *img, struct line* s)
{
#undef  ASSIGN
#define ASSIGN(x, y, w) s->buf_f[x+y*w]     = s->colorf[0];
#include "line_template.h"
}

/* Xiaolin Wu's line algorithm, as seen at http://portal.acm.org/citation.cfm?id=122734 */

/* math.h doesn't like y0 (sucker) */
float floorf(float x);
float truncf(float x);
float fabsf(float x);
static float fractf(float d) { return (d - floorf(d)); }
static float fractinvf(float d) { return (1 - (d - floorf(d))); }

static void draw_antialiased_line_float(pipi_image_t *img, struct line* s)
{
/* Is that an horrible mess ? Yes, it is. */
#undef  PLOT
#define PLOT(x, y, c)  \
    s->buf_f[(((int)(x)*4))+((int)(y))*(img->w*4)] = \
        (c*s->colorf[0]) + (1-c) * s->buf_f[(((int)(x)*4))+((int)(y))*(img->w*4)]; \
    s->buf_f[(1+((int)(x)*4))+((int)(y))*(img->w*4)] = \
        (c*s->colorf[1]) + (1-c) * s->buf_f[(1+((int)(x)*4))+((int)(y))*(img->w*4)]; \
    s->buf_f[(2+((int)(x)*4))+((int)(y))*(img->w*4)] = \
        (c*s->colorf[2]) + (1-c) * s->buf_f[(2+((int)(x)*4))+((int)(y))*(img->w*4)];
#include "aline_template.h"
}


static void draw_antialiased_line_gray(pipi_image_t *img, struct line* s)
{
#undef  PLOT
#define PLOT(x, y, c) s->buf_f[((int)(x))+((int)(y))*img->w] =  \
    (c*s->colorf[0]) + (1-c) * s->buf_f[((int)(x))+((int)(y))*img->w];
#include "aline_template.h"
}