Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,25 @@

All notable changes to this project will be documented in this file.

## [0.3.0] - 2025-05-28

### Added
- **New Bitmap API Layer (`BmpTool`)**:
- Introduced a new public API header `include/bitmap.hpp`.
- Added `BmpTool::load(std::span<const uint8_t>)` function to load BMP data from a memory span into a `BmpTool::Bitmap` (RGBA format). This function bridges to the existing library's `::CreateMatrixFromBitmap` after parsing the input span.
- Added `BmpTool::save(const BmpTool::Bitmap&, std::span<uint8_t>)` function to save a `BmpTool::Bitmap` (RGBA) to a memory span. This function bridges to the existing library's `::CreateBitmapFromMatrix` and then serializes the resulting `::Bitmap::File` to the span.
- Defined `BmpTool::Bitmap` struct for RGBA pixel data and `BmpTool::Result` for error handling.
- Implemented the bridging logic in `src/format/bitmap.cpp`.
- Added Doxygen comments for the new public API.
- **API Roundtrip Test**:
- Added `tests/api_roundtrip.cpp` to verify that loading, saving, and re-loading a bitmap using the new API results in identical data.
- **Build System Updates**:
- Updated CMakeLists.txt files (root and tests) to include the new API implementation and test.
- Set C++ standard to C++20 globally in the root CMakeLists.txt.

### Changed
- The `bitmap` library now exposes the `BmpTool` API via `include/bitmap.hpp` for simplified bitmap operations.

## [0.2.0] - 2024-07-27

### Changed
Expand Down
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
cmake_minimum_required(VERSION 3.10)
project(bitmap VERSION 1.0.0)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_CXX_EXTENSIONS OFF)

include(CTest)
enable_testing()

# Add your main library with all sources
add_library(bitmap STATIC
src/bitmap/bitmap.cpp
src/bitmapfile/bitmap_file.cpp
src/format/bitmap.cpp
)

# Executable for main.cpp (if it's a demo or separate utility)
add_executable(testexe main.cpp)
target_link_libraries(testexe PRIVATE bitmap)

target_include_directories(bitmap PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_SOURCE_DIR}/src/bitmapfile
${CMAKE_CURRENT_SOURCE_DIR}/src/matrix
Expand Down
105 changes: 105 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,111 @@ These are now part of the main source tree under the `src/` directory.
* **Bitmap File Handler (`src/bitmapfile`)**: Handles loading and saving of BMP files.
* **Matrix Library (`src/matrix`)**: A generic matrix manipulation library used by the bitmap processing functions.

## New Span-Based API (`BmpTool`)

For more direct memory-based operations and a stable interface, the `BmpTool` API is provided.

The main header for this API is `include/bitmap.hpp`.

Core components include:
* `BmpTool::Bitmap`: A struct holding image dimensions (width, height, bits-per-pixel) and a `std::vector<uint8_t>` for RGBA pixel data.
* `BmpTool::load()`: Loads BMP data from a `std::span<const uint8_t>` into a `BmpTool::Bitmap`.
* `BmpTool::save()`: Saves a `BmpTool::Bitmap` to a `std::span<uint8_t>`.
* `BmpTool::Result<T, E>`: Used for functions that can return a value or an error, with `BmpTool::BitmapError` providing specific error codes.

### `BmpTool` API Usage Example

