/*
 * This software is copyrighted as noted below.  It may be freely copied,
 * modified, and redistributed, provided that the copyright notice is 
 * preserved on all copies.
 * 
 * There is no warranty or other guarantee of fitness for this software,
 * it is provided solely "as is".  Bug reports or fixes may be sent
 * to the author, who may or may not act on them as he desires.
 *
 * You may not include this software in a program or other software product
 * without supplying the source, or without informing the end-user that the 
 * source is available for no extra charge.
 *
 * If you modify this software, you should include a notice giving the
 * name of the person performing the modification, the date of modification,
 * and the reason for such modification.
 */
/* 
 * rledither.c - Do Floyd Steinberg dithering on an image.  Edge enhancement
 *    can be tuned with command line argument.
 * 
 * Author:	Rod Bogart 
 * 		University of Michigan
 * Date:	Fri Dec 1 1989
 * Copyright (c) 1989, The Regents of the University of Michigan
 * 
 *   -e edge_factor     Zero means no edge enhancement, 1.0 looks pretty good.
 *   -l nchan length    Number of channels in the colormap, and num entries.
 *			Defaults to 3 channels of 256 entries each.
 *   -{tf} mapfile      -t is a colormap listed as  R G B  R G B  R G B ...
 *                      -f is listed as R R R... G G G... B B B...
 *   -o outfile         Output RLE file (stdout default)
 *   infile             Input RLE file (stdin default)
 */
#ifndef lint
static char rcs_ident[] = "$Id: rledither.c,v 3.0.1.1 1992/01/23 16:42:22 spencer Exp $";
#endif

#include <stdio.h>
#include "rle.h"

#define MALLOC_ERR RLE_CHECK_ALLOC( progname, 0, 0 )

rle_map **allocmap();
void filemap(), copymap(), shiftmap(), copy_into_shortrow(), edge_enhance();
void fsdither(), find_closest();

char *progname;

void
main(argc, argv)
int	argc;
char	*argv[];
{
    int i, temp, y;
    int oflag = 0;
    int blend, blend_divisor = 256;
    float edge_factor = 0.0;
    int top, middle, bottom, edgetop, edgebottom;
    rle_hdr 	in_hdr, out_hdr;
    rle_pixel **cleanrows[3], **edgerows[2], **outrow;
    short *shortrows[2];
    char *infname = NULL, *outfname = NULL, *mapfname = NULL;
    FILE *outfile = stdout;
    int nchan = 3, length = 256, tflag = 0;
    rle_map **amap, **map;
    int rle_cnt, rle_err;
    char cmap_comment[80];

    progname = cmd_name( argv );
    in_hdr = *rle_hdr_init( NULL );
    out_hdr = *rle_hdr_init( NULL );


    if ( scanargs( argc, argv,
		  "% e%-edge_factor!f l%-nchan!dlength!d \n\
\ttf!-mapfile!s o%-outfile!s infile%s",
		  &i, &edge_factor, &i, &nchan, &length,
		  &tflag, &mapfname,
		  &oflag, &outfname, &infname ) == 0 )
	exit( 1 );

    in_hdr.rle_file = rle_open_f( progname, infname, "r" );
    rle_names( &in_hdr, progname, infname, 0 );
    rle_names( &out_hdr, progname, outfname, 0 );

    /* Allocate space for the applied map */
    amap = allocmap( nchan, 256, NULL );
    map = allocmap( nchan, 256, NULL );
    filemap( tflag, mapfname, nchan, length, amap );
    copymap( amap, nchan, 256, map );
    shiftmap( amap, nchan, 256, 8 );

    for ( rle_cnt = 0;
	  (rle_err = rle_get_setup( &in_hdr )) == RLE_SUCCESS;
	  rle_cnt++ )
    {
	(void)rle_hdr_cp( &in_hdr, &out_hdr );
	if ( rle_cnt == 0 )
	    outfile = rle_open_f( cmd_name( argv ), outfname, "w" );
	out_hdr.rle_file = outfile;

	rle_addhist( argv, &in_hdr, &out_hdr );

	/* alpha on dithered stuff is too weird... */
	out_hdr.alpha = 0;
	out_hdr.ncolors = 1;	/* Just one chan, the cmap index */
	for (i = 1; i < in_hdr.ncolors; i++)
	    RLE_CLR_BIT( out_hdr, i );	/* Kill extra output channels */
	out_hdr.ncmap = nchan;
	out_hdr.cmaplen = 8;	/* == 256 entries */
	out_hdr.cmap = (rle_map *) amap[0];
	sprintf( cmap_comment, "color_map_length=%d", length );
	rle_putcom( cmap_comment, &out_hdr );
	rle_put_setup( &out_hdr );

	in_hdr.xmax -= in_hdr.xmin;
	in_hdr.xmin = 0;

	/* Oink. */
	for (i = 0; i < 3; i++)
	    if (rle_row_alloc( &in_hdr, &cleanrows[i] ) < 0)
		MALLOC_ERR;

	if (rle_row_alloc( &in_hdr, &edgerows[0] ) < 0)
	    MALLOC_ERR;

	if (rle_row_alloc( &in_hdr, &edgerows[1] ) < 0)
	    MALLOC_ERR;

	if (rle_row_alloc( &out_hdr, &outrow ) < 0)
	    MALLOC_ERR;

	if ( (shortrows[0] = (short *)malloc(in_hdr.ncolors
					     * (in_hdr.xmax+1)
					     * sizeof(short) )) == 0 )
	    MALLOC_ERR;
	if ( (shortrows[1] = (short *)malloc(in_hdr.ncolors
					     * (in_hdr.xmax+1)
					     * sizeof(short) )) == 0 )
	    MALLOC_ERR;

	if (edge_factor < 0.0)
	    blend = 0;
	else
	    blend = (int) (edge_factor * (float) blend_divisor);

	rle_getrow(&in_hdr, cleanrows[2]);
	rle_getrow(&in_hdr, cleanrows[1]);
	bottom = 0;
	middle = 1;
	top = 2;
	edgetop = 1;
	edgebottom = 0;
	/* just copy the top row, it gets no edge enhancement */
	copy_into_shortrow( &in_hdr, 
			    cleanrows[top], shortrows[edgetop] );

	for (y = in_hdr.ymin+2; y <= in_hdr.ymax; y++)
	{
	    rle_getrow(&in_hdr, cleanrows[bottom]);
	    edge_enhance(&in_hdr, 
			 cleanrows[bottom], cleanrows[middle], cleanrows[top],
			 edgerows[edgebottom],
			 blend, blend_divisor);
	    copy_into_shortrow( &in_hdr, 
				edgerows[edgebottom], shortrows[edgebottom] );
	    fsdither(&in_hdr, map, length,
		     shortrows[edgebottom], shortrows[edgetop], outrow);
	    rle_putrow( outrow, in_hdr.xmax+1, &out_hdr);
	    /* swap the row pointers */
	    temp = top;	top = middle;	middle = bottom;	bottom = temp;
	    temp = edgetop;	edgetop = edgebottom;	edgebottom = temp;
	}
	fsdither(&in_hdr, map, length,
		 shortrows[edgebottom], shortrows[edgetop], outrow);
	rle_putrow( outrow, in_hdr.xmax+1, &out_hdr);
	/* just copy the middle row, it gets no edge enhancement */
	copy_into_shortrow( &in_hdr, 
			    cleanrows[middle], shortrows[edgetop] );
	fsdither(&in_hdr, map, length, NULL, shortrows[edgetop], outrow);
	rle_putrow( outrow, in_hdr.xmax+1, &out_hdr);

	rle_puteof( &out_hdr );

	/* Release storage. */
	for (i = 0; i < 3; i++)
	    rle_row_free( &in_hdr, cleanrows[i] );
	rle_row_free( &in_hdr, edgerows[0] );
	rle_row_free( &in_hdr, edgerows[1] );
	rle_row_free( &out_hdr, outrow );
	free( shortrows[0] );
	free( shortrows[1] );
    }

    /* Check for an error.  EOF or EMPTY is ok if at least one image
     * has been read.  Otherwise, print an error message.
     */
    if ( rle_cnt == 0 || (rle_err != RLE_EOF && rle_err != RLE_EMPTY) )
	rle_get_error( rle_err, cmd_name( argv ), infname );

    exit( 0 );
}

