/*
 * 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.
 */
/* 
 * rletopaint.c - Convert an RLE file to macpaint format, using dithering.
 * 
 * Author:	John W. Peterson
 * 		Computer Science Dept.
 * 		University of Utah
 * Date:	Thu Oct 16 1986
 * Copyright (c) 1986, John W. Peterson
 *
 * Byte compression code (compress_line) by Jim Schimpf.
 *
 * Usage is:
 *   rle2paint [-l] [-i] [-g [gamma]] [-o outfile.paint] [infile.rle] 
 *
 * -l		Uses a linear color map in converting the RLE image
 * -r		Invert the bits in the MacPaint output.
 * -g [gamma]	Use a gamma map of [gamma] in converting the output
 *		(default is 2.0)
 * infile.rle	If specified, the input RLE file.  Default stdin.
 * -o outfile	If specified, the MacPaint file will be written here.
 *		Default stdout.
 *
 * Note that the image should be flipped vertically (by rleflip, e.g.),
 * as the two formats number their scanlines oppositely.
 */
static char rcsid[] = "$Header: /tmp_mnt/n/itn/hendrix/u/spencer/RCS/rletopaint.c,v 3.0.1.3 1992/04/30 14:00:05 spencer Exp spencer $";
/*
rletopaint()				Tag the file.
*/


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

#define MIN(x,y) ( (x) < (y) ? (x) : (y) )

/* Scanline storage & RLE row pointers */
rle_hdr hdr;
rle_pixel ** rows;

int gammamap[256];
unsigned char rawbits[73];	/* Uncompressed MacPaint pixels */
unsigned char squishedbits[74]; /* Compressed data */
int inverse_flag = 0;		/* If true then invert image */

/* Gray scale dither table */
int dmgray[8][8] = {   0 ,128 , 32 ,160 ,  8 ,136 , 40 ,168 ,
                 192 , 64 ,224 , 96 ,200 , 72 ,232 ,104 ,
                  48 ,176 , 16 ,144 , 56 ,184 , 24 ,152 ,
                 240 ,112 ,208 , 80 ,248 ,120 ,216 , 88 ,
                  12 ,140 , 44 ,172 ,  4 ,132 , 36 ,164 ,
                 204 , 76 ,236 ,108 ,196 , 68 ,228 ,100 ,
                  60 ,188 , 28 ,156 , 52 ,220 , 20 ,148 ,
                 252 ,124 ,220 , 92 ,244 ,116 ,212 ,84   } ;

int compress_line();
void write_paint_line(), bytes_to_bits();

void
main(argc, argv)
int argc;
char *argv[];
{
    int i;
    int databytes;
    float gam = 2.0;		/* Default gamma */
    int gamma_flag = 0, oflag = 0;
    int linear_flag = 1;
    char * fname = NULL, * out_fname = NULL;
    FILE * out_fp;

    if ( scanargs( argc, argv, "% r%- l%- g%-gamma%f o%-outfile!s infile%s",
		  &inverse_flag, &linear_flag,
		  &gamma_flag, &gam, &oflag, &out_fname, &fname ) == 0)
	exit(-1);

    if (gamma_flag) linear_flag = 0;
    /* Initialize header. */
    hdr = *rle_hdr_init( (rle_hdr *)NULL );
    rle_names( &hdr, cmd_name( argv ), fname, 0 );

    hdr.rle_file = rle_open_f(hdr.cmd, fname, "r");
    rle_get_setup_ok( &hdr, NULL, NULL );

    out_fp = rle_open_f(hdr.cmd, out_fname, "w");
    
    /* set up rows to point to our copy of the scanline */
    if (rle_row_alloc( &hdr, &rows ))
    {
	fprintf(stderr,"%s: malloc failed\n", hdr.cmd);
	exit(-2);
    }

    /* Compute a gamma correction map and tables */
    for ( i = 0; i < 256; i++ )
    {
	if (linear_flag)
	    gammamap[i] = i;
	else
	    gammamap[i] = (int)(0.5 + 255 * pow( i / 255.0, 1.0/gam ));
    }

    /* Output MacPaint header */
    for (i = 0; i < 512; i++)
	putc('\0', out_fp);	 /* Null tells MacPaint to ignore header */

    for (i = 0; i < 72; i++)	/* Kludge - pad bottom with blank lines */
	rawbits[i] = 0;

    /* If the picture doesn't start at the bottom, output blank lines
     * to fill the space */

    databytes = compress_line(); 	/* Blank compressed data */
    for (i = 0; i < hdr.ymin; i++)
	write_paint_line(databytes, out_fp);

    for (i = hdr.ymin; i <= MIN( hdr.ymax, 719 ); i++)
    {
	rle_getrow( &hdr, rows );
        bytes_to_bits( i );
	write_paint_line(compress_line(), out_fp);
    }

    /* Finish up unused picture space */
    if (hdr.ymax < 719)
    {
	for (i = 0; i < 72; i++) /* Kludge - pad bottom with blank lines */
	rawbits[i] = 0;
	
	databytes = compress_line(); 	/* Blank compressed data */
	for (i = hdr.ymax+1; i < 720; i++)
	    write_paint_line(databytes, out_fp);
    }
}
	
    
/* Write a scanline to the macpaint file. */
void
write_paint_line(num, fp)
int num;
FILE *fp;
{
    int j;
    for (j = 0; j < num; j++)
	putc(squishedbits[j], fp);
}

