/* +-------------------------------------------------------------------+ */
/* | Copyright 1993, David Koblas (koblas@netcom.com)		       | */
/* |								       | */
/* | Permission to use, copy, modify, and to distribute this software  | */
/* | and its documentation for any purpose is hereby granted without   | */
/* | fee, provided that the above copyright notice appear in all       | */
/* | copies and that both that copyright notice and this permission    | */
/* | notice appear in supporting documentation.	 There is no	       | */
/* | representations about the suitability of this software for	       | */
/* | any purpose.  this software is provided "as is" without express   | */
/* | or implied warranty.					       | */
/* |								       | */
/* +-------------------------------------------------------------------+ */

/* $Id: readJPEG.c,v 1.21 2005/03/20 20:15:34 demailly Exp $ */

/* Updated for IJG release 6 by Torsten Martinsen, Fri Dec 8 1995 */

#include <stdio.h>
#include <setjmp.h>
#include <stdlib.h>
#include <jpeglib.h>

#include "image.h"
#include "rwTable.h"

extern void *xmalloc(size_t n);

Image *ReadJPEG(char *file);
int TestJPEG(char *file);

int file_JPEG_quality;

static void
trace_message(j_common_ptr cinfo)
{
    char buf[JMSG_LENGTH_MAX];

    cinfo->err->format_message(cinfo, buf);
    RWSetMsg(buf);
}

struct error_mgr {
    struct jpeg_error_mgr pub;	/* "public" fields */
    jmp_buf setjmp_buffer;	/* for return to caller */
};

static struct error_mgr jerr;

typedef struct error_mgr *error_ptr;

/*
 * Here's the routine that will replace the standard error_exit method:
 */
static void
error_exit(j_common_ptr cinfo)
{
    /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
    error_ptr err = (error_ptr) cinfo->err;

    trace_message(cinfo);

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

Image *
ReadJPEG(char *file)
{
    struct jpeg_decompress_struct cinfo;
    FILE *input_file;
    int w, h, row_stride, ncolors, i, k, rgb;
    unsigned char *scanline, *c;
    Image *image;


    if ((input_file = fopen(file, "r")) == NULL)
	return NULL;

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

    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(input_file);
	return NULL;
    }
    jpeg_create_decompress(&cinfo);
    jpeg_stdio_src(&cinfo, input_file);
    jpeg_read_header(&cinfo, TRUE);
    jpeg_start_decompress(&cinfo);

    h = cinfo.output_height;
    w = cinfo.output_width;

    if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
	ncolors = cinfo.desired_number_of_colors;
	image = ImageNewGrey(w, h);
	row_stride = w;
    } else {
        if (cinfo.output_components > 3) {
	    image = ImageNew(w, 0);
            image->height = h;
            /* allow extra space to accommodate data from CMYK color space */
            image->data = (unsigned char *) xmalloc((3*h+1) * w * sizeof(char));
	} else
	    image = ImageNew(w, h);
	row_stride = w * 3;
    }
    cinfo.quantize_colors = FALSE;
    while (cinfo.output_scanline < h) {
	scanline = image->data + cinfo.output_scanline * row_stride;
	(void) jpeg_read_scanlines(&cinfo, (JSAMPROW *)&scanline, (JDIMENSION) 1);
        if (cinfo.output_components > 3) {
	    /* convert from CMYK colorspace to RGB */
	    c = scanline;
            i = w;
            if (cinfo.saw_Adobe_marker)
            while (i--) {
                k = 255 - c[3];
                if ((rgb = *c++ - k) < 0) rgb = 0; *scanline++ = rgb;
                if ((rgb = *c++ - k) < 0) rgb = 0; *scanline++ = rgb;
                if ((rgb = *c++ - k) < 0) rgb = 0; *scanline++ = rgb;
                c++;
	    } else
            while (i--) {
                k = 255 - c[3];
                if ((rgb = k - *c++) < 0) rgb = 0; *scanline++ = rgb;
                if ((rgb = k - *c++) < 0) rgb = 0; *scanline++ = rgb;
                if ((rgb = k - *c++) < 0) rgb = 0; *scanline++ = rgb;
                c++;
	    }
	}
    }
    jpeg_finish_decompress(&cinfo);
    jpeg_destroy_decompress(&cinfo);
    fclose(input_file);

    if (jerr.pub.num_warnings > 0) {	/* XXX */
	RWSetMsg("JPEG warning, image may be corrupted");
	longjmp(jerr.setjmp_buffer, 1);
    }
    if (cinfo.output_components > 3)
        image->data = (unsigned char *) realloc(image->data, 3 * w * h * sizeof(char));
    return image;
}

int
TestJPEG(char *file)
{
    unsigned char buf[2];
    FILE *fd = fopen(file, "r");
    int ret = 0;

    if (fd == NULL)
	return 0;

    if (2 == fread(buf, sizeof(char), 2, fd)) {
	if (buf[0] == 0xff && buf[1] == 0xd8)
	    ret = 1;
    }
    fclose(fd);

    return ret;
}

int
WriteJPEG(char *file, Image * outImage)
{
    struct jpeg_compress_struct cinfo;
    FILE *output_file;
    int w, i, row_stride;
    unsigned char *p, *d, *scanline, *theline = NULL;

    if (outImage->alpha) AlphaWarning("JPEG", 0);

    if ((output_file = fopen(file, "w")) == NULL)
	return 1;

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

    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 output file,
	 * and return.
	 */
	jpeg_destroy_compress(&cinfo);
	fclose(output_file);
	return 1;
    }
    jpeg_create_compress(&cinfo);
    jpeg_stdio_dest(&cinfo, output_file);

    cinfo.image_height = outImage->height;
    cinfo.image_width = w;
    if (outImage->isGrey) {
	cinfo.in_color_space = JCS_GRAYSCALE;
	cinfo.input_components = 1;
    } else {
	cinfo.in_color_space = JCS_RGB;
	cinfo.input_components = 3;
	theline = xmalloc(w * 3);
    }

    jpeg_set_defaults(&cinfo);
    jpeg_set_quality(&cinfo, file_JPEG_quality, False);
    jpeg_start_compress(&cinfo, TRUE);
    row_stride = w * cinfo.input_components;

    while (cinfo.next_scanline < cinfo.image_height) {
	/*
	 * If we have a greyscale or TrueColor image, just feed
	 * the raw data to the JPEG routine. Otherwise, we must
	 * build an array of RGB triples in 'theline'.
	 */
	if (outImage->isGrey || (outImage->cmapSize == 0))
	    scanline = outImage->data + cinfo.next_scanline * row_stride;
	else {
	    d = theline;
	    for (i = 0; i < w; ++i) {
		p = ImagePixel(outImage, i, cinfo.next_scanline);
		*d++ = *p++;
		*d++ = *p++;
		*d++ = *p;
	    }
	    scanline = theline;
	}
	jpeg_write_scanlines(&cinfo, (JSAMPROW*)&scanline, (JDIMENSION) 1);
    }
    jpeg_finish_compress(&cinfo);
    jpeg_destroy_compress(&cinfo);

    fclose(output_file);
    if (!outImage->isGrey && theline)
	free(theline);

    if (jerr.pub.num_warnings > 0) {	/* XXX */
	RWSetMsg("JPEG warning, image may be corrupted");
	longjmp(jerr.setjmp_buffer, 1);
    }
    return 0;
}