void
copy_into_shortrow( in_hdr, rlerow, shortrow )
rle_hdr *in_hdr;
rle_pixel **rlerow;
short *shortrow;
{
    int chan,i;

    for (i=0; i <= in_hdr->xmax; i++)
    {
	for (chan = 0; chan < in_hdr->ncolors; chan++)
	{
	    *shortrow++ = rlerow[chan][i];
	}
    }
}

void
edge_enhance(in_hdr, row_b, row_m, row_t, edge_row, blend, blend_divisor)
rle_hdr *in_hdr;
rle_pixel **row_b, **row_m, **row_t, **edge_row;
int blend_divisor, blend;
{
    int chan, i;
    int total, diff, avg, result;
    for (chan = 0; chan < in_hdr->ncolors; chan++)
    {
	/* i = 0 case, ignore left column of pixels */
	total = row_b[chan][0] + row_b[chan][1] + 
	                         row_m[chan][1] + 
		row_t[chan][0] + row_t[chan][1];
	avg = total / 5;
	diff = avg - row_m[chan][0];
	result = row_m[chan][0] - (diff * blend / blend_divisor);
	if (result < 0)
	    edge_row[chan][0] = 0;
	else if (result > 255)
	    edge_row[chan][0] = 255;
	else
	    edge_row[chan][0] = result;
	
	/* all the nice cases */
	for (i=1; i < in_hdr->xmax; i++)
	{
	    total = row_b[chan][i-1] + row_b[chan][i] + row_b[chan][i+1] + 
		    row_m[chan][i-1]                  + row_m[chan][i+1] + 
		    row_t[chan][i-1] + row_t[chan][i] + row_t[chan][i+1];
	    avg = total >> 3;  /* divide by 8 */
	    diff = avg - row_m[chan][i];
	    result = row_m[chan][i] - (diff * blend / blend_divisor);
	    if (result < 0)
		edge_row[chan][i] = 0;
	    else if (result > 255)
		edge_row[chan][i] = 255;
	    else
		edge_row[chan][i] = result;
	}
	/* i = in_hdr.xmax case, ignore right column of pixels */
	i = in_hdr->xmax;
	total = row_b[chan][i-1] + row_b[chan][i] +  
	        row_m[chan][i-1]                  + 
		row_t[chan][i-1] + row_t[chan][i];
	avg = total / 5;
	diff = avg - row_m[chan][i];
	result = row_m[chan][i] - (diff * blend / blend_divisor);
	if (result < 0)
	    edge_row[chan][i] = 0;
	else if (result > 255)
	    edge_row[chan][i] = 255;
	else
	    edge_row[chan][i] = result;
    }
}

