From 7874702f30d5855319faf197b10aed31f07f5e27 Mon Sep 17 00:00:00 2001 From: Hop311 Date: Mon, 22 May 2023 10:36:23 +0100 Subject: BMP palette parser + Logger macro --- src/openvic/utility/BMP.cpp | 162 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 src/openvic/utility/BMP.cpp (limited to 'src/openvic/utility/BMP.cpp') diff --git a/src/openvic/utility/BMP.cpp b/src/openvic/utility/BMP.cpp new file mode 100644 index 0000000..eb3e2b1 --- /dev/null +++ b/src/openvic/utility/BMP.cpp @@ -0,0 +1,162 @@ +#include "BMP.hpp" + +#include + +#include "Logger.hpp" + +using namespace OpenVic; + +BMP::~BMP() { + close(); +} + +return_t BMP::open(char const* filepath) { + reset(); + errno = 0; + file = fopen(filepath, "rb"); + if (file == nullptr || errno != 0) { + Logger::error("Failed to open BMP file \"", filepath, "\" (errno = ", errno, ")"); + file = nullptr; + return FAILURE; + } + return SUCCESS; +} + +return_t BMP::read_header() { + if (header_validated) { + Logger::error("BMP header already validated!"); + return FAILURE; + } + if (file == nullptr) { + Logger::error("Cannot read BMP header before opening a file"); + return FAILURE; + } + if (fseek(file, 0, SEEK_SET) != 0) { + Logger::error("Failed to move to the beginning of the BMP file!"); + return FAILURE; + } + if (fread(&header, sizeof(header), 1, file) != 1) { + Logger::error("Failed to read BMP header!"); + return FAILURE; + } + return_t ret = SUCCESS; + + // Validate constants + static constexpr uint16_t BMP_SIGNATURE = 0x4d42; + if (header.signature != BMP_SIGNATURE) { + Logger::error("Invalid BMP signature: ", header.signature, " (must be ", BMP_SIGNATURE, ")"); + ret = FAILURE; + } + static constexpr uint32_t DIB_HEADER_SIZE = 40; + if (header.dib_header_size != DIB_HEADER_SIZE) { + Logger::error("Invalid BMP DIB header size: ", header.dib_header_size, " (must be ", DIB_HEADER_SIZE, ")"); + ret = FAILURE; + } + static constexpr uint16_t NUM_PLANES = 1; + if (header.num_planes != NUM_PLANES) { + Logger::error("Invalid BMP plane count: ", header.num_planes, " (must be ", NUM_PLANES, ")"); + ret = FAILURE; + } + static constexpr uint16_t COMPRESSION = 0; // Only support uncompressed BMPs + if (header.compression != COMPRESSION) { + Logger::error("Invalid BMP compression method: ", header.compression, " (must be ", COMPRESSION, ")"); + ret = FAILURE; + } + + // Validate sizes and dimensions + // TODO - image_size_bytes can be 0 for non-compressed BMPs + if (header.file_size != header.offset + header.image_size_bytes) { + Logger::error("Invalid BMP memory sizes: file size = ", header.file_size, " != ", header.offset + header.image_size_bytes, + " = ", header.offset, " + ", header.image_size_bytes, " = image data offset + image data size"); + ret = FAILURE; + } + // TODO - support negative widths (i.e. horizontal flip) + if (header.width_px <= 0) { + Logger::error("Invalid BMP width: ", header.width_px, " (must be positive)"); + ret = FAILURE; + } + // TODO - support negative heights (i.e. vertical flip) + if (header.height_px <= 0) { + Logger::error("Invalid BMP height: ", header.height_px, " (must be positive)"); + ret = FAILURE; + } + // TODO - validate x_resolution_ppm + // TODO - validate y_resolution_ppm + + // Validate colours +#define VALID_BITS_PER_PIXEL 1, 2, 4, 8, 16, 24, 32 +#define STR(x) #x + static const std::set BITS_PER_PIXEL { VALID_BITS_PER_PIXEL }; + if (!BITS_PER_PIXEL.contains(header.bits_per_pixel)) { + Logger::error("Invalid BMP bits per pixel: ", header.bits_per_pixel, " (must be one of " STR(VALID_BITS_PER_PIXEL) ")"); + ret = FAILURE; + } +#undef VALID_BITS_PER_PIXEL +#undef STR + static constexpr uint16_t PALETTE_BITS_PER_PIXEL_LIMIT = 8; + if (header.num_colours != 0 && header.bits_per_pixel > PALETTE_BITS_PER_PIXEL_LIMIT) { + Logger::error("Invalid BMP palette size: ", header.num_colours, " (should be 0 as bits per pixel is ", header.bits_per_pixel, " > 8)"); + ret = FAILURE; + } + // TODO - validate important_colours + + palette_size = header.bits_per_pixel > PALETTE_BITS_PER_PIXEL_LIMIT ? 0 + // Use header.num_colours if it's greater than 0 and at most 1 << header.bits_per_pixel + : 0 < header.num_colours && header.num_colours - 1 >> header.bits_per_pixel == 0 ? header.num_colours + : 1 << header.bits_per_pixel; + + const uint32_t expected_offset = palette_size * PALETTE_COLOUR_SIZE + sizeof(header); + if (header.offset != expected_offset) { + Logger::error("Invalid BMP image data offset: ", header.offset, " (should be ", expected_offset, ")"); + ret = FAILURE; + } + + header_validated = ret == SUCCESS; + return ret; +} + +return_t BMP::read_palette() { + if (file == nullptr) { + Logger::error("Cannot read BMP palette before opening a file"); + return FAILURE; + } + if (!header_validated) { + Logger::error("Cannot read palette before BMP header is validated!"); + return FAILURE; + } + if (palette_size == 0) { + Logger::error("Cannot read BMP palette - header indicates this file doesn't have one"); + return FAILURE; + } + if (fseek(file, sizeof(header), SEEK_SET) != 0) { + Logger::error("Failed to move to the palette in the BMP file!"); + return FAILURE; + } + palette.resize(palette_size); + if (fread(palette.data(), palette_size * PALETTE_COLOUR_SIZE, 1, file) != 1) { + Logger::error("Failed to read BMP header!"); + palette.clear(); + return FAILURE; + } + return SUCCESS; +} + +void BMP::close() { + if (file != nullptr) { + if (fclose(file) != 0) + Logger::error("Failed to close BMP!"); + file = nullptr; + } +} + +void BMP::reset() { + close(); + memset(&header, 0, sizeof(header)); + header_validated = false; + palette_size = 0; + palette.clear(); +} + +std::vector const& BMP::get_palette() const { + return palette; +} -- cgit v1.2.3-56-ga3b1