/*
 * $Id: image_jpeg.c,v 0.89 1999/07/18 07:51:45 zhao Beta $
 *
 *.
 *  This file is part of XForms package
 *  Copyright (c) 1993, 1998-1999  T.C. Zhao 
 *  All rights reserved.
 *.
 *  JFIF file IO support.
 *  Based mostly on Independent JPEG Groups' example code.
 *
 *  Except for a couple of output control parameters, there is no
 *  static variables.
 */
#include <stdio.h>
#include "forms.h"
#include "jpeglib.h"
#include "jerror.h"
#include <setjmp.h>

/* For some reason, jpeg 6.1 does not work well */
#if JPEG_LIB_VERSION < 62
#error does not work well with jpeg lib v6.a
#endif

/* options supported by jpeg reader/writer */

static int do_quantization = 0;
static int quality_factor = 75;
static int smoothing_factor = 0;

void
flimage_jpeg_output_options(FLIMAGE_JPEG_OPTION * op)
{
    quality_factor = op->quality;
    smoothing_factor = op->smoothing;
}

static unsigned int jpeg_getc(j_decompress_ptr cinfo);

static int
JPEG_identify(FILE * fp)
{
    char buf[128];
    int i;

    fread(buf, 1, sizeof(buf), fp);
    rewind(fp);
    buf[sizeof(buf) - 1] = '\0';

    for (i = 0; i < sizeof(buf) - 3 && buf[i] != 'J'; i++)
	;

    return (buf[i] == 'J' && buf[i + 1] == 'F' &&
	    buf[i + 2] == 'I' && buf[i + 3] == 'F');
}


/* cinfo->client_data member only exists in V6B, have to stuff image into
   error handler. Yuck!
 */
typedef struct
{
    struct jpeg_error_mgr pub;
    jmp_buf jmp_buffer;
    struct jpeg_decompress_struct dinfo;
    struct jpeg_compress_struct cinfo;
    FL_IMAGE *image;
}
SPEC;

static void
error_exit(j_common_ptr cinfo)
{
    SPEC *spec = (SPEC *) cinfo->err;
    static char buf[1024];

    cinfo->err->format_message(cinfo, buf);
    spec->image->error_message(spec->image, buf);
    longjmp(spec->jmp_buffer, 1);
}


static boolean
gather_comments(j_decompress_ptr cinfo)
{
    SPEC *spec = (SPEC *) cinfo->err;
    FL_IMAGE *image = spec->image;
    int length, ch, lastch;
    char *s;

    length = jpeg_getc(cinfo) << 8;
    length += jpeg_getc(cinfo) - 2;

    if (image->comments)
	image->comments = fl_realloc(image->comments, length + 1);
    else
	image->comments = fl_malloc(length + 1);

    image->comments[length] = '\0';
    image->comments_len = length;

    for (s = image->comments; --length >= 0;)
    {
	ch = jpeg_getc(cinfo);
	*s++ = ch;
	lastch = ch;
    }

    return TRUE;
}

static boolean
gather_text(j_decompress_ptr cinfo)
{
    SPEC *spec = (SPEC *) cinfo->err;
    FL_IMAGE *image = spec->image;
    int length, ch, lastch;
    char *s;

    length = jpeg_getc(cinfo) << 8;
    length += jpeg_getc(cinfo) - 2;

    if (image->comments)
	image->comments = fl_realloc(image->comments, length + 1);
    else
	image->comments = fl_malloc(length + 1);

    image->comments[length] = '\0';
    image->comments_len = length;


    for (s = image->comments; --length >= 0;)
    {
	ch = jpeg_getc(cinfo);
	*s++ = ch;
	lastch = ch;
    }

    if (image->comments[image->comments_len - 1] == '\n')
	image->comments[image->comments_len - 1] = ' ';

    return TRUE;
}

static int
JPEG_description(FL_IMAGE * im)
{
    SPEC *spec = fl_malloc(sizeof(*spec));
    struct jpeg_decompress_struct *cinfo;

    cinfo = &spec->dinfo;
    cinfo->err = jpeg_std_error(&spec->pub);
    spec->pub.error_exit = error_exit;
    spec->image = im;
    im->io_spec = spec;

    jpeg_create_decompress(cinfo);
    jpeg_set_marker_processor(cinfo, JPEG_COM, gather_comments);
    jpeg_set_marker_processor(cinfo, JPEG_APP0 + 12, gather_text);

    jpeg_stdio_src(cinfo, im->fpin);
    jpeg_read_header(cinfo, TRUE);

    /* decompressison options such as quantization here */
    if (do_quantization)
    {
	cinfo->desired_number_of_colors = 215;
	cinfo->quantize_colors = TRUE;
	cinfo->enable_2pass_quant = TRUE;
	cinfo->two_pass_quantize = TRUE;
	cinfo->dither_mode = JDITHER_FS;
    }

    jpeg_start_decompress(cinfo);

    im->w = cinfo->output_width;
    im->h = cinfo->output_height;
    im->map_len = cinfo->desired_number_of_colors;

    if (cinfo->out_color_space == JCS_GRAYSCALE)
	im->type = FL_IMAGE_GRAY;
    else if (cinfo->out_color_space == JCS_RGB)
	im->type = cinfo->output_components == 3 ? FL_IMAGE_RGB : FL_IMAGE_CI;
    else
    {
	im->error_message(im, "unhandled colorspace");
	return -1;
    }

    im->original_type = im->type;
    return 0;
}


