🚧 Designers never finish their own portfolio. This one ships rough on purpose and gets better in public. If something looks half-done, it probably is, and I'm on it.🚧 Designers never finish their own portfolio. This one ships rough on purpose and gets better in public. If something looks half-done, it probably is, and I'm on it.🚧 Designers never finish their own portfolio. This one ships rough on purpose and gets better in public. If something looks half-done, it probably is, and I'm on it.🚧 Designers never finish their own portfolio. This one ships rough on purpose and gets better in public. If something looks half-done, it probably is, and I'm on it.🚧 Designers never finish their own portfolio. This one ships rough on purpose and gets better in public. If something looks half-done, it probably is, and I'm on it.🚧 Designers never finish their own portfolio. This one ships rough on purpose and gets better in public. If something looks half-done, it probably is, and I'm on it.
Open menu
Switch to Darkhello@product.inc
Seam carving in the browser β€” cover sketch

Seam carving in the browser

Β· Alex Zapadenko

CSS has five answers for a picture that doesn't fit its box: stretch it, crop it, letterbox it, leave it alone, or shrink it until it fits. That's the whole of object-fit, and none of it can tell the sky from the lighthouse. Seam carving can. It finds a path of pixels from top to bottom, one per row, through the least detail in the picture, removes it, and repeats. The picture gets narrower and the empty part goes first.

CSS can't do that, so the question is where it runs instead. There are three places, and they trade work done before the page ships against work done while it's open. Each is running below on the same picture. Drag the edge of the frame.

Carve the files before the page ships

caire, Endre Simo's open-source seam carver, cut this scene into three narrower shapes ahead of time. The frame is a CSS size container, and four container queries show the file closest to its shape. No script picks the file; the script on this page only moves the edge.

A white lighthouse on a rocky islet at left and a small sailboat at right, with calm sea and an empty sky between them.The same scene carved by caire to 4:3.The same scene carved by caire to a square.The same scene carved by caire to 4:5; the lighthouse and the boat are whole and closer together.
Frame width
aspect 1.00
FileSizeShown when the frame iscaire took
wide.jpg, the original1152 Γ— 648, 48 KBwider than 1.55 : 1β€”
4-3.jpg864 Γ— 648, 40 KB1.165 to 1.55 : 116 s
square.jpg648 Γ— 648, 34 KB0.9 to 1.165 : 125 s
portrait.jpg518 Γ— 648, 30 KBnarrower than 0.9 : 130 s

This is the cheapest option by a distance. The page decides nothing at runtime and ships ordinary JPEGs. Even the 4:5 file, with 55% of the width gone, keeps the lighthouse and the boat whole, because the seams came out of the sea and the sky. The cost is that the picture only has the shapes you carved, and object-fit: cover trims whatever falls between them.

When the box follows the viewport rather than the layout, <picture> does the same job and downloads only one file:

<picture>
  <source media="(max-aspect-ratio: 9/10)" srcset="portrait.jpg">
  <source media="(max-aspect-ratio: 233/200)" srcset="square.jpg">
  <source media="(max-aspect-ratio: 31/20)" srcset="4-3.jpg">
  <img src="wide.jpg" alt="…">
</picture>

Carve once, keep the order

A carve removes its seams in a fixed order. Record the step at which each pixel went, and any narrower width is the pixels removed after that step, left in place. Every seam takes exactly one pixel from every row, so every row comes out the same length and there is nothing to recompute. Avidan and Shamir described this in the 2007 paper that introduced seam carving.

700 seams carved ahead of time
Frame width
The full picture, with the pixels removed at this width painted in. The seams take sky and sea first and go around the lighthouse, its rocks and the boat.

Here 700 seams are stored as a starting column plus two bits a row, 115 KB in all, and a redraw is one pass over the picture. That takes a few milliseconds, so the picture keeps up with the drag. Squashed and Cropped show what object-fit: fill and object-fit: cover do with the same frame.

The price is the extra file and a script, and the picture becomes a canvas instead of an <img>. On most pages that trade isn't worth it. It is for a hero that has to hold its subject at every width.

Carve in the page

The same carver runs in a Web Worker, so the page stays responsive while it works. It finds the cheapest seam, removes it and repeats; the marked line is the seam it's about to remove.

Loading the picture…

On this picture a seam takes about 4 ms, so a square takes two seconds. That's fine for a tool that handles one upload, and far too slow to redo every time a layout changes. When it finishes, the result can go to the resizer above, which then works on that picture. An image you choose stays in your browser, scaled to fit 1152 Γ— 864 first.

The carver is about 150 lines with no libraries. It uses forward energy (Rubinstein, Shamir and Avidan, 2008), which scores a seam by the new edges its removal would create rather than the edges already there, so lines crossing the removed area stay straighter.

Where this stops

The scene was generated for this page; carve times are from one Mac on the date above. caire is MIT-licensed. Papers: Avidan and Shamir, Seam Carving for Content-Aware Image Resizing, SIGGRAPH 2007; Rubinstein, Shamir and Avidan, Improved Seam Carving for Video Retargeting, SIGGRAPH 2008.