From 5dd3d3dadfa8b927d148dff8d9567a6056db5404 Mon Sep 17 00:00:00 2001 From: "Andrii Doroshenko (Xrayez)" Date: Sun, 21 Apr 2019 15:49:49 +0300 Subject: [PATCH] Import 4/1 bit bmp images Add some sanity checks according to bmp specification. Read color table and index data within the same scope and then simply extend the color palette. This particular implementation has one limitation: not all 4/1 bit images can be imported as it requires bit unpacking (size dimensions must be a multiple of 8 for 1-bit and 2 (even) for 4-bit images). (cherry picked from commit 6484da572117eaee88567f7b167ab261e28e7873) --- modules/bmp/image_loader_bmp.cpp | 172 ++++++++++++++++++++++++------- modules/bmp/image_loader_bmp.h | 4 + 2 files changed, 138 insertions(+), 38 deletions(-) diff --git a/modules/bmp/image_loader_bmp.cpp b/modules/bmp/image_loader_bmp.cpp index 509b30c7e61..bcc992db241 100644 --- a/modules/bmp/image_loader_bmp.cpp +++ b/modules/bmp/image_loader_bmp.cpp @@ -33,6 +33,7 @@ Error ImageLoaderBMP::convert_to_image(Ref p_image, const uint8_t *p_buffer, const uint8_t *p_color_buffer, + const uint32_t color_table_size, const bmp_header_s &p_header) { Error err = OK; @@ -49,42 +50,82 @@ Error ImageLoaderBMP::convert_to_image(Ref p_image, if (p_header.bmp_info_header.bmp_compression != BI_RGB) { err = FAILED; } + // Check whether we can load it - if (!(bits_per_pixel == 8 || bits_per_pixel == 24 || bits_per_pixel == 32)) { - err = FAILED; + if (bits_per_pixel == 1) { + // Requires bit unpacking... + ERR_FAIL_COND_V(width % 8 != 0, ERR_UNAVAILABLE); + ERR_FAIL_COND_V(height % 8 != 0, ERR_UNAVAILABLE); + + } else if (bits_per_pixel == 4) { + // Requires bit unpacking... + ERR_FAIL_COND_V(width % 2 != 0, ERR_UNAVAILABLE); + ERR_FAIL_COND_V(height % 2 != 0, ERR_UNAVAILABLE); + + } else if (bits_per_pixel == 16) { + + ERR_FAIL_V(ERR_UNAVAILABLE); } - if (err == OK) { - uint32_t line_width = ((p_header.bmp_info_header.bmp_width * - p_header.bmp_info_header.bmp_bit_count / 8) + - 3) & - ~3; + // Image data (might be indexed) + PoolVector data; + int data_len = 0; - PoolVector image_data; - err = image_data.resize(width * height * 4); + if (bits_per_pixel <= 8) { // indexed + data_len = width * height; + } else { // color + data_len = width * height * 4; + } + ERR_FAIL_COND_V(data_len == 0, ERR_BUG); + err = data.resize(data_len); - PoolVector::Write image_data_w = image_data.write(); - uint8_t *write_buffer = image_data_w.ptr(); + PoolVector::Write data_w = data.write(); + uint8_t *write_buffer = data_w.ptr(); - const uint32_t color_index_max = p_header.bmp_info_header.bmp_colors_used - 1; + const uint32_t width_bytes = width * bits_per_pixel / 8; + const uint32_t line_width = (width_bytes + 3) & ~3; + + // The actual data traversal is determined by + // the data width in case of 8/4/1 bit images + const uint32_t w = bits_per_pixel >= 24 ? width : width_bytes; const uint8_t *line = p_buffer + (line_width * (height - 1)); + for (unsigned int i = 0; i < height; i++) { const uint8_t *line_ptr = line; - for (unsigned int j = 0; j < width; j++) { + + for (unsigned int j = 0; j < w; j++) { switch (bits_per_pixel) { + case 1: { + uint8_t color_index = *line_ptr; + + write_buffer[index + 0] = (color_index >> 7) & 1; + write_buffer[index + 1] = (color_index >> 6) & 1; + write_buffer[index + 2] = (color_index >> 5) & 1; + write_buffer[index + 3] = (color_index >> 4) & 1; + write_buffer[index + 4] = (color_index >> 3) & 1; + write_buffer[index + 5] = (color_index >> 2) & 1; + write_buffer[index + 6] = (color_index >> 1) & 1; + write_buffer[index + 7] = (color_index >> 0) & 1; + + index += 8; + line_ptr += 1; + } break; + case 4: { + uint8_t color_index = *line_ptr; + + write_buffer[index + 0] = (color_index >> 4) & 0x0f; + write_buffer[index + 1] = color_index & 0x0f; + + index += 2; + line_ptr += 1; + } break; case 8: { - uint8_t color_index = CLAMP(*line_ptr, 0, color_index_max); - uint32_t color = 0x000000; + uint8_t color_index = *line_ptr; - if (p_color_buffer != NULL) - color = ((uint32_t *)p_color_buffer)[color_index]; + write_buffer[index] = color_index; - write_buffer[index + 2] = color & 0xff; - write_buffer[index + 1] = (color >> 8) & 0xff; - write_buffer[index + 0] = (color >> 16) & 0xff; - write_buffer[index + 3] = 0xff; - index += 4; + index += 1; line_ptr += 1; } break; case 24: { @@ -94,6 +135,7 @@ Error ImageLoaderBMP::convert_to_image(Ref p_image, write_buffer[index + 1] = (color >> 8) & 0xff; write_buffer[index + 0] = (color >> 16) & 0xff; write_buffer[index + 3] = 0xff; + index += 4; line_ptr += 3; } break; @@ -104,6 +146,7 @@ Error ImageLoaderBMP::convert_to_image(Ref p_image, write_buffer[index + 1] = (color >> 8) & 0xff; write_buffer[index + 0] = (color >> 16) & 0xff; write_buffer[index + 3] = color >> 24; + index += 4; line_ptr += 4; } break; @@ -111,7 +154,51 @@ Error ImageLoaderBMP::convert_to_image(Ref p_image, } line -= line_width; } - p_image->create(width, height, 0, Image::FORMAT_RGBA8, image_data); + + if (p_color_buffer == NULL || color_table_size == 0) { // regular pixels + + p_image->create(width, height, 0, Image::FORMAT_RGBA8, data); + + } else { // data is in indexed format, extend it + + // Palette data + PoolVector palette_data; + palette_data.resize(color_table_size * 4); + + PoolVector::Write palette_data_w = palette_data.write(); + uint8_t *pal = palette_data_w.ptr(); + + const uint8_t *cb = p_color_buffer; + + for (unsigned int i = 0; i < color_table_size; ++i) { + uint32_t color = *((uint32_t *)cb); + + pal[i * 4 + 0] = (color >> 16) & 0xff; + pal[i * 4 + 1] = (color >> 8) & 0xff; + pal[i * 4 + 2] = (color)&0xff; + pal[i * 4 + 3] = 0xff; + + cb += 4; + } + // Extend palette to image + PoolVector extended_data; + extended_data.resize(data.size() * 4); + + PoolVector::Write ex_w = extended_data.write(); + uint8_t *dest = ex_w.ptr(); + + const int num_pixels = width * height; + + for (int i = 0; i < num_pixels; i++) { + dest[0] = pal[write_buffer[i] * 4 + 0]; + dest[1] = pal[write_buffer[i] * 4 + 1]; + dest[2] = pal[write_buffer[i] * 4 + 2]; + dest[3] = pal[write_buffer[i] * 4 + 3]; + + dest += 4; + } + p_image->create(width, height, 0, Image::FORMAT_RGBA8, extended_data); + } } } return err; @@ -123,12 +210,9 @@ Error ImageLoaderBMP::load_image(Ref p_image, FileAccess *f, bmp_header_s bmp_header; Error err = ERR_INVALID_DATA; - static const size_t FILE_HEADER_SIZE = 14; - static const size_t INFO_HEADER_SIZE = 40; - // A valid bmp file should always at least have a // file header and a minimal info header - if (f->get_len() > FILE_HEADER_SIZE + INFO_HEADER_SIZE) { + if (f->get_len() > BITMAP_FILE_HEADER_SIZE + BITMAP_INFO_HEADER_MIN_SIZE) { // File Header bmp_header.bmp_file_header.bmp_signature = f->get_16(); if (bmp_header.bmp_file_header.bmp_signature == BITMAP_SIGNATURE) { @@ -138,9 +222,14 @@ Error ImageLoaderBMP::load_image(Ref p_image, FileAccess *f, // Info Header bmp_header.bmp_info_header.bmp_header_size = f->get_32(); + ERR_FAIL_COND_V(bmp_header.bmp_info_header.bmp_header_size < BITMAP_INFO_HEADER_MIN_SIZE, ERR_FILE_CORRUPT); + bmp_header.bmp_info_header.bmp_width = f->get_32(); bmp_header.bmp_info_header.bmp_height = f->get_32(); + bmp_header.bmp_info_header.bmp_planes = f->get_16(); + ERR_FAIL_COND_V(bmp_header.bmp_info_header.bmp_planes != 1, ERR_FILE_CORRUPT); + bmp_header.bmp_info_header.bmp_bit_count = f->get_16(); bmp_header.bmp_info_header.bmp_compression = f->get_32(); bmp_header.bmp_info_header.bmp_size_image = f->get_32(); @@ -153,23 +242,30 @@ Error ImageLoaderBMP::load_image(Ref p_image, FileAccess *f, if (bmp_header.bmp_info_header.bmp_compression != BI_RGB) { ERR_EXPLAIN("Unsupported bmp file: " + f->get_path()); f->close(); - ERR_FAIL_V(err) + ERR_FAIL_V(ERR_UNAVAILABLE); } + // Don't rely on sizeof(bmp_file_header) as structure padding + // adds 2 bytes offset leading to misaligned color table reading + uint32_t ct_offset = BITMAP_FILE_HEADER_SIZE + + bmp_header.bmp_info_header.bmp_header_size; + f->seek(ct_offset); - f->seek(FILE_HEADER_SIZE + - bmp_header.bmp_info_header.bmp_header_size); + uint32_t color_table_size = 0; - if (bmp_header.bmp_info_header.bmp_bit_count < 16 && bmp_header.bmp_info_header.bmp_colors_used == 0) - bmp_header.bmp_info_header.bmp_colors_used = 1 << bmp_header.bmp_info_header.bmp_bit_count; - - // Color table is usually 4 bytes per color -> [B][G][R][0] - uint32_t color_table_size = bmp_header.bmp_info_header.bmp_colors_used * 4; + // bmp_colors_used may report 0 despite having a color table + // for 4 and 1 bit images, so don't rely on this information + if (bmp_header.bmp_info_header.bmp_bit_count <= 8) { + // Support 256 colors max + color_table_size = 1 << bmp_header.bmp_info_header.bmp_bit_count; + } + ERR_FAIL_COND_V(color_table_size == 0, ERR_BUG); PoolVector bmp_color_table; if (color_table_size > 0) { - err = bmp_color_table.resize(color_table_size); + // Color table is usually 4 bytes per color -> [B][G][R][0] + err = bmp_color_table.resize(color_table_size * 4); PoolVector::Write bmp_color_table_w = bmp_color_table.write(); - f->get_buffer(bmp_color_table_w.ptr(), color_table_size); + f->get_buffer(bmp_color_table_w.ptr(), color_table_size * 4); } f->seek(bmp_header.bmp_file_header.bmp_file_offset); @@ -186,7 +282,7 @@ Error ImageLoaderBMP::load_image(Ref p_image, FileAccess *f, PoolVector::Read bmp_buffer_r = bmp_buffer.read(); PoolVector::Read bmp_color_table_r = bmp_color_table.read(); err = convert_to_image(p_image, bmp_buffer_r.ptr(), - bmp_color_table_r.ptr(), bmp_header); + bmp_color_table_r.ptr(), color_table_size, bmp_header); } f->close(); } diff --git a/modules/bmp/image_loader_bmp.h b/modules/bmp/image_loader_bmp.h index b27a47d4026..0082cf778a8 100644 --- a/modules/bmp/image_loader_bmp.h +++ b/modules/bmp/image_loader_bmp.h @@ -37,6 +37,9 @@ class ImageLoaderBMP : public ImageFormatLoader { protected: static const unsigned BITMAP_SIGNATURE = 0x4d42; + static const unsigned BITMAP_FILE_HEADER_SIZE = 14; // bmp_file_header_s + static const unsigned BITMAP_INFO_HEADER_MIN_SIZE = 40; // bmp_info_header_s + enum bmp_compression_s { BI_RGB = 0x00, BI_RLE8 = 0x01, @@ -76,6 +79,7 @@ protected: static Error convert_to_image(Ref p_image, const uint8_t *p_buffer, const uint8_t *p_color_buffer, + const uint32_t color_table_size, const bmp_header_s &p_header); public: