/* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t -*-
 *
 * jpegcodec.c : Contains function definitions for encoding decoding jpeg images
 *
 * Copyright (C) 2003-2004,2007 Novell, Inc (http://www.novell.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
 * and associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
 * subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies or substantial 
 * portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Authors:
 *  	Sanjay Gupta (gsanjay@novell.com)
 *      Vladimir Vukicevic (vladimir@pobox.com)
 *
 */

#include "config.h"
#include "codecs-private.h"
#include "jpegcodec.h"

GUID gdip_jpg_image_format_guid = {0xb96b3caeU, 0x0728U, 0x11d3U, {0x9d, 0x7b, 0x00, 0x00, 0xf8, 0x1e, 0xf3, 0x2e}};

#ifdef HAVE_LIBJPEG

#include <setjmp.h>

/* pkgsrc */
#undef HAVE_STDLIB_H
#include <jpeglib.h>
#include "dstream.h"
#ifdef HAVE_LIBEXIF
#include <libexif/exif-data.h>
#include <libexif/exif-content.h>
#include <libexif/exif-entry.h>
#endif

/* Codecinfo related data*/
static ImageCodecInfo jpeg_codec;
static const WCHAR jpeg_codecname[] = {'B', 'u', 'i','l', 't', '-','i', 'n', ' ', 'J', 'P', 'E', 'G', ' ', 'C', 'o', 'd', 'e', 'c', 0}; /* Built-in JPEG Codec */
static const WCHAR jpeg_extension[] = {'*', '.', 'J', 'P','G', ';','*', '.', 'J','P', 'E', 'G', ';', '*',
        '.', 'J', 'P', 'E', ';', '*', '.', 'J', 'F','I','F', 0}; /* JPG;*.JPEG;*.JPE;*.JFIF */
static const WCHAR jpeg_mimetype[] = {'i', 'm', 'a','g', 'e','/', 'j', 'p', 'e', 'g', 0}; /* image/png */
static const WCHAR jpeg_format[] = {'J', 'P', 'E', 'G', 0}; /* JPEG */
static const BYTE jpeg_sig_pattern[] = { 0xFF, 0xD8 };
static const BYTE jpeg_sig_mask[] = { 0xFF, 0xFF };


ImageCodecInfo *
gdip_getcodecinfo_jpeg ()
{
	jpeg_codec.Clsid = (CLSID) { 0x557cf401, 0x1a04, 0x11d3, { 0x9a, 0x73, 0x0, 0x0, 0xf8, 0x1e, 0xf3, 0x2e } };
	jpeg_codec.FormatID = gdip_jpg_image_format_guid;
	jpeg_codec.CodecName = (const WCHAR*)  jpeg_codecname;
	jpeg_codec.DllName = NULL;
	jpeg_codec.FormatDescription = (const WCHAR*) jpeg_format;
	jpeg_codec.FilenameExtension = (const WCHAR*) jpeg_extension;
	jpeg_codec.MimeType = (const WCHAR*) jpeg_mimetype;
	jpeg_codec.Flags = ImageCodecFlagsEncoder | ImageCodecFlagsDecoder | ImageCodecFlagsSupportBitmap | ImageCodecFlagsBuiltin;
	jpeg_codec.Version = 1;
	jpeg_codec.SigCount = 1;
	jpeg_codec.SigSize = 2;
	jpeg_codec.SigPattern = jpeg_sig_pattern;
	jpeg_codec.SigMask = jpeg_sig_mask;

	return &jpeg_codec;
}


#if !defined(HAVE_SIGSETJMP) && !defined(sigsetjmp)
#define sigjmp_buf jmp_buf
#define sigsetjmp(jb, x) setjmp(jb)
#define siglongjmp longjmp
#endif

#define JPEG_BUFFER_SIZE	65536

struct gdip_stdio_jpeg_source_mgr {
	struct jpeg_source_mgr parent;

	FILE *infp;
	JOCTET *buf;
};
typedef struct gdip_stdio_jpeg_source_mgr *gdip_stdio_jpeg_source_mgr_ptr;

struct gdip_stream_jpeg_source_mgr {
	struct jpeg_source_mgr parent;
	dstream_t *loader;

	JOCTET *buf;
};
typedef struct gdip_stream_jpeg_source_mgr *gdip_stream_jpeg_source_mgr_ptr;

struct gdip_stream_jpeg_dest_mgr {
	struct jpeg_destination_mgr parent;

	PutBytesDelegate putBytesFunc;

