Task 5 of 5
The payoff, and a question a person can answer in a glance: what colour is this picture, mostly? Cut the wheel into 12 bins of 30°, count how many pixels fall in each, and read off the fullest one.
The counting is a histogram, and the one-thread-per-bin shape it has to take when you have no atomics is exactly what Histograms & Binning derives — so that kernel comes ready made below, along with the two you wrote in task 2. What is left for you is the part that is about colour: turning each pixel into a bin number, and refusing to answer for the pixels that have no colour to report.
That refusal is the difference between an answer and a rumour. The stones along the bottom of this picture are grey to within a rounding error, and the direction of a rounding error is still a perfectly valid-looking angle. Bin them and they smear a plausible-looking 96 pixels of nonsense across the whole wheel. Drop anything below a saturation floor and the histogram only counts pixels that actually have a hue — which is why its counts come to fewer than 4,096, on purpose.
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].
hueBin — the bin index for each pixel, or
-1 for a pixel with no usable hue — then find the fullest bin in JavaScript and
log it.-1 when the saturation is below this.constants.floorMath.floor(h / this.constants.width), clamped to this.constants.bins - 1console.log its index4000, not 4,096 — the 96 stones are excluded on purposeThe saturation test comes before anything else, because a pixel that fails it has no angle worth binning:
if (sat[this.thread.y][this.thread.x] < this.constants.floor) {
return -1;
}30° per bin, so the index is the hue divided by the width and floored. The clamp is the same one Histograms & Binning needed: a hue of exactly 360 would otherwise land in bin 12, which no thread owns.
const h = hue[this.thread.y][this.thread.x];
return Math.min(this.constants.bins - 1, Math.floor(h / this.constants.width));The fullest bin is a plain loop over 12 numbers — not worth a kernel. Bin
b covers b * 30 to (b + 1) * 30 degrees, so printing
that range alongside the index tells you what colour the picture actually is.
This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.