/* writeip.c
 *
 * Module: writeip
 * Owner: knox
 * stdin: image internal format
 * args:
 *   name         (name of the output Interpress file)
 *   resolution   (resolution of the destination printer)
 *   compression  (type of compression, if any)
 *
 * Description:
 *    This program reads internal format images and puts them
 *    into Interpress files.  The input internal memory image is read
 *    from standard input.  The name of the output Interpress file is
 *    the first argument of the command line.
 *
 *    The image raster will be stored in the file "name.ip",
 *    where name is read from the command line.  The ".ip"
 *    extension will not be added if it is already present in the
 *    name.  The mask raster present in the internal image will
 *    be applied to the image data in the file.  The "oneiswhite" internal
 *    format data will be inverted to the "oneisblack" Interpress form.
 *    Both the image and mask rasters must be 1 bit/pixel.
 *
 *    The second argument, if present, is the resolution of the
 *    printer on which the image will be printed.  The resolution
 *    is a decimal number representing spots/inch.  The default value
 *    is 300 spots/inch.
 *
 *    If the third argument is present and is the string "compressed",
 *    then each output image strip will be written in the "compressed"
 *    format, formerly called IMG.  If there is no string, then the
 *    image strip is written in "packed pixel" format.
 *
 *    All scanlines are padded out with zeroes to a multiple of 32 bits,
 *    i.e. the number of pixels is rounded up to a multiple of 32.
 *    This solves two problems simultaneously.  The first is that
 *    Print Services 8.0 and earlier assume packed pixel vectors
 *    to be padded to 16 bit boundaries.  Print Services 9.0 and later
 *    assume packed pixel vectors to be padded to 32 bit boundaries.
 *    By rounding the number of pixels to 32, we can satisfy both of them
 *    at the same time.  The second problem is that IMG ("compressed")
 *    compression requires the number of pixels to be a multiple of 8.
 *    By making the number of pixels to be a multiple of 32, we can satisfy
 *    IMG too.
 *
 *
 */

#include <internal.h>
#include <iptokens.h>
#include <stdio.h>

extern unsigned char *malloc();
extern struct header *getheader();
extern long ftell();

#define err0 "usage: writeip filename [resolution]\n"
#define err1 "writeip: Needs one resolution parameter, got %d!\n"
#define err2 "writeip: Resolution cannot be zero or negative, got %d!\n"
#define err3 "writeip: Output Interpress file could not be created, %s!\n"
#define err4 "writeip: Number of scanlines, %d, > 8.5*resolution!\n"
#define err5 "writeip: Number of pixels, %d, > 11*resolution!\n"

struct header *input;
char * program;
int resolution;
int compression;
int fdin;
FILE *fpout;
char *filename;
int npixels;
int nscanlines;

int tb←nzeroes[256];
int tb←nibble[256];
int tb←pos[256];
unsigned char tb←codes[4096];
unsigned char tb←lengths[1024];

#define max(a, b) (a > b ? a : b)

#define COMPRESSION←PACKED   0
#define COMPRESSION←IMG      1

main(argc, argv)
  int argc;
  char *argv[];
  {
  program = argv[0];
  getargs(argc, argv);
  input = getheader(fdin);
  checkheader(argc, argv);
  writefile();
  exit(0);
  }

getargs(argc, argv)
  int argc;
  char *argv[];
  {
  int n;
  fdin = 0;
  if (argc < 2) error(err0);
  resolution = 300;
  compression = COMPRESSION←PACKED;
  if (argc < 3) return;
  n = sscanf(argv[2], "%d", &resolution);
  if (n != 1) error(err1, n);
  if (resolution == 0 || resolution < 0) error(err2, resolution);
  if (argc < 4) return;
  if (strcmp(argv[3], "compressed") == 0)
    {
    compression = COMPRESSION←IMG;
    inittables();
    }
  }

