/***************************************************************************
*
*  This contains routines to compress images one of two ways:
*      1. Vertical Byte Run Length (VRUN)
*      2. Vertical Byte Run Length With Skips (SKIP)
*   from amiga/programs #274, dated jul 8 1987...
*
* The routines here compress a single bit-plane.  There's routines to
* see how big the compressed result will be (xxxx_count_plane() )
* and routines to actually compress the result into a buffer in memory
* (xxxx_comp_plane() )  where xxxx is either vrun or skip depending....
*
*
* VRUN compression format:
*    a VRUN-compressed plane is a concatenation of VRUN-compressed columns.
*    Each column is an op-count followed by a concatenation of op-count op's.
*    An op is in one of these two formats:
*        SAME_OP:  1-byte-count  followed by 1-byte to repeat count times.
*        UNIQ_OP:  1-byte-count  followed by count bytes of data to copy
*    The counts in either op must be 127 or less.  If it's a UNIQ_OP the
*    hi bit of the count op is set (it's masked with 127 to find the actual
*    count).
*
* SKIP compression format:
*    a SKIP compressed plane is a concatenation of SKIP-compressed columns.
*    Like a VRUN this is an op-count followed by a concatenation of op;s.
*    However in this one we have 3 op formats:
*       SKIP_OP:  Hi bit clear.  Count of bytes to skip.
*       UNIQ_OP:  Hi bit set.  Remainder is count of data to copy. Data
*                 follows immediately.
*       SAME_OP:  A zero followed by a one-byte-count followed by one byte
*                 of data to repeat count times.
*
****************************************************************************
*
* Small changes made for SAS/C by Olaf `Olsen' Barthel: added registerized
* parameters and STATIC declarations to make it easier for the compiler to
* inline certain routines.
*                          -olsen
*
***************************************************************************/

#include <exec/types.h>
#include <graphics/gfx.h>

#define MAXRUN 127

static WORD linebytes = 40;
static WORD uniq_count;
static unsigned char *uniq;
static WORD op_count;

UBYTE * __regargs	skip_comp_plane(UBYTE *in, UBYTE *last_in, UBYTE *out, WORD next_line, WORD rows);
WORD __regargs		skip_count_plane(UBYTE *in, UBYTE *last_in, WORD next_line, WORD rows);

STATIC WORD		vskip(unsigned char *in, unsigned char *last_in, WORD count);
STATIC WORD		vsame(unsigned char *in, WORD count);
STATIC WORD		skip_count_line(unsigned char *in, unsigned char *last_in, WORD count);
STATIC unsigned char *	flush_uniq(unsigned char *stuff);
STATIC int		copy_line_to_chars(unsigned char *in, unsigned char *out, int linebytes, int count);
STATIC unsigned char *	skip_comp_line(unsigned char *in, unsigned char *last_in, unsigned char *out, WORD count);
STATIC WORD		vrun_count_line(unsigned char *in, WORD count);
STATIC WORD		vrun_count_plane(unsigned char *in, WORD next_line, WORD rows);
STATIC unsigned char *	vrun_comp_line(unsigned char *in, unsigned char *out, WORD count);
STATIC unsigned char *	vrun_comp_plane(unsigned char *in, unsigned char *out, WORD next_line, WORD rows);

/* count up how many in a column are the same between in and last_in ...
   ie how big of a "skip" can we go on. */

STATIC WORD
vskip(in, last_in, count)
     register unsigned char *in;
     register unsigned char *last_in;
     register WORD count;
{
    register WORD skips;

    skips = 0;
    while (--count >= 0)
    {
	if (*in != *last_in)
	    break;
	in += linebytes;
	last_in += linebytes;
	skips++;
    }
    return (skips);
}

/* vsame() - count up how many in a row (vertically) are the same as the
   first one ... ie how big of a "same" op we can have */
STATIC WORD
vsame(in, count)
     register unsigned char *in;
     register WORD count;
{
    register unsigned char c;
    register WORD same;

    c = *in;
    in += linebytes;
    --count;
    same = 1;
    while (--count >= 0)
    {
	if (*in != c)
	    break;
	same++;
	in += linebytes;
    }
    return (same);
}


/* skip_count_line() - figure out what size this column will compress
   to using vertical-byte-run-length-with-skips encoding */
