Topic: Compression - AsaMarjew/GameBuddy GitHub Wiki
Auteur: Philip van Egmond
Compression
Compression is het kleiner maken van bestandsgrootte. Het gebruiken van compressed files is belangrijk voor meerdere redenen. Hoe kleiner de files die je ontvangt, hoe minder data opslag je nodig hebt. Dit is vooral belangrijk voor grote webapps met veel dataverkeer maar andersom, voor de gebruiker, is het ook nuttig. Vooral voor gebruikers met minder goed internet is het fijn als images niet hun originele 4k resolutie hebben wanneer ze maar in 100x100 pixels worden laten zien als profielfoto. Compressie zorgt dus voor minder dataopslag en snellere laadtijden.
Compression voor GameBuddy
Inmiddels is image compression zeer geanvanceerd. Er zijn verscheidene ingewikkelde compressie methoden waarvan meestal meerderen tegelijk op één image worden toegepast. Gelukkig zijn er libraries met allerlei opties die je kan gebruiken, zoals Browser Image Compression op npm en Compressor.js waar je compressie kwan tweaken zoals je zelf wil.
Deze libraries werken goed, na de demo te hebben geprobeerd, en zijn dus ook goede keuzes voor het project. Echter vind ik image compressie interessant en wil liever zelf gaan kijken wat ik kan doen om filesize kleiner te maken in plaats van deze libraries te gebuiken. Documentatie van libraries lezen ga ik nog vaak genoeg doen dus hier leer ik meer van.
Plan van Aanpak
Er zijn twee makkelijke manieren van afbeelding compressie. De eerste is het aantal pixels verminderen en de tweede is converteren naar .JPEG. De eerste stap is de geüploade afbeelding pakken, een nieuwe image element aanmaken waar je de width en height van verkleind, vervolgens deze nieuwe afbeelding naar JPEG converteren zodat je deze kleinere en gecomprimeerde afbeelding in de database kan stoppen.
JPEG
JPEG is een bestandstype dat compressie uitvoert op afbeeldingen. Het werkt in zeven stappen:
- Color Space Conversion
- Chroma Subsampling
- Discrete Cosine Transform
- Quantization
- Zig-Zag arrangement
- Run-length Encoding
- Huffman Coding
In stap 1 worden de color values van de afbeelding van RGB naar YCbCr geconverteerd. In RGB heb je 3 kanalen die de kleur bepalen. YCbCr heeft één kanaal voor de licht intensiteit en twee voor de kleurwaarden.
Omdat het menselijk oog gevoeliger is voor verschil in licht intensiteit dan verschil in kleur worden in stap 2 de twee kleuren kanalen ingekort om ruimte te besparen.
Stap 3 komt voort uit het feit dat mensen kleine dingen sneller over het oog zien dan grote dingen. De afbeelding wordt in 64 blokjes ingedeeld, deze blokjes worden vervolgens vergeleken met 64 frequentie patronen. De brightness waarden van deze blokjes worden geconverteerd naar frequentie waarden. Zo kom je met een nieuwe set data waar lage waarden minder goed zichtbaar zijn en hoge waarden beter zichtbaar.
In stap 4 worden deze minder zichtbare waarden vervolgens door grotere constanten gedeeld en naar helen afgerond dan de beter zichtbare waarden, waardoor de minder zichtbare delen van de afbeelding meer gecomprimeerd worden.
Na het kwantiseren van deze blokjes blijven er veel nullen over. De string kan efficiënter gestructureerd worden door de nullen zoveel mogelijk te groeperen. In stap 5 wordt dat gedaan door het schema van de 8x8 pixels op een zig zag manier op te slaan, in plaats van links naar recht.
Met stap 6 wordt deze nieuwe string van getallen op een efficiëntere manier geschreven doormiddel van Run-Length Coding. In plaats van getallen die meerdere keren achter elkaar komen allemaal uit te schrijven wordt het aantal opgeschreven met de waarde van het getal. Dus in plaats van 18 nullen op te schrijven krijg je (18, 0).
In stap 7 wordt Huffman Coding toegepast, dit is een manier van getallen in bits opschrijven waar vaker voorkomende getallen in minder bits worden opgeschreven dan hun originele notatie en minder voorkomende getallen in meer bits, waardoor je over een enorme string zichtbaar efficiënter opslaat.
Uitvoering
Wanneer je bij het aanmelden een foto upload activeert de compress functie doormiddel van een onchange op de file input. Het eerste wat het doet is de enctype van de form aanpassen zodat Multer de image upload niet overneemt en we dat zelf kunnen regelen. Vervolgens lezen we deze image in als datastring. Wanneer de string geladen is maken we een nieuw img element aan die we deze data string als src geven zodat we een html kopie van de foto hebben. Hierna maken we een canvas element aan die we schalen tot een breedte van 150px. Dit canvas element vullen we met de img die we net hebben aangemaakt. Als laatst halen we de content van de canvas op als jpeg image data string met een compressie sterkte van 1 en gebruiken dit als upload naar de server.
function compress() {
const file = document.querySelector('#comp').files[0];
if (!file) return;
// als js beschikbaar is verander de form enctype zodat multer niet activeert
const form = document.querySelector('form');
form.setAttribute('enctype', 'application/x-www-form-urlencoded');
// leest de image in als string
const reader = new FileReader();
reader.readAsDataURL(file);
// activeert wanneer de data URL is ingelezen
reader.onload = event => {
// maak een nieuwe img element met de ulr encoded data van de image als src
const imgEl = document.createElement('img');
imgEl.src = event.target.result;
imgEl.onload = e => {
// maak een canvas met met een width van 150 en hoogte vanuit aspect ratio van de image berekend
const canvas = document.createElement('canvas');
const maxWidth = 150;
const scaleSize = maxWidth / e.target.width;
canvas.width = maxWidth;
canvas.height = e.target.height * scaleSize;
// geef aan dat de canvas 2D is en teken de image hierin
const ctx = canvas.getContext('2d');
ctx.drawImage(e.target, 0, 0, canvas.width, canvas.height);
// converteer de content van het canvas naar image url met bestandstype jpeg en een compressie kwaliteit van 1
const srcEncoded = ctx.canvas.toDataURL('image/jpeg', 1);
const imgOutput = document.getElementById('imgOutput');
imgOutput.value = srcEncoded;
};
};
}
Omdat er een javascript bestand nodig is moet er een fallback zijn voor de mensen die javascript niet aan hebben staan, anders kan je geen image uploaden. De standaard enctype van de form is multipart/data-form, dit is wat Multer gebruikt en wanneer het js bestand deze niet aanpast zal multer dus de geselecteerde image uploaden. Je krijgt dan geen compressie voor deze afbeelding maar dat is geen groot probleem omdat er niet veel mensen zijn die js uit hebben staan. De serverside functie die de post request ontvangt checkt eerst of de waarde die javascript meestuurt bestaat. Als het bestaat gebruikt het het resultaat van de compressie om als img de database in te sturen en als het niet bestaat stuurt het de multer upload.
Minification
Minification is vergelijkbaar met compressie, maar niet helemaal hetzelfde omdat bij minification geen extra stap, zoals decompression, nodig is om hetzelfde resultaat als de originele versie te krijgen. Minification is het verwijderen van alle onnodige karakters in code zonder de functionaliteit te veranderen. Dit zijn dingen als whitespaces, new lines en comments, maar ook code herschrijven in shorthand. Met minification is de bestandsgrootte van de code kleiner, waardoor gebruikers het sneller kunnen laden in hun browser.
Ik heb minification niet op het project toegepast omdat ik enorm veel tijd kwijt was aan compression.
Bronnen
- Minification (programming). (2021, 7 mei). In Wikipedia. https://en.wikipedia.org/wiki/Minification_(programming)
- Fengyuan, C. (2018). Compressor.js. Github. https://fengyuanchen.github.io/compressorjs/
- CWL, D. (2021, 6 januari). npm: browser-image-compression. Npm. https://www.npmjs.com/package/browser-image-compression
- Leo Isikdogan. (2018, 1 augustus). How Image Compression Works [Video]. YouTube. https://www.youtube.com/watch?v=Ba89cI9eIg8&ab_channel=LeoIsikdogan
- Codú Community. (2020, 1 juli). Compress Images Before Uploading for Performance | JavaScript [Video]. YouTube. https://www.youtube.com/watch?v=bXf_UdyDzSA&ab_channel=Cod%C3%BACommunity
- Detect when a user chooses a file using a file input. (2010, 10 juli). Stack Overflow. https://stackoverflow.com/questions/3218828/detect-when-a-user-chooses-a-file-using-a-file-input