checkheader(argc, argv)
  int argc;
  char *argv[];
  {
  /* Variables. */
  int width, depth;

  /* Check for errors in the input header using library routines. */
  check(input, program);
  restrict(input, program, "imageExists", "1");
  restrict(input, program, "bitsperPixel", "1");
  restrict(input, program, "maskbitsperPixel", "1");
  restrict(input, program, "signed", "0");
  restrict(input, program, "trcLength", "0");

  /* Check the size of the image. */
  npixels = 32*((input->pixels+31)/32);
  nscanlines = input->scanlines;
  if (nscanlines > 17*resolution/2) error(err4, nscanlines);
  if (npixels > 11*resolution) error(err5, npixels);
 
  /* Make output RES file. */
  filename = (char *) malloc(strlen(argv[1])+1+strlen(".ip"));
  strcpy(filename, argv[1]);
  if (strcmp(".ip", strrchr(filename, '.')) != 0) strcat(filename, ".ip");
  fpout = fopen(filename, "w");
  if (fpout == NULL) error(err3, filename);
  }

writefile()
  {
  /* variables */
  int n, bytesperSL, nblocks, blocksize, total;
  unsigned char *image, *mask;

  /* start Interpress file */
  AppendHeader(fpout, IP←Header);
  AppendOp(OP←beginBlock);          /* BEGIN */
  AppendOp(OP←beginBody);           /* { */
  AppendOp(OP←endBody);             /* } */
  AppendOp(OP←beginBody);           /* { */
  AppendRational(254, 10000*resolution);     /* convert inches to meters */
  AppendOp(OP←scale);
  AppendOp(OP←concatt);
  AppendInteger(17*resolution/4);   /* xDimension, middle of the page */
  AppendInteger(11*resolution/2);   /* yDimension, middle of the page */
  AppendOp(OP←setxy);
  AppendRational(-1, 2);
  AppendOp(OP←scale);
  AppendOp(OP←concatt);
  AppendInteger(nscanlines);        /* xDimension of the image */
  AppendInteger(npixels);           /* yDimension of the image */
  AppendOp(OP←setxyrel);
  AppendRational(-2, 1);
  AppendOp(OP←scale);
  AppendOp(OP←concatt);

  /* npixels must always be a multiple of 32. */
  bytesperSL = npixels/8;

  /* allocate buffer space for scanlines */
  image = malloc(max(bytesperSL, input->bytesperSL));
  if (input->maskExists) mask = malloc(input->maskbytesperSL);

  /* send the raster data in blocks */
  total = nscanlines;
  blocksize = (64*1024-4-16)/bytesperSL;
  nblocks = nscanlines/blocksize;
  for (n=0; n < nblocks; n++)
    {
    writepixelarray(image, mask, blocksize, bytesperSL);
    total -= blocksize;
    }
  if (total > 0) writepixelarray(image, mask, total, bytesperSL);
  free(image);
  if (input->maskExists) free(mask);
 
  /* wrap up the rest of the Interpress file. */
  AppendOp(OP←endBody);             /* } */
  AppendOp(OP←endBlock);            /* END */
  }

writepixelarray(image, mask, blocksize, bytesperSL)
  unsigned char *image, *mask;
  int blocksize, bytesperSL;
  {
  int n, m;
  AppendInteger(blocksize);         /* xPixels */
  AppendInteger(npixels);           /* yPixels */
  AppendInteger(1);                 /* samples/pixel */
  AppendInteger(1);                 /* max sample value */
  AppendInteger(0);                 /* samples interleaved */
  AppendInteger(1);
  AppendOp(OP←scale);               /* transformation */
  if (compression == COMPRESSION←PACKED)
    writepackedpixels(image, mask, blocksize, bytesperSL);
  else
    writecompressedpixels(image, mask, blocksize, bytesperSL);
  AppendOp(OP←makepixelarray);      /* maskImage */
  AppendOp(OP←trans);
  AppendOp(OP←maskpixel);           /* image the mask */
  AppendInteger(blocksize);         /* xDimension of the image */
  AppendOp(OP←setxrel);
  }

