/**
 * imgjpeg.c - interface for libjpeg GIF input routines
 *
 * Copyright (c) 1998
 *      Transvirtual Technologies, Inc.  All rights reserved.
 *
 * See the file "license.terms" for information on usage and redistribution 
 * of this file. 
 */

#include "config.h"

#include <stdio.h>
#include <setjmp.h>
#include <sys/types.h>

#if defined(HAVE_JPEGLIB_H)
#include "jpeglib.h"
#include "jerror.h"
#endif

#include "toolkit.h"


#define MAX_JPEG_COLORS 256

/*
 * Hmmm, libjpeg uses explicit indices to access colormap components. We assume
 * the same values like in rdgif.h (which isn't part of jpeglib-6b anymore).
 * We should add a check for a RGB colormap / organization. 
 */
#define CM_RED		0
#define CM_GREEN	1
#define CM_BLUE		2

/* references to img.c */
Image* createImage ( int width, int height );
void Java_java_awt_Toolkit_imgFreeImage( JNIEnv* env, jclass clazz, Image * img);
XImage* createXImage ( Toolkit* X, int width, int height );


#if defined(HAVE_JPEGLIB_H)

/*******************************************************************************************
 * required typedefs and structs
 */

struct error_mgr {
  struct jpeg_error_mgr pub;
  jmp_buf setjmp_buffer;
};

typedef struct error_mgr * error_ptr;

METHODDEF(void)
error_exit ( j_common_ptr cinfo)
{
  error_ptr myerr = (error_ptr) cinfo->err;
  (*cinfo->err->output_message)(cinfo);
  longjmp(myerr->setjmp_buffer, 1);
}

typedef struct {
  struct jpeg_source_mgr pub;	/* public fields */

  long   buflen;       /* length of external buffer */
  JOCTET * buffer;		/* start of buffer */

  boolean start_of_file;	/* have we gotten any data yet? */
} bufsrc_mgr;

typedef bufsrc_mgr * bufsrc_ptr;


/*******************************************************************************************
 * memory buffer extension for the standard jpeg lib input management (jdatasrc.c)
 */

METHODDEF(void)
init_buf_source (j_decompress_ptr cinfo)
{
  bufsrc_ptr src = (bufsrc_ptr) cinfo->src;

  /* We reset the empty-input-file flag for each image,
   * but we don't clear the input buffer.
   * This is correct behavior for reading a series of images from one source.
   */
  src->start_of_file = TRUE;
}


static JOCTET end_marker[2] = { (JOCTET) 0xFF, (JOCTET) JPEG_EOI };

METHODDEF(boolean)
get_buf_source (j_decompress_ptr cinfo)
{
  bufsrc_ptr src = (bufsrc_ptr) cinfo->src;

  if ( src->pub.next_input_byte && (src->start_of_file == FALSE) ) {
    WARNMS(cinfo, JWRN_JPEG_EOF);
    src->pub.next_input_byte = end_marker;
	src->pub.bytes_in_buffer = sizeof(end_marker);
  }
  else {                    /* we read in one seep (no need to use two buffers) */
	src->pub.next_input_byte = src->buffer;
	src->pub.bytes_in_buffer = src->buflen;
	src->start_of_file = FALSE;
  }

  return TRUE;
}


METHODDEF(void)
skip_buf_source ( j_decompress_ptr cinfo, long num_bytes )
{
  bufsrc_ptr src = (bufsrc_ptr) cinfo->src;

  if ( src->start_of_file == TRUE )
	get_buf_source( cinfo);

  src->pub.next_input_byte += (size_t) num_bytes;
  src->pub.bytes_in_buffer -= (size_t) num_bytes;
}


METHODDEF(void)
term_buf_source (j_decompress_ptr cinfo)
{
  bufsrc_ptr src = (bufsrc_ptr) cinfo->src;

  /*
   * Take care the (external) buffer isn't deleted by jpeg_destroy_decompress.
   * This shouldn't be necessary since jpeg_destroy cleans up by means of the 
   * jpeg memory manager, but we better play it safe
   */
  src->buffer = 0;
}