STATIC WORD
skip_count_line(in, last_in, count)
     register unsigned char *in;
     register unsigned char *last_in;
     WORD count;
{
    WORD local_count;
    WORD a_run;
    WORD run_length;
    WORD uniq_count = 0;
    WORD comp_count = 1;	/* one for the op count */


    if (vskip(in, last_in, count) == count)	/* skip whole column? */
	return (1);
    for (;;)
    {
	if (count <= 0)
	    break;
	local_count = (count < MAXRUN ? count : MAXRUN);
	a_run = 0;
	if ((run_length = vskip(in, last_in, local_count)) > 1)
	{
	    count -= run_length;
	    if (count > 0)	/* the last skip disappears */
		comp_count += 1;
	    a_run = 1;
	}
	else if ((run_length = vsame(in, local_count)) > 3)
	{
	    count -= run_length;
	    a_run = 1;
	    comp_count += 3;
	}
	if (a_run)
	{
	    in += run_length * linebytes;
	    last_in += run_length * linebytes;
	    if (uniq_count > 0)
	    {
		comp_count += uniq_count + 1;
		uniq_count = 0;
	    }
	}
	else
	{
	    in += linebytes;
	    last_in += linebytes;
	    uniq_count++;
	    count -= 1;
	    if (uniq_count == MAXRUN)
	    {
		comp_count += uniq_count + 1;
		uniq_count = 0;
	    }
	}
    }
/*  if (count != 0)
    {
        printf("weird end count %d in skip_line_count\n");
    }*/
    if (uniq_count != 0)
    {
	comp_count += uniq_count + 1;
    }
    return (comp_count);
}

/* skip_count_plane() - figure out what size this plane will compress
   to using vertical-byte-run-length-with-skips encoding */

/*
WORD
skip_count_plane(in, last_in, next_line, rows)
     unsigned char *in;
     unsigned char *last_in;
     WORD next_line;
     WORD rows;
*/

WORD __regargs
skip_count_plane(UBYTE *in, UBYTE *last_in, WORD next_line, WORD rows)
{
    WORD i;
    WORD comp_count;

    linebytes = next_line;
    comp_count = 0;
    i = next_line;
    while (--i >= 0)
    {
	comp_count += skip_count_line(in, last_in, rows);
	in++;
	last_in++;
    }
    return (comp_count);
}

/* flush_uniq() - write out the "uniq" run that's been accumulating while
   we've been looking for skips and "same" runs. */
STATIC unsigned char *
flush_uniq(stuff)
     unsigned char *stuff;
{
    if (uniq_count > 0)
    {
	op_count++;
	*stuff++ = (uniq_count | 0x80);
	copy_line_to_chars(uniq, stuff, linebytes, uniq_count);
	stuff += uniq_count;
	uniq_count = 0;
    }
    return (stuff);
}

STATIC int
copy_line_to_chars(in, out, linebytes, count)
     unsigned char *in, *out;
     int linebytes, count;
{
    while (count--)
    {
	*out = *in;
	out++;
	in += linebytes;
    }
    return (0);
}

/* skip_comp_line() - Compress "in" into "out" using vertical-byte-run-
   with-skips encodeing. Return pointer to "out"'s next free space. */

STATIC unsigned char *
skip_comp_line(in, last_in, out, count)
     register unsigned char *in;
     unsigned char *last_in;
     unsigned char *out;
     WORD count;
{
    register unsigned char *stuffit;
    WORD local_count;
    WORD a_run;
    WORD run_length;

/* if can skip over whole column, then compact a bit by just setting the
   "op count" for this column to zero */
    if (vskip(in, last_in, count) == count)	/* skip whole column? */
    {
	*out++ = 0;
	return (out);
    }

    op_count = 0;		/* haven't done any op's yet */

/* initialize variables which keep track of how many uniq bytes we've gone
   past, and where uniq run started. */
    uniq_count = 0;
    uniq = in;

    stuffit = out + 1;		/* skip past "op-count" slot in out array */
    for (;;)
    {
	if (count <= 0)
	    break;
	local_count = (count < MAXRUN ? count : MAXRUN);
	a_run = 0;		/* first assume not a skip or a same run */
	/* see how much could skip from here.  Two or more is worth skipping! */
	if ((run_length = vskip(in, last_in, local_count)) > 1)
	{
	    a_run = 1;
	    count -= run_length;
	    stuffit = flush_uniq(stuffit);	/* flush pending "uniq" run */
	    if (count > 0)	/* last skip vanishes */
	    {
		op_count++;
		*stuffit++ = run_length;
	    }
	}
	/* four or more of the same byte in a row compresses too */
	else if ((run_length = vsame(in, local_count)) > 3)
	{
	    a_run = 1;
	    count -= run_length;
	    op_count++;
	    stuffit = flush_uniq(stuffit);	/* flush pending "uniq" run */
	    *stuffit++ = 0;
	    *stuffit++ = run_length;
	    *stuffit++ = *in;
	}
	/* if it's a run of some sort update in and last_in pointer, and
           reset the uniq pointer to the current position */
	if (a_run)
	{
	    in += run_length * linebytes;
	    last_in += run_length * linebytes;
	    uniq = in;
	    /* otherwise just continue accumulating stuff in uniq for later flushing
	       or immediate if it get's past MAXRUN */
	}
	else
	{
	    in += linebytes;
	    last_in += linebytes;
	    uniq_count++;
	    count -= 1;
	    if (uniq_count == MAXRUN)
	    {
		stuffit = flush_uniq(stuffit);
		uniq = in;
	    }
	}
    }
/* if came to end of column within a uniq-run still have to flush it */
    if (uniq_count != 0)
    {
	stuffit = flush_uniq(stuffit);
    }
/*  if (count != 0)
    {
        printf("weird end count %d in skip_line_count\n", count);
    }*/
/* and stuff the first byte of this (compressed) column with the op_count */
    *out = op_count;
    return (stuffit);
}