writepackedpixels(image, mask, blocksize, bytesperSL)
  unsigned char *image, *mask;
  int blocksize, bytesperSL;
  {
  int n, m;
  AppendPPV(4+bytesperSL*blocksize, input->bitsperPixel, npixels);
  for (n=0; n < blocksize; n++)
    {
    readscanline(image, mask);
    fwrite(image, 1, bytesperSL, fpout);
    }
  }


#define IMG←RAW 0
#define IMG←BITBACK 1
#define IMG←ENCODED 2
#define IMG←BYTEBACK 3
#define IMG←SOI 0x70
#define IMG←EOI 0x71

writecompressedpixels(image, mask, blocksize, bytesperSL)
  unsigned char *image, *mask;
  int blocksize, bytesperSL;
  {
  long start, end, data, total;
  int n, m, breakcount, nbytes, nibbles, mode;
  int rawlen, bitbacklen, encodedlen, bytebacklen;
  unsigned char *previous, *bitback, *encoded, *byteback, *temp, *choice;
  previous = malloc(input->bytesperSL+2);
  bitback = malloc(input->bytesperSL+2);  /* Two bytes used in makeencoded(). */
  encoded = malloc(input->bytesperSL+2);
  byteback = malloc(input->bytesperSL+2);
  temp = malloc(input->bytesperSL+2);
  start = ftell(fpout);
  nbytes = npixels/8;
  AppendCPV(256, 0, 8, npixels); /* The 256 guarantees a LONG SEQUENCE. */
  data = ftell(fpout);
  writenibbles(IMG←SOI);
  breakcount=16;
  rawlen = 2*nbytes;
  for (n=0; n < blocksize; n++)
    {
    /* Encode in each of the four ways. */
    readscanline(image, mask);
    if (breakcount >= 16) bitbacklen = rawlen+1;   /* i.e. don't use this mode. */
    else bitbacklen = makebitback(image, previous, bitback, temp, nbytes);
    encodedlen = makeencoded(image, encoded, nbytes);
    bytebacklen = makebyteback(image, byteback, temp, nbytes);
    bcopy(image, previous, input->bytesperSL);

    /* Decide which is the shortest encoding and write it out. */
    mode = IMG←RAW;
    nibbles = rawlen;
    choice = image;
    if (encodedlen < nibbles)
      { mode = IMG←ENCODED; nibbles = encodedlen; choice = encoded; }
    if (bitbacklen < nibbles)
      { mode = IMG←BITBACK; nibbles = bitbacklen; choice = bitback; }
    if (bytebacklen < nibbles)
      { mode = IMG←BYTEBACK; nibbles = bytebacklen; choice = byteback; }
    if (mode == IMG←BITBACK) breakcount++;
    else breakcount = 1;
    writenibbles(mode, nibbles, choice);
    }
  writenibbles(IMG←EOI);
  end = ftell(fpout);
  total = end-data+6;  /* add in length of breaktable, range and scanLength. */
  if (total < 256)     /* A long sequence requires at least 256 data bytes. */
    {
    for (n=total; n < 256; n++) putc(0, fpout);
    total = 256;
    end = data+256-6;
    }
  if ((total & 1) == 1)  /* Interpress requires all pixel vectors be even. */
    {
    putc(0, fpout);
    total++;
    end++;
    }
  fseek(fpout, start, 0);
  AppendCPV(total, 0, 8, npixels);
  fseek(fpout, end, 0);
  free(previous);
  free(bitback);
  free(encoded);
  free(byteback);
  free(temp);
  }

