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;
125 reference_h =
timeline->info.height;
127 const float reference_scale_x = w > 0 ?
static_cast<float>(reference_w) /
static_cast<float>(w) : 1.0f;
128 const float reference_scale_y = h > 0 ?
static_cast<float>(reference_h) /
static_cast<float>(h) : 1.0f;
129 const float inverse_scale_x = reference_scale_x > 0.0f ? 1.0f / reference_scale_x : 1.0f;
130 double fps_d = fps.ToDouble();
131 double t = fps_d > 0 ? frame_number / fps_d : frame_number;
140 int r_y = std::round(lerp(0.0f, 2.0f, k_soft) * inverse_scale_x);
142 r_y = std::min(r_y, 1);
145 #pragma omp parallel for
147 for (
int y = 0; y < h; ++y)
148 box_blur_row(&Y[y * w], &tmpY[y * w], w, r_y);
152 float shift = lerp(0.0f, 2.5f, k_bleed) * inverse_scale_x;
153 int r_c = std::round(lerp(0.0f, 3.0f, k_bleed) * inverse_scale_x);
154 float sat = 1.0f - 0.30f * k_bleed;
155 float shift_h = shift * 0.5f;
157 #pragma omp parallel for
159 for (
int y = 0; y < h; ++y) {
160 const float *srcU = &U[y * Uw];
161 const float *srcV = &V[y * Uw];
162 float *dstU = &tmpU[y * Uw];
163 float *dstV = &tmpV[y * Uw];
164 for (
int x = 0; x < Uw; ++x) {
165 float xs = std::clamp(x - shift_h, 0.0f,
float(Uw - 1));
167 int x1 = std::min(x0 + 1, Uw - 1);
169 dstU[x] = srcU[x0] * (1 - t) + srcU[x1] * t;
170 dstV[x] = srcV[x0] * (1 - t) + srcV[x1] * t;
178 #pragma omp parallel for
180 for (
int y = 0; y < h; ++y)
181 box_blur_row(&U[y * Uw], &tmpU[y * Uw], Uw, r_c);
184 #pragma omp parallel for
186 for (
int y = 0; y < h; ++y)
187 box_blur_row(&V[y * Uw], &tmpV[y * Uw], Uw, r_c);
192 uint32_t schedSalt = (uint32_t)(k_bands * 64.0f) ^
193 ((uint32_t)(k_stripe * 64.0f) << 8) ^
194 ((uint32_t)(k_noise * 64.0f) << 16);
195 uint32_t SCHED_SEED = SEED ^ fnv1a_32(schedSalt, 0x9e3779b9u);
196 const float PI = 3.14159265358979323846f;
198 float sigmaY = lerp(0.0f, 0.08f, k_noise);
199 const float decay = 0.88f + 0.08f * k_noise;
200 const float amp = 0.18f * k_noise;
201 const float baseP = 0.0025f + 0.02f * k_noise;
203 float Hfixed = lerp(0.0f, 0.12f * h, k_stripe);
204 float Gfixed = 0.10f * k_stripe;
205 float Nfixed = 1.0f + 1.5f * k_stripe;
207 float rate = 0.4f * k_bands;
208 int dur_frames = std::round(lerp(1.0f, 6.0f, k_bands));
209 float Hburst = lerp(0.06f * h, 0.25f * h, k_bands);
210 float Gburst = lerp(0.10f, 0.25f, k_bands);
211 float sat_band = lerp(0.8f, 0.5f, k_bands);
212 float Nburst = 1.0f + 2.0f * k_bands;
214 struct Band {
float center;
double t0; };
215 std::vector<Band> bands;
216 if (k_bands > 0.0f && rate > 0.0f) {
217 const double win_len = 0.25;
218 int win_idx = int(t / win_len);
219 double lambda = rate * win_len *
220 (0.25 + 1.5f * row_density(SCHED_SEED, frame_number, 0));
221 double prob_ge1 = 1.0 - std::exp(-lambda);
222 double prob_ge2 = 1.0 - std::exp(-lambda) - lambda * std::exp(-lambda);
224 auto spawn_band = [&](
int kseed) {
225 float r1 = hash01(SCHED_SEED, uint32_t(win_idx), 11 + kseed, 0);
226 float start = r1 * win_len;
228 hash01(SCHED_SEED, uint32_t(win_idx), 12 + kseed, 0) * (h - Hburst) +
230 double t0 = win_idx * win_len +
start;
231 double t1 = t0 + dur_frames / (fps_d > 0 ? fps_d : 1.0);
232 if (t >= t0 && t < t1)
233 bands.push_back({center, t0});
236 float r = hash01(SCHED_SEED, uint32_t(win_idx), 9, 0);
244 int kf = int(std::floor(t * ft));
245 float a = float(t * ft - kf);
248 #pragma omp parallel for
250 for (
int y = 0; y < h; ++y) {
251 const int y_ref =
static_cast<int>(std::round(
static_cast<float>(y) * reference_scale_y));
253 if (Hfixed > 0.0f && y >= h - Hfixed)
254 bandF = (y - (h - Hfixed)) / std::max(1.0f, Hfixed);
256 for (
const auto &b : bands) {
257 float halfH = Hburst * 0.5f;
258 float dist = std::abs(y - b.center);
259 float profile = std::max(0.0f, 1.0f - dist / halfH);
260 float life = float((t - b.t0) * fps_d);
261 float env = (life < 1.0f)
263 : (life < dur_frames - 1 ? 1.0f
264 : std::max(0.0f, dur_frames - life));
265 burstF = std::max(burstF, profile * env);
268 float sat_row = 1.0f - (1.0f - sat_band) * burstF;
269 if (burstF > 0.0f && sat_row != 1.0f) {
270 float *urow = &U[y * Uw];
271 float *vrow = &V[y * Uw];
272 for (
int xh = 0; xh < Uw; ++xh) {
278 float rowBias = row_density(SEED, frame_number, y_ref);
279 float p = baseP * (0.25f + 1.5f * rowBias);
280 p *= (1.0f + 1.5f * bandF + 2.0f * burstF);
282 float hum = 0.008f * k_noise *
283 std::sin(2 * PI * (y_ref * (6.0f / reference_h) + 0.08f * t));
284 uint32_t s0 = SEED ^ 0x9e37u * kf ^ 0x85ebu * y_ref;
285 uint32_t s1 = SEED ^ 0x9e37u * (kf + 1) ^ 0x85ebu * y_ref ^ 0x1234567u;
286 auto step = [](uint32_t &s) {
292 float lift = Gfixed * bandF + Gburst * burstF;
293 float rowSigma = sigmaY * (1 + (Nfixed - 1) * bandF +
294 (Nburst - 1) * burstF);
295 float k = 0.15f + 0.35f * hash01(SEED, uint32_t(frame_number), y_ref, 777);
296 float sL = 0.0f, sR = 0.0f;
297 for (
int x = 0; x < w; ++x) {
298 const int x_ref =
static_cast<int>(std::round(
static_cast<float>(x) * reference_scale_x));
299 if (hash01(SEED, uint32_t(frame_number), y_ref, x_ref) < p)
301 if (hash01(SEED, uint32_t(frame_number), y_ref, reference_w - 1 - x_ref) < p * 0.7f)
303 float n = ((step(s0) & 0xFFFFFF) / 16777215.0f) * (1 - a) +
304 ((step(s1) & 0xFFFFFF) / 16777215.0f) * a;
306 float mt = std::clamp((Y[idx] - 0.2f) / (0.8f - 0.2f), 0.0f, 1.0f);
307 float val = Y[idx] + lift + rowSigma * (2 * n - 1) *
308 (0.6f + 0.4f * mt) + hum;
309 float streak = amp * (sL + sR);
310 float newY = val + streak * (k + (1.0f - val));
311 Y[idx] = std::clamp(newY, 0.0f, 1.0f);
317 float A = lerp(0.0f, 3.0f, k_track) * inverse_scale_x;
318 float f = lerp(0.25f, 1.2f, k_track);
319 float Hsk = lerp(0.0f, 0.10f * h, k_track);
320 float S = lerp(0.0f, 5.0f, k_track) * inverse_scale_x;
321 float phase = 2 * PI * (f * t) + 0.7f * (SEED * 0.001f);
322 for (
int y = 0; y < h; ++y) {
323 const float y_ref =
static_cast<float>(y) * reference_scale_y;
324 float base = A * std::sin(2 * PI * 0.0035f * y_ref + phase);
325 float skew = (y >= h - Hsk)
326 ? S * ((y - (h - Hsk)) / std::max(1.0f, Hsk))
331 auto remap_line = [&](
const float *src,
float *dst,
int width,
float scale) {
333 #pragma omp parallel for
335 for (
int y = 0; y < h; ++y) {
336 float off = dx[y] * scale;
337 const float *s = src + y * width;
338 float *d = dst + y * width;
339 int start = std::max(0,
int(std::ceil(-off)));
340 int end = std::min(width,
int(std::floor(width - off)));
341 float xs =
start + off;
346 d[x] = s[x0] * (1 - t) + s[x1] * t;
351 for (
int x = 0; x <
start; ++x)
353 for (
int x =
end; x < width; ++x)
358 remap_line(Y.data(), tmpY.data(), w, 1.0f);
360 remap_line(U.data(), tmpU.data(), Uw, 0.5f);
362 remap_line(V.data(), tmpV.data(), Uw, 0.5f);
366 #pragma omp parallel for
368 for (
int y = 0; y < h; ++y) {
369 float *yrow = &Y[y * w];
370 float *urow = &U[y * Uw];
371 float *vrow = &V[y * Uw];
372 uint32_t *row = base + y * stride;
373 for (
int x = 0; x < w; ++x) {
376 int x1 = std::min(x0 + 1, Uw - 1);
378 float u = (urow[x0] * (1 - t) + urow[x1] * t) * sat;
379 float v = (vrow[x0] * (1 - t) + vrow[x1] * t) * sat;
381 float r = yv + 1.13983f * v;
382 float g = yv - 0.39465f * u - 0.58060f * v;
383 float b = yv + 2.03211f * u;
384 int R = int(std::clamp(r, 0.0f, 1.0f) * 255.0f);
385 int G = int(std::clamp(g, 0.0f, 1.0f) * 255.0f);
386 int B = int(std::clamp(b, 0.0f, 1.0f) * 255.0f);
387 uint32_t A = row[x] & 0xFF000000u;
388 row[x] = A | (R << 16) | (G << 8) | B;
415 }
catch (
const std::exception &) {
416 throw InvalidJSON(
"JSON is invalid (missing keys or invalid data types)");
422 if (!root[
"tracking"].isNull())
424 if (!root[
"bleed"].isNull())
426 if (!root[
"softness"].isNull())
428 if (!root[
"noise"].isNull())
430 if (!root[
"stripe"].isNull())
432 if (!root[
"static_bands"].isNull())
434 if (!root[
"seed_offset"].isNull())
442 "", &
tracking, 0, 1,
false, requested_frame);
445 &
bleed, 0, 1,
false, requested_frame);
448 "", &
softness, 0, 1,
false, requested_frame);
451 &
noise, 0, 1,
false, requested_frame);
454 "Bottom tracking stripe brightness and noise.",
455 &
stripe, 0, 1,
false, requested_frame);
456 root[
"static_bands"] =
459 "Short bright static bands and extra dropouts.",
461 root[
"seed_offset"] =
463 false, requested_frame);
464 return root.toStyledString();