#include "effects/datamosh.hpp" #include #include #include #include #include "effects/corrupt_blocks.hpp" #include "effects/sampling.hpp" namespace effector { const std::vector& DatamoshEffect::params() const { static const std::vector specs = { ParamSpec::makeInt("mosh_block", "Block size", 16, 2, 256, "Width and height in pixels of one block, as a codec would cut them."), ParamSpec::makeDouble("mosh_rate", "Rate", 0.1, 0.0, 1.0, "Chance of each block breaking at some moment of the cycle."), ParamSpec::makeChoice("mosh_kinds", "Damage", "mixed", {"mixed", "repeat", "displace", "colour"}, "repeat freezes a block into a copy of the one to its left, " "displace pulls one in from elsewhere in the image, colour knocks " "it off colour; mixed picks one of them for every block."), ParamSpec::makeDouble("mosh_hold", "Hold", 0.3, 0.01, 1.0, "How much of each cycle a broken block stays broken, give or take " "half; 1 keeps it broken throughout."), ParamSpec::makeInt("mosh_drift", "Drift", 24, 0, 4096, "How far in pixels a displaced block's picture slides while it " "shows."), ParamSpec::makeDouble("mosh_speed", "Speed", 0.5, 0.0, 10.0, "Cycles per second, rounded so that the clip loops without a jump; " "0 holds still.", 0.05, 2), ParamSpec::makeBool("mosh_bleed", "Bleed", false, "Carry each broken block's fault on to the end of its row, the way " "one broken bit spoils the rest of a stream."), }; return specs; } namespace { struct Settings { int size; double rate; std::string kinds; double hold; int drift; double speed; bool bleed; explicit Settings(const ParamValues& values) : size(static_cast(std::max(2L, paramInt(values, "mosh_block", 16)))), rate(std::clamp(paramDouble(values, "mosh_rate", 0.1), 0.0, 1.0)), kinds(paramChoice(values, "mosh_kinds", "mixed")), hold(std::clamp(paramDouble(values, "mosh_hold", 0.3), 0.01, 1.0)), drift(static_cast(std::max(0L, paramInt(values, "mosh_drift", 24)))), speed(std::max(0.0, paramDouble(values, "mosh_speed", 0.5))), bleed(paramBool(values, "mosh_bleed", false)) {} }; // A block that breaks at some moment of the cycle: how, from when, for how // long (both as parts of a cycle), and which way a displaced one smears. struct Break { Block block; double start = 0.0; double length = 0.0; double driftX = 0.0; double driftY = 0.0; }; } // namespace Image DatamoshEffect::animate(Image input, const ParamValues& values, const Moment& at) const { const Settings settings(values); const int width = input.width(); const int height = input.height(); const int size = settings.size; const int cols = (width + size - 1) / size; const int rows = (height + size - 1) / size; // What stays put for the whole clip: the whole map of breaks, block by // block in reading order, as Corrupt draws its damage. Rng steady = at.steady(); std::vector breaks(static_cast(cols) * rows); bool any = false; for (int by = 0; by < rows; ++by) { const Break* carried = nullptr; for (int bx = 0; bx < cols; ++bx) { Break& piece = breaks[static_cast(by) * cols + bx]; if (carried != nullptr) { piece = *carried; // A displaced run keeps moving along with the row, so the // stretch reads as one shifted strip. if (piece.block.damage == Damage::Displace) piece.block.sx += size; carried = &piece; continue; } if (!chance(steady.next(), settings.rate)) continue; piece.block = drawDamage(steady, settings.kinds, bx, width, height, size); piece.start = unitDouble(steady.next()); piece.length = std::min(1.0, settings.hold * (0.5 + unitDouble(steady.next()))); sinCosDegrees(unitDouble(steady.next()) * 360.0, piece.driftY, piece.driftX); if (settings.bleed) carried = &piece; any = true; } } if (!any) return input; // Where this frame is in time: which blocks are broken, and how far // through its stretch each displaced one is. const double t = settings.speed > 0.0 ? at.cycle(settings.speed) : 0.0; std::vector plan(breaks.size()); for (std::size_t i = 0; i < breaks.size(); ++i) { const Break& piece = breaks[i]; if (piece.block.damage == Damage::None) continue; const double into = t - piece.start - std::floor(t - piece.start); if (piece.length < 1.0 && into >= piece.length) continue; Block& block = plan[i]; block = piece.block; if (block.damage == Damage::Displace && settings.drift > 0) { const double slide = settings.drift * into / piece.length; const int sx = block.sx + static_cast(std::lround(slide * piece.driftX)); block.sx = (sx % width + width) % width; block.sy = std::clamp(block.sy + static_cast(std::lround(slide * piece.driftY)), 0, height - 1); } } Image canvas = input; paintBlocks(canvas, input, plan, cols, rows, size); return canvas; } } // namespace effector