Pixel sorting: how it works, with code
Bright parts of a picture melt into long, smooth streaks while the dark parts hold their shape. How pixel sorting works, a canvas starter that sorts its own picture, and the skill that builds a finished glitch-art hero around it.
Free skills to start. Pro is $79 a year for every skill, or $9.99 a month for 10. See pricing
What is a pixel sorting?
Pixel sorting takes the pixels along each column or row of an image and puts some of them in order, usually by brightness, sometimes by hue or warmth. Only stretches of pixels that pass a test, such as being brighter than a threshold, are sorted. Everything else stays where it was, so the picture is still recognizable, but its bright areas smear into streaks that run in one direction.
The look was popularized by the artist Kim Asendorf, whose 2010 glitch-art experiments and open-source ASDFPixelSort script for Processing spread it. It now shows up on album covers, fashion and music sites, posters and generative art. It is quieter and more painterly than the torn-channel kind of glitch: the image looks melted, not broken.
How the effect works
Read the pixels into an array, then walk one line at a time. A mask decides which pixels may move: the usual one is brightness above a threshold. Each unbroken run of masked pixels is a span. Sort the span by its key and write the pixels back in the new order. Pixels outside the mask break the spans, which is why dark outlines and shadows survive while skies and highlights melt.
Two settings make most of the look. The threshold sets how much melts: low and almost everything streaks, high and only the sky and the lights do. Direction sets which way it runs: columns give vertical drips and pillars of light, rows give horizontal smears, and sorting brightest-first or darkest-first flips which end of each streak glows.
01
Mask by threshold
Work out each pixel's brightness (0.21 R + 0.72 G + 0.07 B) and mark the ones above the threshold. A second, upper threshold can keep the very brightest pixels in place.
02
Find the spans
Along each column or row, collect every unbroken run of marked pixels. Each run is sorted on its own, so the spans keep the image's shapes.
03
Sort each span
Order the run by brightness, or by hue or warmth for a different streak. Keep the whole color with its key so pixels move as a unit.
04
Write it back
Put the sorted run back where it came from and draw the result. To animate, reveal the sorted image with a sweep or lower the threshold over time.
HTML
Pixel sorting on a canvas in plain JavaScript
Paints its own dusk scene, sorts bright spans in every column once on a small buffer, then sweeps the sorted version in and out. Set VERTICAL to false for row streaks, and change THRESHOLD to melt more or less. Reduced motion gets the sorted frame.
<canvas id="sort" style="display:block;width:100%;max-width:960px;margin:auto;background:#120c1f"></canvas>
<script>
const W = 480, H = 300 // sort a small buffer once, let CSS scale it up
const THRESHOLD = 0.3 // pixels brighter than this (0 to 1) get sorted
const VERTICAL = true // false sorts along rows instead of columns
const canvas = document.getElementById('sort')
canvas.width = W; canvas.height = H
const g = canvas.getContext('2d')
const still = matchMedia('(prefers-reduced-motion: reduce)').matches
// A picture to sort, painted here so there is no image to load
function paint(c) {
const horizon = H * 0.6
const sky = c.createLinearGradient(0, 0, 0, horizon)
sky.addColorStop(0, '#1d1543'); sky.addColorStop(1, '#ff8a5c')
c.fillStyle = sky; c.fillRect(0, 0, W, horizon)
for (let i = 0; i < 18; i++) { // thin bands of lit cloud
c.fillStyle = 'rgba(255,200,170,' + (0.15 + Math.random() * 0.35) + ')'
c.fillRect(Math.random() * W - 60, H * (0.08 + Math.random() * 0.4), 60 + Math.random() * 200, 2 + Math.random() * 4)
}
c.fillStyle = '#ffe2b8'
c.beginPath(); c.arc(W * 0.62, horizon - H * 0.12, H * 0.1, 0, Math.PI * 2); c.fill()
const lake = c.createLinearGradient(0, horizon, 0, H)
lake.addColorStop(0, '#d9645a'); lake.addColorStop(1, '#1d1543')
c.fillStyle = lake; c.fillRect(0, horizon, W, H - horizon)
c.fillStyle = '#ffe2b8'
for (let y = horizon + 4; y < H; y += 7) c.fillRect(W * 0.62 - 30 + Math.random() * 20, y, 22, 2)
const hills = ['#a8506a', '#6e2f5e', '#3d1d45']
hills.forEach((color, i) => {
c.fillStyle = color
c.beginPath(); c.moveTo(0, horizon)
for (let x = 0; x <= W; x += 8) {
const y = horizon - 18 - i * 14 - Math.sin(x * (0.012 + i * 0.006) + i * 2) * (14 + i * 6)
c.lineTo(x, y)
}
c.lineTo(W, horizon); c.closePath(); c.fill()
})
const img = c.getImageData(0, 0, W, H)
for (let p = 0; p < img.data.length; p += 4) { // a little grain gives the sort something to order
const n = (Math.random() - 0.5) * 24
img.data[p] += n; img.data[p + 1] += n; img.data[p + 2] += n
}
return img
}
function sortSpans(img) {
const d = img.data, out = new Uint8ClampedArray(d)
const lines = VERTICAL ? W : H, len = VERTICAL ? H : W
const at = (line, i) => (VERTICAL ? i * W + line : line * W + i) * 4
const lum = (p) => (0.2126 * d[p] + 0.7152 * d[p + 1] + 0.0722 * d[p + 2]) / 255
for (let line = 0; line < lines; line++) {
let i = 0
while (i < len) {
if (lum(at(line, i)) <= THRESHOLD) { i++; continue } // 1. mask: dark pixels stay put
let end = i // 2. find where this bright span ends
while (end < len && lum(at(line, end)) > THRESHOLD) end++
const span = []
for (let k = i; k < end; k++) { const p = at(line, k); span.push([lum(p), d[p], d[p + 1], d[p + 2]]) }
span.sort((a, b) => b[0] - a[0]) // 3. sort it, brightest first
span.forEach((s, k) => { const p = at(line, i + k); out[p] = s[1]; out[p + 1] = s[2]; out[p + 2] = s[3] }) // 4. write back
i = end
}
}
return new ImageData(out, W, H)
}
const layer = (img) => { const c = document.createElement('canvas'); c.width = W; c.height = H; c.getContext('2d').putImageData(img, 0, 0); return c }
const source = paint(g)
const before = layer(source), after = layer(sortSpans(source)) // the sort runs once, not every frame
const CYCLE = 7 // seconds: sweep in, hold, sweep out, hold
const ease = (x) => x < 0 ? 0 : x > 1 ? 1 : x * x * (3 - 2 * x)
function frame(t) {
const s = (t / 1000) % CYCLE
const k = still ? 1 : s < 3.5 ? ease((s - 0.3) / 1.5) : 1 - ease((s - 5) / 1.5)
const x = Math.round(W * k)
g.drawImage(before, 0, 0)
if (x > 0) g.drawImage(after, 0, 0, x, H, 0, 0, x, H)
if (x > 0 && x < W) { g.fillStyle = 'rgba(255,240,220,0.8)'; g.fillRect(x - 1, 0, 1, H) }
if (!still) requestAnimationFrame(frame)
}
requestAnimationFrame(frame)
</script>A starting point written for this page, free to use in your own projects. The skills below build the finished version with checks, fallbacks and a recorded video.
What to get right on a real page
01
Sort once, or sort small
A 1080p image has about two million pixels, and sorting them all every frame on the main thread will stutter. Sort once and cache the result, sort a downscaled buffer as the starter does, or move the work to a worker or a shader.
02
Keep faces and text legible
Sorted faces turn into smears, and sorted letters disappear. Raise the threshold, add an upper threshold so highlights stay put, or mask the subject out of the sort. Keep the headline as real text beside or over the image, not inside it.
03
Use your own images
Reading pixels from an image on another domain without CORS headers blocks getImageData with a security error. Serve the image from your site, or load it with crossOrigin and the right headers.
04
Hold a still frame for reduced motion
Respect prefers-reduced-motion: show one finished frame instead of the loop. A visitor who turned motion off still gets the look.
Skills that build a pixel sorting
A finished hero built on real pixel sorting, with your colors, fonts, logo and words, and a 1080p video of the loop for launch posts.
- Pixel Sort Glitch HeroClaude CodeCodexCursor+3 more agents
Motion Heroes · Pro
Pixel Sort Glitch Hero
Animated canvas hero · page + embed + 1080p video
A framed print of a dusk landscape beside your headline, painted and pixel-sorted for real on a plain 2D canvas. The print is cut into vertical bands; a sweep lowers each band's threshold in turn so the sky melts into streaks and the sun pulls up into pillars, then the scene re-forms. Most bands sort by brightness, a few by warmth, and you set the low and high thresholds, the amount, the sweep side and the pixel size.
Worth knowing: The landscape is generated from your four colors, not your own photo.
Needs: Node.js 18+, Chrome (or Chromium/Edge), and ffmpeg for the video
See what Pixel Sort Glitch Hero makes
Install
Build a pixel sorting with your agent
Each skill is a SKILL.md folder, so it installs the same way in Claude Code, Codex, Cursor and other agents. The example below uses Pixel Sort Glitch Hero; each skill page has its own prompt.
1. Paste the install prompt
Your agent runs the installer. The first time, it shows a sign-in link so you can approve it in the browser.
Install the "Pixel Sort Glitch Hero" skill from Lookbk (https://lookbk.dev/skills/pixel-sort-glitch-hero). 1. Run: curl -fsSL https://lookbk.dev/cli.js | node - add pixel-sort-glitch-hero 2. If it prints a sign-in link, show it to me. When I say I approved it, run the same command again. 3. When the install finishes, read the SKILL.md it installed and tell me what you need from me to make the first result.
Or run it in a terminal
curl -fsSL https://lookbk.dev/cli.js | node - add pixel-sort-glitch-hero2. Start a new session
Agents load skills when a session starts. The installer writes the folder where your agent looks:
- Claude Code
- ~/.claude/skills/pixel-sort-glitch-hero
- Codex
- ~/.agents/skills/pixel-sort-glitch-hero
- Cursor
- ~/.agents/skills/pixel-sort-glitch-hero
3. Ask for the result
Describe it in plain words, for example:
Build a Pixel Sort Glitch hero for Halfsleep Records, a slow electronic label: near-black page, violet and coral dusk landscape, headline 'Records for the last light.', caption 'HS-031 · Ferry Lights', and record a 12-second loop.
Another agent? Install a skill in any agent
FAQ
Questions
Still stuck? The guides cover installing, agents and licensing in more detail.
Read the guidesCan I pixel sort with CSS only?
No. CSS cannot read or reorder an image's pixels. You need a canvas or a WebGL shader, or you can sort the image once in a script and use the result as a plain picture.
How do I pick the threshold?
Start near the middle (0.3 to 0.5 brightness) and move it while watching the image. Too low and the whole picture melts, too high and nothing does. Pictures with a clear bright sky or light source against darker shapes sort best.
Can I pixel sort a video or animate it?
Yes, by sorting every frame, which is costly at full size, or by sorting once and animating a reveal or a falling threshold. Pixel Sort Glitch Hero sorts its print every frame, at two CSS pixels per sorted pixel by default.
How is pixel sorting different from a glitch effect?
Glitch effects usually split color channels and shift slices of the image to look like a broken signal. Pixel sorting reorders the pixels themselves, so the image looks melted along one direction rather than torn.
Can I get the pixel-sorted hero as a video?
Yes. Pixel Sort Glitch Hero records a seamless 1080p MP4 of its loop and a poster, with portrait sizes for social posts.
