// ========================================================== // SGI Loader // // Design and implementation by // - Sherman Wilcox // - Noam Gat // // References : // ------------ // - The SGI Image File Format, Version 1.0 // http://astronomy.swin.edu.au/~pbourke/dataformats/sgirgb/sgiversion.html // - SGI RGB Image Format // http://astronomy.swin.edu.au/~pbourke/dataformats/sgirgb/ // // // 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 tagSGIHeader { /** IRIS image file magic number. This should be decimal 474. */ WORD magic; /** Storage format: 0 for uncompressed, 1 for RLE compression. */ BYTE storage; /** Number of bytes per pixel channel. Legally 1 or 2. */ BYTE bpc; /** Number of dimensions. Legally 1, 2, or 3. 1 means a single row, XSIZE long 2 means a single 2D image 3 means multiple 2D images */ WORD dimension; /** X size in pixels */ WORD xsize; /** Y size in pixels */ WORD ysize; /** Number of channels. 1 indicates greyscale 3 indicates RGB 4 indicates RGB and Alpha */ WORD zsize; /** Minimum pixel value. This is the lowest pixel value in the image.*/ LONG pixmin; /** Maximum pixel value. This is the highest pixel value in the image.*/ LONG pixmax; /** Ignored. Normally set to 0. */ char dummy[4]; /** Image name. Must be null terminated, therefore at most 79 bytes. */ char imagename[80]; /** Colormap ID. 0 - normal mode 1 - dithered, 3 mits for red and green, 2 for blue, obsolete 2 - index colour, obsolete 3 - not an image but a colourmap */ LONG colormap; /** Ignored. Should be set to 0, makes the header 512 bytes. */ char reserved[404]; } SGIHeader; typedef struct tagRLEStatus { int cnt; int val; } RLEStatus; #ifdef _WIN32 #pragma pack(pop) #else #pragma pack() #endif static const char *SGI_LESS_THAN_HEADER_LENGTH = "Incorrect header size"; static const char *SGI_16_BIT_COMPONENTS_NOT_SUPPORTED = "No 16 bit support"; static const char *SGI_COLORMAPS_NOT_SUPPORTED = "No colormap support"; static const char *SGI_EOF_IN_RLE_INDEX = "EOF in run length encoding"; static const char *SGI_EOF_IN_IMAGE_DATA = "EOF in image data"; static const char *SGI_INVALID_CHANNEL_COUNT = "Invalid channel count"; // ========================================================== // Plugin Interface // ========================================================== static int s_format_id; // ========================================================== // Plugin Implementation // ========================================================== #ifndef FREEIMAGE_BIGENDIAN static void SwapHeader(SGIHeader *header) { SwapShort(&header->magic); SwapShort(&header->dimension); SwapShort(&header->xsize); SwapShort(&header->ysize); SwapShort(&header->zsize); SwapLong((DWORD*)&header->pixmin); SwapLong((DWORD*)&header->pixmax); SwapLong((DWORD*)&header->colormap); } #endif static int get_rlechar(FreeImageIO *io, fi_handle handle, RLEStatus *pstatus) { if (!pstatus->cnt) { int cnt = 0; while (0 == cnt) { BYTE packed = 0; if(io->read_proc(&packed, sizeof(BYTE), 1, handle) < 1) { return EOF; } cnt = packed; } if (cnt == EOF) { return EOF; } pstatus->cnt = cnt & 0x7F; if (cnt & 0x80) { pstatus->val = -1; } else { BYTE packed = 0; if(io->read_proc(&packed, sizeof(BYTE), 1, handle) < 1) { return EOF; } pstatus->val = packed; } } pstatus->cnt--; if (pstatus->val == -1) { BYTE packed = 0; if(io->read_proc(&packed, sizeof(BYTE), 1, handle) < 1) { return EOF; } return packed; } else { return pstatus->val; } } static const char * DLL_CALLCONV Format() { return "SGI"; } static const char * DLL_CALLCONV Description() { return "SGI Image Format"; } static const char * DLL_CALLCONV Extension() { return "sgi"; } static const char * DLL_CALLCONV RegExpr() { return NULL; } static const char * DLL_CALLCONV MimeType() { return "image/x-sgi"; } static BOOL DLL_CALLCONV Validate(FreeImageIO *io, fi_handle handle) { BYTE sgi_signature[2] = { 0x01, 0xDA }; BYTE signature[2] = { 0, 0 }; io->read_proc(signature, 1, sizeof(sgi_signature), handle); return (memcmp(sgi_signature, signature, sizeof(sgi_signature)) == 0); } static BOOL DLL_CALLCONV SupportsExportDepth(int depth) { return FALSE; } static BOOL DLL_CALLCONV SupportsExportType(FREE_IMAGE_TYPE type) { return FALSE; } static FIBITMAP * DLL_CALLCONV Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) { int width = 0, height = 0, zsize = 0; int i, dim; int bitcount; SGIHeader sgiHeader; RLEStatus my_rle_status; FIBITMAP *dib = NULL; LONG *pRowIndex = NULL; try { // read the header memset(&sgiHeader, 0, sizeof(SGIHeader)); if(io->read_proc(&sgiHeader, 1, sizeof(SGIHeader), handle) < sizeof(SGIHeader)) { throw SGI_LESS_THAN_HEADER_LENGTH; } #ifndef FREEIMAGE_BIGENDIAN SwapHeader(&sgiHeader); #endif if(sgiHeader.magic != 474) { throw FI_MSG_ERROR_MAGIC_NUMBER; } BOOL bIsRLE = (sgiHeader.storage == 1) ? TRUE : FALSE; // check for unsupported image types if (sgiHeader.bpc != 1) { // Expected one byte per color component throw SGI_16_BIT_COMPONENTS_NOT_SUPPORTED; } if (sgiHeader.colormap != 0) { // Indexed or dithered images not supported throw SGI_COLORMAPS_NOT_SUPPORTED; } // get the width & height dim = sgiHeader.dimension; width = sgiHeader.xsize; if (dim < 3) { zsize = 1; } else { zsize = sgiHeader.zsize; } if (dim < 2) { height = 1; } else { height = sgiHeader.ysize; } if(bIsRLE) { // read the Offset Tables int index_len = height * zsize; pRowIndex = (LONG*)malloc(index_len * sizeof(LONG)); if(!pRowIndex) { throw FI_MSG_ERROR_MEMORY; } if ((unsigned)index_len != io->read_proc(pRowIndex, sizeof(LONG), index_len, handle)) { throw SGI_EOF_IN_RLE_INDEX; } #ifndef FREEIMAGE_BIGENDIAN // Fix byte order in index for (i = 0; i < index_len; i++) { SwapLong((DWORD*)&pRowIndex[i]); } #endif // Discard row size index for (i = 0; i < (int)(index_len * sizeof(LONG)); i++) { BYTE packed = 0; if( io->read_proc(&packed, sizeof(BYTE), 1, handle) < 1 ) { throw SGI_EOF_IN_RLE_INDEX; } } } switch(zsize) { case 1: bitcount = 8; break; case 2: //Grayscale+Alpha. Need to fake RGBA bitcount = 32; break; case 3: bitcount = 24; break; case 4: bitcount = 32; break; default: throw SGI_INVALID_CHANNEL_COUNT; } dib = FreeImage_Allocate(width, height, bitcount); if(!dib) { throw FI_MSG_ERROR_DIB_MEMORY; } if (bitcount == 8) { // 8-bit SGI files are grayscale images, so we'll generate // a grayscale palette. RGBQUAD *pclrs = FreeImage_GetPalette(dib); for (i = 0; i < 256; i++) { pclrs[i].rgbRed = (BYTE)i; pclrs[i].rgbGreen = (BYTE)i; pclrs[i].rgbBlue = (BYTE)i; pclrs[i].rgbReserved = 0; } } // decode the image memset(&my_rle_status, 0, sizeof(RLEStatus)); int ns = FreeImage_GetPitch(dib); BYTE *pStartRow = FreeImage_GetScanLine(dib, 0); int offset_table[] = { 2, 1, 0, 3 }; int numChannels = zsize; if (zsize < 3) { offset_table[0] = 0; } if (zsize == 2) { //This is how faked grayscale+alpha works. //First channel goes into first //second channel goes into alpha (4th channel) //Two channels are left empty and will be copied later offset_table[1] = 3; numChannels = 4; } LONG *pri = pRowIndex; for (i = 0; i < zsize; i++) { BYTE *pRow = pStartRow + offset_table[i]; for (int j = 0; j < height; j++, pRow += ns, pri++) { BYTE *p = pRow; if (bIsRLE) { my_rle_status.cnt = 0; io->seek_proc(handle, *pri, SEEK_SET); } for (int k = 0; k < width; k++, p += numChannels) { int ch; BYTE packed = 0; if (bIsRLE) { ch = get_rlechar(io, handle, &my_rle_status); packed = (BYTE)ch; } else { ch = io->read_proc(&packed, sizeof(BYTE), 1, handle); } if (ch == EOF) { throw SGI_EOF_IN_IMAGE_DATA; } *p = packed; } } } if (zsize == 2) { BYTE *pRow = pStartRow; //If faking RGBA from grayscale + alpha, copy first channel to second and third for (int i=0; iformat_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; }