Написати кодер GIF


9

Так, старий добрий GIF. Полюблений своєю універсальністю, ненависний до своїх патентів і частково застарілий через свої обмеження (та патенти), GIF складається в основі кольорової палітри та зображення, індексованого палітрою, стиснутого за допомогою алгоритму LZW.

Ваше завдання - написати програму, яка читає зображення у форматі ASCII PPM (магічне число "P3") зі стандартного вводу, а також записує те саме зображення (однаковий піксель на піксель) у форматі GIF на стандартний вихід. Вихід може бути або у двійковій формі, або у тексті ASCII, кожен байт представлений числом від 0 до 255 (включно), розділеним пробілом.

Гарантоване вхідне зображення не матиме більше 256 різних кольорів.

Оцінка:

Ваша програма буде протестована на 3 зразкових зображеннях, а ваш рахунок буде розрахований як:
розмір програми + сума (розмір виводу - розмір опорного зображення для кожного зразка)
Найнижчий результат виграє.

Вимоги:

  • Ваша програма повинна працювати з будь-якими подібними видами зображень різного розміру, а не обмежуватися зразками зображень. Наприклад, ви можете обмежити розміри, кратними 2, або припустити, що максимальний колір ppm становить 255, але він все одно повинен працювати з великою різноманітністю вхідних зображень.
  • Вихід повинен бути дійсним файлом GIF, який може бути завантажений будь-якою сумісною програмою (після перетворення на бінарний, якщо використовується опція виводу ASCII).
  • Ви не можете використовувати будь-які функції обробки зображень (вбудовані або сторонні), ваша програма повинна містити весь відповідний код.
  • Ваша програма повинна працювати в Linux, використовуючи вільно доступне програмне забезпечення.
  • У вихідному коді повинні використовуватися лише символи ASCII.

Зразкові зображення:

Ось 3 зразкових зображення, які будуть використані для оцінки. Ви можете завантажити zip-архів із файлами ppm (скористайтесь кнопкою завантаження у верхній частині цієї сторінки). Або ви можете конвертувати їх із зображень, наведених нижче, за допомогою ImageMagick за допомогою наступної команди:

convert file.png -compress none file.ppm

Я також надаю контрольні суми MD5 файлів ppm для підтвердження.

1. бурштин

amber.png

Довідковий розмір: 38055
контрольна сума MD5 ppm: d1ad863cb556869332074717eb278080

2. сині

blueeyes.png

Довідковий розмір: 28638
контрольна сума MD5 ppm: e9ad410057a5f6c25a22a534259dcf3a

3. перець

peppers.png

Довідковий розмір: 53586
контрольна сума MD5 проміле: 74112dbdbb8b7de5216f9e24c2e1a627


1
Примітка модератора: видалені коментарі поза темою / застарілі коментарі. Будь ласка, дивіться мета для обговорення на зразкових зображеннях у цьому запитанні.
Дверна ручка

Здається, друге зображення було оброблене приблизно так: websiteoptimization.com/speed/tweak/lossy, що пояснило б кращий коефіцієнт стиснення та чутливість до налаштування кодера LZW.
nutki

1
"У вихідному коді повинні використовуватися лише символи ASCII." - значить, інакше кажучи, нам не дозволено робити це завдання в APL?
FUZxxl

@FUZxxl правда, але ви можете використовувати J. Також вам заборонено використовувати Aheui або робити фокуси базового перетворення в GolfScript / CJam.
aditsu кинути, тому що SE - EVIL

Відповіді:


4

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

Ще один підхід. Цього разу я намагаюся зберегти зображення у плитках розміром 64xH. Це добре за специфікаціями, але деякі програми можуть показувати лише першу плитку чи анімацію. Плитка стискається краще через кращу просторову локальність. Я все ще роблю нормальне стиснення, а також для другого зображення і вибираю те, що було коротше. Оскільки це стискає зображення вдвічі, це вдвічі повільніше порівняно з моїм попереднім рішенням.

#!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

Або в моєму коді є якась помилка, або друге зображення оптимізоване для конкретного кодера, оскільки, здавалося б, незначна зміна точно зменшила розмір до посилання. Займає близько 30-х-60-х на зображення.

Гольф-версія.

#!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),';'

Єдине рішення, яке може прийняти компресор GIF, - це коли скинути словник LZW. Загалом, тому, як обрані образи для цього завдання, найкращим моментом для цього є кожен код 4096, який є моментом, коли словник переповнюватиметься. З таким обмеженням словник ніколи не переповнює, що економить пару байтів у реалізації. Ось як це детально працює:

#!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

Додавання евристики для відгадки точки скидання трохи покращує стиснення, але недостатньо для виправдання додаткового коду:

#!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),';'

Дуже хороший! Хоча тут потрібно набагато більше 30-х на зображення. Мене дуже вразив -30 від попередньої версії, мені цікаво, чи можете ви поєднати методи та отримати нижчу оцінку. Також ви могли б трохи написати про те, що робить програма?
aditsu кинути, тому що SE - EVIL

Якщо вимагати, щоб ширина була кратною 64, здається трохи екстремальним ...
aditsu кинь, тому що SE - EVIL

@aditsu, Це не потрібно, якщо ширина не кратна 64, метод плитки не випробуваний і використовується регулярне стиснення. Звичайно, ціною ще ~ 100 символів я міг зробити останню плитку змінним розміром.
nutki

Дуже повільно, і кахельні зображення демонструються як анімація, але .. молодець, дуже вражає те, що ви дійсно можете їх зменшити.
aditsu кинути, тому що SE - EVIL

2

CJam, оцінка 155 + 35306 + 44723 + 21518 = 101702

Просто тупа реалізація посилань. Це повільно, не робить ніякого фактичного стиснення, і це не гольф.

"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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.