BrushCue Example: Desert Mirage Filter Animation¶
You can use this tool online at https://www.brushcue.com/tools/desert-mirage-filter
In [ ]:
!pip install brushcue
In [1]:
import brushcue as bc
duration = 6.0
@bc.brushcue_fn
def frame(time):
input_image = bc.Composition.monet_women_with_parasol()
intensity = 0.25
input_bounds = input_image.bounds()
input_width = input_bounds.width()
return (
input_image.chroma_offset(
bc.Vector2f.from_components((0.08 * intensity), (0.04 * intensity))
)
.brightness_adjust((0.15 * intensity))
.saturation_adjust((1.0 + (0.6 * intensity)))
.spacial_effect_shader(
"let uv = (position - bounds_origin) / bounds_size;\n // Normalize time over the 3-second duration (0 -> 1) then to radians.\n // Integer multiples of 2 PI ensure every wave returns to zero at t=3.0.\n let t = (time % 3.0) / 3.0;\n let t1 = t * 6.283185 * 2.0;\n let t2 = t * 6.283185 * 3.0;\n let t3 = t * 6.283185 * 1.0;\n\n // Mirage heat distortion: layered sine waves rising from the bottom.\n let heat_influence = pow(1.0 - uv.y, 2.5);\n let wave1 = sin(uv.x * 18.0 + uv.y * 6.0 + t1) * 0.007;\n let wave2 = sin(uv.x * 31.0 - uv.y * 9.0 + 1.3 + t2) * 0.004;\n let wave3 = sin(uv.x * 9.0 + uv.y * 22.0 + 2.7 + t3) * 0.005;\n let distort = (wave1 + wave2 + wave3) * heat_influence * intensity;\n\n // Chromatic aberration for a trippy fringe.\n let offset = distort * 1.8;\n let uv_r = clamp(uv + vec2f(distort + offset * 0.5, distort * 0.6), vec2f(0.0), vec2f(1.0));\n let uv_g = clamp(uv + vec2f(distort, distort * 0.8), vec2f(0.0), vec2f(1.0));\n let uv_b = clamp(uv + vec2f(distort - offset * 0.4, distort), vec2f(0.0), vec2f(1.0));\n let r = sample(bounds_origin + uv_r * bounds_size).r;\n let g = sample(bounds_origin + uv_g * bounds_size).g;\n let b = sample(bounds_origin + uv_b * bounds_size).b;\n let a = sample(bounds_origin + uv_g * bounds_size).a;\n return vec4f(r, g, b, a);",
"",
(input_width * 0.031),
bc.Dictionary.create()
.add("intensity", intensity)
.add("time", time)
.add(
"bounds_origin",
bc.Vector2f.from_components(input_bounds.min_x(), input_bounds.min_y()),
)
.add(
"bounds_size",
bc.Vector2f.from_components(input_width, input_bounds.height()),
),
bc.ColorRepresentation.srgb(),
)
.crop(input_bounds)
)
graph = bc.Sequence.graph(duration, frame)
ctx = bc.Context()
sequence = graph.execute(ctx)
# Render `sequence` to a video file as needed.
[wgpu] using backend Metal — adapter 'Apple M5' (IntegratedGpu), driver ''