/* jpeg.c
 * JPEG decoding
 * (c) 2002 Karel 'Clock' Kulhavy
 * This file is a part of the Links program, released under GPL.
 */

#include "cfg.h"

#ifdef G
#include "links.h"

#ifdef HAVE_JPEG
#include <jpeglib.h>

#if BITS_IN_JSAMPLE != 8
#error "You have a weird jpeglib compiled for 12 bits per sample that is not able to read ordinary JPEG's. \
See INSTALL for description how to compile Links with jpeglib statically to supply your own \"good\" version \
of jpeglib or reinstall your system's jpeglib to be a normal one."
#endif /* #if BITS_IN_JSAMPLE != 8 */

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

struct jpg_decoder{
	struct jpeg_decompress_struct *cinfo;
	struct jerr_struct *jerr;
	unsigned char state; /* 0: header 1: start 2: scanlines 3: end 4,5: also
			something */
	int skip_bytes;
	unsigned char *jdata;
	unsigned char *scanlines[16];
};

static struct jerr_struct *global_jerr;
static struct jpeg_decompress_struct *global_cinfo;
static int mesg_unsup_emitted = 0; /* Defaults to zero at program startup and once
				* set is never reset back to zero */


/*#include <jerror.h>*/

METHODDEF(void) my_error_exit(j_common_ptr cinfo)
{
	/*fprintf(stderr, "jpeg error %d (%d)\n", cinfo->err->msg_code, JERR_OUT_OF_MEMORY);*/
	longjmp(global_jerr->setjmp_buffer,2);
}

METHODDEF(void) /* Only for the sake of libjpeg */
nop(j_decompress_ptr cinfo)
{
}

METHODDEF(void)
my_output_message(j_common_ptr cinfo)
{
}

METHODDEF(boolean) my_fill_input_buffer(j_decompress_ptr cinfo)
{
 return FALSE; /* We utilize I/O suspension (or emulsion? ;-) ) */
}

METHODDEF(void) my_skip_input_data(j_decompress_ptr cinfo,long num_bytes)
{
	if ((unsigned long)num_bytes>cinfo->src->bytes_in_buffer)
	{
		/* We have to enter skipping state */
		cinfo->src->next_input_byte+=cinfo->src->bytes_in_buffer;
		((struct jpg_decoder *)(global_cimg->decoder))->skip_bytes
			=(int)(num_bytes-cinfo->src->bytes_in_buffer);
		cinfo->src->bytes_in_buffer=0;
	}
	else
	{
		/* We only pull out some bytes from buffer. */
		cinfo->src->next_input_byte+=num_bytes;
		cinfo->src->bytes_in_buffer-=num_bytes;
	}
}

void jpeg_start(struct cached_image *cimg)
{
	struct jpg_decoder *jd;

	global_cinfo=mem_alloc(sizeof(*global_cinfo));
	global_jerr=mem_alloc(sizeof(*global_jerr));
	global_cinfo->err = jpeg_std_error(&(global_jerr->pub));
	global_jerr->pub.error_exit=my_error_exit;
	global_jerr->pub.output_message=my_output_message;
	if (setjmp(global_jerr->setjmp_buffer)){
g19_2000:
		mem_free(global_cinfo);
		mem_free(global_jerr);
		img_end(cimg);
		return;
	}
	jpeg_create_decompress(global_cinfo);
	if (setjmp(global_jerr->setjmp_buffer)){
		jpeg_destroy_decompress(global_cinfo);
		goto g19_2000;
	}
	jpeg_stdio_src(global_cinfo,stdin);
	global_cinfo->src->init_source=&nop;
	global_cinfo->src->fill_input_buffer=&my_fill_input_buffer;
	global_cinfo->src->skip_input_data=&my_skip_input_data;
	global_cinfo->src->resync_to_restart=&jpeg_resync_to_restart;
	global_cinfo->src->term_source=nop;
	global_cinfo->src->bytes_in_buffer=0;
	global_cinfo->src->next_input_byte=NULL;
	cimg->decoder=mem_alloc(sizeof(struct jpg_decoder));
	jd=(struct jpg_decoder *)cimg->decoder;
	jd->cinfo=global_cinfo;
	jd->jerr=global_jerr;
	jd->state=0;
	jd->skip_bytes=0;
	jd->jdata=NULL;
	/* Scanlines can be left unititialized */
}

/* This is here because libjpeg doesn't support transformation from CMYK
 * to RGB so that we must do it ourselves.
 *
 * data must be non-NULL. */
static void cmyk_to_rgb(unsigned char *data, int pixels)
{
	for (;pixels;pixels--, data+=4)
	{
		/* C -> R */
		data[0]=((data[0])*(data[3])+127)/255;

		/* M -> G */
		data[1]=((data[1])*(data[3])+127)/255;

		/* Y -> B */
		data[2]=((data[2])*(data[3])+127)/255;

		/* Put alpha=1 instead of K */
		data[3]=255;
	}
}

