/* libsswf_tag_image.c++ -- written by Alexis WILKE for Made to Order Software Corp. (c) 2002-2008 */

/*

Copyright (c) 2002-2008 Made to Order Software Corp.

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.

*/

/** \file
 *
 * \brief The implementation of the sswf::TagImage class
 *
 * This file declares the body of the functions which are not
 * inline. It is part of the SSWF library.
 */

#include	"sswf/libsswf.h"

using namespace sswf;




/////////////////////////////////////////// TagImage


/** \class sswf::TagImage
 *
 * \brief The image class in SWF.
 *
 * This class is used to include an image in an SWF animation.
 * Images become fills in a style which is then rendered by a
 * shape object.
 *
 * SWF supports only two image formats: JPEG and Lossless (which
 * is similar to PNG: a compressed bitmap using zlib.)
 *
 * Older movies were constrained to one set of JPEG Tables and any
 * number of DefineBitsJPEG. Newer movies can incorporate the JPEG
 * Tables in the DefineBitsJPEG2. Also, since version 3, SWF supports
 * DefineBitsJPEG3 that includes an alpha channel (compressed with
 * zlib) for JPEG images.
 *
 * Lossless images can also include an alpha channel. It supports
 * different formats such as grey scales and paletted images.
 * The library will automatically try to use a palette if possible
 * (i.e. if your image is small or only uses a very limited number
 * of colors.)
 *
 * \sa <a href="../SWFalexref.html#tag_jpegtables">SWF Alexis' Reference&mdash;DefineJPEGTables</a>
 * \sa <a href="../SWFalexref.html#tag_definebitsjpeg">SWF Alexis' Reference&mdash;DefineBitsJPEG</a>
 * \sa <a href="../SWFalexref.html#tag_definebitsjpeg2">SWF Alexis' Reference&mdash;DefineBitsJPEG2</a>
 * \sa <a href="../SWFalexref.html#tag_definebitsjpeg3">SWF Alexis' Reference&mdash;DefineBitsJPEG3</a>
 * \sa <a href="../SWFalexref.html#tag_definebitslossless">SWF Alexis' Reference&mdash;DefineBitsLossless</a>
 * \sa <a href="../SWFalexref.html#tag_definebitslossless2">SWF Alexis' Reference&mdash;DefineBitsLossless2</a>
 * \sa <a href="../SWFalexref.html#swf_tag">SWF Alexis' Reference&mdash;swf_tag</a>
 */



/** \enum sswf::TagImage::image_format_t
 *
 * \brief The list of formats available for the TagImage
 *
 * This enumeration lists all the possible formats supported
 * by the SWF format. Note that does not define the image
 * formats supported by the SSWF library.
 */

/** \var sswf::TagImage::IMAGE_FORMAT_UNKNOWN
 *
 * \brief By default the format of an image is set to Unknown
 *
 * This is the default format until you specifically call
 * the sswf::TagImage::SetFormat() function.
 *
 * The TagImage does not try to guess the output format for
 * you (at least, not yet.)
 */

/** \var sswf::TagImage::IMAGE_FORMAT_LOSSLESS_BEST
 *
 * \brief Used to let the library select between the different lossless formats
 *
 * This format is a special internal format one can use to let the
 * library choose between the different available lossless formats.
 * This means the library will try all the different modes and
 * choose the one which generates the smallest result.
 *
 * \sa sswf::TagImage::IMAGE_FORMAT_LOSSLESS_8
 * \sa sswf::TagImage::IMAGE_FORMAT_LOSSLESS_16
 * \sa sswf::TagImage::IMAGE_FORMAT_LOSSLESS_32
 */

/** \var sswf::TagImage::IMAGE_FORMAT_LOSSLESS_8
 *
 * \brief Save a paletted image
 *
 * This format is not currently supported because we do not have a
 * working quantization function.
 *
 * The sswf::TagImage::IMAGE_FORMAT_LOSSLESS_BEST may make use of
 * that format if it can transform the entire image to a paletted
 * image.
 *
 * \sa sswf::TagImage::IMAGE_FORMAT_LOSSLESS_BEST
 * \sa sswf::TagImage::IMAGE_FORMAT_LOSSLESS_16
 * \sa sswf::TagImage::IMAGE_FORMAT_LOSSLESS_32
 */

/** \var sswf::TagImage::IMAGE_FORMAT_LOSSLESS_16
 *
 * \brief Save a lossless image in 16 bits
 *
 * Save the image in a lossless 16 bits image. When the
 * sswf::TagImage::IMAGE_FORMAT_LOSSLESS_BEST is used, it
 * switches to the 16 bits format only if all the colors
 * use only the 5 top bits (i.e. bits 0, 1 and 2 are all
 * zeroes.)
 *
 * The 16 bits image is really only 15 bits, 5 bits per
 * component (xrrrrrgggggbbbbb).
 *
 * \sa sswf::TagImage::IMAGE_FORMAT_LOSSLESS_BEST
 * \sa sswf::TagImage::IMAGE_FORMAT_LOSSLESS_8
 * \sa sswf::TagImage::IMAGE_FORMAT_LOSSLESS_32
 */

/** \var sswf::TagImage::IMAGE_FORMAT_LOSSLESS_32
 *
 * \brief Saves the Lossless image in 32 bits
 *
 * This format saves an RGB image in XRGB format and
 * an ARGB image in ARGB format. When no alpha is saved
 * it puts 0 everywhere.
 *
 * \sa sswf::TagImage::IMAGE_FORMAT_LOSSLESS_BEST
 * \sa sswf::TagImage::IMAGE_FORMAT_LOSSLESS_8
 * \sa sswf::TagImage::IMAGE_FORMAT_LOSSLESS_16
 */

/** \var sswf::TagImage::IMAGE_FORMAT_JPEG
 *
 * \brief Saves the image compressed with JPEG
 *
 * This format requests that the TagImage be compressed
 * using the JPEG format.
 */

/** \var sswf::TagImage::IMAGE_FORMAT_max
 *
 * \brief Maximum format number.
 *
 * This definition is only to define the largest possible
 * format.
 */



