/*
 * 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.
 */
/*
 * pcx.c - interface with Zsoft's PCX format.
 *
 * Author:      Raul Rivero
 *              Mathematics Dept.
 *              University of Oviedo
 * Date:        Sun Feb 9 1992
 * Copyright (c) 1992, Raul Rivero
 *
 */
/*
 *
 * Raul Rivero  -  04/09/94
 *
 *      Fixed several bugs into the algorithm which 
 *      read < 256 color images.
 *
 */

#include <lug.h>
#include <lugfnts.h>

#define PCX_HEADERSIZE                  128
#define PCX_MAGICNUMBER                 0x0a
#define PCX_256COLORS                   0x0c
#define PCX_COMPRESS                    0xc0

extern int LUGverbose;

read_pcx_file( name, bitmap )
char *name;
bitmap_hdr *bitmap;
{
  FILE *handle;

  /* Open the file descriptor */
  if ( name != NULL )
    handle = Fopen( name, "rb" );
  else error( 20 );

  /* Read the bitmap */
  read_pcx( handle, bitmap );
  rm_compress();

  /* Close the file */
  Fclose( handle );
}

read_pcx( handle, image )
FILE *handle;
bitmap_hdr *image;
{
  register int i, j, k;
  byte header[ PCX_HEADERSIZE ];
  int bitsperpixel;
  int xsize, ysize;
  int xmax, ymax;
  int xmin, ymin;
  byte version;
  int bytesperline;
  byte *r, *g, *b;
  byte *buffer;
  long position;
  int mask;
  int paletteinfo;
  int truecolor = 0;

  /*
   * Read the header.
   */
  VPRINTF( stderr, "Reading PCX header\n" );
  Fread( header, PCX_HEADERSIZE, 1, handle );
  if ( header[0] != PCX_MAGICNUMBER )   /* a PCX file ? */
    error( 5 );

  /*
   * Get some data from this header.
   */
  version = header[1];
  VPRINTF( stderr, "PCX file version: %d\n", version );

  if ( header[2] != 1 )         /* file with PCX RLE encode ? */
    error( 9 );

  bitsperpixel = ( header[3] == 1 ?  header[65] : header[3] );
  VPRINTF( stderr, "Bits per pixel: %d\n", bitsperpixel );

  xmin = (header[ 5] << 8) | header[ 4];
  ymin = (header[ 7] << 8) | header[ 6];
  xmax = (header[ 9] << 8) | header[ 8];
  ymax = (header[11] << 8) | header[10];
  xsize = xmax - xmin + 1;
  ysize = ymax - ymin + 1;

  bytesperline = (header[67] << 8) | header[66];
  paletteinfo = (header[69] << 8) | header[68];
  VPRINTF( stderr, "Bytes per line: %d\n", bytesperline );
  VPRINTF( stderr, "Palete info: %d\n", paletteinfo );
  /*
   * Fill our header.
   */
  image->xsize = xsize;
  image->ysize = ysize;
  r = image->r = (byte *) Malloc( xsize * ysize );

  /*
   * Cmap info.
   */
  image->magic = LUGUSED;
  image->depth = bitsperpixel;
  image->colors = ( 1 << image->depth );
  /*
   * Read the cmap tag.
   */
  if ( version >= 5 && bitsperpixel == 8 ) {
    /* We could have 256 colors, check it ! */
    position = ftell( handle );
    fseek( handle, -769L, 2 );
    if ( getc( handle ) == PCX_256COLORS ) {
      /* Really, we have 256 colors */
      VPRINTF( stderr, "Reading additional color map\n" );
      image->cmap = (byte *) Malloc( 3 * 256 );
      Fread( image->cmap, 256, 3, handle );
    }else {
      /* 
       * Wow !, we have a true color image. We need
       * more memory.
       */
      truecolor = 1;
      g = image->g = (byte *) Malloc( xsize * ysize );
      b = image->b = (byte *) Malloc( xsize * ysize );
      image->depth = 24;
      image->colors = ( 1 << image->depth );
    }
    /* Go back to the original position */
    fseek( handle, position, 0 );
  }else {
    /* We have (2**bitsperpixel) colors */
    image->cmap = (byte *) Malloc( 3 * image->colors );
    if ( bitsperpixel == 1 ) {
      /*
       * A monochrome palette isn't stored. Why ???!!!, there is a
       * reserved space !!!.
       */
      image->cmap[3] = image->cmap[4] = image->cmap[5] = 255;
    }else {
      /*
       * This cmap is stored on header space ( and we have
       * this header, so we only nedd copy it ).
       */
      bcopy( &(header[16]), image->cmap, 3*image->colors );
    }
  }

  /*
   * Read the raster information.
   */
  VPRINTF( stderr, "Unpacking raster information\n");
  if ( bitsperpixel > 4 ) {
    /*
     * Read a 'normal' image, one byte is one pixel.
     */
    for ( i = 0; i < ysize; i++ ) {
      decodePCX( handle, r, bytesperline );
      r += image->xsize;
      if ( truecolor ) {
        decodePCX( handle, g, bytesperline );
        g += image->xsize;
        decodePCX( handle, b, bytesperline );
        b += image->xsize;
      }
    }
  }else {
    byte *ptrplane;
    int totalbytes = bytesperline * bitsperpixel;
    /*
     * The image was built by planes, so we need
     * join all planes ( arrggg ! ).
     */
    buffer = (byte *) Malloc( totalbytes );

    for ( i = 0; i < ysize; i++ ) {
      decodePCX( handle, buffer, totalbytes );
      ptrplane = buffer; 
      for ( j = 0; j < bitsperpixel; j++ ) {
        /*
         * Glue planes.
         */
        mask = 1 << j;
        for ( k = 0; k < xsize; k++ ) {
          if ( ptrplane[ k >> 3 ] & (0x80 >> (k & 0x0007 )) )
            r[ k ] |= mask;
        }
        /*  Move the pointer to the next plane */
        ptrplane += bytesperline;
      }
      r += image->xsize;
    }
    Free( buffer );
  }
}

