Task 1 of 6
Everything downstream — counting, measuring, tracking — wants a binary mask: one bit per pixel, foreground or background. The cheapest way to make one is a threshold. Pick a number; call every pixel brighter than it foreground.
Per pixel, no neighbours, no order, nothing shared: the friendliest shape a kernel can have, and exactly the pure map "Thinking in Parallel" calls the easy case. One thread, one pixel, one comparison.
It is also where real images bite back. photo is lit unevenly — bright
at the top-left corner, fading away to the bottom-right — with small bright marks
scattered over the whole frame. Run the starter and read the ASCII dump it prints: one
corner comes back solid, the opposite corner comes back empty, and only a diagonal band
across the middle finds the marks at all.
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].
1 where this pixel's
luminance is above this.constants.t, 0 everywhere
else.photo[this.thread.y][this.thread.x]0.299r + 0.587g + 0.114b, not a single channel1 or 0 — brighter than this.constants.t is foregroundTwo steps, both of which you have written before: reduce the pixel to one
number, then compare that number. The image is warm-toned, so red and luminance are
genuinely different pictures — thresholding pixel[0] gives a mask that
is wrong by a couple of lighting bands.
A kernel returns a number, so the "bit" is the number 1 or the
number 0:
if (lum > this.constants.t) return 1;
return 0;const p = photo[this.thread.y][this.thread.x];
const lum = 0.299 * p[0] + 0.587 * p[1] + 0.114 * p[2];
if (lum > this.constants.t) return 1;
return 0;step() in GLSL/WGSL, one predicated store in
CUDA, and a single fused op in every imaging library — it is so cheap that camera ISPs
do it in silicon. Which is exactly why the interesting question is never how to compare,
but what to compare against.
This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.