	JOCTET *buf;
};
typedef struct gdip_stream_jpeg_dest_mgr *gdip_stream_jpeg_dest_mgr_ptr;

struct gdip_jpeg_error_mgr {
	struct jpeg_error_mgr parent;
	sigjmp_buf setjmp_buffer;
	GError **error;
};
typedef struct gdip_jpeg_error_mgr *gdip_jpeg_error_mgr_ptr;


static void
_gdip_jpeg_error_exit (j_common_ptr cinfo)
{
	gdip_jpeg_error_mgr_ptr err = (gdip_jpeg_error_mgr_ptr) cinfo->err;
	char buffer[JMSG_LENGTH_MAX];

	(* cinfo->err->format_message) (cinfo, buffer);

	/* There is some very bizzare stuff happening with error
	 * reporting.  These are supposed to be fatal errors,
	 * but libjpeg goes through a bunch of these calls and then
	 * -finishes uncompressing the image-.  I have no idea
	 * what's going on here.
	 * -- FIXME
	 */

	g_warning ("jpeg error: %d %s\n", err->parent.msg_code, buffer);
	siglongjmp (err->setjmp_buffer, 1);
}

static void
_gdip_jpeg_output_message (j_common_ptr cinfo)
{
	/* shh. */
}

static void
_gdip_source_dummy_init (j_decompress_ptr cinfo)
{
	/* nothing */
}

static BOOL
_gdip_source_stdio_fill_input_buffer (j_decompress_ptr cinfo)
{
	gdip_stdio_jpeg_source_mgr_ptr src = (gdip_stdio_jpeg_source_mgr_ptr) cinfo->src;
	size_t nb;

	nb = fread (src->buf, 1, JPEG_BUFFER_SIZE, src->infp);

	if (nb <= 0) {
		/* this is a hack learned from gdk-pixbuf */
		/* insert fake EOI marker, to try to salvage image
		 * in case of malformed/incomplete input */
		src->buf[0] = (JOCTET) 0xFF;
		src->buf[1] = (JOCTET) JPEG_EOI;
		nb = 2;
	}

	src->parent.next_input_byte = src->buf;
	src->parent.bytes_in_buffer = nb;

	return TRUE;
}

static void
_gdip_source_stdio_skip_input_data (j_decompress_ptr cinfo, long skipbytes)
{
	gdip_stdio_jpeg_source_mgr_ptr src = (gdip_stdio_jpeg_source_mgr_ptr) cinfo->src;

	if (skipbytes > 0) {
		if (skipbytes > (long) src->parent.bytes_in_buffer) {
			skipbytes -= (long) src->parent.bytes_in_buffer;
			fseek (src->infp, skipbytes, SEEK_CUR);

			(void) _gdip_source_stdio_fill_input_buffer (cinfo);
		} else {
			src->parent.next_input_byte += (size_t) skipbytes;
			src->parent.bytes_in_buffer -= (size_t) skipbytes;
		}
	}
}

static BOOL
_gdip_source_stream_fill_input_buffer (j_decompress_ptr cinfo)
{
	gdip_stream_jpeg_source_mgr_ptr src = (gdip_stream_jpeg_source_mgr_ptr) cinfo->src;
	dstream_t *loader = src->loader;
	size_t nb;

	nb = dstream_read (loader, src->buf, JPEG_BUFFER_SIZE, 0);
	if (nb <= 0) {
		/* this is a hack learned from gdk-pixbuf */
		/* insert fake EOI marker, to try to salvage image
		 * in case of malformed/incomplete input */
		src->buf[0] = (JOCTET) 0xFF;
		src->buf[1] = (JOCTET) JPEG_EOI;
		nb = 2;
	}

	src->parent.next_input_byte = src->buf;
	src->parent.bytes_in_buffer = nb;

	return TRUE;
}

static void
_gdip_source_stream_skip_input_data (j_decompress_ptr cinfo, long skipbytes)
{
	gdip_stream_jpeg_source_mgr_ptr src = (gdip_stream_jpeg_source_mgr_ptr) cinfo->src;
	dstream_t *loader = src->loader;

	if (skipbytes > 0) {
		if (skipbytes > (long) src->parent.bytes_in_buffer) {
			skipbytes -= (long) src->parent.bytes_in_buffer;
			dstream_skip (loader, skipbytes);
			(void) _gdip_source_stream_fill_input_buffer (cinfo);
		} else {
			src->parent.next_input_byte += (size_t) skipbytes;
			src->parent.bytes_in_buffer -= (size_t) skipbytes;
		}
	}
}

