From 1a503211d10a341eeed934bcf27b16683c656b16 Mon Sep 17 00:00:00 2001 From: Emi Date: Sat, 22 Aug 2026 15:41:53 +0300 Subject: [PATCH 1/4] new logic for image i/o POC --- CMakeLists.txt | 13 ++------- include/display_image.h | 5 ---- include/image_io.h | 9 +++++++ include/load_image.h | 5 ---- src/engine_logic.cpp | 4 +-- src/{display_image.cpp => image_io.cpp} | 35 ++++++++++++++++++++++--- src/load_image.cpp | 25 ------------------ 7 files changed, 44 insertions(+), 52 deletions(-) delete mode 100644 include/display_image.h create mode 100644 include/image_io.h delete mode 100644 include/load_image.h rename src/{display_image.cpp => image_io.cpp} (55%) delete mode 100644 src/load_image.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b3fe860..4b03846 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,11 +15,7 @@ add_library(image_engine_core STATIC src/ops/rotate.cpp src/executor.cpp src/image.cpp - src/load_image.cpp -) - -add_library(image_engine_sfml STATIC - src/display_image.cpp + src/image_io.cpp ) target_include_directories(image_engine_core PUBLIC @@ -30,14 +26,10 @@ target_include_directories(image_engine_core PUBLIC add_sycl_to_target(TARGET image_engine_core) -target_link_libraries(image_engine_sfml PRIVATE +target_link_libraries(image_engine_core PRIVATE sfml-graphics sfml-window sfml-system ) -target_include_directories(image_engine_sfml PUBLIC - ${CMAKE_SOURCE_DIR}/include -) - add_executable(image-engine src/main.cpp src/engine_logic.cpp @@ -45,7 +37,6 @@ add_executable(image-engine target_link_libraries(image-engine PRIVATE image_engine_core - image_engine_sfml ) add_subdirectory(tests) \ No newline at end of file diff --git a/include/display_image.h b/include/display_image.h deleted file mode 100644 index 1a7103c..0000000 --- a/include/display_image.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once -#include -#include - -void display_image(const Image &img); diff --git a/include/image_io.h b/include/image_io.h new file mode 100644 index 0000000..a7a1139 --- /dev/null +++ b/include/image_io.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include "image.h" +#include + +void display_image(const Image &img); +Image load_image(const std::string &filepath); +Image save_image(const std::string &path, const Image &img); diff --git a/include/load_image.h b/include/load_image.h deleted file mode 100644 index 70d03ab..0000000 --- a/include/load_image.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once -#include "image.h" -#include - -Image load_image(const std::string &filepath); diff --git a/src/engine_logic.cpp b/src/engine_logic.cpp index 60cdec1..df83039 100644 --- a/src/engine_logic.cpp +++ b/src/engine_logic.cpp @@ -1,11 +1,9 @@ #include "engine_config.h" #include "image.h" -#include "load_image.h" -#include "display_image.h" +#include "image_io.h" #include "ops.h" #include "executor.h" #include "types.h" -#include void setup_cli(CLI::App &app, AppContext &ctx) { app.require_subcommand(1); diff --git a/src/display_image.cpp b/src/image_io.cpp similarity index 55% rename from src/display_image.cpp rename to src/image_io.cpp index 10e9afc..8b9cc47 100644 --- a/src/display_image.cpp +++ b/src/image_io.cpp @@ -1,6 +1,10 @@ -#include "display_image.h" -#include -#include +#include "image.h" +#define STB_IMAGE_IMPLEMENTATION + +#include "image_io.h" +#include +#include +#include void display_image(const Image &img) { sf::ContextSettings settings; @@ -36,3 +40,28 @@ void display_image(const Image &img) { window.display(); } } + +Image load_image(const std::string &filepath) { + Image img; + + // force RGBA + unsigned char *data = stbi_load(filepath.c_str(), &img.width, &img.height, &img.channels, 4); + + // stbi function for throwing the full error + if (!data) { + std::print("Full error: {}\n", stbi_failure_reason()); + throw std::runtime_error("Failed to load image: " + filepath); + } + + img.channels = 4; + img.pixels.assign(data, data + img.width * img.height * img.channels); + + stbi_image_free(data); + return img; +} + +Image save_image(const std::string &path, const Image &img) { + Image out; + + return out; +} \ No newline at end of file diff --git a/src/load_image.cpp b/src/load_image.cpp deleted file mode 100644 index 77df21a..0000000 --- a/src/load_image.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#define STB_IMAGE_IMPLEMENTATION -#include "load_image.h" -#include "image.h" -#include -#include -#include - -Image load_image(const std::string &filepath) { - Image img; - - // force RGBA - unsigned char *data = stbi_load(filepath.c_str(), &img.width, &img.height, &img.channels, 4); - - // stbi function for throwing the full error - if (!data) { - std::print("Full error: {}\n", stbi_failure_reason()); - throw std::runtime_error("Failed to load image: " + filepath); - } - - img.channels = 4; - img.pixels.assign(data, data + img.width * img.height * img.channels); - - stbi_image_free(data); - return img; -} From de90568543b5a4ec40f9fc63c7ee3642b1f614c8 Mon Sep 17 00:00:00 2001 From: Emi Date: Sat, 22 Aug 2026 17:45:19 +0300 Subject: [PATCH 2/4] refactor project pipline and save image implemented --- .gitignore | 3 +++ include/executor.h | 16 ++++++++++------ include/image_io.h | 2 +- include/ops.h | 16 ++++++++-------- src/engine_logic.cpp | 13 ++++++++----- src/image_io.cpp | 24 +++++++++++++++++++----- src/ops/blur.cpp | 32 ++++++++++++++++++++++++-------- src/ops/convolution.cpp | 12 ++++++++---- src/ops/crop.cpp | 16 ++++++++-------- src/ops/rotate.cpp | 16 ++++++++-------- 10 files changed, 97 insertions(+), 53 deletions(-) diff --git a/.gitignore b/.gitignore index cdaa8a3..ed4f728 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,9 @@ build/ Build/ build-*/ +# Output directories +output/ + # CMake generated files CMakeFiles/ CMakeCache.txt diff --git a/include/executor.h b/include/executor.h index b5d5f6a..838ad56 100644 --- a/include/executor.h +++ b/include/executor.h @@ -9,18 +9,22 @@ class Executor { ~Executor(); template - void execute(Image &img, OpType op) { + Image execute(Image &img, OpType op) { + Image out; + if (type == Device::NATIVE_CPU) { - op.apply_native(img); + op.apply_native(img, out); } else if (type == Device::COMPARE) { - op.apply_native(img); - op.apply_kernel(img, q); - op.apply_kernel(img, q_s); + op.apply_native(img, out); + op.apply_kernel(img, q, out); + op.apply_kernel(img, q_s, out); } else { - op.apply_kernel(img, q); + op.apply_kernel(img, q, out); } + + return out; } private: diff --git a/include/image_io.h b/include/image_io.h index a7a1139..d4bc870 100644 --- a/include/image_io.h +++ b/include/image_io.h @@ -6,4 +6,4 @@ void display_image(const Image &img); Image load_image(const std::string &filepath); -Image save_image(const std::string &path, const Image &img); +void save_image(const std::string &path, const Image &img); diff --git a/include/ops.h b/include/ops.h index 6f2e13f..0801d0f 100644 --- a/include/ops.h +++ b/include/ops.h @@ -13,8 +13,8 @@ class CropOp { CropOp(int x, int y, int w, int h) : crop_x(x), crop_y(y), crop_w(w), crop_h(h) {} - void apply_native(Image &img); - void apply_kernel(Image &img, sycl::queue &q); + void apply_native(Image &img, Image &img_out); + void apply_kernel(Image &img, sycl::queue &q, Image &img_out); }; class BlurOp { @@ -23,8 +23,8 @@ class BlurOp { BlurOp(int p) : percentage(p) {} - void apply_native(Image &img); - void apply_kernel(Image &img, sycl::queue &q); + void apply_native(Image &img, Image &img_out); + void apply_kernel(Image &img, sycl::queue &q, Image &img_out); }; class RotateOp { @@ -33,8 +33,8 @@ class RotateOp { RotateOp(int a) : angle(a) {} - void apply_native(Image &img); - void apply_kernel(Image &img, sycl::queue &q); + void apply_native(Image &img_in, Image &img_out); + void apply_kernel(Image &img_in, sycl::queue &q, Image &img_out); }; class ConvolutionOp { @@ -60,6 +60,6 @@ class ConvolutionOp { } } - void apply_native(Image &img); - void apply_kernel(Image &img, sycl::queue &q); + void apply_native(Image &img, Image &img_out); + void apply_kernel(Image &img, sycl::queue &q, Image &img_out); }; diff --git a/src/engine_logic.cpp b/src/engine_logic.cpp index df83039..7c96ade 100644 --- a/src/engine_logic.cpp +++ b/src/engine_logic.cpp @@ -36,18 +36,21 @@ void run_operations(const AppContext &ctx) { Executor myExe(ctx.type); Image img = load_image(ctx.filepath); + Image out; + std::string path = "/home/emi/projects/image-engine/output/output.png"; display_image(img); if (ctx.crop_cmd->parsed()) { - myExe.execute(img, CropOp(ctx.cx, ctx.cy, ctx.cw, ctx.ch)); + out = myExe.execute(img, CropOp(ctx.cx, ctx.cy, ctx.cw, ctx.ch)); } else if (ctx.blur_cmd->parsed()) { - myExe.execute(img, BlurOp(ctx.blur_percentage)); + out = myExe.execute(img, BlurOp(ctx.blur_percentage)); } else if (ctx.rotate_cmd->parsed()) { - myExe.execute(img, RotateOp(ctx.rotate_angle)); + out = myExe.execute(img, RotateOp(ctx.rotate_angle)); } else if (ctx.conv_cmd->parsed()) { - myExe.execute(img, ConvolutionOp(kernel)); + out = myExe.execute(img, ConvolutionOp(kernel)); } - display_image(img); + display_image(out); + save_image(path, out); } diff --git a/src/image_io.cpp b/src/image_io.cpp index 8b9cc47..930ca9b 100644 --- a/src/image_io.cpp +++ b/src/image_io.cpp @@ -1,10 +1,12 @@ -#include "image.h" #define STB_IMAGE_IMPLEMENTATION +#include "image.h" #include "image_io.h" -#include #include +#include #include +#include +#include void display_image(const Image &img) { sf::ContextSettings settings; @@ -60,8 +62,20 @@ Image load_image(const std::string &filepath) { return img; } -Image save_image(const std::string &path, const Image &img) { - Image out; +void save_image(const std::string &filepath, const Image &img) { + if (img.pixels.empty() || img.width <= 0 || img.height <= 0) { + throw std::runtime_error("Empty image, abort!"); + } - return out; + std::filesystem::path path(filepath); + if (path.has_parent_path()) { + std::filesystem::create_directories(path.parent_path()); + } + + sf::Image sfml_img; + sfml_img.create(img.width, img.height, img.pixels.data()); + + if (!sfml_img.saveToFile(filepath)) { + throw std::runtime_error("Saving error in: " + filepath); + } } \ No newline at end of file diff --git a/src/ops/blur.cpp b/src/ops/blur.cpp index 18d744b..1fe9358 100644 --- a/src/ops/blur.cpp +++ b/src/ops/blur.cpp @@ -7,7 +7,7 @@ #include #include -void BlurOp::apply_native(Image &img) { +void BlurOp::apply_native(Image &img, Image &img_out) { const int channels = 4; if (percentage == 0) return; @@ -90,31 +90,44 @@ void BlurOp::apply_native(Image &img) { std::cout << "[Profiling][Native CPU] BlurOp execution time: " << duration.count() << " ms" << std::endl; - img.pixels = std::move(blurred_pixels); + img_out.pixels = std::move(blurred_pixels); + img_out.width = img.width; + img_out.height = img.height; } -void BlurOp::apply_kernel(Image &img, sycl::queue &q) { +void BlurOp::apply_kernel(Image &img, sycl::queue &q, Image &img_out) { const int channels = 4; + if (percentage == 0) { + img_out = img; + return; + } + if (percentage > 100) + percentage = 100; + int max_radius = std::min(img.width, img.height) / 10; if (max_radius < 1) max_radius = 1; int radius = (percentage * max_radius) / 100; - if (radius == 0) + if (radius == 0) { + img_out = img; return; + } const int img_w = img.width; const int img_h = img.height; const int r = radius; std::vector temp_pixels(img.pixels.size()); + std::vector blurred_pixels(img.pixels.size()); // <--- 1. Am creat bufferul de ieșire { sycl::buffer in_buf(img.pixels.data(), sycl::range<1>(img.pixels.size())); sycl::buffer tmp_buf(temp_pixels.data(), sycl::range<1>(temp_pixels.size())); + sycl::buffer out_buf(blurred_pixels.data(), sycl::range<1>(blurred_pixels.size())); // <--- 2. Buffer dedicat pentru out - // --- PASS 1: Horizontal Blur --- + // --- PASS 1: Horizontal Blur (in -> tmp) --- sycl::event e1 = q.submit([&](sycl::handler &cgh) { auto in = in_buf.get_access(cgh); auto tmp = tmp_buf.get_access(cgh); @@ -141,10 +154,10 @@ void BlurOp::apply_kernel(Image &img, sycl::queue &q) { }); }); - // --- PASS 2: Vertical Blur --- + // --- PASS 2: Vertical Blur (tmp -> out) --- sycl::event e2 = q.submit([&](sycl::handler &cgh) { auto tmp = tmp_buf.get_access(cgh); - auto out = in_buf.get_access(cgh); + auto out = out_buf.get_access(cgh); // <--- Scriem în out_buf cgh.parallel_for(sycl::range<2>(img_h, img_w), [=](sycl::id<2> idx) { int y = idx[0]; @@ -168,7 +181,6 @@ void BlurOp::apply_kernel(Image &img, sycl::queue &q) { }); }); - // e2.wait() is enough e2.wait(); auto start1 = e1.get_profiling_info(); @@ -183,4 +195,8 @@ void BlurOp::apply_kernel(Image &img, sycl::queue &q) { std::print("[Profiling][{}] BlurOp execution time: {} ms \n", q.get_device().get_info(), total_ms); } + + img_out.pixels = std::move(blurred_pixels); + img_out.width = img.width; + img_out.height = img.height; } diff --git a/src/ops/convolution.cpp b/src/ops/convolution.cpp index d64d35e..d69932d 100644 --- a/src/ops/convolution.cpp +++ b/src/ops/convolution.cpp @@ -8,7 +8,7 @@ #include #include -void ConvolutionOp::apply_native(Image &img) { +void ConvolutionOp::apply_native(Image &img, Image &img_out) { const int channels = 4; int kh = kernel.size(); @@ -64,10 +64,12 @@ void ConvolutionOp::apply_native(Image &img) { std::cout << "[Profiling][Native CPU] ConvolutionOp execution time: " << duration.count() << " ms" << std::endl; - img.pixels = std::move(out); + img_out.pixels = std::move(out); + img_out.width = img.width; + img_out.height = img.height; } -void ConvolutionOp::apply_kernel(Image &img, sycl::queue &q) { +void ConvolutionOp::apply_kernel(Image &img, sycl::queue &q, Image &img_out) { const int channels = 4; const int img_w = img.width; const int img_h = img.height; @@ -129,5 +131,7 @@ void ConvolutionOp::apply_kernel(Image &img, sycl::queue &q) { std::print("[Profiling][{}] ConvolutionOp execution time: {} ms \n", q.get_device().get_info(), duration_ms); } - img.pixels = std::move(out); + img_out.pixels = std::move(out); + img_out.width = img.width; + img_out.height = img.height; } diff --git a/src/ops/crop.cpp b/src/ops/crop.cpp index 1bdb3f3..2d30234 100644 --- a/src/ops/crop.cpp +++ b/src/ops/crop.cpp @@ -7,7 +7,7 @@ #include #include -void CropOp::apply_native(Image &img) { +void CropOp::apply_native(Image &img, Image &img_out) { const int channels = 4; if (crop_x + crop_w > img.width || crop_y + crop_h > img.height || crop_x < 0 || crop_y < 0) { throw std::out_of_range("Crop region goes out of the image boundaries!"); @@ -40,12 +40,12 @@ void CropOp::apply_native(Image &img) { std::cout << "[Profiling][Native CPU] CropOp execution time: " << duration.count() << " ms" << std::endl; - img.pixels = std::move(crepped_pixels); - img.width = crop_w; - img.height = crop_h; + img_out.pixels = std::move(crepped_pixels); + img_out.width = crop_w; + img_out.height = crop_h; } -void CropOp::apply_kernel(Image &img, sycl::queue &q) { +void CropOp::apply_kernel(Image &img, sycl::queue &q, Image &img_out) { const int channels = 4; if (crop_x + crop_w > img.width || crop_y + crop_h > img.height || crop_x < 0 || crop_y < 0) { @@ -93,7 +93,7 @@ void CropOp::apply_kernel(Image &img, sycl::queue &q) { std::print("[Profiling][{}] CropOp execution time: {} ms \n", q.get_device().get_info(), duration_ms); } - img.pixels = std::move(cropped_pixels); - img.width = c_w; - img.height = c_h; + img_out.pixels = std::move(cropped_pixels); + img_out.width = c_w; + img_out.height = c_h; } diff --git a/src/ops/rotate.cpp b/src/ops/rotate.cpp index 15159e5..81f3168 100644 --- a/src/ops/rotate.cpp +++ b/src/ops/rotate.cpp @@ -6,7 +6,7 @@ #include #include -void RotateOp::apply_native(Image &img) { +void RotateOp::apply_native(Image &img, Image &img_out) { // Normalize angle to handle negative numbers or numbers > 360 (e.g., -90 // becomes 270) angle = ((angle % 360) + 360) % 360; @@ -61,12 +61,12 @@ void RotateOp::apply_native(Image &img) { std::cout << "[Profiling][Native CPU] RotateOp execution time: " << duration.count() << " ms" << std::endl; // Update the image object - img.pixels = std::move(rotated_pixels); - img.width = new_w; - img.height = new_h; + img_out.pixels = std::move(rotated_pixels); + img_out.width = new_w; + img_out.height = new_h; } -void RotateOp::apply_kernel(Image &img, sycl::queue &q) { +void RotateOp::apply_kernel(Image &img, sycl::queue &q, Image &img_out) { // Normalize angle int local_angle = ((angle % 360) + 360) % 360; @@ -134,7 +134,7 @@ void RotateOp::apply_kernel(Image &img, sycl::queue &q) { std::print("[Profiling][{}] RotateOp execution time: {} ms \n", q.get_device().get_info(), duration_ms); } - img.pixels = std::move(rotated_pixels); - img.width = new_w; - img.height = new_h; + img_out.pixels = std::move(rotated_pixels); + img_out.width = new_w; + img_out.height = new_h; } From 03f80362b4f7359450a5e8b6484dbf9b94b63c3c Mon Sep 17 00:00:00 2001 From: Emi Date: Wed, 26 Aug 2026 22:28:37 +0300 Subject: [PATCH 3/4] refactor function signature and unittests --- include/executor.h | 6 +- include/ops.h | 8 +- src/ops/blur.cpp | 2 +- src/ops/convolution.cpp | 2 +- src/ops/crop.cpp | 2 +- src/ops/rotate.cpp | 2 +- tests/unit_tests.cpp | 166 ++++++++++++++++++++++++---------------- 7 files changed, 113 insertions(+), 75 deletions(-) diff --git a/include/executor.h b/include/executor.h index 838ad56..070ba93 100644 --- a/include/executor.h +++ b/include/executor.h @@ -17,11 +17,11 @@ class Executor { } else if (type == Device::COMPARE) { op.apply_native(img, out); - op.apply_kernel(img, q, out); - op.apply_kernel(img, q_s, out); + op.apply_kernel(img, out, q); + op.apply_kernel(img, out, q_s); } else { - op.apply_kernel(img, q, out); + op.apply_kernel(img, out, q); } return out; diff --git a/include/ops.h b/include/ops.h index 0801d0f..d4518f1 100644 --- a/include/ops.h +++ b/include/ops.h @@ -14,7 +14,7 @@ class CropOp { CropOp(int x, int y, int w, int h) : crop_x(x), crop_y(y), crop_w(w), crop_h(h) {} void apply_native(Image &img, Image &img_out); - void apply_kernel(Image &img, sycl::queue &q, Image &img_out); + void apply_kernel(Image &img, Image &img_out, sycl::queue &q); }; class BlurOp { @@ -24,7 +24,7 @@ class BlurOp { BlurOp(int p) : percentage(p) {} void apply_native(Image &img, Image &img_out); - void apply_kernel(Image &img, sycl::queue &q, Image &img_out); + void apply_kernel(Image &img, Image &img_out, sycl::queue &q); }; class RotateOp { @@ -34,7 +34,7 @@ class RotateOp { RotateOp(int a) : angle(a) {} void apply_native(Image &img_in, Image &img_out); - void apply_kernel(Image &img_in, sycl::queue &q, Image &img_out); + void apply_kernel(Image &img_in, Image &img_out, sycl::queue &q); }; class ConvolutionOp { @@ -61,5 +61,5 @@ class ConvolutionOp { } void apply_native(Image &img, Image &img_out); - void apply_kernel(Image &img, sycl::queue &q, Image &img_out); + void apply_kernel(Image &img, Image &img_out, sycl::queue &q); }; diff --git a/src/ops/blur.cpp b/src/ops/blur.cpp index 1fe9358..6d9abf5 100644 --- a/src/ops/blur.cpp +++ b/src/ops/blur.cpp @@ -95,7 +95,7 @@ void BlurOp::apply_native(Image &img, Image &img_out) { img_out.height = img.height; } -void BlurOp::apply_kernel(Image &img, sycl::queue &q, Image &img_out) { +void BlurOp::apply_kernel(Image &img, Image &img_out, sycl::queue &q) { const int channels = 4; if (percentage == 0) { diff --git a/src/ops/convolution.cpp b/src/ops/convolution.cpp index d69932d..2675152 100644 --- a/src/ops/convolution.cpp +++ b/src/ops/convolution.cpp @@ -69,7 +69,7 @@ void ConvolutionOp::apply_native(Image &img, Image &img_out) { img_out.height = img.height; } -void ConvolutionOp::apply_kernel(Image &img, sycl::queue &q, Image &img_out) { +void ConvolutionOp::apply_kernel(Image &img, Image &img_out, sycl::queue &q) { const int channels = 4; const int img_w = img.width; const int img_h = img.height; diff --git a/src/ops/crop.cpp b/src/ops/crop.cpp index 2d30234..3db3d39 100644 --- a/src/ops/crop.cpp +++ b/src/ops/crop.cpp @@ -45,7 +45,7 @@ void CropOp::apply_native(Image &img, Image &img_out) { img_out.height = crop_h; } -void CropOp::apply_kernel(Image &img, sycl::queue &q, Image &img_out) { +void CropOp::apply_kernel(Image &img, Image &img_out, sycl::queue &q) { const int channels = 4; if (crop_x + crop_w > img.width || crop_y + crop_h > img.height || crop_x < 0 || crop_y < 0) { diff --git a/src/ops/rotate.cpp b/src/ops/rotate.cpp index 81f3168..9a02435 100644 --- a/src/ops/rotate.cpp +++ b/src/ops/rotate.cpp @@ -66,7 +66,7 @@ void RotateOp::apply_native(Image &img, Image &img_out) { img_out.height = new_h; } -void RotateOp::apply_kernel(Image &img, sycl::queue &q, Image &img_out) { +void RotateOp::apply_kernel(Image &img, Image &img_out, sycl::queue &q) { // Normalize angle int local_angle = ((angle % 360) + 360) % 360; diff --git a/tests/unit_tests.cpp b/tests/unit_tests.cpp index bfae92c..69c2f9f 100644 --- a/tests/unit_tests.cpp +++ b/tests/unit_tests.cpp @@ -5,57 +5,65 @@ TEST(NativeCPU, RotateOp) { Image img; + Image out; + img.width = 2; img.height = 1; img.pixels = {1, 1, 1, 255, 2, 2, 2, 255}; Executor exec(Device::NATIVE_CPU); RotateOp op(90); - exec.execute(img, op); + out = exec.execute(img, op); - EXPECT_EQ(img.width, 1); - EXPECT_EQ(img.height, 2); + EXPECT_EQ(out.width, 1); + EXPECT_EQ(out.height, 2); - EXPECT_EQ(img.pixels[0], 1); // Pixel 0 R - EXPECT_EQ(img.pixels[4], 2); // Pixel 1 R + EXPECT_EQ(out.pixels[0], 1); // Pixel 0 R + EXPECT_EQ(out.pixels[4], 2); // Pixel 1 R } TEST(SYCL_CPU, RotateOp) { Image img; + Image out; + img.width = 2; img.height = 1; img.pixels = {1, 1, 1, 255, 2, 2, 2, 255}; Executor exec(Device::CPU); RotateOp op(90); - exec.execute(img, op); + out = exec.execute(img, op); - EXPECT_EQ(img.width, 1); - EXPECT_EQ(img.height, 2); + EXPECT_EQ(out.width, 1); + EXPECT_EQ(out.height, 2); - EXPECT_EQ(img.pixels[0], 1); // Pixel 0 R - EXPECT_EQ(img.pixels[4], 2); // Pixel 1 R + EXPECT_EQ(out.pixels[0], 1); // Pixel 0 R + EXPECT_EQ(out.pixels[4], 2); // Pixel 1 R } TEST(SYCL_GPU, RotateOp) { Image img; + Image out; + img.width = 2; img.height = 1; img.pixels = {1, 1, 1, 255, 2, 2, 2, 255}; Executor exec(Device::GPU); RotateOp op(90); - exec.execute(img, op); + out = exec.execute(img, op); - EXPECT_EQ(img.width, 1); - EXPECT_EQ(img.height, 2); + EXPECT_EQ(out.width, 1); + EXPECT_EQ(out.height, 2); - EXPECT_EQ(img.pixels[0], 1); // Pixel 0 R - EXPECT_EQ(img.pixels[4], 2); // Pixel 1 R + EXPECT_EQ(out.pixels[0], 1); // Pixel 0 R + EXPECT_EQ(out.pixels[4], 2); // Pixel 1 R } TEST(NativeCPU, CropBottomRightPixel) { Image img; + Image out; + img.width = 2; img.height = 2; // 2x2 Image with RGBA (16 bytes total) @@ -66,54 +74,60 @@ TEST(NativeCPU, CropBottomRightPixel) { Executor exec(Device::NATIVE_CPU); // Crop 1x1 at position x=1, y=1 (the pixel {4,4,4,255}) CropOp op(1, 1, 1, 1); - exec.execute(img, op); - - EXPECT_EQ(img.width, 1); - EXPECT_EQ(img.height, 1); - EXPECT_EQ(img.pixels[0], 4); - EXPECT_EQ(img.pixels[1], 4); - EXPECT_EQ(img.pixels[2], 4); - EXPECT_EQ(img.pixels[3], 255); + out = exec.execute(img, op); + + EXPECT_EQ(out.width, 1); + EXPECT_EQ(out.height, 1); + EXPECT_EQ(out.pixels[0], 4); + EXPECT_EQ(out.pixels[1], 4); + EXPECT_EQ(out.pixels[2], 4); + EXPECT_EQ(out.pixels[3], 255); } TEST(SYCL_CPU, CropBottomRightPixel) { Image img; + Image out; + img.width = 2; img.height = 2; img.pixels = {1, 1, 1, 255, 2, 2, 2, 255, 3, 3, 3, 255, 4, 4, 4, 255}; Executor exec(Device::CPU); CropOp op(1, 1, 1, 1); - exec.execute(img, op); - - EXPECT_EQ(img.width, 1); - EXPECT_EQ(img.height, 1); - EXPECT_EQ(img.pixels[0], 4); - EXPECT_EQ(img.pixels[1], 4); - EXPECT_EQ(img.pixels[2], 4); - EXPECT_EQ(img.pixels[3], 255); + out = exec.execute(img, op); + + EXPECT_EQ(out.width, 1); + EXPECT_EQ(out.height, 1); + EXPECT_EQ(out.pixels[0], 4); + EXPECT_EQ(out.pixels[1], 4); + EXPECT_EQ(out.pixels[2], 4); + EXPECT_EQ(out.pixels[3], 255); } TEST(SYCL_GPU, CropBottomRightPixel) { Image img; + Image out; + img.width = 2; img.height = 2; img.pixels = {1, 1, 1, 255, 2, 2, 2, 255, 3, 3, 3, 255, 4, 4, 4, 255}; Executor exec(Device::GPU); CropOp op(1, 1, 1, 1); - exec.execute(img, op); - - EXPECT_EQ(img.width, 1); - EXPECT_EQ(img.height, 1); - EXPECT_EQ(img.pixels[0], 4); - EXPECT_EQ(img.pixels[1], 4); - EXPECT_EQ(img.pixels[2], 4); - EXPECT_EQ(img.pixels[3], 255); + out = exec.execute(img, op); + + EXPECT_EQ(out.width, 1); + EXPECT_EQ(out.height, 1); + EXPECT_EQ(out.pixels[0], 4); + EXPECT_EQ(out.pixels[1], 4); + EXPECT_EQ(out.pixels[2], 4); + EXPECT_EQ(out.pixels[3], 255); } TEST(NativeCPU, ConvolutionOp) { Image img; + Image out; + img.width = 3; img.height = 3; // Fill with 0s, set center pixel to {100, 100, 100, 255} @@ -126,17 +140,19 @@ TEST(NativeCPU, ConvolutionOp) { Executor exec(Device::NATIVE_CPU); // Identity Kernel ConvolutionOp op({{0.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 0.0f}}); - exec.execute(img, op); + out = exec.execute(img, op); // Check that the center pixel remains the same - EXPECT_EQ(img.pixels[16], 100); - EXPECT_EQ(img.pixels[17], 100); - EXPECT_EQ(img.pixels[18], 100); - EXPECT_EQ(img.pixels[19], 255); + EXPECT_EQ(out.pixels[16], 100); + EXPECT_EQ(out.pixels[17], 100); + EXPECT_EQ(out.pixels[18], 100); + EXPECT_EQ(out.pixels[19], 255); } TEST(SYCL_CPU, ConvolutionOp) { Image img; + Image out; + img.width = 3; img.height = 3; img.pixels.resize(3 * 3 * 4, 0); @@ -147,16 +163,18 @@ TEST(SYCL_CPU, ConvolutionOp) { Executor exec(Device::CPU); ConvolutionOp op({{0.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 0.0f}}); - exec.execute(img, op); + out = exec.execute(img, op); - EXPECT_EQ(img.pixels[16], 100); - EXPECT_EQ(img.pixels[17], 100); - EXPECT_EQ(img.pixels[18], 100); - EXPECT_EQ(img.pixels[19], 255); + EXPECT_EQ(out.pixels[16], 100); + EXPECT_EQ(out.pixels[17], 100); + EXPECT_EQ(out.pixels[18], 100); + EXPECT_EQ(out.pixels[19], 255); } TEST(SYCL_GPU, ConvolutionOp) { Image img; + Image out; + img.width = 3; img.height = 3; img.pixels.resize(3 * 3 * 4, 0); @@ -167,54 +185,74 @@ TEST(SYCL_GPU, ConvolutionOp) { Executor exec(Device::GPU); ConvolutionOp op({{0.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 0.0f}}); - exec.execute(img, op); + out = exec.execute(img, op); - EXPECT_EQ(img.pixels[16], 100); - EXPECT_EQ(img.pixels[17], 100); - EXPECT_EQ(img.pixels[18], 100); - EXPECT_EQ(img.pixels[19], 255); + EXPECT_EQ(out.pixels[16], 100); + EXPECT_EQ(out.pixels[17], 100); + EXPECT_EQ(out.pixels[18], 100); + EXPECT_EQ(out.pixels[19], 255); } TEST(NativeCPU, BlurOp) { Image img; + Image out; + img.width = 10; img.height = 10; - // Initialize with 100 for ALL channels (R, G, B, A) + + // Initialize image with 100 for ALL channels (R, G, B, A) img.pixels.assign(10 * 10 * 4, 100); + // Initialize out with 100 for ALL channels (R, G, B, A) + out.pixels.assign(10 * 10 * 4, 100); + Executor exec(Device::NATIVE_CPU); BlurOp op(50); - exec.execute(img, op); + out = exec.execute(img, op); // Expect all channels to remain 100 after the blur - EXPECT_EQ(img.pixels[0], 100); // R - EXPECT_EQ(img.pixels[3], 100); // A (Blurred) + EXPECT_EQ(out.pixels[0], 100); // R + EXPECT_EQ(out.pixels[3], 100); // A (Blurred) } TEST(SYCL_CPU, BlurOp) { Image img; + Image out; + img.width = 10; img.height = 10; + + // Initialize image with 100 for ALL channels (R, G, B, A) img.pixels.assign(10 * 10 * 4, 100); + // Initialize out with 100 for ALL channels (R, G, B, A) + out.pixels.assign(10 * 10 * 4, 100); + Executor exec(Device::CPU); BlurOp op(50); - exec.execute(img, op); + out = exec.execute(img, op); - EXPECT_EQ(img.pixels[0], 100); - EXPECT_EQ(img.pixels[3], 100); + EXPECT_EQ(out.pixels[0], 100); + EXPECT_EQ(out.pixels[3], 100); } TEST(SYCL_GPU, BlurOp) { Image img; + Image out; + img.width = 10; img.height = 10; + + // Initialize image with 100 for ALL channels (R, G, B, A) img.pixels.assign(10 * 10 * 4, 100); + // Initialize out with 100 for ALL channels (R, G, B, A) + out.pixels.assign(10 * 10 * 4, 100); + Executor exec(Device::GPU); BlurOp op(50); - exec.execute(img, op); + out = exec.execute(img, op); - EXPECT_EQ(img.pixels[0], 100); - EXPECT_EQ(img.pixels[3], 100); + EXPECT_EQ(out.pixels[0], 100); + EXPECT_EQ(out.pixels[3], 100); } \ No newline at end of file From 93a2ab164643c0458b21566886db182f6ac48cee Mon Sep 17 00:00:00 2001 From: Emi Date: Thu, 27 Aug 2026 18:59:48 +0300 Subject: [PATCH 4/4] fix the small issues --- include/image_io.h | 2 +- src/engine_logic.cpp | 3 +-- src/image_io.cpp | 18 +++++++++++------- src/ops/blur.cpp | 12 ++++++------ 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/include/image_io.h b/include/image_io.h index d4bc870..e73c66f 100644 --- a/include/image_io.h +++ b/include/image_io.h @@ -6,4 +6,4 @@ void display_image(const Image &img); Image load_image(const std::string &filepath); -void save_image(const std::string &path, const Image &img); +void save_image(const Image &img, const std::string &filename = "output.png"); diff --git a/src/engine_logic.cpp b/src/engine_logic.cpp index 7c96ade..c0da14b 100644 --- a/src/engine_logic.cpp +++ b/src/engine_logic.cpp @@ -37,7 +37,6 @@ void run_operations(const AppContext &ctx) { Executor myExe(ctx.type); Image img = load_image(ctx.filepath); Image out; - std::string path = "/home/emi/projects/image-engine/output/output.png"; display_image(img); @@ -52,5 +51,5 @@ void run_operations(const AppContext &ctx) { } display_image(out); - save_image(path, out); + save_image(out); } diff --git a/src/image_io.cpp b/src/image_io.cpp index 930ca9b..1b749e6 100644 --- a/src/image_io.cpp +++ b/src/image_io.cpp @@ -62,20 +62,24 @@ Image load_image(const std::string &filepath) { return img; } -void save_image(const std::string &filepath, const Image &img) { +void save_image(const Image &img, const std::string &filename) { if (img.pixels.empty() || img.width <= 0 || img.height <= 0) { throw std::runtime_error("Empty image, abort!"); } - std::filesystem::path path(filepath); - if (path.has_parent_path()) { - std::filesystem::create_directories(path.parent_path()); + std::filesystem::path output_dir = "output"; + std::filesystem::path full_path = output_dir / filename; + + std::filesystem::create_directories(output_dir); + + if (std::filesystem::exists(full_path)) { + std::filesystem::remove(full_path); } sf::Image sfml_img; sfml_img.create(img.width, img.height, img.pixels.data()); - if (!sfml_img.saveToFile(filepath)) { - throw std::runtime_error("Saving error in: " + filepath); + if (!sfml_img.saveToFile(full_path.string())) { + throw std::runtime_error("Saving error in: " + full_path.string()); } -} \ No newline at end of file +} diff --git a/src/ops/blur.cpp b/src/ops/blur.cpp index 6d9abf5..02789a6 100644 --- a/src/ops/blur.cpp +++ b/src/ops/blur.cpp @@ -30,7 +30,7 @@ void BlurOp::apply_native(Image &img, Image &img_out) { auto start = std::chrono::high_resolution_clock::now(); - // PASS 1: Horizontal Blur (img.pixels -> temp_pixels) + // Pass 1 -- Horizontal Blur (img.pixels -> temp_pixels) for (int y = 0; y < img.height; ++y) { for (int x = 0; x < img.width; ++x) { int sum_r = 0, sum_g = 0, sum_b = 0, sum_a = 0; @@ -57,7 +57,7 @@ void BlurOp::apply_native(Image &img, Image &img_out) { } } - // PASS 2: Vertical Blur (temp_pixels -> blurred_pixels) + // Pass 2 -- Vertical Blur (temp_pixels -> blurred_pixels) for (int y = 0; y < img.height; ++y) { for (int x = 0; x < img.width; ++x) { int sum_r = 0, sum_g = 0, sum_b = 0, sum_a = 0; @@ -120,14 +120,14 @@ void BlurOp::apply_kernel(Image &img, Image &img_out, sycl::queue &q) { const int r = radius; std::vector temp_pixels(img.pixels.size()); - std::vector blurred_pixels(img.pixels.size()); // <--- 1. Am creat bufferul de ieșire + std::vector blurred_pixels(img.pixels.size()); { sycl::buffer in_buf(img.pixels.data(), sycl::range<1>(img.pixels.size())); sycl::buffer tmp_buf(temp_pixels.data(), sycl::range<1>(temp_pixels.size())); sycl::buffer out_buf(blurred_pixels.data(), sycl::range<1>(blurred_pixels.size())); // <--- 2. Buffer dedicat pentru out - // --- PASS 1: Horizontal Blur (in -> tmp) --- + // Pass 1 -- Horizontal Blur (in -> tmp) sycl::event e1 = q.submit([&](sycl::handler &cgh) { auto in = in_buf.get_access(cgh); auto tmp = tmp_buf.get_access(cgh); @@ -154,10 +154,10 @@ void BlurOp::apply_kernel(Image &img, Image &img_out, sycl::queue &q) { }); }); - // --- PASS 2: Vertical Blur (tmp -> out) --- + // Pass 2 -- Vertical Blur (tmp -> out) sycl::event e2 = q.submit([&](sycl::handler &cgh) { auto tmp = tmp_buf.get_access(cgh); - auto out = out_buf.get_access(cgh); // <--- Scriem în out_buf + auto out = out_buf.get_access(cgh); cgh.parallel_for(sycl::range<2>(img_h, img_w), [=](sycl::id<2> idx) { int y = idx[0];