From 705fd961d1614b441c6f2e1f404a02e2627eef70 Mon Sep 17 00:00:00 2001 From: Tony Cook Date: Sun, 19 Aug 2001 11:33:25 +0000 Subject: [PATCH] Windows BMP file support --- Changes | 1 + Imager.pm | 36 ++- Imager.xs | 4 + bmp.c | 687 ++++++++++++++++++++++++++++++++++++++++++++++------ palimg.c | 1 + t/t107bmp.t | 88 ++++++- 6 files changed, 733 insertions(+), 84 deletions(-) diff --git a/Changes b/Changes index 6da72b07..2552b403 100644 --- a/Changes +++ b/Changes @@ -473,6 +473,7 @@ Revision history for Perl extension Imager. - glyph size issues for freetyp2 - minor problem in handling of canon option - low-level bmp writing (moving it to laptop) + - Windows BMP reading and writing ================================================================= diff --git a/Imager.pm b/Imager.pm index 482f6c00..3fa13817 100644 --- a/Imager.pm +++ b/Imager.pm @@ -712,7 +712,7 @@ sub read { $self->{ERRSTR}='format not supported'; return undef; } - my %iolready=(jpeg=>1, png=>1, tiff=>1, pnm=>1, raw=>1); + my %iolready=(jpeg=>1, png=>1, tiff=>1, pnm=>1, raw=>1, bmp=>1); if ($iolready{$input{type}}) { # Setup data source @@ -754,6 +754,15 @@ sub read { $self->{DEBUG} && print "loading a png file\n"; } + if ( $input{type} eq 'bmp' ) { + $self->{IMG}=i_readbmp_wiol( $IO ); + if ( !defined($self->{IMG}) ) { + $self->{ERRSTR}='unable to read bmp image'; + return undef; + } + $self->{DEBUG} && print "loading a bmp file\n"; + } + if ( $input{type} eq 'raw' ) { my %params=(datachannels=>3,storechannels=>3,interleave=>1,%input); @@ -859,7 +868,7 @@ sub write { fax_fine=>1, @_); my ($fh, $rc, $fd, $IO); - my %iolready=( tiff=>1, raw=>1, png=>1, pnm=>1 ); # this will be SO MUCH BETTER once they are all in there + my %iolready=( tiff=>1, raw=>1, png=>1, pnm=>1, bmp=>1, ); # this will be SO MUCH BETTER once they are all in there unless ($self->{IMG}) { $self->{ERRSTR}='empty input image'; return undef; } @@ -915,6 +924,12 @@ sub write { return undef; } $self->{DEBUG} && print "writing a png file\n"; + } elsif ( $input{type} eq 'bmp' ) { + if ( !i_writebmp_wiol($self->{IMG}, $IO) ) { + $self->{ERRSTR}='unable to write bmp image'; + return undef; + } + $self->{DEBUG} && print "writing a bmp file\n"; } if (exists $input{'data'}) { @@ -1852,6 +1867,7 @@ sub def_guess_type { return 'jpeg' if ($ext =~ m/^jpe?g$/); return 'pnm' if ($ext =~ m/^p[pgb]m$/); return 'png' if ($ext eq "png"); + return 'bmp' if ($ext eq "bmp" || $ext eq "dib"); return 'gif' if ($ext eq "gif"); return (); } @@ -3419,7 +3435,7 @@ the first block of the first gif comment before each image. Where applicable, the ("name") is the name of that field from the GIF89 standard. -The following ares are set in a TIFF image when read, and can be set +The following tags are set in a TIFF image when read, and can be set to control output: =over @@ -3431,6 +3447,20 @@ the i_aspect_only tag is non-zero. =back +The following tags are set when reading a Windows BMP file is read: + +=over + +=item bmp_compression + +The type of compression, if any. + +=item bmp_important_colors + +The number of important colors as defined by the writer of the image. + +=back + Some standard tags will be implemented as time goes by: =over diff --git a/Imager.xs b/Imager.xs index 12a49dc1..bfb39536 100644 --- a/Imager.xs +++ b/Imager.xs @@ -1594,6 +1594,10 @@ i_writebmp_wiol(im,ig) Imager::ImgRaw im Imager::IO ig +Imager::ImgRaw +i_readbmp_wiol(ig) + Imager::IO ig + Imager::ImgRaw i_scaleaxis(im,Value,Axis) Imager::ImgRaw im diff --git a/bmp.c b/bmp.c index c540b27c..7fc83d23 100644 --- a/bmp.c +++ b/bmp.c @@ -1,13 +1,182 @@ #include "image.h" #include -/* possibly this belongs in a global utilities library - Reads from the specified "file" the specified sizes. - The format codes match those used by perl's pack() function, - though only a few are implemented. - In all cases the vararg arguement is an int *. +/* +=head1 NAME - Returns non-zero if all of the arguments were read. +bmp.c - read and write windows BMP files + +=head1 SYNOPSIS + + i_img *im; + io_glue *ig; + + if (!i_writebmp_wiol(im, ig)) { + ... error ... + } + im = i_readbmp(ig); + +=head1 DESCRIPTION + +Reads and writes Windows BMP files. + +=over + +=cut +*/ + +#define FILEHEAD_SIZE 14 +#define INFOHEAD_SIZE 40 +#define BI_RGB 0 +#define BI_RLE8 1 +#define BI_RLE4 2 +#define BI_BITFIELDS 3 +#define BMPRLE_ENDOFLINE 0 +#define BMPRLE_ENDOFBMP 1 +#define BMPRLE_DELTA 2 + +static int read_packed(io_glue *ig, char *format, ...); +static int write_packed(io_glue *ig, char *format, ...); +static int write_bmphead(io_glue *ig, i_img *im, int bit_count, + int data_size); +static int write_1bit_data(io_glue *ig, i_img *im); +static int write_4bit_data(io_glue *ig, i_img *im); +static int write_8bit_data(io_glue *ig, i_img *im); +static int write_24bit_data(io_glue *ig, i_img *im); +static int read_bmp_pal(io_glue *ig, i_img *im, int count); +static i_img *read_1bit_bmp(io_glue *ig, int xsize, int ysize, + int clr_used); +static i_img *read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, + int compression); +static i_img *read_8bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, + int compression); +static i_img *read_direct_bmp(io_glue *ig, int xsize, int ysize, + int bit_count, int clr_used, int compression); + +/* +=item i_writebmp_wiol(im, io_glue) + +Writes the image as a BMP file. Uses 1-bit, 4-bit, 8-bit or 24-bit +formats depending on the image. + +Never compresses the image. + +=cut +*/ +int +i_writebmp_wiol(i_img *im, io_glue *ig) { + io_glue_commit_types(ig); + i_clear_error(); + + /* pick a format */ + if (im->type == i_direct_type) { + return write_24bit_data(ig, im); + } + else { + int pal_size; + + /* must be paletted */ + pal_size = i_colorcount(im); + if (pal_size <= 2) { + return write_1bit_data(ig, im); + } + else if (pal_size <= 16) { + return write_4bit_data(ig, im); + } + else { + return write_8bit_data(ig, im); + } + } +} + +/* +=item i_readbmp_wiol(ig) + +Reads a Windows format bitmap from the given file. + +Handles BI_RLE4 and BI_RLE8 compressed images. Attempts to handle +BI_BITFIELDS images too, but I need a test image. + +=cut +*/ + +i_img * +i_readbmp_wiol(io_glue *ig) { + int b_magic, m_magic, filesize, dummy, infohead_size; + int xsize, ysize, planes, bit_count, compression, size_image, xres, yres; + int clr_used, clr_important, offbits; + i_img *im; + + io_glue_commit_types(ig); + i_clear_error(); + + if (!read_packed(ig, "CCVvvVVVVvvVVVVVV", &b_magic, &m_magic, &filesize, + &dummy, &dummy, &offbits, &infohead_size, + &xsize, &ysize, &planes, + &bit_count, &compression, &size_image, &xres, &yres, + &clr_used, &clr_important)) { + i_push_error(0, "file too short"); + return 0; + } + if (b_magic != 'B' || m_magic != 'M' || infohead_size != INFOHEAD_SIZE + || planes != 1) { + i_push_error(0, "not a BMP file"); + return 0; + } + + switch (bit_count) { + case 1: + im = read_1bit_bmp(ig, xsize, ysize, clr_used); + break; + + case 4: + im = read_4bit_bmp(ig, xsize, ysize, clr_used, compression); + break; + + case 8: + im = read_8bit_bmp(ig, xsize, ysize, clr_used, compression); + break; + + case 32: + case 24: + case 16: + im = read_direct_bmp(ig, xsize, ysize, bit_count, clr_used, compression); + break; + } + + /* store the resolution */ + if (xres && !yres) + yres = xres; + else if (yres && !xres) + xres = yres; + if (xres) { + i_tags_set_float(&im->tags, "i_xres", 0, xres * 0.0254); + i_tags_set_float(&im->tags, "i_yres", 0, yres * 0.0254); + } + i_tags_addn(&im->tags, "bmp_compression", 0, compression); + i_tags_addn(&im->tags, "bmp_important_colors", 0, clr_important); + + return im; +} + +/* +=back + +=head1 IMPLEMENTATION FUNCTIONS + +Internal functions used in the implementation. + +=over + +=item read_packed(ig, format, ...) + +Reads from the specified "file" the specified sizes. The format codes +match those used by perl's pack() function, though only a few are +implemented. In all cases the vararg arguement is an int *. + +Returns non-zero if all of the arguments were read. + +=cut */ static int read_packed(io_glue *ig, char *format, ...) { @@ -45,6 +214,12 @@ int read_packed(io_glue *ig, char *format, ...) { *p = (char)buf[0]; break; + case '3': /* extension - 24-bit number */ + if (ig->readcb(ig, buf, 3) == -1) + return 0; + *p = buf[0] + (buf[1] << 8) + (buf[2] << 16); + break; + default: m_fatal(1, "Unknown read_packed format code 0x%02x", *format); } @@ -53,6 +228,16 @@ int read_packed(io_glue *ig, char *format, ...) { return 1; } +/* +=item write_packed(ig, format, ...) + +Writes packed data to the specified io_glue. + +Returns non-zero on success. + +=cut +*/ + static int write_packed(io_glue *ig, char *format, ...) { unsigned char buf[4]; @@ -98,12 +283,15 @@ write_packed(io_glue *ig, char *format, ...) { return 1; } -#define FILEHEAD_SIZE 14 -#define INFOHEAD_SIZE 40 -#define BI_RGB 0 -#define BI_RLE8 1 -#define BI_RLE4 2 -#define BI_BITFIELDS 3 +/* +=item write_bmphead(ig, im, bit_count, data_size) + +Writes a Windows BMP header to the file. + +Returns non-zero on success. + +=cut +*/ static int write_bmphead(io_glue *ig, i_img *im, int bit_count, int data_size) { @@ -151,8 +339,8 @@ int write_bmphead(io_glue *ig, i_img *im, int bit_count, int data_size) { if (!write_packed(ig, "CCVvvVVVVvvVVVVVV", 'B', 'M', data_size+offset, 0, 0, offset, INFOHEAD_SIZE, im->xsize, im->ysize, 1, - bit_count, BI_RGB, 0, (int)xres, (int)yres, - colors_used, 0)){ + bit_count, BI_RGB, 0, (int)(xres+0.5), (int)(yres+0.5), + colors_used, colors_used)){ i_push_error(0, "cannot write bmp header"); return 0; } @@ -182,6 +370,15 @@ int write_bmphead(io_glue *ig, i_img *im, int bit_count, int data_size) { return 1; } +/* +=item write_1bit_data(ig, im) + +Writes the image data as a 1-bit/pixel image. + +Returns non-zero on success. + +=cut +*/ static int write_1bit_data(io_glue *ig, i_img *im) { i_palidx *line; @@ -234,6 +431,15 @@ write_1bit_data(io_glue *ig, i_img *im) { return 1; } +/* +=item write_4bit_data(ig, im) + +Writes the image data as a 4-bit/pixel image. + +Returns non-zero on success. + +=cut +*/ static int write_4bit_data(io_glue *ig, i_img *im) { i_palidx *line; @@ -273,6 +479,15 @@ write_4bit_data(io_glue *ig, i_img *im) { return 1; } +/* +=item write_8bit_data(ig, im) + +Writes the image data as a 8-bit/pixel image. + +Returns non-zero on success. + +=cut +*/ static int write_8bit_data(io_glue *ig, i_img *im) { i_palidx *line; @@ -304,6 +519,15 @@ write_8bit_data(io_glue *ig, i_img *im) { static int bgr_chans[] = { 2, 1, 0, }; static int grey_chans[] = { 0, 0, 0, }; +/* +=item write_24bit_data(ig, im) + +Writes the image data as a 24-bit/pixel image. + +Returns non-zero on success. + +=cut +*/ static int write_24bit_data(io_glue *ig, i_img *im) { int *chans; @@ -330,33 +554,15 @@ write_24bit_data(io_glue *ig, i_img *im) { return 1; } -/* no support for writing compressed (RLE8 or RLE4) BMP files */ -int -i_writebmp_wiol(i_img *im, io_glue *ig) { - io_glue_commit_types(ig); - i_clear_error(); +/* +=item read_bmp_pal(ig, im, count) - /* pick a format */ - if (im->type == i_direct_type) { - return write_24bit_data(ig, im); - } - else { - int pal_size; +Reads count palette entries from the file and add them to the image. - /* must be paletted */ - pal_size = i_colorcount(im); - if (pal_size <= 2) { - return write_1bit_data(ig, im); - } - else if (pal_size <= 16) { - return write_4bit_data(ig, im); - } - else { - return write_8bit_data(ig, im); - } - } -} +Returns non-zero on success. +=cut +*/ static int read_bmp_pal(io_glue *ig, i_img *im, int count) { int i; @@ -378,6 +584,15 @@ read_bmp_pal(io_glue *ig, i_img *im, int count) { return 1; } +/* +=item read_1bit_bmp(ig, xsize, ysize, clr_used) + +Reads in the palette and image data for a 1-bit/pixel image. + +Returns the image or NULL. + +=cut +*/ static i_img * read_1bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used) { i_img *im; @@ -403,6 +618,8 @@ read_1bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used) { yinc = 1; } im = i_img_pal_new(xsize, ysize, 3, 256); + if (!clr_used) + clr_used = 2; if (!read_bmp_pal(ig, im, clr_used)) { i_img_destroy(im); return NULL; @@ -435,16 +652,29 @@ read_1bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used) { return im; } -#if 0 +/* +=item read_4bit_bmp(ig, xsize, ysize, clr_used, compression) + +Reads in the palette and image data for a 4-bit/pixel image. + +Returns the image or NULL. + +Hopefully this will be combined with the following function at some +point. + +=cut +*/ static i_img * -read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used) { +read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, + int compression) { i_img *im; int x, y, lasty, yinc; i_palidx *line, *p; unsigned char *packed; int line_size = (xsize + 1)/2; unsigned char *in; + int size, i; line_size = (line_size+3) / 4 * 4; @@ -461,12 +691,17 @@ read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used) { yinc = 1; } im = i_img_pal_new(xsize, ysize, 3, 256); + if (!clr_used) + clr_used = 16; if (!read_bmp_pal(ig, im, clr_used)) { i_img_destroy(im); return NULL; } - packed = mymalloc(line_size); + if (line_size < 260) + packed = mymalloc(260); + else + packed = mymalloc(line_size); line = mymalloc(xsize+1); if (compression == BI_RGB) { while (y != lasty) { @@ -489,57 +724,351 @@ read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used) { } } else if (compression == BI_RLE4) { - return 0; + int read_size; + int want_high; + int count; + + x = 0; + while (1) { + /* there's always at least 2 bytes in a sequence */ + if (ig->readcb(ig, packed, 2) != 2) { + myfree(packed); + myfree(line); + i_push_error(0, "missing data during decompression"); + i_img_destroy(im); + return NULL; + } + else if (packed[0]) { + line[0] = packed[1] >> 4; + line[1] = packed[1] & 0x0F; + for (i = 0; i < packed[0]; i += 2) { + if (i < packed[0]-1) + i_ppal(im, x, x+2, y, line); + else + i_ppal(im, x, x+(packed[0]-i), y, line); + x += 2; + } + } else { + switch (packed[1]) { + case BMPRLE_ENDOFLINE: + x = 0; + y += yinc; + break; + + case BMPRLE_ENDOFBMP: + free(packed); + free(line); + return im; + + case BMPRLE_DELTA: + if (ig->readcb(ig, packed, 2) != 2) { + myfree(packed); + myfree(line); + i_push_error(0, "missing data during decompression"); + i_img_destroy(im); + return NULL; + } + x += packed[0]; + y += yinc * packed[1]; + break; + + default: + count = packed[1]; + size = (count + 1) / 2; + read_size = (size+1) / 2 * 2; + if (ig->readcb(ig, packed, read_size) != read_size) { + myfree(packed); + myfree(line); + i_push_error(0, "missing data during decompression"); + /*i_img_destroy(im);*/ + return im; + } + for (i = 0; i < size; ++i) { + line[0] = packed[i] >> 4; + line[1] = packed[i] & 0xF; + i_ppal(im, x, x+2, y, line); + x += 2; + } + break; + } + } + } + } + else { /*if (compression == BI_RLE4) {*/ + myfree(packed); + myfree(line); + i_push_error(0, "bad compression for 4-bit image"); + i_img_destroy(im); + return NULL; } return im; } -#endif +/* +=item read_8bit_bmp(ig, xsize, ysize, clr_used, compression) -i_img * -i_readbmp_wiol(io_glue *ig) { -#if 0 - int b_magic, m_magic, filesize, dummy, infohead_size; - int xsize, ysize, planes, bit_count, compression, size_image, xres, yres; - int clr_used; +Reads in the palette and image data for a 8-bit/pixel image. + +Returns the image or NULL. + +=cut +*/ +static i_img * +read_8bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, + int compression) { i_img *im; - - io_glue_commit_types(ig); - i_clear_error(); + int x, y, lasty, yinc; + i_palidx *line, *p; + int line_size = xsize; + unsigned char *in; - if (!read_packed(ig, "CCVvvVVVVvvVVVVV", &b_magic, &m_magic, &filesize, - &dummy, &dummy, &infohead_size, &xsize, &ysize, &planes, - &bit_count, &compression, &size_image, &xres, &yres, - &clr_used, &dummy)) { - i_push_error(0, "file too short"); - return 0; + line_size = (line_size+3) / 4 * 4; + + if (ysize > 0) { + y = ysize-1; + lasty = -1; + yinc = -1; } - if (b_magic != 'B' || m_magic != 'M' || infohead_size != INFOHEAD_SIZE - || planes != 1) { - i_push_error(0, "not a BMP file"); - return 0; + else { + /* when ysize is -ve it's a top-down image */ + ysize = -ysize; + y = 0; + lasty = ysize; + yinc = 1; } + im = i_img_pal_new(xsize, ysize, 3, 256); + if (!clr_used) + clr_used = 256; + if (!read_bmp_pal(ig, im, clr_used)) { + i_img_destroy(im); + return NULL; + } + + line = mymalloc(line_size); + if (compression == BI_RGB) { + while (y != lasty) { + if (ig->readcb(ig, line, line_size) != line_size) { + myfree(line); + i_push_error(0, "reading 8-bit bmp data"); + i_img_destroy(im); + return NULL; + } + i_ppal(im, 0, xsize, y, line); + y += yinc; + } + } + else if (compression == BI_RLE8) { + int read_size; + int want_high; + int count; + unsigned char packed[2]; + + x = 0; + while (1) { + /* there's always at least 2 bytes in a sequence */ + if (ig->readcb(ig, packed, 2) != 2) { + myfree(line); + i_push_error(0, "missing data during decompression"); + i_img_destroy(im); + return NULL; + } + if (packed[0]) { + memset(line, packed[1], packed[0]); + i_ppal(im, x, x+packed[0], y, line); + x += packed[0]; + } else { + switch (packed[1]) { + case BMPRLE_ENDOFLINE: + x = 0; + y += yinc; + break; + + case BMPRLE_ENDOFBMP: + free(line); + return im; + + case BMPRLE_DELTA: + if (ig->readcb(ig, packed, 2) != 2) { + myfree(line); + i_push_error(0, "missing data during decompression"); + i_img_destroy(im); + return NULL; + } + x += packed[0]; + y += yinc * packed[1]; + break; + + default: + count = packed[1]; + read_size = (count+1) / 2 * 2; + if (ig->readcb(ig, line, read_size) != read_size) { + myfree(line); + i_push_error(0, "missing data during decompression"); + i_img_destroy(im); + return NULL; + } + i_ppal(im, x, x+count, y, line); + x += count; + break; + } + } + } + } + else { + myfree(line); + i_push_errorf(0, "unknown 8-bit BMP compression %d", compression); + i_img_destroy(im); + return NULL; + } + + return im; +} + +struct bm_masks { + unsigned masks[3]; + int shifts[3]; +}; +static struct bm_masks std_masks[] = +{ + { /* 16-bit */ + { 0770000, 00007700, 00000077, }, + { 10, 4, -2, }, + }, + { /* 24-bit */ + { 0xFF0000, 0x00FF00, 0x0000FF, }, + { 16, 8, 0, }, + }, + { /* 32-bit */ + { 0xFF0000, 0x00FF00, 0x0000FF, }, + { 16, 8, 0, }, + }, +}; + +/* +=item read_direct_bmp(ig, xsize, ysize, bit_count, clr_used, compression) + +Skips the palette and reads in the image data for a direct colour image. + +Returns the image or NULL. + +=cut +*/ +static i_img * +read_direct_bmp(io_glue *ig, int xsize, int ysize, int bit_count, + int clr_used, int compression) { + i_img *im; + int x, y, lasty, yinc; + i_color *line, *p; + unsigned char *in; + int pix_size = bit_count / 8; + int line_size = xsize * pix_size; + struct bm_masks masks; + char unpack_code[2] = ""; + int i; + int extras; + char junk[4]; - switch (bit_count) { - case 1: - im = read_1bit_bmp(ig, xsize, ysize, clr_used); - break; + unpack_code[0] = *("v3V"+pix_size-2); + unpack_code[1] = '\0'; - case 4: - im = read_4bit_bmp(ig, clr_used, compression); - break; + line_size = (line_size+3) / 4 * 4; + extras = line_size - xsize * pix_size; - case 8: - im = read_8bit_bmp(ig, clr_used, compression); - break; + if (ysize > 0) { + y = ysize-1; + lasty = -1; + yinc = -1; + } + else { + /* when ysize is -ve it's a top-down image */ + ysize = -ysize; + y = 0; + lasty = ysize; + yinc = 1; + } + line = mymalloc(line_size); + if (compression == BI_RGB) { + masks = std_masks[pix_size-2]; + + /* there's a potential "palette" after the header */ + for (i = 0; i < clr_used; ++clr_used) { + char buf[4]; + if (ig->readcb(ig, buf, 4) != 4) { + i_push_error(0, "skipping colors"); + return 0; + } + } + } + else if (compression == BI_BITFIELDS) { + int pos, bit; + for (i = 0; i < 3; ++i) { + if (!read_packed(ig, "V", masks.masks+i)) { + i_push_error(0, "reading pixel masks"); + return 0; + } + /* work out a shift for the mask */ + pos = 0; + bit = masks.masks[i] & -masks.masks[i]; + while (bit) { + ++pos; + bit >>= 1; + } + masks.shifts[i] = pos - 8; + } + } - case 32: - case 24: - case 16: - im = read_direct_bmp(ig, clr_used, compression); - break; + im = i_img_empty(NULL, xsize, ysize); + + line = mymalloc(sizeof(i_color) * xsize); + while (y != lasty) { + p = line; + for (x = 0; x < xsize; ++x) { + unsigned pixel; + if (!read_packed(ig, unpack_code, &pixel)) { + i_push_error(0, "reading image data"); + myfree(line); + i_img_destroy(im); + return NULL; + } + for (i = 0; i < 3; ++i) { + if (masks.shifts[i] > 0) + p->channel[i] = (pixel & masks.masks[i]) >> masks.shifts[i]; + else + p->channel[i] = (pixel & masks.masks[i]) << -masks.shifts[i]; + } + ++p; + } + i_plin(im, 0, xsize, y, line); + if (extras) + ig->readcb(ig, junk, extras); + y += yinc; } -#endif - return 0; + myfree(line); + + return im; } + +/* +=head1 SEE ALSO + +Imager(3) + +=head1 AUTHOR + +Tony Cook + +=head1 RESTRICTIONS + +Cannot save as compressed BMP. + +=head1 BUGS + +Doesn't handle OS/2 bitmaps. + +16-bit/pixel images haven't been tested. (I need an image). + +BI_BITFIELDS compression hasn't been tested (I need an image). + +=cut +*/ diff --git a/palimg.c b/palimg.c index 0a989f89..844bf219 100644 --- a/palimg.c +++ b/palimg.c @@ -110,6 +110,7 @@ i_img *i_img_pal_new_low(i_img *im, int x, int y, int channels, int maxpal) { im->bytes = sizeof(i_palidx) * x * y; im->idata = mymalloc(im->bytes); im->channels = channels; + memset(im->idata, 0, im->bytes); im->xsize = x; im->ysize = y; diff --git a/t/t107bmp.t b/t/t107bmp.t index a1529b60..8511680f 100644 --- a/t/t107bmp.t +++ b/t/t107bmp.t @@ -1,9 +1,11 @@ #!perl -w -print "1..4\n"; +print "1..12\n"; use Imager qw(:all); use strict; init_log("testout/t107bmp.log",1); +my $base_diff = 0; +# if you change this make sure you generate new compressed versions my $green=i_color_new(0,255,0,255); my $blue=i_color_new(0,0,255,255); my $red=i_color_new(255,0,0,255); @@ -16,16 +18,50 @@ 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]); +Imager::i_tags_add($img, 'i_xres', 0, '300', 0); +Imager::i_tags_add($img, 'i_yres', 0, undef, 300); write_test(1, $img, "testout/t107_24bit.bmp"); # 'webmap' is noticably faster than the default my $im8 = Imager::i_img_to_pal($img, { make_colors=>'webmap', translate=>'errdiff'}); write_test(2, $im8, "testout/t107_8bit.bmp"); -my $im4 = Imager::i_img_to_pal($img, { max_colors=>16 }); +# use a fixed palette so we get reproducible results for the compressed +# version +my @pal16 = map { NC($_) } + qw(605844 966600 0148b2 00f800 bf0a33 5e009e + 2ead1b 0000f8 004b01 fd0000 0e1695 000002); +my $im4 = Imager::i_img_to_pal($img, { colors=>\@pal16, make_colors=>'none' }); write_test(3, $im4, "testout/t107_4bit.bmp"); my $im1 = Imager::i_img_to_pal($img, { colors=>[ NC(0, 0, 0), NC(176, 160, 144) ], make_colors=>'none', translate=>'errdiff' }); write_test(4, $im1, "testout/t107_1bit.bmp"); +my $bi_rgb = 0; +my $bi_rle8 = 1; +my $bi_rle4 = 2; +my $bi_bitfields = 3; +read_test(5, "testout/t107_24bit.bmp", $img, bmp_compression=>0); +read_test(6, "testout/t107_8bit.bmp", $im8, bmp_compression=>0); +read_test(7, "testout/t107_4bit.bmp", $im4, bmp_compression=>0); +read_test(8, "testout/t107_1bit.bmp", $im1, bmp_compression=>0); +# the following might have slight differences +$base_diff = i_img_diff($img, $im8) * 2; +print "# base difference $base_diff\n"; +read_test(9, "testimg/comp4.bmp", $im4, bmp_compression=>$bi_rle4); +read_test(10, "testimg/comp8.bmp", $im8, bmp_compression=>$bi_rle8); + +my $imoo = Imager->new; +if ($imoo->read(file=>'testout/t107_24bit.bmp')) { + print "ok 11\n"; +} +else { + print "not ok 11 # ",$imoo->errstr,"\n"; +} +if ($imoo->write(file=>'testout/t107_oo.bmp')) { + print "ok 12\n"; +} +else { + print "not 12 # ",$imoo->errstr,"\n"; +} sub write_test { my ($test_num, $im, $filename) = @_; @@ -47,3 +83,51 @@ sub write_test { print "not ok $test_num # $!\n"; } } + +sub read_test { + my ($test_num, $filename, $im, %tags) = @_; + local *FH; + + if (open FH, "< $filename") { + binmode FH; + my $IO = Imager::io_new_fd(fileno(FH)); + my $im_read = Imager::i_readbmp_wiol($IO); + if ($im_read) { + my $diff = i_img_diff($im, $im_read); + if ($diff > $base_diff) { + print "not ok $test_num # image mismatch $diff\n"; + } + else { + my $tags_ok = 1; + for my $tag (keys %tags) { + if (my $index = Imager::i_tags_find($im_read, $tag, 0)) { + my ($name, $value) = Imager::i_tags_get($im_read, $index); + if ($value != $tags{$tag}) { + print "# tag $tag value mismatch $tags{$tag} != $value\n"; + $tags_ok = 0; + } + } + } + if ($tags_ok) { + print "ok $test_num\n"; + } + else { + print "not ok $test_num # bad tag values\n"; + } + # for my $i (0 .. Imager::i_tags_count($im_read)-1) { + # my ($name, $value) = Imager::i_tags_get($im_read, $i); + # print "# tag '$name' => '$value'\n"; + #} + } + } + else { + print "not ok $test_num # ",Imager->_error_as_msg(),"\n"; + } + undef $IO; + close FH; + } + else { + print "not ok $test_num # $!\n"; + } +} + -- 2.39.5