#pragma once #include #include #include #include #include "core/image.hpp" #include "core/parallel.hpp" namespace effector { // One separable box blur pass. `stride` is the distance in bytes between two // neighbouring samples along the blurred axis, `count` how many of them there // are; the window is clamped at both ends. inline void boxPass(const std::uint8_t* in, std::uint8_t* out, int count, std::size_t stride, int radius) { const int window = 2 * radius + 1; int sum[3] = {0, 0, 0}; for (int c = 0; c < 3; ++c) { sum[c] = in[c] * (radius + 1); for (int i = 1; i <= radius; ++i) { sum[c] += in[static_cast(std::min(i, count - 1)) * stride + c]; } } for (int i = 0; i < count; ++i) { for (int c = 0; c < 3; ++c) { out[static_cast(i) * stride + c] = static_cast(sum[c] / window); } const int add = std::min(i + radius + 1, count - 1); const int drop = std::max(i - radius, 0); for (int c = 0; c < 3; ++c) { sum[c] += in[static_cast(add) * stride + c] - in[static_cast(drop) * stride + c]; } } } inline void blurHorizontal(const Image& src, Image& dst, int radius) { parallelFor(static_cast(src.height()), [&](std::size_t begin, std::size_t end) { for (std::size_t y = begin; y < end; ++y) { boxPass(src.row(static_cast(y)), dst.row(static_cast(y)), src.width(), 3, radius); } }); } inline void blurVertical(const Image& src, Image& dst, int radius) { const std::size_t stride = src.stride(); parallelFor(static_cast(src.width()), [&](std::size_t begin, std::size_t end) { for (std::size_t x = begin; x < end; ++x) { const std::size_t offset = x * 3; boxPass(src.data() + offset, dst.data() + offset, src.height(), stride, radius); } }); } // Blurs `canvas` in place: `passes` box blurs of `radius` along the axes // `direction` names ("both", "horizontal" or "vertical"). Shared by the blur // and the focus pull effects. inline void boxBlur(Image& canvas, int radius, int passes, const std::string& direction) { const int width = canvas.width(); const int height = canvas.height(); Image scratch(width, height); for (int pass = 0; pass < passes; ++pass) { if (direction != "vertical") { blurHorizontal(canvas, scratch, std::min(radius, std::max(1, width - 1))); std::swap(canvas, scratch); } if (direction != "horizontal") { blurVertical(canvas, scratch, std::min(radius, std::max(1, height - 1))); std::swap(canvas, scratch); } } } } // namespace effector