# Sharpen: Negative Weights

*Task 4 of 5 · [Convolution & Filters](https://gpu.rocks/learn/convolution-and-filters-66933805.md) · GPU.js Learn*

Filters are not all averages. Give the window **negative weights**
and it starts measuring *differences*. The classic sharpen filter is a cross:
`5` at the center, `−1` at each direct neighbor. Where the image is
flat, the terms cancel to exactly the original value; where it changes, the difference
gets amplified — edges pop.

Sharpened values can overshoot right out of the 0–1 range, so this task computes on a
numeric **luminance map** (`gray[y][x]`, one number per pixel)
and returns raw numbers you can inspect — no color clamping hiding the math.

**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]`.

## Goal

**Goal:** sharpen the 96×96 `gray` map — each cell becomes
`5·center − left − right − up − down`, with neighbor indexes clamped.

## Requirements

- Clamp all four neighbor indexes to `0…this.constants.last`
- Return `5 * gray[y][x]` minus the four clamped neighbor samples
- Keep the kernel numeric — no `graphical: true`, values may leave 0–1

## Hint 1 — why 5 and −1?

The weights sum to 1, so flat regions pass through unchanged:
`5c − 4c = c`. Everything the filter adds comes purely from
center-vs-neighbor *differences*.

## Hint 2 — four clamps, one return

```js
let left = x - 1;
if (left < 0) left = 0;
```

— repeat for
`right`, `up`, `down` against
`this.constants.last`, then a single return with the five terms:

```js
return 5 * gray[y][x] - gray[y][left] - gray[y][right]
  - gray[up][x] - gray[down][x];
```

## Same idea elsewhere

A convolution with learned weights is a CNN layer — cuDNN (CUDA) and MIOpen
(ROCm) are entire libraries for running this exact multiply-accumulate window fast.
Your sharpen filter is the same arithmetic with the weights picked by hand instead of
by gradient descent.

## Starter code

```js
// Sharpen = identity + edge boost: 5×center − the 4 direct neighbors.
const gpu = new GPU({ mode });

const sharpen = gpu.createKernel(function (gray) {
  const x = this.thread.x;
  const y = this.thread.y;
  // TODO: clamp left/right/up/down to 0…this.constants.last, then
  // return 5 * center − left − right − up − down.
  return gray[y][x];
}, {
  output: [96, 96],
  constants: { last: 95 },
});

const result = await sharpen(gray);
console.log('center before:', gray[48][48], ' after:', result[48][48]);
```

---

Interactive version: https://gpu.rocks/learn/convolution-and-filters-66933805/4

[Previous task](https://gpu.rocks/learn/convolution-and-filters-66933805/3.md) · [Next task](https://gpu.rocks/learn/convolution-and-filters-66933805/5.md)