readscanline(image, mask)
  unsigned char *image, *mask;
  {
  int n, nbytes, remainder, bytesperSL, endbytes;
  static int endmask[8] = { 0377, 0200, 0300, 0340, 0360, 0370, 0374, 0376 };
  bytesperSL = npixels/8;
  nbytes = (input->pixels+7)/8;
  remainder = input->pixels & 7;
  endbytes = bytesperSL-nbytes;
  readbytes(fdin, image, input->bytesperSL);
  for (n=0; n < input->bytesperSL; n++) image[n] = ~image[n];
  if (input->maskExists)
    {
    readbytes(fdin, mask, input->maskbytesperSL);
    hardmask(image, mask, nbytes);
    }
  if (endbytes) for (n=0; n < endbytes; n++) image[nbytes+n] = 0;
  if (remainder == 0) return;
  image[nbytes-1] &= endmask[remainder];
  }

makebitback(image, previous, out, temp, nbytes)
  unsigned char *image, *previous, *out, *temp;
  int nbytes;
  {
  int n;
  for (n=0; n < nbytes; n++) temp[n] = image[n] ↑ previous[n];
  return(makeencoded(temp, out, nbytes));
  }

makebyteback(image, out, temp, nbytes)
  unsigned char *image, *out, *temp;
  int nbytes;
  {
  int n;
  temp[0] = image[0];
  for (n=1; n < nbytes; n++) temp[n] = image[n] ↑ image[n-1];
  return(makeencoded(temp, out, nbytes));
  }

makeencoded(image, out, nbytes)
  unsigned char *image, *out;
  int nbytes;
  {
  int pos, nibble, nzeroes, limit;
  int result, unitsof64;
  limit = 2*nbytes;
  result = 0;
  pos = 0;
  *out = 0;
  while (1)
    {
    findnibble(image, &pos, &nibble, &nzeroes, nbytes);
    if (nibble == 0) break;
    unitsof64 = nzeroes >> 6;
    if (unitsof64)
       {
       while (unitsof64 > 63)
         {
	 result += appendcodes(out, result, 0, 63, limit);
	 unitsof64 -= 63;
	 }
       if (unitsof64) result += appendcodes(out, result, 0, unitsof64, limit);
       }
     result += appendcodes(out, result, nibble, nzeroes & 63, limit);
    }
  if (result < limit) return(result);
  else return(limit+1);
  }

appendcodes(out, loc, nibble, nzeroes, limit)
  unsigned char *out;
  int loc, nibble, nzeroes, limit;
  {
  int initial, offset;
  unsigned char *codeptr;
  if (loc >= limit) return(0);
  out += loc/2;
  offset = (nzeroes << 4)+nibble;
  codeptr = tb←codes+(offset << 2);
  initial = 0;
  if (loc & 1)
    {
    codeptr += 2;
    initial = *out;
    }
  *out++ = initial+*codeptr++;
  *out = *codeptr;
  return(tb←lengths[offset]);
  }


findnibble(image, posptr, nibbleptr, nzeroesptr, nbytes)
  unsigned char *image;
  int *posptr, *nibbleptr, *nzeroesptr, nbytes;
  {
  int n, v;
  *nzeroesptr = 0;
  *nibbleptr = 0;
  if ((*posptr & 1) == 1)
    {
    *nibbleptr = 15 & image[(*posptr)/2];
    *posptr += 1;
    if (*nibbleptr) return;
    *nzeroesptr = 1;
    }
  for (n=(*posptr)/2; n < nbytes; n++)
    {
    v = image[n];
    if (v == 0)
      {
      *nzeroesptr += 2;
      *posptr += 2;
      }
    else
      {
      *nzeroesptr += tb←nzeroes[v];
      *nibbleptr = tb←nibble[v];
      *posptr += tb←pos[v];
      return;
      }
    }
  }