/* data must be non-NULL */
static void gray_to_rgb(unsigned char *data, int pixels)
{
	unsigned char *dest;

	dest=data+(pixels-1)*3;
	data+=pixels-1;
	for(;pixels;pixels--,data--,dest-=3){
		dest[2]=*data;
		dest[1]=*data;
		dest[0]=*data;
	}
}

/* Fixes returned data in case they are CMYK or grayscale. */
static inline void fix_data( struct jpg_decoder *deco, int lines_read)
{
	/* ICC bug */
	icc_volatile int a;

	switch (global_cinfo->output_components){
		case 1:
		for (a=0; a<lines_read; a++)
			gray_to_rgb(deco->scanlines[a], global_cinfo
				->output_width);
		break;

		case 3:
		break;

		case 4:
		cmyk_to_rgb(deco->scanlines[0], global_cinfo
			->output_width*lines_read);
		break;

		default: internal_error("Invalid output_components");
	}
}

void jpeg_restart(struct cached_image *cimg, unsigned char *data, int length)
{
	struct jpg_decoder *deco;

	deco=(struct jpg_decoder *)(cimg->decoder);
#ifdef DEBUG
	if (!deco) internal_error("NULL decoder in jpeg_restart");
#endif /* #ifdef DEBUG */
	global_cinfo=((struct jpg_decoder *)(cimg->decoder))->cinfo;
	global_jerr=((struct jpg_decoder *)(cimg->decoder))->jerr;
	/* These global variables are here so that we don't have to pass lots
	 * of structure pointers into each function. The jpeg decoder is never
	 * running twice at the same time so it doesn't matter.
	 */

	/* If the decoder wants us to skip bytes it's not interested in */
	if (deco->skip_bytes>=length){
		/* If the decoder wants to skip as much as or more bytes than
		 * the chunk that has just arrived */
		deco->skip_bytes-=length;
		return;
	}else{
		/* If the decoder wants to skip less bytes than the chunk
		 * that has just arrived */
		data+=deco->skip_bytes;
		length-=deco->skip_bytes;
		deco->skip_bytes=0;
	}

	/* Add the arrived data chunk into the decoder buffer. Sometimes the
	 * chunks are so small the decoder can't move on on a single chunk
	 * so it has to accumulate more chunks together. This is why the buffer
	 * is there. */
	if ((unsigned)global_cinfo->src->bytes_in_buffer + (unsigned)length > MAXINT) overalloc();
	if ((unsigned)global_cinfo->src->bytes_in_buffer + (unsigned)length < (unsigned)length) overalloc();
	if (deco->jdata){
		/* If there is already some decoder buffer, we have to
		 * allocate more space */
		memmove(deco->jdata,global_cinfo->src->next_input_byte,
			global_cinfo->src->bytes_in_buffer);
		deco->jdata=mem_realloc(
			deco->jdata, global_cinfo->src->bytes_in_buffer+length);
	}else{
		/* If there is no decoder buffer we'll have to allocate
		 * space for a new buffer */
		deco->jdata=mem_alloc(global_cinfo->src->bytes_in_buffer+length);
	}

	/* Copy the data iself into the decoder buffer */
	memcpy(deco->jdata+global_cinfo->src->bytes_in_buffer
		,data,length);

	/* Update the next input byte pointer for the decoder to continue at
	 * the right position */
	global_cinfo->src->next_input_byte=(void *)deco->jdata;

	/* ...:::...:..:.:::.:.::::.::.:.:.:.::..::::.::::.:...: */
	/* Update the length of data in the decoder buffer */
	global_cinfo->src->bytes_in_buffer+=length;

	if (setjmp(global_jerr->setjmp_buffer)) goto decoder_ended;
	switch(deco->state){
		case 0:
		/* jpeg_read_header */
		if (JPEG_SUSPENDED==jpeg_read_header(global_cinfo,TRUE))
			break;
		global_cinfo->buffered_image=TRUE;
		deco->state=1;
		/*-fallthrough*/

		case 1:
		/* If the scaling is sufficiently brutal we can leave out
		 * some DCT coefficients...: */
		/* jpeg_start_decompress */
		if (jpeg_start_decompress(global_cinfo)==FALSE)
			break;

		cimg->width=global_cinfo->output_width;
		cimg->height=global_cinfo->output_height;

		switch(cimg->buffer_bytes_per_pixel=
			global_cinfo->output_components)
		{
			case 1:
				/* We'll do the conversion ourselves
				 * because libjpeg seems to be buggy */
				cimg->buffer_bytes_per_pixel=3;
				break;


			case 3: /* RGB or YCrCb. We will ask libjpeg to
				 * possibly convert from YCrCb to RGB. */

				global_cinfo->out_color_space=JCS_RGB;
				break;

			case 4:
				/* CMYK or YCCK. We need to enable conversion
				 * to CMYK and then convert CMYK data to RGBA
				 * with dummy A ourselves.
				 * We will ask libjpeg to possibly convert from
				 * YCCK to CMYK. */
				global_cinfo->out_color_space=JCS_CMYK;
				break;

			default:
			/* Let's make a decompression fatal error here */

			if (!mesg_unsup_emitted){
				error(
			"Unsupported JPEG output components number: %d.\n",
			cimg->buffer_bytes_per_pixel);
				mesg_unsup_emitted=1;
			}
			longjmp(global_jerr->setjmp_buffer,2);

			/* longjmp()  and  siglongjmp() make programs hard to
			 * understand and maintain.  If possible an alternative
			 * should be used. Hahaha :) ;-)
			 */
			/* Free will makes people hard to understand
			 * and maintain. If possible an alternative should be
			 * used.
			 */
			/* With our new LongJump(TM) your jumps will be longer
			 * than with ordinary commercially available jumps.
			 */
		}
		cimg->red_gamma=(float)sRGB_gamma;
		cimg->green_gamma=(float)sRGB_gamma;
		cimg->blue_gamma=(float)sRGB_gamma;
		/* This is defined in the JPEG standard somehow that sRGB
		 * color space is used. */

		cimg->strip_optimized=0;
		/* Strip optimization yet waits to be written. This will
		 * allow huge jpegs to be processed without consuming
		 * Links memory and consuming Xserver memory instead ;-)
		 * However strip optimization is already written for PNG's.
		 */

		if (header_dimensions_known(cimg)) {
			longjmp(global_jerr->setjmp_buffer,2);
		}
new_scan:
		deco->state=2;
		/*-fallthrough*/

		case 2:
		/* jpeg_start_output */
		if (FALSE==jpeg_start_output(global_cinfo,global_cinfo->input_scan_number)){
susp0:
			/* Suspended */
			break;
		}
		deco->state=3;

		case 3:
		/* jpeg_read_scanlines */
			/* color */
		while (global_cinfo->output_scanline < global_cinfo->output_height) {
			int a, lines;

			for (a = 0; a < 16; a++) {
				deco->scanlines[a] = cimg->buffer + ((size_t)global_cinfo->output_scanline + a) * global_cinfo->output_width * cimg->buffer_bytes_per_pixel;
			}

			if ((lines = jpeg_read_scanlines(global_cinfo, deco->scanlines, 1))) {
				/* Some lines were written into cimg buffer */
				cimg->rows_added = 1;
				fix_data(deco, lines);
			} else {
				/* No lines have been written into cimg
				 * buffer */
				/* We are suspended and we want more data */
				goto susp0; /* Break the outer
					     * switch statement */
			}
		}
		deco->state=4;
		/*-fallthrough*/

		case 4:
		/* jpeg_finish_output */
		if (FALSE==jpeg_finish_output(global_cinfo))
		{
			/* Suspended */
			break;
		}
		if (!jpeg_input_complete(global_cinfo))
		{
			/* Some more scans awaited... */
			goto new_scan;
		}
		deco->state=5;
		/*-fallthrough*/

		case 5:
		/* jpeg_finish_decompress */
		if (FALSE==jpeg_finish_decompress(global_cinfo))
			break;
decoder_ended:
		img_end(cimg);
	}
}

