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;
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();
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.
openshot::TimelineBase * timeline
Pointer to the parent timeline instance (if any)
virtual openshot::TimelineBase * ParentTimeline()
Get the associated Timeline pointer (if any)
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.
This class represents a clip (used to arrange readers on the timeline)
virtual Json::Value JsonValue() const
Generate Json::Value for this object.
openshot::ClipBase * ParentClip()
Parent clip object of this effect (which can be unparented and NULL)
Json::Value BasePropertiesJSON(int64_t requested_frame) const
Generate JSON object of base properties (recommended to be used by all effects)
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
openshot::ClipBase * clip
Pointer to the parent clip instance (if any)
EffectInfoStruct info
Information about the current effect.
std::string Json() const override
Generate JSON string of this object.
std::string PropertiesJSON(int64_t requested_frame) const override
Keyframe clump
Even vs clustered irregular structure.
Keyframe shadows
Grain strength in dark regions.
Keyframe softness
Crisp vs soft organic grain.
int seed
Deterministic grain identity.
Keyframe highlights
Grain strength in bright regions.
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Json::Value JsonValue() const override
Generate Json::Value for this object.
Keyframe color_amount
Chroma contribution amount.
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...
Keyframe amount
Overall grain intensity.
Keyframe coherence
Smoothness/stability between frames.
Keyframe color_variation
RGB channel independence.
Keyframe evolution
How much the grain renews over time.
void SetJson(const std::string value) override
Load JSON string into this object.
Keyframe size
Fine to coarse grain scale.
Keyframe midtones
Grain strength in middle tonal regions.
Exception for invalid JSON.
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
double GetValue(int64_t index) const
Get the value at a specific index.
Json::Value JsonValue() const
Generate Json::Value for this object.
This class represents a timeline.
This namespace is the default namespace for all code in the openshot library.
const Json::Value stringToJson(const std::string value)
bool has_video
Determines if this effect manipulates the image of a frame.
bool has_audio
Determines if this effect manipulates the audio of a frame.
std::string class_name
The class name of the effect.
std::string name
The name of the effect.
std::string description
The description of this effect and what it does.