/*
 * 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.
 */
/* 
 * rletoraw.c - Convert the RLE format to the kitchen sink.
 * 
 * Author:	Martin R. Friedmann
 * 		Vision and Modeling Group/Media Lab
 * 		Massachusetts Institute of Technology
 * Date:	Thu Sep 13 1990
 * Copyright (c) 1990, Martin R. Friedmann
 *
 * usage : rletoraw [-{Ns}] [-r] [-w width] [-h height] [-f header-size]
 *                   [-t tailer-size] [-n nchannels] [-a]
 *                   [-p scanline-pad] [-l left-scanline-pad] [-o outfile]
 *                   [infile]
 *
 * -a dont strip the alpha channel from the rle file
 * -s output data in scanline interleaved order
 * -N output data in non-interleaved order (eg. | split -Wid*Height -)
 * -r reverse the channel order (e.g. write data as ABGR instead of
 *    the default RGBA order)
 *
 */

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

/* hey.. why spin normally! */
#define duff(counter, block) {\
  while (counter >= 4) {\
     { block; } \
     { block; } \
     { block; } \
     { block; } \
     counter -= 4;\
  } \
  switch (counter & 3) { \
     case 3:    { block; } \
     case 2:    { block; } \
     case 1:    { block; } \
     case 0:    counter = 0;\
     }\
}


#define WRITE_DATA() \
    if (fwrite( outrows, 1, fwrite_len, outfile ) != fwrite_len) {\
	perror( "write error" );\
	exit(-2);\
    }\
    

