File Coverage

blib/lib/Image/VisualConfirmation.pm
Criterion Covered Total %
statement 62 97 63.9
branch 12 38 31.5
condition 5 21 23.8
subroutine 12 15 80.0
pod 5 5 100.0
total 96 176 54.5


line stmt bran cond sub pod time code
1             package Image::VisualConfirmation;
2              
3             BEGIN {
4 1     1   69226 $Image::VisualConfirmation::VERSION = '0.10007';
5             }
6              
7 1     1   10 use strict;
  1         2  
  1         31  
8 1     1   4 use warnings;
  1         2  
  1         27  
9              
10 1     1   10 use Carp;
  1         1  
  1         101  
11 1     1   1519 use Imager();
  1         58923  
  1         54  
12 1     1   1007 use Path::Class();
  1         84622  
  1         32  
13 1     1   11 use List::Util qw/shuffle/;
  1         2  
  1         2000  
14              
15             # We want to avoid all possible confusions for the user: 0, upper and
16             # lower-case 'o', lower-case 'l' and '1', 'j'
17             our @LETTERS = (
18             'A'..'N', 'P'..'Z', 'a'..'i', 'k', 'm', 'n', 'p'..'z', '2'..'9'
19             );
20              
21             our $DEFAULT_TYPE = 'png';
22             our $DEFAULT_FONT_FACE = 'Arial'; # For Win32
23             our $DEFAULT_FONT_FILE = 'Vera.ttf'; # For all other platforms
24             our $DEFAULT_FONT_TYPE = 'ft2'; # For Vera.ttf
25             our $DEFAULT_FONT_SIZE = 20;
26             our $DEFAULT_CODE_LENGTH = 6;
27              
28             # Instantiate a new object, and then call create_new_image which
29             # does the real work
30             sub new {
31 2     2 1 1702 my ($class, $options) = @_;
32              
33 2         5 my $self = {};
34 2         6 bless $self, $class;
35              
36             # Create an image from the code
37 2         9 $self->create_new_image($options);
38            
39 0         0 return $self;
40             }
41              
42             # Create a new codice and image
43             sub create_new_image {
44 2     2 1 4 my ($self, $options) = @_;
45            
46 2 100 66     45 croak "Arguments must be an hashref"
47             if ( $options ) && ( ref($options) ne 'HASH' );
48              
49             # If we're on Win32, see if the font face is passed, otherwise
50             # grab the default one
51 1 50       7 if ( $^O =~ m/Win/xms ) {
52 0   0     0 $self->{font_face} = $options->{font_face} || $DEFAULT_FONT_FACE;
53             }
54              
55             # Now see if there is a font_file parameter, which is the one
56             # needed on Unix (and will override font_face on Windows)
57 1 50       10 if ( exists $options->{font_file} )
    50          
58             {
59 0         0 $self->{font_file} = $options->{font_file};
60              
61 0         0 $self->{font_type} = $options->{font_type};
62             }
63            
64             # Otherwise we search for the default, but only if we're not
65             # on Windows (we'll use the font_face defined above in that case)
66             elsif ( $^O !~ m/Win/xms ) {
67 1         2 my $font_basedir = __FILE__;
68 1         7 $font_basedir =~ s/\.pm\z//;
69            
70 1         17 my $font_file = Path::Class::File->new(
71             $font_basedir, '/', $DEFAULT_FONT_FILE
72             );
73              
74 1 50       348 croak 'Error getting the default font file. Please specify one'
75             if !-e $font_file;
76 1         125 $self->{font_file} = $font_file;
77              
78 1         5 $self->{font_type} = $DEFAULT_FONT_TYPE;
79             }
80              
81 1   33     6 $self->{code_length} = $options->{code_length} || $DEFAULT_CODE_LENGTH;
82 1   33     6 $self->{font_size} = $options->{font_size} || $DEFAULT_FONT_SIZE;
83            
84 1 50       4 if ( defined $options->{code} ) {
85 0 0       0 $self->{code} = ref($options->{code}) eq 'CODE'
86             ? $options->{code}->()
87             : $options->{code}
88             ;
89             }
90             else {
91 1         17 $self->{code} = $self->_generate_code();
92             }
93            
94 1         2 my ($width, $height);
95 1 50 33     10 if ( (exists $options->{width}) && (exists $options->{height}) ) {
96 0         0 $width = $options->{width};
97 0         0 $height = $options->{heigh};
98             }
99              
100             # Auto-compute the size of the image (if it's not passed)
101             else {
102 1         5 $width
103             = int($self->{font_size}*1.2) * $self->{code_length} + 20;
104 1         3 $height = $self->{font_size}*1.3 + 10;
105             }
106              
107 1 50       10 $self->{image} = Imager->new(
108             xsize => $width,
109             ysize => $height,
110             ) or croak "Can't create image objct: $!";
111              
112             # Background color
113 1         129 $self->_create_bgcolor();
114 1         46 $self->{image}->box( filled => 1, color => $self->{bgcolor} );
115              
116 1         136 $self->_create_string();
117            
118             # Rotate the image just to confuse things a bit
119 0         0 my $degrees = int(rand 10) + 10;
120 0         0 $degrees = (qw/+ -/)[int(rand 2)] . $degrees;
121 0 0       0 $self->{image}
122             = $self->{image}->rotate(degrees => $degrees, back => $self->{bgcolor})
123             or croak $self->{image}->errstr;
124              
125 0 0       0 $self->{image}->filter(type=>"gaussian", stddev=>1)
126             or croak $self->{image}->errstr;
127 0 0       0 $self->{image}->filter(type=>"noise", amount=>50, subtype=>0)
128             or croak $self->{image}->errstr;
129             }
130              
131             # Return the code in a string
132             sub code {
133 0     0 1 0 my $self = shift;
134              
135 0         0 return $self->{code};
136             }
137              
138             # Return the Imager object for the image
139             sub image {
140 0     0 1 0 my $self = shift;
141              
142 0         0 return $self->{image};
143             }
144              
145             # Return the raw data of an image, in the format specified (PNG if
146             # not otherwise stated)
147             sub image_data {
148 0     0 1 0 my ($self, $options) = @_;
149            
150 0 0 0     0 croak "Arguments must be an hashref"
151             if ( $options ) && ( ref($options) ne 'HASH' );
152              
153             # Supply a default image type if it's not already provided
154 0   0     0 $options->{type} = $options->{type} || $DEFAULT_TYPE;
155            
156 0         0 my $image = $self->{image};
157 0         0 my $image_data;
158            
159 0 0       0 $image->write(
160             data => \$image_data,
161             %$options,
162             ) or croak $image->errstr;
163              
164 0         0 return $image_data;
165             }
166              
167             # Generate the code for the image
168             sub _generate_code {
169 1     1   2 my $self = shift;
170              
171 1         2 my $code = '';
172 1         5 for my $i(1 .. $self->{code_length}) {
173 6         59 $code .= $LETTERS[ rand $#LETTERS ];
174             }
175            
176 1         5 return $code;
177             }
178              
179             # Create a random bgcolor
180             sub _create_bgcolor {
181 1     1   3 my $self = shift;
182            
183 1         20 my @components = shuffle(
184             int(rand 100)+156, int(rand 100)+156, int(rand 100)+156
185             );
186            
187 1         13 $self->{bgcolor} = new Imager::Color(
188             shuffle(int(rand 100)+156, int(rand 100)+156, int(rand 100)+156)
189             );
190             }
191              
192             # Create the funky string in the image
193             sub _create_string {
194 1     1   3 my $self = shift;
195              
196 1         2 my $image = $self->{image};
197 1         3 my $code = $self->{code};
198            
199             # Render the font
200 1         2 my $font;
201 1 50       4 if ( exists $self->{font_file} ) {
202 1         4 my %imager_params = (
203             file => $self->{font_file},
204             );
205 1 50       4 if ( defined $self->{font_type} ) {
206 1         2 $imager_params{type} = $self->{font_type};
207             }
208              
209 1 50       12 $font = Imager::Font->new(
210             %imager_params
211             ) or croak "Font file not found: $!";
212             }
213             else {
214 0 0         $font = Imager::Font->new(
215             face => $self->{font_face},
216             ) or croak "Font not found: $!";
217             }
218              
219 0           my @code_chars = split //, $code;
220 0           my $pos_x = 10;
221            
222             # Get background color components for comparison with letter
223             # color components
224 0           my ($bg_red, $bg_green, $bg_blue) = $self->{bgcolor}->rgba();
225              
226 0           my $i = 0;
227 0           while ($i < length($code) ) {
228              
229 0           my $color = Imager::Color->new(
230             shuffle( int(rand 10)+1, int(rand 100)+1, int(rand 100)+1 )
231             );
232            
233             # Make sure the font size grows sometimes a bit (20%)
234 0           my $font_growth = int( rand int($self->{font_size}*0.20) )+1;
235 0           my $font_size = $self->{font_size} + $font_growth;
236              
237 0 0         $image->align_string(
238             font => $font,
239             text => $code_chars[$i],
240             x => $pos_x,
241             y => 10,
242             valign => 'top',
243             size => $font_size,
244             color => $color,
245             aa => 1
246             ) or croak "Error inserting string: $!";
247            
248 0           $pos_x += $self->{font_size} + int(rand (int ($self->{font_size}/2)))+1;
249            
250 0           $i++;
251             }
252             }
253              
254             1;
255              
256             __END__