Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds 16bpp support for BMP File Format #67608

Merged
merged 1 commit into from Apr 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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