void
main(argc,argv) 
int argc;
char *argv[];
{ 
    int i, rle_err;
    char *nullbytes = NULL;
    char *infname = NULL, *outfname = NULL, *progname;
    FILE *infile, *outfile;
    rle_hdr hdr;
    int oflag = 0;
    int fflag = 0, tflag = 0, Nflag = 0, rflag = 0;
    int header = 0, trailer = 0;
    int pflag= 0, right_pad = 0, lflag = 0, left_pad = 0;
    int aflag = 0, output_alpha = 0, sflag = 0;

    int height, width, nochan;
    int red_pos, alpha_pos, green_pos = 0, blue_pos = 0;
    int img_size;

    /* Default color values */
    unsigned char *outrows;
    rle_pixel **inrows;
    int outrows_size;
    int y;
    int line_pos;
    int fwrite_len;
    unsigned char *fwrite_pos;

    progname = cmd_name( argv );

    if ( scanargs( argc, argv, 
		  "% Ns%- r%- a%- f%-header-size!d t%-trailer-size!d \n\
\t\tp%-scanline-pad!d l%-left-scanline-pad!d o%-outfile!s \n\
\t\tinfile%s\n(\
\t-a\tDon't strip the alpha channel from the rle file.\n\
\t-f,-t\tEach output image is preceded (followed) by so many bytes.\n\
\t-l,-p\tScanlines are padded on left (right) end with so many bytes.\n\
\t\tAll padding is with null (0) bytes.\n\
\t-s\tOutput data in scanline interleaved order\n\
\t-N\tOutput data in non-interleaved order (eg. | split -Wid*Height -)\n\
\t-r\treverse the channel order (e.g. write data as ABGR instead of\n\
\t\tthe default RGBA order).)",
		  &Nflag, &rflag, &aflag, &fflag, &header,
		  &tflag, &trailer, &pflag, &right_pad, &lflag, &left_pad,
		  &oflag, &outfname, &infname ) == 0)
	exit( 1 );

    /* Initialize header. */
    hdr = *rle_hdr_init( (rle_hdr *)NULL );
    rle_names( &hdr, cmd_name( argv ), infname, 0 );

    if (Nflag) {
	if (Nflag & 1) {
	    Nflag = 0;
	    sflag = 1;
	} else {
	    Nflag = 1;
	    sflag = 0;
	}
    }
	       

    /* Open Raw file */
    infile = rle_open_f( progname, infname, "r" );
    outfile = rle_open_f( progname, outfname, "w" );


    rle_dflt_hdr.rle_file = infile;
    
    if ( (rle_err = rle_get_setup(&rle_dflt_hdr)) < 0) {
	rle_get_error( rle_err, progname, infname );
	exit ( rle_err );
    }

    rle_dflt_hdr.xmax -= rle_dflt_hdr.xmin;
    rle_dflt_hdr.xmin = 0;
    rle_dflt_hdr.ymax -= rle_dflt_hdr.ymin;
    rle_dflt_hdr.ymin = 0;
    
    width = rle_dflt_hdr.xmax + 1;
    height = rle_dflt_hdr.ymax + 1;
    nochan = rle_dflt_hdr.ncolors;
    
    fprintf(stderr, "width %d, height %d, channels %d\n", width, height, nochan);

    if (! aflag)
	RLE_CLR_BIT(rle_dflt_hdr, RLE_ALPHA);
    output_alpha = (aflag && RLE_BIT(rle_dflt_hdr, RLE_ALPHA)) ? 1 : 0;
    
    /* for -Non-interleaved case, we need nochan-1 whole channels of buffer */
    /* HACK: we allocate more memory; we jack the size for the first fread */
    outrows_size = width * (nochan + output_alpha);
    if ( Nflag )
	outrows_size *= height;
    if ((outrows = (unsigned char *) malloc ( outrows_size )) == NULL ) {
	fprintf(stderr, "%s: No memory available for rows malloc\n", progname);
	exit(-2);
    }
    
    img_size = width * height;

    /* Were only interested in R, G, & B */
    for (i = 3; i < rle_dflt_hdr.ncolors; i++)
	RLE_CLR_BIT(rle_dflt_hdr, i);

    if (rle_row_alloc( &rle_dflt_hdr, &inrows )) {
	fprintf(stderr, "%s: No memory available for rle_row_alloc\n", progname);
	exit(-2);
    }

    /* maybe faster to malloc and fwrite than to do lots of GETCs, Idunno */
    if (fflag || tflag) {
	nullbytes = (char *) malloc ( (header > trailer) ? header : trailer );
	if (! nullbytes)
	    fprintf(stderr, "%s: No memory for nullbytes\n", progname);
	else
	    for( i = (header > trailer) ? header : trailer; i > 0; )
		nullbytes[--i] = 0;
    }

    /* setup byte positions for reversed colors or otherwise */
    if ( rflag ) {
	alpha_pos = 0;
	/* alpha comes first if it's there */
	if (nochan > 2) {
	    red_pos = 2 + output_alpha;
	    green_pos = 1 + output_alpha;
	    blue_pos = 0 + output_alpha;
	} else
	    red_pos = 0 + output_alpha;
    }
    else {
	alpha_pos = nochan;
	red_pos = 0;
	if (nochan > 2) {
	    green_pos = 1;
	    blue_pos = 2;
	}
    }
    
    if ( Nflag ) {
	red_pos *= img_size;
	green_pos *= img_size;
	blue_pos *= img_size;
	alpha_pos *= img_size;
    } else if ( sflag ) {
	red_pos *= width;
	green_pos *= width;
	blue_pos *= width;
	alpha_pos *= width;
    }

    y = height;
    line_pos = 0;
    fwrite_len = outrows_size;
    fwrite_pos = outrows;

    /* write the header */
    if (fflag)
	fwrite( nullbytes, 1, header, outfile );
    
    while (--y >= 0) {
	register rle_pixel *p, *o;
	register int stride = nochan + output_alpha, count;
	
	rle_getrow(&rle_dflt_hdr, inrows);
	/* non-interleaved data is easier to compute than interleaved */
	if ( Nflag ) {
	    /*
	     * This is a wierd case...  We had to read in all of the
	     * scanlines for all but one of the channels...  Then we can
	     * handle things scanline by scanline...  We have to jack
	     * the fread parameters for all of the remaining scanlines
	     */
	    
	    if ( output_alpha ) 
		bcopy(inrows[RLE_ALPHA], outrows + alpha_pos + line_pos,width);
	    
	    bcopy(inrows[RLE_RED], outrows + red_pos + line_pos, width);
	    
	    if (nochan > 2) {
		bcopy(inrows[RLE_GREEN], outrows + green_pos + line_pos,width);
		bcopy(inrows[RLE_BLUE], outrows + blue_pos + line_pos,width);
	    }
	    line_pos += width;
	} else if (sflag) {
	    /* scanline interleaved: we only need to copy the data */
	    if ( output_alpha )
		bcopy (inrows[RLE_ALPHA], outrows + alpha_pos, width);
	    
	    bcopy (inrows[RLE_RED], outrows + red_pos, width);
	    
	    if (nochan > 2) {
		bcopy(inrows[RLE_GREEN], outrows + green_pos, width);
		bcopy(inrows[RLE_BLUE], outrows + blue_pos, width);
	    }
	}
	else 
	{
#define COPY_LINE() duff(count, *o = *p++; o += stride);
	    
	    /* ahhh...  the default.  interleaved data */
	    if ( output_alpha ) {
		o = outrows + alpha_pos;
		p = inrows[RLE_ALPHA];
		count = width;
		COPY_LINE();
	    }	    
	    
	    o = outrows + red_pos;
	    p = inrows[RLE_RED];
	    count = width;
	    COPY_LINE();
	    
	    if (nochan > 2) {
		o = outrows + green_pos;
		p = inrows[RLE_GREEN];
		count = width;
		COPY_LINE();
		
		o = outrows + blue_pos;
		p = inrows[RLE_BLUE];
		count = width;
		COPY_LINE();
	    }
	}
	
	/* LEFT_PAD */
	for (count = 0; count < left_pad; count++)
	    putc('\0', outfile);
	
	/* WRITE_SCANLINE */
	if (! Nflag)
	    WRITE_DATA();
	
	/* RIGHT_PAD */
	for (count = 0; count < right_pad; count++)
	    putc('\0', outfile);
	
    }
    if ( Nflag )
	WRITE_DATA();
    
    /* write the trailer */
    if (tflag)
	fwrite( nullbytes, 1, trailer, outfile );
    
    exit(0);
}

