text: // ==========================================================
  // JPEG lossless transformations
  //
  // Design and implementation by
  // - Petr Pytelka (pyta@lightcomp.com)
  // - Hervé Drolon (drolon@infonie.fr)
  // - Mihail Naydenov (mnaydenov@users.sourceforge.net)
  //
  // This file is part of FreeImage 3
  //
  // COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY
  // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
  // THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
  // OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
  // CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT
  // THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
  // SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL
  // PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
  // THIS DISCLAIMER.
  //
  // Use at your own risk!
  // ==========================================================
  
  extern "C" {
  #define XMD_H
  #undef FAR
  #include <setjmp.h>
  
  #include "../../LibJPEG/jinclude.h"
  #include "../../LibJPEG/jpeglib.h"
  #include "../../LibJPEG/jerror.h"
  #include "../../LibJPEG/transupp.h"
  }
  
  #include "FreeImage.h"
  #include "Utilities.h"
  #include "FreeImageIO.h"
  
  // ----------------------------------------------------------
  //   Source manager & Destination manager setup
  //   (see PluginJPEG.cpp)
  // ----------------------------------------------------------
  
  void jpeg_freeimage_src(j_decompress_ptr cinfo, fi_handle infile, FreeImageIO *io);
  void jpeg_freeimage_dst(j_compress_ptr cinfo, fi_handle outfile, FreeImageIO *io);
  
  // ----------------------------------------------------------
  //   Error handling
  //   (see also PluginJPEG.cpp)
  // ----------------------------------------------------------
  
  /**
  	Receives control for a fatal error.  Information sufficient to
  	generate the error message has been stored in cinfo->err; call
  	output_message to display it.  Control must NOT return to the caller;
  	generally this routine will exit() or longjmp() somewhere.
  */
  METHODDEF(void)
  ls_jpeg_error_exit (j_common_ptr cinfo) {
  	// always display the message
  	(*cinfo->err->output_message)(cinfo);
  
  	// allow JPEG with a premature end of file
  	if((cinfo)->err->msg_parm.i[0] != 13) {
  
  		// let the memory manager delete any temp files before we die
  		jpeg_destroy(cinfo);
  
  		throw FIF_JPEG;
  	}
  }
  
  /**
  	Actual output of any JPEG message.  Note that this method does not know
  	how to generate a message, only where to send it.
  */
  METHODDEF(void)
  ls_jpeg_output_message (j_common_ptr cinfo) {
  	char buffer[JMSG_LENGTH_MAX];
  
  	// create the message
  	(*cinfo->err->format_message)(cinfo, buffer);
  	// send it to user's message proc
  	FreeImage_OutputMessageProc(FIF_JPEG, buffer);
  }
  
  // ----------------------------------------------------------
  //   Main program
  // ----------------------------------------------------------
  
  /**
  Build a crop string. 
  
  @param crop Output crop string
  @param left Specifies the left position of the cropped rectangle
  @param top Specifies the top position of the cropped rectangle
  @param right Specifies the right position of the cropped rectangle
  @param bottom Specifies the bottom position of the cropped rectangle
  @param width Image width
  @param height Image height
  @return Returns TRUE if successful, returns FALSE otherwise
  */
  static BOOL
  getCropString(char* crop, int* left, int* top, int* right, int* bottom, int width, int height) {
  	if(!left || !top || !right || !bottom) {
  		return FALSE;
  	}
  
  	*left = CLAMP(*left, 0, width);
  	*top = CLAMP(*top, 0, height);
  
  	// negative/zero right and bottom count from the edges inwards
  
  	if(*right <= 0) {
  		*right = width + *right;
  	}
  	if(*bottom <= 0) {
  		*bottom = height + *bottom;
  	}
  
  	*right = CLAMP(*right, 0, width);
  	*bottom = CLAMP(*bottom, 0, height);
  
  	// test for empty rect
  
  	if(((*left - *right) == 0) || ((*top - *bottom) == 0)) {
  		return FALSE;
  	}
  
  	// normalize the rectangle
  
  	if(*right < *left) {
  		INPLACESWAP(*left, *right);
  	}
  	if(*bottom < *top) {
  		INPLACESWAP(*top, *bottom);
  	}
  
  	// test for "noop" rect
  
  	if(*left == 0 && *right == width && *top == 0 && *bottom == height) {
  		return FALSE;
  	}
  
  	// build the crop option
  	sprintf(crop, "%dx%d+%d+%d", *right - *left, *bottom - *top, *left, *top);
  
  	return TRUE;
  }
  
  static BOOL
  JPEGTransformFromHandle(FreeImageIO* src_io, fi_handle src_handle, FreeImageIO* dst_io, fi_handle dst_handle, FREE_IMAGE_JPEG_OPERATION operation, int* left, int* top, int* right, int* bottom, BOOL perfect) {
  	const BOOL onlyReturnCropRect = (dst_io == NULL) || (dst_handle == NULL);
  	const long stream_start = onlyReturnCropRect ? 0 : dst_io->tell_proc(dst_handle);
  	BOOL swappedDim = FALSE;
  	BOOL trimH = FALSE;
  	BOOL trimV = FALSE;
  
  	// Set up the jpeglib structures
  	jpeg_decompress_struct srcinfo;
  	jpeg_compress_struct dstinfo;
  	jpeg_error_mgr jsrcerr, jdsterr;
  	jvirt_barray_ptr *src_coef_arrays = NULL;
  	jvirt_barray_ptr *dst_coef_arrays = NULL;
  	// Support for copying optional markers from source to destination file
  	JCOPY_OPTION copyoption;
  	// Image transformation options
  	jpeg_transform_info transfoptions;
  
  	// Initialize structures
  	memset(&srcinfo, 0, sizeof(srcinfo));
  	memset(&jsrcerr, 0, sizeof(jsrcerr));
  	memset(&jdsterr, 0, sizeof(jdsterr));
  	memset(&dstinfo, 0, sizeof(dstinfo));
  	memset(&transfoptions, 0, sizeof(transfoptions));
  
  	// Copy all extra markers from source file
  	copyoption = JCOPYOPT_ALL;
  
  	// Set up default JPEG parameters
  	transfoptions.force_grayscale = FALSE;
  	transfoptions.crop = FALSE;
  
  	// Select the transform option
  	switch(operation) {
  		case FIJPEG_OP_FLIP_H:		// horizontal flip
  			transfoptions.transform = JXFORM_FLIP_H;
  			trimH = TRUE;
  			break;
  		case FIJPEG_OP_FLIP_V:		// vertical flip
  			transfoptions.transform = JXFORM_FLIP_V;
  			trimV = TRUE;
  			break;
  		case FIJPEG_OP_TRANSPOSE:	// transpose across UL-to-LR axis
  			transfoptions.transform = JXFORM_TRANSPOSE;
  			swappedDim = TRUE;
  			break;
  		case FIJPEG_OP_TRANSVERSE:	// transpose across UR-to-LL axis
  			transfoptions.transform = JXFORM_TRANSVERSE;
  			trimH = TRUE;
  			trimV = TRUE;
  			swappedDim = TRUE;
  			break;
  		case FIJPEG_OP_ROTATE_90:	// 90-degree clockwise rotation
  			transfoptions.transform = JXFORM_ROT_90;
  			trimH = TRUE;
  			swappedDim = TRUE;
  			break;
  		case FIJPEG_OP_ROTATE_180:	// 180-degree rotation
  			trimH = TRUE;
  			trimV = TRUE;
  			transfoptions.transform = JXFORM_ROT_180;
  			break;
  		case FIJPEG_OP_ROTATE_270:	// 270-degree clockwise (or 90 ccw)
  			transfoptions.transform = JXFORM_ROT_270;
  			trimV = TRUE;
  			swappedDim = TRUE;
  			break;
  		default:
  		case FIJPEG_OP_NONE:		// no transformation
  			transfoptions.transform = JXFORM_NONE;
  			break;
  	}
  	// (perfect == TRUE) ==> fail if there is non-transformable edge blocks
  	transfoptions.perfect = (perfect == TRUE) ? TRUE : FALSE;
  	// Drop non-transformable edge blocks: trim off any partial edge MCUs that the transform can't handle.
  	transfoptions.trim = TRUE;
  
  	try {
  
  		// Initialize the JPEG decompression object with default error handling
  		srcinfo.err = jpeg_std_error(&jsrcerr);
  		srcinfo.err->error_exit = ls_jpeg_error_exit;
  		srcinfo.err->output_message = ls_jpeg_output_message;
  		jpeg_create_decompress(&srcinfo);
  
  		// Initialize the JPEG compression object with default error handling
  		dstinfo.err = jpeg_std_error(&jdsterr);
  		dstinfo.err->error_exit = ls_jpeg_error_exit;
  		dstinfo.err->output_message = ls_jpeg_output_message;
  		jpeg_create_compress(&dstinfo);
  
  		// Specify data source for decompression
  		jpeg_freeimage_src(&srcinfo, src_handle, src_io);
  
  		// Enable saving of extra markers that we want to copy
  		jcopy_markers_setup(&srcinfo, copyoption);
  
  		// Read the file header
  		jpeg_read_header(&srcinfo, TRUE);
  
  		// crop option
  		char crop[64];
  		const BOOL hasCrop = getCropString(crop, left, top, right, bottom, swappedDim ? srcinfo.image_height : srcinfo.image_width, swappedDim ? srcinfo.image_width : srcinfo.image_height);
  
  		if(hasCrop) {
  			if(!jtransform_parse_crop_spec(&transfoptions, crop)) {
  				FreeImage_OutputMessageProc(FIF_JPEG, "Bogus crop argument %s", crop);
  				throw(1);
  			}
  		}
  
  		// Any space needed by a transform option must be requested before
  		// jpeg_read_coefficients so that memory allocation will be done right
  
  		// Prepare transformation workspace
  		// Fails right away if perfect flag is TRUE and transformation is not perfect
  		if( !jtransform_request_workspace(&srcinfo, &transfoptions) ) {
  			FreeImage_OutputMessageProc(FIF_JPEG, "Transformation is not perfect");
  			throw(1);
  		}
  
  		if(left || top) {
  			// compute left and top offsets, it's a bit tricky, taking into account both
  			// transform, which might have trimed the image,
  			// and crop itself, which is adjusted to lie on a iMCU boundary
  
  			const int fullWidth = swappedDim ? srcinfo.image_height : srcinfo.image_width;
  			const int fullHeight = swappedDim ? srcinfo.image_width : srcinfo.image_height;
  
  			int transformedFullWidth = fullWidth;
  			int transformedFullHeight = fullHeight;
  
  			if(trimH && transformedFullWidth/transfoptions.iMCU_sample_width > 0) {
  				transformedFullWidth = (transformedFullWidth/transfoptions.iMCU_sample_width) * transfoptions.iMCU_sample_width;
  			}
  			if(trimV && transformedFullHeight/transfoptions.iMCU_sample_height > 0) {
  				transformedFullHeight = (transformedFullHeight/transfoptions.iMCU_sample_height) * transfoptions.iMCU_sample_height;
  			}
  
  			const int trimmedWidth = fullWidth - transformedFullWidth;
  			const int trimmedHeight = fullHeight - transformedFullHeight;
  
  			if(left) {
  				*left = trimmedWidth + transfoptions.x_crop_offset * transfoptions.iMCU_sample_width;
  			}
  			if(top) {
  				*top = trimmedHeight + transfoptions.y_crop_offset * transfoptions.iMCU_sample_height;
  			}
  		}
  
  		if(right) {
  			*right = (left ? *left : 0) + transfoptions.output_width;
  		}
  		if(bottom) {
  			*bottom = (top ? *top : 0) + transfoptions.output_height;
  		}
  
  		// if only the crop rect is requested, we are done
  
  		if(onlyReturnCropRect) {
  			jpeg_destroy_compress(&dstinfo);
  			jpeg_destroy_decompress(&srcinfo);
  			return TRUE;
  		}
  
  		// Read source file as DCT coefficients
  		src_coef_arrays = jpeg_read_coefficients(&srcinfo);
  
  		// Initialize destination compression parameters from source values
  		jpeg_copy_critical_parameters(&srcinfo, &dstinfo);
  
  		// Adjust destination parameters if required by transform options;
  		// also find out which set of coefficient arrays will hold the output
  		dst_coef_arrays = jtransform_adjust_parameters(&srcinfo, &dstinfo, src_coef_arrays, &transfoptions);
  
  		// Note: we assume that jpeg_read_coefficients consumed all input
  		// until JPEG_REACHED_EOI, and that jpeg_finish_decompress will
  		// only consume more while (! cinfo->inputctl->eoi_reached).
  		// We cannot call jpeg_finish_decompress here since we still need the
  		// virtual arrays allocated from the source object for processing.
  
  		if(src_handle == dst_handle) {
  			dst_io->seek_proc(dst_handle, stream_start, SEEK_SET);
  		}
  
  		// Specify data destination for compression
  		jpeg_freeimage_dst(&dstinfo, dst_handle, dst_io);
  
  		// Start compressor (note no image data is actually written here)
  		jpeg_write_coefficients(&dstinfo, dst_coef_arrays);
  
  		// Copy to the output file any extra markers that we want to preserve
  		jcopy_markers_execute(&srcinfo, &dstinfo, copyoption);
  
  		// Execute image transformation, if any
  		jtransform_execute_transformation(&srcinfo, &dstinfo, src_coef_arrays, &transfoptions);
  
  		// Finish compression and release memory
  		jpeg_finish_compress(&dstinfo);
  		jpeg_destroy_compress(&dstinfo);
  		jpeg_finish_decompress(&srcinfo);
  		jpeg_destroy_decompress(&srcinfo);
  
  	}
  	catch(...) {
  		jpeg_destroy_compress(&dstinfo);
  		jpeg_destroy_decompress(&srcinfo);
  		return FALSE;
  	}
  
  	return TRUE;
  }
  
  // ----------------------------------------------------------
  //   FreeImage interface
  // ----------------------------------------------------------
  
  BOOL DLL_CALLCONV
  FreeImage_JPEGTransformFromHandle(FreeImageIO* src_io, fi_handle src_handle, FreeImageIO* dst_io, fi_handle dst_handle, FREE_IMAGE_JPEG_OPERATION operation, int* left, int* top, int* right, int* bottom, BOOL perfect) {
  	return JPEGTransformFromHandle(src_io, src_handle, dst_io, dst_handle, operation, left, top, right, bottom, perfect);
  }
  
  static void
  closeStdIO(fi_handle src_handle, fi_handle dst_handle) {
  	if(src_handle) {
  		fclose((FILE*)src_handle);
  	}
  	if(dst_handle && (dst_handle != src_handle)) {
  		fclose((FILE*)dst_handle);
  	}
  }
  
  static BOOL
  openStdIO(const char* src_file, const char* dst_file, FreeImageIO* dst_io, fi_handle* src_handle, fi_handle* dst_handle) {
  	*src_handle = NULL;
  	*dst_handle = NULL;
  	
  	FreeImageIO io;
  	SetDefaultIO (&io);
  	
  	const BOOL isSameFile = (dst_file && (strcmp(src_file, dst_file) == 0)) ? TRUE : FALSE;
  	
  	FILE* srcp = NULL;
  	FILE* dstp = NULL;
  	
  	if(isSameFile) {
  		srcp = fopen(src_file, "r+b");
  		dstp = srcp;
  	}
  	else {
  		srcp = fopen(src_file, "rb");
  		if(dst_file) {
  			dstp = fopen(dst_file, "wb");
  		}
  	}
  	
  	if(!srcp || (dst_file && !dstp)) {
  		if(!srcp) {
  			FreeImage_OutputMessageProc(FIF_JPEG, "Cannot open \"%s\" for reading", src_file);
  		} else {
  			FreeImage_OutputMessageProc(FIF_JPEG, "Cannot open \"%s\" for writing", dst_file);
  		}
  		closeStdIO(srcp, dstp);
  		return FALSE;
  	}
  
  	if(FreeImage_GetFileTypeFromHandle(&io, srcp) != FIF_JPEG) {
  		FreeImage_OutputMessageProc(FIF_JPEG, " Source file \"%s\" is not jpeg", src_file);
  		closeStdIO(srcp, dstp);
  		return FALSE;
  	}
  
  	*dst_io = io;
  	*src_handle = srcp;
  	*dst_handle = dstp;
  
  	return TRUE;
  }
  
  static BOOL
  openStdIOU(const wchar_t* src_file, const wchar_t* dst_file, FreeImageIO* dst_io, fi_handle* src_handle, fi_handle* dst_handle) {
  #ifdef _WIN32
  
  	*src_handle = NULL;
  	*dst_handle = NULL;
  
  	FreeImageIO io;
  	SetDefaultIO (&io);
  	
  	const BOOL isSameFile = (dst_file && (wcscmp(src_file, dst_file) == 0)) ? TRUE : FALSE;
  
  	FILE* srcp = NULL;
  	FILE* dstp = NULL;
  
  	if(isSameFile) {
  		srcp = _wfopen(src_file, L"r+b");
  		dstp = srcp;
  	} else {
  		srcp = _wfopen(src_file, L"rb");
  		if(dst_file) {
  			dstp = _wfopen(dst_file, L"wb");
  		}
  	}
  
  	if(!srcp || (dst_file && !dstp)) {
  		if(!srcp) {
  			FreeImage_OutputMessageProc(FIF_JPEG, "Cannot open source file for reading");
  		} else {
  			FreeImage_OutputMessageProc(FIF_JPEG, "Cannot open destination file for writing");
  		}
  		closeStdIO(srcp, dstp);
  		return FALSE;
  	}
  
  	if(FreeImage_GetFileTypeFromHandle(&io, srcp) != FIF_JPEG) {
  		FreeImage_OutputMessageProc(FIF_JPEG, " Source file is not jpeg");
  		closeStdIO(srcp, dstp);
  		return FALSE;
  	}
  
  	*dst_io = io;
  	*src_handle = srcp;
  	*dst_handle = dstp;
  
  	return TRUE;
  
  #else
  	return FALSE;
  #endif // _WIN32
  }
  
  BOOL DLL_CALLCONV
  FreeImage_JPEGTransform(const char *src_file, const char *dst_file, FREE_IMAGE_JPEG_OPERATION operation, BOOL perfect) {
  	FreeImageIO io;
  	fi_handle src;
  	fi_handle dst;
  	
  	if(!openStdIO(src_file, dst_file, &io, &src, &dst)) {
  		return FALSE;
  	}
  	
  	BOOL ret = JPEGTransformFromHandle(&io, src, &io, dst, operation, NULL, NULL, NULL, NULL, perfect);
  
  	closeStdIO(src, dst);
  
  	return ret;
  }
  
  BOOL DLL_CALLCONV
  FreeImage_JPEGCrop(const char *src_file, const char *dst_file, int left, int top, int right, int bottom) {
  	FreeImageIO io;
  	fi_handle src;
  	fi_handle dst;
  	
  	if(!openStdIO(src_file, dst_file, &io, &src, &dst)) {
  		return FALSE;
  	}
  	
  	BOOL ret = FreeImage_JPEGTransformFromHandle(&io, src, &io, dst, FIJPEG_OP_NONE, &left, &top, &right, &bottom, FALSE);
  	
  	closeStdIO(src, dst);
  	
  	return ret;
  }
  
  BOOL DLL_CALLCONV
  FreeImage_JPEGTransformU(const wchar_t *src_file, const wchar_t *dst_file, FREE_IMAGE_JPEG_OPERATION operation, BOOL perfect) {
  	FreeImageIO io;
  	fi_handle src;
  	fi_handle dst;
  	
  	if(!openStdIOU(src_file, dst_file, &io, &src, &dst)) {
  		return FALSE;
  	}
  	
  	BOOL ret = JPEGTransformFromHandle(&io, src, &io, dst, operation, NULL, NULL, NULL, NULL, perfect);
  	
  	closeStdIO(src, dst);
  
  	return ret;
  }
  
  BOOL DLL_CALLCONV
  FreeImage_JPEGCropU(const wchar_t *src_file, const wchar_t *dst_file, int left, int top, int right, int bottom) {
  	FreeImageIO io;
  	fi_handle src;
  	fi_handle dst;
  	
  	if(!openStdIOU(src_file, dst_file, &io, &src, &dst)) {
  		return FALSE;
  	}
  	
  	BOOL ret = FreeImage_JPEGTransformFromHandle(&io, src, &io, dst, FIJPEG_OP_NONE, &left, &top, &right, &bottom, FALSE);
  
  	closeStdIO(src, dst);
  
  	return ret;
  }
  
  BOOL DLL_CALLCONV
  FreeImage_JPEGTransformCombined(const char *src_file, const char *dst_file, FREE_IMAGE_JPEG_OPERATION operation, int* left, int* top, int* right, int* bottom, BOOL perfect) {
  	FreeImageIO io;
  	fi_handle src;
  	fi_handle dst;
  	
  	if(!openStdIO(src_file, dst_file, &io, &src, &dst)) {
  		return FALSE;
  	}
  	
  	BOOL ret = FreeImage_JPEGTransformFromHandle(&io, src, &io, dst, operation, left, top, right, bottom, perfect);
  
  	closeStdIO(src, dst);
  
  	return ret;
  }
  
  BOOL DLL_CALLCONV
  FreeImage_JPEGTransformCombinedU(const wchar_t *src_file, const wchar_t *dst_file, FREE_IMAGE_JPEG_OPERATION operation, int* left, int* top, int* right, int* bottom, BOOL perfect) {
  	FreeImageIO io;
  	fi_handle src;
  	fi_handle dst;
  	
  	if(!openStdIOU(src_file, dst_file, &io, &src, &dst)) {
  		return FALSE;
  	}
  	
  	BOOL ret = FreeImage_JPEGTransformFromHandle(&io, src, &io, dst, operation, left, top, right, bottom, perfect);
  
  	closeStdIO(src, dst);
  
  	return ret;
  }
  
  // --------------------------------------------------------------------------
  
  static BOOL
  getMemIO(FIMEMORY* src_stream, FIMEMORY* dst_stream, FreeImageIO* dst_io, fi_handle* src_handle, fi_handle* dst_handle) {
  	*src_handle = NULL;
  	*dst_handle = NULL;
  
  	FreeImageIO io;
  	SetMemoryIO (&io);
  
  	if(dst_stream) {
  		FIMEMORYHEADER *mem_header = (FIMEMORYHEADER*)(dst_stream->data);
  		if(mem_header->delete_me != TRUE) {
  			// do not save in a user buffer
  			FreeImage_OutputMessageProc(FIF_JPEG, "Destination memory buffer is read only");
  			return FALSE;
  		}
  	}
  
  	*dst_io = io;
  	*src_handle = src_stream;
  	*dst_handle = dst_stream;
  
  	return TRUE;
  }
  
  BOOL DLL_CALLCONV
  FreeImage_JPEGTransformCombinedFromMemory(FIMEMORY* src_stream, FIMEMORY* dst_stream, FREE_IMAGE_JPEG_OPERATION operation, int* left, int* top, int* right, int* bottom, BOOL perfect) {
  	FreeImageIO io;
  	fi_handle src;
  	fi_handle dst;
  	
  	if(!getMemIO(src_stream, dst_stream, &io, &src, &dst)) {
  		return FALSE;
  	}
  	
  	return FreeImage_JPEGTransformFromHandle(&io, src, &io, dst, operation, left, top, right, bottom, perfect);
  }
  
  