
/*
 * jslice.c
 *
 * This program will take a slice of a given picture and save it to
 * a second file name
 *
 */

/*
 * CHANGES
 * 
 * 31/05/2000 - Added JavaScript enhancements and direct embedding
 * 21/05/2000 - Added image-dimentions
 * 25/05/2000 - Fixed up hyperlink detection from slice file
 *
 */ 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

/*
 * Include file for users of JPEG library.
 * You will need to have included system headers that define at least
 * the typedefs FILE and size_t before you can include jpeglib.h.
 * (stdio.h is sufficient on ANSI-conforming systems.)
 * You may also wish to include "jerror.h".
 */

#include "jpeglib.h"
#include <setjmp.h>

JSAMPROW * arow;	/* points to a row of JSAMPLES*/
JSAMPLE * buffer;	/* Points to large array of R,G,B-order data */
JSAMPLE * image;
JSAMPROW wimage[4097]; /* array of rows for the "whole" image */
JSAMPROW pimage[4097]; /* array of rows for the "partial" image */

#define _VERSION "1.0.0"
#define _MESSAGE "<!-- ----------------------------------------\n\
	Created with the assistance of jslice, v1.0.0 \n\
	written by Paul L Daniels\n\
	pldaniels@pldaniels.com\n\
	http://www.pldaniels.com/jslice\n\
	(C) P.L.Daniels 2000\n\
	------------------------------------------ -->\n\n"
#define _HELP "jslice v1.0.0 - (C) Paul L Daniels 2000\n\n\
	Options Available -\n\
	-i Input JPEG file [ie frontPage.jpg]\n\
	-p Ouput JPEG file prefix [ie fpactive]\n\
	-c Cut/Slice file [ie frontpage.slices]\n\
	-q Quality of JPEG output [ie 85]\n\
	-h OPTIONAL - Header file to prefix to HTML output [ie header.html]\n\
	-f OPTIONAL - Footer file to suffix to HTML output [ie footer.html]\n\
	--help produce this help.\n\
	\n\
	example of using jslice...\n\
		jslice -i frontpage_active.jpg -p fpa -c frontpage.slices -q 75 -h header.html -f footer.html > index.html\n\n\
	end of help.\n\n"
#define MAX_HEIGHT 4096
#define MAX_WIDTH 4096
#define MAX_LINK_SIZE 1024
#define MAX_SLICES 1000

#define _CUT 1
#define _UNCUT 0

#define _QUALITY_DEFAULT 75

#define _ERR_CANTOPENJPEG 101
#define _ERR_CANTWRITEJPEG 102
#define _ERR_CANTREADSCRIPT 103
#define _ERR_INSUFFICENTMEM 200

int image_height;	/* Number of rows in image */
int image_width;		/* Number of columns in image */
int p_height; 	/* width of image after cut */
int p_width;  	/* height of image after cut */


/* define a structure that will keep the details of each box slice.
*/

struct _aslice {

	int ax, bx, ay, by;
	char link[MAX_LINK_SIZE+1];
	};

typedef struct _aslice _tslice;
int slicesx[MAX_WIDTH];
int slicesy[MAX_HEIGHT];


/* And of course, create a global variable out of it.
*/

_tslice slices[MAX_SLICES];