/*
 * Map a 24 bit scanline through to get dithered black and white, generating
 * a row of bits in rawbits.
 */
void
bytes_to_bits( y )
int y;
{
    register unsigned char *r, *g, *b;
    register int i, col, row;
    int pixel;
    int n = MIN(hdr.xmax, 576);
    int B = 0, W = 1;

    if (inverse_flag)
    {
	B = 1; W = 0;		/* Swap meaning of Black and White */
    }
    for (i = 0; i < 72; i++)
	rawbits[i] = 0;

    r = g = b = rows[0];
    if ( hdr.ncolors > 1 )
	g = rows[1];
    if ( hdr.ncolors > 2 )
	b = rows[2];
    for ( row = y % 8, col = 0, i = 0;
	  i < n;
	  i++, r++, g++, b++, col = ((col + 1) & 7) )
    {
	/* Conver to BW (uses YIQ/percentage xformation) */
	if ( hdr.ncolors > 2 )
	    pixel = (35*gammamap[*r] + 55*gammamap[*g] + 10*gammamap[*b]) / 100;
	else if ( hdr.ncolors == 2 )
	    pixel = (gammamap[*r] + gammamap[*g]) / 2;
	else
	    pixel = gammamap[*r];
	if (pixel < 0) pixel += 256;

	/* Use dither to convert to zero/one */
	pixel = ((pixel > dmgray[col][row]) ? W : B);

	/* Compress scanline bytes to unencoded macpaint bits */
	rawbits[(i / 8)] |= pixel << (7-(i % 8));
    }
}
    
/*
 * Compress the bits in rawbits to run-length encoded bytes in squishedbits.
 *
 * Compression is as follows:
 *
 * <CNT><Byte><Byte>...    If <CNT>  < 0x80  i.e. <CNT> different bytes
 * <CNT><Byte>             If <CNT>  > 0x80  i.e. <CNT> repeated bytes
 *
 */

int
compress_line()
{
    int i,j,k = 0,cntpsn,count = 0;
    int flag;
    unsigned char pixel;
#define SAME 1
#define NOTSAME 0
                      
    i = 0;
    j = 2;
    if( rawbits[0] == rawbits[1] )
    {
	flag = SAME;
	cntpsn = 0;
	squishedbits[1] = rawbits[0];
    }
    else
    {
	flag = NOTSAME;
	cntpsn = 0;
	squishedbits[1] = rawbits[0];
    }

    while( i < 72 )
    {
	switch( flag )
	{
                                
	    /*  Same case see how far the run goes then update stuff */

	case SAME:
	    count = 0;
	    for( k=i; k<72; k++ )
	    {
		if( squishedbits[j-1] != rawbits[k] )
		    break;
		else
		    count++;
	    }

	    /* If count is zero then just skip all this stuff and try again
	     * with the flag the other way
	     */
	    if( count != 0 )
	    {

		/* Ok update the count and save the byte in the output line
		 * NOTE: Count is just 2's compliment of the value
		 */
		pixel = -1 * (count-1);
		squishedbits[cntpsn] = 0xff & pixel;

		/*  Set the flag for the other & advance j to next frame */
                       
		flag = NOTSAME;
	    }
	    else
		flag = NOTSAME;
	    break;
                                
	    /*  Not the same, look for a run of something if found quit */

	case NOTSAME:
	    count = 0;
	    for( k=i+1; k<72; k++ )
	    {
		if( squishedbits[j-1] == rawbits[k] )
		    break;
		else
		{
		    count++;
		    squishedbits[j++] = rawbits[k];
		}
	    }
	    /* If count is zero then skip all the updating stuff and just try
	     * the other method
	     */
	    if( count != 0 )
	    {
		if ( k == 72 )
		    squishedbits[cntpsn] = count;
		else
		{
		    squishedbits[cntpsn] = count - 1;

		    /* Set the flag for the other and back up the psn
		     * to get the start of the run.
		     */
		    
		    k = k - 1;
		    j--;
		    flag = SAME;
		}
	    }
	    else
		flag = SAME;
	    break;
	}


	/* End of loop update the positions of the count save lcn and
	 * next character to look at 
	 *   
	 * Only do update on non zero counts 
	 */
                                             
	if( count != 0 )
	{
	    /* Sometimes 'compression' doesn't.  Check for this. */
	    if ( j > k + 1 )
	    {
		squishedbits[0] = k - 1;
		for ( j = 0; j < k; j++ )
		    squishedbits[j + 1] = rawbits[j];
		j++;
	    }
	    cntpsn = j;
	    squishedbits[++j] = rawbits[k];
	    j++;
	    i = k;
	}
    }

    return( j-2 );
}
