File Coverage

blib/lib/Image/ThousandWords.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::ThousandWords;
2              
3             our $NAME = 'Image::ThousandWords';
4             our $VERSION = '0.10';
5             our $CHAT = undef;
6              
7             =head1 NAME
8              
9             Image::ThousandWords - convert an image to colored HTML text
10              
11             =head1 SYNOPSIS
12              
13             use Image::ThousandWords;
14             my $html = Image::ThousandWords::html(
15             image_path => 'image.jpg',
16             text => 'TurnMeIntoAnImage',
17             };
18             print "$html\n\n";
19             exit;
20              
21             Or:
22              
23             use Image::ThousandWords;
24             my $o = Image::ThousandWords->new(
25             text => $text,
26             image_path => 'shakespeare.jpg',
27             whitespace => ' ',
28             line_ends => '',
29             font_size => 20,
30             auto_size => 1,
31             size => 400,
32             font_face => "'Courier New'",
33             );
34             $html = $o->html;
35             print "Content-type:text/html\n\n", $o->wrap_html(background=>'black');
36              
37             B> for examples of use>.
38              
39             =head1 DESCRIPTION
40              
41             This module is designed to take as input the path to an image file,
42             and a string of text, and return an HTML colored text, resembling the image made out
43             of the string, repeated as necessary.
44              
45             Henning Møller-Nielsen wrote the original, Lee Goddard modified it slightly.
46              
47             Henning said,
48              
49             The inspiration I got from http://www4.telge.kth.se/~d99_kme/
50             look at http://rto.dk/images/camel.html or http://rto.dk/images/llama.html for the first versions
51             (and I look like this: http://rto.dk/images/henning.htm)
52              
53             Lee adds:
54              
55             Modified by Lee Goddard (lgoddard-at-cpan.org) 2003, and again 15 February 2004
56             - I did send Henning the mod, but he didn't publish it; I lost it, rewrote it
57             and didn't want to re-write it again. Module now has more parameters, and more
58             control over the HTML, using a combination of CSS and image resizing (the latter
59             being one of Henning's original requests).
60              
61             B> for examples of use>.
62              
63             =head1 DEPENDENCIES
64              
65             Carp
66             GD
67             Image::Thumbnail
68             HTML::Entities
69             Image::Size
70              
71             =cut
72              
73 1     1   34455 use strict;
  1         3  
  1         33  
74 1     1   5 use warnings;
  1         3  
  1         27  
75 1     1   6 use Carp "cluck";
  1         4  
  1         73  
76 1     1   443 use GD;
  0            
  0            
77             use Image::Thumbnail;
78             use HTML::Entities;
79             use Image::Size;
80              
81             # Returns an object blessed into this class
82             sub new { my $class = shift;
83             die unless defined $class and not ref $class;
84             my $self = bless {}, $class;
85             $self->_populate(@_);
86             return $self;
87             }
88              
89             sub _populate { my $self = shift;
90             my $args = (ref $_[0]? shift : {@_});
91             $self->{$_} = $args->{$_} foreach keys %$args;
92             }
93              
94             =head1 CONSTRUCTOR html
95              
96             Returns a HTML formatted string, colored to resemble IMAGE. The string consists
97             of the letters and characters from STRING.
98              
99             Accepts parameters as a hash, list or hash-reference:
100              
101             =over 4
102              
103             =item image_path
104              
105             Path to the file to convert. Must be openable by your version of GD.
106             If you don't supply this, you must supply C as a GD image.
107              
108             =item thumb
109              
110             If you do not supply C (above), you must supply this
111             as a loaded GD image.
112              
113             =item size
114              
115             The length in pixels of the largest size of the image when
116             re-sized prior to conversion to text. Default is C<100>.
117              
118             =item auto_size
119              
120             If set, experimentally tries to re-size the output to be
121             the same size, in pixels, as the input. If supplied, don't
122             bother with C or C (below), but maybe
123             use C (above).
124              
125             So, if you supply C as well as C, you are
126             requesting the output to the be C in pixels, the number
127             of characters in each row being determind by C
128              
129             If you supply C without C, then C
130             specifies the number of characters per row.
131              
132             =item text
133              
134             Text to use in conversion of the C. Default is C,
135             so you'd better supply your own. Note that whitesapce will be stripped.
136              
137             =item whitespace
138              
139             If you don't supply this at all, all whitespace will be removed.
140             If you do supply this, all whitespace will be substituted for whatever
141             this C value is. Intention is that you'll supply a single
142             space to maintain whitespace - or some character to maintain spacing
143             and colouring. See also C below.
144              
145             =item line_ends
146              
147             Replace line terminators C<[\n\r\f]> with this string: may be multiple
148             characters. By default this paramter is set to whatever C
149             is set to - setting this parameter over-rides the effets of the former
150             parameter on line terminators.
151              
152             =item font_face
153              
154             String to use in the HTML, which will be quoted in "double-quotes".
155             Defaults to C<'Arial Black','Lucida Console','Courier New', Courier'>.
156              
157             =item font_size
158              
159             Number of pixels for the size of the font used: default is C<8>.
160              
161             =item line_height
162              
163             Number of pixels for the height of a line of text. Default value
164             is two pixels less than the C.
165              
166             =item scanline_skip
167              
168             You shuldn't need this, but...
169             The number of scan-lines or rows to skip jump in each read of
170             the image. The default is to read every line, which is a C
171             of C<1> - not a very clear name, sorry. Check the relation of this to
172             the C parameter, above.
173              
174             =back
175              
176             B> for examples of use>.
177              
178             =cut
179              
180             sub html {
181             local $_;
182             my $self = (ref $_[0] eq __PACKAGE__? shift : __PACKAGE__->new);
183             $self->_populate(@_);
184             unless (defined $self->{image_path} or defined $self->{thumb}){
185             cluck "No image path" ;
186             return undef;
187             }
188             $self->{result} = "";
189             warn "# Image path: ".$self->{image_path} if defined $self->{image_path} and defined $CHAT;
190             $self->{font_size} = 8 unless defined $self->{font_size};
191             warn "# Font size: ".$self->{font_size} if defined $self->{font_size} and defined $CHAT;
192             $self->{font_face} = "'Arial Black','Lucida Console','Courier New', Courier'" unless defined $self->{font_face};
193             warn "# Font face: ".$self->{font_face} if defined $self->{font_face} and defined $CHAT;
194             $self->{line_height} = $self->{font_size} - int ($self->{font_size}/3) unless defined $self->{line_height};
195             warn "# Line height: ".$self->{line_height} if defined $self->{line_height} and defined $CHAT;
196             $self->{scanline_skip} = 1 unless defined $self->{scanline_skip};
197             warn "# Scanline: ".$self->{scanline_skip} if defined $self->{scanline_skip} and defined $CHAT;
198             $self->{text} = 'aPictureIsWorthAThousandWords' unless defined $self->{text};
199              
200             $self->{whitespace} = "" unless defined $self->{whitespace};
201             my $whitespace;
202             if ($self->{line_ends}){
203             $whitespace = '[ |\t]';
204             $self->{text} =~ s/([\n\r\f])+/$1/sg; # collapse line endings
205             } else {
206             $self->{line_ends} = $self->{whitespace};
207             $whitespace = '\s'
208             }
209             $self->{text} =~ s/${whitespace}+/$self->{whitespace}/smg if defined $self->{whitespace};
210             my @text = split //, $self->{text};
211              
212             # Set size....
213             if (defined $self->{image_path} and $self->{auto_size}){
214             my ($x, $y) = imgsize( $self->{image_path} );
215             unless ($x and $y){
216             cluck "No x or y?";
217             return undef;
218             }
219              
220             # Get the size of original
221             if (defined $self->{size}){
222             my $r = $x>$y ? $x / $self->{size} : $y / $self->{size};
223             $x /= $r;
224             $y /= $r;
225             }
226             warn "# Size is $x,$y" if $CHAT;
227              
228             # Set 'size' to longest side of original, over the font_size
229             # - what is wrong with this?!
230             $x /= $self->{font_size};
231             $y /= $self->{line_height};
232             $x = int($x)+1 if int($x) != $x;
233             $y = int($y)+1 if int($y) != $y;
234             $self->{size} = $x > $y? $x : $y;
235             warn "# New size from $x,$y is $self->{size}, so lines are $x characters of $self->{font_size}px" if $CHAT;
236             }
237             elsif (not defined $self->{size}){
238             $self->{size} = 100;
239             warn "# Set size (B) to: ".$self->{size} if defined $CHAT;
240             } elsif ($self->{size} <= $self->{font_size}){
241             cluck "Please make sure size ($self->{size}) > font_size ($self->{font_size})";
242             return undef;
243             } else {
244             warn "# Size (C) is ".$self->{size} if defined $CHAT;
245             }
246              
247             # Re-size the image if necessary (ie unless orig interface used)
248             # This is quicker than us trying to work out an average colour for
249             # a block of pixels
250             my $t;
251             unless ($self->{thumb}){
252             $t = new Image::Thumbnail(
253             module => 'GD',
254             create => 1,
255             size => $self->{size},
256             inputpath => $self->{image_path},
257             );
258             $self->{thumb} = $t->{thumb};
259             undef $t;
260             }
261              
262             unless (defined $self->{thumb}){
263             cluck "Could not re-size the image";
264             return undef;
265             }
266              
267             my ($x, $y) = $self->{thumb}->getBounds();
268             my ($R, $G, $B) = (-1);
269             @text = (@text, @text) while ($x*$y > scalar(@text));
270              
271             # Finally do the job
272             my $j;
273             for ($j = 0; $j < $y; $j += $self->{scanline_skip}) {
274             my $i;
275             for ($i = 0; $i < $x; $i++) {
276             my $index = $self->{thumb}->getPixel($i, $j);
277             my ($r,$g,$b) = $self->{thumb}->rgb($index);
278             unless (($r == $R) and ($g == $G) and ($b == $B)) {
279             ($R, $G, $B) = ($r, $g, $b);
280             my $color = '#' . sprintf("%.2X%.2X%.2X", $r, $g, $b);
281             $self->{result} .= qq¤¤;
282             }
283             my $char;
284             # Do not begin or end a line with whitespace
285             do {
286             $char = shift @text
287             } while (($i==0 or $i==$x-1) and $char eq $self->{whitespace});
288              
289             if ($self->{line_ends}){
290             if (not $char =~ s/[\n\r\f]/$self->{line_ends}/){
291             $char = HTML::Entities::encode($char);
292             }
293             } else {
294             $char = HTML::Entities::encode($char);
295             }
296              
297             $self->{result} .= $char;
298             }
299             $self->{result} .= "\n
";
300             }
301              
302             if ($self->{result}){
303             $self->{result} = qq¤
¤
304             . $self->{result}
305             . qq¤\n¤;
306             return $self->{result};
307             } else {
308             return undef;
309             }
310             }
311              
312             =head2 METHOD wrap_html
313              
314             Convenience method to return the C field wrapped in
315             HTML to make a complete page. Accepts values valid for CSS
316             in the parameter C, and text in the C (which </td> </tr> <tr> <td class="h" > <a name="317">317</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> is otherwise inherited from the calling object) or the C<image_path>. </td> </tr> <tr> <td class="h" > <a name="318">318</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="319">319</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Sets the C<title> field and returns an HTML page. </td> </tr> <tr> <td class="h" > <a name="320">320</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="321">321</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =cut </td> </tr> <tr> <td class="h" > <a name="322">322</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="323">323</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> sub wrap_html { my ($self, $args) = (shift, (ref $_[0]? shift : {@_})); </td> </tr> <tr> <td class="h" > <a name="324">324</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $args->{background} = "black" unless defined $args->{background}; </td> </tr> <tr> <td class="h" > <a name="325">325</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $self->{title} = $args->{title} if defined $args->{title}; </td> </tr> <tr> <td class="h" > <a name="326">326</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> if (not defined $self->{title}){ </td> </tr> <tr> <td class="h" > <a name="327">327</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $self->{title} = $self->{image_path}; </td> </tr> <tr> <td class="h" > <a name="328">328</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> ($self->{title}) = $self->{title} =~ /([^\\\/]+)\.\w{3,4}$/; </td> </tr> <tr> <td class="h" > <a name="329">329</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $self->{title} =~ s/_+/ /g; </td> </tr> <tr> <td class="h" > <a name="330">330</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> } </td> </tr> <tr> <td class="h" > <a name="331">331</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> return "\n\n<html> </td> </tr> <tr> <td class="h" > <a name="332">332</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> <head><title>$self->{title}
333             $self->{result}
334             \n";
335             }
336              
337             =head1 BACKWARDS COMPARABILITY
338              
339             The original C module's C method is still acceptable.
340              
341             =cut
342              
343             sub giveme ($$) {
344             return html(
345             thumb => $_[0],
346             text => $_[1],
347             );
348             }
349              
350             1;
351              
352             =head1 EXAMPLES FROM HENNING:
353              
354             Made with the v. 0.01 (just a script, inspired by http://www4.telge.kth.se/~d99_kme/)
355              
356             http://rto.dk/images/camel.html
357             http://rto.dk/images/llama.html
358             http://rto.dk/images/henning.html (me)
359              
360             Made with the v. 0.03
361              
362             http://rto.dk/images/neptune.html
363             http://rto.dk/images/mars.html
364             http://rto.dk/images/pluto_charon.html
365             http://rto.dk/images/earth.html
366             http://rto.dk/images/saturn.html
367             http://rto.dk/images/jupiter.html (here the reason for v. 0.04 is apparent)
368             http://rto.dk/images/ira1.html
369             http://rto.dk/images/ira2.html (my colleagues)
370              
371             =head1 KNOWN BUGS
372              
373             None, from a perl perspective. From an image perspective things look different :-)
374              
375             =head1 AUTHOR ETC.
376              
377             Henning Michael Møller-Nielsen, hmn -at- datagraf.dk
378              
379             Slightly modified by Lee Goddard, lgoddard -at- cpan.org
380              
381             =head1 VERSION HISTORY
382              
383             A bit of an overkill, but hey - this is Fun!
384              
385             0.01 Not really a module, just a script
386             0.02 'ThousandWords.pm' came to life
387             0.03 Fixed an error so the first text in a black image wouldn't be white
388             0.04 Fixed an error so the first text in a black image wouldn't be larger
389             than the rest and so spaces no longer would be used
390             0.05 Ah - added POD
391             0.06 Lee added: re-sizing of image; new access method; proper HTML entities
392             0.07 Sod it - full OO interface, more re-sizing, line-feeds/whitespace
393             0.08 Fixed MANIFEST for test.pl
394              
395             Future:
396              
397             ANSI colored text?
398              
399             Work on the 'size' field
400              
401             =head1 SEE ALSO
402              
403             L,
404             L,
405             L.
406              
407             B> for examples of use>.