blob: 84864308e04d46a0124cf7de7214f2d71f993718 [file] [log] [blame]
// ==========================================================
// 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);
const unsigned height = FreeImage_GetHeight(dib);
const unsigned pitch = FreeImage_GetPitch(dib);
BYTE* line = bits;
for(unsigned y = 0; y < height; y++, line += pitch) {
for(BYTE* pixel = line; pixel < line + pitch ; pixel += Bpp) {
SwapShort((WORD*)pixel);
}
}
}
#endif // FREEIMAGE_BIGENDIAN
FIBITMAP* TargaThumbnail::toFIBITMAP() {
if(isNull() || _depth == 0) {
return NULL;
}
const unsigned line_size = _depth * _w / 8;
FIBITMAP* dib = FreeImage_Allocate(_w, _h, _depth);
if(!dib) {
return NULL;
}
const BYTE* line = _data;
const BYTE height = _h;
for (BYTE h = 0; h < height; ++h, line += line_size) {
BYTE* dst_line = FreeImage_GetScanLine(dib, height - 1 - h);
memcpy(dst_line, line, line_size);
}
#ifdef FREEIMAGE_BIGENDIAN
swapShortPixels(dib);
#endif
#if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_RGB
SwapRedBlue32(dib);
#endif
return dib;
}
// ==========================================================
// Internal functions
// ==========================================================
/** This class is used when loading RLE compressed images, it implements io cache of fixed size.
In general RLE compressed images *should* be compressed line by line with line sizes stored in Scan Line Table section.
In reality, however there are images not obeying the specification, compressing image data continuously across lines,
making it impossible to load the file cached at every line.
*/
class IOCache
{
public:
IOCache(FreeImageIO *io, fi_handle handle, size_t size) :
_ptr(NULL), _begin(NULL), _end(NULL), _size(size), _io(io), _handle(handle) {
_begin = (BYTE*)malloc(size);
if (_begin) {
_end = _begin + _size;
_ptr = _end; // will force refill on first access
}
}
~IOCache() {
if (_begin != NULL) {
free(_begin);
}
}
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
*/
static void
loadTrueColor(FIBITMAP* dib, int width, int height, int file_pixel_size, FreeImageIO* io, fi_handle handle, BOOL as24bit) {
const int pixel_size = as24bit ? 3 : file_pixel_size;
// input line cache
BYTE* file_line = (BYTE*)malloc( width * file_pixel_size);
if (!file_line) {
throw FI_MSG_ERROR_MEMORY;
}
for (int y = 0; y < height; y++) {
BYTE *bits = FreeImage_GetScanLine(dib, y);
io->read_proc(file_line, file_pixel_size, width, handle);
BYTE *bgra = file_line;
for (int x = 0; x < width; x++) {
bits[FI_RGBA_BLUE] = bgra[0];
bits[FI_RGBA_GREEN] = bgra[1];
bits[FI_RGBA_RED] = bgra[2];
if (!as24bit) {
bits[FI_RGBA_ALPHA] = bgra[3];
}
bgra += file_pixel_size;
bits += pixel_size;
}
}
free(file_line);
}
/**
For the generic RLE loader we need to abstract away the pixel format.
We use a specific overload based on bits-per-pixel for each type of pixel
*/
template <int nBITS>
inline static void
_assignPixel(BYTE* bits, BYTE* val, BOOL as24bit = FALSE) {
// static assert should go here
assert(FALSE);
}
template <>
inline void
_assignPixel<8>(BYTE* bits, BYTE* val, BOOL as24bit) {
*bits = *val;
}
template <>
inline void
_assignPixel<16>(BYTE* bits, BYTE* val, BOOL as24bit) {
WORD value(*reinterpret_cast<WORD*>(val));
#ifdef FREEIMAGE_BIGENDIAN
SwapShort(&value);
#endif
if (as24bit) {
bits[FI_RGBA_BLUE] = (BYTE)((((value & FI16_555_BLUE_MASK) >> FI16_555_BLUE_SHIFT) * 0xFF) / 0x1F);
bits[FI_RGBA_GREEN] = (BYTE)((((value & FI16_555_GREEN_MASK) >> FI16_555_GREEN_SHIFT) * 0xFF) / 0x1F);
bits[FI_RGBA_RED] = (BYTE)((((value & FI16_555_RED_MASK) >> FI16_555_RED_SHIFT) * 0xFF) / 0x1F);
} else {
*reinterpret_cast<WORD *>(bits) = 0x7FFF & value;
}
}
template <>
inline void
_assignPixel<24>(BYTE* bits, BYTE* val, BOOL as24bit) {
bits[FI_RGBA_BLUE] = val[0];
bits[FI_RGBA_GREEN] = val[1];
bits[FI_RGBA_RED] = val[2];
}
template <>
inline void
_assignPixel<32>(BYTE* bits, BYTE* val, BOOL as24bit) {
if (as24bit) {
_assignPixel<24>(bits, val, TRUE);
} else {
#if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR
*(reinterpret_cast<unsigned*>(bits)) = *(reinterpret_cast<unsigned*> (val));
#else // NOTE This is faster then doing reinterpret_cast to int + INPLACESWAP !
bits[FI_RGBA_BLUE] = val[0];
bits[FI_RGBA_GREEN] = val[1];
bits[FI_RGBA_RED] = val[2];
bits[FI_RGBA_ALPHA] = val[3];
#endif
}
}
/**
Generic RLE loader
*/
template<int bPP>
static void
loadRLE(FIBITMAP* dib, int width, int height, FreeImageIO* io, fi_handle handle, long eof, BOOL as24bit) {
const int file_pixel_size = bPP/8;
const int pixel_size = as24bit ? 3 : file_pixel_size;
const BYTE bpp = as24bit ? 24 : bPP;
const int line_size = CalculateLine(width, bpp);
// Note, many of the params can be computed inside the function.
// However, because this is a template function, it will lead to redundant code duplication.
BYTE rle;
BYTE *line_bits;
// this is used to guard against writing beyond the end of the image (on corrupted rle block)
const BYTE* dib_end = FreeImage_GetScanLine(dib, height);//< one-past-end row
// Compute the rough size of a line...
long pixels_offset = io->tell_proc(handle);
long sz = ((eof - pixels_offset) / height);
// ...and allocate cache of this size (yields good results)
IOCache cache(io, handle, sz);
if(cache.isNull()) {
FreeImage_Unload(dib);
dib = NULL;
return;
}
int x = 0, y = 0;
line_bits = FreeImage_GetScanLine(dib, y);
while (y < height) {
rle = cache.getByte();
BOOL has_rle = rle & 0x80;
rle &= ~0x80; // remove type-bit
BYTE packet_count = rle + 1;
//packet_count might be corrupt, test if we are not about to write beyond the last image bit
if ((line_bits+x) + packet_count*pixel_size > dib_end) {
FreeImage_OutputMessageProc(s_format_id, FI_MSG_ERROR_CORRUPTED);
// return what is left from the bitmap
return;
}
if (has_rle) {
// read a pixel value from file...
BYTE *val = cache.getBytes(file_pixel_size);
//...and fill packet_count pixels with it
for (int ix = 0; ix < packet_count; ix++) {
_assignPixel<bPP>((line_bits+x), val, as24bit);
x += pixel_size;
if (x >= line_size) {
x = 0;
y++;
line_bits = FreeImage_GetScanLine(dib, y);
}
}
} else {
// no rle commpresion
// copy packet_count pixels from file to dib
for (int ix = 0; ix < packet_count; ix++) {
BYTE *val = cache.getBytes(file_pixel_size);
_assignPixel<bPP>((line_bits+x), val, as24bit);
x += pixel_size;
if (x >= line_size) {
x = 0;
y++;
line_bits = FreeImage_GetScanLine(dib, y);
}
} //< packet_count
} //< has_rle
} //< while height
}
// --------------------------------------------------------------------------
static FIBITMAP * DLL_CALLCONV
Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) {
FIBITMAP *dib = NULL;
if (!handle) {
return NULL;
}
try {
const BOOL header_only = (flags & FIF_LOAD_NOPIXELS) == FIF_LOAD_NOPIXELS;
// remember the start offset
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;
}
// read in the bitmap bits
switch (header.image_type) {
case TGA_RGB: { //(24 bit)
//uncompressed
loadTrueColor(dib, header.is_width, header.is_height, pixel_size,io, handle, TRUE);
}
break;
case TGA_RLERGB: { //(24 bit)
loadRLE<24>(dib, header.is_width, header.is_height, io, handle, eof, TRUE);
}
break;
default :
FreeImage_Unload(dib);
return NULL;
}
}
break; // header.is_pixel_depth == 24
case 32 : {
int pixel_bits = 32;
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);
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;
}
// read in the bitmap bits
switch (header.image_type) {
case TGA_RGB: { //(32 bit)
// uncompressed
loadTrueColor(dib, header.is_width, header.is_height, 4 /*file_pixel_size*/, io, handle, TARGA_LOAD_RGB888 & flags);
}
break;
case TGA_RLERGB: { //(32 bit)
loadRLE<32>(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 == 32
} // switch(header.is_pixel_depth)
if (flipvert) {
FreeImage_FlipVertical(dib);
}
if (fliphoriz) {
FreeImage_FlipHorizontal(dib);
}
return dib;
} catch (const char *message) {
if (dib) {
FreeImage_Unload(dib);
}
FreeImage_OutputMessageProc(s_format_id, message);
return NULL;
}
}
// --------------------------------------------------------------------------
static BOOL
hasValidThumbnail(FIBITMAP* dib) {
FIBITMAP* thumbnail = FreeImage_GetThumbnail(dib);
return thumbnail
&& SupportsExportType(FreeImage_GetImageType(thumbnail))
&& SupportsExportDepth(FreeImage_GetBPP(thumbnail))
// Requirements according to the specification:
&& FreeImage_GetBPP(thumbnail) == FreeImage_GetBPP(dib)
&& FreeImage_GetImageType(thumbnail) == FreeImage_GetImageType(dib)
&& FreeImage_GetWidth(thumbnail) <= 255
&& FreeImage_GetHeight(thumbnail) <= 255;
}
/**
Writes the ready RLE packet to buffer
*/
static inline void
flushPacket(BYTE*& dest, unsigned pixel_size, BYTE* packet_begin, BYTE*& packet, BYTE& packet_count, BOOL& has_rle) {
if (packet_count) {
const BYTE type_bit = has_rle ? 0x80 : 0x0;
const BYTE write_count = has_rle ? 1 : packet_count;
// build packet header: zero-based count + type bit
assert(packet_count >= 1);
BYTE rle = packet_count - 1;
rle |= type_bit;
// write packet header
*dest = rle;
++dest;
// write packet data
memcpy(dest, packet_begin, write_count * pixel_size);
dest += write_count * pixel_size;
// reset state
packet_count = 0;
packet = packet_begin;
has_rle = FALSE;
}
}
static inline void
writeToPacket(BYTE* packet, BYTE* pixel, unsigned pixel_size) {
// Take care of channel and byte order here, because packet will be flushed straight to the file
switch (pixel_size) {
case 1:
*packet = *pixel;
break;
case 2: {
WORD val(*(WORD*)pixel);
#ifdef FREEIMAGE_BIGENDIAN
SwapShort(&val);
#endif
*(WORD*)packet = val;
}
break;
case 3: {
packet[0] = pixel[FI_RGBA_BLUE];
packet[1] = pixel[FI_RGBA_GREEN];
packet[2] = pixel[FI_RGBA_RED];
}
break;
case 4: {
#if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR
*(reinterpret_cast<unsigned*>(packet)) = *(reinterpret_cast<unsigned*> (pixel));
#else
packet[0] = pixel[FI_RGBA_BLUE];
packet[1] = pixel[FI_RGBA_GREEN];
packet[2] = pixel[FI_RGBA_RED];
packet[3] = pixel[FI_RGBA_ALPHA];
#endif
}
break;
default:
assert(FALSE);
}
}
static inline BOOL
isEqualPixel(BYTE* lhs, BYTE* rhs, unsigned pixel_size) {
switch (pixel_size) {
case 1:
return *lhs == *rhs;
case 2:
return *(WORD*)lhs == *(WORD*)rhs;
case 3:
return *(WORD*)lhs == *(WORD*)rhs && lhs[2] == rhs[2];
case 4:
return *(unsigned*)lhs == *(unsigned*)rhs;
default:
assert(FALSE);
return FALSE;
}
}
static void
saveRLE(FIBITMAP* dib, FreeImageIO* io, fi_handle handle) {
// Image is compressed line by line, packets don't span multiple lines (TGA2.0 recommendation)
const unsigned width = FreeImage_GetWidth(dib);
const unsigned height = FreeImage_GetHeight(dib);
const unsigned pixel_size = FreeImage_GetBPP(dib)/8;
const unsigned line_size = FreeImage_GetLine(dib);
const BYTE max_packet_size = 128;
BYTE packet_count = 0;
BOOL has_rle = FALSE;
// packet (compressed or not) to be written to line
BYTE* const packet_begin = (BYTE*)malloc(max_packet_size * pixel_size);
BYTE* packet = packet_begin;
// line to be written to disk
// Note: we need some extra bytes for anti-commpressed lines. The worst case is:
// 8 bit images were every 3th pixel is different.
// Rle packet becomes two pixels, but nothing is compressed: two byte pixels are transformed into byte header and byte pixel value
// After every rle packet there is a non-rle packet of one pixel: an extra byte for the header will be added for it
// In the end we gain no bytes from compression, but also must insert a byte at every 3th pixel
// add extra space for anti-commpressed lines
size_t extra_space = (size_t)ceil(width / 3.0);
BYTE* const line_begin = (BYTE*)malloc(width * pixel_size + extra_space);
BYTE* line = line_begin;
BYTE *current = (BYTE*)malloc(pixel_size);
BYTE *next = (BYTE*)malloc(pixel_size);
for(unsigned y = 0; y < height; y++) {
BYTE *bits = FreeImage_GetScanLine(dib, y);
// rewind line pointer
line = line_begin;
for(unsigned x = 0; x < line_size; x += pixel_size) {
AssignPixel(current, (bits + x), pixel_size);
// read next pixel from dib
if( x + 1*pixel_size < line_size) {
AssignPixel(next, (bits + x + 1*pixel_size), pixel_size);
} else {
// last pixel in line
// include current pixel and flush
if(!has_rle) {
writeToPacket(packet, current, pixel_size);
packet += pixel_size;
}
assert(packet_count < max_packet_size);
++packet_count;
flushPacket(line, pixel_size, packet_begin, packet, packet_count, has_rle);
// start anew on next line
break;
}
if(isEqualPixel(current, next, pixel_size)) {
// has rle
if(!has_rle) {
// flush non rle packet
flushPacket(line, pixel_size, packet_begin, packet, packet_count, has_rle);
// start a rle packet
has_rle = TRUE;
writeToPacket(packet, current, pixel_size);
packet += pixel_size;
}
// 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;
line += pixel_size;
}
#endif
}
break;
case 32: {
#if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR
line_source = scanline;
#else
for (unsigned x = 0; x < width; ++x) {
RGBQUAD* quad = ((RGBQUAD *)scanline) + x;
line[0] = quad->rgbBlue;
line[1] = quad->rgbGreen;
line[2] = quad->rgbRed;
line[3] = quad->rgbReserved;
line += pixel_size;
}
#endif
}
break;
}//switch(bpp)
// write line to disk
io->write_proc(line_source, pixel_size, width, handle);
}//for height
free(line_begin);
}
long extension_offset = 0 ;
if(hasValidThumbnail(dib)) {
// write extension area
extension_offset = io->tell_proc(handle);
TGAEXTENSIONAREA ex;
memset(&ex, 0, sizeof(ex));
assert(sizeof(ex) == 495);
ex.extension_size = sizeof(ex);
ex.postage_stamp_offset = extension_offset + ex.extension_size + 0 /*< no Scan Line Table*/;
ex.attributes_type = FreeImage_GetBPP(dib) == 32 ? 3 /*< useful Alpha channel data*/ : 0 /*< no Alpha data*/;
#ifdef FREEIMAGE_BIGENDIAN
SwapExtensionArea(&ex);
#endif
io->write_proc(&ex, sizeof(ex), 1, handle);
// (no Scan Line Table)
// write thumbnail
io->seek_proc(handle, ex.postage_stamp_offset, SEEK_SET);
FIBITMAP* thumbnail = FreeImage_GetThumbnail(dib);
BYTE width = (BYTE)FreeImage_GetWidth(thumbnail);
BYTE height = (BYTE)FreeImage_GetHeight(thumbnail);
io->write_proc(&width, 1, 1, handle);
io->write_proc(&height, 1, 1, handle);
#if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_RGB
SwapRedBlue32(dib);
#endif
#ifdef FREEIMAGE_BIGENDIAN
swapShortPixels(dib);
#endif
const unsigned line_size = FreeImage_GetLine(thumbnail);
for (BYTE h = 0; h < height; ++h) {
BYTE* src_line = FreeImage_GetScanLine(thumbnail, height - 1 - h);
io->write_proc(src_line, 1, line_size, handle);
}
}
// (no Color Correction Table)
// write the footer
TGAFOOTER footer;
footer.extension_offset = extension_offset;
footer.developer_offset = 0;
strcpy(footer.signature, "TRUEVISION-XFILE.");
#ifdef FREEIMAGE_BIGENDIAN
SwapFooter(&footer);
#endif
io->write_proc(&footer, sizeof(footer), 1, handle);
return TRUE;
}
// ==========================================================
// Init
// ==========================================================
void DLL_CALLCONV
InitTARGA(Plugin *plugin, int format_id) {
s_format_id = format_id;
plugin->format_proc = Format;
plugin->description_proc = Description;
plugin->extension_proc = Extension;
plugin->regexpr_proc = RegExpr;
plugin->open_proc = NULL;
plugin->close_proc = NULL;
plugin->pagecount_proc = NULL;
plugin->pagecapability_proc = NULL;
plugin->load_proc = Load;
plugin->save_proc = Save;
plugin->validate_proc = Validate;
plugin->mime_proc = MimeType;
plugin->supports_export_bpp_proc = SupportsExportDepth;
plugin->supports_export_type_proc = SupportsExportType;
plugin->supports_icc_profiles_proc = NULL;
plugin->supports_no_pixels_proc = SupportsNoPixels;
}