/* * This file is part of NetSurf, http://netsurf-browser.org/ * Licensed under the GNU General Public License, * http://www.opensource.org/licenses/gpl-license * Copyright 2006 Richard Wilson */ #include #include #include #include #include #include "image/bmpread.h" #include "image/bitmap.h" #include "utils/log.h" #define READ_SHORT(a, o) (a[o]|(a[o+1]<<8)) #define READ_INT(a, o) (a[o]|(a[o+1]<<8)|(a[o+2]<<16)|(a[o+3]<<24)) bmp_result bmp_analyse_header(struct bmp_image *bmp, char *data); bmp_result bmp_decode_rgb24(struct bmp_image *bmp, char **start, int bytes); bmp_result bmp_decode_rgb16(struct bmp_image *bmp, char **start, int bytes); bmp_result bmp_decode_rgb(struct bmp_image *bmp, char **start, int bytes); bmp_result bmp_decode_mask(struct bmp_image *bmp, char *data, int bytes); bmp_result bmp_decode_rle(struct bmp_image *bmp, char *data, int bytes, int size); void bmp_invalidate(struct bitmap *bitmap, void *private_word); /** * 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(struct bmp_image *bmp) { char *data = bmp->bmp_data; /* ensure we aren't already initialised */ if (bmp->bitmap) return BMP_OK; /* standard 14-byte BMP file header is: * * +0 SHORT 'BM' * +2 INT size of file (in bytes) * +6 SHORT reserved field (1) * +8 SHORT reserved field (2) * +10 INT starting position of image data (in bytes) */ if (bmp->buffer_size < 14) return BMP_INSUFFICIENT_DATA; if ((data[0] != 'B') || (data[1] != 'M')) return BMP_DATA_ERROR; bmp->bitmap_offset = READ_INT(data, 10); /* decode the BMP header */ return bmp_analyse_header(bmp, data + 14); } /** * 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(struct ico_collection *ico) { char *data = ico->ico_data; unsigned int count, i; bmp_result result; struct ico_image *image; int area, max_area = 0; /* ensure we aren't already initialised */ if (ico->first) return BMP_OK; /* standard 6-byte ICO file header is: * * +0 INT 0x00010000 * +4 SHORT number of BMPs to follow */ if (ico->buffer_size < 6) return BMP_INSUFFICIENT_DATA; if (READ_INT(data, 0) != 0x00010000) return BMP_DATA_ERROR; count = READ_SHORT(data, 4); if (count == 0) return BMP_DATA_ERROR; data += 6; /* decode the BMP files */ if (ico->buffer_size < 6 + (16 * count)) return BMP_INSUFFICIENT_DATA; for (i = 0; i < count; i++) { image = calloc(1, sizeof(struct ico_image)); if (!image) return BMP_INSUFFICIENT_MEMORY; image->next = ico->first; ico->first = image; image->bmp.width = data[0]; image->bmp.height = data[1]; image->bmp.buffer_size = READ_INT(data, 8) + 40; image->bmp.bmp_data = ico->ico_data + READ_INT(data, 12); image->bmp.ico = true; data += 16; result = bmp_analyse_header(&image->bmp, image->bmp.bmp_data); if (result != BMP_OK) return result; 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; } bmp_result bmp_analyse_header(struct bmp_image *bmp, char *data) { unsigned int header_size; unsigned int i; int width, height, j; int 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_INT(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 INT size of this header (in bytes) * +4 SHORT image width (in pixels) * +6 SHORT image height (in pixels) * +8 SHORT number of color planes (always 1) * +10 SHORT number of bits per pixel */ width = READ_SHORT(data, 4); height = READ_SHORT(data, 6); if (width < 0) return BMP_DATA_ERROR; if (height < 0) { bmp->reversed = true; height = -height; } bmp->width = width; bmp->height = height; if (READ_SHORT(data, 8) != 1) return BMP_DATA_ERROR; bmp->bpp = READ_SHORT(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 INT size of this header (in bytes) * +4 INT image width (in pixels) * +8 INT image height (in pixels)   * +12 SHORT number of color planes (always 1) * +14 SHORT number of bits per pixel * +16 INT compression methods used * +20 INT size of bitmap (in bytes) * +24 INT horizontal resolution (in pixels per meter) * +28 INT vertical resolution (in pixels per meter) * +32 INT number of colors in the image * +36 INT number of important colors * +40 INT mask identifying bits of red component * +44 INT mask identifying bits of green component * +48 INT mask identifying bits of blue component * +52 INT mask identifying bits of alpha component * +56 INT color space type * +60 INT x coordinate of red endpoint * +64 INT y coordinate of red endpoint * +68 INT z coordinate of red endpoint * +72 INT x coordinate of green endpoint * +76 INT y coordinate of green endpoint * +80 INT z coordinate of green endpoint * +84 INT x coordinate of blue endpoint * +88 INT y coordinate of blue endpoint * +92 INT z coordinate of blue endpoint * +96 INT gamma red coordinate scale value * +100 INT gamma green coordinate scale value * +104 INT gamma blue coordinate scale value */ if (!bmp->ico) { width = READ_INT(data, 4); height = READ_INT(data, 8); if (width < 0) return BMP_DATA_ERROR; if (height < 0) { bmp->reversed = true; height = -height; } bmp->width = width; bmp->height = height; } if (READ_SHORT(data, 12) != 1) return BMP_DATA_ERROR; bmp->bpp = READ_SHORT(data, 14); if (bmp->bpp == 0) bmp->bpp = 8; bmp->encoding = READ_INT(data, 16); if (bmp->encoding > BMP_ENCODING_BITFIELDS) return BMP_DATA_ERROR; 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_INT(data, 40 + (i << 2)); } else { for (i = 0; i < 4; i++) bmp->mask[i] = READ_INT(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_INT(data, 32); if (bmp->colours == 0) bmp->colours = (1 << bmp->bpp); palette_size = 4; } data += header_size; /* 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 = (unsigned int *) malloc(bmp->colours * sizeof(int)); 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); data += palette_size; } } /* create our bitmap */ flags = BITMAP_NEW | BITMAP_CLEAR_MEMORY; if ((!bmp->ico) && (bmp->mask[3] == 0)) flags |= BITMAP_OPAQUE; bmp->bitmap = 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; } bmp->bitmap_offset = (int)data - (int)bmp->bmp_data; 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 * \param height the preferred height */ struct bmp_image *ico_find(struct ico_collection *ico, int width, int height) { struct bmp_image *bmp = NULL; struct ico_image *image; int x, y, cur, distance = (1 << 24); for (image = ico->first; image; image = image->next) { if (((int)image->bmp.width == width) && ((int)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 */ void bmp_invalidate(struct bitmap *bitmap, void *private_word) { struct bmp_image *bmp = (struct 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(struct bmp_image *bmp) { char *data; int 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 = (int)bmp->bmp_data + bmp->buffer_size - (int)data; return bmp_decode_mask(bmp, data, bytes); } /** * 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 */ bmp_result bmp_decode_rgb24(struct bmp_image *bmp, char **start, int bytes) { char *top, *bottom, *end, *data; unsigned int *scanline; unsigned int x, y, swidth, skip; unsigned int addr; unsigned int i, word; data = *start; swidth = bitmap_get_rowstride(bmp->bitmap); top = bitmap_get_buffer(bmp->bitmap); bottom = top + swidth * (bmp->height - 1); end = data + bytes; addr = ((unsigned int)data) & 3; skip = bmp->bpp >> 3; bmp->decoded = true; for (y = 0; y < bmp->height; y++) { while (addr != (((unsigned int)data) & 3)) data++; if ((data + (skip * bmp->width)) > end) return BMP_INSUFFICIENT_DATA; if (bmp->reversed) scanline = (unsigned int *)(top + (y * swidth)); else scanline = (unsigned int *)(bottom - (y * swidth)); if (bmp->encoding == BMP_ENCODING_BITFIELDS) { for (x = 0; x < bmp->width; x++) { word = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); scanline[x] = (0xff << 24); 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])); data += 4; } } else { for (x = 0; x < bmp->width; x++) { scanline[x] = data[2] | (data[1] << 8) | (data[0] << 16) | (data[3] << 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 */ bmp_result bmp_decode_rgb16(struct bmp_image *bmp, char **start, int bytes) { char *top, *bottom, *end, *data; unsigned int *scanline; unsigned int x, y, swidth; unsigned int addr; unsigned int word, i; data = *start; swidth = bitmap_get_rowstride(bmp->bitmap); top = bitmap_get_buffer(bmp->bitmap); bottom = top + swidth * (bmp->height - 1); end = data + bytes; addr = ((unsigned int)data) & 3; bmp->decoded = true; for (y = 0; y < bmp->height; y++) { if (addr != (((unsigned int)data) & 3)) data += 2; if ((data + (2 * bmp->width)) > end) return BMP_INSUFFICIENT_DATA; if (bmp->reversed) scanline = (unsigned int *)(top + (y * swidth)); else scanline = (unsigned int *)(bottom - (y * swidth)); if (bmp->encoding == BMP_ENCODING_BITFIELDS) { for (x = 0; x < bmp->width; x++) { word = data[0] | (data[1] << 8); scanline[x] = (0xff << 24); 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])); data += 2; } } else { for (x = 0; x < bmp->width; x++) { word = data[0] | (data[1] << 8); scanline[x] = ((word & (31 << 0)) << 19) | ((word & (31 << 5)) << 6) | ((word & (31 << 10)) >> 7); 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 */ bmp_result bmp_decode_rgb(struct bmp_image *bmp, char **start, int bytes) { char *top, *bottom, *end, *data; unsigned int *scanline; unsigned int addr; unsigned int x, y, swidth; int i; int bit_shifts[8]; int ppb = 8 / bmp->bpp; int bit_mask = (1 << bmp->bpp) - 1; int cur_byte = 0, bit; for (i = 0; i < ppb; i++) bit_shifts[i] = 8 - ((i + 1) * bmp->bpp); data = *start; swidth = bitmap_get_rowstride(bmp->bitmap); top = bitmap_get_buffer(bmp->bitmap); bottom = top + swidth * (bmp->height - 1); end = data + bytes; addr = ((unsigned int)data) & 3; bmp->decoded = true; for (y = 0; y < bmp->height; y++) { while (addr != (((unsigned int)data) & 3)) data++; bit = 32; 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]; } } *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 */ bmp_result bmp_decode_mask(struct bmp_image *bmp, char *data, int bytes) { char *top, *bottom, *end; unsigned int *scanline; unsigned int addr; unsigned int x, y, swidth; int cur_byte = 0; swidth = bitmap_get_rowstride(bmp->bitmap); top = bitmap_get_buffer(bmp->bitmap); bottom = top + swidth * (bmp->height - 1); end = data + bytes; addr = ((unsigned int)data) & 3; for (y = 0; y < bmp->height; y++) { while (addr != (((unsigned int)data) & 3)) data++; if ((data + (bmp->width >> 3)) > end) return BMP_INSUFFICIENT_DATA; scanline = (unsigned int *)(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 */ bmp_result bmp_decode_rle(struct bmp_image *bmp, char *data, int bytes, int size) { char *top, *bottom, *end; unsigned int *scanline; unsigned int swidth; int i, length, pixels_left; unsigned int x = 0, y = 0, last_y = 0; unsigned int pixel = 0, pixel2; if (bmp->ico) return BMP_DATA_ERROR; swidth = bitmap_get_rowstride(bmp->bitmap); top = bitmap_get_buffer(bmp->bitmap); 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(struct bmp_image *bmp) { if (bmp->bitmap) 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(struct ico_collection *ico) { struct 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); } }