DitherBox
Dithering regolabile per le fotografie, in due interfacce che condividono lo stesso motore: un widget che gira nel browser (la foto non viene caricata da nessuna parte) e un'applicazione da terminale a tutto schermo, con pannelli, temi e l'anteprima disegnata dentro il terminale stesso. Diciannove algoritmi, ventisei tavolozze, quindici preset, cinque lingue.
Cos’è
DitherBox applica il dithering a una fotografia lasciandoti girare le manopole mentre guardi il risultato. Esiste in due interfacce che condividono lo stesso motore: un widget che gira nel browser, quello qui sotto nella scheda Tool, e un’applicazione da terminale a tutto schermo, con pannelli squadrati, temi a sei colori e l’anteprima disegnata dentro il terminale stesso.
Nel widget tutto succede sul canvas del tuo browser. La foto non viene caricata da nessuna parte, non esiste un server che la riceve, e chiudere la scheda è tutto quello che serve per farla sparire.
Cos’è il dithering, e perché serviva
Quando i colori disponibili sono meno di quelli della fotografia, ogni pixel va spostato su quello più vicino fra i pochi ammessi. Fatto senza precauzioni, un cielo che sfuma diventa un blocco unico di colore con un gradino netto dove il tono ha superato la metà: si chiama banding, ed è brutto in un modo riconoscibile.
Il dithering rimedia distribuendo l’errore invece di ingoiarlo. Se un tono sta a metà fra i due colori disponibili, invece di sceglierne uno per tutta l’area si alternano i due, e l’occhio a distanza li media di nuovo in quel mezzo tono. La sfumatura torna, pagata con una texture di punti.
Era una necessità tecnica del 1984, con schermi a un bit e stampanti che sapevano solo mettere o non mettere l’inchiostro. Oggi è una scelta estetica, ed è il motivo per cui questo programma esiste: quella texture ha un carattere che le sfumature perfette non hanno.
Provalo
La scheda Tool qui sopra contiene il widget vero, con una foto già caricata. Carica la tua trascinandola sul riquadro, gira le manopole, e scarica il PNG quando ti piace.
Vale la pena partire da uno dei quindici preset (Macintosh 1984, Game Boy, Giornale, Fanzine fotocopiata, Console 8 bit, Console 16 bit, VGA 256 colori, Amiga Workbench) e poi muovere i singoli parametri, invece di affrontare venti cursori da fermo.
Cosa si può regolare
| Parametro | Intervallo | Cosa fa |
|---|---|---|
| Tavolozza | 26 tavolozze | I colori che il risultato può usare |
| Algoritmo | 19 algoritmi | Come vengono distribuiti i punti |
| Pixel | da 1 a 16 | Riduce prima di ditherare: 1 è il dettaglio pieno, 8 sono pixeloni da 8 bit |
| Forza | 0 a 200% | Quanto errore (o quanto rumore ordinato) viene applicato |
| Soglia | -100 a 100 | Sposta il punto di taglio: in negativo scurisce, in positivo schiarisce |
| Grana | 0 a 100% | Rumore casuale prima della soglia, per rompere le texture troppo regolari |
| Serpentina | on/off | Scansione alternata riga per riga, che toglie le striature diagonali |
| Luminosità, Contrasto, Gamma, Saturazione | Regolazioni di tono, applicate prima del dithering | |
| Nitidezza | 0 a 200% | Maschera di contrasto, per recuperare il dettaglio che il dithering mangia |
| Rapporto | 10 rapporti | Porta il risultato a un rapporto fisso (1:1, 4:3, 16:9, 9:16…), ritagliando o aggiungendo bande |
| Adatta | ritaglia / bande | Che fare di quello che nel rapporto non entra |
| Zoom | 10 a 100% | Quanto è grande il rettangolo di ritaglio: 100 è il più grande che ci sta, sotto ingrandisce |
| Posizione X, Posizione Y | 0 a 100 | Sposta il rettangolo di ritaglio, quando c’è margine per muoverlo |
| Larghezza, Altezza | in pixel, 0 = automatico | Dimensione del file in pixel, tenendo il rapporto della foto; a 0 decidono i megapixel |
| Blocca | on/off | Scrivendo un lato riempie l’altro, tenendo il rapporto |
| Megapixel | da 0,01 alla risoluzione della foto | Risoluzione del risultato: abbassala per rovinare la foto apposta |
| Upscale | on/off | Riporta il risultato alla dimensione originale con pixel netti |
Gli algoritmi
Si dividono in tre famiglie, e la scelta cambia il carattere del risultato più di qualunque altro parametro.
A diffusione dell’errore, dove lo scarto di ogni pixel viene spalmato sui vicini. Texture irregolare, tono molto fedele: floydSteinberg, atkinson, jarvis, stucki, burkes, sierra e le loro varianti.
atkinson è quello del Macintosh del 1984. Diffonde solo sei ottavi dell’errore invece di tutto, e quel po’ che butta via è esattamente da dove viene il contrasto marcato che lo rende riconoscibile a colpo d’occhio.
A matrice ordinata, dove ogni pixel viene confrontato con una soglia che dipende da dove si trova. Texture regolare, aria da videogioco vecchio: bayer2 fino a bayer16, cluster4 e cluster8 (il retino da rotocalco), lines4 (l’incisione).
Senza texture: none (soglia netta) e random (rumore puro).
Le tavolozze, e la cosa controintuitiva
Ventisei tavolozze, dal bianco e nero a un bit al Game Boy, dalla CGA al C64 ai colori di Marathon, passando per NES, Mega Drive, MSX, EGA 16, Workbench, Virtual Boy e il Televideo, più qualunque elenco di colori esadecimali scritto a mano.
Qui sta la scelta tecnica che conta davvero. Le rampe tonali (bianco e nero, grigi, Game Boy, fosfori, blueprint) sono mappate sulla luminanza, non sul colore più vicino nello spazio RGB. Sembra un dettaglio ed è la differenza fra un risultato leggibile e uno inguardabile: un rosso saturo, cercato per vicinanza di colore, finisce sul verde chiaro che gli capita accanto in RGB, quindi una maglietta rossa diventa più luminosa del cielo. Mappato sulla luminanza va sul gradino scuro, che è dove l’occhio si aspetta di trovarlo.
Lo stesso vale per marathon, che prende i colori del gioco del 2025 (rosa e gialli ipersaturi su blu acciaio freddi e neri profondi) e li tratta come una rampa di luminanza. È la differenza fra il blocco di colore piatto che il gioco usa davvero e una nevicata di coriandoli.
L’interfaccia da terminale
Ricalca quella di cliamp: pannelli squadrati, temi a sei colori, i cursori dove starebbe l’equalizzatore. Anche i tasti sono i suoi, quindi chi usa già quello è a casa.
Un terminale non ha pixel, ha caratteri, e una cella è alta il doppio di quanto è larga. Ogni modalità di anteprima usa il carattere in modo diverso: halfblock (un carattere, due pixel), braille (il più dettagliato, due per quattro), quadrant (due per due) e ascii.
Il default è halfblock e non braille per un motivo pratico: ▀ è un blocco, largo esattamente una cella in qualunque font. I glifi braille mancano in parecchi font a spaziatura fissa, il terminale ripiega su un altro font con un avanzamento diverso, le colonne si scollano e la cornice si spezza. Se il tuo font regge il braille, un tasto e il dettaglio più che raddoppia.
L’anteprima viene ditherata direttamente alla risoluzione del terminale, non rimpicciolita dopo. Ditherare grande e poi ridurre significa mediare i pixel, e la media richiude i punti in grigi: la texture sparisce e resta una foto sfocata. Quello che si vede è dithering vero.
Dalla riga di comando
Senza interfaccia, per fare le cose in blocco:
ditherbox foto.jpg -p macintosh -o risultato.png
ditherbox foto.jpg --palette "#0a0c10,#c2fe0b" --megapixels 0.3 -o poster.png
ditherbox ~/Immagini --preset fanzine --out-dir ./risultati
Con due sole tinte il risultato è un duotone: le luci e le ombre della foto finiscono sui due colori scelti, e il dithering fabbrica i mezzi toni in mezzo.
Ogni parametro del motore ha la sua opzione, e le impostazioni permanenti stanno in ~/.config/ditherbox/config.toml. I temi personali usano lo stesso schema a sei colori di cliamp, quindi un tema scritto per quello funziona qui senza modifiche.
Come è fatto
Il motore è JavaScript senza dipendenze e senza un solo riferimento al DOM, quindi lo stesso file gira identico in Node e nel browser. È la ragione per cui le tre interfacce non divergono: lo schema dei parametri vive in un posto solo, e aggiungerne uno lo fa comparire da sé nel terminale, sulla riga di comando e nel widget.
I test coprono motore, primitive del terminale, lettura e scrittura delle immagini, interfaccia, riga di comando e traduzioni. Quelli sull’impaginazione aprono davvero il widget in Chromium e ne controllano il layout; se Chromium non c’è saltano invece di far fallire la suite.
Installazione
Serve solo Node 18 o successivo, niente da compilare e nessun modulo nativo:
npm install -g github:Pricesswg/DitherBox
Per provarlo senza installare niente:
npx github:Pricesswg/DitherBox ~/Immagini
Licenza
MIT: si può usare, modificare e ridistribuire, anche in progetti commerciali, con l’unico obbligo di tenere la nota di copyright. Il motore e il widget non hanno dipendenze; l’applicazione da terminale ne ha due, jpeg-js (BSD-3-Clause) e pngjs (MIT), entrambe licenze permissive che non chiedono a nessuno di aprire il proprio codice.
Larghezza e altezza esatte in pixel (v0.3.3), con un blocco che riempie l'altro lato tenendo il rapporto. I megapixel dicono quanto grande, non quanto larga: se il file deve entrare in uno spazio preciso, un fondale, una copertina, la stampa di una fanzine, servono i due numeri veri e non un'area. Lasciando i campi a zero decidono ancora i megapixel, quindi chi non ha un vincolo di dimensione non se ne accorge.
Inquadratura e ritaglio (v0.3.x). Il risultato si può portare a un rapporto fisso, dal quadrato al 9:16, scegliendo se ritagliare quello che avanza o aggiungere le bande, e il ritaglio non è una scatola automatica: è un rettangolo che si dimensiona con lo Zoom e si sposta con Posizione X e Y, disegnato come guida sopra l'anteprima mentre lo si muove. L'anteprima mostra la foto come sarà vista, non come sta nel file, e c'è una vista 1:1 che ritaglia invece di ricampionare, per giudicare i punti alla loro grandezza vera invece che rimpiccioliti.
Otto tavolozze da console e sette preset d'epoca: NES, Mega Drive, MSX, EGA 16, Workbench, Virtual Boy e Televideo si aggiungono alle diciotto di prima, e i preset che le mettono in posa portano il totale a quindici. Sono scelte di macchine vere, non tinte inventate, ed è il motivo per cui vale la pena averle: la tavolozza del NES non è una palette a caso, è il vincolo con cui quella console disegnava tutto.
Il controllo dei megapixel non mente più sulle foto piccole. La riduzione non fa mai upscale, ed è giusto così, perché chiedere più megapixel di quanti la foto ne abbia non inventa dettaglio; il cursore però arrivava a 24 MP a prescindere, quindi sulla foto di esempio di questa pagina le posizioni in cima producevano tutte lo stesso file. Si trascinava, non succedeva niente, si salvava e usciva la dimensione originale: metà della corsa era morta e niente sullo schermo lo diceva. Ora il cursore si ferma al primo passo che copre la foto, così ogni posizione cambia il risultato, e il numero accanto mostra quello che esce davvero invece di quello che è stato chiesto. Caricare una foto più grande riapre la corsa, e l'app da terminale riceve lo stesso trattamento.
Foto di esempio inclusa e caricata all'avvio, così c'è subito qualcosa su cui girare le manopole invece di un riquadro vuoto. Non è una foto scelta a caso: il muro intonacato è un gradiente lungo e continuo, la rete metallica è un motivo ad alta frequenza, e sono esattamente i due casi che il dithering tratta in modo diverso.
Interfaccia in cinque lingue (inglese, italiano, spagnolo, francese, tedesco) con selettore a caldo. Nessun testo leggibile vive dentro il motore: etichette e messaggi passano da un traduttore indicizzato sulle stringhe inglesi canoniche, e una chiave che manca in una traduzione ripiega sull'inglese invece di sparire in una stringa vuota.
Colori del sito come base del widget e schema imponibile via data-theme. Un widget che segue le preferenze di sistema si ribalta in chiaro addosso a chi tiene il sistema in chiaro, e su una pagina nera diventa un riquadro luminoso in mezzo al buio: con l'attributo la scelta torna a chi scrive la pagina.
Via l'intestazione dal terminale, riga di stato che diventa la barra di avanzamento dell'operazione in corso e anteprima più grande. La barra segue le fasi vere (lettura, elaborazione, scrittura) invece di inventarsi un conto alla rovescia, quindi mentre una fase lavora resta ferma dov'è, e su un terminale stretto la riga lascia cadere le informazioni meno importanti invece di troncare il nome del file.
Anteprima del terminale corretta con Pixel maggiore di uno, e controllo di sintassi su tutti i file dello script delle schermate. Le immagini del README sono generate, non fatte a mano, così non possono raccontare una versione che non esiste più.
Impaginazione del widget, campo per il file, controllo dei megapixel in uscita e tavolozze scritte a mano come elenco di colori esadecimali, accettate ovunque si possa scrivere il nome di una tavolozza.
Prima versione: il motore di dithering (JavaScript puro, senza dipendenze e senza un riferimento al DOM), il widget per il browser e l'applicazione da terminale, tutti e tre che leggono lo schema dei parametri dallo stesso file.