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
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

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

## [0.2.0] - 2024-07-27

### Changed
- Reorganized project structure: moved `bitmap.h` and `bitmap.cpp` to `src/bitmap/`. Updated include paths and CMakeLists.txt accordingly.
- Integrated `matrix` and `bitmapfile` modules directly into `src/` instead of a separate `dependencies` folder.

### Added
- Comprehensive unit tests for the `Bitmap::File` module (`tests/test_bitmap_file.cpp`), covering constructors, file operations (open, save, saveas), and validation logic.
- Comprehensive unit tests for the `Matrix` module (`tests/test_matrix.cpp`), covering constructors, arithmetic operations, manipulations (transpose, inverse, determinant), and merge/split functionality.

### Fixed
- Corrected include paths in various files to reflect the new project structure.
- Updated CMakeLists.txt files to correctly locate and build all source files and tests under the new structure.

## [0.1.0] - 2025-05-23

### Added
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ enable_testing()

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

Expand Down
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,14 @@ The library supports the following image manipulation functions:
## Dependencies

This library utilizes:
* A simple Matrix library (from `dependencies/matrix`)
* A BMP file handling library (from `dependencies/bitmapfile`)
These are included in the `dependencies` directory.
* A simple Matrix library (from `src/matrix`)
* A BMP file handling library (from `src/bitmapfile`)
These are now part of the main source tree under the `src/` directory.

## Core Modules
* **Bitmap Library (`src/bitmap`)**: Provides core image processing functions.
* **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.

## Building the Project

Expand All @@ -58,7 +63,7 @@ ctest
## Basic Usage Example

```cpp
#include "src/bitmap.h" // Adjust path if necessary
#include "bitmap/bitmap.h" // Adjust path if necessary, assumes src/ is an include dir
#include <iostream>

int main() {
Expand Down
2 changes: 1 addition & 1 deletion main.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "src/bitmap.h" // Adjust path if necessary, assumes bitmap.h is in src/
#include "bitmap/bitmap.h" // Adjust path if necessary, assumes bitmap.h is in src/
#include <iostream> // For std::cout, std::cerr

int main() {
Expand Down
File renamed without changes.
4 changes: 2 additions & 2 deletions src/bitmap.h → src/bitmap/bitmap.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#ifndef _BITMAP_
#define _BITMAP_

#include "matrix/matrix.h"
#include "bitmapfile/bitmap_file.h"
#include "../matrix/matrix.h"
#include "../bitmapfile/bitmap_file.h"
#include <cmath> // For mathematical operations like sqrt, pow.
#include <algorithm> // For std::min, std::max.

Expand Down
178 changes: 136 additions & 42 deletions src/bitmapfile/bitmap_file.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
#include "bitmap_file.h"
#include <fstream> // Required for std::ofstream and std::ifstream
#include <fstream>
#include <vector>
#include <cmath> // For std::abs
#include <iostream> // For debugging, remove later

Bitmap::File::File()
Bitmap::File::File() : isValid(false)
{
}

Bitmap::File::File(std::string filename)
Bitmap::File::File(std::string filename) : isValid(false)
{
Bitmap::File::Open(filename);
Open(filename);
}

Bitmap::File::~File()
Expand All @@ -16,33 +19,71 @@ Bitmap::File::~File()

bool Bitmap::File::Save()
{
if (!Bitmap::File::isValid)
{
if (!isValid) {
return false;
}
if (bitmapFilename.empty()) {
return false;
}
// Basic validation for image properties that affect saving
if (bitmapInfoHeader.biWidth <= 0 || bitmapInfoHeader.biHeight == 0 ||
(bitmapInfoHeader.biBitCount != 24 && bitmapInfoHeader.biBitCount != 32)) {
// std::cerr << "Save error: Invalid image dimensions or bit count for saving." << std::endl;
return false;
}


std::ofstream file(bitmapFilename, std::ios::binary);
if (!file)
{
if (!file) {
return false;
}

file.write(reinterpret_cast<char*>(&bitmapFileHeader), sizeof(BITMAPFILEHEADER));
if (!file)
{
uint32_t bytesPerPixel = bitmapInfoHeader.biBitCount / 8;
// This check is technically redundant due to the one at the start of the function, but kept for safety.
if (bytesPerPixel == 0) {
return false;
}

file.write(reinterpret_cast<char*>(&bitmapInfoHeader), sizeof(BITMAPINFOHEADER));
if (!file)
{
uint32_t rowBytesUnpadded = bitmapInfoHeader.biWidth * bytesPerPixel;
uint32_t rowBytesPadded = (rowBytesUnpadded + 3) & ~3;
uint32_t paddingPerRow = rowBytesPadded - rowBytesUnpadded;

// Prepare headers for writing
BITMAPFILEHEADER writeFileHeader = bitmapFileHeader;
BITMAPINFOHEADER writeInfoHeader = bitmapInfoHeader;

writeInfoHeader.biSizeImage = rowBytesPadded * std::abs(writeInfoHeader.biHeight);
writeFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
writeFileHeader.bfSize = writeFileHeader.bfOffBits + writeInfoHeader.biSizeImage;
writeFileHeader.bfType = 0x4D42; // 'BM'
writeFileHeader.bfReserved1 = 0;
writeFileHeader.bfReserved2 = 0;


file.write(reinterpret_cast<const char*>(&writeFileHeader), sizeof(BITMAPFILEHEADER));
if (!file) { return false; }

file.write(reinterpret_cast<const char*>(&writeInfoHeader), sizeof(BITMAPINFOHEADER));
if (!file) { return false; }

const unsigned char* current_pixel_data = bitmapData.data();
// Ensure bitmapData has enough data for what headers claim
if (bitmapData.size() < std::abs(writeInfoHeader.biHeight) * rowBytesUnpadded) {
// std::cerr << "Save error: bitmapData size is insufficient for declared dimensions." << std::endl;
return false;
}

file.write(reinterpret_cast<char*>(bitmapData.data()), bitmapData.size());
if (!file)
{
return false;
unsigned char padding_bytes[3] = {0,0,0};

for (int i = 0; i < std::abs(writeInfoHeader.biHeight); ++i) {
file.write(reinterpret_cast<const char*>(current_pixel_data), rowBytesUnpadded);
if (!file) { return false; }

if (paddingPerRow > 0) {
file.write(reinterpret_cast<const char*>(padding_bytes), paddingPerRow);
if (!file) { return false; }
}
current_pixel_data += rowBytesUnpadded;
}

file.close();
Expand All @@ -52,45 +93,98 @@ bool Bitmap::File::Save()
bool Bitmap::File::SaveAs(std::string filename)
{
Bitmap::File::bitmapFilename = filename;
return Bitmap::File::Save(); // Now SaveAs can just call Save
return Bitmap::File::Save();
}

bool Bitmap::File::Open(std::string filename)
{
isValid = false;
Bitmap::File::bitmapFilename = filename;
std::ifstream file(bitmapFilename, std::ios::binary);
if (!file)
{
isValid = false;
if (!file) {
return false;
}

file.read(reinterpret_cast<char*>(&bitmapFileHeader), sizeof(BITMAPFILEHEADER));
if (!file)
{
isValid = false;
return false;
if (!file || file.gcount() != sizeof(BITMAPFILEHEADER)) {
file.close(); return false;
}

// Check for 'BM' signature
if (bitmapFileHeader.bfType != 0x4D42) { // 'BM' in hex
isValid = false;
return false;
if (bitmapFileHeader.bfType != 0x4D42) {
file.close(); return false;
}

file.read(reinterpret_cast<char*>(&bitmapInfoHeader), sizeof(BITMAPINFOHEADER));
if (!file)
{
isValid = false;
return false;
if (!file || file.gcount() != sizeof(BITMAPINFOHEADER)) {
file.close(); return false;
}

bitmapData.resize(bitmapFileHeader.bfSize - bitmapFileHeader.bfOffBits);
file.read(reinterpret_cast<char*>(bitmapData.data()), bitmapData.size());
if (!file)
{
isValid = false;
return false;
if (bitmapInfoHeader.biWidth <= 0 || bitmapInfoHeader.biHeight == 0 ||
(bitmapInfoHeader.biBitCount != 24 && bitmapInfoHeader.biBitCount != 32)) {
file.close(); return false;
}

uint32_t bytesPerPixel = bitmapInfoHeader.biBitCount / 8;

uint32_t rowBytesUnpadded = bitmapInfoHeader.biWidth * bytesPerPixel;
uint32_t rowBytesPadded = (rowBytesUnpadded + 3) & ~3;
uint32_t paddingPerRow = rowBytesPadded - rowBytesUnpadded;

uint32_t expectedBiSizeImage = rowBytesPadded * std::abs(bitmapInfoHeader.biHeight);
if (bitmapInfoHeader.biCompression == 0 ) {
if (bitmapInfoHeader.biSizeImage == 0) {
bitmapInfoHeader.biSizeImage = expectedBiSizeImage;
} else if (bitmapInfoHeader.biSizeImage != expectedBiSizeImage) {
// Discrepancy. This could be an issue.
// For now, we'll trust calculated actualPixelDataSize for resizing bitmapData.
}
}

// Optional: More robust check for bfSize against expected values
// if (bitmapFileHeader.bfSize != bitmapFileHeader.bfOffBits + bitmapInfoHeader.biSizeImage) {
// // Potentially problematic.
// }

uint32_t actualPixelDataSize = std::abs(bitmapInfoHeader.biHeight) * rowBytesUnpadded;
bitmapData.resize(actualPixelDataSize);

if (actualPixelDataSize == 0) {
// This handles images with 0 width or 0 height correctly.
// An "empty" image (0 pixels) can be considered valid.
isValid = true;
file.close();
return true;
}

file.seekg(bitmapFileHeader.bfOffBits, std::ios::beg);
if (!file) {
bitmapData.clear(); file.close(); return false;
}

unsigned char* currentRowDest = bitmapData.data();
unsigned char padding_buffer[3] = {0,0,0};

for (int i = 0; i < std::abs(bitmapInfoHeader.biHeight); ++i) {
file.read(reinterpret_cast<char*>(currentRowDest), rowBytesUnpadded);
if (!file || file.gcount() != rowBytesUnpadded) {
bitmapData.clear(); file.close(); return false;
}

if (paddingPerRow > 0) {
file.read(reinterpret_cast<char*>(padding_buffer), paddingPerRow);
// EOF is only okay if it's the very last read of the file.
// gcount() != paddingPerRow could be true if EOF was hit mid-padding.
if (file.gcount() != paddingPerRow && !file.eof()) {
bitmapData.clear(); file.close(); return false;
}
// If gcount is less than paddingPerRow but it is EOF, it implies a truncated file.
// This might be an error condition depending on strictness. For now, accept if pixels are read.
if (file.gcount() < paddingPerRow && file.eof() && i < (std::abs(bitmapInfoHeader.biHeight) -1) ){
// If EOF hit during padding but it's not the last row, file is truncated.
bitmapData.clear(); file.close(); return false;
}
}
currentRowDest += rowBytesUnpadded;
}

file.close();
Expand All @@ -103,12 +197,12 @@ void Bitmap::File::Rename(std::string filename)
Bitmap::File::bitmapFilename = filename;
}

std::string Bitmap::File::Filename()
std::string Bitmap::File::Filename() // Removed const
{
return Bitmap::File::bitmapFilename;
}

bool Bitmap::File::IsValid()
bool Bitmap::File::IsValid() // Removed const
{
return Bitmap::File::isValid;
}
Expand Down
Loading