/** \brief Initializes the TagImage object.
 *
 * This function initializes the image to all defaults.
 *
 * This means no image, no alpha channel, default quality.
 *
 * \param[in] parent The TagHeader in which this TagImage is to be inserted
 */
TagImage::TagImage(TagBase *parent)
	: TagBaseID("image", parent)
{
	f_format = IMAGE_FORMAT_UNKNOWN;
	f_image.f_width = 0;
	f_image.f_height = 0;
	f_image.f_alpha = false;
	f_image.f_data = 0;
	f_quality = 0;
	f_count = 0;
	f_colormap = 0;
}


/* Ensures that the image data buffer is freed
TagImage::~TagImage()
{
	// This is automatic as long as the user uses TagImage::MemAlloc() to
	// allocate the buffer.
	//MemFree(f_image.f_data);
}
*/


/** \fn sswf::TagImage::GetImageData(image_t& image_data)
 *
 * \brief Retrieve the current image information.
 *
 * This function retrieves the image information. This is a direct
 * copy of the image structure with the internal data pointer.
 * Do not modify the pointer or free it.
 *
 * \param[out] image_data An image structure
 *
 * \return A copy of the internal image structure
 */


/** \fn sswf::TagImage::SetFormat(image_format_t format)
 *
 * \brief Set the required format for when the image is to be saved.
 *
 * This function setst the format that the TagImage will use when
 * saving the image.
 *
 * The possible formats are:
 *
 * \code
 *	IMAGE_FORMAT_LOSSLESS_BEST
 *	IMAGE_FORMAT_LOSSLESS_8
 *	IMAGE_FORMAT_LOSSLESS_16
 *	IMAGE_FORMAT_LOSSLESS_32
 *	IMAGE_FORMAT_JPEG
 * \endcode
 *
 * The IMAGE_FORMAT_LOSSLESS_BEST format is used to let the system
 * choose between the different LOSSLESS formats. It is lossless
 * meaning that the best to ensure the smallest possible image
 * is used without losing even one pixel (which often means using
 * LOSSLESS 32.)
 *
 * \param[in] format One of the IMAGE_FORMAT_...
 */


/** \fn sswf::TagImage::SetQuality(int quality)
 *
 * \brief Defines the quality level for a JPEG.
 *
 * When saving an image using the JPEG format, one can choose the
 * quality of the image. This is done with this function.
 *
 * The quality is a number from 0 to 100.
 *
 * \param[in] quality	The quality used to compress the image to a JPEG
 */


/** \brief Returns the flags defined for a TagImage.
 *
 * This function defines the type flags for a tag image.
 *
 * It is a definition and it has an identifier.
 *
 * \return SWF_TYPE_DEFINE and SWF_TYPE_HAS_ID
 */
TagBase::swf_type_t TagImage::TypeFlags(void) const
{
	return SWF_TYPE_DEFINE | SWF_TYPE_HAS_ID;
}




/// The offsets to read/write in the header of a targa image file
enum targa_header_t {
	TGA_OFFSET_IDENTIFIER_LENGTH = 0,
	TGA_OFFSET_COLORMAP_TYPE,
	TGA_OFFSET_IMAGE_TYPE,			// includes compression flag
	TGA_OFFSET_COLORMAP_INDEX_LO,
	TGA_OFFSET_COLORMAP_INDEX_HI,
	TGA_OFFSET_COLORMAP_LENGTH_LO,
	TGA_OFFSET_COLORMAP_LENGTH_HI,
	TGA_OFFSET_COLORMAP_SIZE,
	TGA_OFFSET_ORIGIN_X_LO,
	TGA_OFFSET_ORIGIN_X_HI,
	TGA_OFFSET_ORIGIN_Y_LO,
	TGA_OFFSET_ORIGIN_Y_HI,
	TGA_OFFSET_WIDTH_LO,
	TGA_OFFSET_WIDTH_HI,
	TGA_OFFSET_HEIGHT_LO,
	TGA_OFFSET_HEIGHT_HI,
	TGA_OFFSET_BITS_PER_PIXEL,
	TGA_OFFSET_FLAGS,

	TGA_HEADER_SIZE			// the size of the targa files header
};




//********************************************************************************
//**********   JPEG Loader/Saver   ***********************************************
//********************************************************************************
// this is really ugly and hopefully it will change one day!
extern "C" {
// NOTE: in older versions, the JPEG includes didn't support C++
#include	<jpeglib.h>
#include	<jerror.h>
#include	<setjmp.h>

/// Structure used to read a JPG image
typedef struct SSWF_JPG_ERROR {
	struct jpeg_error_mgr	pub;		/* "public" fields of the error manager */
	jmp_buf			setjmp_buffer;	/* for return to caller */
} sswf_jpg_error;

#define	SSWF_JPG_BUFSIZE		4096


/// Structure definition for the source buffer of a JPG image
typedef struct SSWF_JPG_SOURCE {
	struct jpeg_source_mgr	src;
	FILE *			file;
	JOCTET			buffer[SSWF_JPG_BUFSIZE];		/* temporary data */
} sswf_jpg_source;

#define	jsrc	((sswf_jpg_source *) cinfo->src)



/// Structure definition for the destination buffer of a JPG image
typedef struct SSWF_JPG_DESTINATION {
	struct jpeg_destination_mgr	dst;
	Data *				encoding;
	Data *				image;
	Data *				current;
	unsigned long			size;
	unsigned long			offset;
	long				state;
	long				last_code;		/* 0xFF 0x?? - the 0x?? value */
	JOCTET				buffer[SSWF_JPG_BUFSIZE];
} sswf_jpg_destination;

#define	jdst	((sswf_jpg_destination *) cinfo->dest)



/* if an error occurs, this function will be called and the JPEG process will be stopped */
static void sswfJPEGError(j_common_ptr cinfo)
{
	sswf_jpg_error	*myerr;

	myerr = (sswf_jpg_error *) cinfo->err;	/* cinfo->err really points to a img_jpg_error struct, so coerce pointer */
	longjmp(myerr->setjmp_buffer, 1);	/* Return control to the setjmp point */
/*NOTREACHED*/
}




static boolean sswfFillInputBuffer(j_decompress_ptr cinfo)
{
	int		r;

	jsrc->src.next_input_byte = jsrc->buffer;
	r = fread(jsrc->buffer, 1, SSWF_JPG_BUFSIZE, jsrc->file);
	if(r < 0) {
		return FALSE;
	}
	else if(r == 0) {
		/* send an "end of image" - i.e. EOF... */
		jsrc->buffer[0] = JPEG_EOI;
		r = 1;
	}
	jsrc->src.bytes_in_buffer = r;

	return TRUE;
}


static void sswfInitSource(j_decompress_ptr cinfo)
{
	(void) sswfFillInputBuffer(cinfo);
}


static void sswfSkipInputData(j_decompress_ptr cinfo, long num_bytes)
{
	int		r;

	if(num_bytes > 0) {
		r = jsrc->src.bytes_in_buffer;
		r -= num_bytes;
		if(r > 0) {
			jsrc->src.next_input_byte += num_bytes;
			jsrc->src.bytes_in_buffer = r;
		}
		else {
			if(r < 0) {
				// skip these bytes and read in some more data
				fseek(jsrc->file, -r, SEEK_CUR);
			}
			(void) sswfFillInputBuffer(cinfo);
		}
	}
}


static boolean sswfResyncToRestart(j_decompress_ptr cinfo, int desired)
{
	return jpeg_resync_to_restart(cinfo, desired);
}


static void sswfTermSource(j_decompress_ptr cinfo)
{
}






static void sswfWriteData(sswf_jpg_destination *dst, const unsigned char *buffer, unsigned long size)
{
	while(size > 0) {
		switch(dst->state) {
		case 0:
			/* we expect 0xFF */
			if(*buffer != 0xFF) {
				fflush(stdout);
				fprintf(stderr, "WARNING: at offset %ld the JPEG format didn't send us 0xFF when expected! (Got 0x%02X instead)\n", dst->offset, (int) *buffer);
			}
			else {
				dst->state = 1;
			}
			buffer++;
			size--;
			dst->offset++;
			break;

		case 1:
			/* we expect a valid JPEG code - though this isn't tested...
			 * we just catch a few codes we need to know of in order to
			 * insert the extra bytes we need there
			 */
			dst->last_code = *buffer;
			buffer++;
			size--;
			dst->offset++;
			switch(dst->last_code) {
			case 0xD8:		// start -> empty tag
				dst->image->PutByte((unsigned char) 0xFF);
				dst->image->PutByte((unsigned char) 0xD8);
				dst->encoding->PutByte((unsigned char) 0xFF);
				dst->encoding->PutByte((unsigned char) 0xD8);
				dst->state = 0;
				break;

			case 0xD9:		// end -> empty tag
				assert(0, "at offset %ld in the JPEG format, we didn't expect the end marker (0xFF 0xD9)\n", dst->offset);
				/*NOTREACHED*/

			case 0xDA:
				/* switch to image data only now */
				// encoding is over
				dst->encoding->PutByte((unsigned char) 0xFF);
				dst->encoding->PutByte((unsigned char) 0xD9);

				// we need the 0xFF 0xDA still in the other buffer
				dst->image->PutByte((unsigned char) 0xFF);
				dst->image->PutByte((unsigned char) 0xDA);
				dst->state = 5;
				break;

			// not sure which tags are supposed to be in the 1st and 2nd streams...
			case 0xE0:
			case 0xC0:
				dst->current = dst->image;
				dst->state = 2;
				break;

			default:
				dst->current = dst->encoding;
				dst->state = 2;
				break;

			}
			break;

		case 2:
			/* get 1st byte of size */
			dst->size = *buffer * 256;
			dst->state = 3;
			buffer++;
			size--;
			dst->offset++;
			break;

		case 3:
			dst->size += *buffer;
			buffer++;
			size--;
			dst->offset++;

			/* can the size be zero here?!? */
			if(dst->size > 0) {
				dst->current->PutByte((unsigned char) 0xFF);
				dst->current->PutByte((unsigned char) dst->last_code);
				dst->current->PutByte((unsigned char) (dst->size >> 8));
				dst->current->PutByte((unsigned char) dst->size);

				dst->size -= 2;

				dst->state = 4;
			}
			else {
				dst->state = 0;
			}
			break;

		case 4:
			if(size >= dst->size) {
				dst->current->Write(buffer, dst->size);
				buffer += dst->size;
				size -= dst->size;
				dst->offset += dst->size;
				dst->state = 0;
			}
			else {
				dst->current->Write(buffer, size);
				dst->offset += size;
				size = 0;		/* buffer used up */
			}
			break;

		case 5:
			dst->image->Write(buffer, size);
			size = 0;
			break;

		}
	}
}


static void sswfInitDestination(j_compress_ptr cinfo)
{
	jdst->dst.next_output_byte = jdst->buffer;
	jdst->dst.free_in_buffer = SSWF_JPG_BUFSIZE;
}


static boolean sswfEmptyOutputBuffer(j_compress_ptr cinfo)
{
	sswfWriteData(jdst, jdst->buffer, SSWF_JPG_BUFSIZE);
	jdst->dst.next_output_byte = jdst->buffer;
	jdst->dst.free_in_buffer = SSWF_JPG_BUFSIZE;

	return TRUE;
}


static void sswfTermDestination(j_compress_ptr cinfo)
{
	int		sz;

	sz = SSWF_JPG_BUFSIZE - jdst->dst.free_in_buffer;
	sswfWriteData(jdst, jdst->buffer, sz);
	//jdst->data->Write(jdst->buffer, sz);
}



}		// extern "C"




/** \brief Load a JPEG image
 *
 * This function loads and decompresses a JPEG image.
 *
 * If you want to set an image in the TagImage, use SetFilename() instead.
 *
 * \note
 * This function does not call OnError(). It is expected that you will use
 * SetFilename() which generates errors as required. Note that the function
 * specifically returns ErrorManager::ERROR_CODE_UNKNOWN_FORMAT if the
 * image does not look like a JPEG. Other errors means that either there
 * was an I/O error or the JPEG is corrupted.
 *
 * \param[in] filename	The name of the file to be loaded
 * \param[in] im	The bitmap structure where the image is loaded
 *
 * \return An error code or ErrorManager::ERROR_CODE_NONE
 *
 * \sa sswf::TagImage::LoadTGA(const char *filename, image_t& im)
 */
ErrorManager::error_code_t TagImage::LoadJPEG(const char *filename, image_t& im)
{
	sswf_jpg_error			jerr;
	struct jpeg_decompress_struct	cinfo;
	sswf_jpg_source			src;
	unsigned char			*s, *dst;
	long				i, size;
	unsigned long			sl;
	FILE				*f;

/* try to open the JPEG file and ensure it is a JPEG! */
	f = fopen(filename, "rb");
	if(f == NULL) {
		return ErrorManager::ERROR_CODE_IO;
	}
	if(fread(src.buffer, 20, 1, f) != 1) {
		fclose(f);
		return ErrorManager::ERROR_CODE_IO;
	}
	if(src.buffer[ 0] != 0xFF
	|| src.buffer[ 1] != 0xD8
	|| src.buffer[ 2] != 0xFF
	/* a JPEG can start with any header, really!
	|| src.buffer[ 3] != 0xE0
	|| src.buffer[ 4] != 0x00
	|| src.buffer[ 5] != 0x10
	|| src.buffer[ 6] != 0x4A
	|| src.buffer[ 7] != 0x46
	|| src.buffer[ 8] != 0x49
	|| src.buffer[ 9] != 0x46
	|| src.buffer[10] != 0x00*/) {
		fclose(f);
		return ErrorManager::ERROR_CODE_UNKNOWN_FORMAT;
	}
	// rewind so the decompressor reads everything
	fseek(f, 0, SEEK_SET);

/* setup the ugly error handler */
	cinfo.err = jpeg_std_error(&jerr.pub);
	jerr.pub.error_exit = sswfJPEGError;
	if(setjmp(jerr.setjmp_buffer)) {
		/* an error occured! */
		jpeg_destroy_decompress(&cinfo);
		fclose(f);
		return ErrorManager::ERROR_CODE_INVALID_IMAGE;
	}

/* initialize the decompressor */
	jpeg_create_decompress(&cinfo);

/* setup the input callbacks */
	src.src.init_source       = sswfInitSource;
	src.src.fill_input_buffer = sswfFillInputBuffer;
	src.src.skip_input_data   = sswfSkipInputData;
	src.src.resync_to_restart = sswfResyncToRestart;
	src.src.term_source       = sswfTermSource;
	src.file = f;

	cinfo.src = &src.src;

/* read the JPEG setup from the source file */
	jpeg_read_header(&cinfo, TRUE);

/* ask for an RGB image (which is the usual default) */
	cinfo.out_color_space = JCS_RGB;

/* start the decompressor (only after that call can we be sure of the output parameters) */
	jpeg_start_decompress(&cinfo);

/* make sure we're getting an RGB image */
	if(cinfo.output_components != 3) {
		jpeg_destroy_decompress(&cinfo);
		fclose(f);
		return ErrorManager::ERROR_CODE_INVALID_IMAGE;
	}

/* allocate the image in which the JPEG is read */
	im.f_alpha = false;
	im.f_width = cinfo.output_width;
	im.f_height = cinfo.output_height;

	size = im.f_width * im.f_height;
	im.f_data = (unsigned char *) MemAlloc(size * 4, "buffer for image data (JPEG)");

	dst = im.f_data;
	sl = cinfo.output_scanline + 1;
	while(cinfo.output_scanline != sl && cinfo.output_scanline < cinfo.output_height) {
		sl = cinfo.output_scanline;
		jpeg_read_scanlines(&cinfo, (JSAMPARRAY) &dst, 1);

		// transform the RGB in RGBA
		i = cinfo.output_width;
		s = (unsigned char *) dst + i * 3;
		dst += i * 4;
		while(i > 0) {
			i--;
			s -= 3;
			dst -= 4;
			// WARNING: watch out! the order is important in the copy below!
			dst[3] = s[2];
			dst[2] = s[1];
			dst[1] = s[0];
			dst[0] = 255;
		}
		dst += cinfo.output_width * 4;
	}

/* stop and free the decompressor */
	if(cinfo.output_scanline != sl) {
		jpeg_finish_decompress(&cinfo);
	}
	jpeg_destroy_decompress(&cinfo);

	fclose(f);

	return ErrorManager::ERROR_CODE_NONE;
}





/** \brief Save a JPEG in a set of Data buffers.
 *
 * This function saves a JPEG image table encodings and compressed bits in
 * two Data buffers.
 *
 * \param[in] encoding	Save the JPEG tables in this Data buffer
 * \param[in] image	Save the JPEG compressed bits in this Data buffer
 *
 * \return An error or ErrorManager::ERROR_CODE_NONE
 */
