/* libfloyd.c - generic Floyd-Steinberg error distribution routines for PBMPlus
**
** Copyright (C) 1994 Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de)
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation.  This software is provided "as is" without express or
** implied warranty.
*/

#include "ppmfloyd.h"

static void fs_adjust ARGS((ppm_fs_info *fi, int col));


ppm_fs_info *
#if __STDC__
ppm_fs_init(int cols, pixval maxval, int flags)
#else
ppm_fs_init(cols, maxval, flags)
    int cols;
    pixval maxval;
    int flags;
#endif
{
    ppm_fs_info *fi;
    int i;

    if( (fi = (ppm_fs_info *)malloc(sizeof(ppm_fs_info))) &&
        (fi->thisrederr   = (long *)malloc((cols+2) * sizeof(long))) &&
        (fi->thisgreenerr = (long *)malloc((cols+2) * sizeof(long))) &&
        (fi->thisblueerr  = (long *)malloc((cols+2) * sizeof(long))) &&
        (fi->nextrederr   = (long *)malloc((cols+2) * sizeof(long))) &&
        (fi->nextgreenerr = (long *)malloc((cols+2) * sizeof(long))) &&
        (fi->nextblueerr  = (long *)malloc((cols+2) * sizeof(long))) ) {

        fi->lefttoright = 1;
        fi->cols = cols;
        fi->maxval = maxval;
        fi->flags = flags;

        if( flags & FS_RANDOMINIT ) {
            srandom((int)(time(0) ^ getpid()));
            for( i = 0; i < cols +2; i++ ) {
                /* random errors in [-1..+1] */
                fi->thisrederr[i]   = random() % 32 - 16;
                fi->thisgreenerr[i] = random() % 32 - 16;
                fi->thisblueerr[i]  = random() % 32 - 16;
            }
        }
        else {
            for( i = 0; i < cols + 2; i++ )
                fi->thisrederr[i] = fi->thisgreenerr[i] = fi->thisblueerr[i] = 0;
        }
        return fi;
    }
    pm_error("out of memory allocating Floyd-Steinberg control structure");
}


void
ppm_fs_free(fi)
    ppm_fs_info *fi;
{
    if( fi ) {
        free(fi->thisrederr); free(fi->thisgreenerr); free(fi->thisblueerr);
        free(fi->nextrederr); free(fi->nextgreenerr); free(fi->nextblueerr);
        free(fi);
    }
}


int
ppm_fs_startrow(fi, pixrow)
    ppm_fs_info *fi;
    pixel *pixrow;
{
    register int col;

    if( !fi )
        return 0;

    fi->pixrow = pixrow;

    for( col = 0; col < fi->cols + 2; col++ )
        fi->nextrederr[col] = fi->nextgreenerr[col] = fi->nextblueerr[col] = 0;

    if( fi->lefttoright ) {
        fi->col_end = fi->cols;
        col = 0;
    }
    else {
        fi->col_end = -1;
        col = fi->cols - 1;
    }
    fs_adjust(fi, col);
    return col;
}


int
ppm_fs_next(fi, col)
    ppm_fs_info *fi;
    int col;
{
    if( !fi )
        ++col;
    else {
        if( fi->lefttoright )
            ++col;
        else
            --col;
        if( col == fi->col_end )
            col = fi->cols;
        else
            fs_adjust(fi, col);
    }
    return col;
}


void
ppm_fs_endrow(fi)
    ppm_fs_info *fi;
{
    long *tmp;

    if( fi ) {
        tmp = fi->thisrederr;   fi->thisrederr   = fi->nextrederr;   fi->nextrederr   = tmp;
        tmp = fi->thisgreenerr; fi->thisgreenerr = fi->nextgreenerr; fi->nextgreenerr = tmp;
        tmp = fi->thisblueerr;  fi->thisblueerr  = fi->nextblueerr;  fi->nextblueerr  = tmp;
        if( fi->flags & FS_ALTERNATE )
            fi->lefttoright = !(fi->lefttoright);
    }
}


void
ppm_fs_update(fi, col, pP)
    ppm_fs_info *fi;
    int col;
    pixel *pP;
{
    if( fi )
        ppm_fs_update3(fi, col, PPM_GETR(*pP), PPM_GETG(*pP), PPM_GETB(*pP));
}


