3 use Test::More tests => 191;
5 use Imager::Test qw(test_image_raw is_image);
6 init_log("testout/t107bmp.log",1);
11 # if you change this make sure you generate new compressed versions
12 my $green=i_color_new(0,255,0,255);
13 my $blue=i_color_new(0,0,255,255);
14 my $red=i_color_new(255,0,0,255);
16 my $img = test_image_raw();
18 Imager::i_tags_add($img, 'i_xres', 0, '300', 0);
19 Imager::i_tags_add($img, 'i_yres', 0, undef, 300);
20 write_test($img, "testout/t107_24bit.bmp");
21 # 'webmap' is noticably faster than the default
22 my $im8 = Imager::i_img_to_pal($img, { make_colors=>'webmap',
23 translate=>'errdiff'});
24 write_test($im8, "testout/t107_8bit.bmp");
25 # use a fixed palette so we get reproducible results for the compressed
27 my @pal16 = map { NC($_) }
28 qw(605844 966600 0148b2 00f800 bf0a33 5e009e
29 2ead1b 0000f8 004b01 fd0000 0e1695 000002);
30 my $im4 = Imager::i_img_to_pal($img, { colors=>\@pal16, make_colors=>'none' });
31 write_test($im4, "testout/t107_4bit.bmp");
32 my $im1 = Imager::i_img_to_pal($img, { colors=>[ NC(0, 0, 0), NC(176, 160, 144) ],
33 make_colors=>'none', translate=>'errdiff' });
34 write_test($im1, "testout/t107_1bit.bmp");
39 read_test("testout/t107_24bit.bmp", $img,
40 bmp_compression=>0, bmp_bit_count => 24);
41 read_test("testout/t107_8bit.bmp", $im8,
42 bmp_compression=>0, bmp_bit_count => 8);
43 read_test("testout/t107_4bit.bmp", $im4,
44 bmp_compression=>0, bmp_bit_count => 4);
45 read_test("testout/t107_1bit.bmp", $im1, bmp_compression=>0,
47 # the following might have slight differences
48 $base_diff = i_img_diff($img, $im8) * 2;
49 print "# base difference $base_diff\n";
50 read_test("testimg/comp4.bmp", $im4,
51 bmp_compression=>$bi_rle4, bmp_bit_count => 4);
52 read_test("testimg/comp8.bmp", $im8,
53 bmp_compression=>$bi_rle8, bmp_bit_count => 8);
55 my $imoo = Imager->new;
57 ok($imoo->read(file=>'testout/t107_24bit.bmp'), "read via OO")
58 or print "# ",$imoo->errstr,"\n";
60 ok($imoo->write(file=>'testout/t107_oo.bmp'), "write via OO")
61 or print "# ",$imoo->errstr,"\n";
63 # various invalid format tests
64 # we have so many different test images to try to detect all the possible
65 # failure paths in the code, adding these did detect real problems
66 print "# catch various types of invalid bmp files\n";
69 # entries in each array ref are:
70 # - basename of an invalid BMP file
71 # - error message that should be produced
72 # - description of what is being tested
73 # - possible flag to indicate testing only on 32-bit machines
74 [ 'badplanes.bmp', 'not a BMP file', "invalid planes value" ],
75 [ 'badbits.bmp', 'unknown bit count for BMP file (5)',
76 'should fail to read invalid bits' ],
79 [ 'badused1.bmp', 'out of range colors used (3)',
80 'out of range palette size (1-bit)' ],
81 [ 'badcomp1.bmp', 'unknown 1-bit BMP compression (1)',
82 'invalid compression value (1-bit)' ],
83 [ 'bad1wid0.bmp', 'file size limit - image width of 0 is not positive',
86 'file size limit - integer overflow calculating storage',
87 'overflow integers on 32-bit machines (1-bit)', '32bitonly' ],
88 [ 'short1.bmp', 'failed reading 1-bit bmp data',
92 [ 'badused4a.bmp', 'out of range colors used (272)',
93 'should fail to read invalid pal size (272) (4-bit)' ],
94 [ 'badused4b.bmp', 'out of range colors used (17)',
95 'should fail to read invalid pal size (17) (4-bit)' ],
96 [ 'badcomp4.bmp', 'unknown 4-bit BMP compression (1)',
97 'invalid compression value (4-bit)' ],
98 [ 'short4.bmp', 'failed reading 4-bit bmp data',
99 'short uncompressed 4-bit' ],
100 [ 'short4rle.bmp', 'missing data during decompression',
101 'short compressed 4-bit' ],
102 [ 'bad4wid0.bmp', 'file size limit - image width of 0 is not positive',
104 [ 'bad4widbig.bmp', 'file size limit - image width of -2147483628 is not positive',
105 'width big (4-bit)' ],
106 [ 'bad4oflow.bmp', 'file size limit - integer overflow calculating storage',
107 'overflow integers on 32-bit machines (4-bit)', '32bitonly' ],
110 [ 'bad8useda.bmp', 'out of range colors used (257)',
111 'should fail to read invalid pal size (8-bit)' ],
112 [ 'bad8comp.bmp', 'unknown 8-bit BMP compression (2)',
113 'invalid compression value (8-bit)' ],
114 [ 'short8.bmp', 'failed reading 8-bit bmp data',
115 'short uncompressed 8-bit' ],
116 [ 'short8rle.bmp', 'missing data during decompression',
117 'short compressed 8-bit' ],
118 [ 'bad8wid0.bmp', 'file size limit - image width of 0 is not positive',
120 [ 'bad8oflow.bmp', 'file size limit - integer overflow calculating storage',
121 'overflow integers on 32-bit machines (8-bit)', '32bitonly' ],
124 [ 'short24.bmp', 'failed reading image data',
126 [ 'bad24wid0.bmp', 'file size limit - image width of 0 is not positive',
127 'width 0 (24-bit)' ],
128 [ 'bad24oflow.bmp', 'file size limit - integer overflow calculating storage',
129 'overflow integers on 32-bit machines (24-bit)', '32bitonly' ],
130 [ 'bad24comp.bmp', 'unknown 24-bit BMP compression (4)',
131 'bad compression (24-bit)' ],
134 my $intsize = $Config{intsize};
135 for my $test (@tests) {
136 my ($file, $error, $comment, $bit32only) = @$test;
139 skip("only tested on 32-bit machines", 2)
140 if $bit32only && $intsize != 4;
141 ok(!$imoo->read(file=>"testimg/$file"), $comment);
142 is($imoo->errstr, $error, "check error message");
146 # previously we didn't seek to the offbits position before reading
147 # the image data, check we handle it correctly
148 # in each case the first is an original image with a given number of
149 # bits and the second is the same file with data inserted before the
150 # image bits and the offset modified to suit
153 [ 'winrgb2.bmp', 'winrgb2off.bmp', 1 ],
154 [ 'winrgb4.bmp', 'winrgb4off.bmp', 4 ],
155 [ 'winrgb8.bmp', 'winrgb8off.bmp', 8 ],
156 [ 'winrgb24.bmp', 'winrgb24off.bmp', 24 ],
159 for my $comp (@comp) {
160 my ($base_file, $off_file, $bits) = @$comp;
162 my $base_im = Imager->new;
164 ok($base_im->read(file=>"testimg/$base_file"),
166 or print "# ",$base_im->errstr,"\n";
167 my $off_im = Imager->new;
169 ok($off_im->read(file=>"testimg/$off_file"),
171 or print "# ",$off_im->errstr,"\n";
174 skip("missed one file", 1)
175 unless $got_base && $got_off;
176 is(i_img_diff($base_im->{IMG}, $off_im->{IMG}), 0,
177 "compare base and offset image ($bits bits)");
181 { # check file limits are checked
182 my $limit_file = "testout/t107_24bit.bmp";
183 ok(Imager->set_file_limits(reset=>1, width=>149), "set width limit 149");
184 my $im = Imager->new;
185 ok(!$im->read(file=>$limit_file),
186 "should fail read due to size limits");
187 print "# ",$im->errstr,"\n";
188 like($im->errstr, qr/image width/, "check message");
190 ok(Imager->set_file_limits(reset=>1, height=>149), "set height limit 149");
191 ok(!$im->read(file=>$limit_file),
192 "should fail read due to size limits");
193 print "# ",$im->errstr,"\n";
194 like($im->errstr, qr/image height/, "check message");
196 ok(Imager->set_file_limits(reset=>1, width=>150), "set width limit 150");
197 ok($im->read(file=>$limit_file),
198 "should succeed - just inside width limit");
199 ok(Imager->set_file_limits(reset=>1, height=>150), "set height limit 150");
200 ok($im->read(file=>$limit_file),
201 "should succeed - just inside height limit");
203 # 150 x 150 x 3 channel image uses 67500 bytes
204 ok(Imager->set_file_limits(reset=>1, bytes=>67499),
205 "set bytes limit 67499");
206 ok(!$im->read(file=>$limit_file),
207 "should fail - too many bytes");
208 print "# ",$im->errstr,"\n";
209 like($im->errstr, qr/storage size/, "check error message");
210 ok(Imager->set_file_limits(reset=>1, bytes=>67500),
211 "set bytes limit 67500");
212 ok($im->read(file=>$limit_file),
213 "should succeed - just inside bytes limit");
214 Imager->set_file_limits(reset=>1);
217 { # various short read failure tests, each entry has:
218 # source filename, size, expected error
219 # these have been selected based on code coverage, to check each
220 # failure path is checked, where practical
224 "file truncated inside header",
226 20, "file too short to be a BMP file"
229 "1-bit, truncated inside palette",
231 56, "reading BMP palette"
234 "1-bit, truncated in offset region",
235 "winrgb2off.bmp", 64, "failed skipping to image data offset"
238 "1-bit, truncated in image data",
239 "winrgb2.bmp", 96, "failed reading 1-bit bmp data"
242 "4-bit, truncated inside palette",
244 56, "reading BMP palette"
247 "4-bit, truncated in offset region",
248 "winrgb4off.bmp", 120, "failed skipping to image data offset",
251 "4-bit, truncate in image data",
252 "winrgb4.bmp", 120, "failed reading 4-bit bmp data"
255 "4-bit RLE, truncate in uncompressed data",
256 "comp4.bmp", 0x229, "missing data during decompression"
259 "8-bit, truncated in palette",
260 "winrgb8.bmp", 1060, "reading BMP palette"
263 "8-bit, truncated in offset region",
264 "winrgb8off.bmp", 1080, "failed skipping to image data offset"
267 "8-bit, truncated in image data",
268 "winrgb8.bmp", 1080, "failed reading 8-bit bmp data"
271 "8-bit RLE, truncate in uncompressed data",
272 "comp8.bmp", 0x68C, "missing data during decompression"
275 "24-bit, truncate in offset region",
276 "winrgb24off.bmp", 56, "failed skipping to image data offset",
279 "24-bit, truncate in image data",
280 "winrgb24.bmp", 100, "failed reading image data",
285 for my $test (@tests) {
286 my ($desc, $srcfile, $size, $error) = @$test;
287 my $im = Imager->new;
288 open IMDATA, "< testimg/$srcfile"
289 or die "$test_index - $desc: Cannot open testimg/$srcfile: $!";
292 read(IMDATA, $data, $size) == $size
293 or die "$test_index - $desc: Could not read $size data from $srcfile";
295 ok(!$im->read(data => $data, type =>'bmp'),
296 "$test_index - $desc: Should fail to read");
297 is($im->errstr, $error, "$test_index - $desc: check message");
302 { # various short read success tests, each entry has:
303 # source filename, size, expected tags
304 print "# allow_incomplete tests\n";
311 bmp_compression_name => 'BI_RGB',
312 bmp_compression => 0,
313 bmp_used_colors => 2,
321 bmp_compression_name => 'BI_RGB',
322 bmp_compression => 0,
323 bmp_used_colors => 16,
328 "4-bit RLE - uncompressed seq",
331 bmp_compression_name => 'BI_RLE4',
332 bmp_compression => 2,
333 bmp_used_colors => 16,
338 "4-bit RLE - start seq",
341 bmp_compression_name => 'BI_RLE4',
342 bmp_compression => 2,
343 bmp_used_colors => 16,
351 bmp_compression_name => 'BI_RGB',
352 bmp_compression => 0,
353 bmp_used_colors => 256,
358 "8-bit RLE - uncompressed seq",
361 bmp_compression_name => 'BI_RLE8',
362 bmp_compression => 1,
363 bmp_used_colors => 256,
368 "8-bit RLE - initial seq",
371 bmp_compression_name => 'BI_RLE8',
372 bmp_compression => 1,
373 bmp_used_colors => 256,
381 bmp_compression_name => 'BI_RGB',
382 bmp_compression => 0,
383 bmp_used_colors => 0,
390 for my $test (@tests) {
391 my ($desc, $srcfile, $size, $tags) = @$test;
392 my $im = Imager->new;
393 open IMDATA, "< testimg/$srcfile"
394 or die "$test_index - $desc: Cannot open testimg/$srcfile: $!";
397 read(IMDATA, $data, $size) == $size
398 or die "$test_index - $desc: Could not read $size data from $srcfile";
400 ok($im->read(data => $data, type =>'bmp', allow_incomplete => 1),
401 "$test_index - $desc: Should read successfully");
402 # check standard tags are set
403 is($im->tags(name => 'i_format'), 'bmp',
404 "$test_index - $desc: i_format set");
405 is($im->tags(name => 'i_incomplete'), 1,
406 "$test_index - $desc: i_incomplete set");
408 for my $key (keys %$tags) {
409 $check_tags{$key} = $im->tags(name => $key);
411 is_deeply(\%check_tags, $tags, "$test_index - $desc: check tags");
416 { # check handling of reading images with negative height
418 # source file, description
419 print "# check handling of negative height values\n";
422 [ "winrgb2.bmp", "1-bit, uncompressed" ],
423 [ "winrgb4.bmp", "4-bit, uncompressed" ],
424 [ "winrgb8.bmp", "8-bit, uncompressed" ],
425 [ "winrgb24.bmp", "24-bit, uncompressed" ],
426 [ "comp4.bmp", "4-bit, RLE" ],
427 [ "comp8.bmp", "8-bit, RLE" ],
430 for my $test (@tests) {
431 my ($file, $desc) = @$test;
432 open IMDATA, "< testimg/$file"
433 or die "$test_index - Cannot open $file: $!";
435 my $data = do { local $/; <IMDATA> };
437 my $im_orig = Imager->new;
438 $im_orig->read(data => $data)
439 or die "Cannot load original $file: ", $im_orig->errstr;
441 # now negate the height
442 my $orig_height = unpack("V", substr($data, 0x16, 4));
443 my $neg_height = 0xFFFFFFFF & ~($orig_height - 1);
444 substr($data, 0x16, 4) = pack("V", $neg_height);
446 # and read the modified image
447 my $im = Imager->new;
448 ok($im->read(data => $data),
449 "$test_index - $desc: read negated height image")
450 or print "# ", $im->errstr, "\n";
452 # flip orig to match what we should get
453 $im_orig->flip(dir => 'v');
456 is_image($im, $im_orig, "$test_index - $desc: check image");
462 { print "# patched data read failure tests\n";
463 # like the "various invalid format" tests, these generate fail
464 # images from other images included with Imager without providing a
465 # full bmp source, saving on dist size and focusing on the changes needed
466 # to cause the failure
467 # each entry is: source file, patches, expected error, description
471 # low image data offsets
474 { 10 => "3d 00 00 00" },
475 "image data offset too small (61)",
476 "1-bit, small image offset"
480 { 10 => "75 00 00 00" },
481 "image data offset too small (117)",
482 "4-bit, small image offset"
486 { 10 => "35 04 00 00" },
487 "image data offset too small (1077)",
488 "8-bit, small image offset"
492 { 10 => "35 00 00 00" },
493 "image data offset too small (53)",
494 "24-bit, small image offset"
498 for my $test (@tests) {
499 my ($filename, $patches, $error, $desc) = @$test;
501 my $data = load_patched_file("testimg/$filename", $patches);
502 my $im = Imager->new;
503 ok(!$im->read(data => $data, type=>'bmp'),
504 "$test_index - $desc:should fail to read");
505 is($im->errstr, $error, "$test_index - $desc:check message");
510 { # various write failure tests
512 # source, limit, expected error, description
517 "cannot write bmp header: limit reached",
518 "1-bit, writing header"
522 "cannot write bmp header: limit reached",
523 "4-bit, writing header"
527 "cannot write bmp header: limit reached",
528 "8-bit, writing header"
532 "cannot write bmp header: limit reached",
533 "24-bit, writing header"
537 "cannot write palette entry: limit reached",
538 "1-bit, writing palette"
542 "cannot write palette entry: limit reached",
543 "4-bit, writing palette"
547 "cannot write palette entry: limit reached",
548 "8-bit, writing palette"
552 "writing 1 bit/pixel packed data: limit reached",
553 "1-bit, writing image data"
557 "writing 4 bit/pixel packed data: limit reached",
558 "4-bit, writing image data"
561 "winrgb8.bmp", 0x440,
562 "writing 8 bit/pixel packed data: limit reached",
563 "8-bit, writing image data"
566 "winrgb24.bmp", 0x39,
567 "writing image data: limit reached",
568 "24-bit, writing image data"
571 print "# write failure tests\n";
573 for my $test (@tests) {
574 my ($file, $limit, $error, $desc) = @$test;
576 my $im = Imager->new;
577 $im->read(file => "testimg/$file")
578 or die "Cannot read $file: ", $im->errstr;
580 ok(!$im->write(type => 'bmp', callback => limited_write($limit),
582 "$test_index - $desc: write should fail");
583 is($im->errstr, $error, "$test_index - $desc: check error message");
590 my ($im, $filename) = @_;
593 if (open FH, "> $filename") {
595 my $IO = Imager::io_new_fd(fileno(FH));
596 unless (ok(Imager::i_writebmp_wiol($im, $IO), $filename)) {
597 print "# ",Imager->_error_as_msg(),"\n";
603 fail("could not open $filename: $!");
608 my ($filename, $im, %tags) = @_;
611 print "# read_test: $filename\n";
613 $tags{i_format} = "bmp";
615 if (open FH, "< $filename") {
617 my $IO = Imager::io_new_fd(fileno(FH));
618 my $im_read = Imager::i_readbmp_wiol($IO);
620 my $diff = i_img_diff($im, $im_read);
621 if ($diff > $base_diff) {
622 fail("image mismatch reading $filename");
626 for my $tag (keys %tags) {
627 if (my $index = Imager::i_tags_find($im_read, $tag, 0)) {
628 my ($name, $value) = Imager::i_tags_get($im_read, $index);
629 my $exp_value = $tags{$tag};
630 print "# tag $name = '$value' - expect '$exp_value'\n";
631 if ($exp_value =~ /\d/) {
632 if ($value != $tags{$tag}) {
633 print "# tag $tag value mismatch $tags{$tag} != $value\n";
638 if ($value ne $tags{$tag}) {
639 print "# tag $tag value mismatch $tags{$tag} != $value\n";
645 ok($tags_ok, "reading $filename");
646 # for my $i (0 .. Imager::i_tags_count($im_read)-1) {
647 # my ($name, $value) = Imager::i_tags_get($im_read, $i);
648 # print "# tag '$name' => '$value'\n";
653 fail("could not read $filename: ".Imager->_error_as_msg());
659 fail("could not open $filename: $!");
669 $limit -= length $data;
671 print "# write of ", length $data, " bytes successful ($limit left)\n" if $debug_writes;
675 print "# write of ", length $data, " bytes failed\n";
676 Imager::i_push_error(0, "limit reached");
682 sub load_patched_file {
683 my ($filename, $patches) = @_;
685 open IMDATA, "< $filename"
686 or die "Cannot open $filename: $!";
688 my $data = do { local $/; <IMDATA> };
689 for my $offset (keys %$patches) {
690 (my $hdata = $patches->{$offset}) =~ tr/ //d;
691 my $pdata = pack("H*", $hdata);
692 substr($data, $offset, length $pdata) = $pdata;