26 constexpr
float kInv255 = 1.0f / 255.0f;
27 constexpr
float kInvU32Max = 2.0f / 4294967295.0f;
30 constexpr uint32_t kSaltLuma = 0x1000193u;
31 constexpr uint32_t kSaltRed = 0x8da6b343u;
32 constexpr uint32_t kSaltGreen = 0xd8163841u;
33 constexpr uint32_t kSaltBlue = 0xcb1ab31fu;
34 constexpr uint32_t kSoftXor = 0x51633e2du;
35 constexpr uint32_t kClumpXor = 0xa511e9b3u;
37 static float clamp01(
float value) {
38 return std::max(0.0f, std::min(1.0f, value));
41 static int clampByte(
float value) {
46 return static_cast<int>(value + 0.5f);
49 static float lerp(
float a,
float b,
float t) {
50 return a + ((b - a) * t);
53 static uint32_t mix32(uint32_t value) {
62 static uint32_t hash_string(
const std::string& value) {
63 uint32_t h = 2166136261u;
64 for (
unsigned char c : value) {
71 static 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;
79 static 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;
83 static 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);
101 color_variation(0.35),
106 init_effect_details();
109 void FilmGrain::init_effect_details() {
113 info.
description =
"Film-inspired grain texture with tonal, color, and temporal controls.";
118 std::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();
123 const float amount_value = clamp01(
static_cast<float>(
amount.
GetValue(frame_number)));
124 if (amount_value <= 0.00001f)
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)));
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)));
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;
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 };
162 static const std::array<float, 256> inv_alpha = [] {
163 std::array<float, 256> lut{};
165 for (
int i = 1; i < 256; ++i)
166 lut[i] = 255.0f /
static_cast<float>(i);
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;
182 else if (ParentTimeline())
185 reference_width =
timeline->info.width;
186 reference_height =
timeline->info.height;
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;
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);
197 const float reference_y =
static_cast<float>(y) * reference_scale_y;
198 const int fine_iy =
static_cast<int>(reference_y * fine_frequency);
202 float soft_sy_fac = 0.0f;
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);
212 float clump_sy_fac = 0.0f;
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);
222 int cached_fine_ix = -1;
223 int cached_soft_ix0 = -1;
224 int cached_clump_ix0 = -1;
227 float fine_cache[2][4] = {};
231 float soft_col0[2][4] = {}, soft_col1[2][4] = {};
234 float clump_col0[4] = {}, clump_col1[4] = {};
236 for (
int x = 0; x < width; ++x) {
237 const int idx = x * 4;
238 const int A = row[idx + 3];
244 R = row[idx + 0] * kInv255;
245 G = row[idx + 1] * kInv255;
246 B = row[idx + 2] * kInv255;
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;
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)
259 const float reference_x =
static_cast<float>(x) * reference_scale_x;
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]);
273 float soft_sx = 0.0f;
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);
289 soft_col0[t][s] = n00 + (n01 - n00) * soft_sy_fac;
290 soft_col1[t][s] = n10 + (n11 - n10) * soft_sy_fac;
294 const float tx = srx -
static_cast<float>(cached_soft_ix0);
295 soft_sx = tx * tx * (3.0f - 2.0f * tx);
299 float clump_sx = 0.0f;
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;
317 const float tx = crx -
static_cast<float>(cached_clump_ix0);
318 clump_sx = tx * tx * (3.0f - 2.0f * tx);
322 for (
int s = 0; s < num_salts; ++s) {
323 float g0 = fine_cache[0][s];
325 const float soft0 = soft_col0[0][s] + (soft_col1[0][s] - soft_col0[0][s]) * soft_sx;
326 g0 += (soft0 - g0) * softness_value;
329 if (use_temporal_blend) {
330 float g1 = fine_cache[1][s];
332 const float soft1 = soft_col0[1][s] + (soft_col1[1][s] - soft_col0[1][s]) * soft_sx;
333 g1 += (soft1 - g1) * softness_value;
335 grain = g0 + (g1 - g0) * smooth_temporal_mix;
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);
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);
356 const float delta = luma_grain * strength;
357 R = clamp01(R + delta);
358 G = clamp01(G + delta);
359 B = clamp01(B + delta);
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));
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));
404 }
catch (
const std::exception&) {
405 throw InvalidJSON(
"Invalid JSON for FilmGrain effect");
422 if (!root[
"seed"].isNull())
seed = root[
"seed"].asInt();
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);
438 root[
"seed"] =
add_property_json(
"Seed",
seed,
"int",
"Deterministic grain variation.", NULL, 0, 1000000,
false, requested_frame);
439 return root.toStyledString();