GLOBAL(void)
write_JPEG_file (char * filename, int quality)
{
  /* This struct contains the JPEG compression parameters and pointers to
   * working space (which is allocated as needed by the JPEG library).
   * It is possible to have several such structures, representing multiple
   * compression/decompression processes, in existence at once.  We refer
   * to any one struct (and its associated working data) as a "JPEG object".
   */
  struct jpeg_compress_struct cinfo;
  /* This struct represents a JPEG error handler.  It is declared separately
   * because applications often want to supply a specialized error handler
   * (see the second half of this file for an example).  But here we just
   * take the easy way out and use the standard error handler, which will
   * print a message on stderr and call exit() if compression fails.
   * Note that this struct must live as long as the main JPEG parameter
   * struct, to avoid dangling-pointer problems.
   */
  struct jpeg_error_mgr jerr;
  /* More stuff */
  FILE * outfile;		/* target file */
  JSAMPROW row_pointer[1];	/* pointer to JSAMPLE row[s] */
  int row_stride;		/* physical row width in image buffer */

  /* Step 1: allocate and initialize JPEG compression object */

  /* We have to set up the error handler first, in case the initialization
   * step fails.  (Unlikely, but it could happen if you are out of memory.)
   * This routine fills in the contents of struct jerr, and returns jerr's
   * address which we place into the link field in cinfo.
   */
  cinfo.err = jpeg_std_error(&jerr);
  /* Now we can initialize the JPEG compression object. */
  jpeg_create_compress(&cinfo);

  /* Step 2: specify data destination (eg, a file) */
  /* Note: steps 2 and 3 can be done in either order. */

  /* Here we use the library-supplied code to send compressed data to a
   * stdio stream.  You can also write your own code to do something else.
   * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that
   * requires it in order to write binary files.
   */
  if ((outfile = fopen(filename, "wb")) == NULL) {
    fprintf(stderr, "can't open %s\n", filename);
    exit(_ERR_CANTWRITEJPEG);
  }
  jpeg_stdio_dest(&cinfo, outfile);

  /* Step 3: set parameters for compression */

  /* First we supply a description of the input image.
   * Four fields of the cinfo struct must be filled in:
   */
  cinfo.image_width = p_width; 	/* image width and height, in pixels */
  cinfo.image_height = p_height;
  cinfo.input_components = 3;		/* # of color components per pixel */
  cinfo.in_color_space = JCS_RGB; 	/* colorspace of input image */
  /* Now use the library's routine to set default compression parameters.
   * (You must set at least cinfo.in_color_space before calling this,
   * since the defaults depend on the source color space.)
   */
  jpeg_set_defaults(&cinfo);
  /* Now you can set any non-default parameters you wish to.
   * Here we just illustrate the use of quality (quantization table) scaling:
   */
  jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);

  /* Step 4: Start compressor */

  /* TRUE ensures that we will write a complete interchange-JPEG file.
   * Pass TRUE unless you are very sure of what you're doing.
   */
  jpeg_start_compress(&cinfo, TRUE);

  /* Step 5: while (scan lines remain to be written) */
  /*           jpeg_write_scanlines(...); */

  /* Here we use the library's state variable cinfo.next_scanline as the
   * loop counter, so that we don't have to keep track ourselves.
   * To keep things simple, we pass one scanline per call; you can pass
   * more if you wish, though.
   */
  row_stride = image_width * 3;	/* JSAMPLEs per row in image_buffer */

  while (cinfo.next_scanline < cinfo.image_height) {
    /* jpeg_write_scanlines expects an array of pointers to scanlines.
     * Here the array is only one element long, but you could pass
     * more than one scanline at a time if that's more convenient.
     */
		 row_pointer[0] = pimage[cinfo.next_scanline];
    (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
  }

  /* Step 6: Finish compression */

  jpeg_finish_compress(&cinfo);
  /* After finish_compress, we can close the output file. */
  fclose(outfile);

  /* Step 7: release JPEG compression object */

  /* This is an important step since it will release a good deal of memory. */
  jpeg_destroy_compress(&cinfo);

  /* And we're done! */
}


/*
 * Here's the extended error handler struct:
 */

struct my_error_mgr {
  struct jpeg_error_mgr pub;	/* "public" fields */

  jmp_buf setjmp_buffer;	/* for return to caller */
};

typedef struct my_error_mgr * my_error_ptr;

/*
 * Here's the routine that will replace the standard error_exit method:
 */

METHODDEF(void)
my_error_exit (j_common_ptr cinfo)
{
  /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
  my_error_ptr myerr = (my_error_ptr) cinfo->err;

  /* Always display the message. */
  /* We could postpone this until after returning, if we chose. */
  (*cinfo->err->output_message) (cinfo);

  /* Return control to the setjmp point */
  longjmp(myerr->setjmp_buffer, 1);
}


