cd /news/developer-tools/pillow-sharp-or-mozjpeg-for-a-white-… · home › topics › developer-tools › article
[ARTICLE · art-156648] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

Pillow, sharp or mozjpeg for a white-background product photo? Same quality, different stray pixels

A developer benchmarked Pillow, sharp, and sharp's mozjpeg option on three white-background product photos, finding that enabling mozjpeg: true produced files 23–28% smaller at quality 85 with 4:2:0 and reduced stray non-white background pixels from 0.481% to 0.347% on average. Sharp's default and Pillow produced identical stray-pixel shares, while mozjpeg at quality 85 with 4:4:4 matched Pillow's quality-95 stray share in under half the bytes. None of the encoders achieved a fully pure 255 background, which matters for marketplace rules like Amazon's pure-white main-image requirement.

by read4 min views1 publishedOct 24, 2026

I needed JPEGs of three white-background product photos and had three encoders to hand: Pillow, sharp, and sharp with its mozjpeg flag. After saving the same three PNGs all three ways: Pillow and plain sharp leave identical pixels, and the mozjpeg: true flag gave smaller files with fewer stray background pixels at the same quality. None of them gets the background to 100% pure 255. Unless I say otherwise the numbers below are means over three photos on one Apple M4 Mac, with Pillow 11.3.0 and sharp 0.34.5. KB here means 1024 bytes.

The photos are three shots from Wikimedia Commons, not mine: a teddy bear on a blue knit blanket, a Bohemian glass against a grey wall, and a pair of white sneakers on beige tile. All three are CC BY-SA 4.0, with credits under the chart. I made the white-background PNGs in ImgIng (https://imging.ai/) in desktop Chromium: AI cutout on the quick setting, background set to white, exported as PNG. Its export row after a cutout lists WEBP, PNG, AVIF, TIFF and GIF with no visible JPG, so everything JPEG below happened afterwards.

"Background" means the pixels the cutout left fully transparent. In the PNG every one of them is 255, and I decoded each JPEG with Pillow and counted the ones where any channel is no longer 255. Amazon's US seller help page (viewed 2026-10-23) asks main images for a pure white background, RGB 255, 255, 255, and says JPEG is recommended, which is why I care about that last digit.

At quality 85 with 4:2:0 chroma sampling, which is the default for both, the stray share was identical for every photo: 0.656% on the sneakers, 0.286% on the teddy bear, 0.503% on the glass. Sharp's files came out a few KB smaller, 210 against 216 KB on the sneakers. The cjpeg on my machine is libjpeg-turbo, not mozjpeg, and matched Pillow byte for byte, so I left it out.

ImgIng's compress and convert tool is the other way I got JPEG. At 85 its file was within a few KB of sharp's default (211 against 210 KB on the sneakers), and the stray share matched Pillow's at the same quality. Its output measured 4:2:0.

Same quality, same sampling, only mozjpeg: true added. Three-photo means:

Encoder Quality Sampling Mean size Stray background pixels
Pillow 85 4:2:0 175 KB 0.481%
sharp default 85 4:2:0 165 KB 0.481%
sharp mozjpeg 85 4:2:0 125 KB 0.347%
Pillow 95 4:4:4 304 KB 0.123%
sharp mozjpeg 85 4:4:4 141 KB 0.114%

Against sharp's default at the same quality and sampling, the mozjpeg files were 38 to 41% smaller at quality 75, 23 to 28% at 85 and 13 to 20% at 95, per photo. The stray share dropped too. The last two rows are the pair I'd point to: mozjpeg at quality 85 with 4:4:4 ended at about the same stray share as Pillow at 95, in under half the bytes.

Look for the orange sharp mozjpeg line sitting lower and further left than every other one in both panels, meaning smaller files with fewer stray pixels. The ImgIng convert line sits on top of the sharp default line in the left panel. Photos by Dan Bollinger, Kritzolina and JustinLiang522 from Wikimedia Commons, CC BY-SA 4.0, adapted here under the same license.

In Pillow subsampling is 2 for 4:2:0 and 0 for 4:4:4. In sharp the mozjpeg switch is one extra key, and nothing else differs between the second and third rows of the table.

src.save("shoe_pil.jpg", quality=85, subsampling=2)
bg = cut == 0   # alpha of the cutout, 0 = left empty
share = 100 * (np.array(Image.open(p).convert("RGB"))[bg] != 255).any(axis=1).mean()
js
const opts = { quality: 85, chromaSubsampling: "4:2:0" };
await sharp("shoe_white.png").jpeg(opts).toFile("shoe_sharp.jpg");
await sharp("shoe_white.png").jpeg({ ...opts, mozjpeg: true }).toFile("shoe_moz.jpg");

The comment is the real output for the sneaker photo.

For a white product photo I'd use sharp with mozjpeg: true, at quality 85 with 4:4:4 when the stray share matters and with 4:2:0 when the file size does. Chroma sampling mattered more than quality in my numbers: moving Pillow from 4:2:0 to 4:4:4 at quality 85 roughly halved the stray share, and going from 85 to 95 at 4:2:0 cut it by about a third.

Nothing reached zero. Pillow at quality 100 with 4:4:4 still had 0.035% stray pixels at an average of 538 KB, and mozjpeg at 95 with 4:4:4 had 0.045%. So I'd check the finished JPEG instead of trusting a quality number. That's three photos on one machine, so the numbers aren't a promise about your images. I haven't tried other mozjpeg builds, or checked whether any marketplace rejects a background that reads 254 in a few pixels.

── more in #developer-tools 4 stories · sorted by recency
── more on @pillow 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/pillow-sharp-or-mozj…] indexed:0 read:4min 2026-10-24 · —