File Coverage

blib/lib/Data/Petitcom/BMP.pm
Criterion Covered Total %
statement 134 134 100.0
branch 29 48 60.4
condition 18 37 48.6
subroutine 21 21 100.0
pod 0 9 0.0
total 202 249 81.1


line stmt bran cond sub pod time code
1             package Data::Petitcom::BMP;
2              
3 6     6   33 use strict;
  6         9  
  6         206  
4 6     6   31 use warnings;
  6         10  
  6         178  
5              
6 6     6   911 use parent qw{Exporter};
  6         346  
  6         35  
7             our @EXPORT_OK = qw{ DATA2BMP BMP2DATA Load Dump RGB555toRGB888 RGB888toRGB555 };
8              
9 6     6   1568 use bytes ();
  6         18  
  6         89  
10 6     6   30 use Carp ();
  6         28  
  6         150  
11              
12 6     6   28 use constant CHR_WIDTH => 8;
  6         15  
  6         601  
13 6     6   34 use constant CHR_SIZE => CHR_WIDTH * CHR_WIDTH;
  6         9  
  6         1242  
14              
15 6         610 use constant DEFAULT_COLORMAP => [
16             0x000000,0x383838,0xf81800,0xf858c0,0x0038f0,0x7838f8,0x00b8f8,0x905828,0xf8a000,0xf8c8a0,0x007800,0x00f018,0xf8e000,0xb8b8b8,0x000000,0xf8f8f8,
17             0x000000,0x282828,0x883028,0x985880,0x203880,0x604890,0x287088,0x584030,0x886028,0xa09080,0x104010,0x208030,0x887828,0x808080,0xf8f8f8,0x000000,
18             0xf8f8f8,0xf8f8c8,0xf8f898,0xf8f860,0xf8f830,0xf8f800,0xf8c8f8,0xf8c8c8,0xf8c898,0xf8c860,0xf8c830,0xf8c800,0xf898f8,0xf898c8,0xf89898,0xf89860,
19             0xf89830,0xf89800,0xf860f8,0xf860c8,0xf86098,0xf86060,0xf86030,0xf86000,0xf830f8,0xf830c8,0xf83098,0xf83060,0xf83030,0xf83000,0xf800f8,0xf800c8,
20             0xf80098,0xf80060,0xf80030,0xf80000,0xc8f8f8,0xc8f8c8,0xc8f898,0xc8f860,0xc8f830,0xc8f800,0xc8c8f8,0xc8c8c8,0xc8c898,0xc8c860,0xc8c830,0xc8c800,
21             0xc898f8,0xc898c8,0xc89898,0xc89860,0xc89830,0xc89800,0xc860f8,0xc860c8,0xc86098,0xc86060,0xc86030,0xc86000,0xc830f8,0xc830c8,0xc83098,0xc83060,
22             0xc83030,0xc83000,0xc800f8,0xc800c8,0xc80098,0xc80060,0xc80030,0xc80000,0x98f8f8,0x98f8c8,0x98f898,0x98f860,0x98f830,0x98f800,0x98c8f8,0x98c8c8,
23             0x98c898,0x98c860,0x98c830,0x98c800,0x9898f8,0x9898c8,0x989898,0x989860,0x989830,0x989800,0x9860f8,0x9860c8,0x986098,0x986060,0x986030,0x986000,
24             0x9830f8,0x9830c8,0x983098,0x983060,0x983030,0x983000,0x9800f8,0x9800c8,0x980098,0x980060,0x980030,0x980000,0x60f8f8,0x60f8c8,0x60f898,0x60f860,
25             0x60f830,0x60f800,0x60c8f8,0x60c8c8,0x60c898,0x60c860,0x60c830,0x60c800,0x6098f8,0x6098c8,0x609898,0x609860,0x609830,0x609800,0x6060f8,0x6060c8,
26             0x606098,0x606060,0x606030,0x606000,0x6030f8,0x6030c8,0x603098,0x603060,0x603030,0x603000,0x6000f8,0x6000c8,0x600098,0x600060,0x600030,0x600000,
27             0x30f8f8,0x30f8c8,0x30f898,0x30f860,0x30f830,0x30f800,0x30c8f8,0x30c8c8,0x30c898,0x30c860,0x30c830,0x30c800,0x3098f8,0x3098c8,0x309898,0x309860,
28             0x309830,0x309800,0x3060f8,0x3060c8,0x306098,0x306060,0x306030,0x306000,0x3030f8,0x3030c8,0x303098,0x303060,0x303030,0x303000,0x3000f8,0x3000c8,
29             0x300098,0x300060,0x300030,0x300000,0x00f8f8,0x00f8c8,0x00f898,0x00f860,0x00f830,0x00f800,0x00c8f8,0x00c8c8,0x00c898,0x00c860,0x00c830,0x00c800,
30             0x0098f8,0x0098c8,0x009898,0x009860,0x009830,0x009800,0x0060f8,0x0060c8,0x006098,0x006060,0x006030,0x006000,0x0030f8,0x0030c8,0x003098,0x003060,
31             0x003030,0x003000,0x0000f8,0x0000c8,0x000098,0x000060,0x000030,0xe8e8e8,0xd8d8d8,0xb8b8b8,0xa8a8a8,0x888888,0x707070,0x505050,0x404040,0x202020,
32 6     6   36 ];
  6         16  
