Scrivi un codificatore GIF


9

Sì, la buona vecchia GIF. Amata per la sua versatilità, odiata per i suoi brevetti e parzialmente obsoleta a causa dei suoi limiti (e brevetti), GIF è, al centro, una tavolozza di colori e un'immagine indicizzata con una tavolozza compressa utilizzando l'algoritmo LZW.

Il tuo compito è quello di scrivere un programma che legge un'immagine in formato PPM ASCII (numero magico "P3") dall'input standard e scrive la stessa immagine (identico pixel per pixel) in formato GIF nell'output standard. L'output può essere in forma binaria o testo ASCII con ogni byte rappresentato da un numero compreso tra 0 e 255 (incluso), separati da spazi bianchi.

Si garantisce che l'immagine in ingresso non abbia più di 256 colori diversi.

punteggio:

Il tuo programma verrà testato su 3 immagini di esempio e il tuo punteggio verrà calcolato come:
dimensione del programma + somma (dimensione di output - dimensione di riferimento per ogni immagine di esempio)
Il punteggio più basso vince.

Requisiti:

  • Il programma deve funzionare con qualsiasi tipo simile di immagini di varie dimensioni e non essere limitato alle immagini di esempio. Ad esempio, potresti limitare le dimensioni a multipli di 2 o assumere che il colore massimo ppm sia 255, ma dovrebbe comunque funzionare con un'ampia varietà di immagini di input.
  • L'output deve essere un file GIF valido che può essere caricato con qualsiasi programma conforme (dopo la riconversione in binario se si utilizza l'opzione di output ASCII).
  • Non è possibile utilizzare alcuna funzione di elaborazione delle immagini (incorporata o di terze parti), il programma deve contenere tutto il codice pertinente.
  • Il tuo programma deve essere eseguibile su Linux utilizzando software disponibile gratuitamente.
  • Il codice sorgente deve usare solo caratteri ASCII.

Immagini di esempio:

Ecco le 3 immagini di esempio che verranno utilizzate per il punteggio. È possibile scaricare un archivio zip con i file ppm (utilizzare il pulsante di download nella parte superiore di quella pagina). In alternativa, puoi convertirli dalle immagini PNG seguenti, usando ImageMagick con il seguente comando:

convert file.png -compress none file.ppm

Sto anche fornendo i checksum MD5 dei file ppm per conferma.

1. ambra

amber.png

Dimensioni di riferimento: 38055
checksum MD5 di ppm: d1ad863cb556869332074717eb278080

2. blueeyes

blueeyes.png

Dimensioni di riferimento: 28638
checksum MD5 di ppm: e9ad410057a5f6c25a22a534259dcf3a

3. peperoni

peppers.png

Dimensione di riferimento: 53586
checksum MD5 di ppm: 74112dbdbb8b7de5216f9e24c2e1a627


1
Nota del moderatore: commenti fuori tema / obsoleti rimossi. Si prega di consultare meta per la discussione sulle immagini di esempio in questa domanda.
Maniglia della porta

Sembra che la seconda immagine sia stata trattata con qualcosa del genere: websiteoptimization.com/speed/tweak/lossy che spiegherebbe un rapporto di compressione migliore e una sensibilità alle modifiche dell'encoder LZW.
Nutki,

1
"Il codice sorgente deve usare solo caratteri ASCII". Quindi, in altre parole, non ci è permesso fare questa sfida in APL?
FUZxxl

@FUZxxl true, ma è possibile utilizzare J. Non è inoltre consentito utilizzare Aheui o eseguire trucchi di conversione di base in GolfScript / CJam.
Aditsu smesso perché SE è MALE

Risposte:


4

Perl, 515 + -2922 + 0 + -2571 = -4978

Un altro approccio. Questa volta sto cercando di salvare l'immagine in tessere di dimensioni 64xH. Questo va bene secondo le specifiche, ma alcuni software potrebbero mostrare solo il primo riquadro o un'animazione. Le piastrelle si comprimono meglio a causa della migliore località spaziale. Faccio ancora la compressione normale anche per la seconda immagine e scelgo qualsiasi cosa sia stata abbreviata. Dal momento che questo comprime l'immagine due volte, è due volte più lento rispetto alla mia precedente soluzione.