void
fsdither(in_hdr, map, maplen, row_bottom, row_top, outrow)
rle_hdr *in_hdr;
rle_map **map;
int maplen;
short *row_bottom, *row_top;
rle_pixel **outrow;
{
    register rle_pixel *optr;
    register int j;
    register short *thisptr, *nextptr = NULL;
    int chan;
    static int numchan = 0;
    int	lastline = 0, lastpixel ;
    static int *cval=0;
    static rle_pixel *pixel=0;

    if ( numchan != in_hdr->ncolors )
	if ( cval )
	{
	    free( cval );
	    free( pixel );
	}

    numchan = in_hdr->ncolors;
    if (!cval) {
	if ((cval = (int *) malloc( numchan * sizeof(int) )) == 0)
	    MALLOC_ERR;
	if ((pixel = (rle_pixel *) malloc( numchan * sizeof(rle_pixel) )) == 0)
	    MALLOC_ERR;
    }
    optr = outrow[RLE_RED];

    thisptr = row_top;
    if (row_bottom) 
	nextptr = row_bottom;
    else
	lastline = 1;

    for(j=0; j <= in_hdr->xmax ; j++)
    {
	int cmap_index=0;

	lastpixel = (j == in_hdr->xmax) ;

	for (chan = 0; chan < numchan; chan++)
	{
	    cval[chan] = *thisptr++ ;

	    /* Current channel value has been accumulating error, it could be
	     * out of range.
	     */
	    if( cval[chan] < 0 ) cval[chan] = 0 ;
	    else if( cval[chan] > 255 ) cval[chan] = 255 ;

	    pixel[chan] = cval[chan];
	}

	/* find closest color */
	find_closest(map, numchan, maplen, pixel, &cmap_index);
	*optr++ = cmap_index;
	    
	/* thisptr is now looking at pixel to the right of current pixel
	 * nextptr is looking at pixel below current pixel
	 * So, increment thisptr as stuff gets stored.  nextptr gets moved
	 * by one, and indexing is done +/- numchan.
	 */
	for (chan = 0; chan < numchan; chan++)
	{
	    cval[chan] -= map[chan][cmap_index];

	    if( !lastpixel )
	    {
		thisptr[chan] += cval[chan] * 7 / 16 ;
	    }
	    if( !lastline )
	    {
		if( j != 0 )
		{
		    nextptr[-numchan] += cval[chan] * 3 / 16 ;
		}
		nextptr[0] += cval[chan] * 5 / 16 ;
		if( !lastpixel )
		{
		    nextptr[numchan] += cval[chan] / 16 ;
		}
		nextptr ++;
	    }
	}
    }
}

/*****************************************************************
 * TAG( filemap )
 * 
 * NOTE: this code is stolen from rleldmap.
 * Read a color map from a file
 * Inputs:
 * 	tflag:	    	Flag for type of file: 1 means all entries for a
 *  	    	    	channel are together (-f), 2 means all entries for
 *  	    	    	a given index are together (-t).
 *  	mapfname:   	Name of file to read map from.
 *	nchan:		Number of color channels.
 *	length:		Length of each channel.
 * Outputs:
 * 	amap:		Result map.
 * Assumptions:
 *	[None]
 * Algorithm:
 *	[None]
 */
void
filemap( tflag, mapfname, nchan, length, amap )
int tflag, nchan, length;
char *mapfname;
rle_map **amap;
{
    FILE * mapfile;
    register int c, i;
    int ent;

    if ( ( mapfile = fopen( mapfname, "r" ) ) == NULL )
    {
	fprintf( stderr, "%s: Couldn't open map file %s: ",
		 progname, mapfname );
	perror("");
	exit(-1);
    }

    if ( tflag == 1 )		/* channel-major order */
	for ( c = 0; c < nchan; c++ )
	    for ( i = 0; i < length; i++ )
		switch ( fscanf( mapfile, "%d", &ent ) )
		{
		case EOF:	/* EOF */
		    fprintf( stderr,
	"%s: Premature end of file reading map %s at channel %d, entry %d\n",
			     progname, mapfname, c, i );
		    exit(-1);
		    /* NOTREACHED */
		case 1:		/* Got it */
		    amap[c][i] = ent;
		    break;
		case 0:		/* no conversion? */
		    fprintf( stderr,
			    "%s: Bad data in map %s at channel %d, entry %d\n",
			     progname, mapfname, c, i );
		    exit(-1);
		    /* NOTREACHED */
		default:	/* error */
		    fprintf( stderr,
		     "%s: Error reading map %s at channel %d, entry %d\n",
			     progname, mapfname, c, i );
		    exit(-1);
		    /* NOTREACHED */
		}
    else			/* Entry-major order */
	for ( i = 0; i < length; i++ )
	    for ( c = 0; c < nchan; c++ )
		switch ( fscanf( mapfile, "%d", &ent ) )
		{
		case EOF:	/* EOF */
		    fprintf( stderr, 
	"%s: Premature end of file reading map %s at entry %d, channel %d\n",
			     progname, mapfname, i, c );
		    exit(-1);
		    /* NOTREACHED */
		case 1:		/* Got it */
		    amap[c][i] = ent;
		    break;
		case 0:		/* no conversion? */
		    fprintf( stderr,
			    "%s: Bad data in map %s at entry %d, channel %d\n",
			     progname, mapfname, i, c );
		    exit(-1);
		    /* NOTREACHED */
		default:	/* error */
		    fprintf( stderr,
		    "%s: Error reading map %s at entry %d, channel %d: ",
			     progname, mapfname, i, c );
		    perror("");
		    exit(-1);
		    /* NOTREACHED */
		}
    fclose( mapfile );
}

