# Take It Out, Let the Picture Close Up

*Task 4 of 6 · [Seam Carving: Content-Aware Resizing](https://gpu.rocks/learn/seam-carving-a23a0d9b.md) · GPU.js Learn*

Deleting the seam is a splice on a CPU: for each row, remove one element and let
the rest shuffle down. On a GPU there is no splice. A thread writes its own output cell and
nothing else — it cannot push its neighbour along, which is the "no scatter" rule
*Thinking in Parallel* makes a whole module of.

So turn the question round, which is what a gather always is. The output is one column
narrower than the input. The thread that owns output cell `(x, y)` asks: which
input pixel belongs *here*? Everything left of the seam has not moved. Everything
from the seam rightwards has slid one column left, so it comes from
`x + 1`. One `if`, no shuffling, and every row does its own thing at
the same time even though every row's seam is at a different column.

The output being narrower than the input is the reason for `dynamicOutput` —
`carve.setOutput([w - 1, 72])` before each call, and the same kernel keeps
working all the way down.

## Figures

- **nobody is pushed aside — every output pixel reaches for its own source** — Eight input pixels above seven output pixels. Input three is the seam and is removed. Outputs zero to two read straight down; outputs three to six read diagonally from inputs four to seven, one column further right.

## Goal

**Goal:** write `carve` so its output is `plane`
with the seam pixel removed from every row, and everything to its right pulled one column
left.

## Requirements

- Read the seam position for THIS row: `seam[this.thread.y]` — every row removes a different column
- Output cell `x` comes from `plane[y][x]` when `x < seam[y]`, and from `plane[y][x + 1]` otherwise
- Keep the output one column narrower than the input

## Hint 1 — which input pixel is mine?

Say the seam is at column 3 in this row. Output cells 0, 1, 2 are input cells
0, 1, 2 — nothing moved. Output cell 3 is input cell **4**: input cell 3
was the seam and is gone. Output cell 4 is input 5, and so on.

## Hint 2 — the whole kernel

```js
const x = this.thread.x;
const y = this.thread.y;
if (x < seam[y]) return plane[y][x];
return plane[y][x + 1];
```

Note the strict `<`. With `<=` the seam pixel survives and its
right-hand neighbour is deleted instead — the picture still narrows by one, so the
shapes all check out and the wrong pixel is gone.

## Same idea elsewhere

Compaction by gather is the standard GPU answer to "remove some elements": a
thread computes where its data comes from rather than where it goes, because destinations
collide and sources never do. *Stream Compaction* builds the general version with a
prefix sum; here the geometry hands you the offset for free — it is 0 or 1, decided by one
comparison. CUDA's `thrust::remove_if` and WebGPU compaction passes are the
same shape underneath.

## Starter code

```js
// No splice on a GPU. Ask where each output pixel COMES FROM.
const gpu = new GPU({ mode });

const carve = gpu.createKernel(function (plane, seam) {
  const x = this.thread.x;
  const y = this.thread.y;
  // TODO: which input pixel belongs in output cell (x, y)? Everything left
  // of this row's seam has not moved; everything from the seam rightwards
  // came from one column further right.
  return plane[y][x];
}, {
  output: [127, 72],
  immutable: true,
  dynamicOutput: true,
  dynamicArguments: true,
});

const narrower = await carve(plane, seam);

console.log(plane[0].length, 'columns in ·', narrower[0].length, 'columns out');
console.log('row 0 before:', plane[0]);
console.log('row 0 after: ', narrower[0]);
```

---

Interactive version: https://gpu.rocks/learn/seam-carving-a23a0d9b/4

[Previous task](https://gpu.rocks/learn/seam-carving-a23a0d9b/3.md) · [Next task](https://gpu.rocks/learn/seam-carving-a23a0d9b/5.md)
