aboutsummaryrefslogtreecommitdiff
path: root/src/openvic-simulation/utility
diff options
context:
space:
mode:
Diffstat (limited to 'src/openvic-simulation/utility')
-rw-r--r--src/openvic-simulation/utility/BMP.cpp163
-rw-r--r--src/openvic-simulation/utility/BMP.hpp51
-rw-r--r--src/openvic-simulation/utility/Logger.cpp20
-rw-r--r--src/openvic-simulation/utility/Logger.hpp87
-rw-r--r--src/openvic-simulation/utility/NumberUtils.hpp27
-rw-r--r--src/openvic-simulation/utility/StringUtils.hpp127
6 files changed, 475 insertions, 0 deletions
diff --git a/src/openvic-simulation/utility/BMP.cpp b/src/openvic-simulation/utility/BMP.cpp
new file mode 100644
index 0000000..531870b
--- /dev/null
+++ b/src/openvic-simulation/utility/BMP.cpp
@@ -0,0 +1,163 @@
+#include "BMP.hpp"
+
+#include <cstring>
+#include <set>
+
+#include "openvic-simulation/utility/Logger.hpp"
+
+using namespace OpenVic;
+
+BMP::~BMP() {
+ close();
+}
+
+bool 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 false;
+ }
+ return true;
+}
+
+bool BMP::read_header() {
+ if (header_validated) {
+ Logger::error("BMP header already validated!");
+ return false;
+ }
+ if (file == nullptr) {
+ Logger::error("Cannot read BMP header before opening a file");
+ return false;
+ }
+ if (fseek(file, 0, SEEK_SET) != 0) {
+ Logger::error("Failed to move to the beginning of the BMP file!");
+ return false;
+ }
+ if (fread(&header, sizeof(header), 1, file) != 1) {
+ Logger::error("Failed to read BMP header!");
+ return false;
+ }
+
+ header_validated = true;
+
+ // 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, ")");
+ header_validated = false;
+ }
+ 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, ")");
+ header_validated = false;
+ }
+ 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, ")");
+ header_validated = false;
+ }
+ 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, ")");
+ header_validated = false;
+ }
+
+ // 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");
+ header_validated = false;
+ }
+ // TODO - support negative widths (i.e. horizontal flip)
+ if (header.width_px <= 0) {
+ Logger::error("Invalid BMP width: ", header.width_px, " (must be positive)");
+ header_validated = false;
+ }
+ // TODO - support negative heights (i.e. vertical flip)
+ if (header.height_px <= 0) {
+ Logger::error("Invalid BMP height: ", header.height_px, " (must be positive)");
+ header_validated = false;
+ }
+ // 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<uint16_t> 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) ")");
+ header_validated = false;
+ }
+#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)");
+ header_validated = false;
+ }
+ // 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, ")");
+ header_validated = false;
+ }
+
+ return header_validated;
+}
+
+bool BMP::read_palette() {
+ if (file == nullptr) {
+ Logger::error("Cannot read BMP palette before opening a file");
+ return false;
+ }
+ if (!header_validated) {
+ Logger::error("Cannot read palette before BMP header is validated!");
+ return false;
+ }
+ if (palette_size == 0) {
+ Logger::error("Cannot read BMP palette - header indicates this file doesn't have one");
+ return false;
+ }
+ if (fseek(file, sizeof(header), SEEK_SET) != 0) {
+ Logger::error("Failed to move to the palette in the BMP file!");
+ return false;
+ }
+ 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 false;
+ }
+ return true;
+}
+
+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<colour_t> const& BMP::get_palette() const {
+ return palette;
+}
diff --git a/src/openvic-simulation/utility/BMP.hpp b/src/openvic-simulation/utility/BMP.hpp
new file mode 100644
index 0000000..f04b41a
--- /dev/null
+++ b/src/openvic-simulation/utility/BMP.hpp
@@ -0,0 +1,51 @@
+#pragma once
+
+#include <cstdio>
+#include <vector>
+
+#include "openvic-simulation/types/Colour.hpp"
+
+namespace OpenVic {
+ class BMP {
+#pragma pack(push)
+#pragma pack(1)
+ struct header_t {
+ uint16_t signature; // Signature: 0x4d42 (or 'B' 'M')
+ uint32_t file_size; // File size in bytes
+ uint16_t reserved1; // Not used
+ uint16_t reserved2; // Not used
+ uint32_t offset; // Offset to image data in bytes from beginning of file
+ uint32_t dib_header_size; // DIB header size in bytes
+ int32_t width_px; // Width of the image
+ int32_t height_px; // Height of image
+ uint16_t num_planes; // Number of colour planes
+ uint16_t bits_per_pixel; // Bits per pixel
+ uint32_t compression; // Compression type
+ uint32_t image_size_bytes; // Image size in bytes
+ int32_t x_resolution_ppm; // Pixels per meter
+ int32_t y_resolution_ppm; // Pixels per meter
+ uint32_t num_colours; // Number of colours
+ uint32_t important_colours; // Important colours
+ } header;
+#pragma pack(pop)
+
+ FILE* file = nullptr;
+ bool header_validated = false;
+ uint32_t palette_size = 0;
+ std::vector<colour_t> palette;
+
+ public:
+ static constexpr uint32_t PALETTE_COLOUR_SIZE = sizeof(colour_t);
+
+ BMP() = default;
+ ~BMP();
+
+ bool open(char const* filepath);
+ bool read_header();
+ bool read_palette();
+ void close();
+ void reset();
+
+ std::vector<colour_t> const& get_palette() const;
+ };
+}
diff --git a/src/openvic-simulation/utility/Logger.cpp b/src/openvic-simulation/utility/Logger.cpp
new file mode 100644
index 0000000..bf9a67c
--- /dev/null
+++ b/src/openvic-simulation/utility/Logger.cpp
@@ -0,0 +1,20 @@
+#include "Logger.hpp"
+
+#include <iostream>
+
+using namespace OpenVic;
+
+Logger::log_func_t Logger::info_func {};
+Logger::log_queue_t Logger::info_queue {};
+Logger::log_func_t Logger::error_func {};
+Logger::log_queue_t Logger::error_queue {};
+
+char const* Logger::get_filename(char const* filepath) {
+ if (filepath == nullptr) return nullptr;
+ char const* last_slash = filepath;
+ while (*filepath != '\0') {
+ if (*filepath == '\\' || *filepath == '/') last_slash = filepath + 1;
+ filepath++;
+ }
+ return last_slash;
+}
diff --git a/src/openvic-simulation/utility/Logger.hpp b/src/openvic-simulation/utility/Logger.hpp
new file mode 100644
index 0000000..417aba7
--- /dev/null
+++ b/src/openvic-simulation/utility/Logger.hpp
@@ -0,0 +1,87 @@
+#pragma once
+
+#include <functional>
+#include <queue>
+#include <sstream>
+#ifdef __cpp_lib_source_location
+#include <source_location>
+#endif
+
+namespace OpenVic {
+
+#ifndef __cpp_lib_source_location
+#include <string>
+ // Implementation of std::source_location for compilers that do not support it
+ // Note: uses non-standard extensions that are supported by Clang, GCC, and MSVC
+ // https://clang.llvm.org/docs/LanguageExtensions.html#source-location-builtins
+ // https://stackoverflow.com/a/67970107
+ class source_location {
+ std::string _file;
+ int _line;
+ std::string _function;
+
+ public:
+ source_location(std::string f, int l, std::string n) : _file(f), _line(l), _function(n) {}
+ static source_location current(std::string f = __builtin_FILE(), int l = __builtin_LINE(), std::string n = __builtin_FUNCTION()) {
+ return source_location(f, l, n);
+ }
+
+ inline char const* file_name() const { return _file.c_str(); }
+ inline int line() const { return _line; }
+ inline char const* function_name() const { return _function.c_str(); }
+ };
+#endif
+
+ class Logger {
+ using log_func_t = std::function<void(std::string&&)>;
+ using log_queue_t = std::queue<std::string>;
+
+#ifdef __cpp_lib_source_location
+ using source_location = std::source_location;
+#else
+ using source_location = OpenVic::source_location;
+#endif
+
+ static char const* get_filename(char const* filepath);
+
+ template<typename... Ts>
+ struct log {
+ log(log_func_t log_func, log_queue_t& log_queue, Ts&&... ts, source_location const& location) {
+ std::stringstream stream;
+ stream << "\n" << get_filename(location.file_name()) << "("
+ << location.line() << ") `" << location.function_name() << "`: ";
+ ((stream << std::forward<Ts>(ts)), ...);
+ stream << std::endl;
+ log_queue.push(stream.str());
+ if (log_func) {
+ do {
+ log_func(std::move(log_queue.front()));
+ log_queue.pop();
+ } while (!log_queue.empty());
+ }
+ }
+ };
+
+#define LOG_FUNC(name) \
+ private: \
+ static log_func_t name##_func; \
+ static log_queue_t name##_queue; \
+ public: \
+ static void set_##name##_func(log_func_t log_func) { \
+ name##_func = log_func; \
+ } \
+ template <typename... Ts> \
+ struct name { \
+ name(Ts&&... ts, source_location const& location = source_location::current()) { \
+ log<Ts...>{ name##_func, name##_queue, std::forward<Ts>(ts)..., location }; \
+ } \
+ }; \
+ template <typename... Ts> \
+ name(Ts&&...) -> name<Ts...>;
+
+ LOG_FUNC(info)
+ LOG_FUNC(error)
+
+#undef LOG_FUNC
+ };
+}
diff --git a/src/openvic-simulation/utility/NumberUtils.hpp b/src/openvic-simulation/utility/NumberUtils.hpp
new file mode 100644
index 0000000..6211772
--- /dev/null
+++ b/src/openvic-simulation/utility/NumberUtils.hpp
@@ -0,0 +1,27 @@
+#include <cstdint>
+
+namespace OpenVic::NumberUtils {
+ constexpr int64_t round_to_int64(float num) {
+ return (num > 0.0f) ? (num + 0.5f) : (num - 0.5f);
+ }
+
+ constexpr int64_t round_to_int64(double num) {
+ return (num > 0.0) ? (num + 0.5) : (num - 0.5);
+ }
+
+ constexpr uint64_t pow(uint64_t base, size_t exponent) {
+ uint64_t ret = 1;
+ while (exponent-- > 0) {
+ ret *= base;
+ }
+ return ret;
+ }
+
+ constexpr int64_t pow(int64_t base, size_t exponent) {
+ int64_t ret = 1;
+ while (exponent-- > 0) {
+ ret *= base;
+ }
+ return ret;
+ }
+}
diff --git a/src/openvic-simulation/utility/StringUtils.hpp b/src/openvic-simulation/utility/StringUtils.hpp
new file mode 100644
index 0000000..5d6001c
--- /dev/null
+++ b/src/openvic-simulation/utility/StringUtils.hpp
@@ -0,0 +1,127 @@
+#include <cstdint>
+#include <limits>
+#include <string_view>
+
+namespace OpenVic::StringUtils {
+ /* The constexpr function 'string_to_uint64' will convert a string into a uint64_t integer value.
+ * The function takes four parameters: the input string (as a pair of pointers marking the start and
+ * end of the string), a bool pointer for reporting success, and the base for numerical conversion.
+ * The base parameter defaults to 10 (decimal), but it can be any value between 2 and 36. If the base
+ * given is 0, it will be set to 16 if the string starts with "0x" or "0X", otherwise 8 if the string
+ * still starts with "0", otherwise 10. The success bool pointer parameter is used to report whether
+ * or not conversion was successful. It can be nullptr if this information is not needed.
+ */
+ constexpr uint64_t string_to_uint64(char const* str, const char* const end, bool* successful = nullptr, int base = 10) {
+ if (successful != nullptr) *successful = false;
+
+ // Base value should be between 2 and 36. If it's not, return 0 as an invalid case.
+ if (str == nullptr || end <= str || base < 0 || base == 1 || base > 36)
+ return 0;
+
+ // The result of the conversion will be stored in this variable.
+ uint64_t result = 0;
+
+ // If base is zero, base is determined by the string prefix.
+ if (base == 0) {
+ if (*str == '0') {
+ if (str + 1 != end && (str[1] == 'x' || str[1] == 'X')) {
+ base = 16; // Hexadecimal.
+ str += 2; // Skip '0x' or '0X'
+ if (str == end) return 0;
+ } else {
+ base = 8; // Octal.
+ }
+ } else {
+ base = 10; // Decimal.
+ }
+ } else if (base == 16) {
+ // If base is 16 and string starts with '0x' or '0X', skip these characters.
+ if (*str == '0' && str + 1 != end && (str[1] == 'x' || str[1] == 'X')) {
+ str += 2;
+ if (str == end) return 0;
+ }
+ }
+
+ // Convert the number in the string.
+ for (; str != end; ++str) {
+ int digit;
+ if (*str >= '0' && *str <= '9') {
+ digit = *str - '0'; // Calculate digit value for '0'-'9'.
+ } else if (*str >= 'a' && *str <= 'z') {
+ digit = *str - 'a' + 10; // Calculate digit value for 'a'-'z'.
+ } else if (*str >= 'A' && *str <= 'Z') {
+ digit = *str - 'A' + 10; // Calculate digit value for 'A'-'Z'.
+ } else {
+ break; // Stop conversion if current character is not a digit.
+ }
+
+ if (digit >= base) {
+ break; // Stop conversion if current digit is greater than or equal to the base.
+ }
+
+ // Check for overflow on multiplication
+ if (result > std::numeric_limits<uint64_t>::max() / base) {
+ return std::numeric_limits<uint64_t>::max();
+ }
+
+ result *= base;
+
+ // Check for overflow on addition
+ if (result > std::numeric_limits<uint64_t>::max() - digit) {
+ return std::numeric_limits<uint64_t>::max();
+ }
+
+ result += digit;
+ }
+
+ // If successful is not null and the entire string was parsed,
+ // set *successful to true (if not it is already false).
+ if (successful != nullptr && str == end) *successful = true;
+
+ return result;
+ }
+
+ constexpr uint64_t string_to_uint64(char const* str, size_t length, bool* successful = nullptr, int base = 10) {
+ return string_to_uint64(str, str + length, successful, base);
+ }
+
+ inline uint64_t string_to_uint64(const std::string_view str, bool* successful = nullptr, int base = 10) {
+ return string_to_uint64(str.data(), str.length(), successful, base);
+ }
+
+ constexpr int64_t string_to_int64(char const* str, const char* const end, bool* successful = nullptr, int base = 10) {
+ if (successful != nullptr) *successful = false;
+
+ if (str == nullptr || end <= str) return 0;
+
+ // This flag will be set if the number is negative.
+ bool is_negative = false;
+
+ // Check if there is a sign character.
+ if (*str == '+' || *str == '-') {
+ if (*str == '-')
+ is_negative = true;
+ ++str;
+ if (str == end) return 0;
+ }
+
+ const uint64_t result = string_to_uint64(str, end, successful, base);
+ if (!is_negative) {
+ if (result >= std::numeric_limits<int64_t>::max())
+ return std::numeric_limits<int64_t>::max();
+ return result;
+ } else {
+ if (result > std::numeric_limits<int64_t>::max())
+ return std::numeric_limits<int64_t>::min();
+ return -result;
+ }
+ }
+
+ constexpr int64_t string_to_int64(char const* str, size_t length, bool* successful = nullptr, int base = 10) {
+ return string_to_int64(str, str + length, successful, base);
+ }
+
+ inline int64_t string_to_int64(const std::string_view str, bool* successful = nullptr, int base = 10) {
+ return string_to_int64(str.data(), str.length(), successful, base);
+ }
+}