/*****************************************************************
 * TAG( allocmap )
 * 
 * Allocate a color map of a given size.
 * Inputs:
 * 	nchan:		Number of channels in the map.
 *	length:		Length of each channel of the map.
 *	cmap:		If non-null, the storage to be used.
 * Outputs:
 * 	Returns a pointer to an array[nchan] of pointers to
 *	array[length] of rle_map.  The rle_map array can also
 *  	be addressed contiguously as return[0][chan*length+i].
 * Assumptions:
 *	If cmap is supplied, it is an array of nchan*length rle_maps.
 * Algorithm:
 *	[None]
 */
rle_map **
allocmap( nchan, length, cmap )
int nchan;
int length;
rle_map * cmap;
{
    rle_map ** map;
    register int i;

    map = (rle_map **)malloc( nchan * sizeof( rle_map * ) );
    RLE_CHECK_ALLOC( progname, map, 0 );
    if ( cmap == NULL )
    {
	map[0] = (rle_map *)malloc( nchan * length * sizeof( rle_map ) );
	RLE_CHECK_ALLOC( progname, map[0], 0 );
    }
    else
	map[0] = cmap;
    for ( i = 1; i < nchan; i++ )
	map[i] = &map[i-1][length];
    return map;
}

/*****************************************************************
 * TAG( shiftmap )
 * 
 * Shift the entries in the color map to left justify them.
 * Inputs:
 * 	map:		The color map.
 *	nchan:		Number of color channels in the map.
 *	length:		Number of entries in each channel.
 *	bits:		Number of bits in each entry.
 * Outputs:
 * 	map:		Left justified map (modified in place).
 * Assumptions:
 * 	map[0] points to a contiguous array of nchan*length rle_maps
 *  	(as set up by allocmap).
 * Algorithm:
 *	[None]
 */
void
shiftmap( map, nchan, length, bits )
int nchan, length, bits;
rle_map **map;
{
    register rle_map * e;
    register int i;
    
    bits = 16 - bits;
    if ( bits == 0 )
	return;			/* no work! */

    for ( i = nchan * length, e = map[0]; i > 0; i--, e++ )
	*e <<= bits;
}

void
copymap( inmap, nchan, length, outmap )
rle_map **inmap, **outmap;
int nchan, length;
{
    register rle_map *ie, *oe;
    register int i;
    
    for ( i = nchan * length, ie = inmap[0], oe = outmap[0]; 
	  i > 0; i--, ie++, oe++ )
	*oe = *ie;
}

void
find_closest(map, nchan, maplen, pixel, index)
rle_map ** map;
int nchan, maplen;
rle_pixel *pixel;
int *index;
{
    int i, closest, chan;
    long bestdist, dist;

    closest = -1;
    bestdist = 256*256*nchan + 1;
    for (i=0; i < maplen; i++)
    {
	dist = 0L;
	for (chan = 0; chan < nchan; chan++)
	{
	    int tmp = map[chan][i] - pixel[chan];
	    dist += tmp * tmp;
	}
	if (dist < bestdist)
	{
	    closest = i;
	    bestdist = dist;
	}
    }
    *index = closest;
}
