Arsitektur Dithering untuk Optimasi Egress

Dithering mengonversi gambar gradien kontinu menjadi representasi palet warna terbatas (misalnya 1-bit monokrom atau 4-warna) tanpa menghilangkan ilusi kedalaman visual. Teknik ini memangkas ukuran aset secara drastis—sering kali mereduksi payload hingga 80-90% dibandingkan JPEG/WebP standar—sehingga menekan biaya transfer data (egress) dan mempercepat render pada koneksi rendah atau layar e-ink.

Implementasi pipeline dithering berfokus pada trade-off lokasi pemrosesan: memproses seluruh aset di muka saat build pipeline (batch build-time) atau merendernya secara dinamis via Edge Worker berbasis WebAssembly (WASM). Pilihan ini dipengaruhi oleh karakteristik dua algoritma utama:

  • Ordered Dithering (Bayer Matrix): Algoritma berbasis matriks threshold deterministik. Komputasi bersifat point-wise tanpa ketergantungan antar-piksel, menjadikannya operasi O(1) per piksel yang sangat kompatibel dengan paralelisasi SIMD dan ramah beban CPU rendah.
  • Floyd-Steinberg Dithering: Algoritma error diffusion yang menyebarkan residu kuantisasi ke piksel tetangga (kanan, bawah-kiri, bawah, bawah-kanan). Proses bersifat sekuensial baris demi baris, membutuhkan ketergantungan memori lokal, dan memakan siklus CPU lebih tinggi.

Pendekatan 1: Build-Time Batch Processing (Sharp/libvips)

Pada pendekatan ini, pemrosesan dithering didelegasikan ke pipeline CI/CD atau script build berkala. Gambar sumber diproses menggunakan sharp (pembungkus C++ libvips) dan hasilnya disimpan langsung ke object storage (seperti AWS S3 atau Cloudflare R2), lalu didistribusikan melalui CDN statis.

Contoh Implementasi: Batch Script dengan Sharp

import sharp from "sharp";
import { promises as fs } from "fs";
import path from "path";

async function ditherImage(inputPath, outputPath) {
  // Floyd-Steinberg via libvips color quantization (1-bit output)
  await sharp(inputPath)
    .resize({ width: 800, withoutEnlargement: true })
    .grayscale()
    .threshold(128, { grayscale: true }) // Alternatif: kuantisasi palet kustom
    .png({ colours: 2, dither: 1.0 })
    .toFile(outputPath);
}

// ponytail: single-threaded loop; add p-limit worker pool when catalog > 1000 items
async function run() {
  const files = await fs.readdir("./raw-assets");
  for (const file of files) {
    if (file.endsWith(".png") || file.endsWith(".jpg")) {
      await ditherImage(
        path.join("./raw-assets", file),
        path.join("./dist-assets", path.parse(file).name + ".png")
      );
    }
  }
}

run().catch(console.error);

Karakteristik Teknis Build-Time

  • Biaya Komputasi Runtime Nol: Permintaan pengguna murni dilayani oleh CDN cache atau static bucket. Tidak ada risiko cold-start maupun batas waktu eksekusi runtime.
  • Dampak CI/CD: Skalabilitas linier negatif terhadap volume aset. Katalog dengan 50.000 gambar akan meningkatkan durasi build pipeline secara ekstrem jika tidak menggunakan incremental build hashing yang ketat.
  • Storage Footprint: Setiap kombinasi variasi (misal: 1-bit hitam-putih, 2-bit grayscale, thumbnail) membutuhkan objek terpisah di S3, melipatgandakan biaya penyimpanan statis.

Pendekatan 2: On-Demand Dynamic Processing (Edge Worker + WASM/Rust)

Pendekatan edge memindahkan transformasi ke CDN edge node (seperti Cloudflare Workers, Fastly Compute@Edge). Worker mengekstrak gambar asli dari origin, menjalankan algoritma dithering yang dikompilasi ke WebAssembly (WASM), mengembalikan output terkompresi, dan menyimpannya di Edge Cache API.

Contoh Implementasi: Ordered Dithering Core (Rust -> WASM)

