8 poin oleh GN⁺ 10 hari lalu | 2 komentar | Bagikan ke WhatsApp
  • Perbedaan antara cara standar membagi dengan 255 dan cara alternatif membagi dengan 256 setelah bias 0,5 saat mengonversi warna bilangan bulat 8-bit ke floating-point
  • Cara 255 memetakan bilangan bulat 0 ke 0.0 dan 255 ke 1.0, sehingga hitam dan putih mudah ditangani secara langsung, serta sesuai dengan cara GPU melakukan konversi UNORM-to-float
  • Cara 256 menggunakan (img + 0.5) / 256.0 untuk menempatkan tiap nilai di tengah interval, sehingga penanganan batas lebih sederhana untuk pekerjaan seperti dithering, tetapi 0 tidak lagi menjadi 0.0 sehingga logika pemrosesan terikat pada input 8-bit
  • Pada cara 255, interval di kedua ujung hanya selebar setengah, sehingga jika bilangan acak seragam [0, 1] dibulatkan kembali ke 8-bit, 0 dan 255 muncul setengah lebih sering dibanding nilai lain, tetapi konversi bolak-balik gambar nyata tetap bekerja tanpa kehilangan
  • Jika memproses gambar dari orang lain, jawaban yang benar adalah normalisasi dengan 255; cara 256 hanya layak dipertimbangkan jika Anda mengendalikan penyimpanan dan pemuatan sepenuhnya

Pengaturan masalah

  • Dalam program yang menerima gambar, mengubahnya ke floating-point, memprosesnya, lalu menyimpannya kembali sebagai warna 8-bit, yang menjadi pokok bahasan adalah cara konversi integer ke floating-point
  • Ada dua pendekatan
    • Cara standar (dibagi 255): pixels = img / 255.0 → proses → output = np.trunc(result * 255 + 0.5)
    • Cara alternatif (dibagi 256): pixels = (img + 0.5) / 256.0 → proses → output = np.trunc(result * 256)
    • Pada kedua kasus, nilai dibatasi ke 0–255 sebelum konversi tipe akhir: output.clip(0, 255).astype(np.uint8)
  • Cara standar memetakan integer 0 ke 0.0 dan 255 ke 1.0, sama seperti cara GPU mengonversi UNORM ke float
  • Cara alternatif menambahkan bias 0,5 sehingga integer 0 dipetakan ke 0.5/256 = 0.001953125
    • Akibatnya, tanpa mengetahui konstanta ini, piksel hitam tidak bisa dideteksi
    • Walaupun memakai perhitungan floating-point, logikanya tetap terikat pada input 8-bit
    • Pada cara standar, hitam selalu bisa diasumsikan sebagai 0.0

Sanggahan terhadap 255.0

  • Jika cara standar digambarkan pada garis bilangan, hasilnya terlihat agak aneh
  • Ada bin yang lebih kecil di kedua ujung

    • Bin ujung pada rumus standar menonjol keluar dari rentang [0,1], sehingga bentuk rentangnya tampak “direnggangkan”
    • Saat floating-point dikembalikan ke integer, lebar bin di kedua ujung hanya setengah dari bin lain
      • Ini membuat algoritme menjadi “lebih sulit” menghasilkan nilai ekstrem
      • Jika menghasilkan noise seragam [0,1] lalu membulatkannya dengan rumus standar, nilai 0 dan 255 hanya muncul setengah sesering integer lain
    • Pada histogram satu juta bilangan acak seragam, bisa dilihat bahwa bin 0 dan 255 hanya setengah setinggi bin lain
    • Namun sulit membayangkan situasi nyata di mana bias penghindaran nilai ekstrem ini benar-benar menjadi masalah
      • Gambar asli tetap bisa dikonversi bolak-balik tanpa kehilangan (uint8 → float → uint8)
      • Hasil yang sedikit melewati 0.0 atau 1.0 tetap dibulatkan ke bin yang benar sehingga distribusi output kembali seragam
      • Contoh: jika selama pemrosesan warna dikurangi 0.005, pada cara standar hitam turun di bawah 0, sedangkan pada cara alternatif tetap positif, tetapi keduanya tetap menghasilkan integer 0 pada output akhir
  • Ketidakakuratan

    • Nilai floating-point pada cara standar tidak eksak, misalnya 128/255.0 ≈ 0.501961, sedangkan 128/256.0 = 0.5
    • Jarak antarnilai floating-point sedikit berubah karena galat pembulatan, tetapi galatnya sangat kecil sehingga bukan masalah praktis
      • Floating-point 32-bit memiliki mantissa 23-bit, dan galatnya berada pada tingkat bit paling rendah, kurang dari 2⁻²³
      • Galat relatif 0.00001% tidak berarti bahkan untuk pemrosesan gambar presisi tinggi; ketidakakuratan ini adalah masalah estetika, bukan teknis
  • Nilai yang tidak berada pada rentang integer

    • Cara alternatif menempatkan tiap nilai floating-point tepat di tengah antara dua integer
      • Karena nilai kuantisasi aslinya tidak diketahui, titik tengah dua integer berurutan menjadi kompromi yang baik sebagai estimasi
    • Ada klaim bahwa ini memudahkan dithering (posting blog Andrew Kesler tahun 2015, "Converting Color Depth")
      • Noise bisa ditambahkan tanpa mengkhawatirkan edge case
      • Sebaliknya, nilai ekstrem yang canggung pada rumus standar memerlukan penanganan hati-hati agar distribusi noise tetap konsisten

