#pragma once #include #include #include #include "core/image.hpp" #include "core/parallel.hpp" #include "core/rng.hpp" namespace effector { // Copies `src` into the canvas starting at (sx, sy), repeating it as needed so // that every pixel is covered even when the source is smaller than the canvas. // Shared by the effects that do not build an image of their own but still want // something to work on when they lead a chain. inline void fillFromSource(Image& canvas, const Image& src, int sx, int sy) { const int width = canvas.width(); const std::size_t total = static_cast(width) * 3; const int copyWidth = std::min(width, src.width() - sx); const std::size_t copyBytes = static_cast(copyWidth) * 3; parallelFor(static_cast(canvas.height()), [&](std::size_t begin, std::size_t end) { for (std::size_t y = begin; y < end; ++y) { const int srcY = sy + (static_cast(y) % std::max(1, src.height() - sy)); const std::uint8_t* in = src.row(srcY) + static_cast(sx) * 3; std::uint8_t* out = canvas.row(static_cast(y)); std::memcpy(out, in, copyBytes); 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; } } }); } // The canvas a pure transform starts from: a crop of a randomly chosen source, // or black when there is none. Inside a chain the only source is the stage // before, which has the canvas size, so no random value is drawn at all; only a // transform leading a chain with photographs loaded spends any of the rng here. inline Image canvasFromSources(const std::vector& sources, int width, int height, Rng& rng) { Image canvas(width, height); if (!sources.empty()) { const std::size_t index = rng.below(sources.size() - 1); const Image& src = sources[index]; const int sx = static_cast(rng.inRange(0, std::max(0, src.width() - width))); const int sy = static_cast(rng.inRange(0, std::max(0, src.height() - height))); fillFromSource(canvas, src, sx, sy); } return canvas; } } // namespace effector