#include "effects/striproll.hpp" #include #include #include #include #include #include "core/parallel.hpp" namespace effector { const std::vector& StripRollEffect::params() const { static const std::vector specs = { ParamSpec::makeInt("roll_height", "Strip height", 24, 1, 512, "Height in pixels of each strip."), ParamSpec::makeInt("roll_jitter", "Height jitter", 0, 0, 256, "Random +/- variation added to the strip height (0 = uniform)."), ParamSpec::makeBool("roll_keep_order", "Cycle images in order", false, "Take strips from the images in order instead of picking randomly."), ParamSpec::makeChoice("roll_axis", "Axis", "vertical", {"vertical", "horizontal"}, "vertical rolls the picture in each strip up or down through its " "image; horizontal slides it sideways."), ParamSpec::makeChoice("roll_direction", "Direction", "alternate", {"alternate", "same", "random"}, "alternate rolls every other strip the other way; same rolls them " "all one way; random picks for each strip."), ParamSpec::makeInt("roll_speed", "Speed", 120, 0, 8192, "Pixels per second the picture rolls, rounded so that each strip goes " "round its image a whole number of times in the clip; 0 holds still."), }; return specs; } namespace { struct Settings { int stripHeight; int jitter; bool keepOrder; bool horizontal; std::string direction; int speed; explicit Settings(const ParamValues& values) : stripHeight(static_cast(std::max(1L, paramInt(values, "roll_height", 24)))), jitter(static_cast(std::max(0L, paramInt(values, "roll_jitter", 0)))), keepOrder(paramBool(values, "roll_keep_order", false)), horizontal(paramChoice(values, "roll_axis", "vertical") == "horizontal"), direction(paramChoice(values, "roll_direction", "alternate")), speed(static_cast(std::max(0L, paramInt(values, "roll_speed", 120)))) {} }; // One strip of the canvas: where it lies, the image it shows, where in that // image its picture starts, and which way it rolls. struct Strip { int dstY; int height; std::size_t sourceIndex; int sx; int sy; int way; // +1 or -1 }; int wrap(long value, int span) { return static_cast((value % span + span) % span); } } // namespace Image StripRollEffect::compose(const std::vector& sources, int width, int height, const ParamValues& values, const Moment& at) const { const Settings settings(values); // What stays put for the whole clip: the strips, drawn as Scanlines draws // them, and the way each one rolls. Rng steady = at.steady(); std::vector strips; strips.reserve(static_cast(height / settings.stripHeight) + 8); for (int y = 0; y < height;) { Strip strip{}; strip.dstY = y; strip.sourceIndex = settings.keepOrder ? strips.size() % sources.size() : steady.below(sources.size() - 1); const Image& src = sources[strip.sourceIndex]; int h = settings.stripHeight; if (settings.jitter > 0) { h += static_cast(steady.inRange(-settings.jitter, settings.jitter)); } strip.height = std::max(1, std::min(h, height - y)); strip.sy = static_cast(steady.inRange(0, src.height() - 1)); strip.sx = static_cast(steady.inRange(0, src.width() - 1)); if (settings.direction == "random") { strip.way = (steady.next() >> 63) != 0 ? 1 : -1; } else if (settings.direction == "alternate") { strip.way = strips.size() % 2 == 0 ? 1 : -1; } else { strip.way = 1; } strips.push_back(strip); y += strip.height; } // Where this frame is in time: how far round its image each strip has // rolled, a whole number of rounds in the clip, the nearest to the speed. std::vector offsets(strips.size(), 0); if (settings.speed > 0) { for (std::size_t i = 0; i < strips.size(); ++i) { const Image& src = sources[strips[i].sourceIndex]; const int span = settings.horizontal ? src.width() : src.height(); const double rounds = std::max(1.0, std::round(settings.speed * at.length() / span)); const double into = std::fmod(at.seconds() * rounds / at.length(), 1.0); offsets[i] = strips[i].way * std::lround(into * span); } } Image canvas(width, height); parallelFor(strips.size(), [&](std::size_t begin, std::size_t end) { for (std::size_t i = begin; i < end; ++i) { const Strip& strip = strips[i]; const Image& src = sources[strip.sourceIndex]; const long down = settings.horizontal ? 0 : offsets[i]; const long across = settings.horizontal ? offsets[i] : 0; const int startX = wrap(strip.sx + across, src.width()); for (int line = 0; line < strip.height; ++line) { const std::uint8_t* in = src.row(wrap(strip.sy + line + down, src.height())); std::uint8_t* out = canvas.row(strip.dstY + line); // Round the image's width as often as the canvas needs. int x = 0; int from = startX; while (x < width) { const int run = std::min(width - x, src.width() - from); std::memcpy(out + static_cast(x) * 3, in + static_cast(from) * 3, static_cast(run) * 3); x += run; from = 0; } } } }); return canvas; } } // namespace effector