/* pbmtogem.c - read a portable bitmap and produce a GEM .img file
**
** Author: David Beckemeyer (bdt!david)
**
** Much of the code for this program was taken from other
** pbmto* programs.  I just modified the code to produce
** a .img header and generate .img "Bit Strings".
**
** Thanks to Diomidis D. Spinellis for the .img header format.
**
** Copyright (C) 1988 by David Beckemeyer (bdt!david) and Jef Poskanzer.
**
** Modified by Johann Haider to produce Atari ST compatible files
**
** 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.
*/
/*
*  92/07/11 jh
*
*  Changes made to this program:
*  changed header length to count words to conform with Atari ST documentation
*  removed rounding of the imagewidth to the next word boundary
*  removed arbitrary limit to imagewidth
*  changed pattern length to 1 to simplify locating of compressable parts
*       in real world images
*  add solid run and pattern run compression
*
*  Deficiencies:
*  Compression of repeated scanlines not added
*  
*       Johann Haider (jh@fortec.tuwien.ac.at)
*
* 94/01/31 Andreas Schwab (schwab@ls5.informatik.uni-dortmund.de)
* Changed to remove architecture dependencies
* Added compression of repeated scanlines
*
* Feb 2010 afu
* Added dimension check to prevent short int from overflowing
* Changed code style (ANSI-style function definitions, etc.)
*/

#include <stdio.h>
#include <string.h>
#include "pbm.h"

#define SOLID_0 0
#define SOLID_1 0xff
#define MINRUN 4
#define INT16MAX 32767

#define putsolid(v,c) putc((v&0x80)|c, stdout)
#define putpattern(v,c) putc(0, stdout);putc(c, stdout);putc(v, stdout)

static short item;
static int outcol, outmax;
static short bitsperitem, bitshift;
static short linerepeat;
static unsigned char *outrow, *lastrow;

static void
putinit (int const rows, int const cols)
{

  if (pm_writebigshort (stdout, (short) 1) == -1 /* Image file version */
      || pm_writebigshort (stdout, (short) 8) == -1 /* Header length */
      || pm_writebigshort (stdout, (short) 1) == -1 /* Number of planes */
      || pm_writebigshort (stdout, (short) 1) == -1 /* Pattern length */
      || pm_writebigshort (stdout, (short) 372) == -1 /* Pixel width */
      || pm_writebigshort (stdout, (short) 372) == -1 /* Pixel height */
      || pm_writebigshort (stdout, (short) cols) == -1
      || pm_writebigshort (stdout, (short) rows) == -1)
    pm_error ("write error");
  item = 0;
  bitsperitem = 0;
  bitshift = 7;
  outcol = 0;
  outmax = (cols + 7) / 8;
  outrow = (unsigned char *) pm_allocrow (outmax, sizeof (unsigned char));
  lastrow = (unsigned char *) pm_allocrow (outmax, sizeof (unsigned char));
  linerepeat = -1;
}

static void
putitem( )
    {
    outrow[outcol++] = item;
    item = 0;
    bitsperitem = 0;
    bitshift = 7;
    }


static void
putbit( bit const b )
    {
    if ( bitsperitem == 8 )
        putitem( );
    ++bitsperitem;
    if ( b == PBM_BLACK )
        item += 1 << bitshift;
    --bitshift;
    }


static void
putstring ( unsigned char *p, int n)
{
#ifdef DEBUG
    fprintf (stderr, "Bitstring, length: %d, pos %d\n", n, outcol);
#endif
    (void) putc((char) 0x80, stdout);     /* a Bit string */
    (void) putc(n, stdout);     /* count */
    fwrite( p, n, 1, stdout );
}


static void
flushrow( )
    {
    unsigned char *outp, *p, *q;
    int count;
    int col = outmax;

    if (linerepeat > 1)
      {
        /* Put out line repeat count */
        fwrite ("\0\0\377", 3, 1, stdout);
        putchar (linerepeat);
      }
    for (outp = p = lastrow; col > 0;)
    {
            for (q = p, count=0; (count < col) && (*q == *p); q++,count++);
            if (count > MINRUN)
            {
                if (p > outp)
                {
                    putstring (outp, p-outp);
                    outp = p;
                }
                col -= count;
                switch (*p)
                {
                case SOLID_0:
#ifdef DEBUG
/*                      if (outcol > 0) */
                        fprintf (stderr, "Solid run 0, length: %d\n", count);
#endif
                        putsolid (SOLID_0, count);
                        break;

                case SOLID_1:
#ifdef DEBUG
                        fprintf (stderr, "Solid run 1, length: %d, pos %d\n", count, outcol);
#endif
                        putsolid (SOLID_1, count);
                        break;
                default:
#ifdef DEBUG
                        fprintf (stderr, "Pattern run, length: %d\n", count);
#endif
                        putpattern (*p, count);
                        break;
                }
                outp = p = q;
            }
            else
            {
                p++;
                col--;
            }
    }           
    if (p > outp)
         putstring (outp, p-outp);
    if (ferror (stdout))
      pm_error ("write error");
}


static void
putrow( )
{
  if (bitsperitem > 0)
    putitem ();
  outcol = 0;
  if (linerepeat == -1 || linerepeat == 255
      || memcmp (outrow, lastrow, outmax) != 0)
    {
      unsigned char *temp;
      if (linerepeat != -1) /* Unless first line */
        flushrow ();
      /* Swap the pointers */
      temp = outrow; outrow = lastrow; lastrow = temp;
      linerepeat = 1;
    }
  else
    /* Repeated line */
    linerepeat++;
}


int
main( int argc, char* argv[])
    {
    FILE* ifp;
    bit* bitrow;
    int rows, cols, format, row, col;

    pbm_init( &argc, argv );

    if ( argc > 2 )
        pm_usage( "[pbmfile]" );

    if ( argc == 2 )
        ifp = pm_openr( argv[1] );
    else
        ifp = stdin;

    pbm_readpbminit( ifp, &cols, &rows, &format );

    if( rows>INT16MAX || cols>INT16MAX )
      pm_error ("Input image is too large.");


    bitrow = pbm_allocrow( cols );

    putinit (rows, cols);
    for ( row = 0; row < rows; ++row )
        {
#ifdef DEBUG
        fprintf (stderr, "row %d\n", row);
#endif
        pbm_readpbmrow( ifp, bitrow, cols, format );
        for ( col = 0; col < cols; ++col )
            putbit( bitrow[col] );
        putrow( );
        }
    flushrow ();

    pm_close( ifp );


    exit( 0 );
    }
