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:
parent
49b6423874
commit
6484da5721
2 changed files with 138 additions and 38 deletions
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue