// ==========================================================
// Sun rasterfile Loader
//
// Design and implementation by 
// - Hervé Drolon (drolon@infonie.fr)
//
// 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 tagSUNHEADER {
	DWORD magic;		// Magic number
	DWORD width;		// Image width in pixels
	DWORD height;		// Image height in pixels
	DWORD depth;		// Depth (1, 8, 24 or 32 bits) of each pixel
	DWORD length;		// Image length (in bytes)
	DWORD type;			// Format of file (see RT_* below)
	DWORD maptype;		// Type of colormap (see RMT_* below)
	DWORD maplength;	// Length of colormap (in bytes)
} SUNHEADER;

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

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

// Following the header is the colormap, for maplength bytes (unless maplength is zero),
// then the image. Each row of the image is rounded to 2 bytes.

#define RAS_MAGIC 0x59A66A95 // Magic number for Sun rasterfiles

// Sun supported type's

#define RT_OLD			0	// Old format (raw image in 68000 byte order)
#define RT_STANDARD		1	// Raw image in 68000 byte order
#define RT_BYTE_ENCODED	2	// Run-length encoding of bytes 
#define RT_FORMAT_RGB	3	// XRGB or RGB instead of XBGR or BGR
#define RT_FORMAT_TIFF	4	// TIFF <-> standard rasterfile
#define RT_FORMAT_IFF	5	// IFF (TAAC format) <-> standard rasterfile

#define RT_EXPERIMENTAL 0xffff	// Reserved for testing

// These are the possible colormap types.
// if it's in RGB format, the map is made up of three byte arrays
// (red, green, then blue) that are each 1/3 of the colormap length.

#define RMT_NONE		0	// maplength is expected to be 0
#define RMT_EQUAL_RGB	1	// red[maplength/3], green[maplength/3], blue[maplength/3]
#define RMT_RAW			2	// Raw colormap
#define RESC			128 // Run-length encoding escape character

// ----- NOTES -----
// Each line of the image is rounded out to a multiple of 16 bits.
// This corresponds to the rounding convention used by the memory pixrect
// package (/usr/include/pixrect/memvar.h) of the SunWindows system.
// The ras_encoding field (always set to 0 by Sun's supported software)
// was renamed to ras_length in release 2.0.  As a result, rasterfiles
// of type 0 generated by the old software claim to have 0 length; for
// compatibility, code reading rasterfiles must be prepared to compute the
// TRUE length from the width, height, and depth fields.

// ==========================================================
// Internal functions
// ==========================================================

static void
ReadData(FreeImageIO *io, fi_handle handle, BYTE *buf, DWORD length, BOOL rle) {
	// Read either Run-Length Encoded or normal image data

	static BYTE repchar, remaining= 0;

	if (rle) {
		// Run-length encoded read

		while(length--) {
			if (remaining) {
				remaining--;
				*(buf++)= repchar;
			} else {
				io->read_proc(&repchar, 1, 1, handle);

				if (repchar == RESC) {
					io->read_proc(&remaining, 1, 1, handle);

					if (remaining == 0) {
						*(buf++)= RESC;
					} else {
						io->read_proc(&repchar, 1, 1, handle);

						*(buf++)= repchar;
					}
				} else {
					*(buf++)= repchar;
				}
			}
		}
	} else {
		// Normal read
	
		io->read_proc(buf, length, 1, handle);
	}
}

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

static int s_format_id;

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

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

static const char * DLL_CALLCONV
Description() {
	return "Sun Raster Image";
}

static const char * DLL_CALLCONV
Extension() {
	return "ras";
}

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

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

static BOOL DLL_CALLCONV
Validate(FreeImageIO *io, fi_handle handle) {
	BYTE ras_signature[] = { 0x59, 0xA6, 0x6A, 0x95 };
	BYTE signature[4] = { 0, 0, 0, 0 };

	io->read_proc(signature, 1, sizeof(ras_signature), handle);

	return (memcmp(ras_signature, signature, sizeof(ras_signature)) == 0);
}

static BOOL DLL_CALLCONV
SupportsExportDepth(int depth) {
	return FALSE;
}