```cpp
#include "include/bitmap.hpp" // Public API
#include <vector>
#include <fstream> // For reading/writing files to/from buffer for example
#include <iostream>

// Helper to read a file into a vector
std::vector<uint8_t> read_file_to_buffer(const std::string& filepath) {
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
if (!file) return {};
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8_t> buffer(size);
if (file.read(reinterpret_cast<char*>(buffer.data()), size)) {
return buffer;
}
return {};
}

// Helper to write a buffer to a file
bool write_buffer_to_file(const std::string& filepath, const std::vector<uint8_t>& buffer, size_t actual_size) {
std::ofstream file(filepath, std::ios::binary);
if (!file) return false;
file.write(reinterpret_cast<const char*>(buffer.data()), actual_size);
return file.good();
}

int main() {
// Load an existing BMP into a buffer
std::vector<uint8_t> input_bmp_data = read_file_to_buffer("input.bmp");
if (input_bmp_data.empty()) {
std::cerr << "Failed to read input.bmp into buffer." << std::endl;
return 1;
}

// Use BmpTool::load
BmpTool::Result<BmpTool::Bitmap, BmpTool::BitmapError> load_result = BmpTool::load(input_bmp_data);

if (load_result.isError()) {
std::cerr << "BmpTool::load failed: " << static_cast<int>(load_result.error()) << std::endl;
return 1;
}
BmpTool::Bitmap my_bitmap = load_result.value();
std::cout << "Loaded input.bmp into BmpTool::Bitmap: "
<< my_bitmap.w << "x" << my_bitmap.h << " @ " << my_bitmap.bpp << "bpp" << std::endl;

// (Perform some manipulation on my_bitmap.data if desired)
// For example, invert the red channel for all pixels:
// for (size_t i = 0; i < my_bitmap.data.size(); i += 4) {
// my_bitmap.data[i] = 255 - my_bitmap.data[i]; // Invert Red
// }

// Prepare a buffer for saving
// Estimate required size: headers (54) + data (W*H*4)
size_t estimated_output_size = 54 + my_bitmap.w * my_bitmap.h * 4;
std::vector<uint8_t> output_bmp_buffer(estimated_output_size);

// Use BmpTool::save
BmpTool::Result<void, BmpTool::BitmapError> save_result = BmpTool::save(my_bitmap, output_bmp_buffer);

if (save_result.isError()) { // For Result<void,E>, isError() or checking error() != E::Ok
std::cerr << "BmpTool::save failed: " << static_cast<int>(save_result.error()) << std::endl;
return 1;
}
std::cout << "BmpTool::Bitmap saved to buffer." << std::endl;

// To get the actual size of the BMP written to the buffer (needed for writing to file):
// One way is to read bfSize from the header in output_bmp_buffer
// For example:
// #include "src/bitmapfile/bitmap_file.h" // For BITMAPFILEHEADER definition
// BITMAPFILEHEADER* fh = reinterpret_cast<BITMAPFILEHEADER*>(output_bmp_buffer.data());
// size_t actual_written_size = fh->bfSize;
// This requires including the BITMAPFILEHEADER definition.
// The save function itself doesn't return it, so this is a known aspect of the API.
// For this example, we'll use estimated_output_size, but actual_written_size is more robust.
// A more robust approach would be to parse bfSize or ensure save guarantees fitting within the span and updating its size.
// For this example, we assume the buffer is large enough and we write what's estimated.
// If the actual BMP is smaller, this might write extra uninitialized bytes from the buffer.
// If actual is larger (should not happen with correct estimation and save), it's a problem.
// The best is to parse bfSize from output_bmp_buffer.data().

if (write_buffer_to_file("output_new_api.bmp", output_bmp_buffer, estimated_output_size /* ideally actual_written_size from parsed header */)) {
std::cout << "Output buffer saved to output_new_api.bmp" << std::endl;
} else {
std::cerr << "Failed to write output_new_api.bmp." << std::endl;
}

return 0;
}
```

## Building the Project

The project uses CMake for building.
Expand Down
197 changes: 197 additions & 0 deletions include/bitmap.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
#pragma once // Use pragma once for include guard

#include <cstdint> // For uint32_t, uint8_t
#include <vector> // For std::vector
#include <variant> // For std::variant in Result, std::monostate
#include <string> // For error messages if needed (though enum is primary)
#include <span> // For std::span (will be used by load/save)
#include <stdexcept> // For std::runtime_error, std::bad_variant_access

