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 6484da5721)
This commit is contained in:
Andrii Doroshenko (Xrayez) 2019-04-21 15:49:49 +03:00 committed by Rémi Verschelde
parent c6ee986f53
commit 5dd3d3dadf
2 changed files with 138 additions and 38 deletions

View File

@ -33,6 +33,7 @@
Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image, Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image,
const uint8_t *p_buffer, const uint8_t *p_buffer,
const uint8_t *p_color_buffer, const uint8_t *p_color_buffer,
const uint32_t color_table_size,
const bmp_header_s &p_header) { const bmp_header_s &p_header) {
Error err = OK; Error err = OK;
@ -49,42 +50,82 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image,
if (p_header.bmp_info_header.bmp_compression != BI_RGB) { if (p_header.bmp_info_header.bmp_compression != BI_RGB) {
err = FAILED; err = FAILED;
} }
// Check whether we can load it
if (!(bits_per_pixel == 8 || bits_per_pixel == 24 || bits_per_pixel == 32)) { if (bits_per_pixel == 1) {
err = FAILED; // 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) { if (err == OK) {
uint32_t line_width = ((p_header.bmp_info_header.bmp_width * // Image data (might be indexed)
p_header.bmp_info_header.bmp_bit_count / 8) + PoolVector<uint8_t> data;
3) & int data_len = 0;
~3;
PoolVector<uint8_t> image_data; if (bits_per_pixel <= 8) { // indexed
err = image_data.resize(width * height * 4); 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<uint8_t>::Write image_data_w = image_data.write(); PoolVector<uint8_t>::Write data_w = data.write();
uint8_t *write_buffer = image_data_w.ptr(); 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)); const uint8_t *line = p_buffer + (line_width * (height - 1));
for (unsigned int i = 0; i < height; i++) { for (unsigned int i = 0; i < height; i++) {
const uint8_t *line_ptr = line; 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) { 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: { case 8: {
uint8_t color_index = CLAMP(*line_ptr, 0, color_index_max); uint8_t color_index = *line_ptr;
uint32_t color = 0x000000;
if (p_color_buffer != NULL) write_buffer[index] = color_index;
color = ((uint32_t *)p_color_buffer)[color_index];
write_buffer[index + 2] = color & 0xff; index += 1;
write_buffer[index + 1] = (color >> 8) & 0xff;
write_buffer[index + 0] = (color >> 16) & 0xff;
write_buffer[index + 3] = 0xff;
index += 4;
line_ptr += 1; line_ptr += 1;
} break; } break;
case 24: { case 24: {
@ -94,6 +135,7 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image,
write_buffer[index + 1] = (color >> 8) & 0xff; write_buffer[index + 1] = (color >> 8) & 0xff;
write_buffer[index + 0] = (color >> 16) & 0xff; write_buffer[index + 0] = (color >> 16) & 0xff;
write_buffer[index + 3] = 0xff; write_buffer[index + 3] = 0xff;
index += 4; index += 4;
line_ptr += 3; line_ptr += 3;
} break; } break;
@ -104,6 +146,7 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image,
write_buffer[index + 1] = (color >> 8) & 0xff; write_buffer[index + 1] = (color >> 8) & 0xff;
write_buffer[index + 0] = (color >> 16) & 0xff; write_buffer[index + 0] = (color >> 16) & 0xff;
write_buffer[index + 3] = color >> 24; write_buffer[index + 3] = color >> 24;
index += 4; index += 4;
line_ptr += 4; line_ptr += 4;
} break; } break;
@ -111,7 +154,51 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image,
} }
line -= line_width; 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<uint8_t> palette_data;
palette_data.resize(color_table_size * 4);
PoolVector<uint8_t>::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<uint8_t> extended_data;
extended_data.resize(data.size() * 4);
PoolVector<uint8_t>::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; return err;
@ -123,12 +210,9 @@ Error ImageLoaderBMP::load_image(Ref<Image> p_image, FileAccess *f,
bmp_header_s bmp_header; bmp_header_s bmp_header;
Error err = ERR_INVALID_DATA; 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 // A valid bmp file should always at least have a
// file header and a minimal info header // 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 // File Header
bmp_header.bmp_file_header.bmp_signature = f->get_16(); bmp_header.bmp_file_header.bmp_signature = f->get_16();
if (bmp_header.bmp_file_header.bmp_signature == BITMAP_SIGNATURE) { if (bmp_header.bmp_file_header.bmp_signature == BITMAP_SIGNATURE) {
@ -138,9 +222,14 @@ Error ImageLoaderBMP::load_image(Ref<Image> p_image, FileAccess *f,
// Info Header // Info Header
bmp_header.bmp_info_header.bmp_header_size = f->get_32(); 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_width = f->get_32();
bmp_header.bmp_info_header.bmp_height = f->get_32(); bmp_header.bmp_info_header.bmp_height = f->get_32();
bmp_header.bmp_info_header.bmp_planes = f->get_16(); 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_bit_count = f->get_16();
bmp_header.bmp_info_header.bmp_compression = f->get_32(); bmp_header.bmp_info_header.bmp_compression = f->get_32();
bmp_header.bmp_info_header.bmp_size_image = f->get_32(); bmp_header.bmp_info_header.bmp_size_image = f->get_32();
@ -153,23 +242,30 @@ Error ImageLoaderBMP::load_image(Ref<Image> p_image, FileAccess *f,
if (bmp_header.bmp_info_header.bmp_compression != BI_RGB) { if (bmp_header.bmp_info_header.bmp_compression != BI_RGB) {
ERR_EXPLAIN("Unsupported bmp file: " + f->get_path()); ERR_EXPLAIN("Unsupported bmp file: " + f->get_path());
f->close(); 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 + uint32_t color_table_size = 0;
bmp_header.bmp_info_header.bmp_header_size);
if (bmp_header.bmp_info_header.bmp_bit_count < 16 && bmp_header.bmp_info_header.bmp_colors_used == 0) // bmp_colors_used may report 0 despite having a color table
bmp_header.bmp_info_header.bmp_colors_used = 1 << bmp_header.bmp_info_header.bmp_bit_count; // for 4 and 1 bit images, so don't rely on this information
if (bmp_header.bmp_info_header.bmp_bit_count <= 8) {
// Color table is usually 4 bytes per color -> [B][G][R][0] // Support 256 colors max
uint32_t color_table_size = bmp_header.bmp_info_header.bmp_colors_used * 4; color_table_size = 1 << bmp_header.bmp_info_header.bmp_bit_count;
}
ERR_FAIL_COND_V(color_table_size == 0, ERR_BUG);
PoolVector<uint8_t> bmp_color_table; PoolVector<uint8_t> bmp_color_table;
if (color_table_size > 0) { 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<uint8_t>::Write bmp_color_table_w = bmp_color_table.write(); PoolVector<uint8_t>::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); f->seek(bmp_header.bmp_file_header.bmp_file_offset);
@ -186,7 +282,7 @@ Error ImageLoaderBMP::load_image(Ref<Image> p_image, FileAccess *f,
PoolVector<uint8_t>::Read bmp_buffer_r = bmp_buffer.read(); PoolVector<uint8_t>::Read bmp_buffer_r = bmp_buffer.read();
PoolVector<uint8_t>::Read bmp_color_table_r = bmp_color_table.read(); PoolVector<uint8_t>::Read bmp_color_table_r = bmp_color_table.read();
err = convert_to_image(p_image, bmp_buffer_r.ptr(), 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(); f->close();
} }

View File

@ -37,6 +37,9 @@ class ImageLoaderBMP : public ImageFormatLoader {
protected: protected:
static const unsigned BITMAP_SIGNATURE = 0x4d42; 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 { enum bmp_compression_s {
BI_RGB = 0x00, BI_RGB = 0x00,
BI_RLE8 = 0x01, BI_RLE8 = 0x01,
@ -76,6 +79,7 @@ protected:
static Error convert_to_image(Ref<Image> p_image, static Error convert_to_image(Ref<Image> p_image,
const uint8_t *p_buffer, const uint8_t *p_buffer,
const uint8_t *p_color_buffer, const uint8_t *p_color_buffer,
const uint32_t color_table_size,
const bmp_header_s &p_header); const bmp_header_s &p_header);
public: public: