#pragma once #include #include #include #include "core/image.hpp" #include "core/parallel.hpp" #include "core/rng.hpp" namespace effector { // One cut-out: `w` by `h` pixels taken from source `sourceIndex` at (sx, sy) // and laid down at (dstX, dstY). Shared by the tiles and the tile swap effects. struct Tile { int dstX; int dstY; int w; int h; int sourceIndex; int sx; int sy; }; // Splits `span` into consecutive pieces of `base` pixels give or take `jitter`, // the last one clipped so that the pieces cover the span exactly. inline std::vector planSpans(Rng& rng, int base, int jitter, int span) { std::vector spans; spans.reserve(static_cast(span / base) + 8); int used = 0; while (used < span) { int size = base; if (jitter > 0) size += static_cast(rng.inRange(-jitter, jitter)); size = std::max(1, std::min(size, span - used)); spans.push_back(size); used += size; } return spans; } inline void paintTile(Image& canvas, const Image& src, const Tile& tile) { const int copyWidth = std::min(tile.w, src.width() - tile.sx); const int copyHeight = std::min(tile.h, src.height() - tile.sy); const std::size_t copyBytes = static_cast(copyWidth) * 3; const std::size_t total = static_cast(tile.w) * 3; const std::size_t offset = static_cast(tile.dstX) * 3; for (int line = 0; line < copyHeight; ++line) { const std::uint8_t* in = src.row(tile.sy + line) + static_cast(tile.sx) * 3; std::uint8_t* out = canvas.row(tile.dstY + line) + offset; std::memcpy(out, in, copyBytes); // Repeat the cut sideways when the source is narrower than the tile, // doubling the filled span on each pass. std::size_t filled = copyBytes; while (filled < total) { const std::size_t chunk = std::min(filled, total - filled); std::memcpy(out + filled, out, chunk); filled += chunk; } } // Same again downwards, out of the lines already written. for (int line = copyHeight; line < tile.h; ++line) { std::memcpy(canvas.row(tile.dstY + line) + offset, canvas.row(tile.dstY + (line % copyHeight)) + offset, total); } } // Paints the whole plan. Overlapping tiles (the scattered layout) are painted // in plan order on one thread: letting threads race for the same pixels would // make the output depend on their scheduling. inline void paintTiles(Image& canvas, const std::vector& sources, const std::vector& plan, bool overlapping) { if (overlapping) { for (const Tile& tile : plan) { paintTile(canvas, sources[static_cast(tile.sourceIndex)], tile); } return; } parallelFor(plan.size(), [&](std::size_t begin, std::size_t end) { for (std::size_t i = begin; i < end; ++i) { paintTile(canvas, sources[static_cast(plan[i].sourceIndex)], plan[i]); } }); } } // namespace effector