25 : tracking(0.55), bleed(0.65), softness(0.40), noise(0.50), stripe(0.25f),
26 staticBands(0.20f), seed_offset(0) {
27 init_effect_details();
32 : tracking(t), bleed(b), softness(s), noise(n), stripe(st),
33 staticBands(sb), seed_offset(seed) {
34 init_effect_details();
37 void AnalogTape::init_effect_details() {
46 static inline float lerp(
float a,
float b,
float t) {
return a + (b - a) * t; }
49 int64_t frame_number) {
50 std::shared_ptr<QImage> img = frame->GetImage();
52 int h = img->height();
54 int stride = img->bytesPerLine() / 4;
55 uint32_t *base =
reinterpret_cast<uint32_t *
>(img->bits());
57 if (w != last_w || h != last_h) {
71 #pragma omp parallel for
73 for (
int y = 0; y < h; ++y) {
74 uint32_t *row = base + y * stride;
75 float *yrow = &Y[y * w];
76 float *urow = &U[y * Uw];
77 float *vrow = &V[y * Uw];
78 for (
int x2 = 0; x2 < Uw; ++x2) {
80 uint32_t p0 = row[x0];
81 float r0 = ((p0 >> 16) & 0xFF) / 255.0f;
82 float g0 = ((p0 >> 8) & 0xFF) / 255.0f;
83 float b0 = (p0 & 0xFF) / 255.0f;
84 float y0 = 0.299f * r0 + 0.587f * g0 + 0.114f * b0;
85 float u0 = -0.14713f * r0 - 0.28886f * g0 + 0.436f * b0;
86 float v0 = 0.615f * r0 - 0.51499f * g0 - 0.10001f * b0;
91 uint32_t p1 = row[x0 + 1];
92 float r1 = ((p1 >> 16) & 0xFF) / 255.0f;
93 float g1 = ((p1 >> 8) & 0xFF) / 255.0f;
94 float b1 = (p1 & 0xFF) / 255.0f;
95 float y1 = 0.299f * r1 + 0.587f * g1 + 0.114f * b1;
96 float u1 = -0.14713f * r1 - 0.28886f * g1 + 0.436f * b1;
97 float v1 = 0.615f * r1 - 0.51499f * g1 - 0.10001f * b1;
100 v = (v0 + v1) * 0.5f;
115 else if (ParentTimeline())
120 fps =
clip->Reader()->info.fps;
121 double fps_d = fps.ToDouble();
122 double t = fps_d > 0 ? frame_number / fps_d : frame_number;
131 int r_y = std::round(lerp(0.0f, 2.0f, k_soft));
133 r_y = std::min(r_y, 1);
136 #pragma omp parallel for
138 for (
int y = 0; y < h; ++y)
139 box_blur_row(&Y[y * w], &tmpY[y * w], w, r_y);
143 float shift = lerp(0.0f, 2.5f, k_bleed);
144 int r_c = std::round(lerp(0.0f, 3.0f, k_bleed));
145 float sat = 1.0f - 0.30f * k_bleed;
146 float shift_h = shift * 0.5f;
148 #pragma omp parallel for
150 for (
int y = 0; y < h; ++y) {
151 const float *srcU = &U[y * Uw];
152 const float *srcV = &V[y * Uw];
153 float *dstU = &tmpU[y * Uw];
154 float *dstV = &tmpV[y * Uw];
155 for (
int x = 0; x < Uw; ++x) {
156 float xs = std::clamp(x - shift_h, 0.0f,
float(Uw - 1));
158 int x1 = std::min(x0 + 1, Uw - 1);
160 dstU[x] = srcU[x0] * (1 - t) + srcU[x1] * t;
161 dstV[x] = srcV[x0] * (1 - t) + srcV[x1] * t;
169 #pragma omp parallel for
171 for (
int y = 0; y < h; ++y)
172 box_blur_row(&U[y * Uw], &tmpU[y * Uw], Uw, r_c);
175 #pragma omp parallel for
177 for (
int y = 0; y < h; ++y)
178 box_blur_row(&V[y * Uw], &tmpV[y * Uw], Uw, r_c);
183 uint32_t schedSalt = (uint32_t)(k_bands * 64.0f) ^
184 ((uint32_t)(k_stripe * 64.0f) << 8) ^
185 ((uint32_t)(k_noise * 64.0f) << 16);
186 uint32_t SCHED_SEED = SEED ^ fnv1a_32(schedSalt, 0x9e3779b9u);
187 const float PI = 3.14159265358979323846f;
189 float sigmaY = lerp(0.0f, 0.08f, k_noise);
190 const float decay = 0.88f + 0.08f * k_noise;
191 const float amp = 0.18f * k_noise;
192 const float baseP = 0.0025f + 0.02f * k_noise;
194 float Hfixed = lerp(0.0f, 0.12f * h, k_stripe);
195 float Gfixed = 0.10f * k_stripe;
196 float Nfixed = 1.0f + 1.5f * k_stripe;
198 float rate = 0.4f * k_bands;
199 int dur_frames = std::round(lerp(1.0f, 6.0f, k_bands));
200 float Hburst = lerp(0.06f * h, 0.25f * h, k_bands);
201 float Gburst = lerp(0.10f, 0.25f, k_bands);
202 float sat_band = lerp(0.8f, 0.5f, k_bands);
203 float Nburst = 1.0f + 2.0f * k_bands;
205 struct Band {
float center;
double t0; };
206 std::vector<Band> bands;
207 if (k_bands > 0.0f && rate > 0.0f) {
208 const double win_len = 0.25;
209 int win_idx = int(t / win_len);
210 double lambda = rate * win_len *
211 (0.25 + 1.5f * row_density(SCHED_SEED, frame_number, 0));
212 double prob_ge1 = 1.0 - std::exp(-lambda);
213 double prob_ge2 = 1.0 - std::exp(-lambda) - lambda * std::exp(-lambda);
215 auto spawn_band = [&](
int kseed) {
216 float r1 = hash01(SCHED_SEED, uint32_t(win_idx), 11 + kseed, 0);
217 float start = r1 * win_len;
219 hash01(SCHED_SEED, uint32_t(win_idx), 12 + kseed, 0) * (h - Hburst) +
221 double t0 = win_idx * win_len +
start;
222 double t1 = t0 + dur_frames / (fps_d > 0 ? fps_d : 1.0);
223 if (t >= t0 && t < t1)
224 bands.push_back({center, t0});
227 float r = hash01(SCHED_SEED, uint32_t(win_idx), 9, 0);
235 int kf = int(std::floor(t * ft));
236 float a = float(t * ft - kf);
239 #pragma omp parallel for
241 for (
int y = 0; y < h; ++y) {
243 if (Hfixed > 0.0f && y >= h - Hfixed)
244 bandF = (y - (h - Hfixed)) / std::max(1.0f, Hfixed);
246 for (
const auto &b : bands) {
247 float halfH = Hburst * 0.5f;
248 float dist = std::abs(y - b.center);
249 float profile = std::max(0.0f, 1.0f - dist / halfH);
250 float life = float((t - b.t0) * fps_d);
251 float env = (life < 1.0f)
253 : (life < dur_frames - 1 ? 1.0f
254 : std::max(0.0f, dur_frames - life));
255 burstF = std::max(burstF, profile * env);
258 float sat_row = 1.0f - (1.0f - sat_band) * burstF;
259 if (burstF > 0.0f && sat_row != 1.0f) {
260 float *urow = &U[y * Uw];
261 float *vrow = &V[y * Uw];
262 for (
int xh = 0; xh < Uw; ++xh) {
268 float rowBias = row_density(SEED, frame_number, y);
269 float p = baseP * (0.25f + 1.5f * rowBias);
270 p *= (1.0f + 1.5f * bandF + 2.0f * burstF);
272 float hum = 0.008f * k_noise *
273 std::sin(2 * PI * (y * (6.0f / h) + 0.08f * t));
274 uint32_t s0 = SEED ^ 0x9e37u * kf ^ 0x85ebu * y;
275 uint32_t s1 = SEED ^ 0x9e37u * (kf + 1) ^ 0x85ebu * y ^ 0x1234567u;
276 auto step = [](uint32_t &s) {
282 float lift = Gfixed * bandF + Gburst * burstF;
283 float rowSigma = sigmaY * (1 + (Nfixed - 1) * bandF +
284 (Nburst - 1) * burstF);
285 float k = 0.15f + 0.35f * hash01(SEED, uint32_t(frame_number), y, 777);
286 float sL = 0.0f, sR = 0.0f;
287 for (
int x = 0; x < w; ++x) {
288 if (hash01(SEED, uint32_t(frame_number), y, x) < p)
290 if (hash01(SEED, uint32_t(frame_number), y, w - 1 - x) < p * 0.7f)
292 float n = ((step(s0) & 0xFFFFFF) / 16777215.0f) * (1 - a) +
293 ((step(s1) & 0xFFFFFF) / 16777215.0f) * a;
295 float mt = std::clamp((Y[idx] - 0.2f) / (0.8f - 0.2f), 0.0f, 1.0f);
296 float val = Y[idx] + lift + rowSigma * (2 * n - 1) *
297 (0.6f + 0.4f * mt) + hum;
298 float streak = amp * (sL + sR);
299 float newY = val + streak * (k + (1.0f - val));
300 Y[idx] = std::clamp(newY, 0.0f, 1.0f);
306 float A = lerp(0.0f, 3.0f, k_track);
307 float f = lerp(0.25f, 1.2f, k_track);
308 float Hsk = lerp(0.0f, 0.10f * h, k_track);
309 float S = lerp(0.0f, 5.0f, k_track);
310 float phase = 2 * PI * (f * t) + 0.7f * (SEED * 0.001f);
311 for (
int y = 0; y < h; ++y) {
312 float base = A * std::sin(2 * PI * 0.0035f * y + phase);
313 float skew = (y >= h - Hsk)
314 ? S * ((y - (h - Hsk)) / std::max(1.0f, Hsk))
319 auto remap_line = [&](
const float *src,
float *dst,
int width,
float scale) {
321 #pragma omp parallel for
323 for (
int y = 0; y < h; ++y) {
324 float off = dx[y] * scale;
325 const float *s = src + y * width;
326 float *d = dst + y * width;
327 int start = std::max(0,
int(std::ceil(-off)));
328 int end = std::min(width,
int(std::floor(width - off)));
329 float xs =
start + off;
334 d[x] = s[x0] * (1 - t) + s[x1] * t;
339 for (
int x = 0; x <
start; ++x)
341 for (
int x =
end; x < width; ++x)
346 remap_line(Y.data(), tmpY.data(), w, 1.0f);
348 remap_line(U.data(), tmpU.data(), Uw, 0.5f);
350 remap_line(V.data(), tmpV.data(), Uw, 0.5f);
354 #pragma omp parallel for
356 for (
int y = 0; y < h; ++y) {
357 float *yrow = &Y[y * w];
358 float *urow = &U[y * Uw];
359 float *vrow = &V[y * Uw];
360 uint32_t *row = base + y * stride;
361 for (
int x = 0; x < w; ++x) {
364 int x1 = std::min(x0 + 1, Uw - 1);
366 float u = (urow[x0] * (1 - t) + urow[x1] * t) * sat;
367 float v = (vrow[x0] * (1 - t) + vrow[x1] * t) * sat;
369 float r = yv + 1.13983f * v;
370 float g = yv - 0.39465f * u - 0.58060f * v;
371 float b = yv + 2.03211f * u;
372 int R = int(std::clamp(r, 0.0f, 1.0f) * 255.0f);
373 int G = int(std::clamp(g, 0.0f, 1.0f) * 255.0f);
374 int B = int(std::clamp(b, 0.0f, 1.0f) * 255.0f);
375 uint32_t A = row[x] & 0xFF000000u;
376 row[x] = A | (R << 16) | (G << 8) | B;
403 }
catch (
const std::exception &) {
404 throw InvalidJSON(
"JSON is invalid (missing keys or invalid data types)");
410 if (!root[
"tracking"].isNull())
412 if (!root[
"bleed"].isNull())
414 if (!root[
"softness"].isNull())
416 if (!root[
"noise"].isNull())
418 if (!root[
"stripe"].isNull())
420 if (!root[
"static_bands"].isNull())
422 if (!root[
"seed_offset"].isNull())
430 "", &
tracking, 0, 1,
false, requested_frame);
433 &
bleed, 0, 1,
false, requested_frame);
436 "", &
softness, 0, 1,
false, requested_frame);
439 &
noise, 0, 1,
false, requested_frame);
442 "Bottom tracking stripe brightness and noise.",
443 &
stripe, 0, 1,
false, requested_frame);
444 root[
"static_bands"] =
447 "Short bright static bands and extra dropouts.",
449 root[
"seed_offset"] =
451 false, requested_frame);
452 return root.toStyledString();