| 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__ |