/* Copyright (C) 1992 Aladdin Enterprises.  All rights reserved.
   Distributed by Free Software Foundation, Inc.

This file is part of Ghostscript.

Ghostscript is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY.  No author or distributor accepts responsibility
to anyone for the consequences of using it or for whether it serves any
particular purpose or works at all, unless he says so in writing.  Refer
to the Ghostscript General Public License for full details.

Everyone is granted permission to copy, modify and redistribute
Ghostscript, but only under the conditions described in the Ghostscript
General Public License.  A copy of this license is supposed to have been
given to you along with Ghostscript so you can know your rights and
responsibilities.  It should be in a file named COPYING.  Among other
things, the copyright notice and this notice must be preserved on all
copies.  */

/*
 * gdevilbm.c
 * deepilbm driver for GhostScript
 * this is a straight-forward modification of gdevpbm.c
 * Author: Andreas Heitmann
 */

#define DINA4
#define COMPRESSION cmpByteRun1
#define X_DPI 72
#define Y_DPI 72

#include <stdio.h>
#include <dos/dos.h>
#include <utility/hooks.h>
#include <libraries/iffparse.h>
#include <clib/iffparse_protos.h>
#include "gdevilbm.h"
#include "gdevprn.h"
#include "gxlum.h"
#include "ilbmpacker.h"

struct IFFParseBase *IFFParseBase=NULL;

typedef struct gx_device_ilbm_s {
 gx_device_common;
 gx_prn_device_common;
 struct IFFHandle *iff; /* IFF Handle */
 byte palette[256][3];  /* CLUT palette */
 gx_color_index ncols;  /* number of allocated colors in CLUT */
 byte compression;      /* cmpRun || cmpNone */
} ilbm_device;

#define ilbm_prn_device(\
 procs,dev_name,\
 num_comp, depth, max_gray, max_rgb,\
 print_page) {\
 prn_device_body(\
  ilbm_device,\
  procs, dev_name,\
  WIDTH_10THS, HEIGHT_10THS, X_DPI, Y_DPI, 0, 0, 0, 0,\
  num_comp, depth, max_gray, max_rgb, max_gray + 1, \
  max_rgb + 1, print_page\
 )\
}


/* For ILBM we need our own color mapping procedures. */
private dev_proc_map_rgb_color(ilbm24_map_rgb_color);
private dev_proc_map_color_rgb(ilbm24_map_color_rgb);
private dev_proc_print_page(ilbm24_print_page);

private dev_proc_map_rgb_color(ilbm8_map_rgb_color);
private dev_proc_map_color_rgb(ilbm8_map_color_rgb);
private dev_proc_print_page(ilbm8_print_page);

private dev_proc_output_page(gdev_ilbm_output_page);
private dev_proc_open_device(gdev_ilbm_open);

/* The device procedures */
private gx_device_procs ilbm24_procs =
prn_color_procs(
  gdev_ilbm_open,
  gdev_ilbm_output_page,
  gdev_prn_close,
  ilbm24_map_rgb_color,
  ilbm24_map_color_rgb);

private gx_device_procs ilbm8_procs =
prn_color_procs(
  gdev_ilbm_open,
  gdev_ilbm_output_page,
  gdev_prn_close,
  ilbm8_map_rgb_color,
  ilbm8_map_color_rgb);

/* The device descriptors themselves */
ilbm_device gs_ilbm24_device =
ilbm_prn_device(ilbm24_procs,"ilbm24",3,24,255,255,ilbm24_print_page);

ilbm_device gs_ilbm8_device=
ilbm_prn_device(ilbm8_procs,"ilbm8",3,8,31,4,ilbm8_print_page);

/* Map an RGB color to a ILBM24 color tuple. */
private gx_color_index
ilbm24_map_rgb_color(gx_device * dev, ushort r, ushort g, ushort b)
{
 return
  (gx_color_value_to_byte(r)<<16)+
  (gx_color_value_to_byte(g)<<8)+
  gx_color_value_to_byte(b);
}

/*
 * Map a ILBM24 color tuple back to an RGB color.
 */
