Add Datamosh, a video effect whose Corrupt blocks break and heal over the clip
Corrupt's block damage moves to effects/corrupt_blocks.hpp so both share it; Corrupt renders exactly as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,11 @@ commit that made it. The interface shows this file under Changelog in its menu.
|
|||||||
the gate, flicker, dust and scratches. On the command line it is
|
the gate, flicker, dust and scratches. On the command line it is
|
||||||
`-e filmgate` with `--gate-grain`, `--gate-weave`, `--gate-flicker`,
|
`-e filmgate` with `--gate-grain`, `--gate-weave`, `--gate-flicker`,
|
||||||
`--gate-dust` and `--gate-scratches`.
|
`--gate-dust` and `--gate-scratches`.
|
||||||
|
- Datamosh, a video effect: blocks of the picture break as Corrupt breaks
|
||||||
|
them, each for a stretch of the clip before it heals, and a displaced
|
||||||
|
block's picture smears along while it shows. On the command line it is
|
||||||
|
`-e datamosh` with `--mosh-rate`, `--mosh-hold`, `--mosh-drift`,
|
||||||
|
`--mosh-speed` and the rest.
|
||||||
- A Quality choice under Video on Setup: Small file, Standard or High, for
|
- A Quality choice under Video on Setup: Small file, Standard or High, for
|
||||||
how the saved video is encoded. It is kept in the project, and on the
|
how the saved video is encoded. It is kept in the project, and on the
|
||||||
command line it is `--video-quality small|standard|high`. Standard writes
|
command line it is `--video-quality small|standard|high`. Standard writes
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ add_library(effector_core STATIC
|
|||||||
src/effects/blur.cpp
|
src/effects/blur.cpp
|
||||||
src/effects/channelsplit.cpp
|
src/effects/channelsplit.cpp
|
||||||
src/effects/corrupt.cpp
|
src/effects/corrupt.cpp
|
||||||
|
src/effects/datamosh.cpp
|
||||||
src/effects/feedback.cpp
|
src/effects/feedback.cpp
|
||||||
src/effects/filmgate.cpp
|
src/effects/filmgate.cpp
|
||||||
src/effects/flow.cpp
|
src/effects/flow.cpp
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "effects/blur.hpp"
|
#include "effects/blur.hpp"
|
||||||
#include "effects/channelsplit.hpp"
|
#include "effects/channelsplit.hpp"
|
||||||
#include "effects/corrupt.hpp"
|
#include "effects/corrupt.hpp"
|
||||||
|
#include "effects/datamosh.hpp"
|
||||||
#include "effects/feedback.hpp"
|
#include "effects/feedback.hpp"
|
||||||
#include "effects/filmgate.hpp"
|
#include "effects/filmgate.hpp"
|
||||||
#include "effects/flow.hpp"
|
#include "effects/flow.hpp"
|
||||||
@@ -49,6 +50,7 @@ const std::vector<EffectPtr>& effects() {
|
|||||||
std::make_shared<FlowEffect>(),
|
std::make_shared<FlowEffect>(),
|
||||||
std::make_shared<SweepEffect>(),
|
std::make_shared<SweepEffect>(),
|
||||||
std::make_shared<FilmGateEffect>(),
|
std::make_shared<FilmGateEffect>(),
|
||||||
|
std::make_shared<DatamoshEffect>(),
|
||||||
};
|
};
|
||||||
return registry;
|
return registry;
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-75
@@ -1,10 +1,9 @@
|
|||||||
#include "effects/corrupt.hpp"
|
#include "effects/corrupt.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstring>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "core/parallel.hpp"
|
#include "effects/corrupt_blocks.hpp"
|
||||||
#include "effects/sampling.hpp"
|
#include "effects/sampling.hpp"
|
||||||
#include "effects/source_fill.hpp"
|
#include "effects/source_fill.hpp"
|
||||||
|
|
||||||
@@ -28,19 +27,6 @@ const std::vector<ParamSpec>& CorruptEffect::params() const {
|
|||||||
return specs;
|
return specs;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
enum class Damage : std::uint8_t { None, Repeat, Displace, Colour };
|
|
||||||
|
|
||||||
struct Block {
|
|
||||||
Damage damage = Damage::None;
|
|
||||||
int sx = 0; // displace: where the block is read from
|
|
||||||
int sy = 0;
|
|
||||||
int shift[3] = {0, 0, 0}; // colour: added to each channel
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
Image CorruptEffect::render(const std::vector<Image>& sources, int width, int height, Rng& rng,
|
Image CorruptEffect::render(const std::vector<Image>& sources, int width, int height, Rng& rng,
|
||||||
const ParamValues& values) const {
|
const ParamValues& values) const {
|
||||||
if (width < 1 || height < 1) throw EffectError("Canvas size must be at least 1x1 pixels.");
|
if (width < 1 || height < 1) throw EffectError("Canvas size must be at least 1x1 pixels.");
|
||||||
@@ -55,23 +41,6 @@ Image CorruptEffect::render(const std::vector<Image>& sources, int width, int he
|
|||||||
const int cols = (width + size - 1) / size;
|
const int cols = (width + size - 1) / size;
|
||||||
const int rows = (height + size - 1) / size;
|
const int rows = (height + size - 1) / size;
|
||||||
|
|
||||||
auto drawBlock = [&](int bx) {
|
|
||||||
Block block;
|
|
||||||
if (kinds == "repeat") block.damage = Damage::Repeat;
|
|
||||||
else if (kinds == "displace") block.damage = Damage::Displace;
|
|
||||||
else if (kinds == "colour") block.damage = Damage::Colour;
|
|
||||||
else block.damage = static_cast<Damage>(1 + rng.below(2));
|
|
||||||
// The first block of a row has nothing to its left to repeat.
|
|
||||||
if (block.damage == Damage::Repeat && bx == 0) block.damage = Damage::Displace;
|
|
||||||
if (block.damage == Damage::Displace) {
|
|
||||||
block.sx = static_cast<int>(rng.inRange(0, std::max(0, width - size)));
|
|
||||||
block.sy = static_cast<int>(rng.inRange(0, std::max(0, height - size)));
|
|
||||||
} else if (block.damage == Damage::Colour) {
|
|
||||||
for (int& shift : block.shift) shift = static_cast<int>(rng.inRange(-128, 128));
|
|
||||||
}
|
|
||||||
return block;
|
|
||||||
};
|
|
||||||
|
|
||||||
// The whole damage map is drawn up front, block by block in reading order.
|
// The whole damage map is drawn up front, block by block in reading order.
|
||||||
std::vector<Block> plan(static_cast<std::size_t>(cols) * rows);
|
std::vector<Block> plan(static_cast<std::size_t>(cols) * rows);
|
||||||
for (int by = 0; by < rows; ++by) {
|
for (int by = 0; by < rows; ++by) {
|
||||||
@@ -88,54 +57,13 @@ Image CorruptEffect::render(const std::vector<Image>& sources, int width, int he
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!chance(rng.next(), rate)) continue;
|
if (!chance(rng.next(), rate)) continue;
|
||||||
block = drawBlock(bx);
|
block = drawDamage(rng, kinds, bx, width, height, size);
|
||||||
if (bleed) carried = █
|
if (bleed) carried = █
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Image canvas = input;
|
Image canvas = input;
|
||||||
// Block rows are independent (a repeat only ever reads to its left), so
|
paintBlocks(canvas, input, plan, cols, rows, size);
|
||||||
// they run in parallel and each walks its row in order.
|
|
||||||
parallelFor(static_cast<std::size_t>(rows), [&](std::size_t begin, std::size_t end) {
|
|
||||||
for (std::size_t by = begin; by < end; ++by) {
|
|
||||||
const int y0 = static_cast<int>(by) * size;
|
|
||||||
const int h = std::min(size, height - y0);
|
|
||||||
for (int bx = 0; bx < cols; ++bx) {
|
|
||||||
const Block& block = plan[by * static_cast<std::size_t>(cols) + bx];
|
|
||||||
if (block.damage == Damage::None) continue;
|
|
||||||
const int x0 = bx * size;
|
|
||||||
const int w = std::min(size, width - x0);
|
|
||||||
const std::size_t bytes = static_cast<std::size_t>(w) * 3;
|
|
||||||
|
|
||||||
for (int line = 0; line < h; ++line) {
|
|
||||||
std::uint8_t* out = canvas.row(y0 + line) + static_cast<std::size_t>(x0) * 3;
|
|
||||||
switch (block.damage) {
|
|
||||||
case Damage::Repeat:
|
|
||||||
std::memmove(out, out - static_cast<std::size_t>(size) * 3, bytes);
|
|
||||||
break;
|
|
||||||
case Damage::Displace: {
|
|
||||||
const int sy = std::min(block.sy + line, height - 1);
|
|
||||||
const std::uint8_t* in = input.row(sy);
|
|
||||||
for (int x = 0; x < w; ++x) {
|
|
||||||
const int sx = (block.sx + x) % width;
|
|
||||||
std::memcpy(out + static_cast<std::size_t>(x) * 3,
|
|
||||||
in + static_cast<std::size_t>(sx) * 3, 3);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Damage::Colour:
|
|
||||||
for (std::size_t i = 0; i < bytes; ++i) {
|
|
||||||
out[i] = static_cast<std::uint8_t>(
|
|
||||||
std::clamp(out[i] + block.shift[i % 3], 0, 255));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Damage::None:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "core/image.hpp"
|
||||||
|
#include "core/parallel.hpp"
|
||||||
|
#include "core/rng.hpp"
|
||||||
|
|
||||||
|
namespace effector {
|
||||||
|
|
||||||
|
// The damage a codec does to one block of the picture. Shared by the corrupt
|
||||||
|
// and the datamosh effects.
|
||||||
|
enum class Damage : std::uint8_t { None, Repeat, Displace, Colour };
|
||||||
|
|
||||||
|
struct Block {
|
||||||
|
Damage damage = Damage::None;
|
||||||
|
int sx = 0; // displace: where the block is read from
|
||||||
|
int sy = 0;
|
||||||
|
int shift[3] = {0, 0, 0}; // colour: added to each channel
|
||||||
|
};
|
||||||
|
|
||||||
|
// Draws the damage of one block, of the kind `kinds` names ("mixed" picks
|
||||||
|
// one), for a block `bx` blocks from the left of a canvas `width` by `height`.
|
||||||
|
inline Block drawDamage(Rng& rng, const std::string& kinds, int bx, int width, int height,
|
||||||
|
int size) {
|
||||||
|
Block block;
|
||||||
|
if (kinds == "repeat") block.damage = Damage::Repeat;
|
||||||
|
else if (kinds == "displace") block.damage = Damage::Displace;
|
||||||
|
else if (kinds == "colour") block.damage = Damage::Colour;
|
||||||
|
else block.damage = static_cast<Damage>(1 + rng.below(2));
|
||||||
|
// The first block of a row has nothing to its left to repeat.
|
||||||
|
if (block.damage == Damage::Repeat && bx == 0) block.damage = Damage::Displace;
|
||||||
|
if (block.damage == Damage::Displace) {
|
||||||
|
block.sx = static_cast<int>(rng.inRange(0, std::max(0, width - size)));
|
||||||
|
block.sy = static_cast<int>(rng.inRange(0, std::max(0, height - size)));
|
||||||
|
} else if (block.damage == Damage::Colour) {
|
||||||
|
for (int& shift : block.shift) shift = static_cast<int>(rng.inRange(-128, 128));
|
||||||
|
}
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lays the damage map `plan` (`cols` by `rows` blocks of `size` pixels, in
|
||||||
|
// reading order) over `canvas`, which starts as a copy of `input`. A displaced
|
||||||
|
// block reads `input` from (sx, sy) on, wrapping across its width.
|
||||||
|
inline void paintBlocks(Image& canvas, const Image& input, const std::vector<Block>& plan, int cols,
|
||||||
|
int rows, int size) {
|
||||||
|
const int width = canvas.width();
|
||||||
|
const int height = canvas.height();
|
||||||
|
// Block rows are independent (a repeat only ever reads to its left), so
|
||||||
|
// they run in parallel and each walks its row in order.
|
||||||
|
parallelFor(static_cast<std::size_t>(rows), [&](std::size_t begin, std::size_t end) {
|
||||||
|
for (std::size_t by = begin; by < end; ++by) {
|
||||||
|
const int y0 = static_cast<int>(by) * size;
|
||||||
|
const int h = std::min(size, height - y0);
|
||||||
|
for (int bx = 0; bx < cols; ++bx) {
|
||||||
|
const Block& block = plan[by * static_cast<std::size_t>(cols) + bx];
|
||||||
|
if (block.damage == Damage::None) continue;
|
||||||
|
const int x0 = bx * size;
|
||||||
|
const int w = std::min(size, width - x0);
|
||||||
|
const std::size_t bytes = static_cast<std::size_t>(w) * 3;
|
||||||
|
|
||||||
|
for (int line = 0; line < h; ++line) {
|
||||||
|
std::uint8_t* out = canvas.row(y0 + line) + static_cast<std::size_t>(x0) * 3;
|
||||||
|
switch (block.damage) {
|
||||||
|
case Damage::Repeat:
|
||||||
|
std::memmove(out, out - static_cast<std::size_t>(size) * 3, bytes);
|
||||||
|
break;
|
||||||
|
case Damage::Displace: {
|
||||||
|
const int sy = std::min(block.sy + line, height - 1);
|
||||||
|
const std::uint8_t* in = input.row(sy);
|
||||||
|
for (int x = 0; x < w; ++x) {
|
||||||
|
const int sx = (block.sx + x) % width;
|
||||||
|
std::memcpy(out + static_cast<std::size_t>(x) * 3,
|
||||||
|
in + static_cast<std::size_t>(sx) * 3, 3);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Damage::Colour:
|
||||||
|
for (std::size_t i = 0; i < bytes; ++i) {
|
||||||
|
out[i] = static_cast<std::uint8_t>(
|
||||||
|
std::clamp(out[i] + block.shift[i % 3], 0, 255));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Damage::None:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace effector
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
#include "effects/datamosh.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "effects/corrupt_blocks.hpp"
|
||||||
|
#include "effects/sampling.hpp"
|
||||||
|
|
||||||
|
namespace effector {
|
||||||
|
|
||||||
|
const std::vector<ParamSpec>& DatamoshEffect::params() const {
|
||||||
|
static const std::vector<ParamSpec> 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<int>(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<int>(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<Break> breaks(static_cast<std::size_t>(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<std::size_t>(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<Block> 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<int>(std::lround(slide * piece.driftX));
|
||||||
|
block.sx = (sx % width + width) % width;
|
||||||
|
block.sy = std::clamp(block.sy + static_cast<int>(std::lround(slide * piece.driftY)), 0,
|
||||||
|
height - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Image canvas = input;
|
||||||
|
paintBlocks(canvas, input, plan, cols, rows, size);
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace effector
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "effects/video_effect.hpp"
|
||||||
|
|
||||||
|
namespace effector {
|
||||||
|
|
||||||
|
// The corrupt effect in motion: blocks of the picture break as a damaged
|
||||||
|
// stream breaks them, and each broken block holds for a stretch of the clip
|
||||||
|
// before it heals, a displaced one smearing its picture along while it shows.
|
||||||
|
//
|
||||||
|
// Which blocks break, how, and when in the cycle each is broken is drawn from
|
||||||
|
// at.steady(); a frame only asks which of them are broken at its moment, so
|
||||||
|
// the clip loops without a jump.
|
||||||
|
class DatamoshEffect : public VideoEffect {
|
||||||
|
public:
|
||||||
|
std::string name() const override { return "datamosh"; }
|
||||||
|
std::string title() const override { return "Datamosh"; }
|
||||||
|
std::string description() const override {
|
||||||
|
return "Breaks blocks of the picture as Corrupt does, each for a stretch of the clip, "
|
||||||
|
"displaced blocks smearing along while they show.";
|
||||||
|
}
|
||||||
|
const std::vector<ParamSpec>& params() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Image animate(Image input, const ParamValues& values, const Moment& at) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace effector
|
||||||
@@ -2115,6 +2115,8 @@ int main() {
|
|||||||
{"gate_scratches", 0L}},
|
{"gate_scratches", 0L}},
|
||||||
{{"gate_weave", 0L}, {"gate_grain", 0.0}, {"gate_flicker", 0.0}, {"gate_dust", 0L},
|
{{"gate_weave", 0L}, {"gate_grain", 0.0}, {"gate_flicker", 0.0}, {"gate_dust", 0L},
|
||||||
{"gate_scratches", 0L}}},
|
{"gate_scratches", 0L}}},
|
||||||
|
{"datamosh", {{"mosh_rate", 0.4}, {"mosh_block", 8L}, {"mosh_bleed", true}}, {},
|
||||||
|
{{"mosh_rate", 0.0}}},
|
||||||
};
|
};
|
||||||
for (const auto& [name, lively, smooth, off] : moving) {
|
for (const auto& [name, lively, smooth, off] : moving) {
|
||||||
const auto effect = effectByName(name);
|
const auto effect = effectByName(name);
|
||||||
|
|||||||
Reference in New Issue
Block a user