SHOW:
|
|
- or go back to the newest paste.
1 | #/bin/perl | |
2 | # | |
3 | # Conversion d'image au format MAP thomson. | |
4 | # | |
5 | # Usage: perl <script.pl> <image>.<ext> <image>.<ext> ... | |
6 | # ou find <dir> -name '*.gif' | perl <script.pl> (entrée des images par l'entrée standard) | |
7 | # ==> produit x<image>.map (image thomson) et x<image>.gif (version visible ailleurs que thomson). | |
8 | # | |
9 | # Ce script est complètement expérimental et change tout le temps. Beaucoup de code est inutilisé | |
10 | # ou commenté du fait des évolution succéssives. Il n'est pas écrit pour être utilisé en production. | |
11 | # Un jour j'en ferais une version belle et propre en créant un plugin XnView. | |
12 | # | |
13 | # (c) Samuel DEVULDER 2007-2013 | |
14 | # | |
15 | ||
16 | #use Graphics::MagickXX; | |
17 | use Image::Magick; | |
18 | ||
19 | $SIG{'INT'} = 'DEFAULT'; | |
20 | $SIG{'CHLD'} = 'IGNORE'; | |
21 | ||
22 | # suppression du buffer pour l'affichage en sortie | |
23 | #$| = 1; | |
24 | ||
25 | # variables globale | |
26 | $glb_magick = Image::Magick->new; | |
27 | $glb_to7pal = 2; # 2 = palette TO7, 1 = TO7/70, 0 = TO9 | |
28 | $glb_maxcol = $glb_to7pal>1?8:16; # nb total de couls | |
29 | $glb_lab = 0; # distance couleur cielab | |
30 | $glb_dith = 0; # avec 3 ca donne des images pas mal colorées! | |
31 | $glb_gamma = 2.20; #1/0.45; | |
32 | $glb_clean = 0.2; | |
33 | $glb_att = 1; #.85; | |
34 | ||
35 | # error dispersion matrix. Index represents: | |
36 | # X 3 | |
37 | # 0 1 2 | |
38 | @glb_err = (0.000, 0.000, 0.000, 0.000) if 1; # no dith | |
39 | @glb_err = (0.200, 0.700, 0.100, 0.000) if 0; # nice lines | |
40 | @glb_err = (0.062, 0.312, 0.187, 0.437) if 0; # floyd steinberg | |
41 | @glb_err = (0.187, 0.312, 0.062, 0.437) if 1; # floyd steinberg | |
42 | @glb_err = (0.000, 0.500, 0.000, 0.500) if 0; # simple | |
43 | @glb_err = (0.000, 1.000, 0.000, 0.000) if 0; | |
44 | @glb_err = (0.100, 0.500, 0.100, 0.300) if 0; | |
45 | @glb_err = (0.300, 0.500, 0.100, 0.100) if 0; | |
46 | @glb_err = (0.250, 0.500, 0.125, 0.125) if 0; | |
47 | @glb_err = (0.500, 0.000, 0.500, 0.000) if 0; # motifs inca | |
48 | @glb_err = (0.200, 0.500, 0.100, 0.200) if 0; # motifs inca | |
49 | @glb_err = (0.000, 0.400, 0.400, 0.200) if 0; # motifs inca | |
50 | @glb_err = (0.250, 0.250, 0.000, 0.500) if 0; # sierra 2-4a | |
51 | @glb_err = (0.333, 0.334, 0.000, 0.333) if 0; | |
52 | @glb_err = (0.233, 0.333, 0.234, 0.200) if 0; # à voir | |
53 | @glb_err = (0.233, 0.367, 0.200, 0.200) if 1*0; # à voir | |
54 | ||
55 | @glb_err = (0.250, 0.500, 0.250, 0.000) if 0; | |
56 | ||
57 | @glb_err = (0.200, 0.233, 0.367, 0.200) if 0; # serpente | |
58 | @glb_err = (0.250, 0.500, 0.250, 0.000) if 0; | |
59 | ||
60 | @glb_err = (0.100, 0.500, 0.000, 0.400) if 0; # simple (horiz) | |
61 | @glb_err = (0.025, 0.125, 0.050, 0.125) if 0; # permet d'avoir des plages de couleurs constantes . Ca rend plutot pas mal pour les jeux videos. | |
62 | @glb_err = (0.050, 0.125, 0.050, 0.125) if 0; # permet d'avoir des plages de couleurs constantes . Ca rend plutot pas mal pour les jeux videos. | |
63 | @glb_err = (0.100, 0.150, 0.050, 0.200) if 0; # fs attenue (pas mal pour les jeux) | |
64 | @glb_err = (0.125, 0.250, 0.125, 0.250) if 0; # modified atkinson | |
65 | ||
66 | @mat = ( | |
67 | [ 0,48,12,60, 3,51,15,63], | |
68 | [32,16,44,28,35,19,47,31], | |
69 | [ 8,56, 4,52,11,59, 7,55], | |
70 | [40,24,36,20,43,27,39,23], | |
71 | [ 2,50,14,62, 1,49,13,61], | |
72 | [34,18,46,30,33,17,45,29], | |
73 | [10,58, 6,54, 9,57, 5,53], | |
74 | [42,26,38,22,41,25,37,21]); | |
75 | ||
76 | $mat_y = 1+$#mat; | |
77 | $mat_x = 1+$#{$mat[0]}; | |
78 | $max = 0; | |
79 | for $y (0..$mat_y-1) { | |
80 | for $x (0..$mat_x-1) { | |
81 | ++$mat[$y][$x]; | |
82 | $max = $mat[$y][$x] if $mat[$y][$x]>$max; | |
83 | } | |
84 | } | |
85 | for $y (0..$mat_y-1) { | |
86 | for $x (0..$mat_x-1) { | |
87 | $mat[$y][$x] /= $max; | |
88 | } | |
89 | } | |
90 | ||
91 | # construit les maps pour la multiplication | |
92 | for($i = -256; $i<256; ++$i) {$glb_map0[$i & 0x1ff] = xint($i * $glb_err[0] * &glb_att($i)) & 0x1ff;} | |
93 | for($i = -256; $i<256; ++$i) {$glb_map1[$i & 0x1ff] = xint($i * $glb_err[1] * &glb_att($i)) & 0x1ff;} | |
94 | for($i = -256; $i<256; ++$i) {$glb_map2[$i & 0x1ff] = xint($i * $glb_err[2] * &glb_att($i)) & 0x1ff;} | |
95 | for($i = -256; $i<256; ++$i) {$glb_map3[$i & 0x1ff] = xint($i * $glb_err[3] * &glb_att($i)) & 0x1ff;} | |
96 | for($i = -256; $i<256; ++$i) {$glb_sqr [$i & 0x1ff] = $i * $i;} | |
97 | $glb_err0 = $glb_err[0]>0; | |
98 | $glb_err1 = $glb_err[1]>0; | |
99 | $glb_err2 = $glb_err[2]>0; | |
100 | $glb_err3 = $glb_err[3]>0; | |
101 | ||
102 | # limit error | |
103 | $clamp = -48; | |
104 | for($i = -256; $i<256; ++$i) {$glb_clamp[$i & 0x1ff] = ($i< $clamp ? $clamp : $i) & 0x1ff;} | |
105 | ||
106 | # map une intensité entre 0..255 vers l'intensité produite par le circuit EFxxx le plus proche (16 valeurs) | |
107 | @ef_vals = (0, 39, 74, 101, 122, 140, 157, 171, 185, 195, 206, 216, 227, 237, 248, 255) if 1; | |
108 | ||
109 | # eval perso | |
110 | @ef_vals = (0,78,116,138,157,171,182,187,205,215,222,229,238,244,249,255) if 0; | |
111 | @ef_vals = (0,51,91,117,142,161,172,187,199,210,220,227,236,244,248,255) if 1; | |
112 | ||
113 | # ef TEO | |
114 | @ef_vals = (0, 100, 127, 142, 163, 179, 191,203, 215, 223, 231, 239, 243, 247, 251, 255) if 1; | |
115 | @ef_vals = (0, 127, 169, 188, 198, 205, 212, 219, 223, 227, 232, 239, 243, 247, 251, 255) if 0; # eval prehisto | |
116 | @ef_vals = (0, 174, 192, 203, 211, 218, 224, 229, 233, 237, 240, 244, 247, 249, 252, 255) if 0; # prehisto 2 | |
117 | @ef_vals = (0, 169, 188, 200, 209, 216, 222, 227, 232, 236, 239, 243, 246, 249, 252, 255) if 0; # prehisto 3 | |
118 | @ef_vals = (0, 153, 175, 189, 199, 207, 215, 221, 227, 232, 236, 241, 245, 248, 252, 255) if 0; # prehisto 4 | |
119 | ||
120 | @intens = @ef_vals; | |
121 | #@intens = (0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 218, 224, 240, 255); | |
122 | #@intens = (0, 66, 96, 120, 138, 153, 168, 180, 192, 201, 210, 219, 228, 237, 246, 252); | |
123 | #@intens = (0, 32, 66, 98, 128, 152, 170, 185, 200, 212, 224, 233, 245, 255); | |
124 | #@intens = (0, 60, 98, 128, 152, 170, 185, 200, 212, 224, 233, 245, 255); | |
125 | @intens = (0, 32, 64, 96, 128, 160, 192, 255) if 0; | |
126 | @intens = (0, 16, 32, 48, 64, 96, 128, 160, 192, 224, 255) if 0; | |
127 | @intens = (0, 32, 64, 128, 255) if 0; | |
128 | @intens = (0, 50, 100, 200, 255) if 0; ## <== pas mal | |
129 | @intens = (0, 32, 64, 128, 192, 255) if 0; | |
130 | @intens = (0, 33, 66, 99, 133, 166, 200, 255) if 0; | |
131 | @intens = (0, 98, 128, 152, 170, | |
132 | 185, #200, | |
133 | 212, #224, | |
134 | 233, #241, | |
135 | 251) if 0; | |
136 | @intens = (0, 45, 98, 185, 255) if 0; | |
137 | @intens = (0, 16, 32, 64, 128, 192, 224, 240, 255) if 0; ## | |
138 | @intens = (0, 32, 64, 128, 192, 224, 255) if 0; | |
139 | @intens = (0, 16, 32, 64, 128, 255) if 0; | |
140 | ||
141 | # basse luminosité | |
142 | @intens = (0, 39, 74, 122, 195, 227, 255) if 0; # ajustement aux niveaux thomson | |
143 | @intens = (0, 39, 74, 122, 195, 227, 248) if 0; # ajustement aux niveaux thomson | |
144 | @intens = (0, 39, 101, 195, 255) if 0; | |
145 | @intens = (0, 39, 74, 122, 185, 216, 255) if 0; # ajustement aux niveaux thomson | |
146 | ||
147 | # 0 64 128 192 256~ | |
148 | # 0 32 64 96 128 160 192 224 256 | |
149 | @intens = (0, 78, 116, 138, 157, 187, 222, 238, 255) if 0; | |
150 | @intens = (0, 78, 116, 157, 195, 222, 255) if 0; | |
151 | @intens = (0, 78, 138, 222, 255) if 0; | |
152 | @intens = (0, 78, 138, 157, 255) if 0; | |
153 | @intens = (0, 78, 157, 244) if 0; | |
154 | @intens = (0, 138, 255) if 0; | |
155 | @intens = (0, 51, 91, 117, 161, 187, 227, 255) if 0; | |
156 | @intens = (0, 42, 84, 126, 168, 210, 255) if 0; | |
157 | @intens = (0, 51, 102, 153, 204, 255) if 0; | |
158 | @intens = (0, 16, 32, 64, 128, 192, 224, 240, 255) if 0; | |
159 | @intens = (0, 100, 127, 142, 179, 215, 255) if 0; | |
160 | ||
161 | # equi reparti | |
162 | @intens = (0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 255) if 0; | |
163 | @intens = (0, 32, 64, 96, 128, 160, 192, 224, 255) if 0; | |
164 | @intens = (0, 48, 96, 144, 192, 255) if 0; | |
165 | ||
166 | if($glb_gamma) { | |
167 | #print join(",", @intens), "\n"; | |
168 | foreach (@intens) {$_ = &gamma($_);} | |
169 | #print join(",", @intens), "\n"; | |
170 | foreach (@ef_vals) {$_ = &gamma($_);} | |
171 | } | |
172 | ||
173 | # remap des intens | |
174 | for($i=0; $i<=$#intens; ++$i) { | |
175 | my($z) = 0; | |
176 | for($j=0, $m=1e30; $j<=$#ef_vals; ++$j) { | |
177 | next if $ef_vals[$j]<0; | |
178 | $k = $intens[$i] - $ef_vals[$j]; $k = -$k if $k<0; | |
179 | if($k<$m) {$m=$k; $z = $ef_vals[$j];} | |
180 | } | |
181 | $intens[$i] = $z; | |
182 | } | |
183 | ||
184 | # mapping des intensités | |
185 | @map_ef = (); | |
186 | for($i=0; $i<256; ++$i) { | |
187 | for($j=0, $m=1e30; $j<=$#intens; ++$j) { | |
188 | next if $intens[$j]<0; | |
189 | $k = $i - $intens[$j]; $k = -$k if $k<0; | |
190 | if($k<$m) {$m=$k; $map_ef[$i] = $intens[$j];} | |
191 | } | |
192 | for($j=0; $j<=$#intens && $intens[$j]<=$i; ++$j) { | |
193 | next if $intens[$j]<0; | |
194 | $map_ef2[$i] = $intens[$j]; | |
195 | } | |
196 | } | |
197 | #@map_ef = (); | |
198 | ||
199 | ||
200 | # analyse des fichiers en argments | |
201 | @files = @ARGV; | |
202 | ||
203 | # si aucun fichier, alors on les prends depuis l'entrée standard | |
204 | if(!@files) { | |
205 | while(<>) { | |
206 | chomp; | |
207 | next if /chlgdls/; | |
208 | y%\\%/%; | |
209 | s%^([\S]):%/cygdrive/$1%; | |
210 | push(@files, $_); | |
211 | } | |
212 | } | |
213 | ||
214 | # extension supportées | |
215 | $supported_ext = "\.(gif|pnm|png|jpg|jpeg|ps)"; | |
216 | # fichier a effacer pour stopper le prog | |
217 | $stopme = ".stop_me"; | |
218 | open(f, ">$stopme"); close(f); | |
219 | ||
220 | #&start_wd; | |
221 | ||
222 | # traitement de tous les fichiers | |
223 | $cpt = 0; | |
224 | foreach $in (@files) { | |
225 | last if ! -e "$stopme"; | |
226 | next unless $in =~ /$supported_ext$/i; | |
227 | ||
228 | ++$cpt; | |
229 | ||
230 | next if $in eq "-"; | |
231 | #next if $in =~ /ord/; | |
232 | next if $in =~ /6846/; | |
233 | ||
234 | $out = $in; $out=~s/$supported_ext$/.gif/i; $out=~s/.*[\/\\]//; | |
235 | $out = "x$out"; | |
236 | ||
237 | print $cpt,"/",1+$#files," $in => $out\n"; | |
238 | ||
239 | &reset_wd; | |
240 | ||
241 | next if -e $out; | |
242 | ||
243 | # lecture | |
244 | my(@px) = &read_image($in); | |
245 | ||
246 | @px = &cleanup(@px) if 1; | |
247 | ||
248 | # creation palette 16 couls (passage par une globale pour simplifier le code) | |
249 | @glb_pal = &find_palette($glb_maxcol, @px); | |
250 | ||
251 | #&print_pal(@glb_pal); | |
252 | ||
253 | # precalc distance entre les couleurs de la palette | |
254 | $glb_dist = (); | |
255 | for($i=0; $i<$glb_maxcol; ++$i) { | |
256 | $glb_dist[$i + $i*$glb_maxcol] = 0; | |
257 | for($j = 0; $j<$i; ++$j) { | |
258 | $glb_dist[$j + $i*$glb_maxcol] = $glb_dist[$i + $j*$glb_maxcol] = &irgb_dist($glb_pal[$i], $glb_pal[$j]); | |
259 | } | |
260 | } | |
261 | ||
262 | if(0) { | |
263 | $px[1] = &rgb2irgb(1,1,1); | |
264 | $px[2] = &rgb2irgb(1,1,1); | |
265 | $px[3] = &rgb2irgb(1,1,1); | |
266 | $px[321] = &rgb2irgb(1,1,1); | |
267 | $px[322] = &rgb2irgb(1,1,1); | |
268 | $px[323] = &rgb2irgb(1,1,1); | |
269 | } | |
270 | ||
271 | if(0) { | |
272 | # pour tester : dither sans contrainte | |
273 | my(@p2) = @px; | |
274 | for($p=$y=0; $y<200; ++$y) { | |
275 | for($x=0; $x<320; ++$x) { | |
276 | $p = 320*$y+$x; | |
277 | my($rvb) = $p2[$p] = &irgb_map($p2[$p], \@glb_clamp); | |
278 | for($i=0, $dm=1e30; $i<$glb_maxcol; ++$i) { | |
279 | $d = &irgb_dist($rvb, $glb_pal[$i]); | |
280 | print &irgb2hex($glb_pal[$i]), " $i => $d\n" if 0; | |
281 | if($d<$dm) {$dm = $d; $p2[$p] = $glb_pal[$i];} | |
282 | } | |
283 | print "$x,$y : ", &irgb2hex($rvb), "=>", &irgb2hex($p2[$p]), " $dm\n\n" if 0; | |
284 | $rvb = &irgb_sub($rvb, $p2[$p]); | |
285 | #print " /_\\ = ", &irgb2hex($rvb), "\n"; | |
286 | $p2[$p + 319] = &irgb_add_cln($p2[$p + 319], &irgb_map($rvb, \@glb_map0)) if $glb_err0 && $y<199 && $x>0; | |
287 | $p2[$p + 320] = &irgb_add_cln($p2[$p + 320], &irgb_map($rvb, \@glb_map1)) if $glb_err1 && $y<199; | |
288 | $p2[$p + 321] = &irgb_add_cln($p2[$p + 321], &irgb_map($rvb, \@glb_map2)) if $glb_err2 && $y<199 && $x<319; | |
289 | $p2[$p + 001] = &irgb_add_cln($p2[$p + 001], &irgb_map($rvb, \@glb_map3)) if $glb_err3 && $x<319; | |
290 | } | |
291 | } | |
292 | &write_image("${out}.gif", @p2); | |
293 | } | |
294 | ||
295 | my(%palx); | |
296 | foreach $i (@px) {$palx{$i} = 1;last if length(keys %palx)>2;} | |
297 | $mono = length(keys %palx)<3; | |
298 | %palx = (); | |
299 | ||
300 | # process image | |
301 | my($p, $y, $x) = (0,0,0); | |
302 | for($y=0; $y<200; ++$y) { | |
303 | print "\r> ", int($y/2), "% "; | |
304 | for($x=0; $x<320; $x+=8) { | |
305 | $p = $y * 320 + $x; | |
306 | #for($i=0; $i<8; ++$i) {$px[$p+$i] = &irgb_map($px[$p+$i], \@glb_clamp);} | |
307 | for($i=0; $i<8; ++$i) {$px[$p+$i] = &irgb_sat($px[$p+$i]);} | |
308 | my($forme, $fond) = &couple6(@px[$p..$p+7]); | |
309 | #print "===> ", &irgb2hex($forme), " ", &irgb2hex($fond),"\n"; | |
310 | for($i=0; $i<8; ++$i, ++$p) { | |
311 | my($rvb) = &irgb_sat($px[$p]); | |
312 | #$rvb = &irgb_add_cln($rvb, &rgb2irgb(rand(0.02),rand(0.02),rand(0.02))) if 0; #($i+$y) & 8; | |
313 | # meilleur couleur approchante | |
314 | $px[$p] = (&irgb_dist($forme, $rvb) < &irgb_dist($fond, $rvb)) ? $forme : $fond; | |
315 | #print $i,"::", &irgb2hex($rvb),"=>",&irgb2hex($px[$p]),"\n"; | |
316 | #for($dm = 1e30, $k = 0; $k<$glb_maxcol; ++$k) {if(($d = &irgb_dist($rvb, $glb_pal[$k])) < $dm) {$dm = $d; $px[$p] = $glb_pal[$k];}}; | |
317 | ||
318 | #if(($px[$p] & 0xff) > 0x40) { | |
319 | # print &irgb2hex($px[$p]),"\n", &irgb2hex($rvb), " f=", &irgb2hex($forme), ":", &irgb_dist($forme, $rvb)," F=", &irgb2hex($fond),":",&irgb_dist($fond,$rvb),"\n"; | |
320 | #} | |
321 | #if(($px[$p] & 0xff) < 0x80) { | |
322 | # print &irgb2hex($px[$p]),"\n", &irgb2hex($rvb), " f=", &irgb2hex($forme), ":", &irgb_dist($forme, $rvb)," F=", &irgb2hex($fond),":",&irgb_dist($fond,$rvb),"\n"; | |
323 | #} | |
324 | ||
325 | # diffusion d'erreur | |
326 | if(!$mono) { | |
327 | #print " p=",&irgb2hex($rvb); | |
328 | $rvb = &irgb_sub($rvb, $px[$p]); | |
329 | #print " q=", &irgb2hex($px[$p]), " d=", &irgb2hex($rvb); | |
330 | #print " m=", irgb2hex(&irgb_map($rvb, \@glb_map1)), " n=", &irgb2hex($px[$p+320]), " X=", &irgb2hex(&irgb_uadd($px[$p + 320], &irgb_map($rvb, \@glb_map1))), "\n"; | |
331 | $px[$p + 319] = &irgb_add_cln($px[$p + 319], &irgb_map($rvb, \@glb_map0)) if $glb_err0 && $y<199 && ($x+$i)>0; | |
332 | $px[$p + 320] = &irgb_add_cln($px[$p + 320], &irgb_map($rvb, \@glb_map1)) if $glb_err1 && $y<199; | |
333 | $px[$p + 321] = &irgb_add_cln($px[$p + 321], &irgb_map($rvb, \@glb_map2)) if $glb_err2 && $y<199 && ($x+$i)<319; | |
334 | $px[$p + 001] = &irgb_add_cln($px[$p + 001], &irgb_map($rvb, \@glb_map3)) if $glb_err3 && ($x+$i)<319; | |
335 | } | |
336 | # pour voir les limites octets | |
337 | $px[$p] = $i&1? $forme : $fond if 0; | |
338 | $px[$p] ^= 0x0ff3fcff if $i==0 && 0; | |
339 | } | |
340 | } | |
341 | $| = 1; print "\r"; $| = 0; | |
342 | } | |
343 | %dist_cache = (); | |
344 | ||
345 | # ecriture des pixels et lecture | |
346 | #$out =~ s/.gif$/.c16.gif/; | |
347 | &write_image($out, @px); | |
348 | ||
349 | $out =~ s/.gif$//; | |
350 | #&write_map("$out.mpa", 1, @px); | |
351 | #&write_map("$out.mpb", 2, @px); | |
352 | &write_map("$out.map", 3, @px); | |
353 | ||
354 | sleep(1); | |
355 | } | |
356 | unlink($stopme); | |
357 | ||
358 | if(0) { | |
359 | %m = (); | |
360 | foreach $out (<rgb/*.MAP>) { | |
361 | open(IN, "cygpath -w -s \"$out\" |"); $zz = <IN>; chomp($zz); close(IN); | |
362 | $zz=~y/~\\/_\//; | |
363 | $m{$out} = $zz; | |
364 | } | |
365 | foreach $out (keys %m) { | |
366 | rename($out, $m{$out}); | |
367 | } | |
368 | } | |
369 | ||
370 | sub glb_att { | |
371 | return 1; | |
372 | my($i) = @_; | |
373 | $i = -$i if $i<0; | |
374 | return 1 if $i>32; | |
375 | return .5*(1+$i/32)/2; | |
376 | } | |
377 | ||
378 | sub print_pal { | |
379 | my(@pal) = @_; | |
380 | my($i, @t); | |
381 | foreach $i (@pal) { | |
382 | my($r) = ($i>>20) & 255; | |
383 | my($g) = ($i>>10) & 255; | |
384 | my($b) = ($i>>00) & 255; | |
385 | ||
386 | push(@t, sprintf("%3d,%3d,%3d", $r, $g, $b)); | |
387 | } | |
388 | for $i (sort(@t)) { | |
389 | print "$i\n"; | |
390 | } | |
391 | } | |
392 | # retourne la palette TO7/70 | |
393 | sub to770_palette { | |
394 | return ( | |
395 | &rgb2irgb(0.0000,0.0000,0.0000), &rgb2irgb(1.0000,0.0000,0.0000), | |
396 | &rgb2irgb(0.0000,1.0000,0.0000), &rgb2irgb(1.0000,1.0000,0.0000), | |
397 | &rgb2irgb(0.0000,0.0000,1.0000), &rgb2irgb(1.0000,0.0000,1.0000), | |
398 | &rgb2irgb(0.0000,1.0000,1.0000), &rgb2irgb(1.0000,1.0000,1.0000), | |
399 | &rgb2irgb(0.4375,0.4375,0.4375), &rgb2irgb(0.6250,0.1875,0.1875), | |
400 | &rgb2irgb(0.1875,0.6250,0.1875), &rgb2irgb(0.6250,0.6250,0.1875), | |
401 | &rgb2irgb(0.1875,0.1875,0.6250), &rgb2irgb(0.6250,0.1875,0.6250), | |
402 | &rgb2irgb(0.4375,0.8750,0.8750), &rgb2irgb(0.5375,0.6875,0.0000) | |
403 | ) if 0; # pas de gamma | |
404 | return ( | |
405 | &rgb2irgb(0.0000,0.0000,0.0000), &rgb2irgb(1.0000,0.0000,0.0000), | |
406 | &rgb2irgb(0.0000,1.0000,0.0000), &rgb2irgb(1.0000,1.0000,0.0000), | |
407 | &rgb2irgb(0.0000,0.0000,1.0000), &rgb2irgb(1.0000,0.0000,1.0000), | |
408 | &rgb2irgb(0.0000,1.0000,1.0000), &rgb2irgb(1.0000,1.0000,1.0000), | |
409 | &rgb8irgb(212, 212, 212), &rgb8irgb(242, 152, 152), | |
410 | &rgb8irgb(152, 242, 152), &rgb8irgb(242, 242, 152), | |
411 | &rgb8irgb(152, 152, 242), &rgb8irgb(242, 152, 242), | |
412 | &rgb8irgb(212, 255, 255), &rgb8irgb(255, 211, 1), | |
413 | ) if 1; # gamma | |
414 | return ( | |
415 | &rgb2irgb(0.0000,0.0000,0.0000), &rgb2irgb(1.0000,0.0000,0.0000), | |
416 | &rgb2irgb(0.0000,1.0000,0.0000), &rgb2irgb(1.0000,1.0000,0.0000), | |
417 | &rgb2irgb(0.0000,0.0000,1.0000), &rgb2irgb(1.0000,0.0000,1.0000), | |
418 | &rgb2irgb(0.0000,1.0000,1.0000), &rgb2irgb(1.0000,1.0000,1.0000), | |
419 | &rgb2irgb(0.6980,0.6980,0.6980), &rgb2irgb(0.8320,0.4640,0.4640), | |
420 | &rgb2irgb(0.4640,0.8320,0.4640), &rgb2irgb(0.8320,0.8320,0.4640), | |
421 | &rgb2irgb(0.4640,0.4640,0.8320), &rgb2irgb(0.8320,0.4640,0.8320), | |
422 | &rgb2irgb(0.6980,0.9680,0.9680), &rgb2irgb(0.8710,0.4640,0.0000) | |
423 | ); # gamma | |
424 | } | |
425 | ||
426 | sub rgb8irgb { | |
427 | return &rgb2irgb($_[0]/255.0, $_[1]/255.0, $_[2]/255.0); | |
428 | } | |
429 | ||
430 | sub test_niveaux { | |
431 | my (@args) = @_; | |
432 | ||
433 | my($dither) = 0; | |
434 | ||
435 | # args=(seuil, niveaux..., -max, pixels...) | |
436 | my($seuil, $max, $t, @niv, @px, @pal) = 0; | |
437 | ||
438 | foreach $t (@args) { | |
439 | if($seuil==0) { | |
440 | $seuil = $t; | |
441 | $max = 0; | |
442 | } elsif($max==0) { | |
443 | if($t>=0) { | |
444 | push(@niv, $map_ef[$t]); | |
445 | } else { | |
446 | $max = -$t; | |
447 | for($t=0; $t<256; ++$t) { | |
448 | my($m, $d, $n); $m = 1e30; | |
449 | foreach $n (@niv) {$d = $n - $t; $d = -$d if $d<0; if($d<$m) {$m = $d; $pal[$t] = $n;}} | |
450 | } | |
451 | } | |
452 | } else { | |
453 | push(@px, $t) if 1; | |
454 | push(@px, ((($map_ef[$t>>20]<<10) + $map_ef[($t>>10) & 0xff])<<10) + $map_ef[$t & 0xff]) if 0; | |
455 | } | |
456 | } | |
457 | my($w, $h) = (320, 200); | |
458 | ($w, $h) = (160, 200) if 160*200==1+$#px; | |
459 | ($w, $h) = (160, 100) if 160*100==1+$#px; | |
460 | ($w, $h) = (80, 100) if 80*100==1+$#px; | |
461 | ($w, $h) = (80, 50) if 80*50==1+$#px; | |
462 | $seuil = $seuil*$seuil*$w*$h; | |
463 | ||
464 | # color reduce | |
465 | @niv = (); | |
466 | my($x, $y, $p, $m, $d, @out); | |
467 | if($dither) { | |
468 | my @tmp = @px; | |
469 | for($y=0, $p=0; $y<$h; ++$y) { | |
470 | for($x=0; $x<$w; ++$x, ++$p) { | |
471 | $rvb = $px[$p]; | |
472 | my ($r,$v,$b) = ($pal[$rvb>>20], $pal[($rvb>>10) & 0xff], $pal[$rvb & 0xff]); | |
473 | push(@niv, $r, $v, $b); | |
474 | $px[$p] = ($r<<20) + ($v<<10) + $b; | |
475 | $rvb = &fs_diff($rvb, $px[$p]); | |
476 | $px[$p + $w - 1] = &fs_prop($px[$p + $w - 1], $rvb, \@glb_map0) if $glb_err0 && $y<$h-1 && $x>0; | |
477 | $px[$p + $w + 0] = &fs_prop($px[$p + $w + 0], $rvb, \@glb_map1) if $glb_err1 && $y<$h-1; | |
478 | $px[$p + $w + 1] = &fs_prop($px[$p + $w + 1], $rvb, \@glb_map2) if $glb_err2 && $y<$h-1 && $x<$w-1; | |
479 | $px[$p + 000001] = &fs_prop($px[$p + 000001], $rvb, \@glb_map3) if $glb_err3 && $x<$w-1; | |
480 | } | |
481 | } | |
482 | @px = @tmp; | |
483 | } else { | |
484 | foreach $t (@px) {push(@niv, $pal[$t>>20], $pal[($t>>10) & 0xff], $pal[$t & 0xff]);} | |
485 | } | |
486 | open(OUT,">.toto2.pnm"); print OUT "P6\n$w $h\n255\n", pack('C*', @niv), "\n"; close(OUT); | |
487 | @$glb_magick = (); | |
488 | $glb_magick->Set(page=>"$wx$h+0+0"); | |
489 | $glb_magick->Read(".toto2.pnm"); | |
490 | $glb_magick->Write(".toto3.png"); | |
491 | unlink(".toto2.pnm"); | |
492 | ||
493 | $glb_magick->Quantize(colors=>$max, colorspace=>"RGB", treedepth=>0, dither=>"False"); | |
494 | $glb_magick->Write(".toto4.png"); | |
495 | @niv = $glb_magick->GetPixels(map=>"RGB", height=>$h, normalize=>"True"); | |
496 | my(%pal, $rvb); | |
497 | for($t=$#niv+1; ($t-=3)>=0;) { | |
498 | $rvb = &rgb2irgb(@niv[$t..$t+2]); | |
499 | $rvb = ((($pal[$rvb>>20]<<10) + $pal[($rvb>>10) & 0xff])<<10) + $pal[$rvb & 0xff]; | |
500 | $pal{$rvb} = 1; | |
501 | } | |
502 | @niv = (keys(%pal), (0) x $max)[0..($max-1)]; | |
503 | ||
504 | # dither & calcul d'erreur | |
505 | my($err) = 0; | |
506 | my($cache, %cache) = 1; | |
507 | for($y=$p=0; $err < $seuil && $y<$h; ++$y) { | |
508 | for($x=0; $err < $seuil && $x<$w; ++$x, ++$p) { | |
509 | $rvb = $px[$p]; | |
510 | # on trouve le niv le plus proche | |
511 | $t = $cache{$rvb} if $cache; | |
512 | if(!$cache || !defined($t)) { | |
513 | $m = 1e30; foreach $t (@niv) {$d = &irgb_dist($t, $rvb); if($d<$m) {$m = $d;$px[$p] = $t;}} | |
514 | $cache{$rvb} = $px[$p] if $cache; | |
515 | } else { | |
516 | $m = &irgb_dist($t, $rvb); | |
517 | $px[$p] = $t; | |
518 | } | |
519 | push(@out, $px[$p]>>20, ($px[$p]>>10)&255, $px[$p]&255); | |
520 | $err += &sq($m); | |
521 | if($dither) { | |
522 | $rvb = &fs_diff($rvb, $px[$p]); | |
523 | $px[$p + $w - 1] = &fs_prop($px[$p + $w - 1], $rvb, \@glb_map0) if $glb_err0 && $y<$h-1 && $x>0; | |
524 | $px[$p + $w + 0] = &fs_prop($px[$p + $w + 0], $rvb, \@glb_map1) if $glb_err1 && $y<$h-1; | |
525 | $px[$p + $w + 1] = &fs_prop($px[$p + $w + 1], $rvb, \@glb_map2) if $glb_err2 && $y<$h-1 && $x<$w-1; | |
526 | $px[$p + 000001] = &fs_prop($px[$p + 000001], $rvb, \@glb_map3) if $glb_err3 && $x<$w-1; | |
527 | } | |
528 | } | |
529 | } | |
530 | ||
531 | if($err < $seuil) { | |
532 | open(OUT,">.toto2.pnm"); print OUT "P6\n$w $h\n255\n", pack('C*', @out), "\n"; close(OUT); | |
533 | @$glb_magick = (); | |
534 | $glb_magick->Set(page=>"$wx$h+0+0"); | |
535 | $glb_magick->Read(".toto2.pnm"); | |
536 | $glb_magick->Write(".toto2.png"); | |
537 | unlink(".toto2.pnm"); | |
538 | } | |
539 | ||
540 | # fini | |
541 | $glb_magick->Set(page=>"320x200+0+0"); | |
542 | sleep(0.5); | |
543 | return (sprintf("%.05f", sqrt($err/$w/$h)), @niv); | |
544 | } | |
545 | ||
546 | # calcul d'une palette de 16 couleurs | |
547 | sub find_palette_orig { | |
548 | my($max, @px) = @_; | |
549 | ||
550 | # cas TO7 | |
551 | return &to770_palette if $glb_to7pal; | |
552 | ||
553 | # vrai algo | |
554 | my($mask) = 0x0f03c0f0; $mask = -1; | |
555 | ||
556 | # si l'image a suffisament peu de couleurs alors on retourne la palette de l'image | |
557 | # directement | |
558 | my($i, %pal); | |
559 | foreach $i (@px) { | |
560 | $pal{$i & $mask} = 1; | |
561 | last if length(keys %pal)>$max; | |
562 | } | |
563 | my(@t) = keys(%pal); | |
564 | return @t if $#t<$max; | |
565 | %pal = (); | |
566 | ||
567 | # sinon on quantifie l'image: | |
568 | ||
569 | #return &xxx_palette($max, @px) if $#map_ef>=0; | |
570 | ||
571 | # on remap l'image aux niveau produits par les puces thomson! | |
572 | if($#map_ef>=0) { | |
573 | @t = (); | |
574 | my($x, $y, $p, $rvb, $r, $v, $b); | |
575 | for($y=0, $p=0; $y<200; ++$y) { | |
576 | for($x=0; $x<320; ++$x, ++$p) { | |
577 | $rvb = $px[$p]; | |
578 | $r=$map_ef[$rvb>>20]; $v=$map_ef[($rvb>>10) & 0xff]; $b=$map_ef[$rvb & 0xff]; | |
579 | push(@t, &ammag($r), &ammag($v), &ammag($b)); | |
580 | #push(@t, $r=($rvb>>20), $v=(($rvb>>10) & 0xff), $b=($rvb & 0xff)); | |
581 | if(1) { | |
582 | $px[$p] = ((($r<<10)+$v)<<10)+$b; | |
583 | $rvb = &irgb_sub($rvb, $px[$p]); | |
584 | $px[$p + 319] = &irgb_uadd($px[$p + 319], &irgb_map($rvb, \@glb_map0)) if $glb_err0 && $y<199 && $x>0; | |
585 | $px[$p + 320] = &irgb_uadd($px[$p + 320], &irgb_map($rvb, \@glb_map1)) if $glb_err1 && $y<199; | |
586 | $px[$p + 321] = &irgb_uadd($px[$p + 321], &irgb_map($rvb, \@glb_map2)) if $glb_err2 && $y<199 && $x<319; | |
587 | $px[$p + 001] = &irgb_uadd($px[$p + 001], &irgb_map($rvb, \@glb_map3)) if $glb_err3 && $x<319; | |
588 | } | |
589 | } | |
590 | } | |
591 | open(OUT,">.toto2.pnm"); print OUT "P6\n320 200\n255\n", pack('C*', @t), "\n"; close(OUT); | |
592 | @$glb_magick = (); | |
593 | $glb_magick->Read(".toto2.pnm"); | |
594 | #$glb_magick->Resize(geometry=>"160x100!"); | |
595 | #$glb_magick->Resize(geometry=>"320x200!"); | |
596 | $glb_magick->Write(".toto2.png"); | |
597 | #$glb_magick->Read(".toto2.png"); | |
598 | unlink(".toto2.pnm"); | |
599 | } | |
600 | ||
601 | if(0) { #recherche | |
602 | # sinon on quantifie l'image: | |
603 | my($c, $err, @pal, $e, @p) = (0, 1e30); | |
604 | ||
605 | # on divise le nombre de pixels par 4 | |
606 | if(1) { | |
607 | my($x, $y, $p, @t); | |
608 | for($p=$y=0; $y<200; $y+=2, $p+=320) { | |
609 | for($x=0; $x<320; $x+=2, $p+=2) { | |
610 | push(@t, &irgb_avg(&irgb_avg($px[$p], $px[$p+1]), &irgb_avg($px[$p+320], $px[$p+321]))); | |
611 | } | |
612 | } | |
613 | @px = @t; @t = (); | |
614 | if(0) { | |
615 | for($p=$y=0; $y<100; $y+=1, $p+=0) { | |
616 | for($x=0; $x<160; $x+=2, $p+=2) { | |
617 | push(@t, &irgb_avg($px[$p], $px[$p+1])); | |
618 | } | |
619 | } | |
620 | @px = @t; @t = (); | |
621 | if(0) { | |
622 | for($p=$y=0; $y<100; $y+=2, $p+=80) { | |
623 | for($x=0; $x<80; $x+=1, $p+=1) { | |
624 | push(@t, &irgb_avg($px[$p], $px[$p+80])); | |
625 | } | |
626 | } | |
627 | @px = @t; @t = (); | |
628 | } | |
629 | } | |
630 | } | |
631 | ||
632 | # 0 | |
633 | ($e, @p) = &test_niveaux($err, @ef_vals, -$max, @px); | |
634 | print $c++,"=$e"; if($e < $err) {@pal = @p; $err = $e; print "*";} print "\n"; | |
635 | ||
636 | # 1 | |
637 | #($e, @p) = &test_niveaux($err, (0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 255), -$max, @px); | |
638 | ($e, @p) = &test_niveaux($err, (0, 50, 100, 150, 200, 250), -$max, @px); | |
639 | print $c++,"=$e"; if($e < $err) {@pal = @p; $err = $e; print "*";} print "\n"; | |
640 | ||
641 | # 2 | |
642 | ($e, @p) = &test_niveaux($err, (0, 32, 64, 96, 128, 160, 192, 224, 255), -$max, @px); | |
643 | print $c++,"=$e"; if($e < $err) {@pal = @p; $err = $e; print "*";} print "\n"; | |
644 | ||
645 | # 3 | |
646 | ($e, @p) = &test_niveaux($err, (0, 50, 100, 200, 255), -$max, @px); | |
647 | print $c++,"=$e"; if($e < $err) {@pal = @p; $err = $e; print "*";} print "\n"; | |
648 | ||
649 | # 4 | |
650 | ($e, @p) = &test_niveaux($err, (0, 100, 140, 180, 200, 220, 240, 255), -$max, @px); | |
651 | print $c++,"=$e"; if($e < $err) {@pal = @p; $err = $e; print "*";} print "\n"; | |
652 | ||
653 | # 5 | |
654 | ($e, @p) = &test_niveaux($err, (0, 32, 64, 128, 192, 224, 255), -$max, @px); | |
655 | print $c++,"=$e"; if($e < $err) {@pal = @p; $err = $e; print "*";} print "\n"; | |
656 | ||
657 | # 6 | |
658 | ($e, @p) = &test_niveaux($err, (0, 16, 32, 48, 80, 112, 144, 208, 208, 240, 255), -$max, @px); | |
659 | print $c++,"=$e"; if($e < $err) {@pal = @p; $err = $e; print "*";} print "\n"; | |
660 | ||
661 | # 7 | |
662 | ($e, @p) = &test_niveaux($err, (0, 16, 32, 64, 128, 192, 224, 240, 255), -$max, @px); | |
663 | print $c++,"=$e"; if($e < $err) {@pal = @p; $err = $e; print "*";} print "\n"; | |
664 | ||
665 | # 8 | |
666 | ($e, @p) = &test_niveaux($err, (0, 64, 128, 192, 255), -$max, @px); | |
667 | print $c++,"=$e"; if($e < $err) {@pal = @p; $err = $e; print "*";} print "\n"; | |
668 | ||
669 | # 9 | |
670 | ($e, @p) = &test_niveaux($err, (0, 48, 96, 144, 192, 255), -$max, @px); | |
671 | print $c++,"=$e"; if($e < $err) {@pal = @p; $err = $e; print "*";} print "\n"; | |
672 | ||
673 | # 10 | |
674 | ($e, @p) = &test_niveaux($err, (0, 96, 112, 128, 144, 192, 255), -$max, @px); | |
675 | print $c++,"=$e"; if($e < $err) {@pal = @p; $err = $e; print "*";} print "\n"; | |
676 | ||
677 | # 11 | |
678 | ($e, @p) = &test_niveaux($err, (0, 128, 255), -$max, @px); | |
679 | print $c++,"=$e"; if($e < $err) {@pal = @p; $err = $e; print "*";} print "\n"; | |
680 | ||
681 | # 12 | |
682 | ($e, @p) = &test_niveaux($err, (0, 39, 101, 195, 255), -$max, @px); | |
683 | print $c++,"=$e"; if($e < $err) {@pal = @p; $err = $e; print "*";} print "\n"; | |
684 | ||
685 | return @pal; | |
686 | } | |
687 | ||
688 | ||
689 | ||
690 | my($colorspace) = "CMYK"; | |
691 | #$colorspace="HSV"; | |
692 | $colorspace = "RGB"; | |
693 | #$colorspace="YUV"; | |
694 | $glb_magick->AdaptiveResize(geometry=>"80x200!") if 0; | |
695 | $glb_magick->Posterize(levels=>16, dither=>"False") if 0; | |
696 | $glb_magick->Posterize(levels=>6, dither=>"False") if 0; | |
697 | $glb_magick->Posterize(levels=>4, dither=>"False") if 0; | |
698 | $glb_magick->Posterize(levels=>3, dither=>"False") if 0; | |
699 | # pas mal du tout: | |
700 | $glb_magick->Quantize(colors=>$max, colorspace=>$colorspace, treedepth=>0, dither=>"False"); | |
701 | @t = $glb_magick->GetPixels(map=>"RGB", height=>200, normalize=>"True"); | |
702 | for($i=$#t+1; ($i-=3)>=0;) { | |
703 | $rvb = &rgb2irgb(@t[$i..$i+2]); | |
704 | #$rvb = ((($map_ef2[$rvb>>20]<<10) + $map_ef2[($rvb>>10) & 0xff])<<10) + $map_ef2[$rvb & 0xff] if $#map_ef>=0; | |
705 | $rvb = ((($map_ef[$rvb>>20]<<10) + $map_ef[($rvb>>10) & 0xff])<<10) + $map_ef[$rvb & 0xff] if $#map_ef>=0; | |
706 | $pal{$rvb & $mask} = 1; | |
707 | } | |
708 | @t = (keys(%pal), (0) x $max)[0..($max-1)]; | |
709 | #foreach $t (@t) { print &irgb2hex($t), "\n"; } | |
710 | return @t; | |
711 | } | |
712 | ||
713 | # calcul d'une palette de 16 couleurs | |
714 | sub find_palette { | |
715 | my($max, @px) = @_; | |
716 | ||
717 | # cas TO7 | |
718 | return &to770_palette if $glb_to7pal; | |
719 | ||
720 | # vrai algo | |
721 | my($mask) = 0x0f03c0f0; $mask = -1; | |
722 | ||
723 | # si l'image a suffisament peu de couleurs alors on retourne la palette de l'image | |
724 | # directement | |
725 | my($i, %pal); | |
726 | foreach $i (@px) { | |
727 | $pal{$i & $mask} = 1; | |
728 | last if length(keys %pal)>$max; | |
729 | } | |
730 | my(@t) = keys(%pal); | |
731 | #for $t (@t) { | |
732 | # print &irgb2hex($t), " = ", $pal{$t},"\n"; | |
733 | #} | |
734 | return @t if $#t<$max; | |
735 | %pal = (); | |
736 | ||
737 | # sinon on quantifie l'image: | |
738 | my($use_dith) = 1; | |
739 | my($alt) = 0; | |
740 | ||
741 | #return &xxx_palette($max, @px) if $#map_ef>=0; | |
742 | ||
743 | # on remap l'image aux niveau produits par les puces thomson! | |
744 | if($#map_ef>=0) { | |
745 | @t = simple_dither($use_dith, @px) unless $alt; | |
746 | @t = prox_dither ($use_dith, @px) if $alt; | |
747 | } | |
748 | ||
749 | # idee par groupe de $w pixels on sature les valeurs RGB avec | |
750 | # les min/max ontenus pour ce groupe. L'idee est de réduire | |
751 | # la disperssion des couleurs | |
752 | if(1) { | |
753 | my($w) = 8; | |
754 | for($i=0; $i<=$#t; $i+=3*$w) { | |
755 | my($r,$v,$b) = (1,1,1); | |
756 | my($R,$V,$B) = (0,0,0); | |
757 | my($j); | |
758 | for($j=$i; $j<$i+$w*3; $j+=3) { | |
759 | $r = $t[$j+0] if $t[$j+0]<$r; | |
760 | $v = $t[$j+1] if $t[$j+1]<$v; | |
761 | $b = $t[$j+2] if $t[$j+2]<$b; | |
762 | $R = $t[$j+0] if $t[$j+0]>$R; | |
763 | $V = $t[$j+1] if $t[$j+1]>$V; | |
764 | $B = $t[$j+2] if $t[$j+2]>$B; | |
765 | } | |
766 | my($t) = 0.5; | |
767 | for($j=$i; $j<$i+$w*3; $j+=3) { | |
768 | $t[$j+0] = $t[$j+0] < (1-$t)*$r + $t*$R ? $r : $R; | |
769 | $t[$j+1] = $t[$j+1] < (1-$t)*$v + $t*$V ? $v : $V; | |
770 | $t[$j+2] = $t[$j+2] < (1-$t)*$b + $t*$B ? $b : $B; | |
771 | } | |
772 | } | |
773 | } | |
774 | ||
775 | # on réduit à 64 couls | |
776 | $glb_magick->Quantize(colors=>($alt?48:24)*0+64*1+128*0+256*0, colorspace=>"RGB", treedepth=>0, dither=>($use_dith && !$alt & 0?"True":"False")); | |
777 | $glb_magick->Write("toto3.gif"); | |
778 | @t = $glb_magick->GetPixels(map=>"RGB", height=>200, normalize=>"True"); | |
779 | ||
780 | # on comptabilise les couleurs renormalisées au format Thomson | |
781 | %pal = (); | |
782 | $pal{0} = 1+$#t; | |
783 | for($i=$#t+1; ($i-=3)>=0;) { | |
784 | $rvb = &rgb2irgb(@t[$i..$i+2]); | |
785 | #$rvb = ((($map_ef2[$rvb>>20]<<10) + $map_ef2[($rvb>>10) & 0xff])<<10) + $map_ef2[$rvb & 0xff] if $#map_ef>=0; | |
786 | $rvb = ((($map_ef[$rvb>>20]<<10) + $map_ef[($rvb>>10) & 0xff])<<10) + $map_ef[$rvb & 0xff] if $#map_ef>=0; | |
787 | ++$pal{$rvb & $mask}; | |
788 | } | |
789 | ||
790 | # on trie par frequence | |
791 | my(@cpt) = (sort { $pal{$b} - $pal{$a} } keys %pal); | |
792 | ||
793 | # selection par popularité? | |
794 | return (@cpt, (0) x $max)[0..($max-1)] if 0; | |
795 | ||
796 | # affichage des stats | |
797 | my($dbg) = 0; | |
798 | if($dbg) { | |
799 | for $t (@cpt) { | |
800 | print &irgb2hex($t), " = ", $pal{$t},"\n"; | |
801 | } | |
802 | } | |
803 | ||
804 | # on coupe les couls sous-représentées | |
805 | my($thr) = 8; | |
806 | @t = @cpt; @cpt = (); | |
807 | for $t (@t) { | |
808 | push(@cpt, $t) if $pal{$t} >= $thr; | |
809 | } | |
810 | ||
811 | # on prend la couleur la plus frequente, puis la plus loin de celle là jusqu'à 10 couls ensuite une fois sur 2 on prend la plus ancienne | |
812 | @t = (); | |
813 | push(@t, shift(@cpt)); | |
814 | while($#t < $max && $#cpt>=0) { | |
815 | #print "\n\n"; | |
816 | #for $t (@t) { | |
817 | # print &irgb2hex($t), " = ", $pal{$t},"\n"; | |
818 | #} | |
819 | if($#t < 10 || ($#t & 1)) { | |
820 | #print "\n\n\n$#t, plus loin\n"; | |
821 | $i = &find_furthest(\@t, \@cpt); | |
822 | push(@t, splice(@cpt, $i, 1, ())); | |
823 | } else { | |
824 | #print "\n\n\n$#t, plus freq"; | |
825 | # on prends la plus frequente | |
826 | push(@t, shift(@cpt)); | |
827 | } | |
828 | } | |
829 | ||
830 | # on complète avec des zero | |
831 | @t = (@t, (0) x $max)[0..($max-1)]; | |
832 | $dbg = 0; | |
833 | if($dbg) { | |
834 | print "\n\n";foreach $t (@t) { | |
835 | my($r) = &ammag(($t>>20) & 0x1ff); | |
836 | my($g) = &ammag(($t>>10) & 0x1ff); | |
837 | my($b) = &ammag(($t>>00) & 0x1ff); | |
838 | print &irgb2hex($t), " = ", $pal{$t}, " ", $r,",",$g,",",$b," ",$t,"\n"; | |
839 | } | |
840 | } | |
841 | ||
842 | return @t; | |
843 | } | |
844 | ||
845 | # dithering simple sans contraintes de proximité | |
846 | sub simple_dither { | |
847 | my($use_dith, @px) = @_; | |
848 | ||
849 | my($x, $y, $p, $rvb, $r, $v, $b, @t); | |
850 | for($y=0, $p=0; $y<200; ++$y) { | |
851 | for($x=0; $x<320; ++$x, ++$p) { | |
852 | $rvb = $px[$p]; | |
853 | $r=$map_ef[$rvb>>20]; $v=$map_ef[($rvb>>10) & 0xff]; $b=$map_ef[$rvb & 0xff]; | |
854 | push(@t, &ammag($r), &ammag($v), &ammag($b)); | |
855 | #push(@t, $r=($rvb>>20), $v=(($rvb>>10) & 0xff), $b=($rvb & 0xff)); | |
856 | if($use_dith) { | |
857 | $px[$p] = ((($r<<10)+$v)<<10)+$b; | |
858 | $rvb = &irgb_sub($rvb, $px[$p]); | |
859 | $px[$p + 319] = &irgb_uadd($px[$p + 319], &irgb_map($rvb, \@glb_map0)) if $glb_err0 && $y<199 && $x>0; | |
860 | $px[$p + 320] = &irgb_uadd($px[$p + 320], &irgb_map($rvb, \@glb_map1)) if $glb_err1 && $y<199; | |
861 | $px[$p + 321] = &irgb_uadd($px[$p + 321], &irgb_map($rvb, \@glb_map2)) if $glb_err2 && $y<199 && $x<319; | |
862 | $px[$p + 001] = &irgb_uadd($px[$p + 001], &irgb_map($rvb, \@glb_map3)) if $glb_err3 && $x<319; | |
863 | } | |
864 | } | |
865 | } | |
866 | open(OUT,">.toto2.pnm"); print OUT "P6\n320 200\n255\n", pack('C*', @t), "\n"; close(OUT); | |
867 | @$glb_magick = (); | |
868 | $glb_magick->Read(".toto2.pnm"); | |
869 | unlink(".toto2.pnm"); | |
870 | ||
871 | $glb_magick->Write("toto2_.png"); | |
872 | ||
873 | return @t; | |
874 | } | |
875 | ||
876 | # dither sans contraintes de couleurs, mais avec contrainte de proximité | |
877 | sub prox_dither { | |
878 | my($use_dith, @px) = @_; | |
879 | ||
880 | my($x, $y, $fond, $forme, $i, $p, $rvb, $r, $v, $b, @t); | |
881 | for($y=$p=0; $y<200; ++$y) { | |
882 | for($x=0; $x<320; $x += 8) { | |
883 | ($fond, $forme) = &prox_couple(@px[$p..$p+7]); | |
884 | for($i = 0; $i<8; ++$i, ++$p) { | |
885 | $rvb = $px[$p]; | |
886 | $rvb = &irgb_dist($fond, $rvb) < &irgb_dist($forme, $rvb) ? $fond : $forme; | |
887 | $r=$map_ef[$rvb>>20]; $v=$map_ef[($rvb>>10) & 0xff]; $b=$map_ef[$rvb & 0xff]; | |
888 | push(@t, &ammag($r), &ammag($v), &ammag($b)); | |
889 | if($use_dither | 1) { | |
890 | #$px[$p] = ((($r<<10)+$v)<<10)+$b; | |
891 | $rvb = &irgb_sub($px[$p], $rvb); | |
892 | $px[$p + 319] = &irgb_uadd($px[$p + 319], &irgb_map($rvb, \@glb_map0)) if $glb_err0 && $y<199 && $x+$i>0; | |
893 | $px[$p + 320] = &irgb_uadd($px[$p + 320], &irgb_map($rvb, \@glb_map1)) if $glb_err1 && $y<199; | |
894 | $px[$p + 321] = &irgb_uadd($px[$p + 321], &irgb_map($rvb, \@glb_map2)) if $glb_err2 && $y<199 && $x+$i<319; | |
895 | $px[$p + 001] = &irgb_uadd($px[$p + 001], &irgb_map($rvb, \@glb_map3)) if $glb_err3 && $x+$i<319; | |
896 | } | |
897 | } | |
898 | } | |
899 | } | |
900 | open(OUT,">.toto2.pnm"); print OUT "P6\n320 200\n255\n", pack('C*', @t), "\n"; close(OUT); | |
901 | @$glb_magick = (); | |
902 | $glb_magick->Read(".toto2.pnm"); | |
903 | #$glb_magick->Write(".toto2.png"); | |
904 | unlink(".toto2.pnm"); | |
905 | ||
906 | return @t; | |
907 | } | |
908 | ||
909 | sub prox_couple { | |
910 | my(@octet) = @_; | |
911 | ||
912 | my($i, $im, $j, $jm, $d, $dm, $rgb, $r, $g, $b, %cpt, @px); | |
913 | ||
914 | # dither de l'octet sans contraintes pour extraire les stats | |
915 | @px = (@octet); | |
916 | for($i=0; $i<8; ++$i) { | |
917 | $rgb = $px[$i]; | |
918 | $r=$map_ef[$rgb>>20]; $v=$map_ef[($rgb>>10) & 0xff]; $b=$map_ef[$rgb & 0xff]; | |
919 | ++$cpt{$px[$i] = ((($r<<10)+$v)<<10)+$b}; | |
920 | $px[$i+1] = &irgb_add_cln($px[$i+1], &irgb_map(&irgb_sub($rgb, $px[$i]), \@glb_map3)) if $i<7; | |
921 | } | |
922 | ||
923 | # on trie par frequence | |
924 | my(@cpt) = (sort { $cpt{$b} - $cpt{$a} } keys %cpt); | |
925 | ||
926 | #print "\n\n"; | |
927 | #foreach $t (@octet) { | |
928 | # print &irgb2hex($t), " "; | |
929 | #} | |
930 | #print "\n\n"; | |
931 | #foreach $t (@cpt) { | |
932 | # print &irgb2hex($t), " = ", $cpt{$t}, "\n"; | |
933 | #} | |
934 | ||
935 | # 1 ou 2 couls utilisées: pas de probs | |
936 | if($#cpt<=1) { | |
937 | # on s'assure qu'on en a au moins 2 | |
938 | $cpt[1] = $cpt[0] if $#cpt==0; | |
939 | ||
940 | return ($cpt[1], $cpt[0]); | |
941 | } | |
942 | ||
943 | # les 2 couls principales couvrent 7 pixels sur les 8, on les gardes, tant pis pour la mintorité | |
944 | if($cpt{$cpt[0]} + $cpt{$cpt[1]} >= 6) { | |
945 | return ($cpt[1], $cpt[0]); | |
946 | } | |
947 | ||
948 | # si la 1ere couvre 4 pixels, on prend comme 2eme celle qui fait le moins d'err | |
949 | if($cpt{$cpt[0]} >= 6) { | |
950 | $dm = 1e30; | |
951 | for($i=1; $i<=$#cpt; ++$i) { | |
952 | @px = (@octet); | |
953 | for($d = $j = 0; $j<8 && $d<$dm; ++$j) { | |
954 | $d1 = &irgb_dist($cpt[0], $px[$j]); | |
955 | $d2 = &irgb_dist($cpt[$i],$px[$j]); | |
956 | if($d1 < $d2) {$d += &sq($d1); $rgb = $cpt[0];} else {$d += &sq($d2); $rgb = $cpt[$i];} | |
957 | $px[$j+1] = &irgb_add_cln($px[$j+1], &irgb_map(&irgb_sub($px[$j], $rgb), \@glb_map3)) if $glb_err3 && $j<7; | |
958 | } | |
959 | if($d < $dm) {$dm = $d; $im = $i;} | |
960 | } | |
961 | return ($cpt[$im], $cpt[0]); | |
962 | } | |
963 | ||
964 | # sinon tester tous les couple avec dither | |
965 | my($r, $rm); | |
966 | $dm = 1e30; | |
967 | for($i=0; $i<=$#cpt; ++$i) { | |
968 | for($j=0; $j<$i; ++$j) { | |
969 | @px = (@octet); | |
970 | for($r = $d = $k = 0; $k<8 && $d<$dm; ++$k) { | |
971 | $di = &irgb_dist($cpt[$i], $px[$k]); | |
972 | $dj = &irgb_dist($cpt[$j], $px[$k]); | |
973 | if($di <= $dj) {$r |= 1; $rgb = $cpt[$i]; $d += &sq($di);} else {$r |= 2; $rgb = $cpt[$j]; $d += &sq($dj);} | |
974 | $px[$k+1] = &irgb_add_cln($px[$k+1], &irgb_map(&irgb_sub($px[$k], $rgb), \@glb_map3)) if $glb_err3 && $k<7; | |
975 | } | |
976 | if($d < $dm) {$rm = $r; $dm = $d; $im = $i; $jm = $j} | |
977 | } | |
978 | } | |
979 | return ($cpt[$im], $cpt[$jm]); | |
980 | } | |
981 | ||
982 | sub find_furthest { | |
983 | my ($set, $cols) = @_; | |
984 | my ($d, $dm, $i, $im); | |
985 | for($i = $#{$cols}, $dm = $im = 0; $i>=0; --$i) { | |
986 | $d = &set_dist($cols->[$i], $set); | |
987 | #print "$i ", &irgb2hex($cols->[$i])," ==> $d, $dm\n"; | |
988 | if($d > $dm) {$dm = $d; $im = $i; #print"^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"; | |
989 | } | |
990 | } | |
991 | #print "*** ", &irgb2hex($cols->[$im])," $im ($dm)\n"; | |
992 | return $im; | |
993 | } | |
994 | ||
995 | sub set_dist { | |
996 | my($rgb, $set) = @_; | |
997 | my($dm, $d, $col) = 1e30; | |
998 | my $dc = 0; | |
999 | foreach $col (@$set) { | |
1000 | $d = &irgb_dist($rgb, $col); | |
1001 | $dc += $d; | |
1002 | #print &irgb2hex($rgb),",",&irgb2hex($col), "====$d\n"; | |
1003 | if($d<$dm) {$dm = $d;} | |
1004 | } | |
1005 | return $dm; # + $dc; | |
1006 | } | |
1007 | ||
1008 | # sauvegarde de l'image | |
1009 | sub write_image { | |
1010 | my($file, @px) = @_; | |
1011 | ||
1012 | # on replace tout entre 0 et 255 | |
1013 | my($t, $c, @p); | |
1014 | foreach $t (@px) { | |
1015 | my($b) = $t & 0x100 ? 0 : $t & 0xff; $t >>= 10; | |
1016 | my($v) = $t & 0x100 ? 0 : $t & 0xff; $t >>= 10; | |
1017 | my($r) = $t & 0x100 ? 0 : $t & 0xff; | |
1018 | if(0 && $#map_ef>=0) { | |
1019 | $r = $map_ef2[$r]; | |
1020 | $v = $map_ef2[$v]; | |
1021 | $b = $map_ef2[$b]; | |
1022 | } | |
1023 | push(@p, &ammag($r), &ammag($v), &ammag($b)); #, $r, $v, $b); | |
1024 | } | |
1025 | # on passe par un fichier temporaire | |
1026 | open(OUT,">.toto.pnm"); print OUT "P6\n320 200\n255\n", pack('C*', @p), "\n"; close(OUT); | |
1027 | #open(OUT, ">.toto.pnm"); print OUT "P6\n640 400\n255\n"; for($t = 0; $t<=$#p; $t+=640*3) {print OUT pack('C*', @p[$t..$t+640*3-1], @p[$t..$t+640*3-1]);} print OUT "\n"; close(OUT); | |
1028 | @$glb_magick = (); | |
1029 | $glb_magick->Read(".toto.pnm"); | |
1030 | $glb_magick->Set(page=>"320x200+0+0"); | |
1031 | # $glb_magick->Set(page=>"640x400+0+0"); | |
1032 | #$glb_magick->Gamma(gamma=>1.2); | |
1033 | #$glb_magick->Resize(geometry=>"640x400!"); | |
1034 | #$glb_magick->Border(width=>"320",height=>"100",color=>"black"); | |
1035 | #unlink(".toto.pnm"); | |
1036 | ||
1037 | # sauvegarde | |
1038 | $glb_magick->Write($file); | |
1039 | } | |
1040 | ||
1041 | # gamma / normalize / sigmoidal | |
1042 | # 0 = orig / Linear / off | |
1043 | # 1 = orig / Linear / on | |
1044 | # 2 = orig / Normalize / off | |
1045 | # 3 = orig / Normalize / on | |
1046 | # 4 = gamma / Linear / off | |
1047 | # 5 = gamma / Linear / on | |
1048 | # 6 = gamma / Normalize / off | |
1049 | # 7 = gamma / Normalize / on | |
1050 | ||
1051 | # lit une image au format 320 x 200 sous la forme r10v10b10 | |
1052 | sub read_image { | |
1053 | my($file) = @_; | |
1054 | ||
1055 | @$glb_magick = (); | |
1056 | my($x) = $glb_magick->Read($file); | |
1057 | warn "$x" if "$x"; | |
1058 | ||
1059 | # formattage en 320x200 si necessaire | |
1060 | $glb_magick->Enhance(); | |
1061 | $glb_magick->Normalize(); # | |
1062 | #$glb_magick->LinearStretch('black-point'=>0, 'white-point'=>1); | |
1063 | #$glb_magick->Contrast(sharpen=>"True"); | |
1064 | #$glb_magick->Set(antialias=>"True"); | |
1065 | $glb_magick->SigmoidalContrast(contrast=>2); | |
1066 | $glb_magick->AdaptiveResize(geometry=>"320x200", filter=>"lanczos", blur=>1); | |
1067 | $glb_magick->Border(width=>"320",height=>"100",color=>"black"); | |
1068 | # $glb_magick->Blur(1); | |
1069 | # $glb_magick->OilPaint(2); | |
1070 | $glb_magick->Set(gravity=>"Center"); | |
1071 | # $glb_magick->Crop(geometry=>"320x200!", gravity=>"center"); | |
1072 | $glb_magick->Crop(geometry=>"320x200!"); | |
1073 | $glb_magick->Set(page=>"320x200+0+0"); | |
1074 | $glb_magick->Resize(geometry=>"320x200!", filter=>"lanczos", blur=>1); | |
1075 | #$glb_magick->ReduceNoise(radius=>0); | |
1076 | #$glb_magick->Gamma(gamma=>0.8) if $glb_to7pal; | |
1077 | #$glb_magick->Gamma(gamma=>0.45); | |
1078 | #$glb_magick->AdaptiveSharpen(radius=>3); | |
1079 | #$glb_magick->AdaptiveBlur(radius=>4); | |
1080 | #$glb_magick->Contrast(sharpen=>"True"); | |
1081 | #$glb_magick->Evaluate(operator=>"Multiply", value=>"0.9"); | |
1082 | ||
1083 | #$glb_magick->Quantize(colors=>$glb_maxcol, colorspace=>"CYMK", dither=>"True"); | |
1084 | #$glb_magick->OrderedDither(threshold=>"h4x4", channel=>"RGB"); | |
1085 | if($glb_dith>=2) { | |
1086 | #dither en 16 couls | |
1087 | my(@t) = $glb_magick->GetPixels(map=>"RGB", height=>200, normalize=>"True"); | |
1088 | my($i, $j, $t, $p, @p); | |
1089 | my($m) = $glb_dith-1; | |
1090 | for($j=$p=0; $j<200; ++$j) { | |
1091 | for($i=0; $i<320; ++$i) { | |
1092 | $t = $t[$p] * $m; ++$t if $t<$m && $t - int($t)>=$mat[$i % $mat_x][$j % $mat_y]; $t[$p++] = (int($t)*255)/$m; | |
1093 | $t = $t[$p] * $m; ++$t if $t<$m && $t - int($t)>=$mat[$i % $mat_x][$j % $mat_y]; $t[$p++] = (int($t)*255)/$m; | |
1094 | $t = $t[$p] * $m; ++$t if $t<$m && $t - int($t)>=$mat[$i % $mat_x][$j % $mat_y]; $t[$p++] = (int($t)*255)/$m; | |
1095 | } | |
1096 | } | |
1097 | open(OUT,">.toto.pnm"); print OUT "P6\n320 200\n255\n", pack('C*', @t), "\n"; close(OUT); | |
1098 | @$glb_magick = (); | |
1099 | $glb_magick->Read(".toto.pnm"); | |
1100 | $glb_magick->Write(".toto.png"); | |
1101 | unlink(".toto.pnm"); | |
1102 | } | |
1103 | my(@t) = $glb_magick->GetPixels(map=>"RGB", height=>200, normalize=>"True"); | |
1104 | my($i, @px); | |
1105 | for($i = 0; $i<$#t; $i += 3) { | |
1106 | push(@px, &rgb2irgb($t[$i], $t[$i+1], $t[$i+2])); | |
1107 | } | |
1108 | ||
1109 | #$glb_magick->Write("totof.png"); | |
1110 | ||
1111 | return @px; | |
1112 | } | |
1113 | ||
1114 | sub ammag { | |
1115 | return $_[0] unless $glb_gamma; | |
1116 | my $t = $_[0]/255; | |
1117 | #if($t<=0.018) {$t = 4.5*$t;} else {$t = 1.099*($t**(1/$glb_gamma))-0.099;} | |
1118 | $t = $t**(1/$glb_gamma); | |
1119 | return xint(255*$t); | |
1120 | } | |
1121 | ||
1122 | sub gamma { | |
1123 | return $_[0] unless $glb_gamma; | |
1124 | my $t = $_[0]/255; | |
1125 | #if($t<=0.081) {$t = $t/4.5;} else {$t = (($t+0.099)/1.099)**$glb_gamma;} | |
1126 | $t = $t**$glb_gamma; | |
1127 | return xint($t*255); #**1.2; #**1.4; | |
1128 | } | |
1129 | ||
1130 | # affichage | |
1131 | sub irgb2hex { | |
1132 | my($irgb) = @_; | |
1133 | my($s) = ""; | |
1134 | if($irgb & 0x100) {$s = sprintf("-%02x$s", (($irgb ^ 0x1ff)&0xff) + 1);} else {$s = sprintf("+%02x$s", $irgb & 0xff);} $irgb >>= 10; | |
1135 | if($irgb & 0x100) {$s = sprintf("-%02x$s", (($irgb ^ 0x1ff)&0xff) + 1);} else {$s = sprintf("+%02x$s", $irgb & 0xff);} $irgb >>= 10; | |
1136 | if($irgb & 0x100) {$s = sprintf("-%02x$s", (($irgb ^ 0x1ff)&0xff) + 1);} else {$s = sprintf("+%02x$s", $irgb & 0xff);} $irgb >>= 10; | |
1137 | return $s; | |
1138 | } | |
1139 | ||
1140 | # addition d'une valeur irgb signée .. inclu saturation -256 +255 | |
1141 | sub irgb_add { | |
1142 | my($irgb1, $irgb2) = @_; | |
1143 | ||
1144 | my($r) = $irgb1 + $irgb2; | |
1145 | my($o) = (($irgb1 & 0x0ff3fcff) + ($irgb2 & 0x0ff3fcff)) ^ ($r>>1); | |
1146 | $r = ($r & ~0x000003ff) | (0x00000100 - (($r & 0x00000100)>>8)) if $o & 0x00000100; | |
1147 | $r = ($r & ~0x000ffc00) | (0x00040000 - (($r & 0x00040000)>>8)) if $o & 0x00040000; | |
1148 | $r = ($r & ~0x3ff00000) | (0x10000000 - (($r & 0x10000000)>>8)) if $o & 0x10000000; | |
1149 | return $r & 0x1ff7fdff if 1; # saturation -256 et +255 | |
1150 | } | |
1151 | ||
1152 | sub irgb_add_cln { | |
1153 | my($t) = &irgb_add(@_); | |
1154 | ||
1155 | return $t; | |
1156 | ||
1157 | my($r, $g, $b); | |
1158 | ||
1159 | $r = ($t>>00) & 0x1FF; | |
1160 | $g = ($t>>10) & 0x1FF; | |
1161 | $b = ($t>>20) & 0x1FF; | |
1162 | ||
1163 | $r = ($r^0x1FF)+1 if $r & 0x100; | |
1164 | $g = ($g^0x1FF)+1 if $g & 0x100; | |
1165 | $b = ($b^0x1FF)+1 if $b & 0x100; | |
1166 | ||
1167 | my($M) = $r; | |
1168 | $M = $g if $g>$M; | |
1169 | $M = $b if $b>$M; | |
1170 | ||
1171 | $M/=4.2; | |
1172 | ||
1173 | $t &= ~(0x1FF<<00) if $r<$M; | |
1174 | $t &= ~(0x1FF<<10) if $g<$M; | |
1175 | $t &= ~(0x1FF<<20) if $b<$M; | |
1176 | ||
1177 | return $t; | |
1178 | } | |
1179 | ||
1180 | # addition d'une valeur irgb signée .. inclu saturation 0 +255 | |
1181 | sub irgb_uadd { | |
1182 | return &irgb_sat(&irgb_add(@_)); | |
1183 | } | |
1184 | ||
1185 | # sature les irgb<0 à 0 | |
1186 | sub irgb_sat { | |
1187 | my($irgb) = @_; | |
1188 | ||
1189 | return (((0x10040100 - (($irgb & 0x10040100)>>8)) ^ 0xff3fcff) & $irgb) & 0xff3fcff; | |
1190 | } | |
1191 | ||
1192 | # soustraction de deux valeurs irgb>=0 (pas de satur) | |
1193 | sub irgb_sub { | |
1194 | my($rgb1, $rgb2) = @_; | |
1195 | return (($rgb1 | 0x20080200) - $rgb2) & 0x1ff7fdff; | |
1196 | } | |
1197 | ||
1198 | sub irgb_sub2 { | |
1199 | my($rgb1, $rgb2) = @_; | |
1200 | my($t) = &irgb_sub(@_); | |
1201 | ||
1202 | my($r) = ($t>>00) & 0x1FF; | |
1203 | my($g) = ($t>>10) & 0x1FF; | |
1204 | my($b) = ($t>>20) & 0x1FF; | |
1205 | ||
1206 | $r = ($r^0x1FF)+1 if $r & 0x100; | |
1207 | $g = ($g^0x1FF)+1 if $g & 0x100; | |
1208 | $b = ($b^0x1FF)+1 if $b & 0x100; | |
1209 | ||
1210 | my($m) = ($r+$g+$b)/3; | |
1211 | $r = 0 if $r<$m; | |
1212 | $g = 0 if $g<$m; | |
1213 | $b = 0 if $b<$m; | |
1214 | ||
1215 | $r = 0x1ff&(-$r) if $t & 0x100; | |
1216 | $g = 0x1ff&(-$g) if $t & 0x100<<10; | |
1217 | $b = 0x1ff&(-$b) if $t & 0x100<<20; | |
1218 | return ((($b<<10)|$g)<<10)|$r; | |
1219 | } | |
1220 | ||
1221 | # valeur opposée | |
1222 | sub irgb_neg { | |
1223 | my($rgb) = @_; | |
1224 | return (0x20080200 - $rgb) & 0x1ff7fdff; | |
1225 | } | |
1226 | ||
1227 | # module du vecteur irgb | |
1228 | sub irgb_module { | |
1229 | my($rgb) = @_; | |
1230 | my($d); | |
1231 | $d = $glb_sqr[0x1ff & $rgb]; $rgb >>= 10; | |
1232 | $d += $glb_sqr[0x1ff & $rgb]; $rgb >>= 10; | |
1233 | $d += $glb_sqr[0x1ff & $rgb]; | |
1234 | return sqrt($d); | |
1235 | } | |
1236 | ||
1237 | # applique une table sur un irgb (en gros ca sert pour les multiplications par des constantes) | |
1238 | sub irgb_map { | |
1239 | my($rgb, $map) = @_; | |
1240 | my($r); | |
1241 | $r = $map->[$rgb & 0x1ff]; $rgb >>= 10; | |
1242 | $r |= $map->[$rgb & 0x1ff]<<10; $rgb >>= 10; | |
1243 | $r |= $map->[$rgb]<<20; | |
1244 | return $r; | |
1245 | } | |
1246 | ||
1247 | sub irgb_cln { | |
1248 | my($t) = @_; | |
1249 | ||
1250 | my($r) = ($t>>00) & 0x1FF; | |
1251 | my($g) = ($t>>10) & 0x1FF; | |
1252 | my($b) = ($t>>20) & 0x1FF; | |
1253 | ||
1254 | $r = ($r^0x1FF)+1 if $r & 0x100; | |
1255 | $g = ($g^0x1FF)+1 if $g & 0x100; | |
1256 | $b = ($b^0x1FF)+1 if $b & 0x100; | |
1257 | ||
1258 | #print "$r $g $b => "; | |
1259 | ||
1260 | my($m) = ($r+$g+$b)/4; | |
1261 | $r = 0 if $r<$m; | |
1262 | $g = 0 if $g<$m; | |
1263 | $b = 0 if $b<$m; | |
1264 | ||
1265 | #print "$r $g $b "; | |
1266 | ||
1267 | $r = 0x1ff&(-$r) if $t & 0x100; | |
1268 | $g = 0x1ff&(-$g) if $t & 0x100<<10; | |
1269 | $b = 0x1ff&(-$b) if $t & 0x100<<20; | |
1270 | return ((($b<<10)|$g)<<10)|$r; | |
1271 | } | |
1272 | ||
1273 | # rgb (0..1) vers irgb | |
1274 | sub rgb2irgb { | |
1275 | my(@rgb) = @_; | |
1276 | my($t); | |
1277 | if($glb_gamma) { | |
1278 | $rgb[0] = &gamma($rgb[0]*255)/255; | |
1279 | $rgb[1] = &gamma($rgb[1]*255)/255; | |
1280 | $rgb[2] = &gamma($rgb[2]*255)/255; | |
1281 | } | |
1282 | $t = (int(255*$rgb[0]) & 0x1ff); | |
1283 | $t = (int(255*$rgb[1]) & 0x1ff) | ($t<<10); | |
1284 | $t = (int(255*$rgb[2]) & 0x1ff) | ($t<<10); | |
1285 | return $t; | |
1286 | } | |
1287 | ||
1288 | # irgb vers rgb (0..1). si la composante est negative, elle est clampée à 0 | |
1289 | sub irgb2rgb { | |
1290 | my($t) = @_; | |
1291 | ||
1292 | my($b) = ($t & 0x100) ? 0 : ($t & 255)/255.0; $t >>= 10; | |
1293 | my($v) = ($t & 0x100) ? 0 : ($t & 255)/255.0; $t >>= 10; | |
1294 | my($r) = ($t & 0x100) ? 0 : ($t & 255)/255.0; | |
1295 | ||
1296 | return ($r, $v, $b); | |
1297 | } | |
1298 | ||
1299 | # moyenne de 2 couleurs >= 0 | |
1300 | sub irgb_avg { | |
1301 | my($rgb1, $rgb2) = @_; | |
1302 | return (($rgb1 + $rgb2 + 0x100401) & ~0x20180601)>>1; | |
1303 | } | |
1304 | ||
1305 | # rgb (0..1) vers xyz | |
1306 | sub rgb2xyz { | |
1307 | my($r, $v, $b) = @_; | |
1308 | return (0.618*$r + 0.177*$v + 0.205*$b, | |
1309 | 0.299*$r + 0.587*$v + 0.114*$b, | |
1310 | 0.056*$v + 0.944*$b); | |
1311 | } | |
1312 | ||
1313 | # xyz vers cie lab | |
1314 | sub xyz2lab { | |
1315 | my($x, $y, $z) = @_; | |
1316 | #my($xn, $yn, $zn) = &rgb2xyz(1,1,1); $x /= $xn; $y /= $yn; $z /= $zn; | |
1317 | my($l,$a,$b); | |
1318 | if($y>0.008856) { | |
1319 | $l = 116*($y ** 0.33333333333333) - 16; | |
1320 | } else { | |
1321 | $l = 903*$y; | |
1322 | } | |
1323 | $a = 500*(&f_lab($x) - &f_lab($y)); | |
1324 | $b = 200*(&f_lab($y) - &f_lab($z)); | |
1325 | return ($l,$a,$b); | |
1326 | } | |
1327 | ||
1328 | sub f_lab { | |
1329 | my($v) = @_; | |
1330 | ||
1331 | if($v>0.008856) { | |
1332 | return $v ** 0.333333333333333; | |
1333 | } else { | |
1334 | return 7.787*$v + 16/116.0; | |
1335 | } | |
1336 | } | |
1337 | ||
1338 | # rgb vers lab | |
1339 | sub rgb2lab { | |
1340 | return &xyz2lab(&rgb2xyz(@_)); | |
1341 | } | |
1342 | ||
1343 | # approximated CIE formula from http://www.compuphase.com/cmetric.htm#GAMMA | |
1344 | sub irgb_cie_dist_fast { | |
1345 | my($rgb1, $rgb2) = @_; | |
1346 | ||
1347 | my($rmean) = (($rgb1 + $rgb2) >> 21) & 0x1ff; | |
1348 | my($rgb) = &irgb_sub($rgb1, $rgb2); | |
1349 | ||
1350 | $d = ($glb_sqr[0x1ff & $rgb] * (512 + $rmean)) >> 8; $rgb >>= 10; | |
1351 | $d += $glb_sqr[0x1ff & $rgb] * 4; $rgb >>= 10; | |
1352 | $d += ($glb_sqr[0x1ff & $rgb] * (767 - $rmean)) >> 8; | |
1353 | return sqrt($d); | |
1354 | } | |
1355 | ||
1356 | # calcule la distance entre les deux couleurs r10g10b10 | |
1357 | sub irgb_dist { | |
1358 | my($rgb1, $rgb2) = @_; | |
1359 | #die &irgb2hex($rgb1) if $rgb1 & 0x10040100; | |
1360 | #die &irgb2hex($rgb2) if $rgb2 & 0x10040100; | |
1361 | if($glb_lab) { | |
1362 | return &irgb_cie_dist_fast($rgb1, $rgb2); | |
1363 | #my($k) = $rgb1."_".$rgb2; | |
1364 | my($d); # = $dist_cache{$k}; | |
1365 | #if(!defined $d) { | |
1366 | my($r1, $g1, $b1) = &xyz2lab(&rgb2xyz(&irgb2rgb($rgb1))); | |
1367 | my($r2, $g2, $b2) = &xyz2lab(&rgb2xyz(&irgb2rgb($rgb2))); | |
1368 | ||
1369 | $r1 -= $r2; $g1 -= $g2; $b1 -= $b2; | |
1370 | $d = sqrt($r1*$r1 + $g1*$g1 + $b1*$b1); | |
1371 | #$dist_cache{$k} = $d; | |
1372 | #} | |
1373 | return $d; | |
1374 | } else { | |
1375 | return &irgb_module(&irgb_sub($rgb1, $rgb2)); | |
1376 | } | |
1377 | } | |
1378 | ||
1379 | # retourne le couple forme/fond pour un octet donné | |
1380 | sub couple { | |
1381 | my(@octet) = @_; | |
1382 | ||
1383 | return &couple2(@octet) if 0; | |
1384 | return &couple3(@octet) if 0; | |
1385 | return &couple4(@octet) if 1; | |
1386 | ||
1387 | # on commence un dither classique mais horizontal de l'octet | |
1388 | my($i, $im, $j, $jm, $d, $dm, $rgb, @octet_pal); | |
1389 | $#octet_pal = 7; | |
1390 | for($i=0; $i<8; ++$i) { | |
1391 | $rgb = $octet[$i]; | |
1392 | # on trouve la coul la plus proche | |
1393 | $dm = 1e30; | |
1394 | for($j=0; $j<$glb_maxcol; ++$j) { | |
1395 | $d = &irgb_dist($glb_pal[$j], $rgb); | |
1396 | if($d<$dm) {$dm = $d; $octet_pal[$i] = $j;} | |
1397 | } | |
1398 | # on propage l'erreur | |
1399 | #$qq = &irgb_map(&irgb_sub($octet[$i], $glb_pal[$octet_pal[$i]]), \@glb_map3); | |
1400 | #print &irgb2hex($octet[$i]),",",&irgb2hex($glb_pal[$octet_pal[$i]])," e=",&irgb2hex(&irgb_sub($octet[$i], $glb_pal[$octet_pal[$i]])), " " if $qq; | |
1401 | #print "m=",&irgb2hex($qq)," " if $qq; | |
1402 | #print &irgb2hex($octet[$i + 1]), " => " if $qq; | |
1403 | $octet[$i+1] = &irgb_add_cln($octet[$i+1], &irgb_map(&irgb_sub($rgb, $glb_pal[$octet_pal[$i]]), \@glb_map3)) if $i<7; | |
1404 | #print &irgb2hex($octet[$i + 1]), "\n" if $qq; | |
1405 | } | |
1406 | ||
1407 | # ensuite on trouve le couple qui conduit au minimum d'erreur | |
1408 | $dm = 1e30; my($t, @line); | |
1409 | for($i=0; $i<$glb_maxcol; ++$i) { | |
1410 | for($j=0; $j<$i; ++$j) { | |
1411 | $d = &couple_dist($i, $j, $octet_pal[0]); | |
1412 | $d += &couple_dist($i, $j, $octet_pal[1]) if $d<$dm; | |
1413 | $d += &couple_dist($i, $j, $octet_pal[2]) if $d<$dm; | |
1414 | $d += &couple_dist($i, $j, $octet_pal[3]) if $d<$dm; | |
1415 | $d += &couple_dist($i, $j, $octet_pal[4]) if $d<$dm; | |
1416 | $d += &couple_dist($i, $j, $octet_pal[5]) if $d<$dm; | |
1417 | $d += &couple_dist($i, $j, $octet_pal[6]) if $d<$dm; | |
1418 | $d += &couple_dist($i, $j, $octet_pal[7]) if $d<$dm; | |
1419 | ||
1420 | if($d<$dm) {$dm = $d; $im = $i; $jm = $j;} | |
1421 | } | |
1422 | } | |
1423 | ||
1424 | return ($glb_pal[$im], $glb_pal[$jm]); | |
1425 | } | |
1426 | ||
1427 | sub couple_dist { | |
1428 | my($forme, $fond, $pixel) = @_; | |
1429 | ||
1430 | my($t, $a, $b) = $pixel*$glb_maxcol; | |
1431 | return ($a=$glb_dist[$t + $forme]) < ($b=$glb_dist[$t + $fond]) ? $a : $b; | |
1432 | } | |
1433 | ||
1434 | sub couple_dist_sq { | |
1435 | my($t) = &couple_dist(@_); | |
1436 | return $t*$t; | |
1437 | } | |
1438 | ||
1439 | sub couple2 { | |
1440 | my(@octet) = @_; | |
1441 | my($d, $dm, $im, $jm); $dm = 1e30; | |
1442 | for($i=0; $i<$glb_maxcol; ++$i) { | |
1443 | for($j=0; $j<$i; ++$j) { | |
1444 | $d = &couple2_dist($i, $j, $octet[0]); | |
1445 | $d += &couple2_dist($i, $j, $octet[1]) if $d<$dm; | |
1446 | $d += &couple2_dist($i, $j, $octet[2]) if $d<$dm; | |
1447 | $d += &couple2_dist($i, $j, $octet[3]) if $d<$dm; | |
1448 | $d += &couple2_dist($i, $j, $octet[4]) if $d<$dm; | |
1449 | $d += &couple2_dist($i, $j, $octet[5]) if $d<$dm; | |
1450 | $d += &couple2_dist($i, $j, $octet[6]) if $d<$dm; | |
1451 | $d += &couple2_dist($i, $j, $octet[7]) if $d<$dm; | |
1452 | ||
1453 | if($d<$dm) {$dm = $d; $im = $i; $jm = $j;} | |
1454 | } | |
1455 | } | |
1456 | return ($glb_pal[$im], $glb_pal[$jm]); | |
1457 | } | |
1458 | ||
1459 | sub couple2_dist { | |
1460 | my($forme, $fond, $pixel) = @_; | |
1461 | my($a,$b); | |
1462 | return ($a=&irgb_dist($glb_pal[$forme], $pixel)) < ($b=&irgb_dist($glb_pal[$fond], $pixel)) ? $a : $b; | |
1463 | } | |
1464 | ||
1465 | sub couple2_dist_sq { | |
1466 | my($t) = &couple2_dist(@_); | |
1467 | return $t * $t; | |
1468 | } | |
1469 | ||
1470 | # retourne le couple forme/fond pour un octet donné | |
1471 | sub couple3 { | |
1472 | my(@octet) = @_; | |
1473 | ||
1474 | # on commence un dither classique mais horizontal de l'octet | |
1475 | my($i, $im, $j, $jm, $d, $dm, $rgb, @octet_pal); | |
1476 | $#octet_pal = 7; | |
1477 | for($i=0; $i<8; ++$i) { | |
1478 | $rgb = $octet[$i]; | |
1479 | # on trouve la coul la plus proche | |
1480 | $dm = 1e30; | |
1481 | for($j=0; $j<$glb_maxcol; ++$j) { | |
1482 | $d = &irgb_dist($glb_pal[$j], $rgb); | |
1483 | if($d<$dm) {$dm = $d; $octet_pal[$i] = $j;} | |
1484 | } | |
1485 | # on propage l'erreur | |
1486 | #$qq = &irgb_map(&irgb_sub($octet[$i], $glb_pal[$octet_pal[$i]]), \@glb_map3); | |
1487 | #print &irgb2hex($octet[$i]),",",&irgb2hex($glb_pal[$octet_pal[$i]])," e=",&irgb2hex(&irgb_sub($octet[$i], $glb_pal[$octet_pal[$i]])), " " if $qq; | |
1488 | #print "m=",&irgb2hex($qq)," " if $qq; | |
1489 | #print &irgb2hex($octet[$i + 1]), " => " if $qq; | |
1490 | $octet[$i+1] = &irgb_uadd($octet[$i+1], &irgb_map(&irgb_sub($rgb, $glb_pal[$octet_pal[$i]]), \@glb_map3)) if $i<7; | |
1491 | #print &irgb2hex($octet[$i + 1]), "\n" if $qq; | |
1492 | } | |
1493 | ||
1494 | #la couleur fond est la couleur la plus choisie par octet_pal[i] | |
1495 | my(@cpt) = (0) x 8; | |
1496 | $i = -1; | |
1497 | foreach $j (@octet_pal) {if(++$cpt[$j] > $i) {$i = $cpt[$j]; $im = $j;}} | |
1498 | ||
1499 | # ensuite on trouve le couple qui conduit au minimum d'erreur | |
1500 | $dm = 1e30; my($t, @line); | |
1501 | for($j=0; $j<$glb_maxcol; ++$j) { | |
1502 | $d = &couple_dist($im, $j, $octet_pal[0]); | |
1503 | $d += &couple_dist($im, $j, $octet_pal[1]) if $d<$dm; | |
1504 | $d += &couple_dist($im, $j, $octet_pal[2]) if $d<$dm; | |
1505 | $d += &couple_dist($im, $j, $octet_pal[3]) if $d<$dm; | |
1506 | $d += &couple_dist($im, $j, $octet_pal[4]) if $d<$dm; | |
1507 | $d += &couple_dist($im, $j, $octet_pal[5]) if $d<$dm; | |
1508 | $d += &couple_dist($im, $j, $octet_pal[6]) if $d<$dm; | |
1509 | $d += &couple_dist($im, $j, $octet_pal[7]) if $d<$dm; | |
1510 | ||
1511 | if($d<$dm) {$dm = $d; $jm = $j;} | |
1512 | } | |
1513 | ||
1514 | return ($glb_pal[$im], $glb_pal[$jm]); | |
1515 | } | |
1516 | ||
1517 | sub couple4 { | |
1518 | return &couple5(@_) if 0; | |
1519 | ||
1520 | my(@octet) = @_; | |
1521 | ||
1522 | # on commence un dither classique mais horizontal de l'octet | |
1523 | my($i, $im, $j, $jm, $d, $dm, $rgb, @octet_pal); | |
1524 | $#octet_pal = 7; | |
1525 | for($i=0; $i<8; ++$i) { | |
1526 | $rgb = $octet[$i]; | |
1527 | # on trouve la coul la plus proche | |
1528 | $dm = 1e30; | |
1529 | for($j=0; $j<$glb_maxcol; ++$j) { | |
1530 | $d = &irgb_dist($glb_pal[$j], $rgb); | |
1531 | if($d<$dm) {$dm = $d; $octet_pal[$i] = $j;} | |
1532 | } | |
1533 | # on propage l'erreur | |
1534 | #$qq = &irgb_map(&irgb_sub($octet[$i], $glb_pal[$octet_pal[$i]]), \@glb_map3); | |
1535 | #print &irgb2hex($octet[$i]),",",&irgb2hex($glb_pal[$octet_pal[$i]])," e=",&irgb2hex(&irgb_sub($octet[$i], $glb_pal[$octet_pal[$i]])), " " if $qq; | |
1536 | #print "m=",&irgb2hex($qq)," " if $qq; | |
1537 | #print &irgb2hex($octet[$i + 1]), " => " if $qq; | |
1538 | $octet[$i+1] = &irgb_uadd($octet[$i+1], &irgb_map(&irgb_sub($rgb, $glb_pal[$octet_pal[$i]]), \@glb_map3)) if $i<7; | |
1539 | #print &irgb2hex($octet[$i + 1]), "\n" if $qq; | |
1540 | } | |
1541 | ||
1542 | # comptage des occurences | |
1543 | $dm = -1; my(@cpt) = (0) x $glb_maxcol; my($filt_cpt) = 0; | |
1544 | foreach $j (@octet_pal) {if(++$cpt[$j] > $dm) {$dm = $cpt[$jm = $j];}} | |
1545 | if($dm >= 8) { | |
1546 | $im = 0; | |
1547 | } elsif($dm >= 4) { | |
1548 | # une couleur domine de loin: on la prend en fond. On cherche | |
1549 | # alors la forme qui minimise l'erreur sur l'octet. | |
1550 | $dm = 1e30; $im = 0; | |
1551 | for($i = 0; $i < $glb_maxcol; ++$i) { | |
1552 | next unless $cpt[$i]>0 || $filt_cpt; | |
1553 | $d = &couple_dist($i, $jm, $octet_pal[0]); | |
1554 | $d += &couple_dist($i, $jm, $octet_pal[1]) if $d<$dm; | |
1555 | $d += &couple_dist($i, $jm, $octet_pal[2]) if $d<$dm; | |
1556 | $d += &couple_dist($i, $jm, $octet_pal[3]) if $d<$dm; | |
1557 | $d += &couple_dist($i, $jm, $octet_pal[4]) if $d<$dm; | |
1558 | $d += &couple_dist($i, $jm, $octet_pal[5]) if $d<$dm; | |
1559 | $d += &couple_dist($i, $jm, $octet_pal[6]) if $d<$dm; | |
1560 | $d += &couple_dist($i, $jm, $octet_pal[7]) if $d<$dm; | |
1561 | ||
1562 | if($d<$dm) {$dm = $d; $im = $i;} | |
1563 | } | |
1564 | } else { | |
1565 | # sinon on essaye tous les couples sans dither | |
1566 | return &couple2(@_) if 0; | |
1567 | # avec dither | |
1568 | return &couple2(@octet) if 0; | |
1569 | $dm = 1e30; $im = 0; | |
1570 | for($i=0; $i<$glb_maxcol; ++$i) { | |
1571 | next unless $cpt[$i]>0 || $filt_cpt || 1; | |
1572 | for($j=0; $j<$i; ++$j) { | |
1573 | next unless $cpt[$j]>0 || $filt_cpt; | |
1574 | $d = &couple_dist($i, $j, $octet_pal[0]); | |
1575 | $d += &couple_dist($i, $j, $octet_pal[1]) if $d<$dm; | |
1576 | $d += &couple_dist($i, $j, $octet_pal[2]) if $d<$dm; | |
1577 | $d += &couple_dist($i, $j, $octet_pal[3]) if $d<$dm; | |
1578 | $d += &couple_dist($i, $j, $octet_pal[4]) if $d<$dm; | |
1579 | $d += &couple_dist($i, $j, $octet_pal[5]) if $d<$dm; | |
1580 | $d += &couple_dist($i, $j, $octet_pal[6]) if $d<$dm; | |
1581 | $d += &couple_dist($i, $j, $octet_pal[7]) if $d<$dm; | |
1582 | ||
1583 | if($d<$dm) {$dm = $d; $im = $i; $jm = $j;} | |
1584 | } | |
1585 | } | |
1586 | } | |
1587 | ||
1588 | return ($glb_pal[$im], $glb_pal[$jm]); | |
1589 | } | |
1590 | ||
1591 | sub couple5__ { | |
1592 | my(@octet) = @_; | |
1593 | ||
1594 | # calcul de la coul moyenne sur l'octet | |
1595 | my(@moy); | |
1596 | $moy[0] = (($octet[0] + $octet[1])>>1) & 0xff3fcff; | |
1597 | $moy[1] = (($octet[2] + $octet[3])>>1) & 0xff3fcff; | |
1598 | $moy[2] = (($octet[4] + $octet[5])>>1) & 0xff3fcff; | |
1599 | $moy[3] = (($octet[6] + $octet[7])>>1) & 0xff3fcff; | |
1600 | ||
1601 | $moy[0] = (($moy[0] + $moy[1])>>1) & 0xff3fcff; | |
1602 | $moy[1] = (($moy[2] + $moy[3])>>1) & 0xff3fcff; | |
1603 | ||
1604 | # le fond = le plus proche de la moyenne | |
1605 | my($j, $jm, $d, $dm); | |
1606 | for($dm=1e30, $j=0; $j<$glb_maxcol; ++$j) { | |
1607 | if(($d = &irgb_dist($glb_pal[$j], $moy[0]))<$dm) {$dm =$d; $jm = $j;} | |
1608 | } | |
1609 | for($dm=1e30, $i=0; $i<$glb_maxcol; ++$i) { | |
1610 | if(($d = &irgb_dist($glb_pal[$i], $moy[1]))<$dm) {$dm =$d; $im = $i;} | |
1611 | } | |
1612 | ||
1613 | return ($glb_pal[$im], $glb_pal[$jm]); | |
1614 | } | |
1615 | ||
1616 | sub couple5_ { | |
1617 | my(@octet) = @_; | |
1618 | ||
1619 | # on commence un dither classique mais horizontal de l'octet | |
1620 | my($i, $im, $j, $jm, $d, $dm, $rgb, @octet_pal); | |
1621 | my(@dist) = (0) x ($glb_maxcol * 8); | |
1622 | ||
1623 | $#octet_pal = 7; | |
1624 | for($i=0; $i<8; ++$i) { | |
1625 | $rgb = $octet[$i]; | |
1626 | for($dm=1e30, $j=0; $j<$glb_maxcol; ++$j) { | |
1627 | $d = &irgb_dist($glb_pal[$j], $rgb); | |
1628 | if($d<$dm) {$dm = $d; $octet[$i] = $glb_pal[$octet_pal[$i] = $j];} | |
1629 | } | |
1630 | # $octet[$i+1] = &irgb_uadd($octet[$i+1], &irgb_map(&irgb_sub($rgb, $glb_pal[$octet_pal[$i]]), \@glb_map3)) if $i<7; | |
1631 | } | |
1632 | # comptage des occurences | |
1633 | $dm = -1; my(@cpt) = (0) x $glb_maxcol; my($filt_cpt) = 0; | |
1634 | foreach $j (@octet_pal) {if(++$cpt[$j] > $dm) {$dm = $cpt[$jm = $j];}} | |
1635 | if($dm >= 8) { | |
1636 | $im = 0; | |
1637 | print "*"; | |
1638 | } elsif($dm >= 0) { | |
1639 | # une couleur domine de loin: on la prend en fond. On cherche | |
1640 | # alors la forme qui minimise l'erreur sur l'octet d'origine | |
1641 | $dm = 1e30; $im = 0; my($p) = 0; | |
1642 | for($i = 0; $i < $glb_maxcol; ++$i) { | |
1643 | $d = $dist[$p++]; | |
1644 | $d += $dist[$p++]; | |
1645 | $d += $dist[$p++]; | |
1646 | $d += $dist[$p++]; | |
1647 | $d += $dist[$p++]; | |
1648 | $d += $dist[$p++]; | |
1649 | $d += $dist[$p++]; | |
1650 | $d += $dist[$p++]; | |
1651 | if($d<$dm) {$dm = $d; $im = $i;} | |
1652 | } | |
1653 | print "#"; | |
1654 | } else { | |
1655 | # on regroupe les pixels 2 par 2, on trouve le plus proche dans la palette | |
1656 | @octet_pal = (0) x 4; | |
1657 | for($i=0; $i<4; ++$i) { | |
1658 | $rgb = (($octet[$i*2] + $octet[$i*2+1])>>1) & 0xff3fcff; | |
1659 | for($dm=1e30, $j=0; $j<$glb_maxcol; ++$j) { | |
1660 | $dist[$j*4 + $i] = $d = &irgb_dist($glb_pal[$j], $rgb); | |
1661 | if($d<$dm) {$dm = $d; $octet_pal[$i] = $j;} | |
1662 | } | |
1663 | } | |
1664 | # comptage des occurences | |
1665 | $dm = -1; my(@cpt) = (0) x $glb_maxcol; my($filt_cpt) = 0; | |
1666 | foreach $j (@octet_pal) {if(++$cpt[$j] > $dm) {$dm = $cpt[$jm = $j];}} | |
1667 | if($dm >= 2) { | |
1668 | $dm = 1e30; $im = 0; my($p) = $jm*4; | |
1669 | for($i = 0; $i < $glb_maxcol; ++$i) { | |
1670 | $p = $i * 4; | |
1671 | $d = $dist[$p++]; | |
1672 | $d += $dist[$p++] if $d < $dm; | |
1673 | $d += $dist[$p++] if $d < $dm; | |
1674 | $d += $dist[$p ] if $d < $dm; | |
1675 | if($d<$dm) {$dm = $d; $im = $i;} | |
1676 | } | |
1677 | print ":"; | |
1678 | } else { | |
1679 | couple2(@octet) if 0; | |
1680 | $dm = 1e30; $im = 0; | |
1681 | for($i=0; $i<$glb_maxcol; ++$i) { | |
1682 | for($j=0; $j<$i; ++$j) { | |
1683 | $d = &couple_dist($i, $j, $octet_pal[0]); | |
1684 | $d += &couple_dist($i, $j, $octet_pal[1]) if $d<$dm; | |
1685 | $d += &couple_dist($i, $j, $octet_pal[2]) if $d<$dm; | |
1686 | $d += &couple_dist($i, $j, $octet_pal[3]) if $d<$dm; | |
1687 | ||
1688 | if($d<$dm) {$dm = $d; $im = $i; $jm = $j;} | |
1689 | } | |
1690 | } | |
1691 | print "."; | |
1692 | } | |
1693 | } | |
1694 | print sprintf("%x%x ", $im, $jm); | |
1695 | return ($glb_pal[$im], $glb_pal[$jm]); | |
1696 | } | |
1697 | ||
1698 | sub couple5 { | |
1699 | my(@octet) = @_; | |
1700 | ||
1701 | my($i, $j, $rgb, $dm, @px); | |
1702 | ||
1703 | if(0) { | |
1704 | # horiz dither first | |
1705 | for($i=0; $i<8; ++$i) { | |
1706 | $rgb = $octet[$i]; | |
1707 | for($j=0, $dm=1e30; $j<$glb_maxcol; ++$j) { | |
1708 | $d = &irgb_dist($glb_pal[$j], $rgb); | |
1709 | if($d<$dm) {$dm = $d; $octet[$i] = $glb_pal[$j];} | |
1710 | } | |
1711 | $octet[$i+1] = &irgb_uadd($octet[$i+1], &irgb_map(&irgb_sub($rgb, $octet[$i]), \@glb_map3)) if $i<7; | |
1712 | } | |
1713 | } | |
1714 | ||
1715 | foreach $j (@octet) {my @t = &irgb2rgb($j); push(@px, $t[0]*255, $t[1]*255, $t[2]*255);} | |
1716 | ||
1717 | my(@mean1) = ( | |
1718 | ($px[0] + $px[3] + $px[6] + $px[9]) / 4, | |
1719 | ($px[1] + $px[4] + $px[7] + $px[10]) / 4, | |
1720 | ($px[2] + $px[5] + $px[8] + $px[11]) / 4, | |
1721 | ($px[12] + $px[15] + $px[18] + $px[21]) / 4, | |
1722 | ($px[13] + $px[16] + $px[19] + $px[22]) / 4, | |
1723 | ($px[14] + $px[17] + $px[20] + $px[23]) / 4, | |
1724 | ); | |
1725 | my($d1, $d2, @mean2); | |
1726 | ||
1727 | # on trouve les deux clusters | |
1728 | while(1) { | |
1729 | #print join(",", @mean1),"\n"; | |
1730 | @mean2 = (0,0,0,0,0,0); $d1 = $d2 = 0; | |
1731 | for($i=0; $i<8; ++$i) { | |
1732 | @rgb = @px[($i*3)..($i*3+2)]; | |
1733 | if(&rgbdist(@mean1[0..2], @rgb) < &rgbdist(@mean1[3..5], @rgb)) { | |
1734 | ++$d1; $mean2[0] += $rgb[0]; $mean2[1] += $rgb[1]; $mean2[2] += $rgb[2]; | |
1735 | } else { | |
1736 | ++$d2; $mean2[3] += $rgb[0]; $mean2[4] += $rgb[1]; $mean2[5] += $rgb[2]; | |
1737 | } | |
1738 | } | |
1739 | # si un cluster est vide, on repart pour un tour | |
1740 | if($d1 == 0) { | |
1741 | # on trouve le point le plus eloigné de l'autre centre | |
1742 | @mean2 = (127, 127, 127, @mean2[3..5]); | |
1743 | } elsif($d2 == 0) { | |
1744 | @mean2 = (@mean2[0..2], 127, 127, 128); | |
1745 | } | |
1746 | ||
1747 | $d1 = 1 unless $d1>0; $d2 = 1 unless $d2>0; | |
1748 | $mean2[0] = int($mean2[0] / $d1); $mean2[1] = int($mean2[1] / $d1); $mean2[2] = int($mean2[2] / $d1); | |
1749 | $mean2[3] = int($mean2[3] / $d2); $mean2[4] = int($mean2[4] / $d2); $mean2[5] = int($mean2[5] / $d2); | |
1750 | ||
1751 | last if $mean2[0]==$mean1[0] && $mean2[1]==$mean1[1] && $mean2[2]==$mean1[2] && $mean2[3]==$mean1[3] && $mean2[4]==$mean1[4] && $mean2[5]==$mean1[5]; | |
1752 | @mean1 = @mean2; | |
1753 | } | |
1754 | ||
1755 | #print join(",", @mean1),"\n"; | |
1756 | my($mean) = ((($mean1[0]<<10) + $mean1[1])<<10) + $mean1[2]; | |
1757 | for($dm=1e30, $i=0; $i<$glb_maxcol; ++$i) { | |
1758 | $d = &irgb_dist($mean, $glb_pal[$i]); | |
1759 | if($d<$dm) {$dm = $d; $im = $i;} | |
1760 | } | |
1761 | $mean = ((($mean1[3]<<10) + $mean1[4])<<10) + $mean1[5]; | |
1762 | for($dm=1e30, $i=0; $i<$glb_maxcol; ++$i) { | |
1763 | $d = &irgb_dist($mean, $glb_pal[$i]); | |
1764 | if($d<$dm) {$dm = $d; $jm = $i;} | |
1765 | } | |
1766 | #print &irgb2hex($glb_pal[$im]), " ", &irgb2hex($glb_pal[$jm]),"\n"; | |
1767 | ||
1768 | return ($glb_pal[$im], $glb_pal[$jm]); | |
1769 | } | |
1770 | ||
1771 | sub rgbdist { | |
1772 | my($r1, $g1, $b1, $r2, $g2, $b2) = @_; | |
1773 | $r1 -= $r2; | |
1774 | $g1 -= $g2; | |
1775 | $b1 -= $b2; | |
1776 | return sqrt($r1*$r1 + $g1*$g1 + $b1*$b1); | |
1777 | } | |
1778 | ||
1779 | sub cleanup { | |
1780 | return @_ if 0; | |
1781 | my($thr) = $glb_clean; | |
1782 | return @_ unless $thr>0; | |
1783 | my(@r, $i, $t); | |
1784 | my(@t) = @_; | |
1785 | ||
1786 | if(1) { | |
1787 | # les composantes bien trop faibles sont eliminées | |
1788 | @r = @t; @t = (); | |
1789 | for $i (@r) { | |
1790 | my($r) = ($i>>00) & 0xFF; | |
1791 | my($g) = ($i>>10) & 0xFF; | |
1792 | my($b) = ($i>>20) & 0xFF; | |
1793 | my($M) = $r; | |
1794 | $M = $g if $g>$M; | |
1795 | $M = $b if $b>$M; | |
1796 | my($m) = $r; | |
1797 | $m = $g if $g<$m; | |
1798 | $m = $b if $b<$m; | |
1799 | my($l) = 0.299*$r + 0.587*$g + 0.114*$b; | |
1800 | #$m = $m*3 + $r + $g + $b; | |
1801 | #$m /= 16; | |
1802 | if(0) { | |
1803 | $M /= 4.2; #4.2; # pas mal | |
1804 | #$m = 255/8 if $m>255/8; | |
1805 | ||
1806 | #while(($r<$m && $g<$m) || ($r<$m && $b<$m) || ($g<$m && $b<$m)) {$m/=1.05; last if $m<1e-3;} | |
1807 | if($l<38 && $m<$M) { | |
1808 | my($t) = ($m + $M)>>1; | |
1809 | $r = 0 if $r<=$t; | |
1810 | $g = 0 if $g<=$t; | |
1811 | $b = 0 if $b<=$t; | |
1812 | #$r = 0 if $r < $m; | |
1813 | #$g = 0 if $g < $m; | |
1814 | #$b = 0 if $b < $m; | |
1815 | #if($g<$m) {$g=0;} | |
1816 | #elsif($r<$m) {$r=0;} | |
1817 | #elsif($b<$m) {$b=0;} | |
1818 | } | |
1819 | } elsif(1) { | |
1820 | $M /= 4.2; #4.2; # pas mal | |
1821 | if($m<$M) { | |
1822 | my($t) = $M; | |
1823 | $r = 0 if $r<=$t; | |
1824 | $g = 0 if $g<=$t; | |
1825 | $b = 0 if $b<=$t; | |
1826 | #$r = 0 if $r < $m; | |
1827 | #$g = 0 if $g < $m; | |
1828 | #$b = 0 if $b < $m; | |
1829 | #if($g<$m) {$g=0;} | |
1830 | #elsif($r<$m) {$r=0;} | |
1831 | #elsif($b<$m) {$b=0;} | |
1832 | } | |
1833 | } elsif(0) { | |
1834 | my($n) = $r; | |
1835 | $n = $g if $g<$n; | |
1836 | $n = $b if $b<$n; | |
1837 | if($n<$m/8) { | |
1838 | $r = 0 if $r <= $n*2; | |
1839 | $g = 0 if $g <= $n*2; | |
1840 | $b = 0 if $b <= $n*2; | |
1841 | } | |
1842 | } | |
1843 | push(@t, ((($b<<10)|$g)<<10)|$r); | |
1844 | } | |
1845 | } | |
1846 | ||
1847 | if(1) { | |
1848 | # on elimine les composantes plus faibles que 10% du max | |
1849 | @r = @t; @t = (); | |
1850 | for($i=0; $i<=$#r; $i+=8) { | |
1851 | my($maxr, $maxv, $maxb) = (0, 0, 0); | |
1852 | my($minr, $minv, $minb) = (1, 1, 1); | |
1853 | my($rgb, @rgb); | |
1854 | my(@o) = @r[$i..$i+7]; | |
1855 | for $rgb (@o) { | |
1856 | @rgb = &irgb2rgb($rgb); | |
1857 | $maxr = $rgb[0] if $rgb[0] > $maxr; | |
1858 | $maxv = $rgb[1] if $rgb[1] > $maxv; | |
1859 | $maxb = $rgb[2] if $rgb[2] > $maxb; | |
1860 | $minr = $rgb[0] if $rgb[0] < $minr; | |
1861 | $minv = $rgb[1] if $rgb[1] < $minv; | |
1862 | $minb = $rgb[2] if $rgb[2] < $minb; | |
1863 | } | |
1864 | $maxr = (1-$thr)*$minr + $thr*$maxr; | |
1865 | $maxv = (1-$thr)*$minv + $thr*$maxv; | |
1866 | $maxb = (1-$thr)*$minb + $thr*$maxb; | |
1867 | for $rgb (@o) { | |
1868 | @rgb = &irgb2rgb($rgb); | |
1869 | $rgb[0] = $minr if $rgb[0] < $maxr; | |
1870 | $rgb[1] = $minv if $rgb[1] < $maxv; | |
1871 | $rgb[2] = $minb if $rgb[2] < $maxb; | |
1872 | #$rgb[0] = $maxr if $rgb[0] > $maxr; | |
1873 | #$rgb[1] = $maxv if $rgb[1] > $maxv; | |
1874 | #$rgb[2] = $maxb if $rgb[2] > $maxb; | |
1875 | $t = int($rgb[0]*255)&0x1ff; $t<<=10; | |
1876 | $t += int($rgb[1]*255)&0x1ff; $t<<=10; | |
1877 | $t += int($rgb[2]*255)&0x1ff; | |
1878 | push(@t, $t); | |
1879 | } | |
1880 | } | |
1881 | } | |
1882 | ||
1883 | if(0) { | |
1884 | my($min, $max) = (32,128); | |
1885 | my($mi2) = $min>>1; | |
1886 | my($p, $x, $y); | |
1887 | for($p=$y=0; $y<200; ++$y) { | |
1888 | for($x=0; $x<320; ++$x) { | |
1889 | $p = 320*$y+$x; | |
1890 | my($rvb) = $t[$p]; | |
1891 | my($r) = ($rvb>>00) & 0x1FF; | |
1892 | my($g) = ($rvb>>10) & 0x1FF; | |
1893 | my($b) = ($rvb>>20) & 0x1FF; | |
1894 | $r=0 if $r&0x100; | |
1895 | $g=0 if $g&0x100; | |
1896 | $b=0 if $b&0x100; | |
1897 | my($m) = $r; $m = $g if $g>$m; $m = $b if $b>$m; $m=$max+1; | |
1898 | $r = ($r<$mi2 ? 0:$min) if $r<$min && $m>$max; | |
1899 | $g = ($g<$mi2 ? 0:$min) if $g<$min && $m>$max; | |
1900 | $b = ($b<$mi2 ? 0:$min) if $b<$min && $m>$max; | |
1901 | $t[$p] = ((($b<<10)|$g)<<10)|$r; | |
1902 | $rvb = &irgb_sub($rvb, $t[$p]); | |
1903 | #print " /_\\ = ", &irgb2hex($rvb), "\n"; | |
1904 | $t[$p + 319] = &irgb_add_cln($t[$p + 319], &irgb_map($rvb, \@glb_map0)) if $glb_err0 && $y<199 && $x>0; | |
1905 | $t[$p + 320] = &irgb_add_cln($t[$p + 320], &irgb_map($rvb, \@glb_map1)) if $glb_err1 && $y<199; | |
1906 | $t[$p + 321] = &irgb_add_cln($t[$p + 321], &irgb_map($rvb, \@glb_map2)) if $glb_err2 && $y<199 && $x<319; | |
1907 | $t[$p + 001] = &irgb_add_cln($t[$p + 001], &irgb_map($rvb, \@glb_map3)) if $glb_err3 && $x<319; | |
1908 | } | |
1909 | } | |
1910 | } | |
1911 | ||
1912 | &write_image("zzz.png", @t); | |
1913 | ||
1914 | return @t; | |
1915 | } | |
1916 | ||
1917 | sub couple6 { | |
1918 | my(@octet) = @_; | |
1919 | ||
1920 | my($dbg) = 0; | |
1921 | ||
1922 | my($i, $im, $j, $jm, $d, $dm, $rgb, %cpt); | |
1923 | my(@px) = (@octet); | |
1924 | ||
1925 | # dither de l'octet | |
1926 | for($i=0; $i<8; ++$i) { | |
1927 | $rgb = $px[$i]; | |
1928 | for($dm=1e30, $jm=$j=0; $j<$glb_maxcol; ++$j) { | |
1929 | $d = &irgb_dist($glb_pal[$j], $rgb); | |
1930 | #print "$j => $d :: $jm\n"; | |
1931 | if($d<$dm) {$dm = $d; $jm = $j;} | |
1932 | } | |
1933 | ++$cpt{$jm}; | |
1934 | $px[$i+1] = &irgb_add_cln($px[$i+1], &irgb_map(&irgb_sub($rgb, $glb_pal[$jm]), \@glb_map3)) if $i<7; | |
1935 | } | |
1936 | ||
1937 | my(@cpt) = (sort { $cpt{$b} - $cpt{$a} } keys %cpt); | |
1938 | ||
1939 | if($dbg) { | |
1940 | print "\n\n"; | |
1941 | for $t (@octet) { | |
1942 | print &irgb2hex($t), " "; | |
1943 | } | |
1944 | print "\n\n"; | |
1945 | for $t (@cpt) { | |
1946 | print &irgb2hex($glb_pal[$t]), " ", $cpt{$t}, "\n"; | |
1947 | } | |
1948 | } | |
1949 | ||
1950 | # 1 ou 2 couls utilisées: pas de probs | |
1951 | if($#cpt<=1) { | |
1952 | print " "; | |
1953 | # on s'assure qu'on en a au moins 2 | |
1954 | $cpt{$cpt[1] = 0} = 0 if $#cpt==0; | |
1955 | ||
1956 | return ($glb_pal[$cpt[1]], $glb_pal[$cpt[0]]); | |
1957 | } | |
1958 | ||
1959 | # les 2 couls principales couvrent 7 pixels sur les 8 | |
1960 | if($cpt{$cpt[0]} + $cpt{$cpt[1]} >= 6) { | |
1961 | print "."; | |
1962 | return ($glb_pal[$cpt[1]], $glb_pal[$cpt[0]]); | |
1963 | } | |
1964 | ||
1965 | #return (0x0ff3fcff,0); | |
1966 | ||
1967 | if(0) { | |
1968 | # on fusionne les couleurs voisines | |
1969 | $rgb = 0x0c0300c0; | |
1970 | for($i=$#cpt; $i>=0; --$i) { | |
1971 | for($j=$i-1; $j>=0; --$j) { | |
1972 | if(($rgb & $glb_pal[$cpt[$i]]) == ($rgb & $glb_pal[$cpt[$j]])) { | |
1973 | $cpt{$cpt[$j]} += $cpt{$cpt[$i]}; | |
1974 | delete $cpt{$cpt[$i]}; print "*"; | |
1975 | @cpt = (@cpt[0..$i-1], @cpt[$i+1..$#cpt]); | |
1976 | last; | |
1977 | } | |
1978 | } | |
1979 | } | |
1980 | ||
1981 | @cpt = (sort { $cpt{$b} - $cpt{$a} } keys %cpt); | |
1982 | ||
1983 | # 1 ou 2 couls utilisées: pas de probs | |
1984 | if($#cpt<=1) { | |
1985 | print "_"; | |
1986 | # on s'assure qu'on en a au moins 2 | |
1987 | $cpt{$cpt[1] = 0} = 0 if $#cpt==0; | |
1988 | ||
1989 | return ($glb_pal[$cpt[1]], $glb_pal[$cpt[0]]); | |
1990 | } | |
1991 | ||
1992 | # les 2 couls principales couvrent 7 pixels sur les 8 | |
1993 | if($cpt{$cpt[0]} + $cpt{$cpt[1]} >= 7) { | |
1994 | print ":"; | |
1995 | return ($glb_pal[$cpt[1]], $glb_pal[$cpt[0]]); | |
1996 | } | |
1997 | } | |
1998 | ||
1999 | # si la 1ere couvre 4 pixels, alors on prends la 2eme qui fait le moins d'err | |
2000 | if($cpt{$cpt[0]} >= 6) { | |
2001 | $jm = $cpt[0]; | |
2002 | for($dm=1e30, $i=0; $i<$glb_maxcol; ++$i) { | |
2003 | next if $i==$jm; | |
2004 | #next unless defined $cpt{$i}; | |
2005 | $delta = 0; | |
2006 | $d = 0; @px = (@octet); | |
2007 | if(0) { | |
2008 | foreach $j (@px) {$d += $glb_dist[$i*$glb_maxcol + $j] if $j!=$jm;} | |
2009 | } elsif(0) { | |
2010 | for($j = 0; $j<8 && $d<$dm; ++$j) {$d += &couple2_dist_sq($i, $jm, $octet[$j]);} | |
2011 | } else { | |
2012 | for($j = 0; $j<8 && $d<$dm; ++$j) { | |
2013 | $d1 = &irgb_dist($glb_pal[$i ], $px[$j]); | |
2014 | $d2 = &irgb_dist($glb_pal[$jm], $px[$j]); | |
2015 | if($d1 < $d2) {$d += &sq($d1); $rgb = $glb_pal[$i];} else {$d += &sq($d2); $rgb = $glb_pal[$jm];} | |
2016 | $px[$j+1] = &irgb_add_cln($px[$j+1], &irgb_map(&irgb_sub($px[$j], $rgb), \@glb_map3)) if $glb_err3 && $j<7; | |
2017 | } | |
2018 | #$d += &irgb_module($delta); | |
2019 | } | |
2020 | if($d < $dm) {$dm = $d; $im = $i;} | |
2021 | } | |
2022 | print "o"; | |
2023 | return ($glb_pal[$im], $glb_pal[$jm]); | |
2024 | } | |
2025 | ||
2026 | # TODO: prendre les couleurs les moins utilisées.. reduire leur résolution et voir si on peut les mapper | |
2027 | # sur l'une des couleurs plus utilisée | |
2028 | ||
2029 | # fusionner les couleurs les plus proches: seuil = 1/16 au début, puis 1/8 après | |
2030 | ||
2031 | if(0) { | |
2032 | # reduire la resolution de la palette de 0..255 à 0..7 pour merger les couleurs proches | |
2033 | %cpt = (); @px = @octet; $msk = 0x0e0380e0; | |
2034 | for($i=0; $i<8; ++$i) { | |
2035 | $rgb = $px[$i] & $msk; | |
2036 | for($dm=1e30, $jm=$j=0; $j<$glb_maxcol; ++$j) { | |
2037 | $d = &irgb_dist($glb_pal[$j] & $msk, $rgb); | |
2038 | if($d<$dm) {$dm = $d; $jm = $j;} | |
2039 | } | |
2040 | ++$cpt{$px[$i] = $jm}; | |
2041 | $px[$i+1] = &irgb_uadd($px[$i+1], &irgb_map(&irgb_sub($rgb, $glb_pal[$jm] & $msk), \@glb_map3)) if $glb_err3 && $i<7; | |
2042 | } | |
2043 | @cpt = (sort { $cpt{$b} - $cpt{$a} } keys %cpt); | |
2044 | ||
2045 | if($#cpt<=1) { | |
2046 | # on s'assure qu'on en a au moins 2 | |
2047 | $cpt{$cpt[1] = 0} = 0 if $#cpt==0; | |
2048 | print "_"; | |
2049 | return ($glb_pal[$cpt[1]], $glb_pal[$cpt[0]]); | |
2050 | } | |
2051 | ||
2052 | # les 2 couls principales couvrent 7 pixels sur les 8 (plus strict) | |
2053 | if($cpt{$cpt[0]} + $cpt{$cpt[1]} >= 7) { | |
2054 | print ":"; | |
2055 | return ($glb_pal[$cpt[1]], $glb_pal[$cpt[0]]); | |
2056 | } | |
2057 | } | |
2058 | ||
2059 | if(0) { | |
2060 | # utilisation de rayons | |
2061 | for($dm=1e30, $i=0; $i<$glb_maxcol; ++$i) { | |
2062 | for($j=0; $j<$glb_maxcol; ++$j) { | |
2063 | @px = @octet; $d = 0; $ir = $jr = 0; | |
2064 | for($k = 0; $k<8 && $d<$dm; ++$k) { | |
2065 | ||
2066 | $di = &irgb_dist($glb_pal[$i], $px[$k]); | |
2067 | $dj = &irgb_dist($glb_pal[$j], $px[$k]); | |
2068 | if($di <= $dj) { | |
2069 | $ir = $di if $di>$ir; | |
2070 | $rgb = $glb_pal[$i]; | |
2071 | } else { | |
2072 | $jr = $dj if $dj>$jr; | |
2073 | $rgb = $glb_pal[$j]; | |
2074 | } | |
2075 | $d = $ir + $jr; | |
2076 | $px[$i+1] = &irgb_uadd($px[$i+1], &irgb_map(&irgb_sub($px[$k], $rgb), \@glb_map3)) if $glb_err3 && $i<7; | |
2077 | } | |
2078 | if($d < $dm) {$dm = $d; $im = $i; $jm = $j} | |
2079 | } | |
2080 | } | |
2081 | print "#"; | |
2082 | return ($glb_pal[$im], $glb_pal[$jm]); | |
2083 | } | |
2084 | ||
2085 | # sinon tester tous les couple avec dither | |
2086 | my($r, $rm); | |
2087 | for($dm=1e30, $i=0; $i<$glb_maxcol; ++$i) { | |
2088 | #next unless defined $cpt{$i}; | |
2089 | for($j=0; $j<$i; ++$j) { | |
2090 | next unless defined $cpt{$j}; | |
2091 | @px = (@octet); | |
2092 | #print $i,",",$j, "##", &irgb2hex($glb_pal[$i])," ",&irgb2hex($glb_pal[$j]),"\n"; | |
2093 | #for($k=0; $k<8; ++$k) {print " ", &irgb2hex($px[$k]);} print "\n"; | |
2094 | for($r = $d = $k = 0; $k<8 && $d<$dm; ++$k) { | |
2095 | $di = &irgb_dist($glb_pal[$i], $px[$k]); | |
2096 | $dj = &irgb_dist($glb_pal[$j], $px[$k]); | |
2097 | if($di <= $dj) { | |
2098 | $r |= 1; | |
2099 | $rgb = $glb_pal[$i]; | |
2100 | $d += &sq($di); | |
2101 | } else { | |
2102 | $r |= 2; | |
2103 | $rgb = $glb_pal[$j]; | |
2104 | $d += &sq($dj); | |
2105 | } | |
2106 | #print $k,"->", &irgb2hex($octet[$k]), ":", &irgb2hex($rgb),"=",$d,"\n"; | |
2107 | $px[$k+1] = &irgb_add_cln($px[$k+1], &irgb_map(&irgb_sub($px[$k], $rgb), \@glb_map3)) if $glb_err3 && $k<7; | |
2108 | } | |
2109 | #print "DDDDD ",irgb2hex($delta),"\n"; | |
2110 | #$d += &irgb_module($delta); | |
2111 | #print $i,",",$j, "==", &irgb2hex($glb_pal[$i])," ",&irgb2hex($glb_pal[$j])," == ",$d," (",$dm,")\n"; | |
2112 | if($d < $dm) {$rm = $r; $dm = $d; $im = $i; $jm = $j} | |
2113 | } | |
2114 | } | |
2115 | ||
2116 | if($rm == 3) { | |
2117 | print "#"; | |
2118 | #print "==> ", $im, ",", $jm, " ",&irgb2hex($glb_pal[$im])," ",&irgb2hex($glb_pal[$jm]),"\n"; | |
2119 | return ($glb_pal[$im], $glb_pal[$jm]); | |
2120 | } | |
2121 | ||
2122 | # si en fait on a qu'une seule couleur reele (parce que la palette | |
2123 | # n'est pas assez discriminante par exemple), alors on prend la couleur | |
2124 | # la plus frequente, et on cherche la coul realisant la plus petite erreur | |
2125 | if($rm != 3) { | |
2126 | $jm = $cpt[0]; | |
2127 | for($dm=1e30, $i=0; $i<$glb_maxcol; ++$i) { | |
2128 | next if $i==$jm; | |
2129 | @px = (@octet); | |
2130 | for($d = $j = 0; $j<8 && $d<$dm; ++$j) { | |
2131 | $d1 = &irgb_dist($glb_pal[$i ], $px[$j]); | |
2132 | $d2 = &irgb_dist($glb_pal[$jm], $px[$j]); | |
2133 | if($d1<$d2) {$rgb = $glb_pal[$i]; $d += &sq($d1);} else {$rgb = $glb_pal[$jm]; $d += &sq($d2);} | |
2134 | $px[$j+1] = &irgb_add_cln($px[$j+1], &irgb_map(&irgb_sub($px[$j], $rgb), \@glb_map3)) if $glb_err3 && $j<7; | |
2135 | } | |
2136 | if($d < $dm) {$dm = $d; $im = $i;} | |
2137 | } | |
2138 | #print $cpt{$cpt[0]}," $dm\n"; | |
2139 | return ($glb_pal[$im], $glb_pal[$jm]); | |
2140 | } | |
2141 | ||
2142 | ||
2143 | if(0) { | |
2144 | print "#$dm\n"; | |
2145 | ||
2146 | ### on vérifie que le couple est bien utilisé | |
2147 | @px = (@octet); $r = 0; | |
2148 | for($k = 0; $k<8; ++$k) { | |
2149 | $di = &irgb_dist($glb_pal[$im], $px[$k]); | |
2150 | $dj = &irgb_dist($glb_pal[$jm], $px[$k]); | |
2151 | if($di <= $dj) { | |
2152 | $rgb = $glb_pal[$im]; | |
2153 | $r |= 1; | |
2154 | } else { | |
2155 | $rgb = $glb_pal[$jm]; | |
2156 | $r |= 2; | |
2157 | } | |
2158 | $px[$k+1] = &irgb_add_cln($px[$k+1], &irgb_map(&irgb_sub($px[$k], $rgb), \@glb_map3)) if $glb_err3 && $k<7; | |
2159 | } | |
2160 | ||
2161 | if($r!=3) { | |
2162 | print "\n\n"; | |
2163 | for($dm=1e30, $i=0; $i<$glb_maxcol; ++$i) { | |
2164 | for($j=0; $j<=$i; ++$j) { | |
2165 | @px = (@octet); | |
2166 | for($d = $k = 0; $k<8 && $d<$dm; ++$k) { | |
2167 | $di = &irgb_dist($glb_pal[$i], $px[$k]); | |
2168 | $dj = &irgb_dist($glb_pal[$j], $px[$k]); | |
2169 | if($di <= $dj) { | |
2170 | $d += &sq($di); | |
2171 | $rgb = $glb_pal[$i]; | |
2172 | } else { | |
2173 | $d += &sq($dj); | |
2174 | $rgb = $glb_pal[$j]; | |
2175 | } | |
2176 | $px[$k+1] = &irgb_add_cln($px[$k+1], &irgb_map(&irgb_sub($px[$k], $rgb), \@glb_map3)) if $glb_err3 && $k<7; | |
2177 | } | |
2178 | print "$i,$j ==> $d\n"; | |
2179 | if($d < $dm) {print "^^^\n"; $dm = $d; $im = $i; $jm = $j} | |
2180 | } | |
2181 | } | |
2182 | print "\n\n"; | |
2183 | @px = (@octet); $r = 0; | |
2184 | for($d = $k = 0; $k<8; ++$k) { | |
2185 | $di = &irgb_dist($glb_pal[$im], $px[$k]); | |
2186 | $dj = &irgb_dist($glb_pal[$jm], $px[$k]); | |
2187 | if($di <= $dj) { | |
2188 | $d += $di; | |
2189 | $rgb = $glb_pal[$im]; | |
2190 | $r |= 1; | |
2191 | } else { | |
2192 | $rgb = $glb_pal[$jm]; | |
2193 | $r |= 2; | |
2194 | } | |
2195 | print &irgb2hex($px[$k]),"=>$di,$dj=>",&irgb2hex($rgb),"\n"; | |
2196 | $px[$k+1] = &irgb_add_cln($px[$k+1], &irgb_map(&irgb_sub($px[$k], $rgb), \@glb_map3)) if $glb_err3 && $k<7; | |
2197 | } | |
2198 | die; | |
2199 | } | |
2200 | } | |
2201 | ||
2202 | print "#"; | |
2203 | return ($glb_pal[$im], $glb_pal[$jm]); | |
2204 | } | |
2205 | ||
2206 | sub sq { | |
2207 | return $_[0]*$_[0]; | |
2208 | } | |
2209 | ||
2210 | sub xint { | |
2211 | if(0) { | |
2212 | # round to even? | |
2213 | my($t) = @_; | |
2214 | # round to even | |
2215 | my($halfway) = int($t*2)==$t*2; | |
2216 | if($t>=0) {$t = int($t + 0.5);} else {$t = int($t - 0.5);} | |
2217 | if($halfway) {$t = int($t/2)*2;} | |
2218 | return $t; | |
2219 | } | |
2220 | ||
2221 | return int($_[0]); | |
2222 | return int(0.5 + $_[0]) if $_[0]>=0; | |
2223 | return - int(0.5 - $_[0]); | |
2224 | } | |
2225 | ||
2226 | sub write_map { | |
2227 | my($name, $ram_ab, @px) = @_; | |
2228 | ||
2229 | my($i, $t); | |
2230 | ||
2231 | # récupération de la palette RGB | |
2232 | my(%pal); | |
2233 | foreach $i (@px) {++$pal{$i};} | |
2234 | #my(@t) = (sort { $pal{$b} - $pal{$a} } keys %pal); | |
2235 | my(@t) = (sort { &irgb_module($a) - &irgb_module($b) } keys %pal); | |
2236 | die "trop de couleurs" if $#t>15; | |
2237 | @t = (@t, (0) x 15)[0..15]; | |
2238 | ||
2239 | # pour le tour on utilise la couleur la plus sombre | |
2240 | @t = ($t[0], $t[15], @t[1..14]); | |
2241 | my($tour) = 0; | |
2242 | for($i=0; $i<15; ++$i) { | |
2243 | $tour = $i if &irgb_module($t[$i])<&irgb_module($t[$tour]); | |
2244 | } | |
2245 | ||
2246 | # pour que l'afficheur de préhisto marche, il faut que le tour soit | |
2247 | # d'indexe fixe. On fait en sorte que ce soit toujours 0 | |
2248 | if($tour != 0) { | |
2249 | my($t) = $t[$tour]; | |
2250 | $t[$tour] = $t[0]; | |
2251 | $t[0] = $t; | |
2252 | $tour = 0; | |
2253 | } | |
2254 | ||
2255 | # conversion de la palette vers les intensités thomson | |
2256 | my(@pal, %rgb2pal); | |
2257 | if($glb_to7pal) { | |
2258 | @t = &to770_palette; | |
2259 | for($i=0; $i<=$#t; ++$i) {$rgb2pal{$t[$i]} = $i;} | |
2260 | } elsif($#map_ef>=0) { | |
2261 | foreach $i (@t) { | |
2262 | $rgb2pal{$i} = ($#pal + 1)>>1; | |
2263 | ||
2264 | #print &irgb2hex($i),":"; | |
2265 | my($v, $j, $d, $m, $p); | |
2266 | ||
2267 | $v = $i & 255; $i>>=10; #print "$v "; | |
2268 | for($m=1e30, $j = 0; $j<=$#ef_vals; ++$j) { | |
2269 | $d = $ef_vals[$j] - $v; $d = -$d if $d<0; | |
2270 | if($d<$m) {$m = $d; $p = $j;} | |
2271 | } | |
2272 | my($b) = $p; | |
2273 | $v = $i & 255; $i>>=10; #print "$v "; | |
2274 | for($m=1e30, $j = 0; $j<=$#ef_vals; ++$j) { | |
2275 | $d = $ef_vals[$j] - $v; $d = -$d if $d<0; | |
2276 | if($d<$m) {$m = $d; $p = $j;} | |
2277 | } | |
2278 | my($g) = $p; | |
2279 | $v = $i & 255; $i>>=10; #print "$v "; | |
2280 | for($m=1e30, $j = 0; $j<=$#ef_vals; ++$j) { | |
2281 | $d = $ef_vals[$j] - $v; $d = -$d if $d<0; | |
2282 | if($d<$m) {$m = $d; $p = $j;} | |
2283 | } | |
2284 | my($r) = $p; | |
2285 | ||
2286 | push(@pal, $b, $g*16 + $r); | |
2287 | #print sprintf("%x%x%x,", $b,$g,$r); | |
2288 | } | |
2289 | } else { | |
2290 | foreach $i (@t) { | |
2291 | $rgb2pal{$i} = ($#pal + 1)>>1; | |
2292 | ||
2293 | my($r, $g, $b) = &irgb2rgb($i); | |
2294 | push(@pal, int($b*15), int($g*15)*16 + int($r*15)); | |
2295 | } | |
2296 | } | |
2297 | @t = (); | |
2298 | ||
2299 | # construction rama / ramb | |
2300 | my(@rama, @ramb); $white = &rgb2irgb(1,1,1); | |
2301 | my($idx, @cols) = (0) x 81; | |
2302 | for($i=0; $i<$#px; $i += 8) { | |
2303 | my(@octet) = @px[$i..($i+7)]; | |
2304 | # on trouve les deux couleurs | |
2305 | my(%col) = (); | |
2306 | foreach $t (@octet) {$col{$t} = 1;} | |
2307 | @t = keys %col; | |
2308 | die "trop de couleur pour l'octet" if $#t>1; | |
2309 | ||
2310 | # cas special pour la neige | |
2311 | @t = ($white,0) if $#t==0 && $t[0]==$white; | |
2312 | @t = ($white,$t[0]) if $#t==0; | |
2313 | ||
2314 | # 1 seule couleur.. on essaye de récuperer les couleurs de la ligne d'avant si possible | |
2315 | if($#t==0) { | |
2316 | if($t[0] == $cols[$idx]) { | |
2317 | $t[1] = $cols[$idx+1]; | |
2318 | } elsif($t[0] == $cols[$idx+1]) { | |
2319 | $t[1] = $cols[$idx]; | |
2320 | } else { | |
2321 | $t[1] = 7; | |
2322 | } | |
2323 | } | |
2324 | ||
2325 | @cols[$idx..$idx+1] = @t; | |
2326 | $idx=0 if ($idx+=2)==80; | |
2327 | ||
2328 | my($forme, $fond) = ($t[0], $t[1]); | |
2329 | ||
2330 | %col = (); | |
2331 | # pour l'instant | |
2332 | $t = 0; | |
2333 | for($j=0; $j<8; ++$j) { | |
2334 | $t += (128>>$j) if $octet[$j]!=$fond; | |
2335 | } | |
2336 | ||
2337 | $forme = $rgb2pal{$forme}; | |
2338 | $fond = $rgb2pal{$fond}; | |
2339 | # pour favoriser les répétitions en ramb, on fait $forme>=$fond | |
2340 | if($forme >= $fond) { | |
2341 | push(@rama, $t); | |
2342 | push(@ramb, ($forme * 8 + ($fond&7) + ($fond & 8)*16) ^ (128+64)); | |
2343 | } else { | |
2344 | push(@rama, $t^255); | |
2345 | push(@ramb, ($fond * 8 + ($forme&7) + ($forme & 8)*16) ^ (128+64)); | |
2346 | } | |
2347 | } | |
2348 | ||
2349 | # compression à proprement parler | |
2350 | my(@data); | |
2351 | push(@data, | |
2352 | # bm 40 | |
2353 | 0, | |
2354 | # ncols-1 | |
2355 | 39, | |
2356 | # nlines-1 | |
2357 | 24); | |
2358 | push(@data, | |
2359 | # ram a | |
2360 | &to7_comp(@rama), | |
2361 | 0, 0) if $ram_ab & 1; | |
2362 | push(@data, | |
2363 | # ram b | |
2364 | &to7_comp(@ramb), | |
2365 | 0, 0) if $ram_ab & 2; | |
2366 | ||
2367 | # if faut aligner l'extension sur une addr paire | |
2368 | push(@data, 0) unless $#data & 1; | |
2369 | ||
2370 | push(@data, | |
2371 | # to-snap | |
2372 | 0, 0, 0, $tour, 0, 0, @pal, 0xa5, 0x5a) unless $glb_to7pal; | |
2373 | ||
2374 | # ecriture fichier binaire | |
2375 | open(OUT, ">$name"); | |
2376 | print OUT pack('C*', 0, int((1+$#data)/256), (1+$#data)&255, 0, 0); | |
2377 | print OUT pack('C*', @data); | |
2378 | print OUT pack('C*', 255, 0, 0, 0, 0); | |
2379 | close(OUT); | |
2380 | } | |
2381 | ||
2382 | sub to7_comp { | |
2383 | my(@data) = @_; | |
2384 | my(@result, @partial); | |
2385 | ||
2386 | for(my $p=0; $p<8000; ++$p) { | |
2387 | # on lit car num fois | |
2388 | my($num, $car) = (1, $data[($p % 200)*40 + int($p/200)]); | |
2389 | while($num<255 && $p+1<8000 && $data[(($p+1) % 200)*40 + int(($p+1)/200)]==$car) {++$p; ++$num;} | |
2390 | my($default) = 1; | |
2391 | if($#partial>$[) { | |
2392 | # 01 aa 01 bb ==> 00 02 aa bb | |
2393 | if($default && $num==1 && $partial[0]==1) {@partial = (00, 02, $partial[1], $car); $default = 0;} | |
2394 | # 00 n xx xx xx 01 bb ==> 00 n+1 xx xx xx bb | |
2395 | if($default && $num==1 && $partial[0]==0 && $partial[1]<255) {push(@partial, $car); $partial[1]++; $default = 0;} | |
2396 | # 00 n xx xx xx 02 bb ==> 00 n+2 xx xx xx bb bb (pas utile mais sert quand combiné à la regle ci-dessus) | |
2397 | if($default && $num==2 && $partial[0]==0 && $partial[1]<254) {push(@partial, $car, $car); $partial[1]+=2; $default = 0;} | |
2398 | # 01 aa 02 bb ==> 00 03 aa bb bb | |
2399 | } | |
2400 | if($default) {push(@result, @partial); @partial = ($num, $car);} | |
2401 | } | |
2402 | push(@result, @partial); | |
2403 | ||
2404 | return @result; | |
2405 | } | |
2406 | ||
2407 | sub wd_file { | |
2408 | return ".watchdog"; | |
2409 | } | |
2410 | ||
2411 | sub reset_wd { | |
2412 | unlink &wd_file; | |
2413 | } | |
2414 | ||
2415 | sub start_wd { | |
2416 | my($pause) = 300; | |
2417 | my($child) = fork; | |
2418 | die "fork failed" unless defined $child; | |
2419 | return unless $child; | |
2420 | while(1) { | |
2421 | sleep($pause); | |
2422 | my($f) = &wd_file; | |
2423 | if(-f $f) { | |
2424 | &reset_wd; | |
2425 | kill 9, $child; | |
2426 | die "Watch dog detected inactivity for $pause sec, exiting"; | |
2427 | } else { | |
2428 | open(WDFILE,">$f");close(WDFILE); | |
2429 | } | |
2430 | } | |
2431 | } |