Task 4 of 5
Here is the whole module's argument, in one exercise. You want every pixel of the red
ball in frame — all of it, top to bottom, shadow included.
In RGB that is a threshold on numbers that move when the light moves. "Red" comes out as
something like r > 0.5 and r − g > 0.3, and it works beautifully on the lit
top of the ball. Turn the light down and every one of those numbers falls with it, until the
test stops being true — for a pixel that is exactly as red as it ever was. That kernel is
written for you below, so you can watch it happen.
In HSV, brightness lives in V and nowhere else. Multiply a pixel's r, g and b by the same
factor and H and S do not move at all. So the test becomes "hue near red, saturated enough",
and the shadow costs you nothing. One wrinkle, and it is task 3's wrinkle: red sits at 0°, so
a 15° wedge around it runs from 345° up over the seam to 15°. h > 345 && h
< 15 is true for no angle whatsoever. Measure the distance round the wheel
instead.
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].
hsvMask — 1 for pixels within
this.constants.tol degrees of this.constants.target on the wheel
and at least this.constants.minSat saturated, 0 for everything
else — and log how many pixels each mask found.h − target into −180 … 180 before comparing — the wedge straddles 0°s >= this.constants.minSat: a pixel with no hue reports -1, which is one degree from red, and the saturation floor is what keeps it out1 or 0, nothing in betweenconsole.log both mask counts — the HSV one should find the whole ball, the RGB one only its lit halfExactly the fold from task 3, then drop the sign:
let d = h - this.constants.target;
if (d > 180) { d = d - 360; }
if (d < -180) { d = d + 360; }
if (d < 0) { d = -d; }
Now d is a distance in degrees, 0 … 180, and it does not care where the
seam is.
Two conditions, and both matter:
if (d <= this.constants.tol && s >= this.constants.minSat) {
return 1;
}
return 0;Total each mask with a plain nested loop in JavaScript after the kernels have run. The HSV count should be comfortably the larger — and the gap between them is the part of the ball that RGB gave up on because a lamp was dimmer there.
This page is an interactive exercise — the editor, the GPU runner and your saved progress need JavaScript. The text above is the full brief.