File Coverage

blib/lib/Image/Xbm.pm
Criterion Covered Total %
statement 177 214 82.7
branch 64 116 55.1
condition 31 95 32.6
subroutine 17 22 77.2
pod 14 14 100.0
total 303 461 65.7


line stmt bran cond sub pod time code
1             package Image::Xbm ; # Documented at the __END__
2              
3 1     1   49500 use strict ;
  1         2  
  1         29  
4              
5 1     1   4 use vars qw( $VERSION @ISA ) ;
  1         1  
  1         61  
6             $VERSION = '1.09' ;
7              
8 1     1   913 use Image::Base ;
  1         1956  
  1         44  
9              
10             @ISA = qw( Image::Base ) ;
11              
12 1     1   6 use Carp qw( carp croak ) ;
  1         3  
  1         50  
13 1     1   4 use Symbol () ;
  1         2  
  1         2885  
14              
15              
16             # Private class data
17              
18             my $DEF_SIZE = 8192 ;
19             my $UNSET = -1 ;
20             my $MASK = 7 ;
21             my $ROWS = 12 ;
22              
23             # If you inherit don't clobber these fields!
24             my @FIELD = qw( -file -width -height -hotx -hoty -bits
25             -setch -unsetch -sethotch -unsethotch ) ;
26              
27             my @MASK = ( 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 ) ;
28              
29              
30             ### Private methods
31             #
32             # _class_get class object
33             # _class_set class object
34             # _get object inherited
35             # _set object inherited
36              
37             {
38             my %Ch = ( -setch => '#', -unsetch => '-',
39             -sethotch => 'H', -unsethotch => 'h' ) ;
40            
41              
42             sub _class_get { # Class and object method
43 7     7   11 my $self = shift ;
44 7   33     18 my $class = ref( $self ) || $self ;
45              
46 7         28 $Ch{shift()} ;
47             }
48              
49              
50             sub _class_set { # Class and object method
51 0     0   0 my $self = shift ;
52 0   0     0 my $class = ref( $self ) || $self ;
53              
54 0         0 my $field = shift ;
55 0         0 my $val = shift ;
56              
57 0 0       0 croak "_class_set() `$field' has no value" unless defined $val ;
58              
59 0         0 $Ch{$field} = $val ;
60             }
61             }
62              
63              
64       0     sub DESTROY {
65             ; # Save's time
66             }
67              
68              
69             ### Public methods
70              
71             sub new_from_string { # Class and object method
72 1     1 1 963 my $self = shift ;
73 1   33     8 my $class = ref( $self ) || $self ;
74              
75 1         1 my @line ;
76            
77 1 50       4 if( @_ > 1 ) {
78 0         0 chomp( @line = @_ ) ;
79             }
80             else {
81 1         10 @line = split /\n/, $_[0] ;
82             }
83              
84 1         6 my( $setch, $sethotch, $unsethotch ) =
85             $class->get( '-setch', '-sethotch', '-unsethotch' ) ;
86              
87 1         1 my $width ;
88 1         2 my $y = 0 ;
89            
90 1         5 $self = $class->new( '-width' => $DEF_SIZE, '-height' => $DEF_SIZE ) ;
91              
92 1         3 foreach my $line ( @line ) {
93 6 50       19 next if $line =~ /^\s*$/ ;
94 6 100       12 unless( defined $width ) {
95 1         2 $width = length $line ;
96 1         9 $self->_set( '-width' => $width ) ;
97             }
98 6         18 for( my $x = 0 ; $x < $width ; $x++ ) {
99 30         40 my $c = substr( $line, $x, 1 ) ;
100 30 50       81 $self->xybit( $x, $y, $c eq $setch ? 1 : $c eq $sethotch ? 1 : 0 ) ;
    100          
101 30 50 33     150 $self->set( '-hotx' => $x, '-hoty' => $y )
102             if $c eq $sethotch or $c eq $unsethotch ;
103             }
104 6         11 $y++ ;
105             }
106              
107 1         3 $self->_set( '-height' => $y ) ;
108              
109 1         7 $self ;
110             }
111              
112              
113             sub new { # Class and object method
114 4     4 1 5 my $self = shift ;
115 4   66     16 my $class = ref( $self ) || $self ;
116 4 100       8 my $obj = ref $self ? $self : undef ;
117 4         17 my %arg = @_ ;
118              
119             # Defaults
120 4         13 $self = {
121             '-hotx' => $UNSET,
122             '-hoty' => $UNSET,
123             '-bits' => '',
124             } ;
125              
126 4         7 bless $self, $class ;
127              
128             # If $obj->new copy original object's data
129 4 100       12 if( defined $obj ) {
130 1         2 foreach my $field ( @FIELD ) {
131 10         47 $self->_set( $field, $obj->get( $field ) ) ;
132             }
133             }
134              
135             # Any options specified override
136 4         10 foreach my $field ( @FIELD ) {
137 40 100       124 $self->_set( $field, $arg{$field} ) if defined $arg{$field} ;
138             }
139              
140 4         8 my $file = $self->get( '-file' ) ;
141 4 50 66     40 $self->load if defined $file and -r $file and not $self->{'-bits'} ;
      66        
142              
143 4 50 66     20 croak "new() `$file' not found or unreadable"
144             if defined $file and not defined $self->get( '-width' ) ;
145              
146              
147 4         9 foreach my $field ( qw( -width -height ) ) {
148 8 50       16 croak "new() $field must be set" unless defined $self->get( $field ) ;
149             }
150              
151 4         12 $self ;
152             }
153              
154              
155             sub new_from_serialised { # Class and object method
156 1     1 1 363 my $self = shift ;
157 1   33     7 my $class = ref( $self ) || $self ;
158 1         2 my $serialised = shift ;
159              
160 1         4 $self = $class->new( '-width' => $DEF_SIZE, '-height' => $DEF_SIZE ) ;
161              
162 1         7 my( $flen, $blen, $width, $height, $hotx, $hoty, $data ) =
163             unpack "n N n n n n A*", $serialised ;
164            
165 1         5 my( $file, $bits ) = unpack "A$flen A$blen", $data ;
166              
167 1         4 $self->_set( '-file' => $file ) ;
168 1         7 $self->_set( '-width' => $width ) ;
169 1         6 $self->_set( '-height' => $height ) ;
170 1 50       8 $self->_set( '-hotx' => $hotx > $width ? $UNSET : $hotx ) ;
171 1 50       7 $self->_set( '-hoty' => $hoty > $height ? $UNSET : $hoty ) ;
172 1         6 $self->_set( '-bits' => $bits ) ;
173              
174 1         5 $self ;
175             }
176              
177              
178             sub serialise { # Object method
179 1     1 1 2 my $self = shift ;
180             # my $class = ref( $self ) || $self ;
181              
182 1         5 my( $file, $bits ) = $self->get( -file, -bits ) ;
183 1         2 my $flen = length( $file ) ;
184 1         2 my $blen = length( $bits ) ;
185              
186 1         5 pack "n N n n n n A$flen A$blen",
187             $flen, $blen,
188             $self->get( -width ), $self->get( -height ),
189             $self->get( -hotx ), $self->get( -hoty ),
190             $file, $bits ;
191             }
192              
193              
194             sub get { # Object method (and class method for class attributes)
195 105     105 1 120 my $self = shift ;
196 105   66     214 my $class = ref( $self ) || $self ;
197            
198 105         106 my @result ;
199              
200 105         211 while( @_ ) {
201 111         137 my $field = shift ;
202              
203 111 100       212 if( $field =~ /^-(?:un)?set(?:hot)?ch$/o ) {
204 7         14 push @result, $class->_class_get( $field ) ;
205             }
206             else {
207 104         251 push @result, $self->_get( $field ) ;
208             }
209             }
210              
211 105 100       743 wantarray ? @result : shift @result ;
212             }
213              
214              
215             sub set { # Object method (and class method for class attributes)
216 4     4 1 8 my $self = shift ;
217 4   33     10 my $class = ref( $self ) || $self ;
218            
219 4         11 while( @_ ) {
220 4         5 my $field = shift ;
221 4         6 my $val = shift ;
222              
223 4 50       9 carp "set() -field has no value" unless defined $val ;
224 4 50 33     36 carp "set() $field is read-only"
      33        
225             if $field eq '-bits' or $field eq '-width' or $field eq '-height' ;
226 4 50 33     17 carp "set() -hotx `$val' is out of range"
      66        
227             if $field eq '-hotx' and ( $val < $UNSET or $val >= $self->get( '-width' ) ) ;
228 4 50 33     14 carp "set() -hoty `$val' is out of range"
      66        
229             if $field eq '-hoty' and ( $val < $UNSET or $val >= $self->get( '-height' ) ) ;
230              
231 4 50       10 if( $field =~ /^-(?:un)?set(?:hot)?ch$/o ) {
232 0         0 $class->_class_set( $field, $val ) ;
233             }
234             else {
235 4         11 $self->_set( $field, $val ) ;
236             }
237             }
238             }
239              
240              
241             sub xybit { # Object method
242 60     60 1 90 my $self = shift ;
243             # my $class = ref( $self ) || $self ;
244              
245 60         74 my( $x, $y, $val ) = @_ ;
246              
247             # No range checking
248 60         648 my $offset = ( $y * $self->get( '-width' ) ) + $x ;
249              
250 60 100       97 if( defined $val ) {
251 30         77 CORE::vec( $self->{'-bits'}, $offset, 1 ) = $val ;
252             }
253             else {
254 30         87 CORE::vec( $self->{'-bits'}, $offset, 1 ) ;
255             }
256             }
257              
258              
259             sub xy { # Object method
260 0     0 1 0 my $self = shift ;
261             # my $class = ref( $self ) || $self ;
262              
263 0         0 my( $x, $y, $val ) = @_ ;
264              
265             # No range checking
266 0         0 my $offset = ( $y * $self->get( '-width' ) ) + $x ;
267              
268 0 0       0 if( defined $val ) {
269 0 0 0     0 $val = 1 if ( $val =~ /^\d+$/ and $val >= 1 ) or
      0        
      0        
      0        
270             ( lc $val eq 'black' ) or
271             ( $val =~ /^#(\d+)$/ and hex $1 ) ;
272 0         0 CORE::vec( $self->{'-bits'}, $offset, 1 ) = $val ;
273             }
274             else {
275 0 0       0 CORE::vec( $self->{'-bits'}, $offset, 1 ) ? 'black' : 'white' ;
276             }
277             }
278              
279              
280             sub vec { # Object method
281 0     0 1 0 my $self = shift ;
282             # my $class = ref( $self ) || $self ;
283              
284 0         0 my( $offset, $val ) = @_ ;
285              
286             # No range checking
287 0 0       0 if( defined $val ) {
288 0         0 CORE::vec( $self->{'-bits'}, $offset, 1 ) = $val ;
289             }
290             else {
291 0         0 CORE::vec( $self->{'-bits'}, $offset, 1 ) ;
292             }
293             }
294              
295              
296             sub is_equal { # Object method
297 1     1 1 462 my $self = shift ;
298 1   33     7 my $class = ref( $self ) || $self ;
299 1         2 my $obj = shift ;
300              
301 1 50 33     11 croak "is_equal() can only compare $class objects"
302             unless ref $obj and $obj->isa( __PACKAGE__ ) ;
303              
304             # We ignore -file, -hotx and -hoty when we consider equality.
305 1 50 33     4 return 0 if $self->get( '-width' ) != $obj->get( '-width' ) or
      33        
306             $self->get( '-height' ) != $obj->get( '-height' ) or
307             $self->get( '-bits' ) ne $obj->get( '-bits' ) ;
308              
309 1         4 1 ;
310             }
311              
312              
313             sub as_string { # Object method
314 0     0 1 0 my $self = shift ;
315             # my $class = ref( $self ) || $self ;
316              
317 0   0     0 my $hotch = shift || 0 ;
318              
319 0         0 my( $setch, $unsetch,
320             $sethotch, $unsethotch,
321             $hotx, $hoty,
322             $bits,
323             $width, $height ) =
324             $self->get(
325             '-setch', '-unsetch',
326             '-sethotch', '-unsethotch',
327             '-hotx', '-hoty',
328             '-bits',
329             '-width', '-height' ) ;
330              
331 0         0 my $bitindex = 0 ;
332 0         0 my $string = '' ;
333              
334 0         0 for( my $y = 0 ; $y < $height ; $y++ ) {
335 0         0 for( my $x = 0 ; $x < $width ; $x++ ) {
336 0 0 0     0 if( $hotch and $x == $hotx and $y == $hoty ) {
      0        
337 0 0       0 $string .= CORE::vec( $bits, $bitindex, 1 ) ?
338             $sethotch : $unsethotch ;
339             }
340             else {
341 0 0       0 $string .= CORE::vec( $bits, $bitindex, 1 ) ?
342             $setch : $unsetch ;
343             }
344 0         0 $bitindex++ ;
345             }
346 0         0 $string .= "\n" ;
347             }
348              
349 0         0 $string ;
350             }
351              
352              
353             sub as_binstring { # Object method
354 3     3 1 1330 my $self = shift ;
355             # my $class = ref( $self ) || $self ;
356              
357 3         8 unpack "b*", $self->get( '-bits' ) ;
358             }
359              
360              
361             # The algorithm is based on the one used in Thomas Boutell's GD library.
362             sub load { # Object method
363 1     1 1 2 my $self = shift ;
364             # my $class = ref( $self ) || $self ;
365              
366 1   33     8 my $file = shift() || $self->get( '-file' ) ;
367              
368 1 50       13 croak "load() no file specified" unless $file ;
369              
370 1         3 $self->set( '-file', $file ) ;
371              
372 1         7 my( @val, $width, $height, $hotx, $hoty ) ;
373 1         2 local $_ ;
374 1         4 my $fh = Symbol::gensym ;
375              
376 1 50       17 if( not ref $file ) {
    0          
377 1 50       33 open $fh, $file or croak "load() failed to open `$file': $!" ;
378             }
379             elsif( ref($file) eq 'SCALAR' ) {
380 0         0 require IO::String;
381 0         0 $fh = IO::String->new( $$file );
382             }
383             else {
384 0 0       0 seek($file, 0, 0) or croak "load() can't rewind handle for `$file': $!";
385 0         0 $fh = $file;
386             }
387              
388 1         27 while( <$fh> ) {
389 4 100       18 $width = $1, next if /#define.*width\s+(\d+)/o ;
390 3 100       12 $height = $1, next if /#define.*height\s+(\d+)/o ;
391 2 50       10 $hotx = $1, next if /#define.*_x_hot\s+(\d+)/o ;
392 2 50       6 $hoty = $1, next if /#define.*_y_hot\s+(\d+)/o ;
393 2         12 push @val, map { hex } /0[xX]([A-Fa-f\d][A-Fa-f\d]?)/g ;
  6         16  
394             }
395 1 50 33     12 croak "load() failed to find dimension(s) in `$file'"
396             unless defined $width and defined $height ;
397              
398 1 50       9 close $fh or croak "load() failed to close `$file': $!" ;
399              
400 1         4 $self->_set( '-width', $width ) ;
401 1         8 $self->_set( '-height', $height ) ;
402 1 50       7 $self->set( '-hotx', defined $hotx ? $hotx : $UNSET ) ;
403 1 50       9 $self->set( '-hoty', defined $hoty ? $hoty : $UNSET ) ;
404              
405 1         7 my( $x, $y ) = ( 0, 0 ) ;
406 1         2 my $bitindex = 0 ;
407 1         1 my $bits = '' ;
408             BYTE:
409 1         3 for( my $i = 0 ; ; $i++ ) {
410             BIT:
411 6         12 for( my $bit = 1 ; $bit <= 128 ; $bit <<= 1 ) {
412 30 100       64 CORE::vec( $bits, $bitindex++, 1 ) = ( $val[$i] & $bit ) ? 1 : 0 ;
413 30         32 $x++ ;
414 30 100       72 if( $x == $width ) {
415 6         7 $x = 0 ;
416 6         5 $y++ ;
417 6 100       12 last BYTE if $y == $height ;
418 5         8 last BIT ;
419             }
420             }
421             }
422              
423 1         3 $self->_set( '-bits', $bits ) ;
424             }
425              
426              
427             # The algorithm is based on the X Consortium's bmtoa program.
428             sub save { # Object method
429 1     1 1 2 my $self = shift ;
430             # my $class = ref( $self ) || $self ;
431              
432 1   33     5 my $file = shift() || $self->get( '-file' ) ;
433              
434 1 50       3 croak "save() no file specified" unless $file ;
435              
436 1         4 $self->set( '-file', $file ) ;
437              
438 1         10 my( $width, $height, $hotx, $hoty ) =
439             $self->get( '-width', '-height', '-hotx', '-hoty' ) ;
440              
441 1         3 my $MASK1 = $MASK + 1 ;
442 1         2 my $ROWSn1 = $ROWS - 1 ;
443              
444 1         6 my $fh = Symbol::gensym ;
445 1 50       90 open $fh, ">$file" or croak "save() failed to open `$file': $!" ;
446              
447 1         4 $file =~ s,^.*/,,o ;
448 1         5 $file =~ s/\.xbm$//o ;
449 1         5 $file =~ tr/[-_A-Za-z0-9]/_/c ;
450            
451 1         18 print $fh "#define ${file}_width $width\n#define ${file}_height $height\n" ;
452 1 50 33     10 print $fh "#define ${file}_x_hot $hotx\n#define ${file}_y_hot $hoty\n"
453             if $hotx > $UNSET and $hoty > $UNSET ;
454 1         5 print $fh "static unsigned char ${file}_bits[] = {\n" ;
455              
456 1         2 my $padded = ( $width & $MASK ) != 0 ;
457 1         2 my @char ;
458 1         2 my $char = 0 ;
459 1         5 for( my $y = 0 ; $y < $height ; $y++ ) {
460 6         13 for( my $x = 0 ; $x < $width ; $x++ ) {
461 30         37 my $mask = $x & $MASK ;
462 30 100       60 $char[$char] = 0 unless defined $char[$char] ;
463 30 100       60 $char[$char] |= $MASK[$mask] if $self->xybit( $x, $y ) ;
464 30 50       101 $char++ if $mask == $MASK ;
465             }
466 6 50       20 $char++ if $padded ;
467             }
468              
469 1         2 my $i = 0 ;
470 1         3 my $bytes_per_char = ( $width + $MASK ) / $MASK1 ;
471 1         11 foreach $char ( @char ) {
472 6         16 printf $fh " 0x%02x", $char ;
473 6 100       14 print $fh "," unless $i == $#char ;
474 6 50       10 print $fh "\n" if $i % $ROWS == $ROWSn1 ;
475 6         8 $i++ ;
476             }
477 1         2 print $fh " } ;\n";
478              
479 1 50       63 close $fh or croak "save() failed to close `$file': $!" ;
480             }
481              
482              
483             1 ;
484              
485              
486             __END__