Add eight effects that throw something chaotic into the chain

Slit shift, channel split, pixel sort, corruption, shatter, warp, bit
crush and feedback: tearing bands sideways, knocking the colour planes
out of register, sorting runs of pixels, damaging blocks the way a broken
stream does, breaking the picture into shards, pushing it along a noise
field, mangling its bytes and laying it over itself again and again.

All eight are pure transforms. None needs sources, so any of them can
lead a chain, and like blur they start from a crop of a random source
when they do and photographs are loaded. canvasFromSources holds that
start once for them.

Each draws its whole plan from the rng before touching a pixel, and
pixel sort hands every line a seed of its own, so the parallel work
never decides anything. The rotations in shatter and feedback take their
sine and cosine from a short series of plain multiplications rather than
libm, so a seed lands on the same pixels whatever the machine's maths
library makes of std::sin.

The tests hold every one of them to the same things -- a seed
reproduces it, it runs without sources and as a later stage, a sliver
of a canvas does not trip it, turned right down it leaves its input
alone -- plus a check of what each actually does, and the registry now
checks that no two effects declare the same param key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Si
2026-09-23 20:36:09 +02:00
co-authored by Claude Opus 5.5
parent 8113a06da7
commit ce3a422f6a
21 changed files with 1548 additions and 0 deletions
+18
View File
@@ -2,9 +2,11 @@
#include <algorithm>
#include <cstring>
#include <vector>
#include "core/image.hpp"
#include "core/parallel.hpp"
#include "core/rng.hpp"
namespace effector {
@@ -35,4 +37,20 @@ inline void fillFromSource(Image& canvas, const Image& src, int sx, int sy) {
});
}
// 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<Image>& 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<int>(rng.inRange(0, std::max(0, src.width() - width)));
const int sy = static_cast<int>(rng.inRange(0, std::max(0, src.height() - height)));
fillFromSource(canvas, src, sx, sy);
}
return canvas;
}
} // namespace effector