Alien-FreeImage

 view release on metacpan or  search on metacpan

src/Source/FreeImage/PluginTARGA.cpp  view on Meta::CPAN

// ==========================================================
// TARGA Loader and Writer
//
// Design and implementation by
// - Floris van den Berg (flvdberg@wxs.nl)
// - Jani Kajala (janik@remedy.fi)
// - Martin Weber (martweb@gmx.net)
// - Machiel ten Brinke (brinkem@uni-one.nl)
// - Peter Lemmens (peter.lemmens@planetinternet.be)
// - 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!
// ==========================================================

#include "FreeImage.h"
#include "Utilities.h"

// ----------------------------------------------------------
//   Constants + headers
// ----------------------------------------------------------

#ifdef _WIN32
#pragma pack(push, 1)
#else
#pragma pack(1)
#endif

typedef struct tagTGAHEADER {
	BYTE id_length;				//! length of the image ID field
	BYTE color_map_type;		//! whether a color map is included
	BYTE image_type;			//! compression and color types

	WORD cm_first_entry;		//! first entry index (offset into the color map table)
	WORD cm_length;				//! color map length (number of entries)
	BYTE cm_size;				//! color map entry size, in bits (number of bits per pixel)

	WORD is_xorigin;			//! X-origin of image (absolute coordinate of lower-left corner for displays where origin is at the lower left)
	WORD is_yorigin;			//! Y-origin of image (as for X-origin)
	WORD is_width;				//! image width
	WORD is_height;				//! image height
	BYTE is_pixel_depth;		//! bits per pixel
	BYTE is_image_descriptor;	//! image descriptor, bits 3-0 give the alpha channel depth, bits 5-4 give direction
} TGAHEADER;

typedef struct tagTGAEXTENSIONAREA {
	WORD extension_size;		// Size in bytes of the extension area, always 495
	char author_name[41];		// Name of the author. If not used, bytes should be set to NULL (\0) or spaces
	char author_comments[324];	// A comment, organized as four lines, each consisting of 80 characters plus a NULL
	WORD datetime_stamp[6];		// Date and time at which the image was created
	char job_name[41];			// Job ID
	WORD job_time[3];			// Hours, minutes and seconds spent creating the file (for billing, etc.)
	char software_id[41];		// The application that created the file
	BYTE software_version[3];
	DWORD key_color;
	WORD pixel_aspect_ratio[2];
	WORD gamma_value[2];
	DWORD color_correction_offset;	// Number of bytes from the beginning of the file to the color correction table if present
	DWORD postage_stamp_offset;		// Number of bytes from the beginning of the file to the postage stamp image if present
	DWORD scan_line_offset;			// Number of bytes from the beginning of the file to the scan lines table if present
	BYTE attributes_type;			// Specifies the alpha channel
} TGAEXTENSIONAREA;

typedef struct tagTGAFOOTER {
	DWORD extension_offset;	// extension area offset : offset in bytes from the beginning of the file
	DWORD developer_offset;	// developer directory offset : offset in bytes from the beginning of the file
	char signature[18];		// signature string : contains "TRUEVISION-XFILE.\0"
} TGAFOOTER;

#ifdef _WIN32
#pragma pack(pop)
#else
#pragma pack()
#endif

static const char *FI_MSG_ERROR_CORRUPTED = "Image data corrupted";

// ----------------------------------------------------------
// Image type
//
#define TGA_NULL		0	// no image data included
#define TGA_CMAP		1	// uncompressed, color-mapped image
#define TGA_RGB			2	// uncompressed, true-color image
#define TGA_MONO		3	// uncompressed, black-and-white image
#define TGA_RLECMAP		9	// run-length encoded, color-mapped image
#define TGA_RLERGB		10	// run-length encoded, true-color image
#define TGA_RLEMONO		11	// run-length encoded, black-and-white image
#define TGA_CMPCMAP		32	// compressed (Huffman/Delta/RLE) color-mapped image (e.g., VDA/D) - Obsolete
#define TGA_CMPCMAP4	33	// compressed (Huffman/Delta/RLE) color-mapped four pass image (e.g., VDA/D) - Obsolete

// ==========================================================
// Thumbnail functions
// ==========================================================

class TargaThumbnail
{
public:
	TargaThumbnail() : _w(0), _h(0), _depth(0), _data(NULL) { 
	}
	~TargaThumbnail() { 
		if(_data) {
			free(_data); 
		}
	}

	BOOL isNull() const { 
		return (_data == NULL); 
	}
	
	BOOL read(FreeImageIO *io, fi_handle handle, size_t size) {
		io->read_proc(&_w, 1, 1, handle);
		io->read_proc(&_h, 1, 1, handle);
		
		const size_t sizeofData = size - 2;
		_data = (BYTE*)malloc(sizeofData);
		if(_data) {
			return (io->read_proc(_data, 1, (unsigned)sizeofData, handle) == sizeofData);
		}
		return FALSE;
	}
	
	void setDepth(BYTE dp) { 
		_depth = dp;
	}
	
	FIBITMAP* toFIBITMAP();
	
private:
	BYTE _w;
	BYTE _h;
	BYTE _depth;
	BYTE* _data;
};

#ifdef FREEIMAGE_BIGENDIAN
static void 
swapShortPixels(FIBITMAP* dib) {
	if(FreeImage_GetImageType(dib) != FIT_BITMAP) {
		return;
	}
		
	const unsigned Bpp = FreeImage_GetBPP(dib)/8;
	if(Bpp != 2) {
		return;
	}
		
	BYTE* bits = FreeImage_GetBits(dib);

src/Source/FreeImage/PluginTARGA.cpp  view on Meta::CPAN

		
	BOOL isNull() { return _begin == NULL;}
	
	inline
	BYTE getByte() {
		if (_ptr >= _end) {
			// need refill
			_ptr = _begin;
			_io->read_proc(_ptr, sizeof(BYTE), (unsigned)_size, _handle);	//### EOF - no problem?
		}

		BYTE result = *_ptr;

		_ptr++;

		return result;
	}
	
	inline
	BYTE* getBytes(size_t count /*must be < _size!*/) {
		if (_ptr + count >= _end) {
			
			// need refill

			// 'count' bytes might span two cache bounds,
			// SEEK back to add the remains of the current cache again into the new one

			long read = long(_ptr - _begin);
			long remaining = long(_size - read);

			_io->seek_proc(_handle, -remaining, SEEK_CUR);

			_ptr = _begin;
			_io->read_proc(_ptr, sizeof(BYTE), (unsigned)_size, _handle);	//### EOF - no problem?
		}

		BYTE *result = _ptr;

		_ptr += count;

		return result;
	}

private:
	IOCache& operator=(const IOCache& src); // deleted
	IOCache(const IOCache& other); // deleted

private:
	BYTE *_ptr;
	BYTE *_begin;
	BYTE *_end;
	const size_t _size;
	const FreeImageIO *_io;	
	const fi_handle _handle;	
};

#ifdef FREEIMAGE_BIGENDIAN
static void
SwapHeader(TGAHEADER *header) {
	SwapShort(&header->cm_first_entry);
	SwapShort(&header->cm_length);
	SwapShort(&header->is_xorigin);
	SwapShort(&header->is_yorigin);
	SwapShort(&header->is_width);
	SwapShort(&header->is_height);
}

static void
SwapExtensionArea(TGAEXTENSIONAREA *ex) {
	SwapShort(&ex->extension_size);
	SwapShort(&ex->datetime_stamp[0]);
	SwapShort(&ex->datetime_stamp[1]);
	SwapShort(&ex->datetime_stamp[2]);
	SwapShort(&ex->datetime_stamp[3]);
	SwapShort(&ex->datetime_stamp[4]);
	SwapShort(&ex->datetime_stamp[5]);
	SwapShort(&ex->job_time[0]);
	SwapShort(&ex->job_time[1]);
	SwapShort(&ex->job_time[2]);
	SwapLong (&ex->key_color);
	SwapShort(&ex->pixel_aspect_ratio[0]);
	SwapShort(&ex->pixel_aspect_ratio[1]);
	SwapShort(&ex->gamma_value[0]);
	SwapShort(&ex->gamma_value[1]);
	SwapLong (&ex->color_correction_offset);
	SwapLong (&ex->postage_stamp_offset);
	SwapLong (&ex->scan_line_offset);
}

static void
SwapFooter(TGAFOOTER *footer) {
	SwapLong(&footer->extension_offset);
	SwapLong(&footer->developer_offset);
}

#endif // FREEIMAGE_BIGENDIAN

// ==========================================================
// Plugin Interface
// ==========================================================

static int s_format_id;

// ==========================================================
// Plugin Implementation
// ==========================================================

static const char * DLL_CALLCONV
Format() {
	return "TARGA";
}

static const char * DLL_CALLCONV
Description() {
	return "Truevision Targa";
}

static const char * DLL_CALLCONV
Extension() {
	return "tga,targa";
}

static const char * DLL_CALLCONV
RegExpr() {
	return NULL;
}

static const char * DLL_CALLCONV
MimeType() {
	return "image/x-tga";
}

static BOOL 
isTARGA20(FreeImageIO *io, fi_handle handle) {
	const unsigned sizeofSig = 18;
	BYTE signature[sizeofSig];
	// tga_signature = "TRUEVISION-XFILE." (TGA 2.0 only)
	BYTE tga_signature[sizeofSig] = { 84, 82, 85, 69, 86, 73, 83, 73, 79, 78, 45, 88, 70, 73, 76, 69, 46, 0 };
	// get the start offset
	const long start_offset = io->tell_proc(handle);
	// get the end-of-file
	io->seek_proc(handle, 0, SEEK_END);
	const long eof = io->tell_proc(handle);
	// read the signature
	io->seek_proc(handle, start_offset + eof - sizeofSig, SEEK_SET);
	io->read_proc(&signature, 1, sizeofSig, handle);
	// rewind
	io->seek_proc(handle, start_offset, SEEK_SET);
		
	return (memcmp(tga_signature, signature, sizeofSig) == 0);
}

static BOOL DLL_CALLCONV
Validate(FreeImageIO *io, fi_handle handle) { 
	if(isTARGA20(io, handle)) {
		return TRUE;
	}
		
	// not a 2.0 image, try testing if it's a valid TGA anyway (not robust)
	{
		const long start_offset = io->tell_proc(handle);
		
		// get the header
		TGAHEADER header;
		io->read_proc(&header, sizeof(tagTGAHEADER), 1, handle);
#ifdef FREEIMAGE_BIGENDIAN
		SwapHeader(&header);
#endif
		// rewind
		io->seek_proc(handle, start_offset, SEEK_SET);

		// the color map type should be a 0 or a 1...
		if(header.color_map_type != 0 && header.color_map_type != 1) {
			return FALSE;
		}
		// if the color map type is 1 then we validate the map entry information...
		if(header.color_map_type > 0) {
			// it doesn't make any sense if the first entry is larger than the color map table
			if(header.cm_first_entry >= header.cm_length) {
				return FALSE;
			}
			// check header.cm_size, don't allow 0 or anything bigger than 32
			if(header.cm_size == 0 || header.cm_size > 32) {
				return FALSE;
			}
		}
		// the width/height shouldn't be 0, right ?
		if(header.is_width == 0 || header.is_height == 0) {
			return FALSE;
		}
		// let's now verify all the types that are supported by FreeImage (this is our final verification)
		switch(header.image_type) {
			case TGA_CMAP:
			case TGA_RGB:
			case TGA_MONO:
			case TGA_RLECMAP:
			case TGA_RLERGB:
			case TGA_RLEMONO:
				switch(header.is_pixel_depth) {
					case 8	:
					case 16:
					case 24:
					case 32:
						return TRUE;
					default:
						return FALSE;
				}
				break;
			default:
				return FALSE;
		}
	}
}

static BOOL DLL_CALLCONV
SupportsExportDepth(int depth) {
	return (
		(depth == 8) ||
		(depth == 16) ||
		(depth == 24) ||
		(depth == 32)
		);
}

static BOOL DLL_CALLCONV
SupportsExportType(FREE_IMAGE_TYPE type) {
	return (type == FIT_BITMAP) ? TRUE : FALSE;
}

static BOOL DLL_CALLCONV
SupportsNoPixels() {
	return TRUE;
}

// ----------------------------------------------------------

/**
Used for all 32 and 24 bit loading of uncompressed images
*/

src/Source/FreeImage/PluginTARGA.cpp  view on Meta::CPAN

		long start_offset = io->tell_proc(handle);

		// remember end-of-file (used for RLE cache)
		io->seek_proc(handle, 0, SEEK_END);
		long eof = io->tell_proc(handle);
		io->seek_proc(handle, start_offset, SEEK_SET);

		// read and process the bitmap's footer

		TargaThumbnail thumbnail;
		if(isTARGA20(io, handle)) {
			TGAFOOTER footer;
			const long footer_offset = start_offset + eof - sizeof(footer);
			
			io->seek_proc(handle, footer_offset, SEEK_SET);
			io->read_proc(&footer, sizeof(tagTGAFOOTER), 1, handle);
			
#ifdef FREEIMAGE_BIGENDIAN
			SwapFooter(&footer);
#endif
			BOOL hasExtensionArea = footer.extension_offset > 0;
			if(hasExtensionArea) { 
				TGAEXTENSIONAREA extensionarea;
				io->seek_proc(handle, footer.extension_offset, SEEK_SET);
				io->read_proc(&extensionarea, sizeof(extensionarea), 1, handle);
				
#ifdef FREEIMAGE_BIGENDIAN
				SwapExtensionArea(&extensionarea);
#endif

				DWORD postage_stamp_offset = extensionarea.postage_stamp_offset;
				BOOL hasThumbnail = (postage_stamp_offset > 0) && (postage_stamp_offset < (DWORD)footer_offset);
				if(hasThumbnail) {
					io->seek_proc(handle, postage_stamp_offset, SEEK_SET);
					thumbnail.read(io, handle, footer_offset - postage_stamp_offset);
				}
			}
		}
		
		// read and process the bitmap's header

		TGAHEADER header;

		io->seek_proc(handle, start_offset, SEEK_SET);
		io->read_proc(&header, sizeof(tagTGAHEADER), 1, handle);

#ifdef FREEIMAGE_BIGENDIAN
		SwapHeader(&header);