private int
ilbm24_map_color_rgb(gx_device * dev, gx_color_index color, ushort prgb[3])
{
 prgb[0] = gx_color_value_from_byte((color>>16)&0xff);
 prgb[1] = gx_color_value_from_byte((color>> 8)&0xff);
 prgb[2] = gx_color_value_from_byte( color     &0xff);
 return 0;
}

/*
 * 8-Bit Color-Mapping routines
 * Map an RGB color to a CLUT index
 */
private gx_color_index
ilbm8_map_rgb_color(gx_device *pdev, ushort r, ushort g, ushort b)
{
 byte red_val, green_val, blue_val;
 gx_color_index i;
 static int warn = 1;
 gx_color_index ret = gx_no_color_index;

 red_val  =gx_color_value_to_byte(r);
 green_val=gx_color_value_to_byte(g);
 blue_val =gx_color_value_to_byte(b);

 /*
  * Look for an exact match among the colors already allocated. This
  * includes the pre-allocated default color cube.
  */
 for (i = 0; i < bdev->ncols; i++) {
  if (bdev->palette[i][0] == red_val &&
      bdev->palette[i][1] == green_val &&
      bdev->palette[i][2] == blue_val) {
   ret = i;
  }
 }
 if (ret==gx_no_color_index) {
  if (bdev->ncols >= (1 <<bdev->color_info.depth)) {
   if (warn) {
    eprintf("gs: last spare color map entry allocated\n");
    warn = 0;
   }
  }
  else {
   bdev->palette[i][0]=red_val;
   bdev->palette[i][1]=green_val;
   bdev->palette[i][2]=blue_val;
   bdev->ncols++;
   ret = i;
  }				/* else alloc new color */
 }				/* end if no spare colors */
 return ret;
}

/*
 * Map a CLUT index to an RGB-value
 */
private int
ilbm8_map_color_rgb(gx_device *pdev, gx_color_index color, ushort prgb[3])
{
 prgb[0]=gx_color_value_from_byte(bdev->palette[color][0]);
 prgb[1]=gx_color_value_from_byte(bdev->palette[color][1]);
 prgb[2]=gx_color_value_from_byte(bdev->palette[color][2]);
 return 0;
}

/* ------ Individual page printing routines ------ */

private int
print_iff_header(gx_device_printer *pdev)
{
 struct BitMapHeader pagebmhd;

 PushChunk(bdev->iff,ID_ILBM,ID_BMHD,sizeof(pagebmhd));
 pagebmhd.w=bdev->width;
 pagebmhd.h=bdev->height;
 pagebmhd.x=0;
 pagebmhd.y=0;
 pagebmhd.nPlanes=bdev->color_info.depth;
 pagebmhd.masking=mskNone;
 pagebmhd.compression=bdev->compression;
 pagebmhd.pad1=0;
 pagebmhd.transparentColor=0;
 pagebmhd.xAspect=10;
 pagebmhd.yAspect=10;
 pagebmhd.pageWidth=bdev->width;
 pagebmhd.pageHeight=bdev->height;
 WriteChunkBytes(bdev->iff,&pagebmhd,sizeof(pagebmhd));
 PopChunk(bdev->iff);
 if(bdev->color_info.depth<24) {
  PushChunk(bdev->iff,ID_ILBM,ID_CMAP,3*(1<<bdev->color_info.depth));
  WriteChunkBytes(bdev->iff,bdev->palette,3*(1<<bdev->color_info.depth));
  PopChunk(bdev->iff);
 }
}

