diff --git a/include/bitmap.hpp b/include/bitmap.hpp index 4e68808..0acf96c 100644 --- a/include/bitmap.hpp +++ b/include/bitmap.hpp @@ -194,4 +194,189 @@ Result load(std::span bmp_data); */ Result save(const Bitmap& bitmap, std::span out_bmp_buffer); +/** + * @brief Shrinks the image by a given scale factor. + * @param bitmap The input bitmap. + * @param scaleFactor The factor by which to shrink the image (e.g., 2 for half size). Must be positive. + * @return Result containing the shrunken bitmap or an error. + */ +Result shrink(const Bitmap& bitmap, int scaleFactor); + +/** + * @brief Rotates the image 90 degrees counter-clockwise. + * @param bitmap The input bitmap. + * @return Result containing the rotated bitmap or an error. + */ +Result rotateCounterClockwise(const Bitmap& bitmap); + +/** + * @brief Rotates the image 90 degrees clockwise. + * @param bitmap The input bitmap. + * @return Result containing the rotated bitmap or an error. + */ +Result rotateClockwise(const Bitmap& bitmap); + +/** + * @brief Mirrors the image horizontally. + * @param bitmap The input bitmap. + * @return Result containing the mirrored bitmap or an error. + */ +Result mirror(const Bitmap& bitmap); + +/** + * @brief Flips the image vertically. + * @param bitmap The input bitmap. + * @return Result containing the flipped bitmap or an error. + */ +Result flip(const Bitmap& bitmap); + +/** + * @brief Converts the image to greyscale. + * @param bitmap The input bitmap. + * @return Result containing the greyscale bitmap or an error. + */ +Result greyscale(const Bitmap& bitmap); + +/** + * @brief Changes the overall brightness of the image. + * @param bitmap The input bitmap. + * @param brightness The brightness factor (e.g., 1.0 for no change, >1.0 for brighter, <1.0 for darker). + * @return Result containing the bitmap with adjusted brightness or an error. + */ +Result changeBrightness(const Bitmap& bitmap, float brightness); + +/** + * @brief Changes the overall contrast of the image. + * @param bitmap The input bitmap. + * @param contrast The contrast factor (e.g., 1.0 for no change). + * @return Result containing the bitmap with adjusted contrast or an error. + */ +Result changeContrast(const Bitmap& bitmap, float contrast); + +/** + * @brief Changes the overall saturation of the image. + * @param bitmap The input bitmap. + * @param saturation The saturation factor (e.g., 1.0 for no change, >1.0 for more saturated, <1.0 for less saturated). + * @return Result containing the bitmap with adjusted saturation or an error. + */ +Result changeSaturation(const Bitmap& bitmap, float saturation); + +/** + * @brief Changes the saturation of the blue channel in the image. + * @param bitmap The input bitmap. + * @param saturation The saturation factor for the blue channel. + * @return Result containing the bitmap with adjusted blue channel saturation or an error. + */ +Result changeSaturationBlue(const Bitmap& bitmap, float saturation); + +/** + * @brief Changes the saturation of the green channel in the image. + * @param bitmap The input bitmap. + * @param saturation The saturation factor for the green channel. + * @return Result containing the bitmap with adjusted green channel saturation or an error. + */ +Result changeSaturationGreen(const Bitmap& bitmap, float saturation); + +/** + * @brief Changes the saturation of the red channel in the image. + * @param bitmap The input bitmap. + * @param saturation The saturation factor for the red channel. + * @return Result containing the bitmap with adjusted red channel saturation or an error. + */ +Result changeSaturationRed(const Bitmap& bitmap, float saturation); + +/** + * @brief Changes the saturation of the magenta component (red and blue channels) in the image. + * @param bitmap The input bitmap. + * @param saturation The saturation factor for magenta. + * @return Result containing the bitmap with adjusted magenta saturation or an error. + */ +Result changeSaturationMagenta(const Bitmap& bitmap, float saturation); + +/** + * @brief Changes the saturation of the yellow component (red and green channels) in the image. + * @param bitmap The input bitmap. + * @param saturation The saturation factor for yellow. + * @return Result containing the bitmap with adjusted yellow saturation or an error. + */ +Result changeSaturationYellow(const Bitmap& bitmap, float saturation); + +/** + * @brief Changes the saturation of the cyan component (green and blue channels) in the image. + * @param bitmap The input bitmap. + * @param saturation The saturation factor for cyan. + * @return Result containing the bitmap with adjusted cyan saturation or an error. + */ +Result changeSaturationCyan(const Bitmap& bitmap, float saturation); + +/** + * @brief Changes the luminance of the blue channel in the image. + * @param bitmap The input bitmap. + * @param luminance The luminance factor for the blue channel. + * @return Result containing the bitmap with adjusted blue channel luminance or an error. + */ +Result changeLuminanceBlue(const Bitmap& bitmap, float luminance); + +/** + * @brief Changes the luminance of the green channel in the image. + * @param bitmap The input bitmap. + * @param luminance The luminance factor for the green channel. + * @return Result containing the bitmap with adjusted green channel luminance or an error. + */ +Result changeLuminanceGreen(const Bitmap& bitmap, float luminance); + +/** + * @brief Changes the luminance of the red channel in the image. + * @param bitmap The input bitmap. + * @param luminance The luminance factor for the red channel. + * @return Result containing the bitmap with adjusted red channel luminance or an error. + */ +Result changeLuminanceRed(const Bitmap& bitmap, float luminance); + +/** + * @brief Changes the luminance of the magenta component (red and blue channels) in the image. + * @param bitmap The input bitmap. + * @param luminance The luminance factor for magenta. + * @return Result containing the bitmap with adjusted magenta luminance or an error. + */ +Result changeLuminanceMagenta(const Bitmap& bitmap, float luminance); + +/** + * @brief Changes the luminance of the yellow component (red and green channels) in the image. + * @param bitmap The input bitmap. + * @param luminance The luminance factor for yellow. + * @return Result containing the bitmap with adjusted yellow luminance or an error. + */ +Result changeLuminanceYellow(const Bitmap& bitmap, float luminance); + +/** + * @brief Changes the luminance of the cyan component (green and blue channels) in the image. + * @param bitmap The input bitmap. + * @param luminance The luminance factor for cyan. + * @return Result containing the bitmap with adjusted cyan luminance or an error. + */ +Result changeLuminanceCyan(const Bitmap& bitmap, float luminance); + +/** + * @brief Inverts the colors of the image. + * @param bitmap The input bitmap. + * @return Result containing the inverted bitmap or an error. + */ +Result invertColors(const Bitmap& bitmap); + +/** + * @brief Applies a sepia tone to the image. + * @param bitmap The input bitmap. + * @return Result containing the sepia toned bitmap or an error. + */ +Result applySepiaTone(const Bitmap& bitmap); + +/** + * @brief Applies a box blur to the image. + * @param bitmap The input bitmap. + * @param blurRadius The radius of the blur box (e.g., 1 for a 3x3 box). Defaults to 1. Must be non-negative. + * @return Result containing the blurred bitmap or an error. + */ +Result applyBoxBlur(const Bitmap& bitmap, int blurRadius = 1); + } // namespace BmpTool diff --git a/main.cpp b/main.cpp index cfd4aae..3807d6e 100644 --- a/main.cpp +++ b/main.cpp @@ -1,73 +1,164 @@ -#include "bitmap/bitmap.h" // Adjust path if necessary, assumes bitmap.h is in src/ +#include "../../include/bitmap.hpp" // Using BmpTool API +#include "../../src/bitmapfile/bitmap_file.h" // For BITMAPFILEHEADER for writeFile logic + #include // For std::cout, std::cerr +#include // For std::vector +#include // For std::string +#include // For std::ifstream, std::ofstream +#include // For uint8_t etc. +#include // For std::span +#include // For std::memcpy + +// File I/O Utilities +std::vector readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + std::cerr << "Error: Could not open file for reading: " << filename << std::endl; + return {}; + } + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + std::vector buffer(static_cast(size)); // Ensure size_t for vector constructor + if (file.read(reinterpret_cast(buffer.data()), size)) { + return buffer; + } + std::cerr << "Error: Could not read file: " << filename << std::endl; + return {}; +} + +bool writeFile(const std::string& filename, const std::vector& data) { + std::ofstream file(filename, std::ios::binary); + if (!file.is_open()) { + std::cerr << "Error: Could not open file for writing: " << filename << std::endl; + return false; + } + file.write(reinterpret_cast(data.data()), data.size()); + if (!file.good()) { + std::cerr << "Error: Could not write all data to file: " << filename << std::endl; + return false; + } + return true; +} + +// Helper to print BmpTool errors +void printError(BmpTool::BitmapError error, const std::string& operation_name) { + std::cerr << "Error during " << operation_name << ": " << static_cast(error) << std::endl; +} + int main() { - Bitmap::File originalBitmap; - const char* inputFilename = "test.bmp"; + const std::string inputFilename = "test.bmp"; - std::cout << "Bitmap Processing Demo" << std::endl; - std::cout << "----------------------" << std::endl; + std::cout << "Bitmap Processing Demo with BmpTool API" << std::endl; + std::cout << "---------------------------------------" << std::endl; std::cout << "Attempting to load image: " << inputFilename << std::endl; - if (!originalBitmap.Open(inputFilename)) { + std::vector file_data = readFile(inputFilename); + if (file_data.empty()) { std::cerr << "********************************************************************************" << std::endl; - std::cerr << "Error: Could not open '" << inputFilename << "'." << std::endl; + std::cerr << "Error: Could not read '" << inputFilename << "'." << std::endl; std::cerr << "Please ensure this file exists in the same directory as the executable." << std::endl; std::cerr << "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; + + BmpTool::Result load_result = BmpTool::load(file_data); + if (!load_result.isSuccess()) { + printError(load_result.error(), "loading " + inputFilename); + return 1; + } + BmpTool::Bitmap originalBitmap = load_result.value(); + std::cout << "'" << inputFilename << "' loaded successfully. Dimensions: " + << originalBitmap.w << "x" << originalBitmap.h << std::endl << std::endl; + + BmpTool::Bitmap currentBitmap; // To hold results of operations // --- 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; + auto invert_result = BmpTool::invertColors(originalBitmap); + if (invert_result.isSuccess()) { + currentBitmap = invert_result.value(); + std::vector output_buffer(54 + currentBitmap.w * currentBitmap.h * 4); // Estimate + auto save_res = BmpTool::save(currentBitmap, output_buffer); + if (save_res.isSuccess()) { + BITMAPFILEHEADER fh_out; + std::memcpy(&fh_out, output_buffer.data(), sizeof(BITMAPFILEHEADER)); + std::vector actual_data(output_buffer.begin(), output_buffer.begin() + fh_out.bfSize); + if (writeFile("output_inverted.bmp", actual_data)) { + std::cout << "Saved: output_inverted.bmp" << std::endl; + } else { + std::cerr << "Error writing output_inverted.bmp" << std::endl; + } } else { - std::cerr << "Error: Failed to save output_inverted.bmp." << std::endl; + printError(save_res.error(), "saving inverted image"); } } else { - std::cerr << "Error: Failed to invert colors." << std::endl; + printError(invert_result.error(), "inverting colors"); } 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; + auto sepia_result = BmpTool::applySepiaTone(originalBitmap); + if (sepia_result.isSuccess()) { + currentBitmap = sepia_result.value(); + std::vector output_buffer(54 + currentBitmap.w * currentBitmap.h * 4); + auto save_res = BmpTool::save(currentBitmap, output_buffer); + if (save_res.isSuccess()) { + BITMAPFILEHEADER fh_out; + std::memcpy(&fh_out, output_buffer.data(), sizeof(BITMAPFILEHEADER)); + std::vector actual_data(output_buffer.begin(), output_buffer.begin() + fh_out.bfSize); + if (writeFile("output_sepia.bmp", actual_data)) { + std::cout << "Saved: output_sepia.bmp" << std::endl; + } else { + std::cerr << "Error writing output_sepia.bmp" << std::endl; + } } else { - std::cerr << "Error: Failed to save output_sepia.bmp." << std::endl; + printError(save_res.error(), "saving sepia image"); } } else { - std::cerr << "Error: Failed to apply sepia tone." << std::endl; + printError(sepia_result.error(), "applying sepia tone"); } 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 + // currentBitmap = originalBitmap; // This would make a shallow copy if Bitmap struct is not careful. + // For safety, let's reload or re-assign from originalBitmap if we need a pristine copy. + // Or ensure Bitmap struct has proper copy semantics if it manages its own data deeply. + // Given BmpTool::Bitmap is a struct with std::vector, it has deep copy semantics. + BmpTool::Bitmap tempProcessedBitmap = originalBitmap; + - processedBitmap = ApplyBoxBlur(processedBitmap, 2); // Radius 2 - if (!processedBitmap.IsValid()) { - std::cerr << "Error: Failed to apply box blur." << std::endl; + auto blur_result = BmpTool::applyBoxBlur(tempProcessedBitmap, 2); + if (!blur_result.isSuccess()) { + printError(blur_result.error(), "applying box blur"); } else { + tempProcessedBitmap = blur_result.value(); 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; + auto contrast_result = BmpTool::changeContrast(tempProcessedBitmap, 1.5f); + if (!contrast_result.isSuccess()) { + printError(contrast_result.error(), "changing contrast after blur"); } else { + tempProcessedBitmap = contrast_result.value(); std::cout << "Step 2: Contrast increased." << std::endl; - if (processedBitmap.SaveAs("output_blurred_contrasted.bmp")) { - std::cout << "Saved: output_blurred_contrasted.bmp" << std::endl; + + std::vector output_buffer(54 + tempProcessedBitmap.w * tempProcessedBitmap.h * 4); + auto save_res = BmpTool::save(tempProcessedBitmap, output_buffer); + if (save_res.isSuccess()) { + BITMAPFILEHEADER fh_out; + std::memcpy(&fh_out, output_buffer.data(), sizeof(BITMAPFILEHEADER)); + std::vector actual_data(output_buffer.begin(), output_buffer.begin() + fh_out.bfSize); + if (writeFile("output_blurred_contrasted.bmp", actual_data)) { + std::cout << "Saved: output_blurred_contrasted.bmp" << std::endl; + } else { + std::cerr << "Error writing output_blurred_contrasted.bmp" << std::endl; + } } else { - std::cerr << "Error: Failed to save output_blurred_contrasted.bmp." << std::endl; + printError(save_res.error(), "saving blurred_contrasted image"); } } } @@ -75,22 +166,39 @@ int main() { // --- 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 + tempProcessedBitmap = originalBitmap; // Start with a fresh copy - shrunkBitmap = ShrinkImage(shrunkBitmap, 2); - if (!shrunkBitmap.IsValid()) { - std::cerr << "Error: Failed to shrink image." << std::endl; + auto shrink_result = BmpTool::shrink(tempProcessedBitmap, 2); + if (!shrink_result.isSuccess()) { + printError(shrink_result.error(), "shrinking image"); } else { + tempProcessedBitmap = shrink_result.value(); 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; + + if (tempProcessedBitmap.w == 0 || tempProcessedBitmap.h == 0) { + std::cout << "Image shrunk to zero dimensions, skipping further processing and save." << 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; + auto greyscale_result = BmpTool::greyscale(tempProcessedBitmap); + if(!greyscale_result.isSuccess()){ + printError(greyscale_result.error(), "applying greyscale after shrinking"); } else { - std::cerr << "Error: Failed to save output_shrunk_greyscale.bmp." << std::endl; + tempProcessedBitmap = greyscale_result.value(); + std::cout << "Step 2: Greyscale applied." << std::endl; + + std::vector output_buffer(54 + tempProcessedBitmap.w * tempProcessedBitmap.h * 4); + auto save_res = BmpTool::save(tempProcessedBitmap, output_buffer); + if (save_res.isSuccess()) { + BITMAPFILEHEADER fh_out; + std::memcpy(&fh_out, output_buffer.data(), sizeof(BITMAPFILEHEADER)); + std::vector actual_data(output_buffer.begin(), output_buffer.begin() + fh_out.bfSize); + if(writeFile("output_shrunk_greyscale.bmp", actual_data)){ + std::cout << "Saved: output_shrunk_greyscale.bmp" << std::endl; + } else { + std::cerr << "Error writing output_shrunk_greyscale.bmp" << std::endl; + } + } else { + printError(save_res.error(), "saving shrunk_greyscale image"); + } } } } diff --git a/src/format/bitmap.cpp b/src/format/bitmap.cpp index 1c0eb06..0135392 100644 --- a/src/format/bitmap.cpp +++ b/src/format/bitmap.cpp @@ -3,166 +3,155 @@ #include // For std::memcpy #include // For std::min, std::max (potentially) #include // For robust error checking if needed beyond enums +#include // Required for std::numeric_limits // Own project includes #include "../../include/bitmap.hpp" // For BmpTool::Bitmap, Result, BitmapError -#include "bitmap_internal.hpp" // For BmpTool::Format::Internal structures and helpers +#include "format_internal_helpers.hpp" // Re-added include // External library includes (as per task) #include "../../src/bitmapfile/bitmap_file.h" // For BITMAPFILEHEADER, BITMAPINFOHEADER from external lib #include "../../src/bitmap/bitmap.h" // For ::Pixel, ::CreateMatrixFromBitmap, ::CreateBitmapFromMatrix #include "../../src/matrix/matrix.h" // For Matrix::Matrix #include "../simd_utils.hpp" // Added include -#include "format_internal_helpers.hpp" // Added include for the new helpers // Define constants for BMP format (can be used by Format::Internal helpers or if save needs them directly) +// These constants are still used by the load function. constexpr uint16_t BMP_MAGIC_TYPE_CONST = 0x4D42; // 'BM' constexpr uint32_t BI_RGB_CONST = 0; // No compression namespace BmpTool { -// Namespace for internal helper functions and structures, kept for potential future use -// or if other parts of the library (not modified here) depend on them. -namespace Format { -namespace Internal { +// Forward declarations removed, now using format_internal_helpers.hpp -bool validateHeaders(const InternalBitmapFileHeader& fileHeader, const InternalBitmapInfoHeader& infoHeader) { - if (fileHeader.bfType != BMP_MAGIC_TYPE_CONST) { - return false; - } - if (infoHeader.biPlanes != 1) { - return false; - } - if (infoHeader.biCompression != BI_RGB_CONST) { - return false; - } - if (infoHeader.biBitCount != 24 && infoHeader.biBitCount != 32) { - return false; - } - if (infoHeader.biWidth <= 0 || infoHeader.biHeight == 0) { - return false; - } - if (fileHeader.bfOffBits < (sizeof(InternalBitmapFileHeader) + infoHeader.biSize)) { - // This check is simplified. A full check would also consider bfOffBits < fileHeader.bfSize - } - if((infoHeader.biWidth * infoHeader.biHeight) > std::numeric_limits::max()) { - return false; // Prevent overflow in size calculations - } - return true; -} - -uint32_t calculateRowPadding(uint32_t width, uint16_t bpp) { - uint32_t bytes_per_row_unpadded = (width * bpp) / 8; - uint32_t remainder = bytes_per_row_unpadded % 4; - if (remainder == 0) { - return 0; - } - return 4 - remainder; -} - -} // namespace Internal -} // namespace Format +// The BmpTool::Format::Internal namespace and its functions are removed as they are no longer used. -// The BmpTool::load function (modified in the previous step, using external library) Result load(std::span bmp_data) { - // 1. Parse Input Span (Manual BMP Header Parsing) - if (bmp_data.size() < sizeof(BITMAPFILEHEADER)) { // Using external lib's BITMAPFILEHEADER + // 1. Read BITMAPFILEHEADER and BITMAPINFOHEADER + if (bmp_data.size() < sizeof(BITMAPFILEHEADER)) { return BitmapError::InvalidFileHeader; } - BITMAPFILEHEADER fh; + BITMAPFILEHEADER fh; std::memcpy(&fh, bmp_data.data(), sizeof(BITMAPFILEHEADER)); - if (bmp_data.size() < sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)) { // Using external lib's BITMAPINFOHEADER + if (bmp_data.size() < sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)) { return BitmapError::InvalidImageHeader; } - BITMAPINFOHEADER ih; + BITMAPINFOHEADER ih; std::memcpy(&ih, bmp_data.data() + sizeof(BITMAPFILEHEADER), sizeof(BITMAPINFOHEADER)); - // Basic Validations - if (fh.bfType != BMP_MAGIC_TYPE_CONST) { // 'BM' + // 2. Perform essential early checks + if (fh.bfType != BMP_MAGIC_TYPE_CONST) { return BitmapError::NotABmp; } - if (ih.biCompression != BI_RGB_CONST) { - return BitmapError::UnsupportedBpp; + if (ih.biCompression != BI_RGB_CONST) { + // CreateMatrixFromBitmap doesn't explicitly check this, but assumes uncompressed. + return BitmapError::UnsupportedBpp; } if (ih.biBitCount != 24 && ih.biBitCount != 32) { + // CreateMatrixFromBitmap checks this. return BitmapError::UnsupportedBpp; } - if (ih.biPlanes != 1) { + if (ih.biWidth <= 0 || ih.biHeight == 0) { // abs(ih.biHeight) > 0 is covered by ih.biHeight == 0 + // CreateMatrixFromBitmap checks this via matrix dimensions. return BitmapError::InvalidImageHeader; } - if (fh.bfOffBits < (sizeof(BITMAPFILEHEADER) + ih.biSize) || fh.bfOffBits >= fh.bfSize || fh.bfSize > bmp_data.size()) { + if (fh.bfOffBits < sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) || fh.bfOffBits >= bmp_data.size()) { return BitmapError::InvalidFileHeader; } - if (ih.biWidth <= 0 || ih.biHeight == 0) { - return BitmapError::InvalidImageHeader; - } - - long abs_height_long = ih.biHeight < 0 ? -ih.biHeight : ih.biHeight; - if (abs_height_long == 0) { - return BitmapError::InvalidImageHeader; - } - uint32_t abs_height = static_cast(abs_height_long); + // Check for bfOffBits + ih.biSizeImage <= bmp_data.size() if ih.biSizeImage is not zero. + // This check is more reliably done after calculating expected_pixel_data_size. + // if (ih.biSizeImage != 0 && (fh.bfOffBits + ih.biSizeImage > bmp_data.size())) { + // return BitmapError::InvalidImageData; + // } - // 2. Populate ::Bitmap::File Object - ::Bitmap::File temp_bmp_file; + + // 3. Create a ::Bitmap::File object + ::Bitmap::File temp_bmp_file; + + // 4. Assign fh and ih temp_bmp_file.bitmapFileHeader = fh; temp_bmp_file.bitmapInfoHeader = ih; - uint32_t bits_per_pixel = ih.biBitCount; - uint32_t bytes_per_pixel_src = bits_per_pixel / 8; + // 5. Calculate the expected pixel data size + uint32_t abs_height = (ih.biHeight < 0) ? -static_cast(ih.biHeight) : static_cast(ih.biHeight); + if (abs_height == 0) { // Should have been caught by ih.biHeight == 0, but defensive check. + return BitmapError::InvalidImageHeader; + } + uint32_t bytes_per_pixel_src = ih.biBitCount / 8; uint32_t unpadded_row_size_src = ih.biWidth * bytes_per_pixel_src; - uint32_t padded_row_size_src = (unpadded_row_size_src + 3) & (~3); - uint32_t expected_pixel_data_size = padded_row_size_src * abs_height; + // Use Format::Internal::calculateRowPadding or replicate logic if we decide to remove the namespace entirely later + // For now, let's assume calculateRowPadding is available or we can inline its logic if needed. + // uint32_t padding_per_row = Format::Internal::calculateRowPadding(ih.biWidth, ih.biBitCount); + // uint32_t padded_row_size_src = unpadded_row_size_src + padding_per_row; + // More direct calculation for padded_row_size_src: + uint32_t padded_row_size_src = (unpadded_row_size_src + 3) & (~3); - if (ih.biSizeImage != 0 && ih.biSizeImage != expected_pixel_data_size) { - if (ih.biSizeImage < expected_pixel_data_size) { - return BitmapError::InvalidImageData; - } + // Prevent overflow for expected_pixel_data_size calculation + if (abs_height > 0 && padded_row_size_src > (std::numeric_limits::max() / abs_height) ) { + return BitmapError::InvalidImageData; // Calculation would overflow } + uint32_t expected_pixel_data_size = padded_row_size_src * abs_height; - if (fh.bfOffBits + expected_pixel_data_size > fh.bfSize) { - return BitmapError::InvalidImageData; - } + // 6. Check if fh.bfOffBits + expected_pixel_data_size <= bmp_data.size() if (fh.bfOffBits + expected_pixel_data_size > bmp_data.size()) { - return BitmapError::InvalidImageData; + // This also covers the case where ih.biSizeImage might be 0 or incorrect, + // relying on calculated size. + return BitmapError::InvalidImageData; + } + + // Additional check for ih.biSizeImage if it's provided and seems too small (though CreateMatrixFromBitmap might handle variations) + // If ih.biSizeImage is present and smaller than calculated, it could be an issue. + // However, the primary check is against bmp_data.size(). + if (ih.biSizeImage != 0 && ih.biSizeImage < expected_pixel_data_size) { + // This might indicate a truncated BMP, even if bmp_data has enough bytes for expected_pixel_data_size + // For now, we prioritize expected_pixel_data_size for buffer allocation. + // CreateMatrixFromBitmap will be the final arbiter of data integrity. } - // Remove invalid check using bmp_data.data().width() and MAX_SIZE_T - // The intent is to prevent overflow in size calculation, so use a safe check: - if (abs_height != 0 && ih.biWidth > (std::numeric_limits::max() / abs_height)) { - return BitmapError::InvalidImageData; // Prevent overflow in size calculation - } + + // 7. Resize temp_bmp_file.bitmapData and copy the pixel data temp_bmp_file.bitmapData.resize(expected_pixel_data_size); - std::memcpy(temp_bmp_file.bitmapData.data(), bmp_data.data() + fh.bfOffBits, expected_pixel_data_size); - temp_bmp_file.SetValid(); + if (expected_pixel_data_size > 0) { // Only copy if there's data to copy + std::memcpy(temp_bmp_file.bitmapData.data(), bmp_data.data() + fh.bfOffBits, expected_pixel_data_size); + } - // 3. Convert to Matrix<::Pixel> - Matrix::Matrix<::Pixel> image_matrix = ::CreateMatrixFromBitmap(temp_bmp_file); - if (image_matrix.cols() == 0 || image_matrix.rows() == 0) { // Changed Width/Height to cols/rows - return BitmapError::InvalidImageData; + // 8. Call temp_bmp_file.SetValid() + temp_bmp_file.SetValid(); // Assuming this marks the file as ready for CreateMatrixFromBitmap + + // 9. Call CreateMatrixFromBitmap + Matrix::Matrix<::Pixel> image_matrix = ::CreateMatrixFromBitmap(temp_bmp_file); + + // 10. If image_matrix.cols() == 0 || image_matrix.rows() == 0, return error + if (image_matrix.cols() == 0 || image_matrix.rows() == 0) { + return BitmapError::InvalidImageData; // CreateMatrixFromBitmap failed to produce a valid matrix } - // 4. Convert Matrix<::Pixel> (assumed RGBA by ::Pixel members) to BmpTool::Bitmap (RGBA) + // 11. Convert image_matrix to BmpTool::Bitmap bmp_out Bitmap bmp_out; - bmp_out.w = image_matrix.cols(); // Changed Width to cols - bmp_out.h = image_matrix.rows(); // Changed Height to rows - bmp_out.bpp = 32; + bmp_out.w = image_matrix.cols(); + bmp_out.h = image_matrix.rows(); + bmp_out.bpp = 32; // Output is always 32bpp RGBA + + // Safeguard against overflow for bmp_out.data.resize + if (bmp_out.h > 0 && bmp_out.w > (std::numeric_limits::max() / bmp_out.h / 4)) { // 4 bytes per pixel + return BitmapError::InvalidImageData; // Output image dimensions too large + } bmp_out.data.resize(static_cast(bmp_out.w) * bmp_out.h * 4); - // The loop converting image_matrix to bmp_out.data - // bmp_out.data is already resized. for (uint32_t y = 0; y < bmp_out.h; ++y) { - const ::Pixel* src_bgra_pixels_row = &image_matrix[y][0]; + const ::Pixel* src_bgra_pixels_row = &image_matrix[y][0]; // ::Pixel is BGRA uint8_t* dest_rgba_data_row = bmp_out.data.data() + (static_cast(y) * bmp_out.w * 4); + // Swizzle BGRA from ::Pixel to RGBA for BmpTool::Bitmap internal_swizzle_bgra_to_rgba_simd(src_bgra_pixels_row, dest_rgba_data_row, bmp_out.w); } + + // 12. Return bmp_out return bmp_out; } - // Helper function to convert an array of ::Pixel (BGRA order) to an array of uint8_t (RGBA order) void internal_swizzle_bgra_to_rgba_simd(const ::Pixel* src_bgra_pixels, uint8_t* dest_rgba_data, size_t num_pixels) { size_t current_pixel_idx = 0; @@ -237,74 +226,79 @@ Result save(const Bitmap& bitmap_in, std::span out_b // 2. Convert BmpTool::Bitmap (RGBA) to Matrix<::Pixel> (RGBA) // Assuming ::Pixel struct has members .red, .green, .blue, .alpha - Matrix::Matrix<::Pixel> image_matrix(bitmap_in.h, bitmap_in.w); // Changed order to (rows, cols) + // Matrix::Matrix<::Pixel> image_matrix(bitmap_in.h, bitmap_in.w); // This was the first declaration + // The actual first useful declaration is just below, after input validation. - // The loop converting bitmap_in.data to image_matrix - // image_matrix is already sized. + // 1. Perform input validation on bitmap_in + if (bitmap_in.w == 0 || bitmap_in.h == 0) { + return BitmapError::InvalidImageData; + } + if (bitmap_in.bpp != 32) { + return BitmapError::UnsupportedBpp; // Expects 32bpp RGBA input + } + const size_t expected_input_data_size = static_cast(bitmap_in.w) * bitmap_in.h * 4; // 4 bytes for RGBA + if (bitmap_in.data.size() < expected_input_data_size) { + return BitmapError::InvalidImageData; // Not enough pixel data provided + } + + // 2. Convert BmpTool::Bitmap (RGBA) to Matrix::Matrix<::Pixel> (BGRA) + // This is the correct place for the image_matrix declaration and initialization + Matrix::Matrix<::Pixel> image_matrix(bitmap_in.h, bitmap_in.w); // Matrix constructor is (rows, cols) for (uint32_t y = 0; y < bitmap_in.h; ++y) { const uint8_t* src_rgba_data_row = &bitmap_in.data[(static_cast(y) * bitmap_in.w * 4)]; ::Pixel* dest_bgra_pixels_row = &image_matrix[y][0]; internal_swizzle_rgba_to_bgra_simd(src_rgba_data_row, dest_bgra_pixels_row, bitmap_in.w); } - // 3. Convert Matrix<::Pixel> to ::Bitmap::File - // ::CreateBitmapFromMatrix is expected to produce a ::Bitmap::File with BMP-formatted data (e.g., BGRA, bottom-up) + // 3. Call ::CreateBitmapFromMatrix ::Bitmap::File temp_bmp_file = ::CreateBitmapFromMatrix(image_matrix); - if (!temp_bmp_file.IsValid()) { + // 4. If !temp_bmp_file.IsValid(), return BitmapError::UnknownError + if (!temp_bmp_file.IsValid()) { return BitmapError::UnknownError; // Error during CreateBitmapFromMatrix } - // 4. Serialize ::Bitmap::File to Output Span + // 5. Calculate total_required_size // Ensure bfSize in the header is correct. CreateBitmapFromMatrix should set this. - // If not, it must be calculated: - uint32_t calculated_total_size = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + temp_bmp_file.bitmapData.size(); - - // If CreateBitmapFromMatrix doesn't set bfSize correctly, or sets it to 0. - if (temp_bmp_file.bitmapFileHeader.bfSize != calculated_total_size) { - // Optionally, log a warning or adjust if there's a policy. - // For safety, ensure bfSize is what we expect for the data being copied. - // temp_bmp_file.bitmapFileHeader.bfSize = calculated_total_size; // Uncomment if necessary - } - // It's safer to use the calculated_total_size for buffer check if bfSize from lib is unreliable. - // However, the data to copy comes from temp_bmp_file, so its internal consistency is key. - + // Also, bfOffBits should be correctly set by CreateBitmapFromMatrix. + // We rely on temp_bmp_file.bitmapData.size() for the pixel data size. uint32_t total_required_size = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + temp_bmp_file.bitmapData.size(); + // As an integrity check, bfSize from the library should match calculated total size + if (temp_bmp_file.bitmapFileHeader.bfSize != total_required_size) { + // This might indicate an internal issue with CreateBitmapFromMatrix or a misunderstanding of its output. + // For robustness, one might choose to trust total_required_size or return an error. + // Given the instructions, we proceed with total_required_size for buffer check. + // Optionally: temp_bmp_file.bitmapFileHeader.bfSize = total_required_size; + // And: temp_bmp_file.bitmapFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); + } + + // 6. If out_bmp_buffer.size() < total_required_size, return BitmapError::OutputBufferTooSmall if (out_bmp_buffer.size() < total_required_size) { return BitmapError::OutputBufferTooSmall; } - // If CreateBitmapFromMatrix does not correctly set bfSize, this could be problematic. - // For now, we trust CreateBitmapFromMatrix sets its headers correctly. - // If bfSize is not total_required_size, the output file might be technically incorrect - // but still contain the right amount of data if total_required_size is used for memcpy. - // The most robust approach is to ensure temp_bmp_file.bitmapFileHeader.bfSize IS total_required_size. - // If we have to fix it: - // temp_bmp_file.bitmapFileHeader.bfSize = total_required_size; - // temp_bmp_file.bitmapFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); // Also ensure this - + // 7. Copy temp_bmp_file.bitmapFileHeader, temp_bmp_file.bitmapInfoHeader, and temp_bmp_file.bitmapData uint8_t* buffer_ptr = out_bmp_buffer.data(); std::memcpy(buffer_ptr, &temp_bmp_file.bitmapFileHeader, sizeof(BITMAPFILEHEADER)); buffer_ptr += sizeof(BITMAPFILEHEADER); std::memcpy(buffer_ptr, &temp_bmp_file.bitmapInfoHeader, sizeof(BITMAPINFOHEADER)); buffer_ptr += sizeof(BITMAPINFOHEADER); - // Ensure that bitmapData is not empty before attempting to access its data() pointer if (!temp_bmp_file.bitmapData.empty()) { std::memcpy(buffer_ptr, temp_bmp_file.bitmapData.data(), temp_bmp_file.bitmapData.size()); } else if (total_required_size > (sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER))) { - // If bitmapData is empty but headers indicated pixel data, this is an inconsistency. - return BitmapError::InvalidImageData; // Or UnknownError from CreateBitmapFromMatrix + // This case (empty data but headers imply data) should ideally be caught by temp_bmp_file.IsValid() + // or result in temp_bmp_file.bitmapData.size() being consistent. + // If CreateBitmapFromMatrix produced such a state and marked it valid, it's an issue. + return BitmapError::InvalidImageData; // Or UnknownError from CreateBitmapFromMatrix inconsistency } - - // 5. Return - return BmpTool::Success{}; // This will implicitly convert to Result(Success{}) + // 8. Return BmpTool::Success{} + return BmpTool::Success{}; } - // Helper function to convert an array of uint8_t (RGBA order) to an array of ::Pixel (BGRA order) void internal_swizzle_rgba_to_bgra_simd(const uint8_t* src_rgba_data, ::Pixel* dest_bgra_pixels, size_t num_pixels) { size_t current_pixel_idx = 0; @@ -360,4 +354,968 @@ void internal_swizzle_rgba_to_bgra_simd(const uint8_t* src_rgba_data, ::Pixel* d } } +// Implementation of image manipulation functions + +Result shrink(const Bitmap& bmp_tool_bitmap, int scaleFactor) { + // 1. Validate input BmpTool::Bitmap and other parameters + if (bmp_tool_bitmap.w == 0 || bmp_tool_bitmap.h == 0 || bmp_tool_bitmap.bpp != 32 || + bmp_tool_bitmap.data.size() < static_cast(bmp_tool_bitmap.w) * bmp_tool_bitmap.h * 4) { + return BitmapError::InvalidImageData; + } + if (scaleFactor <= 0) { + return BitmapError::InvalidImageData; // scaleFactor must be positive + } + + // 2. Convert BmpTool::Bitmap (RGBA) to ::Bitmap::File (via Matrix::Matrix<::Pixel> BGRA) + Matrix::Matrix<::Pixel> image_matrix(bmp_tool_bitmap.h, bmp_tool_bitmap.w); + for (uint32_t y = 0; y < bmp_tool_bitmap.h; ++y) { + const uint8_t* src_rgba_data_row = &bmp_tool_bitmap.data[(static_cast(y) * bmp_tool_bitmap.w * 4)]; + ::Pixel* dest_bgra_pixels_row = &image_matrix[y][0]; + internal_swizzle_rgba_to_bgra_simd(src_rgba_data_row, dest_bgra_pixels_row, bmp_tool_bitmap.w); + } + + ::Bitmap::File core_bitmap_file = ::CreateBitmapFromMatrix(image_matrix); + if (!core_bitmap_file.IsValid()) { + return BitmapError::UnknownError; + } + + // 3. Call the core function from src/bitmap/bitmap.cpp + ::Bitmap::File result_core_bitmap_file = ::ShrinkImage(core_bitmap_file, scaleFactor); + + if (!result_core_bitmap_file.IsValid()) { + // ShrinkImage might return an invalid/empty bitmap if scaleFactor is too large, + // leading to zero width/height. This is handled by the conversion step below. + // If it's invalid for other reasons, it's an UnknownError. + if (result_core_bitmap_file.bitmapInfoHeader.biWidth == 0 || result_core_bitmap_file.bitmapInfoHeader.biHeight == 0){ + // This is a valid outcome for shrink, will result in an empty BmpTool::Bitmap + } else { + return BitmapError::UnknownError; // Core operation failed for other reasons + } + } + + // 4. Convert resulting ::Bitmap::File back to BmpTool::Bitmap (RGBA) + Matrix::Matrix<::Pixel> result_image_matrix = ::CreateMatrixFromBitmap(result_core_bitmap_file); + + Bitmap final_bmp_tool_bitmap; + final_bmp_tool_bitmap.w = result_image_matrix.cols(); + final_bmp_tool_bitmap.h = result_image_matrix.rows(); + final_bmp_tool_bitmap.bpp = 32; + + if (final_bmp_tool_bitmap.w > 0 && final_bmp_tool_bitmap.h > 0) { + if (final_bmp_tool_bitmap.h > 0 && final_bmp_tool_bitmap.w > (std::numeric_limits::max() / final_bmp_tool_bitmap.h / 4)) { + return BitmapError::InvalidImageData; // Output image dimensions too large + } + final_bmp_tool_bitmap.data.resize(static_cast(final_bmp_tool_bitmap.w) * final_bmp_tool_bitmap.h * 4); + for (uint32_t y = 0; y < final_bmp_tool_bitmap.h; ++y) { + const ::Pixel* src_bgra_pixels_row = &result_image_matrix[y][0]; + uint8_t* dest_rgba_data_row = &final_bmp_tool_bitmap.data[(static_cast(y) * final_bmp_tool_bitmap.w * 4)]; + internal_swizzle_bgra_to_rgba_simd(src_bgra_pixels_row, dest_rgba_data_row, final_bmp_tool_bitmap.w); + } + } + // If w or h is 0, data remains empty, which is correct. + + // 5. Return + return final_bmp_tool_bitmap; +} + +Result rotateCounterClockwise(const Bitmap& bmp_tool_bitmap) { + if (bmp_tool_bitmap.w == 0 || bmp_tool_bitmap.h == 0 || bmp_tool_bitmap.bpp != 32 || + bmp_tool_bitmap.data.size() < static_cast(bmp_tool_bitmap.w) * bmp_tool_bitmap.h * 4) { + return BitmapError::InvalidImageData; + } + + Matrix::Matrix<::Pixel> image_matrix(bmp_tool_bitmap.h, bmp_tool_bitmap.w); + for (uint32_t y = 0; y < bmp_tool_bitmap.h; ++y) { + const uint8_t* src_rgba_data_row = &bmp_tool_bitmap.data[(static_cast(y) * bmp_tool_bitmap.w * 4)]; + ::Pixel* dest_bgra_pixels_row = &image_matrix[y][0]; + internal_swizzle_rgba_to_bgra_simd(src_rgba_data_row, dest_bgra_pixels_row, bmp_tool_bitmap.w); + } + + ::Bitmap::File core_bitmap_file = ::CreateBitmapFromMatrix(image_matrix); + if (!core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + ::Bitmap::File result_core_bitmap_file = ::RotateImageCounterClockwise(core_bitmap_file); + if (!result_core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + Matrix::Matrix<::Pixel> result_image_matrix = ::CreateMatrixFromBitmap(result_core_bitmap_file); + Bitmap final_bmp_tool_bitmap; + final_bmp_tool_bitmap.w = result_image_matrix.cols(); + final_bmp_tool_bitmap.h = result_image_matrix.rows(); + final_bmp_tool_bitmap.bpp = 32; + + if (final_bmp_tool_bitmap.w > 0 && final_bmp_tool_bitmap.h > 0) { + if (final_bmp_tool_bitmap.h > 0 && final_bmp_tool_bitmap.w > (std::numeric_limits::max() / final_bmp_tool_bitmap.h / 4)) { + return BitmapError::InvalidImageData; + } + final_bmp_tool_bitmap.data.resize(static_cast(final_bmp_tool_bitmap.w) * final_bmp_tool_bitmap.h * 4); + for (uint32_t y = 0; y < final_bmp_tool_bitmap.h; ++y) { + const ::Pixel* src_bgra_pixels_row = &result_image_matrix[y][0]; + uint8_t* dest_rgba_data_row = &final_bmp_tool_bitmap.data[(static_cast(y) * final_bmp_tool_bitmap.w * 4)]; + internal_swizzle_bgra_to_rgba_simd(src_bgra_pixels_row, dest_rgba_data_row, final_bmp_tool_bitmap.w); + } + } + return final_bmp_tool_bitmap; +} + +Result rotateClockwise(const Bitmap& bmp_tool_bitmap) { + if (bmp_tool_bitmap.w == 0 || bmp_tool_bitmap.h == 0 || bmp_tool_bitmap.bpp != 32 || + bmp_tool_bitmap.data.size() < static_cast(bmp_tool_bitmap.w) * bmp_tool_bitmap.h * 4) { + return BitmapError::InvalidImageData; + } + + Matrix::Matrix<::Pixel> image_matrix(bmp_tool_bitmap.h, bmp_tool_bitmap.w); + for (uint32_t y = 0; y < bmp_tool_bitmap.h; ++y) { + const uint8_t* src_rgba_data_row = &bmp_tool_bitmap.data[(static_cast(y) * bmp_tool_bitmap.w * 4)]; + ::Pixel* dest_bgra_pixels_row = &image_matrix[y][0]; + internal_swizzle_rgba_to_bgra_simd(src_rgba_data_row, dest_bgra_pixels_row, bmp_tool_bitmap.w); + } + + ::Bitmap::File core_bitmap_file = ::CreateBitmapFromMatrix(image_matrix); + if (!core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + ::Bitmap::File result_core_bitmap_file = ::RotateImageClockwise(core_bitmap_file); + if (!result_core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + Matrix::Matrix<::Pixel> result_image_matrix = ::CreateMatrixFromBitmap(result_core_bitmap_file); + Bitmap final_bmp_tool_bitmap; + final_bmp_tool_bitmap.w = result_image_matrix.cols(); + final_bmp_tool_bitmap.h = result_image_matrix.rows(); + final_bmp_tool_bitmap.bpp = 32; + + if (final_bmp_tool_bitmap.w > 0 && final_bmp_tool_bitmap.h > 0) { + if (final_bmp_tool_bitmap.h > 0 && final_bmp_tool_bitmap.w > (std::numeric_limits::max() / final_bmp_tool_bitmap.h / 4)) { + return BitmapError::InvalidImageData; + } + final_bmp_tool_bitmap.data.resize(static_cast(final_bmp_tool_bitmap.w) * final_bmp_tool_bitmap.h * 4); + for (uint32_t y = 0; y < final_bmp_tool_bitmap.h; ++y) { + const ::Pixel* src_bgra_pixels_row = &result_image_matrix[y][0]; + uint8_t* dest_rgba_data_row = &final_bmp_tool_bitmap.data[(static_cast(y) * final_bmp_tool_bitmap.w * 4)]; + internal_swizzle_bgra_to_rgba_simd(src_bgra_pixels_row, dest_rgba_data_row, final_bmp_tool_bitmap.w); + } + } + return final_bmp_tool_bitmap; +} + +Result mirror(const Bitmap& bmp_tool_bitmap) { + if (bmp_tool_bitmap.w == 0 || bmp_tool_bitmap.h == 0 || bmp_tool_bitmap.bpp != 32 || + bmp_tool_bitmap.data.size() < static_cast(bmp_tool_bitmap.w) * bmp_tool_bitmap.h * 4) { + return BitmapError::InvalidImageData; + } + + Matrix::Matrix<::Pixel> image_matrix(bmp_tool_bitmap.h, bmp_tool_bitmap.w); + for (uint32_t y = 0; y < bmp_tool_bitmap.h; ++y) { + const uint8_t* src_rgba_data_row = &bmp_tool_bitmap.data[(static_cast(y) * bmp_tool_bitmap.w * 4)]; + ::Pixel* dest_bgra_pixels_row = &image_matrix[y][0]; + internal_swizzle_rgba_to_bgra_simd(src_rgba_data_row, dest_bgra_pixels_row, bmp_tool_bitmap.w); + } + + ::Bitmap::File core_bitmap_file = ::CreateBitmapFromMatrix(image_matrix); + if (!core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + ::Bitmap::File result_core_bitmap_file = ::MirrorImage(core_bitmap_file); + if (!result_core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + Matrix::Matrix<::Pixel> result_image_matrix = ::CreateMatrixFromBitmap(result_core_bitmap_file); + Bitmap final_bmp_tool_bitmap; + final_bmp_tool_bitmap.w = result_image_matrix.cols(); + final_bmp_tool_bitmap.h = result_image_matrix.rows(); + final_bmp_tool_bitmap.bpp = 32; + + if (final_bmp_tool_bitmap.w > 0 && final_bmp_tool_bitmap.h > 0) { + if (final_bmp_tool_bitmap.h > 0 && final_bmp_tool_bitmap.w > (std::numeric_limits::max() / final_bmp_tool_bitmap.h / 4)) { + return BitmapError::InvalidImageData; + } + final_bmp_tool_bitmap.data.resize(static_cast(final_bmp_tool_bitmap.w) * final_bmp_tool_bitmap.h * 4); + for (uint32_t y = 0; y < final_bmp_tool_bitmap.h; ++y) { + const ::Pixel* src_bgra_pixels_row = &result_image_matrix[y][0]; + uint8_t* dest_rgba_data_row = &final_bmp_tool_bitmap.data[(static_cast(y) * final_bmp_tool_bitmap.w * 4)]; + internal_swizzle_bgra_to_rgba_simd(src_bgra_pixels_row, dest_rgba_data_row, final_bmp_tool_bitmap.w); + } + } + return final_bmp_tool_bitmap; +} + +Result flip(const Bitmap& bmp_tool_bitmap) { + if (bmp_tool_bitmap.w == 0 || bmp_tool_bitmap.h == 0 || bmp_tool_bitmap.bpp != 32 || + bmp_tool_bitmap.data.size() < static_cast(bmp_tool_bitmap.w) * bmp_tool_bitmap.h * 4) { + return BitmapError::InvalidImageData; + } + + Matrix::Matrix<::Pixel> image_matrix(bmp_tool_bitmap.h, bmp_tool_bitmap.w); + for (uint32_t y = 0; y < bmp_tool_bitmap.h; ++y) { + const uint8_t* src_rgba_data_row = &bmp_tool_bitmap.data[(static_cast(y) * bmp_tool_bitmap.w * 4)]; + ::Pixel* dest_bgra_pixels_row = &image_matrix[y][0]; + internal_swizzle_rgba_to_bgra_simd(src_rgba_data_row, dest_bgra_pixels_row, bmp_tool_bitmap.w); + } + + ::Bitmap::File core_bitmap_file = ::CreateBitmapFromMatrix(image_matrix); + if (!core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + ::Bitmap::File result_core_bitmap_file = ::FlipImage(core_bitmap_file); + if (!result_core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + Matrix::Matrix<::Pixel> result_image_matrix = ::CreateMatrixFromBitmap(result_core_bitmap_file); + Bitmap final_bmp_tool_bitmap; + final_bmp_tool_bitmap.w = result_image_matrix.cols(); + final_bmp_tool_bitmap.h = result_image_matrix.rows(); + final_bmp_tool_bitmap.bpp = 32; + + if (final_bmp_tool_bitmap.w > 0 && final_bmp_tool_bitmap.h > 0) { + if (final_bmp_tool_bitmap.h > 0 && final_bmp_tool_bitmap.w > (std::numeric_limits::max() / final_bmp_tool_bitmap.h / 4)) { + return BitmapError::InvalidImageData; + } + final_bmp_tool_bitmap.data.resize(static_cast(final_bmp_tool_bitmap.w) * final_bmp_tool_bitmap.h * 4); + for (uint32_t y = 0; y < final_bmp_tool_bitmap.h; ++y) { + const ::Pixel* src_bgra_pixels_row = &result_image_matrix[y][0]; + uint8_t* dest_rgba_data_row = &final_bmp_tool_bitmap.data[(static_cast(y) * final_bmp_tool_bitmap.w * 4)]; + internal_swizzle_bgra_to_rgba_simd(src_bgra_pixels_row, dest_rgba_data_row, final_bmp_tool_bitmap.w); + } + } + return final_bmp_tool_bitmap; +} + +Result greyscale(const Bitmap& bmp_tool_bitmap) { + if (bmp_tool_bitmap.w == 0 || bmp_tool_bitmap.h == 0 || bmp_tool_bitmap.bpp != 32 || + bmp_tool_bitmap.data.size() < static_cast(bmp_tool_bitmap.w) * bmp_tool_bitmap.h * 4) { + return BitmapError::InvalidImageData; + } + + Matrix::Matrix<::Pixel> image_matrix(bmp_tool_bitmap.h, bmp_tool_bitmap.w); + for (uint32_t y = 0; y < bmp_tool_bitmap.h; ++y) { + const uint8_t* src_rgba_data_row = &bmp_tool_bitmap.data[(static_cast(y) * bmp_tool_bitmap.w * 4)]; + ::Pixel* dest_bgra_pixels_row = &image_matrix[y][0]; + internal_swizzle_rgba_to_bgra_simd(src_rgba_data_row, dest_bgra_pixels_row, bmp_tool_bitmap.w); + } + + ::Bitmap::File core_bitmap_file = ::CreateBitmapFromMatrix(image_matrix); + if (!core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + ::Bitmap::File result_core_bitmap_file = ::GreyscaleImage(core_bitmap_file); + if (!result_core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + Matrix::Matrix<::Pixel> result_image_matrix = ::CreateMatrixFromBitmap(result_core_bitmap_file); + Bitmap final_bmp_tool_bitmap; + final_bmp_tool_bitmap.w = result_image_matrix.cols(); + final_bmp_tool_bitmap.h = result_image_matrix.rows(); + final_bmp_tool_bitmap.bpp = 32; + + if (final_bmp_tool_bitmap.w > 0 && final_bmp_tool_bitmap.h > 0) { + if (final_bmp_tool_bitmap.h > 0 && final_bmp_tool_bitmap.w > (std::numeric_limits::max() / final_bmp_tool_bitmap.h / 4)) { + return BitmapError::InvalidImageData; + } + final_bmp_tool_bitmap.data.resize(static_cast(final_bmp_tool_bitmap.w) * final_bmp_tool_bitmap.h * 4); + for (uint32_t y = 0; y < final_bmp_tool_bitmap.h; ++y) { + const ::Pixel* src_bgra_pixels_row = &result_image_matrix[y][0]; + uint8_t* dest_rgba_data_row = &final_bmp_tool_bitmap.data[(static_cast(y) * final_bmp_tool_bitmap.w * 4)]; + internal_swizzle_bgra_to_rgba_simd(src_bgra_pixels_row, dest_rgba_data_row, final_bmp_tool_bitmap.w); + } + } + return final_bmp_tool_bitmap; +} + +Result changeBrightness(const Bitmap& bmp_tool_bitmap, float brightness) { + if (bmp_tool_bitmap.w == 0 || bmp_tool_bitmap.h == 0 || bmp_tool_bitmap.bpp != 32 || + bmp_tool_bitmap.data.size() < static_cast(bmp_tool_bitmap.w) * bmp_tool_bitmap.h * 4) { + return BitmapError::InvalidImageData; + } + + Matrix::Matrix<::Pixel> image_matrix(bmp_tool_bitmap.h, bmp_tool_bitmap.w); + for (uint32_t y = 0; y < bmp_tool_bitmap.h; ++y) { + const uint8_t* src_rgba_data_row = &bmp_tool_bitmap.data[(static_cast(y) * bmp_tool_bitmap.w * 4)]; + ::Pixel* dest_bgra_pixels_row = &image_matrix[y][0]; + internal_swizzle_rgba_to_bgra_simd(src_rgba_data_row, dest_bgra_pixels_row, bmp_tool_bitmap.w); + } + + ::Bitmap::File core_bitmap_file = ::CreateBitmapFromMatrix(image_matrix); + if (!core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + ::Bitmap::File result_core_bitmap_file = ::ChangeImageBrightness(core_bitmap_file, brightness); + if (!result_core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + Matrix::Matrix<::Pixel> result_image_matrix = ::CreateMatrixFromBitmap(result_core_bitmap_file); + Bitmap final_bmp_tool_bitmap; + final_bmp_tool_bitmap.w = result_image_matrix.cols(); + final_bmp_tool_bitmap.h = result_image_matrix.rows(); + final_bmp_tool_bitmap.bpp = 32; + + if (final_bmp_tool_bitmap.w > 0 && final_bmp_tool_bitmap.h > 0) { + if (final_bmp_tool_bitmap.h > 0 && final_bmp_tool_bitmap.w > (std::numeric_limits::max() / final_bmp_tool_bitmap.h / 4)) { + return BitmapError::InvalidImageData; + } + final_bmp_tool_bitmap.data.resize(static_cast(final_bmp_tool_bitmap.w) * final_bmp_tool_bitmap.h * 4); + for (uint32_t y = 0; y < final_bmp_tool_bitmap.h; ++y) { + const ::Pixel* src_bgra_pixels_row = &result_image_matrix[y][0]; + uint8_t* dest_rgba_data_row = &final_bmp_tool_bitmap.data[(static_cast(y) * final_bmp_tool_bitmap.w * 4)]; + internal_swizzle_bgra_to_rgba_simd(src_bgra_pixels_row, dest_rgba_data_row, final_bmp_tool_bitmap.w); + } + } + return final_bmp_tool_bitmap; +} + +Result changeContrast(const Bitmap& bmp_tool_bitmap, float contrast) { + if (bmp_tool_bitmap.w == 0 || bmp_tool_bitmap.h == 0 || bmp_tool_bitmap.bpp != 32 || + bmp_tool_bitmap.data.size() < static_cast(bmp_tool_bitmap.w) * bmp_tool_bitmap.h * 4) { + return BitmapError::InvalidImageData; + } + + Matrix::Matrix<::Pixel> image_matrix(bmp_tool_bitmap.h, bmp_tool_bitmap.w); + for (uint32_t y = 0; y < bmp_tool_bitmap.h; ++y) { + const uint8_t* src_rgba_data_row = &bmp_tool_bitmap.data[(static_cast(y) * bmp_tool_bitmap.w * 4)]; + ::Pixel* dest_bgra_pixels_row = &image_matrix[y][0]; + internal_swizzle_rgba_to_bgra_simd(src_rgba_data_row, dest_bgra_pixels_row, bmp_tool_bitmap.w); + } + + ::Bitmap::File core_bitmap_file = ::CreateBitmapFromMatrix(image_matrix); + if (!core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + ::Bitmap::File result_core_bitmap_file = ::ChangeImageContrast(core_bitmap_file, contrast); + if (!result_core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + Matrix::Matrix<::Pixel> result_image_matrix = ::CreateMatrixFromBitmap(result_core_bitmap_file); + Bitmap final_bmp_tool_bitmap; + final_bmp_tool_bitmap.w = result_image_matrix.cols(); + final_bmp_tool_bitmap.h = result_image_matrix.rows(); + final_bmp_tool_bitmap.bpp = 32; + + if (final_bmp_tool_bitmap.w > 0 && final_bmp_tool_bitmap.h > 0) { + if (final_bmp_tool_bitmap.h > 0 && final_bmp_tool_bitmap.w > (std::numeric_limits::max() / final_bmp_tool_bitmap.h / 4)) { + return BitmapError::InvalidImageData; + } + final_bmp_tool_bitmap.data.resize(static_cast(final_bmp_tool_bitmap.w) * final_bmp_tool_bitmap.h * 4); + for (uint32_t y = 0; y < final_bmp_tool_bitmap.h; ++y) { + const ::Pixel* src_bgra_pixels_row = &result_image_matrix[y][0]; + uint8_t* dest_rgba_data_row = &final_bmp_tool_bitmap.data[(static_cast(y) * final_bmp_tool_bitmap.w * 4)]; + internal_swizzle_bgra_to_rgba_simd(src_bgra_pixels_row, dest_rgba_data_row, final_bmp_tool_bitmap.w); + } + } + return final_bmp_tool_bitmap; +} + +Result changeSaturation(const Bitmap& bmp_tool_bitmap, float saturation) { + if (bmp_tool_bitmap.w == 0 || bmp_tool_bitmap.h == 0 || bmp_tool_bitmap.bpp != 32 || + bmp_tool_bitmap.data.size() < static_cast(bmp_tool_bitmap.w) * bmp_tool_bitmap.h * 4) { + return BitmapError::InvalidImageData; + } + + Matrix::Matrix<::Pixel> image_matrix(bmp_tool_bitmap.h, bmp_tool_bitmap.w); + for (uint32_t y = 0; y < bmp_tool_bitmap.h; ++y) { + const uint8_t* src_rgba_data_row = &bmp_tool_bitmap.data[(static_cast(y) * bmp_tool_bitmap.w * 4)]; + ::Pixel* dest_bgra_pixels_row = &image_matrix[y][0]; + internal_swizzle_rgba_to_bgra_simd(src_rgba_data_row, dest_bgra_pixels_row, bmp_tool_bitmap.w); + } + + ::Bitmap::File core_bitmap_file = ::CreateBitmapFromMatrix(image_matrix); + if (!core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + ::Bitmap::File result_core_bitmap_file = ::ChangeImageSaturation(core_bitmap_file, saturation); + if (!result_core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + Matrix::Matrix<::Pixel> result_image_matrix = ::CreateMatrixFromBitmap(result_core_bitmap_file); + Bitmap final_bmp_tool_bitmap; + final_bmp_tool_bitmap.w = result_image_matrix.cols(); + final_bmp_tool_bitmap.h = result_image_matrix.rows(); + final_bmp_tool_bitmap.bpp = 32; + + if (final_bmp_tool_bitmap.w > 0 && final_bmp_tool_bitmap.h > 0) { + if (final_bmp_tool_bitmap.h > 0 && final_bmp_tool_bitmap.w > (std::numeric_limits::max() / final_bmp_tool_bitmap.h / 4)) { + return BitmapError::InvalidImageData; + } + final_bmp_tool_bitmap.data.resize(static_cast(final_bmp_tool_bitmap.w) * final_bmp_tool_bitmap.h * 4); + for (uint32_t y = 0; y < final_bmp_tool_bitmap.h; ++y) { + const ::Pixel* src_bgra_pixels_row = &result_image_matrix[y][0]; + uint8_t* dest_rgba_data_row = &final_bmp_tool_bitmap.data[(static_cast(y) * final_bmp_tool_bitmap.w * 4)]; + internal_swizzle_bgra_to_rgba_simd(src_bgra_pixels_row, dest_rgba_data_row, final_bmp_tool_bitmap.w); + } + } + return final_bmp_tool_bitmap; +} + +Result changeSaturationBlue(const Bitmap& bmp_tool_bitmap, float saturation) { + if (bmp_tool_bitmap.w == 0 || bmp_tool_bitmap.h == 0 || bmp_tool_bitmap.bpp != 32 || + bmp_tool_bitmap.data.size() < static_cast(bmp_tool_bitmap.w) * bmp_tool_bitmap.h * 4) { + return BitmapError::InvalidImageData; + } + + Matrix::Matrix<::Pixel> image_matrix(bmp_tool_bitmap.h, bmp_tool_bitmap.w); + for (uint32_t y = 0; y < bmp_tool_bitmap.h; ++y) { + const uint8_t* src_rgba_data_row = &bmp_tool_bitmap.data[(static_cast(y) * bmp_tool_bitmap.w * 4)]; + ::Pixel* dest_bgra_pixels_row = &image_matrix[y][0]; + internal_swizzle_rgba_to_bgra_simd(src_rgba_data_row, dest_bgra_pixels_row, bmp_tool_bitmap.w); + } + + ::Bitmap::File core_bitmap_file = ::CreateBitmapFromMatrix(image_matrix); + if (!core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + ::Bitmap::File result_core_bitmap_file = ::ChangeImageSaturationBlue(core_bitmap_file, saturation); + if (!result_core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + Matrix::Matrix<::Pixel> result_image_matrix = ::CreateMatrixFromBitmap(result_core_bitmap_file); + Bitmap final_bmp_tool_bitmap; + final_bmp_tool_bitmap.w = result_image_matrix.cols(); + final_bmp_tool_bitmap.h = result_image_matrix.rows(); + final_bmp_tool_bitmap.bpp = 32; + + if (final_bmp_tool_bitmap.w > 0 && final_bmp_tool_bitmap.h > 0) { + if (final_bmp_tool_bitmap.h > 0 && final_bmp_tool_bitmap.w > (std::numeric_limits::max() / final_bmp_tool_bitmap.h / 4)) { + return BitmapError::InvalidImageData; + } + final_bmp_tool_bitmap.data.resize(static_cast(final_bmp_tool_bitmap.w) * final_bmp_tool_bitmap.h * 4); + for (uint32_t y = 0; y < final_bmp_tool_bitmap.h; ++y) { + const ::Pixel* src_bgra_pixels_row = &result_image_matrix[y][0]; + uint8_t* dest_rgba_data_row = &final_bmp_tool_bitmap.data[(static_cast(y) * final_bmp_tool_bitmap.w * 4)]; + internal_swizzle_bgra_to_rgba_simd(src_bgra_pixels_row, dest_rgba_data_row, final_bmp_tool_bitmap.w); + } + } + return final_bmp_tool_bitmap; +} + +Result changeSaturationGreen(const Bitmap& bmp_tool_bitmap, float saturation) { + if (bmp_tool_bitmap.w == 0 || bmp_tool_bitmap.h == 0 || bmp_tool_bitmap.bpp != 32 || + bmp_tool_bitmap.data.size() < static_cast(bmp_tool_bitmap.w) * bmp_tool_bitmap.h * 4) { + return BitmapError::InvalidImageData; + } + + Matrix::Matrix<::Pixel> image_matrix(bmp_tool_bitmap.h, bmp_tool_bitmap.w); + for (uint32_t y = 0; y < bmp_tool_bitmap.h; ++y) { + const uint8_t* src_rgba_data_row = &bmp_tool_bitmap.data[(static_cast(y) * bmp_tool_bitmap.w * 4)]; + ::Pixel* dest_bgra_pixels_row = &image_matrix[y][0]; + internal_swizzle_rgba_to_bgra_simd(src_rgba_data_row, dest_bgra_pixels_row, bmp_tool_bitmap.w); + } + + ::Bitmap::File core_bitmap_file = ::CreateBitmapFromMatrix(image_matrix); + if (!core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + ::Bitmap::File result_core_bitmap_file = ::ChangeImageSaturationGreen(core_bitmap_file, saturation); + if (!result_core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + Matrix::Matrix<::Pixel> result_image_matrix = ::CreateMatrixFromBitmap(result_core_bitmap_file); + Bitmap final_bmp_tool_bitmap; + final_bmp_tool_bitmap.w = result_image_matrix.cols(); + final_bmp_tool_bitmap.h = result_image_matrix.rows(); + final_bmp_tool_bitmap.bpp = 32; + + if (final_bmp_tool_bitmap.w > 0 && final_bmp_tool_bitmap.h > 0) { + if (final_bmp_tool_bitmap.h > 0 && final_bmp_tool_bitmap.w > (std::numeric_limits::max() / final_bmp_tool_bitmap.h / 4)) { + return BitmapError::InvalidImageData; + } + final_bmp_tool_bitmap.data.resize(static_cast(final_bmp_tool_bitmap.w) * final_bmp_tool_bitmap.h * 4); + for (uint32_t y = 0; y < final_bmp_tool_bitmap.h; ++y) { + const ::Pixel* src_bgra_pixels_row = &result_image_matrix[y][0]; + uint8_t* dest_rgba_data_row = &final_bmp_tool_bitmap.data[(static_cast(y) * final_bmp_tool_bitmap.w * 4)]; + internal_swizzle_bgra_to_rgba_simd(src_bgra_pixels_row, dest_rgba_data_row, final_bmp_tool_bitmap.w); + } + } + return final_bmp_tool_bitmap; +} + +Result changeSaturationRed(const Bitmap& bmp_tool_bitmap, float saturation) { + if (bmp_tool_bitmap.w == 0 || bmp_tool_bitmap.h == 0 || bmp_tool_bitmap.bpp != 32 || + bmp_tool_bitmap.data.size() < static_cast(bmp_tool_bitmap.w) * bmp_tool_bitmap.h * 4) { + return BitmapError::InvalidImageData; + } + + Matrix::Matrix<::Pixel> image_matrix(bmp_tool_bitmap.h, bmp_tool_bitmap.w); + for (uint32_t y = 0; y < bmp_tool_bitmap.h; ++y) { + const uint8_t* src_rgba_data_row = &bmp_tool_bitmap.data[(static_cast(y) * bmp_tool_bitmap.w * 4)]; + ::Pixel* dest_bgra_pixels_row = &image_matrix[y][0]; + internal_swizzle_rgba_to_bgra_simd(src_rgba_data_row, dest_bgra_pixels_row, bmp_tool_bitmap.w); + } + + ::Bitmap::File core_bitmap_file = ::CreateBitmapFromMatrix(image_matrix); + if (!core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + ::Bitmap::File result_core_bitmap_file = ::ChangeImageSaturationRed(core_bitmap_file, saturation); + if (!result_core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + Matrix::Matrix<::Pixel> result_image_matrix = ::CreateMatrixFromBitmap(result_core_bitmap_file); + Bitmap final_bmp_tool_bitmap; + final_bmp_tool_bitmap.w = result_image_matrix.cols(); + final_bmp_tool_bitmap.h = result_image_matrix.rows(); + final_bmp_tool_bitmap.bpp = 32; + + if (final_bmp_tool_bitmap.w > 0 && final_bmp_tool_bitmap.h > 0) { + if (final_bmp_tool_bitmap.h > 0 && final_bmp_tool_bitmap.w > (std::numeric_limits::max() / final_bmp_tool_bitmap.h / 4)) { + return BitmapError::InvalidImageData; + } + final_bmp_tool_bitmap.data.resize(static_cast(final_bmp_tool_bitmap.w) * final_bmp_tool_bitmap.h * 4); + for (uint32_t y = 0; y < final_bmp_tool_bitmap.h; ++y) { + const ::Pixel* src_bgra_pixels_row = &result_image_matrix[y][0]; + uint8_t* dest_rgba_data_row = &final_bmp_tool_bitmap.data[(static_cast(y) * final_bmp_tool_bitmap.w * 4)]; + internal_swizzle_bgra_to_rgba_simd(src_bgra_pixels_row, dest_rgba_data_row, final_bmp_tool_bitmap.w); + } + } + return final_bmp_tool_bitmap; +} + +Result changeSaturationMagenta(const Bitmap& bmp_tool_bitmap, float saturation) { + if (bmp_tool_bitmap.w == 0 || bmp_tool_bitmap.h == 0 || bmp_tool_bitmap.bpp != 32 || + bmp_tool_bitmap.data.size() < static_cast(bmp_tool_bitmap.w) * bmp_tool_bitmap.h * 4) { + return BitmapError::InvalidImageData; + } + + Matrix::Matrix<::Pixel> image_matrix(bmp_tool_bitmap.h, bmp_tool_bitmap.w); + for (uint32_t y = 0; y < bmp_tool_bitmap.h; ++y) { + const uint8_t* src_rgba_data_row = &bmp_tool_bitmap.data[(static_cast(y) * bmp_tool_bitmap.w * 4)]; + ::Pixel* dest_bgra_pixels_row = &image_matrix[y][0]; + internal_swizzle_rgba_to_bgra_simd(src_rgba_data_row, dest_bgra_pixels_row, bmp_tool_bitmap.w); + } + + ::Bitmap::File core_bitmap_file = ::CreateBitmapFromMatrix(image_matrix); + if (!core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + ::Bitmap::File result_core_bitmap_file = ::ChangeImageSaturationMagenta(core_bitmap_file, saturation); + if (!result_core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + Matrix::Matrix<::Pixel> result_image_matrix = ::CreateMatrixFromBitmap(result_core_bitmap_file); + Bitmap final_bmp_tool_bitmap; + final_bmp_tool_bitmap.w = result_image_matrix.cols(); + final_bmp_tool_bitmap.h = result_image_matrix.rows(); + final_bmp_tool_bitmap.bpp = 32; + + if (final_bmp_tool_bitmap.w > 0 && final_bmp_tool_bitmap.h > 0) { + if (final_bmp_tool_bitmap.h > 0 && final_bmp_tool_bitmap.w > (std::numeric_limits::max() / final_bmp_tool_bitmap.h / 4)) { + return BitmapError::InvalidImageData; + } + final_bmp_tool_bitmap.data.resize(static_cast(final_bmp_tool_bitmap.w) * final_bmp_tool_bitmap.h * 4); + for (uint32_t y = 0; y < final_bmp_tool_bitmap.h; ++y) { + const ::Pixel* src_bgra_pixels_row = &result_image_matrix[y][0]; + uint8_t* dest_rgba_data_row = &final_bmp_tool_bitmap.data[(static_cast(y) * final_bmp_tool_bitmap.w * 4)]; + internal_swizzle_bgra_to_rgba_simd(src_bgra_pixels_row, dest_rgba_data_row, final_bmp_tool_bitmap.w); + } + } + return final_bmp_tool_bitmap; +} + +Result changeSaturationYellow(const Bitmap& bmp_tool_bitmap, float saturation) { + if (bmp_tool_bitmap.w == 0 || bmp_tool_bitmap.h == 0 || bmp_tool_bitmap.bpp != 32 || + bmp_tool_bitmap.data.size() < static_cast(bmp_tool_bitmap.w) * bmp_tool_bitmap.h * 4) { + return BitmapError::InvalidImageData; + } + + Matrix::Matrix<::Pixel> image_matrix(bmp_tool_bitmap.h, bmp_tool_bitmap.w); + for (uint32_t y = 0; y < bmp_tool_bitmap.h; ++y) { + const uint8_t* src_rgba_data_row = &bmp_tool_bitmap.data[(static_cast(y) * bmp_tool_bitmap.w * 4)]; + ::Pixel* dest_bgra_pixels_row = &image_matrix[y][0]; + internal_swizzle_rgba_to_bgra_simd(src_rgba_data_row, dest_bgra_pixels_row, bmp_tool_bitmap.w); + } + + ::Bitmap::File core_bitmap_file = ::CreateBitmapFromMatrix(image_matrix); + if (!core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + ::Bitmap::File result_core_bitmap_file = ::ChangeImageSaturationYellow(core_bitmap_file, saturation); + if (!result_core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + Matrix::Matrix<::Pixel> result_image_matrix = ::CreateMatrixFromBitmap(result_core_bitmap_file); + Bitmap final_bmp_tool_bitmap; + final_bmp_tool_bitmap.w = result_image_matrix.cols(); + final_bmp_tool_bitmap.h = result_image_matrix.rows(); + final_bmp_tool_bitmap.bpp = 32; + + if (final_bmp_tool_bitmap.w > 0 && final_bmp_tool_bitmap.h > 0) { + if (final_bmp_tool_bitmap.h > 0 && final_bmp_tool_bitmap.w > (std::numeric_limits::max() / final_bmp_tool_bitmap.h / 4)) { + return BitmapError::InvalidImageData; + } + final_bmp_tool_bitmap.data.resize(static_cast(final_bmp_tool_bitmap.w) * final_bmp_tool_bitmap.h * 4); + for (uint32_t y = 0; y < final_bmp_tool_bitmap.h; ++y) { + const ::Pixel* src_bgra_pixels_row = &result_image_matrix[y][0]; + uint8_t* dest_rgba_data_row = &final_bmp_tool_bitmap.data[(static_cast(y) * final_bmp_tool_bitmap.w * 4)]; + internal_swizzle_bgra_to_rgba_simd(src_bgra_pixels_row, dest_rgba_data_row, final_bmp_tool_bitmap.w); + } + } + return final_bmp_tool_bitmap; +} + +Result changeSaturationCyan(const Bitmap& bmp_tool_bitmap, float saturation) { + if (bmp_tool_bitmap.w == 0 || bmp_tool_bitmap.h == 0 || bmp_tool_bitmap.bpp != 32 || + bmp_tool_bitmap.data.size() < static_cast(bmp_tool_bitmap.w) * bmp_tool_bitmap.h * 4) { + return BitmapError::InvalidImageData; + } + + Matrix::Matrix<::Pixel> image_matrix(bmp_tool_bitmap.h, bmp_tool_bitmap.w); + for (uint32_t y = 0; y < bmp_tool_bitmap.h; ++y) { + const uint8_t* src_rgba_data_row = &bmp_tool_bitmap.data[(static_cast(y) * bmp_tool_bitmap.w * 4)]; + ::Pixel* dest_bgra_pixels_row = &image_matrix[y][0]; + internal_swizzle_rgba_to_bgra_simd(src_rgba_data_row, dest_bgra_pixels_row, bmp_tool_bitmap.w); + } + + ::Bitmap::File core_bitmap_file = ::CreateBitmapFromMatrix(image_matrix); + if (!core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + ::Bitmap::File result_core_bitmap_file = ::ChangeImageSaturationCyan(core_bitmap_file, saturation); + if (!result_core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + Matrix::Matrix<::Pixel> result_image_matrix = ::CreateMatrixFromBitmap(result_core_bitmap_file); + Bitmap final_bmp_tool_bitmap; + final_bmp_tool_bitmap.w = result_image_matrix.cols(); + final_bmp_tool_bitmap.h = result_image_matrix.rows(); + final_bmp_tool_bitmap.bpp = 32; + + if (final_bmp_tool_bitmap.w > 0 && final_bmp_tool_bitmap.h > 0) { + if (final_bmp_tool_bitmap.h > 0 && final_bmp_tool_bitmap.w > (std::numeric_limits::max() / final_bmp_tool_bitmap.h / 4)) { + return BitmapError::InvalidImageData; + } + final_bmp_tool_bitmap.data.resize(static_cast(final_bmp_tool_bitmap.w) * final_bmp_tool_bitmap.h * 4); + for (uint32_t y = 0; y < final_bmp_tool_bitmap.h; ++y) { + const ::Pixel* src_bgra_pixels_row = &result_image_matrix[y][0]; + uint8_t* dest_rgba_data_row = &final_bmp_tool_bitmap.data[(static_cast(y) * final_bmp_tool_bitmap.w * 4)]; + internal_swizzle_bgra_to_rgba_simd(src_bgra_pixels_row, dest_rgba_data_row, final_bmp_tool_bitmap.w); + } + } + return final_bmp_tool_bitmap; +} + +Result changeLuminanceBlue(const Bitmap& bmp_tool_bitmap, float luminance) { + if (bmp_tool_bitmap.w == 0 || bmp_tool_bitmap.h == 0 || bmp_tool_bitmap.bpp != 32 || + bmp_tool_bitmap.data.size() < static_cast(bmp_tool_bitmap.w) * bmp_tool_bitmap.h * 4) { + return BitmapError::InvalidImageData; + } + + Matrix::Matrix<::Pixel> image_matrix(bmp_tool_bitmap.h, bmp_tool_bitmap.w); + for (uint32_t y = 0; y < bmp_tool_bitmap.h; ++y) { + const uint8_t* src_rgba_data_row = &bmp_tool_bitmap.data[(static_cast(y) * bmp_tool_bitmap.w * 4)]; + ::Pixel* dest_bgra_pixels_row = &image_matrix[y][0]; + internal_swizzle_rgba_to_bgra_simd(src_rgba_data_row, dest_bgra_pixels_row, bmp_tool_bitmap.w); + } + + ::Bitmap::File core_bitmap_file = ::CreateBitmapFromMatrix(image_matrix); + if (!core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + ::Bitmap::File result_core_bitmap_file = ::ChangeImageLuminanceBlue(core_bitmap_file, luminance); + if (!result_core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + Matrix::Matrix<::Pixel> result_image_matrix = ::CreateMatrixFromBitmap(result_core_bitmap_file); + Bitmap final_bmp_tool_bitmap; + final_bmp_tool_bitmap.w = result_image_matrix.cols(); + final_bmp_tool_bitmap.h = result_image_matrix.rows(); + final_bmp_tool_bitmap.bpp = 32; + + if (final_bmp_tool_bitmap.w > 0 && final_bmp_tool_bitmap.h > 0) { + if (final_bmp_tool_bitmap.h > 0 && final_bmp_tool_bitmap.w > (std::numeric_limits::max() / final_bmp_tool_bitmap.h / 4)) { + return BitmapError::InvalidImageData; + } + final_bmp_tool_bitmap.data.resize(static_cast(final_bmp_tool_bitmap.w) * final_bmp_tool_bitmap.h * 4); + for (uint32_t y = 0; y < final_bmp_tool_bitmap.h; ++y) { + const ::Pixel* src_bgra_pixels_row = &result_image_matrix[y][0]; + uint8_t* dest_rgba_data_row = &final_bmp_tool_bitmap.data[(static_cast(y) * final_bmp_tool_bitmap.w * 4)]; + internal_swizzle_bgra_to_rgba_simd(src_bgra_pixels_row, dest_rgba_data_row, final_bmp_tool_bitmap.w); + } + } + return final_bmp_tool_bitmap; +} + +Result changeLuminanceGreen(const Bitmap& bmp_tool_bitmap, float luminance) { + if (bmp_tool_bitmap.w == 0 || bmp_tool_bitmap.h == 0 || bmp_tool_bitmap.bpp != 32 || + bmp_tool_bitmap.data.size() < static_cast(bmp_tool_bitmap.w) * bmp_tool_bitmap.h * 4) { + return BitmapError::InvalidImageData; + } + + Matrix::Matrix<::Pixel> image_matrix(bmp_tool_bitmap.h, bmp_tool_bitmap.w); + for (uint32_t y = 0; y < bmp_tool_bitmap.h; ++y) { + const uint8_t* src_rgba_data_row = &bmp_tool_bitmap.data[(static_cast(y) * bmp_tool_bitmap.w * 4)]; + ::Pixel* dest_bgra_pixels_row = &image_matrix[y][0]; + internal_swizzle_rgba_to_bgra_simd(src_rgba_data_row, dest_bgra_pixels_row, bmp_tool_bitmap.w); + } + + ::Bitmap::File core_bitmap_file = ::CreateBitmapFromMatrix(image_matrix); + if (!core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + ::Bitmap::File result_core_bitmap_file = ::ChangeImageLuminanceGreen(core_bitmap_file, luminance); + if (!result_core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + Matrix::Matrix<::Pixel> result_image_matrix = ::CreateMatrixFromBitmap(result_core_bitmap_file); + Bitmap final_bmp_tool_bitmap; + final_bmp_tool_bitmap.w = result_image_matrix.cols(); + final_bmp_tool_bitmap.h = result_image_matrix.rows(); + final_bmp_tool_bitmap.bpp = 32; + + if (final_bmp_tool_bitmap.w > 0 && final_bmp_tool_bitmap.h > 0) { + if (final_bmp_tool_bitmap.h > 0 && final_bmp_tool_bitmap.w > (std::numeric_limits::max() / final_bmp_tool_bitmap.h / 4)) { + return BitmapError::InvalidImageData; + } + final_bmp_tool_bitmap.data.resize(static_cast(final_bmp_tool_bitmap.w) * final_bmp_tool_bitmap.h * 4); + for (uint32_t y = 0; y < final_bmp_tool_bitmap.h; ++y) { + const ::Pixel* src_bgra_pixels_row = &result_image_matrix[y][0]; + uint8_t* dest_rgba_data_row = &final_bmp_tool_bitmap.data[(static_cast(y) * final_bmp_tool_bitmap.w * 4)]; + internal_swizzle_bgra_to_rgba_simd(src_bgra_pixels_row, dest_rgba_data_row, final_bmp_tool_bitmap.w); + } + } + return final_bmp_tool_bitmap; +} + +Result changeLuminanceRed(const Bitmap& bmp_tool_bitmap, float luminance) { + if (bmp_tool_bitmap.w == 0 || bmp_tool_bitmap.h == 0 || bmp_tool_bitmap.bpp != 32 || + bmp_tool_bitmap.data.size() < static_cast(bmp_tool_bitmap.w) * bmp_tool_bitmap.h * 4) { + return BitmapError::InvalidImageData; + } + + Matrix::Matrix<::Pixel> image_matrix(bmp_tool_bitmap.h, bmp_tool_bitmap.w); + for (uint32_t y = 0; y < bmp_tool_bitmap.h; ++y) { + const uint8_t* src_rgba_data_row = &bmp_tool_bitmap.data[(static_cast(y) * bmp_tool_bitmap.w * 4)]; + ::Pixel* dest_bgra_pixels_row = &image_matrix[y][0]; + internal_swizzle_rgba_to_bgra_simd(src_rgba_data_row, dest_bgra_pixels_row, bmp_tool_bitmap.w); + } + + ::Bitmap::File core_bitmap_file = ::CreateBitmapFromMatrix(image_matrix); + if (!core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + ::Bitmap::File result_core_bitmap_file = ::ChangeImageLuminanceRed(core_bitmap_file, luminance); + if (!result_core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + Matrix::Matrix<::Pixel> result_image_matrix = ::CreateMatrixFromBitmap(result_core_bitmap_file); + Bitmap final_bmp_tool_bitmap; + final_bmp_tool_bitmap.w = result_image_matrix.cols(); + final_bmp_tool_bitmap.h = result_image_matrix.rows(); + final_bmp_tool_bitmap.bpp = 32; + + if (final_bmp_tool_bitmap.w > 0 && final_bmp_tool_bitmap.h > 0) { + if (final_bmp_tool_bitmap.h > 0 && final_bmp_tool_bitmap.w > (std::numeric_limits::max() / final_bmp_tool_bitmap.h / 4)) { + return BitmapError::InvalidImageData; + } + final_bmp_tool_bitmap.data.resize(static_cast(final_bmp_tool_bitmap.w) * final_bmp_tool_bitmap.h * 4); + for (uint32_t y = 0; y < final_bmp_tool_bitmap.h; ++y) { + const ::Pixel* src_bgra_pixels_row = &result_image_matrix[y][0]; + uint8_t* dest_rgba_data_row = &final_bmp_tool_bitmap.data[(static_cast(y) * final_bmp_tool_bitmap.w * 4)]; + internal_swizzle_bgra_to_rgba_simd(src_bgra_pixels_row, dest_rgba_data_row, final_bmp_tool_bitmap.w); + } + } + return final_bmp_tool_bitmap; +} + +Result changeLuminanceMagenta(const Bitmap& bmp_tool_bitmap, float luminance) { + if (bmp_tool_bitmap.w == 0 || bmp_tool_bitmap.h == 0 || bmp_tool_bitmap.bpp != 32 || + bmp_tool_bitmap.data.size() < static_cast(bmp_tool_bitmap.w) * bmp_tool_bitmap.h * 4) { + return BitmapError::InvalidImageData; + } + + Matrix::Matrix<::Pixel> image_matrix(bmp_tool_bitmap.h, bmp_tool_bitmap.w); + for (uint32_t y = 0; y < bmp_tool_bitmap.h; ++y) { + const uint8_t* src_rgba_data_row = &bmp_tool_bitmap.data[(static_cast(y) * bmp_tool_bitmap.w * 4)]; + ::Pixel* dest_bgra_pixels_row = &image_matrix[y][0]; + internal_swizzle_rgba_to_bgra_simd(src_rgba_data_row, dest_bgra_pixels_row, bmp_tool_bitmap.w); + } + + ::Bitmap::File core_bitmap_file = ::CreateBitmapFromMatrix(image_matrix); + if (!core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + ::Bitmap::File result_core_bitmap_file = ::ChangeImageLuminanceMagenta(core_bitmap_file, luminance); + if (!result_core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + Matrix::Matrix<::Pixel> result_image_matrix = ::CreateMatrixFromBitmap(result_core_bitmap_file); + Bitmap final_bmp_tool_bitmap; + final_bmp_tool_bitmap.w = result_image_matrix.cols(); + final_bmp_tool_bitmap.h = result_image_matrix.rows(); + final_bmp_tool_bitmap.bpp = 32; + + if (final_bmp_tool_bitmap.w > 0 && final_bmp_tool_bitmap.h > 0) { + if (final_bmp_tool_bitmap.h > 0 && final_bmp_tool_bitmap.w > (std::numeric_limits::max() / final_bmp_tool_bitmap.h / 4)) { + return BitmapError::InvalidImageData; + } + final_bmp_tool_bitmap.data.resize(static_cast(final_bmp_tool_bitmap.w) * final_bmp_tool_bitmap.h * 4); + for (uint32_t y = 0; y < final_bmp_tool_bitmap.h; ++y) { + const ::Pixel* src_bgra_pixels_row = &result_image_matrix[y][0]; + uint8_t* dest_rgba_data_row = &final_bmp_tool_bitmap.data[(static_cast(y) * final_bmp_tool_bitmap.w * 4)]; + internal_swizzle_bgra_to_rgba_simd(src_bgra_pixels_row, dest_rgba_data_row, final_bmp_tool_bitmap.w); + } + } + return final_bmp_tool_bitmap; +} + +Result changeLuminanceYellow(const Bitmap& bmp_tool_bitmap, float luminance) { + if (bmp_tool_bitmap.w == 0 || bmp_tool_bitmap.h == 0 || bmp_tool_bitmap.bpp != 32 || + bmp_tool_bitmap.data.size() < static_cast(bmp_tool_bitmap.w) * bmp_tool_bitmap.h * 4) { + return BitmapError::InvalidImageData; + } + + Matrix::Matrix<::Pixel> image_matrix(bmp_tool_bitmap.h, bmp_tool_bitmap.w); + for (uint32_t y = 0; y < bmp_tool_bitmap.h; ++y) { + const uint8_t* src_rgba_data_row = &bmp_tool_bitmap.data[(static_cast(y) * bmp_tool_bitmap.w * 4)]; + ::Pixel* dest_bgra_pixels_row = &image_matrix[y][0]; + internal_swizzle_rgba_to_bgra_simd(src_rgba_data_row, dest_bgra_pixels_row, bmp_tool_bitmap.w); + } + + ::Bitmap::File core_bitmap_file = ::CreateBitmapFromMatrix(image_matrix); + if (!core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + ::Bitmap::File result_core_bitmap_file = ::ChangeImageLuminanceYellow(core_bitmap_file, luminance); + if (!result_core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + Matrix::Matrix<::Pixel> result_image_matrix = ::CreateMatrixFromBitmap(result_core_bitmap_file); + Bitmap final_bmp_tool_bitmap; + final_bmp_tool_bitmap.w = result_image_matrix.cols(); + final_bmp_tool_bitmap.h = result_image_matrix.rows(); + final_bmp_tool_bitmap.bpp = 32; + + if (final_bmp_tool_bitmap.w > 0 && final_bmp_tool_bitmap.h > 0) { + if (final_bmp_tool_bitmap.h > 0 && final_bmp_tool_bitmap.w > (std::numeric_limits::max() / final_bmp_tool_bitmap.h / 4)) { + return BitmapError::InvalidImageData; + } + final_bmp_tool_bitmap.data.resize(static_cast(final_bmp_tool_bitmap.w) * final_bmp_tool_bitmap.h * 4); + for (uint32_t y = 0; y < final_bmp_tool_bitmap.h; ++y) { + const ::Pixel* src_bgra_pixels_row = &result_image_matrix[y][0]; + uint8_t* dest_rgba_data_row = &final_bmp_tool_bitmap.data[(static_cast(y) * final_bmp_tool_bitmap.w * 4)]; + internal_swizzle_bgra_to_rgba_simd(src_bgra_pixels_row, dest_rgba_data_row, final_bmp_tool_bitmap.w); + } + } + return final_bmp_tool_bitmap; +} + +Result changeLuminanceCyan(const Bitmap& bmp_tool_bitmap, float luminance) { + if (bmp_tool_bitmap.w == 0 || bmp_tool_bitmap.h == 0 || bmp_tool_bitmap.bpp != 32 || + bmp_tool_bitmap.data.size() < static_cast(bmp_tool_bitmap.w) * bmp_tool_bitmap.h * 4) { + return BitmapError::InvalidImageData; + } + + Matrix::Matrix<::Pixel> image_matrix(bmp_tool_bitmap.h, bmp_tool_bitmap.w); + for (uint32_t y = 0; y < bmp_tool_bitmap.h; ++y) { + const uint8_t* src_rgba_data_row = &bmp_tool_bitmap.data[(static_cast(y) * bmp_tool_bitmap.w * 4)]; + ::Pixel* dest_bgra_pixels_row = &image_matrix[y][0]; + internal_swizzle_rgba_to_bgra_simd(src_rgba_data_row, dest_bgra_pixels_row, bmp_tool_bitmap.w); + } + + ::Bitmap::File core_bitmap_file = ::CreateBitmapFromMatrix(image_matrix); + if (!core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + ::Bitmap::File result_core_bitmap_file = ::ChangeImageLuminanceCyan(core_bitmap_file, luminance); + if (!result_core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + Matrix::Matrix<::Pixel> result_image_matrix = ::CreateMatrixFromBitmap(result_core_bitmap_file); + Bitmap final_bmp_tool_bitmap; + final_bmp_tool_bitmap.w = result_image_matrix.cols(); + final_bmp_tool_bitmap.h = result_image_matrix.rows(); + final_bmp_tool_bitmap.bpp = 32; + + if (final_bmp_tool_bitmap.w > 0 && final_bmp_tool_bitmap.h > 0) { + if (final_bmp_tool_bitmap.h > 0 && final_bmp_tool_bitmap.w > (std::numeric_limits::max() / final_bmp_tool_bitmap.h / 4)) { + return BitmapError::InvalidImageData; + } + final_bmp_tool_bitmap.data.resize(static_cast(final_bmp_tool_bitmap.w) * final_bmp_tool_bitmap.h * 4); + for (uint32_t y = 0; y < final_bmp_tool_bitmap.h; ++y) { + const ::Pixel* src_bgra_pixels_row = &result_image_matrix[y][0]; + uint8_t* dest_rgba_data_row = &final_bmp_tool_bitmap.data[(static_cast(y) * final_bmp_tool_bitmap.w * 4)]; + internal_swizzle_bgra_to_rgba_simd(src_bgra_pixels_row, dest_rgba_data_row, final_bmp_tool_bitmap.w); + } + } + return final_bmp_tool_bitmap; +} + +Result invertColors(const Bitmap& bmp_tool_bitmap) { + if (bmp_tool_bitmap.w == 0 || bmp_tool_bitmap.h == 0 || bmp_tool_bitmap.bpp != 32 || + bmp_tool_bitmap.data.size() < static_cast(bmp_tool_bitmap.w) * bmp_tool_bitmap.h * 4) { + return BitmapError::InvalidImageData; + } + + Matrix::Matrix<::Pixel> image_matrix(bmp_tool_bitmap.h, bmp_tool_bitmap.w); + for (uint32_t y = 0; y < bmp_tool_bitmap.h; ++y) { + const uint8_t* src_rgba_data_row = &bmp_tool_bitmap.data[(static_cast(y) * bmp_tool_bitmap.w * 4)]; + ::Pixel* dest_bgra_pixels_row = &image_matrix[y][0]; + internal_swizzle_rgba_to_bgra_simd(src_rgba_data_row, dest_bgra_pixels_row, bmp_tool_bitmap.w); + } + + ::Bitmap::File core_bitmap_file = ::CreateBitmapFromMatrix(image_matrix); + if (!core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + ::Bitmap::File result_core_bitmap_file = ::InvertImageColors(core_bitmap_file); + if (!result_core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + Matrix::Matrix<::Pixel> result_image_matrix = ::CreateMatrixFromBitmap(result_core_bitmap_file); + Bitmap final_bmp_tool_bitmap; + final_bmp_tool_bitmap.w = result_image_matrix.cols(); + final_bmp_tool_bitmap.h = result_image_matrix.rows(); + final_bmp_tool_bitmap.bpp = 32; + + if (final_bmp_tool_bitmap.w > 0 && final_bmp_tool_bitmap.h > 0) { + if (final_bmp_tool_bitmap.h > 0 && final_bmp_tool_bitmap.w > (std::numeric_limits::max() / final_bmp_tool_bitmap.h / 4)) { + return BitmapError::InvalidImageData; + } + final_bmp_tool_bitmap.data.resize(static_cast(final_bmp_tool_bitmap.w) * final_bmp_tool_bitmap.h * 4); + for (uint32_t y = 0; y < final_bmp_tool_bitmap.h; ++y) { + const ::Pixel* src_bgra_pixels_row = &result_image_matrix[y][0]; + uint8_t* dest_rgba_data_row = &final_bmp_tool_bitmap.data[(static_cast(y) * final_bmp_tool_bitmap.w * 4)]; + internal_swizzle_bgra_to_rgba_simd(src_bgra_pixels_row, dest_rgba_data_row, final_bmp_tool_bitmap.w); + } + } + return final_bmp_tool_bitmap; +} + +Result applySepiaTone(const Bitmap& bmp_tool_bitmap) { + if (bmp_tool_bitmap.w == 0 || bmp_tool_bitmap.h == 0 || bmp_tool_bitmap.bpp != 32 || + bmp_tool_bitmap.data.size() < static_cast(bmp_tool_bitmap.w) * bmp_tool_bitmap.h * 4) { + return BitmapError::InvalidImageData; + } + + Matrix::Matrix<::Pixel> image_matrix(bmp_tool_bitmap.h, bmp_tool_bitmap.w); + for (uint32_t y = 0; y < bmp_tool_bitmap.h; ++y) { + const uint8_t* src_rgba_data_row = &bmp_tool_bitmap.data[(static_cast(y) * bmp_tool_bitmap.w * 4)]; + ::Pixel* dest_bgra_pixels_row = &image_matrix[y][0]; + internal_swizzle_rgba_to_bgra_simd(src_rgba_data_row, dest_bgra_pixels_row, bmp_tool_bitmap.w); + } + + ::Bitmap::File core_bitmap_file = ::CreateBitmapFromMatrix(image_matrix); + if (!core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + ::Bitmap::File result_core_bitmap_file = ::ApplySepiaTone(core_bitmap_file); // Corrected function name + if (!result_core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + Matrix::Matrix<::Pixel> result_image_matrix = ::CreateMatrixFromBitmap(result_core_bitmap_file); + Bitmap final_bmp_tool_bitmap; + final_bmp_tool_bitmap.w = result_image_matrix.cols(); + final_bmp_tool_bitmap.h = result_image_matrix.rows(); + final_bmp_tool_bitmap.bpp = 32; + + if (final_bmp_tool_bitmap.w > 0 && final_bmp_tool_bitmap.h > 0) { + if (final_bmp_tool_bitmap.h > 0 && final_bmp_tool_bitmap.w > (std::numeric_limits::max() / final_bmp_tool_bitmap.h / 4)) { + return BitmapError::InvalidImageData; + } + final_bmp_tool_bitmap.data.resize(static_cast(final_bmp_tool_bitmap.w) * final_bmp_tool_bitmap.h * 4); + for (uint32_t y = 0; y < final_bmp_tool_bitmap.h; ++y) { + const ::Pixel* src_bgra_pixels_row = &result_image_matrix[y][0]; + uint8_t* dest_rgba_data_row = &final_bmp_tool_bitmap.data[(static_cast(y) * final_bmp_tool_bitmap.w * 4)]; + internal_swizzle_bgra_to_rgba_simd(src_bgra_pixels_row, dest_rgba_data_row, final_bmp_tool_bitmap.w); + } + } + return final_bmp_tool_bitmap; +} + +Result applyBoxBlur(const Bitmap& bmp_tool_bitmap, int blurRadius) { + if (bmp_tool_bitmap.w == 0 || bmp_tool_bitmap.h == 0 || bmp_tool_bitmap.bpp != 32 || + bmp_tool_bitmap.data.size() < static_cast(bmp_tool_bitmap.w) * bmp_tool_bitmap.h * 4) { + return BitmapError::InvalidImageData; + } + if (blurRadius < 0) { + return BitmapError::InvalidImageData; // blurRadius must be non-negative + } + + Matrix::Matrix<::Pixel> image_matrix(bmp_tool_bitmap.h, bmp_tool_bitmap.w); + for (uint32_t y = 0; y < bmp_tool_bitmap.h; ++y) { + const uint8_t* src_rgba_data_row = &bmp_tool_bitmap.data[(static_cast(y) * bmp_tool_bitmap.w * 4)]; + ::Pixel* dest_bgra_pixels_row = &image_matrix[y][0]; + internal_swizzle_rgba_to_bgra_simd(src_rgba_data_row, dest_bgra_pixels_row, bmp_tool_bitmap.w); + } + + ::Bitmap::File core_bitmap_file = ::CreateBitmapFromMatrix(image_matrix); + if (!core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + ::Bitmap::File result_core_bitmap_file = ::ApplyBoxBlur(core_bitmap_file, blurRadius); // Corrected function name + if (!result_core_bitmap_file.IsValid()) { return BitmapError::UnknownError; } + + Matrix::Matrix<::Pixel> result_image_matrix = ::CreateMatrixFromBitmap(result_core_bitmap_file); + Bitmap final_bmp_tool_bitmap; + final_bmp_tool_bitmap.w = result_image_matrix.cols(); + final_bmp_tool_bitmap.h = result_image_matrix.rows(); + final_bmp_tool_bitmap.bpp = 32; + + if (final_bmp_tool_bitmap.w > 0 && final_bmp_tool_bitmap.h > 0) { + if (final_bmp_tool_bitmap.h > 0 && final_bmp_tool_bitmap.w > (std::numeric_limits::max() / final_bmp_tool_bitmap.h / 4)) { + return BitmapError::InvalidImageData; + } + final_bmp_tool_bitmap.data.resize(static_cast(final_bmp_tool_bitmap.w) * final_bmp_tool_bitmap.h * 4); + for (uint32_t y = 0; y < final_bmp_tool_bitmap.h; ++y) { + const ::Pixel* src_bgra_pixels_row = &result_image_matrix[y][0]; + uint8_t* dest_rgba_data_row = &final_bmp_tool_bitmap.data[(static_cast(y) * final_bmp_tool_bitmap.w * 4)]; + internal_swizzle_bgra_to_rgba_simd(src_bgra_pixels_row, dest_rgba_data_row, final_bmp_tool_bitmap.w); + } + } + return final_bmp_tool_bitmap; +} + } // namespace BmpTool diff --git a/src/format/bitmap_internal.hpp b/src/format/bitmap_internal.hpp deleted file mode 100644 index 1f7f1e6..0000000 --- a/src/format/bitmap_internal.hpp +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once - -#include // For fixed-width integer types like uint16_t, uint32_t - -namespace BmpTool { -namespace Format { -namespace Internal { - -// Ensure structures are packed to match file format -#pragma pack(push, 1) - -/** - * @brief Internal representation of the BMP file header. - * Corresponds to the BITMAPFILEHEADER structure in the BMP file format. - */ -struct InternalBitmapFileHeader { - uint16_t bfType; ///< Specifies the file type, must be 0x4D42 ('BM'). - uint32_t bfSize; ///< Specifies the size, in bytes, of the bitmap file. - uint16_t bfReserved1; ///< Reserved; must be zero. - uint16_t bfReserved2; ///< Reserved; must be zero. - uint32_t bfOffBits; ///< Specifies the offset, in bytes, from the beginning of the - ///< InternalBitmapFileHeader structure to the bitmap bits. -}; - -/** - * @brief Internal representation of the BMP information header. - * Corresponds to the BITMAPINFOHEADER structure in the BMP file format. - */ -struct InternalBitmapInfoHeader { - uint32_t biSize; ///< Specifies the number of bytes required by the structure. - int32_t biWidth; ///< Specifies the width of the bitmap, in pixels. - int32_t biHeight; ///< Specifies the height of the bitmap, in pixels. - ///< If biHeight is positive, the bitmap is a bottom-up DIB. - ///< If biHeight is negative, the bitmap is a top-down DIB. - uint16_t biPlanes; ///< Specifies the number of planes for the target device. Must be 1. - uint16_t biBitCount; ///< Specifies the number of bits-per-pixel (bpp). - ///< Common values are 1, 4, 8, 16, 24, and 32. - uint32_t biCompression; ///< Specifies the type of compression for a compressed bottom-up bitmap. - ///< 0 (BI_RGB) means uncompressed. - uint32_t biSizeImage; ///< Specifies the size, in bytes, of the image. - ///< This may be set to zero for BI_RGB bitmaps. - int32_t biXPelsPerMeter; ///< Specifies the horizontal resolution, in pixels-per-meter, of the target device. - int32_t biYPelsPerMeter; ///< Specifies the vertical resolution, in pixels-per-meter, of the target device. - uint32_t biClrUsed; ///< Specifies the number of color indexes in the color table that are actually used by the bitmap. - uint32_t biClrImportant; ///< Specifies the number of color indexes that are required for displaying the bitmap. - ///< If this value is zero, all colors are required. -}; - -#pragma pack(pop) - -/** - * @brief Validates the essential fields of the BMP file and info headers. - * - * Checks for correct magic number, supported bits-per-pixel, and other common constraints. - * - * @param fileHeader The internal file header structure to validate. - * @param infoHeader The internal info header structure to validate. - * @return True if the headers are considered valid for basic processing, false otherwise. - */ -bool validateHeaders(const InternalBitmapFileHeader& fileHeader, const InternalBitmapInfoHeader& infoHeader); - -/** - * @brief Calculates the number of padding bytes needed for each row in a BMP image. - * - * BMP image rows are padded to be a multiple of 4 bytes. - * - * @param width The width of the bitmap in pixels. - * @param bpp The bits per pixel of the bitmap. - * @return The number of padding bytes (0 to 3) required for each row. - */ -uint32_t calculateRowPadding(uint32_t width, uint16_t bpp); - -} // namespace Internal -} // namespace Format -} // namespace BmpTool diff --git a/src/format/format_internal_helpers.hpp b/src/format/format_internal_helpers.hpp index fa39270..71cc969 100644 --- a/src/format/format_internal_helpers.hpp +++ b/src/format/format_internal_helpers.hpp @@ -1,11 +1,18 @@ #pragma once #include // For size_t -// Include bitmap.h for the definition of ::Pixel -#include "../bitmap/bitmap.h" +// Include bitmap.h for the definition of ::Pixel from the core library, +// assuming the swizzle functions operate on ::Pixel. +// The path might need to be relative to where this header is included from, +// or we assume include paths are set up for "bitmap/bitmap.h" or similar. +// Given its previous usage in tests/test_swizzle.cpp as "../src/bitmap/bitmap.h" (if that's what it was) +// and in src/format/bitmap.cpp as "../../src/bitmap/bitmap.h" +// For a header in src/format/, to reach src/bitmap/bitmap.h, it would be "../bitmap/bitmap.h" +#include "../bitmap/bitmap.h" namespace BmpTool { +// These functions are defined in src/format/bitmap.cpp void internal_swizzle_bgra_to_rgba_simd(const ::Pixel* src_bgra_pixels, uint8_t* dest_rgba_data, size_t num_pixels); void internal_swizzle_rgba_to_bgra_simd(const uint8_t* src_rgba_data, ::Pixel* dest_bgra_pixels, size_t num_pixels); diff --git a/tests/fuzz/CMakeLists.txt b/tests/fuzz/CMakeLists.txt new file mode 100644 index 0000000..f63d82c --- /dev/null +++ b/tests/fuzz/CMakeLists.txt @@ -0,0 +1,26 @@ +# Fuzzing +include(Fuzzing) # Requires Fuzzing.cmake from https://github.com/google/oss-fuzz/tree/master/infra/base-images/base-builder/Fuzzing.cmake +# Or use your project's specific fuzzing setup. +# This example assumes a Fuzzing_add_fuzzer macro or similar. + +# If Fuzzing_add_fuzzer is not available, you might need to define targets manually: +# add_executable(fuzz_bitmap fuzz_bitmap.cpp) +# target_link_libraries(fuzz_bitmap PRIVATE ${OSS_FUZZ_STDLIBS} bitmap) # Assuming 'bitmap' is your main library target + +# List of fuzz targets +set(FUZZ_TARGETS + fuzz_bitmap + fuzz_bitmap_file + fuzz_bmp_tool_save + fuzz_convert_bgr_to_bgra + fuzz_image_operations + fuzz_matrix + fuzz_swizzle_bgra_to_rgba + fuzz_swizzle_rgba_to_bgra + fuzz_bmptool_api_manipulations # New fuzz target +) + +foreach(TARGET ${FUZZ_TARGETS}) + Fuzzing_add_fuzzer(${TARGET} ${TARGET}.cpp) + target_link_libraries(${TARGET} PRIVATE bitmap) # Link against your main library +endforeach() diff --git a/tests/fuzz/fuzz_bmptool_api_manipulations.cpp b/tests/fuzz/fuzz_bmptool_api_manipulations.cpp new file mode 100644 index 0000000..11a0ee2 --- /dev/null +++ b/tests/fuzz/fuzz_bmptool_api_manipulations.cpp @@ -0,0 +1,152 @@ +#include "../../include/bitmap.hpp" // For BmpTool API +#include +#include +#include +#include // For std::memcpy +#include // For std::min +#include // For std::bad_alloc + +// Helper to consume data from the fuzzer input +template +T Consume(const uint8_t** data_ptr, size_t* size_ptr) { + if (*size_ptr < sizeof(T)) { + return T{}; + } + T value; + std::memcpy(&value, *data_ptr, sizeof(T)); + *data_ptr += sizeof(T); + *size_ptr -= sizeof(T); + return value; +} + +// Helper to consume a float value (scaled from a byte) +float ConsumeFloat(const uint8_t** data_ptr, size_t* size_ptr, float min_val = 0.0f, float max_val = 2.0f) { + if (*size_ptr == 0) return (min_val + max_val) / 2.0f; // Default if no data + uint8_t byte_val = Consume(data_ptr, size_ptr); + if (min_val == max_val) return min_val; + float range = max_val - min_val; + // if (range == 0) return min_val; // Already handled by min_val == max_val + return min_val + (static_cast(byte_val) / 255.0f) * range; +} + +// Helper to consume an integer within a range +int ConsumeInt(const uint8_t** data_ptr, size_t* size_ptr, int min_val = 0, int max_val = 10) { + if (*size_ptr == 0) return min_val; // Default if no data + uint8_t byte_val = Consume(data_ptr, size_ptr); + if (max_val == min_val) return min_val; + int range_val = max_val - min_val + 1; + if (range_val <= 0) range_val = 1; // Avoid modulo by zero or negative range + return min_val + (byte_val % range_val); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + if (Size < 10) { // Need some minimal data for dimensions and choice + return 0; + } + + const uint8_t* current_data_ptr = Data; + size_t current_size_ptr = Size; + + // 1. Construct BmpTool::Bitmap from fuzzer data + BmpTool::Bitmap src_bmp; + // Consume dimensions carefully, ensuring they are not excessively large but also non-zero + uint16_t w_raw = Consume(¤t_data_ptr, ¤t_size_ptr); + uint16_t h_raw = Consume(¤t_data_ptr, ¤t_size_ptr); + + src_bmp.w = (w_raw % 128) + 1; + src_bmp.h = (h_raw % 128) + 1; + src_bmp.bpp = 32; + + size_t pixel_data_size = static_cast(src_bmp.w) * src_bmp.h * 4; + const size_t MAX_ALLOC = 128 * 128 * 4; // Adjusted max to match dimension constraints more closely + + if (pixel_data_size == 0 || pixel_data_size > MAX_ALLOC) { // Should not happen if w,h >0 and within 128 + return 0; + } + + try { + src_bmp.data.resize(pixel_data_size); + } catch (const std::bad_alloc&) { + return 0; // Cannot allocate memory + } + + if (current_size_ptr >= pixel_data_size) { + std::memcpy(src_bmp.data.data(), current_data_ptr, pixel_data_size); + current_data_ptr += pixel_data_size; + current_size_ptr -= pixel_data_size; + } else { + if (current_size_ptr > 0) { + std::memcpy(src_bmp.data.data(), current_data_ptr, current_size_ptr); + // Fill the rest with a repeating pattern of the remaining data to avoid purely zeroed tails + size_t remaining_fill = pixel_data_size - current_size_ptr; + uint8_t* fill_ptr = src_bmp.data.data() + current_size_ptr; + while (remaining_fill > 0) { + size_t to_copy = std::min(current_size_ptr, remaining_fill); + if (to_copy == 0) break; // No source data left to copy from, rest will be zero + std::memcpy(fill_ptr, current_data_ptr, to_copy); + fill_ptr += to_copy; + remaining_fill -= to_copy; + } + } + current_size_ptr = 0; // All data consumed for pixels + } + + // Choose an operation + uint8_t operation_choice = Consume(¤t_data_ptr, ¤t_size_ptr); + BmpTool::Result result(BmpTool::BitmapError::UnknownError); + + // Ensure parameters for operations are also consumed from the remaining data + switch (operation_choice % 24) { + case 0: result = BmpTool::shrink(src_bmp, ConsumeInt(¤t_data_ptr, ¤t_size_ptr, 1, 8)); break; + case 1: result = BmpTool::rotateCounterClockwise(src_bmp); break; + case 2: result = BmpTool::rotateClockwise(src_bmp); break; + case 3: result = BmpTool::mirror(src_bmp); break; + case 4: result = BmpTool::flip(src_bmp); break; + case 5: result = BmpTool::greyscale(src_bmp); break; + case 6: result = BmpTool::changeBrightness(src_bmp, ConsumeFloat(¤t_data_ptr, ¤t_size_ptr, 0.1f, 3.0f)); break; + case 7: result = BmpTool::changeContrast(src_bmp, ConsumeFloat(¤t_data_ptr, ¤t_size_ptr, 0.1f, 3.0f)); break; + case 8: result = BmpTool::changeSaturation(src_bmp, ConsumeFloat(¤t_data_ptr, ¤t_size_ptr, 0.0f, 3.0f)); break; + case 9: result = BmpTool::changeSaturationBlue(src_bmp, ConsumeFloat(¤t_data_ptr, ¤t_size_ptr, 0.0f, 3.0f)); break; + case 10: result = BmpTool::changeSaturationGreen(src_bmp, ConsumeFloat(¤t_data_ptr, ¤t_size_ptr, 0.0f, 3.0f)); break; + case 11: result = BmpTool::changeSaturationRed(src_bmp, ConsumeFloat(¤t_data_ptr, ¤t_size_ptr, 0.0f, 3.0f)); break; + case 12: result = BmpTool::changeSaturationMagenta(src_bmp, ConsumeFloat(¤t_data_ptr, ¤t_size_ptr, 0.0f, 3.0f)); break; + case 13: result = BmpTool::changeSaturationYellow(src_bmp, ConsumeFloat(¤t_data_ptr, ¤t_size_ptr, 0.0f, 3.0f)); break; + case 14: result = BmpTool::changeSaturationCyan(src_bmp, ConsumeFloat(¤t_data_ptr, ¤t_size_ptr, 0.0f, 3.0f)); break; + case 15: result = BmpTool::changeLuminanceBlue(src_bmp, ConsumeFloat(¤t_data_ptr, ¤t_size_ptr, 0.0f, 2.0f)); break; + case 16: result = BmpTool::changeLuminanceGreen(src_bmp, ConsumeFloat(¤t_data_ptr, ¤t_size_ptr, 0.0f, 2.0f)); break; + case 17: result = BmpTool::changeLuminanceRed(src_bmp, ConsumeFloat(¤t_data_ptr, ¤t_size_ptr, 0.0f, 2.0f)); break; + case 18: result = BmpTool::changeLuminanceMagenta(src_bmp, ConsumeFloat(¤t_data_ptr, ¤t_size_ptr, 0.0f, 2.0f)); break; + case 19: result = BmpTool::changeLuminanceYellow(src_bmp, ConsumeFloat(¤t_data_ptr, ¤t_size_ptr, 0.0f, 2.0f)); break; + case 20: result = BmpTool::changeLuminanceCyan(src_bmp, ConsumeFloat(¤t_data_ptr, ¤t_size_ptr, 0.0f, 2.0f)); break; + case 21: result = BmpTool::invertColors(src_bmp); break; + case 22: result = BmpTool::applySepiaTone(src_bmp); break; + case 23: result = BmpTool::applyBoxBlur(src_bmp, ConsumeInt(¤t_data_ptr, ¤t_size_ptr, 0, 3)); break; + default: + result = BmpTool::greyscale(src_bmp); + break; + } + + // Process the result to ensure it's valid or handle errors gracefully. + // This helps catch issues where operations might produce invalid Bitmap objects + // that are not necessarily crashes but violate API contracts (e.g., wrong bpp, data size mismatch). + if (result.isSuccess()) { + const auto& processed_bmp = result.value(); + if (processed_bmp.w > 0 && processed_bmp.h > 0) { + if (processed_bmp.bpp != 32) return -1; // Should always be 32 + if (processed_bmp.data.size() != static_cast(processed_bmp.w) * processed_bmp.h * 4) { + return -1; // Data size mismatch + } + } else { + // If width or height is zero, data should be empty. + if (!processed_bmp.data.empty()) return -1; + if (processed_bmp.w == 0 && processed_bmp.h == 0 && processed_bmp.bpp == 0) { + // This is a default-constructed state from some operations, treat as valid empty. + } else if (processed_bmp.bpp != 32 && !(processed_bmp.w == 0 && processed_bmp.h == 0)) { + // If not w=0,h=0, bpp should be 32. + return -1; + } + } + } // else it's an error, which is fine for fuzzing (API handled it). + + return 0; +} diff --git a/tests/test_bmptool_api_manipulations.cpp b/tests/test_bmptool_api_manipulations.cpp new file mode 100644 index 0000000..f508679 --- /dev/null +++ b/tests/test_bmptool_api_manipulations.cpp @@ -0,0 +1,311 @@ +#include +#include "../../include/bitmap.hpp" // BmpTool API +#include +#include +#include // For std::iota (not used in this version) +#include // For std::round (used in greyscale for clarity) + +// Helper function to get a pixel's RGBA value +// Returns a vector {R, G, B, A}. Asserts if x, y are out of bounds. +std::vector getPixel(const BmpTool::Bitmap& bmp, uint32_t x, uint32_t y) { + EXPECT_LT(x, bmp.w); + EXPECT_LT(y, bmp.h); + if (x >= bmp.w || y >= bmp.h) return {0,0,0,0}; // Should fail test due to EXPECT_LT + + size_t index = (y * bmp.w + x) * 4; // 4 bytes per pixel (RGBA) + EXPECT_LT(index + 3, bmp.data.size()); + if (index + 3 >= bmp.data.size()) return {0,0,0,0}; + + return {bmp.data[index], bmp.data[index+1], bmp.data[index+2], bmp.data[index+3]}; +} + + +TEST(BmpToolApiManipulationTest, ShrinkValidScaleBy2) { + BmpTool::Bitmap src; + src.w = 4; src.h = 4; src.bpp = 32; + src.data.resize(4 * 4 * 4, 0); // Initialize with black pixels + // P0 (0,0) = Red + src.data[0] = 255; src.data[1] = 0; src.data[2] = 0; src.data[3] = 255; + // P1 (1,0) = Green + src.data[4] = 0; src.data[5] = 255; src.data[6] = 0; src.data[7] = 255; + // P4 (0,1) = Blue + src.data[16] = 0; src.data[17] = 0; src.data[18] = 255; src.data[19] = 255; + // P5 (1,1) = White + src.data[20] = 255;src.data[21] = 255;src.data[22] = 255;src.data[23] = 255; + + auto result = BmpTool::shrink(src, 2); + ASSERT_TRUE(result.isSuccess()) << "Shrink failed: " << static_cast(result.error()); + BmpTool::Bitmap shrunk_bmp = result.value(); + + ASSERT_EQ(shrunk_bmp.w, 2); + ASSERT_EQ(shrunk_bmp.h, 2); + ASSERT_EQ(shrunk_bmp.bpp, 32); + ASSERT_EQ(shrunk_bmp.data.size(), 2 * 2 * 4); + + // Pixel (0,0) in shrunk should be average of (0,0),(1,0),(0,1),(1,1) in src + // R = (255+0+0+255)/4 = 127.5 -> 127 or 128 (core lib uses integer division) + // G = (0+255+0+255)/4 = 127.5 -> 127 or 128 + // B = (0+0+255+255)/4 = 127.5 -> 127 or 128 + // A = (255+255+255+255)/4 = 255 + // Actual core lib ShrinkPixel averages then assigns. (255+0+0+255)/4 = 510/4 = 127 + auto p00 = getPixel(shrunk_bmp, 0, 0); + EXPECT_EQ(p00[0], (255+0+0+255)/4); // R + EXPECT_EQ(p00[1], (0+255+0+255)/4); // G + EXPECT_EQ(p00[2], (0+0+255+255)/4); // B + EXPECT_EQ(p00[3], (255+255+255+255)/4); // A +} + +TEST(BmpToolApiManipulationTest, ShrinkScaleBy1) { + BmpTool::Bitmap src; + src.w = 2; src.h = 2; src.bpp = 32; + src.data = {255,0,0,255, 0,255,0,255, 0,0,255,255, 10,20,30,40}; + + auto result = BmpTool::shrink(src, 1); + ASSERT_TRUE(result.isSuccess()) << "Shrink(1) failed: " << static_cast(result.error()); + BmpTool::Bitmap shrunk_bmp = result.value(); + ASSERT_EQ(shrunk_bmp.w, src.w); + ASSERT_EQ(shrunk_bmp.h, src.h); + ASSERT_EQ(shrunk_bmp.bpp, src.bpp); + ASSERT_EQ(shrunk_bmp.data, src.data); // Should be identical +} + +TEST(BmpToolApiManipulationTest, ShrinkInvalidScaleFactor) { + BmpTool::Bitmap src; + src.w = 2; src.h = 2; src.bpp = 32; src.data.resize(2*2*4); + auto result_zero = BmpTool::shrink(src, 0); + ASSERT_TRUE(result_zero.isError()); + ASSERT_EQ(result_zero.error(), BmpTool::BitmapError::InvalidImageData); + + auto result_neg = BmpTool::shrink(src, -1); + ASSERT_TRUE(result_neg.isError()); + ASSERT_EQ(result_neg.error(), BmpTool::BitmapError::InvalidImageData); +} + +TEST(BmpToolApiManipulationTest, Greyscale) { + BmpTool::Bitmap src; + src.w = 1; src.h = 1; src.bpp = 32; + src.data = {255, 128, 64, 250}; // R,G,B,A + auto result = BmpTool::greyscale(src); + ASSERT_TRUE(result.isSuccess()) << "Greyscale failed: " << static_cast(result.error()); + BmpTool::Bitmap grey_bmp = result.value(); + + ASSERT_EQ(grey_bmp.w, 1); + ASSERT_EQ(grey_bmp.h, 1); + ASSERT_EQ(grey_bmp.bpp, 32); + ASSERT_EQ(grey_bmp.data.size(), 4); + + // Core lib ::GreyscalePixel uses: avg = (R + G + B) / 3; + uint8_t expected_grey = static_cast(std::round((255.0 + 128.0 + 64.0) / 3.0)); + // (255+128+64)/3 = 447/3 = 149 + auto p00 = getPixel(grey_bmp, 0, 0); + EXPECT_EQ(p00[0], 149); // R + EXPECT_EQ(p00[1], 149); // G + EXPECT_EQ(p00[2], 149); // B + EXPECT_EQ(p00[3], 250); // Alpha should be preserved +} + +TEST(BmpToolApiManipulationTest, InvertColors) { + BmpTool::Bitmap src; + src.w = 1; src.h = 1; src.bpp = 32; + src.data = {200, 100, 50, 150}; // R,G,B,A + auto result = BmpTool::invertColors(src); + ASSERT_TRUE(result.isSuccess()) << "InvertColors failed: " << static_cast(result.error()); + BmpTool::Bitmap inverted_bmp = result.value(); + + ASSERT_EQ(inverted_bmp.w, 1); + ASSERT_EQ(inverted_bmp.h, 1); + ASSERT_EQ(inverted_bmp.bpp, 32); + ASSERT_EQ(inverted_bmp.data.size(), 4); + + auto p00 = getPixel(inverted_bmp, 0, 0); + EXPECT_EQ(p00[0], 255 - 200); // R + EXPECT_EQ(p00[1], 255 - 100); // G + EXPECT_EQ(p00[2], 255 - 50); // B + EXPECT_EQ(p00[3], 150); // Alpha unchanged +} + +TEST(BmpToolApiManipulationTest, RotateClockwiseNonSquare) { + BmpTool::Bitmap src; // 2x1 bitmap + src.w = 2; src.h = 1; src.bpp = 32; + // P(0,0)=Red, P(1,0)=Green + src.data = {255,0,0,255, 0,255,0,255}; + + auto result = BmpTool::rotateClockwise(src); + ASSERT_TRUE(result.isSuccess()) << "RotateClockwise failed: " << static_cast(result.error()); + BmpTool::Bitmap rotated_bmp = result.value(); + + ASSERT_EQ(rotated_bmp.w, 1); // Old height + ASSERT_EQ(rotated_bmp.h, 2); // Old width + ASSERT_EQ(rotated_bmp.bpp, 32); + ASSERT_EQ(rotated_bmp.data.size(), 1 * 2 * 4); + + // Original P(0,0) Red moves to New P(0,0) + // Original P(1,0) Green moves to New P(0,1) + auto p00_new = getPixel(rotated_bmp, 0, 0); // Should be Red + EXPECT_EQ(p00_new[0], 255); EXPECT_EQ(p00_new[1], 0); EXPECT_EQ(p00_new[2], 0); EXPECT_EQ(p00_new[3], 255); + + auto p01_new = getPixel(rotated_bmp, 0, 1); // Should be Green + EXPECT_EQ(p01_new[0], 0); EXPECT_EQ(p01_new[1], 255); EXPECT_EQ(p01_new[2], 0); EXPECT_EQ(p01_new[3], 255); +} + + +TEST(BmpToolApiManipulationTest, ApplyBoxBlurRadius0) { + BmpTool::Bitmap src; + src.w = 2; src.h = 2; src.bpp = 32; + src.data = {255,0,0,255, 0,255,0,255, 0,0,255,255, 10,20,30,40}; + + auto result = BmpTool::applyBoxBlur(src, 0); + ASSERT_TRUE(result.isSuccess()) << "applyBoxBlur(0) failed: " << static_cast(result.error()); + BmpTool::Bitmap blurred_bmp = result.value(); + ASSERT_EQ(blurred_bmp.w, src.w); + ASSERT_EQ(blurred_bmp.h, src.h); + ASSERT_EQ(blurred_bmp.data, src.data); // Should be identical +} + +TEST(BmpToolApiManipulationTest, ApplyBoxBlurRadius1) { + BmpTool::Bitmap src; // 3x1 bitmap + src.w = 3; src.h = 1; src.bpp = 32; + // P0=Red(255,0,0), P1=Green(0,255,0), P2=Blue(0,0,255) all alpha 255 + src.data = { + 255,0,0,255, 0,255,0,255, 0,0,255,255 + }; + + auto result = BmpTool::applyBoxBlur(src, 1); + ASSERT_TRUE(result.isSuccess()) << "applyBoxBlur(1) failed: " << static_cast(result.error()); + BmpTool::Bitmap blurred_bmp = result.value(); + + ASSERT_EQ(blurred_bmp.w, src.w); + ASSERT_EQ(blurred_bmp.h, src.h); + ASSERT_EQ(blurred_bmp.data.size(), src.data.size()); + + // Central pixel P1(1,0) blurred. Neighborhood is P0, P1, P2 (since it's 1D effectively for radius 1 on a 1-row image) + // R_avg = (255+0+0)/3 = 85 + // G_avg = (0+255+0)/3 = 85 + // B_avg = (0+0+255)/3 = 85 + // Alpha_avg = (255+255+255)/3 = 255 + auto p1_blurred = getPixel(blurred_bmp, 1, 0); + EXPECT_EQ(p1_blurred[0], 85); + EXPECT_EQ(p1_blurred[1], 85); + EXPECT_EQ(p1_blurred[2], 85); + EXPECT_EQ(p1_blurred[3], 255); + + // Edge pixel P0(0,0) blurred. Neighborhood is P0, P1 (kernel clamps at edges) + // R_avg = (255+0)/2 = 127 (integer division) + // G_avg = (0+255)/2 = 127 + // B_avg = (0+0)/2 = 0 + // Alpha_avg = (255+255)/2 = 255 + auto p0_blurred = getPixel(blurred_bmp, 0, 0); + EXPECT_EQ(p0_blurred[0], 127); + EXPECT_EQ(p0_blurred[1], 127); + EXPECT_EQ(p0_blurred[2], 0); + EXPECT_EQ(p0_blurred[3], 255); +} + +TEST(BmpToolApiManipulationTest, ApplyBoxBlurInvalidRadius) { + BmpTool::Bitmap src; + src.w = 2; src.h = 2; src.bpp = 32; src.data.resize(2*2*4); + auto result = BmpTool::applyBoxBlur(src, -1); + ASSERT_TRUE(result.isError()); + ASSERT_EQ(result.error(), BmpTool::BitmapError::InvalidImageData); +} + +// Test for an empty bitmap input +TEST(BmpToolApiManipulationTest, EmptyBitmapInput) { + BmpTool::Bitmap src; // Default constructed: w=0, h=0, bpp=0, empty data + + auto result_shrink = BmpTool::shrink(src, 2); + ASSERT_TRUE(result_shrink.isError()); + ASSERT_EQ(result_shrink.error(), BmpTool::BitmapError::InvalidImageData); + + auto result_greyscale = BmpTool::greyscale(src); + ASSERT_TRUE(result_greyscale.isError()); + ASSERT_EQ(result_greyscale.error(), BmpTool::BitmapError::InvalidImageData); + + // Add one more for good measure + auto result_invert = BmpTool::invertColors(src); + ASSERT_TRUE(result_invert.isError()); + ASSERT_EQ(result_invert.error(), BmpTool::BitmapError::InvalidImageData); +} + +TEST(BmpToolApiManipulationTest, BitmapWithZeroDimension) { + BmpTool::Bitmap src; + src.w = 0; src.h = 10; src.bpp = 32; src.data.clear(); // No data for zero width + + auto result_rotate = BmpTool::rotateClockwise(src); + ASSERT_TRUE(result_rotate.isError()); + ASSERT_EQ(result_rotate.error(), BmpTool::BitmapError::InvalidImageData); + + src.w = 10; src.h = 0; src.bpp = 32; src.data.clear(); + auto result_flip = BmpTool::flip(src); + ASSERT_TRUE(result_flip.isError()); + ASSERT_EQ(result_flip.error(), BmpTool::BitmapError::InvalidImageData); +} + +TEST(BmpToolApiManipulationTest, BitmapWithWrongBpp) { + BmpTool::Bitmap src; + src.w = 1; src.h = 1; src.bpp = 24; // Wrong bpp for this API + src.data.resize(1*1*3); // 3 bytes for 24bpp + + auto result_mirror = BmpTool::mirror(src); + ASSERT_TRUE(result_mirror.isError()); + ASSERT_EQ(result_mirror.error(), BmpTool::BitmapError::InvalidImageData); +} + +TEST(BmpToolApiManipulationTest, BitmapWithInsufficientData) { + BmpTool::Bitmap src; + src.w = 2; src.h = 2; src.bpp = 32; + src.data.resize(2*2*4 - 1); // One byte less than required + + auto result_sepia = BmpTool::applySepiaTone(src); + ASSERT_TRUE(result_sepia.isError()); + ASSERT_EQ(result_sepia.error(), BmpTool::BitmapError::InvalidImageData); +} + +// TODO: Add more specific tests for color manipulation functions +// (changeBrightness, changeContrast, changeSaturation variants, changeLuminance variants) +// For these, checking a single pixel's transformation against expected values would be good. +// Example for changeBrightness: +TEST(BmpToolApiManipulationTest, ChangeBrightness) { + BmpTool::Bitmap src; + src.w = 1; src.h = 1; src.bpp = 32; + src.data = {100, 150, 200, 255}; // R,G,B,A + + auto result = BmpTool::changeBrightness(src, 1.2f); // Increase brightness by 20% + ASSERT_TRUE(result.isSuccess()) << "ChangeBrightness failed: " << static_cast(result.error()); + BmpTool::Bitmap bright_bmp = result.value(); + + auto p00 = getPixel(bright_bmp, 0, 0); + // Expected: R = 100*1.2=120, G = 150*1.2=180, B = 200*1.2=240. Alpha unchanged. + // Clamping at 255 should be handled by core lib. + EXPECT_EQ(p00[0], static_cast(std::min(255.0f, 100.0f * 1.2f))); + EXPECT_EQ(p00[1], static_cast(std::min(255.0f, 150.0f * 1.2f))); + EXPECT_EQ(p00[2], static_cast(std::min(255.0f, 200.0f * 1.2f))); + EXPECT_EQ(p00[3], 255); +} + +TEST(BmpToolApiManipulationTest, ChangeBrightnessDarkenAndClamp) { + BmpTool::Bitmap src; + src.w = 1; src.h = 1; src.bpp = 32; + src.data = {10, 20, 300 /*invalid, but test clamping in core*/, 255}; + src.data[2] = 250; // Valid original blue + + auto result = BmpTool::changeBrightness(src, 0.5f); // Decrease brightness by 50% + ASSERT_TRUE(result.isSuccess()) << "ChangeBrightness failed: " << static_cast(result.error()); + BmpTool::Bitmap dark_bmp = result.value(); + + auto p00 = getPixel(dark_bmp, 0, 0); + EXPECT_EQ(p00[0], static_cast(10.0f * 0.5f)); // 5 + EXPECT_EQ(p00[1], static_cast(20.0f * 0.5f)); // 10 + EXPECT_EQ(p00[2], static_cast(250.0f * 0.5f)); // 125 + EXPECT_EQ(p00[3], 255); + + // Test clamping at 0 (though factor is positive, if original was 0) + src.data = {0,0,0,255}; + result = BmpTool::changeBrightness(src, 0.1f); + ASSERT_TRUE(result.isSuccess()); + dark_bmp = result.value(); + p00 = getPixel(dark_bmp, 0,0); + EXPECT_EQ(p00[0], 0); + EXPECT_EQ(p00[1], 0); + EXPECT_EQ(p00[2], 0); +}