/* skip_comp_plane() - Compress "in" into "out" using vertical-byte-run-
   with-skips encodeing. Return pointer to "out"'s next free space. */

/*
unsigned char *
skip_comp_plane(in, last_in, out, next_line, rows)
     unsigned char *in;
     unsigned char *last_in;
     unsigned char *out;
     WORD next_line;
     WORD rows;
*/


UBYTE * __regargs
skip_comp_plane(UBYTE *in, UBYTE *last_in, UBYTE *out, WORD next_line, WORD rows)
{
    WORD i;

    linebytes = next_line;
    i = next_line;
    while (--i >= 0)
    {
	out = skip_comp_line(in, last_in, out, rows);
	in++;
	last_in++;
    }
    return (out);
}

/* vrun_count_line() - figure out what size this column will compress
   to using vertical-byte-run-length encoding */

STATIC WORD
vrun_count_line(in, count)
     register unsigned char *in;
     WORD count;
{
    WORD local_count;
    WORD a_run;
    WORD run_length;
    WORD uniq_count = 0;
    WORD comp_count = 1;	/* one for the op count */


    for (;;)
    {
	if (count <= 0)
	    break;
	local_count = (count < MAXRUN ? count : MAXRUN);
	a_run = 0;
	if ((run_length = vsame(in, local_count)) > 2)
	{
	    a_run = 1;
	    comp_count += 2;
	}
	if (a_run)
	{
	    in += run_length * linebytes;
	    count -= run_length;
	    if (uniq_count > 0)
	    {
		comp_count += uniq_count + 1;
		uniq_count = 0;
	    }
	}
	else
	{
	    in += linebytes;
	    uniq_count++;
	    count -= 1;
	    if (uniq_count == MAXRUN)
	    {
		comp_count += uniq_count + 1;
		uniq_count = 0;
	    }
	}
    }
/*  if (count != 0)
    {
        printf("weird end count %d in vrun_line_count\n");
    }*/
    if (uniq_count != 0)
    {
	comp_count += uniq_count + 1;
    }
    return (comp_count);
}

/* vrun_count_plane() - figure out what size this plane will compress
   to using vertical-byte-run-length encoding */
STATIC WORD
vrun_count_plane(in, next_line, rows)
     unsigned char *in;
     WORD next_line;
     WORD rows;
{
    WORD i;
    WORD comp_count;

    linebytes = next_line;
    comp_count = 0;
    i = next_line;
    while (--i >= 0)
    {
	comp_count += vrun_count_line(in, rows);
	in++;
    }
    return (comp_count);
}


/* vrun_comp_line() - Compress "in" into "out" using vertical-byte-run
   encodeing. Return pointer to "out"'s next free space. */

STATIC unsigned char *
vrun_comp_line(in, out, count)
     register unsigned char *in;
     unsigned char *out;
     WORD count;
{
    register unsigned char *stuffit;
    WORD local_count;
    WORD a_run;
    WORD run_length;

    uniq_count = op_count = 0;
    uniq = in;

    stuffit = out + 1;
    for (;;)
    {
	if (count <= 0)
	    break;
	local_count = (count < MAXRUN ? count : MAXRUN);
	a_run = 0;
	if ((run_length = vsame(in, local_count)) > 2)
	{
	    a_run = 1;
	    stuffit = flush_uniq(stuffit);
	    *stuffit++ = run_length;
	    *stuffit++ = *in;
	}
	if (a_run)
	{
	    op_count++;
	    in += run_length * linebytes;
	    count -= run_length;
	    uniq = in;
	}
	else
	{
	    in += linebytes;
	    uniq_count++;
	    count -= 1;
	    if (uniq_count == MAXRUN)
	    {
		stuffit = flush_uniq(stuffit);
		uniq = in;
	    }
	}
    }
    if (uniq_count != 0)
    {
	stuffit = flush_uniq(stuffit);
    }
/*  if (count != 0)
    {
        printf("weird end count %d in vrun_line_count\n", count);
    }*/
    *out = op_count;
    return (stuffit);
}

/* vrun_comp_plane() - Compress "in" into "out" using vertical-byte-run
   encodeing. Return pointer to "out"'s next free space. */

STATIC unsigned char *
vrun_comp_plane(in, out, next_line, rows)
     unsigned char *in;
     unsigned char *out;
     WORD next_line;
     WORD rows;
{
    WORD i;

    linebytes = next_line;
    i = next_line;
    while (--i >= 0)
    {
	out = vrun_comp_line(in, out, rows);
	in++;
    }
    return (out);
}