static void
_gdip_source_dummy_term (j_decompress_ptr cinfo)
{
	/* nothing */
}

static void
_gdip_dest_stream_init (j_compress_ptr cinfo)
{
	gdip_stream_jpeg_dest_mgr_ptr dest = (gdip_stream_jpeg_dest_mgr_ptr) cinfo->dest;
	dest->parent.next_output_byte = dest->buf;
	dest->parent.free_in_buffer = JPEG_BUFFER_SIZE;
}

static BOOL
_gdip_dest_stream_empty_output_buffer (j_compress_ptr cinfo)
{
	gdip_stream_jpeg_dest_mgr_ptr dest = (gdip_stream_jpeg_dest_mgr_ptr) cinfo->dest;

	dest->putBytesFunc (dest->buf, JPEG_BUFFER_SIZE);

	dest->parent.next_output_byte = dest->buf;
	dest->parent.free_in_buffer = JPEG_BUFFER_SIZE;

	return TRUE;
}

static void
_gdip_dest_stream_term (j_compress_ptr cinfo)
{
	gdip_stream_jpeg_dest_mgr_ptr dest = (gdip_stream_jpeg_dest_mgr_ptr) cinfo->dest;

	dest->putBytesFunc (dest->buf, JPEG_BUFFER_SIZE - dest->parent.free_in_buffer);
}