static BOOL DLL_CALLCONV 
SupportsExportType(FREE_IMAGE_TYPE type) {
	return FALSE;
}

static BOOL DLL_CALLCONV
SupportsNoPixels() {
	return TRUE;
}

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

static FIBITMAP * DLL_CALLCONV
Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) {
	SUNHEADER header;	// Sun file header
	WORD linelength;	// Length of raster line in bytes
	WORD fill;			// Number of fill bytes per raster line
	BOOL rle;			// TRUE if RLE file
	BOOL isRGB;			// TRUE if file type is RT_FORMAT_RGB
	BYTE fillchar;

	FIBITMAP *dib = NULL;
	BYTE *bits;			// Pointer to dib data
	WORD x, y;

	if(!handle) {
		return NULL;
	}

	BOOL header_only = (flags & FIF_LOAD_NOPIXELS) == FIF_LOAD_NOPIXELS;

	try {
		// Read SUN raster header

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

#ifndef FREEIMAGE_BIGENDIAN
		// SUN rasterfiles are big endian only

		SwapLong(&header.magic);
		SwapLong(&header.width);
		SwapLong(&header.height);
		SwapLong(&header.depth);
		SwapLong(&header.length);
		SwapLong(&header.type);
		SwapLong(&header.maptype);
		SwapLong(&header.maplength);
#endif

		// Verify SUN identifier

		if (header.magic != RAS_MAGIC) {
			throw FI_MSG_ERROR_MAGIC_NUMBER;
		}

		// Allocate a new DIB

		switch(header.depth) {
			case 1:
			case 8:
				dib = FreeImage_AllocateHeader(header_only, header.width, header.height, header.depth);
				break;

			case 24:
				dib = FreeImage_AllocateHeader(header_only, header.width, header.height, header.depth, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK);
				break;

			case 32:
				dib = FreeImage_AllocateHeader(header_only, header.width, header.height, header.depth, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK);
				break;
		}

		if (dib == NULL) {
			throw FI_MSG_ERROR_DIB_MEMORY;
		}

		// Check the file format

		rle = FALSE;
		isRGB = FALSE;

		switch(header.type) {
			case RT_OLD:
			case RT_STANDARD:
			case RT_FORMAT_TIFF: // I don't even know what these format are...
			case RT_FORMAT_IFF: //The TIFF and IFF format types indicate that the raster
				//file was originally converted from either of these file formats.
				//so lets at least try to process them as RT_STANDARD
				break;

			case RT_BYTE_ENCODED:
				rle = TRUE;
				break;

			case RT_FORMAT_RGB:
				isRGB = TRUE;
				break;

			default:
				throw FI_MSG_ERROR_UNSUPPORTED_FORMAT;
		}

		// set up the colormap if needed

		switch(header.maptype) {
			case RMT_NONE :
			{				
				if (header.depth < 24) {
					// Create linear color ramp

					RGBQUAD *pal = FreeImage_GetPalette(dib);

					int numcolors = 1 << header.depth;

					for (int i = 0; i < numcolors; i++) {
						pal[i].rgbRed	= (BYTE)((255 * i) / (numcolors - 1));
						pal[i].rgbGreen = (BYTE)((255 * i) / (numcolors - 1));
						pal[i].rgbBlue	= (BYTE)((255 * i) / (numcolors - 1));
					}
				}

				break;
			}

			case RMT_EQUAL_RGB:
			{
				BYTE *r, *g, *b;

				// Read SUN raster colormap

				int numcolors = 1 << header.depth;
				if((DWORD)(3 * numcolors) > header.maplength) {
					// some RAS may have less colors than the full palette
					numcolors = header.maplength / 3;
				} else {
					throw "Invalid palette";
				}

				r = (BYTE*)malloc(3 * numcolors * sizeof(BYTE));
				g = r + numcolors;
				b = g + numcolors;

				RGBQUAD *pal = FreeImage_GetPalette(dib);

				io->read_proc(r, 3 * numcolors, 1, handle);

				for (int i = 0; i < numcolors; i++) {
					pal[i].rgbRed	= r[i];
					pal[i].rgbGreen = g[i];
					pal[i].rgbBlue	= b[i];
				}

				free(r);
				break;
			}

			case RMT_RAW:
			{
				BYTE *colormap;

				// Read (skip) SUN raster colormap.

				colormap = (BYTE *)malloc(header.maplength * sizeof(BYTE));

				io->read_proc(colormap, header.maplength, 1, handle);

				free(colormap);
				break;
			}
		}

		if(header_only) {
			// header only mode
			return dib;
		}

		// Calculate the line + pitch
		// Each row is multiple of 16 bits (2 bytes).

		if (header.depth == 1) {
			linelength = (WORD)((header.width / 8) + (header.width % 8 ? 1 : 0));
		} else {
			linelength = (WORD)header.width;
		}

		fill = (linelength % 2) ? 1 : 0;

		unsigned pitch = FreeImage_GetPitch(dib);

		// Read the image data
		
		switch(header.depth) {
			case 1:
			case 8:
			{
				bits = FreeImage_GetBits(dib) + (header.height - 1) * pitch;

				for (y = 0; y < header.height; y++) {
					ReadData(io, handle, bits, linelength, rle);

					bits -= pitch;

					if (fill) {
						ReadData(io, handle, &fillchar, fill, rle);
					}
				}

				break;
			}

			case 24:
			{
				BYTE *buf, *bp;

				buf = (BYTE*)malloc(header.width * 3);

				for (y = 0; y < header.height; y++) {
					bits = FreeImage_GetBits(dib) + (header.height - 1 - y) * pitch;

					ReadData(io, handle, buf, header.width * 3, rle);

					bp = buf;

					if (isRGB) {
						for (x = 0; x < header.width; x++) {
							bits[FI_RGBA_RED] = *(bp++);	// red
							bits[FI_RGBA_GREEN] = *(bp++);	// green
							bits[FI_RGBA_BLUE] = *(bp++);	// blue

							bits += 3;
						}
					} else {
						for (x = 0; x < header.width; x++) {
							bits[FI_RGBA_RED] = *(bp + 2);	// red
							bits[FI_RGBA_GREEN] = *(bp + 1);// green
							bits[FI_RGBA_BLUE] = *bp;       // blue

							bits += 3; bp += 3;
						}
					}

					if (fill) {
						ReadData(io, handle, &fillchar, fill, rle);
					}
				}

				free(buf);
				break;
			}

			case 32:
			{
				BYTE *buf, *bp;

				buf = (BYTE*)malloc(header.width * 4);

				for (y = 0; y < header.height; y++) {
					bits = FreeImage_GetBits(dib) + (header.height - 1 - y) * pitch;

					ReadData(io, handle, buf, header.width * 4, rle);

					bp = buf;

					if (isRGB) {
						for (x = 0; x < header.width; x++) {
							bits[FI_RGBA_ALPHA] = *(bp++);	// alpha
							bits[FI_RGBA_RED] = *(bp++);	// red
							bits[FI_RGBA_GREEN] = *(bp++);	// green
							bits[FI_RGBA_BLUE] = *(bp++);	// blue

							bits += 4;
						}
					}
					else {
						for (x = 0; x < header.width; x++) {
							bits[FI_RGBA_RED] = *(bp + 3);	// red
							bits[FI_RGBA_GREEN] = *(bp + 2); // green
							bits[FI_RGBA_BLUE] = *(bp + 1);	// blue
							bits[FI_RGBA_ALPHA] = *bp;		// alpha

							bits += 4;
							bp += 4;
						}
					}

					if (fill) {
						ReadData(io, handle, &fillchar, fill, rle);
					}
				}

				free(buf);
				break;
			}
		}
		
		return dib;

	} catch (const char *text) {
		if(dib) {
			FreeImage_Unload(dib);
		}
		FreeImage_OutputMessageProc(s_format_id, text);
	}

	return NULL;
}

// ==========================================================
//   Init
// ==========================================================

void DLL_CALLCONV
InitRAS(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 = NULL;
	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;
}