void jpeg_destroy_decoder(struct cached_image *cimg)
{
	struct jpg_decoder *deco = (struct jpg_decoder *)cimg->decoder;
	jpeg_destroy_decompress(deco->cinfo);
	mem_free(deco->cinfo);
	mem_free(deco->jerr);
	if (deco->jdata) mem_free(deco->jdata);
}

void add_jpeg_version(unsigned char **s, int *l)
{
#if defined(LIBJPEG_TURBO_VERSION)
	add_to_str(s, l, cast_uchar "JPEG-TURBO (");
	add_to_str(s, l, cast_uchar stringify(LIBJPEG_TURBO_VERSION));
	add_to_str(s, l, cast_uchar ", ABI ");
#else
	add_to_str(s, l, cast_uchar "JPEG (");
#endif
#if defined(JPEG_LIB_VERSION_MAJOR) && defined(JPEG_LIB_VERSION_MINOR)
	add_num_to_str(s, l, JPEG_LIB_VERSION_MAJOR);
	add_chr_to_str(s, l, '.');
	add_num_to_str(s, l, JPEG_LIB_VERSION_MINOR);
#else
	add_num_to_str(s, l, JPEG_LIB_VERSION / 10);
	add_chr_to_str(s, l, '.');
	add_num_to_str(s, l, JPEG_LIB_VERSION % 10);
#endif
	add_chr_to_str(s, l, ')');
}

#endif /* #ifdef HAVE_JPEG */

#endif /* #ifdef G */

