/****************************************************************************
 * hackppm: Ad-hoc program to adjust PPM files for webify - Steve Ward 12/95*
 * Usage: Operates as a UNIX filter, eg					    *
 *     cat file.ppm | hackppm <options> >newfile.ppm			    *
 *									    *
 * where <options> may include						    *
 *									    *
 * 	rotate n	rotate image n*90 degrees clockwise.		    *
 * 	clip # # # #	clip (make smaller) the image by removing specified *
 * 			number of pixels from top, bottom, left, and right  *
 * 			margins respectively.				    *
 *      negative        reverse the video				    *
 *									    *
 * This program is a quick hack to circumvent problems many users were	    *
 * having with webify; it isn't pretty.  One day I may find time to	    *
 * clean up hackppm, generalize its functions, and make it a useful	    *
 * tool in its own right.						    *
 ****************************************************************************/

#include <stdio.h>

int Rotation;			/* Number of 90-degree CW increments 	    */
int Negative;			/* 0 (normal) or -1 (reversed) video	    */
int ClipTop, ClipBot,		/* Pixels to take off each edge.	    */
    ClipLeft, ClipRight;

#define LINE 100

struct Header
 { char Magic[LINE];
   int width, height;
   unsigned int maxval;		/* Maximum value of each component	    */
   int size;			/* Size, in pixels (each RGB triple)	    */
   char binary;			/* Nonzero if binary format		    */
   char pxlsize;		/* 1=gray, 3=rgb			    */
   int *RGBData;		/* The pixel data array.		    */
 } In, Out;

/* Allocate a new raster:				*/
int *NewRaster(int h, int w, int pxlsize)
 { int size = w*h*pxlsize, *it;
   if (!(it = (int *) malloc(size*(sizeof *it))))
    { fprintf(stderr, "hackppm: Can't allocate %dx%dx%d raster!\n", h, w, pxlsize);
      exit(-1);
    }
   return it;
 }

/* Read a character, ignoring comments:						  */
int RdC(FILE *f)
 { int ch;
   for (ch=getc(f);;)
    { if (ch == '#')
      { while ((ch=getc(f)) != '\n') continue;  }
      else return ch;
    }
 }

int ReadInt(FILE *f)
 { int it=0, ch;
   while ((ch=RdC(f)) <= ' ')			/* Ignore leading whitespace.	*/
     if (ch == EOF) return 0;
   while (ch != EOF)
    { if ((ch >= '0') && (ch <= '9'))
       { it *= 10; it += ch-'0'; ch = RdC(f);
       }
      else return it;
    }
   return 0;
 }

void ReadPPMHeader(FILE *f, struct Header *h)
 { h->Magic[0] = RdC(f);
   h->Magic[1] = RdC(f);
   h->binary = 0;
   if (h->Magic[0] != 'P') goto bad;
   switch (h->Magic[1])
    { case '5': h->binary = 1;			/* Binary, Gray			   */
      case '2': h->pxlsize = 1; break;		/* Text-format, grayscale	   */
      case '6': h->binary = 1;			/* Binary, RGB			   */
      case '3': h->pxlsize = 3; break;		/* Text-format, RGB		   */

      case '4':	h->binary = 1;			/* special case: 1-bit mono	   */
      		h->maxval = 1;
      		h->pxlsize = 1;
      		h->width = ReadInt(f);
      		h->height = ReadInt(f);
      		h->size = (h->height) * (h->width);
      		return;
      default: goto bad;
    }
   h->width = ReadInt(f);
   h->height = ReadInt(f);
   h->maxval = ReadInt(f);
   h->size = (h->height) * (h->width);
   return;
bad:
   fprintf(stderr, "hackppm: Unknown format in PPM file header: %s\n", h->Magic);
   exit(-1);
 }

void WritePPMFile(FILE *f, struct Header *h)
 { int n, *p, c;

   /* Special case 1-bit mono...						*/
   if ((h->Magic[1]) == '4')
   	h->Magic[1] = '5';		/* FORCE TO MODE P5! */

/****************************************************************************
 * Code to write out a P4-format file -- Abandoned 10/22/95 SAW		    *
 * (since cjpeg doesn't seem to accept P4-format files)			    *
 **************************************************************************s!
 *    { int bits=0, dx = h->width, dy = h->height, x, y;		    *
 *									    *
 *      fprintf(f, "P4 %d %d\n", dx, dy);				    *
 *      for (y=0, p=h->RGBData; y<dy; y++)				    *
 *       { for (x=0; x<dx;)						    *
 * 	  { bits <<= 1; bits |= (*p++ & 1);				    *
 * 	    if (!(++x & 7)) { putc(bits, f); bits = 0; }		    *
 * 	  }								    *
 * 	 if (dx&7) { bits <<= (8-(dx&7)); putc(bits, f); }		    *
 *       }								    *
 *      return;								    *
 *    }									    *
 ****************************************************************************/

   if (h->binary)
    { fprintf(f, "%s %u %u %u ",
	      h->Magic, h->width, h->height, h->maxval);
    }
   else
    { fprintf(f, "%s\n%s\n%u %u\n%u\n",
	   h->Magic, "# Generated by hackppm",
	   h->width, h->height,
	   h->maxval, h->size);
    }

   for (n=0, p=h->RGBData; n<h->size; n++)
    { for (c=0; c<(h->pxlsize); c++)
       { int val = *p++;
	 if (Negative) val = h->maxval - val;
	 if (h->binary) 
	  { if ((h->maxval) < 256) putc(val, f);
	    else putw(val, f);
	  }
	 else fprintf(f, " %d", val);
       }
      if (!(h->binary))fprintf(f, "\n");
    }
 }