static int
JPEG_read_pixels(FL_IMAGE * im)
{
    SPEC *spec = im->io_spec;
    struct jpeg_decompress_struct *cinfo = &spec->dinfo;
    int k = 0, i, j, stride, err;
    JSAMPARRAY buf;
    unsigned short *gray, *ci;

    if (setjmp(spec->jmp_buffer))
    {
	jpeg_destroy_decompress(cinfo);
	/* keep what we'be got */
	return (im->completed > im->w / 2) ? 1 : -1;
    }

    err = 0;
    stride = cinfo->output_width * cinfo->output_components;

    buf = cinfo->mem->alloc_sarray((j_common_ptr) cinfo, JPOOL_IMAGE,
				stride, 1 /* cinfo->rec_outbuf_height  */ );

    while (cinfo->output_scanline < cinfo->output_height && !err)
    {
	k = jpeg_read_scanlines(cinfo, buf, 1 /* cinfo->rec_out_height  */ );
	im->completed = cinfo->output_scanline;

	if (!(cinfo->output_scanline & FLIMAGE_REPFREQ))
	    im->visual_cue(im, "Reading JPEG");

	if (im->type == FL_IMAGE_RGB)
	{
	    for (i = j = 0; i < cinfo->output_width; i++, j += 3)
	    {
		im->red[cinfo->output_scanline - 1][i] = buf[0][j];
		im->green[cinfo->output_scanline - 1][i] = buf[0][j + 1];
		im->blue[cinfo->output_scanline - 1][i] = buf[0][j + 2];
	    }
	}
	else if (im->type == FL_IMAGE_CI)
	{
	    im->map_len = cinfo->actual_number_of_colors;
	    for (i = 0; i < cinfo->actual_number_of_colors; i++)
	    {
		im->red_lut[i] = cinfo->colormap[0][i];
		im->green_lut[i] = cinfo->colormap[1][i];
		im->blue_lut[i] = cinfo->colormap[2][i];
	    }
	    ci = im->ci[cinfo->output_scanline - 1];
	    for (i = 0; i < cinfo->output_width; i++)
		ci[i] = buf[0][i];
	}
	else if (im->type == FL_IMAGE_GRAY)
	{
	    gray = im->gray[cinfo->output_scanline - 1];
	    for (i = j = 0; i < im->w; i++)
		gray[i] = buf[0][i];
	}
	else
	{
	    flimage_error(im, "%s: unknown color space", im->infile);
	    err = 1;
	}
    }

    jpeg_finish_decompress(cinfo);
    jpeg_destroy_decompress(cinfo);

    return (im->completed > im->h / 3) ? 1 : -1;
}

static int
JPEG_write(FL_IMAGE * im)
{
    SPEC *spec = fl_malloc(sizeof(*spec));
    struct jpeg_compress_struct *cinfo;
/*    JSAMPROW rowptr[1]; */
    JSAMPARRAY rowptr;
    unsigned char *buf;
    int i;

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

    if (setjmp(spec->jmp_buffer))
    {
	jpeg_destroy_compress(cinfo);
	fl_free(spec);
	im->io_spec = 0;
	return -1;
    }

    jpeg_create_compress(cinfo);
    jpeg_stdio_dest(cinfo, im->fpout);

    cinfo->image_width = im->w;
    cinfo->image_height = im->h;

    if (im->type == FL_IMAGE_RGB)
    {
	cinfo->input_components = 3;
	cinfo->in_color_space = JCS_RGB;
    }
    else if (im->type == FL_IMAGE_GRAY)
    {
	cinfo->input_components = 1;
	cinfo->in_color_space = JCS_GRAYSCALE;
    }

    jpeg_set_defaults(cinfo);
    jpeg_set_quality(cinfo, quality_factor, TRUE);
    cinfo->smoothing_factor = smoothing_factor;

    jpeg_start_compress(cinfo, TRUE);

    if (im->comments)
	jpeg_write_marker(cinfo, JPEG_COM, (void *) im->comments,
			  im->comments_len);

    rowptr = cinfo->mem->alloc_sarray((j_common_ptr) cinfo, JPOOL_IMAGE,
				      cinfo->input_components * im->w, 1);

    while (cinfo->next_scanline < cinfo->image_height)
    {
	im->completed = cinfo->next_scanline;
	if (!(im->completed & FLIMAGE_REPFREQ))
	    im->visual_cue(im, "Writing JPEG ");

	for (buf = rowptr[0], i = 0; i < im->w; i++)
	{
	    if (cinfo->input_components == 3)
	    {
		*buf++ = im->red[cinfo->next_scanline][i];
		*buf++ = im->green[cinfo->next_scanline][i];
		*buf++ = im->blue[cinfo->next_scanline][i];
	    }
	    else
	    {
		*buf++ = im->gray[cinfo->next_scanline][i];
	    }

	}
	jpeg_write_scanlines(cinfo, rowptr, 1);
    }

    jpeg_finish_compress(cinfo);
    fflush(im->fpout);

    jpeg_destroy_compress(cinfo);
    return 1;
}

static unsigned int
jpeg_getc(j_decompress_ptr cinfo)
{
    struct jpeg_source_mgr *datasrc = cinfo->src;

    if (datasrc->bytes_in_buffer == 0)
    {
	if (!datasrc->fill_input_buffer(cinfo))
	    ERREXIT(cinfo, JERR_CANT_SUSPEND);
    }
    datasrc->bytes_in_buffer--;
    return GETJOCTET(*datasrc->next_input_byte++);
}



void
flimage_enable_jpeg(void)
{
    flimage_add_format("JPEG/JFIF format", "jpeg", "jpg",
		       FL_IMAGE_RGB | FL_IMAGE_GRAY,
		       JPEG_identify,
		       JPEG_description,
		       JPEG_read_pixels,
		       JPEG_write);
}
