Adds some basic 16BPP BMP Support.

This commit adds some basic 16bpp support for BMP File Format.

Added support for reading and using of 16 bit mask values from file. All values are scaled to ARGB255 format based on bit depth of source color channel.

Removed warning, it's no longer required as 16 bit with alpha bit(s) are now supported.

Adjusted spacing, added spacing and brackets to make clang static check happy.
This commit is contained in:
ScorpionInc 2023-04-03 19:17:37 -04:00 committed by ScorpionInc
parent ef025711a6
commit e74a0eff16
2 changed files with 78 additions and 5 deletions

View File

@ -32,6 +32,16 @@
#include "core/io/file_access_memory.h" #include "core/io/file_access_memory.h"
static uint8_t get_mask_width(uint16_t mask) {
// Returns number of ones in the binary value of the parameter: mask.
// Uses a Simple pop_count.
uint8_t c = 0u;
for (; mask != 0u; mask &= mask - 1u) {
c++;
}
return c;
}
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,
@ -71,9 +81,6 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image,
vformat("4-bpp BMP images must have a width that is a multiple of 2, but the imported BMP is %d pixels wide.", int(width))); vformat("4-bpp BMP images must have a width that is a multiple of 2, but the imported BMP is %d pixels wide.", int(width)));
ERR_FAIL_COND_V_MSG(height % 2 != 0, ERR_UNAVAILABLE, ERR_FAIL_COND_V_MSG(height % 2 != 0, ERR_UNAVAILABLE,
vformat("4-bpp BMP images must have a height that is a multiple of 2, but the imported BMP is %d pixels tall.", int(height))); vformat("4-bpp BMP images must have a height that is a multiple of 2, but the imported BMP is %d pixels tall.", int(height)));
} else if (bits_per_pixel == 16) {
ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "16-bpp BMP images are not supported.");
} }
// Image data (might be indexed) // Image data (might be indexed)
@ -96,7 +103,7 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image,
// The actual data traversal is determined by // The actual data traversal is determined by
// the data width in case of 8/4/2/1 bit images // the data width in case of 8/4/2/1 bit images
const uint32_t w = bits_per_pixel >= 24 ? width : width_bytes; const uint32_t w = bits_per_pixel >= 16 ? width : width_bytes;
const uint8_t *line = p_buffer + (line_width * (height - 1)); const uint8_t *line = p_buffer + (line_width * (height - 1));
const uint8_t *end_buffer = p_buffer + p_header.bmp_file_header.bmp_file_size - p_header.bmp_file_header.bmp_file_offset; const uint8_t *end_buffer = p_buffer + p_header.bmp_file_header.bmp_file_size - p_header.bmp_file_header.bmp_file_offset;
@ -149,6 +156,34 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image,
index += 1; index += 1;
line_ptr += 1; line_ptr += 1;
} break; } break;
case 16: {
uint16_t rgb = (static_cast<uint16_t>(line_ptr[1]) << 8) | line_ptr[0];
// A1R5G5B5/X1R5G5B5 => uint16_t
// [A/X]1R5G2 | G3B5 => uint8_t | uint8_t
uint8_t ba = (rgb & p_header.bmp_bitfield.alpha_mask) >> p_header.bmp_bitfield.alpha_offset; // Alpha 0b 1000 ...
uint8_t b0 = (rgb & p_header.bmp_bitfield.red_mask) >> p_header.bmp_bitfield.red_offset; // Red 0b 0111 1100 ...
uint8_t b1 = (rgb & p_header.bmp_bitfield.green_mask) >> p_header.bmp_bitfield.green_offset; // Green 0b 0000 0011 1110 ...
uint8_t b2 = (rgb & p_header.bmp_bitfield.blue_mask); // >> p_header.bmp_bitfield.blue_offset; // Blue 0b ... 0001 1111
// Next we apply some color scaling going from a variable value space to a 256 value space.
// This may be simplified some but left as is for legibility.
// float scaled_value = unscaled_value * byte_max_value / color_channel_maxium_value + rounding_offset;
float f0 = b0 * 255.0f / static_cast<float>(p_header.bmp_bitfield.red_max) + 0.5f;
float f1 = b1 * 255.0f / static_cast<float>(p_header.bmp_bitfield.green_max) + 0.5f;
float f2 = b2 * 255.0f / static_cast<float>(p_header.bmp_bitfield.blue_max) + 0.5f;
write_buffer[index + 0] = static_cast<uint8_t>(f0); // R
write_buffer[index + 1] = static_cast<uint8_t>(f1); // G
write_buffer[index + 2] = static_cast<uint8_t>(f2); // B
if (p_header.bmp_bitfield.alpha_mask_width > 0) {
write_buffer[index + 3] = ba * 0xFF; // Alpha value(Always true or false so no scaling)
} else {
write_buffer[index + 3] = 0xFF; // No Alpha channel, Show everything.
}
index += 4;
line_ptr += 2;
} break;
case 24: { case 24: {
write_buffer[index + 2] = line_ptr[0]; write_buffer[index + 2] = line_ptr[0];
write_buffer[index + 1] = line_ptr[1]; write_buffer[index + 1] = line_ptr[1];
@ -253,13 +288,32 @@ Error ImageLoaderBMP::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField
bmp_header.bmp_info_header.bmp_important_colors = f->get_32(); bmp_header.bmp_info_header.bmp_important_colors = f->get_32();
switch (bmp_header.bmp_info_header.bmp_compression) { switch (bmp_header.bmp_info_header.bmp_compression) {
case BI_BITFIELDS: {
bmp_header.bmp_bitfield.red_mask = f->get_32();
bmp_header.bmp_bitfield.green_mask = f->get_32();
bmp_header.bmp_bitfield.blue_mask = f->get_32();
bmp_header.bmp_bitfield.alpha_mask = f->get_32();
bmp_header.bmp_bitfield.red_mask_width = get_mask_width(bmp_header.bmp_bitfield.red_mask);
bmp_header.bmp_bitfield.green_mask_width = get_mask_width(bmp_header.bmp_bitfield.green_mask);
bmp_header.bmp_bitfield.blue_mask_width = get_mask_width(bmp_header.bmp_bitfield.blue_mask);
bmp_header.bmp_bitfield.alpha_mask_width = get_mask_width(bmp_header.bmp_bitfield.alpha_mask);
bmp_header.bmp_bitfield.alpha_offset = bmp_header.bmp_bitfield.red_mask_width + bmp_header.bmp_bitfield.green_mask_width + bmp_header.bmp_bitfield.blue_mask_width;
bmp_header.bmp_bitfield.red_offset = bmp_header.bmp_bitfield.green_mask_width + bmp_header.bmp_bitfield.blue_mask_width;
bmp_header.bmp_bitfield.green_offset = bmp_header.bmp_bitfield.blue_mask_width;
bmp_header.bmp_bitfield.red_max = (1 << bmp_header.bmp_bitfield.red_mask_width) - 1;
bmp_header.bmp_bitfield.green_max = (1 << bmp_header.bmp_bitfield.green_mask_width) - 1;
bmp_header.bmp_bitfield.blue_max = (1 << bmp_header.bmp_bitfield.blue_mask_width) - 1;
} break;
case BI_RLE8: case BI_RLE8:
case BI_RLE4: case BI_RLE4:
case BI_CMYKRLE8: case BI_CMYKRLE8:
case BI_CMYKRLE4: { case BI_CMYKRLE4: {
// Stop parsing. // Stop parsing.
ERR_FAIL_V_MSG(ERR_UNAVAILABLE, ERR_FAIL_V_MSG(ERR_UNAVAILABLE,
vformat("Compressed BMP files are not supported: %s", f->get_path())); vformat("RLE compressed BMP files are not yet supported: %s", f->get_path()));
} break; } break;
} }
// Don't rely on sizeof(bmp_file_header) as structure padding // Don't rely on sizeof(bmp_file_header) as structure padding

View File

@ -74,6 +74,25 @@ protected:
uint32_t bmp_colors_used = 0; uint32_t bmp_colors_used = 0;
uint32_t bmp_important_colors = 0; uint32_t bmp_important_colors = 0;
} bmp_info_header; } bmp_info_header;
struct bmp_bitfield_s {
uint16_t alpha_mask = 0x8000;
uint16_t red_mask = 0x7C00;
uint16_t green_mask = 0x03E0;
uint16_t blue_mask = 0x001F;
uint16_t alpha_mask_width = 1u;
uint16_t red_mask_width = 5u;
uint16_t green_mask_width = 5u;
uint16_t blue_mask_width = 5u;
uint8_t alpha_offset = 15u; // Used for bit shifting.
uint8_t red_offset = 10u; // Used for bit shifting.
uint8_t green_offset = 5u; // Used for bit shifting.
//uint8_t blue_offset = 0u; // Always LSB aligned no shifting needed.
//uint8_t alpha_max = 1u; // Always boolean or on, so no scaling needed.
uint8_t red_max = 32u; // Used for color space scaling.
uint8_t green_max = 32u; // Used for color space scaling.
uint8_t blue_max = 32u; // Used for color space scaling.
} bmp_bitfield;
}; };
static Error convert_to_image(Ref<Image> p_image, static Error convert_to_image(Ref<Image> p_image,