#endif

		thumbnail.setDepth(header.is_pixel_depth);
			
		const int line = CalculateLine(header.is_width, header.is_pixel_depth);
		const int pixel_bits = header.is_pixel_depth;
		const int pixel_size = pixel_bits/8;

		int fliphoriz = (header.is_image_descriptor & 0x10) ? 1 : 0;
		int flipvert = (header.is_image_descriptor & 0x20) ? 1 : 0;

		// skip comment
		io->seek_proc(handle, header.id_length, SEEK_CUR);

		switch (header.is_pixel_depth) {
			case 8 : {
				dib = FreeImage_AllocateHeader(header_only, header.is_width, header.is_height, 8);
				
				if (dib == NULL) {
					throw FI_MSG_ERROR_DIB_MEMORY;
				}
					
				// read the palette (even if header only)

				RGBQUAD *palette = FreeImage_GetPalette(dib);

				if (header.color_map_type > 0) {
					unsigned count, csize;

					// calculate the color map size
					csize = header.cm_length * header.cm_size / 8;
					
					// read the color map
					BYTE *cmap = (BYTE*)malloc(csize * sizeof(BYTE));
					if (cmap == NULL) {
						throw FI_MSG_ERROR_DIB_MEMORY;
					}
					io->read_proc(cmap, sizeof(BYTE), csize, handle);

					// build the palette

					switch (header.cm_size) {
						case 16: {
							WORD *rgb555 = (WORD*)&cmap[0];
							unsigned start = (unsigned)header.cm_first_entry;
							unsigned stop = MIN((unsigned)256, (unsigned)header.cm_length);

							for (count = start; count < stop; count++) {
								palette[count].rgbRed   = (BYTE)((((*rgb555 & FI16_555_RED_MASK) >> FI16_555_RED_SHIFT) * 0xFF) / 0x1F);
								palette[count].rgbGreen = (BYTE)((((*rgb555 & FI16_555_GREEN_MASK) >> FI16_555_GREEN_SHIFT) * 0xFF) / 0x1F);
								palette[count].rgbBlue  = (BYTE)((((*rgb555 & FI16_555_BLUE_MASK) >> FI16_555_BLUE_SHIFT) * 0xFF) / 0x1F);
								rgb555++;
							}
						}
						break;

						case 24: {
							FILE_BGR *bgr = (FILE_BGR*)&cmap[0];
							unsigned start = (unsigned)header.cm_first_entry;
							unsigned stop = MIN((unsigned)256, (unsigned)header.cm_length);

							for (count = start; count < stop; count++) {
								palette[count].rgbBlue  = bgr->b;
								palette[count].rgbGreen = bgr->g;
								palette[count].rgbRed   = bgr->r;
								bgr++;
							}
						}
						break;

						case 32: {
							BYTE trns[256];

							// clear the transparency table
							memset(trns, 0xFF, 256);

							FILE_BGRA *bgra = (FILE_BGRA*)&cmap[0];
							unsigned start = (unsigned)header.cm_first_entry;
							unsigned stop = MIN((unsigned)256, (unsigned)header.cm_length);

							for (count = start; count < stop; count++) {
								palette[count].rgbBlue  = bgra->b;
								palette[count].rgbGreen = bgra->g;
								palette[count].rgbRed   = bgra->r;
								// alpha
								trns[count] = bgra->a;
								bgra++;
							}

							// set the tranparency table
							FreeImage_SetTransparencyTable(dib, trns, 256);
						}
						break;

					} // switch(header.cm_size)

					free(cmap);
				}
				
				// handle thumbnail
				
				FIBITMAP* th = thumbnail.toFIBITMAP();
				if(th) {
					RGBQUAD* pal = FreeImage_GetPalette(dib);
					RGBQUAD* dst_pal = FreeImage_GetPalette(th);
					if(dst_pal && pal) {
						for(unsigned i = 0; i < FreeImage_GetColorsUsed(dib); i++) {
							dst_pal[i] = pal[i];
						}
					}

					FreeImage_SetTransparencyTable(th, FreeImage_GetTransparencyTable(dib), FreeImage_GetTransparencyCount(dib));
					
					FreeImage_SetThumbnail(dib, th);
					FreeImage_Unload(th);				
				}

				if(header_only) {
					return dib;
				}
					
				// read in the bitmap bits

				switch (header.image_type) {
					case TGA_CMAP:
					case TGA_MONO: {
						BYTE *bits = NULL;

						for (unsigned count = 0; count < header.is_height; count++) {
							bits = FreeImage_GetScanLine(dib, count);
							io->read_proc(bits, sizeof(BYTE), line, handle);
						}
					}
					break;

					case TGA_RLECMAP:
					case TGA_RLEMONO: { //(8 bit)
						loadRLE<8>(dib, header.is_width, header.is_height, io, handle, eof, FALSE);
					}
					break;

					default :
						FreeImage_Unload(dib);
						return NULL;
				}
			}
			break; // header.is_pixel_depth == 8

			case 15 :

			case 16 : {
				int pixel_bits = 16;

				// allocate the dib

				if (TARGA_LOAD_RGB888 & flags) {
					pixel_bits = 24;
					dib = FreeImage_AllocateHeader(header_only, header.is_width, header.is_height, pixel_bits, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK);

				} else {
					dib = FreeImage_AllocateHeader(header_only, header.is_width, header.is_height, pixel_bits, FI16_555_RED_MASK, FI16_555_GREEN_MASK, FI16_555_BLUE_MASK);
				}

				if (dib == NULL) {
					throw FI_MSG_ERROR_DIB_MEMORY;
				}
				
				// handle thumbnail
				
				FIBITMAP* th = thumbnail.toFIBITMAP();
				if(th) {
					if(TARGA_LOAD_RGB888 & flags) {
						FIBITMAP* t = FreeImage_ConvertTo24Bits(th);
						FreeImage_Unload(th);
						th = t;
					}

					FreeImage_SetThumbnail(dib, th);
					FreeImage_Unload(th);
				}
						
				if(header_only) {
					return dib;
				}

				int line = CalculateLine(header.is_width, pixel_bits);

				const unsigned pixel_size = unsigned(pixel_bits) / 8;
				const unsigned src_pixel_size = sizeof(WORD);

				// note header.cm_size is a misleading name, it should be seen as header.cm_bits
				// ignore current position in file and set filepointer explicitly from the beginning of the file

				int garblen = 0;

				if (header.color_map_type != 0) {
					garblen = (int)((header.cm_size + 7) / 8) * header.cm_length; /* should byte align */

				} else {
					garblen = 0;
				}

				io->seek_proc(handle, start_offset, SEEK_SET);
				io->seek_proc(handle, sizeof(tagTGAHEADER) + header.id_length + garblen, SEEK_SET);

				// read in the bitmap bits

				switch (header.image_type) {
					case TGA_RGB: { //(16 bit)
						// input line cache
						BYTE *in_line = (BYTE*)malloc(header.is_width * sizeof(WORD));

						if (!in_line)
							throw FI_MSG_ERROR_MEMORY;

						const int h = header.is_height;

						for (int y = 0; y < h; y++) {
							
							BYTE *bits = FreeImage_GetScanLine(dib, y);
							io->read_proc(in_line, src_pixel_size, header.is_width, handle);
							
							BYTE *val = in_line;
							for (int x = 0; x < line; x += pixel_size) {

								_assignPixel<16>(bits+x, val, TARGA_LOAD_RGB888 & flags);

								val += src_pixel_size;
							}
						}

						free(in_line);
					}
					break;

					case TGA_RLERGB: { //(16 bit)
						loadRLE<16>(dib, header.is_width, header.is_height, io, handle, eof, TARGA_LOAD_RGB888 & flags);
					}
					break;

					default :
						FreeImage_Unload(dib);
						return NULL;
				}
			}
			break; // header.is_pixel_depth == 15 or 16

			case 24 : {

				dib = FreeImage_AllocateHeader(header_only, header.is_width, header.is_height, pixel_bits, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK);

				if (dib == NULL) {
					throw FI_MSG_ERROR_DIB_MEMORY;
				}
				
				FIBITMAP* th = thumbnail.toFIBITMAP();
				if(th) {
					FreeImage_SetThumbnail(dib, th);
					FreeImage_Unload(th);
				}
				
				if(header_only) {
					return dib;
				}

src/Source/FreeImage/PluginTARGA.cpp  view on Meta::CPAN

				// otherwise do nothing. We will just increase the count at the end

			} else {

				// no rle

				if(has_rle) {
					// flush rle packet

					// include current pixel first
					assert(packet_count < max_packet_size);
					++packet_count;

					flushPacket(line, pixel_size, packet_begin, packet, packet_count, has_rle);

					// start anew on the next pixel
					continue;

				} else {

					writeToPacket(packet, current, pixel_size);
					packet += pixel_size;
				}

			}

			// increase counter on every pixel

			++packet_count;

			if(packet_count == max_packet_size) {
				flushPacket(line, pixel_size, packet_begin, packet, packet_count, has_rle);
			}

		}//for width

		// write line to disk
		io->write_proc(line_begin, 1, (unsigned)(line - line_begin), handle);

	}//for height

	free(line_begin);
	free(packet_begin);
	free(current);
	free(next);
}

static BOOL DLL_CALLCONV
Save(FreeImageIO *io, FIBITMAP *dib, fi_handle handle, int page, int flags, void *data) {
	if ((dib == NULL) || (handle == NULL)) {
		return FALSE;
	}

	RGBQUAD *palette = FreeImage_GetPalette(dib);
	const unsigned bpp = FreeImage_GetBPP(dib);

	// write the file header

	TGAHEADER header;

	header.id_length = 0;
	header.cm_first_entry = 0;
	header.is_xorigin = 0;
	header.is_yorigin = 0;
	header.is_width = (WORD)FreeImage_GetWidth(dib);
	header.is_height = (WORD)FreeImage_GetHeight(dib);
	header.is_pixel_depth = (BYTE)bpp;
	header.is_image_descriptor = (bpp == 32 ? 8 : 0);

	if (palette) {
		header.color_map_type = 1;
		header.image_type = (TARGA_SAVE_RLE & flags) ? TGA_RLECMAP : TGA_CMAP;
		header.cm_length = (WORD)(1 << bpp);

		if (FreeImage_IsTransparent(dib)) {
			header.cm_size = 32;
		} else {
			header.cm_size = 24;
		}

	} else {
		header.color_map_type = 0;
		header.image_type = (TARGA_SAVE_RLE & flags) ? TGA_RLERGB : TGA_RGB;
		header.cm_length = 0;
		header.cm_size = 0;
	}

	// write the header

#ifdef FREEIMAGE_BIGENDIAN
	SwapHeader(&header);
#endif

	io->write_proc(&header, sizeof(header), 1, handle);

#ifdef FREEIMAGE_BIGENDIAN
	SwapHeader(&header);
#endif

	// write the palette

	if (palette) {
		if (FreeImage_IsTransparent(dib)) {
			FILE_BGRA *bgra_pal = (FILE_BGRA*)malloc(header.cm_length * sizeof(FILE_BGRA));

			// get the transparency table
			BYTE *trns = FreeImage_GetTransparencyTable(dib);

			for (unsigned i = 0; i < header.cm_length; i++) {
				bgra_pal[i].b = palette[i].rgbBlue;
				bgra_pal[i].g = palette[i].rgbGreen;
				bgra_pal[i].r = palette[i].rgbRed;
				bgra_pal[i].a = trns[i];
			}

			io->write_proc(bgra_pal, sizeof(FILE_BGRA), header.cm_length, handle);

			free(bgra_pal);

		} else {
			FILE_BGR *bgr_pal = (FILE_BGR*)malloc(header.cm_length * sizeof(FILE_BGR));

			for (unsigned i = 0; i < header.cm_length; i++) {
				bgr_pal[i].b = palette[i].rgbBlue;
				bgr_pal[i].g = palette[i].rgbGreen;
				bgr_pal[i].r = palette[i].rgbRed;
			}

			io->write_proc(bgr_pal, sizeof(FILE_BGR), header.cm_length, handle);

			free(bgr_pal);
		}
	}

	// write the data bits 


	if (TARGA_SAVE_RLE & flags) {
		
		saveRLE(dib, io, handle);

	} else {
		
		// -- no rle compression --
		
		const unsigned width = header.is_width;
		const unsigned height = header.is_height;
		const unsigned pixel_size = bpp/8;

		BYTE *line, *const line_begin = (BYTE*)malloc(width * pixel_size);
		BYTE *line_source = line_begin;

		for (unsigned y = 0; y < height; y++) {
			BYTE *scanline = FreeImage_GetScanLine(dib, y);

			// rewind the line pointer
			line = line_begin;

			switch (bpp) {
				case 8: {
					// don't copy line, read straight from dib
					line_source = scanline;
				}
				break;

				case 16: {
					for (unsigned x = 0; x < width; x++) {
						WORD pixel = *(((WORD *)scanline) + x);
						
#ifdef FREEIMAGE_BIGENDIAN
						SwapShort(&pixel);
#endif
						*(WORD*)line = pixel;

						line += pixel_size;
					}
				}
				break;

				case 24: {
					
#if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR
						line_source = scanline;
#else 
					for (unsigned x = 0; x < width; ++x) {
						RGBTRIPLE* trip = ((RGBTRIPLE *)scanline) + x;
						line[0] = trip->rgbtBlue;
						line[1] = trip->rgbtGreen;
						line[2] = trip->rgbtRed;



( run in 0.768 second using v1.01-cache-2.11-cpan-119454b85a5 )