# Sobel Edge Detection

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

The payoff: run **two convolutions at once**. Sobel's
`Gx` filter responds to horizontal change, `Gy` to vertical change,
and the length of that gradient vector — `√(gx² + gy²)` — is how
*edge-like* the pixel is, whatever the edge's direction.

This is a two-kernel pipeline like the finale of **Data In, Data Out**: a
numeric pass turns the image
into a luminance map (written for you), then the Sobel pass reads each map cell's eight
neighbors, applies both weight grids, and paints the magnitude. Border pixels have no
full neighborhood, so the starter already paints them black — your work lives in the
`else` branch.

**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:** finish the Sobel kernel — read the 3×3 neighborhood of
`gray`, compute `gx` and `gy` with the weights shown in
the starter, and paint `Math.sqrt(gx * gx + gy * gy)` as a gray value.

## Requirements

- Read the eight neighbors of `gray[y][x]` (no clamping needed — the border branch already ran)
- Apply both weight grids: `gx` from the right column minus the left, `gy` from the bottom row minus the top
- Paint the magnitude `Math.sqrt(gx * gx + gy * gy)` as gray via `this.color(m, m, m, 1)`

## Hint 1 — name the neighborhood

Pull the nine cells into locals first —
`const tl = gray[y - 1][x - 1];` through
`const br = gray[y + 1][x + 1];` — then the two weighted sums are easy to
read off the grids.

## Hint 2 — the two sums

```js
const gx = (tr + 2 * mr + br) - (tl + 2 * ml + bl);
```

— right column minus left column, middle counted double. `gy` is the same
with rows: `(bl + 2 * bm + br) - (tl + 2 * tm + tr)`.

## Hint 3 — the finish

```js
const m = Math.sqrt(gx * gx + gy * gy);
this.color(m, m, m, 1);
```

— flat areas give 0 (black), sharp edges overshoot 1 and clamp to white.

## Same idea elsewhere

Sobel is the hello-world of GPU vision: it opens the OpenCL and CUDA imaging
tutorials, camera ISPs run it in silicon, and edge maps feed feature detectors
everywhere. Fusing two directional filters into one pass is exactly how you would write
it in WGSL or Metal, too.

## Starter code

```js
// Two directional convolutions, one kernel, magnitude out.
const gpu = new GPU({ mode });

// Pass 1 — luminance map (Data In, Data Out déjà vu; already done for you).
const luminance = gpu.createKernel(function (image) {
  const pixel = image[this.thread.y][this.thread.x];
  return 0.299 * pixel[0] + 0.587 * pixel[1] + 0.114 * pixel[2];
}, { output: [128, 128] });

// Pass 2 — Sobel. Gx and Gy weigh the same 3×3 neighborhood:
//
//        Gx              Gy
//    -1   0  +1      -1  -2  -1
//    -2   0  +2       0   0   0
//    -1   0  +1      +1  +2  +1
//
const sobel = gpu.createKernel(function (gray) {
  const x = this.thread.x;
  const y = this.thread.y;
  if (x === 0 || y === 0 || x === this.constants.last || y === this.constants.last) {
    this.color(0, 0, 0, 1); // border: no full neighborhood — paint it black
  } else {
    // TODO: read the 8 neighbors, compute gx and gy with the grids above,
    // then paint the magnitude Math.sqrt(gx * gx + gy * gy).
    const l = gray[y][x];
    this.color(l, l, l, 1);
  }
}, {
  output: [128, 128],
  graphical: true,
  constants: { last: 127 },
});

const grayMap = await luminance(inputImage);
await sobel(grayMap);
render(sobel.canvas);
```

---

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

[Previous task](https://gpu.rocks/learn/convolution-and-filters-66933805/4.md)
