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
21 changes: 21 additions & 0 deletions include/bitmap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,16 @@ struct Bitmap {
*/
Result<Bitmap, BitmapError> load(std::span<const uint8_t> bmp_data);

/**
* @brief Loads a bitmap from a file path.
*
* Opens and reads the BMP file from the given path, then constructs a Bitmap object.
*
* @param filepath The path to the BMP file.
* @return A Result object containing either a Bitmap on success or a BitmapError on failure.
*/
Result<Bitmap, BitmapError> load(const std::string& filepath);

/**
* @brief Saves a Bitmap object to a memory span as BMP file data.
*
Expand All @@ -194,6 +204,17 @@ Result<Bitmap, BitmapError> load(std::span<const uint8_t> bmp_data);
*/
Result<void, BitmapError> save(const Bitmap& bitmap, std::span<uint8_t> out_bmp_buffer);

/**
* @brief Saves a Bitmap object to a file path.
*
* Converts the Bitmap object into the BMP file format and writes it to the specified file.
*
* @param bitmap The Bitmap object to save.
* @param filepath The path to the file where the BMP data will be saved.
* @return A Result object containing Success on success or a BitmapError on failure.
*/
Result<void, BitmapError> save(const Bitmap& bitmap, const std::string& filepath);

/**
* @brief Shrinks the image by a given scale factor.
* @param bitmap The input bitmap.
Expand Down
120 changes: 26 additions & 94 deletions main.cpp
Original file line number Diff line number Diff line change
@@ -1,44 +1,9 @@
#include "../../include/bitmap.hpp" // Using BmpTool API
#include "../../src/bitmapfile/bitmap_file.h" // For BITMAPFILEHEADER for writeFile logic

#include <iostream> // For std::cout, std::cerr
#include <vector> // For std::vector
#include <vector> // For std::vector (used by BmpTool::Bitmap)
#include <string> // For std::string
#include <fstream> // For std::ifstream, std::ofstream
#include <cstdint> // For uint8_t etc.
#include <span> // For std::span
#include <cstring> // For std::memcpy

// File I/O Utilities
std::vector<uint8_t> readFile(const std::string& filename) {
std::ifstream file(filename, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
std::cerr << "Error: Could not open file for reading: " << filename << std::endl;
return {};
}
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8_t> buffer(static_cast<size_t>(size)); // Ensure size_t for vector constructor
if (file.read(reinterpret_cast<char*>(buffer.data()), size)) {
return buffer;
}
std::cerr << "Error: Could not read file: " << filename << std::endl;
return {};
}

bool writeFile(const std::string& filename, const std::vector<uint8_t>& data) {
std::ofstream file(filename, std::ios::binary);
if (!file.is_open()) {
std::cerr << "Error: Could not open file for writing: " << filename << std::endl;
return false;
}
file.write(reinterpret_cast<const char*>(data.data()), data.size());
if (!file.good()) {
std::cerr << "Error: Could not write all data to file: " << filename << std::endl;
return false;
}
return true;
}
#include <cstdint> // For uint8_t etc. (used by BmpTool::Bitmap)

// Helper to print BmpTool errors
void printError(BmpTool::BitmapError error, const std::string& operation_name) {
Expand All @@ -53,19 +18,14 @@ int main() {
std::cout << "---------------------------------------" << std::endl;
std::cout << "Attempting to load image: " << inputFilename << std::endl;

std::vector<uint8_t> file_data = readFile(inputFilename);
if (file_data.empty()) {
BmpTool::Result<BmpTool::Bitmap, BmpTool::BitmapError> load_result = BmpTool::load(inputFilename);
if (!load_result.isSuccess()) {
std::cerr << "********************************************************************************" << std::endl;
std::cerr << "Error: Could not read '" << inputFilename << "'." << std::endl;
std::cerr << "Please ensure this file exists in the same directory as the executable." << std::endl;
std::cerr << "Error: Could not load '" << inputFilename << "' using BmpTool::load." << std::endl;
std::cerr << "Please ensure this file exists in the same directory as the executable and is a valid BMP." << std::endl;
std::cerr << "You can copy any BMP image (e.g., from Windows, or download one) and rename it to 'test.bmp'." << std::endl;
std::cerr << "A common source of BMP files is MS Paint (save as BMP)." << std::endl;
std::cerr << "********************************************************************************" << std::endl;
return 1;
}

BmpTool::Result<BmpTool::Bitmap, BmpTool::BitmapError> load_result = BmpTool::load(file_data);
if (!load_result.isSuccess()) {
printError(load_result.error(), "loading " + inputFilename);
return 1;
}
Expand All @@ -80,19 +40,12 @@ int main() {
auto invert_result = BmpTool::invertColors(originalBitmap);
if (invert_result.isSuccess()) {
currentBitmap = invert_result.value();
std::vector<uint8_t> output_buffer(54 + currentBitmap.w * currentBitmap.h * 4); // Estimate
auto save_res = BmpTool::save(currentBitmap, output_buffer);
if (save_res.isSuccess()) {
BITMAPFILEHEADER fh_out;
std::memcpy(&fh_out, output_buffer.data(), sizeof(BITMAPFILEHEADER));
std::vector<uint8_t> actual_data(output_buffer.begin(), output_buffer.begin() + fh_out.bfSize);
if (writeFile("output_inverted.bmp", actual_data)) {
std::cout << "Saved: output_inverted.bmp" << std::endl;
} else {
std::cerr << "Error writing output_inverted.bmp" << std::endl;
}
auto save_res_file = BmpTool::save(currentBitmap, "output_inverted.bmp");
if (save_res_file.isSuccess()) {
std::cout << "Saved: output_inverted.bmp" << std::endl;
} else {
printError(save_res.error(), "saving inverted image");
std::cerr << "Error writing output_inverted.bmp directly to file." << std::endl;
printError(save_res_file.error(), "saving inverted image");
}
} else {
printError(invert_result.error(), "inverting colors");
Expand All @@ -104,19 +57,12 @@ int main() {
auto sepia_result = BmpTool::applySepiaTone(originalBitmap);
if (sepia_result.isSuccess()) {
currentBitmap = sepia_result.value();
std::vector<uint8_t> output_buffer(54 + currentBitmap.w * currentBitmap.h * 4);
auto save_res = BmpTool::save(currentBitmap, output_buffer);
if (save_res.isSuccess()) {
BITMAPFILEHEADER fh_out;
std::memcpy(&fh_out, output_buffer.data(), sizeof(BITMAPFILEHEADER));
std::vector<uint8_t> actual_data(output_buffer.begin(), output_buffer.begin() + fh_out.bfSize);
if (writeFile("output_sepia.bmp", actual_data)) {
std::cout << "Saved: output_sepia.bmp" << std::endl;
} else {
std::cerr << "Error writing output_sepia.bmp" << std::endl;
}
auto save_res_file = BmpTool::save(currentBitmap, "output_sepia.bmp");
if (save_res_file.isSuccess()) {
std::cout << "Saved: output_sepia.bmp" << std::endl;
} else {
printError(save_res.error(), "saving sepia image");
std::cerr << "Error writing output_sepia.bmp directly to file." << std::endl;
printError(save_res_file.error(), "saving sepia image");
}
} else {
printError(sepia_result.error(), "applying sepia tone");
Expand Down Expand Up @@ -146,19 +92,12 @@ int main() {
tempProcessedBitmap = contrast_result.value();
std::cout << "Step 2: Contrast increased." << std::endl;

std::vector<uint8_t> output_buffer(54 + tempProcessedBitmap.w * tempProcessedBitmap.h * 4);
auto save_res = BmpTool::save(tempProcessedBitmap, output_buffer);
if (save_res.isSuccess()) {
BITMAPFILEHEADER fh_out;
std::memcpy(&fh_out, output_buffer.data(), sizeof(BITMAPFILEHEADER));
std::vector<uint8_t> actual_data(output_buffer.begin(), output_buffer.begin() + fh_out.bfSize);
if (writeFile("output_blurred_contrasted.bmp", actual_data)) {
std::cout << "Saved: output_blurred_contrasted.bmp" << std::endl;
} else {
std::cerr << "Error writing output_blurred_contrasted.bmp" << std::endl;
}
auto save_res_file = BmpTool::save(tempProcessedBitmap, "output_blurred_contrasted.bmp");
if (save_res_file.isSuccess()) {
std::cout << "Saved: output_blurred_contrasted.bmp" << std::endl;
} else {
printError(save_res.error(), "saving blurred_contrasted image");
std::cerr << "Error writing output_blurred_contrasted.bmp directly to file." << std::endl;
printError(save_res_file.error(), "saving blurred_contrasted image");
}
}
}
Expand All @@ -185,19 +124,12 @@ int main() {
tempProcessedBitmap = greyscale_result.value();
std::cout << "Step 2: Greyscale applied." << std::endl;

std::vector<uint8_t> output_buffer(54 + tempProcessedBitmap.w * tempProcessedBitmap.h * 4);
auto save_res = BmpTool::save(tempProcessedBitmap, output_buffer);
if (save_res.isSuccess()) {
BITMAPFILEHEADER fh_out;
std::memcpy(&fh_out, output_buffer.data(), sizeof(BITMAPFILEHEADER));
std::vector<uint8_t> actual_data(output_buffer.begin(), output_buffer.begin() + fh_out.bfSize);
if(writeFile("output_shrunk_greyscale.bmp", actual_data)){
std::cout << "Saved: output_shrunk_greyscale.bmp" << std::endl;
} else {
std::cerr << "Error writing output_shrunk_greyscale.bmp" << std::endl;
}
auto save_res_file = BmpTool::save(tempProcessedBitmap, "output_shrunk_greyscale.bmp");
if (save_res_file.isSuccess()) {
std::cout << "Saved: output_shrunk_greyscale.bmp" << std::endl;
} else {
printError(save_res.error(), "saving shrunk_greyscale image");
std::cerr << "Error writing output_shrunk_greyscale.bmp directly to file." << std::endl;
printError(save_res_file.error(), "saving shrunk_greyscale image");
}
}
}
Expand Down
111 changes: 111 additions & 0 deletions src/format/bitmap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <algorithm> // For std::min, std::max (potentially)
#include <stdexcept> // For robust error checking if needed beyond enums
#include <limits> // Required for std::numeric_limits
#include <fstream> // For std::ifstream
#include <string> // For std::string

// Own project includes
#include "../../include/bitmap.hpp" // For BmpTool::Bitmap, Result, BitmapError
Expand All @@ -26,6 +28,31 @@ namespace BmpTool {

// The BmpTool::Format::Internal namespace and its functions are removed as they are no longer used.

Result<Bitmap, BitmapError> load(const std::string& filepath) {
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
return BitmapError::IoError;
}

std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);

if (size == 0) { // Handle empty file case
file.close();
return BitmapError::InvalidImageData; // Or NotABmp / InvalidFileHeader
}

std::vector<uint8_t> buffer(static_cast<size_t>(size));
if (!file.read(reinterpret_cast<char*>(buffer.data()), size)) {
file.close();
return BitmapError::IoError;
}

file.close();

return load(std::span<const uint8_t>(buffer.data(), buffer.size()));
}

Result<Bitmap, BitmapError> load(std::span<const uint8_t> bmp_data) {
// 1. Read BITMAPFILEHEADER and BITMAPINFOHEADER
if (bmp_data.size() < sizeof(BITMAPFILEHEADER)) {
Expand Down Expand Up @@ -354,6 +381,90 @@ void internal_swizzle_rgba_to_bgra_simd(const uint8_t* src_rgba_data, ::Pixel* d
}
}

Result<void, BitmapError> save(const Bitmap& bitmap_in, const std::string& filepath) {
// 1. Input Validation
if (bitmap_in.w == 0 || bitmap_in.h == 0) {
return BitmapError::InvalidImageData;
}
if (bitmap_in.bpp != 32) {
return BitmapError::UnsupportedBpp;
}
// Calculate expected data size for RGBA
// Prevent overflow when calculating expected_input_data_size
if (bitmap_in.h > 0 && bitmap_in.w > (std::numeric_limits<uint32_t>::max() / bitmap_in.h / 4) ) {
return BitmapError::InvalidImageData; // Output image dimensions too large for uint32_t calculation
}
const size_t expected_input_data_size = static_cast<size_t>(bitmap_in.w) * bitmap_in.h * 4;
if (bitmap_in.data.empty() || bitmap_in.data.size() < expected_input_data_size) {
return BitmapError::InvalidImageData;
}

// 2. Estimate buffer size
// A common size for BITMAPFILEHEADER is 14 bytes, BITMAPINFOHEADER is 40 bytes. Total 54.
// Max pixel data size. Add some padding just in case, though CreateBitmapFromMatrix output should be tight.
// uint32_t max_pixel_data_size = bitmap_in.w * bitmap_in.h * 4; // Assuming 4 bytes per pixel
// uint32_t estimated_buffer_size = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + max_pixel_data_size + (bitmap_in.h * 4); // Extra padding per row

// Let's use a more direct estimation based on how the span save works, it relies on CreateBitmapFromMatrix
// which sets the correct BITMAPFILEHEADER.bfSize.
// The underlying save(span) will return OutputBufferTooSmall if this is too small.
// A generous estimate: header sizes + data size + a bit for row padding (though 32bpp usually has no row padding if w is multiple of 4 pixels).
// The previous save function uses `sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + temp_bmp_file.bitmapData.size()`.
// `temp_bmp_file.bitmapData.size()` is the critical part. This is `padded_row_size * height`.
// For 32bpp (4 bytes/pixel), `unpadded_row_size = w * 4`. `padded_row_size = (unpadded_row_size + 3) & ~3`.
// So, `padded_row_size` is `w * 4` if `w*4` is a multiple of 4 (always true). So no padding for 32bpp.
// Thus, pixel data size is `w * h * 4`.
size_t estimated_buffer_size = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + expected_input_data_size;


std::vector<uint8_t> temp_buffer(estimated_buffer_size);

// 3. Call span-based save
Result<void, BitmapError> save_result = save(bitmap_in, std::span<uint8_t>(temp_buffer.data(), temp_buffer.size()));

if (save_result.isError()) {
// If it's OutputBufferTooSmall, our initial estimate was wrong, which is unlikely for 32bpp
// as it usually doesn't require padding that would exceed w*h*4.
// However, it's good to propagate the error.
return save_result.error();
}

// 4. Get actual BMP size from header
BITMAPFILEHEADER fh;
std::memcpy(&fh, temp_buffer.data(), sizeof(BITMAPFILEHEADER));
size_t actual_bmp_size = fh.bfSize;

// 5. Validate actual_bmp_size
if (actual_bmp_size == 0 || actual_bmp_size > temp_buffer.size() || actual_bmp_size < (sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER))) {
// If bfSize is 0, or larger than our buffer (should be caught by span save's OutputBufferTooSmall),
// or smaller than minimum header size, something is wrong.
return BitmapError::UnknownError;
}

// 6. Open output file
std::ofstream file(filepath, std::ios::binary | std::ios::trunc); // Truncate if file exists
if (!file.is_open()) {
return BitmapError::IoError;
}

// 7. Write actual data to file
file.write(reinterpret_cast<const char*>(temp_buffer.data()), actual_bmp_size);

if (!file.good()) { // Check for write errors
file.close();
// Attempt to remove partially written file
// std::remove(filepath.c_str()); // Optional: consider error handling for remove
return BitmapError::IoError;
}

// 8. Close file
file.close();

// 9. Return success
return Success{};
}


// Implementation of image manipulation functions

Result<Bitmap, BitmapError> shrink(const Bitmap& bmp_tool_bitmap, int scaleFactor) {
Expand Down