OpenShot Library | libopenshot 1.1.1
FilmGrain.cpp
Go to the documentation of this file.
1
9// Copyright (c) 2008-2026 OpenShot Studios, LLC
10//
11// SPDX-License-Identifier: LGPL-3.0-or-later
12
13#include "FilmGrain.h"
14#include "Clip.h"
15#include "Exceptions.h"
16#include "Timeline.h"
17
18#include <algorithm>
19#include <array>
20#include <cmath>
21#include <cstdint>
22
23using namespace openshot;
24
25namespace {
26constexpr float kInv255 = 1.0f / 255.0f;
27constexpr float kInvU32Max = 2.0f / 4294967295.0f; // multiply instead of divide in hash_signed
28
29// Salt constants for each grain channel and noise layer
30constexpr uint32_t kSaltLuma = 0x1000193u;
31constexpr uint32_t kSaltRed = 0x8da6b343u;
32constexpr uint32_t kSaltGreen = 0xd8163841u;
33constexpr uint32_t kSaltBlue = 0xcb1ab31fu;
34constexpr uint32_t kSoftXor = 0x51633e2du;
35constexpr uint32_t kClumpXor = 0xa511e9b3u;
36
37static float clamp01(float value) {
38 return std::max(0.0f, std::min(1.0f, value));
39}
40
41static int clampByte(float value) {
42 if (value <= 0.0f)
43 return 0;
44 if (value >= 255.0f)
45 return 255;
46 return static_cast<int>(value + 0.5f);
47}
48
49static float lerp(float a, float b, float t) {
50 return a + ((b - a) * t);
51}
52
53static uint32_t mix32(uint32_t value) {
54 value ^= value >> 16;
55 value *= 0x7feb352du;
56 value ^= value >> 15;
57 value *= 0x846ca68bu;
58 value ^= value >> 16;
59 return value;
60}
61
62static uint32_t hash_string(const std::string& value) {
63 uint32_t h = 2166136261u;
64 for (unsigned char c : value) {
65 h ^= c;
66 h *= 16777619u;
67 }
68 return h;
69}
70
71static uint32_t hash_coords(uint32_t seed, int x, int y, int t, uint32_t salt) {
72 uint32_t h = seed ^ salt;
73 h ^= static_cast<uint32_t>(x) * 0x9e3779b9u;
74 h ^= static_cast<uint32_t>(y) * 0x85ebca6bu;
75 h ^= static_cast<uint32_t>(t) * 0xc2b2ae35u;
76 return mix32(h);
77}
78
79static float hash_signed(uint32_t seed, int x, int y, int t, uint32_t salt) {
80 return hash_coords(seed, x, y, t, salt) * kInvU32Max - 1.0f;
81}
82
83static float tonal_weight(float luma, float shadow_strength, float midtone_strength, float highlight_strength) {
84 const float shadow = (1.0f - luma) * (1.0f - luma);
85 const float highlight = luma * luma;
86 const float centered = std::abs(luma - 0.5f) * 2.0f;
87 const float midtone = clamp01(1.0f - centered * centered);
88 return (shadow * shadow_strength) + (midtone * midtone_strength) + (highlight * highlight_strength);
89}
90}
91
92FilmGrain::FilmGrain()
93 : amount(0.25),
94 size(0.20),
95 softness(0.25),
96 clump(0.20),
97 shadows(0.80),
98 midtones(1.00),
99 highlights(0.55),
100 color_amount(0.20),
101 color_variation(0.35),
102 evolution(0.65),
103 coherence(0.55),
104 seed(1)
105{
106 init_effect_details();
107}
108
109void FilmGrain::init_effect_details() {
111 info.class_name = "FilmGrain";
112 info.name = "Film Grain";
113 info.description = "Film-inspired grain texture with tonal, color, and temporal controls.";
114 info.has_audio = false;
115 info.has_video = true;
116}
117
118std::shared_ptr<openshot::Frame> FilmGrain::GetFrame(std::shared_ptr<openshot::Frame> frame, int64_t frame_number) {
119 std::shared_ptr<QImage> frame_image = frame->GetImage();
120 if (!frame_image)
121 return frame;
122
123 const float amount_value = clamp01(static_cast<float>(amount.GetValue(frame_number)));
124 if (amount_value <= 0.00001f)
125 return frame;
126
127 const float size_value = clamp01(static_cast<float>(size.GetValue(frame_number)));
128 const float softness_value = clamp01(static_cast<float>(softness.GetValue(frame_number)));
129 const float clump_value = clamp01(static_cast<float>(clump.GetValue(frame_number)));
130 const float shadows_value = clamp01(static_cast<float>(shadows.GetValue(frame_number)));
131 const float midtones_value = clamp01(static_cast<float>(midtones.GetValue(frame_number)));
132 const float highlights_value = clamp01(static_cast<float>(highlights.GetValue(frame_number)));
133 const float color_amount_value = clamp01(static_cast<float>(color_amount.GetValue(frame_number)));
134 const float color_variation_value = clamp01(static_cast<float>(color_variation.GetValue(frame_number)));
135 const float evolution_value = clamp01(static_cast<float>(evolution.GetValue(frame_number)));
136 const float coherence_value = clamp01(static_cast<float>(coherence.GetValue(frame_number)));
137
138 const float cell_size = lerp(1.0f, 9.0f, size_value);
139 const float fine_frequency = 1.0f / cell_size;
140 const float soft_frequency = fine_frequency * lerp(0.45f, 0.12f, softness_value);
141 const float clump_frequency = fine_frequency * lerp(0.35f, 0.08f, clump_value);
142 const float temporal_rate = lerp(0.0f, 1.0f, evolution_value) * lerp(1.0f, 8.0f, 1.0f - coherence_value);
143 const float temporal_position = static_cast<float>(frame_number) * temporal_rate;
144 const int time0 = static_cast<int>(std::floor(temporal_position));
145 const int time1 = time0 + 1;
146 const float temporal_mix = temporal_rate <= 0.00001f ? 0.0f : clamp01(temporal_position - static_cast<float>(time0));
147 const float smooth_temporal_mix = temporal_mix * temporal_mix * (3.0f - 2.0f * temporal_mix);
148 const uint32_t base_seed = static_cast<uint32_t>(seed) ^ mix32(hash_string(Id()));
149 const float intensity_scale = amount_value * 0.18f;
150 const bool use_temporal_blend = temporal_rate > 0.00001f && smooth_temporal_mix > 0.00001f;
151 const bool use_softness = softness_value > 0.00001f;
152 const bool use_clump = clump_value > 0.00001f;
153 const bool use_color = color_amount_value > 0.00001f;
154 const bool use_color_variation = use_color && color_variation_value > 0.00001f;
155
156 // How many temporal samples and how many channel salts to compute
157 const int num_times = use_temporal_blend ? 2 : 1;
158 const int num_salts = use_color_variation ? 4 : 1;
159 const int time_buckets[2] = { time0, time1 };
160 const uint32_t base_salts[4] = { kSaltLuma, kSaltRed, kSaltGreen, kSaltBlue };
161
162 static const std::array<float, 256> inv_alpha = [] {
163 std::array<float, 256> lut{};
164 lut[0] = 0.0f;
165 for (int i = 1; i < 256; ++i)
166 lut[i] = 255.0f / static_cast<float>(i);
167 return lut;
168 }();
169
170 unsigned char* pixels = reinterpret_cast<unsigned char*>(frame_image->bits());
171 const int width = frame_image->width();
172 const int height = frame_image->height();
173 const int stride = frame_image->bytesPerLine();
174 const int pixel_count = width * height;
175 int reference_width = width;
176 int reference_height = height;
177
178 Clip* clip = static_cast<Clip*>(ParentClip());
179 Timeline* timeline = nullptr;
180 if (clip && clip->ParentTimeline())
181 timeline = static_cast<Timeline*>(clip->ParentTimeline());
182 else if (ParentTimeline())
183 timeline = static_cast<Timeline*>(ParentTimeline());
184 if (timeline && timeline->info.width > 0 && timeline->info.height > 0) {
185 reference_width = timeline->info.width;
186 reference_height = timeline->info.height;
187 }
188 const float reference_scale_x = width > 0 ? static_cast<float>(reference_width) / static_cast<float>(width) : 1.0f;
189 const float reference_scale_y = height > 0 ? static_cast<float>(reference_height) / static_cast<float>(height) : 1.0f;
190
191 #pragma omp parallel for if(pixel_count >= 16384) schedule(static)
192 for (int y = 0; y < height; ++y) {
193 unsigned char* row = pixels + (y * stride);
194
195 // Pre-compute all y-dependent values once per row (not per pixel).
196 // reference_x/y coords are always non-negative, so (int) cast is safe instead of floor.
197 const float reference_y = static_cast<float>(y) * reference_scale_y;
198 const int fine_iy = static_cast<int>(reference_y * fine_frequency);
199
200 // Soft noise: y-fractional components are constant across the row
201 int soft_iy0 = 0;
202 float soft_sy_fac = 0.0f; // smoothstep of the y fractional part
203 if (use_softness) {
204 const float sry = reference_y * soft_frequency;
205 soft_iy0 = static_cast<int>(sry);
206 const float t = sry - static_cast<float>(soft_iy0);
207 soft_sy_fac = t * t * (3.0f - 2.0f * t);
208 }
209
210 // Clump noise: same idea
211 int clump_iy0 = 0;
212 float clump_sy_fac = 0.0f;
213 if (use_clump) {
214 const float cry = reference_y * clump_frequency;
215 clump_iy0 = static_cast<int>(cry);
216 const float t = cry - static_cast<float>(clump_iy0);
217 clump_sy_fac = t * t * (3.0f - 2.0f * t);
218 }
219
220 // Per-row x-cell caches. Initialized to -1 so the first pixel always
221 // triggers a fill. All computed cell indices are >= 0, so -1 is a safe sentinel.
222 int cached_fine_ix = -1;
223 int cached_soft_ix0 = -1;
224 int cached_clump_ix0 = -1;
225
226 // fine_cache[time_idx][salt_idx]: direct hash value per fine-grid cell
227 float fine_cache[2][4] = {};
228
229 // soft_col0/col1[time_idx][salt_idx]: value_noise y-axis columns pre-lerped.
230 // Per pixel we only need one lerp across x instead of 4 hash calls + 3 lerps.
231 float soft_col0[2][4] = {}, soft_col1[2][4] = {};
232
233 // clump_col0/col1[salt_idx]: clump only uses time0
234 float clump_col0[4] = {}, clump_col1[4] = {};
235
236 for (int x = 0; x < width; ++x) {
237 const int idx = x * 4;
238 const int A = row[idx + 3];
239 if (A <= 0)
240 continue;
241
242 float R, G, B;
243 if (A == 255) {
244 R = row[idx + 0] * kInv255;
245 G = row[idx + 1] * kInv255;
246 B = row[idx + 2] * kInv255;
247 } else {
248 const float inv_a = inv_alpha[A];
249 R = (row[idx + 0] * inv_a) * kInv255;
250 G = (row[idx + 1] * inv_a) * kInv255;
251 B = (row[idx + 2] * inv_a) * kInv255;
252 }
253
254 const float luma = (0.299f * R) + (0.587f * G) + (0.114f * B);
255 const float tone = tonal_weight(luma, shadows_value, midtones_value, highlights_value);
256 if (tone <= 0.00001f)
257 continue;
258
259 const float reference_x = static_cast<float>(x) * reference_scale_x;
260
261 // Fine grain: one hash per (cell, time, salt). Update when the x-cell changes.
262 const int fine_ix = static_cast<int>(reference_x * fine_frequency);
263 if (fine_ix != cached_fine_ix) {
264 cached_fine_ix = fine_ix;
265 for (int t = 0; t < num_times; ++t)
266 for (int s = 0; s < num_salts; ++s)
267 fine_cache[t][s] = hash_signed(base_seed, fine_ix, fine_iy, time_buckets[t], base_salts[s]);
268 }
269
270 // Soft noise: bilinear value_noise, but y-axis columns are pre-lerped into
271 // soft_col0/col1. Only 4 hashes needed when the x-cell changes (~every 7 px),
272 // then 1 lerp per pixel instead of 4 hashes + 3 lerps every pixel.
273 float soft_sx = 0.0f;
274 if (use_softness) {
275 const float srx = reference_x * soft_frequency;
276 const int soft_ix0 = static_cast<int>(srx);
277 if (soft_ix0 != cached_soft_ix0) {
278 cached_soft_ix0 = soft_ix0;
279 const int soft_ix1 = soft_ix0 + 1;
280 const int soft_iy1 = soft_iy0 + 1;
281 for (int t = 0; t < num_times; ++t) {
282 for (int s = 0; s < num_salts; ++s) {
283 const uint32_t salt = base_salts[s] ^ kSoftXor;
284 const float n00 = hash_signed(base_seed, soft_ix0, soft_iy0, time_buckets[t], salt);
285 const float n10 = hash_signed(base_seed, soft_ix1, soft_iy0, time_buckets[t], salt);
286 const float n01 = hash_signed(base_seed, soft_ix0, soft_iy1, time_buckets[t], salt);
287 const float n11 = hash_signed(base_seed, soft_ix1, soft_iy1, time_buckets[t], salt);
288 // y-interpolate left and right columns separately; x-interpolation is per-pixel
289 soft_col0[t][s] = n00 + (n01 - n00) * soft_sy_fac;
290 soft_col1[t][s] = n10 + (n11 - n10) * soft_sy_fac;
291 }
292 }
293 }
294 const float tx = srx - static_cast<float>(cached_soft_ix0);
295 soft_sx = tx * tx * (3.0f - 2.0f * tx);
296 }
297
298 // Clump noise: same column-caching strategy, only time0
299 float clump_sx = 0.0f;
300 if (use_clump) {
301 const float crx = reference_x * clump_frequency;
302 const int clump_ix0 = static_cast<int>(crx);
303 if (clump_ix0 != cached_clump_ix0) {
304 cached_clump_ix0 = clump_ix0;
305 const int clump_ix1 = clump_ix0 + 1;
306 const int clump_iy1 = clump_iy0 + 1;
307 for (int s = 0; s < num_salts; ++s) {
308 const uint32_t salt = base_salts[s] ^ kClumpXor;
309 const float n00 = hash_signed(base_seed, clump_ix0, clump_iy0, time0, salt);
310 const float n10 = hash_signed(base_seed, clump_ix1, clump_iy0, time0, salt);
311 const float n01 = hash_signed(base_seed, clump_ix0, clump_iy1, time0, salt);
312 const float n11 = hash_signed(base_seed, clump_ix1, clump_iy1, time0, salt);
313 clump_col0[s] = n00 + (n01 - n00) * clump_sy_fac;
314 clump_col1[s] = n10 + (n11 - n10) * clump_sy_fac;
315 }
316 }
317 const float tx = crx - static_cast<float>(cached_clump_ix0);
318 clump_sx = tx * tx * (3.0f - 2.0f * tx);
319 }
320
321 float grains[4];
322 for (int s = 0; s < num_salts; ++s) {
323 float g0 = fine_cache[0][s];
324 if (use_softness) {
325 const float soft0 = soft_col0[0][s] + (soft_col1[0][s] - soft_col0[0][s]) * soft_sx;
326 g0 += (soft0 - g0) * softness_value;
327 }
328 float grain;
329 if (use_temporal_blend) {
330 float g1 = fine_cache[1][s];
331 if (use_softness) {
332 const float soft1 = soft_col0[1][s] + (soft_col1[1][s] - soft_col0[1][s]) * soft_sx;
333 g1 += (soft1 - g1) * softness_value;
334 }
335 grain = g0 + (g1 - g0) * smooth_temporal_mix;
336 } else {
337 grain = g0;
338 }
339 if (use_clump) {
340 const float cluster = clump_col0[s] + (clump_col1[s] - clump_col0[s]) * clump_sx;
341 grain *= lerp(1.0f, 0.45f + (std::abs(cluster) * 1.35f), clump_value);
342 }
343 grains[s] = grain;
344 }
345
346 const float strength = intensity_scale * tone;
347 const float luma_grain = grains[0];
348 if (use_color_variation) {
349 const float red_grain = luma_grain + (grains[1] - luma_grain) * color_variation_value;
350 const float green_grain = luma_grain + (grains[2] - luma_grain) * color_variation_value;
351 const float blue_grain = luma_grain + (grains[3] - luma_grain) * color_variation_value;
352 R = clamp01(R + (luma_grain + (red_grain - luma_grain) * color_amount_value) * strength);
353 G = clamp01(G + (luma_grain + (green_grain - luma_grain) * color_amount_value) * strength);
354 B = clamp01(B + (luma_grain + (blue_grain - luma_grain) * color_amount_value) * strength);
355 } else {
356 const float delta = luma_grain * strength;
357 R = clamp01(R + delta);
358 G = clamp01(G + delta);
359 B = clamp01(B + delta);
360 }
361
362 if (A == 255) {
363 row[idx + 0] = static_cast<unsigned char>(clampByte(R * 255.0f));
364 row[idx + 1] = static_cast<unsigned char>(clampByte(G * 255.0f));
365 row[idx + 2] = static_cast<unsigned char>(clampByte(B * 255.0f));
366 } else {
367 const float alpha_percent = static_cast<float>(A) * kInv255;
368 row[idx + 0] = static_cast<unsigned char>(clampByte(R * 255.0f * alpha_percent));
369 row[idx + 1] = static_cast<unsigned char>(clampByte(G * 255.0f * alpha_percent));
370 row[idx + 2] = static_cast<unsigned char>(clampByte(B * 255.0f * alpha_percent));
371 }
372 }
373 }
374
375 return frame;
376}
377
378std::string FilmGrain::Json() const {
379 return JsonValue().toStyledString();
380}
381
382Json::Value FilmGrain::JsonValue() const {
383 Json::Value root = EffectBase::JsonValue();
384 root["type"] = info.class_name;
385 root["amount"] = amount.JsonValue();
386 root["size"] = size.JsonValue();
387 root["softness"] = softness.JsonValue();
388 root["clump"] = clump.JsonValue();
389 root["shadows"] = shadows.JsonValue();
390 root["midtones"] = midtones.JsonValue();
391 root["highlights"] = highlights.JsonValue();
392 root["color_amount"] = color_amount.JsonValue();
393 root["color_variation"] = color_variation.JsonValue();
394 root["evolution"] = evolution.JsonValue();
395 root["coherence"] = coherence.JsonValue();
396 root["seed"] = seed;
397 return root;
398}
399
400void FilmGrain::SetJson(const std::string value) {
401 try {
402 const Json::Value root = openshot::stringToJson(value);
403 SetJsonValue(root);
404 } catch (const std::exception&) {
405 throw InvalidJSON("Invalid JSON for FilmGrain effect");
406 }
407}
408
409void FilmGrain::SetJsonValue(const Json::Value root) {
411 if (!root["amount"].isNull()) amount.SetJsonValue(root["amount"]);
412 if (!root["size"].isNull()) size.SetJsonValue(root["size"]);
413 if (!root["softness"].isNull()) softness.SetJsonValue(root["softness"]);
414 if (!root["clump"].isNull()) clump.SetJsonValue(root["clump"]);
415 if (!root["shadows"].isNull()) shadows.SetJsonValue(root["shadows"]);
416 if (!root["midtones"].isNull()) midtones.SetJsonValue(root["midtones"]);
417 if (!root["highlights"].isNull()) highlights.SetJsonValue(root["highlights"]);
418 if (!root["color_amount"].isNull()) color_amount.SetJsonValue(root["color_amount"]);
419 if (!root["color_variation"].isNull()) color_variation.SetJsonValue(root["color_variation"]);
420 if (!root["evolution"].isNull()) evolution.SetJsonValue(root["evolution"]);
421 if (!root["coherence"].isNull()) coherence.SetJsonValue(root["coherence"]);
422 if (!root["seed"].isNull()) seed = root["seed"].asInt();
423}
424
425std::string FilmGrain::PropertiesJSON(int64_t requested_frame) const {
426 Json::Value root = BasePropertiesJSON(requested_frame);
427 root["amount"] = add_property_json("Amount", amount.GetValue(requested_frame), "float", "Overall grain intensity.", &amount, 0.0, 1.0, false, requested_frame);
428 root["size"] = add_property_json("Size", size.GetValue(requested_frame), "float", "Fine to coarse grain scale.", &size, 0.0, 1.0, false, requested_frame);
429 root["softness"] = add_property_json("Softness", softness.GetValue(requested_frame), "float", "Hard crisp grain to softer organic grain.", &softness, 0.0, 1.0, false, requested_frame);
430 root["clump"] = add_property_json("Clump", clump.GetValue(requested_frame), "float", "Even grain to clustered irregular grain.", &clump, 0.0, 1.0, false, requested_frame);
431 root["shadows"] = add_property_json("Shadows", shadows.GetValue(requested_frame), "float", "Grain strength in dark regions.", &shadows, 0.0, 1.0, false, requested_frame);
432 root["midtones"] = add_property_json("Midtones", midtones.GetValue(requested_frame), "float", "Grain strength in middle tonal regions.", &midtones, 0.0, 1.0, false, requested_frame);
433 root["highlights"] = add_property_json("Highlights", highlights.GetValue(requested_frame), "float", "Grain strength in bright regions.", &highlights, 0.0, 1.0, false, requested_frame);
434 root["color_amount"] = add_property_json("Color Amount", color_amount.GetValue(requested_frame), "float", "How much grain affects chroma instead of mostly luma.", &color_amount, 0.0, 1.0, false, requested_frame);
435 root["color_variation"] = add_property_json("Color Variation", color_variation.GetValue(requested_frame), "float", "Correlated to independently varied color grain.", &color_variation, 0.0, 1.0, false, requested_frame);
436 root["evolution"] = add_property_json("Evolution", evolution.GetValue(requested_frame), "float", "How much grain renews over time.", &evolution, 0.0, 1.0, false, requested_frame);
437 root["coherence"] = add_property_json("Coherence", coherence.GetValue(requested_frame), "float", "How stable and smooth grain remains between frames.", &coherence, 0.0, 1.0, false, requested_frame);
438 root["seed"] = add_property_json("Seed", seed, "int", "Deterministic grain variation.", NULL, 0, 1000000, false, requested_frame);
439 return root.toStyledString();
440}
Header file for Clip class.
Header file for all Exception classes.
Header file for FilmGrain effect.
Header file for Timeline class.
std::string Id() const
Get the Id of this clip object.
Definition: ClipBase.h:88
openshot::TimelineBase * timeline
Pointer to the parent timeline instance (if any)
Definition: ClipBase.h:40
virtual openshot::TimelineBase * ParentTimeline()
Get the associated Timeline pointer (if any)
Definition: ClipBase.h:94
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:100
This class represents a clip (used to arrange readers on the timeline)
Definition: Clip.h:89
virtual Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: EffectBase.cpp:106
openshot::ClipBase * ParentClip()
Parent clip object of this effect (which can be unparented and NULL)
Definition: EffectBase.cpp:686
Json::Value BasePropertiesJSON(int64_t requested_frame) const
Generate JSON object of base properties (recommended to be used by all effects)
Definition: EffectBase.cpp:261
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: EffectBase.cpp:150
openshot::ClipBase * clip
Pointer to the parent clip instance (if any)
Definition: EffectBase.h:77
EffectInfoStruct info
Information about the current effect.
Definition: EffectBase.h:114
std::string Json() const override
Generate JSON string of this object.
Definition: FilmGrain.cpp:378
std::shared_ptr< openshot::Frame > GetFrame(int64_t frame_number) override
This method is required for all derived classes of ClipBase, and returns a new openshot::Frame object...
Definition: FilmGrain.h:55
std::string PropertiesJSON(int64_t requested_frame) const override
Definition: FilmGrain.cpp:425
Keyframe clump
Even vs clustered irregular structure.
Definition: FilmGrain.h:43
Keyframe shadows
Grain strength in dark regions.
Definition: FilmGrain.h:44
Keyframe softness
Crisp vs soft organic grain.
Definition: FilmGrain.h:42
int seed
Deterministic grain identity.
Definition: FilmGrain.h:51
Keyframe highlights
Grain strength in bright regions.
Definition: FilmGrain.h:46
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition: FilmGrain.cpp:409
Json::Value JsonValue() const override
Generate Json::Value for this object.
Definition: FilmGrain.cpp:382
Keyframe color_amount
Chroma contribution amount.
Definition: FilmGrain.h:47
Keyframe amount
Overall grain intensity.
Definition: FilmGrain.h:40
Keyframe coherence
Smoothness/stability between frames.
Definition: FilmGrain.h:50
Keyframe color_variation
RGB channel independence.
Definition: FilmGrain.h:48
Keyframe evolution
How much the grain renews over time.
Definition: FilmGrain.h:49
void SetJson(const std::string value) override
Load JSON string into this object.
Definition: FilmGrain.cpp:400
Keyframe size
Fine to coarse grain scale.
Definition: FilmGrain.h:41
Keyframe midtones
Grain strength in middle tonal regions.
Definition: FilmGrain.h:45
Exception for invalid JSON.
Definition: Exceptions.h:224
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: KeyFrame.cpp:372
double GetValue(int64_t index) const
Get the value at a specific index.
Definition: KeyFrame.cpp:258
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: KeyFrame.cpp:339
This class represents a timeline.
Definition: Timeline.h:154
This namespace is the default namespace for all code in the openshot library.
Definition: AnimatedCurve.h:24
const Json::Value stringToJson(const std::string value)
Definition: Json.cpp:16
bool has_video
Determines if this effect manipulates the image of a frame.
Definition: EffectBase.h:43
bool has_audio
Determines if this effect manipulates the audio of a frame.
Definition: EffectBase.h:44
std::string class_name
The class name of the effect.
Definition: EffectBase.h:39
std::string name
The name of the effect.
Definition: EffectBase.h:40
std::string description
The description of this effect and what it does.
Definition: EffectBase.h:41