Add Film gate, a video effect of film running through a projector
Film grain's field moves to effects/grain_field.hpp so both share it; Film grain renders exactly as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+3
-38
@@ -4,7 +4,7 @@
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "core/parallel.hpp"
|
||||
#include "effects/grain_field.hpp"
|
||||
#include "effects/source_fill.hpp"
|
||||
|
||||
namespace effector {
|
||||
@@ -23,14 +23,6 @@ const std::vector<ParamSpec>& GrainEffect::params() const {
|
||||
return specs;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Response curve: 1 at mid grey, falling to 0 at black and at white, which is
|
||||
// roughly how grain behaves on film.
|
||||
inline int midtoneWeight(int value) { return 255 - std::abs(2 * value - 255); }
|
||||
|
||||
} // namespace
|
||||
|
||||
Image GrainEffect::render(const std::vector<Image>& sources, int width, int height, Rng& rng,
|
||||
const ParamValues& values) const {
|
||||
if (width < 1 || height < 1) throw EffectError("Canvas size must be at least 1x1 pixels.");
|
||||
@@ -54,35 +46,8 @@ Image GrainEffect::render(const std::vector<Image>& sources, int width, int heig
|
||||
|
||||
// The whole grain field is drawn serially from the rng, exactly like the
|
||||
// scanlines plan, and only the pixel arithmetic afterwards is parallel.
|
||||
const int cellsX = (width + size - 1) / size;
|
||||
const int cellsY = (height + size - 1) / size;
|
||||
const int channels = colour ? 3 : 1;
|
||||
std::vector<std::int8_t> field(static_cast<std::size_t>(cellsX) * cellsY * channels);
|
||||
for (auto& cell : field) cell = static_cast<std::int8_t>(rng.inRange(-127, 127));
|
||||
|
||||
// 127 * 255 is the largest product of a grain and a midtone weight; scaling
|
||||
// by `amount` there keeps the whole thing in integers.
|
||||
const int scale = static_cast<int>(amount * 255.0 * 65536.0 / 127.0);
|
||||
|
||||
parallelFor(static_cast<std::size_t>(height), [&](std::size_t begin, std::size_t end) {
|
||||
for (std::size_t y = begin; y < end; ++y) {
|
||||
std::uint8_t* out = canvas.row(static_cast<int>(y));
|
||||
const std::size_t rowCell =
|
||||
static_cast<std::size_t>(static_cast<int>(y) / size) * cellsX;
|
||||
|
||||
for (int x = 0; x < width; ++x) {
|
||||
const std::size_t cell = (rowCell + static_cast<std::size_t>(x / size)) * channels;
|
||||
for (int c = 0; c < 3; ++c) {
|
||||
const int value = out[static_cast<std::size_t>(x) * 3 + c];
|
||||
const int grain = field[cell + (colour ? static_cast<std::size_t>(c) : 0)];
|
||||
const int weight = midtones ? midtoneWeight(value) : 255;
|
||||
const int delta = grain * weight / 255 * scale / 65536;
|
||||
out[static_cast<std::size_t>(x) * 3 + c] =
|
||||
static_cast<std::uint8_t>(std::clamp(value + delta, 0, 255));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
const GrainField field = drawGrain(rng, width, height, size, colour);
|
||||
applyGrain(canvas, field, amount, midtones);
|
||||
|
||||
return canvas;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user