#include "effects/pixelsort.hpp" #include #include #include #include #include "core/parallel.hpp" #include "effects/sampling.hpp" #include "effects/sort_runs.hpp" #include "effects/source_fill.hpp" namespace effector { const std::vector& PixelSortEffect::params() const { static const std::vector specs = { ParamSpec::makeDouble("sort_low", "Lower threshold", 0.25, 0.0, 1.0, "Pixels darker than this break a run and stay where they are."), ParamSpec::makeDouble("sort_high", "Upper threshold", 0.8, 0.0, 1.0, "Pixels brighter than this break a run and stay where they are."), ParamSpec::makeChoice("sort_key", "Sort by", "luma", {"luma", "hue", "saturation"}, "What orders the pixels inside a run."), ParamSpec::makeChoice("sort_direction", "Direction", "horizontal", {"horizontal", "vertical"}, "Whether rows or columns are sorted."), ParamSpec::makeBool("sort_reverse", "Reverse", false, "Sort from high to low instead of from low to high."), ParamSpec::makeDouble("sort_line_chance", "Line chance", 1.0, 0.0, 1.0, "Chance of each row (or column) being sorted at all."), ParamSpec::makeInt("sort_max_run", "Longest run", 0, 0, 8192, "Each run is cut after a random length up to this many pixels, which " "frays the edges of the sorted streaks (0 = no limit)."), }; return specs; } namespace { struct LinePlan { bool sorted = false; std::uint64_t seed = 0; // for the run lengths, when they are limited }; } // namespace Image PixelSortEffect::render(const std::vector& 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."); const int low = static_cast(std::clamp(paramDouble(values, "sort_low", 0.25), 0.0, 1.0) * 255.0 + 0.5); const int high = static_cast(std::clamp(paramDouble(values, "sort_high", 0.8), 0.0, 1.0) * 255.0 + 0.5); const std::string keyName = paramChoice(values, "sort_key", "luma"); const bool vertical = paramChoice(values, "sort_direction", "horizontal") == "vertical"; const bool reverse = paramBool(values, "sort_reverse", false); const double lineChance = std::clamp(paramDouble(values, "sort_line_chance", 1.0), 0.0, 1.0); const int maxRun = static_cast(std::max(0L, paramInt(values, "sort_max_run", 0))); Image canvas = canvasFromSources(sources, width, height, rng); if (vertical) canvas = transposed(canvas); const int lines = canvas.height(); const int span = canvas.width(); // Which lines are sorted, and a seed for each line's run lengths, are drawn // serially; the lines then sort in parallel, each from its own generator. std::vector plan(static_cast(lines)); for (auto& line : plan) { line.sorted = chance(rng.next(), lineChance); if (maxRun > 0) line.seed = rng.next(); } SortRule rule; rule.low = low; rule.high = high; rule.key = sortKeyNamed(keyName); rule.reverse = reverse; rule.maxRun = maxRun; parallelFor(static_cast(lines), [&](std::size_t begin, std::size_t end) { std::vector>> run; for (std::size_t line = begin; line < end; ++line) { if (!plan[line].sorted) continue; Rng runRng(plan[line].seed); sortLine(canvas.row(static_cast(line)), span, rule, runRng, run); } }); return vertical ? transposed(canvas) : canvas; } } // namespace effector