private int
ilbm8_print_page(gx_device_printer *pdev,FILE *dummy)
{
 const uint outputw=RowBytes(bdev->width);
 const uint line_size=gdev_prn_raster((gx_device_printer *)bdev);
 const uint pack_size=MaxPackedSize(outputw);
 byte *const data  =(byte *)gs_malloc(1,line_size,"ilbm8_print_page");
 byte *const output=(byte *)gs_malloc(8,outputw  ,"ilbm8_print_page");
 byte *const pack  =
  (bdev->compression==cmpByteRun1)
  ?(byte *)gs_malloc(1,pack_size,"ilbm8_print_page"):NULL;
 byte *bp,*planep;
 unsigned long line;
 uint x0,packedbytes;
 byte mask,x1,plane;

 if(pack!=NULL||bdev->compression!=cmpByteRun1) {
  if(output!=NULL) {
   if (data != NULL) {
    print_iff_header(pdev);
    PushChunk(bdev->iff,ID_ILBM,ID_BODY,IFFSIZE_UNKNOWN);
    for(line=0;line<bdev->height;line++) {
     gdev_prn_copy_scan_lines((gx_device_printer *)bdev,line,data,line_size);
     memset(output,0,8*outputw);
     planep=output;
     for(mask=1<<0;mask;mask<<=1) {
      bp=data;
      for(x0=outputw;x0;x0--) {
       for(x1=8;x1;x1--) {
	(*planep)<<=1; *planep|=(*bp++&mask)==mask;
       }
       planep++;
      }
     }
     if(bdev->compression==cmpByteRun1) {
      for(plane=bdev->color_info.depth,planep=output;
          plane>0;
	  plane--,planep+=outputw) {
       packedbytes=packrow(planep,pack,outputw);
       WriteChunkBytes(bdev->iff,pack,packedbytes); 
      }
     }
     else {
      WriteChunkRecords(bdev->iff,output,outputw,bdev->color_info.depth);
     }
    }
    PopChunk(bdev->iff);	/* Pop Body Chunk */
    gs_free(data,1,line_size,"ilbm8_print_page");
   }
   else  return -1;
   gs_free(output,8,outputw,"ilbm8_print_page");
  }
  else return -1;
  if(bdev->compression==cmpByteRun1) {
   gs_free(pack,1,pack_size,"ilbm8_print_page");
  }
 }
 else return -1;
 return 0;
}

/*
 * Print a color-mapped page with 24 Bit
 *
 * This function is too different from ilbm8_print_page, so for
 * higher output speed the main loop is rewritten
 *
 */
private int
ilbm24_print_page(gx_device_printer *pdev,FILE *dummy)
{
 const uint outputw=RowBytes(bdev->width);
 const uint line_size=gdev_prn_raster((gx_device_printer *)bdev);
 const uint pack_size=MaxPackedSize(outputw);
 byte *const data = (byte *)gs_malloc(line_size,1,"ilbm24_print_page");
 byte *const output=(byte *)gs_malloc(24,outputw ,"ilbm24_print_page");
 byte *const pack  =
 (bdev->compression==cmpByteRun1)
 ?(byte *)gs_malloc(1,pack_size,"ilbm8_print_page"):NULL;
 byte *bp,*planep,*red,*green,*blue;
 unsigned long line;
 uint x0,packedbytes;
 byte mask,x1,plane;

 if(pack!=NULL||bdev->compression!=cmpByteRun1) {
  if(output!=NULL) {
   if (data != NULL) {
    print_iff_header(pdev);
    PushChunk(bdev->iff,ID_ILBM,ID_BODY,IFFSIZE_UNKNOWN);
    for(line=0;line<bdev->height;line++) {
     gdev_prn_copy_scan_lines((gx_device_printer *)bdev,line,data,line_size);
     memset(output,0,24*outputw);
     red  =output;
     green=red  +8*outputw;
     blue =green+8*outputw;
     for(mask=0;mask<8;mask++) {
      bp=data;
      for(x0=outputw;x0;x0--) {
       for(x1=8;x1;x1--) {
	*red  =*red  <<1|*bp++>>mask&1;
	*green=*green<<1|*bp++>>mask&1;
	*blue =*blue <<1|*bp++>>mask&1;
       }
       red++;
       green++;
       blue++;
      }
     }
     if(bdev->compression==cmpByteRun1) {
      for(plane=bdev->color_info.depth,planep=output;
          plane>0;
	  plane--,planep+=outputw) {
       packedbytes=packrow(planep,pack,outputw);
       WriteChunkBytes(bdev->iff,pack,packedbytes); 
      }
     }
     else {
      WriteChunkRecords(bdev->iff,output,outputw,bdev->color_info.depth);
     }
    } /* end for each line */
    PopChunk(bdev->iff);	/* Pop Body Chunk */
    gs_free(data,1,line_size,"ilbm24_print_page");
   }
   else  return -1;
   gs_free(output,8,outputw,"ilbm24_print_page");
  }
  else return -1;
  if(bdev->compression==cmpByteRun1) {
   gs_free(pack,1,pack_size,"ilbm24_print_page");
  }
 }
 else return -1;
 return 0;
}

