File Coverage

blib/lib/Image/Caa.pm
Criterion Covered Total %
statement 211 266 79.3
branch 26 56 46.4
condition 6 16 37.5
subroutine 30 31 96.7
pod 2 7 28.5
total 275 376 73.1


line stmt bran cond sub pod time code
1             package Image::Caa;
2              
3 3     3   108328 use strict;
  3         11  
  3         134  
4 3     3   16 use warnings;
  3         5  
  3         145  
5              
6             our $VERSION = '1.01';
7              
8             # dark colors
9 3     3   24 use constant CAA_COLOR_BLACK => 0;
  3         11  
  3         317  
10 3     3   16 use constant CAA_COLOR_RED => 1;
  3         4  
  3         134  
11 3     3   14 use constant CAA_COLOR_GREEN => 2;
  3         6  
  3         129  
12 3     3   15 use constant CAA_COLOR_YELLOW => 3;
  3         3  
  3         209  
13 3     3   15 use constant CAA_COLOR_BLUE => 4;
  3         41  
  3         136  
14 3     3   15 use constant CAA_COLOR_MAGENTA => 5;
  3         4  
  3         162  
15 3     3   13 use constant CAA_COLOR_CYAN => 6;
  3         5  
  3         187  
16 3     3   44 use constant CAA_COLOR_LIGHTGRAY => 7;
  3         6  
  3         238  
17              
18             # light colors
19 3     3   22 use constant CAA_COLOR_DARKGRAY => 8;
  3         6  
  3         133  
20 3     3   15 use constant CAA_COLOR_LIGHTRED => 9;
  3         5  
  3         125  
21 3     3   24 use constant CAA_COLOR_LIGHTGREEN => 10;
  3         4  
  3         126  
22 3     3   20 use constant CAA_COLOR_BROWN => 11;
  3         5  
  3         278  
23 3     3   14 use constant CAA_COLOR_LIGHTBLUE => 12;
  3         11  
  3         151  
24 3     3   15 use constant CAA_COLOR_LIGHTMAGENTA => 13;
  3         5  
  3         117  
25 3     3   16 use constant CAA_COLOR_LIGHTCYAN => 14;
  3         5  
  3         131  
26 3     3   15 use constant CAA_COLOR_WHITE => 15;
  3         4  
  3         131  
27              
28 3     3   15 use constant CAA_LOOKUP_VAL => 32;
  3         5  
  3         128  
29 3     3   22 use constant CAA_LOOKUP_SAT => 32;
  3         4  
  3         174  
30 3     3   45 use constant CAA_LOOKUP_HUE => 16;
  3         4  
  3         5375  
31              
32 3     3   25 use constant CAA_HSV_XRATIO => 6;
  3         5  
  3         183  
33 3     3   16 use constant CAA_HSV_YRATIO => 3;
  3         7  
  3         117  
34 3     3   14 use constant CAA_HSV_HRATIO => 3;
  3         5  
  3         13482  
