File Coverage

blib/lib/Image/Term256Color.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Image::Term256Color;
2              
3 1     1   26673 use 5.008;
  1         5  
  1         144  
4 1     1   7 use strict;
  1         2  
  1         35  
5 1     1   5 use warnings;
  1         7  
  1         32  
6              
7 1     1   495 use GD::Image;
  0            
  0            
8             use Term::ExtendedColor ':attributes';
9              
10             =head1 NAME
11              
12             Image::Term256Color - Display images in your 256 color terminal! (kinda)
13              
14             =head1 VERSION
15              
16             Version 0.04
17              
18             =cut
19              
20             our $VERSION = 0.05;
21              
22             =head1 SYNOPSIS
23              
24             Converts an image to 256 color terminal displayable ascii. Mostly for fun.
25              
26             use Image::Term256Color;
27              
28             print Image::Term256Color::convert( 'myimage.jpg' ) . "\n";
29              
30             Scalar context spits out a string containing term color coded text
31             representing the entire image.
32              
33             print Image::Term256Color::convert( 'myimage.jpg' , { scale_ratio => .5 } ) . "\n";
34              
35             Scale 'myimage.jpg' by 50% before converting.
36              
37             my @img_rows = Image::Term256Color::convert( 'myimage.jpg' );
38              
39             Array context gives an array of strings. Each string representing a row
40             within the image. Unlike scalar context, there are no newlines.
41              
42             =cut
43              
44              
45             # Giant table 'o term color codes and their RGB equivalents (roughly)
46              
47             my $termcolors = {
48             16 => [0 , 0 , 0], 52 => [95 , 0 , 0], 88 => [135 , 0 , 0],
49             17 => [0 , 0 , 95], 53 => [95 , 0 , 95], 89 => [135 , 0 , 95],
50             18 => [0 , 0 , 135], 54 => [95 , 0 , 135], 90 => [135 , 0 , 135],
51             19 => [0 , 0 , 175], 55 => [95 , 0 , 175], 91 => [135 , 0 , 175],
52             20 => [0 , 0 , 215], 56 => [95 , 0 , 215], 92 => [135 , 0 , 215],
53             21 => [0 , 0 , 255], 57 => [95 , 0 , 255], 93 => [135 , 0 , 255],
54             22 => [0 , 95 , 0], 58 => [95 , 95 , 0], 94 => [135 , 95 , 0],
55             23 => [0 , 95 , 95], 59 => [95 , 95 , 95], 95 => [135 , 95 , 95],
56             24 => [0 , 95 , 135], 60 => [95 , 95 , 135], 96 => [135 , 95 , 135],
57             25 => [0 , 95 , 175], 61 => [95 , 95 , 175], 97 => [135 , 95 , 175],
58             26 => [0 , 95 , 215], 62 => [95 , 95 , 215], 98 => [135 , 95 , 215],
59             27 => [0 , 95 , 255], 63 => [95 , 95 , 255], 99 => [135 , 95 , 255],
60             28 => [0 , 135 , 0], 64 => [95 , 135 , 0], 100 => [135 , 135 , 0],
61             29 => [0 , 135 , 95], 65 => [95 , 135 , 95], 101 => [135 , 135 , 95],
62             30 => [0 , 135 , 135], 66 => [95 , 135 , 135], 102 => [135 , 135 , 135],
63             31 => [0 , 135 , 175], 67 => [95 , 135 , 175], 103 => [135 , 135 , 175],
64             32 => [0 , 135 , 215], 68 => [95 , 135 , 215], 104 => [135 , 135 , 215],
65             33 => [0 , 135 , 255], 69 => [95 , 135 , 255], 105 => [135 , 135 , 255],
66             34 => [0 , 175 , 0], 70 => [95 , 175 , 0], 106 => [135 , 175 , 0],
67             35 => [0 , 175 , 95], 71 => [95 , 175 , 95], 107 => [135 , 175 , 95],
68             36 => [0 , 175 , 135], 72 => [95 , 175 , 135], 108 => [135 , 175 , 135],
69             37 => [0 , 175 , 175], 73 => [95 , 175 , 175], 109 => [135 , 175 , 175],
70             38 => [0 , 175 , 215], 74 => [95 , 175 , 215], 110 => [135 , 175 , 215],
71             39 => [0 , 175 , 255], 75 => [95 , 175 , 255], 111 => [135 , 175 , 255],
72             40 => [0 , 215 , 0], 76 => [95 , 215 , 0], 112 => [135 , 215 , 0],
73             41 => [0 , 215 , 95], 77 => [95 , 215 , 95], 113 => [135 , 215 , 95],
74             42 => [0 , 215 , 135], 78 => [95 , 215 , 135], 114 => [135 , 215 , 135],
75             43 => [0 , 215 , 175], 79 => [95 , 215 , 175], 115 => [135 , 215 , 175],
76             44 => [0 , 215 , 215], 80 => [95 , 215 , 215], 116 => [135 , 215 , 215],
77             45 => [0 , 215 , 255], 81 => [95 , 215 , 255], 117 => [135 , 215 , 255],
78             46 => [0 , 255 , 0], 82 => [95 , 255 , 0], 118 => [135 , 255 , 0],
79             47 => [0 , 255 , 95], 83 => [95 , 255 , 95], 119 => [135 , 255 , 95],
80             48 => [0 , 255 , 135], 84 => [95 , 255 , 135], 120 => [135 , 255 , 135],
81             49 => [0 , 255 , 175], 85 => [95 , 255 , 175], 121 => [135 , 255 , 175],
82             50 => [0 , 255 , 215], 86 => [95 , 255 , 215], 122 => [135 , 255 , 215],
83             51 => [0 , 255 , 255], 87 => [95 , 255 , 255], 123 => [135 , 255 , 255],
84              
85             124 => [175 , 0 , 0], 160 => [215 , 0 , 0], 196 => [255 , 0 , 0],
86             125 => [175 , 0 , 95], 161 => [215 , 0 , 95], 197 => [255 , 0 , 95],
87             126 => [175 , 0 , 135], 162 => [215 , 0 , 135], 198 => [255 , 0 , 135],
88             127 => [175 , 0 , 175], 163 => [215 , 0 , 175], 199 => [255 , 0 , 175],
89             128 => [175 , 0 , 215], 164 => [215 , 0 , 215], 200 => [255 , 0 , 215],
90             129 => [175 , 0 , 255], 165 => [215 , 0 , 255], 201 => [255 , 0 , 255],
91             130 => [175 , 95 , 0], 166 => [215 , 95 , 0], 202 => [255 , 95 , 0],
92             131 => [175 , 95 , 95], 167 => [215 , 95 , 95], 203 => [255 , 95 , 95],
93             132 => [175 , 95 , 135], 168 => [215 , 95 , 135], 204 => [255 , 95 , 135],
94             133 => [175 , 95 , 175], 169 => [215 , 95 , 175], 205 => [255 , 95 , 175],
95             134 => [175 , 95 , 215], 170 => [215 , 95 , 215], 206 => [255 , 95 , 215],
96             135 => [175 , 95 , 255], 171 => [215 , 95 , 255], 207 => [255 , 95 , 255],
97             136 => [175 , 135 , 0], 172 => [215 , 135 , 0], 208 => [255 , 135 , 0],
98             137 => [175 , 135 , 95], 173 => [215 , 135 , 95], 209 => [255 , 135 , 95],
99             138 => [175 , 135 , 135], 174 => [215 , 135 , 135], 210 => [255 , 135 , 135],
100             139 => [175 , 135 , 175], 175 => [215 , 135 , 175], 211 => [255 , 135 , 175],
101             140 => [175 , 135 , 215], 176 => [215 , 135 , 215], 212 => [255 , 135 , 215],
102             141 => [175 , 135 , 255], 177 => [215 , 135 , 255], 213 => [255 , 135 , 255],
103             142 => [175 , 175 , 0], 178 => [215 , 175 , 0], 214 => [255 , 175 , 0],
104             143 => [175 , 175 , 95], 179 => [215 , 175 , 95], 215 => [255 , 175 , 95],
105             144 => [175 , 175 , 135], 180 => [215 , 175 , 135], 216 => [255 , 175 , 135],
106             145 => [175 , 175 , 175], 181 => [215 , 175 , 175], 217 => [255 , 175 , 175],
107             146 => [175 , 175 , 215], 182 => [215 , 175 , 215], 218 => [255 , 175 , 215],
108             147 => [175 , 175 , 255], 183 => [215 , 175 , 255], 219 => [255 , 175 , 255],
109             148 => [175 , 215 , 0], 184 => [215 , 215 , 0], 220 => [255 , 215 , 0],
110             149 => [175 , 215 , 95], 185 => [215 , 215 , 95], 221 => [255 , 215 , 95],
111             150 => [175 , 215 , 135], 186 => [215 , 215 , 135], 222 => [255 , 215 , 135],
112             151 => [175 , 215 , 175], 187 => [215 , 215 , 175], 223 => [255 , 215 , 175],
113             152 => [175 , 215 , 215], 188 => [215 , 215 , 215], 224 => [255 , 215 , 215],
114             153 => [175 , 215 , 255], 189 => [215 , 215 , 255], 225 => [255 , 215 , 255],
115             154 => [175 , 255 , 0], 190 => [215 , 255 , 0], 226 => [255 , 255 , 0],
116             155 => [175 , 255 , 95], 191 => [215 , 255 , 95], 227 => [255 , 255 , 95],
117             156 => [175 , 255 , 135], 192 => [215 , 255 , 135], 228 => [255 , 255 , 135],
118             157 => [175 , 255 , 175], 193 => [215 , 255 , 175], 229 => [255 , 255 , 175],
119             158 => [175 , 255 , 215], 194 => [215 , 255 , 215], 230 => [255 , 255 , 215],
120             159 => [175 , 255 , 255], 195 => [215 , 255 , 255], 231 => [255 , 255 , 255],
121              
122             # Gray scale ramp
123             232 => [8 , 8 , 8],
124             233 => [18 , 18 , 18],
125             234 => [28 , 28 , 28],
126             235 => [38 , 38 , 38],
127             236 => [48 , 48 , 48],
128             237 => [58 , 58 , 58],
129             238 => [68 , 68 , 68],
130             239 => [78 , 78 , 78],
131             240 => [88 , 88 , 88],
132             241 => [98 , 98 , 98],
133             242 => [108 , 108 , 108],
134             243 => [118 , 118 , 118],
135             244 => [128 , 128 , 128],
136             245 => [138 , 138 , 138],
137             246 => [148 , 148 , 148],
138             247 => [158 , 158 , 158],
139             248 => [168 , 168 , 168],
140             249 => [178 , 178 , 178],
141             250 => [188 , 188 , 188],
142             251 => [198 , 198 , 198],
143             252 => [208 , 208 , 208],
144             253 => [218 , 218 , 218],
145             254 => [228 , 228 , 228],
146             255 => [238 , 238 , 238],
147             };
148              
149             # Create our palette object and lookup array
150             my $pal = GD::Image->new();
151              
152             my @colorlookup;
153             foreach my $k ( keys( %{$termcolors} )){
154             $pal->colorAllocate($termcolors->{$k}->[0],
155             $termcolors->{$k}->[1],
156             $termcolors->{$k}->[2]);
157              
158             push(@colorlookup, $k);
159             }
160              
161             =head1 SUBROUTINES/METHODS
162              
163             =head2 convert
164              
165             =head4 Image::Term256Color::convert( $filename , \%options );
166              
167             =head4 Image::Term256Color::convert( *FILEHANDLE , \%options );
168              
169             =head4 Image::Term256Color::convert( $data , \%options );
170              
171             Accepts a $filename , *FILEHANDLE or scalar containing image data for PNG ,
172             Jpeg , Gif and GD2 image types. This is thrown right into GD::Image (See L);
173              
174             The \%options hashref may consist of:
175              
176             =over
177              
178             =item * scale_ratio
179              
180             scale_ratio is a decimal number used to scale the original image
181             before converting to colored text.
182              
183             =item * scale_x
184              
185             scale_x represents the number of characters wide the resulting encoded
186             image will be. This option scales the image height proportionally.
187              
188             =item * utf8
189              
190             utf8 can either be 0 or 1. This option enables utf8 'block'
191             characters that effectively double the resolution.
192              
193             =back
194              
195             Returns a color coded string with newlines in scalar context and an
196             array of strings represting rows in the image in array context.
197              
198             Returns a 0 in scalar context and empty array in array context if there
199             is an error with the provided image.
200              
201             =cut
202              
203             sub convert {
204             my ($img, $opts) = @_;
205              
206             my $gdimg;
207             my $utf8 = 0;
208              
209             if( $opts ){
210             if( $opts->{scale_ratio} || $opts->{scale_x} ){
211             my $origimg = GD::Image->new($img);
212             unless( $origimg ){
213             return wantarray ? () : 0;
214             }
215              
216             my ($width, $height) = $origimg->getBounds();
217             my $ratio;
218              
219             if( $opts->{scale_ratio} ){
220             $ratio = $opts->{scale_ratio};
221              
222             } elsif( $opts->{scale_x} ){
223             $ratio = $opts->{scale_x} / $width;
224              
225             }
226             $gdimg = GD::Image->new($width * $ratio,
227             $height * $ratio);
228             if( $origimg->transparent() >= 0 ){
229             $gdimg->colorAllocate($origimg->rgb($origimg->transparent()));
230             $gdimg->transparent($origimg->transparent());
231             }
232             $gdimg->copyResized($origimg, 0,0,0,0,
233             $width * $ratio,
234             $height * $ratio,
235             $width , $height);
236              
237             }
238              
239             if( $opts->{utf8} ){
240             $utf8 = 1;
241             }
242             }
243              
244             unless( $gdimg ){
245             $gdimg = GD::Image->new($img);
246             unless( $gdimg ){
247             return wantarray ? () : 0;
248             }
249             }
250              
251             return convert_from_gdimg( $gdimg, $utf8 );
252             }
253              
254             # Undocumented functions
255             sub get_term256color {
256             my( $r , $g , $b ) = @_;
257              
258             return $colorlookup[$pal->colorClosest( $r , $g , $b )];
259             }
260              
261             sub gdimg_to_ansi {
262             my($img, $color_check) = @_;
263              
264             my @termimg;
265             my $char = ' ';
266             my ($width, $height) = $img->getBounds();
267              
268             for( my $i=0; $i<$height; $i++ ){
269             my $rowstring;
270             my $localstring; # Used to group spaces of the same color
271             my $curcolor = 0;
272             for( my $j=0; $j<$width; $j++ ){
273             my $newcolor = $color_check->($j, $i);
274              
275             if( $newcolor != $curcolor ){
276             $rowstring .= $curcolor >= 0 ? bg( $curcolor , $localstring) : clear() . $localstring;
277             $curcolor = $newcolor;
278             $localstring = '';
279             }
280              
281             $localstring .= $char;
282             }
283              
284             $rowstring .= $curcolor >= 0 ? bg( $curcolor , $localstring) : clear() . $localstring;
285             push(@termimg, $rowstring);
286             }
287              
288             return @termimg;
289             }
290              
291             sub gdimg_to_utf8 {
292             my($img, $color_check) = @_;
293              
294             my @termimg;
295             my ($width, $height) = $img->getBounds();
296              
297             for( my $i=0; $i<$height; $i+=2 ){
298             my $rowstring;
299             my $localstring; # Used to group spaces of the same color
300             my $curcolor = 0;
301             for( my $j=0; $j<$width; $j++ ){
302             my $colortop = $color_check->($j, $i);
303             my $colorbottom = $color_check->($j, $i+1);
304              
305             if( $colortop == $colorbottom ){
306             $rowstring .= $colortop >= 0 ? bg( $colortop, ' ') : clear() . ' ';
307             }
308             elsif( $colortop != $colorbottom ){
309             if( $colortop >= 0 && $colorbottom >= 0 ){
310             $rowstring .= bg( $colorbottom, fg( $colortop, '▀' ));
311             }
312             elsif( $colortop >= 0 && $colorbottom < 0 ){
313             $rowstring .= clear() . fg( $colortop, '▀' );
314             }
315             elsif( $colortop < 0 && $colorbottom >= 0 ){
316             $rowstring .= clear() . fg( $colorbottom, '▄' );
317             }
318             }
319             }
320              
321             push(@termimg, $rowstring);
322             }
323              
324             return @termimg;
325             }
326              
327             sub convert_from_gdimg {
328             my ($img, $utf8) = @_;
329              
330             my $transparent = $img->transparent();
331              
332             my $color_check;
333             if( $transparent >= 0 ){
334             $color_check = sub {
335             my( $j, $i ) = @_;
336             my $ptotest = $img->getPixel($j,$i);
337             if( $img->colorExact( $img->rgb($ptotest) ) < 0 ){
338             return -1;
339             } else {
340             return $colorlookup[$pal->colorClosest($img->rgb($ptotest))];
341             }
342             };
343             } else {
344             $color_check = sub {
345             my( $j, $i ) = @_;
346             my $ptotest = $img->getPixel($j,$i);
347             return $colorlookup[$pal->colorClosest($img->rgb($ptotest))];
348             };
349             }
350              
351             my @termimg = $utf8 ? gdimg_to_utf8($img, $color_check) : gdimg_to_ansi($img, $color_check);
352              
353             return wantarray ? @termimg : join("\n", @termimg);
354             }
355              
356             =head1 EXAMPLES
357              
358             Please look at L and L bundled with this distribution.
359              
360             =head1 AUTHOR
361              
362             Colin, C<< >>
363              
364             =head1 BUGS
365              
366             Please report any bugs or feature requests through
367             the web interface at L. I will be notified, and then you'll
368             automatically be notified of progress on your bug as I make changes.
369              
370              
371              
372              
373             =head1 SUPPORT
374              
375             You can find documentation for this module with the perldoc command.
376              
377             perldoc Image::Term256Color
378              
379              
380             You can also look for information at:
381              
382             =over 4
383              
384             =item * GitHub in moshen/Image-Term256Color (report bugs here)
385              
386             L
387              
388             =item * AnnoCPAN: Annotated CPAN documentation
389              
390             L
391              
392             =item * CPAN Ratings
393              
394             L
395              
396             =item * Search CPAN
397              
398             L
399              
400             =back
401              
402              
403             =head1 ACKNOWLEDGEMENTS
404              
405             L! It does all the work.
406              
407             =head1 LICENSE AND COPYRIGHT
408              
409             Copyright 2011 Colin.
410              
411             This program is free software; you can redistribute it and/or modify it
412             under the terms of either: the GNU General Public License as published
413             by the Free Software Foundation; or the Artistic License.
414              
415             See http://dev.perl.org/licenses/ for more information.
416              
417              
418             =cut
419              
420             1; # End of Image::Term256Color