Als Google die Schließung von Squoosh ankündigte Anfang 2023, verlor die Webentwickler-Community ein wertvolles Tool für clientseitige Bildkompression. Bei OneImage sahen wir eine Gelegenheit, nicht nur diese Funktionalität zu bewahren, sondern sie zu verbessern. Dieser Artikel beschreibt den technischen Ansatz, den wir verfolgt haben, um eine produktionsreife, verbesserte Version von Squoosh zu bauen, die Datenschutz, Performance und Entwicklererfahrung priorisiert.
Google Squoosh's Architektur verstehen
Google Squoosh war Pionier im Konzept, Bild-Encoder vollständig im Browser mit WebAssembly (WASM) auszuführen. Die ursprüngliche Architektur bestand aus:
- Clientseitige Verarbeitung: Alle Kompression erfolgt lokal und gewährleistet Datenschutz
- WebAssembly-Codecs: Native-Speed-Encoder kompiliert zu WASM (MozJPEG, OxiPNG, WebP, AVIF)
- Web Workers: Entlastung schwerer Berechnungen zur Vermeidung von UI-Blockierung
- Canvas API: Bildmanipulation und Vorschau-Generierung
Obwohl bahnbrechend, hatte Squoosh Einschränkungen:
- PNG-Kompression verließ sich allein auf OxiPNG, das Kompressionsrate über Geschwindigkeit priorisierte
- Keine eingebauten Batch-Verarbeitungsfähigkeiten
- Begrenzte Preset-Konfigurationen für gängige Anwendungsfälle
- Eng gekoppelte UI und Kompressionslogik
Unsere Verbesserungsstrategie
1. Integration von libimagequant-wasm für überlegene PNG-Kompression
Der Eckpfeiler unserer Verbesserung ist libimagequant-wasm, ein WebAssembly-Port der industriestandardmäßigen pngquant-Bibliothek. Diese Bibliothek verwendet einen ausgeklügelten Farbquantisierungs-Algorithmus, der visuell überlegene Ergebnisse im Vergleich zur einfachen Palettenreduktion produziert.
Warum libimagequant?
- Perzeptuelle Qualität: Verwendet einen modifizierten Median-Cut-Algorithmus, optimiert für menschliche Wahrnehmung
- Adaptive Paletten: Generiert optimale Paletten von 2-256 Farben basierend auf Bildinhalt
- Transparenzbehandlung: Bewahrt Alpha-Kanäle während der Kompression
- Performance: Läuft dank WASM mit nahezu nativer Geschwindigkeit
Implementierungsdetails
So haben wir libimagequant in unsere Kompressions-Pipeline integriert:
import LibImageQuant from '@fe-daily/libimagequant-wasm';
import * as wasmModule from '@fe-daily/libimagequant-wasm/wasm/libimagequant_wasm.js';
async function compressPNG(imageData: ImageData, level: number): Promise<Uint8Array> {
const quantizer = new LibImageQuant({ wasmModule });
// Kompressionslevel (0-10) auf Farbanzahl (256-2) abbilden
const maxColors = Math.max(2, 256 - (25.6 * level));
const quantized = await quantizer.quantizeImageData(imageData, {
maxColors: Math.floor(maxColors),
speed: 1, // Balance zwischen Qualität und Geschwindigkeit
quality: {
min: 0,
target: 100 // Höchste Qualität innerhalb Farblimit anstreben
}
});
return new Uint8Array(quantized.pngBytes);
}
Wichtige Parameter erklärt:
maxColors: Steuert die Palettengröße. Weniger Farben = kleinere Datei, aber potenziell schlechtere Qualitätspeed: Bereich 1-10, wobei 1 am langsamsten aber höchste Qualität istquality.target: Setzt den Zielqualitätsschwellenwert (0-100)
Unser Squoosh-Tool verwendet diese Implementierung, um Kompressionsraten von 60-80% mit minimalem Wahrnehmungsverlust zu liefern.
2. Architektur eines robusten Web Worker-Systems
Um zu verhindern, dass der Browser während der Kompression einfriert (besonders bei großen Bildern oder Batch-Operationen), haben wir eine dedizierte Web Worker-Architektur gebaut:
// compression-worker.ts
import { EncoderOptions, CompressResult } from './squoosh-types';
import LibImageQuant from '@fe-daily/libimagequant-wasm';
import * as wasmModule from '@fe-daily/libimagequant-wasm/wasm/libimagequant_wasm.js';
interface CompressMessage {
type: 'compress';
imageData: ImageData;
options: EncoderOptions;
}
self.onmessage = async (e: MessageEvent<CompressMessage>) => {
const { type, imageData, options } = e.data;
if (type === 'compress') {
try {
const result = await compress(imageData, options);
self.postMessage({ success: true, result });
} catch (error) {
self.postMessage({
success: false,
error: error instanceof Error ? error.message : 'Unbekannter Fehler',
});
}
}
};
async function compress(
imageData: ImageData,
options: EncoderOptions
): Promise<CompressResult> {
switch (options.type) {
case 'png':
return await compressPNGWithQuantization(imageData, options);
case 'jpeg':
return await compressJPEG(imageData, options);
case 'webp':
return await compressWebP(imageData, options);
case 'avif':
return await compressAVIF(imageData, options);
}
}
Worker-Vorteile:
- Nicht-blockierende UI während Kompression
- Fähigkeit, langläufige Operationen abzubrechen
- Parallele Verarbeitung für Batch-Operationen (mehrere Worker)
- Speicherisolierung verhindert Main-Thread-Speicherlecks
3. Smarte Kompressions-Presets bauen
Anstatt rohe Encoder-Parameter freizulegen, haben wir drei Presets erstellt, die für gängige Anwendungsfälle optimiert sind:
const PRESET_CONFIGS = {
highQuality: {
png: { level: 3 }, // ~200 Farben
jpeg: { quality: 90 },
webp: { quality: 90 },
avif: { quality: 85 }
},
balanced: {
png: { level: 5 }, // ~128 Farben
jpeg: { quality: 80 },
webp: { quality: 80 },
avif: { quality: 70 }
},
minSize: {
png: { level: 8 }, // ~50 Farben
jpeg: { quality: 60 },
webp: { quality: 60 },
avif: { quality: 50 }
}
};
Diese Presets wurden durch umfangreiche Tests auf verschiedenen Bildtypen (Fotos, Illustrationen, Screenshots, UI-Elemente) kalibriert, um optimale Qualitäts-Größen-Kompromisse zu finden.
4. Implementierung effizienter Batch-Verarbeitung
Für Benutzer, die mehrere Bilder komprimieren, haben wir ein Warteschlangensystem mit Fortschrittsverfolgung gebaut:
class BatchCompressor {
private queue: BatchItem[] = [];
private activeWorkers: Set<Worker> = new Set();
private maxConcurrency = navigator.hardwareConcurrency || 4;
async processBatch(files: File[], options: ProcessorOptions) {
const batchId = Date.now();
for (const file of files) {
this.queue.push({
id: `${batchId}-${file.name}`,
file,
status: 'pending',
options
});
}
await this.processQueue();
}
private async processQueue() {
while (this.queue.some(item => item.status === 'pending')) {
if (this.activeWorkers.size < this.maxConcurrency) {
const item = this.queue.find(i => i.status === 'pending');
if (item) {
item.status = 'processing';
await this.processItem(item);
}
} else {
await new Promise(resolve => setTimeout(resolve, 100));
}
}
}
}
Dieser Ansatz:
- Maximiert CPU-Auslastung durch parallele Worker
- Verhindert Browser-Abstürze durch Begrenzung der Parallelität
- Bietet Echtzeit-Fortschrittsupdates für Benutzer
Probieren Sie unsere Batch-Verarbeitung in Aktion auf OneImage Squoosh.
Performance-Optimierungen
Speicherverwaltung
Große Bilder können schnell Browser-Speicher erschöpfen. Wir haben mehrere Minderungsstrategien implementiert:
async function processLargeImage(file: File): Promise<CompressResult> {
const MAX_DIMENSION = 4096;
const img = await loadImage(file);
// Bei Bedarf herunterskalieren
let { width, height } = img;
if (width > MAX_DIMENSION || height > MAX_DIMENSION) {
const scale = Math.min(MAX_DIMENSION / width, MAX_DIMENSION / height);
width = Math.floor(width * scale);
height = Math.floor(height * scale);
}
// OffscreenCanvas verwenden, wenn verfügbar für bessere Speicherbehandlung
const canvas = typeof OffscreenCanvas !== 'undefined'
? new OffscreenCanvas(width, height)
: document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
const imageData = ctx.getImageData(0, 0, width, height);
// Canvas- und Bild-Ressourcen sofort freigeben
URL.revokeObjectURL(img.src);
return await compress(imageData, options);
}
WASM-Modul-Caching
WebAssembly-Module profitieren von aggressivem Caching:
let cachedWasmModule: typeof wasmModule | null = null;
async function getWasmModule() {
if (!cachedWasmModule) {
cachedWasmModule = await import(
'@fe-daily/libimagequant-wasm/wasm/libimagequant_wasm.js'
);
}
return cachedWasmModule;
}
Dies reduziert die Initialisierungszeit von ~500ms auf nahezu sofort bei nachfolgenden Kompressionen.
Erweiterung der Werkzeugkiste
Während Squoosh sich auf Kompression konzentriert, haben wir eine vollständige Suite komplementärer Tools gebaut:
- Image Resize: Intelligente Skalierung mit mehreren Algorithmen
- EXIF Remover: Metadaten für Datenschutz entfernen
- Image Overlay: Wasserzeichen und Branding
- Image Blur: Schwärzung für sensible Inhalte
- Crop Tool: Präzises Seitenverhältnis-Zuschneiden
Alle Tools teilen dieselben architektonischen Prinzipien: Datenschutz zuerst, WASM-betrieben und vollständig clientseitig.
Browser-Erweiterungs-Integration
Wir haben die Web-App-Architektur in eine Browser-Erweiterung erweitert, die ermöglicht:
- Sofortiger Zugriff über Toolbar-Popup
- Kontextmenü-Integration für Rechtsklick-Kompression
- Tab-basiertes Zustandsmanagement
- Lokale Speicherung für Preset-Präferenzen
Die Erweiterung verwendet dieselbe Web Worker- und WASM-Infrastruktur wieder und gewährleistet konsistentes Verhalten über Plattformen hinweg.
Deployment und Infrastruktur
Edge Computing mit Cloudflare
Wir deployen OneImage Squoosh auf Cloudflare Pages und nutzen:
- Globales CDN für <50ms initiale Ladezeiten weltweit
- HTTP/3 und Brotli-Kompression für Assets
- Intelligente Caching-Header für WASM-Module
- Null Cold Starts (nur statische Assets)
Build-Optimierung
Unsere Next.js-Konfiguration beinhaltet:
// next.config.ts
module.exports = {
webpack: (config, { isServer }) => {
// Unterstützung für .wasm-Dateien
config.experiments = {
asyncWebAssembly: true,
layers: true,
};
// Worker-Imports optimieren
config.module.rules.push({
test: /\.worker\.(ts|js)$/,
use: { loader: 'worker-loader' }
});
return config;
},
// Aggressives Code-Splitting
experimental: {
optimizePackageImports: [
'@jsquash/jpeg',
'@jsquash/png',
'@jsquash/webp',
'@jsquash/avif'
]
}
};
Dies stellt sicher, dass Encoder on-demand geladen werden und das initiale Bundle unter 100KB (gzippt) bleibt.
Testing und Qualitätssicherung
Wir pflegen umfassende Testabdeckung für Kompressionslogik:
// __tests__/advanced-compressor.test.ts
describe('AdvancedImageCompressor', () => {
it('sollte PNG mit libimagequant komprimieren', async () => {
const compressor = new AdvancedImageCompressor();
const mockFile = createMockImageFile('test.png', 1000, 1000);
const result = await compressor.compress(mockFile, {
encode: { type: 'png', options: { level: 5 } }
});
expect(result.format).toBe('png');
expect(result.size).toBeLessThan(mockFile.size);
expect(result.data).toBeInstanceOf(Uint8Array);
});
it('sollte Batch-Verarbeitung mit Parallelität handhaben', async () => {
const files = Array(10).fill(null).map((_, i) =>
createMockImageFile(`test-${i}.png`, 500, 500)
);
const startTime = Date.now();
await batchCompress(files, { preset: 'balanced' });
const duration = Date.now() - startTime;
// Sollte schneller sein als sequentielle Verarbeitung
expect(duration).toBeLessThan(10 * 1000); // <1s pro Bild
});
});
Gelernte Lektionen
- WASM ist produktionsreif: Mit ordnungsgemäßem Modulladen und Caching rivalisiert WASM-Performance mit nativen Anwendungen
- Web Workers sind essentiell: Für jede CPU-intensive Aufgabe ist Entlastung auf Worker nicht verhandelbar
- Benutzer-Presets > rohe Kontrollen: Die meisten Benutzer bevorzugen "gute Standards" gegenüber granularem Tuning
- Speicher zählt: Profilieren Sie immer Speichernutzung bei großen Bildern und implementieren Sie Schutzmaßnahmen
- Datenschutz verkauft sich: Die Betonung von "keine Server-Uploads" kommt bei Benutzern stark an
Open Source und Community
Obwohl OneImage Squoosh ein kommerzielles Produkt ist, tragen wir zum Ökosystem bei:
- Bug-Reports und PRs an @jsquash-Maintainer
- Dokumentationsverbesserungen für libimagequant-wasm
- Teilen von Performance-Benchmarks und Best Practices
Fazit
Ein verbessertes Squoosh zu bauen erforderte mehr als nur die Integration von libimagequant-wasm. Es erforderte sorgfältige architektonische Entscheidungen rund um Web Workers, Speicherverwaltung, Benutzererfahrung und Deployment-Infrastruktur. Das Ergebnis ist ein Tool, das die Privatsphäre der Benutzer respektiert und gleichzeitig professionelle Kompressionsleistung liefert.
Probieren Sie OneImage Squoosh heute aus oder erkunden Sie unsere Browser-Erweiterung für noch schnelleren Zugriff. Für Entwickler, die ähnliche Tools bauen, hoffen wir, dass dieser technische Deep-Dive eine nützliche Blaupause bietet.
---
Referenzen und weiterführende Literatur
- Google Squoosh (archiviert) - Original-Projekt-Repository
- libimagequant-wasm - WebAssembly-Port von pngquant
- pngquant - Original-libimagequant-Bibliothek
- @jsquash - WebAssembly-Bild-Codec-Sammlung
- WebAssembly-Dokumentation - Mozilla Developer Network
- Web Workers API - MDN-Leitfaden
- Canvas API - Bildmanipulations-Referenz
- Bildkompressions-Best-Practices - web.dev-Leitfaden
- OneImage Squoosh - Tool ausprobieren
- OneImage Browser-Erweiterung - Browser-Erweiterungsversion
- Bildformat-Leitfaden - Umfassender Format-Vergleich
