Skip to content

Commit

Permalink
Adds some basic 16BPP BMP Support.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ScorpionInc committed Apr 12, 2023
1 parent ef02571 commit e74a0ef
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 5 deletions.
64 changes: 59 additions & 5 deletions modules/bmp/image_loader_bmp.cpp
Expand Up @@ -32,6 +32,16 @@

#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,
const uint8_t *p_buffer,
const uint8_t *p_color_buffer,
Expand Down Expand Up @@ -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)));
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)));

} else if (bits_per_pixel == 16) {
ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "16-bpp BMP images are not supported.");
}

// Image data (might be indexed)
Expand All @@ -96,7 +103,7 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image,

// The actual data traversal is determined by
// 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 *end_buffer = p_buffer + p_header.bmp_file_header.bmp_file_size - p_header.bmp_file_header.bmp_file_offset;

Expand Down Expand Up @@ -149,6 +156,34 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image,
index += 1;
line_ptr += 1;
} 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: {
write_buffer[index + 2] = line_ptr[0];
write_buffer[index + 1] = line_ptr[1];
Expand Down Expand Up @@ -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();

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_RLE4:
case BI_CMYKRLE8:
case BI_CMYKRLE4: {
// Stop parsing.
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;
}
// Don't rely on sizeof(bmp_file_header) as structure padding
Expand Down
19 changes: 19 additions & 0 deletions modules/bmp/image_loader_bmp.h
Expand Up @@ -74,6 +74,25 @@ class ImageLoaderBMP : public ImageFormatLoader {
uint32_t bmp_colors_used = 0;
uint32_t bmp_important_colors = 0;
} 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,
Expand Down

0 comments on commit e74a0ef

Please sign in to comment.