/*
 * This software is copyrighted as noted below.  It may be freely copied,
 * modified, and redistributed, provided that the copyright notice is
 * preserved on all copies.
 *
 * There is no warranty or other guarantee of fitness for this software,
 * it is provided solely "as is".  Bug reports or fixes may be sent
 * to the author, who may or may not act on them as he desires.
 *
 * You may not include this software in a program or other software product
 * without supplying the source, or without informing the end-user that the
 * source is available for no extra charge.
 *
 * If you modify this software, you should include a notice giving the
 * name of the person performing the modification, the date of modification,
 * and the reason for such modification.
 */
/*
 * jpeg.c - interface with the JPEG format.
 *
 * Author:      Raul Rivero
 *              Mathematics Dept.
 *              University of Oviedo
 * Date:        Fri Mar 12 1993
 * Copyright (c) 1993, Raul Rivero
 *
 */
/*
 * Raul Rivero  -  09/26/94
 *
 *      New code has been added to support the new "The Independent JPEG
 *      Group's Library", the 5.?.
 *
 *      There is a problem to known the version of the IJL library, the
 *      include headers have changed ( jinclude.h --> jpeglib.h ) so we 
 *      cannot get the version from the JPEG_LIB_VERSION define.
 *
 *      You'll need define iJPEGNEW to get the new code, sorry :).
 *
 */

#include <lug.h>
#include <lugfnts.h>


/*
 * Look here !!!
 * =============
 *
 * This code is valid only if we have defined the iJPEG or iJPEGNEW
 * macro ( read the root Makefile for more information ).
 *
 */

#ifdef iJPEG

#include <jinclude.h>

static bitmap_hdr *jpeg_image;
byte *jpeg_r, *jpeg_g, *jpeg_b;
extern int LUGverbose;

/*
 * Predefine what we'll need.
 */
void
set_jpeg_header(
#ifdef USE_PROTOTYPES
        compress_info_ptr
#endif
);

void
set_jpeg_row(
#ifdef USE_PROTOTYPES
        compress_info_ptr ,
        JSAMPARRAY
#endif
);

void
end_set_jpeg(
#ifdef USE_PROTOTYPES
        compress_info_ptr
#endif
);

void
c_ui_method_selection(
#ifdef USE_PROTOTYPES
        compress_info_ptr
#endif
);

void
get_jpeg_header(
#ifdef USE_PROTOTYPES
        decompress_info_ptr
#endif
);

void
get_jpeg_cmap(
#ifdef USE_PROTOTYPES
        decompress_info_ptr ,
        int ,
        JSAMPARRAY
#endif
);

void
get_jpeg_row(
#ifdef USE_PROTOTYPES
        decompress_info_ptr ,
        int ,
        JSAMPIMAGE
#endif
);

void
end_put_jpeg(
#ifdef USE_PROTOTYPES
        decompress_info_ptr
#endif
);

void
d_ui_method_selection(
#ifdef USE_PROTOTYPES
        decompress_info_ptr
#endif
);


write_jpeg_file( name, bitmap )
char *name;
bitmap_hdr *bitmap;
{
  FILE *handle;

  if ( name )
    handle = (FILE *) Fopen( name, "wb" );
  else handle = stdout;

  /* Do it ! */
  write_jpeg( handle, bitmap );

  Fclose( handle );
}

write_jpeg( handle, bitmap )
FILE *handle;
bitmap_hdr *bitmap;
{
  /* Write the JPEG image with a quality = 75 */
  write_jpeg_opt( handle, bitmap, 75 );
}

write_jpeg_opt( handle, bitmap, quality ) 
FILE *handle;
bitmap_hdr *bitmap;
int quality;
{
  struct Compress_info_struct cinfo;
  struct Compress_methods_struct c_methods;
  struct External_methods_struct e_methods;

  if ( bitmap->magic != LUGUSED )
    error( 19 );

  VPRINTF( stderr, "Writing a JPEG image ( quality = %d )\n", quality );
  /* Initialize the system-dependent method pointers. */
  cinfo.methods = &c_methods;   /* links to method structs */
  cinfo.emethods = &e_methods;

  /* Select std error/trace message & memory allocation routines */
  jselerror( &e_methods );
  jselmemmgr( &e_methods );

  /* Initialize pointer to raw data */
  jpeg_image = bitmap;
  jpeg_r = jpeg_image->r;
  jpeg_g = jpeg_image->g;
  jpeg_b = jpeg_image->b;

  /* Define our own routines for input data handling */
  c_methods.input_init    = set_jpeg_header;
  c_methods.get_input_row = set_jpeg_row;
  c_methods.input_term    = end_set_jpeg;
  c_methods.c_ui_method_selection = c_ui_method_selection;

  /* Set up JPEG parameters in the cinfo data structure. */
  j_c_defaults( &cinfo, quality, FALSE );
#ifdef ENTROPY_OPT_SUPPORTED
  /* Best results */
  cinfo.optimize_coding = TRUE;
#endif

  /* No input file */
  cinfo.input_file = NULL;

  /* Assign the output file */
  cinfo.output_file = handle;

  /* Here we go! */
  VPRINTF( stderr, "Encoding JPEG\n" );
  jpeg_compress( &cinfo );
}

void
set_jpeg_header( cinfo )
compress_info_ptr cinfo;
/* Initialize for input; return image size and component data. */
{
  cinfo->image_width  = jpeg_image->xsize;
  cinfo->image_height = jpeg_image->ysize;
  cinfo->input_components = 3;          /* or 1 for grayscale */
  cinfo->in_color_space = CS_RGB;       /* or CS_GRAYSCALE for grayscale */
  cinfo->data_precision = 8;            /* bits per pixel component value */
}

void
set_jpeg_row( cinfo, pixel_row )
compress_info_ptr cinfo;
JSAMPARRAY pixel_row;
/* Read next row of pixels into pixel_row[][] */
{
  register FILE * infile = cinfo->input_file;
  register JSAMPROW ptr0, ptr1, ptr2;
  register long col;

  ptr0 = pixel_row[0];
  ptr1 = pixel_row[1];
  ptr2 = pixel_row[2];
  for (col = 0; col < cinfo->image_width; col++) {
    *ptr0++ = (JSAMPLE) *jpeg_r++; /* red */
    *ptr1++ = (JSAMPLE) *jpeg_g++; /* green */
    *ptr2++ = (JSAMPLE) *jpeg_b++; /* blue */
  }
}

void
end_set_jpeg( cinfo )
compress_info_ptr cinfo;
/* Finish up at the end of the input */
{
  /* Defined only for compatibility reasons */
  VPRINTF( stderr, "JPEG process finished\n" );
}

void
c_ui_method_selection( cinfo )
compress_info_ptr cinfo;
{
  /* If the input is gray scale, generate a monochrome JPEG file. */
  if (cinfo->in_color_space == CS_GRAYSCALE)
    j_monochrome_default(cinfo);
  /* For now, always select JFIF output format. */
  jselwjfif(cinfo);
}

read_jpeg_file( name, bitmap )
char *name;
bitmap_hdr *bitmap;
{
  FILE *handle;

  /* Open the file descriptor */
  if ( name != NULL )
    handle = Fopen( name, "rb" );
  else handle = stdin;

  /* Read the bitmap */
  read_jpeg( handle, bitmap );
  rm_compress();

  /* Close the file */
  Fclose( handle );
}

read_jpeg( handle, bitmap )
FILE *handle;
bitmap_hdr *bitmap;
{
  struct Decompress_info_struct cinfo;
  struct Decompress_methods_struct dc_methods;
  struct External_methods_struct e_methods;

  jpeg_image = bitmap;

  cinfo.input_file = handle;
  cinfo.output_file = NULL;     /* if no actual output file involved */

  /* Initialize the system-dependent method pointers. */
  cinfo.methods  = &dc_methods;
  cinfo.emethods = &e_methods;

  /* Select std error/trace message & memory allocation routines */
  jselerror( &e_methods );
  jselmemmgr( &e_methods );

  dc_methods.d_ui_method_selection = d_ui_method_selection;

  /* Set up default decompression parameters. */
  j_d_defaults( &cinfo, TRUE );

  /* Set up to read a JFIF or baseline-JPEG file. */
  jselrjfif( &cinfo );

  /* Here we go! */
  jpeg_decompress( &cinfo );
}

void
get_jpeg_header( cinfo )
decompress_info_ptr cinfo;
/* This routine should do any setup required */
{
  int totalsize = cinfo->image_width * cinfo->image_height;

  VPRINTF( stderr, "Reading JPEG header\n" );
  /* Fill our bitmap header */
  jpeg_image->xsize  = cinfo->image_width;
  jpeg_image->ysize  = cinfo->image_height;
  jpeg_image->depth  = 24;
  jpeg_image->colors = ( 1 << jpeg_image->depth );
  jpeg_image->magic  = LUGUSED;

  /* Alloc memory */
  jpeg_r = jpeg_image->r = (byte *) Malloc( totalsize );
  jpeg_g = jpeg_image->g = (byte *) Malloc( totalsize );
  jpeg_b = jpeg_image->b = (byte *) Malloc( totalsize );
}

void
get_jpeg_cmap( cinfo, num_colors, colormap )
decompress_info_ptr cinfo; 
int num_colors; 
JSAMPARRAY colormap;
/* Write the color map */
{
  /* You need not provide this routine if you always set cinfo->quantize_colors
   * FALSE; but a safer practice is to provide it and have it just print an
   * error message, like this:
   */
  fprintf(stderr, "get_jpeg_camp called: there's a bug here somewhere!\n");
}

void
get_jpeg_row( cinfo, num_rows, pixel_data )
decompress_info_ptr cinfo;
int num_rows;
JSAMPIMAGE pixel_data;
/* Write some rows of output data */
{
  register FILE * outfile = cinfo->output_file;
  register JSAMPROW ptr0, ptr1, ptr2;
  register long col;
  register int row;

  VPRINTF( stderr, "\rUncoding JPEG ( position %d )",
           jpeg_r - jpeg_image->r );
  VFLUSH( stderr );
  for ( row = 0; row < num_rows; row++ ) {
    ptr0 = pixel_data[0][row];
    ptr1 = pixel_data[1][row];
    ptr2 = pixel_data[2][row];
    for ( col = 0; col < cinfo->image_width; col++ ) {
      *jpeg_r++ = GETJSAMPLE(*ptr0); ptr0++;    /* red */
      *jpeg_g++ = GETJSAMPLE(*ptr1); ptr1++;    /* green */
      *jpeg_b++ = GETJSAMPLE(*ptr2); ptr2++;    /* blue */
    }
  }
}

void
end_put_jpeg( cinfo )
decompress_info_ptr cinfo;
/* Finish up at the end of the input */
{
  /* Defined only for compatibility reasons */
  VPRINTF( stderr, "\nJPEG process finished\n" );
}

void
d_ui_method_selection( cinfo )
decompress_info_ptr cinfo;
{
  /* if grayscale input, force grayscale output; */
  /* else leave the output colorspace as set by main routine. */
  if (cinfo->jpeg_color_space == CS_GRAYSCALE)
    cinfo->out_color_space = CS_GRAYSCALE;

  /* select output routines */
  cinfo->methods->output_init = get_jpeg_header;
  cinfo->methods->put_color_map = get_jpeg_cmap;
  cinfo->methods->put_pixel_rows = get_jpeg_row;
  cinfo->methods->output_term = end_put_jpeg;
}


#else  /* iJPEG */

#  ifdef iJPEGNEW

#include <jpeglib.h>
#include <jerror.h>

extern LUGverbose;


read_jpeg_file( name, bitmap )
char *name;
bitmap_hdr *bitmap;
{
  FILE *handle;

  /* Open the file descriptor */
  if ( name != NULL )
    handle = Fopen( name, "rb" );
  else handle = stdin;

  /* Read the bitmap */
  read_jpeg( handle, bitmap );
  rm_compress();

  /* Close the file */
  Fclose( handle );
}

read_jpeg( handle, bitmap )
FILE *handle;
bitmap_hdr *bitmap;
{
  int i, j;
  struct jpeg_decompress_struct cinfo;
  struct jpeg_error_mgr jerr;
  byte *rptr, *gptr, *bptr;
  JSAMPLE *rgb_aux[1], *aux_ptr;

  /* Use the default IJL's error handlers */
  cinfo.err = jpeg_std_error( &jerr );

  /* Create the decompression object */
 jpeg_create_decompress( &cinfo );

  /* Read the file's header ... */
  jpeg_stdio_src( &cinfo, handle );
  (void) jpeg_read_header( &cinfo, TRUE );

  /* ... and fill our header */
  bitmap->magic = LUGUSED;
  bitmap->xsize = cinfo.image_width;
  bitmap->ysize = cinfo.image_height;

  VPRINTF( stderr, "Reading a JPEG image\n" );
  VPRINTF( stderr, "\t%dx%d pixels\n", bitmap->xsize, bitmap->ysize ); 
  
  /* What kind of image ? */
  switch ( cinfo.num_components ) {
    case 1:
	/* A grayscaled image */
        bitmap->depth  = 8;
        bitmap->colors = 1 << bitmap->depth;
        bitmap->cmap   = create_bw_pallete();
        rptr = bitmap->r = (byte *) Malloc( bitmap->xsize * bitmap->ysize );
        VPRINTF( stderr, "\tgrayscaled colorspace\n");
        break;
    case 3:
    case 4:
        /* A true color image */
        bitmap->depth  = 24;
        bitmap->colors = 1 << bitmap->depth;
        rptr = bitmap->r = (byte *) Malloc( bitmap->xsize * bitmap->ysize );
        gptr = bitmap->g = (byte *) Malloc( bitmap->xsize * bitmap->ysize );
        bptr = bitmap->b = (byte *) Malloc( bitmap->xsize * bitmap->ysize );
        /* We wanna rgb values from the IJL routines */
        cinfo.out_color_space = JCS_RGB;
        VPRINTF( stderr, "\ttrue color space\n" );
        break;
    default:
        /* Hmmmmmmm ?! */
        jpeg_destroy_decompress( &cinfo );
        /* Unset the LUG mark */
        bitmap->magic = LUGUSED - 1;
        fprintf( stderr, "I don't know how to handle %d components in a JPEG file\n", 
                 cinfo.num_components );
        error( 99 );
        return;  /* not used now, but ... */
        break;
  }

  /* Start decompressor */
  jpeg_start_decompress( &cinfo );
  VPRINTF( stderr, "\tdecompressor started\n" );

  /* Get memory for our tmp array */
  rgb_aux[0] = (JSAMPLE *) Malloc( sizeof(JSAMPLE) * cinfo.output_width * cinfo.output_components );

  j = 0;
  while ( cinfo.output_scanline < cinfo.output_height ) {
    /* Get the next sacnline */
    (void) jpeg_read_scanlines( &cinfo, rgb_aux, 1 );
    if ( ++j % 10 == 0 ) {
      VPRINTF( stderr, "\r\tprocessing line %d", 
               cinfo.output_scanline);
      VFLUSH( stderr ); 
      j = 0;
    }
    /* Unpack it */
    for ( i = 0, aux_ptr = rgb_aux[0]; i < bitmap->xsize; i++ ) {
      *rptr++ = *aux_ptr++;
      if ( cinfo.output_components == 3 ) {
        *gptr++ = *aux_ptr++;
        *bptr++ = *aux_ptr++;
      }
    }
  }

  VPRINTF( stderr, "\r\tprocessed %d lines   \n\tdone.\n", cinfo.output_scanline);

  /* Release our memory */
  Free( rgb_aux[0] );

  /* Say bye to the decompressor :) */
  (void) jpeg_finish_decompress( &cinfo );
  jpeg_destroy_decompress( &cinfo );
}


write_jpeg_file( name, bitmap )
char *name;
bitmap_hdr *bitmap;
{
  FILE *handle;

  if ( name )
    handle = (FILE *) Fopen( name, "wb" );
  else handle = stdout;

  /* Do it ! */
  write_jpeg( handle, bitmap );

  Fclose( handle );
}

write_jpeg( handle, bitmap )
FILE *handle;
bitmap_hdr *bitmap;
{
  /* Write the JPEG image with a quality = 75 */
  write_jpeg_opt( handle, bitmap, 75 );
}

write_jpeg_opt( handle, bitmap, quality ) 
FILE *handle;
bitmap_hdr *bitmap;
int quality;
{
  int i, j;
  struct jpeg_compress_struct cinfo;
  struct jpeg_error_mgr jerr;
  byte *rptr, *gptr, *bptr;
  JSAMPLE *rgb_aux[1], *aux_ptr;


  if ( bitmap->magic != LUGUSED ) 
    error( 19 );

  /* We need a true color or gray mapped image. Really ? */
  if ( bitmap->depth < 24 && !isagrayscaled(bitmap) ){
    /* 
     * Ok, we have a mapped image ==> not supported. So it'll be converted
     * to true clor and ... voila. 
     */
    bitmap_hdr out;

    to24( bitmap, &out );
    write_jpeg_opt( handle, &out, quality );
    return 1;
  }

  /* Use the default IJL's error handlers */
  cinfo.err = jpeg_std_error( &jerr );

  /* Create the compression object */
 jpeg_create_compress( &cinfo );

  /* The destination is ... */
  jpeg_stdio_dest( &cinfo, handle );

  /* Fill the IJL's image header */
  cinfo.image_width = bitmap->xsize;
  cinfo.image_height = bitmap->ysize;
  cinfo.input_components = ( bitmap->depth < 24 ? 1 : 3 ); 
  cinfo.in_color_space = ( bitmap->depth < 24 ? JCS_GRAYSCALE : JCS_RGB );

  VPRINTF( stderr, "Writting a JPEG file\n" );
  VPRINTF( stderr, "\t%dx%d pixels\n", bitmap->xsize, bitmap->ysize );
  VPRINTF( stderr, "\t%s color space\n",
           ( bitmap->depth < 24 ? "JCS_GRAYSCALE" : "JCS_RGB" )); 

  /* Fix these stored values and the quality */
  jpeg_set_defaults( &cinfo );
  jpeg_set_quality( &cinfo, quality, TRUE );
  VPRINTF( stderr, "\tquality fixed to %d\n", quality );

  /* Start the compressor */
  jpeg_start_compress(&cinfo, TRUE);
  VPRINTF( stderr, "\tcompressor started\n" );

  /* Get memory for our tmp array */
  rgb_aux[0] = (JSAMPLE *) Malloc( sizeof(JSAMPLE) * cinfo.image_width * cinfo.input_components );

  /* Initialize our pointers */
  rptr = bitmap->r;
  if ( cinfo.input_components == 3 ) {
    gptr = bitmap->g;
    bptr = bitmap->b;
  }

  /* And ... the loop! :) */ 
  while (cinfo.next_scanline < cinfo.image_height) {
    if ( ++j % 10 == 0 ) {
      VPRINTF( stderr, "\r\tprocessing line %d",
               cinfo.next_scanline);
      VFLUSH( stderr );
      j = 0;
    }
    /* Pack the r-g-b data */
    for ( i = 0, aux_ptr = rgb_aux[0]; i < bitmap->xsize; i++ ) {
      *aux_ptr++ = *rptr++;
      if ( cinfo.input_components == 3 ) {
        *aux_ptr++ = *gptr++;
        *aux_ptr++ = *bptr++;
      }
    }
    (void) jpeg_write_scanlines( &cinfo, rgb_aux, 1 );
  }

  VPRINTF( stderr, "\r\tprocessed %d lines   \n\tdone.\n", cinfo.image_height );

  /* Free our memory */
  Free( rgb_aux[0] );

  /* And close the compressor */
  jpeg_finish_compress( &cinfo );
  jpeg_destroy_compress( &cinfo );

  return 1;
}

#  endif  /* iJPEGNEW */

#endif  /* iJPEG */