ErrorManager::error_code_t TagImage::SaveJPEG(Data& encoding, Data& image)
{
	sswf_jpg_error			jerr;
	struct jpeg_compress_struct	cinfo;
	sswf_jpg_destination		dst;
	const unsigned char		*s;
	unsigned char			*row, *d;
	long				sz;

	row = 0;

/* create the JPEG error handler */
	cinfo.err = jpeg_std_error(&jerr.pub);
	jerr.pub.error_exit = sswfJPEGError;
	if(setjmp(jerr.setjmp_buffer)) {
		/* an error occured */
		jpeg_destroy_compress(&cinfo);
		MemFree(row);
		return OnError(ErrorManager::ERROR_CODE_JPEG, "an error occured while compressing a JPEG image.");
	}

/* create the compressor */
	jpeg_create_compress(&cinfo);

/* set the output callbacks */
	dst.dst.init_destination =    sswfInitDestination;
	dst.dst.empty_output_buffer = sswfEmptyOutputBuffer;
	dst.dst.term_destination =    sswfTermDestination;
	dst.size = 0;
	dst.offset = 0;
	dst.state = 0;
	dst.encoding = &encoding;
	dst.image = &image;

	cinfo.dest = &dst.dst;

/* setup the compression parameters */
	cinfo.image_width      = f_image.f_width;
	cinfo.image_height     = f_image.f_height;
	cinfo.input_components = 3;				/* default to RGB 24 bits */
	cinfo.in_color_space   = JCS_RGB;			/* always required */

	jpeg_set_defaults(&cinfo);

/* any quality specified? */
	if(f_quality != 0) {				/* any quality definition? */
		jpeg_set_quality(&cinfo, f_quality, TRUE);
	}

/* create a temporary row buffer */
	row = (unsigned char *) MemAlloc(cinfo.image_width * 3, "row used to read a JPEG image");

/* compress the image */
	jpeg_start_compress(&cinfo, TRUE);
	s = f_image.f_data;
	while(cinfo.next_scanline < cinfo.image_height) {
		sz = cinfo.image_width;
		d = row;
		do {
			d[0] = s[1];
			d[1] = s[2];
			d[2] = s[3];
			d += 3;
			s += 4;
			sz--;
		} while(sz > 0);
		jpeg_write_scanlines(&cinfo, (JSAMPARRAY) &row, 1);
	}

/* release the JPEG library structures */
	jpeg_finish_compress(&cinfo);
	jpeg_destroy_compress(&cinfo);

	MemFree(row);

	return ErrorManager::ERROR_CODE_NONE;
}


//********************************************************************************
//**********   JPEG Loader/Saver   ************************************* END *****
//********************************************************************************



/** \brief Load a Targa image
 *
 * This function loads and decompresses a Targa image.
 *
 * If you want to set an image in the TagImage, use SetFilename() instead.
 *
 * \param[in] filename	The name of the file to be loaded
 * \param[in] im	The bitmap structure where the image is loaded
 *
 * \return An error code or ErrorManager::ERROR_CODE_NONE
 *
 * \sa sswf::TagImage::LoadJPEG(const char *filename, image_t& im)
 */
ErrorManager::error_code_t TagImage::LoadTGA(const char *filename, image_t& im)
{
	unsigned char	header[18];
	long		width, height, depth, size, cnt, c, flags;
	unsigned char	*s, *d, temp;
	FILE		*f;

	f = fopen(filename, "rb");
	if(f == NULL) {
		return ErrorManager::ERROR_CODE_IO;
	}
	if(fread(header, TGA_HEADER_SIZE, 1, f) != 1) {		// read the targa header
		fclose(f);
		return ErrorManager::ERROR_CODE_IO;
	}

	width =  header[TGA_OFFSET_WIDTH_LO ] + header[TGA_OFFSET_WIDTH_HI ] * 256;
	height = header[TGA_OFFSET_HEIGHT_LO] + header[TGA_OFFSET_HEIGHT_HI] * 256;
	depth =  header[TGA_OFFSET_BITS_PER_PIXEL];

	// for the flags, you should have bit 3 set when you include
	// some alpha; at this time I kind of ignore that...
	flags =  header[TGA_OFFSET_FLAGS];
	if(depth == 32) {
		flags &= ~8;
	}

//printf(">>> Reading TGA file (%ld, %ld, %ld)\n", width, height, depth);

	if(width == 0 || height == 0			// refuse wrong sizes
	|| (depth != 24 && depth != 32)			// only RGB and RGBA suppored
	|| header[TGA_OFFSET_COLORMAP_TYPE] != 0	// no colormap formats supported
	|| header[TGA_OFFSET_IMAGE_TYPE] != 2		// no compression supported
	|| (flags & ~0x20) != 0) {			// only accept top to bottom flag
		errno = EINVAL;
		fclose(f);
		return ErrorManager::ERROR_CODE_UNKNOWN_FORMAT;
	}
	depth /= 8;					// depth in bytes

	if(header[TGA_OFFSET_IDENTIFIER_LENGTH] != 0) {
		// skip the comment
		fseek(f, header[TGA_OFFSET_IDENTIFIER_LENGTH], SEEK_CUR);
	}

	im.f_alpha = depth == 4;
	im.f_width = width;
	im.f_height = height;

	// read the data at once
	size = width * height;
	im.f_data = (unsigned char *) MemAlloc(size * 4, "buffer for image data");
	if(fread(im.f_data, size * depth, 1, f) != 1) {
		fclose(f);
		return ErrorManager::ERROR_CODE_IO;
	}

	// we're done with the input file
	fclose(f);

// TODO: I should swap the R and B in each loop and have only one loop!
	// images are saved in 32 bits in SWF even when only 24 are defined (use XRGB)
	if(depth == 3) {
		cnt = size;
		s = im.f_data + size * 3;
		d = im.f_data + size * 4;
		do {
			s -= 3;
			d -= 4;
			// watch out, the order is VERY important here (for the very 1st pixel)
			d[3] = s[2];
			d[2] = s[1];
			d[1] = s[0];
			d[0] = 255;		/* this IS taken in account even without the V3.0 tag!!! */

#if 0
			if(d[1] == 255 && d[2] == 255 && d[3] == 255) {
				d[1] = 128;
				d[2] = 128;
				d[3] = 128;
			}
#endif
			cnt--;
		} while(cnt > 0);
	}
	else {
		// targas are saved as BGRA and we need ARGB instead
		// but we swap R and B later, so we generate ABGR
		cnt = size;
		d = im.f_data;
		do {
			temp = d[3];
			d[3] = d[2];
			d[2] = d[1];
			d[1] = d[0];
			d[0] = temp;
			d += 4;
			cnt--;
		} while(cnt > 0);
	}

	// the red and blue are inverted in Targa files
	cnt = size;
	s = im.f_data;
	do {
		c = s[1];
		s[1] = s[3];
		s[3] = (char) c;
		s += 4;
		cnt--;
	} while(cnt > 0);

	// flip the image vertically if necessary
	if((header[TGA_OFFSET_FLAGS] & 0x20) == 0) {
		cnt = height / 2;
		c = width * 4;
		s = im.f_data;
		d = s + size * 4;
		do {
			d -= c;
			swap(s, d, c);
			s += c;
			cnt--;
		} while(cnt > 0);
	}

#if 0
	cnt = size;
	s = im.f_data;
	do {			/* shows that we have ARGB instead of RGBA as mentioned in the doc. */
		s[0] = 255 - (long) (sqrt((cnt % width) * (cnt % width) + (cnt / width) * (cnt / width)) * 255
								/ sqrt(width * width + height * height));
		s[1] = 0;
		s[2] = 0;
		s[3] = 0;
		s += 4;
		cnt--;
	} while(cnt > 0);
	im.f_alpha = 1;
#endif
#if 0
	cnt = width;
	s = im.f_data;
	d = s + size * 4;
	do {
		d -= 4;
		d[0] = 255;
		d[1] = 255;
		d[2] = 0;
		d[3] = 0;
		s[0] = 255;
		s[1] = 255;
		s[2] = 0;
		s[3] = 0;
		s += 4;
		cnt--;
	} while(cnt > 0);
	cnt = height;
	s = im.f_data;
	d = s + width * 4;
	do {
		d[0 - 4] = 255;
		d[1 - 4] = 0;
		d[2 - 4] = 255;
		d[3 - 4] = 0;
		s[0] = 255;
		s[1] = 255;
		s[2] = 0;
		s[3] = 0;
		s += width * 4;
		d += width * 4;
		cnt--;
	} while(cnt > 0);
	cnt = height;
	s = im.f_data + 10 * 4;
	d = s + width * 4 - 20 * 4;
	do {
		d[0 - 4] = 255;
		d[1 - 4] = 0;
		d[2 - 4] = 255;
		d[3 - 4] = 0;
		s[0] = 255;
		s[1] = 255;
		s[2] = 0;
		s[3] = 0;
		s += width * 4;
		d += width * 4;
		cnt--;
	} while(cnt > 0);
#endif

	return ErrorManager::ERROR_CODE_NONE;
}