void
#if __STDC__
ppm_fs_update3(ppm_fs_info *fi, int col, pixval r, pixval g, pixval b)
#else
ppm_fs_update3(fi, col, r, g, b)
    ppm_fs_info *fs;
    int col;
    pixval r, g, b;
#endif
{
    register long rerr, gerr, berr, err;
    int errcol = col+1;

    if( !fi )
        return;

    rerr = (long)(fi->red)   - r;
    gerr = (long)(fi->green) - g;
    berr = (long)(fi->blue)  - b;

    if( fi->lefttoright ) {
        register long two_err;

        two_err = 2*rerr;
        err = rerr;     fi->nextrederr[errcol+1] += err;    /* 1/16 */
        err += two_err; fi->nextrederr[errcol-1] += err;    /* 3/16 */
        err += two_err; fi->nextrederr[errcol  ] += err;    /* 5/16 */
        err += two_err; fi->thisrederr[errcol+1] += err;    /* 7/16 */

        two_err = 2*gerr;
        err = gerr;     fi->nextgreenerr[errcol+1] += err;    /* 1/16 */
        err += two_err; fi->nextgreenerr[errcol-1] += err;    /* 3/16 */
        err += two_err; fi->nextgreenerr[errcol  ] += err;    /* 5/16 */
        err += two_err; fi->thisgreenerr[errcol+1] += err;    /* 7/16 */

        two_err = 2*berr;
        err = berr;     fi->nextblueerr[errcol+1] += err;    /* 1/16 */
        err += two_err; fi->nextblueerr[errcol-1] += err;    /* 3/16 */
        err += two_err; fi->nextblueerr[errcol  ] += err;    /* 5/16 */
        err += two_err; fi->thisblueerr[errcol+1] += err;    /* 7/16 */
    }
    else {
        register long two_err;

        two_err = 2*rerr;
        err = rerr;     fi->nextrederr[errcol-1] += err;    /* 1/16 */
        err += two_err; fi->nextrederr[errcol+1] += err;    /* 3/16 */
        err += two_err; fi->nextrederr[errcol  ] += err;    /* 5/16 */
        err += two_err; fi->thisrederr[errcol-1] += err;    /* 7/16 */

        two_err = 2*gerr;
        err = gerr;     fi->nextgreenerr[errcol-1] += err;    /* 1/16 */
        err += two_err; fi->nextgreenerr[errcol+1] += err;    /* 3/16 */
        err += two_err; fi->nextgreenerr[errcol  ] += err;    /* 5/16 */
        err += two_err; fi->thisgreenerr[errcol-1] += err;    /* 7/16 */

        two_err = 2*berr;
        err = berr;     fi->nextblueerr[errcol-1] += err;    /* 1/16 */
        err += two_err; fi->nextblueerr[errcol+1] += err;    /* 3/16 */
        err += two_err; fi->nextblueerr[errcol  ] += err;    /* 5/16 */
        err += two_err; fi->thisblueerr[errcol-1] += err;    /* 7/16 */
    }
}


static void
fs_adjust(fi, col)
    ppm_fs_info *fi;
    int col;
{
    register long r, g, b;
    register pixel *pP;
    int errcol = col+1;
    pixval maxval = fi->maxval;

    pP = &(fi->pixrow[col]);

    /* Use Floyd-Steinberg errors to adjust actual color. */
    r = fi->thisrederr  [errcol]; if( r < 0 ) r -= 8; else r += 8; r /= 16;
    g = fi->thisgreenerr[errcol]; if( g < 0 ) g -= 8; else g += 8; g /= 16;
    b = fi->thisblueerr [errcol]; if( b < 0 ) b -= 8; else b += 8; b /= 16;

    r += PPM_GETR(*pP); if ( r < 0 ) r = 0; else if ( r > maxval ) r = maxval;
    g += PPM_GETG(*pP); if ( g < 0 ) g = 0; else if ( g > maxval ) g = maxval;
    b += PPM_GETB(*pP); if ( b < 0 ) b = 0; else if ( b > maxval ) b = maxval;

    PPM_ASSIGN(*pP, r, g, b);
    fi->red = r; fi->green = g; fi->blue = b;
}

