Task 3 of 5

Box Blur: the Window Goes 2D

Take the sliding window into two dimensions and you have image filtering. A 3×3 box blur is the simplest case: every output pixel is the plain average of the 3×3 patch centered on it — nine reads, per color channel, per pixel. 131,072 threads each do their nine reads at once.

Same edge problem, now on four sides: clamp both coordinates into 0…this.constants.last before indexing. Average red, green and blue separately and hand the result to this.color().

Array layout in gpu.js

Image data comes in row-major: image[y][x] is the pixel in row y, column x, and each pixel is an [r, g, b, a] array with channels from 0 to 1. Mind the inversion that catches everyone — sizes are given width-first (output: [width, height]), but indexing runs row-first, so this thread's own pixel is image[this.thread.y][this.thread.x]. Swap those two and you read the transpose of your image. Three-dimensional data follows the same rule: output: [w, h, d] is indexed [z][y][x].

nine reads and an average; off the edge, the border pixel answers twice
Goal: blur inputImage with a 3×3 box filter — each painted pixel is the average of its 3×3 neighborhood, edges clamped.

Requirements

Hint 1 — the neighborhood loop

for (let dy = 0; dy < 3; dy++) nested with dx, and the sample position is this.thread.y + dy - 1, this.thread.x + dx - 1 — the - 1 centers the window on this thread's pixel.

Hint 2 — clamp, then read
let sy = this.thread.y + dy - 1;
if (sy < 0) sy = 0;
if (sy > this.constants.last) sy = this.constants.last;

— same for sx — then const pixel = image[sy][sx]; and add pixel[0], pixel[1], pixel[2] into three running sums.

Hint 3 — the finish

After the loops: this.color(r / 9, g / 9, b / 9, 1); — nine samples went in, so divide by nine on the way out.

Same idea elsewhere

Blur passes ship in every production toolkit — Metal Performance Shaders' MPSImageBox, NVIDIA's NPP filtering routines, WebGPU post-processing chains. The fast ones exploit that a box blur is separable: a horizontal pass then a vertical pass — six reads per pixel instead of nine.

All tasks in Convolution & Filters

  1. Slide a Window: 1D Convolution
  2. Any Filter, One Kernel
  3. Box Blur: the Window Goes 2D
  4. Sharpen: Negative Weights
  5. Sobel Edge Detection

This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.