Skip to content

Commit 96fac3e

Browse files
authored
Merge pull request #15 from JacobBorden/feat/add-file-io-overloads
feat: Add file I/O overloads for BmpTool API
2 parents b4035fd + 7bc721d commit 96fac3e

3 files changed

Lines changed: 158 additions & 94 deletions

File tree

include/bitmap.hpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,16 @@ struct Bitmap {
182182
*/
183183
Result<Bitmap, BitmapError> load(std::span<const uint8_t> bmp_data);
184184

185+
/**
186+
* @brief Loads a bitmap from a file path.
187+
*
188+
* Opens and reads the BMP file from the given path, then constructs a Bitmap object.
189+
*
190+
* @param filepath The path to the BMP file.
191+
* @return A Result object containing either a Bitmap on success or a BitmapError on failure.
192+
*/
193+
Result<Bitmap, BitmapError> load(const std::string& filepath);
194+
185195
/**
186196
* @brief Saves a Bitmap object to a memory span as BMP file data.
187197
*
@@ -194,6 +204,17 @@ Result<Bitmap, BitmapError> load(std::span<const uint8_t> bmp_data);
194204
*/
195205
Result<void, BitmapError> save(const Bitmap& bitmap, std::span<uint8_t> out_bmp_buffer);
196206

207+
/**
208+
* @brief Saves a Bitmap object to a file path.
209+
*
210+
* Converts the Bitmap object into the BMP file format and writes it to the specified file.
211+
*
212+
* @param bitmap The Bitmap object to save.
213+
* @param filepath The path to the file where the BMP data will be saved.
214+
* @return A Result object containing Success on success or a BitmapError on failure.
215+
*/
216+
Result<void, BitmapError> save(const Bitmap& bitmap, const std::string& filepath);
217+
197218
/**
198219
* @brief Shrinks the image by a given scale factor.
199220
* @param bitmap The input bitmap.

main.cpp

Lines changed: 26 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,9 @@
11
#include "../../include/bitmap.hpp" // Using BmpTool API
2-
#include "../../src/bitmapfile/bitmap_file.h" // For BITMAPFILEHEADER for writeFile logic
32

43
#include <iostream> // For std::cout, std::cerr
5-
#include <vector> // For std::vector
4+
#include <vector> // For std::vector (used by BmpTool::Bitmap)
65
#include <string> // For std::string
7-
#include <fstream> // For std::ifstream, std::ofstream
8-
#include <cstdint> // For uint8_t etc.
9-
#include <span> // For std::span
10-
#include <cstring> // For std::memcpy
11-
12-
// File I/O Utilities
13-
std::vector<uint8_t> readFile(const std::string& filename) {
14-
std::ifstream file(filename, std::ios::binary | std::ios::ate);
15-
if (!file.is_open()) {
16-
std::cerr << "Error: Could not open file for reading: " << filename << std::endl;
17-
return {};
18-
}
19-
std::streamsize size = file.tellg();
20-
file.seekg(0, std::ios::beg);
21-
std::vector<uint8_t> buffer(static_cast<size_t>(size)); // Ensure size_t for vector constructor
22-
if (file.read(reinterpret_cast<char*>(buffer.data()), size)) {
23-
return buffer;
24-
}
25-
std::cerr << "Error: Could not read file: " << filename << std::endl;
26-
return {};
27-
}
28-
29-
bool writeFile(const std::string& filename, const std::vector<uint8_t>& data) {
30-
std::ofstream file(filename, std::ios::binary);
31-
if (!file.is_open()) {
32-
std::cerr << "Error: Could not open file for writing: " << filename << std::endl;
33-
return false;
34-
}
35-
file.write(reinterpret_cast<const char*>(data.data()), data.size());
36-
if (!file.good()) {
37-
std::cerr << "Error: Could not write all data to file: " << filename << std::endl;
38-
return false;
39-
}
40-
return true;
41-
}
6+
#include <cstdint> // For uint8_t etc. (used by BmpTool::Bitmap)
427

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

56-
std::vector<uint8_t> file_data = readFile(inputFilename);
57-
if (file_data.empty()) {
21+
BmpTool::Result<BmpTool::Bitmap, BmpTool::BitmapError> load_result = BmpTool::load(inputFilename);
22+
if (!load_result.isSuccess()) {
5823
std::cerr << "********************************************************************************" << std::endl;
59-
std::cerr << "Error: Could not read '" << inputFilename << "'." << std::endl;
60-
std::cerr << "Please ensure this file exists in the same directory as the executable." << std::endl;
24+
std::cerr << "Error: Could not load '" << inputFilename << "' using BmpTool::load." << std::endl;
25+
std::cerr << "Please ensure this file exists in the same directory as the executable and is a valid BMP." << std::endl;
6126
std::cerr << "You can copy any BMP image (e.g., from Windows, or download one) and rename it to 'test.bmp'." << std::endl;
6227
std::cerr << "A common source of BMP files is MS Paint (save as BMP)." << std::endl;
6328
std::cerr << "********************************************************************************" << std::endl;
64-
return 1;
65-
}
66-
67-
BmpTool::Result<BmpTool::Bitmap, BmpTool::BitmapError> load_result = BmpTool::load(file_data);
68-
if (!load_result.isSuccess()) {
6929
printError(load_result.error(), "loading " + inputFilename);
7030
return 1;
7131
}
@@ -80,19 +40,12 @@ int main() {
8040
auto invert_result = BmpTool::invertColors(originalBitmap);
8141
if (invert_result.isSuccess()) {
8242
currentBitmap = invert_result.value();
83-
std::vector<uint8_t> output_buffer(54 + currentBitmap.w * currentBitmap.h * 4); // Estimate
84-
auto save_res = BmpTool::save(currentBitmap, output_buffer);
85-
if (save_res.isSuccess()) {
86-
BITMAPFILEHEADER fh_out;
87-
std::memcpy(&fh_out, output_buffer.data(), sizeof(BITMAPFILEHEADER));
88-
std::vector<uint8_t> actual_data(output_buffer.begin(), output_buffer.begin() + fh_out.bfSize);
89-
if (writeFile("output_inverted.bmp", actual_data)) {
90-
std::cout << "Saved: output_inverted.bmp" << std::endl;
91-
} else {
92-
std::cerr << "Error writing output_inverted.bmp" << std::endl;
93-
}
43+
auto save_res_file = BmpTool::save(currentBitmap, "output_inverted.bmp");
44+
if (save_res_file.isSuccess()) {
45+
std::cout << "Saved: output_inverted.bmp" << std::endl;
9446
} else {
95-
printError(save_res.error(), "saving inverted image");
47+
std::cerr << "Error writing output_inverted.bmp directly to file." << std::endl;
48+
printError(save_res_file.error(), "saving inverted image");
9649
}
9750
} else {
9851
printError(invert_result.error(), "inverting colors");
@@ -104,19 +57,12 @@ int main() {
10457
auto sepia_result = BmpTool::applySepiaTone(originalBitmap);
10558
if (sepia_result.isSuccess()) {
10659
currentBitmap = sepia_result.value();
107-
std::vector<uint8_t> output_buffer(54 + currentBitmap.w * currentBitmap.h * 4);
108-
auto save_res = BmpTool::save(currentBitmap, output_buffer);
109-
if (save_res.isSuccess()) {
110-
BITMAPFILEHEADER fh_out;
111-
std::memcpy(&fh_out, output_buffer.data(), sizeof(BITMAPFILEHEADER));
112-
std::vector<uint8_t> actual_data(output_buffer.begin(), output_buffer.begin() + fh_out.bfSize);
113-
if (writeFile("output_sepia.bmp", actual_data)) {
114-
std::cout << "Saved: output_sepia.bmp" << std::endl;
115-
} else {
116-
std::cerr << "Error writing output_sepia.bmp" << std::endl;
117-
}
60+
auto save_res_file = BmpTool::save(currentBitmap, "output_sepia.bmp");
61+
if (save_res_file.isSuccess()) {
62+
std::cout << "Saved: output_sepia.bmp" << std::endl;
11863
} else {
119-
printError(save_res.error(), "saving sepia image");
64+
std::cerr << "Error writing output_sepia.bmp directly to file." << std::endl;
65+
printError(save_res_file.error(), "saving sepia image");
12066
}
12167
} else {
12268
printError(sepia_result.error(), "applying sepia tone");
@@ -146,19 +92,12 @@ int main() {
14692
tempProcessedBitmap = contrast_result.value();
14793
std::cout << "Step 2: Contrast increased." << std::endl;
14894

149-
std::vector<uint8_t> output_buffer(54 + tempProcessedBitmap.w * tempProcessedBitmap.h * 4);
150-
auto save_res = BmpTool::save(tempProcessedBitmap, output_buffer);
151-
if (save_res.isSuccess()) {
152-
BITMAPFILEHEADER fh_out;
153-
std::memcpy(&fh_out, output_buffer.data(), sizeof(BITMAPFILEHEADER));
154-
std::vector<uint8_t> actual_data(output_buffer.begin(), output_buffer.begin() + fh_out.bfSize);
155-
if (writeFile("output_blurred_contrasted.bmp", actual_data)) {
156-
std::cout << "Saved: output_blurred_contrasted.bmp" << std::endl;
157-
} else {
158-
std::cerr << "Error writing output_blurred_contrasted.bmp" << std::endl;
159-
}
95+
auto save_res_file = BmpTool::save(tempProcessedBitmap, "output_blurred_contrasted.bmp");
96+
if (save_res_file.isSuccess()) {
97+
std::cout << "Saved: output_blurred_contrasted.bmp" << std::endl;
16098
} else {
161-
printError(save_res.error(), "saving blurred_contrasted image");
99+
std::cerr << "Error writing output_blurred_contrasted.bmp directly to file." << std::endl;
100+
printError(save_res_file.error(), "saving blurred_contrasted image");
162101
}
163102
}
164103
}
@@ -185,19 +124,12 @@ int main() {
185124
tempProcessedBitmap = greyscale_result.value();
186125
std::cout << "Step 2: Greyscale applied." << std::endl;
187126

188-
std::vector<uint8_t> output_buffer(54 + tempProcessedBitmap.w * tempProcessedBitmap.h * 4);
189-
auto save_res = BmpTool::save(tempProcessedBitmap, output_buffer);
190-
if (save_res.isSuccess()) {
191-
BITMAPFILEHEADER fh_out;
192-
std::memcpy(&fh_out, output_buffer.data(), sizeof(BITMAPFILEHEADER));
193-
std::vector<uint8_t> actual_data(output_buffer.begin(), output_buffer.begin() + fh_out.bfSize);
194-
if(writeFile("output_shrunk_greyscale.bmp", actual_data)){
195-
std::cout << "Saved: output_shrunk_greyscale.bmp" << std::endl;
196-
} else {
197-
std::cerr << "Error writing output_shrunk_greyscale.bmp" << std::endl;
198-
}
127+
auto save_res_file = BmpTool::save(tempProcessedBitmap, "output_shrunk_greyscale.bmp");
128+
if (save_res_file.isSuccess()) {
129+
std::cout << "Saved: output_shrunk_greyscale.bmp" << std::endl;
199130
} else {
200-
printError(save_res.error(), "saving shrunk_greyscale image");
131+
std::cerr << "Error writing output_shrunk_greyscale.bmp directly to file." << std::endl;
132+
printError(save_res_file.error(), "saving shrunk_greyscale image");
201133
}
202134
}
203135
}

src/format/bitmap.cpp

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#include <algorithm> // For std::min, std::max (potentially)
55
#include <stdexcept> // For robust error checking if needed beyond enums
66
#include <limits> // Required for std::numeric_limits
7+
#include <fstream> // For std::ifstream
8+
#include <string> // For std::string
79

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

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

31+
Result<Bitmap, BitmapError> load(const std::string& filepath) {
32+
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
33+
if (!file.is_open()) {
34+
return BitmapError::IoError;
35+
}
36+
37+
std::streamsize size = file.tellg();
38+
file.seekg(0, std::ios::beg);
39+
40+
if (size == 0) { // Handle empty file case
41+
file.close();
42+
return BitmapError::InvalidImageData; // Or NotABmp / InvalidFileHeader
43+
}
44+
45+
std::vector<uint8_t> buffer(static_cast<size_t>(size));
46+
if (!file.read(reinterpret_cast<char*>(buffer.data()), size)) {
47+
file.close();
48+
return BitmapError::IoError;
49+
}
50+
51+
file.close();
52+
53+
return load(std::span<const uint8_t>(buffer.data(), buffer.size()));
54+
}
55+
2956
Result<Bitmap, BitmapError> load(std::span<const uint8_t> bmp_data) {
3057
// 1. Read BITMAPFILEHEADER and BITMAPINFOHEADER
3158
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
354381
}
355382
}
356383

384+
Result<void, BitmapError> save(const Bitmap& bitmap_in, const std::string& filepath) {
385+
// 1. Input Validation
386+
if (bitmap_in.w == 0 || bitmap_in.h == 0) {
387+
return BitmapError::InvalidImageData;
388+
}
389+
if (bitmap_in.bpp != 32) {
390+
return BitmapError::UnsupportedBpp;
391+
}
392+
// Calculate expected data size for RGBA
393+
// Prevent overflow when calculating expected_input_data_size
394+
if (bitmap_in.h > 0 && bitmap_in.w > (std::numeric_limits<uint32_t>::max() / bitmap_in.h / 4) ) {
395+
return BitmapError::InvalidImageData; // Output image dimensions too large for uint32_t calculation
396+
}
397+
const size_t expected_input_data_size = static_cast<size_t>(bitmap_in.w) * bitmap_in.h * 4;
398+
if (bitmap_in.data.empty() || bitmap_in.data.size() < expected_input_data_size) {
399+
return BitmapError::InvalidImageData;
400+
}
401+
402+
// 2. Estimate buffer size
403+
// A common size for BITMAPFILEHEADER is 14 bytes, BITMAPINFOHEADER is 40 bytes. Total 54.
404+
// Max pixel data size. Add some padding just in case, though CreateBitmapFromMatrix output should be tight.
405+
// uint32_t max_pixel_data_size = bitmap_in.w * bitmap_in.h * 4; // Assuming 4 bytes per pixel
406+
// uint32_t estimated_buffer_size = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + max_pixel_data_size + (bitmap_in.h * 4); // Extra padding per row
407+
408+
// Let's use a more direct estimation based on how the span save works, it relies on CreateBitmapFromMatrix
409+
// which sets the correct BITMAPFILEHEADER.bfSize.
410+
// The underlying save(span) will return OutputBufferTooSmall if this is too small.
411+
// 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).
412+
// The previous save function uses `sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + temp_bmp_file.bitmapData.size()`.
413+
// `temp_bmp_file.bitmapData.size()` is the critical part. This is `padded_row_size * height`.
414+
// For 32bpp (4 bytes/pixel), `unpadded_row_size = w * 4`. `padded_row_size = (unpadded_row_size + 3) & ~3`.
415+
// So, `padded_row_size` is `w * 4` if `w*4` is a multiple of 4 (always true). So no padding for 32bpp.
416+
// Thus, pixel data size is `w * h * 4`.
417+
size_t estimated_buffer_size = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + expected_input_data_size;
418+
419+
420+
std::vector<uint8_t> temp_buffer(estimated_buffer_size);
421+
422+
// 3. Call span-based save
423+
Result<void, BitmapError> save_result = save(bitmap_in, std::span<uint8_t>(temp_buffer.data(), temp_buffer.size()));
424+
425+
if (save_result.isError()) {
426+
// If it's OutputBufferTooSmall, our initial estimate was wrong, which is unlikely for 32bpp
427+
// as it usually doesn't require padding that would exceed w*h*4.
428+
// However, it's good to propagate the error.
429+
return save_result.error();
430+
}
431+
432+
// 4. Get actual BMP size from header
433+
BITMAPFILEHEADER fh;
434+
std::memcpy(&fh, temp_buffer.data(), sizeof(BITMAPFILEHEADER));
435+
size_t actual_bmp_size = fh.bfSize;
436+
437+
// 5. Validate actual_bmp_size
438+
if (actual_bmp_size == 0 || actual_bmp_size > temp_buffer.size() || actual_bmp_size < (sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER))) {
439+
// If bfSize is 0, or larger than our buffer (should be caught by span save's OutputBufferTooSmall),
440+
// or smaller than minimum header size, something is wrong.
441+
return BitmapError::UnknownError;
442+
}
443+
444+
// 6. Open output file
445+
std::ofstream file(filepath, std::ios::binary | std::ios::trunc); // Truncate if file exists
446+
if (!file.is_open()) {
447+
return BitmapError::IoError;
448+
}
449+
450+
// 7. Write actual data to file
451+
file.write(reinterpret_cast<const char*>(temp_buffer.data()), actual_bmp_size);
452+
453+
if (!file.good()) { // Check for write errors
454+
file.close();
455+
// Attempt to remove partially written file
456+
// std::remove(filepath.c_str()); // Optional: consider error handling for remove
457+
return BitmapError::IoError;
458+
}
459+
460+
// 8. Close file
461+
file.close();
462+
463+
// 9. Return success
464+
return Success{};
465+
}
466+
467+
357468
// Implementation of image manipulation functions
358469

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

0 commit comments

Comments
 (0)