/*
 * Sample routine for JPEG decompression.  We assume that the source file name
 * is passed in.  We want to return 1 on success, 0 on error.
 */


GLOBAL(int)
read_JPEG_file (char * filename)
{

	
  /* This struct contains the JPEG decompression parameters and pointers to
   * working space (which is allocated as needed by the JPEG library).
   */
  struct jpeg_decompress_struct cinfo;
  /* We use our private extension JPEG error handler.
   * Note that this struct must live as long as the main JPEG parameter
   * struct, to avoid dangling-pointer problems.
   */
  struct my_error_mgr jerr;
  /* More stuff */
  FILE * infile;		/* source file */
	JSAMPROW row_pointer[1];
	
  int row_stride;		/* physical row width in output buffer */
	

  if ((infile = fopen(filename, "rb")) == NULL) {
    fprintf(stderr, "can't open %s\n", filename);
    exit(_ERR_CANTOPENJPEG);
  }

  cinfo.err = jpeg_std_error(&jerr.pub);
  jerr.pub.error_exit = my_error_exit;

  if (setjmp(jerr.setjmp_buffer)) {
    jpeg_destroy_decompress(&cinfo);
    fclose(infile);
    exit(_ERR_INSUFFICENTMEM);
  }

  jpeg_create_decompress(&cinfo);

  jpeg_stdio_src(&cinfo, infile);

  (void) jpeg_read_header(&cinfo, TRUE);
  (void) jpeg_start_decompress(&cinfo);


	image_width = cinfo.output_width;
	image_height = cinfo.output_height;
	
  row_stride = cinfo.output_width * cinfo.output_components;	

		
  while (cinfo.output_scanline < cinfo.output_height) {

		wimage[cinfo.output_scanline] = malloc(sizeof(JSAMPLE) *(row_stride+1));
  	row_pointer[0] = wimage[cinfo.output_scanline];
		
    (void) jpeg_read_scanlines(&cinfo, row_pointer, 1);

    /* Assume put_scanline_someplace wants a pointer and sample count. */

	  }

  /* Step 7: Finish decompression */

  (void) jpeg_finish_decompress(&cinfo);
  /* We can ignore the return value since suspension is not possible
   * with the stdio data source.
   */

  /* Step 8: Release JPEG decompression object */

  /* This is an important step since it will release a good deal of memory. */
  jpeg_destroy_decompress(&cinfo);

  /* After finish_decompress, we can close the input file.
   * Here we postpone it until after no more JPEG errors are possible,
   * so as to simplify the setjmp error logic above.  (Actually, I don't
   * think that jpeg_destroy can do an error exit, but why assume anything...)
   */
  fclose(infile);

  /* At this point you may want to check to see whether any corrupt-data
   * warnings occurred (test whether jerr.pub.num_warnings is nonzero).
   */

  /* And we're done! */
  return 1;
}





int get_cut_count_x( int maxx ) {

	int count = 0;
	int x;

	for (x = 0; x < maxx; x++) {
		if (slicesx[x] != _UNCUT) count++;
		}

	return count;
	}


int get_cut_count_y( int maxy ) {

	int count = 0;
	int y;

	for (y = 0; y < maxy; y++) {
		if (slicesy[y] != _UNCUT) count++;
		}

	return count;
	}





int count_cuts( int sx, int fx ) {

	int cuts = 0;
	int cx;

	/* for every pixel across the horizontal...*/
	for (cx = sx; cx < fx; cx++){
	
		/* if we've found a cut mark...*/
		if (slicesx[cx] != _UNCUT) {
		
			/* increase our cut count */
			cuts++;

			/* if the next pixel is also cut, but from the same row
			 * then we must skip it, as it's just consecutive cutouts
			 */
			if (slicesx[cx+1] == slicesx[cx]) { 
				cx++;
				}
				
			} /* if slices != UNCUT */
			
		} /* for */
		
	return cuts;
	}





/* cut
 *
 * this procesdure "cuts" out the required portion of the picture.
 *
 */
 