namespace BmpTool {

/**
* @brief Represents errors that can occur during Bitmap operations.
*/
enum class BitmapError {
Ok, ///< Operation completed successfully. (Note: Used by save for Result<void,E>)
InvalidFileHeader, ///< The BITMAPFILEHEADER is invalid or corrupted.
InvalidImageHeader, ///< The BITMAPINFOHEADER is invalid or corrupted.
UnsupportedBpp, ///< The bits-per-pixel value is not supported (e.g., not 24 or 32).
InvalidImageData, ///< The pixel data is corrupt or inconsistent with header information.
OutputBufferTooSmall, ///< The provided output buffer (e.g., for saving) is too small.
IoError, ///< A generic error occurred during an I/O operation (e.g., reading/writing data).
NotABmp, ///< The file is not a BMP file (e.g., magic identifier is incorrect).
UnknownError ///< An unspecified error occurred.
};

/**
* @brief A placeholder type to represent a successful operation
* when a Result<void, E> is needed.
*/
struct Success {};

/**
* @brief A template class to represent a result that can either be a value or an error.
*
* @tparam T The type of the value if the operation is successful.
* @tparam E The type of the error if the operation fails.
*/
template <typename T, typename E>
class Result {
public:
/**
* @brief Constructs a Result with a success value.
* @param value The value to store.
*/
Result(T value) : m_data(value) {}

/**
* @brief Constructs a Result with an error.
* @param error The error to store.
*/
Result(E error) : m_data(error) {}

/**
* @brief Checks if the Result holds a success value.
* @return True if it holds a value, false otherwise.
*/
bool isSuccess() const {
return std::holds_alternative<T>(m_data);
}

/**
* @brief Checks if the Result holds an error.
* @return True if it holds an error, false otherwise.
*/
bool isError() const {
return std::holds_alternative<E>(m_data);
}

/**
* @brief Gets the success value.
* @return The stored value.
* @throw std::runtime_error if the Result holds an error or variant is valueless.
*/
T value() const {
if (!isSuccess()) { // Check if it's NOT a success
throw std::runtime_error("Attempted to access value from an error Result or variant holds unexpected type.");
}
// isSuccess() being true implies std::holds_alternative<T>(m_data) is true.
return std::get<T>(m_data);
}

/**
* @brief Gets the error.
* @return The stored error.
* @throw std::runtime_error if the Result holds a success value or variant is valueless.
*/
E error() const {
if (!isError()) { // Check if it's NOT an error
throw std::runtime_error("Attempted to access error from a success Result or variant holds unexpected type.");
}
// isError() being true implies std::holds_alternative<E>(m_data) is true.
return std::get<E>(m_data);
}

// Operator bool for easy checking like if(result)
explicit operator bool() const {
return isSuccess();
}

private:
std::variant<T, E> m_data;
};


/**
* @brief Specialization of Result for operations that return void on success.
* @tparam E The error type.
*/
template <typename E>
class Result<void, E> {
public:
/**
* @brief Constructs a success Result (with no specific value).
* @param success_tag Placeholder to indicate success.
*/
Result(Success /*success_tag*/ = Success{}) : m_data(Success{}) {}

/**
* @brief Constructs an error Result.
* @param error The error value.
*/
Result(E error) : m_data(error) {}

/**
* @brief Checks if the result is a success.
* @return True if the operation succeeded, false otherwise.
*/
bool isSuccess() const {
return std::holds_alternative<Success>(m_data);
}

/**
* @brief Checks if the result is an error.
* @return True if the operation failed, false otherwise.
*/
bool isError() const {
return std::holds_alternative<E>(m_data);
}

// No value() method for Result<void, E> as there's no value to return.

/**
* @brief Gets the error value.
* @return The error value.
* @throws std::runtime_error if called when not holding an error.
*/
E error() const {
if (!isError()) {
throw std::runtime_error("Called error() on a success Result<void, E> or variant holds unexpected type.");
}
return std::get<E>(m_data);
}

// Operator bool for easy checking like if(result)
explicit operator bool() const {
return isSuccess();
}

private:
std::variant<Success, E> m_data;
};


/**
* @brief Represents a bitmap image.
*/
struct Bitmap {
uint32_t w; ///< Width of the bitmap in pixels.
uint32_t h; ///< Height of the bitmap in pixels.
uint32_t bpp; ///< Bits per pixel (e.g., 24 for RGB, 32 for RGBA).
std::vector<uint8_t> data; ///< Pixel data, typically in RGBA format.
};

/**
* @brief Loads a bitmap from a memory span.
*
* Parses the BMP file data provided in the span and constructs a Bitmap object.
*
* @param bmp_data A span of constant uint8_t representing the raw BMP file data.
* @return A Result object containing either a Bitmap on success or a BitmapError on failure.
*/
Result<Bitmap, BitmapError> load(std::span<const uint8_t> bmp_data);

/**
* @brief Saves a Bitmap object to a memory span as BMP file data.
*
* Converts the Bitmap object into the BMP file format and writes it to the provided output buffer.
*
* @param bitmap The Bitmap object to save.
* @param out_bmp_buffer A span of uint8_t where the BMP file data will be written.
* The span must be large enough to hold the entire BMP file.
* @return A Result object containing Success on success or a BitmapError on failure.
*/
Result<void, BitmapError> save(const Bitmap& bitmap, std::span<uint8_t> out_bmp_buffer);

} // namespace BmpTool
Loading