/*
 * Copyright (C) 1997-2009, R3vis Corporation.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA, or visit http://www.gnu.org/copyleft/lgpl.html.
 *
 * Original Contributor:
 *   Wes Bethel, R3vis Corporation, Marin County, California
 *   http://www.r3vis.com/
 * Additional Contributor(s):
 *
 * The OpenRM project is located at http://openrm.sourceforge.net/.
 */
/*
 * $Id: rmjpeg.c,v 1.9 2005/02/19 16:10:45 wes Exp $
 * Version: $Name: v180-alpha-02 $
 * $Revision: 1.9 $
 * $Log: rmjpeg.c,v $
 * Revision 1.9  2005/02/19 16:10:45  wes
 * Distro sync and consolidation.
 *
 * Revision 1.8  2005/01/23 17:09:39  wes
 * Copyright update to 2005.
 *
 * Revision 1.7  2004/03/10 01:49:53  wes
 * Updated documentation on rmiWriteJPEG.
 *
 * Revision 1.6  2004/01/17 04:09:05  wes
 * Updated copyright line for 2004.
 *
 * Revision 1.5  2003/06/20 01:38:14  wes
 * Added code to rmiWriteJPEG that will permit only RMimages with
 * RM_IMAGE_RGB format pixels to be processed. All other formats will
 * produce an error message. Future work might be to accommodate a greater
 * breadth of RMimage pixel formats.
 *
 * Revision 1.4  2003/06/19 20:53:56  wes
 * Minor documentation tweaks.
 *
 * Revision 1.3  2003/02/14 00:21:20  wes
 * Minor code cleanups.
 *
 * Revision 1.2  2003/02/02 02:07:21  wes
 * Updated copyright to 2003.
 *
 * Revision 1.1.1.1  2003/01/28 02:15:23  wes
 * Manual rebuild of rm150 repository.
 *
 * Revision 1.4  2003/01/16 22:21:19  wes
 * Updated all source files to reflect new organization of header files:
 * all header files formerly located in include/rmaux, include/rmi, include/rmv
 * are now located in include/rm.
 *
 * Revision 1.3  2002/06/02 15:18:45  wes
 * Fixed minor indexing bug.
 *
 * Revision 1.2  2001/06/04 01:00:05  wes
 * Added documentation for JPEG i/o routines.
 *
 * Revision 1.1  2001/06/03 20:18:15  wes
 * Initial entry (v140-beta1)
 *
 */

#include <rm/rm.h>
#include <rm/rmi.h>

#include <setjmp.h>		/* ugh */

#if RM_JPEG
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;
#endif

/*
 * Here's the routine that will replace the standard error_exit method:
 */
#if RM_JPEG
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);
}
#endif

/*
 * ----------------------------------------------------
 * @Name rmiWriteJPEG
 @pstart
 RMenum  rmiWriteJPEG(const char *filename,
                      int quality,
                      const RMimage *toWrite)
 @pend

 @astart

 const char *filename - the name of the file to be written.

 int quality - integer value that controls degree of lossiness in
 JPEG file. The value of quality ranges from 0-100, with 100 being
 the least amount of loss, and smaller numbers producing increasing
 amounts of error. Smaller numbers result in smaller output file size.

 const RMimage *toWrite - the RMimage object that will be written
 to the file in JPEG format.
 
 @aend

 @dstart
 Writes a JPEG image raster file named "filename" using the input RMimage
 object "toWrite" as the source. The quality of the JPEG file is determined
 by the "quality" parameter, which ranges from 0-100. Smaller quality values
 produce greater amounts of compression, smaller files and poorer image
 image quality. Larger quality values produce larger files, less compression,
 and better image quality.
 
 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 This routine works only on RMimage objects that contain RGB images.
 @dend
 * ----------------------------------------------------
 */