33 6         12401 use constant SPRITE_SIZE => {
34             8 => [ 8, 16, 32 ],
35             16 => [ 8, 16, 32 ],
36             32 => [ 8, 16, 32, 64 ],
37             64 => [ 32, 64 ],
38 6     6   43 };
  6         13  
39              
40 27 50 33 27 0 359 sub is_valid_width { $_[0] && $_[0] <= 256 && $_[0] % CHR_WIDTH == 0 }
41 27 50 33 27 0 253 sub is_valid_height { $_[0] && $_[0] <= 192 && $_[0] % CHR_WIDTH == 0 }
42             sub is_valid_spsize {
43 14     14 0 1084 my ($width, $height) = @_;
44 14 100 66     98 return unless ($width && $height);
45 13 100       23 for ( @{ SPRITE_SIZE->{$width} } ) { return 1 if ( $height == $_ ) }
  13         63  
  23         93  
46             }
47              
48             sub _xy(&;%) {
49 10     10   918 my $code = shift;
50              
51 10         66 my %opts = @_;
52 10   50     255 my $width = delete $opts{width} || 256;
53 10   50     39 my $height = delete $opts{height} || 64;
54 10   50     207 my $sp_width = delete $opts{sp_width} || 16;
55 10   50     243 my $sp_height = delete $opts{sp_height} || 16;
56 10         24 my $vflip = delete $opts{vflip};
57 10         23 my $debug = delete $opts{debug};
58 10 50 33     92 Carp::croak "invalid sp_width: $sp_width"
59             if ( $sp_width > $width || $width % $sp_width );
60 10 50 33     70 Carp::croak "invalid sp_height: $sp_height"
61             if ( $sp_height > $height || $height % $sp_height );
62              
63 10         31 my $sp_cols = $width / $sp_width;
64 10         21 my $sp_rows = $height / $sp_height;
65 10         26 my $sp_nums = $sp_cols * $sp_rows;
66 10         19 my $sp_size = $sp_width * $sp_height;
67              
68 10         20 my $chr_cols = $sp_width / CHR_WIDTH;
69 10         21 my $chr_rows = $sp_height / CHR_WIDTH;
70 10         17 my $chr_nums = $chr_cols * $chr_rows;
71              
72 10 100       40 my $flip_y = ($vflip) ? ($height - 1) : 0;
73 10         32 for my $i ( 0 .. ( $sp_nums - 1 ) ) {
74 100         497 my $sp_x = $i % $sp_cols * $sp_width;
75 100         191 my $sp_y = int( $i / $sp_cols ) * $sp_height;
76              
77 100         220 for my $j ( 0 .. ( $chr_nums - 1 ) ) {
78 3088         13346 my $chr_x = $sp_x + ( $j % $chr_cols * CHR_WIDTH );
79 3088         4600 my $chr_y = $sp_y + ( int( $j / $chr_cols ) * CHR_WIDTH );
80              
81 3088         5907 for my $k ( 0 .. ( CHR_SIZE - 1 ) ) {
82 197632         700780 my $x = $chr_x + ( $k % CHR_WIDTH );
83 197632         319659 my $y = abs( $flip_y - ( $chr_y + ( int( $k / CHR_WIDTH ) ) ) );
84              
85 197632 50       374191 print STDERR sprintf("(% 3d, % 3d)\n", $x, $y) if ($debug);
86              
87 197632         904125 my $pixel = $code->( $x, $y, {
88             width => $width,
89             height => $height,
90             sp_width => $sp_width,
91             sp_height => $sp_height,
92             count => ( $sp_size * $i ) + ( CHR_SIZE * $j ) + $k,
93             } );
94             }
95              
96             }
97              
98             }
99             }
100              
101             sub DATA2BMP {
102 3     3 0 657 my ($data, %opts) = @_;
103 3   50     14 my $width = delete $opts{width} || 256;
104 3 50       12 Carp::croak "invalid width: $width"
105             unless ( is_valid_width($width) );
106 3   50     15 my $height = delete $opts{height} || 64;
107 3 50       10 Carp::croak "invalid height: $height"
108             unless ( is_valid_height($height) );
109 3   50     16 my $sp_width = delete $opts{sp_width} || 16;
110 3   50     14 my $sp_height = delete $opts{sp_height} || 16;
111 3 50       13 Carp::croak "invalid sprite size: $sp_width x $sp_height "
112             unless( is_valid_spsize($sp_width, $sp_height ) );
113              
114 3         5 my @pixels;
115             _xy {
116 65792     65792   92236 my ( $x, $y, $info ) = @_;
117 65792         82836 my $index = $width * $y + $x;
118 65792         210774 $pixels[$index] = bytes::substr $data, $info->{count}, 1;
119             }
120 3         33 width => $width,
121             height => $height,
122             sp_width => $sp_width,
123             sp_height => $sp_height,
124             vflip => 1,
125             debug => 0;
126              
127 65792         103807 return Dump(
128             width => $width,
129             height => $height,
130 3         343 pixels => [ map { unpack 'C', $_ } @pixels ],
131             );
132             }
133              
134             sub BMP2DATA {
135 5     5 0 677 my ($raw_bmp, %opts) = @_;
136 5   50     31 my $sp_width = delete $opts{sp_width} || 16;
137 5   50     23 my $sp_height = delete $opts{sp_height} || 16;
138 5 50       28 Carp::croak "invalid sprite size: $sp_width x $sp_height "
139             unless( is_valid_spsize($sp_width, $sp_height ) );
140              
141 5         21 my $bmp = Load($raw_bmp);
142 5         15 my $data;
143             _xy {
144 131328     131328   165883 my ( $x, $y ) = @_;
145 131328         235318 my $offset = $bmp->{width} * ( ( $bmp->{height} - 1 ) - $y ) + $x;
146 131328         1004343 $data .= pack( 'C', $bmp->{pixels}->[$offset] );
147             }
148 5         404 width => $bmp->{width},
149             height => $bmp->{height},
150             sp_width => $sp_width,
151             sp_height => $sp_height,
152             debug => 0;
153              
154 5         2537 return $data;
155             }
156              
157             sub Load {
158 19     19 0 568 my $raw_bmp = shift;
159              
160 19         118 my @file_header = unpack 'a2VvvV', bytes::substr( $raw_bmp, 0, 14 );
161 19         3483 my $type = $file_header[0];
162 19 50       78 Carp::croak "invalid type: $type"
163             if ( $type ne 'BM' );
164              
165 19         63 my @info_header = unpack 'VVVvvVVVVVV', bytes::substr( $raw_bmp, 0x0E, 40 );
166 19         242 my $width = $info_header[1];
167 19 50       62 Carp::croak "invalid width: $width"
168             unless ( is_valid_width($width) );
169 19         40 my $height = $info_header[2];
170 19 50       67 Carp::croak "invalid height: $height"
171             unless ( is_valid_height($height) );
172 19         40 my $bit = $info_header[4];
173 19 50       61 Carp::croak "invalid bit: $bit"
174             if ( $bit != 8 );
175              
176 19         68 my @colormap = unpack 'V*', bytes::substr( $raw_bmp, 0x36, 256 * 4 );
177 19         847 my @pixels = unpack 'C*', bytes::substr( $raw_bmp, 0x0436, $width * $height );
178 19 100       71363 if ( my $lack = ( $width * $height ) - @pixels ) {
179 6         707 push @pixels, 0x00 for ( 1 .. $lack );
180             }
181              
182             return +{
183 19         991 width => $width,
184             height => $height,
185             colormap => \@colormap,
186             pixels => \@pixels,
187             };
188             }
189              
190             sub Dump {
191 5 50   5 0 687 my $bmp = ( ref $_[0] eq 'HASH' ) ? shift : {@_};
192              
193 5         24 my $width = delete $bmp->{width};
194 5 50       25 Carp::croak "invalid width: $width"
195             unless ( is_valid_width($width) );
196 5         18 my $height = delete $bmp->{height};
197 5 50       21 Carp::croak "invalid height: $height"
198             unless ( is_valid_height($height) );
199 5   100     38 my $colormap = delete $bmp->{colormap} || DEFAULT_COLORMAP;
200 5 50       19 Carp::croak "invalid colormap: " . scalar @$colormap
201             if ( scalar @$colormap != 256 );
202 5         15 my $pixels = delete $bmp->{pixels};
203 5 50       24 Carp::croak "pixels mismatch: " . scalar @$pixels
204             if ( scalar @$pixels != $width * $height );
205              
206 5         12 my $size = 14 + 40 + 256 * 4 + $width * $height;
207 5         48 my $raw_bmp = pack 'a2VvvV', "BM", $size, 0, 0, 14 + 40;
208 5         34 $raw_bmp .= pack "VVVvvVVVVVV", 40, $width, $height, 1, 8, 0, 0, 0, 0, 0, 0;
209 5         57 $raw_bmp .= pack 'V*', @$colormap;
210 5         999 $raw_bmp .= pack 'C*', @$pixels;
211              
212 5         7713 return $raw_bmp;
213             }
214              
215             sub RGB888toRGB555 {
216 6 100   6 0 2609 my $rgb888 = (ref $_[0] eq 'ARRAY') ? shift : [ @_ ];
217 261         270 my @rgb555 = map {
218 6         14 my $rgb = $_;
219 261         456 my ( $r, $g, $b ) = map { $_ >> 3 } (
  783         1260  
220             ($rgb >> 16 ) & 0xFF,
221             ($rgb >> 8 ) & 0xFF,
222             $rgb & 0xFF,
223             );
224             # Unused(1), Blue(5), Green(5), Red(5)
225 261         525 ( $b << 10 ) | ( $g << 5 ) | $r;
226             } @$rgb888;
227 6         47 return \@rgb555;
228             }
229              
230             sub RGB555toRGB888 {
231 6 50   6 0 2408 my $rgb555 = (ref $_[0] eq 'ARRAY') ? shift : [ @_ ];
232 261         248 my @rgb888 = map {
233 6         15 my $rgb = $_;
234 261         368 my ($r, $g, $b) = map { $_ << 3 | $_ >> 2 } (
  783         1291  
235             $rgb & 0x1F,
236             ($rgb >> 5) & 0x1F,
237             ($rgb >> 10) & 0x1F,
238             );
239             # Reserved(8), Red(8), Green(8), Blue(8)
240 261         458 ($r << 16) | ($g << 8) | $b;
241             } @$rgb555;
242 6         44 return \@rgb888;
243             }
244              
245              
246              
247             1;