Add a film grain effect
Grain is the third effect and the second that declares needsSources() false, so it works as a filter stage in a cascade and can also lead one, filling the canvas from a source the way blur does. Its knobs are the amount a grain may push a pixel, the grain size in pixels, whether the channels are drawn apart into coloured grain, and whether the response is weighted towards the midtones — which it is by default, since film grains least where it is least exposed. That last one means grain over an empty canvas stays black unless the weighting is turned off. Determinism comes from the same shape the scanlines effect uses: the whole grain field is drawn serially from the Rng into one cell per grain before any threading, and only the integer pixel arithmetic afterwards runs under parallelFor. The canvas fill blur used to keep to itself moves to effects/source_fill.hpp unchanged, now that two effects want it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+1
-25
@@ -5,6 +5,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "core/parallel.hpp"
|
||||
#include "effects/source_fill.hpp"
|
||||
|
||||
namespace effector {
|
||||
|
||||
@@ -24,31 +25,6 @@ const std::vector<ParamSpec>& BlurEffect::params() const {
|
||||
|
||||
namespace {
|
||||
|
||||
// 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.
|
||||
void fillFromSource(Image& canvas, const Image& src, int sx, int sy) {
|
||||
const int width = canvas.width();
|
||||
const std::size_t total = static_cast<std::size_t>(width) * 3;
|
||||
const int copyWidth = std::min(width, src.width() - sx);
|
||||
const std::size_t copyBytes = static_cast<std::size_t>(copyWidth) * 3;
|
||||
|
||||
parallelFor(static_cast<std::size_t>(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<int>(y) % std::max(1, src.height() - sy));
|
||||
const std::uint8_t* in = src.row(srcY) + static_cast<std::size_t>(sx) * 3;
|
||||
std::uint8_t* out = canvas.row(static_cast<int>(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;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
Reference in New Issue
Block a user