/* * Copyright 2006 Richard Wilson * Copyright 2008 Sean Fox * * This file is part of NetSurf, http://www.netsurf-browser.org/ * * NetSurf is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * NetSurf is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include "libnsbmp.h" #include "utils/log.h" /* The functions provided by this file allow for the decoding of Microsoft's BMP and ICO image file formats. READING BMP FILES ================= To begin decoding a BMP, the caller should initialise a 'bmp_bitmap_callback_vt' structure with the appropriate values necessary to handle bitmap images. Next, a 'bmp_image' structure should be initialised by calling bmp_create(). This structure should then be passed to bmp_analyse() along with the BMP data to process and the size of this data. Once the analysis has begun, the decoder completes the width and height variables. To decode the image, the caller must use bmp_decode() which selects the proper decoding method based on the BMP info header and assigns the decoded bitmap image to the 'bitmap' member of the 'bmp_image' structure. The bitmap image is stored with 4 bytes-per-pixel in RGBA format. It should be noted that bmp_finalise() should always be called, even if the image was never decoded. It is also the responsibility of the caller to free 'bmp_data'. READING ICO FILES ================= To begin decoding an ICO, the caller should initialise a 'bmp_bitmap_callback_vt' structure with the appropriate values necessary to handle bitmap images. Next, an 'ico_collection' structure should be initialised by calling ico_create(). This structure should then be passed to ico_analyse() along with the ICO data to process and the size of this data. Once the analysis has begun, the decoder completes the width and height variables. Because ICO collections contain multiple bitmap images, the width and height will contain the values of the largest available image. The caller then obtains a BMP from the ICO collection by calling ico_find() with the requested width and height. To decode the image, the caller must use bmp_decode() which selects the proper decoding method based on the BMP info header and assigns the decoded bitmap image to the 'bitmap' member of the 'bmp_image' structure. The bitmap image is stored with 4 bytes-per-pixel in RGBA format. It should be noted that ico_finalise() should always be called, even if no images were decoded. Because ico_finalise() calls bmp_finalise() for each bitmap within the collection, the caller is not required to perform this function. However, it is the responsibility of the caller to free 'ico_data'. [dynis] - Tue 1st July 2008 */ /* squashes unused variable compiler warnings */ #define UNUSED(x) ((x)=(x)) /* BMP flags */ #define BMP_FILE_HEADER_SIZE 14 #define ICO_FILE_HEADER_SIZE 6 #define ICO_DIR_ENTRY_SIZE 16 static inline int8_t read_int8(uint8_t *data, unsigned int o) { return (int8_t) data[o]; } static inline uint8_t read_uint8(uint8_t *data, unsigned int o) { return (uint8_t) data[o]; } static inline int16_t read_int16(uint8_t *data, unsigned int o) { return (int16_t) (data[o] | (data[o+1] << 8)); } static inline uint16_t read_uint16(uint8_t *data, unsigned int o) { return (uint16_t) (data[o] | (data[o+1] << 8)); } static inline int32_t read_int32(uint8_t *data, unsigned int o) { return (int32_t) (data[o] | (data[o+1] << 8) | (data[o+2] << 16) | (data[o+3] << 24)); } static inline uint32_t read_uint32(uint8_t *data, unsigned int o) { return (uint32_t) (data[o] | (data[o+1] << 8) | (data[o+2] << 16) | (data[o+3] << 24)); } static bmp_result next_ico_image(ico_collection *ico, ico_image *image); static bmp_result bmp_analyse_header(bmp_image *bmp, unsigned char *data); static bmp_result bmp_decode_rgb24(bmp_image *bmp, uint8_t **start, int bytes); static bmp_result bmp_decode_rgb16(bmp_image *bmp, uint8_t **start, int bytes); static bmp_result bmp_decode_rgb(bmp_image *bmp, uint8_t **start, int bytes); static bmp_result bmp_decode_mask(bmp_image *bmp, uint8_t *data, int bytes); static bmp_result bmp_decode_rle(bmp_image *bmp, uint8_t *data, int bytes, int size); static void bmp_invalidate(void *bitmap, void *private_word); /** Initialises necessary bmp_image members. */ void bmp_create(bmp_image *bmp, bmp_bitmap_callback_vt *bitmap_callbacks) { memset(bmp, 0, sizeof(bmp_image)); bmp->bitmap_callbacks = *bitmap_callbacks; } /** Initialises necessary ico_collection members. */ void ico_collection_create(ico_collection *ico, bmp_bitmap_callback_vt *bitmap_callbacks) { memset(ico, 0, sizeof(ico_collection)); ico->bitmap_callbacks = *bitmap_callbacks; } /** * Analyse a BMP prior to decoding. * * This function will scan the data provided and perform simple checks to * ensure the data is a valid BMP. * * This function must be called before bmp_decode() and sets up all the * relevant values in the bmp structure. * * \param bmp the BMP image to analyse * \return BMP_OK on success */ bmp_result bmp_analyse(bmp_image *bmp, size_t size, unsigned char *cdata) { uint8_t *data = (uint8_t *)cdata; /* ensure we aren't already initialised */ if (bmp->bitmap) return BMP_OK; /* initialize values */ bmp->buffer_size = size; bmp->bmp_data = data; /* standard 14-byte BMP file header is: * * +0 UINT16 File Type ('BM') * +2 UINT32 Size of File (in bytes) * +6 INT16 Reserved Field (1) * +8 INT16 Reserved Field (2) * +10 UINT32 Starting Position of Image Data (offset in bytes) */ if (bmp->buffer_size < BMP_FILE_HEADER_SIZE) return BMP_INSUFFICIENT_DATA; if ((data[0] != (uint8_t)'B') || (data[1] != (uint8_t)'M')) return BMP_DATA_ERROR; bmp->bitmap_offset = read_uint32(data, 10); data += BMP_FILE_HEADER_SIZE; /* decode the BMP header */ return bmp_analyse_header(bmp, data); } /** * Analyse an ICO prior to decoding. * * This function will scan the data provided and perform simple checks to * ensure the data is a valid ICO. * * This function must be called before ico_find(). * * \param ico the ICO image to analyse * \return BMP_OK on success */ bmp_result ico_analyse(ico_collection *ico, size_t size, uint8_t *data) { uint16_t count, i; bmp_result result; int area, max_area = 0; /* ensure we aren't already initialised */ if (ico->first) return BMP_OK; /* initialize values */ ico->buffer_size = size; ico->ico_data = data; /* 6-byte ICO file header is: * * +0 INT16 Reserved (should be 0) * +2 UINT16 Type (1 for ICO, 2 for CUR) * +4 UINT16 Number of BMPs to follow */ if (ico->buffer_size < ICO_FILE_HEADER_SIZE) return BMP_INSUFFICIENT_DATA; // if (read_int16(data, 2) != 0x0000) // return BMP_DATA_ERROR; if (read_uint16(data, 2) != 0x0001) return BMP_DATA_ERROR; count = read_uint16(data, 4); if (count == 0) return BMP_DATA_ERROR; data += ICO_FILE_HEADER_SIZE; /* check if we have enough data for the directory */ if (ico->buffer_size < (uint32_t)(ICO_FILE_HEADER_SIZE + (ICO_DIR_ENTRY_SIZE * count))) return BMP_INSUFFICIENT_DATA; /* Decode the BMP files. * * 16-byte ICO directory entry is: * * +0 UINT8 Width (0 for 256 pixels) * +1 UINT8 Height (0 for 256 pixels) * +2 UINT8 Colour count (0 if more than 256 colours) * +3 INT8 Reserved (should be 0, but may not be) * +4 UINT16 Colour Planes (should be 0 or 1) * +6 UINT16 Bits Per Pixel * +8 UINT32 Size of BMP info header + bitmap data in bytes * +12 UINT32 Offset (points to the BMP info header, not the bitmap data) */ for (i = 0; i < count; i++) { ico_image *image; image = calloc(1, sizeof(ico_image)); if (!image) return BMP_INSUFFICIENT_MEMORY; result = next_ico_image(ico, image); if (result != BMP_OK) return result; image->bmp.width = read_uint8(data, 0); if (image->bmp.width == 0) image->bmp.width = 256; image->bmp.height = read_uint8(data, 1); if (image->bmp.height == 0) image->bmp.height = 256; image->bmp.buffer_size = read_uint32(data, 8); image->bmp.bmp_data = ico->ico_data + read_uint32(data, 12); image->bmp.ico = true; data += ICO_DIR_ENTRY_SIZE; result = bmp_analyse_header(&image->bmp, image->bmp.bmp_data); if (result != BMP_OK) return result; /* adjust the size based on the images available */ area = image->bmp.width * image->bmp.height; if (area > max_area) { ico->width = image->bmp.width; ico->height = image->bmp.height; max_area = area; } } return BMP_OK; } /** * Allocates memory for the next BMP in an ICO collection * * Sets proper structure values * * \param ico the ICO collection to add the image to * \param image a pointer to the ICO image to be initialised */ static bmp_result next_ico_image(ico_collection *ico, ico_image *image) { bmp_create(&image->bmp, &ico->bitmap_callbacks); image->next = ico->first; ico->first = image; return BMP_OK; } static bmp_result bmp_analyse_header(bmp_image *bmp, uint8_t *data) { uint32_t header_size; uint32_t i; uint8_t j; int32_t width, height; uint8_t palette_size; unsigned int flags; /* a variety of different bitmap headers can follow, depending * on the BMP variant. A full description of the various headers * can be found at http://www.fileformat.info/format/bmp/ */ header_size = read_uint32(data, 0); if (bmp->buffer_size < (14 + header_size)) return BMP_INSUFFICIENT_DATA; if (header_size == 12) { /* the following header is for os/2 and windows 2.x and consists of: * * +0 UINT32 size of this header (in bytes) * +4 INT16 image width (in pixels) * +6 INT16 image height (in pixels) * +8 UINT16 number of colour planes (always 1) * +10 UINT16 number of bits per pixel */ width = read_int16(data, 4); height = read_int16(data, 6); if (width < 0) return BMP_DATA_ERROR; if (height < 0) { bmp->reversed = true; height = -height; } /* ICOs only support 256*256 resolutions * In the case of the ICO header, the height is actually the added * height of XOR-Bitmap and AND-Bitmap (double the visible height) * Technically we could remove this check and ICOs with bitmaps * of any size could be processed; this is to conform to the spec. */ if (bmp->ico) { if ((width > 256) || (height > 512)) { return BMP_DATA_ERROR; } else { bmp->width = width; bmp->height = height / 2; } } else { bmp->width = width; bmp->height = height; } if (read_uint16(data, 8) != 1) return BMP_DATA_ERROR; bmp->bpp = read_uint16(data, 10); bmp->colours = (1 << bmp->bpp); palette_size = 3; } else if (header_size < 40) { return BMP_DATA_ERROR; } else { /* the following header is for windows 3.x and onwards. it is a * minimum of 40 bytes and (as of Windows 95) a maximum of 108 bytes. * * +0 UINT32 size of this header (in bytes) * +4 INT32 image width (in pixels) * +8 INT32 image height (in pixels) * +12 UINT16 number of colour planes (always 1) * +14 UINT16 number of bits per pixel * +16 UINT32 compression methods used * +20 UINT32 size of bitmap (in bytes) * +24 UINT32 horizontal resolution (in pixels per meter) * +28 UINT32 vertical resolution (in pixels per meter) * +32 UINT32 number of colours in the image * +36 UINT32 number of important colours * +40 UINT32 mask identifying bits of red component * +44 UINT32 mask identifying bits of green component * +48 UINT32 mask identifying bits of blue component * +52 UINT32 mask identifying bits of alpha component * +56 UINT32 color space type * +60 UINT32 x coordinate of red endpoint * +64 UINT32 y coordinate of red endpoint * +68 UINT32 z coordinate of red endpoint * +72 UINT32 x coordinate of green endpoint * +76 UINT32 y coordinate of green endpoint * +80 UINT32 z coordinate of green endpoint * +84 UINT32 x coordinate of blue endpoint * +88 UINT32 y coordinate of blue endpoint * +92 UINT32 z coordinate of blue endpoint * +96 UINT32 gamma red coordinate scale value * +100 UINT32 gamma green coordinate scale value * +104 UINT32 gamma blue coordinate scale value */ width = read_int32(data, 4); height = read_int32(data, 8); if (width < 0) return BMP_DATA_ERROR; if (height < 0) { bmp->reversed = true; height = -height; } /* ICOs only support 256*256 resolutions * In the case of the ICO header, the height is actually the added * height of XOR-Bitmap and AND-Bitmap (double the visible height) * Technically we could remove this check and ICOs with bitmaps * of any size could be processed; this is to conform to the spec. */ if (bmp->ico) { if ((width > 256) || (height > 512)) { return BMP_DATA_ERROR; } else { bmp->width = width; bmp->height = height / 2; } } else { bmp->width = width; bmp->height = height; } if (read_uint16(data, 12) != 1) return BMP_DATA_ERROR; bmp->bpp = read_uint16(data, 14); if (bmp->bpp == 0) bmp->bpp = 8; bmp->encoding = read_uint32(data, 16); if (bmp->encoding > BMP_ENCODING_BITFIELDS) return BMP_DATA_ERROR; /* Bitfield encoding means we have red, green, blue, and alpha masks. * Here we aquire the masks and determine the required bit shift to * align them in our 24-bit color 8-bit alpha format. */ if (bmp->encoding == BMP_ENCODING_BITFIELDS) { if ((bmp->bpp != 16) && (bmp->bpp != 32)) return BMP_DATA_ERROR; if (header_size == 40) { header_size += 12; if (bmp->buffer_size < (14 + header_size)) return BMP_INSUFFICIENT_DATA; for (i = 0; i < 3; i++) bmp->mask[i] = read_uint32(data, 40 + (i << 2)); } else { for (i = 0; i < 4; i++) bmp->mask[i] = read_uint32(data, 40 + (i << 2)); } for (i = 0; i < 4; i++) { if (bmp->mask[i] == 0) break; for (j = 31; j > 0; j--) if (bmp->mask[i] & (1 << j)) { if ((j - 7) > 0) bmp->mask[i] &= 0xff << (j - 7); else bmp->mask[i] &= 0xff >> (-(j - 7)); bmp->shift[i] = (i << 3) - (j - 7); break; } } } bmp->colours = read_uint32(data, 32); if (bmp->colours == 0) bmp->colours = (1 << bmp->bpp); palette_size = 4; } data += header_size; /* if there's no alpha mask, flag the bmp opaque */ if ((!bmp->ico) && (bmp->mask[3] == 0)) { flags |= BMP_OPAQUE; bmp->opaque = true; } /* we only have a palette for <16bpp */ if (bmp->bpp < 16) { /* we now have a series of palette entries of the format: * * +0 BYTE blue * +1 BYTE green * +2 BYTE red * * if the palette is from an OS/2 or Win2.x file then the entries * are padded with an extra byte. */ if (bmp->buffer_size < (14 + header_size + (4 * bmp->colours))) return BMP_INSUFFICIENT_DATA; bmp->colour_table = (uint32_t *)malloc(bmp->colours * 4); if (!bmp->colour_table) return BMP_INSUFFICIENT_MEMORY; for (i = 0; i < bmp->colours; i++) { bmp->colour_table[i] = data[2] | (data[1] << 8) | (data[0] << 16); if (bmp->opaque) bmp->colour_table[i] |= (0xff << 24); data += palette_size; } } /* create our bitmap */ flags |= BMP_NEW | BMP_CLEAR_MEMORY; bmp->bitmap = bmp->bitmap_callbacks.bitmap_create(bmp->width, bmp->height, flags); if (!bmp->bitmap) { if (bmp->colour_table) free(bmp->colour_table); bmp->colour_table = NULL; return BMP_INSUFFICIENT_MEMORY; } /* BMPs within ICOs don't have BMP file headers, so the image data should * always be right after the colour table. */ if (bmp->ico) bmp->bitmap_offset = (intptr_t)data - (intptr_t)bmp->bmp_data; bmp->bitmap_callbacks.bitmap_set_suspendable(bmp->bitmap, bmp, bmp_invalidate); return BMP_OK; } /** * Finds the closest BMP within an ICO collection * * This function finds the BMP with dimensions as close to a specified set * as possible from the images in the collection. * * \param ico the ICO collection to examine * \param width the preferred width (0 to use ICO header width) * \param height the preferred height (0 to use ICO header height) */ bmp_image *ico_find(ico_collection *ico, uint16_t width, uint16_t height) { bmp_image *bmp = NULL; ico_image *image; int x, y, cur, distance = (1 << 24); if (width == 0) width = ico->width; if (height == 0) height = ico->height; for (image = ico->first; image; image = image->next) { if ((image->bmp.width == width) && (image->bmp.height == height)) return &image->bmp; x = image->bmp.width - width; y = image->bmp.height - height; cur = (x * x) + (y * y); if (cur < distance) { distance = cur; bmp = &image->bmp; } } return bmp; } /** * Invalidates a BMP * * This function sets the BMP into a state such that the bitmap image data * can be released from memory. * * \param bmp the BMP image to invalidate */ static void bmp_invalidate(void *bitmap, void *private_word) { UNUSED(bitmap); bmp_image *bmp = (bmp_image *)private_word; bmp->decoded = false; } /** * Decode a BMP * * This function decodes the BMP data such that bmp->bitmap is a valid * image. The state of bmp->decoded is set to TRUE on exit such that it * can easily be identified which BMPs are in a fully decoded state. * * \param bmp the BMP image to decode * \return BMP_OK on success */ bmp_result bmp_decode(bmp_image *bmp) { uint8_t *data; uint32_t bytes; bmp_result result = BMP_OK; assert(bmp->bitmap); data = bmp->bmp_data + bmp->bitmap_offset; bytes = bmp->buffer_size - bmp->bitmap_offset; switch (bmp->encoding) { case BMP_ENCODING_RGB: if (bmp->bpp >= 24) result = bmp_decode_rgb24(bmp, &data, bytes); else if (bmp->bpp > 8) result = bmp_decode_rgb16(bmp, &data, bytes); else result = bmp_decode_rgb(bmp, &data, bytes); break; case BMP_ENCODING_RLE8: result = bmp_decode_rle(bmp, data, bytes, 8); break; case BMP_ENCODING_RLE4: result = bmp_decode_rle(bmp, data, bytes, 4); break; case BMP_ENCODING_BITFIELDS: if (bmp->bpp == 32) result = bmp_decode_rgb24(bmp, &data, bytes); else if (bmp->bpp == 16) result = bmp_decode_rgb16(bmp, &data, bytes); else return BMP_DATA_ERROR; } if ((!bmp->ico) || (result != BMP_OK)) return result; bytes = (uintptr_t)bmp->bmp_data + bmp->buffer_size - (uintptr_t)data; return bmp_decode_mask(bmp, data, bytes); } /** * Decode a BMP using "limited transparency" * * Bitmaps do not have native transparency support. However, there is a * "trick" that is used in some instances in which the first pixel of the * bitmap becomes the "transparency index". The decoding application can * replace this index with whatever background colour it chooses to * create the illusion of transparency. * * When to use transparency is at the discretion of the decoding * application. * * \param bmp the BMP image to decode * \param colour the colour to use as "transparent" * \return BMP_OK on success */ bmp_result bmp_decode_trans(bmp_image *bmp, uint32_t colour) { bmp->limited_trans = true; bmp->trans_colour = colour; return bmp_decode(bmp); } /** * Decode BMP data stored in 24bpp colour. * * \param bmp the BMP image to decode * \param start the data to decode, updated to last byte read on success * \param bytes the number of bytes of data available * \return BMP_OK on success */ static bmp_result bmp_decode_rgb24(bmp_image *bmp, uint8_t **start, int bytes) { uint8_t *top, *bottom, *end, *data; uint32_t *scanline; uint32_t x, y; uint32_t swidth, skip; intptr_t addr; uint8_t i; uint32_t word; data = *start; swidth = bmp->bitmap_callbacks.bitmap_get_bpp(bmp->bitmap) * bmp->width; top = bmp->bitmap_callbacks.bitmap_get_buffer(bmp->bitmap); if (!top) return BMP_INSUFFICIENT_MEMORY; bottom = top + swidth * (bmp->height - 1); end = data + bytes; addr = ((intptr_t)data) & 3; skip = bmp->bpp >> 3; bmp->decoded = true; /* Determine transparent index */ if (bmp->limited_trans) { if (bmp->encoding == BMP_ENCODING_BITFIELDS) bmp->transparent_index = read_uint32(data, 0); else bmp->transparent_index = data[2] | (data[1] << 8) | (data[0] << 16) | (data[3] << 24); } for (y = 0; y < bmp->height; y++) { while (addr != (((intptr_t)data) & 3)) data++; if (bmp->reversed) scanline = (uint32_t *)(top + (y * swidth)); else scanline = (uint32_t *)(bottom - (y * swidth)); if (bmp->encoding == BMP_ENCODING_BITFIELDS) { for (x = 0; x < bmp->width; x++) { word = read_uint32(data, 0); for (i = 0; i < 4; i++) if (bmp->shift[i] > 0) scanline[x] |= ((word & bmp->mask[i]) << bmp->shift[i]); else scanline[x] |= ((word & bmp->mask[i]) >> (-bmp->shift[i])); /* 32-bit BMPs have alpha masks, but sometimes they're not utilized */ if (bmp->opaque) scanline[x] |= (0xff << 24); data += 4; } } else { for (x = 0; x < bmp->width; x++) { scanline[x] = data[2] | (data[1] << 8) | (data[0] << 16); if ((bmp->limited_trans) && (scanline[x] == bmp->transparent_index)) scanline[x] = bmp->trans_colour; if (bmp->opaque) scanline[x] |= (0xff << 24); data += skip; } } } *start = data; return BMP_OK; } /** * Decode BMP data stored in 16bpp colour. * * \param bmp the BMP image to decode * \param start the data to decode, updated to last byte read on success * \param bytes the number of bytes of data available * \return BMP_OK on success */ static bmp_result bmp_decode_rgb16(bmp_image *bmp, uint8_t **start, int bytes) { uint8_t *top, *bottom, *end, *data; uint32_t *scanline; uint32_t x, y, swidth; intptr_t addr; uint8_t i; uint16_t word; data = *start; swidth = bmp->bitmap_callbacks.bitmap_get_bpp(bmp->bitmap) * bmp->width; top = bmp->bitmap_callbacks.bitmap_get_buffer(bmp->bitmap); if (!top) return BMP_INSUFFICIENT_MEMORY; bottom = top + swidth * (bmp->height - 1); end = data + bytes; addr = ((intptr_t)data) & 3; bmp->decoded = true; /* Determine transparent index */ if (bmp->limited_trans) bmp->transparent_index = read_uint16(data, 0); for (y = 0; y < bmp->height; y++) { if (addr != (((intptr_t)data) & 3)) data += 2; if ((data + (2 * bmp->width)) > end) return BMP_INSUFFICIENT_DATA; if (bmp->reversed) scanline = (uint32_t *)(top + (y * swidth)); else scanline = (uint32_t *)(bottom - (y * swidth)); if (bmp->encoding == BMP_ENCODING_BITFIELDS) { for (x = 0; x < bmp->width; x++) { word = read_uint16(data, 0); if ((bmp->limited_trans) && (word == bmp->transparent_index)) scanline[x] = bmp->trans_colour; else { scanline[x] = 0; for (i = 0; i < 4; i++) if (bmp->shift[i] > 0) scanline[x] |= ((word & bmp->mask[i]) << bmp->shift[i]); else scanline[x] |= ((word & bmp->mask[i]) >> (-bmp->shift[i])); if (bmp->opaque) scanline[x] |= (0xff << 24); } data += 2; } } else { for (x = 0; x < bmp->width; x++) { word = read_uint16(data, 0); if ((bmp->limited_trans) && (word == bmp->transparent_index)) scanline[x] = bmp->trans_colour; else { /* 16-bit RGB defaults to RGB555 */ scanline[x] = ((word & (31 << 0)) << 19) | ((word & (31 << 5)) << 6) | ((word & (31 << 10)) >> 7); } if (bmp->opaque) scanline[x] |= (0xff << 24); data += 2; } } } *start = data; return BMP_OK; } /** * Decode BMP data stored with a palette and in 8bpp colour or less. * * \param bmp the BMP image to decode * \param start the data to decode, updated to last byte read on success * \param bytes the number of bytes of data available * \return BMP_OK on success */ static bmp_result bmp_decode_rgb(bmp_image *bmp, uint8_t **start, int bytes) { uint8_t *top, *bottom, *end, *data; uint32_t *scanline; intptr_t addr; uint32_t x, y, swidth; uint8_t bit_shifts[8]; uint8_t ppb = 8 / bmp->bpp; uint8_t bit_mask = (1 << bmp->bpp) - 1; uint8_t cur_byte = 0, bit, i; for (i = 0; i < ppb; i++) bit_shifts[i] = 8 - ((i + 1) * bmp->bpp); data = *start; swidth = bmp->bitmap_callbacks.bitmap_get_bpp(bmp->bitmap) * bmp->width; top = bmp->bitmap_callbacks.bitmap_get_buffer(bmp->bitmap); if (!top) return BMP_INSUFFICIENT_MEMORY; bottom = top + swidth * (bmp->height - 1); end = data + bytes; addr = ((intptr_t)data) & 3; bmp->decoded = true; /* Determine transparent index */ if (bmp->limited_trans) bmp->transparent_index = bmp->colour_table[(*data >> bit_shifts[0]) & bit_mask]; for (y = 0; y < bmp->height; y++) { while (addr != (((intptr_t)data) & 3)) data++; bit = 8; if ((data + (bmp->width / ppb)) > end) return BMP_INSUFFICIENT_DATA; if (bmp->reversed) scanline = (unsigned int *)(top + (y * swidth)); else scanline = (unsigned int *)(bottom - (y * swidth)); for (x = 0; x < bmp->width; x++) { if (bit >= ppb) { bit = 0; cur_byte = *data++; } scanline[x] = bmp->colour_table[(cur_byte >> bit_shifts[bit++]) & bit_mask]; if ((bmp->limited_trans) && (scanline[x] == bmp->transparent_index)) scanline[x] = bmp->trans_colour; } } *start = data; return BMP_OK; } /** * Decode a 1bpp mask for an ICO * * \param bmp the BMP image to decode * \param data the data to decode * \param bytes the number of bytes of data available * \return BMP_OK on success */ static bmp_result bmp_decode_mask(bmp_image *bmp, uint8_t *data, int bytes) { uint8_t *top, *bottom, *end; uint32_t *scanline; intptr_t addr; uint32_t x, y, swidth; uint32_t cur_byte = 0; swidth = bmp->bitmap_callbacks.bitmap_get_bpp(bmp->bitmap) * bmp->width; top = bmp->bitmap_callbacks.bitmap_get_buffer(bmp->bitmap); if (!top) return BMP_INSUFFICIENT_MEMORY; bottom = top + swidth * (bmp->height - 1); end = data + bytes; addr = ((intptr_t)data) & 3; for (y = 0; y < bmp->height; y++) { while (addr != (((intptr_t)data) & 3)) data++; if ((data + (bmp->width >> 3)) > end) return BMP_INSUFFICIENT_DATA; scanline = (uint32_t *)(bottom - (y * swidth)); for (x = 0; x < bmp->width; x++) { if ((x & 7) == 0) cur_byte = *data++; if ((cur_byte & 128) == 0) scanline[x] |= (0xff << 24); cur_byte = cur_byte << 1; } } return BMP_OK; } /** * Decode BMP data stored encoded in either RLE4 or RLE8. * * \param bmp the BMP image to decode * \param data the data to decode * \param bytes the number of bytes of data available * \param size the size of the RLE tokens (4 or 8) * \return BMP_OK on success */ static bmp_result bmp_decode_rle(bmp_image *bmp, uint8_t *data, int bytes, int size) { uint8_t *top, *bottom, *end; uint32_t *scanline; uint32_t swidth; uint32_t i, length, pixels_left; uint32_t x = 0, y = 0, last_y = 0; uint32_t pixel = 0, pixel2; if (bmp->ico) return BMP_DATA_ERROR; swidth = bmp->bitmap_callbacks.bitmap_get_bpp(bmp->bitmap) * bmp->width; top = bmp->bitmap_callbacks.bitmap_get_buffer(bmp->bitmap); if (!top) return BMP_INSUFFICIENT_MEMORY; bottom = top + swidth * (bmp->height - 1); end = data + bytes; bmp->decoded = true; do { if (data + 2 > end) return BMP_INSUFFICIENT_DATA; length = *data++; if (length == 0) { length = *data++; if (length == 0) { /* 00 - 00 means end of scanline */ x = 0; if (last_y == y) { if (++y > bmp->height) return BMP_DATA_ERROR; } last_y = y; } else if (length == 1) { /* 00 - 01 means end of RLE data */ return BMP_OK; } else if (length == 2) { /* 00 - 02 - XX - YY means move cursor */ if (data + 2 > end) return BMP_INSUFFICIENT_DATA; x += *data++; if (x >= bmp->width) return BMP_DATA_ERROR; y += *data++; if (y >= bmp->height) return BMP_DATA_ERROR; } else { /* 00 - NN means escape NN pixels */ if (bmp->reversed) { pixels_left = (y + 1) * bmp->width - x; scanline = (unsigned int *)(top + (y * swidth)); } else { pixels_left = (bmp->height - y + 1) * bmp->width - x; scanline = (unsigned int *)(bottom - (y * swidth)); } if (length > pixels_left) length = pixels_left; if (data + length > end) return BMP_INSUFFICIENT_DATA; /* the following code could be easily optimised by simply * checking the bounds on entry and using some simply copying * routines if so */ if (size == 8) { for (i = 0; i < length; i++) { if (x >= bmp->width) { x = 0; if (++y > bmp->height) return BMP_DATA_ERROR; scanline -= bmp->width; } scanline[x++] = bmp->colour_table[(int)*data++]; } } else { for (i = 0; i < length; i++) { if (x >= bmp->width) { x = 0; if (++y > bmp->height) return BMP_DATA_ERROR; scanline -= bmp->width; } if ((i & 1) == 0) { pixel = *data++; scanline[x++] = bmp->colour_table [pixel >> 4]; } else { scanline[x++] = bmp->colour_table [pixel & 0xf]; } } length = (length + 1) >> 1; } if ((length & 1) && (*data++ != 0x00)) return BMP_DATA_ERROR; } } else { /* NN means perform RLE for NN pixels */ if (bmp->reversed) { pixels_left = (y + 1) * bmp->width - x; scanline = (unsigned int *)(top + (y * swidth)); } else { pixels_left = (bmp->height - y + 1) * bmp->width - x; scanline = (unsigned int *)(bottom - (y * swidth)); } if (length > pixels_left) length = pixels_left; /* the following code could be easily optimised by simply * checking the bounds on entry and using some simply copying * routines if so */ if (size == 8) { pixel = bmp->colour_table[(int)*data++]; for (i = 0; i < length; i++) { if (x >= bmp->width) { x = 0; if (++y > bmp->height) return BMP_DATA_ERROR; scanline -= bmp->width; } scanline[x++] = pixel; } } else { pixel2 = *data++; pixel = bmp->colour_table[pixel2 >> 4]; pixel2 = bmp->colour_table[pixel2 & 0xf]; for (i = 0; i < length; i++) { if (x >= bmp->width) { x = 0; if (++y > bmp->height) return BMP_DATA_ERROR; scanline -= bmp->width; } if ((i & 1) == 0) scanline[x++] = pixel; else scanline[x++] = pixel2; } } } } while (data < end); return BMP_OK; } /** * Finalise a BMP prior to destruction. * * \param bmp the BMP image to finalise */ void bmp_finalise(bmp_image *bmp) { if (bmp->bitmap) bmp->bitmap_callbacks.bitmap_destroy(bmp->bitmap); bmp->bitmap = NULL; if (bmp->colour_table) free(bmp->colour_table); bmp->colour_table = NULL; } /** * Finalise an ICO prior to destruction. * * \param ico the ICO image to finalise */ void ico_finalise(ico_collection *ico) { ico_image *image; for (image = ico->first; image; image = image->next) bmp_finalise(&image->bmp); while (ico->first) { image = ico->first; ico->first = image->next; free(image); } }