/** \brief Set the alpha channel of an image
 *
 * This function takes an image and a mask as input and
 * copy the mask in the alpha channel of the image.
 *
 * The function works only if both images are exactly of
 * the same size (i.e. same width and height.)
 *
 * \param[in] im	The image receiving the mask as its alpha channel
 * \param[in] mask	The image used as the alpha channel
 *
 * \return An error code or ErrorManager::ERROR_CODE_NONE
 */
ErrorManager::error_code_t TagImage::SetAlpha(image_t& im, const image_t& mask)
{
	long			cnt;
	const unsigned char	*s;
	unsigned char		*d;

	if(im.f_width != mask.f_width
	|| im.f_height != mask.f_height) {
		return OnError(ErrorManager::ERROR_CODE_SIZE_MISMATCH,
			"the image and mask do not both have the same size (%ld, %ld) versus (%ld, %ld)",
				im.f_width, im.f_height, mask.f_width, mask.f_height);
	}

	cnt = im.f_width * im.f_height;
	s = mask.f_data;
	d = im.f_data;
	while(cnt > 0) {
		d[0] = (s[1] + s[2] + s[3]) / 3;
		if(d[0] != 255) {
			/* We actually need to have the alpha already applied to
			 * the components because the SWF players won't do it for you.
			 * With OpenGL, you can use ONE for SRC and ONE_MINUS_SRC for
			 * DST. This can make a difference in the resulting quality.
			 * And using just the processor or MMX (or some equivalent)
			 * you can save many instructions since the data is partially
			 * processed already.
			 */
			im.f_alpha = true;
			d[1] = (d[1] * d[0]) / 255;
			d[2] = (d[2] * d[0]) / 255;
			d[3] = (d[3] * d[0]) / 255;
		}
		s += 4;
		d += 4;
		cnt--;
	}

	return ErrorManager::ERROR_CODE_NONE;
}


/** \brief Load an image in the specified TagImage
 *
 * read one or two JPEG or targa files when filenames are specified
 *
 * \return An error code or ErrorManager::ERROR_CODE_NONE
 */
ErrorManager::error_code_t TagImage::SetFilename(const char *image, const char *mask)
{
	image_t				msk;
	ErrorManager::error_code_t	ec;

	f_count = 0;
	MemClean(&f_colormap);
	MemClean(&f_image.f_data);

	ec = LoadJPEG(image, f_image);
	if(ec == ErrorManager::ERROR_CODE_UNKNOWN_FORMAT) {
		ec = LoadTGA(image, f_image);
	}
	if(ec != ErrorManager::ERROR_CODE_NONE) {
		return ec;
	}
	if(!f_image.f_alpha && mask != 0) {
		msk.f_data = 0;
		ec = LoadJPEG(mask, msk);
		if(ec == ErrorManager::ERROR_CODE_UNKNOWN_FORMAT) {
			ec = LoadTGA(mask, msk);
		}
		if(ec != ErrorManager::ERROR_CODE_NONE) {
			MemFree(msk.f_data);
			return ec;
		}
		SetAlpha(f_image, msk);
		MemFree(msk.f_data);
	}

//printf("Alpha = %d\n", f_image.f_alpha);

	return ErrorManager::ERROR_CODE_NONE;
}



/** \brief Set an image in the TagImage object
 *
 * This function saves the specified image in the TagImage object.
 *
 * The image is specified by all of its characteristics: width,
 * height, buffer (data), whether it includes an alpha channel
 * and whether it has a colormap.
 *
 * The data pointer is saved as is in the image structure. This
 * means the TagImage becomes the owner of that pointer. It will
 * automatically free it with a call to MemFree().
 *
 * It is expected that you use your image memory allocator to
 * allocate this buffer:
 *
 * \code
 *	TagImage my_image;
 *
 *	...
 *	data = my_image->MemAlloc(width * height * 4, "My Image");
 *	...
 *	my_image->SetImage(...);
 *	...
 *	// MemFree(data) -- done by the TagImage destructor when necessary
 *	...
 * \endcode
 *
 * \todo
 * The colormap parameter is currently ignored.
 *
 * \param[in] width	The width of the image
 * \param[in] height	The height of the image
 * \param[in] data	The pointer to the data buffer (ARGB)
 * \param[in] alpha	Whether the alpha channel is valid
 * \param[in] count	The size of the colormap
 * \param[in] colormap	The colors in the colormap (RGBA)
 */
void TagImage::SetImage(long width, long height, unsigned char *data, bool alpha, long count, unsigned char *colormap)
{
	MemClean(&f_colormap);
	MemClean(&f_image.f_data);

	f_image.f_alpha = alpha;
	f_image.f_width = width;
	f_image.f_height = height;
	f_count = count;
	f_colormap = colormap;
	f_image.f_data = data;
}



/** \brief Check that the image can be saved.
 *
 * This function makes sure that the image format was defined
 * and depending on the format and whether it uses an alpha
 * channel, decides whether the minimum version is 2 or 3.
 *
 * \return An error code or ErrorManager::ERROR_CODE_NONE
 */
ErrorManager::error_code_t TagImage::PreSave(void)
{
	switch(f_format) {
	//case IMAGE_FORMAT_LOSSLESS_8: -- we don't support this one yet...
	case IMAGE_FORMAT_LOSSLESS_16:
		MinimumVersion(2);
		break;

	case IMAGE_FORMAT_LOSSLESS_BEST:
	case IMAGE_FORMAT_LOSSLESS_32:
	case IMAGE_FORMAT_JPEG:
		MinimumVersion(f_image.f_alpha ? 3 : 2);
		break;

	default:
		return OnError(ErrorManager::ERROR_CODE_UNSUPPORTED_IMAGE_FORMAT, "the specified image format is not supported or still undefined.");

	}

	return ErrorManager::ERROR_CODE_NONE;
}



/** \brief Save the image in the specified Data buffer.
 *
 * This function saves the image in the specified Data buffer.
 * Whenever saving the image in one of the Lossless formats,
 * the function tries to transform it to a paletted image
 * since in general a paletted image can be saved in a
 * smaller image.
 *
 * \bug
 * At this time, the function does not support LOSSLESS8.
 *
 * \return An error code or ErrorManager::ERROR_CODE_NONE
 */
