Pecah Logo Wall Jadi Tile: 196 Jadi 113, Grid Makin Sehat
Satu gambar raksasa dinding logo klien saya pecah jadi 113 tile seragam: segmentasi whitespace, kanvas 220 x 130, lalu grid next/image yang stabil.
Ringkasan
Halaman homepage tadinya nge-load satu gambar raksasa berisi dinding logo klien. Penulis motong jadi tile pakai scan baris-kolom piksel putih, samain ukuran via ImageOps.pad, terus render pakai next/image dengan lazy loading. HTTP/2 bikin banyak file gak masalah, dan gerbang mutu nyingkirin tile jelek sampai tinggal 113.
Tiga tab network saya buka berurutan, dan yang kelihatan cuma satu item mencurigakan: satu request gambar raksasa dari halaman klien. Sumbernya gampang dilacak. Halaman 21 company profile, dinding logo klien yang discan jadi satu file utuh, lalu di-embed utuh juga di homepage.
Percobaan pertama saya justru arah sebaliknya: jangan dipecah. Puluhan file kecil berarti puluhan request HTTP, dan di kepala saya masih nempel pelajaran lama soal browser yang cuma buka enam koneksi per domain. Ternyata itu cerita lama. Lewat HTTP/2, request jalan paralel dalam satu koneksi yang sama [4], jadi banyaknya file bukan lagi hukuman otomatis.
Alasan beneran buat memecah gambar itu jadi tile ada di tempat lain, ada tiga: layout yang stabil, lazy loading per logo, dan ukuran yang bisa ngikutin lebar layar. Cerita rebuild-nya kayak gini.
Segmentasi whitespace, bukan machine learning
Teknik buat motong raster jadi blok rapi udah ada sejak era document analysis: recursive X-Y cut, segmentasi lewat projection profile cuts [5]. Intinya sederhana. Baris yang isinya cuma piksel putih artinya celah; deretan celah itu jadi garis potong. Untuk dinding logo yang rapi, pendekatan ini cukup. Gak perlu OpenCV berat, apalagi model.
Praktiknya: threshold dulu gambarnya jadi hitam-putih, scan jumlah piksel non-putih per baris dan per kolom, lalu area konten di antara dua celah lebar jadi kandidat potongan. Kalau butuh otomatisasi penuh, tersedia skimage.measure.label [6] buat nandain tiap region terhubung sebagai satu logo. Untuk kasus saya, versi scan baris-kolom aja udah selesai dalam hitungan detik.
Cara ngecek hasilnya juga murah:
from PIL import Image
im = Image.open("wall.png").convert("L")
w, h = im.size
px = im.load()
for y in range(0, h, 2):
nonwhite = sum(1 for x in range(0, w, 2) if px[x, y] < 245)
print(y, nonwhite)
Baris dengan angka nol berjajar rapat itu calon garis potong. Kalau angkanya nol terus tanpa blok lonjakan yang jelas, berarti threshold-nya salah tangkap atau gambarnya perlu dibersihin dulu.
Kanvas seragam 220 x 130
Hasil potongan mentah tingginya beda-beda, dan grid yang isinya tile macem-macem rasio bakal keliatan berantakan. Solusinya satu: tiap potongan dipaksa masuk kanvas putih berukuran sama, 220 x 130 piksel. Fungsi ImageOps.pad di Pillow [1] persis untuk ini, resize plus padding sekaligus ke rasio yang diminta. Rasio asli tiap logo tetap terjaga, gak ada yang gepeng.
Tanpa langkah ini, kerapian grid bergantung pada kebetulan. Dengan langkah ini, frontend bisa nerima aset apa pun tanpa penyesuaian manual.
Kontrak frontend dan gerbang mutu
Di sisi Next.js, komponen client-strip.tsx merender grid responsif: tiga kolom di ponsel, naik lima, tujuh, sampai sembilan di desktop. Tiap tile dirender lewat komponen Image dari next/image.
Dua atribut wajib: width dan height, supaya browser bisa reserve space sejak awal dan mencegah layout shift [2]. Tambahkan sizes sebagai petunjuk seberapa besar tile dirender di tiap breakpoint, jadi browser bisa milih file yang pas.
Karena sekumpulan logo klien ini murni dekoratif, alt="" [2] plus aria-hidden cukup; screen reader gak perlu baca 113 kali nama perusahaan. Terakhir, loading="lazy" [3] nunda fetch tile yang di bawah fold sampai user scroll mendekat.
Nilai sizes-nya sendiri gak perlu mistis: sekitar 11 persen lebar layar di desktop, 14 di tablet, 30 di ponsel, ngikutin jumlah kolom tiap breakpoint. Setelah tayang, cek lewat devtools bahwa file yang ke-download beneran versi kecil, bukan kanvas aslinya.
Bagian yang paling sering dilewatin orang: gerbang mutu. Hasil potongan pertama adalah 196 tile, dan tidak semuanya layak tayang. Ada fragmen terpotong yang jelek, ada duplikat. Setelah dibersihin dua gelombang, angkanya berhenti di 113. Sisanya gak saya paksain masuk.
Detail kecil yang menyelamatkan: penomoran tile berbasis slot. Setiap tile punya nomor tetap, dan kalau satu nomor gugur di gerbang mutu, nomor itu dibiarkan kosong di konstanta jumlah, bukan digeser. Akibatnya logo tetangga gak akan nempel jadi satu blok aneh cuma karena satu tile dibuang.
Keputusan akhirnya sederhana: gambar utuh itu gak balik lagi. Yang berubah sebenarnya bukan tampilan, tapi siapa yang pegang kendali ukuran. Sekarang browser yang nentuin, bukan file gambarnya.
## Sources [1] https://pillow.readthedocs.io/en/stable/reference/ImageOps.html [2] https://nextjs.org/docs/app/api-reference/components/image [3] https://developer.mozilla.org/en-US/docs/Web/Performance/Guides/Lazy_loading [4] https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Evolution_of_HTTP [5] https://www.haralick.org/conferences/71280952.pdf [6] https://scikit-image.org/docs/dev/api/skimage.measure.html