static GpStatus
gdip_load_jpeg_image_internal (struct jpeg_source_mgr *src, GpImage **image)
{
	struct jpeg_decompress_struct	cinfo;
	struct gdip_jpeg_error_mgr	jerr;
	GpBitmap	*result;
	BYTE		*destbuf;
	BYTE		*destptr;
	BYTE		*lines[4] = {NULL, NULL, NULL, NULL};
	GpStatus	status;
	int		stride;
	unsigned long long int size;

	destbuf = NULL;
	result = NULL;

	cinfo.err = jpeg_std_error ((struct jpeg_error_mgr *) &jerr);
	jerr.parent.error_exit = _gdip_jpeg_error_exit;
	jerr.parent.output_message = _gdip_jpeg_output_message;

	if (sigsetjmp (jerr.setjmp_buffer, 1)) {
		/* Error occured during decompression */
		status = OutOfMemory;
		goto error;
	}

	jpeg_create_decompress (&cinfo);
	cinfo.src = src;

	jpeg_read_header (&cinfo, TRUE);

	cinfo.do_fancy_upsampling = FALSE;
	cinfo.do_block_smoothing = FALSE;

	result = gdip_bitmap_new_with_frame (NULL, TRUE);
	if (!result) {
		status = OutOfMemory;
		goto error;
	}

	result->type = ImageTypeBitmap;
	result->active_bitmap->width = cinfo.image_width;
	result->active_bitmap->height = cinfo.image_height;
	result->active_bitmap->image_flags = ImageFlagsReadOnly | ImageFlagsHasRealPixelSize;

	if (cinfo.density_unit == 1) { /* dpi */
		result->active_bitmap->dpi_horz = cinfo.X_density;
		result->active_bitmap->dpi_vert = cinfo.Y_density;
	} else if (cinfo.density_unit == 2) { /* dots/cm */
		result->active_bitmap->dpi_horz = cinfo.X_density * 2.54;
		result->active_bitmap->dpi_vert = cinfo.Y_density * 2.54;
	} else { /* unknown density */
		result->active_bitmap->dpi_horz = 0;
		result->active_bitmap->dpi_vert = 0;
	}

	if (result->active_bitmap->dpi_horz && result->active_bitmap->dpi_vert)
		result->active_bitmap->image_flags |= ImageFlagsHasRealDPI;

	if (cinfo.num_components == 1) {
		result->cairo_format = CAIRO_FORMAT_A8;
		result->active_bitmap->pixel_format = PixelFormat8bppIndexed;
		size = 1;
	} else if (cinfo.num_components == 3) {
		/* libjpeg gives us RGB for many formats and
		 * we convert to RGB format when needed. JPEG
		 * does not support alpha (transparency). */
		result->cairo_format = CAIRO_FORMAT_ARGB32;
		result->active_bitmap->pixel_format = PixelFormat24bppRGB;
		size = 4;
	} else if (cinfo.num_components == 4) {
		result->cairo_format = CAIRO_FORMAT_ARGB32;
		result->active_bitmap->pixel_format = PixelFormat32bppRGB;
		size = 4;
	} else {
		status = InvalidParameter;
		goto error;
	}

	switch (cinfo.jpeg_color_space) {
	case JCS_GRAYSCALE:
		result->active_bitmap->image_flags |= ImageFlagsColorSpaceGRAY;
		if (cinfo.num_components == 1) {
			result->active_bitmap->palette = gdip_create_greyscale_palette (256);
			if (!result->active_bitmap->palette) {				
				status = OutOfMemory;
				goto error;
			}
		}
		break;
	default:
		result->active_bitmap->image_flags |= ImageFlagsColorSpaceRGB;
		break;
	}

	size *= cinfo.image_width;
	/* stride is a (signed) _int_ and once multiplied by 4 it should hold a value that can be allocated by GdipAlloc
	 * this effectively limits 'width' to 536870911 pixels */
	if (size > G_MAXINT32) {
		status = OutOfMemory;
		goto error;
	}
	stride = result->active_bitmap->stride = size;

	/* Request cairo-compat output */
	/* libjpeg can do only following conversions,
	 * YCbCr => GRAYSCALE, YCbCr => RGB
	 * GRAYSCALE => RGB, YCCK => CMYK.
	 * Therefore, we convert YCbCr, GRAYSCALE to RGB and
	 * YCCK to CMYK using the libjpeg. We convert CMYK
	 * to RGB ourself.
	 */
	switch (cinfo.jpeg_color_space) {
	case JCS_GRAYSCALE:
		/* special case for indexed 256 greyscale images (bug #81552) */
		if (cinfo.num_components == 1) {
			cinfo.out_color_space = JCS_GRAYSCALE;
			cinfo.out_color_components = 1;
			break;
		}
		/* else treat as RGB and */
		/* fall through */
	case JCS_RGB:
	case JCS_YCbCr:
		cinfo.out_color_space = JCS_RGB;
		cinfo.out_color_components = 3;
		break;
	case JCS_YCCK:
	case JCS_CMYK:
		cinfo.out_color_space = JCS_CMYK;
		cinfo.out_color_components = 4;
		break;
	default:
		/* Unsupported JPEG color space */
		status = InvalidParameter;
		goto error;
	}

	jpeg_start_decompress (&cinfo);

	/* ensure total 'size' does not overflow an integer and fits inside our 2GB limit */
	size *= cinfo.output_height;
	if (size > G_MAXINT32) {
		status = OutOfMemory;
		goto error;
	}
	destbuf = GdipAlloc (size);
	if (destbuf == NULL) {
		status = OutOfMemory;
		goto error;
	}
	destptr = destbuf;

	while (cinfo.output_scanline < cinfo.output_height) {
		int i;
		int nlines;
		for (i = 0; i < cinfo.rec_outbuf_height; i++) {
			lines[i] = destptr;
			destptr += stride;
		}

		nlines = jpeg_read_scanlines (&cinfo, lines, cinfo.rec_outbuf_height);

		/* If the out colorspace is not RBG, we need to convert it to RBG. */
		if (cinfo.out_color_space == JCS_CMYK) {
			int i, j;

			for (i = 0; i < cinfo.rec_outbuf_height; i++) {
				BYTE *lineptr = lines [i];

				for (j = 0; j < cinfo.output_width; j++) {
					JOCTET c, m, y, k;
					JOCTET r, g, b;

					c = lineptr [0];
					m = lineptr [1];
					y = lineptr [2];
					k = lineptr [3];

					/* Adobe photoshop seems to have a bug and inverts the CMYK data.
					 * We might need to remove this check, if Adobe decides to fix it. */
					if (cinfo.saw_Adobe_marker) {
						b = (k * c) / 255;
						g = (k * m) / 255;
						r = (k * y) / 255;
					}  else {
						b = (255 - k) * (255 - c) / 255;
						g = (255 - k) * (255 - m) / 255;
						r = (255 - k) * (255 - y) / 255;
					}

					set_pixel_bgra(lineptr, 0, b, g, r, 0xff);
					lineptr += 4;
				}
			}
		} else if (cinfo.out_color_components == 1) {
			/* no decoding required, we already have all we need */
		} else {
			int width = result->active_bitmap->width;
			for (i = 0; i < nlines; i++) {
				int j;
				BYTE *inptr, *outptr;

				inptr = lines[i] + (width * 3);
				outptr = lines[i] + stride;
				/* this loop can't work for the last two lines as it will overwrite values it requires */
				for (j = 0; j < width - 2; j++) {
					/* Note the swapping of R and B, to get ARGB from what
					 * looks like BGR data. */
					inptr -= 3;
					outptr -= 4;
					g_assert (inptr && outptr);
					set_pixel_bgra(outptr, 0, inptr[2], inptr[1], inptr[0], 0xff);
				}
				/* keep last 2 lines in temporary variables */
				if (width > 1) {
					BYTE b2, g2, r2, b1, g1, r1;
					inptr -= 3;
					g_assert (inptr);
					b2 = inptr[2];
					g2 = inptr[1];
					r2 = inptr[0];
					inptr -= 3;
					g_assert (inptr);
					b1 = inptr[2];
					g1 = inptr[1];
					r1 = inptr[0];
					outptr -= 4;
					g_assert (outptr);
					set_pixel_bgra(outptr, 0, b2, g2, r2, 0xff);
					outptr -= 4;
					set_pixel_bgra(outptr, 0, b1, g1, r1, 0xff);
				} else 	if (width > 0) {
					/* in case the jpeg has a single line */
					BYTE b, g, r;
					inptr -= 3;
					g_assert (inptr);
					b = inptr[2];
					g = inptr[1];
					r = inptr[0];
					outptr -= 4;
					g_assert (outptr);
					set_pixel_bgra(outptr, 0, b, g, r, 0xff);
				}
			}
		}
	}

	jpeg_finish_decompress (&cinfo);
	jpeg_destroy_decompress (&cinfo);

	result->active_bitmap->scan0 = destbuf;
	result->active_bitmap->reserved = GBD_OWN_SCAN0;
	
	*image = result;
	return Ok;

error:
	/* coverity[dead_error_line] */
	if (destbuf != NULL) {
		GdipFree (destbuf);
	}

	if (result != NULL) {
		gdip_bitmap_dispose (result);
	}
	*image = NULL;
	return status;
}

