BrushCue Example: Kaleidoscope Animation¶

Open In Colab

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

In [ ]:
!pip install brushcue
In [1]:
import brushcue
import io

input_image_1 = brushcue.composition_monet_women_with_parasol() # insert your own image here
duration_2 = brushcue.float_passthrough(3.0)
time_3 = brushcue.float_passthrough(0.0)
string_constant_4 = brushcue.string_constant("  // Get normalized coordinates (0.0 to 1.0)\n  let dims_u = textureDimensions(input_texture);\n  let dims = vec2<f32>(dims_u);\n\n  // Pixel-center UV (reduces half-texel bias vs vec2(position)/dims)\n  let uv = (vec2<f32>(position) + vec2<f32>(0.5)) / dims;\n\n  // Center to [-0.5, 0.5] space\n  var p = uv - vec2<f32>(0.5);\n\n  // Correct for aspect ratio so the kaleidoscope is perfectly circular/square\n  let aspect = dims.x / dims.y;\n  p.x = p.x * aspect;\n\n  p = rotate2(p, rotation);\n\n  // Polar\n  let angle_raw = atan2(p.y, p.x);\n  var r = length(p);\n\n  // Optional: subtle radial warp to feel more \"glass-like\"\n  // (set warp=0.0 to disable)\n  r = r + warp * sin(r * warp_frequency);\n\n  // Kaleidoscope fold (clean wedge mirror)\n  let TAU = 6.28318530718;\n  let seg = TAU / segments;\n\n  // Map angle into [0, seg), then mirror fold into [0, seg/2]\n  var a = fract((angle_raw + TAU) / seg) * seg;\n  a = abs(a - 0.5 * seg);\n\n  // Radius scale (controls zoom/coverage)\n  let r2 = r * 1.5;\n\n  // Back to UV — undo aspect correction before sampling\n  var k = vec2<f32>(cos(a), sin(a)) * r2;\n  k.x = k.x / aspect;\n  k = k + vec2<f32>(0.5);\n\n  // Prefer mirror wrap to avoid \"stuck-to-edge\" clamp rings\n  k = vec2<f32>(mirror01(k.x), mirror01(k.y));\n\n  return sample(input_texture, k);")
string_constant_5 = brushcue.string_constant("// Helper function for safe texture sampling (nearest-neighbor)\nfn sample_at(texture: texture_2d<f32>, uv: vec2<f32>) -> vec4<f32> {\n  let dims = textureDimensions(texture);\n  let clamped = clamp(uv, vec2<f32>(0.0), vec2<f32>(0.9999));\n  let pos = vec2<u32>(clamped * vec2<f32>(dims));\n  return textureLoad(texture, pos, 0);\n}\n\n// Mirror-wrap a scalar into [0, 1] with reflected repeats.\n// This tends to look more \"kaleidoscope-like\" than clamping.\nfn mirror01(x: f32) -> f32 {\n  let t = fract(x);\n  return 1.0 - abs(2.0 * t - 1.0);\n}\n\n// 2D rotation around origin\nfn rotate2(v: vec2<f32>, radians: f32) -> vec2<f32> {\n  let c = cos(radians);\n  let s = sin(radians);\n  return vec2<f32>(c * v.x - s * v.y, s * v.x + c * v.y);\n}")
composition_color_profile_6 = brushcue.composition_color_profile(input_image_1)
bool_constant_7 = brushcue.bool_constant(True)
string_constant_8 = brushcue.string_constant("warp_frequency")
string_constant_9 = brushcue.string_constant("warp")
string_constant_10 = brushcue.string_constant("rotation")
dictionary_create_11 = brushcue.dictionary_create()
string_constant_12 = brushcue.string_constant("segments")
int_to_float_13 = brushcue.int_to_float(5)
float_divide_14 = brushcue.float_divide(time_3, duration_2)
pi_15 = brushcue.pi()
float_add_to_dictionary_16 = brushcue.float_add_to_dictionary(dictionary_create_11, string_constant_12, int_to_float_13)
float_multiply_17 = brushcue.float_multiply(2.0, pi_15)
float_multiply_18 = brushcue.float_multiply(float_divide_14, float_multiply_17)
float_add_to_dictionary_19 = brushcue.float_add_to_dictionary(float_add_to_dictionary_16, string_constant_10, float_multiply_18)
float_add_to_dictionary_20 = brushcue.float_add_to_dictionary(float_add_to_dictionary_19, string_constant_9, 0.0)
float_add_to_dictionary_21 = brushcue.float_add_to_dictionary(float_add_to_dictionary_20, string_constant_8, 5.0)
kaleidoscope_shader_22 = brushcue.composition_custom_transformer_shader(input_image_1, string_constant_4, string_constant_5, composition_color_profile_6, composition_color_profile_6, float_add_to_dictionary_21, bool_constant_7)
sequence_graph_23 = brushcue.sequence_graph(duration_2, time_3, kaleidoscope_shader_22)

ctx = brushcue.Context()
result = sequence_graph_23.execute(ctx)
result = result.as_sequence()
# You can render the result to an video file