File Coverage

blib/lib/Image/BMP.pm
Criterion Covered Total %
statement 206 367 56.1
branch 91 192 47.4
condition 35 104 33.6
subroutine 28 51 54.9
pod 13 42 30.9
total 373 756 49.3


line stmt bran cond sub pod time code
1             package Image::BMP;
2              
3             # Filename: Image/BMP.pm
4             # Author: David Ljung Madison
5             # See License: http://MarginalHacks.com/License/
6             #
7             # Description: Reads a .bmp file.
8             # Can also "draw" bmp in ascii art. Cute, eh?
9             #
10             # Limitations: See perlpod doc at bottom
11             #
12             # I couldn't find a standard spec for the format. I chose the fields using:
13             # http://www.daubnet.com/formats/BMP.html
14             #
15             # If you find a simple BMP image that this can't handle, I'd be interested
16             # in seeing it, though I can't guarantee I'll update the code to make it work..
17             #
18             # CHANGELOG
19             # ---------
20             #
21             # Version 1.26 2024/02/06
22             # -----------------------
23             # * Add 'diff' code to testing so we aren't relying on system('diff')
24             #
25             # Version 1.25 2024/02/05
26             # -----------------------
27             # * Close 'bug' requesting update from indirect object creation to new method
28             #
29             # Version 1.23 2024/02/05
30             # -----------------------
31             # * Add tests and a CHANGELOG to be super professional and fancy
32             #
33             # Version 1.22 2024/02/05
34             # -----------------------
35             # * Try to fix packaging issues
36             #
37             # Version 1.20 2024/02/03
38             # -----------------------
39             # * Try to fix packaging issues
40             #
41             # Version 1.19 2016/05/22
42             # -----------------------
43             # * Stupid filehandle bug fix for view_ascii
44             #
45             # Version 1.18 2016/05/21
46             # -----------------------
47             # * Fix for non-byte indexes, reads and writes (Thanks for the inspiration, Mike Paolucci)
48             #
49             # Version 1.17 2012/06/02
50             # -----------------------
51             # * Fix for B/W images with sizes indivisible by 8 (Thanks Jiri Holec, jr.holec volny cz)
52             #
53             # Version 1.16 2008/06/19
54             # -----------------------
55             # * Handle bitfield compression (Thanks Anatoly Savchenkov, asavchenkov alarity com)
56             #
57             # Version 1.15 2007/11/29
58             # -----------------------
59             # + Fix to avoid seeing 24b images as B&W (Thanks Christian Walde, mithaldu yahoo de)
60             #
61             # Version 1.14 2006/09/07
62             # -----------------------
63             # + Fix for border case on last byte in image (Thanks Peter Dons Tychsen, pdt gnmobile com)
64             # + Fix for ColorsUsed==0 (Thanks Marton Nemeth, Marton.Nemeth knorr-bremse com)
65             # See MSDN / Administration and Management Graphics and Multimedia / Bitmaps /
66             # About Bitmaps / Bitmap storage
67             # http://windowssdk.msdn.microsoft.com/en-us/library/ms532311.aspx
68             # and MSDN / Administration and Management Graphics and Multimedia / Bitmaps /
69             # About Bitmaps / Bitmap reference / Bitmap structures / BITMAPINFOHEADER
70             # http://windowssdk.msdn.microsoft.com/en-us/library/ms532290.aspx
71             #
72             # Version 1.13 2006/06/11
73             # -----------------------
74             # + Initial public release
75              
76 1     1   321232 use strict;
  1         2  
  1         46  
77 1     1   620 use IO::File;
  1         12082  
  1         196  
78 1     1   14 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK $LIBRARY);
  1         2  
  1         91  
79 1     1   7 use Carp;
  1         4  
  1         92  
80              
81 1     1   8 use Exporter ();
  1         2  
  1         8060  
82             @ISA = qw(Exporter);
83             @EXPORT_OK = qw(open_file open_pipe close load colormap xy xy_rgb xy_index set save view_ascii debug remember_image ignore_imagemagick_bug add_pixel file);
84              
85             $VERSION = '1.26';
86             $LIBRARY = __PACKAGE__;
87              
88             ##################################################
89             # Object stuff
90             ##################################################
91             sub new {
92 11     11 1 78786 my $class = shift;
93 11         37 my $self = bless {}, $class;
94 11         47 return $self->init(@_);
95             }
96              
97             sub init {
98 11     11 0 27 my $self = shift;
99 11         57 my %args = @_;
100              
101             # Init values
102 11         130 $self->{debug} = 0;
103             ## Pick one of the following for image->ascii conversion
104             ## Simple and good for black and white:
105             #my $ascii = ' .-xXX';
106             ## 16 colors somewhat based off of Scarecrow's ASCII Art FAQ
107             #my $ascii = ' .,;+xzmXYUCOMW%';
108             ## Scarecrow's ASCII 70 colors (but very font dependent)
109 11         27 my $ascii = ' .\'`^",:;Il!i><~+_-?][}{1)(|\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$';
110              
111 11         331 $self->{ascii_array} = [split(//,$ascii)];
112              
113              
114             # Get arg values
115 11         127 map($self->{$_}=$args{$_}, keys %args);
116              
117 11 50       75 $self->open_file() if $self->{file};
118              
119 11         68 $self;
120             }
121              
122             END {
123             # Cleanup code
124             }
125              
126             # Access to fields
127             sub _setget {
128 11     11   26 my ($field, $self, $val) = @_;
129 11 50       30 $self->{$field} = $val if defined $val;
130 11         23 $self->{$field};
131             }
132              
133 11     11 1 67 sub debug { _setget('debug',@_); }
134 0     0 1 0 sub remember_image { _setget('remember_image',@_); }
135 0     0 0 0 sub ignore_imagemagick_bug { _setget('ignore_imagemagick_bug',@_); }
136             sub add_pixel {
137 0     0 1 0 my ($self, $val) = @_;
138 0 0       0 return $self->{add_pixel} unless defined $val;
139 0 0       0 if ($val) {
140 0 0       0 return error("add_pixel must be set to a code reference ('0' to clear)")
141             unless ref $val eq 'CODE';
142 0         0 $self->{add_pixel} = $val;
143             } else {
144             delete $self->{add_pixel}
145 0         0 }
146             }
147              
148 0     0 0 0 sub file { open_file(@_); } # alias
149              
150             ##################################################
151             # Debugging and output
152             ##################################################
153             sub _debug($@) {
154 557706     557706   910478 my ($self,$lvl) = (shift,shift);
155 557706 100       1202446 return unless $lvl <= $self->{debug};
156 11         592 printf STDERR @_;
157             }
158 0     0 0 0 sub error { carp "[$LIBRARY] ERROR: ",@_; return 0; }
  0         0  
159 0     0 0 0 sub fatal { croak "[$LIBRARY] ERROR: ",@_; }
160              
161             ##################################################
162             # Reading the bitmap
163             ##################################################
164             sub open_file($$) {
165 11     11 1 28 my ($bmp,$file) = @_;
166 11   33     58 $file = $file || $bmp->{file};
167 11         43 $bmp->_debug(1,"BMP: $file\n");
168 11         82 $bmp->{fh} = IO::File->new();
169 11         800 $bmp->{file} = $file;
170              
171             # Avoid using open unless we need it. Bit kludgy. Unnecessary??
172 11 50       56 $bmp->{_pipe} = ($file =~ /\|/) ? 1 : 0;
173 11 50       25 if ($bmp->{_pipe}) {
174             open($bmp->{fh},$bmp->{file})
175 0 0       0 || fatal("Couldn't open pipe: $file");
176             } else {
177 11 50       527 sysopen($bmp->{fh},$bmp->{file},O_RDONLY)
178             || fatal("Couldn't open file: $file");
179             }
180              
181 11         63 $bmp->read_header;
182 11         40 $bmp->read_infoheader;
183 11         38 $bmp->read_index;
184              
185             # Clear the internal keys
186 11         76 foreach my $k ( keys %$bmp ) {
187 262 50 100     584 delete $bmp->{$k} if $k =~ /^_/ && $k ne '_pipe' && $k ne '_colors';
      66        
188             }
189 11         39 delete $bmp->{Image};
190             }
191              
192             sub open_pipe($$) {
193 0     0 1 0 my ($bmp,$pipe) = @_;
194             # Perl is just too easy.
195 0         0 $bmp->open_file("$pipe |");
196             }
197              
198             sub close() {
199 0     0 1 0 my ($bmp) = @_;
200 0 0 0     0 return unless $bmp && $bmp->{fh};
201 0         0 close $bmp->{fh};
202             }
203              
204             sub read_bmp_str {
205 122707     122707 0 203550 my ($bmp,$bytes) = @_;
206 122707         166686 my $str;
207 122707         986086 my $num = sysread($bmp->{fh}, $str, $bytes);
208 122707 100       319286 $bmp->{_byte}+=$num if defined $bmp->{_byte};
209 122707 50       217290 fatal("Wanted $bytes bytes, saw $num") unless $num==$bytes;
210 122707         418153 $bmp->_debug(5,"read_bmp_str($bmp->{file},$bytes) = $str\n");
211 122707         261111 $str;
212             }
213              
214             sub read_bmp {
215 122696     122696 0 201541 my ($bmp,$bytes) = @_;
216 122696         218400 my $data = read_bmp_str($bmp,$bytes);
217 122696         311345 my @data = unpack('C*',$data);
218 122696         169485 my $num=0;
219 122696         197841 foreach my $d ( reverse @data ) {
220 204407         332200 $num = $num*256 + $d;
221             }
222 122696         423881 $bmp->_debug(4,"read_bmp($bmp->{file},$bytes) = $num\n");
223 122696         284302 $num;
224             }
225              
226             sub split_byte {
227 24839     24839 0 45691 my ($byte) = @_;
228 24839         254126 split('',substr(unpack("B32",pack('N',$byte)),24,8));
229             }
230              
231             sub read_bmp_bits {
232 238602     238602 0 400024 my ($bmp,$bits) = @_;
233              
234             # Just read bytes if aligned
235 238602 100       486851 return read_bmp($bmp,$bits/8) if ($bits%8)==0;
236              
237             # Otherwise pull needed bits (save leftover for next read_bmp_bits)
238 198708 100       434624 $bmp->{_extra_bits} = [] unless $bmp->{_extra_bits};
239 198708         265824 my @bits;
240 198708         414153 while ($bits-->0) {
241 198708 100       271645 unless ($#{$bmp->{_extra_bits}}>=0) {
  198708         458944  
242 24839         43100 push(@{$bmp->{_extra_bits}}, split_byte(read_bmp($bmp,1)));
  24839         58160  
243             }
244 198708         308029 push(@bits, shift(@{$bmp->{_extra_bits}}));
  198708         509829  
245             }
246 198708         473633 @bits;
247             }
248              
249             # Read bmp to pad out to some chunksize (or 4 bytes)
250             sub pad_bmp {
251 921     921 0 2195 my ($bmp, $chunk) = @_;
252 921   50     3148 $chunk = $chunk || 4;
253 921         2662 my $pad = $chunk - $bmp->{_byte}%$chunk;
254 921 100       2315 $pad=0 if $pad==$chunk;
255 921 100       2682 $pad = $bmp->{_size}-$bmp->{_byte}-1 if ($bmp->{_byte}+$pad>=$bmp->{_size});
256             # Use read_bmp_bits in case we have _extra_bits to read
257 921 100       3214 read_bmp_bits($bmp,$pad*8) if $pad>0;
258             }
259              
260             # Writing files
261             sub write_file($$) {
262 0     0 0 0 my ($bmp,$wfile) = @_;
263 0   0     0 $wfile = $wfile || $bmp->{wfile};
264 0 0       0 return unless $wfile;
265 0         0 $bmp->{wfile} = $wfile;
266 0         0 $bmp->{wfh} = IO::File->new();
267 0 0       0 sysopen($bmp->{wfh},$bmp->{wfile},O_WRONLY|O_CREAT)
268             || fatal("Couldn't write file: $wfile");
269             }
270              
271             sub write_bmp_str {
272 0     0 0 0 my ($bmp,$bytes, $str) = @_;
273 0         0 my $num = syswrite($bmp->{wfh}, $str, $bytes);
274 0 0       0 fatal("Wanted to write $bytes bytes, wrote $num [$str]") unless $num==$bytes;
275 0         0 $bmp->_debug(5,"write_bmp_str($bmp->{wfile},$bytes,$str)\n");
276 0         0 $num;
277             }
278              
279             sub write_bmp {
280 0     0 0 0 my ($bmp,$bytes,$val) = @_;
281 0         0 my @data;
282 0         0 for(my $i=0; $i<$bytes; $i++) {
283 0         0 push(@data, $val&255);
284 0         0 $val >>= 8;
285             }
286 0         0 my $str = pack('C*',@data);
287 0         0 my $num = write_bmp_str($bmp,$bytes,$str);
288 0         0 $bmp->_debug(4,"write_bmp($bmp->{wfile},$val) <= $str\n");
289 0         0 $num;
290             }
291              
292             sub split_bits {
293 0     0 0 0 my ($val,$bits) = @_;
294 0 0       0 fatal("Can't handle >32b numbers") if $bits>32;
295 0 0       0 fatal("Tried fitting [$val] into $bits bits") if $val>=(1<<$bits);
296 0         0 split('',substr(unpack("B32",pack("N",$val)),32-$bits));
297             }
298              
299             sub write_bmp_bits {
300 0     0 0 0 my ($bmp,$bits,$val) = @_;
301              
302             # Just write bytes if aligned
303 0 0       0 return write_bmp($bmp,$bits/8,$val) if ($bits%8)==0;
304              
305             # Break up bits
306 0         0 push(@{$bmp->{_extra_wr_bits}},split_bits($val,$bits));
  0         0  
307 0         0 while ($#{$bmp->{_extra_wr_bits}}>=7) {
  0         0  
308 0         0 my @byte = splice(@{$bmp->{_extra_wr_bits}},0,8);
  0         0  
309 0         0 my $byte = 0;
310 0         0 map { $byte = $byte<<1 | $_ } @byte;
  0         0  
311 0         0 write_bmp($bmp,1,$byte);
312             }
313             }
314              
315             ##################################################
316             # Header
317             ##################################################
318             ### short int of 2 bytes, int of 4 bytes, and long int of 8 bytes.
319             # typedef struct {
320             # unsigned short int type; /* Magic identifier (BM) */
321             # unsigned int size; /* File size in bytes */
322             # unsigned short int reserved1, reserved2;
323             # unsigned int offset; /* Offset to image data, bytes */
324             # } HEADER;
325             sub read_header() {
326 11     11 0 44 my ($bmp) = @_;
327              
328 11         38 $bmp->{Signature} = read_bmp_str($bmp,2);
329 11         30 $bmp->{FileSize} = read_bmp($bmp,4);
330 11         25 read_bmp($bmp,2); # reserved1
331 11         24 read_bmp($bmp,2); # reserved2
332 11         22 $bmp->{DataOffset} = read_bmp($bmp,4);
333              
334 11 50       35 fatal("Not a bitmap: [$bmp->{file}]") unless $bmp->{Signature} eq "BM";
335             }
336              
337             sub write_header() {
338 0     0 0 0 my ($bmp) = @_;
339              
340 0         0 write_bmp_str($bmp,2, $bmp->{Signature});
341 0         0 my $fsize = $bmp->{DataOffset} + $bmp->{Width}*$bmp->{Height}*$bmp->{BitCount}/8;
342 0         0 write_bmp($bmp,4, $fsize);
343 0         0 write_bmp($bmp,2,0); # reserved1
344 0         0 write_bmp($bmp,2,0); # reserved2
345             # Arguably we should recalc DataOffset
346 0         0 write_bmp($bmp,4, $bmp->{DataOffset});
347              
348 0 0       0 fatal("Not a bitmap: [$bmp->{file}]") unless $bmp->{Signature} eq "BM";
349             }
350              
351             ##################################################
352             # Image info data
353             ##################################################
354             #typedef struct {
355             # unsigned int size; /* Header size in bytes */
356             # int width,height; /* Width and height of image */
357             # unsigned short int planes; /* Number of colour planes */
358             # unsigned short int bits; /* Bits per pixel */
359             # unsigned int compression; /* Compression type */
360             # unsigned int imagesize; /* Image size in bytes */
361             # int XpixelsPerM,YpixelsPerM; /* Pixels per meter */
362             # unsigned int ColorsUsed; /* Number of colours */
363             # unsigned int ColorsImportant; /* Important colours */
364             #} INFOHEADER;
365              
366             sub read_infoheader() {
367 11     11 0 24 my ($bmp) = @_;
368 11         20 $bmp->{HeaderSize} = read_bmp($bmp,4);
369 11         20 $bmp->{Width} = read_bmp($bmp,4);
370 11         22 $bmp->{Height} = abs(read_bmp($bmp,4)); # Can be negative if !BITMAPCOREHEADER (then image goes top->bottom)
371 11         25 $bmp->{Planes} = read_bmp($bmp,2);
372 11         26 $bmp->{BitCount} = read_bmp($bmp,2);
373 11         44 $bmp->{ColorBytes} = int(($bmp->{BitCount}+7)/8);
374 11         25 $bmp->{Compression} = read_bmp($bmp,4);
375             # Compression BI_RGB = 0; (no compression)
376             # Compression BI_RLE8 = 1;
377             # Compression BI_RLE4 = 2;
378             # Compression BI_BITFIELDS = 3;
379 11   50     41 my $compStr = (qw(BI_RGB BI_RLE8 BI_RLE4 BI_BITFIELDS))[$bmp->{Compression}] || '??';
380 11         25 $bmp->{ImageSize} = read_bmp($bmp,4);
381 11         20 $bmp->{XpixelsPerM} = read_bmp($bmp,4);
382 11         20 $bmp->{YpixelsPerM} = read_bmp($bmp,4);
383 11         20 $bmp->{ColorsUsed} = read_bmp($bmp,4);
384 11         19 $bmp->{ColorsImportant} = read_bmp($bmp,4);
385 11 100       36 $bmp->{ColorsUsed} = 1<<$bmp->{BitCount} if $bmp->{ColorsUsed} == 0;
386              
387 11         68 $bmp->_debug(1,"Image: $bmp->{BitCount}/$bmp->{ColorsUsed} colors. Geometry: $bmp->{Width}x$bmp->{Height} $bmp->{ImageSize} [comp: $compStr ($bmp->{Compression})]\n");
388              
389 11         50 $bmp->_debug(2,"Header Size: $bmp->{HeaderSize}B Image: $bmp->{ImageSize}B $bmp->{Width}x$bmp->{Height} $bmp->{XpixelsPerM}x$bmp->{YpixelsPerM}/meter\n");
390 11         48 $bmp->_debug(2,"Planes=$bmp->{Planes} Bitcount=$bmp->{BitCount} ColorBytes=$bmp->{ColorBytes} Important=$bmp->{ColorsImportant}\n");
391              
392             # Header formats we can't read (yet??)
393 11         55 foreach my $sh ([12,'OS21XBITMAPHEADER'], [64,'OS22XBITMAPHEADER'], [52,'BITMAPV2INFOHEADER'], [56,'BITMAPV3INFOHEADER'], [124,'BITMAPV5HEADER']) {
394 55         82 my ($s,$h) = @$sh;
395 55 50       113 fatal("Sorry, can't read bitmaps written with $h\n [$bmp->{file}]") if $bmp->{HeaderSize}==$s;
396             }
397              
398             fatal("Unknown bitmap format (hdr size $bmp->{HeaderSize}!=40): [$bmp->{file}]")
399 11 50 66     50 unless $bmp->{HeaderSize}==40 || $bmp->{HeaderSize}==108;
400              
401             # BITMAPV4HEADER has 68 more bytes to read
402 11 100       35 read_bmp($bmp,68) if $bmp->{HeaderSize}==108;
403              
404 11   33     50 $bmp->{_colors} = $bmp->{ColorsUsed} || 1<<$bmp->{BitCount};
405 11 100       26 $bmp->{_colors} = 0 if $bmp->{_colors}==(1<<24); # No truecolor map
406              
407             # Treat mask colors as color indexes (just to skip them) and reset
408             # compression to none [Thanks Anatoly Savchenkov, asavchenkov alarity com]
409             # MSDN Article: "BI_BITFIELDS"
410             # Specifies that the bitmap is not compressed and that the color table
411             # consists of three DWORD color masks that specify the red, green, and
412             # blue components, respectively, of each pixel. This is valid when used
413             # with 16- and 32-bpp bitmaps.
414             ($bmp->{_colors}, $bmp->{Compression}) = (3,0)
415 11 100       26 if $bmp->{Compression} == 3;
416              
417 11         56 my $DataOffset = 14+$bmp->{HeaderSize}+4*$bmp->{_colors};
418             error("Corrupt bitmap header? [$bmp->{file}]\n (DataOffset!=14+HeaderSize+4*Colors?)")
419 11 50       27 unless $bmp->{DataOffset} == $DataOffset;
420 11         22 $bmp->{DataOffset} = $DataOffset;
421              
422             # Do we use indexed color?
423 11         253 $bmp->{IndexedColor} = 1;
424 11 100       28 $bmp->{IndexedColor} = 0 if $bmp->{BitCount}==24; # True color
425 11 100       29 $bmp->{IndexedColor} = 0 if $bmp->{BitCount}==32; # True color
426             #$bmp->{IndexedColor} = 0 if $bmp->{BitCount}==1; # B&W -> Better to read it in case it's inverted?
427 11 50       32 $bmp->{IndexedColor} = 0 if !$bmp->{ColorsUsed};
428             }
429              
430             sub write_infoheader() {
431 0     0 0 0 my ($bmp) = @_;
432 0         0 write_bmp($bmp,4, $bmp->{HeaderSize});
433 0         0 write_bmp($bmp,4, $bmp->{Width});
434 0         0 write_bmp($bmp,4, $bmp->{Height});
435 0         0 write_bmp($bmp,2, $bmp->{Planes});
436 0         0 write_bmp($bmp,2, $bmp->{BitCount});
437 0         0 write_bmp($bmp,4, 0); # No compression on writing
438              
439             # Calc imagesize (width*height*bits + padding)
440 0         0 my $line = $bmp->{Width} * $bmp->{BitCount};
441 0 0       0 my $pad = 32-$line%32; $pad=0 if $pad==32;
  0         0  
442 0         0 my $size = ($line+$pad)*$bmp->{Height}; #*$bmp->{BitCount};
443              
444 0         0 write_bmp($bmp,4, int($size/8));
445 0         0 write_bmp($bmp,4, $bmp->{XpixelsPerM});
446 0         0 write_bmp($bmp,4, $bmp->{YpixelsPerM});
447 0         0 write_bmp($bmp,4, $bmp->{ColorsUsed});
448 0         0 write_bmp($bmp,4, $bmp->{ColorsImportant});
449             }
450              
451             sub rgb {
452 623622     623622 0 885030 my ($rgb) = @_;
453 623622 50       1009813 $rgb=0 unless defined $rgb;
454 623622         1645778 ((($rgb>>16) & 0xff),
455             (($rgb>>8 ) & 0xff),
456             (($rgb>>0 ) & 0xff));
457             }
458              
459             sub read_index() {
460 11     11 0 18 my ($bmp) = @_;
461              
462 11 100       34 unless ($bmp->{IndexedColor}) {
463             # Sometimes when ColorsUsed is 0 they still have the
464             # basic greyscale map, we need to skip past it.
465             # Still read it in case it's reversed or some such..
466 2 50       6 if ($bmp->{_pipe}) {
467 0         0 read_bmp($bmp,4*$bmp->{_colors});
468             } else {
469 2         20 sysseek($bmp->{fh},$bmp->{DataOffset},SEEK_SET);
470             }
471 2         5 return;
472             }
473              
474 9         36 for(my $i=0; $i<$bmp->{ColorsUsed}; $i++) {
475             # r,g,b
476 1288         1888 my $rgb = read_bmp($bmp,4);
477 1288         2424 $bmp->{Index}{rgb}[$i] = $rgb;
478 1288         4939 $bmp->{Index}{back}{$rgb} = $i;
479             }
480             }
481              
482             sub write_index() {
483 0     0 0 0 my ($bmp) = @_;
484              
485 0         0 unless (1 || $bmp->{IndexedColor}) {
486             # Sometimes when ColorsUsed is 0 they still have the
487             # basic greyscale map, we need to get past the DataOffset we wrote
488             # We could've recalced DataOffset above, but I'm lazy..
489             write_bmp($bmp,4*$bmp->{_colors},0);
490             return;
491             }
492              
493 0         0 for(my $i=0; $i<$bmp->{ColorsUsed}; $i++) {
494 0         0 write_bmp($bmp,4, $bmp->{Index}{rgb}[$i]);
495             }
496             }
497              
498             sub colormap {
499 312251     312251 1 508823 my ($bmp, $index) = @_;
500              
501             # B&W
502             return $index ? 0xffffff : 0x000000
503 312251 100 33     897780 if $bmp->{BitCount}==1 || (!$bmp->{ColorsUsed} && $bmp->{BitCount}!=24);
    100 66        
504              
505             # True color
506 113543 100       226325 return $index unless $bmp->{IndexedColor};
507              
508 75655         221767 $bmp->{Index}{rgb}[$index];
509             }
510              
511             sub decolormap {
512 0     0 0 0 my ($bmp, $color) = @_;
513              
514             # B&W
515 0 0 0     0 return $color ? 1 : 0 if $bmp->{BitCount}==1 || !$bmp->{ColorsUsed};
    0          
516              
517             # True color
518 0 0       0 return $color unless $bmp->{IndexedColor};
519              
520 0         0 my $index = $bmp->{Index}{back}{$color};
521 0 0       0 return $index if defined $index;
522 0         0 fatal("Color [$color] not found in orginal colormap\nCurrently the colormap is not updated with new colors\n");
523             }
524              
525             ##################################################
526             # Image
527             ##################################################
528             sub next_xy {
529 312251     312251 0 531241 my ($bmp,$x,$y,$pad) = @_;
530              
531             # Padding at end of each line
532 312251 100 100     900102 pad_bmp($bmp) if $pad && $x==$bmp->{Width}-1;
533 312251 50       681810 return (undef,undef) if $bmp->{_byte}>$bmp->{_size};
534              
535 312251 100       599289 ($x,$y) = (0, $y-1) if (++$x >= $bmp->{Width});
536 312251 100 100     851552 return (undef,undef) unless defined $y && $y>=0;
537 312237         666084 ($x,$y);
538             }
539              
540             sub error_too_big {
541 3     3 0 24 my ($bmp) = @_;
542             error("Corrupt BMP - too big.\n",
543             " (ImageMagick sometimes incorrectly places endline marker",
544             " Set option 'ignore_imagemagick_bug' to hide this message)")
545 3 50       13 unless $bmp->{ignore_imagemagick_bug}++;
546             }
547              
548             sub _add_pixel {
549 312251     312251   516647 my ($bmp,$x,$y,$color) = @_;
550 312251 100 66     921120 return error_too_big($bmp) unless defined $y && $y>=0;
551              
552 312248         709581 $bmp->_debug(3,"Pixel($x,$y) = %0.2x,%0.2x,%0.2x\n",rgb($color));
553              
554             # Save it in our 2D array
555             $bmp->{Image}[$x][$y] = $color
556 312248 50 33     1162858 if !$bmp->{add_pixel} || $bmp->{remember_image};
557              
558             # add_pixel function?
559 312248 50       626709 return unless $bmp->{add_pixel};
560             fatal("add_pixel must be a subroutine pointer [not ".(ref $bmp->{add_pixel})."]")
561 0 0       0 unless (ref $bmp->{add_pixel} eq 'CODE');
562 0         0 &{$bmp->{add_pixel}}($bmp,$x,$y,rgb($color));
  0         0  
563             }
564              
565             sub load() {
566 11     11 1 29 my ($bmp, $file) = @_;
567              
568 11 50       22 $bmp->file($file) if $file;
569 11 50       51 return error("You haven't opened a file yet") unless $bmp->{file};
570              
571 11 50       31 if ($bmp->{_image_loaded}) {
572 0 0 0     0 if ($bmp->{_pipe}) {
    0          
573 0         0 return error("You can't call load twice on a pipe.\n Use 'remember_image' option");
574             } elsif ($bmp->{_image_remembered} && !$bmp->{add_pixel}) {
575             # There's no reason to do this again, unless they want
576             # to save the image, or else call their add_pixel again.
577 0         0 return 1;
578             }
579 0         0 sysseek($bmp->{fh},$bmp->{DataOffset},SEEK_SET);
580             }
581              
582             # Compressed?
583 11 100 66     46 my $rle = ($bmp->{Compression}==1 && $bmp->{BitCount}==8) ? 1 : 0;
584             fatal("Can't handle this bitmap compression: [$bmp->{file}]\n\t(Try 'convert -compress None')")
585 11 50 66     40 if $bmp->{Compression} && !$rle;
586              
587             # We need to read bits for this - which would mean buffering and shit..
588             fatal("Can't handle non-byte indexes - sorry [$bmp->{BitCount} bits].")
589 11 50 66     61 unless $bmp->{BitCount}==1 || ($bmp->{BitCount}%8)==0;
590              
591             # Calculate size
592 11         29 my $line = $bmp->{Width} * $bmp->{BitCount};
593             # Each line is padded to 4 bytes
594 11 100       25 my $pad = 32-$line%32; $pad=0 if $pad==32;
  11         27  
595 11         30 $bmp->{_sizebits} = ($line+$pad)*$bmp->{Height}; #*$bmp->{BitCount};
596 11         33 $bmp->{_size} = $bmp->{_sizebits}/8;
597              
598 11 100       24 $bmp->{_size} = $bmp->{ImageSize} if $rle;
599 11   33     31 $bmp->{ImageSize} = $bmp->{ImageSize} || $bmp->{_size};
600              
601             error("Error - imagesize doesn't seem to be calculated properly:\n".
602             " (imagesize < width+padding * height)")
603 11 50       34 unless $bmp->{_size} == $bmp->{ImageSize};
604              
605 11         76 $bmp->_debug(1,"Reading image data - [$bmp->{Width} x $bmp->{Height} x $bmp->{BitCount}]...\n");
606              
607             # Image starts from bottom left and reads right then up
608 11         54 my ($x,$y) = (0, $bmp->{Height}-1);
609 11         21 $bmp->{_byte}=0;
610 11         35 while ($bmp->{_byte}<=$bmp->{_size}) {
611 266359 100       454844 if ($rle) {
612 28254         48719 my $n = read_bmp($bmp,1);
613 28254         47146 my $c = read_bmp($bmp,1);
614 28254 100       53668 if ($n) {
615             # Repeat next byte 'n' times
616             #TODO: Compression lvl 2 (4-bit color) needs to flip colors back and forth...
617 27928         62938 while ($n-->0) {
618 74146         124956 _add_pixel($bmp,$x,$y,colormap($bmp,$c));
619 74146         129161 ($x,$y) = next_xy($bmp,$x,$y);
620             }
621 27928 100       84683 last unless defined $x;
622             } else {
623 326 50       1101 if ($c==0) {
    0          
    0          
624             # End of line
625 326 50       1555 $x=0 if $x;
626             #($x,$y) = (0,$y-1) if $x;
627             } elsif ($c==1) {
628             # End of bitmap
629 0         0 last;
630             # Sometimes there are bytes left in _size - I don't know why...
631             # Oh - actually we should be 4byte aligned - that might be it.
632              
633             } elsif ($c==2) {
634             # Delta. Following 2 bytes are offset x,y
635             # Argh.. Not tested. I need an image that uses this encoding.
636 0         0 print STDERR "Untested delta code.. Please send me a copy of this image for testing!\n";
637 0         0 my $dx = read_bmp($bmp,1);
638 0         0 my $dy = read_bmp($bmp,1);
639 0         0 $x+=$dx;
640 0         0 $y-=$dy;
641              
642             } else {
643             # Following 'c' bytes are regular colors. Pad if 'c' is odd.
644 0         0 my $pad = $c&1;
645 0         0 while ($c-->0) {
646 0         0 my $index = read_bmp($bmp,1);
647 0         0 _add_pixel($bmp,$x,$y,colormap($bmp,$index));
648 0         0 ($x,$y) = next_xy($bmp,$x,$y);
649             }
650 0 0 0     0 error("Corrupt BMP: pad byte should be zero")
651             if ($pad && read_bmp($bmp,1))
652             }
653             }
654             } else {
655 238105         458724 my ($index) = read_bmp_bits($bmp,$bmp->{BitCount});
656 238105         445554 my $color = colormap($bmp,$index);
657 238105         536271 _add_pixel($bmp,$x,$y,$color);
658              
659 238105         439058 ($x,$y) = next_xy($bmp,$x,$y,1);
660 238105 100       687795 last unless defined $x;
661             }
662             }
663              
664 11         49 $bmp->{_image_loaded} = 1;
665 11 50 33     70 $bmp->{_image_remembered} = (!$bmp->{add_pixel} || $bmp->{remember_image}) ? 1 : 0;
666              
667             # Should finish at:
668             error("Premature end of BMP file [$x,$y]")
669 11 0 0     28 if defined $x && ($x!=$bmp->{Width}-1 || $y);
      33        
670              
671 11         55 1;
672             }
673              
674             # We can't do some things until we have the image read
675             sub needs_image {
676 11     11 0 40 my ($bmp,$do) = @_;
677              
678 11 50 33     57 return undef if !$bmp->{_image_loaded} && !$bmp->load;
679              
680             # Do we have image data?
681 11 50       44 unless ($bmp->{_image_remembered}) {
682 0         0 error("Can't $do with add_pixel functions\n (Unless you set 'remember_image')\n");
683 0         0 return undef;
684             }
685             }
686              
687             sub save() {
688 0     0 0 0 my ($bmp, $file) = @_;
689              
690 0         0 $bmp->needs_image("save images");
691              
692 0         0 $bmp->write_file($file);
693              
694 0         0 $bmp->write_header;
695 0         0 $bmp->write_infoheader;
696 0         0 $bmp->write_index;
697              
698 0         0 $bmp->_debug(1,"Writing image data...\n");
699              
700             # Each line is padded to 4 bytes
701 0         0 my $line = $bmp->{Width} * $bmp->{BitCount};
702 0 0       0 my $pad = 32-$line%32; $pad=0 if $pad==32;
  0         0  
703              
704             # Image starts from bottom left and reads right then up
705 0         0 for (my $y=$bmp->{Height}-1; $y>=0; $y--) {
706 0         0 for (my $x=0; $x<$bmp->{Width}; $x++) {
707 0         0 my $color = xy($bmp,$x,$y);
708 0         0 my $index = $bmp->decolormap($color);
709 0         0 write_bmp_bits($bmp, $bmp->{BitCount}, $index);
710             }
711             # Pad each line
712 0 0       0 write_bmp($bmp,int($pad/8),0) if $pad>0;
713             }
714 0         0 1;
715             }
716              
717             # "Darkness" is distance from white (0 to 1)
718             my $MAXDARK = sqrt(0xff*0xff*3);
719             sub darkness {
720 311374     311374 0 448011 my ($r,$g,$b) = @_;
721 311374 50       520063 ($r,$g,$b) = rgb($r) unless defined $g;
722 311374         904188 my $dark = sqrt((0xff-$r)**2+(0xff-$g)**2+(0xff-$b)**2) / $MAXDARK;
723             }
724              
725             # Get or set a given pixel, undef on error
726             sub xy_index {
727 0     0 1 0 my ($bmp,$x,$y, $index) = @_;
728              
729 0         0 $bmp->needs_image("use xy method");
730              
731 0 0 0     0 if ($x>=$bmp->{Width} || $x<0 ||
      0        
      0        
732             $y>=$bmp->{Height} || $y<0) {
733 0         0 error("xy_index($x,$y) is out of bounds [$bmp->{Width}x$bmp->{Height}]");
734 0         0 return undef;
735             }
736              
737 0 0       0 return $bmp->{Image}[$x][$y] = $bmp->colormap($index) if defined($index);
738 0   0     0 $bmp->decolormap($bmp->{Image}[$x][$y] || 0);
739             }
740              
741             sub xy {
742 0     0 1 0 my ($bmp,$x,$y, $val) = @_;
743              
744 0         0 $bmp->needs_image("use xy method");
745              
746 0 0 0     0 if ($x>=$bmp->{Width} || $x<0 ||
      0        
      0        
747             $y>=$bmp->{Height} || $y<0) {
748 0         0 error("xy($x,$y) is out of bounds [$bmp->{Width}x$bmp->{Height}]");
749 0         0 return undef;
750             }
751              
752 0 0 0     0 return $bmp->{Image}[$x][$y] || 0 unless defined $val;
753 0         0 $bmp->{Image}[$x][$y] = $val;
754             }
755              
756             sub xy_rgb {
757 0     0 1 0 my ($bmp,$x,$y, $r,$g,$b) = @_;
758              
759 0 0       0 if (defined($r)) {
760 0         0 my $color = (($r&0xff)<<16)|(($g&0xff)<<8)|(($b&0xff)<<0);
761 0         0 return $bmp->xy($x,$y,$color);
762             }
763 0         0 my $color = $bmp->xy($x,$y);
764 0 0       0 return undef unless defined $color;
765 0         0 return rgb($color);
766             }
767              
768             # Simple ascii viewer
769             sub view_ascii {
770 11     11 1 84 my ($bmp,$file) = @_;
771              
772 11         16 my $fh;
773 11 50 33     47 if (!$file || $file eq '-') {
774 0 0       0 open($fh,'>&STDOUT') || fatal("Can't dup STDOUT for view_ascii??");
775             } else {
776 11 50       1802 open($fh,'>', $file) || fatal("Couldn't open view_ascii output [$file]");
777             }
778              
779 11         66 $bmp->needs_image("use view_ascii method");
780              
781 11         63 for(my $y=0; $y<$bmp->{Height}; $y++) {
782 1249         2847 for(my $x=0; $x<$bmp->{Width}; $x++) {
783             # Go ahead. Just *try* to figure it out.
784 311374         393331 print $fh $bmp->{ascii_array}[int($#{$bmp->{ascii_array}}*darkness($bmp->{Image}[$x][$y]))];
  311374         650462  
785             }
786 1249         3778 print $fh "\n";
787             }
788              
789 11 50 33     1026 !$file || $file eq '-' || CORE::close($fh);
790             }
791              
792             # View it upside-down. More immediate gratification, due to upside-down
793             # nature of bitmaps. Useful for testing, but only works with some images.
794             sub flipped_ascii {
795 0     0 0   my ($bmp) = @_;
796 0           my $saved_pixel = $bmp->{add_pixel};
797             $bmp->{add_pixel} = sub {
798 0     0     my ($bmp,$x,$y,$r,$g,$b) = @_;
799 0           print "\n"x ($bmp->{_lasty} - $y);
800 0 0         $bmp->{_lastx}=0 unless $bmp->{_lasty} == $y;
801 0           print " "x ($bmp->{_lastx} - $x - 1);
802 0           print $bmp->{ascii_array}[int($#{$bmp->{ascii_array}}*darkness($r,$g,$b))];
  0            
803 0           ($bmp->{_lastx},$bmp->{_lasty}) = ($x,$y);
804 0           };
805 0           $bmp->load;
806 0           $bmp->{add_pixel} = $saved_pixel;
807             }
808              
809             1;
810              
811             __END__