编写一个GIF编码器


9

是的,很好的旧GIF。GIF因其多功能性而广受青睐,由于对其专利(和专利)的局限性而讨厌其专利,因此GIF的核心是调色板和使用LZW算法压缩的调色板索引图像。

您的任务是编写一个程序,该程序从标准输入读取ASCII PPM格式的图像(“ P3”幻数),然后将GIF格式的同一图像(逐像素相同)写入标准输出。输出可以是二进制形式,也可以是ASCII文本,每个字节由0到255(含)之间的数字表示,并用空格分隔。

确保输入图像的颜色不超过256种。

得分:

您的程序将在3个样本图像上进行测试,您的得分将计算为:
程序大小+总和(输出大小-每个样本图像的参考大小)
得分最低。

要求:

  • 您的程序必须可以处理各种尺寸的任何相似种类的图像,并且不仅限于示例图像。例如,您可以将尺寸限制为2的倍数,或假设ppm最大颜色为255,但它仍应适用于各种输入图像。
  • 输出必须是可以与任何兼容程序一起加载的有效GIF文件(如果使用ASCII输出选项,则转换回二进制文件之后)。
  • 您不能使用任何图像处理功能(内置或第三方),您的程序必须包含所有相关代码。
  • 您的程序必须可以使用免费软件在Linux中运行。
  • 源代码只能使用ASCII字符。

样本图片:

这是将用于评分的3个示例图像。您可以下载包含ppm文件zip存档(使用该页面顶部的下载按钮)。或者,您可以使用ImageMagick通过以下命令从下面的png图像中转换它们:

convert file.png -compress none file.ppm

我还提供了ppm文件的MD5校验和以供确认。

1.琥珀色

amber.png

参考大小
:38055 MD5 ppm校验和:d1ad863cb556869332074717eb278080

2.蓝眼睛

blueeyes.png

参考大小:
ppm的28638 MD5校验和:e9ad410057a5f6c25a22a534259dcf3a

3.辣椒

peppers.png

参考大小:
ppm的53586 MD5校验和:74112dbdbb8b7de5216f9e24c2e1a627


1
主持人注意:主题/过时的注释已删除。请参阅meta,以获取有关此问题中示例图像的讨论。
门把手

看来第二张图片已被这样处理:websiteoptimization.com/speed/tweak/lossy可以解释它具有更好的压缩率以及对LZW编码器调整的敏感性。
nutki

1
“源代码只能使用ASCII字符。”-换句话说,在APL中不允许我们这样做吗?
FUZxxl 2015年

@FUZxxl是,但是您可以使用J。也不允许您使用Aheui或在GolfScript / CJam中进行基本转换技巧。
aidtsu退出是因为SE为EVIL,2015年

Answers:


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

我的代码中有错误,或者第二张图像针对特定的编码器进行了优化,因为看似微不足道的更改将大小减小到了参考值的精确范围。每个图像大约需要30s-60s。

高尔夫版。

#!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给我留下了深刻的印象,我想知道是否可以组合使用这些方法并获得较低的分数。另外,您能写一点有关程序的作用吗?
aidtsu退出是因为SE为EVIL,2015年

要求宽度为64的倍数似乎有点极端...
aidtsu退出了,因为SE是EVIL

@aditsu,这不是必需的,如果宽度不是64的倍数,则不尝试切片方法并且使用常规压缩。当然,我可以再花100个字符来制作最后一个图块的可变大小。
nutki

非常慢,并且平铺的图像显示为动画,但是..干得好,看到您真的可以缩小它们,这真是令人印象深刻。
aidtsu退出是因为SE为EVIL,2015年

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.