From 1a503211d10a341eeed934bcf27b16683c656b16 Mon Sep 17 00:00:00 2001 From: Emi Date: Sat, 22 Aug 2026 15:41:53 +0300 Subject: [PATCH 1/2] 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/2] 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; }