RMenum
rmiWriteJPEG(const char *filename,
	     int quality,
	     const RMimage *toWrite)
{
#if RM_JPEG    
    int image_width;
    int image_height;
    unsigned char * pixeldata;

    /*  this code is more or less a cut-n-paste of the decompress file()
	routine in the example.c file shipped with the jpeg-6b source.
	comments have been left pretty much intact. */
  
    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 j;
    int bytesPerScanline;       /* physical row width in image buffer */

    if ((rmImageGetImageSize(toWrite, NULL, &image_width, &image_height, NULL, NULL, NULL) == RM_WHACKED) ||
	((pixeldata = (unsigned char *)rmImageGetPixelData(toWrite)) == NULL))
    {
	rmError("rmiWriteJPEGFile() : error reading image dimensions or pixel data. No JPEG file is being produced. ");
	return(RM_WHACKED);
    }

    /* next, check for valid pixel format and type */
    {
	RMenum pFormat, pType;
	
	pFormat = rmImageGetFormat (toWrite);
	pType = rmImageGetType (toWrite);

	if (pFormat != RM_IMAGE_RGB)
	{
	    rmError("rmiWriteJPEG() error: the input RMimage does not have RM_IMAGE_RGB format pixels, unable to write the JPEG file. ");
	    return RM_WHACKED;
	}

	if (pType != RM_UNSIGNED_BYTE)
	{
	    rmError("rmiWriteJPEG() error: the input RMimage does not have RM_UNSIGNED_BYTE pixels, unable to write the JPEG file. ");
	    return RM_WHACKED;
	}
    }



    /* 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)
    {
	char buf[1024];
	
	sprintf(buf,"rmiWriteJPEGFile(): can't open output file %s. ",filename);
	rmError(buf);
	return(RM_WHACKED);
    }
    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 = image_width; 	/* image width and height, in pixels */
    cinfo.image_height = image_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(...); */

    /* JSAMPLEs per row in image_buffer */
    bytesPerScanline = rmImageGetBytesPerScanline(toWrite);
    
    for (j=0;j<image_height;j++)
    {
	row_pointer[0] = pixeldata + (j * bytesPerScanline);
	(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! */

    return(RM_CHILL);
#else
    rmWarning("rmiWriteJPEG warning: JPEG support has been intentionally disabled.");
    return(RM_WHACKED);
#endif
}


#if RM_JPEG
static RMenum
readJPEG (const char * filename,
	  int *widthReturn,
	  int *heightReturn,
	  unsigned char **pixelDataReturn)
{
    /* 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 */
    JSAMPARRAY buffer;		/* Output row buffer */
    int row_stride;		/* physical row width in output buffer */
    unsigned char *pd;
    int indx=0;
    
    /* In this example we want to open the input file before doing anything else,
     * so that the setjmp() error recovery below can assume the file is open.
     * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that
     * requires it in order to read binary files.
     */
    
    if ((infile = fopen(filename, "rb")) == NULL)
	return RM_WHACKED;

    
    /* Step 1: allocate and initialize JPEG decompression object */
    
    /* We set up the normal JPEG error routines, then override error_exit. */
    memset(&cinfo, 0, sizeof(struct jpeg_decompress_struct));
    cinfo.err = jpeg_std_error(&jerr.pub);
    jerr.pub.error_exit = my_error_exit;
    
    /* Establish the setjmp return context for my_error_exit to use. */
    if (setjmp(jerr.setjmp_buffer)) {
	/* If we get here, the JPEG code has signaled an error.
	 * We need to clean up the JPEG object, close the input file, and return.
	 */
	jpeg_destroy_decompress(&cinfo);
	fclose(infile);
	return RM_WHACKED;
    }
    
    /* Now we can initialize the JPEG decompression object. */
    jpeg_create_decompress(&cinfo);
    
    /* Step 2: specify data source (eg, a file) */
    
    jpeg_stdio_src(&cinfo, infile);
    
    /* Step 3: read file parameters with jpeg_read_header() */
    
    (void) jpeg_read_header(&cinfo, TRUE);
    /* We can ignore the return value from jpeg_read_header since
     *   (a) suspension is not possible with the stdio data source, and
     *   (b) we passed TRUE to reject a tables-only JPEG file as an error.
     * See libjpeg.doc for more info.
     */
    
    /* Step 4: set parameters for decompression */
    
    /* In this example, we don't need to change any of the defaults set by
     * jpeg_read_header(), so we do nothing here.
     */
    
    /* Step 5: Start decompressor */
    
    (void) jpeg_start_decompress(&cinfo);
    /* We can ignore the return value since suspension is not possible
     * with the stdio data source.
     */
    
    /* We may need to do some setup of our own at this point before reading
     * the data.  After jpeg_start_decompress() we have the correct scaled
     * output image dimensions available, as well as the output colormap
     * if we asked for color quantization.
     * In this example, we need to make an output work buffer of the right size.
     */ 
    /* JSAMPLEs per row in output buffer */
    row_stride = cinfo.output_width * cinfo.output_components;
    /* Make a one-row-high sample array that will go away when done with image */
    
    buffer = (*cinfo.mem->alloc_sarray)
	((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
    
    /* wes: config pixel buffer */
    *widthReturn = cinfo.output_width;
    *heightReturn = cinfo.output_height;
    pd = (unsigned char *)malloc(sizeof(unsigned char)*cinfo.output_width*cinfo.output_height*cinfo.output_components);
    indx = 0;
    
    /* Step 6: while (scan lines remain to be read) */
    /*           jpeg_read_scanlines(...); */
    
    /* Here we use the library's state variable cinfo.output_scanline as the
     * loop counter, so that we don't have to keep track ourselves.
   */
  while (cinfo.output_scanline < cinfo.output_height) {
    /* jpeg_read_scanlines expects an array of pointers to scanlines.
     * Here the array is only one element long, but you could ask for
     * more than one scanline at a time if that's more convenient.
     */
      (void) jpeg_read_scanlines(&cinfo, buffer, 1);
      
      /* Assume put_scanline_someplace wants a pointer and sample count. */
      /*    put_scanline_someplace(buffer[0], row_stride); */
      memcpy(pd+indx, buffer[0], row_stride);
      indx += row_stride;
  }
    
    
    /* 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).
     */
    
    *pixelDataReturn = pd;
    /* And we're done! */
    return RM_CHILL;
}
#endif

/*
 * ----------------------------------------------------
 * @Name rmiReadJPEG
 @pstart
 RMimage *rmiReadJPEG (const char * filename)
 @pend

 @astart
 const char * filename - a character string, the name of the file
 containing a JPEG image to read (input).
 @aend

 @dstart
 This routine will open and read a JPEG image file. If there are
 no errors, this routine will create and return to the caller an
 RMimage object containing the raster image data. If there is a
 problem reading the JPEG file, NULL is returned.
 @dend
 * ----------------------------------------------------
 */

RMimage *
rmiReadJPEG (const char * filename)
{
#if RM_JPEG
    int width, height;
    unsigned char *pixelData=NULL;
    RMimage *t=NULL;
    
    if (readJPEG (filename, &width, &height, &pixelData) == RM_CHILL)
    {
	t = rmImageNew(2, width, height, 1, RM_IMAGE_RGB, RM_UNSIGNED_BYTE, RM_COPY_DATA);
#ifdef RM_WIN
	/*
	 * Windows OpenGL implementations mandate 4-byte alignment of
	 * pixel data per scanline. When we created the image (above),
	 * the pixel data buffer is 4-byte aligned per scanline. We add
	 * some code here to copy from the buffer returned by the
	 * read jpeg routine to pad out to 4-byte scanline boundaries.
	 */
	{
	unsigned char *d, *s, *d2;
	int i,j;
	int swidth = rmImageGetBytesPerScanline(t);
	int extraBytes = swidth - width*3;
	
	d2 = d = (unsigned char *)malloc(sizeof(unsigned char)*height*swidth);

	s = pixelData;
	for (j=0;j<height;j++)
	{
	    memcpy(d, s, sizeof(unsigned char)*3*width);
	    d += width*3;
	    s += width*3;
	    d += extraBytes; 
	}

	rmImageSetPixelData(t, d2, RM_COPY_DATA, NULL);
	free((void *)d2);
    }
#else
	rmImageSetPixelData(t, pixelData, RM_COPY_DATA, NULL);
#endif
	free((void *)pixelData);
    }
    return(t);
#else
    rmWarning("rmiReadJPEG warning: JPEG support has been intentionally disabled.");
    return(NULL);
#endif
}