#ifdef HAVE_LIBEXIF
static void
add_properties_from_entry (ExifEntry *entry, void *user_data)
{
	ActiveBitmapData *bitmap_data = (ActiveBitmapData *) user_data;

	gdip_bitmapdata_property_add (bitmap_data, entry->tag, entry->size, entry->format, entry->data);
}

static void
add_properties_from_content (ExifContent *content, void *user_data)
{
	exif_content_foreach_entry (content, add_properties_from_entry, user_data);
}

static int
get_exif_tag_value_as_int (ExifData* exif, ExifTag tag)
{
	ExifEntry* entry = exif_data_get_entry (exif, tag);

	if (entry) {
		ExifByteOrder order;

		order = exif_data_get_byte_order (exif);
		return (int)exif_get_short (entry->data, order);
	}

	return -1;
}

static int
get_exif_tag_value_as_resolution (ExifData* exif, ExifTag tag)
{
	ExifEntry* entry = exif_data_get_entry (exif, tag);

	if (entry) {
		gchar buf[1024];

		exif_entry_get_value (entry, buf, 1024);
		return (int)g_ascii_strtoll (buf, NULL, 0);
	}

	return 0;
}

static void
load_exif_data (ExifData *exif_data, GpImage *image)
{
	ActiveBitmapData *bitmap;

	if (exif_data == NULL)
		return;

	bitmap = image->active_bitmap;
	exif_data_foreach_content (exif_data, add_properties_from_content, bitmap);
	/* thumbnail */
	if (exif_data->size != 0) {
		gdip_bitmapdata_property_add (bitmap, PropertyTagThumbnailData, exif_data->size, PropertyTagTypeByte, exif_data->data);
	}

	/* Get image resolution from the EXIF data if either the x-rolution or y-resolution was not set */
	if (image->active_bitmap->dpi_horz == 0 || image->active_bitmap->dpi_vert == 0) {
		int resolution_unit = get_exif_tag_value_as_int (exif_data, EXIF_TAG_RESOLUTION_UNIT);

		int x_resolution = get_exif_tag_value_as_resolution (exif_data, EXIF_TAG_X_RESOLUTION);
		int y_resolution = get_exif_tag_value_as_resolution (exif_data, EXIF_TAG_Y_RESOLUTION);

		if (resolution_unit == 2) { /* dpi */
			image->active_bitmap->dpi_horz = x_resolution;
			image->active_bitmap->dpi_vert = y_resolution;
		}
		else if (resolution_unit == 3) { /* dots/cm */
			image->active_bitmap->dpi_horz = x_resolution * 2.54;
			image->active_bitmap->dpi_vert = y_resolution * 2.54;
		}

		// Other densities are not supported. 
	}

	exif_data_unref (exif_data);
}
#endif