int cut( int ax, int ay, int bx, int by ) {

	int cx, cy;

	/* get our dimentions of the image, 
	 * remember, that a image has a MINIMUM size of 1x1
	 * NOT 0x0
	 */	
	p_width = bx - ax +1;
	p_height = by - ay +1;
	
	/* for every row... */
	for (cy = 0; cy < p_height; cy++) {

		/* allocate the memory for the new row of the image */	
		pimage[cy] = malloc(sizeof(JSAMPLE) *(p_width*3));
		
		/* for every pixel */
		for (cx = 0; cx < (p_width*3); cx++) {
		
			/* copy across the pixels */
			pimage[cy][cx] = wimage[cy+ay][cx+(ax*3)];
			
			} /* for cx */
		} /* for cy */
		
	return 0;
	
	}
			
			
			
	/* 
	 * dump_slices
	 *
	 * Debugging and information routine
	 */
int dump_slices( void ) {

	int i;
	fprintf(stdout,"\n<!-- ");
	for (i = 0; i < MAX_WIDTH; i++){
		if (slicesx[i] != _UNCUT) fprintf(stdout,"%d, ",i);
	}
	fprintf(stdout," -->\n");

	return 0;
}





int script_cut( char *script_name, char *foutprefix, int quality ){

#define MAX 1023

	FILE *script;
	int count;
	int ax;
	int i;
	int slicewidth;
	int tally;
	int cols;
	int lastx;
	int ffr;
	int slicew, sliceh;
	int real_url = 0;
	int urltest;
	int linecount = 0;
	char line[1024];
	char foutname[1024];
	char *link;
	
	script = fopen(script_name,"r");
	
	/* 
	 * Make sure we don't try reading dud files 
	 */
	if (script == NULL) {
		fprintf(stderr,"Error when trying to open the slice-coordinate file %s\nPlease check to see that the file name is correct, and that it is a text only file\n",script_name);
		exit(_ERR_CANTREADSCRIPT);
		}
		
	count = 0;
	
	/* clean out the slices rulers */
	for (ax = 0; ax < MAX_WIDTH; ax++) {
		slicesx[ax] = _UNCUT;
		slicesy[ax] = _UNCUT;
		}

	/* get all our lines... */
	while (fgets(line, MAX, script) != NULL) {
	
		linecount++;
		
		/*
		 * Digest our line if it's not a comment 
		 */
		if ((line[0] != '#')&&(strlen(line) > 7)){

			/* get the coordinates... */
			slices[count].ax = atoi(strtok(line," "));
			if (slices[count].ax > image_width) {
				fprintf(stderr,"Line %d: X1 value of %d too large, reduced to %d\n",linecount,slices[count].ax,image_width);
				slices[count].ax = image_width;
				}
			if (slices[count].ax < 0) {
				fprintf(stderr,"Line %d: X1 value of %d too small, value now 0.\n",linecount,slices[count].ax);
				slices[count].ax = 0;
				}

		slices[count].ay = atoi(strtok(NULL," "));
			if (slices[count].ay >= image_height) {
				fprintf(stderr,"Line %d: Y1 value of %d too large, reduced to %d\n",linecount,slices[count].ay,image_height-2);
				slices[count].ay = image_height-2;
				}
			if (slices[count].ay < 0) {
				fprintf(stderr,"Line %d: Y1 value of %d too small, value now 0.\n",linecount,slices[count].ay);
				slices[count].ay = 0;
				}

		slices[count].bx = atoi(strtok(NULL," ")); 
			if (slices[count].bx > image_width) {
				fprintf(stderr,"Line %d: X2 value of %d too large, reduced to %d\n",linecount,slices[count].bx,image_width);
				slices[count].bx = image_width;
				}
			if (slices[count].bx < 0) {
				fprintf(stderr,"Line %d: X2 value of %d too small, value now 0.\n",linecount,slices[count].bx);
				slices[count].bx = 0;
				}

		slices[count].by = atoi(strtok(NULL," "));
			if (slices[count].by >= image_height) {
				fprintf(stderr,"Line %d: Y2 value of %d too large, reduced to %d\n",linecount,slices[count].by,image_height-2);
				slices[count].by = image_height-2;
				}
			if (slices[count].by < 0) {
				fprintf(stderr,"Line %d: Y2 value of %d too small, value now 0.\n",linecount,slices[count].by);
				slices[count].by = 0;
				}
		
		/* get our link... */
		link = strtok(NULL,"\n\r");
		
		/*
		 * Test to see that the URL string is potentially valid
		 *
		 */
		if (link != NULL) {
			for (urltest = 0; urltest < strlen(link); urltest++){
				real_url += isalnum(link[urltest]);
				}
			if (urltest > 0) strcpy(slices[count].link, link);
			}
		else slices[count].link[0] = '\0';
		

		/*
		 * Stamp in the slice arrays our marks where we have to slice
		 *
		 * 0.1.3 - We mark in the top-y value in the ax and bx here
		 * so that we can determin if we are counting one or two
		 * columns when we come to counting up the number of cuts
		 * between a given ax -> bx
		 *
		 */
		slicesx[slices[count].ax] = slices[count].ay +_CUT;
		slicesx[slices[count].bx] = slices[count].ay +_CUT;
		slicesy[slices[count].ay] = _CUT;
		slicesy[slices[count].by] = _CUT;

		count++;
		}
	}

	/*
	 * Let the HTML Table start flowing
	 *
	 */
	fprintf(stdout,"<TABLE border=\"0\" cellpadding=\"0\" cellspacing=\"0\"><TR>\n");
		
	/* 
	 * Print out our SLICING SPACERS! 
	 *
	 */
	slicewidth = tally = 1;
	for ( ax = 1; ax <= image_width; ax++ ){
		if (slicesx[ax] != _UNCUT) {
			if (slicesx[ax-1] != slicesx[ax]){
				fprintf(stdout,"<TD><img src=\"spacer.gif\" width=\"%d\" height=\"1\"></td>",slicewidth);
				tally += slicewidth;
				slicewidth=0;
				}
			}
		slicewidth++;
		}

	fprintf(stdout,"\n<!-- total width = %d -->\n",tally);
	fprintf(stdout,"</tr>\n<TR>\n");

	/* print out commentory information about the slices 
	 * to the HTML file
	 */
	dump_slices();

	lastx = -1;
	ffr = 1;
	tally = 0;
	
	for ( i = 0; i < count; i++ ){

		/* create our filename of the output slice */
		sprintf(foutname,"%s%d.jpg",foutprefix,i+1);
		
		/* determine how many columns we have for this slice */
		cols = count_cuts(slices[i].ax, slices[i].bx);

		/* Catch the scenario where the blocks wrap back around */
		if (lastx >= slices[i].ax){
			ffr = 1;
			fprintf(stdout,"</tr>\n<TR>\n");
		}

		lastx = slices[i].ax;


		slicew = slices[i].bx -slices[i].ax +1;
		sliceh = slices[i].by -slices[i].ay +1;
		
		if (slices[i].link[0] != '\0') {
			fprintf(stdout,"<TD colspan=\"%d\"><a href=\"%s\"><img src=\"%s\" border=\"0\" width=\"%d\" height=\"%d\"></a></td>\n",cols,slices[i].link,foutname,slicew,sliceh);
		} else {
		fprintf(stdout,"<TD colspan=\"%d\"><img src=\"%s\" border=\"0\" width=\"%d\" height=\"%d\"></td>\n",cols,foutname,slicew, sliceh);
		}

		cut(slices[i].ax, slices[i].ay, slices[i].bx, slices[i].by);

		write_JPEG_file(foutname,quality);

		ffr = 0;
	}

	fprintf(stdout,"\n<!-- total width = %d -->\n",tally);
	
	fprintf(stdout,"</tr></table>");
			


	return 0;
	}
	



	/*
	 * filetostdout
	 *
	 * prints a given file to stdout
	 *
	 */
