BrushCue Example: Film Grain¶

Open In Colab

You can use this tool online at https://www.brushcue.com/tools/film-grain

In [ ]:
!pip install brushcue
In [1]:
import brushcue
from PIL import Image
import io

input_image_1 = brushcue.composition_monet_women_with_parasol() # insert your own image here
string_constant_2 = brushcue.string_constant("let texture_dimens = textureDimensions(input_texture);\nlet width = f32(texture_dimens.x);\nlet height = f32(texture_dimens.y);\nlet avg_dimen = (width + height) * 0.5;\nlet uv = vec2<f32>(f32(position.x) / width, f32(position.y) / height);\nlet L = input.x;\nlet a = input.y;\nlet b = input.z;\nlet alpha = input.a;\n\nlet grain_strength = (1.0 - L) * grain_strength_param * 0.1;\n\nlet fine_freq = fine_grain_frequency * avg_dimen / 1024.0;\nlet medium_freq = medium_grain_frequency * avg_dimen / 1024.0;\nlet high_freq = high_grain_frequency * avg_dimen / 1024.0;\n\n// Fine grain noise\nlet p = uv * fine_freq;\nlet pi = floor(p);\nlet pf = fract(p);\nlet u = pf * pf * (3.0 - 2.0 * pf);\n\nlet h1 = fract(sin(dot(pi, vec2<f32>(12.9898, 78.233))) * 43758.5453);\nlet h2 = fract(sin(dot(pi + vec2<f32>(1.0, 0.0), vec2<f32>(12.9898, 78.233))) * 43758.5453);\nlet h3 = fract(sin(dot(pi + vec2<f32>(0.0, 1.0), vec2<f32>(12.9898, 78.233))) * 43758.5453);\nlet h4 = fract(sin(dot(pi + vec2<f32>(1.0, 1.0), vec2<f32>(12.9898, 78.233))) * 43758.5453);\n\nlet v0 = mix(h1, h2, u.x);\nlet v1 = mix(h3, h4, u.x);\nlet fine_noise = mix(v0, v1, u.y);\n\n// Medium grain noise\nlet medium_p = uv * medium_freq;\nlet medium_pi = floor(medium_p);\nlet medium_pf = fract(medium_p);\nlet medium_u = medium_pf * medium_pf * (3.0 - 2.0 * medium_pf);\n\nlet mh1 = fract(sin(dot(medium_pi, vec2<f32>(12.9898, 78.233))) * 43758.5453);\nlet mh2 = fract(sin(dot(medium_pi + vec2<f32>(1.0, 0.0), vec2<f32>(12.9898, 78.233))) * 43758.5453);\nlet mh3 = fract(sin(dot(medium_pi + vec2<f32>(0.0, 1.0), vec2<f32>(12.9898, 78.233))) * 43758.5453);\nlet mh4 = fract(sin(dot(medium_pi + vec2<f32>(1.0, 1.0), vec2<f32>(12.9898, 78.233))) * 43758.5453);\n\nlet mv0 = mix(mh1, mh2, medium_u.x);\nlet mv1 = mix(mh3, mh4, medium_u.x);\nlet medium_noise = mix(mv0, mv1, medium_u.y);\n\n// High grain noise\nlet high_p = uv * high_freq;\nlet high_pi = floor(high_p);\nlet high_pf = fract(high_p);\nlet high_u = high_pf * high_pf * (3.0 - 2.0 * high_pf);\n\nlet hh1 = fract(sin(dot(high_pi, vec2<f32>(12.9898, 78.233))) * 43758.5453);\nlet hh2 = fract(sin(dot(high_pi + vec2<f32>(1.0, 0.0), vec2<f32>(12.9898, 78.233))) * 43758.5453);\nlet hh3 = fract(sin(dot(high_pi + vec2<f32>(0.0, 1.0), vec2<f32>(12.9898, 78.233))) * 43758.5453);\nlet hh4 = fract(sin(dot(high_pi + vec2<f32>(1.0, 1.0), vec2<f32>(12.9898, 78.233))) * 43758.5453);\n\nlet hv0 = mix(hh1, hh2, high_u.x);\nlet hv1 = mix(hh3, hh4, high_u.x);\nlet high_noise = mix(hv0, hv1, high_u.y);\n\n// Combine noises with separate weights, normalize to -0.5 to 0.5\nlet combined_noise = (fine_noise * fine_weight + medium_noise * medium_weight + high_noise * high_weight) - 0.5;\nlet grain_amount = combined_noise * grain_strength;\n\n// Apply grain primarily to luminance\nlet L_grained = clamp(L + grain_amount, 0.0, 1.0);\n\n// Minimal color grain for subtle variation\nlet a_grained = clamp(a + grain_amount * 0.02, -0.4, 0.4);\nlet b_grained = clamp(b + grain_amount * 0.02, -0.4, 0.4);\n\nreturn vec4<f32>(L_grained, a_grained, b_grained, alpha);")
string_constant_3 = brushcue.string_constant("// No helpers")
color_profile_oklaba_4 = brushcue.color_profile_ok_lab_a()
color_profile_oklaba_5 = brushcue.color_profile_ok_lab_a()
bool_constant_6 = brushcue.bool_constant(True)
string_constant_7 = brushcue.string_constant("grain_strength_param")
string_constant_8 = brushcue.string_constant("high_weight")
string_constant_9 = brushcue.string_constant("high_grain_frequency")
string_constant_10 = brushcue.string_constant("medium_weight")
string_constant_11 = brushcue.string_constant("medium_grain_frequency")
string_constant_12 = brushcue.string_constant("fine_weight")
dictionary_create_13 = brushcue.dictionary_create()
string_constant_14 = brushcue.string_constant("fine_grain_frequency")
float_add_to_dictionary_15 = brushcue.float_add_to_dictionary(dictionary_create_13, string_constant_14, 150.0)
float_add_to_dictionary_16 = brushcue.float_add_to_dictionary(float_add_to_dictionary_15, string_constant_12, 0.5)
float_add_to_dictionary_17 = brushcue.float_add_to_dictionary(float_add_to_dictionary_16, string_constant_11, 50.0)
float_add_to_dictionary_18 = brushcue.float_add_to_dictionary(float_add_to_dictionary_17, string_constant_10, 0.3)
float_add_to_dictionary_19 = brushcue.float_add_to_dictionary(float_add_to_dictionary_18, string_constant_9, 300.0)
float_add_to_dictionary_20 = brushcue.float_add_to_dictionary(float_add_to_dictionary_19, string_constant_8, 0.2)
float_add_to_dictionary_21 = brushcue.float_add_to_dictionary(float_add_to_dictionary_20, string_constant_7, 1.0)
film_grain_shader_22 = brushcue.composition_custom_transformer_shader(input_image_1, string_constant_2, string_constant_3, color_profile_oklaba_4, color_profile_oklaba_5, float_add_to_dictionary_21, bool_constant_6)

ctx = brushcue.Context()
result = film_grain_shader_22.execute(ctx)
composition = result.as_composition()
data_bytes = composition.to_image_bytes(ctx)
img = Image.open(io.BytesIO(data_bytes))
img.thumbnail((400, 400)) # remove this line for full resolution
img
Out[1]:
No description has been provided for this image