GIF 인코더 작성


9

예, 좋은 오래된 GIF입니다. 다재다능 함을 좋아하고 특허를 싫어하고 그 한계 (및 특허)로 인해 일부 사용되지 않는 GIF는 핵심적으로 LZW 알고리즘을 사용하여 압축 된 색상 팔레트 및 팔레트 색인 이미지로 구성됩니다.

당신의 임무는 표준 입력 에서 ASCII PPM 형식 의 이미지 ( "P3"매직 넘버) 를 읽고 GIF 형식의 동일한 이미지 (동일한 픽셀)를 표준 출력에 쓰는 프로그램을 작성하는 것입니다. 출력은 2 진 형식이거나 각 바이트가 0-255 (포함) 사이의 공백으로 구분 된 ASCII 텍스트 일 ​​수 있습니다.

입력 이미지는 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

참조 크기 :
ppm의 38055 MD5 체크섬 : d1ad863cb556869332074717eb278080

2. 파란 눈

blueeyes.png

참조 크기
: 28638 MD5 ppm 체크섬 : e9ad410057a5f6c25a22a534259dcf3a

3. 고추

peppers.png

기준 크기 :
ppm의 53586 MD5 체크섬 : 74112dbdbb8b7de5216f9e24c2e1a627


1
사회자 참고 사항 : 주제 외 / 사용하지 않는 의견이 삭제되었습니다. 이 질문의 샘플 이미지에 대한 자세한 내용은 메타참조하십시오 .
Doorknob

두 번째 이미지는 더 나은 압축률과 LZW 인코더 조정에 대한 감도를 설명하는 websiteoptimization.com/speed/tweak/lossy 와 같은 것으로 처리되었습니다 .
nutki

1
“소스 코드는 ASCII 문자 만 사용해야합니다.”— 즉, APL에서이 과제를 수행 할 수 없습니까?
FUZxxl

@FUZxxl true이지만 J를 사용할 수 있습니다. 또한 Aheui를 사용하거나 GolfScript / CJam에서 기본 변환 트릭을 ​​수행 할 수 없습니다.
SE가 EVIL이기 때문에 aditsu 종료

답변:


4

펄, 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;"

펄, 354 + 12 + 0 + -1 = 365418 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,';'

펄, 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에 깊은 인상을 받았습니다. 방법을 결합하여 더 낮은 점수를 얻을 수 있는지 궁금합니다. 또한 프로그램이하는 일에 대해 조금 쓸 수 있습니까?
SE가 EVIL이기 때문에 Aditsu 종료

너비가 64의 배수가되도록 요구하는 것은 약간 극단적 인 것 같습니다.
SE는 EVIL이기 때문에 종료

@aditsu, 너비가 64의 배수가 아닌 경우 타일링 방법을 시도하지 않고 정기적 인 압축이 사용됩니다. 물론 ~ 100 자의 비용으로 마지막 타일을 가변 크기로 만들 수 있습니다.
nutki

매우 느리고 타일 이미지는 애니메이션으로 표시되지만 실제로는 더 작게 만들 수 있다는 점이 매우 인상적입니다.
SE가 EVIL이기 때문에 aditsu가 종료했습니다

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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.