/*
 * Open the current page for printing.
 */
int
gdev_ilbm_open_file(gx_device * pdev)
{
 long ifferror;
 int ret=0;
 char *fname = bdev->fname;
 char pfname[sizeof(bdev->fname) + 10];

 if (strchr(fname, '%')) {
  sprintf(pfname, fname, bdev->page_count);
  fname = pfname;
 }
 if(IFFParseBase==NULL) {
  IFFParseBase=(struct IFFParseBase *)OpenLibrary("iffparse.library",33);
  if(IFFParseBase==NULL) {
   eprintf("gdev_ilbm_open_file(): cannot open iffparse.library\n");
   ret=-1;
  }
 }
 if(ret==0) {
  bdev->iff=AllocIFF();
  if(bdev->iff==NULL) {
   eprintf("gdev_ilbm_open_file(): cannot allocate IFFHandle\n");
   ret=-1;
  }
 }
 if(ret==0) {
  bdev->iff->iff_Stream=Open(fname,MODE_NEWFILE);
  if(bdev->iff->iff_Stream==NULL) {
   eprintf1("gdev_ilbm_open_file(): cannot open file %s for writing\n",fname);
   ret=-1;
  }
 }
 if(ret==0) {
  InitIFFasDOS(bdev->iff);
  ifferror=OpenIFF(bdev->iff,IFFF_WRITE);
  if(ifferror!=0) {
   eprintf1("gdev_ilbm_open_file(): iff-error %d\n",ifferror);
   ret=-1;
  }
 }
 if(ret==0) {
  PushChunk(bdev->iff,ID_ILBM,ID_FORM,IFFSIZE_UNKNOWN);
 }
 return ret;
}

/*
 * Close the current page.
 */
int
gdev_ilbm_close_file(gx_device *pdev)
{
 /* if (strchr(bdev->fname, '%')) { */
 PopChunk(bdev->iff);		/* Pop FORM chunk */
 CloseIFF(bdev->iff);
 if(bdev->iff->iff_Stream!=NULL) Close((BPTR)bdev->iff->iff_Stream);
 bdev->iff->iff_Stream = NULL;
 FreeIFF(bdev->iff);
 if(IFFParseBase!=NULL) {
  CloseLibrary((struct Library *)IFFParseBase);
  IFFParseBase=NULL;
 }
 /* } */
 return 0;
}

/*
 * Generic routine to send the page to the printer.
 *
 * Note that num_copies is currently ignored: this is wrong. 
 */
int
gdev_ilbm_output_page(gx_device *pdev, int num_copies, int flush)
{
 int code;

 bdev->page_count++;
 if ((code = gdev_ilbm_open_file(pdev)) >= 0) {
  /* print the accumulated page description */
  if ((code= (*bdev->print_page) ((gx_device_printer *)pdev,NULL)) >= 0) {
   if ((code = gdev_ilbm_close_file(pdev)) >= 0) {
    if (bdev->buffer_space) {   /* reinitialize clist for writing */
     code = (*gs_clist_device_procs.output_page) (pdev, num_copies, 0);
    }
   }
  }
 }
 return code;
}


int
gdev_ilbm_open(gx_device * pdev)
{
 gdev_prn_open(pdev);

 bdev->iff=NULL;
 memset(bdev->palette,0,sizeof(bdev->palette));
 bdev->ncols = 0;
 bdev->compression=COMPRESSION;

 if(bdev->color_info.depth==8) { /* CLUT setup */
  gx_color_index color;
  byte r, g, b;

  /* allocate black & white as first 2 colors */
  SETRGB(  0,   0,   0);
  SETRGB(255, 255, 255);

  /* allocate evenly space color cube */
  while (bdev->ncols < 64) {
   color = bdev->ncols - 1;
   r = (color  & 0x03)    *((1<<bdev->color_info.depth)-1)/(4-1);
   g = ((color & 0x0c)>>2)*((1<<bdev->color_info.depth)-1)/(4-1);
   b = ((color & 0x30)>>4)*((1<<bdev->color_info.depth)-1)/(4-1);
   SETRGB(r, g, b);
  }
 }
}