decodePCX( handle, buffer, linesize )
FILE *handle;
byte *buffer;
int linesize;
{
  int cin;
  int count;

  while ( linesize > 0 ) {
    cin = getc( handle );
    if ( (cin & PCX_COMPRESS) == PCX_COMPRESS ) {
      /* A block compressed */
      count = cin & 0x3f;       /* 00111111 */
      if ( count > linesize )
        error( 6 );

      /* Get the real data to copy */
      cin = getc( handle );
      memset( buffer, cin, count );

      /* Update pointers */
      linesize -= count;
      buffer += count;
    }else {
      /* Only one data */
      *buffer++ = cin;
      linesize--;
    }
  }
}

write_pcx_file( name, image )
char *name;
bitmap_hdr *image;
{
  FILE *handle;

  /* Open the file descriptor */
  if ( name != NULL )
    handle = Fopen( name, "wb" );
  else handle = stdout;

  /* Write the bitmap */
  write_pcx( handle, image );

  /* Close the file */
  Fclose( handle );

}

write_pcx( handle, image )
FILE *handle;
bitmap_hdr *image;
{
  register int i;
  byte *raster;

  if ( image->magic != LUGUSED )
    error( 19 );

  if ( image->depth > 8 )
    error( 15 );

  write_pcx_header( handle, image->xsize, image->ysize );

  raster = image->r;
  for ( i = 0; i < image->ysize; i++ ) {
    encodePCX( handle, raster, image->xsize );
    raster += image->xsize;
  }
  write_pcx_cmap( handle, image->colors, image->cmap );
}

write_pcx_header( handle, xsize, ysize )
FILE *handle;
int xsize, ysize;
{
  byte header[PCX_HEADERSIZE];

  bzero( header, PCX_HEADERSIZE );
  /*
   * We always write an image with 256 colors. I'm soory
   * but it's more easy ( and, ... a image with less than
   * 256 colors, what is it ? ).
   */

  /*
   * Fill the PCX header.
   */
  header[ 0] = PCX_MAGICNUMBER;
  header[ 1] = 0x05;            /* version 3.0 */
  header[ 2] = 0x01;            /* PCX RLE encode */
  header[ 3] = 0x08;            /* eight bytes per pixel */

  /* Image left */
  header[ 4] = 0;
  header[ 5] = 0;
  /* Image top */
  header[ 6] = 0;
  header[ 7] = 0;
  /* Image right */
  header[ 8] = LSB( xsize - 1 );
  header[ 9] = MSB( xsize - 1 );
  /* Image bottom */
  header[10] = LSB( ysize - 1 );
  header[11] = MSB( ysize - 1 );
  /* Screen width */
  header[12] = LSB( xsize );
  header[13] = MSB( xsize );
  /* Screen bottom */
  header[14] = LSB( ysize );
  header[15] = MSB( ysize );

  header[65] = 1;               /* one plane */
  /* Bytes per line */
  header[66] = LSB( xsize );
  header[67] = MSB( xsize );
  /* Interpret palette as color/bw */
  header[68] = 0x01;
  header[69] = 0;

  Fwrite( header, PCX_HEADERSIZE, 1, handle );
}

write_pcx_cmap( handle, colors, cmap )
FILE *handle;
int colors;
byte *cmap;
{
  byte auxcmap[ 3*256 ];

  bzero( auxcmap, 3*256 );
  bcopy( cmap, auxcmap, 3*colors );

  /* 256 colors signature */
  fputc( PCX_256COLORS, handle );
  Fwrite( auxcmap, 256, 3, handle );
}

encodePCX( handle, buffer, size )
FILE *handle;
byte *buffer;
int size;
{
  int count = 1;
  byte *end, *ptr;
  byte out[2560];
  int current, new;

  ptr = out;
  current = *buffer++;
  end = buffer + size;
  while ( buffer < end ) {
    new = *buffer++;
    while ( current == new && count < 63 && buffer < end ) {
      count++;
      new = *buffer++;
    }
    if ( count > 1 || ( current & PCX_COMPRESS ) == PCX_COMPRESS )
      *ptr++ = count | PCX_COMPRESS;
    *ptr++ = current;
    count = 1;
    current = new;
  }

  count = ptr - out;
  Fwrite( out, count, 1, handle );
}
