Afbeelding groter na comprimeren? De 3 echte oorzaken
Er is waarschijnlijk niets mis met je compressor. Als het comprimeren van afbeeldingen niet werkt en het resultaat even groot terugkomt, of merkbaar groter dan wat je erin stopte, ligt de oorzaak vrijwel altijd bij één van drie dingen. Een kapotte tool hoort daar niet bij.
Eerst de meest voorkomende. Elke tool die comprimeert via het canvas-element van de browser gooit het color type van je PNG weg en encodeert elke pixel opnieuw als 32-bits RGBA. We duwden alle 83 Open Graph-kaarten van deze site als PNG door canvas.toBlob('image/png') in Chrome 151.0.0.0. Alle 83 kwamen groter terug. De mediane groei was +76,6%, de kleinste +35,6%, de grootste +237,2%.
De tweede oorzaak: je JPEG is al gecomprimeerd. Haal hem vijf rondes door een encoder op kwaliteit 0.8 en de bestandsgrootte staat na de tweede ronde stil, terwijl elke ronde het beeld verder aantast.
De derde: je PNG is al gekwantiseerd. Er is geen kleurredundantie meer over die een tweede ronde kan weghalen.
Geen van deze drie los je op door de kwaliteitsschuif verder omlaag te trekken. De oplossing is een ander formaat of een andere encoder. Diezelfde 83 PNG’s, omgezet naar WebP op kwaliteit 0.8, werden allemaal kleiner: mediaan −94,2%.
Hoe deze cijfers tot stand kwamen. Chrome 151.0.0.0 aangestuurd door Playwright, macOS 26.5.2, Node v25.8.2, upng-js 2.1.0, ImageMagick. De set van 83 bestanden is elke PNG in de map
public/ogvan deze site, niet een steekproef daaruit. Vier extra gecontroleerde bestanden op 1200×630 en 800×600 dekken de gevallen foto, grafiek, palet en JPEG apart af.
1. Comprimeren van afbeeldingen werkt niet: triage in dertig seconden
Zoek je eigen rij op en lees daarna de sectie waar die naar verwijst.
| Wat je erin stopte | Wat je terugkreeg | Onderliggende oorzaak | Lezen |
|---|---|---|---|
| PNG-schermafbeelding of grafiek | Groter dan het origineel | canvas encodeerde hem opnieuw als 32-bits RGBA | Sectie 2 |
| JPEG-foto | Groter dan het origineel | Hij is opgeslagen als PNG | Sectie 2 |
| JPEG-foto | Nauwelijks verschil | Zit al op zijn plateau qua grootte | Sectie 3 |
| PNG die al door een compressor is gegaan | Geen verschil, of iets groter | Geen speelruimte meer over | Sectie 4 |
| Wat dan ook | Kleiner, maar wazig of verkleurd | Generatieverlies, of een verwijderd ICC-profiel | Secties 3 en 6 |
De rijen sluiten elkaar niet uit. Een JPEG-foto die je in een PNG-export op basis van canvas gooit, raakt de eerste twee tegelijk — zo wordt een bestand van 47.828 bytes er een van 809.415 bytes.
Wil je de diagnose overslaan en gewoon een kleiner bestand? Onze tool om afbeeldingen te comprimeren gebruikt een kwantiserende PNG-encoder in plaats van een omweg via het canvas, en gooit zijn eigen resultaat weg zodra dat niet kleiner is dan wat je hebt geüpload.
2. Oorzaak één: het canvas van de browser schrijft altijd 32-bits RGBA
Wat canvas.toBlob() echt met je PNG doet
In een omweg via het canvas zit geen compressiestap; alles wat geen pixelwaarde is, sneuvelt onderweg.
Teken een afbeelding op een canvas en de browser decodeert hem naar een platte RGBA-buffer: vier bytes per pixel, geen palet, geen trucs met bitdiepte, geen metadata. HTMLCanvasElement.toBlob() encodeert die buffer daarna helemaal opnieuw. De PNG-specificatie definieert zes color types, en een PNG-encoder mag zelf de goedkoopste kiezen die de afbeelding kan weergeven. De canvas-encoder van Chrome kiest niet. Hij levert altijd color type 6.
| Color type dat erin gaat | Wat canvas.toBlob('image/png') teruggeeft |
|---|---|
| RGB (color type 2) | RGBA (color type 6) |
| Palet (color type 3) | RGBA (color type 6) |
| JPEG (heeft geen PNG color type) | RGBA (color type 6) |
Je kunt dit rechtstreeks uit de bytes aflezen. Byte 25 van een PNG-bestand is de bitdiepte en byte 26 het color type, allebei binnen de IHDR-chunk:
xxd -s 24 -l 2 -p suspect.png
# 0806 -> bit depth 8, color type 6 (RGBA)
Alle drie de invoertypen hierboven leverden depth=8 type=6 op. Een afbeelding met een palet van 64 kleuren slaat één byte per pixel op plus een kleine tabel; na de omweg zijn het vier bytes per pixel en is de tabel verdwenen. Deflate haalt daar een deel van terug, maar nooit alles.
Reproduceer het in je eigen browser met dit fragment voor de console. Het laat je een bestand kiezen, haalt het door het canvas en drukt beide groottes af plus het color type:
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/png,image/jpeg';
input.onchange = async () => {
const file = input.files[0];
const bitmap = await createImageBitmap(file);
const canvas = document.createElement('canvas');
canvas.width = bitmap.width;
canvas.height = bitmap.height;
canvas.getContext('2d').drawImage(bitmap, 0, 0);
const blob = await new Promise((r) => canvas.toBlob(r, 'image/png'));
const head = new Uint8Array(await blob.slice(0, 26).arrayBuffer());
console.log(file.name, file.size, '->', blob.size);
console.log('bit depth', head[24], 'color type', head[25]);
};
input.click();
Bij image/png negeert toBlob zijn kwaliteitsargument. PNG is verliesvrij, dus er valt voor een kwaliteitsgetal niets in te ruilen, en een schuifregelaar die in een canvas-tool de PNG-compressie lijkt te sturen, doet in werkelijkheid niets.
83 bestanden gemeten: elke gecomprimeerde afbeelding kwam groter terug dan het origineel
De volledige meting, zonder krenten uit de pap te pikken:
| Meting | Waarde |
|---|---|
| Geteste bestanden | 83 (elke PNG in public/og, zowel color type RGB als palet) |
| Bestanden die groeiden | 83 / 83 (100%) |
| Kleinste toename | +35,6% |
| Mediane toename | +76,6% |
| Grootste toename | +237,2% |
Eén bestand, zodat je de vorm ervan ziet: aes-decrypt.png ging van 505.516 B naar 898.014 B, een toename van +77,6%. Datzelfde bestand als WebP op kwaliteit 0.8 is 27.188 B, −94,6%.
Let op de grens van de steekproef. Deze 83 bestanden zijn Open Graph-kaarten: 1200×630, vlakke achtergronden, grote letters, een handvol merkkleuren. Dat is het soort grafiek waar een goede PNG-encoder raad mee weet, en juist daarom richt een omweg via het canvas bij hen zoveel schade aan. Het resultaat geldt dus sterk voor PNG’s met grafische inhoud; het is geen bewering dat elke PNG ter wereld groeit door een omweg via het canvas. Een fotografische PNG die al als volledige RGBA was opgeslagen, heeft veel minder te verliezen.
Is je gecomprimeerde afbeelding groter dan het origineel en draait de tool in een browsertabblad, kijk dan eerst naar de encoder en pas daarna naar je instellingen.
Waarom een JPEG die je als PNG opslaat 16,9× groter wordt
De grootste uitschieter staat in de laatste rij hieronder. Vier gecontroleerde bestanden, alle vier door hetzelfde canvas-pad gehaald:
| Bestand | Aard | Origineel | canvas-PNG | Verschil | JPEG q92 | JPEG q80 | WebP q80 |
|---|---|---|---|---|---|---|---|
og-a.png | Grafiek, 2.351 kleuren, RGB | 306.302 | 607.481 | +98,3% | 56.459 | 37.695 | 13.852 |
quantized.png | Palet van 64 kleuren | 59.843 | 184.856 | +208,9% | 78.388 | 44.442 | 16.046 |
photo.png | Foto, 479.373 kleuren | 498.639 | 867.763 | +74,0% | 77.321 | 45.050 | 24.068 |
photo.jpg | JPEG q82 | 47.828 | 809.415 | +1.592% (16,9×) | 58.762 (+22,9%) | 47.152 | 23.862 |
Die laatste rij is 47.828 bytes erin, 809.415 bytes eruit.
Het mechanisme erachter verklaart een hele familie meldingen van het type “door comprimeren werd het juist groter”. JPEG is een codec met kwaliteitsverlies die in het frequentiedomein werkt: hij zet blokken van 8×8 om in DCT-coëfficiënten, kwantiseert die stevig en bewaart wat overblijft. PNG is een verliesvrije codec die in het ruimtelijke domein werkt: hij voorspelt elke pixel uit zijn buren en comprimeert de restwaarden met deflate. Decodeer een JPEG en je krijgt pixels waarin elk artefact zit dat de kwantisator heeft toegevoegd: ringing rond randen, blokvorming in vloeiende verlopen, subtiele ruis waar het origineel die niet had.
Sla die pixels op als PNG en je vraagt een verliesvrije codec om de artefacten perfect te bewaren. Dat doet hij netjes. Precies de ruis die JPEG heeft gemaakt om het bestand klein te krijgen, maakt de PNG nu groot, want ruis is nou net wat een voorspellende verliesvrije encoder niet kan comprimeren.
Overal waar een standaardinstelling “opslaan als PNG” voor een foto staat, gebeurt dit: tools voor schermafbeeldingen, exports uit designprogramma’s, chatclients, sommige uploadwidgets.
Dezelfde pixels, 3,46× verschil, allebei verliesvrij
De canvas-encoder is meetbaar zwak. Neem de pixelbuffer van og-a.png en encodeer die op twee manieren, allebei volledig verliesvrij:
| Encoder | Uitvoer | Color type |
|---|---|---|
Chrome canvas toBlob('image/png') | 607.481 B | RGBA (gedwongen opgewaardeerd) |
upng-js encode(..., cnum=0) | 175.491 B | RGB (oorspronkelijke color type behouden) |
3,46×, bij identieke pixels. We hebben de claim van verliesvrijheid gecontroleerd in plaats van aangenomen: als je de uitvoer van upng-js bij cnum=0 decodeert, krijg je een buffer die byte voor byte identiek is aan de RGBA-buffer die erin ging. Voor die 3,46× lever je niets in.
Dat verklaart meteen waarom twee online compressors, allebei gratis, allebei met dezelfde belofte, resultaten geven die niet in de verste verte vergelijkbaar zijn. “Comprimeren in de browser” kan twee volstrekt verschillende implementaties betekenen. De één geeft de pixels door aan canvas.toBlob en levert af wat er terugkomt. De ander heeft een echte PNG-encoder aan boord en bepaalt zelf het color type. Dezelfde invoer, dezelfde browser, 3,46× verschil.
3. Oorzaak twee: je JPEG heeft niets meer te geven
Vijf rondes hercomprimeren, en de grootte staat stil
Neem photo.jpg, die al op kwaliteit 82 was opgeslagen, en comprimeer hem vijf keer achter elkaar opnieuw op kwaliteit 0.8, waarbij elke generatie de volgende voedt:
| Generatie | Bytes | t.o.v. vorige |
|---|---|---|
| 0 (origineel, q82) | 47.828 | — |
| 1 | 47.152 | −1,4% |
| 2 | 47.164 | +0,0% |
| 3 | 47.156 | −0,0% |
| 4 | 47.156 | 0,0% |
| 5 | 47.156 | 0,0% |
De eerste ronde levert je 1,4% op. Vanaf generatie 2 zit de bestandsgrootte vastgeklonken in een band van ±12 bytes, en generatie 4 en 5 zijn precies even groot als generatie 3.
Zo ziet “comprimeren werkt niet” eruit als de invoer een JPEG is. De tool heeft gedraaid, de encoder ook. Er viel niets meer weg te halen, omdat de kwantiseringstabellen op kwaliteit 80 grofweg dezelfde coëfficiënten al op nul zetten die kwaliteit 82 had bewaard. Een coëfficiënt die weg is, kun je geen tweede keer weghalen.
Je kunt het lokaal zelf zien gebeuren:
cp photo.jpg gen0.jpg
for i in 1 2 3 4 5; do
magick "gen$((i-1)).jpg" -quality 80 "gen$i.jpg"
done
stat -f%z gen0.jpg gen1.jpg gen2.jpg gen3.jpg gen4.jpg gen5.jpg
Op Linux gebruik je in plaats daarvan stat -c%s. De exacte bytetellingen hangen af van de encoder waartegen jouw ImageMagick-build linkt, dus verwacht niet dat de tabel hierboven cijfer voor cijfer reproduceert. Het gaat om de vorm: één betekenisvolle daling, daarna een vlakke lijn.
Het kwaliteitsgetal omhoog draaien is geen compressie
Kijk nog eens naar de rij photo.jpg in sectie 2. Dat origineel van kwaliteit 82 opnieuw comprimeren op kwaliteit 92 leverde 58.762 bytes op: +22,9%.
Dat verrast mensen die aannemen dat de kwaliteitsparameter een knop is die van “klein” naar “groot” loopt en die je overal mag neerzetten. Het is geen absoluut kwaliteitsdoel. De parameter kiest een kwantiseringstabel, en een gedecodeerde afbeelding opnieuw door een fijnere tabel halen dan de tabel die hem heeft voortgebracht, slaat de bestaande artefacten nauwkeuriger op én stapelt er een verse ronde verlies bovenop. Groter bestand, slechter beeld, allebei tegelijk.
Hercomprimeer een JPEG dus nooit met een kwaliteitsinstelling die hoger ligt dan die waarmee hij is opgeslagen. Weet je niet welke dat was, comprimeer dan helemaal niet opnieuw en ga terug naar het bronbestand.
Generatieverlies: het kwaliteitsverlies bij JPEG-hercompressie dat je niet ziet
In de plateautabel zit een valkuil. De grootte veranderde na generatie 2 niet meer, maar de afbeelding wel. Elke ronde decodeert naar pixels, transformeert opnieuw en kwantiseert opnieuw. Coëfficiënten die in de ene generatie net aan een grens overleefden, gaan er in de volgende overheen.
De schade laat zich niet zien waar je ernaar zoekt. Op thumbnailformaat zijn generatie 5 en generatie 1 niet uit elkaar te houden. Zoom in tot 100% en controleer de plekken waar JPEG altijd het eerst bezwijkt: harde randen tegen vlakke achtergronden, tekst en vloeiende verlopen waar blokvorming zichtbaar wordt als tegels van 8×8. In een build-pipeline die bij elke deploy opnieuw comprimeert, stapelt dit zich maandenlang geruisloos op.
We hebben hier bestandsgroottes gemeten, geen waargenomen kwaliteit, dus voor deze vijf generaties noemen we geen PSNR- of SSIM-cijfer; we hebben er geen gemeten. De groottedata zeggen op zichzelf al genoeg: vanaf generatie 2 zitten de kosten volledig aan de kwaliteitskant en is de opbrengst nul.
Kleurverschuivingen zijn een aparte storing met dezelfde aanleiding. Canvas draagt geen metadata mee, dus een omweg via toBlob gooit het EXIF-blok weg en het ICC-profiel meteen erbij. Een afbeelding die met Display P3 of Adobe RGB getagd binnenkomt, komt er ongetagd weer uit, en dat lezen viewers als sRGB. De pixelwaarden zijn niet verschoven. De instructies om ze te interpreteren wel.
4. Oorzaak drie: de PNG wordt niet kleiner omdat hij al gekwantiseerd was
Is je PNG al een keer door een compressor gegaan, dan heeft de tweede ronde niets om mee te werken. Dit is de rij quantized.png: een bestand van 59.843 bytes dat al was teruggebracht tot een palet van 64 kleuren, langs drie aparte paden gehaald:
| Pad | Resultaat | t.o.v. origineel |
|---|---|---|
Verliesvrij opnieuw encoderen (upng cnum=0) | 61.377 | +2,6% |
| Kwantiseren naar 256 kleuren | 61.366 | +2,5% |
| Kwantiseren naar 64 kleuren | 61.366 | +2,5% |
Elk pad kwam groter uit dan het origineel. Niet veel groter, maar groter, en dat geldt ook voor het kwantiseren naar 64 kleuren van een bestand dat er al 64 had.
PNG-compressie werkt door redundantie weg te halen: herhaalde kleuren, voorspelbare buren, een klein palet. Een eerdere ronde heeft dat allemaal al opgeraapt. Wat overblijft, is bijna niet te comprimeren, en de minieme toename is de overhead van de encoder zelf: een iets andere volgorde in het palet, andere filterkeuzes per scanline, en een deflate die net iets minder mazzel had.
“0% bespaard” op een al geoptimaliseerde PNG is dus het juiste resultaat, geen storing. Een tool die een kleine toename meldt en daarna jouw originele bestand bewaart, gedraagt zich correct. Een tool die je dat grotere bestand toch meegeeft, doet dat niet.
5. Bij PNG zit de winst in kwantiseren, niet in opnieuw encoderen
Verliesvrij opnieuw encoderen versus 256 kleuren versus 64 kleuren
Drie bestanden, drie strategieën:
| Bestand | Origineel | Verliesvrij (cnum=0) | 256 kleuren | 64 kleuren |
|---|---|---|---|---|
og-a.png | 306.302 | 175.491 (−42,7%) | 110.772 (−63,8%) | 76.230 (−75,1%) |
photo.png | 498.639 | 587.863 (+17,9%) | 109.284 (−78,1%) | 61.397 (−87,7%) |
quantized.png | 59.843 | 61.377 (+2,6%) | 61.366 (+2,5%) | 61.366 (+2,5%) |
Verliesvrij opnieuw encoderen is het zwakste middel. Het won 42,7% op de grafiek, gaf 17,9% terug op de foto en verloor 2,6% op het al gekwantiseerde bestand. Fotografische inhoud is zelfs een competente verliesvrije PNG-encoder de baas, want er valt geen palet te vinden en naburige pixels voorspellen elkaar slecht.
De reductie zit in kwantiseren, en het verschil is groot: 63,8% tegen 42,7% op dezelfde grafiek bij 256 kleuren, en 75,1% bij 64. Op de opdrachtregel:
magick logo.png -colors 64 PNG8:logo-64.png
magick logo.png -colors 256 PNG8:logo-256.png
Het voorvoegsel PNG8: dwingt een PNG met palet af. Zonder dat voorvoegsel kan ImageMagick de kleuren wel terugbrengen en daarna alsnog een truecolor-bestand wegschrijven, waardoor het grootste deel van de winst verdampt.
Wanneer een palet veilig is, en wanneer je banding krijgt
Kwantiseren gaat gepaard met kwaliteitsverlies. Het legt elke pixel op de dichtstbijzijnde ingang in een beperkt palet, dus de vraag is of jouw inhoud genoeg verschillende kleuren heeft om dat zichtbaar te maken.
Veilig: iconen, logo’s, schermafbeeldingen van interfaces, vlakke illustraties, diagrammen, alles met grote vlakken in één kleur en harde randen. Zulke afbeeldingen bevatten meestal hooguit een paar honderd verschillende kleuren, dus een palet van 256 ingangen kost je vrijwel niets en zelfs 64 overleeft het vaak.
Riskant: foto’s, vloeiende verlopen, zachte slagschaduwen en halftransparante overlays. Een verloop terugbrengen tot 64 stappen levert zichtbare banden op, en dithering ruilt die banden in voor ruis, die je vervolgens weer een stuk bestandsgrootte kost. Gedeeltelijke transparantie over een verloop is het lastigste geval van allemaal.
Transparantie verdient een eigen controle, want hoeveel ervan overleeft hangt af van de encoder en niet van het kwantiseren zelf. De PNG8: van ImageMagick schrijft binaire transparantie weg: een pixel is óf volledig dekkend óf volledig doorzichtig, en een zachte anti-aliased rand komt hard terug. Een gespecialiseerde PNG-kwantiseerder houdt het volledige alfakanaal aan, en daarmee ook de zachte rand. Heeft je asset een slagschaduw of vervaagde randen, vergelijk die twee dan voordat je een keuze vastlegt.
Bekijk het resultaat op 100%, niet in een thumbnail. Banding is precies het artefact dat een verkleinde voorvertoning betrouwbaar verbergt.
6. De oplossing: verander van formaat in plaats van opnieuw te comprimeren
Een beslistabel voor formaten
| Inhoud | Gebruik | Waarom |
|---|---|---|
| Foto’s | WebP, of JPEG voor maximale compatibiliteit | Frequentiecodering met kwaliteitsverlies is wat foto’s nodig hebben |
| Schermafbeeldingen, interfacegrafiek | Gekwantiseerde PNG, of WebP | Vlakke kleuren, harde randen, kleine paletten |
| Iconen en logo’s | SVG als je de vector hebt, anders gekwantiseerde PNG | Vectoren kennen geen resolutieprobleem |
| Alles wat transparantie nodig heeft | WebP of PNG | Allebei dragen een volledig alfakanaal |
| Animatie | WebP | Eén formaat in plaats van een GIF |
| Pixelnauwkeurige archivering | PNG, verliesvrij | Het enige geval waarin verliesvrij een eis is |
Dit is bewust de korte versie. De efficiëntie van de encoders en de browserondersteuning van de moderne formaten hebben hun eigen artikel: WebP vs AVIF vs JPEG.
Wat WebP met diezelfde 83 bestanden deed
Dezelfde set van 83 bestanden die via canvas-PNG in 100% van de gevallen groeide, maar nu omgezet naar WebP op kwaliteit 0.8: 83 van de 83 werden kleiner, mediaan −94,2%. Het losse bestand van eerder, aes-decrypt.png, ging van 505.516 B naar 27.188 B, −94,6%.
De gecontroleerde bestanden bevestigen dat. og-a.png: 306.302 als PNG, 13.852 als WebP q80. photo.png: 498.639 als PNG, 24.068 als WebP q80. photo.jpg: 47.828 als JPEG, 23.862 als WebP q80.
Eén kanttekening bij die laatste vergelijking. WebP op kwaliteit 0.8 werkt met kwaliteitsverlies, dus het speelt niet onder gelijke voorwaarden tegen PNG, en een bestaande JPEG opnieuw encoderen naar WebP kost je nog steeds een generatie. Vergelijk dus met wat je op het punt stond uit te leveren, niet met een hypothetisch perfect origineel.
Wanneer je de PNG toch moet houden
Verliesvrij is soms een eis en geen voorkeur. Houd PNG aan voor assets die terug de designpipeline in gaan en opnieuw bewerkt worden, voor schermafbeeldingen die in tests op pixelniveau worden vergeleken en voor interface-uitsnedes waar één verschoven kleur een visuele diff laat mislukken. Hetzelfde geldt voor alles wat later nog samengesteld wordt, waar artefacten van het kwantiseren zich zouden opstapelen. Gebruik in die gevallen een fatsoenlijke kwantiserende encoder als de inhoud dat toelaat, en accepteer de bestandsgrootte als dat niet zo is.
Nog een soort “groter” die niets met comprimeren te maken heeft
Plaats je afbeeldingen inline als data-URI’s, dan is de omvang in je CSS of HTML niet de omvang op schijf. Base64 encodeert elke 3 bytes als 4 tekens, plus padding, dus de tekst is rekenkundig +33% groter dan de bytes die erin zitten, nog vóór enige compressie tijdens het transport. Een perfect geoptimaliseerde afbeelding wordt een derde groter op het moment dat je hem inline plaatst. Wanneer die afweging de moeite waard is, staat in onze gids over data-URI’s inline plaatsen.
7. Aan de slag: browser, opdrachtregel, build-pipeline
In de browser
Bij een tool in de browser is de vraag of PNG door het canvas gaat. In onze tool om afbeeldingen te comprimeren gebeurt dat niet. De tool kwantiseert PNG-invoer naar een kleurenpalet en schrijft die weg als een echte PNG met het alfakanaal intact, zodat transparantie en zachte randen overleven; de kwaliteitsschuif staat voor een paletgrootte en niet voor een toBlob-argument dat PNG toch zou negeren. Kwaliteit 100 komt neer op verliesvrij opnieuw encoderen. JPEG en WebP gaan wel door het canvas, en daar is de kwaliteitsparameter echt en doet hij wat je verwacht.
Eén gedrag lijkt op een bug en is het niet: als het gecomprimeerde resultaat niet kleiner is dan het bestand dat je hebt geüpload, gooit de tool zijn eigen uitvoer weg en houdt hij jouw originele bytes. Bij een al geoptimaliseerde PNG zie je dan “0% bespaard”. Dat is sectie 4 die precies doet wat de bedoeling is.
De verwerking gebeurt lokaal: de tool draait in je browser en er gaat niets naar een server.
Op de opdrachtregel
cwebp zit bij libwebp en is de snelste manier om te testen of een ander formaat je probleem oplost:
# Lossy WebP, quality 0-100
cwebp -q 80 photo.png -o photo.webp
# Lossless WebP, compression effort 0-9
cwebp -z 9 logo.png -o logo.webp
ImageMagick 7 dekt het omzetten en het kwantiseren af:
# PNG to JPEG at a chosen quality
magick photo.png -quality 80 photo.jpg
# Quantize to a 64-color palette PNG
magick logo.png -colors 64 PNG8:logo-64.png
# Drop EXIF and other metadata
magick photo.jpg -strip photo-clean.jpg
Op macOS staat sips er al op en heb je geen afhankelijkheden nodig:
# Convert to JPEG; formatOptions takes 0-100 or low/normal/high/best
sips -s format jpeg -s formatOptions 80 photo.png --out photo.jpg
# Resize so the longest edge is 1200 px, aspect ratio preserved
sips -Z 1200 photo.jpg --out photo-1200.jpg
# Read the dimensions back
sips -g pixelWidth -g pixelHeight photo-1200.jpg
Verklein voordat je comprimeert. Encoders werken op pixels, en de goedkoopste pixel is er een die niet bestaat.
In een build-pipeline
Zodra je dit automatiseert, verschuift de beslissing naar de vraag waar het werk gebeurt en welke library het doet, en dat verandert de afwegingen volledig: afbeeldingcompressie in de browser versus Node.js behandelt die vergelijking. Houd hierbij één regel aan: comprimeer bij elke build vanaf het originele bronbestand, nooit vanaf de uitvoer van de vorige build. Doe je dat niet, dan zakt je pipeline stilletjes langs de curve van het generatieverlies terwijl de bestandsgroottes er volkomen stabiel uitzien.
8. Vijf overtuigingen die de metingen niet ondersteunen
“Twee keer comprimeren maakt het kleiner.” Generatie 1 leverde 1,4% op. Generatie 2 tot en met 5 bleven binnen ±12 bytes terwijl het beeld verder achteruitging. De tweede ronde is pure kostenpost.
“PNG is verliesvrij, dus het is het betere formaat.” Verliesvrij is een eigenschap, geen deugd. Onze testfoto is 498.639 bytes als PNG en 24.068 als WebP q80. Een foto bit-perfect bewaren terwijl hij alleen ooit op een scherm bekeken wordt, levert niets op en kost je het grootste deel van het bestand.
“Kwaliteit 100 is de veilige keuze.” Een JPEG van kwaliteit 82 opnieuw comprimeren op kwaliteit 92 leverde +22,9% en een slechter beeld op. Boven de kwaliteitsinstelling van het origineel betekent het getal niet langer “veiliger”, maar “groter”.
“Het bestand is groot omdat de resolutie hoog is.” Resolutie doet ertoe, maar bij dezelfde resolutie doet het formaat meer. og-a.png is hoe dan ook 1200×630: 607.481 bytes als canvas-PNG, 13.852 bytes als WebP q80. Hetzelfde aantal pixels.
“Online compressors zijn allemaal hetzelfde.” Dezelfde pixels, dezelfde browser, allebei verliesvrij: 607.481 bytes uit canvas, 175.491 uit upng-js. Een spreiding van 3,46× tussen twee tools die zichzelf identiek omschrijven.
9. Veelgestelde vragen
Waarom is mijn gecomprimeerde afbeelding groter dan het origineel?
Omdat de tool hem opnieuw heeft geëncodeerd in plaats van gecomprimeerd. De uitvoer van een canvas in de browser is altijd een PNG met 32-bits RGBA, waarbij paletten sneuvelen en elke pixel vier bytes kost. In onze test met 83 echte PNG’s groeiden alle 83, met een mediane toename van +76,6%. Zet je afbeelding in plaats daarvan om naar WebP of JPEG.
Waarom wordt mijn PNG niet kleiner als ik hem comprimeer?
PNG is verliesvrij, dus een kwaliteitsschuif heeft niets om in te ruilen. De reductie komt van het terugbrengen van het aantal kleuren, en als het bestand al gekwantiseerd was, valt er niets meer terug te brengen. Onze test-PNG met 64 kleuren kwam op +2,5% terug nadat we hem een tweede keer naar 64 kleuren hadden gekwantiseerd.
Verlies je kwaliteit als je een JPEG twee keer comprimeert?
Ja, en je krijgt er bijna niets voor terug. Vijf rondes op kwaliteit 0.8: 47.828 bytes zakte in de eerste ronde naar 47.152 en bleef daarna vier rondes lang binnen 12 bytes hangen. De grootte bewoog niet meer terwijl elke ronde het beeld opnieuw kwantiseerde. Bewaar je originelen.
Kun je een bestand beter als PNG of als JPEG kleiner maken?
Voor foto’s: JPEG of WebP, zonder uitzondering. Onze testfoto mat 498.639 bytes als PNG, 45.050 als JPEG q80 en 24.068 als WebP q80. Houd PNG voor vlakke grafiek, scherpe tekst en transparantie, waar een klein palet het compressiewerk doet.
Waarom werd mijn afbeelding wazig na het comprimeren?
Twee verschillende oorzaken. Encoders die met kwaliteitsverlies werken, geven bij lage kwaliteitsinstellingen zichtbare blokvorming rond randen en tekst. Herhaalde rondes voegen generatieverlies toe, ook als de bestandsgrootte niet meer verandert. Zijn de kleuren verschoven in plaats van verzacht, dan heeft de omweg via het canvas je ICC-profiel weggegooid, want canvas draagt helemaal geen metadata mee.
Kun je een afbeelding comprimeren zonder kwaliteitsverlies?
Ja, maar verwacht er veel minder van. Verliesvrij opnieuw encoderen herschrijft alleen identieke pixels efficiënter: van 306.302 naar 175.491 bytes op onze grafiek, en na decoderen bleek de buffer byte voor byte identiek. Dezelfde aanpak op een foto ging de verkeerde kant op, +17,9%. Wil je echte winst zonder zichtbaar verlies, gebruik dan WebP op kwaliteit 80.
Waarom is mijn PNG zo groot terwijl het maar een schermafbeelding is?
Schermafbeeldingen worden opgeslagen als volledige RGBA-PNG, vier bytes per pixel vóór compressie, en een Retina-scherm verdubbelt het aantal pixels in beide richtingen. Vlakke inhoud reageert goed op het terugbrengen van het palet: onze Open Graph-kaart zakte 63,8% bij 256 kleuren en 75,1% bij 64.
Levert verkleinen meer op dan comprimeren?
Meestal wel, en de twee versterken elkaar. Beide afmetingen halveren haalt driekwart van de pixels weg voordat de encoder begint, en de bestandsgrootte volgt grofweg het aantal pixels. Verklein eerst naar de afmetingen die je daadwerkelijk toont, comprimeer daarna één keer. Een camerabestand op volle resolutie op een plek voor een thumbnail verspilt allebei de bewerkingen.