ErrorManager::error_code_t TagImage::Save(Data& data)
{
	long				size, count, idx, w, h, adjusted_width;
	ErrorManager::error_code_t	ec;
	unsigned short			c16, *b16;
	unsigned char			*s, *d, *b8, palette[256 * 4];
	uLongf				sz;
	Data				jpeg_encoding, jpeg_image;

	switch(f_format) {
	case IMAGE_FORMAT_LOSSLESS_8:
		assert(0, "Lossless 8 format not supported yet (no quantisation available)");
		return OnError(ErrorManager::ERROR_CODE_UNSUPPORTED_IMAGE_FORMAT, "the specified image format is not supported or still undefined.");
		/* TODO: we need to generate a palette...
		size = ((f_image.f_width + 1) & -2) * f_image.f_height;
		b8 = (unsigned char *) MemAlloc(size, "8 bits image");
		s = f_image.f_data;
		d = (unsigned char *) b8;
		sz = size / 2;
		do {
			d[0] = (s[1] + s[2] + s[3]) / 3;
			d += 1;
			s += 4;
			sz--;
		} while(sz > 0);
		sz = (size * 11) / 10 + 256;		// buffer + 10% + 256 bytes
		d = (unsigned char *) MemAlloc(sz, "compressed image buffer");
		compress((Bytef *) d, &sz, (const Bytef *) b8, size);
		MemFree(b8);	// done with the temp. buffer
		// here we can compute the exact size without using a sub_data!
		size = 2 + 1 + 2 + 2 + sz;
		SaveTag(data, SWF_TAG_DEFINE_BITS_LOSSLESS, size);
		SaveID(data);
		data.PutByte(3);	// format - 8 bits
		data.PutShort(f_image.f_width);
		data.PutShort(f_image.f_height);
		data.Write(d, sz);
		MemFree(d);
		*/
		break;

	case IMAGE_FORMAT_LOSSLESS_16:
lossless_16:
		adjusted_width = (f_image.f_width + 1) & -2;
		size = adjusted_width * f_image.f_height * 2;
		b16 = (unsigned short *) MemAlloc(size, "16 bits image");
		s = f_image.f_data;
		d = (unsigned char *) b16;
		h = 0;
		do {
			w = f_image.f_width;
			do {
				c16 = ((s[1] & 0xF8) << 7) | ((s[2] & 0xF8) << 2) | ((s[3] & 0xF8) >> 3);
				d[0] = c16 >> 8;
				d[1] = (char) c16;
				d += 2;
				s += 4;
				w--;
			} while(w > 0);
			if((f_image.f_width & 1) != 0) {
				d[0] = 0;
				d[1] = 0;
				d += 2;
			}
			h++;
		} while(h < f_image.f_height);
		sz = (size * 11) / 10 + 256;		// buffer + 10% + 256 bytes
		d = (unsigned char *) MemAlloc(sz, "compressed image buffer");
		compress((Bytef *) d, &sz, (const Bytef *) b16, size);
		MemFree(b16);	// done with the temp. buffer
		// here we can compute the exact size without using a sub_data!
		size = 2 + 1 + 2 + 2 + sz;
		SaveTag(data, SWF_TAG_DEFINE_BITS_LOSSLESS, size);
		SaveID(data);
		data.PutByte(4);	/* format - 16 bits */
		data.PutShort((short) f_image.f_width);
		data.PutShort((short) f_image.f_height);
		data.Write(d, sz);
		MemFree(d);
		break;

	case IMAGE_FORMAT_LOSSLESS_BEST:
		// first try with a palette
		count = 0;
		adjusted_width = (f_image.f_width + 3) & -4;
		size = adjusted_width * f_image.f_height + 256 * 4;
		b8 = (unsigned char *) MemAlloc(size, "8 bits image & space for palette");
		memset(b8, 0, size);	// better compression if pads are zeroes
		h = f_image.f_height;
		s = f_image.f_data;
		h = 0;
		do {
			w = f_image.f_width;
			// the 256 * 4 is to skip the space for the palette
			d = b8 + h * adjusted_width + 256 * 4;
			do {
				idx = count;
				while(idx > 0) {
					idx--;
					if(palette[idx * 4 + 3] == s[0]
					&& palette[idx * 4 + 0] == s[1]
					&& palette[idx * 4 + 1] == s[2]
					&& palette[idx * 4 + 2] == s[3]) {
						*d = (unsigned char) idx;
						goto found;
					}
				}
				if(count == 256) {
#if 0
// use this to force a colormap and make sure you test this case...
					*d = 0;
					goto found;
#endif
					goto not_paletted;
				}
				palette[count * 4 + 3] = s[0];
				palette[count * 4 + 0] = s[1];
				palette[count * 4 + 1] = s[2];
				palette[count * 4 + 2] = s[3];
				*d = (unsigned char) count;
				count++;
found:
				d++;
				s += 4;
				w--;
			} while(w > 0);
			h++;
		} while(h < f_image.f_height);
		/* this did work, save as an 8 bits paletted image! */
		// stick the palette right before the data
		// (b8 will usually start with undefined data)
		if(f_image.f_alpha) {
			s = b8 + (256 - count) * 4;
			memcpy(s, palette, count * 4);
		}
		else {
			s = b8 + 256 * 4 - count * 3;
			for(idx = 0; idx < count; idx++) {
				s[idx * 3 + 0] = palette[idx * 4 + 0];
				s[idx * 3 + 1] = palette[idx * 4 + 1];
				s[idx * 3 + 2] = palette[idx * 4 + 2];
			}
		}
		size = d - s;
		sz = (size * 11) / 10 + 256;		// buffer + 10% + 256 bytes
		d = (unsigned char *) MemAlloc(sz, "compressed image buffer");
		compress((Bytef *) d, &sz, (const Bytef *) s, size);
		MemFree(b8);	// done with the temp. buffer
		// here we can compute the exact size without using a sub_data!
		size = 2 + 1 + 2 + 2 + 1 + sz;
		SaveTag(data, f_image.f_alpha ? SWF_TAG_DEFINE_BITS_LOSSLESS2 : SWF_TAG_DEFINE_BITS_LOSSLESS, size);
		SaveID(data);
		data.PutByte(3);	// format - 8 bits
		data.PutShort((short) f_image.f_width);
		data.PutShort((short) f_image.f_height);
		data.PutByte(count - 1);
		data.Write(d, sz);
		MemFree(d);
		break;

not_paletted:
		if(!f_image.f_alpha) {
			/* test if we could use 16 bits (RGB555) instead */
			count = 0;
			sz = size = f_image.f_width * f_image.f_height;
			do {
				if((s[1] & 7) != 0 || (s[2] & 7) != 0 || (s[3] & 7) != 0) {
					count++;
				}
				s += 4;
				sz--;
			} while(sz > 0);
			if(count <= size / 10) {
				/* less than 10% of loss, do it! */
				goto lossless_16;
			}
		}
		/*FALLTHROUGH*/
	case IMAGE_FORMAT_LOSSLESS_32:
		size = f_image.f_width * f_image.f_height * 4;
		sz = (size * 11) / 10 + 256;		// buffer + 10% + 256 bytes
		d = (unsigned char *) MemAlloc(sz, "compressed image buffer");
		compress((Bytef *) d, &sz, (const Bytef *) f_image.f_data, size);

		// here we can compute the exact size without using a sub_data!
		size = 2 + 1 + 2 + 2 + sz;
		SaveTag(data, f_image.f_alpha ? SWF_TAG_DEFINE_BITS_LOSSLESS2 : SWF_TAG_DEFINE_BITS_LOSSLESS, size);

		SaveID(data);
		data.PutByte(5);	/* format - 32 bits */
		data.PutShort((short) f_image.f_width);
		data.PutShort((short) f_image.f_height);
		data.Write(d, sz);

		MemFree(d);
		break;

	case IMAGE_FORMAT_JPEG:
		// we need a sub data because we can't infer the size of
		// the result before it is actually saved in a data buffer
		ec = SaveJPEG(jpeg_encoding, jpeg_image);
		if(ec != ErrorManager::ERROR_CODE_NONE) {
			return ec;
		}

		if(f_image.f_alpha) {
			size = f_image.f_width * f_image.f_height;
			b8 = (unsigned char *) MemAlloc(size, "alpha channel buffer");
			s = f_image.f_data;
			d = b8;
			sz = size;
			do {
				*d++ = s[0];
				s += 4;
				sz--;
			} while(sz > 0);
			sz = (size * 11) / 10 + 256;		// buffer + 10% + 256 bytes
			d = (unsigned char *) MemAlloc(sz, "compressed image buffer");
			compress((Bytef *) d, &sz, (const Bytef *) b8, size);

			MemFree(b8);

			size = 2 + 4 + jpeg_encoding.ByteSize() + jpeg_image.ByteSize() + sz;
			SaveTag(data, SWF_TAG_DEFINE_BITS_JPEG3, size);

			SaveID(data);
			data.PutLong(jpeg_encoding.ByteSize() + jpeg_image.ByteSize());
			data.Append(jpeg_encoding);
			data.Append(jpeg_image);
			data.Write(d, sz);

			MemFree(d);
		}
		else {
			size = 2 + jpeg_encoding.ByteSize() + jpeg_image.ByteSize();
			SaveTag(data, SWF_TAG_DEFINE_BITS_JPEG2, size);

			SaveID(data);
			data.Append(jpeg_encoding);
			data.Append(jpeg_image);
		}

		break;

	default:
		assert(0, "unknown image format");
		return OnError(ErrorManager::ERROR_CODE_UNSUPPORTED_IMAGE_FORMAT, "the specified image format is not supported or still undefined.");
		/*NOTREACHED*/

	}

	return ErrorManager::ERROR_CODE_NONE;
}





/* The following options fold the documentation; use 'zi' to turn on and off
 *
 * vim: foldexpr=getline(v\:lnum)!~'^/\\*\\*'&&getline(v\:lnum)!~'^\ \\*'?0\:1 foldcolumn=2 foldmethod=expr
 */
