J'ai amélioré l'algo de conversion. D'une part, au lieu de prendre des niveaux rouges verts bleus identiques pour toutes les images, ils sont dorénavant étalonnés pour chaque image. En outre avec le découpage 3*4+4=16 précédent il y a deux fois le noir dans la palette. Si on les fusionne, on peut libérer un niveau de vert supplémentaire. On a ainsi 3 bleus, 4 rouges, et 5 verts. Le nombre de couleurs visibles à l'écran sans tramage monte alors à 3*4*5=60.
Avec ces 60 couleurs dont les niveaux sont spécialement adaptés à chaque image, le rendu est bien meilleur. Jugez donc:
Fichier(s) joint(s):
Avatar_The_Movie1.gif [ 10.04 Kio | Vu 17397 fois ]
Fichier(s) joint(s):
_44417686_horse2.gif [ 8.32 Kio | Vu 17397 fois ]
Fichier(s) joint(s):
4bbc1a1956b3e05e667233f0debd5b70cb7fb5fc2aea9d7aec9e5e01a76aee0d_.gif [ 8.88 Kio | Vu 17397 fois ]
Voici le code pour les curieux:
Code:
#/bin/perl
#
# tst_bm16.pl - Conversion video en gif 15 couls 160x200.
#
# par Samuel DEVULDER, Sept-Oct 2015.
#
$file = $ARGV[0];
exit unless -f $file;
$name = $file; $name =~ s/\.[^\.]*$//;
$name=~s/.*[\/\\]//; $name = "out/$name.gif";
exit if -e $name;
mkdir("out");
# params par defaut
mkdir("tmp");
$img_pattern = "tmp/img%05d.bmp";
($w, $h) = (160, 100); # 16:9
$fps = 5;
$dither = "bayer4";
$zigzag = 0;
# recherche la taille de l'image
($x,$y, $aspect_ratio) = (160,100,"16:9");
open(IN, "./ffmpeg -i \"$file\" 2>&1 |");
while(<IN>) {
if(/, (\d+)x(\d+)/) {
($x,$y) = ($1, $2);
# 4:3
if(abs($x - 4/3*$y) < abs($x - 16/9*$y)) {
($w,$h,$aspect_ratio) = (133,100,"4:3");
}
}
}
close(IN);
$h = int(($w=160)*$y/$x);
$w = int(($h=100)*$x/$y) if $h>100;
print "\n",$file," : ${x}x${y} ($aspect_ratio) -> ${w}x${h}\n";
# tuyau vers ffmpeg pour images
open(FFMPEG,'| (read line; $line)');# -vf format=gray
binmode(FFMPEG);
# fichier video (entree)
open(IN, "<$file");
binmode(IN);
# détermination des parametres de dither
open(DITHER_O,"| ./ffmpeg -i - -v 0 -r 1 -s ${w}x${h} -an $img_pattern");
open(DITHER_I, "<$file");
binmode(DITHER_I);
binmode(DITHER_O);
# init image magick
&magick_init;
# parametres de dither
&dither_init(\*DITHER_I, \*DITHER_O, $dither, $w, $h);
# determination du zoom optimal
$cmd = "./ffmpeg -i - -v 0 -r $fps -s ${w}x${h} -an $img_pattern\n";
syswrite(FFMPEG, $cmd, length($cmd));
# nettoyage
unlink(<tmp/img*.bmp>);
# multiplicateur audio
@audio_cor = (8, 255);
# compteur image
$cpt = 1;
# ecran courant
$gif = Image::Magick->new(size=>"320x200");
$start = time;
$pause = 60;
while(1) {
my $img = &next_image(\*IN, \*FFMPEG, $w, $h);
last unless $img;
#$img->Write("out/toto.png");
$img->Set(dispose=>"None");
$img->Set(delay=>int(100/$fps));
push(@$gif, $img);
undef $img;
# infos à l'écran
if($cpt%$fps == 0) {
++$time;
my($d) = time-$start+.0001;
print STDERR sprintf("%d:%02d:%02d (%.2gx) \r",
int($time/3600), int($time/60)%60, $time%60,
int(100*$time/$d)/100);
# pour ne pas trop faire chauffer le CPU
if($d>$pause) {$pause = $d+60;sleep(10);}
}
# pas plus de 3000 imgs (5mins)
last if $cpt==3000;
}
# ecriture du fichier gif
$gif->Set(dispose=>"None");
$gif->Set(Layers=>"optimize-trans");
$gif->Set(delay=>int(100/$fps));
$z=$gif->Write($name); print "$name: $z\n" if $z;
print STDERR "\n";
unlink(<tmp/img*.bmp>);
sub tune_image {
my($tmp) = @_;
$tmp->Modulate(saturation=>120); # un peu plus de couleurs
$tmp->Evaluate(operator=>'Multiply', value=>255/245);
}
# Lit l'image suivante. Retourne 1 si ok, 0 si fin fichier
sub next_image {
my($IN, $OUT, $w, $h) = @_;
# nom du fichier BMP
my $name = sprintf($img_pattern, $cpt++);
my $tmp = Image::Magick->new();
if($file =~ /\.(jpg|jpeg|png|bmp)$/i || $file=~/\.gif$/i && (-s $file)<=500*1024) {
return 0 if $cpt==3;
my $z = $tmp->Read($file);
print STDERR "$z" if $z;
return 0 if $z; # si erreur => fin fichier
$tmp->Resize(geometry=>"${w}x${h}");
} else {
# taille fichier BMP
my $expected_size = $h*(($w*3 + 3)&~3) + 54; # couleur
#print "$w, $h, $expected_size\n";
# on nourrit ffmpeg jusqu'a obtenir un fichier BMP fini
while($expected_size != -s $name) {
my $buf;
my $read = read($IN,$buf,1634);
last unless $read;
syswrite $OUT, $buf, $read;
}
# lecture image
my $z = $tmp->Read($name);
#print STDERR $z if $z;
return 0 if $z; # si erreur => fin fichier
unlink $name if $name ne $file;
}
# dither
$tmp->Set(depth=>16);
$tmp->Set(colorspace=>$LINEAR_SPACE);
&tune_image($tmp);
my $img=Image::Magick->new(size=>"160x100");
$img->Read("xc:black");
$img->Composite(image=>$tmp, operator=>"Over", x=>(160-$w)>>1, y=>(100-$h)>>1);
my @px = $img->GetPixels(height=>100, normalize=>"True"); undef $tmp; undef $img;
for my $c (@px) {$c = int($c*255);}
$dR = sub {
my($v, $d,$a,$b) = @_;
my($k) = "$v,$d";
my $t = $glb_dR{$k};
return $t if defined $t;
$d /= $mat_x*$mat_y+1.0;
($a,$b) = ($lin_pal[$RED2],$lin_pal[$RED3]);
return $glb_dR{$k}=(($v-$a)/($b-$a)>=$d ? $teo_pal[$RED3] : $teo_pal[$RED2]) if $v>=$a;
($a,$b) = ($lin_pal[$RED1],$a);
return $glb_dR{$k}=(($v-$a)/($b-$a)>=$d ? $teo_pal[$RED2] : $teo_pal[$RED1]) if $v>=$a;
($a,$b) = ($lin_pal[$RED0],$a);
return $glb_dR{$k}=(($v-$a)/($b-$a)>=$d ? $teo_pal[$RED1] : $teo_pal[$RED0]);
} unless $dR;
$dG = sub {
my($v, $d,$a,$b) = @_;
my($k) = "$v,$d";
my $t = $glb_dG{$k};
return $t if defined $t;
$d /= $mat_x*$mat_y+1.0;
($a,$b) = ($lin_pal[$GRN3],$lin_pal[$GRN4]);
return $glb_dG{$k}=(($v-$a)/($b-$a)>=$d ? $teo_pal[$GRN4] : $teo_pal[$GRN3]) if $v>=$a;
($a,$b) = ($lin_pal[$GRN2],$a);
return $glb_dG{$k}=(($v-$a)/($b-$a)>=$d ? $teo_pal[$GRN3] : $teo_pal[$GRN2]) if $v>=$a;
($a,$b) = ($lin_pal[$GRN1],$a);
return $glb_dG{$k}=(($v-$a)/($b-$a)>=$d ? $teo_pal[$GRN2] : $teo_pal[$GRN1]) if $v>=$a;
($a,$b) = ($lin_pal[$GRN0],$a);
return $glb_dG{$k}=(($v-$a)/($b-$a)>=$d ? $teo_pal[$GRN1] : $teo_pal[$GRN0]);
} unless $dG;
$dB = sub {
my($v, $d,$a,$b) = @_;
my($k) = "$v,$d";
my $t = $glb_dB{$k};
return $t if defined $t;
$d /= $mat_x*$mat_y+1.0;
($a,$b) = ($lin_pal[$BLU1],$lin_pal[$BLU2]);
return $glb_dB{$k}=(($v-$a)/($b-$a)>=$d ? $teo_pal[$BLU2] : $teo_pal[$BLU1]) if $v>=$a;
($a,$b) = ($lin_pal[$BLU0],$a);
return $glb_dB{$k}=(($v-$a)/($b-$a)>=$d ? $teo_pal[$BLU1] : $teo_pal[$BLU0]);
} unless $dB;
local @px2 = (0)x(3*320*200);
$pset = sub {
my($x,$y, $r,$g,$b) = @_;
my($p)=($y*320+$x)*6;
my($q)=$p+320*3;
($p,$q)=($q,$p) if $zigzag && ($x&1);
@px2[$p..$p+5] = (0,$g,0, 0,$g,0);
@px2[$q..$q+5] = ($r,0,$b, $r,0,$b);
} unless $pset;
for my $y (0..99) {
for my $x (0..159) {
my($d) = $mat[$x%$mat_x][$y%$mat_y];
my(@p) = splice(@px, 0, 3);
$pset->($x,$y, $dR->($p[0],$d),$dG->($p[1],$d),$dB->($p[2],$d));
}
}
return &px2img(320,200,@px2);
}
sub dither_init {
my($in, $out, $dither, $w, $h) = @_;
my $expected_size = $h*(($w*3 + 3)&~3) + 54;
# param dither
@mat = ([1]);
@mat = ([1,3],
[4,2]) if $dither eq "bayer2";
@mat = ([7,8,2],
[6,9,4],
[3,5,1]) if $dither eq "sam3";
@mat = ([3,7,4],
[6,1,9],
[2,8,5]) if $dither eq "3x3";
@mat = ([6, 9, 7, 12],
[14, 3, 15, 1],
[8, 11, 5, 10],
[16, 2, 13, 4]) if $dither eq "vac4";
@mat = ([1,9,3,11],
[13,5,15,7],
[4,12,2,10],
[16,8,14,6]) if $dither eq "bayer4";
@mat = ([21,2,16,11,6],
[9,23,5,18,14],
[19,12,7,24,1],
[22,3,17,13,8],
[15,10,25,4,20]) if $dither eq "vac5";
@mat = ([35,57,19,55,7,51,4,21],
[29,6,41,27,37,17,59,45],
[61,15,53,12,62,25,33,9],
[23,39,31,49,2,47,13,43],
[3,52,8,22,36,58,20,56],
[38,18,60,46,30,5,42,28],
[63,26,34,11,64,16,54,10],
[14,48,1,44,24,40,32,50]) if $dither eq "vac8";
$mat_x = 1+$#mat;
$mat_y = 1+$#{$mat[0]};
@teo_pal = (0,100,127,142,163,179,191,203,215,223,231,239,243,247,251,255);
@lin_pal = (0, 33, 54, 69, 93,115,133,152,173,188,204,220,229,237,246,255);
my @pc2teo = ();
for my $i (1..$#teo_pal) {
my($a,$b,$c) = ($teo_pal[$i-1],($teo_pal[$i-1]+$teo_pal[$i])>>1,$teo_pal[$i]);
for my $j ($a..$b) {$pc2teo[$j] = $i-1;}
for my $j ($b+1..$c) {$pc2teo[$j] = $i;}
}
my @tabR = (0)x16;
my @tabG = (0)x16;
my @tabB = (0)x16;
if($file =~ /\.(jpg|jpeg|png|bmp)$/i || $file=~/\.gif$/i && (-s $file)<=500*1024) {
close($in);close($out);
my $img = Image::Magick->new();
my $x = $img->Read($file); die "$x, stopped $!" if $x;
$img->Resize(geometry=>"${w}x${h}");
&tune_image($img);
# trammage
my @px = $img->GetPixels(height=>$h, normalize=>"True");
undef $img;
for(my $i=0; $i<$#px; $i+=3) {
++$tabR[$pc2teo[int($px[$i+0]*255)]];
++$tabG[$pc2teo[int($px[$i+1]*255)]];
++$tabB[$pc2teo[int($px[$i+2]*255)]];
}
} else {
my $time = 1;
my $run = 1;
while(1) {
my $name = sprintf($img_pattern, $time);
if($expected_size != -s $name) {
last unless $run;
my $buf;
my $read = read($in,$buf,163840);
if($read) {
syswrite $out, $buf, $read;
} else {
$run = 0;
close($out);
}
} else {
# image complete!
print STDERR $time++,"s\r";
# lecture de l'image
my $img = Image::Magick->new();
my $x = $img->Read($name); die "$x, stopped $!" if $x;
unlink $name if $name ne $img_pattern;
&tune_image($img);
if(!defined $pal4096) {
my @px;
for my $r (0..15) {
for my $g (0..15) {
for my $b (0..15) {
push(@px, $teo_pal[$r], $teo_pal[$g], $teo_pal[$b]);
}
}
}
$pal4096 = &px2img(256,16, @px);
}
#$img->Remap(image=>$pal4096, dither=>"true", "dither-method"=>"Floyd-Steinberg");
# trammage
my @px = $img->GetPixels(height=>$h, normalize=>"True");
undef $img;
for(my $i=0; $i<$#px; $i+=3) {
++$tabR[$pc2teo[int($px[$i+0]*255)]];
++$tabG[$pc2teo[int($px[$i+1]*255)]];
++$tabB[$pc2teo[int($px[$i+2]*255)]];
}
}
}
close($in);close($out);
unlink(<tmp/img*.bmp>);
}
my $f = sub {
my($name, @t) = @_;
my($tot, $acc, $max, @r);
my(@tab) = splice(@t,0,16);
for my $t (@tab) {$max = $t if $t>$max; $tot += $t;}
$r[0] = -1; my($z) = $tot;
for my $t (@t) {
for(my $i=$r[$#r]+1; $i<16; ++$i) {
$acc += $tab[$i];
if($acc>=$t*$z) {
$z-=$acc; $acc=0;
push(@r, $i);
last;
}
}
}
shift @r;
if($#r!=$#t) {
my($deb,$fin)=($r[0], $r[$#r]);
$z = $tot;
for my $i (0..$deb) {$z -= $tab[$i];}
for my $i ($fin..15) {$z -= $tab[$i];}
@r = ($deb);
for my $t (@t[1..$#t-1]) {
for(my $i=$r[$#r]+1; $i<16; ++$i) {
$acc += $tab[$i];
if($acc>=$t*$z) {
$z-=$acc; $acc=0;
push(@r, $i);
last;
}
}
}
push(@r, $fin);
@r = sort { $a <=> $b } @r;
}
for my $i (0..$#tab) {print sprintf("%s%2d:%3d%% %s\n", $name, $i, int(100*$tab[$i]/($tot+1)), "X"x(int(50*$tab[$i]/($max+1))));}
print join(' ', @r),"\n";
return @r;
};
# ($RED0, $RED1, $RED2, $RED3) = $f->("RED", @tabR, 0.02, 0.33, 0.66, 0.95);
# ($GRN0, $GRN1, $GRN2, $GRN3) = $f->("GRN", @tabR, 0.02, 0.33, 0.66, 0.95);
# ($BLU0, $BLU1, $BLU2) = $f->("GRN", @tabR, 0.02, 0.50, 0.95);
($GRN0, $GRN1, $GRN2, $GRN3,$GRN4) = $f->("GRN", @tabG, 0.0, 0.20, 0.20, 0.50, 0.85);
($RED0, $RED1, $RED2, $RED3) = $f->("RED", @tabR, 0.0, 0.20, 0.50, 0.85);
($BLU0, $BLU1, $BLU2) = $f->("BLU", @tabB, 0.0, 0.50, 0.85);
}
sub px2img {
my($width,$height,@px) = @_;
open(PX2IMG,">/tmp/.toto2.pnm");
print PX2IMG "P6\n$width $height\n255\n",pack('C*', @px),"\n";
close(PX2IMG);
my $img2 = Image::Magick->new();
$img2->ReadImage("/tmp/.toto2.pnm");
unlink "/tmp/.toto2.pnm";
return $img2;
}
sub magick_init {
if(!$_magick) {
$_magick = 1;
eval 'use Image::Magick;';
# determination de l'espace RGB lineaire
my $img = Image::Magick->new(size=>"256x1", depth=>16);
$img->Read('gradient:black-white');
$img->Set(colorspace=>'RGB');
#$img->Set(colorspace=>"Gray") unless $coul;
my @px1 = $img->GetPixel(x=>128, y=>0);
$img->Read('gradient:black-white');
$img->Set(colorspace=>'sRGB');
#$img->Set(colorspace=>"Gray") unless $coul;
my @px2 = $img->GetPixel(x=>128, y=>0);
my $d1 = $px1[0]-0.5; $d1=-$d1 if $d1<0;
my $d2 = $px2[0]-0.5; $d2=-$d2 if $d2<0;
$LINEAR_SPACE = $d1>=$d2 ? "RGB" : "sRGB";
#print $px1[0], " ",$px2[0]," $LINEAR_SPACE\n";
}
}