35              
36              
37             sub new {
38 15     15 1 6406 my $class = shift;
39 15         60 my %opts = @_;
40 15         36 my $opts = \%opts;
41              
42 15         549 my $self = bless {}, $class;
43              
44 15   100     131 $self->{driver} = $self->load_submodule($opts->{driver} || 'DriverANSI', $opts);
45 14   100     75 $self->{dither} = $self->load_submodule($opts->{dither} || 'DitherNone', $opts);
46 14 100       64 $self->{solid_background} = $opts->{black_bg} ? 0 : 1;
47              
48 14         103 $self->{hsv_palette} = [
49             # weight, hue, saturation, value
50             4, 0x0, 0x0, 0x0, # black
51             5, 0x0, 0x0, 0x5ff, # 30%
52             5, 0x0, 0x0, 0x9ff, # 70%
53             4, 0x0, 0x0, 0xfff, # white
54             3, 0x1000, 0xfff, 0x5ff, # dark yellow
55             2, 0x1000, 0xfff, 0xfff, # light yellow
56             3, 0x0, 0xfff, 0x5ff, # dark red
57             2, 0x0, 0xfff, 0xfff # light red
58             ];
59              
60 14         47 $self->init();
61              
62 14         259 return $self;
63             }
64              
65              
66             sub init {
67 14     14 0 26 my ($self) = @_;
68              
69 14         34 $self->{hsv_distances} = [];
70              
71 14         52 for (my $v = 0; $v < CAA_LOOKUP_VAL; $v++){
72 448         966 for (my $s = 0; $s < CAA_LOOKUP_SAT; $s++){
73 14336         28102 for (my $h = 0; $h < CAA_LOOKUP_HUE; $h++){
74              
75 229376         316223 my $val = 0xfff * $v / (CAA_LOOKUP_VAL - 1);
76 229376         298320 my $sat = 0xfff * $s / (CAA_LOOKUP_SAT - 1);
77 229376         279769 my $hue = 0xfff * $h / (CAA_LOOKUP_HUE - 1);
78              
79             # Initialise distances to the distance between pure black HSV
80             # coordinates and our white colour (3)
81              
82 229376         245760 my $outbg = 3;
83 229376         245480 my $outfg = 3;
84 229376         448520 my $distbg = $self->HSV_DISTANCE(0, 0, 0, 3);
85 229376         455098 my $distfg = $self->HSV_DISTANCE(0, 0, 0, 3);
86              
87              
88             # Calculate distances to eight major colour values and store the
89             # two nearest points in our lookup table.
90              
91 229376         540708 for (my $i = 0; $i < 8; $i++){
92              
93 1835008         3585878 my $dist = $self->HSV_DISTANCE($hue, $sat, $val, $i);
94              
95 1835008 100       5473272 if ($dist <= $distbg){
    100          
96              
97 651252         705941 $outfg = $outbg;
98 651252         701596 $distfg = $distbg;
99 651252         738054 $outbg = $i;
100 651252         1550822 $distbg = $dist;
101              
102             }elsif ($dist <= $distfg){
103              
104 286846         282773 $outfg = $i;
105 286846         650782 $distfg = $dist;
106             }
107             }
108              
109 229376         978311 $self->{hsv_distances}->[$v]->[$s]->[$h] = ($outfg << 4) | $outbg;
110             }
111             }
112             }
113             }
114              
115             sub init_instance {
116 5     5 0 10 my ($self) = @_;
117              
118 5         18 $self->{lookup_colors} = [];
119              
120             # These ones are constant
121 5         17 $self->{lookup_colors}->[0] = CAA_COLOR_BLACK;
122 5         10 $self->{lookup_colors}->[1] = CAA_COLOR_DARKGRAY;
123 5         12 $self->{lookup_colors}->[2] = CAA_COLOR_LIGHTGRAY;
124 5         14 $self->{lookup_colors}->[3] = CAA_COLOR_WHITE;
125              
126             # These ones will be overwritten
127 5         10 $self->{lookup_colors}->[4] = CAA_COLOR_MAGENTA;
128 5         26 $self->{lookup_colors}->[5] = CAA_COLOR_LIGHTMAGENTA;
129 5         12 $self->{lookup_colors}->[6] = CAA_COLOR_RED;
130 5         11 $self->{lookup_colors}->[7] = CAA_COLOR_LIGHTRED;
131             }
132              
133             #
134             # Draw a bitmap on the screen.
135             #
136             # Draw a bitmap at the given coordinates. The bitmap can be of any size and
137             # will be stretched to the text area.
138             #
139             # x1 X coordinate of the upper-left corner of the drawing area.
140             # y1 Y coordinate of the upper-left corner of the drawing area.
141             # x2 X coordinate of the lower-right corner of the drawing area.
142             # y2 Y coordinate of the lower-right corner of the drawing area.
143             # image Image Magick picture object to be drawn.
144             #
145              
146             sub draw_bitmap{
147 5     5 1 43 my ($self, $x1, $y1, $x2, $y2, $image) = @_;
148              
149 5         9 my $w = $x2-$x1;
150 5         9 my $h = $y2-$y1;
151              
152 5         9 my $iw = 0;
153 5         9 my $ih = 0;
154 5         8 my $h_pad = 0;
155 5         11 my $v_pad = 0;
156              
157 5 50       18 if (defined $image){
158              
159             # resize to fit in the box
160              
161 0         0 $image->Scale('100%,67%');
162 0         0 my $x = $image->Resize(geometry => ($w-2).'x'.($h-2));
163 0 0       0 warn "$x" if "$x";
164              
165 0         0 ($iw, $ih) = $image->Get('columns', 'rows');
166              
167 0         0 $h_pad = 1 + int(($w - $iw) / 2);
168 0         0 $v_pad = 1 + int(($h - $ih) / 2);
169             }
170              
171 5         21 $self->init_instance();
172 5         34 $self->{driver}->init();
173              
174              
175             # Only used when background is black
176              
177 5         14 my $white_colors = [
178             CAA_COLOR_BLACK,
179             CAA_COLOR_DARKGRAY,
180             CAA_COLOR_LIGHTGRAY,
181             CAA_COLOR_WHITE,
182             ];
183              
184 5         19 my $light_colors = [
185             CAA_COLOR_LIGHTMAGENTA,
186             CAA_COLOR_LIGHTRED,
187             CAA_COLOR_YELLOW,
188             CAA_COLOR_LIGHTGREEN,
189             CAA_COLOR_LIGHTCYAN,
190             CAA_COLOR_LIGHTBLUE,
191             CAA_COLOR_LIGHTMAGENTA,
192             ];
193              
194 5         18 my $dark_colors = [
195             CAA_COLOR_MAGENTA,
196             CAA_COLOR_RED,
197             CAA_COLOR_BROWN,
198             CAA_COLOR_GREEN,
199             CAA_COLOR_CYAN,
200             CAA_COLOR_BLUE,
201             CAA_COLOR_MAGENTA,
202             ];
203              
204              
205             # FIXME: choose better characters!
206              
207 5         18 my $density_chars =
208             " ".
209             ". ".
210             ".. ".
211             "....".
212             "::::".
213             ";=;=".
214             "tftf".
215             '%$%$'.
216             "&KSZ".
217             "WXGM".
218             '@@@@'.
219             "8888".
220             "####".
221             "????";
222              
223 5         183 my @density_chars = split //, $density_chars;
224 5         23 $density_chars = \@density_chars;
225              
226 5         8 my $density_chars_size = scalar(@{$density_chars}) - 1;
  5         13  
227              
228 5         7 my $x = 0;
229 5         10 my $y = 0;
230 5         7 my $deltax = 0;
231 5         6 my $deltay = 0;
232              
233              
234 5         7 my $tmp;
235 5 50       16 if ($x1 > $x2){ $tmp = $x2; $x2 = $x1; $x1 = $tmp; }
  0         0  
  0         0  
  0         0  
236 5 50       14 if ($y1 > $y2){ $tmp = $y2; $y2 = $y1; $y1 = $tmp; }
  0         0  
  0         0  
  0         0  
237              
238 5         9 $deltax = $x2 - $x1 + 1;
239 5         9 $deltay = $y2 - $y1 + 1;
240              
241              
242 5 50       67 for ($y = $y1 > 0 ? $y1 : 0; $y <= $y2; $y++){
243 10         45 $self->{dither}->init($y);
244 10 50       38 for ($x = $x1 > 0 ? $x1 : 0; $x <= $x2; $x++){
245              
246 20         25 my $ch = 0;
247 20         22 my $r = 0;
248 20         20 my $g = 0;
249 20         15 my $b = 0;
250 20         23 my $a = 0;
251 20         20 my $hue = 0;
252 20         20 my $sat = 0;
253 20         24 my $val = 0;
254 20         21 my $fromx = 0;
255 20         20 my $fromy = 0;
256 20         20 my $tox = 0;
257 20         20 my $toy = 0;
258 20         13 my $myx = 0;
259 20         26 my $myy = 0;
260 20         19 my $dots = 0;
261 20         24 my $outfg = 0;
262 20         18 my $outbg = 0;
263 20         23 my $outch = chr 0;
264              
265             # First get RGB
266              
267 20 50       33 if (defined $image){
268              
269 0         0 my $px = ($x - $x1) - $h_pad;
270 0         0 my $py = ($y - $y1) - $v_pad;
271              
272 0         0 my $to_l = $px < 0;
273 0         0 my $to_t = $py < 0;
274 0         0 my $to_r = $px >= $iw;
275 0         0 my $to_b = $py >= $ih;
276              
277 0 0 0     0 if ($to_l || $to_t || $to_r || $to_b){
      0        
      0        
278              
279 0         0 $r = 0xfff;
280 0         0 $g = 0xfff;
281 0         0 $b = 0xfff;
282              
283             }else{
284              
285 0         0 ($r, $g, $b, $a) = split /,/, $image->Get("pixel[$px,$py]");
286              
287 0         0 $r >>= 4;
288 0         0 $g >>= 4;
289 0         0 $b >>= 4;
290             }
291              
292             #if (bitmap->has_alpha && a < 0x800) continue;
293              
294             # Now get HSV from RGB
295 0         0 ($hue, $sat, $val) = $self->rgb2hsv_default($r, $g, $b);
296              
297             }else{
298              
299 20         38 $hue = int(0x5fff * (($x-$x1) / ($x2-$x1)));
300 20         29 $sat = int(0xfff * (($y-$y1) / ($y2-$y1)));
301 20         26 $val = int(0xfff * (($y-$y1) / ($y2-$y1)));
302 20         24 $val = 0x777;
303             }
304              
305              
306             # The hard work: calculate foreground and background colours,
307             # as well as the most appropriate character to output.
308              
309 20 50       39 if ($self->{solid_background}){
310              
311 20         26 my $point = chr 0;
312 20         19 my $distfg = 0;
313 20         26 my $distbg = 0;
314              
315 20         44 $self->{lookup_colors}->[4] = $dark_colors->[1 + $hue / 0x1000];
316 20         38 $self->{lookup_colors}->[5] = $light_colors->[1 + $hue / 0x1000];
317 20         31 $self->{lookup_colors}->[6] = $dark_colors->[$hue / 0x1000];
318 20         32 $self->{lookup_colors}->[7] = $light_colors->[$hue / 0x1000];
319              
320 20         65 my $idx_v = ($val + $self->{dither}->get() * (0x1000 / CAA_LOOKUP_VAL) / 0x100) * (CAA_LOOKUP_VAL - 1) / 0x1000;
321 20         56 my $idx_s = ($sat + $self->{dither}->get() * (0x1000 / CAA_LOOKUP_SAT) / 0x100) * (CAA_LOOKUP_SAT - 1) / 0x1000;
322 20         92 my $idx_h = (($hue & 0xfff) + $self->{dither}->get() * (0x1000 / CAA_LOOKUP_HUE) / 0x100) * (CAA_LOOKUP_HUE - 1) / 0x1000;
323              
324 20         49 $point = $self->{hsv_distances}->[$idx_v]->[$idx_s]->[$idx_h];
325              
326 20         120 $distfg = $self->HSV_DISTANCE($hue % 0xfff, $sat, $val, ($point >> 4));
327 20         57 $distbg = $self->HSV_DISTANCE($hue % 0xfff, $sat, $val, ($point & 0xf));
328              
329             # Sanity check due to the lack of precision in hsv_distances,
330             # and distbg can be > distfg because of dithering fuzziness.
331              
332 20 50       49 if ($distbg > $distfg){ $distbg = $distfg; }
  0         0  
333              
334 20         34 $outfg = $self->{lookup_colors}->[($point >> 4)];
335 20         30 $outbg = $self->{lookup_colors}->[($point & 0xf)];
336              
337 20         33 $ch = $distbg * 2 * ($density_chars_size - 1) / ($distbg + $distfg);
338 20         64 $ch = 4 * $ch + $self->{dither}->get() / 0x40;
339              
340 20 100       25 if ($ch >= scalar(@{$density_chars})){
  20         55  
341              
342 15         14 $ch = scalar(@{$density_chars}) - 1;
  15         25  
343             }
344              
345 20         40 $outch = $density_chars->[$ch];
346              
347             }else{
348              
349 0         0 $outbg = CAA_COLOR_BLACK;
350              
351 0 0       0 if ($sat < 0x200 + $self->{dither}->get() * 0x8){
    0          
352              
353 0         0 $outfg = $white_colors->[1 + ($val * 2 + $self->{dither}->get() * 0x10) / 0x1000];
354              
355             }elsif ($val > 0x800 + $self->{dither}->get() * 0x4){
356              
357 0         0 $outfg = $light_colors->[($hue + $self->{dither}->get() * 0x10) / 0x1000];
358              
359             }else{
360 0         0 $outfg = $dark_colors->[($hue + $self->{dither}->get() * 0x10) / 0x1000];
361             }
362              
363 0         0 $ch = ($val + 0x2 * $self->{dither}->get()) * 10 / 0x1000;
364 0         0 $ch = 4 * $ch + $self->{dither}->get() / 0x40;
365              
366 0         0 $outch = $density_chars->[$ch];
367             }
368              
369             # Now output the character
370 20         64 $self->{driver}->set_color($outfg, $outbg);
371 20         57 $self->{driver}->putchar($x, $y, $outch);
372              
373 20         58 $self->{dither}->increment();
374             }
375             }
376              
377 5         18 $self->{driver}->fini();
378             }
379              
380             sub rgb2hsv_default {
381 0     0 0 0 my ($self, $r, $g, $b) = @_;
382              
383 0         0 my ($hue, $sat, $val) = (0, 0, 0);
384              
385 0         0 my $min = $r;
386 0         0 my $max = $r;
387              
388 0 0       0 $min = $g if $min > $g;
389 0 0       0 $max = $g if $max < $g;
390 0 0       0 $min = $b if $min > $b;
391 0 0       0 $max = $b if $max < $b;
392              
393 0         0 my $delta = $max - $min; # 0 - 0xfff
394 0         0 $val = $max; # 0 - 0xfff
395              
396 0 0       0 if ($delta){
397              
398 0         0 $sat = 0xfff * $delta / $max; # 0 - 0xfff
399              
400             # Generate *hue between 0 and 0x5fff
401              
402 0 0       0 if ($r == $max){
    0          
403 0         0 $hue = 0x1000 + 0x1000 * ($g - $b) / $delta;
404             }elsif ($g == $max){
405 0         0 $hue = 0x3000 + 0x1000 * ($b - $r) / $delta;
406             }else{
407 0         0 $hue = 0x5000 + 0x1000 * ($r - $g) / $delta;
408             }
409             }else{
410 0         0 $sat = 0;
411 0         0 $hue = 0;
412             }
413              
414 0         0 return ($hue, $sat, $val);
415             }
416              
417              
418             sub HSV_DISTANCE{
419 2293800     2293800 0 3165216 my ($self, $h, $s, $v, $index) = @_;
420              
421 2293800         4128561 my $v1 = $v - $self->{hsv_palette}->[$index * 4 + 3];
422 2293800         3456118 my $s1 = $s - $self->{hsv_palette}->[$index * 4 + 2];
423 2293800         3500070 my $h1 = $h - $self->{hsv_palette}->[$index * 4 + 1];
424              
425 2293800 100       4919191 my $s2 = $self->{hsv_palette}->[$index * 4 + 3] ? CAA_HSV_YRATIO * $s1 * $s1 : 0;
426 2293800 100       4409578 my $h2 = $self->{hsv_palette}->[$index * 4 + 2] ? CAA_HSV_HRATIO * $h1 * $h1 : 0;
427              
428 2293800         5935058 return $self->{hsv_palette}->[$index * 4] * ((CAA_HSV_XRATIO * $v1 * $v1) + $s2 + $h2);
429             }
430              
431             sub load_submodule {
432 29     29 0 51 my ($self, $module, $args) = @_;
433              
434 29         2206 eval "require Image::Caa::$module";
435 29 100       185 warn $@ if $@;
436              
437 29         46 my $obj = undef;
438 29         8761 eval "\$obj = new Image::Caa::$module(\$args)";
439 29 100       138 warn $@ if $@;
440              
441 29 100 66     163 if (!$@ && defined $obj){
442              
443 28         113 return $obj;
444             }
445              
446 1         205 die "Image::Caa - Couldn't load 'Image::Caa::$module'";
447             }
448              
449             1;
450              
451             __END__