Dua jenis kuantisator

  • Kedua pendekatan ini bisa dipandang sebagai dua jenis uniform scalar quantizer
  • Menurut artikel Wikipedia tentang kuantisasi, kuantisator seragam untuk data input bertanda diklasifikasikan menjadi dua jenis
    • mid-tread: memetakan 0 ke level rekonstruksi bernilai 0 (bagian pijakan tangga)
    • mid-riser: memetakan 0 ke ambang klasifikasi bernilai 0 (bagian tegak tangga)
    • Wikipedia mengutip makalah tahun 1977 sebagai sumber (Allen Gresho, "Quantization")
  • Rumus kuantisator (L adalah jumlah level output, misalnya 256)
    • Kuantisator tangga mid-tread: encoding k = trunc(xL + 0.5), decoding yₖ = k/L
    • Kuantisator tangga mid-riser: encoding k = trunc(xL), decoding yₖ = (k+0.5)/L
  • Jika diterapkan di sini
    • Rumus standar = mid-tread (L=255)
    • Rumus alternatif = mid-riser (L=256)
  • Cara standar adalah kombinasi penggunaan mid-tread untuk input tak bertanda dengan kode L=255, yang bukan pilihan optimal untuk input 8-bit
    • Ini dipilih demi kemudahan pemrograman karena memetakan kedua ujung ke 0.0 dan 1.0
  • Galat kuantisasi lebih tinggi, tetapi sebenarnya tidak

    • Jika sistemnya adalah mengenkode bilangan riil berdistribusi seragam x∈[0,1] ke integer 8-bit lalu merekonstruksinya kembali menjadi bilangan riil, maka rumus standar memang membuang bandwidth
      • Rentang yang dapat direpresentasikan oleh cara standar adalah [-0.5/255, 255.5/255], lebih lebar dari yang diperlukan untuk input [0,1], sehingga galat rekonstruksi meningkat
      • Menurut perhitungan pengguna StackOverflow Peter Mudrievskij, galat absolut rata-ratanya adalah 1/1020 untuk pembagi 255 dan 1/1024 untuk pembagi 256, sehingga membagi dengan 256 secara teori sedikit lebih presisi
    • Namun pada praktiknya, bukan itu yang sedang dilakukan
      • Asumsinya adalah memuat gambar RGB 8-bit, memprosesnya, lalu menyimpannya kembali; saat penyimpanan, cara kuantisasi tidak bisa dikendalikan, dan informasi yang hilang akan lenyap permanen
      • Jika gambar disimpan dengan dikalikan dan dibulatkan memakai rumus standar, memuatnya kembali dengan membagi 256 tidak bisa memulihkan presisi
      • Klaim galat rekonstruksi yang lebih kecil hanya bermakna jika Anda mengendalikan baik penyimpanan maupun pemuatan
    • Jika memuat gambar milik orang lain dengan rumus alternatif, justru akan menimbulkan lebih banyak galat
      • Kemungkinan besar gambar tersebut dikuantisasi dengan rumus standar, sehingga mendekode dengan skala yang salah secara teori menjadi tidak akurat
      • Dalam praktiknya, karena warna bukan besaran absolut, yang terjadi hanya pemrosesan dalam rentang yang sedikit lebih kecil dengan offset kecil
    • Langkah encoding dan decoding dari kedua kuantisator ini tidak boleh dicampur; ini adalah bentuk kode rusak yang mudah sekali dibuat