#!perl -n0
sub r{$r.=1&"@_">>$_ for 0..log(@d|256)/log 2}
@k=/(\d+) (\d+)/;
@l=map{$$_||=(push(@t,split$"),++$i)}/\d+ \d+ \d+/g;
print+GIF89a,pack(vvCxxC768,@k,~8,@t);
sub v{($w,$h)=@_;for$k(0.."@k"/$w-1){
$k*=$w;$r='';@d=();@p=grep+($z++-$k)%"@k"<$w,@l;
$"=' ';$_="@p ";$"='|';while(/./){
r 256;%d=map{($_,$_-1)}@d=1..256;
$d{$&}=@d+2,r$d{$1},unshift@d,$&while@d<4095&&s/^(@d) (\d*)/$2/}
r 257;$_=pack"b*",$r;
$h.=pack"Cv4n(C/a)*",44,$k,0,$w,$k[1],8,/.{0,255}/gs
}$b=$h if!$b||length$b>length$h}
"@k"%64||v 64;v"@k";print"$b;"

Perl, 354 + 12 + 0 + -1 = 365 418 9521 51168 56639

O c'è qualche bug nel mio codice o la seconda immagine è ottimizzata per un codificatore specifico poiché una modifica apparentemente insignificante ha ridotto esattamente la dimensione al riferimento. Prende circa 30s-60s per immagine.

Versione golfizzata.

#!perl -n0
sub r{$r.=1&"@_">>$_ for 0..log(@d|256)/log 2}
@k=/(\d+) (\d+)/;
@p=map{$$_||=(push(@t,split$"),++$i)}/\d+ \d+ \d+/g;
$_="@p ";$"='|';while(/./){
r 256;%d=map{($_,$_-1)}@d=1..256;
$d{$&}=@d+2,r$d{$1},unshift@d,$&while@d<4095&&s/^(@d) (\d*)/$2/}
r 257;$_=pack"b*",$r;
print+GIF89a,pack(vvCxxC768,@k,~8,@t),
pack("Cx4vvn(C/a)*",44,@k,8,/.{0,255}/gs),';'

L'unica decisione che un compressore GIF può prendere è quando ripristinare il dizionario LZW. In generale, a causa di come sono state scelte le immagini per questa attività, il momento migliore per farlo è ogni 4096 codici, ovvero il momento in cui il dizionario traboccerebbe. Con tale limite il dizionario non trabocca mai, risparmiando un paio di byte nell'implementazione. Ecco come funziona in dettaglio:

#!perl -n0
# function to add one codeword to the output stream @r.
# the current codeword length is based on the dictionary size/
sub r{push@r,map"@_">>$_,0..log(@d|256)/log 2}
# get the dimensions into @k
@k=/(\d+) (\d+)/;
# get pixel indexes to @p and palette to @t
@p=map{$$_||=(push(@t,split$"),++$i)}/\d+ \d+ \d+/g;
# convert index table into space separated string 
$_="@p ";$"='|';
# LZW encoder; while something to encode
while(/\S/){
# output reset code
r 256;
# reset code dictionary $d is the last code number,
# %d is the map of codes and @d list of codes
$d=257;%d=map{($_,$_-1)}@d=1..256;
# find codes using regexp, stop at dictionary overflow
while($d<4096&&s/^(@d) (\d*)/$2/){
unshift@d,$&;$d{$&}=++$d;r$d{$1}}}
# end LZW encoder; output end code
r 257;
# convert bit string @r to bytes $f
vec($f,$j++,1)=$_ for@r;
# output header up to the color table
print+GIF89a,pack(vvCvC768,@k,~8,0,@t),
# output rest of the header
pack(Cv4CC,44,0,0,@k,0,8),
# output the LZW compressed data $f slicing into sub-blocks
$f=~s/.{0,255}/chr(length$&).$&/egsr,';'

Perl, 394 + -8 + 0 + -12 = 374

L'aggiunta di un'euristica per indovinare il punto di ripristino migliora un po 'la compressione ma non abbastanza per giustificare il codice aggiuntivo:

#!perl -n0
sub r{$r.=1&"@_">>$_ for 0..log(@d|256)/log 2}
@k=/(\d+) (\d+)/;
@p=map{$$_||=(push(@t,split$"),++$i)}/\d+ \d+ \d+/g;
$_="@p ";$"='|';while(/./){
r 256;%d=map{($_,$_-1)}@d=1..256;
$d{$&}=@d+2,r$d{$1},unshift@d,$&while
(@d<4001||(/((@d) ){11}/,$&=~y/ //>12))&@d<4095&&s/^(@d) (\d*)/$2/}
r 257;$_=pack"b*",$r;
print+GIF89a,pack(vvCxxC768,@k,~8,@t),
pack("Cx4vvn(C/a)*",44,@k,8,/.{0,255}/gs),';'

Molto bella! Anche se ci vuole molto più di 30 secondi per immagine qui. Sono rimasto piuttosto colpito dal -30 della versione precedente, mi chiedo se puoi combinare i metodi e ottenere un punteggio più basso. Inoltre, potresti scrivere qualcosa su ciò che fa il programma?
Aditsu ha smesso perché SE è MALE

Richiedere che la larghezza sia un multiplo di 64 sembra un po 'estremo ...
aditsu smettila perché SE è MALE

@aditsu, Non è richiesto, se la larghezza non è multipla di 64, il metodo di piastrellatura non viene provato e viene utilizzata la compressione regolare. Ovviamente ad un costo di altri ~ 100 caratteri ho potuto rendere l'ultima dimensione variabile delle tessere.
Nutki,

Molto lento, e le immagini piastrellate mostrano animazioni, ma ... ben fatto, è abbastanza impressionante vedere che puoi davvero ridurle.
Aditsu ha smesso perché SE è MALE

2

CJam, punteggio 155 + 35306 + 44723 + 21518 = 101702

Solo un'implementazione di riferimento stupida. È lento, non esegue alcuna compressione effettiva e non è golfato.

"GIF89a":iS*SqN/S*S%1>:i3/:M0=2<256f{md\}S*:ZS247S0S0SM1>_|:PL*_,768\m0a*+S*S44S0S0S0S0SZS0S8SM1>Pf{\a#}254/256a*{512+2b1>W%}%:+8/{W%2b}%255/{_,S@S*S}/0S59
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.