Back to Articles

2026-04-29

Zoom Blur Is Updated

By Anthony Dito

Zoom Blur is now a dedicated Composition Zoom Blur node, available in Python, and easier to use in the editor with a new drag handle for the center point.

Zoom Blur Is Updated

Zoom Blur has been one of our most popular tools. We have made changes to how it works and how you can interact with it.

What Changed

Previously, Zoom Blur was implemented as a Custom Transformer Shader. While functional, this made it cumbersome to incorporate into your own projects. Zoom Blur is now its own dedicated Composition Zoom Blur node in the graph editor, and it is also available in the Python API. The parameters remain the same: a center point and a strength value.

Using Zoom Blur from Python

You can now apply Zoom Blur directly from Python:

import brushcue

ctx = brushcue.Context()
img = brushcue.composition_image_from_path("my_image.jpg")
center = brushcue.vector2f_from_components(0.5, 0.5)
zoom_blurred_img = brushcue.composition_zoom_blur(img, center, 0.5)

Check out the Python API documentation and the Zoom Blur example for more details.

Easier to Use on the BrushCue Website

We have also improved the Zoom Blur experience in the BrushCue editor. You will now see a drag handle on the canvas where you can interactively set the effect center.

Zoom Blur Tool Demo

How the Shader Works

In the spirit of transparency, here is the WGSL shader that powers Zoom Blur. You can use this as a starting point to create your own variation with a Custom Transformer Shader.

@group(0) @binding(0) var input_texture: texture_2d<f32>;
@group(0) @binding(1) var<uniform> center: vec2<f32>;
@group(0) @binding(2) var<uniform> strength: f32;

fn do_transformation(input: vec4<f32>, position: vec2<u32>) -> vec4<f32> {
  let dims = vec2<f32>(textureDimensions(input_texture));
  let uv = (vec2<f32>(position) + vec2<f32>(0.5)) / dims;
  let center_uv = clamp(center, vec2<f32>(0.0), vec2<f32>(1.0));
  let dir = uv - center_uv;
  let dist = length(dir);
  let max_dist = 0.85;
  let distance_scale = smoothstep(0.0, max_dist, min(dist, max_dist));
  let effective_strength = strength * distance_scale * 0.85;
  let pixel_hash = fract(sin(dot(vec2<f32>(position), vec2<f32>(12.9898, 78.233))) * 43758.5453);

  var result = vec4<f32>(0.0);
  var total_weight = 0.0;
  let samples = 32;

  for (var i = 0; i < samples; i++) {
    let base_t = (f32(i) + 0.5) / f32(samples);
    let jitter = (pixel_hash - 0.5) / f32(samples);
    let t = clamp(base_t + jitter, 0.0, 1.0);
    let eased_t = t * t * (3.0 - 2.0 * t);
    let weight = 1.0 - eased_t * 0.65;
    let sample_uv = uv - dir * eased_t * effective_strength;
    result += sample(input_texture, sample_uv) * weight;
    total_weight += weight;
  }

  return result / total_weight;
}

fn sample(tex: texture_2d<f32>, uv: vec2<f32>) -> vec4<f32>; // function implemented for you

The effect works by sampling the image 32 times along the direction from each pixel toward the center. Each sample is jittered slightly (using a per-pixel hash) to avoid banding. Samples closer to the center are weighted more heavily, and the blur strength is scaled by distance from the center so that the effect falls off naturally near the focal point.

We hope this update makes Zoom Blur easier to use in your projects. As always, feel free to reach out to feedback@brushcue.com with any feedback or questions.