Kesimpulan

  • Jika Anda memproses gambar yang diberikan orang lain, maka nilai RGB harus dinormalisasi dengan 255
    • Kekhawatiran tentang nilai floating-point yang tidak eksak atau galat rekonstruksi abstrak bukan alasan yang baik untuk memilih cara alternatif
  • Jika Anda mengendalikan sepenuhnya penyimpanan dan pemuatan gambar, tidak perlu memetakan 0 ke 0, dan tidak masalah jika kode pemrosesan terikat pada rentang dinamis 8-bit, maka membagi dengan 256 bisa memberi sedikit tambahan presisi
    • Namun perhatikan bahwa rekan kerja Anda mungkin memuat gambar dengan rumus standar dan merusak rencananya

Pandangan lain

2 komentar

 
GN⁺ 9 hari lalu
Opini Hacker News
  • Apa tepatnya arti nilai warna biasanya tidak terlalu penting pada 8 bit per komponen. Galat akibat perbedaan penyebut 255 atau 256 sangat kecil, dan untuk melihat bedanya orang harus punya kepekaan warna yang baik serta melihat layar dari jarak sangat dekat, sementara monitor atau layar ponsel pun biasanya tidak terkalibrasi dengan baik
    Tetapi jika kita membuat sinyal VGA dengan mikrokontroler dan hanya punya 8 pin keluaran warna (merah 3, hijau 3, biru 2), ini bisa jadi cukup merepotkan. Dalam kasus ini, nilai warna adalah level tegangan 0V~0.7V yang harus dikirim ke monitor VGA itu sendiri
    Kanal biru dipetakan sebagai 0→0V, 1→0.23V, 2→0.47V, 3→0.7V, sedangkan merah/hijau dipetakan sebagai 0→0V, 1→0.1V, …, 7→0.7V. Jika titik ujung dikecualikan, tegangan biru sama sekali tidak sejajar dengan tegangan merah/hijau, sehingga kita tidak bisa melihat abu-abu murni, dan warna terdekat pun akan sedikit bercampur nuansa biru atau kuning tergantung arah perbedaannya
    Selain itu, hampir semua gradasi yang mencampurkan biru dengan kanal lain juga akan tampak meleset. Misalnya, warna-warna terdekat pada garis dari merah murni ke putih murni akan tampak sedikit oranye atau ungu
    Kode untuk keluaran VGA warna 8 bit dengan framebuffer ganda 320x240 di Raspberry Pi Pico 2 ada di sini: https://github.com/moefh/pico-vga-8bit-demo

    • Saya masih ingat waktu kecil melihat layar CRT ber-noise dan memperhatikan garis biru dan kuning yang samar di tepinya. Saya selalu penasaran kenapa justru dua warna itu; kalau penyebabnya sama, akhirnya sekarang saya paham
    • Koreksi gamma terlupakan. Sebelum mengubah nilai dari rentang 0~255 menjadi tegangan, PC biasanya menaikkan nilainya ke pangkat 2.2
      Dengan begitu, perbedaan antara nilai kecil dan besar menjadi jauh lebih menonjol: 2^2.2 = 4.595, 255^2.2 = 196,964.699
    • Untuk masalah ini, dithering temporal tampaknya paling cocok. Modulasi delta-sigma per piksel cukup mudah dilakukan
      Jika berubah pada 30Hz, manusia tampaknya akan sulit membedakan antara sedikit kebiruan dan sedikit kekuningan
    • Mungkin itu sebabnya warna RGBI begitu umum pada era 80-an
  • Argumen yang mendukung 255 bisa dilihat dari kasus ekstrem berupa gambar hitam-putih. Pada satu bit, 0 adalah hitam dan 1 adalah putih
    Cukup jelas bahwa 0 harus dipetakan ke 0.0 dan 1 ke 1.0. Itu hitam-putih, bukan abu-abu terang (0.25) dan abu-abu gelap (0.75). Artinya, gambar hitam-putih dinormalisasi dengan 1, bukan 2
    Untuk 2 bit, biasanya 0=hitam, 1=abu-abu terang, 2=abu-abu gelap, 3=putih, sehingga wajar dipetakan ke 0.0, 0.33, 0.66, 1.0. Hitam harus tetap hitam, putih harus tetap putih, dan jaraknya juga harus sama, jadi dinormalisasi dengan 3
    Jika logika ini diteruskan sampai 8 bit, hasilnya adalah normalisasi dengan 255. Pada 8 bit pun, meskipun perbedaannya jadi sangat kecil, hitam tetap harus 0.0 dan putih tetap harus 1.0
    Pendekatan lain, yaitu normalisasi 256 pada 8 bit, membuat rentang keluaran berubah sesuai jumlah bit. Pada 1 bit hasilnya jadi [0.25, 0.75], pada 2 bit jadi [0.125, 0.875], dan seterusnya. Biasanya yang diinginkan adalah jumlah nuansa bertambah seiring jumlah bit bertambah, bukan kontrasnya yang berubah

  • Ini benar-benar tulisan yang memancing pemikiran, dan secara pribadi membuat saya meninjau ulang asumsi yang saya pegang
    Dari sudut pandang teknik elektro, saya sulit setuju dengan pemaparan tentang “dua jenis quantizer” dalam tulisan itu. Secara matematis mungkin ketat, tetapi itu bukan penjelasan yang berbasis pada sistem nyata
    ADC pada dasarnya selalu memiliki ketidakpastian kuantisasi ±1/2 LSB. Karakteristik transfernya selalu berupa sampling mid-tread, setidaknya saya belum pernah melihat contoh tandingannya. Ini berlaku baik untuk ADC bipolar maupun unipolar
    Kode terendah merepresentasikan tegangan negatif acuan dan kode tertinggi merepresentasikan tegangan positif acuan. Grafik karakteristik transfer menunjukkan bahwa, seperti yang diperlihatkan dalam tulisan, rentang tertinggi/terendah pada dasarnya memiliki lebar 1/2 LSB
    Dalam sistem unipolar, tegangan tengah tidak bisa direpresentasikan secara tepat; dengan kata lain, masalah abu-abu memang muncul. Dalam sistem bipolar, 0V adalah nilai N/2 pada mid-tread, tetapi itu tidak berarti ada “256 interval”
    Jadi saya akan tetap memakai (VREF+ - VREF-) * k / (2^N - 1). Artinya saya setuju dengan normalisasi 255. Pada akhirnya ini sama seperti kesalahan tiang pagar: nilainya ada N, tetapi intervalnya hanya N-1. Jika jumlah interval lebih sedikit daripada jumlah nilai, satu interval harus dibagi di antara dua nilai, sehingga titik ujung mendapat interval 1/2 LSB

    • Semua dokumen ADC yang pernah saya lihat menyebutkan bahwa skala penuh positif tidak bisa direpresentasikan. Misalnya, pada ADC 8 bit ±1V, -128 berarti -1V, sedangkan +127 berarti 127/128=0.99219V
      Transisi dari 126 ke 127 terjadi pada titik yang berjarak 1.5 LSB dari ujung penuh skala positif. Selisih 1 LSB berarti perbedaan 1/128=0.00781V, bukan 2/255=0.00784V
      Namun dalam praktiknya, jika yang penting adalah tegangan dan ketidakpastian, perbedaan seperti ini hampir selalu tidak berarti. Tegangan referensi punya bias dan ada juga galat linearitas. 1 LSB tidak persis cocok baik dengan 1/128 maupun 2/255, sehingga parameter kalibrasi tetap diperlukan
  • Ini mirip dengan perbedaan antara sampel berpusat pada node dan sampel berpusat pada sel dalam komputasi ilmiah, dilihat dalam satu dimensi. Kita harus memutuskan apakah nilainya berada di tengah interval (atau di tengah segitiga/tetrahedron) atau di batas interval (atau di titik sudut segitiga/tetrahedron)
    Dalam komputasi ilmiah, memulai pemrosesan data tanpa tahu bagaimana nilai itu harus diinterpretasikan tidak masuk akal. Dalam pemrosesan sinyal audio juga begitu: jika kita hanya menerima stream bilangan bulat, kita harus tahu maksud representasi bilangan itu, misalnya apakah itu encoding mu-law atau linear, agar bisa menghitung kembali sinyal aslinya. Kita berharap metadata yang melekat pada nilainya memberikan jawaban itu
    Namun pada nilai piksel 8 bit, jika format file tidak punya metadata yang memadai untuk menyampaikan maksud representasinya, kita akan terombang-ambing dan tidak ada jawaban yang benar. Seperti kata penulis, tidak adil menyalahkan orang yang memilih pendekatan yang memberi hasil lebih baik untuk kebutuhannya sendiri, tetapi kita tetap bisa mengingatkan bahwa bit tanpa konteks berarti maknanya sudah rusak

    • Ini mengingatkan pada nilai normalisasi yang dipakai dalam kuantisasi citra satelit ESA Sentinel-2 level-2
      Kurang lebih seperti ini: Digital Number DN=0 dibiarkan sebagai nilai “NO_DATA”, dan saat DN berada dalam rentang [1; 1;215-1], nilai reflektansi L2A SR dihitung sebagai L2A_SRi = (L2A_DNi + BOA_ADD_OFFSETi) / QUANTIFICATION_VALUE
      https://sentiwiki.copernicus.eu/web/s2-products
  • Ada kekeliruan dengan menganggap ada 256 tingkat dari 0 sampai 255. Sebenarnya ada 256 nilai yang bisa direpresentasikan dengan 8-bit, dan jarak dari 0 (hitam) ke 255 (putih murni) adalah 255 langkah
    Jadi membagi dengan 255 bukanlah masalah. Tentu saja 128 bukan abu-abu setengah yang persis, dan nilai 8-bit terkuantisasi 0~255 hampir selalu berada di sRGB, bukan ruang persepsi linear
    Kebingungan serupa juga muncul di API modern saat menangani posisi sampling. Ini karena posisinya dinyatakan sebagai koordinat, bukan pusat piksel

    • API BeOS berbasis pusat piksel. Sekarang mungkin sudah tidak ada yang peduli
  • Secara aljabar, jawabannya jelas: f(x) -> [0, 255]
    Jika f(n * 0) == n * f(0) tidak berlaku, hal aneh akan terjadi. Misalnya jika f(x) -> [0, 255], maka f(0) + f(0) + f(0) = 0 + 0 + 0 = 0 = f(0)
    Sebaliknya, jika f(x) -> [0.5/8, 7.5/8], maka f(0) + f(0) + f(0) = 0.5/8 + 0.5/8 + 0.5/8 = 1.5/8 != f(0)
    Jika memilih yang belakang, kita tidak bisa berharap perhitungan di sisi x dan perhitungan di sisi f(x) akan saling cocok. Artinya korespondensi aljabar rusak

  • Saya cenderung mendukung solusi +0.5. Pertama, saya tidak suka interval setengah ukuran di tepi, dan kedua, representasi berbasis 255 biasanya bukan HDR melainkan gambar SDR
    Nilai RGB merepresentasikan luminans untuk suatu keadaan adaptasi, dan “0” pada adegan siang bukan berarti “luminans 0”. Itu hanya sekitar 0,001 kali titik paling terang, dan jumlah fotonnya masih jutaan, jauh lebih besar dari 0
    Dalam satu pengertian, mata mengalami kontras sebagai skala yang bergeser, dan tidak ada 0 absolut di dalam sistem. Misalnya, sistem siaran secara historis memakai 16~235 untuk rentang luminans SDR. Saya melihat logika “0 harus ada” sebagai sesuatu yang menimbulkan bias, dan menurut saya dalam kebanyakan kasus 0 tidak diperlukan

    • Dari sudut pandang orang yang banyak mengerjakan pemrosesan dan rendering gambar untuk VFX, sepertinya orang lupa bahwa setelah ini akan ada transformasi ruang warna. Pada SDR lama, ini berubah ke Rec.709 linear dari sRGB, sedangkan pada format yang lebih baru berubah ke gamut yang lebih lebar. Jadi pemampatan rentang dinamis terjadi setelah pemuatan
      Selain itu, cukup banyak workflow pemrosesan gambar dan compositing yang, benar atau salah, mengasumsikan bahwa 0 berarti 0. Karena itu, banyak yang menganggap pada 8-bit, 0u dipetakan ke 0.0f dan 255 ke 1.0f. Jika nilai 0 pada mask atau alpha menjadi sedikit lebih besar dari 0.0, akan muncul artefak ketika ada kode yang memakai ambang keras 0.0 untuk memask operasi lain. Sebaliknya, jika 255 pada alpha tidak lagi bernilai 1.0f, objek akan menjadi sangat sedikit transparan setelah premultiplication
      Hal yang sama bisa terjadi ketika +0.5 membuat 254 menjadi 1.0f dalam masking
    • Tulisannya berfokus pada RGB, tetapi masalah kuantisasi yang sama ada pada semua jenis sinyal yang dipetakan antara representasi diskret dan kontinu
      Intinya bukan apakah kita merepresentasikan 0 foton, melainkan apakah kita memaksimalkan informasi yang disimpan dalam 1 byte. Idealnya, kita tidak boleh mengurangi penggunaan nilai byte 0, dan juga tidak boleh menambahkan bias pada data yang seharusnya masuk ke bucket ke-0. Bahkan dalam ruang warna yang berjalan dari terang ke sangat terang, semua byte seharusnya merepresentasikan potongan rentang kecerahan dengan ukuran yang sama
    • Fakta bahwa sistem siaran secara historis memakai 16~235 untuk rentang luminans SDR justru adalah masalahnya. Sayangnya bahkan HDMI yang “modern” pun masih menderita kebiasaan aneh ini, sehingga jika display dan sumber tidak sepakat, gambar bisa terlihat pudar atau black crush
    • Kedua solusi sama-sama menambahkan 0.5. Bedanya adalah di mana hal itu terjadi dalam prosesnya
    • Ide yang menarik, tetapi rasanya seperti mengguncang dunia. Dari sudut pandang program pemrosesan, hitam lama (0.0) dan putih (1.0) berubah menjadi abu-abu sangat gelap dan abu-abu sangat terang
  • Jika penggaris punya tanda sampai 12 inci, normalisasinya harus berdasarkan panjang L, bukan 13 yang merupakan jumlah titik pada penggaris

    • Analogi itu membingungkan. Saya tidak tahu apakah “penggaris” itu penggaris 255 inci dengan 256 titik bertanda 0~255, atau penggaris 256 inci dengan 256 interval 1 inci sehingga L = 256×1
    • Kalau yang benar-benar ingin dihitung adalah tiang pagar, maka kesalahan tiang pagar bukanlah kesalahan
    • Betul, tetapi >> 8 jauh lebih cepat
    • Siapa yang menetapkan bahwa angka merepresentasikan titik? Bisa saja yang direpresentasikan adalah interval di antara titik-titik itu
    • Atau saya yang bodoh. Bukankah 0 dimulai dari titik awal?
  • Tulisan ini menyenangkan untuk dibaca karena membahas topik yang sudah lama tidak saya pikirkan. Ini mengingatkan saya pada momen-momen di pengembangan game ketika logika game memakai matematika floating-point, tetapi pixel art harus digambar pada koordinat bilangan bulat
    Di beberapa tempat saya memakai pendekatan yang mirip +0.5 agar hasilnya terlihat tidak terlalu aneh. Terutama saat ada kamera yang bergerak, dan kameranya juga harus dipotong
    Tulisan Jonathan Blow dari 2002 yang ditautkan di bawah [1] juga menarik. Visualisasi pada tulisan pertama sangat membantu saat masuk lebih dalam
    [1] https://web.archive.org/web/20240706043551/https://number-no...

 
