Blog · September 16, 2026

The stripes in your gradient aren't a JPEG problem.

You export the screenshot set, open the first frame, and the deep navy background has stripes in it: a staircase of flat bands where a smooth fade should be. The reflex is to blame compression, so you re-export as PNG at maximum quality — and the stripes are still there. The encoder never added them. They're in the pixel values themselves, and they come from arithmetic.

Where the stripes come from

Almost every image you will ever ship stores red, green and blue as one byte each: 256 possible values per channel. A gradient is a request to move smoothly from one triple to another, but the renderer can only write whole numbers, so it rounds. How visible the rounding is comes down to one ratio — how far the color travels, divided by how many pixels it has to travel across.

Take a tasteful, dark, "premium" background: #12141C at the top of an iPhone screenshot fading to #1B1F2E at the bottom. That's a big design decision and a tiny numeric one. Red moves from 18 to 27, green from 20 to 31, blue from 28 to 46. The busiest channel, blue, has 18 distinct values to cover 2,868 pixels of height, so each one becomes a flat band 159 pixels tall, and the eye reads the boundary between neighbouring bands as a drawn line. Run the same height from black to white instead and you get 255 steps of about 11 pixels each, which nobody can see. Subtle gradients band and dramatic ones don't, for the same reason: the subtle one has fewer numbers to spend.

Why the dark ones are the worst

Two things gang up on dark gradients. The first is that your visual system is edge-obsessed. Neighbouring cells in the retina inhibit each other, which sharpens boundaries: put two flat patches of nearly the same tone side by side and each one looks brighter along the edge that faces its darker neighbour. Ernst Mach described the effect in the 1860s, and the phantom light and dark fringes at every step are named after him. A one-value change that should be invisible gets outlined for free.

The second is that a step isn't the same size everywhere. Going from value 10 to 11 is a ten percent change; going from 200 to 201 is half a percent. The sRGB curve exists partly to soften this, spending more of its 256 codes on dark tones — the halfway code, 128, lands at only about 22% of the panel's full brightness. It helps, and it isn't enough, which is why streaming video bands in night scenes and rarely in daylight ones, and why HDR pipelines moved to ten bits — 1,024 values per channel — in the first place. Your store screenshots don't get ten bits. Apple takes PNG or JPEG; Google Play takes JPEG or 24-bit PNG. Eight bits per channel is the ceiling, so the fix has to live inside those 256 values.

What re-exporting actually changes

JPEG makes banding worse, but it doesn't cause it. The encoder works in 8×8 blocks and throws away fine detail first, so it can turn a soft boundary into a blocky one and erase any noise that was hiding the steps. But a lossless PNG of the same 8-bit pixels has the same 18 blue values in the same 18 bands. The quality slider changes how faithfully the stripes are reproduced, not whether they exist. It's also worth knowing that neither store serves the file you upload: both re-encode screenshots for delivery at whatever size the device asks for, so the encoder at the end of the chain was never yours anyway. Everything you can control happens before export.

Fix one: change the arithmetic

The cleanest fix is to give the gradient more numbers or fewer pixels. Widen the color distance, so the two ends differ by 40 or 60 values in at least one channel instead of 18. Shorten the run, so a fade that used to span the whole frame covers the top third and then settles into flat color, which can't band. Or move the hue as well as the brightness: a navy that also drifts slightly toward violet has three channels stepping at different heights, so the boundaries stop lining up and no single line runs the full width of the frame. And use the composition. The device, the headline and any shape you place across the zone where the bands would be hide most of what remains.

Fix two: dither

Dithering is the fix printers, video encoders and Photoshop's gradient tool — it's the "Dither" checkbox in the options bar — have relied on for decades. Instead of rounding every pixel in a band to the same value, you add a tiny amount of noise before rounding, so the pixels near a boundary become a scattered mix of the two neighbouring values. The average tone is still a smooth ramp, the hard line is gone, and the eye, which is poor at seeing individual pixels but excellent at seeing edges, blends the scatter into a fade. At 100% zoom it costs almost nothing. The catch is that you have to know whether your renderer does it. Nothing in CSS asks for dithering, so a browser gradient gets whatever its rasterizer decides; export tools vary; and the only reliable check is to zoom into the darkest part of an actual export.

Fix three: grain you can see

Designers have solved this with a texture layer for as long as their tools have allowed it, and it's the one fix that improves the image even where there were no bands. A film-grain overlay at two to four percent opacity does what dithering does with a slightly larger, deliberate speckle, and it reads as print texture rather than as noise — it's most of what makes a flat dark background look like a lit surface instead of a screen. It's what ShotCanvas does on 23 of its 26 templates: the mobile app paints deterministic grain, roughly one speck per 55 square pixels split half light and half dark, at 3% opacity on dark backgrounds and 1.4% on light ones, and Studio's Aurora template tiles fractal noise at 3.2% over its near-black sky for the same reason. The grain is seeded, so two exports of the same design are pixel-identical, and it's scaled to the export size, so it stays fine at 2,868 pixels instead of turning into confetti. One caveat applies to any grain: noise is the first thing a JPEG encoder throws away, so it protects a PNG far better than a heavily compressed JPEG.

How to check before you ship

Judge the export, not the editor. Zoom to 100% on the darkest region, then look at the same file on an OLED phone at low brightness, which is the harshest test a screenshot will face: OLED black is true black, and every step above it is fully visible. Temporarily pushing the contrast up in any image viewer will exaggerate the bands so you can count them. If you can count them, so can a user scrolling your listing in bed with the lights off.

The version to remember: banding is arithmetic, not compression. A subtle gradient over a long run has too few values to spend. Give it more distance, more channels, or grain, and check the darkest corner of the real export at 100% on a phone.

Where it bites hardest: the store carousel

Store screenshots are the perfect storm for this — dark, subtle backgrounds are the default "premium" choice, the frames are nearly 3,000 pixels tall, and they're viewed on phones, often at night. A set that looks flawless on a bright monitor can arrive on the listing with rings around the device. ShotCanvas bakes the grain into the render on its lit templates, at every export size, so the fade you see in the editor is the fade the store receives.

Export a set without the stripes Why your brand color looks different on every phone