GpStatus 
gdip_load_jpeg_image_from_file (FILE *fp, const char *filename, GpImage **image)
{
	GpStatus st;

	gdip_stdio_jpeg_source_mgr_ptr src;

	src = (gdip_stdio_jpeg_source_mgr_ptr) GdipAlloc (sizeof (struct gdip_stdio_jpeg_source_mgr));
	if (src == NULL) {
		return OutOfMemory;
	}

	src->buf = GdipAlloc (JPEG_BUFFER_SIZE * sizeof(JOCTET));
	if (src->buf == NULL) {
		GdipFree(src);
		return OutOfMemory;
	}

	src->parent.init_source = _gdip_source_dummy_init;
	src->parent.fill_input_buffer = (boolean(*)(j_decompress_ptr))_gdip_source_stdio_fill_input_buffer;
	src->parent.skip_input_data = _gdip_source_stdio_skip_input_data;
	src->parent.resync_to_restart = jpeg_resync_to_restart;
	src->parent.term_source = _gdip_source_dummy_term;
	src->parent.bytes_in_buffer = 0;
	src->parent.next_input_byte = NULL;

	src->infp = fp;

	st = gdip_load_jpeg_image_internal ((struct jpeg_source_mgr *) src, image);
	GdipFree (src->buf);
	GdipFree (src);
#ifdef HAVE_LIBEXIF
	if (st == Ok) {
		load_exif_data (exif_data_new_from_file (filename), *image);
	}
#endif

	return st;
}

GpStatus
gdip_load_jpeg_image_from_stream_delegate (dstream_t *loader, GpImage **image)
{
	GpStatus st;
#ifdef HAVE_LIBEXIF
	unsigned int length;
	BYTE *ptr;
#endif

	gdip_stream_jpeg_source_mgr_ptr src;

	src = (gdip_stream_jpeg_source_mgr_ptr) GdipAlloc (sizeof (struct gdip_stream_jpeg_source_mgr));
	if (!src) {
		return OutOfMemory;
	}

	src->buf = GdipAlloc (JPEG_BUFFER_SIZE * sizeof(JOCTET));
	if (!src->buf) {
		GdipFree (src);
		return OutOfMemory;
	}

	src->parent.init_source = _gdip_source_dummy_init;
	src->parent.fill_input_buffer = (boolean(*)(j_decompress_ptr))_gdip_source_stream_fill_input_buffer;
	src->parent.skip_input_data = _gdip_source_stream_skip_input_data;
	src->parent.resync_to_restart = jpeg_resync_to_restart;
	src->parent.term_source = _gdip_source_dummy_init;
	src->parent.bytes_in_buffer = 0;
	src->parent.next_input_byte = NULL;

	src->loader = loader;
#ifdef HAVE_LIBEXIF
	dstream_keep_exif_buffer (loader);
#endif

	st = gdip_load_jpeg_image_internal ((struct jpeg_source_mgr *) src, image);
	GdipFree (src->buf);
	GdipFree (src);
#ifdef HAVE_LIBEXIF
	if (st == Ok){
		dstream_get_exif_buffer (loader, &ptr, &length);
		load_exif_data (exif_data_new_from_data (ptr, length), *image);
	}
#endif

	return st;
}

