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).
This commit is contained in:
Andrii Doroshenko (Xrayez) 2019-04-21 15:49:49 +03:00
parent 49b6423874
commit 6484da5721
2 changed files with 138 additions and 38 deletions

View file

@ -33,6 +33,7 @@
Error ImageLoaderBMP::convert_to_image(Ref<Image> 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<Image> 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<uint8_t> data;
int data_len = 0;
PoolVector<uint8_t> 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<uint8_t>::Write image_data_w = image_data.write();
uint8_t *write_buffer = image_data_w.ptr();
PoolVector<uint8_t>::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<Image> 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<Image> 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<Image> 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<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;
@ -123,12 +210,9 @@ Error ImageLoaderBMP::load_image(Ref<Image> 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<Image> 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<Image> 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<uint8_t> 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<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);
@ -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_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();
}

View file

@ -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<Image> 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: