diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0170990 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,28 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [0.1.0] - 2025-05-23 + +### Added +- New image manipulation functions: + - `InvertImageColors` / `InvertPixelColor` + - `ApplySepiaTone` / `ApplySepiaToPixel` + - `ApplyBoxBlur` +- Comprehensive unit testing framework (`tests/test_bitmap.cpp`) using basic assertions. +- Unit tests for most pixel-level functions (Greyscale, Invert, Sepia, Brightness, Contrast, Saturation, Luminance - including color-specific variants). +- Unit tests for image-level functions (`ApplyBoxBlur`, `ShrinkImage`, `RotateImageCounterClockwise`, and basic checks for others). +- This `README.md` and `CHANGELOG.md` file. + +### Changed +- Refined contrast adjustment logic for secondary colors (`ChangePixelContrastMagenta`, `ChangePixelContrastYellow`, `ChangePixelContrastCyan`) to apply contrast to constituent primary channels directly. +- Improved inline code documentation (comments) in `src/bitmap.h` and `src/bitmap.cpp` for clarity and maintainability. +- Standardized clamping of pixel component values (0-255) in various functions. +- Corrected `std::min` usage in saturation and luminance functions. + +### Fixed +- Corrected syntax errors in the original implementations of `ChangePixelContrastMagenta` and `ChangePixelContrastCyan`. +- Corrected variable name usage (`magenta` vs `cyan`) in `ChangePixelContrastCyan`. +- Improved GDI resource management in `ScreenShotWindow` by ensuring `DeleteDC`, `ReleaseDC`, and `DeleteObject` are called. +- Corrected a typo in `ChangePixelSaturationBlue` condition. +- Corrected a typo in the `ChangePixelBrightness` parameter name in `src/bitmap.h`. diff --git a/CMakeLists.txt b/CMakeLists.txt index 54c2b0e..5060648 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0.0) +cmake_minimum_required(VERSION 3.10) project(bitmap VERSION 1.0.0) include(CTest) @@ -22,6 +22,22 @@ FetchContent_MakeAvailable(matrix) link_libraries(matrix) link_libraries(bitmapfile) + +add_library(bitmap STATIC src/bitmap.cpp ) # Main library +link_libraries(bitmap) # Link bitmap library to subsequent targets if needed + +# Executable for main.cpp (if it's a demo or separate utility) +add_executable(testexe main.cpp) +target_link_libraries(testexe PRIVATE bitmap bitmapfile matrix) # Link testexe against our libraries + +# Add test executable +# It needs to compile the test source file, the bitmap source file, and the dependency's source file +add_executable(bitmap_tests tests/test_bitmap.cpp src/bitmap.cpp dependencies/bitmapfile/src/bitmap_file.cpp) +# Link bitmap_tests against the matrix library (as bitmap.cpp depends on it) +target_link_libraries(bitmap_tests PRIVATE matrix) + +# Add test to CTest +add_test(NAME BitmapUnitTests COMMAND bitmap_tests) add_library(bitmap STATIC src/bitmap.cpp ) link_libraries(bitmap) add_executable(testexe main.cpp) diff --git a/README.md b/README.md new file mode 100644 index 0000000..62a5e6a --- /dev/null +++ b/README.md @@ -0,0 +1,88 @@ +# C++ Bitmap Image Manipulation Library + +A simple C++ library for performing various image manipulation operations on BMP (Bitmap) files. +This library currently focuses on pixel-level adjustments and basic geometric transformations. + +## Features + +The library supports the following image manipulation functions: + +* **Color Adjustments:** + * Greyscale Conversion + * Brightness Adjustment (Overall and per R,G,B,Magenta,Yellow,Cyan channel) + * Contrast Adjustment (Overall and per R,G,B,Magenta,Yellow,Cyan channel) + * Saturation Adjustment (Overall and per R,G,B,Magenta,Yellow,Cyan channel) + * Luminance Adjustment (per R,G,B,Magenta,Yellow,Cyan channel) + * Invert Colors + * Sepia Tone +* **Effects & Filters:** + * Box Blur +* **Geometric Transformations:** + * Shrink Image (reduce size) + * Rotate Image (Clockwise and Counter-Clockwise) + * Mirror Image (horizontal flip) + * Flip Image (vertical flip) +* **Utility:** + * Screen Capture (Windows only: `ScreenShotWindow`) + * Load BMP from file + * Save BMP to file + +## 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. + +## Building the Project + +The project uses CMake for building. + +```bash +# Create a build directory +mkdir build +cd build + +# Configure the project +cmake .. + +# Build the library (if configured as a library) and executables (like main example and tests) +make +# or use your specific build system command e.g., mingw32-make + +# Run tests (if configured) +ctest +# or directly run the test executable: ./bitmap_tests (or tests\bitmap_tests.exe on Windows) +``` + +## Basic Usage Example + +```cpp +#include "src/bitmap.h" // Adjust path if necessary +#include + +int main() { + Bitmap::File myBitmap; + + // Load an image + if (!myBitmap.Open("input.bmp")) { + std::cerr << "Error opening input.bmp" << std::endl; + return 1; + } + + // Apply some manipulations + myBitmap = GreyscaleImage(myBitmap); + myBitmap = ChangeImageBrightness(myBitmap, 1.2f); // Increase brightness by 20% + myBitmap = ApplyBoxBlur(myBitmap, 2); // Apply box blur with radius 2 + + // Save the result + if (!myBitmap.SaveAs("output.bmp")) { + std::cerr << "Error saving output.bmp" << std::endl; + return 1; + } + + std::cout << "Image processed and saved as output.bmp" << std::endl; + return 0; +} +``` +Refer to `main.cpp` for more examples. diff --git a/main.cpp b/main.cpp index fbdba6b..d5bde8c 100644 --- a/main.cpp +++ b/main.cpp @@ -1,15 +1,101 @@ -#include "src/bitmap.h" +#include "src/bitmap.h" // Adjust path if necessary, assumes bitmap.h is in src/ +#include // For std::cout, std::cerr -int main() -{ - Bitmap::File bitmapFile; - bitmapFile.Open("test.bmp"); - bitmapFile = ShrinkImage(bitmapFile, 5); - //bitmapFile = GreyscaleImage(bitmapFile); +int main() { + Bitmap::File originalBitmap; + const char* inputFilename = "test.bmp"; - bitmapFile = ChangeImageLuminanceYellow(bitmapFile, 2); - bitmapFile = ChangeImageSaturationYellow(bitmapFile, 0); - bitmapFile.SaveAs("test2.bmp"); + std::cout << "Bitmap Processing Demo" << std::endl; + std::cout << "----------------------" << std::endl; + std::cout << "Attempting to load image: " << inputFilename << std::endl; + if (!originalBitmap.Open(inputFilename)) { + std::cerr << "********************************************************************************" << std::endl; + std::cerr << "Error: Could not open '" << inputFilename << "'." << std::endl; + std::cerr << "Please ensure this file exists in the same directory as the executable." << 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; + } + std::cout << "'" << inputFilename << "' loaded successfully." << std::endl << std::endl; + + // --- Example 1: Invert Colors --- + std::cout << "Processing: Invert Colors..." << std::endl; + Bitmap::File invertedBitmap = InvertImageColors(originalBitmap); + if (invertedBitmap.IsValid()) { + if (invertedBitmap.SaveAs("output_inverted.bmp")) { + std::cout << "Saved: output_inverted.bmp" << std::endl; + } else { + std::cerr << "Error: Failed to save output_inverted.bmp." << std::endl; + } + } else { + std::cerr << "Error: Failed to invert colors." << std::endl; + } + std::cout << std::endl; + + // --- Example 2: Apply Sepia Tone to the original --- + std::cout << "Processing: Apply Sepia Tone..." << std::endl; + Bitmap::File sepiaBitmap = ApplySepiaTone(originalBitmap); + if (sepiaBitmap.IsValid()) { + if (sepiaBitmap.SaveAs("output_sepia.bmp")) { + std::cout << "Saved: output_sepia.bmp" << std::endl; + } else { + std::cerr << "Error: Failed to save output_sepia.bmp." << std::endl; + } + } else { + std::cerr << "Error: Failed to apply sepia tone." << std::endl; + } + std::cout << std::endl; + + // --- Example 3: Apply Box Blur and then change contrast --- + // We'll use a copy of the original for this chain of operations. + std::cout << "Processing: Box Blur (Radius 2) then Contrast (Factor 1.5)..." << std::endl; + Bitmap::File processedBitmap = originalBitmap; // Start with a fresh copy for multi-step processing + + processedBitmap = ApplyBoxBlur(processedBitmap, 2); // Radius 2 + if (!processedBitmap.IsValid()) { + std::cerr << "Error: Failed to apply box blur." << std::endl; + } else { + std::cout << "Step 1: Box blur applied." << std::endl; + + processedBitmap = ChangeImageContrast(processedBitmap, 1.5f); // Increase contrast + if (!processedBitmap.IsValid()) { + std::cerr << "Error: Failed to change contrast after blur." << std::endl; + } else { + std::cout << "Step 2: Contrast increased." << std::endl; + if (processedBitmap.SaveAs("output_blurred_contrasted.bmp")) { + std::cout << "Saved: output_blurred_contrasted.bmp" << std::endl; + } else { + std::cerr << "Error: Failed to save output_blurred_contrasted.bmp." << std::endl; + } + } + } + std::cout << std::endl; + + // --- Example 4: Demonstrate Shrink Image & Grayscale --- + std::cout << "Processing: Shrink Image (Factor 2) then Grayscale..." << std::endl; + Bitmap::File shrunkBitmap = originalBitmap; // Start with a fresh copy + + shrunkBitmap = ShrinkImage(shrunkBitmap, 2); + if (!shrunkBitmap.IsValid()) { + std::cerr << "Error: Failed to shrink image." << std::endl; + } else { + std::cout << "Step 1: Image shrunk." << std::endl; + shrunkBitmap = GreyscaleImage(shrunkBitmap); + if(!shrunkBitmap.IsValid()){ + std::cerr << "Error: Failed to apply greyscale after shrinking." << std::endl; + } else { + std::cout << "Step 2: Greyscale applied." << std::endl; + if(shrunkBitmap.SaveAs("output_shrunk_greyscale.bmp")){ + std::cout << "Saved: output_shrunk_greyscale.bmp" << std::endl; + } else { + std::cerr << "Error: Failed to save output_shrunk_greyscale.bmp." << std::endl; + } + } + } + std::cout << std::endl; + + std::cout << "Main example processing complete. Check for output_*.bmp files." << std::endl; return 0; } \ No newline at end of file diff --git a/src/bitmap.cpp b/src/bitmap.cpp index 990c458..f6dbb27 100644 --- a/src/bitmap.cpp +++ b/src/bitmap.cpp @@ -1,114 +1,270 @@ #include "bitmap.h" -#include +#include // For standard I/O (though not explicitly used in this file's current state). +#include // For std::min and std::max, used in ApplyBoxBlur and color adjustments. +#include // For std::vector, used by Matrix class and underlying bitmap data. +// Captures a screenshot of a specified window and returns it as a Bitmap::File object. +// This function uses Windows API calls to interact with window handles and device contexts. Bitmap::File ScreenShotWindow(HWND windowHandle) { - Bitmap::File bitmapFile; - OpenIcon(windowHandle); - BringWindowToTop(windowHandle); - SetActiveWindow(windowHandle); + Bitmap::File bitmapFile; // Structure to hold bitmap data and headers. + + // Prepare window for capture + OpenIcon(windowHandle); // If window is minimized, restore it. + BringWindowToTop(windowHandle); // Bring the window to the foreground. + SetActiveWindow(windowHandle); // Set the window as active. + + // Get device context of the window. HDC deviceContextHandle = GetDC(windowHandle); + // Create a compatible device context (DC) for the bitmap. HDC deviceContext = CreateCompatibleDC(deviceContextHandle); + RECT windowRectangle; - GetClientRect(windowHandle, &windowRectangle); + GetClientRect(windowHandle, &windowRectangle); // Get the dimensions of the client area of the window. + + // Create a compatible bitmap with the dimensions of the window's client area. HBITMAP bitmapHandle = CreateCompatibleBitmap(deviceContextHandle, windowRectangle.right, windowRectangle.bottom); - BITMAP deviceContextBitmap; + BITMAP deviceContextBitmap; // Structure to hold bitmap metadata. + + // Select the bitmap into the compatible DC. SelectObject(deviceContext, bitmapHandle); + + // Copy the screen data from the window's DC to the compatible DC (and thus to the bitmap). + // SRCCOPY indicates a direct copy of the source to the destination. BitBlt(deviceContext, 0, 0, windowRectangle.right, windowRectangle.bottom, deviceContextHandle, 0, 0, SRCCOPY); + + // Retrieve metadata about the created bitmap. int objectGotSuccessfully = GetObject(bitmapHandle, sizeof(BITMAP), &deviceContextBitmap); + if (objectGotSuccessfully) { + // Populate BITMAPINFOHEADER bitmapFile.bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bitmapFile.bitmapInfo.bmiHeader.biWidth = deviceContextBitmap.bmWidth; - bitmapFile.bitmapInfo.bmiHeader.biHeight = deviceContextBitmap.bmHeight; - bitmapFile.bitmapInfo.bmiHeader.biPlanes = deviceContextBitmap.bmPlanes; - bitmapFile.bitmapInfo.bmiHeader.biBitCount = deviceContextBitmap.bmBitsPixel; - bitmapFile.bitmapInfo.bmiHeader.biCompression = BI_RGB; - int imageSize = deviceContextBitmap.bmWidth * deviceContextBitmap.bmHeight * deviceContextBitmap.bmBitsPixel / 8; - bitmapFile.bitmapInfo.bmiHeader.biSizeImage = deviceContextBitmap.bmHeight * deviceContextBitmap.bmWidth; + bitmapFile.bitmapInfo.bmiHeader.biHeight = deviceContextBitmap.bmHeight; // Positive height for bottom-up DIB. + bitmapFile.bitmapInfo.bmiHeader.biPlanes = deviceContextBitmap.bmPlanes; // Usually 1. + bitmapFile.bitmapInfo.bmiHeader.biBitCount = deviceContextBitmap.bmBitsPixel; // Bits per pixel (e.g., 24 or 32). + bitmapFile.bitmapInfo.bmiHeader.biCompression = BI_RGB; // Uncompressed RGB. + // Calculate image size in bytes. For BI_RGB, this can be 0 if biHeight is positive. + // However, explicitly calculating it is safer for raw data access. + int imageSize = deviceContextBitmap.bmWidth * deviceContextBitmap.bmHeight * (deviceContextBitmap.bmBitsPixel / 8); + bitmapFile.bitmapInfo.bmiHeader.biSizeImage = imageSize; // Total size of the image data. + // Resize the vector to hold the pixel data. bitmapFile.bitmapData.resize(imageSize); - int offsetSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); - int fileSize = offsetSize + imageSize; + + // Populate BITMAPFILEHEADER + int offsetSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); // Offset to pixel data. + int fileSize = offsetSize + imageSize; // Total file size. bitmapFile.bitmapFileHeader.bfSize = fileSize; - bitmapFile.bitmapFileHeader.bfType = 0x4D42; + bitmapFile.bitmapFileHeader.bfType = 0x4D42; // BM signature for bitmap files. bitmapFile.bitmapFileHeader.bfOffBits = offsetSize; bitmapFile.bitmapFileHeader.bfReserved1 = 0; bitmapFile.bitmapFileHeader.bfReserved2 = 0; + + // Retrieve the actual pixel data from the bitmap. + // DIB_RGB_COLORS indicates that the bmiColors member of BITMAPINFO is RGB. int DIBitsGotSuccessfully = GetDIBits(deviceContextHandle, bitmapHandle, 0, deviceContextBitmap.bmHeight, &bitmapFile.bitmapData[0], &bitmapFile.bitmapInfo, DIB_RGB_COLORS); if (DIBitsGotSuccessfully) - bitmapFile.SetValid(); + bitmapFile.SetValid(); // Mark the bitmap file as valid. } + // Clean up GDI objects. + DeleteDC(deviceContext); // Delete the compatible DC. + ReleaseDC(windowHandle, deviceContextHandle); // Release the window's DC. + DeleteObject(bitmapHandle); // Delete the bitmap object. + + return bitmapFile; +} + +// Inverts the color of a single pixel (Red, Green, Blue channels). Alpha is unchanged. +Pixel InvertPixelColor(Pixel pixel) +{ + pixel.red = 255 - pixel.red; + pixel.green = 255 - pixel.green; + pixel.blue = 255 - pixel.blue; + // Alpha remains unchanged + return pixel; +} + +// Inverts the colors of the image. +Bitmap::File InvertImageColors(Bitmap::File bitmapFile) +{ + Matrix::Matrix imageMatrix = CreateMatrixFromBitmap(bitmapFile); + for (auto rows : imageMatrix) + for (auto &pixels : rows) + pixels = InvertPixelColor(pixels); + bitmapFile = CreateBitmapFromMatrix(imageMatrix); + return bitmapFile; +} + +// Applies sepia tone to a single pixel. +Pixel ApplySepiaToPixel(Pixel pixel) +{ + // Standard sepia calculation + unsigned int tr = (unsigned int)(0.393 * pixel.red + 0.769 * pixel.green + 0.189 * pixel.blue); + unsigned int tg = (unsigned int)(0.349 * pixel.red + 0.686 * pixel.green + 0.168 * pixel.blue); + unsigned int tb = (unsigned int)(0.272 * pixel.red + 0.534 * pixel.green + 0.131 * pixel.blue); + + pixel.red = (tr > 255) ? 255 : (BYTE)tr; + pixel.green = (tg > 255) ? 255 : (BYTE)tg; + pixel.blue = (tb > 255) ? 255 : (BYTE)tb; + // Alpha remains unchanged + return pixel; +} + +// Applies a sepia tone to the image. +Bitmap::File ApplySepiaTone(Bitmap::File bitmapFile) +{ + Matrix::Matrix imageMatrix = CreateMatrixFromBitmap(bitmapFile); + for (auto rows : imageMatrix) + for (auto &pixels : rows) + pixels = ApplySepiaToPixel(pixels); + bitmapFile = CreateBitmapFromMatrix(imageMatrix); return bitmapFile; } +// Applies a box blur to the image with a given radius. +// Each pixel's new value is the average of its neighbors within a square box. +Bitmap::File ApplyBoxBlur(Bitmap::File bitmapFile, int blurRadius) +{ + if (blurRadius <= 0) { + // No blur or invalid radius, return original image without processing. + return bitmapFile; + } + + Matrix::Matrix originalMatrix = CreateMatrixFromBitmap(bitmapFile); + Matrix::Matrix blurredMatrix(originalMatrix.rows(), originalMatrix.cols()); + + // Iterate over each pixel in the original image. + for (int r = 0; r < originalMatrix.rows(); ++r) // r for row + { + for (int c = 0; c < originalMatrix.cols(); ++c) // c for column + { + unsigned int sumRed = 0, sumGreen = 0, sumBlue = 0, sumAlpha = 0; + int count = 0; // Number of pixels included in the blur box. + + // Iterate over the box defined by blurRadius around the current pixel (r, c). + // std::max and std::min are used to handle boundary conditions, ensuring we don't go out of bounds. + for (int i = std::max(0, r - blurRadius); i <= std::min(originalMatrix.rows() - 1, r + blurRadius); ++i) + { + for (int j = std::max(0, c - blurRadius); j <= std::min(originalMatrix.cols() - 1, c + blurRadius); ++j) + { + // Accumulate color and alpha values. + sumRed += originalMatrix[i][j].red; + sumGreen += originalMatrix[i][j].green; + sumBlue += originalMatrix[i][j].blue; + sumAlpha += originalMatrix[i][j].alpha; + count++; + } + } + + // Calculate the average color and alpha values. + if (count > 0) + { + blurredMatrix[r][c].red = (BYTE)(sumRed / count); + blurredMatrix[r][c].green = (BYTE)(sumGreen / count); + blurredMatrix[r][c].blue = (BYTE)(sumBlue / count); + blurredMatrix[r][c].alpha = (BYTE)(sumAlpha / count); // Blur alpha channel as well. + } + else + { + // This case should ideally not be reached if blurRadius > 0 and matrix is not empty. + // As a fallback, copy the original pixel to the blurred matrix. + blurredMatrix[r][c] = originalMatrix[r][c]; + } + } + } + // Convert the matrix of blurred pixels back to a Bitmap::File object. + return CreateBitmapFromMatrix(blurredMatrix); +} + +// Converts a Bitmap::File object (containing raw bitmap data and headers) +// into a Matrix::Matrix for easier pixel manipulation. Matrix::Matrix CreateMatrixFromBitmap(Bitmap::File bitmapFile) { + // Initialize the matrix with dimensions from the bitmap header. + // Note: Bitmap rows are often stored bottom-up, but matrix access is typically top-down. + // The loop structure (i from 0 to rows-1) handles this naturally if pixel data is ordered correctly. Matrix::Matrix imageMatrix(bitmapFile.bitmapInfo.bmiHeader.biHeight, bitmapFile.bitmapInfo.bmiHeader.biWidth); - if (bitmapFile.bitmapInfo.bmiHeader.biBitCount == 32) + + if (bitmapFile.bitmapInfo.bmiHeader.biBitCount == 32) // For 32-bit bitmaps (BGRA) { - int k = 0; + int k = 0; // Index for bitmapFile.bitmapData for (int i = 0; i < imageMatrix.rows(); i++) for (int j = 0; j < imageMatrix.cols(); j++) { + // Pixel data in bitmap files is typically stored in BGR or BGRA order. imageMatrix[i][j].blue = bitmapFile.bitmapData[k]; imageMatrix[i][j].green = bitmapFile.bitmapData[k + 1]; imageMatrix[i][j].red = bitmapFile.bitmapData[k + 2]; imageMatrix[i][j].alpha = bitmapFile.bitmapData[k + 3]; - k += 4; + k += 4; // Move to the next pixel (4 bytes) } } - - else if (bitmapFile.bitmapInfo.bmiHeader.biBitCount == 24) + else if (bitmapFile.bitmapInfo.bmiHeader.biBitCount == 24) // For 24-bit bitmaps (BGR) { - int k = 0; + int k = 0; // Index for bitmapFile.bitmapData for (int i = 0; i < imageMatrix.rows(); i++) for (int j = 0; j < imageMatrix.cols(); j++) { imageMatrix[i][j].blue = bitmapFile.bitmapData[k]; imageMatrix[i][j].green = bitmapFile.bitmapData[k + 1]; imageMatrix[i][j].red = bitmapFile.bitmapData[k + 2]; - imageMatrix[i][j].alpha = 0; - k += 3; + imageMatrix[i][j].alpha = 0; // Default alpha to 0 (opaque) for 24-bit images. + k += 3; // Move to the next pixel (3 bytes) } } + // Note: Other bit depths (e.g., 1, 4, 8, 16-bit) would require more complex handling. return imageMatrix; } +// Converts a Matrix::Matrix (representing an image) +// back into a Bitmap::File object (with raw bitmap data and headers). +// Assumes output is always a 32-bit bitmap. Bitmap::File CreateBitmapFromMatrix(Matrix::Matrix imageMatrix) { Bitmap::File bitmapFile; + + // Populate BITMAPINFOHEADER bitmapFile.bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bitmapFile.bitmapInfo.bmiHeader.biWidth = imageMatrix.cols(); - bitmapFile.bitmapInfo.bmiHeader.biHeight = imageMatrix.rows(); + bitmapFile.bitmapInfo.bmiHeader.biHeight = imageMatrix.rows(); // Positive height for bottom-up DIB. bitmapFile.bitmapInfo.bmiHeader.biPlanes = 1; - bitmapFile.bitmapInfo.bmiHeader.biBitCount = 32; - bitmapFile.bitmapInfo.bmiHeader.biCompression = BI_RGB; - int imageSize = imageMatrix.size() * 32 / 8; - bitmapFile.bitmapInfo.bmiHeader.biSizeImage = imageMatrix.size(); + bitmapFile.bitmapInfo.bmiHeader.biBitCount = 32; // Outputting as 32-bit BGRA. + bitmapFile.bitmapInfo.bmiHeader.biCompression = BI_RGB; // Uncompressed. + // Calculate image size in bytes for a 32-bit image. + int imageSize = imageMatrix.size() * (32 / 8); // imageMatrix.size() is rows * cols. + bitmapFile.bitmapInfo.bmiHeader.biSizeImage = imageSize; + // Resize the vector to hold the pixel data. bitmapFile.bitmapData.resize(imageSize); - int offsetSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); - int fileSize = offsetSize + imageSize; + + // Populate BITMAPFILEHEADER + int offsetSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); // Offset to pixel data. + int fileSize = offsetSize + imageSize; // Total file size. bitmapFile.bitmapFileHeader.bfSize = fileSize; - bitmapFile.bitmapFileHeader.bfType = 0x4D42; + bitmapFile.bitmapFileHeader.bfType = 0x4D42; // BM signature for bitmap files. bitmapFile.bitmapFileHeader.bfOffBits = offsetSize; bitmapFile.bitmapFileHeader.bfReserved1 = 0; bitmapFile.bitmapFileHeader.bfReserved2 = 0; - int k = 0; + + int k = 0; // Index for bitmapFile.bitmapData for (int i = 0; i < imageMatrix.rows(); i++) for (int j = 0; j < imageMatrix.cols(); j++) { + // Store pixel data in BGRA order. bitmapFile.bitmapData[k] = imageMatrix[i][j].blue; bitmapFile.bitmapData[k + 1] = imageMatrix[i][j].green; bitmapFile.bitmapData[k + 2] = imageMatrix[i][j].red; bitmapFile.bitmapData[k + 3] = imageMatrix[i][j].alpha; - k += 4; + k += 4; // Move to the next pixel (4 bytes) } - bitmapFile.SetValid(); + + bitmapFile.SetValid(); // Mark the bitmap file as valid. return bitmapFile; } +// Converts an image to greyscale. Bitmap::File GreyscaleImage(Bitmap::File bitmapFile) { Matrix::Matrix imageMatrix = CreateMatrixFromBitmap(bitmapFile); @@ -119,17 +275,29 @@ Bitmap::File GreyscaleImage(Bitmap::File bitmapFile) return bitmapFile; } +// Shrinks an image by an integer scale factor using averaging. Bitmap::File ShrinkImage(Bitmap::File bitmapFile, int scaleFactor) { + if (scaleFactor <= 0) return bitmapFile; // Or handle error appropriately + Matrix::Matrix imageMatrix = CreateMatrixFromBitmap(bitmapFile); - Matrix::Matrix shrunkenMatrix(imageMatrix.rows() / scaleFactor, imageMatrix.cols() / scaleFactor); + // Calculate dimensions of the new, shrunken matrix. + int newRows = imageMatrix.rows() / scaleFactor; + int newCols = imageMatrix.cols() / scaleFactor; + if (newRows == 0 || newCols == 0) return bitmapFile; // Cannot shrink to zero size + + Matrix::Matrix shrunkenMatrix(newRows, newCols); + for (int i = 0; i < shrunkenMatrix.rows(); i++) for (int j = 0; j < shrunkenMatrix.cols(); j++) { - int averageRed = 0; - int averageGreen = 0; - int averageBlue = 0; - int averageAlpha = 0; + unsigned int averageRed = 0; + unsigned int averageGreen = 0; + unsigned int averageBlue = 0; + unsigned int averageAlpha = 0; + int numPixels = 0; // Count of pixels in the block for averaging. + + // Iterate over the block of pixels in the original image that corresponds to the current pixel in the shrunken image. for (int k = 0; (k < scaleFactor) && ((k + (i * scaleFactor)) < imageMatrix.rows()); k++) for (int l = 0; (l < scaleFactor) && ((l + (j * scaleFactor)) < imageMatrix.cols()); l++) { @@ -137,20 +305,22 @@ Bitmap::File ShrinkImage(Bitmap::File bitmapFile, int scaleFactor) averageGreen += imageMatrix[k + (i * scaleFactor)][l + (j * scaleFactor)].green; averageBlue += imageMatrix[k + (i * scaleFactor)][l + (j * scaleFactor)].blue; averageAlpha += imageMatrix[k + (i * scaleFactor)][l + (j * scaleFactor)].alpha; + numPixels++; } - averageRed = averageRed / (scaleFactor * scaleFactor); - averageGreen = averageGreen / (scaleFactor * scaleFactor); - averageBlue = averageBlue / (scaleFactor * scaleFactor); - averageAlpha = averageAlpha / (scaleFactor * scaleFactor); - shrunkenMatrix[i][j].red = averageRed; - shrunkenMatrix[i][j].green = averageGreen; - shrunkenMatrix[i][j].blue = averageBlue; - shrunkenMatrix[i][j].alpha = averageAlpha; + + if (numPixels > 0) { + shrunkenMatrix[i][j].red = (BYTE)(averageRed / numPixels); + shrunkenMatrix[i][j].green = (BYTE)(averageGreen / numPixels); + shrunkenMatrix[i][j].blue = (BYTE)(averageBlue / numPixels); + shrunkenMatrix[i][j].alpha = (BYTE)(averageAlpha / numPixels); + } + // Else, if numPixels is 0 (should not happen if newRows/newCols > 0), pixel remains default (likely black). } bitmapFile = CreateBitmapFromMatrix(shrunkenMatrix); return bitmapFile; } +// Rotates the image 90 degrees counter-clockwise. Bitmap::File RotateImageCounterClockwise(Bitmap::File bitmapFile) { Matrix::Matrix imageMatrix = CreateMatrixFromBitmap(bitmapFile); @@ -162,6 +332,7 @@ Bitmap::File RotateImageCounterClockwise(Bitmap::File bitmapFile) return bitmapFile; } +// Rotates the image 90 degrees clockwise. Bitmap::File RotateImageClockwise(Bitmap::File bitmapFile) { Matrix::Matrix imageMatrix = CreateMatrixFromBitmap(bitmapFile); @@ -173,6 +344,7 @@ Bitmap::File RotateImageClockwise(Bitmap::File bitmapFile) return bitmapFile; } +// Mirrors the image horizontally (left to right). Bitmap::File MirrorImage(Bitmap::File bitmapFile) { Matrix::Matrix imageMatrix = CreateMatrixFromBitmap(bitmapFile); @@ -184,6 +356,7 @@ Bitmap::File MirrorImage(Bitmap::File bitmapFile) return bitmapFile; } +// Flips the image vertically (top to bottom). Bitmap::File FlipImage(Bitmap::File bitmapFile) { Matrix::Matrix imageMatrix = CreateMatrixFromBitmap(bitmapFile); @@ -195,6 +368,7 @@ Bitmap::File FlipImage(Bitmap::File bitmapFile) return bitmapFile; } +// Changes the overall brightness of the image. Bitmap::File ChangeImageBrightness(Bitmap::File bitmapFile, float brightness) { Matrix::Matrix imageMatrix = CreateMatrixFromBitmap(bitmapFile); @@ -205,6 +379,7 @@ Bitmap::File ChangeImageBrightness(Bitmap::File bitmapFile, float brightness) return bitmapFile; } +// Changes the overall saturation of the image. Bitmap::File ChangeImageSaturation(Bitmap::File bitmapFile, float saturation) { Matrix::Matrix imageMatrix = CreateMatrixFromBitmap(bitmapFile); @@ -216,6 +391,8 @@ Bitmap::File ChangeImageSaturation(Bitmap::File bitmapFile, float saturation) return bitmapFile; } +// Converts a single pixel to its greyscale equivalent. +// Greyscale is calculated by averaging the red, green, and blue components. Pixel GreyScalePixel(Pixel pixel) { int average = (pixel.red + pixel.blue + pixel.green) / 3; @@ -225,34 +402,36 @@ Pixel GreyScalePixel(Pixel pixel) return pixel; } -Pixel ChangePixelBrightness(Pixel pixel, float brightness) +// Changes the brightness of a single pixel. +// It calculates the average intensity and then scales each color component relative to this average. +Pixel ChangePixelBrightness(Pixel pixel, float brightness) // Parameter name was 'brightnessl' in header, using 'brightness' here. { int average = (pixel.red + pixel.green + pixel.blue) / 3; int newAverage = (int)(average * brightness); - if (((pixel.red - average) + newAverage <= 255) && ((pixel.red - average) + newAverage >= 0)) - pixel.red = (pixel.red - average) + newAverage; - else if (((pixel.red - average) + newAverage < 0)) - pixel.red = 0; - else - pixel.red = 255; - - if (((pixel.green - average) + newAverage <= 255) && ((pixel.green - average) + newAverage >= 0)) - pixel.green = (pixel.green - average) + newAverage; - else if (((pixel.green - average) + newAverage < 0)) - pixel.green = 0; - else - pixel.green = 255; - - if (((pixel.blue - average) + newAverage <= 255) && ((pixel.blue - average) + newAverage >= 0)) - pixel.blue = (pixel.blue - average) + newAverage; - else if (((pixel.blue - average) + newAverage < 0)) - pixel.blue = 0; - else - pixel.blue = 255; + // Calculate new red value, clamping to 0-255 + int new_red_val = (pixel.red - average) + newAverage; + if (new_red_val < 0) pixel.red = 0; + else if (new_red_val > 255) pixel.red = 255; + else pixel.red = (BYTE)new_red_val; + + // Calculate new green value, clamping to 0-255 + int new_green_val = (pixel.green - average) + newAverage; + if (new_green_val < 0) pixel.green = 0; + else if (new_green_val > 255) pixel.green = 255; + else pixel.green = (BYTE)new_green_val; + + // Calculate new blue value, clamping to 0-255 + int new_blue_val = (pixel.blue - average) + newAverage; + if (new_blue_val < 0) pixel.blue = 0; + else if (new_blue_val > 255) pixel.blue = 255; + else pixel.blue = (BYTE)new_blue_val; + // Alpha remains unchanged. return pixel; } +// Changes the saturation of a single pixel. +// It adjusts how far each color component is from the average intensity (greyscale). Pixel ChangePixelSaturation(Pixel pixel, float saturation) { int average = (pixel.red + pixel.green + pixel.blue) / 3; @@ -287,6 +466,7 @@ Pixel ChangePixelSaturation(Pixel pixel, float saturation) return pixel; } +// Changes the overall contrast of the image. Bitmap::File ChangeImageContrast(Bitmap::File bitmapFile, float contrast) { Matrix::Matrix imageMatrix = CreateMatrixFromBitmap(bitmapFile); @@ -297,262 +477,201 @@ Bitmap::File ChangeImageContrast(Bitmap::File bitmapFile, float contrast) return bitmapFile; } +// Changes the contrast of a single pixel. +// This is done by scaling the difference of each color component from a mid-point (128). Pixel ChangePixelContrast(Pixel pixel, float contrast) { // Adjust red component int new_red = (int)(128 + (pixel.red - 128) * contrast); - if (new_red < 0) - new_red = 0; - if (new_red > 255) - new_red = 255; - pixel.red = new_red; + pixel.red = (BYTE)std::max(0, std::min(255, new_red)); // Clamp to 0-255 // Adjust green component int new_green = (int)(128 + (pixel.green - 128) * contrast); - if (new_green < 0) - new_green = 0; - if (new_green > 255) - new_green = 255; - pixel.green = new_green; + pixel.green = (BYTE)std::max(0, std::min(255, new_green)); // Clamp to 0-255 // Adjust blue component int new_blue = (int)(128 + (pixel.blue - 128) * contrast); - if (new_blue < 0) - new_blue = 0; - if (new_blue > 255) - new_blue = 255; - pixel.blue = new_blue; - + pixel.blue = (BYTE)std::max(0, std::min(255, new_blue)); // Clamp to 0-255 + // Alpha remains unchanged. return pixel; } +// Changes the contrast of the red channel of a single pixel. Pixel ChangePixelContrastRed(Pixel pixel, float contrast) { - int new_red = (int)(128 + (pixel.red - 128) * contrast); - if (new_red < 0) - new_red = 0; - if (new_red > 255) - new_red = 255; - pixel.red = new_red; - + pixel.red = (BYTE)std::max(0, std::min(255, new_red)); // Clamp to 0-255 + // Other channels and alpha remain unchanged. return pixel; } +// Changes the contrast of the green channel of a single pixel. Pixel ChangePixelContrastGreen(Pixel pixel, float contrast) { - int new_green = (int)(128 + (pixel.green - 128) * contrast); - if (new_green < 0) - new_green = 0; - if (new_green > 255) - new_green = 255; - pixel.green = new_green; - + pixel.green = (BYTE)std::max(0, std::min(255, new_green)); // Clamp to 0-255 + // Other channels and alpha remain unchanged. return pixel; } +// Changes the contrast of the blue channel of a single pixel. Pixel ChangePixelContrastBlue(Pixel pixel, float contrast) { int new_blue = (int)(128 + (pixel.blue - 128) * contrast); - if (new_blue < 0) - new_blue = 0; - if (new_blue > 255) - new_blue = 255; - pixel.blue = new_blue; + pixel.blue = (BYTE)std::max(0, std::min(255, new_blue)); // Clamp to 0-255 + // Other channels and alpha remain unchanged. return pixel; } +// Applies contrast adjustment to the red and blue channels of a pixel (Magenta). +// Contrast is applied directly to the constituent primary color channels. Pixel ChangePixelContrastMagenta(Pixel pixel, float contrast) { - int magenta = (std::min)(pixel.red)(pixel.blue); - int redoffset = pixel.red - magenta; - int blueoffset = pixel.blue - magenta; - - int new_red = (int)(128 + (magenta - 128) * contrast) + redoffset; - if (new_red < 0) - new_red = 0; - if (new_red > 255) - new_red = 255; - pixel.red = new_red; + // Adjust red component + int new_red = (int)(128 + (pixel.red - 128) * contrast); + pixel.red = (BYTE)std::max(0, std::min(255, new_red)); // Clamp to 0-255 - int new_blue = (int)(128 + (magenta - 128) * contrast) + blueoffset; - if (new_blue < 0) - new_blue = 0; - if (new_blue > 255) - new_blue = 255; - pixel.blue = new_blue; + // Adjust blue component + int new_blue = (int)(128 + (pixel.blue - 128) * contrast); + pixel.blue = (BYTE)std::max(0, std::min(255, new_blue)); // Clamp to 0-255 + // Green channel remains unchanged for magenta contrast return pixel; } +// Applies contrast adjustment to the red and green channels of a pixel (Yellow). +// Contrast is applied directly to the constituent primary color channels. Pixel ChangePixelContrastYellow(Pixel pixel, float contrast) { + // Adjust red component + int new_red = (int)(128 + (pixel.red - 128) * contrast); + pixel.red = (BYTE)std::max(0, std::min(255, new_red)); // Clamp to 0-255 - int yellow = (std::min)(pixel.red)(pixel.green); - int redoffset = pixel.red - yellow; - int greenoffset = pixel.green - yellow; - - int new_red = (int)(128 + (yellow - 128) * contrast) + redoffset; - if (new_red < 0) - new_red = 0; - if (new_red > 255) - new_red = 255; - pixel.red = new_red; - - int new_green = (int)(128 + (yellow - 128) * contrast) + greenoffset; - if (new_green < 0) - new_green = 0; - if (new_green > 255) - new_green = 255; - pixel.green = new_green; + // Adjust green component + int new_green = (int)(128 + (pixel.green - 128) * contrast); + pixel.green = (BYTE)std::max(0, std::min(255, new_green)); // Clamp to 0-255 + // Blue channel remains unchanged for yellow contrast return pixel; } +// Applies contrast adjustment to the green and blue channels of a pixel (Cyan). +// Contrast is applied directly to the constituent primary color channels. Pixel ChangePixelContrastCyan(Pixel pixel, float contrast) { + // Adjust green component + int new_green = (int)(128 + (pixel.green - 128) * contrast); + pixel.green = (BYTE)std::max(0, std::min(255, new_green)); // Clamp to 0-255 - int cyan = (std::min)(pixel.green)(pixel.blue) int greenoffset = green - cyan; - int blueoffest = blue - cyan; - - int new_green = (int)(128 + (cyan - 128) * contrast) + greenoffset; - if (new_green < 0) - new_green = 0; - if (new_green > 255) - new_green = 255; - pixel.green = new_green; - - int new_blue = (int)(128 + (magenta - 128) * contrast) + blueoffset; - if (new_blue < 0) - new_blue = 0; - if (new_blue > 255) - new_blue = 255; - pixel.blue = new_blue; + // Adjust blue component + int new_blue = (int)(128 + (pixel.blue - 128) * contrast); + pixel.blue = (BYTE)std::max(0, std::min(255, new_blue)); // Clamp to 0-255 + // Red channel remains unchanged for cyan contrast return pixel; } +// Changes the saturation of the blue channel of a single pixel. +// Only applies if blue is the dominant or co-dominant color. Pixel ChangePixelSaturationBlue(Pixel pixel, float saturation) { int average = (pixel.red + pixel.green + pixel.blue) / 3; - if ((pixel.blue >= pixel.red) && (pixel.blue >= pixel.red)) + // Check if blue is a dominant component to avoid desaturating other colors. + if ((pixel.blue >= pixel.red) && (pixel.blue >= pixel.green)) // Simplified condition, original was (pixel.blue >= pixel.red) && (pixel.blue >= pixel.red) { - if ((((pixel.blue - average) * saturation + average) <= 255) && (((pixel.blue - average) * saturation + average) >= 0)) - pixel.blue = (BYTE)((pixel.blue - average) * saturation + average); - else if ((((pixel.blue - average) * saturation + average) > 255)) - pixel.blue = 255; - else - pixel.blue = 0; + int new_blue = (int)((pixel.blue - average) * saturation + average); + pixel.blue = (BYTE)std::max(0, std::min(255, new_blue)); // Clamp to 0-255 } + // Alpha remains unchanged. return pixel; } +// Changes the saturation of the green channel of a single pixel. +// Only applies if green is the dominant or co-dominant color. Pixel ChangePixelSaturationGreen(Pixel pixel, float saturation) { int average = (pixel.red + pixel.green + pixel.blue) / 3; if ((pixel.green >= pixel.red) && (pixel.green >= pixel.blue)) { - if ((((pixel.green - average) * saturation + average) <= 255) && (((pixel.green - average) * saturation + average) >= 0)) - pixel.green = (BYTE)((pixel.green - average) * saturation + average); - else if ((((pixel.green - average) * saturation + average) > 255)) - pixel.green = 255; - else - pixel.green = 0; + int new_green = (int)((pixel.green - average) * saturation + average); + pixel.green = (BYTE)std::max(0, std::min(255, new_green)); // Clamp to 0-255 } + // Alpha remains unchanged. return pixel; } +// Changes the saturation of the red channel of a single pixel. +// Only applies if red is the dominant or co-dominant color. Pixel ChangePixelSaturationRed(Pixel pixel, float saturation) { int average = (pixel.red + pixel.green + pixel.blue) / 3; if ((pixel.red >= pixel.blue) && (pixel.red >= pixel.green)) { - if ((((pixel.red - average) * saturation + average) <= 255) && (((pixel.red - average) * saturation + average) >= 0)) - pixel.red = (BYTE)((pixel.red - average) * saturation + average); - else if ((((pixel.red - average) * saturation + average) > 255)) - pixel.red = 255; - else - pixel.red = 0; + int new_red = (int)((pixel.red - average) * saturation + average); + pixel.red = (BYTE)std::max(0, std::min(255, new_red)); // Clamp to 0-255 } + // Alpha remains unchanged. return pixel; } +// Changes the saturation of the magenta component (red and blue channels) of a single pixel. Pixel ChangePixelSaturationMagenta(Pixel pixel, float saturation) { int average = (pixel.red + pixel.blue + pixel.green) / 3; - int magenta = (std::min)(pixel.red, pixel.blue); - int redOffset = pixel.red - magenta; - int blueOffset = pixel.blue - magenta; - if (magenta >= average) - magenta = (int)((magenta - average) * saturation + average); - if ((redOffset + magenta <= 255) && (redOffset + magenta >= 0)) - pixel.red = redOffset + magenta; - else if (redOffset + magenta > 255) - pixel.red = 255; - else - pixel.red = 0; - - if ((blueOffset + magenta <= 255) && (blueOffset + magenta >= 0)) - pixel.blue = blueOffset + magenta; - else if (blueOffset + magenta > 255) - pixel.blue = 255; - else - pixel.blue = 0; + int magenta_component = std::min(pixel.red, pixel.blue); // The "amount" of magenta. + int redOffset = pixel.red - magenta_component; // How much "more red" than magenta. + int blueOffset = pixel.blue - magenta_component; // How much "more blue" than magenta. + if (magenta_component >= average) // Only saturate if magenta is more intense than average. + { + magenta_component = (int)((magenta_component - average) * saturation + average); + } + // Reconstruct and clamp. + pixel.red = (BYTE)std::max(0, std::min(255, redOffset + magenta_component)); + pixel.blue = (BYTE)std::max(0, std::min(255, blueOffset + magenta_component)); + // Green and Alpha remain unchanged. return pixel; } +// Changes the saturation of the yellow component (red and green channels) of a single pixel. Pixel ChangePixelSaturationYellow(Pixel pixel, float saturation) { int average = (pixel.red + pixel.blue + pixel.green) / 3; - int yellow = (std::min)(pixel.red, pixel.green); - int redOffset = pixel.red - yellow; - int greenOffset = pixel.green - yellow; - if (yellow >= average) - yellow = (int)((yellow - average) * saturation + average); - if ((redOffset + yellow <= 255) && (redOffset + yellow >= 0)) - pixel.red = redOffset + yellow; - else if (redOffset + yellow > 255) - pixel.red = 255; - else - pixel.red = 0; - - if ((greenOffset + yellow <= 255) && (greenOffset + yellow >= 0)) - pixel.green = greenOffset + yellow; - else if (greenOffset + yellow > 255) - pixel.green = 255; - else - pixel.green = 0; + int yellow_component = std::min(pixel.red, pixel.green); // The "amount" of yellow. + int redOffset = pixel.red - yellow_component; // How much "more red" than yellow. + int greenOffset = pixel.green - yellow_component; // How much "more green" than yellow. + if (yellow_component >= average) // Only saturate if yellow is more intense than average. + { + yellow_component = (int)((yellow_component - average) * saturation + average); + } + // Reconstruct and clamp. + pixel.red = (BYTE)std::max(0, std::min(255, redOffset + yellow_component)); + pixel.green = (BYTE)std::max(0, std::min(255, greenOffset + yellow_component)); + // Blue and Alpha remain unchanged. return pixel; } +// Changes the saturation of the cyan component (green and blue channels) of a single pixel. Pixel ChangePixelSaturationCyan(Pixel pixel, float saturation) { int average = (pixel.red + pixel.blue + pixel.green) / 3; - int cyan = (std::min)(pixel.blue, pixel.green); - int greenOffset = pixel.green - cyan; - int blueOffset = pixel.blue - cyan; - if (cyan >= average) - cyan = (int)((cyan - average) * saturation + average); - if ((greenOffset + cyan <= 255) && (greenOffset + cyan >= 0)) - pixel.green = greenOffset + cyan; - else if (greenOffset + cyan > 255) - pixel.green = 255; - else - pixel.green = 0; - - if ((blueOffset + cyan <= 255) && (blueOffset + cyan >= 0)) - pixel.blue = blueOffset + cyan; - else if (blueOffset + cyan > 255) - pixel.blue = 255; - else - pixel.blue = 0; + int cyan_component = std::min(pixel.blue, pixel.green); // The "amount" of cyan. + int greenOffset = pixel.green - cyan_component; // How much "more green" than cyan. + int blueOffset = pixel.blue - cyan_component; // How much "more blue" than cyan. + if (cyan_component >= average) // Only saturate if cyan is more intense than average. + { + cyan_component = (int)((cyan_component - average) * saturation + average); + } + // Reconstruct and clamp. + pixel.green = (BYTE)std::max(0, std::min(255, greenOffset + cyan_component)); + pixel.blue = (BYTE)std::max(0, std::min(255, blueOffset + cyan_component)); + // Red and Alpha remain unchanged. return pixel; } +// Changes the saturation of the blue channel in the image. Bitmap::File ChangeImageSaturationBlue(Bitmap::File bitmapFile, float saturation) { Matrix::Matrix imageMatrix = CreateMatrixFromBitmap(bitmapFile); @@ -564,6 +683,7 @@ Bitmap::File ChangeImageSaturationBlue(Bitmap::File bitmapFile, float saturation return bitmapFile; } +// Changes the saturation of the green channel in the image. Bitmap::File ChangeImageSaturationGreen(Bitmap::File bitmapFile, float saturation) { Matrix::Matrix imageMatrix = CreateMatrixFromBitmap(bitmapFile); @@ -575,6 +695,7 @@ Bitmap::File ChangeImageSaturationGreen(Bitmap::File bitmapFile, float saturatio return bitmapFile; } +// Changes the saturation of the red channel in the image. Bitmap::File ChangeImageSaturationRed(Bitmap::File bitmapFile, float saturation) { Matrix::Matrix imageMatrix = CreateMatrixFromBitmap(bitmapFile); @@ -586,6 +707,7 @@ Bitmap::File ChangeImageSaturationRed(Bitmap::File bitmapFile, float saturation) return bitmapFile; } +// Changes the saturation of the magenta component in the image. Bitmap::File ChangeImageSaturationMagenta(Bitmap::File bitmapFile, float saturation) { Matrix::Matrix imageMatrix = CreateMatrixFromBitmap(bitmapFile); @@ -597,6 +719,7 @@ Bitmap::File ChangeImageSaturationMagenta(Bitmap::File bitmapFile, float saturat return bitmapFile; } +// Changes the saturation of the yellow component in the image. Bitmap::File ChangeImageSaturationYellow(Bitmap::File bitmapFile, float saturation) { Matrix::Matrix imageMatrix = CreateMatrixFromBitmap(bitmapFile); @@ -608,6 +731,7 @@ Bitmap::File ChangeImageSaturationYellow(Bitmap::File bitmapFile, float saturati return bitmapFile; } +// Changes the saturation of the cyan component in the image. Bitmap::File ChangeImageSaturationCyan(Bitmap::File bitmapFile, float saturation) { Matrix::Matrix imageMatrix = CreateMatrixFromBitmap(bitmapFile); @@ -619,196 +743,144 @@ Bitmap::File ChangeImageSaturationCyan(Bitmap::File bitmapFile, float saturation return bitmapFile; } +// Changes the luminance of the blue channel of a single pixel. +// This function adjusts all color channels if blue is dominant, effectively changing the pixel's overall brightness. Pixel ChangePixelLuminanceBlue(Pixel pixel, float luminance) { - if ((pixel.blue >= pixel.red) && (pixel.blue >= pixel.green)) + if ((pixel.blue >= pixel.red) && (pixel.blue >= pixel.green)) // Only if blue is dominant or co-dominant { int average = (pixel.red + pixel.green + pixel.blue) / 3; int newAverage = (int)(average * luminance); - if (((pixel.red - average) + newAverage <= 255) && ((pixel.red - average) + newAverage >= 0)) - pixel.red = (pixel.red - average) + newAverage; - else if (((pixel.red - average) + newAverage < 0)) - pixel.red = 0; - else - pixel.red = 255; - if (((pixel.green - average) + newAverage <= 255) && ((pixel.green - average) + newAverage >= 0)) - pixel.green = (pixel.green - average) + newAverage; - else if (((pixel.green - average) + newAverage < 0)) - pixel.green = 0; - else - pixel.green = 255; + int new_red_val = (pixel.red - average) + newAverage; + pixel.red = (BYTE)std::max(0, std::min(255, new_red_val)); - if (((pixel.blue - average) + newAverage <= 255) && ((pixel.blue - average) + newAverage >= 0)) - pixel.blue = (pixel.blue - average) + newAverage; - else if (((pixel.blue - average) + newAverage < 0)) - pixel.blue = 0; - else - pixel.blue = 255; - } + int new_green_val = (pixel.green - average) + newAverage; + pixel.green = (BYTE)std::max(0, std::min(255, new_green_val)); + int new_blue_val = (pixel.blue - average) + newAverage; + pixel.blue = (BYTE)std::max(0, std::min(255, new_blue_val)); + } + // Alpha remains unchanged. return pixel; } +// Changes the luminance of the green channel of a single pixel. +// Adjusts all color channels if green is dominant. Pixel ChangePixelLuminanceGreen(Pixel pixel, float luminance) { - if ((pixel.green >= pixel.red) && (pixel.green >= pixel.blue)) + if ((pixel.green >= pixel.red) && (pixel.green >= pixel.blue)) // Only if green is dominant or co-dominant { int average = (pixel.red + pixel.green + pixel.blue) / 3; int newAverage = (int)(average * luminance); - if (((pixel.red - average) + newAverage <= 255) && ((pixel.red - average) + newAverage >= 0)) - pixel.red = (pixel.red - average) + newAverage; - else if (((pixel.red - average) + newAverage < 0)) - pixel.red = 0; - else - pixel.red = 255; - if (((pixel.green - average) + newAverage <= 255) && ((pixel.green - average) + newAverage >= 0)) - pixel.green = (pixel.green - average) + newAverage; - else if (((pixel.green - average) + newAverage < 0)) - pixel.green = 0; - else - pixel.green = 255; + int new_red_val = (pixel.red - average) + newAverage; + pixel.red = (BYTE)std::max(0, std::min(255, new_red_val)); - if (((pixel.blue - average) + newAverage <= 255) && ((pixel.blue - average) + newAverage >= 0)) - pixel.blue = (pixel.blue - average) + newAverage; - else if (((pixel.blue - average) + newAverage < 0)) - pixel.blue = 0; - else - pixel.blue = 255; - } + int new_green_val = (pixel.green - average) + newAverage; + pixel.green = (BYTE)std::max(0, std::min(255, new_green_val)); + int new_blue_val = (pixel.blue - average) + newAverage; + pixel.blue = (BYTE)std::max(0, std::min(255, new_blue_val)); + } + // Alpha remains unchanged. return pixel; } +// Changes the luminance of the red channel of a single pixel. +// Adjusts all color channels if red is dominant. Pixel ChangePixelLuminanceRed(Pixel pixel, float luminance) { - if ((pixel.red >= pixel.green) && (pixel.red >= pixel.blue)) + if ((pixel.red >= pixel.green) && (pixel.red >= pixel.blue)) // Only if red is dominant or co-dominant { int average = (pixel.red + pixel.green + pixel.blue) / 3; int newAverage = (int)(average * luminance); - if (((pixel.red - average) + newAverage <= 255) && ((pixel.red - average) + newAverage >= 0)) - pixel.red = (pixel.red - average) + newAverage; - else if (((pixel.red - average) + newAverage < 0)) - pixel.red = 0; - else - pixel.red = 255; - if (((pixel.green - average) + newAverage <= 255) && ((pixel.green - average) + newAverage >= 0)) - pixel.green = (pixel.green - average) + newAverage; - else if (((pixel.green - average) + newAverage < 0)) - pixel.green = 0; - else - pixel.green = 255; + int new_red_val = (pixel.red - average) + newAverage; + pixel.red = (BYTE)std::max(0, std::min(255, new_red_val)); - if (((pixel.blue - average) + newAverage <= 255) && ((pixel.blue - average) + newAverage >= 0)) - pixel.blue = (pixel.blue - average) + newAverage; - else if (((pixel.blue - average) + newAverage < 0)) - pixel.blue = 0; - else - pixel.blue = 255; - } + int new_green_val = (pixel.green - average) + newAverage; + pixel.green = (BYTE)std::max(0, std::min(255, new_green_val)); + int new_blue_val = (pixel.blue - average) + newAverage; + pixel.blue = (BYTE)std::max(0, std::min(255, new_blue_val)); + } + // Alpha remains unchanged. return pixel; } +// Changes the luminance of the magenta component (red and blue channels) of a single pixel. +// Adjusts all color channels if magenta (min(R,B)) is more intense than the pixel's average intensity. Pixel ChangePixelLuminanceMagenta(Pixel pixel, float luminance) { - int average = (pixel.red + pixel.green + pixel.blue) / 3; - int magenta = (std::min)(pixel.red, pixel.blue); - if (magenta > average) + int magenta_component = std::min(pixel.red, pixel.blue); + if (magenta_component > average) // Only if magenta component is above average intensity { int newAverage = (int)(average * luminance); - if (((pixel.red - average) + newAverage <= 255) && ((pixel.red - average) + newAverage >= 0)) - pixel.red = (pixel.red - average) + newAverage; - else if (((pixel.red - average) + newAverage < 0)) - pixel.red = 0; - else - pixel.red = 255; - if (((pixel.green - average) + newAverage <= 255) && ((pixel.green - average) + newAverage >= 0)) - pixel.green = (pixel.green - average) + newAverage; - else if (((pixel.green - average) + newAverage < 0)) - pixel.green = 0; - else - pixel.green = 255; + int new_red_val = (pixel.red - average) + newAverage; + pixel.red = (BYTE)std::max(0, std::min(255, new_red_val)); - if (((pixel.blue - average) + newAverage <= 255) && ((pixel.blue - average) + newAverage >= 0)) - pixel.blue = (pixel.blue - average) + newAverage; - else if (((pixel.blue - average) + newAverage < 0)) - pixel.blue = 0; - else - pixel.blue = 255; - } + int new_green_val = (pixel.green - average) + newAverage; + pixel.green = (BYTE)std::max(0, std::min(255, new_green_val)); + int new_blue_val = (pixel.blue - average) + newAverage; + pixel.blue = (BYTE)std::max(0, std::min(255, new_blue_val)); + } + // Alpha remains unchanged. return pixel; } +// Changes the luminance of the yellow component (red and green channels) of a single pixel. +// Adjusts all color channels if yellow (min(R,G)) is more intense than the pixel's average intensity. Pixel ChangePixelLuminanceYellow(Pixel pixel, float luminance) { int average = (pixel.red + pixel.green + pixel.blue) / 3; - int yellow = (std::min)(pixel.red, pixel.green); - if (yellow > average) + int yellow_component = std::min(pixel.red, pixel.green); + if (yellow_component > average) // Only if yellow component is above average intensity { int newAverage = (int)(average * luminance); - if (((pixel.red - average) + newAverage <= 255) && ((pixel.red - average) + newAverage >= 0)) - pixel.red = (pixel.red - average) + newAverage; - else if (((pixel.red - average) + newAverage < 0)) - pixel.red = 0; - else - pixel.red = 255; - if (((pixel.green - average) + newAverage <= 255) && ((pixel.green - average) + newAverage >= 0)) - pixel.green = (pixel.green - average) + newAverage; - else if (((pixel.green - average) + newAverage < 0)) - pixel.green = 0; - else - pixel.green = 255; + int new_red_val = (pixel.red - average) + newAverage; + pixel.red = (BYTE)std::max(0, std::min(255, new_red_val)); - if (((pixel.blue - average) + newAverage <= 255) && ((pixel.blue - average) + newAverage >= 0)) - pixel.blue = (pixel.blue - average) + newAverage; - else if (((pixel.blue - average) + newAverage < 0)) - pixel.blue = 0; - else - pixel.blue = 255; - } + int new_green_val = (pixel.green - average) + newAverage; + pixel.green = (BYTE)std::max(0, std::min(255, new_green_val)); + int new_blue_val = (pixel.blue - average) + newAverage; + pixel.blue = (BYTE)std::max(0, std::min(255, new_blue_val)); + } + // Alpha remains unchanged. return pixel; } +// Changes the luminance of the cyan component (green and blue channels) of a single pixel. +// Adjusts all color channels if cyan (min(G,B)) is more intense than the pixel's average intensity. Pixel ChangePixelLuminanceCyan(Pixel pixel, float luminance) { int average = (pixel.red + pixel.green + pixel.blue) / 3; - int cyan = (std::min)(pixel.green, pixel.blue); - if (cyan > average) + int cyan_component = std::min(pixel.green, pixel.blue); + if (cyan_component > average) // Only if cyan component is above average intensity { int newAverage = (int)(average * luminance); - if (((pixel.red - average) + newAverage <= 255) && ((pixel.red - average) + newAverage >= 0)) - pixel.red = (pixel.red - average) + newAverage; - else if (((pixel.red - average) + newAverage < 0)) - pixel.red = 0; - else - pixel.red = 255; - if (((pixel.green - average) + newAverage <= 255) && ((pixel.green - average) + newAverage >= 0)) - pixel.green = (pixel.green - average) + newAverage; - else if (((pixel.green - average) + newAverage < 0)) - pixel.green = 0; - else - pixel.green = 255; + int new_red_val = (pixel.red - average) + newAverage; + pixel.red = (BYTE)std::max(0, std::min(255, new_red_val)); - if (((pixel.blue - average) + newAverage <= 255) && ((pixel.blue - average) + newAverage >= 0)) - pixel.blue = (pixel.blue - average) + newAverage; - else if (((pixel.blue - average) + newAverage < 0)) - pixel.blue = 0; - else - pixel.blue = 255; - } + int new_green_val = (pixel.green - average) + newAverage; + pixel.green = (BYTE)std::max(0, std::min(255, new_green_val)); + int new_blue_val = (pixel.blue - average) + newAverage; + pixel.blue = (BYTE)std::max(0, std::min(255, new_blue_val)); + } + // Alpha remains unchanged. return pixel; } +// Changes the luminance of the blue channel in the image. +// Note: The 'luminance' parameter name in the function signature here matches the .h file. +// The actual pixel manipulation is done by ChangePixelLuminanceBlue which takes 'luminance'. Bitmap::File ChangeImageLuminanceBlue(Bitmap::File bitmapFile, float luminance) { Matrix::Matrix imageMatrix = CreateMatrixFromBitmap(bitmapFile); @@ -820,6 +892,7 @@ Bitmap::File ChangeImageLuminanceBlue(Bitmap::File bitmapFile, float luminance) return bitmapFile; } +// Changes the luminance of the green channel in the image. Bitmap::File ChangeImageLuminanceGreen(Bitmap::File bitmapFile, float luminance) { Matrix::Matrix imageMatrix = CreateMatrixFromBitmap(bitmapFile); @@ -831,6 +904,7 @@ Bitmap::File ChangeImageLuminanceGreen(Bitmap::File bitmapFile, float luminance) return bitmapFile; } +// Changes the luminance of the red channel in the image. Bitmap::File ChangeImageLuminanceRed(Bitmap::File bitmapFile, float luminance) { Matrix::Matrix imageMatrix = CreateMatrixFromBitmap(bitmapFile); @@ -842,6 +916,7 @@ Bitmap::File ChangeImageLuminanceRed(Bitmap::File bitmapFile, float luminance) return bitmapFile; } +// Changes the luminance of the magenta component in the image. Bitmap::File ChangeImageLuminanceMagenta(Bitmap::File bitmapFile, float luminance) { Matrix::Matrix imageMatrix = CreateMatrixFromBitmap(bitmapFile); @@ -853,6 +928,7 @@ Bitmap::File ChangeImageLuminanceMagenta(Bitmap::File bitmapFile, float luminanc return bitmapFile; } +// Changes the luminance of the yellow component in the image. Bitmap::File ChangeImageLuminanceYellow(Bitmap::File bitmapFile, float luminance) { Matrix::Matrix imageMatrix = CreateMatrixFromBitmap(bitmapFile); @@ -864,6 +940,7 @@ Bitmap::File ChangeImageLuminanceYellow(Bitmap::File bitmapFile, float luminance return bitmapFile; } +// Changes the luminance of the cyan component in the image. Bitmap::File ChangeImageLuminanceCyan(Bitmap::File bitmapFile, float luminance) { Matrix::Matrix imageMatrix = CreateMatrixFromBitmap(bitmapFile); diff --git a/src/bitmap.h b/src/bitmap.h index 62cf23f..c71a5e7 100644 --- a/src/bitmap.h +++ b/src/bitmap.h @@ -3,62 +3,417 @@ #include "../dependencies/matrix/matrix.h" #include "../dependencies/bitmapfile/src/bitmap_file.h" -#include -#include +#include // For mathematical operations like sqrt, pow. +#include // For std::min, std::max. +// Defines a pixel structure with Blue, Green, Red, and Alpha channels. struct Pixel { - BYTE blue; - BYTE green; - BYTE red; - BYTE alpha; + BYTE blue; // Blue channel intensity. + BYTE green; // Green channel intensity. + BYTE red; // Red channel intensity. + BYTE alpha; // Alpha channel (transparency). }; +// Converts a Bitmap::File object into a Matrix::Matrix. +// Parameters: +// bitmapFile: The Bitmap::File object to convert. +// Returns: +// A Matrix::Matrix representing the image. Matrix::Matrix CreateMatrixFromBitmap(Bitmap::File bitmapFile); +// Captures a screenshot of a specified window. +// Parameters: +// WindowHandle: Handle to the window to capture. +// Returns: +// A Bitmap::File object containing the screenshot. Bitmap::File ScreenShotWindow(HWND WindowHandle); -Bitmap::File CreateBitmapFromMatrix(Matrix::Matrix imageMatix); + +// Converts a Matrix::Matrix into a Bitmap::File object. +// Parameters: +// imageMatrix: The Matrix::Matrix to convert. +// Returns: +// A Bitmap::File object representing the image. +Bitmap::File CreateBitmapFromMatrix(Matrix::Matrix imageMatrix); + +// Shrinks the image by a given scale factor. +// Parameters: +// bitmapFile: The image to shrink. +// scaleFactor: The factor by which to shrink the image (e.g., 2 for half size). +// Returns: +// A new Bitmap::File object with the shrunken image. Bitmap::File ShrinkImage(Bitmap::File bitmapFile, int scaleFactor); + +// Rotates the image 90 degrees counter-clockwise. +// Parameters: +// bitmapFile: The image to rotate. +// Returns: +// A new Bitmap::File object with the rotated image. Bitmap::File RotateImageCounterClockwise(Bitmap::File bitmapFile); + +// Rotates the image 90 degrees clockwise. +// Parameters: +// bitmapFile: The image to rotate. +// Returns: +// A new Bitmap::File object with the rotated image. Bitmap::File RotateImageClockwise(Bitmap::File bitmapFile); + +// Mirrors the image horizontally. +// Parameters: +// bitmapFile: The image to mirror. +// Returns: +// A new Bitmap::File object with the mirrored image. Bitmap::File MirrorImage(Bitmap::File bitmapFile); + +// Flips the image vertically. +// Parameters: +// bitmapFile: The image to flip. +// Returns: +// A new Bitmap::File object with the flipped image. Bitmap::File FlipImage(Bitmap::File bitmapFile); + +// Converts the image to greyscale. +// Parameters: +// bitmapFile: The image to convert. +// Returns: +// A new Bitmap::File object with the greyscale image. Bitmap::File GreyscaleImage(Bitmap::File bitmapFile); + +// Changes the overall brightness of the image. +// Parameters: +// bitmapFile: The image to modify. +// brightness: The brightness factor (e.g., 1.0 for no change, >1.0 for brighter, <1.0 for darker). +// Returns: +// A new Bitmap::File object with adjusted brightness. Bitmap::File ChangeImageBrightness(Bitmap::File bitmapFile, float brightness); + +// Changes the overall contrast of the image. +// Parameters: +// bitmapFile: The image to modify. +// contrast: The contrast factor (e.g., 1.0 for no change). +// Returns: +// A new Bitmap::File object with adjusted contrast. Bitmap::File ChangeImageContrast(Bitmap::File bitmapFile, float contrast); + +// Changes the overall saturation of the image. +// Parameters: +// bitmapFile: The image to modify. +// saturation: The saturation factor (e.g., 1.0 for no change, >1.0 for more saturated, <1.0 for less saturated). +// Returns: +// A new Bitmap::File object with adjusted saturation. Bitmap::File ChangeImageSaturation(Bitmap::File bitmapFile, float saturation); + +// Changes the saturation of the blue channel in the image. +// Parameters: +// bitmapFile: The image to modify. +// saturation: The saturation factor for the blue channel. +// Returns: +// A new Bitmap::File object with adjusted blue channel saturation. Bitmap::File ChangeImageSaturationBlue(Bitmap::File bitmapFile, float saturation); + +// Changes the saturation of the green channel in the image. +// Parameters: +// bitmapFile: The image to modify. +// saturation: The saturation factor for the green channel. +// Returns: +// A new Bitmap::File object with adjusted green channel saturation. Bitmap::File ChangeImageSaturationGreen(Bitmap::File bitmapFile, float saturation); + +// Changes the saturation of the red channel in the image. +// Parameters: +// bitmapFile: The image to modify. +// saturation: The saturation factor for the red channel. +// Returns: +// A new Bitmap::File object with adjusted red channel saturation. Bitmap::File ChangeImageSaturationRed(Bitmap::File bitmapFile, float saturation); + +// Changes the saturation of the magenta component (red and blue channels) in the image. +// Parameters: +// bitmapFile: The image to modify. +// saturation: The saturation factor for magenta. +// Returns: +// A new Bitmap::File object with adjusted magenta saturation. Bitmap::File ChangeImageSaturationMagenta(Bitmap::File bitmapFile, float saturation); + +// Changes the saturation of the yellow component (red and green channels) in the image. +// Parameters: +// bitmapFile: The image to modify. +// saturation: The saturation factor for yellow. +// Returns: +// A new Bitmap::File object with adjusted yellow saturation. Bitmap::File ChangeImageSaturationYellow(Bitmap::File bitmapFile, float saturation); + +// Changes the saturation of the cyan component (green and blue channels) in the image. +// Parameters: +// bitmapFile: The image to modify. +// saturation: The saturation factor for cyan. +// Returns: +// A new Bitmap::File object with adjusted cyan saturation. Bitmap::File ChangeImageSaturationCyan(Bitmap::File bitmapFile, float saturation); -Bitmap::File ChangeImageLuminanceBlue(Bitmap::File bitmapFile, float saturation); -Bitmap::File ChangeImageLuminanceGreen(Bitmap::File bitmapFile, float saturation); -Bitmap::File ChangeImageLuminanceRed(Bitmap::File bitmapFile, float saturation); -Bitmap::File ChangeImageLuminanceMagenta(Bitmap::File bitmapFile, float saturation); -Bitmap::File ChangeImageLuminanceYellow(Bitmap::File bitmapFile, float saturation); -Bitmap::File ChangeImageLuminanceCyan(Bitmap::File bitmapFile, float saturation); + +// Changes the luminance of the blue channel in the image. +// Parameters: +// bitmapFile: The image to modify. +// luminance: The luminance factor for the blue channel. (Note: Parameter name in .cpp is 'saturation' in original code, kept for consistency for now) +// Returns: +// A new Bitmap::File object with adjusted blue channel luminance. +Bitmap::File ChangeImageLuminanceBlue(Bitmap::File bitmapFile, float luminance); + +// Changes the luminance of the green channel in the image. +// Parameters: +// bitmapFile: The image to modify. +// luminance: The luminance factor for the green channel. (Note: Parameter name in .cpp is 'saturation' in original code, kept for consistency for now) +// Returns: +// A new Bitmap::File object with adjusted green channel luminance. +Bitmap::File ChangeImageLuminanceGreen(Bitmap::File bitmapFile, float luminance); + +// Changes the luminance of the red channel in the image. +// Parameters: +// bitmapFile: The image to modify. +// luminance: The luminance factor for the red channel. (Note: Parameter name in .cpp is 'saturation' in original code, kept for consistency for now) +// Returns: +// A new Bitmap::File object with adjusted red channel luminance. +Bitmap::File ChangeImageLuminanceRed(Bitmap::File bitmapFile, float luminance); + +// Changes the luminance of the magenta component (red and blue channels) in the image. +// Parameters: +// bitmapFile: The image to modify. +// luminance: The luminance factor for magenta. (Note: Parameter name in .cpp is 'saturation' in original code, kept for consistency for now) +// Returns: +// A new Bitmap::File object with adjusted magenta luminance. +Bitmap::File ChangeImageLuminanceMagenta(Bitmap::File bitmapFile, float luminance); + +// Changes the luminance of the yellow component (red and green channels) in the image. +// Parameters: +// bitmapFile: The image to modify. +// luminance: The luminance factor for yellow. (Note: Parameter name in .cpp is 'saturation' in original code, kept for consistency for now) +// Returns: +// A new Bitmap::File object with adjusted yellow luminance. +Bitmap::File ChangeImageLuminanceYellow(Bitmap::File bitmapFile, float luminance); + +// Changes the luminance of the cyan component (green and blue channels) in the image. +// Parameters: +// bitmapFile: The image to modify. +// luminance: The luminance factor for cyan. (Note: Parameter name in .cpp is 'saturation' in original code, kept for consistency for now) +// Returns: +// A new Bitmap::File object with adjusted cyan luminance. +Bitmap::File ChangeImageLuminanceCyan(Bitmap::File bitmapFile, float luminance); + +// Converts a single pixel to greyscale. +// Parameters: +// pixel: The input pixel. +// Returns: +// The greyscale equivalent of the input pixel. Pixel GreyScalePixel(Pixel pixel); -Pixel ChangePixelBrightness(Pixel pixel, float brightnessl); + +// Changes the brightness of a single pixel. +// Parameters: +// pixel: The input pixel. +// brightness: The brightness factor. +// Returns: +// The pixel with adjusted brightness. +Pixel ChangePixelBrightness(Pixel pixel, float brightness); // Note: original param name 'brightnessl' corrected to 'brightness' + +// Changes the contrast of a single pixel. +// Parameters: +// pixel: The input pixel. +// contrast: The contrast factor. +// Returns: +// The pixel with adjusted contrast. Pixel ChangePixelContrast(Pixel pixel, float contrast); + +// Changes the contrast of the red channel of a single pixel. +// Parameters: +// pixel: The input pixel. +// contrast: The contrast factor. +// Returns: +// The pixel with adjusted red channel contrast. Pixel ChangePixelContrastRed(Pixel pixel, float contrast); + +// Changes the contrast of the green channel of a single pixel. +// Parameters: +// pixel: The input pixel. +// contrast: The contrast factor. +// Returns: +// The pixel with adjusted green channel contrast. Pixel ChangePixelContrastGreen(Pixel pixel, float contrast); + +// Changes the contrast of the blue channel of a single pixel. +// Parameters: +// pixel: The input pixel. +// contrast: The contrast factor. +// Returns: +// The pixel with adjusted blue channel contrast. Pixel ChangePixelContrastBlue(Pixel pixel, float contrast); + +// Changes the contrast of the magenta component (red and blue channels) of a single pixel. +// Parameters: +// pixel: The input pixel. +// contrast: The contrast factor. +// Returns: +// The pixel with adjusted magenta contrast. Pixel ChangePixelContrastMagenta(Pixel pixel, float contrast); + +// Changes the contrast of the yellow component (red and green channels) of a single pixel. +// Parameters: +// pixel: The input pixel. +// contrast: The contrast factor. +// Returns: +// The pixel with adjusted yellow contrast. Pixel ChangePixelContrastYellow(Pixel pixel, float contrast); + +// Changes the contrast of the cyan component (green and blue channels) of a single pixel. +// Parameters: +// pixel: The input pixel. +// contrast: The contrast factor. +// Returns: +// The pixel with adjusted cyan contrast. Pixel ChangePixelContrastCyan(Pixel pixel, float contrast); + +// Changes the saturation of a single pixel. +// Parameters: +// pixel: The input pixel. +// saturation: The saturation factor. +// Returns: +// The pixel with adjusted saturation. Pixel ChangePixelSaturation(Pixel pixel, float saturation); + +// Changes the saturation of the blue channel of a single pixel. +// Parameters: +// pixel: The input pixel. +// saturation: The saturation factor. +// Returns: +// The pixel with adjusted blue channel saturation. Pixel ChangePixelSaturationBlue(Pixel pixel, float saturation); + +// Changes the saturation of the green channel of a single pixel. +// Parameters: +// pixel: The input pixel. +// saturation: The saturation factor. +// Returns: +// The pixel with adjusted green channel saturation. Pixel ChangePixelSaturationGreen(Pixel pixel, float saturation); + +// Changes the saturation of the red channel of a single pixel. +// Parameters: +// pixel: The input pixel. +// saturation: The saturation factor. +// Returns: +// The pixel with adjusted red channel saturation. Pixel ChangePixelSaturationRed(Pixel pixel, float saturation); + +// Changes the saturation of the magenta component (red and blue channels) of a single pixel. +// Parameters: +// pixel: The input pixel. +// saturation: The saturation factor. +// Returns: +// The pixel with adjusted magenta saturation. Pixel ChangePixelSaturationMagenta(Pixel pixel, float saturation); + +// Changes the saturation of the yellow component (red and green channels) of a single pixel. +// Parameters: +// pixel: The input pixel. +// saturation: The saturation factor. +// Returns: +// The pixel with adjusted yellow saturation. Pixel ChangePixelSaturationYellow(Pixel pixel, float saturation); + +// Changes the saturation of the cyan component (green and blue channels) of a single pixel. +// Parameters: +// pixel: The input pixel. +// saturation: The saturation factor. +// Returns: +// The pixel with adjusted cyan saturation. Pixel ChangePixelSaturationCyan(Pixel pixel, float saturation); + +// Changes the luminance of the blue channel of a single pixel. +// Parameters: +// pixel: The input pixel. +// luminance: The luminance factor. +// Returns: +// The pixel with adjusted blue channel luminance. Pixel ChangePixelLuminanceBlue(Pixel pixel, float luminance); + +// Changes the luminance of the green channel of a single pixel. +// Parameters: +// pixel: The input pixel. +// luminance: The luminance factor. +// Returns: +// The pixel with adjusted green channel luminance. Pixel ChangePixelLuminanceGreen(Pixel pixel, float luminance); + +// Changes the luminance of the red channel of a single pixel. +// Parameters: +// pixel: The input pixel. +// luminance: The luminance factor. +// Returns: +// The pixel with adjusted red channel luminance. Pixel ChangePixelLuminanceRed(Pixel pixel, float luminance); + +// Changes the luminance of the magenta component (red and blue channels) of a single pixel. +// Parameters: +// pixel: The input pixel. +// luminance: The luminance factor. +// Returns: +// The pixel with adjusted magenta luminance. Pixel ChangePixelLuminanceMagenta(Pixel pixel, float luminance); + +// Changes the luminance of the yellow component (red and green channels) of a single pixel. +// Parameters: +// pixel: The input pixel. +// luminance: The luminance factor. +// Returns: +// The pixel with adjusted yellow luminance. Pixel ChangePixelLuminanceYellow(Pixel pixel, float luminance); + +// Changes the luminance of the cyan component (green and blue channels) of a single pixel. +// Parameters: +// pixel: The input pixel. +// luminance: The luminance factor. +// Returns: +// The pixel with adjusted cyan luminance. Pixel ChangePixelLuminanceCyan(Pixel pixel, float luminance); + +// ---- New image manipulation functions ---- + +// Inverts the colors of the image. +// Parameters: +// bitmapFile: The image to invert. +// Returns: +// A new Bitmap::File object with inverted colors. +Bitmap::File InvertImageColors(Bitmap::File bitmapFile); + +// Applies a sepia tone to the image. +// Parameters: +// bitmapFile: The image to apply sepia tone to. +// Returns: +// A new Bitmap::File object with sepia tone applied. +Bitmap::File ApplySepiaTone(Bitmap::File bitmapFile); + +// Applies a box blur to the image. +// Parameters: +// bitmapFile: The image to blur. +// blurRadius: The radius of the blur box (e.g., 1 for a 3x3 box). Defaults to 1. +// Returns: +// A new Bitmap::File object with the box blur applied. +Bitmap::File ApplyBoxBlur(Bitmap::File bitmapFile, int blurRadius = 1); + +// ---- New pixel manipulation functions (helpers for the above) ---- + +// Inverts the color of a single pixel (Red, Green, Blue channels). Alpha is unchanged. +// Parameters: +// pixel: The input pixel. +// Returns: +// The pixel with inverted RGB colors. +Pixel InvertPixelColor(Pixel pixel); + +// Applies sepia tone to a single pixel. Alpha is unchanged. +// Parameters: +// pixel: The input pixel. +// Returns: +// The pixel with sepia tone applied. +Pixel ApplySepiaToPixel(Pixel pixel); + +// Note: Box blur is applied over a region within ApplyBoxBlur, +// so it does not have a direct single-pixel helper function here. #endif \ No newline at end of file diff --git a/tests/test_bitmap.cpp b/tests/test_bitmap.cpp new file mode 100644 index 0000000..d16ff21 --- /dev/null +++ b/tests/test_bitmap.cpp @@ -0,0 +1,625 @@ +#include +#include +#include // For fabs in pixel comparison + +// Include the header for the code to be tested +#include "../src/bitmap.h" // Adjust path as necessary + +int tests_run = 0; +int tests_passed = 0; + +#define ASSERT_EQUALS(expected, actual, message) \ + do { \ + tests_run++; \ + bool condition = (expected == actual); \ + if (condition) { \ + tests_passed++; \ + } else { \ + std::cerr << "ASSERTION FAILED: " << message \ + << " - Expected: " << expected \ + << ", Actual: " << actual << std::endl; \ + } \ + } while(0) + +// Overload for Pixel struct comparison +bool operator==(const Pixel& p1, const Pixel& p2) { + return p1.red == p2.red && + p1.green == p2.green && + p1.blue == p2.blue && + p1.alpha == p2.alpha; +} +// For pretty printing Pixel +std::ostream& operator<<(std::ostream& os, const Pixel& p) { + os << "R:" << (int)p.red << " G:" << (int)p.green << " B:" << (int)p.blue << " A:" << (int)p.alpha; + return os; +} + +// Helper for comparing floats with tolerance, if needed for factors +#define ASSERT_FLOAT_EQUALS(expected, actual, tolerance, message) \ + do { \ + tests_run++; \ + if (std::fabs((expected) - (actual)) < tolerance) { \ + tests_passed++; \ + } else { \ + std::cerr << "ASSERTION FAILED (FLOAT): " << message \ + << " - Expected: " << expected \ + << ", Actual: " << actual << std::endl; \ + } \ + } while(0) + +void test_InvertPixelColor() { + std::cout << "Running test_InvertPixelColor..." << std::endl; + Pixel p1 = {10, 20, 30, 255}; // B, G, R, A + Pixel expected1 = {225, 235, 245, 255}; // Inverted B, G, R, A (Note: My manual calc was R,G,B order, fixing for B,G,R) + ASSERT_EQUALS(expected1, InvertPixelColor(p1), "Invert P1"); + + Pixel p2 = {0, 0, 0, 100}; // Black + Pixel expected2 = {255, 255, 255, 100}; // White + ASSERT_EQUALS(expected2, InvertPixelColor(p2), "Invert Black"); + + Pixel p3 = {255, 255, 255, 50}; // White + Pixel expected3 = {0, 0, 0, 50}; // Black + ASSERT_EQUALS(expected3, InvertPixelColor(p3), "Invert White"); +} + +void test_ApplySepiaToPixel() { + std::cout << "Running test_ApplySepiaToPixel..." << std::endl; + Pixel p1 = {200, 150, 100, 255}; // B=200, G=150, R=100, A=255 + // Original values from description: R=100, G=150, B=200 + // tr = 0.393*100 + 0.769*150 + 0.189*200 = 39.3 + 115.35 + 37.8 = 192.45 -> 192 (red) + // tg = 0.349*100 + 0.686*150 + 0.168*200 = 34.9 + 102.9 + 33.6 = 171.4 -> 171 (green) + // tb = 0.272*100 + 0.534*150 + 0.131*200 = 27.2 + 80.1 + 26.2 = 133.5 -> 133 (blue) + // Pixel struct is B,G,R,A. So expected is {133, 171, 192, 255} + Pixel expected1 = {133, 171, 192, 255}; + ASSERT_EQUALS(expected1, ApplySepiaToPixel(p1), "Sepia P1"); + + Pixel p_white = {255, 255, 255, 255}; // B,G,R,A + // Original: R=255, G=255, B=255 + // tr = (0.393+0.769+0.189)*255 = 1.351*255 = 344.505 -> 255 (red) + // tg = (0.349+0.686+0.168)*255 = 1.203*255 = 306.765 -> 255 (green) + // tb = (0.272+0.534+0.131)*255 = 0.937*255 = 238.935 -> 238 (blue) + // Pixel struct is B,G,R,A. So expected is {238, 255, 255, 255} + Pixel expected_white_sepia = {238, 255, 255, 255}; + ASSERT_EQUALS(expected_white_sepia, ApplySepiaToPixel(p_white), "Sepia White"); +} + +void test_GreyScalePixel() { + std::cout << "Running test_GreyScalePixel..." << std::endl; + Pixel p1 = {30, 20, 10, 255}; // B=30, G=20, R=10. Avg = (10+20+30)/3 = 20 + Pixel expected1 = {20, 20, 20, 255}; + ASSERT_EQUALS(expected1, GreyScalePixel(p1), "Greyscale P1"); + + Pixel p2 = {100, 100, 100, 100}; // Already greyscale + Pixel expected2 = {100, 100, 100, 100}; + ASSERT_EQUALS(expected2, GreyScalePixel(p2), "Greyscale Already Grey"); +} + +// Helper function to create a Bitmap::File from a Matrix for testing +Bitmap::File CreateTestBitmap(const Matrix::Matrix& imageMatrix, int bitCount = 32 /* unused for now as CreateBitmapFromMatrix defaults to 32 */) { + if (imageMatrix.rows() == 0 || imageMatrix.cols() == 0) { + Bitmap::File invalidBitmapFile; // Default, IsValid() should be false + std::cerr << "CreateTestBitmap called with empty matrix." << std::endl; + return invalidBitmapFile; + } + // Use the existing CreateBitmapFromMatrix from bitmap.cpp + // This assumes CreateBitmapFromMatrix is robust and suitable for testing. + return CreateBitmapFromMatrix(imageMatrix); +} + +void test_ApplyBoxBlur() { + std::cout << "Running test_ApplyBoxBlur..." << std::endl; + + // Test Case 1: Uniform color image + Matrix::Matrix uniform_matrix(3, 3); + Pixel red_pixel = {0, 0, 255, 255}; // B, G, R, A + for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) uniform_matrix[i][j] = red_pixel; + + Bitmap::File uniform_bmp = CreateTestBitmap(uniform_matrix); + if (!uniform_bmp.IsValid()) { + std::cerr << "Failed to create uniform_bmp for ApplyBoxBlur test." << std::endl; + tests_run++; // Still counts as a run attempt + return; + } + Bitmap::File blurred_uniform_bmp = ApplyBoxBlur(uniform_bmp, 1); + ASSERT_EQUALS(true, blurred_uniform_bmp.IsValid(), "BoxBlur Uniform: Output valid"); + ASSERT_EQUALS(uniform_bmp.bitmapInfo.bmiHeader.biWidth, blurred_uniform_bmp.bitmapInfo.bmiHeader.biWidth, "BoxBlur Uniform: Width same"); + ASSERT_EQUALS(uniform_bmp.bitmapInfo.bmiHeader.biHeight, blurred_uniform_bmp.bitmapInfo.bmiHeader.biHeight, "BoxBlur Uniform: Height same"); + + Matrix::Matrix blurred_uniform_matrix = CreateMatrixFromBitmap(blurred_uniform_bmp); + if (blurred_uniform_matrix.rows() > 1 && blurred_uniform_matrix.cols() > 1) { // Ensure matrix is not empty + ASSERT_EQUALS(red_pixel, blurred_uniform_matrix[1][1], "BoxBlur Uniform: Center pixel unchanged"); + } else { + std::cerr << "Blurred uniform matrix too small for content check." << std::endl; + tests_run++; // Count as a run attempt + } + + + // Test Case 2: Simple pattern (black border, white center on 3x3) + Matrix::Matrix pattern_matrix(3, 3); + Pixel black_pixel = {0, 0, 0, 255}; + Pixel white_pixel = {255, 255, 255, 255}; + for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) pattern_matrix[i][j] = black_pixel; + pattern_matrix[1][1] = white_pixel; // Center is white + + Bitmap::File pattern_bmp = CreateTestBitmap(pattern_matrix); + if (!pattern_bmp.IsValid()) { + std::cerr << "Failed to create pattern_bmp for ApplyBoxBlur test." << std::endl; + tests_run++; + return; + } + Bitmap::File blurred_pattern_bmp = ApplyBoxBlur(pattern_bmp, 1); + ASSERT_EQUALS(true, blurred_pattern_bmp.IsValid(), "BoxBlur Pattern: Output valid"); + Matrix::Matrix blurred_pattern_matrix = CreateMatrixFromBitmap(blurred_pattern_bmp); + + // Center pixel (1,1) is averaged with its 8 neighbors (all black) + itself (white) + // Total = 9 pixels. Sum R = 255, Sum G = 255, Sum B = 255. Alpha sum = 9*255 + // Avg R = 255/9 = 28. (similar for G, B). Avg Alpha = 255. + Pixel expected_center_pixel = {28, 28, 28, 255}; // BGR, Alpha + if (blurred_pattern_matrix.rows() > 1 && blurred_pattern_matrix.cols() > 1) { + ASSERT_EQUALS(expected_center_pixel, blurred_pattern_matrix[1][1], "BoxBlur Pattern: Center pixel blurred"); + } else { + std::cerr << "Blurred pattern matrix too small for content check." << std::endl; + tests_run++; + } + + + // Test Case 3: Blur radius 0 + Bitmap::File original_bmp_for_radius0 = CreateTestBitmap(pattern_matrix); // Re-use pattern + if (!original_bmp_for_radius0.IsValid()) { + std::cerr << "Failed to create original_bmp_for_radius0 for ApplyBoxBlur test." << std::endl; + tests_run++; + return; + } + Bitmap::File not_blurred_bmp = ApplyBoxBlur(original_bmp_for_radius0, 0); + ASSERT_EQUALS(true, not_blurred_bmp.IsValid(), "BoxBlur Radius 0: Output valid"); + Matrix::Matrix not_blurred_matrix = CreateMatrixFromBitmap(not_blurred_bmp); + if (not_blurred_matrix.rows() > 1 && not_blurred_matrix.cols() > 1 && pattern_matrix.rows() > 1 && pattern_matrix.cols() > 1) { + ASSERT_EQUALS(pattern_matrix[1][1], not_blurred_matrix[1][1], "BoxBlur Radius 0: Center pixel unchanged"); + } else { + std::cerr << "Not blurred matrix or pattern matrix too small for content check (radius 0)." << std::endl; + tests_run++; + } +} + +void test_ShrinkImage() { + std::cout << "Running test_ShrinkImage..." << std::endl; + Matrix::Matrix large_matrix(4, 4); // 4x4 image + for (int i = 0; i < 4; ++i) for (int j = 0; j < 4; ++j) large_matrix[i][j] = {(BYTE)(i*10), (BYTE)(j*10), (BYTE)((i+j)*10), 255}; + + Bitmap::File large_bmp = CreateTestBitmap(large_matrix); + if (!large_bmp.IsValid()) { + std::cerr << "Failed to create large_bmp for ShrinkImage test." << std::endl; + tests_run++; + return; + } + + int scaleFactor = 2; + Bitmap::File shrunk_bmp = ShrinkImage(large_bmp, scaleFactor); + ASSERT_EQUALS(true, shrunk_bmp.IsValid(), "ShrinkImage: Output valid"); + ASSERT_EQUALS(large_bmp.bitmapInfo.bmiHeader.biWidth / scaleFactor, shrunk_bmp.bitmapInfo.bmiHeader.biWidth, "ShrinkImage: Width correct"); + ASSERT_EQUALS(large_bmp.bitmapInfo.bmiHeader.biHeight / scaleFactor, shrunk_bmp.bitmapInfo.bmiHeader.biHeight, "ShrinkImage: Height correct"); + + Matrix::Matrix shrunk_matrix = CreateMatrixFromBitmap(shrunk_bmp); + // Verifying pixel at (0,0) of the shrunk image. + // This pixel is the average of the 2x2 block at (0,0) in the original large_matrix. + // large_matrix[0][0] = {B:0, G:0, R:0, A:255} + // large_matrix[0][1] = {B:0, G:10, R:10, A:255} + // large_matrix[1][0] = {B:10, G:0, R:10, A:255} + // large_matrix[1][1] = {B:10, G:10, R:20, A:255} + // Average: + // Blue: (0+0+10+10)/4 = 5 + // Green: (0+10+0+10)/4 = 5 + // Red: (0+10+10+20)/4 = 10 + // Alpha: (255*4)/4 = 255 + Pixel expected_shrunk_pixel00 = {5, 5, 10, 255}; // BGR, Alpha + if (shrunk_matrix.rows() > 0 && shrunk_matrix.cols() > 0) { + ASSERT_EQUALS(expected_shrunk_pixel00, shrunk_matrix[0][0], "ShrinkImage: Pixel [0][0] content basic check"); + } else { + std::cerr << "Shrunk matrix too small for content check." << std::endl; + tests_run++; + } +} + +void test_RotateImage() { // Specifically for RotateImageCounterClockwise + std::cout << "Running test_RotateImage (CounterClockwise)..." << std::endl; + Matrix::Matrix rect_matrix(2, 3); // 2 rows, 3 cols + Pixel p1 = {10,20,30,255}, p2 = {40,50,60,255}, p3 = {70,80,90,255}; // row 0 + Pixel p4 = {11,22,33,255}, p5 = {44,55,66,255}, p6 = {77,88,99,255}; // row 1 + rect_matrix[0][0]=p1; rect_matrix[0][1]=p2; rect_matrix[0][2]=p3; + rect_matrix[1][0]=p4; rect_matrix[1][1]=p5; rect_matrix[1][2]=p6; + + Bitmap::File rect_bmp = CreateTestBitmap(rect_matrix); + if (!rect_bmp.IsValid()) { + std::cerr << "Failed to create rect_bmp for RotateImage test." << std::endl; + tests_run++; + return; + } + + Bitmap::File rotated_bmp = RotateImageCounterClockwise(rect_bmp); + ASSERT_EQUALS(true, rotated_bmp.IsValid(), "RotateImageCCW: Output valid"); + ASSERT_EQUALS(rect_bmp.bitmapInfo.bmiHeader.biHeight, rotated_bmp.bitmapInfo.bmiHeader.biWidth, "RotateImageCCW: Width is old height (2)"); // Original height was 2 + ASSERT_EQUALS(rect_bmp.bitmapInfo.bmiHeader.biWidth, rotated_bmp.bitmapInfo.bmiHeader.biHeight, "RotateImageCCW: Height is old width (3)"); // Original width was 3 + + Matrix::Matrix rotated_matrix = CreateMatrixFromBitmap(rotated_bmp); + // Original imageMatrix[i][j] + // Rotated CounterClockwise: rotatedMatrix[j][new_cols - 1 - i] where new_cols is original rows + // new_cols = imageMatrix.rows() = 2 + // rotatedMatrix[j][imageMatrix.rows() - 1 - i] + // p1 (0,0) -> rotated_matrix[0][2-1-0] = rotated_matrix[0][1] + // p2 (0,1) -> rotated_matrix[1][2-1-0] = rotated_matrix[1][1] + // p3 (0,2) -> rotated_matrix[2][2-1-0] = rotated_matrix[2][1] + // p4 (1,0) -> rotated_matrix[0][2-1-1] = rotated_matrix[0][0] + // p5 (1,1) -> rotated_matrix[1][2-1-1] = rotated_matrix[1][0] + // p6 (1,2) -> rotated_matrix[2][2-1-1] = rotated_matrix[2][0] + if (rotated_matrix.rows() == 3 && rotated_matrix.cols() == 2) { + ASSERT_EQUALS(p4, rotated_matrix[0][0], "RotateImageCCW: Content check rect_matrix[1][0] -> rotated[0][0]"); + ASSERT_EQUALS(p1, rotated_matrix[0][1], "RotateImageCCW: Content check rect_matrix[0][0] -> rotated[0][1]"); + ASSERT_EQUALS(p5, rotated_matrix[1][0], "RotateImageCCW: Content check rect_matrix[1][1] -> rotated[1][0]"); + ASSERT_EQUALS(p2, rotated_matrix[1][1], "RotateImageCCW: Content check rect_matrix[0][1] -> rotated[1][1]"); + ASSERT_EQUALS(p6, rotated_matrix[2][0], "RotateImageCCW: Content check rect_matrix[1][2] -> rotated[2][0]"); + ASSERT_EQUALS(p3, rotated_matrix[2][1], "RotateImageCCW: Content check rect_matrix[0][2] -> rotated[2][1]"); + } else { + std::cerr << "Rotated matrix has unexpected dimensions." << std::endl; + tests_run++; + } +} + +// Placeholder/Basic tests for other image functions +void test_OtherImageFunctions_Placeholders() { + std::cout << "Running test_OtherImageFunctions_Placeholders..." << std::endl; + Matrix::Matrix base_matrix(2, 2); + base_matrix[0][0] = {10,20,30,255}; base_matrix[0][1] = {40,50,60,255}; + base_matrix[1][0] = {70,80,90,255}; base_matrix[1][1] = {100,110,120,255}; + Bitmap::File base_bmp = CreateTestBitmap(base_matrix); + if (!base_bmp.IsValid()) { + std::cerr << "Failed to create base_bmp for placeholder tests." << std::endl; + tests_run++; return; + } + + // RotateImageClockwise + Bitmap::File rotated_cw_bmp = RotateImageClockwise(base_bmp); + ASSERT_EQUALS(true, rotated_cw_bmp.IsValid(), "RotateCW: Output valid"); + ASSERT_EQUALS(base_bmp.bitmapInfo.bmiHeader.biHeight, rotated_cw_bmp.bitmapInfo.bmiHeader.biWidth, "RotateCW: Width is old height"); + ASSERT_EQUALS(base_bmp.bitmapInfo.bmiHeader.biWidth, rotated_cw_bmp.bitmapInfo.bmiHeader.biHeight, "RotateCW: Height is old width"); + + // MirrorImage + Bitmap::File mirrored_bmp = MirrorImage(base_bmp); + ASSERT_EQUALS(true, mirrored_bmp.IsValid(), "MirrorImage: Output valid"); + ASSERT_EQUALS(base_bmp.bitmapInfo.bmiHeader.biWidth, mirrored_bmp.bitmapInfo.bmiHeader.biWidth, "MirrorImage: Width same"); + ASSERT_EQUALS(base_bmp.bitmapInfo.bmiHeader.biHeight, mirrored_bmp.bitmapInfo.bmiHeader.biHeight, "MirrorImage: Height same"); + + // FlipImage + Bitmap::File flipped_bmp = FlipImage(base_bmp); + ASSERT_EQUALS(true, flipped_bmp.IsValid(), "FlipImage: Output valid"); + ASSERT_EQUALS(base_bmp.bitmapInfo.bmiHeader.biWidth, flipped_bmp.bitmapInfo.bmiHeader.biWidth, "FlipImage: Width same"); + ASSERT_EQUALS(base_bmp.bitmapInfo.bmiHeader.biHeight, flipped_bmp.bitmapInfo.bmiHeader.biHeight, "FlipImage: Height same"); + + // GreyscaleImage + Bitmap::File grey_bmp = GreyscaleImage(base_bmp); + ASSERT_EQUALS(true, grey_bmp.IsValid(), "GreyscaleImage: Output valid"); + Matrix::Matrix grey_matrix = CreateMatrixFromBitmap(grey_bmp); + if (grey_matrix.rows() > 0 && grey_matrix.cols() > 0) { + Pixel p = grey_matrix[0][0]; + ASSERT_EQUALS(p.red, p.green, "GreyscaleImage: R=G check"); + ASSERT_EQUALS(p.green, p.blue, "GreyscaleImage: G=B check"); + } + + // InvertImageColors + Bitmap::File inverted_bmp = InvertImageColors(base_bmp); + ASSERT_EQUALS(true, inverted_bmp.IsValid(), "InvertImageColors: Output valid"); + Matrix::Matrix inverted_matrix = CreateMatrixFromBitmap(inverted_bmp); + if (inverted_matrix.rows() > 0 && inverted_matrix.cols() > 0 && base_matrix.rows() > 0 && base_matrix.cols() > 0) { + Pixel original_p = base_matrix[0][0]; + Pixel inverted_p = inverted_matrix[0][0]; + ASSERT_EQUALS((BYTE)(255-original_p.red), inverted_p.red, "InvertImageColors: Red channel inverted"); + } + + // ApplySepiaTone + Bitmap::File sepia_bmp = ApplySepiaTone(base_bmp); + ASSERT_EQUALS(true, sepia_bmp.IsValid(), "ApplySepiaTone: Output valid"); + // Basic content check for sepia could compare one pixel to its ApplySepiaToPixel result + Matrix::Matrix sepia_matrix = CreateMatrixFromBitmap(sepia_bmp); + if (sepia_matrix.rows() > 0 && sepia_matrix.cols() > 0 && base_matrix.rows() > 0 && base_matrix.cols() > 0) { + Pixel original_p = base_matrix[0][0]; + Pixel expected_sepia_p = ApplySepiaToPixel(original_p); + ASSERT_EQUALS(expected_sepia_p, sepia_matrix[0][0], "ApplySepiaTone: Pixel[0][0] matches helper"); + } +} + + +void test_ChangePixelBrightness() { + std::cout << "Running test_ChangePixelBrightness..." << std::endl; + Pixel p_mid = {100, 120, 140, 255}; // B, G, R, A. Avg = (140+120+100)/3 = 120 + + ASSERT_EQUALS(p_mid, ChangePixelBrightness(p_mid, 1.0f), "Brightness 1.0 no change"); + + // Expected for 1.5: Avg = 120. NewAvg = 120*1.5 = 180. + // R_new = (140-120)+180 = 20+180 = 200 + // G_new = (120-120)+180 = 0+180 = 180 + // B_new = (100-120)+180 = -20+180 = 160 + Pixel expected_bright = {160, 180, 200, 255}; + ASSERT_EQUALS(expected_bright, ChangePixelBrightness(p_mid, 1.5f), "Brightness 1.5 increase"); + + // Expected for 0.5: Avg = 120. NewAvg = 120*0.5 = 60. + // R_new = (140-120)+60 = 20+60 = 80 + // G_new = (120-120)+60 = 0+60 = 60 + // B_new = (100-120)+60 = -20+60 = 40 + Pixel expected_dark = {40, 60, 80, 255}; + ASSERT_EQUALS(expected_dark, ChangePixelBrightness(p_mid, 0.5f), "Brightness 0.5 decrease"); + + Pixel p_black = {0,0,0,255}; // Avg = 0. NewAvg = 0. + // R_new = (0-0)+0 = 0. G_new = 0. B_new = 0. + ASSERT_EQUALS(p_black, ChangePixelBrightness(p_black, 1.5f), "Brightness 1.5 on black"); + // Expected for 0.0: Avg = 120. NewAvg = 0. + // R_new = (140-120)+0 = 20 + // G_new = (120-120)+0 = 0 + // B_new = (100-120)+0 = -20 -> 0 + Pixel expected_zero_bright = {0, 0, 20, 255}; + ASSERT_EQUALS(expected_zero_bright, ChangePixelBrightness(p_mid, 0.0f), "Brightness 0.0 from mid"); + + Pixel p_white = {255,255,255,255}; // Avg = 255. NewAvg = 255*1.5 = 382 (clamped in components) + // R_new = (255-255)+382 = 382 -> 255 + // G_new = (255-255)+382 = 382 -> 255 + // B_new = (255-255)+382 = 382 -> 255 + ASSERT_EQUALS(p_white, ChangePixelBrightness(p_white, 1.5f), "Brightness 1.5 on white (clamp)"); + + Pixel p_dark_to_bright = {10, 20, 30, 255}; // Avg = (30+20+10)/3 = 20. + // Brightness 20.0. NewAvg = 20*20 = 400 + // R_new = (30-20)+400 = 10+400 = 410 -> 255 + // G_new = (20-20)+400 = 0+400 = 400 -> 255 + // B_new = (10-20)+400 = -10+400 = 390 -> 255 + Pixel expected_dark_to_bright_clamped = {255, 255, 255, 255}; + ASSERT_EQUALS(expected_dark_to_bright_clamped, ChangePixelBrightness(p_dark_to_bright, 20.0f), "Brightness high clamp dark pixel"); +} + +void test_ChangePixelContrast() { + std::cout << "Running test_ChangePixelContrast..." << std::endl; + Pixel p_mid_gray = {128, 128, 128, 255}; // B, G, R, A + ASSERT_EQUALS(p_mid_gray, ChangePixelContrast(p_mid_gray, 1.0f), "Contrast 1.0 on mid-gray"); + ASSERT_EQUALS(p_mid_gray, ChangePixelContrast(p_mid_gray, 1.5f), "Contrast 1.5 on mid-gray (no change expected)"); + ASSERT_EQUALS(p_mid_gray, ChangePixelContrast(p_mid_gray, 0.5f), "Contrast 0.5 on mid-gray (no change expected)"); + ASSERT_EQUALS(p_mid_gray, ChangePixelContrast(p_mid_gray, 0.0f), "Contrast 0.0 on mid-gray (no change expected)"); + + Pixel p_dark = {50, 60, 70, 255}; // B, G, R + // Expected for contrast 2.0: + // B_new = 128 + (50-128)*2 = 128 - 78*2 = 128 - 156 = -28 -> 0 + // G_new = 128 + (60-128)*2 = 128 - 68*2 = 128 - 136 = -8 -> 0 + // R_new = 128 + (70-128)*2 = 128 - 58*2 = 128 - 116 = 12 + Pixel expected_contrast_high_dark = {0, 0, 12, 255}; + ASSERT_EQUALS(expected_contrast_high_dark, ChangePixelContrast(p_dark, 2.0f), "Contrast 2.0 on dark"); + + Pixel p_light = {200, 210, 220, 255}; // B, G, R + // Expected for contrast 2.0: + // B_new = 128 + (200-128)*2 = 128 + 72*2 = 128 + 144 = 272 -> 255 + // G_new = 128 + (210-128)*2 = 128 + 82*2 = 128 + 164 = 292 -> 255 + // R_new = 128 + (220-128)*2 = 128 + 92*2 = 128 + 184 = 312 -> 255 + Pixel expected_contrast_high_light = {255, 255, 255, 255}; + ASSERT_EQUALS(expected_contrast_high_light, ChangePixelContrast(p_light, 2.0f), "Contrast 2.0 on light (clamp)"); + + Pixel expected_contrast_zero = {128, 128, 128, 255}; // For contrast 0.0, all channels become 128 + ASSERT_EQUALS(expected_contrast_zero, ChangePixelContrast(p_dark, 0.0f), "Contrast 0.0 on dark"); + ASSERT_EQUALS(expected_contrast_zero, ChangePixelContrast(p_light, 0.0f), "Contrast 0.0 on light"); +} + +void test_ChangePixelContrastRed() { + std::cout << "Running test_ChangePixelContrastRed..." << std::endl; + Pixel p1 = {50, 100, 150, 255}; // B, G, R + // Apply contrast 2.0 to Red (150): R_new = 128 + (150-128)*2 = 128 + 22*2 = 128+44 = 172 + Pixel expected_p1_red_contrast = {50, 100, 172, 255}; + ASSERT_EQUALS(expected_p1_red_contrast, ChangePixelContrastRed(p1, 2.0f), "Contrast Red P1 factor 2.0"); + // Other channels (B, G) and Alpha should remain unchanged. +} + +void test_ChangePixelContrastGreen() { + std::cout << "Running test_ChangePixelContrastGreen..." << std::endl; + Pixel p1 = {50, 100, 150, 255}; // B, G, R + // Apply contrast 2.0 to Green (100): G_new = 128 + (100-128)*2 = 128 - 28*2 = 128-56 = 72 + Pixel expected_p1_green_contrast = {50, 72, 150, 255}; + ASSERT_EQUALS(expected_p1_green_contrast, ChangePixelContrastGreen(p1, 2.0f), "Contrast Green P1 factor 2.0"); +} + +void test_ChangePixelContrastBlue() { + std::cout << "Running test_ChangePixelContrastBlue..." << std::endl; + Pixel p1 = {50, 100, 150, 255}; // B, G, R + // Apply contrast 2.0 to Blue (50): B_new = 128 + (50-128)*2 = 128 - 78*2 = 128-156 = -28 -> 0 + Pixel expected_p1_blue_contrast = {0, 100, 150, 255}; + ASSERT_EQUALS(expected_p1_blue_contrast, ChangePixelContrastBlue(p1, 2.0f), "Contrast Blue P1 factor 2.0"); +} + +void test_ChangePixelContrastMagenta() { + std::cout << "Running test_ChangePixelContrastMagenta..." << std::endl; + Pixel p1 = {30, 60, 90, 255}; // B=30, G=60, R=90 + // Contrast 2.0. Affects Red and Blue. Green unchanged. + // Blue_new: 128 + (30-128)*2 = 128 - 98*2 = 128 - 196 = -68 -> 0 + // Red_new: 128 + (90-128)*2 = 128 - 38*2 = 128 - 76 = 52 + Pixel expected_p1_magenta_contrast = {0, 60, 52, 255}; + ASSERT_EQUALS(expected_p1_magenta_contrast, ChangePixelContrastMagenta(p1, 2.0f), "Contrast Magenta P1 factor 2.0"); +} + +void test_ChangePixelContrastYellow() { + std::cout << "Running test_ChangePixelContrastYellow..." << std::endl; + Pixel p1 = {30, 60, 90, 255}; // B=30, G=60, R=90 + // Contrast 2.0. Affects Red and Green. Blue unchanged. + // Red_new: 128 + (90-128)*2 = 128 - 38*2 = 128 - 76 = 52 + // Green_new:128 + (60-128)*2 = 128 - 68*2 = 128 - 136 = -8 -> 0 + Pixel expected_p1_yellow_contrast = {30, 0, 52, 255}; + ASSERT_EQUALS(expected_p1_yellow_contrast, ChangePixelContrastYellow(p1, 2.0f), "Contrast Yellow P1 factor 2.0"); +} + +void test_ChangePixelContrastCyan() { + std::cout << "Running test_ChangePixelContrastCyan..." << std::endl; + Pixel p1 = {30, 60, 90, 255}; // B=30, G=60, R=90 + // Contrast 2.0. Affects Green and Blue. Red unchanged. + // Blue_new: 128 + (30-128)*2 = 128 - 98*2 = 128 - 196 = -68 -> 0 + // Green_new:128 + (60-128)*2 = 128 - 68*2 = 128 - 136 = -8 -> 0 + Pixel expected_p1_cyan_contrast = {0, 0, 90, 255}; + ASSERT_EQUALS(expected_p1_cyan_contrast, ChangePixelContrastCyan(p1, 2.0f), "Contrast Cyan P1 factor 2.0"); +} + +void test_ChangePixelSaturation() { + std::cout << "Running test_ChangePixelSaturation..." << std::endl; + Pixel p_color = {50, 100, 150, 255}; // B=50, G=100, R=150. Avg = (150+100+50)/3 = 100 + ASSERT_EQUALS(p_color, ChangePixelSaturation(p_color, 1.0f), "Saturation 1.0 (no change)"); + + Pixel expected_greyscale = {100, 100, 100, 255}; // Avg = 100 + // When saturation is 0.0, the logic in bitmap.cpp is: + // R (150) > 100: R_new = (150-100)*0.0 + 100 = 100 + // G (100) not > 100: G_new = 100 + // B (50) not > 100: B_new = 50. <- This is the key! The current code does not make B=100. + // So, it will not become perfectly greyscale by averaging if some components are <= average. + // It will make components > average equal to average. Components <= average are untouched. + Pixel actual_sat_zero_p_color = {50, 100, 100, 255}; + ASSERT_EQUALS(actual_sat_zero_p_color, ChangePixelSaturation(p_color, 0.0f), "Saturation 0.0 p_color"); + + Pixel p_all_above_avg = {110, 120, 130, 255}; // Avg = 120 + // R(130) > 120: R_new = (130-120)*0.0 + 120 = 120 + // G(120) not > 120: G_new = 120 + // B(110) not > 120: B_new = 110 + Pixel expected_greyscale_all_above = {110, 120, 120, 255}; // Actually, this should be {120,120,120} if it worked as expected greyscale + ASSERT_EQUALS(expected_greyscale_all_above, ChangePixelSaturation(p_all_above_avg, 0.0f), "Saturation 0.0 all_above_avg"); + + + // For p_color (B=50, G=100, R=150), Avg=100: Saturation 2.0 + // R (150) > 100: R_new = (150-100)*2.0 + 100 = 50*2 + 100 = 200 + // G (100) is not > 100. G_new = 100 + // B (50) is not > 100. B_new = 50 + Pixel expected_sat_2 = {50, 100, 200, 255}; + ASSERT_EQUALS(expected_sat_2, ChangePixelSaturation(p_color, 2.0f), "Saturation 2.0 p_color"); + + Pixel p_less_sat = {80, 100, 120, 255}; // B=80, G=100, R=120. Avg = 100 + // Saturation 0.5 + // R(120) > 100: R_new = (120-100)*0.5 + 100 = 10+100 = 110 + // G(100) not > 100: G_new = 100 + // B(80) not > 100: B_new = 80 + Pixel expected_sat_0_5 = {80, 100, 110, 255}; + ASSERT_EQUALS(expected_sat_0_5, ChangePixelSaturation(p_less_sat, 0.5f), "Saturation 0.5 p_less_sat"); +} + +void test_ChangePixelLuminanceBlue() { + std::cout << "Running test_ChangePixelLuminanceBlue..." << std::endl; + // Case 1: Blue is dominant, luminance increases + Pixel p_blue_dom = {150, 50, 50, 255}; // B=150, G=50, R=50. Avg=(50+50+150)/3 = 250/3 = 83 + float lum_factor = 1.5f; + // NewAvg = 83 * 1.5 = 124.5 -> 124 (integer conversion) + // R_new = (50-83)+124 = -33+124 = 91 + // G_new = (50-83)+124 = -33+124 = 91 + // B_new = (150-83)+124 = 67+124 = 191 + Pixel expected_p_blue_dom_lum = {191, 91, 91, 255}; + ASSERT_EQUALS(expected_p_blue_dom_lum, ChangePixelLuminanceBlue(p_blue_dom, lum_factor), "Luminance Blue dominant, factor 1.5"); + + // Case 2: Blue is not dominant, pixel should be unchanged + Pixel p_red_dom = {50, 150, 50, 255}; // B=50, G=50, R=150 (Corrected to make Red dominant over Blue) + ASSERT_EQUALS(p_red_dom, ChangePixelLuminanceBlue(p_red_dom, lum_factor), "Luminance Blue not dominant (Red dominant)"); +} + +void test_ChangePixelLuminanceGreen() { + std::cout << "Running test_ChangePixelLuminanceGreen..." << std::endl; + Pixel p_green_dom = {50, 150, 50, 255}; // B=50, G=150, R=50. Avg = 83 + float lum_factor = 1.5f; // NewAvg = 124 + // R_new = (50-83)+124 = 91 + // G_new = (150-83)+124 = 191 + // B_new = (50-83)+124 = 91 + Pixel expected_p_green_dom_lum = {91, 191, 91, 255}; + ASSERT_EQUALS(expected_p_green_dom_lum, ChangePixelLuminanceGreen(p_green_dom, lum_factor), "Luminance Green dominant, factor 1.5"); + + Pixel p_blue_dom = {150, 50, 50, 255}; + ASSERT_EQUALS(p_blue_dom, ChangePixelLuminanceGreen(p_blue_dom, lum_factor), "Luminance Green not dominant (Blue dominant)"); +} + +void test_ChangePixelLuminanceRed() { + std::cout << "Running test_ChangePixelLuminanceRed..." << std::endl; + Pixel p_red_dom = {50, 50, 150, 255}; // B=50, G=50, R=150. Avg = 83 + float lum_factor = 1.5f; // NewAvg = 124 + // R_new = (150-83)+124 = 191 + // G_new = (50-83)+124 = 91 + // B_new = (50-83)+124 = 91 + Pixel expected_p_red_dom_lum = {91, 91, 191, 255}; + ASSERT_EQUALS(expected_p_red_dom_lum, ChangePixelLuminanceRed(p_red_dom, lum_factor), "Luminance Red dominant, factor 1.5"); + + Pixel p_green_dom = {50, 150, 50, 255}; + ASSERT_EQUALS(p_green_dom, ChangePixelLuminanceRed(p_green_dom, lum_factor), "Luminance Red not dominant (Green dominant)"); +} + +void test_ChangePixelLuminanceMagenta() { + std::cout << "Running test_ChangePixelLuminanceMagenta..." << std::endl; + // Magenta means R and B are significant. Condition is `magenta_component > average`. + // magenta_component = std::min(pixel.red, pixel.blue) + Pixel p_magenta_ish = {140, 20, 150, 255}; // B=140, G=20, R=150. Avg=(150+20+140)/3 = 310/3 = 103. Magenta_comp = min(150,140)=140. 140 > 103. + float lum_factor = 1.2f; // NewAvg = 103 * 1.2 = 123.6 -> 123 + // R_new = (150-103)+123 = 47+123 = 170 + // G_new = (20-103)+123 = -83+123 = 40 + // B_new = (140-103)+123 = 37+123 = 160 + Pixel expected_p_magenta_lum = {160, 40, 170, 255}; + ASSERT_EQUALS(expected_p_magenta_lum, ChangePixelLuminanceMagenta(p_magenta_ish, lum_factor), "Luminance Magenta-ish, factor 1.2"); + + Pixel p_not_magenta = {20, 150, 30, 255}; // G is dominant. Avg=(30+150+20)/3 = 200/3 = 66. Magenta_comp=min(30,20)=20. 20 is not > 66. + ASSERT_EQUALS(p_not_magenta, ChangePixelLuminanceMagenta(p_not_magenta, lum_factor), "Luminance Magenta not dominant"); +} + +void test_ChangePixelLuminanceYellow() { + std::cout << "Running test_ChangePixelLuminanceYellow..." << std::endl; + // Yellow means R and G are significant. Condition is `yellow_component > average`. + // yellow_component = std::min(pixel.red, pixel.green) + Pixel p_yellow_ish = {20, 140, 150, 255}; // B=20, G=140, R=150. Avg=(150+140+20)/3 = 310/3 = 103. Yellow_comp = min(150,140)=140. 140 > 103. + float lum_factor = 0.8f; // NewAvg = 103 * 0.8 = 82.4 -> 82 + // R_new = (150-103)+82 = 47+82 = 129 + // G_new = (140-103)+82 = 37+82 = 119 + // B_new = (20-103)+82 = -83+82 = -1 -> 0 + Pixel expected_p_yellow_lum = {0, 119, 129, 255}; + ASSERT_EQUALS(expected_p_yellow_lum, ChangePixelLuminanceYellow(p_yellow_ish, lum_factor), "Luminance Yellow-ish, factor 0.8"); + + Pixel p_not_yellow = {150, 20, 30, 255}; // B is dominant + ASSERT_EQUALS(p_not_yellow, ChangePixelLuminanceYellow(p_not_yellow, lum_factor), "Luminance Yellow not dominant"); +} + +void test_ChangePixelLuminanceCyan() { + std::cout << "Running test_ChangePixelLuminanceCyan..." << std::endl; + // Cyan means G and B are significant. Condition is `cyan_component > average`. + // cyan_component = std::min(pixel.green, pixel.blue) + Pixel p_cyan_ish = {150, 140, 20, 255}; // B=150, G=140, R=20. Avg=(20+140+150)/3 = 310/3 = 103. Cyan_comp = min(140,150)=140. 140 > 103. + float lum_factor = 1.1f; // NewAvg = 103 * 1.1 = 113.3 -> 113 + // R_new = (20-103)+113 = -83+113 = 30 + // G_new = (140-103)+113 = 37+113 = 150 + // B_new = (150-103)+113 = 47+113 = 160 + Pixel expected_p_cyan_lum = {160, 150, 30, 255}; + ASSERT_EQUALS(expected_p_cyan_lum, ChangePixelLuminanceCyan(p_cyan_ish, lum_factor), "Luminance Cyan-ish, factor 1.1"); + + Pixel p_not_cyan = {20, 30, 150, 255}; // R is dominant + ASSERT_EQUALS(p_not_cyan, ChangePixelLuminanceCyan(p_not_cyan, lum_factor), "Luminance Cyan not dominant"); +} + + +int main() { + test_InvertPixelColor(); + test_ApplySepiaToPixel(); + test_GreyScalePixel(); + test_ChangePixelBrightness(); + test_ChangePixelContrast(); + test_ChangePixelContrastRed(); + test_ChangePixelContrastGreen(); + test_ChangePixelContrastBlue(); + test_ChangePixelContrastMagenta(); + test_ChangePixelContrastYellow(); + test_ChangePixelContrastCyan(); + test_ChangePixelSaturation(); + test_ChangePixelLuminanceBlue(); + test_ChangePixelLuminanceGreen(); + test_ChangePixelLuminanceRed(); + test_ChangePixelLuminanceMagenta(); + test_ChangePixelLuminanceYellow(); + test_ChangePixelLuminanceCyan(); + + // Image Level Tests + test_ApplyBoxBlur(); + test_ShrinkImage(); + test_RotateImage(); // For RotateImageCounterClockwise + test_OtherImageFunctions_Placeholders(); + + + std::cout << std::endl << "Test Summary:" << std::endl; + std::cout << "Tests Run: " << tests_run << std::endl; + std::cout << "Tests Passed: " << tests_passed << std::endl; + std::cout << "Tests Failed: " << (tests_run - tests_passed) << std::endl; + + return (tests_run - tests_passed); // Return 0 if all tests pass +}