diff options
Diffstat (limited to 'src/im_format_ico.cpp')
-rw-r--r-- | src/im_format_ico.cpp | 650 |
1 files changed, 650 insertions, 0 deletions
diff --git a/src/im_format_ico.cpp b/src/im_format_ico.cpp new file mode 100644 index 0000000..d4d1884 --- /dev/null +++ b/src/im_format_ico.cpp @@ -0,0 +1,650 @@ +/** \file + * \brief ICO - Windows Icon + * + * See Copyright Notice in im_lib.h + * $Id: im_format_ico.cpp,v 1.1 2008/10/17 06:10:16 scuri Exp $ + */ + +#include "im_format.h" +#include "im_format_all.h" +#include "im_util.h" +#include "im_counter.h" + +#include "im_binfile.h" + +#include <stdlib.h> +#include <string.h> +#include <memory.h> + +/* +typedef struct +{ + WORD idReserved; // Reserved (must be 0) + WORD idType; // Resource Type (1 for icons) + WORD idCount; // How many images? + ICONDIRENTRY idEntries[1]; // An entry for each image (idCount of 'em) +} ICONDIR, *LPICONDIR; // 6 +typedef struct +{ + BYTE bWidth; // Width, in pixels, of the image + BYTE bHeight; // Height, in pixels, of the image + BYTE bColorCount; // Number of colors in image (0 if >=8bpp) + BYTE bReserved; // Reserved ( must be 0) + WORD wPlanes; // Color Planes + WORD wBitCount; // Bits per pixel + DWORD dwBytesInRes; // How many bytes in this resource? + DWORD dwImageOffset; // Where in the file is this image? +} ICONDIRENTRY, *LPICONDIRENTRY; // 16 +typdef struct +{ + BITMAPINFOHEADER icHeader; // DIB header + RGBQUAD icColors[1]; // Color table + BYTE icXOR[1]; // DIB bits for XOR mask + BYTE icAND[1]; // DIB bits for AND mask (1 bpp) +} ICONIMAGE, *LPICONIMAGE; +*/ + +static const char* iICOCompTable[1] = +{ + "NONE" +}; + +class imFormatICO: public imFormat +{ + imBinFile* handle; /* the binary file handle */ + unsigned short bpp; /* number of bits per pixel */ + unsigned int offset[10], + next_offset; + int line_raw_size; // raw line size + + int ReadPalette(); + int WritePalette(); + void FixRGBOrder(); + +public: + imFormatICO() + :imFormat("ICO", + "Windows Icon", + "*.ico;", + iICOCompTable, + 1, + 1) + {} + ~imFormatICO() {} + + int Open(const char* file_name); + int New(const char* file_name); + void Close(); + void* Handle(int index); + int ReadImageInfo(int index); + int ReadImageData(void* data); + int WriteImageInfo(); + int WriteImageData(void* data); + int CanWrite(const char* compression, int color_mode, int data_type) const; +}; + +void imFormatRegisterICO(void) +{ + imFormatRegister(new imFormatICO()); +} + +int imFormatICO::Open(const char* file_name) +{ + unsigned short word; + + /* opens the binary file for reading with intel byte order */ + handle = imBinFileOpen(file_name); + if (!handle) + return IM_ERR_OPEN; + + imBinFileByteOrder(handle, IM_LITTLEENDIAN); + + /* reads the reserved value */ + imBinFileRead(handle, &word, 1, 2); + if (imBinFileError(handle)) + { + imBinFileClose(handle); + return IM_ERR_ACCESS; + } + + if (word != 0) + { + imBinFileClose(handle); + return IM_ERR_FORMAT; + } + + /* reads the resource type */ + imBinFileRead(handle, &word, 1, 2); + if (word != 1) + { + imBinFileClose(handle); + return IM_ERR_FORMAT; + } + + /* reads the number of images */ + imBinFileRead(handle, &word, 1, 2); + + this->image_count = word > 10? 10: word; + strcpy(this->compression, "NONE"); + + for (int i = 0; i < this->image_count; i++) + { + /* skip ICONDIRENTRY data except image offset */ + imBinFileSeekOffset(handle, 12); + + /* reads the image offset */ + imBinFileRead(handle, &this->offset[i], 1, 4); + + if (imBinFileError(handle)) + { + imBinFileClose(handle); + return IM_ERR_ACCESS; + } + } + + return IM_ERR_NONE; +} + +int imFormatICO::New(const char* file_name) +{ + /* opens the binary file for writing with intel byte order */ + handle = imBinFileNew(file_name); + if (!handle) + return IM_ERR_OPEN; + + imBinFileByteOrder(handle, IM_LITTLEENDIAN); + + imushort word_value = 0; + imBinFileWrite(handle, &word_value, 1, 2); /* reserved */ + word_value = 1; + imBinFileWrite(handle, &word_value, 1, 2); /* resource type */ + imBinFileWrite(handle, &word_value, 1, 2); /* number of images, at least one, must update at close */ + + this->next_offset = 6 + 5 * 16; // offset to the first image, room for 5 ICONDIRENTRY + + return IM_ERR_NONE; +} + +void imFormatICO::Close() +{ + if (this->is_new) + { + if (this->image_count > 1) + { + imBinFileSeekTo(handle, 4); + imushort word_value = (imushort)this->image_count; + imBinFileWrite(handle, &word_value, 1, 2); /* number of images */ + } + } + + imBinFileClose(handle); +} + +void* imFormatICO::Handle(int index) +{ + if (index == 0) + return (void*)this->handle; + else + return NULL; +} + +int imFormatICO::ReadImageInfo(int index) +{ + this->file_data_type = IM_BYTE; + unsigned int dword_value; + + if (index >= image_count) + return IM_ERR_DATA; + + // offset + size + imBinFileSeekTo(handle, this->offset[index] + 4); + + /* reads the image width */ + imBinFileRead(handle, &dword_value, 1, 4); + this->width = (int)dword_value; + + /* reads the image height */ + imBinFileRead(handle, &dword_value, 1, 4); + this->height = (int)(dword_value / 2); + + /* jump 2 bytes (planes) */ + imBinFileSeekOffset(handle, 2); + + // bpp + imBinFileRead(handle, &this->bpp, 1, 2); + + if (imBinFileError(handle)) + return IM_ERR_ACCESS; + + // sanity check + if (this->bpp != 1 && this->bpp != 4 && this->bpp != 8 && + this->bpp != 24 && this->bpp != 32) + return IM_ERR_DATA; + + if (this->bpp > 8) + { + this->file_color_mode = IM_RGB; + this->file_color_mode |= IM_PACKED; + if (this->bpp == 32) + this->file_color_mode |= IM_ALPHA; + } + else + { + this->file_color_mode = IM_MAP; + this->palette_count = 1 << bpp; + } + + if (this->bpp < 8) + this->convert_bpp = this->bpp; + + this->line_raw_size = imFileLineSizeAligned(this->width, this->bpp, 4); + this->line_buffer_extra = 4; // room enough for padding + + /* jump 8 bytes (compression, image size, resolution) */ + imBinFileSeekOffset(handle, 16); + + if (this->bpp <= 8) + { + /* reads the number of colors used */ + imBinFileRead(handle, &dword_value, 1, 4); + + /* updates the palette_count based on the number of colors used */ + if (dword_value != 0 && (int)dword_value < this->palette_count) + this->palette_count = dword_value; + + /* jump 4 bytes (important colors) */ + imBinFileSeekOffset(handle, 4); + } + else + { + /* jump 8 bytes (used colors, important colors) */ + imBinFileSeekOffset(handle, 8); + } + + if (imBinFileError(handle)) + return IM_ERR_ACCESS; + + if (this->bpp <= 8) + return ReadPalette(); + + return IM_ERR_NONE; +} + +int imFormatICO::WriteImageInfo() +{ + this->file_data_type = IM_BYTE; + this->file_color_mode = imColorModeSpace(this->user_color_mode); + + if (this->image_count == 5) + return IM_ERR_DATA; + + if (this->width > 255 || this->height > 255) + return IM_ERR_DATA; + + if (this->file_color_mode == IM_BINARY) + { + this->bpp = 1; + this->convert_bpp = 1; + } + else if (this->file_color_mode == IM_RGB) + { + this->file_color_mode |= IM_PACKED; + if (imColorModeHasAlpha(this->user_color_mode)) + { + this->file_color_mode |= IM_ALPHA; + this->bpp = 32; + } + else + this->bpp = 24; + } + else + this->bpp = 8; + + this->line_raw_size = imFileLineSizeAligned(this->width, this->bpp, 4); + this->line_buffer_extra = 4; // room enough for padding + int palette_size = (this->bpp > 8)? 0: this->palette_count*4; + + imbyte byte_value; + imushort word_value; + + /* updates the ICON directory entry */ + + imBinFileSeekTo(handle, 6 + this->image_count * 16); // ICONDIR + i * ICONDIRENTRY + + byte_value = (imbyte)this->width; + imBinFileWrite(handle, &byte_value, 1, 1); /* width */ + byte_value = (imbyte)this->height; + imBinFileWrite(handle, &byte_value, 1, 1); /* height */ + byte_value = (imbyte)((this->bpp > 8)? 0: this->palette_count); + imBinFileWrite(handle, &byte_value, 1, 1); /* color count */ + imBinFileWrite(handle, (void*)"\0", 1, 1); /* reserved */ + word_value = 1; + imBinFileWrite(handle, &word_value, 1, 2); /* planes */ + word_value = this->bpp; + imBinFileWrite(handle, &word_value, 1, 2); /* bit count */ + int and_line_size = imFileLineSizeAligned(this->width, 1, 4); + int resource_size = 40 + palette_size + (line_raw_size + and_line_size) * this->height; + unsigned int dword_value = resource_size; + imBinFileWrite(handle, &dword_value, 1, 4); /* resource size */ + dword_value = this->next_offset; + imBinFileWrite(handle, &dword_value, 1, 4); /* data offset */ + + this->offset[this->image_count] = this->next_offset; + this->next_offset += resource_size; + + /* writes the image */ + + imBinFileSeekTo(handle, this->offset[this->image_count]); + + dword_value = 40; + imBinFileWrite(handle, &dword_value, 1, 4); /* header size */ + dword_value = this->width; + imBinFileWrite(handle, &dword_value, 1, 4); /* width */ + dword_value = this->height*2; + imBinFileWrite(handle, &dword_value, 1, 4); /* height */ + word_value = 1; + imBinFileWrite(handle, &word_value, 1, 2); /* planes */ + word_value = this->bpp; + imBinFileWrite(handle, &word_value, 1, 2); /* bpp */ + dword_value = 0; + imBinFileWrite(handle, &dword_value, 1, 4); /* compression */ + dword_value = line_raw_size * this->height; + imBinFileWrite(handle, &dword_value, 1, 4); /* data size */ + + imBinFileWrite(handle, (void*)"\0\0\0\0\0\0\0\0", 8, 1); /* resolution */ + + dword_value = (this->bpp > 8)? 0: this->palette_count; + imBinFileWrite(handle, &dword_value, 1, 4); /* colors used */ + dword_value = 0; + imBinFileWrite(handle, &dword_value, 1, 4); /* colors important (all) */ + + /* tests if everything was ok */ + if (imBinFileError(handle)) + return IM_ERR_ACCESS; + + if (this->bpp < 24) + return WritePalette(); + + return IM_ERR_NONE; +} + +int imFormatICO::ReadPalette() +{ + /* reads the color palette */ + unsigned char bmp_colors[256 * 4]; + imBinFileRead(handle, bmp_colors, this->palette_count * 4, 1); + + if (imBinFileError(handle)) + return IM_ERR_ACCESS; + + /* convert the color map to the IM format */ + for (int c = 0; c < this->palette_count; c++) + { + int i = c * 4; + this->palette[c] = imColorEncode(bmp_colors[i + 2], + bmp_colors[i + 1], + bmp_colors[i]); + } + + return IM_ERR_NONE; +} + +int imFormatICO::WritePalette() +{ + unsigned char bmp_colors[256 * 4]; + + /* convert the color map to the IM format */ + for (int c = 0; c < this->palette_count; c++) + { + int i = c * 4; + imColorDecode(&bmp_colors[i + 2], &bmp_colors[i + 1], &bmp_colors[i], this->palette[c]); + bmp_colors[i + 3] = 0; + } + + /* writes the color palette */ + imBinFileWrite(handle, bmp_colors, this->palette_count * 4, 1); + + if (imBinFileError(handle)) + return IM_ERR_ACCESS; + + return IM_ERR_NONE; +} + +void imFormatICO::FixRGBOrder() +{ + if (this->bpp == 24) + { + imbyte* byte_data = (imbyte*)this->line_buffer; + for (int x = 0; x < this->width; x++) + { + int c = x*3; + imbyte temp = byte_data[c]; // swap R and B + byte_data[c] = byte_data[c+2]; + byte_data[c+2] = temp; + } + } + else /* bpp == 32 */ + { + /* inverts the DWORD values if not intel */ + if (imBinCPUByteOrder() == IM_BIGENDIAN) + imBinSwapBytes4(this->line_buffer, this->width); + + unsigned int* dword_data = (unsigned int*)this->line_buffer; + imbyte* byte_data = (imbyte*)this->line_buffer; + + for (int x = 0; x < this->width; x++) + { + unsigned int dword_value = dword_data[x]; + int c = x*4; + byte_data[c] = (imbyte)((0x00FF0000 & dword_value) >> 16); + byte_data[c+1] = (imbyte)((0x0000FF00 & dword_value) >> 8); + byte_data[c+2] = (imbyte)((0x000000FF & dword_value) >> 0); + byte_data[c+3] = (imbyte)((0xFF000000 & dword_value) >> 24); + } + } +} + +static inline int PixelOffset(int is_top_down, int is_packed, int width, int height, int depth, int col, int row, int plane) +{ + if (is_top_down) + row = height-1 - row; + + if (is_packed) + return row*width*depth + col*depth + plane; + else + return plane*width*height + row*width + col; +} + +int imFormatICO::ReadImageData(void* data) +{ + imCounterTotal(this->counter, this->height, "Reading ICO..."); + + for (int row = 0; row < this->height; row++) + { + imBinFileRead(handle, this->line_buffer, this->line_raw_size, 1); + if (imBinFileError(handle)) + return IM_ERR_ACCESS; + + if (this->bpp > 8) + FixRGBOrder(); + + imFileLineBufferRead(this, data, row, 0); + + if (!imCounterInc(this->counter)) + return IM_ERR_COUNTER; + } + + if ((imColorModeHasAlpha(this->user_color_mode) && this->bpp!=32) || /* user has alpha and file does not have alpha -> alpha came from AND data */ + imColorModeSpace(this->user_color_mode) == IM_MAP) /* or MAP */ + { + int line_size = imFileLineSizeAligned(this->width, 1, 4); + int image_size = this->height*line_size; + imbyte* and_data = new imbyte[image_size]; + + imBinFileRead(handle, and_data, image_size, 1); + if (imBinFileError(handle)) + return IM_ERR_ACCESS; + + imbyte* and_data_line = and_data; + imbyte* user_data = (imbyte*)data; + unsigned long histo[256]; + int depth = imColorModeDepth(this->user_color_mode); + int alpha_plane = 0; + if (imColorModeHasAlpha(this->user_color_mode)) + alpha_plane = depth - 1; + else + memset(histo, 0, 256*sizeof(unsigned long)); + + for (int j = 0; j < this->height; j++) + { + for (int i = 0; i < this->width; i++) + { + int offset = PixelOffset(imColorModeIsTopDown(this->user_color_mode), + imColorModeIsPacked(this->user_color_mode), + this->width, this->height, depth, i, j, alpha_plane); + + if (imColorModeHasAlpha(this->user_color_mode)) + { + if (((and_data_line[i / 8] >> (7 - i % 8)) & 0x01)) + user_data[offset] = 0; + else + user_data[offset] = 255; + } + else + { + /* the most repeated index with transparency will be the transparent index. */ + if (((and_data_line[i / 8] >> (7 - i % 8)) & 0x01)) + histo[user_data[offset]]++; + } + } + and_data_line += line_size; + } + + if (imColorModeSpace(this->user_color_mode) == IM_MAP) + { + imbyte transp_index = 0; + unsigned long histo_max = histo[0]; + + for (int i = 1; i < 256; i++) + { + if (histo_max < histo[i]) + { + histo_max = histo[i]; + transp_index = (imbyte)i; + } + } + AttribTable()->Set("TransparencyIndex", IM_BYTE, 1, &transp_index); + } + + delete [] and_data; + } + + return IM_ERR_NONE; +} + +int imFormatICO::WriteImageData(void* data) +{ + imCounterTotal(this->counter, this->height, "Writing ICO..."); + + /* Image Data */ + + for (int row = 0; row < this->height; row++) + { + imFileLineBufferWrite(this, data, row, 0); + + if (this->bpp > 8) + FixRGBOrder(); + + imBinFileWrite(handle, this->line_buffer, this->line_raw_size, 1); + + if (imBinFileError(handle)) + return IM_ERR_ACCESS; + + if (!imCounterInc(this->counter)) + return IM_ERR_COUNTER; + } + + /* AND Data */ + + int and_line_size = imFileLineSizeAligned(this->width, 1, 4); + int and_size = this->height*and_line_size; + imbyte* and_data = new imbyte[and_size]; + memset(and_data, 0, and_size); /* zero = opaque */ + + if (imColorModeHasAlpha(this->user_color_mode)) + { + imbyte* and_data_line = and_data; + imbyte* user_data = (imbyte*)data; + int depth = imColorModeDepth(this->user_color_mode); + int alpha_plane = depth - 1; + + for (int j = 0; j < this->height; j++) + { + for (int i = 0; i < this->width; i++) + { + int offset = PixelOffset(imColorModeIsTopDown(this->user_color_mode), + imColorModeIsPacked(this->user_color_mode), + this->width, this->height, depth, i, j, alpha_plane); + + if (user_data[offset] == 0) /* mark only full transparent pixels */ + and_data_line[i / 8] |= (0x01 << (7 - (i % 8))); + } + and_data_line += and_line_size; + } + } + else + { + const imbyte* transp_index = (const imbyte*)AttribTable()->Get("TransparencyIndex"); + if (imColorModeSpace(this->user_color_mode) == IM_MAP && transp_index) + { + imbyte* and_data_line = and_data; + imbyte* user_data = (imbyte*)data; + int depth = imColorModeDepth(this->user_color_mode); + + for (int j = 0; j < this->height; j++) + { + for (int i = 0; i < this->width; i++) + { + int offset = PixelOffset(imColorModeIsTopDown(this->user_color_mode), + imColorModeIsPacked(this->user_color_mode), + this->width, this->height, depth, i, j, 0); + + if (user_data[offset] == *transp_index) + and_data_line[i / 8] |= (0x01 << (7 - (i % 8))); + } + and_data_line += and_line_size; + } + } + } + + imBinFileWrite(handle, and_data, and_size, 1); + delete [] and_data; + + if (imBinFileError(handle)) + return IM_ERR_ACCESS; + + this->image_count++; + + return IM_ERR_NONE; +} + +int imFormatICO::CanWrite(const char* compression, int color_mode, int data_type) const +{ + int color_space = imColorModeSpace(color_mode); + + if (color_space == IM_YCBCR || color_space == IM_LAB || + color_space == IM_LUV || color_space == IM_XYZ || + color_space == IM_CMYK) + return IM_ERR_DATA; + + if (data_type != IM_BYTE) + return IM_ERR_DATA; + + if (!compression || compression[0] == 0) + return IM_ERR_NONE; + + if (!imStrEqual(compression, "NONE")) + return IM_ERR_COMPRESS; + + return IM_ERR_NONE; +} |