GLOBAL(void)
jpeg_buffer_src (j_decompress_ptr cinfo, unsigned char* buf, long len )
{
  bufsrc_ptr src;

  if (cinfo->src == NULL) {	/* first time for this JPEG object? */
    cinfo->src = (struct jpeg_source_mgr *)
      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
				  sizeof(bufsrc_mgr));
  }

  src = (bufsrc_ptr) cinfo->src;
  src->pub.init_source = init_buf_source;
  src->pub.fill_input_buffer = get_buf_source;
  src->pub.skip_input_data = skip_buf_source;
  src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */
  src->pub.term_source = term_buf_source;

  src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
  src->pub.next_input_byte = NULL; /* until buffer loaded */

  src->buffer = buf;
  src->buflen = len;
}



/************************************************************************************
 * JPEG auxiliary funcs (production callbacks)
 */

void
jscan_to_img( Image * img, JSAMPROW buf, struct jpeg_decompress_struct * cinfo)
{
  register col, pix, rgb, idx;
  register JSAMPARRAY colormap = cinfo->colormap;

  for ( col = 0; col < cinfo->output_width; col++) {
    idx = *buf++;
    rgb = (colormap[CM_RED][idx] << 16) | (colormap[CM_GREEN][idx] << 8) | colormap[CM_BLUE][idx];    
    pix = pixelValue( X, rgb);
    XPutPixel( img->xImg, col, cinfo->output_scanline-1, pix);
  }
}


/************************************************************************************
 * Jpeg production
 */

Image*
readJpeg ( struct jpeg_decompress_struct* cinfo, int colors )
{
  struct error_mgr jerr;
  JSAMPARRAY buffer;
  Image      *img = 0;
  int        row_stride;

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

  if ( setjmp(jerr.setjmp_buffer)) {
	if ( img )
	  Java_java_awt_Toolkit_imgFreeImage( 0, 0, img);
    return 0;
  }

  jpeg_read_header( cinfo, TRUE);

  if ( colors < 8 )
    colors = 8;
  else if ( colors > 256 )
    colors = 256;

  cinfo->desired_number_of_colors = colors;
  cinfo->quantize_colors = TRUE;
  cinfo->out_color_space = JCS_RGB;

  jpeg_start_decompress( cinfo);
  row_stride = cinfo->output_width * cinfo->output_components;

  /* it's time to create the target image */
  img = createImage( cinfo->output_width, cinfo->output_height);
  img->xImg = createXImage( X, img->width, img->height);

  buffer = (*cinfo->mem->alloc_sarray)((j_common_ptr) cinfo, JPOOL_IMAGE, row_stride, 1);

  while (cinfo->output_scanline < cinfo->output_height) {
    jpeg_read_scanlines( cinfo, buffer, 1);
    jscan_to_img( img, buffer[0], cinfo);
  }

  jpeg_finish_decompress( cinfo);

  return img;
}

#endif /* HAVE_JPEGLIB_H */


/**************************************************************************************
 * these are the "exported" production interface functions
 */

Image*
readJpegFile ( FILE* infile )
{
  Image *img = 0;

#if defined(HAVE_JPEGLIB_H)
  struct jpeg_decompress_struct cinfo;

  jpeg_create_decompress( &cinfo);
  jpeg_stdio_src( &cinfo, infile);

  img = readJpeg( &cinfo, MAX_JPEG_COLORS);

  jpeg_destroy_decompress( &cinfo);
#endif /* HAVE_JPEGLIB_H */

  return img;
}


Image*
readJpegData ( unsigned char* buf, long len )
{
  Image *img = 0;

#if defined(HAVE_JPEGLIB_H)
  struct jpeg_decompress_struct cinfo;

  jpeg_create_decompress( &cinfo);
  jpeg_buffer_src( &cinfo, buf, len);

  img = readJpeg( &cinfo, MAX_JPEG_COLORS);

  jpeg_destroy_decompress( &cinfo);
#endif /* HAVE_JPEGLIB_H */

  return img;
}