static GpStatus
gdip_save_jpeg_image_internal (FILE *fp, PutBytesDelegate putBytesFunc, GpImage *image, GDIPCONST EncoderParameters *params)
{
	gdip_stream_jpeg_dest_mgr_ptr	dest = NULL;
	struct jpeg_compress_struct	cinfo;
	struct gdip_jpeg_error_mgr	jerr;
	const EncoderParameter		*param;
	JOCTET		*scanline = NULL;
	int		need_argb_conversion = 0;
	GpStatus	status;

	cinfo.mem = NULL;

	/* Verify that we can support this pixel format */
	switch (image->active_bitmap->pixel_format) {
		case PixelFormat32bppARGB:
		case PixelFormat32bppPARGB:
		case PixelFormat32bppRGB:
		case PixelFormat24bppRGB:
			break;

		case PixelFormat1bppIndexed:
		case PixelFormat4bppIndexed:
		case PixelFormat8bppIndexed:
			image = gdip_convert_indexed_to_rgb (image);
			if (image == NULL) {
				return OutOfMemory;
			}

			/* We recurse, makes flow and cleanup more straightforward */
			status = gdip_save_jpeg_image_internal(fp, putBytesFunc, image, params);
			return status;

		default:
			status = InvalidParameter;
			goto error;
	}

	cinfo.err = jpeg_std_error ((struct jpeg_error_mgr *) &jerr);
	jerr.parent.error_exit = _gdip_jpeg_error_exit;
	jerr.parent.output_message = _gdip_jpeg_output_message;

	if (sigsetjmp (jerr.setjmp_buffer, 1)) {
		status = GenericError;
		goto error;
	}

	jpeg_create_compress (&cinfo);

	if (fp != NULL) {
		jpeg_stdio_dest (&cinfo, fp);
	} else {
		dest = GdipAlloc (sizeof (struct gdip_stream_jpeg_dest_mgr));
		if (!dest) {
			status = OutOfMemory;
			goto error;
		}

		dest->parent.init_destination = _gdip_dest_stream_init;
		dest->parent.empty_output_buffer = (boolean(*)(j_compress_ptr))_gdip_dest_stream_empty_output_buffer;
		dest->parent.term_destination = _gdip_dest_stream_term;

		dest->putBytesFunc = putBytesFunc;
		dest->buf = GdipAlloc (JPEG_BUFFER_SIZE);
		if (!dest->buf) {
			status = OutOfMemory;
			goto error;
		}

		cinfo.dest = (struct jpeg_destination_mgr *) dest;
	}

	cinfo.image_width = image->active_bitmap->width;
	cinfo.image_height = image->active_bitmap->height;
	if (gdip_get_pixel_format_components (image->active_bitmap->pixel_format) == 3) {
		cinfo.in_color_space = JCS_RGB;
		cinfo.input_components = 3;
		if (image->active_bitmap->pixel_format == PixelFormat24bppRGB) {
			need_argb_conversion = 1;
		} else {
			need_argb_conversion = 0;
		}
	} else if (gdip_get_pixel_format_components (image->active_bitmap->pixel_format) == 4) {
		cinfo.in_color_space = JCS_RGB;
		cinfo.input_components = 3;
		need_argb_conversion = 1;
	} else if (gdip_get_pixel_format_components (image->active_bitmap->pixel_format) == 1) {
		cinfo.in_color_space = JCS_RGB;
		cinfo.input_components = 1;
		need_argb_conversion = 1; /* do this conversion */
	}

	jpeg_set_defaults (&cinfo);

	if (gdip_get_pixel_format_components (image->active_bitmap->pixel_format) == 1) {
		jpeg_set_colorspace (&cinfo, JCS_GRAYSCALE);
	}

	/* Handle encoding parameters */
	if (params) {
		param = gdip_find_encoder_parameter (params, &GdipEncoderQuality);
		if (param != NULL) {
			int quality;

			if (param->Type == EncoderParameterValueTypeLong) {
				quality = * (int *) param->Value;
			} else if (param->Type == EncoderParameterValueTypeLongRange) {
				const int *pval = (int *) param->Value;

				quality = (pval[0] + pval[1]) / 2;
			} else if (param->Type == EncoderParameterValueTypeByte) {
				quality = *(BYTE*)param->Value;
			} else if (param->Type == EncoderParameterValueTypeShort) {
				quality = *(short *)param->Value;
			} else {
				/* Should we report an error here? */
				quality = 80;
			}

			jpeg_set_quality (&cinfo, quality, 0);
		}
	}

	jpeg_start_compress (&cinfo, TRUE);

	if (need_argb_conversion) {
		BYTE *inptr, *outptr;
		int i, j;

		scanline = GdipAlloc (image->active_bitmap->stride);
		if (!scanline) {
			status = OutOfMemory;
			goto error;
		}

		for (i = 0; i < image->active_bitmap->height; i++) {
			inptr = image->active_bitmap->scan0 + (i * image->active_bitmap->stride);
			outptr = scanline;

			for (j = 0; j < image->active_bitmap->width; j++) {
#ifdef WORDS_BIGENDIAN
				*outptr++ = inptr[1]; /* R */
				*outptr++ = inptr[2]; /* G */
				*outptr++ = inptr[3]; /* B */
#else
				*outptr++ = inptr[2]; /* R */
				*outptr++ = inptr[1]; /* G */
				*outptr++ = inptr[0]; /* B */
#endif
				inptr += 4;		  /* skip RGB+A */
			}

			jpeg_write_scanlines (&cinfo, &scanline, 1);
		}

		GdipFree (scanline);
		scanline = NULL;
	} else {
		int i;
		for (i = 0; i < image->active_bitmap->height; i++) {
			scanline = image->active_bitmap->scan0 + (i * image->active_bitmap->stride);
			jpeg_write_scanlines (&cinfo, &scanline, 1);
		}
	}

	jpeg_finish_compress (&cinfo);
	jpeg_destroy_compress (&cinfo);

	if (dest != NULL) {
		if (dest->buf != NULL) {
			GdipFree (dest->buf);
		}
		GdipFree (dest);
	}

	return Ok;

error:

	jpeg_destroy_compress (&cinfo);

	/* coverity[dead_error_line] */
	if (dest != NULL) {
		if (dest->buf != NULL) {
			GdipFree (dest->buf);
		}
		GdipFree (dest);
	}

	if (scanline != NULL && need_argb_conversion) {
		GdipFree (scanline);
	}

	return status;
}

