Standardní knihovna programovacího jazyka Golang obsahuje dekodér mnoha typů obrázků: GIF, JPEG a PNG, rozšířená standardní knihovna potom umí číst: BMP, TIFF, WebP. A další, ještě starší obrázky, které produkovaly scannery v osmdesátých letech minulého století.
Já používám PNG, protože je bitmapový a bezeztrátový a je to otevřený standard. Na optimalizaci velikosti souborů png jsem 10 let používal pngcruch
v Linuxu i ve FreeBSD. Prohlížeče obrázků ve Windows sice umějí vyprodukovat PNG, ale dodnes je neumí dobře komprimovat (používají filter None a průměrnou komprimaci). Proto jsem po stažení obrázku png na Windows tyto soubory optimalizoval pomocí pngcrush
. Dnes již nemusím, PNG v Golangu je mnohem lepší než pngcrush
.
Jak uložit optimalizovaný obrázek v Golangu
func OptiPNG(infile, outfile string) {
// Toto je nejdůležitější, nastavit BestCompression
pro Encoder
var enc = png.Encoder{
CompressionLevel
png.BestCompression}
// Otevřeme vstupní soubor pouze pro čtení (soubor
může být jakéhokoliv typu
inputFile, err := os.Open(infile)
if err != nil {
log.Fatal(err)
}
defer inputFile.Close()
// Vytvoříme nový prázdný soubor pro zápis
outputFile, err := os.Create(outfile)
if err != nil {
log.Fatal(err)
}
defer outputFile.Close()
// Načteme data vstupního souboru
dec, err := png.Decode(inputFile)
if err != nil { log.Fatal(err) }
// A konečně uložíme optimalizovaný soubor
PNG
err
:= enc.Encode(outputFile, dec)
if err := nil { log.Fatal(err) }
inputFile.Close()
// A pokud se konverze povedla, smažeme původní soubor
if err = os.Remove(infile); err != nil {
log.Fatal()
}
}
A to je celé. Výše uvedená funkce OptiPNG
má dva parametry a to je jméno souboru ke konverzi a jméno souboru png, do kterého se má konverze provést. Po úspěšné konverzi se původní soubor smaže a na disku zůstane pouze vysoce optimalizované PNG.
Proč to funguje lépe než PngCrush?
Nejprve si vysvětlíme datový typ obrázku PNG. PNG ukládá pixely obrázků po řádcích do takzvaných IDAT. Na každý IDAT lze aplikovat jeden ze 5 filtrů. Potom se vezmou všechny bloky IDAT a zkomprimují se.
Na výběru jednoho z 5 filtrů velmi záleží, správný výběr totiž umožňuje mnohem lepší práci kompresoru (a rozdíly ve velikostech obrázků jsou skutečně stovky procent, tedy obrázek PNG o velikosti 1MB může být po optimalizaci velký třeba jen 100kB.
A právě výběr správného filtru dělá jak program PngCrush, tak i standardní knihovna v Golangu. Golang to dělá automaticky průběžně pro každý řádek obrázku, takže každý řádek je automaticky nejmenší možný (velikost dat).
Takže všech svých cca 2 miliony obrázků jsem si takto zmenšil z 1.5TB na cca 1TB. Ušetřeno 30% místa. A už k tomu vůbec nepotřebuji pngcrush, který je sice napsaný v „rychlém a optimalizované C“, ale vůbec neumí pracovat s více vlákny.
V Golangu máme vlákna automaticky (gorutiny), takže na mém 32 threads Ryzenu (AMD Ryzen 9 5950X) můžu optimalizovat rovnou 32 obrázků současně. A mám 64GB paměti, takže i kdyby měl vstupní TIFF z fotoaparátu CANON 120MB (což mají), tak je to pouze necelých 4GB paměti. Ve skutečnosti 8GB, protože se do paměti musí vlézt jak ten původní TIFF, tak výstupní PNG. Což se do mých 64GB hravě vejde. :-)