int filetostdout( char *fname ){

	FILE *f = fopen(fname,"r");
	char achar;
	
	/* if the file opened okay... */
	if (f) {
		while ( (achar = fgetc(f))!= EOF) fprintf(stdout,"%c",achar);
		fclose(f);
		}
	else {
		fprintf(stderr,"Error: could not open file %s\n",fname);
		}
		
	return 0;
	}
	
			
	
/*
 * main
 *
 * The basic procedure is as follows...
 *
 * read in the input jpeg,
 * x1,y1 -> x2, y2 coord's 
 * output file name, and 
 * the quality factor
 *
 * read in the required file
 * copy the required "slice" into a new buffer
 * write the new buffer to the new file name
 *
 */
int main( int argc, char **argv ) {

	int quality;
	int aindex; /* argv index */
	char *infile=NULL, *outfile=NULL, *scriptfile=NULL, *headerfile=NULL, *footerfile=NULL;
	

/*	if (argc < 9) {
	
		fprintf(stderr,"jslice v%s usage :\n %s -i <input jpeg> -p <output jpeg prefix> \
-c <cut list file> -q <output quality> \
[-h <header file>] [-f <footer file>]\n\n",_VERSION,argv[0]);
		
		exit(1);
		}
	*/
	/* 
	 * Get the command line parameters ... 
	 */
	for ( aindex = 1; aindex < argc; aindex++ ){
	
		if (argv[aindex][0] == '-'){
		
			if ( argv[aindex][1] == 'i' ){
				infile = argv[aindex+1];
				}
			else
			if ( argv[aindex][1] == 'p' ){
				outfile = argv[aindex+1];
				}
			else
			if ( argv[aindex][1] == 'c' ){
				scriptfile = argv[aindex+1];
				}
			else			
			if ( argv[aindex][1] == 'q' ){
				quality = atoi(argv[aindex+1]);
				}
			else
			if ( argv[aindex][1] == 'h' ){
				headerfile = argv[aindex+1];
				}
			else
			if ( argv[aindex][1] == 'f' ){
				footerfile = argv[aindex+1];
				}
			else 
			if (strstr(argv[aindex],"help")){
				fprintf(stderr,_HELP);
				exit(1);
				}
			else {
			/* if nothing fitted... then dump HELP!! */
				fprintf(stderr,_HELP);
				exit(1);
				}
			} /* if this was a '-' argument */
		
		} /* for */
		
		
	/* 
	 * Check for errors in the input... 
	 *
	 */
	
	if (!infile) {
		fprintf(stderr,"Error: I -need- an input JPEG file, use the -i option\n");
		exit(1);
		}
		
	if (!outfile) {
		fprintf(stderr,"Error: output file PREFIX required, use the -p option\n");
		exit(1);
		}
		
	if (!scriptfile) {
		fprintf(stderr,"Error: cutlist file not specified, use the -c option\n");		
		exit(1);
		}
		
	if (!quality) {
		fprintf(stderr,"Warning: quality not specified, using %d%%\n",_QUALITY_DEFAULT);
		quality = _QUALITY_DEFAULT;
		}
			
	if ((quality > 100)||(quality < 1)) {
		fprintf(stderr,"Invalid JPEG quality setting, please choose a value between 1 and 100\n");
		exit(1);
		}
	 
	 
	/* Now, let us read the input JPEG file to the buffer */
	read_JPEG_file(infile);
	
	/* Test to see if this picture isn't too large for our jslice program
	 */
	if ((image_width >= MAX_WIDTH) || (image_height >= MAX_HEIGHT)){
		fprintf(stderr,"Sorry, but jslice has been compiled only to handle pictures up to %d x %d pixels\n",MAX_WIDTH,MAX_HEIGHT);
		exit(1);
		}
	
	/*
	 * We've gotten this far... now lets actually produce some results
	 */

	/*
	 * Print my message
	 *
	 */
	fprintf(stdout,_MESSAGE);		


	if (headerfile) {
		filetostdout(headerfile);
		}

	script_cut(scriptfile, outfile, quality);
	
	if (footerfile) {
		filetostdout(footerfile);
		}
		
	return 0;
	}