GpStatus 
gdip_save_jpeg_image_to_file (FILE *fp, GpImage *image, GDIPCONST EncoderParameters *params)
{
	return gdip_save_jpeg_image_internal (fp, NULL, image, params);
}

GpStatus
gdip_save_jpeg_image_to_stream_delegate (PutBytesDelegate putBytesFunc,
										GpImage *image,
										GDIPCONST EncoderParameters *params)

{
	return gdip_save_jpeg_image_internal (NULL, putBytesFunc, image, params);
}

#else

/* No libjpeg */

#include "image.h"
#include "dstream.h"

ImageCodecInfo *
gdip_getcodecinfo_jpeg ()
{
	return NULL;
}

GpStatus
gdip_load_jpeg_image_from_file (FILE *fp, const char *filename, GpImage **image)
{
	*image = NULL;
	return UnknownImageFormat;
}

GpStatus 
gdip_save_jpeg_image_to_file (FILE *fp, GpImage *image, GDIPCONST EncoderParameters *params)
{
	return UnknownImageFormat;
}

GpStatus
gdip_load_jpeg_image_from_stream_delegate (dstream_t *loader, GpImage **image)
{
	*image = NULL;
	return UnknownImageFormat;
}

GpStatus
gdip_save_jpeg_image_to_stream_delegate (PutBytesDelegate putBytesFunc,
										GpImage *image,
										GDIPCONST EncoderParameters *params)
{
	return UnknownImageFormat;
}

#endif

GpStatus
gdip_fill_encoder_parameter_list_jpeg (EncoderParameters *buffer, UINT size)
{
	JpegEncoderParameters *jpegBuffer = (JpegEncoderParameters *) buffer;

	if (!buffer || size != sizeof (JpegEncoderParameters))
		return InvalidParameter;

	jpegBuffer->count = 5;

	jpegBuffer->transformation.Guid = GdipEncoderTransformation;
	jpegBuffer->transformation.NumberOfValues = 5;
	jpegBuffer->transformation.Type = EncoderParameterValueTypeLong;
	jpegBuffer->transformationData[0] = EncoderValueTransformRotate90;
	jpegBuffer->transformationData[1] = EncoderValueTransformRotate180;
	jpegBuffer->transformationData[2] = EncoderValueTransformRotate270;
	jpegBuffer->transformationData[3] = EncoderValueTransformFlipHorizontal;
	jpegBuffer->transformationData[4] = EncoderValueTransformFlipVertical;
	jpegBuffer->transformation.Value = &jpegBuffer->transformationData;

	jpegBuffer->quality.Guid = GdipEncoderQuality;
	jpegBuffer->quality.NumberOfValues = 1;
	jpegBuffer->quality.Type = EncoderParameterValueTypeLongRange;
	jpegBuffer->qualityRange[0] = 0;
	jpegBuffer->qualityRange[1] = 100;
	jpegBuffer->quality.Value = &jpegBuffer->qualityRange;

	jpegBuffer->luminanceTable.Guid = GdipEncoderLuminanceTable;
	jpegBuffer->luminanceTable.NumberOfValues = 0;
	jpegBuffer->luminanceTable.Type = EncoderParameterValueTypeShort;
	jpegBuffer->luminanceTable.Value = NULL;

	jpegBuffer->chrominanceTable.Guid = GdipEncoderChrominanceTable;
	jpegBuffer->chrominanceTable.NumberOfValues = 0;
	jpegBuffer->chrominanceTable.Type = EncoderParameterValueTypeShort;
	jpegBuffer->chrominanceTable.Value = NULL;

	jpegBuffer->imageItems.Guid = GdipEncoderImageItems;
	jpegBuffer->imageItems.NumberOfValues = 0;
	jpegBuffer->imageItems.Type = 9; // Undocumented type.
	jpegBuffer->imageItems.Value = NULL;

	return Ok;
}
