JavaScript: a sharpen filter and why clamping matters
Apply a sharpening kernel to a step edge and see the overshoot clamped to the valid range.
On this page
JavaScript arrays and the convolution idea.
- Apply a sharpening kernel to a step edge and see the overshoot clamped to the valid range.
- Predict the sample output and explain the state changes.
- Complete the practice task and check the boundary cases.
The concept
A sharpening kernel emphasizes the difference between a pixel and its four neighbours: 5 times the center minus the neighbours above, below, left, and right. On a flat area the weights sum to 1, so the pixel is unchanged. Near an edge it exaggerates the contrast.
At a step from 50 to 200, the dark side of the edge becomes darker than 50 and the bright side brighter than 200, a halo-like overshoot. The raw result can leave the valid 0 to 255 range, here reaching −100 and 350, so each output is clamped. Without clamping, such values would wrap around or be stored incorrectly.
Code example
const width = 5;
const image = [];
for (let r = 0; r < 5; r++) {
for (let c = 0; c < width; c++) {
image.push(c < 2 ? 50 : 200);
}
}
const clamp = (v) => Math.min(255, Math.max(0, v));
const at = (r, c) => image[r * width + c];
for (let r = 1; r < 4; r++) {
const row = [];
for (let c = 1; c < 4; c++) {
const raw = 5 * at(r, c) - at(r - 1, c) - at(r + 1, c) - at(r, c - 1) - at(r, c + 1);
row.push(clamp(raw));
}
console.log(row.join(" "));
}0 255 200 0 255 200 0 255 200
Follow the code
Open the walkthrough to inspect the example one step at a time.
Show guided walkthrough
const width = 5;const image = [];for (let r = 0; r < 5; r++) { for (let c = 0; c < width; c++) { image.push(c < 2 ? 50 : 200); }} const clamp = (v) => Math.min(255, Math.max(0, v));const at = (r, c) => image[r * width + c]; for (let r = 1; r < 4; r++) { const row = []; for (let c = 1; c < 4; c++) { const raw = 5 * at(r, c) - at(r - 1, c) - at(r + 1, c) - at(r, c - 1) - at(r, c + 1); row.push(clamp(raw)); } console.log(row.join(" "));}Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| Columns 0 and 1 are dark (50) and columns 2 to 4 are bright (200): a vertical step edge. | — | — | |
| At column 1 the raw value is 5·50 − (50+50+50+200) = −100. At column 2 it is 5·200 − (200+200+50+200) = 350. At column 3 it is 200. | raw = −100, 350, 200 | — | |
| Clamp into 0 to 255: −100 becomes 0, 350 becomes 255, and 200 is already valid. | clamped = 0, 255, 200 | — | |
| Same result on every row, because the image is identical down each column. | clamped = 0, 255, 200 | 0 255 200 0 255 200 0 255 200 |
Behind the scenes
How JavaScript processes this code
A JavaScript engine parses and runs the source. A function body runs when the function is called. console.log writes to the console.
Cost and common mistakes
O(p) for p pixels with a fixed 3×3 kernel.
- Sharpening also amplifies noise. A light blur before sharpening, or unsharp masking, gives cleaner results.
- Applying the kernel in place overwrites pixels that later neighbours still need. Write results to a separate array.
Try it yourself
What would the raw value be at a flat pixel where the pixel and all four neighbours equal 100?
Reveal answer and reasoning
5·100 − 4·100 = 100, so flat regions are unchanged.