GN⁺ 10 hari lalu
Pendapat di Lobste.rs
  • Terlihat berantakan, tapi memang benar, nilainya harus 255
    Kalau terasa tidak intuitif, lihat kasus terdegenerasi 2-bit. Jika satu-satunya nilai integer yang mungkin adalah 0, 1, 2, 3, lalu kita hitung seluruh konversi integer→floating point, maka untuk menghindari perilaku aneh seperti hitam/putih yang bukan benar-benar hitam/putih atau jarak yang jelas tidak merata, hasilnya menjadi 0.0, 0.33..., 0.66..., 1.0
    Karena itu, konversi baliknya bukan dengan mengalikan 4(2^2), melainkan mengalikan 3
    • Bagian awalnya benar, tetapi dari situ tidak otomatis mengikuti bahwa “konversi balik harus dikali 3, bukan 4”
      Konversi balik membutuhkan kuantisasi (pembulatan), dan justru di situlah simetri pecah
      Jika Anda membuat gradien bilangan real yang seragam pada rentang 0..=1 lalu mengkuantisasinya menjadi 0, 1, 2, 3, akan terlihat bahwa mengalikan 3 menghasilkan distribusi yang tidak merata. round() setelah ×3 membuat 1 dan 2 terwakili berlebihan, sementara floor atau ceil setelah ×3 membuat 0 atau 3 seperti titik singular sehingga gradien tampak hanya memakai 3 dari 4 warna
      Logika /3 dan ×3 tampak baik untuk konversi bolak-balik angka yang tepat, tetapi nilai antara sangat dipengaruhi oleh pilihan pembulatan, dan itu menjadi penting begitu Anda mulai memproses data
      Proporsi integer hanya menjadi seragam jika Anda mengalikan dengan (4-ε) lalu floor, yang setara dengan ×4, floor(), dan clamp(). Ini terasa seperti kesalahan aneh selisih 1 atau ε, tetapi secara intuitif itulah solusi yang paling enak dilihat
  • Saya cukup bingung karena judulnya. Tidak tahu apakah memang disengaja, tetapi ujung-ujungnya jadi terlihat seperti “apakah 0..1 dipetakan ke [0..255.0], atau ke [0.5..255.5]?”
    Bagi saya jawabannya selalu “jelas” [0.0..255.0], tetapi tampaknya itu tidak sejelas itu bagi semua orang
    Tulisan itu mengatakan bahwa rentang “ekstrem” hanya memiliki setengah kapasitas dibanding rentang lain, dan menurut saya framing ini juga kurang tepat
    Jika tidak ada nilai di luar [0..1], maka yang terlihat seperti rentang sempit itu hanyalah artefak perenderan. Itu hanya dirender lebih sempit karena bucket-nya dipotong dengan pengetahuan bahwa tidak ada nilai di luar rentang
    Sebaliknya, jika ada nilai di luar [0..1], maka rentang itu tak terbatas. Tulisan tersebut mengakui yang kedua, tetapi tidak mengakui yang pertama
    Begitu yang pertama diakui, perilaku yang benar tampak jelas, tetapi fakta bahwa tulisan seperti ini bisa muncul juga berarti masalah ini secara objektif tidak benar-benar “jelas” :D
    • Jika 0…255.0 benar-benar sejelas itu, maka rentang nilai floating point mana yang seharusnya kembali menjadi integer 0, dan rentang mana yang seharusnya kembali menjadi integer 255?
      Jika 0..<1 pergi ke integer 0, dan 254>..255.0 pergi ke integer 255, maka 128 akan hilang. Mungkin yang diinginkan adalah 127.5..128.5 menjadi 128, tetapi lalu setengah rentang ini harus pergi ke mana?
      Jika seluruhnya digeser sedikit demi menyesuaikan 128, maka 0..0.99609375 akan dipetakan ke integer 0
  • Pendekatan standar juga tampaknya muncul karena orang secara alami memanggil round()
    Karena cara itu terasa cukup alami bagi banyak orang, sepertinya ia menjadi standar karena kesederhanaannya
  • Saya penasaran apakah pendekatan kebalikan dari yang ingin dicapai dengan 256 juga berguna. Artinya, 0.0 dikirim ke 0, 1.0 dikirim ke 255, dan nilai floating point lainnya dipetakan ke 1 sampai 254
    uint8_t output = 0.0f >= result  
                     ? 0  
                     : 1.0f <= result  
                     ? 255  
                     : 1 + 253*result;  
    
    Akan bagus jika selama pemrosesan pun hitam tetap hitam, dan putih tetap putih
    • Dengan cara ini, 0 dan 255 mendapat porsi lebih besar dalam interval satuan dibanding angka lain. Kira-kira 0.8%, yaitu 255/253
  • Gambar pertama terlihat rusak di lingkungan saya
    • Penulis artikelnya di sini. Maksudnya file gambarnya rusak? Saya memang mengompresnya dengan pngcrush. Atau maksudnya isi gambarnya ada yang salah?