LoadPixels(FILE *f, struct Header *h)
 { int *raster, *p, pixels, c;
   raster = NewRaster(h->width, h->height, h->pxlsize);

   if (h->binary)	
    { if ((h->Magic[1]) == '4')		/* 1-bit pixels			*/
       { int x, y, dx = h->width, dy = h->height;
	 for (y=0, p=raster; y<dy; y++)
	  { int bits;
	    for (x=0; x<dx;)
	     { if ((x++ & 7) == 0)
	       { if (feof(f))
		  { fprintf(stderr, "hackppm: Input file too short!\n");
		    exit(-1);
		  }
		 bits = getc(f) & 0xFF;
	       }
	       if (bits & 0x80) *p++ = 0; else *p++ = 1;
	       bits <<= 1;

	     }
	  }
 	/* h->Magic[1] = '5'; /* FUDGE */
       }

      else if (h->maxval < 256)	   	/* 8-bit pixels			*/
       { for (p=raster, pixels=0; pixels<(h->size); pixels++)
	  { for (c=0; c<(h->pxlsize); c++) *p++ = getc(f)&0xFF;
	    if (feof(f))
	     { fprintf(stderr, "hackppm: Input file too short!\n");
	       exit(-1);
	     }
	  }
       }

      else				/* 32-bit pixels		*/
       { for (p=raster, pixels=0; pixels<(h->size); pixels++)
	  { for (c=0; c<(h->pxlsize); c++) *p++ = getw(f);
	    if (feof(f))
	     { fprintf(stderr, "hackppm: Input file too short!\n");
	       exit(-1);
	     }
	  }
       }
    }
   else
    { for (p=raster, pixels=0; pixels<(h->size); pixels++)
       { for (c=0; c<(h->pxlsize); c++) *p++ = ReadInt(f);
	 if (feof(f))
	  { fprintf(stderr, "hackppm: Input file too short!\n");
	    exit(-1);
	  }
       }
    }

   getc(f);
   if (!feof(f))
    { int x = 0;
      fprintf(stderr, "hackppm: input file too long by ");
      do {getc(f); x++; } while (!feof(f));
      fprintf(stderr, "%d bytes!\n", x);
      exit(-1);
    }
   h->RGBData = raster;
 }

int main(int argc, char **argv)
 { int argn;
   char *arg;
   for (argn=1; argn<argc; argn++)
    { arg = argv[argn];
      if (*arg == '-')
       { switch(*++arg)
	  { case 'h': Usage(); return 0;
	    default: fprintf(stderr, "hackppm: illegal option %s\n",
			     argv[argn]);
	    	     exit(-1);
	  }
       }
      if (*arg == 'r') Rotation = atoi(argv[++argn]);
      if (*arg == 'n') Negative = -1;
      if (*arg == 'c')
       { ClipTop = atoi(argv[++argn]);
         ClipBot = atoi(argv[++argn]);
         ClipLeft = atoi(argv[++argn]);
         ClipRight = atoi(argv[++argn]);
       }
    }
   ReadPPMHeader(stdin, &In);

   LoadPixels(stdin, &In);
   if (Rotation)
    { int i = Rotation & 3;
      while (i--) Rotate(&In);
    }
   if (ClipTop||ClipBot||ClipLeft||ClipRight) Clip(&In);
   WritePPMFile(stdout, &In);

   return 0;
 }


/* Rotate image 90 degrees clockwise:
 */
Rotate(struct Header *h)
 { int *new = NewRaster(h->width, h->height, h->pxlsize), *from, *to;
   int x, y, nx, ny, temp, c, W=h->height, H=h->width, P=h->pxlsize;

   from = h->RGBData;
   for (y=0; y<(h->height); y++)
    for (x=0; x<(h->width); x++)
     { nx = W - y - 1;
       ny = x;
       to = &(new[P*(W*ny+nx)]);
       for (c=0; c<P; c++) *to++ = *from++;
     }

   h->width = W; h->height = H;
   free(h->RGBData);
   h->RGBData = new;
 }

/* Adjust margins per ClipTop, etc
 */
Clip(struct Header *h)
 { int W = h->width - ClipLeft - ClipRight;
   int H = h->height - ClipTop - ClipBot;
   int *new = NewRaster(W,H,h->pxlsize), *old, *from, *to;
   int x, y, nx, ny, temp, c, P=h->pxlsize;

   old = h->RGBData;

   for (y=0; y<(h->height); y++)
    { if (y < ClipTop) continue;
      ny = y-ClipTop;
      if (ny >= H) continue;
       { for (x=ClipLeft; x<(W+ClipLeft); x++)
	  { from = &(old[P*(y*(h->width)+x)]);
	    nx = x-ClipLeft;
	    to = &(new[P*(ny*W+nx)]);
	    for (c=0; c<P; c++) *to++ = *from++;
	  }
       }
    }

   h->width = W; h->height = H; h->size = H*W;
   free(old);
   h->RGBData = new;
 }

Usage()
 { fprintf(stdout,
	   "hackppm: support program for ps2html\n");
 }
