diff --git a/include/bitmap.hpp b/include/bitmap.hpp index 0acf96c..b73b829 100644 --- a/include/bitmap.hpp +++ b/include/bitmap.hpp @@ -182,6 +182,16 @@ struct Bitmap { */ Result load(std::span 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 load(const std::string& filepath); + /** * @brief Saves a Bitmap object to a memory span as BMP file data. * @@ -194,6 +204,17 @@ Result load(std::span bmp_data); */ Result save(const Bitmap& bitmap, std::span 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 save(const Bitmap& bitmap, const std::string& filepath); + /** * @brief Shrinks the image by a given scale factor. * @param bitmap The input bitmap. diff --git a/main.cpp b/main.cpp index 3807d6e..f4db064 100644 --- a/main.cpp +++ b/main.cpp @@ -1,44 +1,9 @@ #include "../../include/bitmap.hpp" // Using BmpTool API -#include "../../src/bitmapfile/bitmap_file.h" // For BITMAPFILEHEADER for writeFile logic #include // For std::cout, std::cerr -#include // For std::vector +#include // For std::vector (used by BmpTool::Bitmap) #include // For std::string -#include // For std::ifstream, std::ofstream -#include // For uint8_t etc. -#include // For std::span -#include // For std::memcpy - -// File I/O Utilities -std::vector 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 buffer(static_cast(size)); // Ensure size_t for vector constructor - if (file.read(reinterpret_cast(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& 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(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 // For uint8_t etc. (used by BmpTool::Bitmap) // Helper to print BmpTool errors void printError(BmpTool::BitmapError error, const std::string& operation_name) { @@ -53,19 +18,14 @@ int main() { std::cout << "---------------------------------------" << std::endl; std::cout << "Attempting to load image: " << inputFilename << std::endl; - std::vector file_data = readFile(inputFilename); - if (file_data.empty()) { + BmpTool::Result 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 load_result = BmpTool::load(file_data); - if (!load_result.isSuccess()) { printError(load_result.error(), "loading " + inputFilename); return 1; } @@ -80,19 +40,12 @@ int main() { auto invert_result = BmpTool::invertColors(originalBitmap); if (invert_result.isSuccess()) { currentBitmap = invert_result.value(); - std::vector 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 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"); @@ -104,19 +57,12 @@ int main() { auto sepia_result = BmpTool::applySepiaTone(originalBitmap); if (sepia_result.isSuccess()) { currentBitmap = sepia_result.value(); - std::vector 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 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"); @@ -146,19 +92,12 @@ int main() { tempProcessedBitmap = contrast_result.value(); std::cout << "Step 2: Contrast increased." << std::endl; - std::vector 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 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"); } } } @@ -185,19 +124,12 @@ int main() { tempProcessedBitmap = greyscale_result.value(); std::cout << "Step 2: Greyscale applied." << std::endl; - std::vector 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 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"); } } } diff --git a/src/format/bitmap.cpp b/src/format/bitmap.cpp index 0135392..7cad209 100644 --- a/src/format/bitmap.cpp +++ b/src/format/bitmap.cpp @@ -4,6 +4,8 @@ #include // For std::min, std::max (potentially) #include // For robust error checking if needed beyond enums #include // Required for std::numeric_limits +#include // For std::ifstream +#include // For std::string // Own project includes #include "../../include/bitmap.hpp" // For BmpTool::Bitmap, Result, BitmapError @@ -26,6 +28,31 @@ namespace BmpTool { // The BmpTool::Format::Internal namespace and its functions are removed as they are no longer used. +Result 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 buffer(static_cast(size)); + if (!file.read(reinterpret_cast(buffer.data()), size)) { + file.close(); + return BitmapError::IoError; + } + + file.close(); + + return load(std::span(buffer.data(), buffer.size())); +} + Result load(std::span bmp_data) { // 1. Read BITMAPFILEHEADER and BITMAPINFOHEADER if (bmp_data.size() < sizeof(BITMAPFILEHEADER)) { @@ -354,6 +381,90 @@ void internal_swizzle_rgba_to_bgra_simd(const uint8_t* src_rgba_data, ::Pixel* d } } +Result 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::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(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 temp_buffer(estimated_buffer_size); + + // 3. Call span-based save + Result save_result = save(bitmap_in, std::span(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(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 shrink(const Bitmap& bmp_tool_bitmap, int scaleFactor) {