#!perl -w
use strict;
-use Test::More tests => 89;
+use Test::More tests => 213;
use Imager qw(:all);
+use Imager::Test qw(test_image_raw is_image is_color3 test_image);
init_log("testout/t107bmp.log",1);
-#BEGIN { require 't/testtools.pl'; } # BEGIN to apply prototypes
+
+my $debug_writes = 0;
my $base_diff = 0;
# if you change this make sure you generate new compressed versions
my $blue=i_color_new(0,0,255,255);
my $red=i_color_new(255,0,0,255);
-my $img=Imager::ImgRaw::new(150,150,3);
-
-i_box_filled($img,70,25,130,125,$green);
-i_box_filled($img,20,25,80,125,$blue);
-i_arc($img,75,75,30,0,361,$red);
-i_conv($img,[0.1, 0.2, 0.4, 0.2, 0.1]);
+my $img = test_image_raw();
Imager::i_tags_add($img, 'i_xres', 0, '300', 0);
Imager::i_tags_add($img, 'i_yres', 0, undef, 300);
}
{ # check file limits are checked
- my $limit_file = "testout/t104.ppm";
+ my $limit_file = "testout/t107_24bit.bmp";
ok(Imager->set_file_limits(reset=>1, width=>149), "set width limit 149");
my $im = Imager->new;
ok(!$im->read(file=>$limit_file),
"should succeed - just inside bytes limit");
Imager->set_file_limits(reset=>1);
}
-
+
+{ # various short read failure tests, each entry has:
+ # source filename, size, expected error
+ # these have been selected based on code coverage, to check each
+ # failure path is checked, where practical
+ my @tests =
+ (
+ [
+ "file truncated inside header",
+ "winrgb2.bmp",
+ 20, "file too short to be a BMP file"
+ ],
+ [
+ "1-bit, truncated inside palette",
+ "winrgb2.bmp",
+ 56, "reading BMP palette"
+ ],
+ [
+ "1-bit, truncated in offset region",
+ "winrgb2off.bmp", 64, "failed skipping to image data offset"
+ ],
+ [
+ "1-bit, truncated in image data",
+ "winrgb2.bmp", 96, "failed reading 1-bit bmp data"
+ ],
+ [
+ "4-bit, truncated inside palette",
+ "winrgb4.bmp",
+ 56, "reading BMP palette"
+ ],
+ [
+ "4-bit, truncated in offset region",
+ "winrgb4off.bmp", 120, "failed skipping to image data offset",
+ ],
+ [
+ "4-bit, truncate in image data",
+ "winrgb4.bmp", 120, "failed reading 4-bit bmp data"
+ ],
+ [
+ "4-bit RLE, truncate in uncompressed data",
+ "comp4.bmp", 0x229, "missing data during decompression"
+ ],
+ [
+ "8-bit, truncated in palette",
+ "winrgb8.bmp", 1060, "reading BMP palette"
+ ],
+ [
+ "8-bit, truncated in offset region",
+ "winrgb8off.bmp", 1080, "failed skipping to image data offset"
+ ],
+ [
+ "8-bit, truncated in image data",
+ "winrgb8.bmp", 1080, "failed reading 8-bit bmp data"
+ ],
+ [
+ "8-bit RLE, truncate in uncompressed data",
+ "comp8.bmp", 0x68C, "missing data during decompression"
+ ],
+ [
+ "24-bit, truncate in offset region",
+ "winrgb24off.bmp", 56, "failed skipping to image data offset",
+ ],
+ [
+ "24-bit, truncate in image data",
+ "winrgb24.bmp", 100, "failed reading image data",
+ ],
+ );
+
+ my $test_index = 0;
+ for my $test (@tests) {
+ my ($desc, $srcfile, $size, $error) = @$test;
+ my $im = Imager->new;
+ open IMDATA, "< testimg/$srcfile"
+ or die "$test_index - $desc: Cannot open testimg/$srcfile: $!";
+ binmode IMDATA;
+ my $data;
+ read(IMDATA, $data, $size) == $size
+ or die "$test_index - $desc: Could not read $size data from $srcfile";
+ close IMDATA;
+ ok(!$im->read(data => $data, type =>'bmp'),
+ "$test_index - $desc: Should fail to read");
+ is($im->errstr, $error, "$test_index - $desc: check message");
+ ++$test_index;
+ }
+}
+
+{ # various short read success tests, each entry has:
+ # source filename, size, expected tags
+ print "# allow_incomplete tests\n";
+ my @tests =
+ (
+ [
+ "1-bit",
+ "winrgb2.bmp", 96,
+ {
+ bmp_compression_name => 'BI_RGB',
+ bmp_compression => 0,
+ bmp_used_colors => 2,
+ i_lines_read => 8,
+ },
+ ],
+ [
+ "4-bit",
+ "winrgb4.bmp", 250,
+ {
+ bmp_compression_name => 'BI_RGB',
+ bmp_compression => 0,
+ bmp_used_colors => 16,
+ i_lines_read => 11,
+ },
+ ],
+ [
+ "4-bit RLE - uncompressed seq",
+ "comp4.bmp", 0x229,
+ {
+ bmp_compression_name => 'BI_RLE4',
+ bmp_compression => 2,
+ bmp_used_colors => 16,
+ i_lines_read => 44,
+ },
+ ],
+ [
+ "4-bit RLE - start seq",
+ "comp4.bmp", 0x97,
+ {
+ bmp_compression_name => 'BI_RLE4',
+ bmp_compression => 2,
+ bmp_used_colors => 16,
+ i_lines_read => 8,
+ },
+ ],
+ [
+ "8-bit",
+ "winrgb8.bmp", 1250,
+ {
+ bmp_compression_name => 'BI_RGB',
+ bmp_compression => 0,
+ bmp_used_colors => 256,
+ i_lines_read => 8,
+ },
+ ],
+ [
+ "8-bit RLE - uncompressed seq",
+ "comp8.bmp", 0x68C,
+ {
+ bmp_compression_name => 'BI_RLE8',
+ bmp_compression => 1,
+ bmp_used_colors => 256,
+ i_lines_read => 27,
+ },
+ ],
+ [
+ "8-bit RLE - initial seq",
+ "comp8.bmp", 0x487,
+ {
+ bmp_compression_name => 'BI_RLE8',
+ bmp_compression => 1,
+ bmp_used_colors => 256,
+ i_lines_read => 20,
+ },
+ ],
+ [
+ "24-bit",
+ "winrgb24.bmp", 800,
+ {
+ bmp_compression_name => 'BI_RGB',
+ bmp_compression => 0,
+ bmp_used_colors => 0,
+ i_lines_read => 12,
+ },
+ ],
+ );
+
+ my $test_index = 0;
+ for my $test (@tests) {
+ my ($desc, $srcfile, $size, $tags) = @$test;
+ my $im = Imager->new;
+ open IMDATA, "< testimg/$srcfile"
+ or die "$test_index - $desc: Cannot open testimg/$srcfile: $!";
+ binmode IMDATA;
+ my $data;
+ read(IMDATA, $data, $size) == $size
+ or die "$test_index - $desc: Could not read $size data from $srcfile";
+ close IMDATA;
+ ok($im->read(data => $data, type =>'bmp', allow_incomplete => 1),
+ "$test_index - $desc: Should read successfully");
+ # check standard tags are set
+ is($im->tags(name => 'i_format'), 'bmp',
+ "$test_index - $desc: i_format set");
+ is($im->tags(name => 'i_incomplete'), 1,
+ "$test_index - $desc: i_incomplete set");
+ my %check_tags;
+ for my $key (keys %$tags) {
+ $check_tags{$key} = $im->tags(name => $key);
+ }
+ is_deeply(\%check_tags, $tags, "$test_index - $desc: check tags");
+ ++$test_index;
+ }
+}
+
+{ # check handling of reading images with negative height
+ # each entry is:
+ # source file, description
+ print "# check handling of negative height values\n";
+ my @tests =
+ (
+ [ "winrgb2.bmp", "1-bit, uncompressed" ],
+ [ "winrgb4.bmp", "4-bit, uncompressed" ],
+ [ "winrgb8.bmp", "8-bit, uncompressed" ],
+ [ "winrgb24.bmp", "24-bit, uncompressed" ],
+ [ "comp4.bmp", "4-bit, RLE" ],
+ [ "comp8.bmp", "8-bit, RLE" ],
+ );
+ my $test_index = 0;
+ for my $test (@tests) {
+ my ($file, $desc) = @$test;
+ open IMDATA, "< testimg/$file"
+ or die "$test_index - Cannot open $file: $!";
+ binmode IMDATA;
+ my $data = do { local $/; <IMDATA> };
+ close IMDATA;
+ my $im_orig = Imager->new;
+ $im_orig->read(data => $data)
+ or die "Cannot load original $file: ", $im_orig->errstr;
+
+ # now negate the height
+ my $orig_height = unpack("V", substr($data, 0x16, 4));
+ my $neg_height = 0xFFFFFFFF & ~($orig_height - 1);
+ substr($data, 0x16, 4) = pack("V", $neg_height);
+
+ # and read the modified image
+ my $im = Imager->new;
+ ok($im->read(data => $data),
+ "$test_index - $desc: read negated height image")
+ or print "# ", $im->errstr, "\n";
+
+ # flip orig to match what we should get
+ $im_orig->flip(dir => 'v');
+
+ # check it out
+ is_image($im, $im_orig, "$test_index - $desc: check image");
+
+ ++$test_index;
+ }
+}
+
+{ print "# patched data read failure tests\n";
+ # like the "various invalid format" tests, these generate fail
+ # images from other images included with Imager without providing a
+ # full bmp source, saving on dist size and focusing on the changes needed
+ # to cause the failure
+ # each entry is: source file, patches, expected error, description
+
+ my @tests =
+ (
+ # low image data offsets
+ [
+ "winrgb2.bmp",
+ { 10 => "3d 00 00 00" },
+ "image data offset too small (61)",
+ "1-bit, small image offset"
+ ],
+ [
+ "winrgb4.bmp",
+ { 10 => "75 00 00 00" },
+ "image data offset too small (117)",
+ "4-bit, small image offset"
+ ],
+ [
+ "winrgb8.bmp",
+ { 10 => "35 04 00 00" },
+ "image data offset too small (1077)",
+ "8-bit, small image offset"
+ ],
+ [
+ "winrgb24.bmp",
+ { 10 => "35 00 00 00" },
+ "image data offset too small (53)",
+ "24-bit, small image offset"
+ ],
+ # compression issues
+ [
+ "comp8.bmp",
+ { 0x436 => "97" },
+ "invalid data during decompression",
+ "8bit, RLE run beyond edge of image"
+ ],
+ [
+ # caused glibc malloc or valgrind to complain
+ "comp8.bmp",
+ { 0x436 => "94 00 00 03" },
+ "invalid data during decompression",
+ "8bit, literal run beyond edge of image"
+ ],
+ [
+ "comp4.bmp",
+ { 0x76 => "FF bb FF BB" },
+ "invalid data during decompression",
+ "4bit - RLE run beyond edge of image"
+ ],
+ [
+ "comp4.bmp",
+ { 0x76 => "94 bb 00 FF" },
+ "invalid data during decompression",
+ "4bit - literal run beyond edge of image"
+ ],
+ );
+ my $test_index = 0;
+ for my $test (@tests) {
+ my ($filename, $patches, $error, $desc) = @$test;
+
+ my $data = load_patched_file("testimg/$filename", $patches);
+ my $im = Imager->new;
+ ok(!$im->read(data => $data, type=>'bmp'),
+ "$test_index - $desc:should fail to read");
+ is($im->errstr, $error, "$test_index - $desc:check message");
+ ++$test_index;
+ }
+}
+
+{ # various write failure tests
+ # each entry is:
+ # source, limit, expected error, description
+ my @tests =
+ (
+ [
+ "winrgb2.bmp", 1,
+ "cannot write bmp header: limit reached",
+ "1-bit, writing header"
+ ],
+ [
+ "winrgb4.bmp", 1,
+ "cannot write bmp header: limit reached",
+ "4-bit, writing header"
+ ],
+ [
+ "winrgb8.bmp", 1,
+ "cannot write bmp header: limit reached",
+ "8-bit, writing header"
+ ],
+ [
+ "winrgb24.bmp", 1,
+ "cannot write bmp header: limit reached",
+ "24-bit, writing header"
+ ],
+ [
+ "winrgb2.bmp", 0x38,
+ "cannot write palette entry: limit reached",
+ "1-bit, writing palette"
+ ],
+ [
+ "winrgb4.bmp", 0x38,
+ "cannot write palette entry: limit reached",
+ "4-bit, writing palette"
+ ],
+ [
+ "winrgb8.bmp", 0x38,
+ "cannot write palette entry: limit reached",
+ "8-bit, writing palette"
+ ],
+ [
+ "winrgb2.bmp", 0x40,
+ "writing 1 bit/pixel packed data: limit reached",
+ "1-bit, writing image data"
+ ],
+ [
+ "winrgb4.bmp", 0x80,
+ "writing 4 bit/pixel packed data: limit reached",
+ "4-bit, writing image data"
+ ],
+ [
+ "winrgb8.bmp", 0x440,
+ "writing 8 bit/pixel packed data: limit reached",
+ "8-bit, writing image data"
+ ],
+ [
+ "winrgb24.bmp", 0x39,
+ "writing image data: limit reached",
+ "24-bit, writing image data"
+ ],
+ );
+ print "# write failure tests\n";
+ my $test_index = 0;
+ for my $test (@tests) {
+ my ($file, $limit, $error, $desc) = @$test;
+
+ my $im = Imager->new;
+ $im->read(file => "testimg/$file")
+ or die "Cannot read $file: ", $im->errstr;
+
+ ok(!$im->write(type => 'bmp', callback => limited_write($limit),
+ maxbuffer => 1),
+ "$test_index - $desc: write should fail");
+ is($im->errstr, $error, "$test_index - $desc: check error message");
+
+ ++$test_index;
+ }
+}
+
+{
+ ok(grep($_ eq 'bmp', Imager->read_types), "check bmp in read types");
+ ok(grep($_ eq 'bmp', Imager->write_types), "check bmp in write types");
+}
+
+{
+ # RT #30075
+ # give 4/2 channel images a background color when saving to BMP
+ my $im = Imager->new(xsize=>16, ysize=>16, channels=>4);
+ $im->box(filled => 1, xmin => 8, color => '#FFE0C0');
+ $im->box(filled => 1, color => NC(0, 192, 192, 128),
+ ymin => 8, xmax => 7);
+ ok($im->write(file=>"testout/t107_alpha.bmp", type=>'bmp'),
+ "should succeed writing 4 channel image");
+ my $imread = Imager->new;
+ ok($imread->read(file => 'testout/t107_alpha.bmp'), "read it back");
+ is_color3($imread->getpixel('x' => 0, 'y' => 0), 0, 0, 0,
+ "check transparent became black");
+ is_color3($imread->getpixel('x' => 8, 'y' => 0), 255, 224, 192,
+ "check color came through");
+ is_color3($imread->getpixel('x' => 0, 'y' => 15), 0, 96, 96,
+ "check translucent came through");
+ my $data;
+ ok($im->write(data => \$data, type => 'bmp', i_background => '#FF0000'),
+ "write with red background");
+ ok($imread->read(data => $data, type => 'bmp'),
+ "read it back");
+ is_color3($imread->getpixel('x' => 0, 'y' => 0), 255, 0, 0,
+ "check transparent became red");
+ is_color3($imread->getpixel('x' => 8, 'y' => 0), 255, 224, 192,
+ "check color came through");
+ is_color3($imread->getpixel('x' => 0, 'y' => 15), 127, 96, 96,
+ "check translucent came through");
+}
+
+{ # RT 41406
+ my $data;
+ my $im = test_image();
+ ok($im->write(data => \$data, type => 'bmp'), "write using OO");
+ my $size = unpack("V", substr($data, 34, 4));
+ is($size, 67800, "check data size");
+}
+
sub write_test {
my ($im, $filename) = @_;
local *FH;
}
}
+sub limited_write {
+ my ($limit) = @_;
+
+ return
+ sub {
+ my ($data) = @_;
+ $limit -= length $data;
+ if ($limit >= 0) {
+ print "# write of ", length $data, " bytes successful ($limit left)\n" if $debug_writes;
+ return 1;
+ }
+ else {
+ print "# write of ", length $data, " bytes failed\n";
+ Imager::i_push_error(0, "limit reached");
+ return;
+ }
+ };
+}
+
+sub load_patched_file {
+ my ($filename, $patches) = @_;
+
+ open IMDATA, "< $filename"
+ or die "Cannot open $filename: $!";
+ binmode IMDATA;
+ my $data = do { local $/; <IMDATA> };
+ for my $offset (keys %$patches) {
+ (my $hdata = $patches->{$offset}) =~ tr/ //d;
+ my $pdata = pack("H*", $hdata);
+ substr($data, $offset, length $pdata) = $pdata;
+ }
+
+ return $data;
+}