writenibbles(mode, length, buffer)
  int mode, length;
  unsigned char *buffer;
  {
  static number = 0;
  static int leftovers = 0;
  static int nibble = 0;
  int nbytes;
  switch (mode)
    {
    case IMG←SOI:
      fputc(IMG←SOI, fpout);
      leftovers = 0;
      return;
    case IMG←EOI:
      if (leftovers)
        {
        fputc(nibble+(IMG←EOI >> 4), fpout);
        fputc((IMG←EOI & 15) << 4, fpout);
        }
      else fputc(IMG←EOI, fpout);
      return;
    default:
      if (leftovers)
        {
        fputc(nibble+(mode >> 4), fpout);
	if (length)
	  {
          fputc(((mode & 15) << 4)+(buffer[0] >> 4), fpout);
	  length--;
	  leftshift(buffer, length);
	  leftovers = 0;
	  }
	else
	  {
	  nibble = (mode & 15) << 4;
	  leftovers = 1;
	  }
        }
      else fputc(mode, fpout);
      nbytes = length/2;
      if (nbytes) fwrite(buffer, 1, nbytes, fpout);
      if (length & 1)
        {
	leftovers = 1;
	nibble = buffer[nbytes];
	}
    }
  }

leftshift(buffer, length)
  unsigned char *buffer;
  int length;
  {
  int n;
  for (n=0; n < length/2; n++)
    {
    *buffer++ = ((*buffer << 4)+(*(buffer+1) >> 4)) & 255;
    }
  if (length & 1) *buffer = (*buffer << 4) & 255;
  }


inittables()
  {
  int n, nzeroes, nibble, temp;
  for (n=1; n < 16; n++)
    {
    tb←nzeroes[n] = 1;
    tb←nibble[n] = n & 15;
    tb←pos[n] = 2;
    }
  for (n=16; n < 256; n++)
    {
    tb←nzeroes[n] = 0;
    tb←nibble[n] = (n >> 4) & 15;
    tb←pos[n] = 1;
    }
  bzero(tb←codes, 4096);
  for (n=0; n < 1024; n++)
    {
    nzeroes = (n >> 4) & 63;
    nibble = n & 15;
    if (nibble == 8 || nibble == 4 || nibble == 2 || nibble == 1)
      {
      if (nzeroes == 0)
        {
	tb←lengths[n] = 1;
	temp = 8+Axx(nibble);
	code12bits(tb←codes+4*n, temp << 8);
        }
      else
        {
	if (nzeroes > 25)
          {
	  tb←lengths[n] = 3;
	  temp = 3072+(nzeroes << 4)+nibble;
	  code12bits(tb←codes+4*n, temp);
          }
        else
          {
	  tb←lengths[n] = 2;
	  temp = (nzeroes << 2)+Axx(nibble);
	  code12bits(tb←codes+4*n, temp << 4);
          }
        }
      }
    else
      {
      if (nzeroes > 1 || nibble == 0)
        {
	tb←lengths[n] = 3;
	temp = 3072+(nzeroes << 4)+nibble;
	code12bits(tb←codes+4*n, temp);
        }
      else
        {
	tb←lengths[n] = 2;
	if (nibble == 3) nibble = 4;
	temp = 96+(nibble << 1)+nzeroes;
	code12bits(tb←codes+4*n, temp << 4);
        }
      }
    }
  }

Axx(nibble)
  int nibble;
  {
  if (nibble == 8) return(0);
  if (nibble == 4) return(1);
  if (nibble == 2) return(2);
  return(3);
  }

code12bits(table, code)
  unsigned char *table;
  int code;
  {
  *table++ = (code >> 4) & 255;
  *table++ = (code << 4) & 240;
  *table++ = (code >> 8) & 15;
  *table++ = code & 255;
  }


/* Change Log
 *
 * K. Knox, 21-Feb-85 18:48:54, Created first version.
 * K. Knox,  7-May-85 15:25:19, Added padding parameter.
 * K. Knox,  6-Jun-85 15:19:57, Corrected padding parameter, did 32 bits wrong.
 * K. Knox,  1-Nov-85 17:47:54, Added IMG compression, removed padding parameter.
 * K. Knox,  6-Dec-85  9:04:08, Interpress requires all pixel vectors be even.
 * K. Knox,  6-Dec-85  9:04:08, Changed "IMG" to "compressed".
 *
 *
 */