OpenShot Library | libopenshot  0.5.0
AnalogTape.cpp
Go to the documentation of this file.
1 
9 // Copyright (c) 2008-2025 OpenShot Studios, LLC
10 //
11 // SPDX-License-Identifier: LGPL-3.0-or-later
12 
13 #include "AnalogTape.h"
14 #include "Clip.h"
15 #include "Exceptions.h"
16 #include "ReaderBase.h"
17 #include "Timeline.h"
18 
19 #include <algorithm>
20 #include <cmath>
21 
22 using namespace openshot;
23 
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();
28 }
29 
31  Keyframe st, Keyframe sb, int seed)
32  : tracking(t), bleed(b), softness(s), noise(n), stripe(st),
33  staticBands(sb), seed_offset(seed) {
34  init_effect_details();
35 }
36 
37 void AnalogTape::init_effect_details() {
39  info.class_name = "AnalogTape";
40  info.name = "Analog Tape";
41  info.description = "Vintage home video wobble, bleed, and grain.";
42  info.has_video = true;
43  info.has_audio = false;
44 }
45 
46 static inline float lerp(float a, float b, float t) { return a + (b - a) * t; }
47 
48 std::shared_ptr<Frame> AnalogTape::GetFrame(std::shared_ptr<Frame> frame,
49  int64_t frame_number) {
50  std::shared_ptr<QImage> img = frame->GetImage();
51  int w = img->width();
52  int h = img->height();
53  int Uw = (w + 1) / 2;
54  int stride = img->bytesPerLine() / 4;
55  uint32_t *base = reinterpret_cast<uint32_t *>(img->bits());
56 
57  if (w != last_w || h != last_h) {
58  last_w = w;
59  last_h = h;
60  Y.resize(w * h);
61  U.resize(Uw * h);
62  V.resize(Uw * h);
63  tmpY.resize(w * h);
64  tmpU.resize(Uw * h);
65  tmpV.resize(Uw * h);
66  dx.resize(h);
67  }
68 
69 
70 #ifdef _OPENMP
71 #pragma omp parallel for
72 #endif
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) {
79  int x0 = x2 * 2;
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;
87  yrow[x0] = y0;
88 
89  float u, v;
90  if (x0 + 1 < w) {
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;
98  yrow[x0 + 1] = y1;
99  u = (u0 + u1) * 0.5f;
100  v = (v0 + v1) * 0.5f;
101  } else {
102  u = u0;
103  v = v0;
104  }
105  urow[x2] = u;
106  vrow[x2] = v;
107  }
108  }
109 
110  Fraction fps(1, 1);
111  Clip *clip = (Clip *)ParentClip();
112  Timeline *timeline = nullptr;
113  if (clip && clip->ParentTimeline())
114  timeline = (Timeline *)clip->ParentTimeline();
115  else if (ParentTimeline())
116  timeline = (Timeline *)ParentTimeline();
117  if (timeline)
118  fps = timeline->info.fps;
119  else if (clip && clip->Reader())
120  fps = clip->Reader()->info.fps;
121  double fps_d = fps.ToDouble();
122  double t = fps_d > 0 ? frame_number / fps_d : frame_number;
123 
124  const float k_track = tracking.GetValue(frame_number);
125  const float k_bleed = bleed.GetValue(frame_number);
126  const float k_soft = softness.GetValue(frame_number);
127  const float k_noise = noise.GetValue(frame_number);
128  const float k_stripe = stripe.GetValue(frame_number);
129  const float k_bands = staticBands.GetValue(frame_number);
130 
131  int r_y = std::round(lerp(0.0f, 2.0f, k_soft));
132  if (k_noise > 0.6f)
133  r_y = std::min(r_y, 1);
134  if (r_y > 0) {
135 #ifdef _OPENMP
136 #pragma omp parallel for
137 #endif
138  for (int y = 0; y < h; ++y)
139  box_blur_row(&Y[y * w], &tmpY[y * w], w, r_y);
140  Y.swap(tmpY);
141  }
142 
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;
147 #ifdef _OPENMP
148 #pragma omp parallel for
149 #endif
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));
157  int x0 = int(xs);
158  int x1 = std::min(x0 + 1, Uw - 1);
159  float t = xs - x0;
160  dstU[x] = srcU[x0] * (1 - t) + srcU[x1] * t;
161  dstV[x] = srcV[x0] * (1 - t) + srcV[x1] * t;
162  }
163  }
164  U.swap(tmpU);
165  V.swap(tmpV);
166 
167  if (r_c > 0) {
168 #ifdef _OPENMP
169 #pragma omp parallel for
170 #endif
171  for (int y = 0; y < h; ++y)
172  box_blur_row(&U[y * Uw], &tmpU[y * Uw], Uw, r_c);
173  U.swap(tmpU);
174 #ifdef _OPENMP
175 #pragma omp parallel for
176 #endif
177  for (int y = 0; y < h; ++y)
178  box_blur_row(&V[y * Uw], &tmpV[y * Uw], Uw, r_c);
179  V.swap(tmpV);
180  }
181 
182  uint32_t SEED = fnv1a_32(Id()) ^ (uint32_t)seed_offset;
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;
188 
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;
193 
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;
197 
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;
204 
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);
214 
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;
218  float center =
219  hash01(SCHED_SEED, uint32_t(win_idx), 12 + kseed, 0) * (h - Hburst) +
220  0.5f * 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});
225  };
226 
227  float r = hash01(SCHED_SEED, uint32_t(win_idx), 9, 0);
228  if (r < prob_ge1)
229  spawn_band(0);
230  if (r < prob_ge2)
231  spawn_band(1);
232  }
233 
234  float ft = 2.0f;
235  int kf = int(std::floor(t * ft));
236  float a = float(t * ft - kf);
237 
238 #ifdef _OPENMP
239 #pragma omp parallel for
240 #endif
241  for (int y = 0; y < h; ++y) {
242  float bandF = 0.0f;
243  if (Hfixed > 0.0f && y >= h - Hfixed)
244  bandF = (y - (h - Hfixed)) / std::max(1.0f, Hfixed);
245  float burstF = 0.0f;
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)
252  ? life
253  : (life < dur_frames - 1 ? 1.0f
254  : std::max(0.0f, dur_frames - life));
255  burstF = std::max(burstF, profile * env);
256  }
257 
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) {
263  urow[xh] *= sat_row;
264  vrow[xh] *= sat_row;
265  }
266  }
267 
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);
271 
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) {
277  s ^= s << 13;
278  s ^= s >> 17;
279  s ^= s << 5;
280  return s;
281  };
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)
289  sL = 1.0f;
290  if (hash01(SEED, uint32_t(frame_number), y, w - 1 - x) < p * 0.7f)
291  sR = 1.0f;
292  float n = ((step(s0) & 0xFFFFFF) / 16777215.0f) * (1 - a) +
293  ((step(s1) & 0xFFFFFF) / 16777215.0f) * a;
294  int idx = y * w + x;
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);
301  sL *= decay;
302  sR *= decay;
303  }
304  }
305 
306  float A = lerp(0.0f, 3.0f, k_track); // pixels
307  float f = lerp(0.25f, 1.2f, k_track); // Hz
308  float Hsk = lerp(0.0f, 0.10f * h, k_track); // pixels
309  float S = lerp(0.0f, 5.0f, k_track); // pixels
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))
315  : 0.0f;
316  dx[y] = base + skew;
317  }
318 
319  auto remap_line = [&](const float *src, float *dst, int width, float scale) {
320 #ifdef _OPENMP
321 #pragma omp parallel for
322 #endif
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;
330  int x0 = int(xs);
331  float t = xs - x0;
332  for (int x = start; x < end; ++x) {
333  int x1 = x0 + 1;
334  d[x] = s[x0] * (1 - t) + s[x1] * t;
335  xs += 1.0f;
336  x0 = int(xs);
337  t = xs - x0;
338  }
339  for (int x = 0; x < start; ++x)
340  d[x] = s[0];
341  for (int x = end; x < width; ++x)
342  d[x] = s[width - 1];
343  }
344  };
345 
346  remap_line(Y.data(), tmpY.data(), w, 1.0f);
347  Y.swap(tmpY);
348  remap_line(U.data(), tmpU.data(), Uw, 0.5f);
349  U.swap(tmpU);
350  remap_line(V.data(), tmpV.data(), Uw, 0.5f);
351  V.swap(tmpV);
352 
353 #ifdef _OPENMP
354 #pragma omp parallel for
355 #endif
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) {
362  float xs = x * 0.5f;
363  int x0 = int(xs);
364  int x1 = std::min(x0 + 1, Uw - 1);
365  float t = xs - x0;
366  float u = (urow[x0] * (1 - t) + urow[x1] * t) * sat;
367  float v = (vrow[x0] * (1 - t) + vrow[x1] * t) * sat;
368  float yv = yrow[x];
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;
377  }
378  }
379 
380  return frame;
381 }
382 
383 // JSON
384 std::string AnalogTape::Json() const { return JsonValue().toStyledString(); }
385 
386 Json::Value AnalogTape::JsonValue() const {
387  Json::Value root = EffectBase::JsonValue();
388  root["type"] = info.class_name;
389  root["tracking"] = tracking.JsonValue();
390  root["bleed"] = bleed.JsonValue();
391  root["softness"] = softness.JsonValue();
392  root["noise"] = noise.JsonValue();
393  root["stripe"] = stripe.JsonValue();
394  root["static_bands"] = staticBands.JsonValue();
395  root["seed_offset"] = seed_offset;
396  return root;
397 }
398 
399 void AnalogTape::SetJson(const std::string value) {
400  try {
401  Json::Value root = openshot::stringToJson(value);
402  SetJsonValue(root);
403  } catch (const std::exception &) {
404  throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
405  }
406 }
407 
408 void AnalogTape::SetJsonValue(const Json::Value root) {
410  if (!root["tracking"].isNull())
411  tracking.SetJsonValue(root["tracking"]);
412  if (!root["bleed"].isNull())
413  bleed.SetJsonValue(root["bleed"]);
414  if (!root["softness"].isNull())
415  softness.SetJsonValue(root["softness"]);
416  if (!root["noise"].isNull())
417  noise.SetJsonValue(root["noise"]);
418  if (!root["stripe"].isNull())
419  stripe.SetJsonValue(root["stripe"]);
420  if (!root["static_bands"].isNull())
421  staticBands.SetJsonValue(root["static_bands"]);
422  if (!root["seed_offset"].isNull())
423  seed_offset = root["seed_offset"].asInt();
424 }
425 
426 std::string AnalogTape::PropertiesJSON(int64_t requested_frame) const {
427  Json::Value root = BasePropertiesJSON(requested_frame);
428  root["tracking"] =
429  add_property_json("Tracking", tracking.GetValue(requested_frame), "float",
430  "", &tracking, 0, 1, false, requested_frame);
431  root["bleed"] =
432  add_property_json("Bleed", bleed.GetValue(requested_frame), "float", "",
433  &bleed, 0, 1, false, requested_frame);
434  root["softness"] =
435  add_property_json("Softness", softness.GetValue(requested_frame), "float",
436  "", &softness, 0, 1, false, requested_frame);
437  root["noise"] =
438  add_property_json("Noise", noise.GetValue(requested_frame), "float", "",
439  &noise, 0, 1, false, requested_frame);
440  root["stripe"] =
441  add_property_json("Stripe", stripe.GetValue(requested_frame), "float",
442  "Bottom tracking stripe brightness and noise.",
443  &stripe, 0, 1, false, requested_frame);
444  root["static_bands"] =
445  add_property_json("Static Bands", staticBands.GetValue(requested_frame),
446  "float",
447  "Short bright static bands and extra dropouts.",
448  &staticBands, 0, 1, false, requested_frame);
449  root["seed_offset"] =
450  add_property_json("Seed Offset", seed_offset, "int", "", NULL, 0, 1000,
451  false, requested_frame);
452  return root.toStyledString();
453 }
openshot::ClipBase::add_property_json
Json::Value add_property_json(std::string name, float value, std::string type, std::string memo, const Keyframe *keyframe, float min_value, float max_value, bool readonly, int64_t requested_frame) const
Generate JSON for a property.
Definition: ClipBase.cpp:96
openshot::stringToJson
const Json::Value stringToJson(const std::string value)
Definition: Json.cpp:16
openshot::ClipBase::timeline
openshot::TimelineBase * timeline
Pointer to the parent timeline instance (if any)
Definition: ClipBase.h:40
openshot::EffectBase::info
EffectInfoStruct info
Information about the current effect.
Definition: EffectBase.h:69
openshot::AnalogTape::PropertiesJSON
std::string PropertiesJSON(int64_t requested_frame) const override
Definition: AnalogTape.cpp:426
openshot::AnalogTape::AnalogTape
AnalogTape()
Definition: AnalogTape.cpp:24
Clip.h
Header file for Clip class.
openshot
This namespace is the default namespace for all code in the openshot library.
Definition: Compressor.h:28
openshot::EffectBase::ParentClip
openshot::ClipBase * ParentClip()
Parent clip object of this effect (which can be unparented and NULL)
Definition: EffectBase.cpp:201
openshot::AnalogTape::seed_offset
int seed_offset
seed offset for deterministic randomness
Definition: AnalogTape.h:108
openshot::Clip
This class represents a clip (used to arrange readers on the timeline)
Definition: Clip.h:89
openshot::EffectBase::JsonValue
virtual Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: EffectBase.cpp:79
openshot::Fraction
This class represents a fraction.
Definition: Fraction.h:30
openshot::AnalogTape::GetFrame
std::shared_ptr< openshot::Frame > GetFrame(std::shared_ptr< openshot::Frame > frame, int64_t frame_number) override
This method is required for all derived classes of ClipBase, and returns a modified openshot::Frame o...
Timeline.h
Header file for Timeline class.
openshot::Keyframe::SetJsonValue
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: KeyFrame.cpp:372
openshot::Keyframe::JsonValue
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: KeyFrame.cpp:339
openshot::AnalogTape::noise
Keyframe noise
grain/dropouts amount
Definition: AnalogTape.h:105
openshot::EffectBase::BasePropertiesJSON
Json::Value BasePropertiesJSON(int64_t requested_frame) const
Generate JSON object of base properties (recommended to be used by all effects)
Definition: EffectBase.cpp:179
openshot::Keyframe
A Keyframe is a collection of Point instances, which is used to vary a number or property over time.
Definition: KeyFrame.h:53
openshot::AnalogTape::JsonValue
Json::Value JsonValue() const override
Generate Json::Value for this object.
Definition: AnalogTape.cpp:386
openshot::AnalogTape::bleed
Keyframe bleed
color bleed amount
Definition: AnalogTape.h:103
openshot::InvalidJSON
Exception for invalid JSON.
Definition: Exceptions.h:217
openshot::Timeline
This class represents a timeline.
Definition: Timeline.h:154
openshot::EffectBase::InitEffectInfo
void InitEffectInfo()
Definition: EffectBase.cpp:24
openshot::EffectInfoStruct::has_audio
bool has_audio
Determines if this effect manipulates the audio of a frame.
Definition: EffectBase.h:41
openshot::ClipBase::end
float end
The position in seconds to end playing (used to trim the ending of a clip)
Definition: ClipBase.h:38
openshot::ClipBase::start
float start
The position in seconds to start playing (used to trim the beginning of a clip)
Definition: ClipBase.h:37
openshot::AnalogTape::SetJsonValue
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition: AnalogTape.cpp:408
openshot::EffectInfoStruct::class_name
std::string class_name
The class name of the effect.
Definition: EffectBase.h:36
ReaderBase.h
Header file for ReaderBase class.
openshot::AnalogTape::tracking
Keyframe tracking
tracking wobble amount
Definition: AnalogTape.h:102
openshot::EffectInfoStruct::description
std::string description
The description of this effect and what it does.
Definition: EffectBase.h:38
openshot::EffectInfoStruct::has_video
bool has_video
Determines if this effect manipulates the image of a frame.
Definition: EffectBase.h:40
openshot::ClipBase::Id
void Id(std::string value)
Definition: ClipBase.h:94
AnalogTape.h
Header file for AnalogTape effect class.
openshot::AnalogTape::stripe
Keyframe stripe
bottom tracking stripe strength
Definition: AnalogTape.h:106
openshot::AnalogTape::SetJson
void SetJson(const std::string value) override
Load JSON string into this object.
Definition: AnalogTape.cpp:399
openshot::EffectInfoStruct::name
std::string name
The name of the effect.
Definition: EffectBase.h:37
openshot::AnalogTape::staticBands
Keyframe staticBands
burst static band strength
Definition: AnalogTape.h:107
openshot::AnalogTape::Json
std::string Json() const override
Generate JSON string of this object.
Definition: AnalogTape.cpp:384
openshot::AnalogTape::softness
Keyframe softness
luma blur radius
Definition: AnalogTape.h:104
Exceptions.h
Header file for all Exception classes.
openshot::EffectBase::SetJsonValue
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: EffectBase.cpp:115
openshot::Keyframe::GetValue
double GetValue(int64_t index) const
Get the value at a specific index.
Definition: KeyFrame.cpp:258
openshot::EffectBase::clip
openshot::ClipBase * clip
Pointer to the parent clip instance (if any)
Definition: EffectBase.h:59