// Rust: Bayer 4x4 Ordered Dithering
const BAYER_4X4: [f32; 16] = [
     0.0/16.0,  8.0/16.0,  2.0/16.0, 10.0/16.0,
    12.0/16.0,  4.0/16.0, 14.0/16.0,  6.0/16.0,
     3.0/16.0, 11.0/16.0,  1.0/16.0,  9.0/16.0,
    15.0/16.0,  7.0/16.0, 13.0/16.0,  5.0/16.0,
];

pub fn ordered_dither(pixels: &mut [u8], width: usize, height: usize) {
    for y in 0..height {
        for x in 0..width {
            let idx = y * width + x;
            let old_val = pixels[idx] as f32 / 255.0;
            let threshold = BAYER_4X4[(y % 4) * 4 + (x % 4)];
            pixels[idx] = if old_val > threshold { 255 } else { 0 };
        }
    }
}
// skipped: SIMD vectorization; add when targeting AVX2/Neon edge runtimes

Karakteristik Teknis Edge Worker

  • Batasan Eksekusi CPU: Worker serverless memiliki plafon CPU ketat (umumnya 10ms–50ms per request pada tier standar). Floyd-Steinberg pada gambar resolusi > 1080p dapat melampaui batas ini dan memicu kegagalan runtime (Worker CPU Time Exceeded). Bayer dithering jauh lebih aman di lingkungan ini.
  • Cold Start dan Cache Hit Ratio: Request pertama (cache-miss) menanggung overhead: fetch origin, inisialisasi modul WASM, decode PNG/JPEG, kalkulasi dither, encode, dan write-back ke edge cache. Jika cache hit ratio rendah (< 85%), tail latency (p99) akan memburuk.
  • Struktur Biaya: Menghilangkan storage varian S3, namun memunculkan biaya komputasi edge per request yang belum tercache (compute duration + worker invocation cost).

Perbandingan Parameter Arsitektur

Metrik / ParameterBuild-Time (Sharp/CI)On-Demand Edge (WASM)
Algoritma OptimalFloyd-Steinberg & OrderedOrdered (Bayer)
Biaya Storage OriginTinggi (N varian per gambar)Rendah (Hanya gambar master)
Biaya ComputeCI/CD Runner minutesEdge Invocations + CPU time
Cache InvalidationSederhana (Atomic deploy)Kompleks (Purge edge cache)
Latency (p50 / p99)Sangat Rendah (CDN static hit)Bervariasi (Tergantung Cache Hit %)
Batas Ukuran GambarHampir tak terbatas (RAM host)Ketat (< 2-4MB input limit)

Evaluasi Beban Kerja: Katalog Statis vs UGC

1. Katalog Statis (E-commerce, Dokumentasi, Portofolio)

Aset jarang berubah dan variasinya dapat diprediksi saat deploy. Pola akses mengikuti kurva Zipfian terdistribusi normal.

  • Rekomendasi: Gunakan Build-Time Batch Processing.
  • Alasan: Durasi eksekusi CI/CD dapat diisolasi dengan caching artefak (misal: hashing nama file source). Menghilangkan risiko eksekusi CPU edge terlampaui dan menjamin cache hit 100% pada layer CDN tanpa cold-start.

2. User-Generated Content (UGC & Media Dinamis)

Pengguna mengunggah gambar resolusi sembarang secara kontinu. Tidak memungkinkan melakukan rebuild CI/CD setiap kali ada upload baru.

  • Rekomendasi: Gunakan On-Demand Edge Worker dengan Bayer Dithering, atau Asynchronous Event-Driven Pipeline.
  • Alasan Teknis: Jika transformasi harus real-time on-the-fly, gunakan Rust/WASM dengan algoritma Ordered Dithering untuk menjamin durasi eksekusi tetap di bawah ambang batas batas CPU worker (misal < 20ms).
  • Alternatif Pragmatis: Jika Floyd-Steinberg mutlak dibutuhkan untuk UGC resolusi tinggi, jangan jalankan di Edge Worker. Gunakan arsitektur asinkron: upload ke S3 -> trigger AWS Lambda/Cloud Run (libvips) -> simpan output dither ke S3 varian.

Peringatan Produksi: Hindari menjalankan Floyd-Steinberg dithering beresolusi di atas 1200x1200px di dalam edge worker standar tanpa isolated execution plan. Operasi error-diffusion sekuensial pada buffer memori besar akan memicu pemutusan thread worker oleh runtime watchdog.