X-Git-Url: http://git.imager.perl.org/imager.git/blobdiff_plain/b1e9695262d2b5b7cdb5e96951b3addb64ffd261..e1c0692925:/bmp.c diff --git a/bmp.c b/bmp.c index 0e8dc62c..caf59b42 100644 --- a/bmp.c +++ b/bmp.c @@ -1,3 +1,4 @@ +#define IMAGER_NO_CONTEXT #include #include "imageri.h" @@ -35,6 +36,11 @@ Reads and writes Windows BMP files. #define BMPRLE_ENDOFBMP 1 #define BMPRLE_DELTA 2 +#define SIGNBIT32 ((i_upacked_t)1U << 31) +#define SIGNBIT16 ((i_upacked_t)1U << 15) + +#define SIGNMAX32 ((1UL << 31) - 1) + 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, @@ -45,14 +51,19 @@ 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, - int compression, long offbits); + int compression, long offbits, int allow_incomplete); static i_img *read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, - int compression, long offbits); + int compression, long offbits, int allow_incomplete); static i_img *read_8bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, - int compression, long offbits); + int compression, long offbits, int allow_incomplete); static i_img *read_direct_bmp(io_glue *ig, int xsize, int ysize, int bit_count, int clr_used, int compression, - long offbits); + long offbits, int allow_incomplete); + +/* used for the read_packed() and write_packed() functions, an integer + * type */ +typedef long i_packed_t; +typedef unsigned long i_upacked_t; /* =item i_writebmp_wiol(im, io_glue) @@ -66,7 +77,7 @@ Never compresses the image. */ int i_writebmp_wiol(i_img *im, io_glue *ig) { - io_glue_commit_types(ig); + dIMCTXim(im); i_clear_error(); /* pick a format */ @@ -102,23 +113,23 @@ BI_BITFIELDS images too, but I need a test image. */ i_img * -i_readbmp_wiol(io_glue *ig) { - int b_magic, m_magic, filesize, res1, res2, infohead_size; - int xsize, ysize, planes, bit_count, compression, size_image, xres, yres; - int clr_used, clr_important, offbits; +i_readbmp_wiol(io_glue *ig, int allow_incomplete) { + i_packed_t b_magic, m_magic, filesize, res1, res2, infohead_size; + i_packed_t xsize, ysize, planes, bit_count, compression, size_image, xres, yres; + i_packed_t clr_used, clr_important, offbits; i_img *im; + dIMCTXio(ig); - mm_log((1, "i_readbmp_wiol(ig %p)\n", ig)); + im_log((aIMCTX, 1, "i_readbmp_wiol(ig %p)\n", ig)); - io_glue_commit_types(ig); i_clear_error(); - if (!read_packed(ig, "CCVvvVVVVvvVVVVVV", &b_magic, &m_magic, &filesize, + if (!read_packed(ig, "CCVvvVVV!V!vvVVVVVV", &b_magic, &m_magic, &filesize, &res1, &res2, &offbits, &infohead_size, &xsize, &ysize, &planes, &bit_count, &compression, &size_image, &xres, &yres, &clr_used, &clr_important)) { - i_push_error(0, "file too short"); + i_push_error(0, "file too short to be a BMP file"); return 0; } if (b_magic != 'B' || m_magic != 'M' || infohead_size != INFOHEAD_SIZE @@ -127,39 +138,43 @@ i_readbmp_wiol(io_glue *ig) { return 0; } - mm_log((1, " bmp header: filesize %d offbits %d xsize %d ysize %d planes %d " + im_log((aIMCTX, 1, " bmp header: filesize %d offbits %d xsize %d ysize %d planes %d " "bit_count %d compression %d size %d xres %d yres %d clr_used %d " - "clr_important %d\n", filesize, offbits, xsize, ysize, planes, - bit_count, compression, size_image, xres, yres, clr_used, - clr_important)); + "clr_important %d\n", (int)filesize, (int)offbits, (int)xsize, + (int)ysize, (int)planes, (int)bit_count, (int)compression, + (int)size_image, (int)xres, (int)yres, (int)clr_used, + (int)clr_important)); - if (!i_int_check_image_file_limits(xsize, ysize, 3, sizeof(i_sample_t))) { - mm_log((1, "i_readbmp_wiol: image size exceeds limits\n")); + if (!i_int_check_image_file_limits(xsize, abs(ysize), 3, sizeof(i_sample_t))) { + im_log((aIMCTX, 1, "i_readbmp_wiol: image size exceeds limits\n")); return NULL; } switch (bit_count) { case 1: - im = read_1bit_bmp(ig, xsize, ysize, clr_used, compression, offbits); + im = read_1bit_bmp(ig, xsize, ysize, clr_used, compression, offbits, + allow_incomplete); break; case 4: - im = read_4bit_bmp(ig, xsize, ysize, clr_used, compression, offbits); + im = read_4bit_bmp(ig, xsize, ysize, clr_used, compression, offbits, + allow_incomplete); break; case 8: - im = read_8bit_bmp(ig, xsize, ysize, clr_used, compression, offbits); + im = read_8bit_bmp(ig, xsize, ysize, clr_used, compression, offbits, + allow_incomplete); break; case 32: case 24: case 16: im = read_direct_bmp(ig, xsize, ysize, bit_count, clr_used, compression, - offbits); + offbits, allow_incomplete); break; default: - i_push_errorf(0, "unknown bit count for BMP file (%d)", bit_count); + im_push_errorf(aIMCTX, 0, "unknown bit count for BMP file (%d)", (int)bit_count); return NULL; } @@ -203,52 +218,69 @@ Returns non-zero if all of the arguments were read. =cut */ -static -int read_packed(io_glue *ig, char *format, ...) { +static int +read_packed(io_glue *ig, char *format, ...) { unsigned char buf[4]; va_list ap; - int *p; + i_packed_t *p; + i_packed_t work; + int code; + int shrieking; /* format code has a ! flag */ va_start(ap, format); while (*format) { - p = va_arg(ap, int *); + p = va_arg(ap, i_packed_t *); - switch (*format) { + code = *format++; + shrieking = *format == '!'; + if (shrieking) ++format; + + switch (code) { case 'v': - if (ig->readcb(ig, buf, 2) != 2) + if (i_io_read(ig, buf, 2) != 2) return 0; - *p = buf[0] + (buf[1] << 8); + work = buf[0] + ((i_packed_t)buf[1] << 8); + if (shrieking) + *p = (work ^ SIGNBIT16) - SIGNBIT16; + else + *p = work; break; case 'V': - if (ig->readcb(ig, buf, 4) != 4) + if (i_io_read(ig, buf, 4) != 4) return 0; - *p = buf[0] + (buf[1] << 8) + (buf[2] << 16) + (buf[3] << 24); + work = buf[0] + (buf[1] << 8) + ((i_packed_t)buf[2] << 16) + ((i_packed_t)buf[3] << 24); + if (shrieking) + *p = (work ^ SIGNBIT32) - SIGNBIT32; + else + *p = work; break; case 'C': - if (ig->readcb(ig, buf, 1) != 1) + if (i_io_read(ig, buf, 1) != 1) return 0; *p = buf[0]; break; case 'c': - if (ig->readcb(ig, buf, 1) != 1) + if (i_io_read(ig, buf, 1) != 1) return 0; *p = (char)buf[0]; break; case '3': /* extension - 24-bit number */ - if (ig->readcb(ig, buf, 3) != 3) + if (i_io_read(ig, buf, 3) != 3) return 0; - *p = buf[0] + (buf[1] << 8) + (buf[2] << 16); + *p = buf[0] + (buf[1] << 8) + ((i_packed_t)buf[2] << 16); break; default: - i_fatal(1, "Unknown read_packed format code 0x%02x", *format); + { + dIMCTXio(ig); + im_fatal(aIMCTX, 1, "Unknown read_packed format code 0x%02x", code); + } } - ++format; } return 1; } @@ -272,14 +304,14 @@ write_packed(io_glue *ig, char *format, ...) { va_start(ap, format); while (*format) { - i = va_arg(ap, unsigned int); + i = va_arg(ap, i_upacked_t); switch (*format) { case 'v': buf[0] = i & 255; buf[1] = i / 256; - if (ig->writecb(ig, buf, 2) == -1) - return 0; + if (i_io_write(ig, buf, 2) == -1) + goto fail; break; case 'V': @@ -287,25 +319,32 @@ write_packed(io_glue *ig, char *format, ...) { buf[1] = (i >> 8) & 0xFF; buf[2] = (i >> 16) & 0xFF; buf[3] = (i >> 24) & 0xFF; - if (ig->writecb(ig, buf, 4) == -1) - return 0; + if (i_io_write(ig, buf, 4) == -1) + goto fail; break; case 'C': case 'c': buf[0] = i & 0xFF; - if (ig->writecb(ig, buf, 1) == -1) - return 0; + if (i_io_write(ig, buf, 1) == -1) + goto fail; break; default: - i_fatal(1, "Unknown write_packed format code 0x%02x", *format); + { + dIMCTXio(ig); + im_fatal(aIMCTX, 1, "Unknown write_packed format code 0x%02x", *format); + } } ++format; } va_end(ap); return 1; + + fail: + va_end(ap); + return 0; } /* @@ -324,6 +363,12 @@ int write_bmphead(io_glue *ig, i_img *im, int bit_count, int data_size) { int got_xres, got_yres, aspect_only; int colors_used = 0; int offset = FILEHEAD_SIZE + INFOHEAD_SIZE; + dIMCTXim(im); + + if (im->xsize > SIGNMAX32 || im->ysize > SIGNMAX32) { + i_push_error(0, "image too large to write to BMP"); + return 0; + } got_xres = i_tags_get_float(&im->tags, "i_xres", 0, &xres); got_yres = i_tags_get_float(&im->tags, "i_yres", 0, &yres); @@ -362,10 +407,15 @@ int write_bmphead(io_glue *ig, i_img *im, int bit_count, int data_size) { offset += 4 * colors_used; } - 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+0.5), (int)(yres+0.5), - colors_used, colors_used)){ + if (!write_packed(ig, "CCVvvVVVVvvVVVVVV", 'B', 'M', + (i_upacked_t)(data_size+offset), + (i_upacked_t)0, (i_upacked_t)0, (i_upacked_t)offset, + (i_upacked_t)INFOHEAD_SIZE, (i_upacked_t)im->xsize, + (i_upacked_t)im->ysize, (i_upacked_t)1, + (i_upacked_t)bit_count, (i_upacked_t)BI_RGB, + (i_upacked_t)data_size, + (i_upacked_t)(xres+0.5), (i_upacked_t)(yres+0.5), + (i_upacked_t)colors_used, (i_upacked_t)colors_used)){ i_push_error(0, "cannot write bmp header"); return 0; } @@ -376,15 +426,16 @@ int write_bmphead(io_glue *ig, i_img *im, int bit_count, int data_size) { for (i = 0; i < colors_used; ++i) { i_getcolors(im, i, &c, 1); if (im->channels >= 3) { - if (!write_packed(ig, "CCCC", c.channel[2], c.channel[1], - c.channel[0], 0)) { + if (!write_packed(ig, "CCCC", (i_upacked_t)(c.channel[2]), + (i_upacked_t)(c.channel[1]), + (i_upacked_t)(c.channel[0]), (i_upacked_t)0)) { i_push_error(0, "cannot write palette entry"); return 0; } } else { - if (!write_packed(ig, "CCCC", c.channel[0], c.channel[0], - c.channel[0], 0)) { + i_upacked_t v = c.channel[0]; + if (!write_packed(ig, "CCCC", v, v, v, 0)) { i_push_error(0, "cannot write palette entry"); return 0; } @@ -414,6 +465,7 @@ write_1bit_data(io_glue *ig, i_img *im) { int line_size = (im->xsize+7) / 8; int x, y; int unpacked_size; + dIMCTXim(im); /* round up to nearest multiple of four */ line_size = (line_size + 3) / 4 * 4; @@ -452,7 +504,7 @@ write_1bit_data(io_glue *ig, i_img *im) { if (mask != 0x80) { *out++ = byte; } - if (ig->writecb(ig, packed, line_size) < 0) { + if (i_io_write(ig, packed, line_size) < 0) { myfree(packed); myfree(line); i_push_error(0, "writing 1 bit/pixel packed data"); @@ -462,7 +514,8 @@ write_1bit_data(io_glue *ig, i_img *im) { myfree(packed); myfree(line); - ig->closecb(ig); + if (i_io_close(ig)) + return 0; return 1; } @@ -484,6 +537,7 @@ write_4bit_data(io_glue *ig, i_img *im) { int line_size = (im->xsize+1) / 2; int x, y; int unpacked_size; + dIMCTXim(im); /* round up to nearest multiple of four */ line_size = (line_size + 3) / 4 * 4; @@ -511,7 +565,7 @@ write_4bit_data(io_glue *ig, i_img *im) { for (x = 0; x < im->xsize; x += 2) { *out++ = (line[x] << 4) + line[x+1]; } - if (ig->writecb(ig, packed, line_size) < 0) { + if (i_io_write(ig, packed, line_size) < 0) { myfree(packed); myfree(line); i_push_error(0, "writing 4 bit/pixel packed data"); @@ -521,7 +575,8 @@ write_4bit_data(io_glue *ig, i_img *im) { myfree(packed); myfree(line); - ig->closecb(ig); + if (i_io_close(ig)) + return 0; return 1; } @@ -541,6 +596,7 @@ write_8bit_data(io_glue *ig, i_img *im) { int line_size = im->xsize; int y; int unpacked_size; + dIMCTXim(im); /* round up to nearest multiple of four */ line_size = (line_size + 3) / 4 * 4; @@ -559,7 +615,7 @@ write_8bit_data(io_glue *ig, i_img *im) { for (y = im->ysize-1; y >= 0; --y) { i_gpal(im, 0, im->xsize, y, line); - if (ig->writecb(ig, line, line_size) < 0) { + if (i_io_write(ig, line, line_size) < 0) { myfree(line); i_push_error(0, "writing 8 bit/pixel packed data"); return 0; @@ -567,14 +623,12 @@ write_8bit_data(io_glue *ig, i_img *im) { } myfree(line); - ig->closecb(ig); + if (i_io_close(ig)) + return 0; return 1; } -static int bgr_chans[] = { 2, 1, 0, }; -static int grey_chans[] = { 0, 0, 0, }; - /* =item write_24bit_data(ig, im) @@ -586,10 +640,13 @@ Returns non-zero on success. */ static int write_24bit_data(io_glue *ig, i_img *im) { - int *chans; unsigned char *samples; int y; int line_size = 3 * im->xsize; + i_color bg; + dIMCTXim(im); + + i_get_file_background(im, &bg); /* just in case we implement a direct format with 2bytes/pixel (unlikely though) */ @@ -602,12 +659,19 @@ write_24bit_data(io_glue *ig, i_img *im) { if (!write_bmphead(ig, im, 24, line_size * im->ysize)) return 0; - chans = im->channels >= 3 ? bgr_chans : grey_chans; - samples = mymalloc(line_size); /* checked 29jun05 tonyc */ + samples = mymalloc(4 * im->xsize); memset(samples, 0, line_size); for (y = im->ysize-1; y >= 0; --y) { - i_gsamp(im, 0, im->xsize, y, samples, chans, 3); - if (ig->writecb(ig, samples, line_size) < 0) { + unsigned char *samplep = samples; + int x; + i_gsamp_bg(im, 0, im->xsize, y, samples, 3, &bg); + for (x = 0; x < im->xsize; ++x) { + unsigned char tmp = samplep[2]; + samplep[2] = samplep[0]; + samplep[0] = tmp; + samplep += 3; + } + if (i_io_write(ig, samples, line_size) < 0) { i_push_error(0, "writing image data"); myfree(samples); return 0; @@ -615,7 +679,8 @@ write_24bit_data(io_glue *ig, i_img *im) { } myfree(samples); - ig->closecb(ig); + if (i_io_close(ig)) + return 0; return 1; } @@ -632,8 +697,9 @@ Returns non-zero on success. static int read_bmp_pal(io_glue *ig, i_img *im, int count) { int i; - int r, g, b, x; + i_packed_t r, g, b, x; i_color c; + dIMCTXio(ig); for (i = 0; i < count; ++i) { if (!read_packed(ig, "CCCC", &b, &g, &r, &x)) { @@ -663,22 +729,23 @@ Returns the image or NULL. */ static i_img * read_1bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, - int compression, long offbits) { + int compression, long offbits, int allow_incomplete) { i_img *im; - int x, y, lasty, yinc; + int x, y, lasty, yinc, start_y; i_palidx *line, *p; unsigned char *packed; int line_size = (xsize + 7)/8; int bit; unsigned char *in; long base_offset; + dIMCTXio(ig); if (compression != BI_RGB) { - i_push_errorf(0, "unknown 1-bit BMP compression (%d)", compression); + im_push_errorf(aIMCTX, 0, "unknown 1-bit BMP compression (%d)", compression); return NULL; } - if (xsize + 8 < xsize) { /* if there was overflow */ + if ((i_img_dim)((i_img_dim_u)xsize + 8) < xsize) { /* if there was overflow */ /* we check with 8 because we allocate that much for the decoded line buffer */ i_push_error(0, "integer overflow during memory allocation"); @@ -690,27 +757,28 @@ read_1bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, line_size = (line_size+3) / 4 * 4; if (ysize > 0) { - y = ysize-1; + start_y = ysize-1; lasty = -1; yinc = -1; } else { /* when ysize is -ve it's a top-down image */ ysize = -ysize; - y = 0; + start_y = 0; lasty = ysize; yinc = 1; } + y = start_y; if (!clr_used) clr_used = 2; if (clr_used < 0 || clr_used > 2) { - i_push_errorf(0, "out of range colors used (%d)", clr_used); + im_push_errorf(aIMCTX, 0, "out of range colors used (%d)", clr_used); return NULL; } base_offset = FILEHEAD_SIZE + INFOHEAD_SIZE + clr_used * 4; if (offbits < base_offset) { - i_push_errorf(0, "image data offset too small (%ld)", offbits); + im_push_errorf(aIMCTX, 0, "image data offset too small (%ld)", offbits); return NULL; } @@ -727,7 +795,7 @@ read_1bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, rare */ char buffer; while (base_offset < offbits) { - if (ig->readcb(ig, &buffer, 1) != 1) { + if (i_io_read(ig, &buffer, 1) != 1) { i_img_destroy(im); i_push_error(0, "failed skipping to image data offset"); return NULL; @@ -741,12 +809,19 @@ read_1bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, packed = mymalloc(line_size); /* checked 29jun05 tonyc */ line = mymalloc(xsize+8); /* checked 29jun05 tonyc */ while (y != lasty) { - if (ig->readcb(ig, packed, line_size) != line_size) { + if (i_io_read(ig, packed, line_size) != line_size) { myfree(packed); myfree(line); - i_push_error(0, "failed reading 1-bit bmp data"); - i_img_destroy(im); - return NULL; + if (allow_incomplete) { + i_tags_setn(&im->tags, "i_incomplete", 1); + i_tags_setn(&im->tags, "i_lines_read", abs(start_y - y)); + return im; + } + else { + i_push_error(0, "failed reading 1-bit bmp data"); + i_img_destroy(im); + return NULL; + } } in = packed; bit = 0x80; @@ -782,7 +857,7 @@ point. */ static i_img * read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, - int compression, long offbits) { + int compression, long offbits, int allow_incomplete) { i_img *im; int x, y, lasty, yinc; i_palidx *line, *p; @@ -791,34 +866,37 @@ read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, unsigned char *in; int size, i; long base_offset; + int starty; + dIMCTXio(ig); /* line_size is going to be smaller than xsize in most cases (and when it's not, xsize is itself small), and hence not overflow */ line_size = (line_size+3) / 4 * 4; if (ysize > 0) { - y = ysize-1; + starty = ysize-1; lasty = -1; yinc = -1; } else { /* when ysize is -ve it's a top-down image */ ysize = -ysize; - y = 0; + starty = 0; lasty = ysize; yinc = 1; } + y = starty; if (!clr_used) clr_used = 16; if (clr_used > 16 || clr_used < 0) { - i_push_errorf(0, "out of range colors used (%d)", clr_used); + im_push_errorf(aIMCTX, 0, "out of range colors used (%d)", clr_used); return NULL; } base_offset = FILEHEAD_SIZE + INFOHEAD_SIZE + clr_used * 4; if (offbits < base_offset) { - i_push_errorf(0, "image data offset too small (%ld)", offbits); + im_push_errorf(aIMCTX, 0, "image data offset too small (%ld)", offbits); return NULL; } @@ -835,7 +913,7 @@ read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, rare */ char buffer; while (base_offset < offbits) { - if (ig->readcb(ig, &buffer, 1) != 1) { + if (i_io_read(ig, &buffer, 1) != 1) { i_img_destroy(im); i_push_error(0, "failed skipping to image data offset"); return NULL; @@ -853,12 +931,19 @@ read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, if (compression == BI_RGB) { i_tags_add(&im->tags, "bmp_compression_name", 0, "BI_RGB", -1, 0); while (y != lasty) { - if (ig->readcb(ig, packed, line_size) != line_size) { + if (i_io_read(ig, packed, line_size) != line_size) { myfree(packed); myfree(line); - i_push_error(0, "failed reading 4-bit bmp data"); - i_img_destroy(im); - return NULL; + if (allow_incomplete) { + i_tags_setn(&im->tags, "i_incomplete", 1); + i_tags_setn(&im->tags, "i_lines_read", abs(y - starty)); + return im; + } + else { + i_push_error(0, "failed reading 4-bit bmp data"); + i_img_destroy(im); + return NULL; + } } in = packed; p = line; @@ -876,28 +961,45 @@ read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, else if (compression == BI_RLE4) { int read_size; int count; + i_img_dim xlimit = (xsize + 1) / 2 * 2; /* rounded up */ i_tags_add(&im->tags, "bmp_compression_name", 0, "BI_RLE4", -1, 0); x = 0; while (1) { /* there's always at least 2 bytes in a sequence */ - if (ig->readcb(ig, packed, 2) != 2) { + if (i_io_read(ig, packed, 2) != 2) { myfree(packed); myfree(line); - i_push_error(0, "missing data during decompression"); - i_img_destroy(im); - return NULL; + if (allow_incomplete) { + i_tags_setn(&im->tags, "i_incomplete", 1); + i_tags_setn(&im->tags, "i_lines_read", abs(y - starty)); + return im; + } + else { + 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; - } + int count = packed[0]; + if (x + count > xlimit) { + /* this file is corrupt */ + myfree(packed); + myfree(line); + i_push_error(0, "invalid data during decompression"); + im_log((aIMCTX, 1, "read 4-bit: scanline overflow x %d + count %d vs xlimit %d (y %d)\n", + (int)x, count, (int)xlimit, (int)y)); + i_img_destroy(im); + return NULL; + } + /* fill in the line */ + for (i = 0; i < count; i += 2) + line[i] = packed[1] >> 4; + for (i = 1; i < count; i += 2) + line[i] = packed[1] & 0x0F; + i_ppal(im, x, x+count, y, line); + x += count; } else { switch (packed[1]) { case BMPRLE_ENDOFLINE: @@ -911,12 +1013,19 @@ read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, return im; case BMPRLE_DELTA: - if (ig->readcb(ig, packed, 2) != 2) { + if (i_io_read(ig, packed, 2) != 2) { myfree(packed); myfree(line); - i_push_error(0, "missing data during decompression"); - i_img_destroy(im); - return NULL; + if (allow_incomplete) { + i_tags_setn(&im->tags, "i_incomplete", 1); + i_tags_setn(&im->tags, "i_lines_read", abs(y - starty)); + return im; + } + else { + i_push_error(0, "missing data during decompression"); + i_img_destroy(im); + return NULL; + } } x += packed[0]; y += yinc * packed[1]; @@ -924,14 +1033,31 @@ read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, default: count = packed[1]; + if (x + count > xlimit) { + /* this file is corrupt */ + myfree(packed); + myfree(line); + i_push_error(0, "invalid data during decompression"); + im_log((aIMCTX, 1, "read 4-bit: scanline overflow (unpacked) x %d + count %d vs xlimit %d (y %d)\n", + (int)x, count, (int)xlimit, (int)y)); + i_img_destroy(im); + return NULL; + } size = (count + 1) / 2; read_size = (size+1) / 2 * 2; - if (ig->readcb(ig, packed, read_size) != read_size) { + if (i_io_read(ig, packed, read_size) != read_size) { myfree(packed); myfree(line); - i_push_error(0, "missing data during decompression"); - /*i_img_destroy(im);*/ - return im; + if (allow_incomplete) { + i_tags_setn(&im->tags, "i_incomplete", 1); + i_tags_setn(&im->tags, "i_lines_read", abs(y - starty)); + return im; + } + else { + i_push_error(0, "missing data during decompression"); + i_img_destroy(im); + return NULL; + } } for (i = 0; i < size; ++i) { line[0] = packed[i] >> 4; @@ -947,7 +1073,7 @@ read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, else { /*if (compression == BI_RLE4) {*/ myfree(packed); myfree(line); - i_push_errorf(0, "unknown 4-bit BMP compression (%d)", compression); + im_push_errorf(aIMCTX, 0, "unknown 4-bit BMP compression (%d)", compression); i_img_destroy(im); return NULL; } @@ -956,7 +1082,7 @@ read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, } /* -=item read_8bit_bmp(ig, xsize, ysize, clr_used, compression) +=item read_8bit_bmp(ig, xsize, ysize, clr_used, compression, allow_incomplete) Reads in the palette and image data for a 8-bit/pixel image. @@ -966,12 +1092,13 @@ Returns the image or NULL. */ static i_img * read_8bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, - int compression, long offbits) { + int compression, long offbits, int allow_incomplete) { i_img *im; - int x, y, lasty, yinc; + int x, y, lasty, yinc, start_y; i_palidx *line; int line_size = xsize; long base_offset; + dIMCTXio(ig); line_size = (line_size+3) / 4 * 4; if (line_size < xsize) { /* if it overflowed (unlikely, but check) */ @@ -980,27 +1107,28 @@ read_8bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, } if (ysize > 0) { - y = ysize-1; + start_y = ysize-1; lasty = -1; yinc = -1; } else { /* when ysize is -ve it's a top-down image */ ysize = -ysize; - y = 0; + start_y = 0; lasty = ysize; yinc = 1; } + y = start_y; if (!clr_used) clr_used = 256; if (clr_used > 256 || clr_used < 0) { - i_push_errorf(0, "out of range colors used (%d)", clr_used); + im_push_errorf(aIMCTX, 0, "out of range colors used (%d)", clr_used); return NULL; } base_offset = FILEHEAD_SIZE + INFOHEAD_SIZE + clr_used * 4; if (offbits < base_offset) { - i_push_errorf(0, "image data offset too small (%ld)", offbits); + im_push_errorf(aIMCTX, 0, "image data offset too small (%ld)", offbits); return NULL; } @@ -1017,7 +1145,7 @@ read_8bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, rare */ char buffer; while (base_offset < offbits) { - if (ig->readcb(ig, &buffer, 1) != 1) { + if (i_io_read(ig, &buffer, 1) != 1) { i_img_destroy(im); i_push_error(0, "failed skipping to image data offset"); return NULL; @@ -1030,11 +1158,18 @@ read_8bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, if (compression == BI_RGB) { i_tags_add(&im->tags, "bmp_compression_name", 0, "BI_RGB", -1, 0); while (y != lasty) { - if (ig->readcb(ig, line, line_size) != line_size) { + if (i_io_read(ig, line, line_size) != line_size) { myfree(line); - i_push_error(0, "failed reading 8-bit bmp data"); - i_img_destroy(im); - return NULL; + if (allow_incomplete) { + i_tags_setn(&im->tags, "i_incomplete", 1); + i_tags_setn(&im->tags, "i_lines_read", abs(start_y - y)); + return im; + } + else { + i_push_error(0, "failed reading 8-bit bmp data"); + i_img_destroy(im); + return NULL; + } } i_ppal(im, 0, xsize, y, line); y += yinc; @@ -1050,13 +1185,27 @@ read_8bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, x = 0; while (1) { /* there's always at least 2 bytes in a sequence */ - if (ig->readcb(ig, packed, 2) != 2) { + if (i_io_read(ig, packed, 2) != 2) { myfree(line); - i_push_error(0, "missing data during decompression"); - i_img_destroy(im); - return NULL; + if (allow_incomplete) { + i_tags_setn(&im->tags, "i_incomplete", 1); + i_tags_setn(&im->tags, "i_lines_read", abs(start_y-y)); + return im; + } + else { + i_push_error(0, "missing data during decompression"); + i_img_destroy(im); + return NULL; + } } if (packed[0]) { + if (x + packed[0] > xsize) { + /* this file isn't incomplete, it's corrupt */ + myfree(line); + i_push_error(0, "invalid data during decompression"); + i_img_destroy(im); + return NULL; + } memset(line, packed[1], packed[0]); i_ppal(im, x, x+packed[0], y, line); x += packed[0]; @@ -1072,11 +1221,18 @@ read_8bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, return im; case BMPRLE_DELTA: - if (ig->readcb(ig, packed, 2) != 2) { + if (i_io_read(ig, packed, 2) != 2) { myfree(line); - i_push_error(0, "missing data during decompression"); - i_img_destroy(im); - return NULL; + if (allow_incomplete) { + i_tags_setn(&im->tags, "i_incomplete", 1); + i_tags_setn(&im->tags, "i_lines_read", abs(start_y-y)); + return im; + } + else { + i_push_error(0, "missing data during decompression"); + i_img_destroy(im); + return NULL; + } } x += packed[0]; y += yinc * packed[1]; @@ -1084,12 +1240,27 @@ read_8bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, default: count = packed[1]; + if (x + count > xsize) { + /* runs shouldn't cross a line boundary */ + /* this file isn't incomplete, it's corrupt */ + myfree(line); + i_push_error(0, "invalid data during decompression"); + i_img_destroy(im); + return NULL; + } read_size = (count+1) / 2 * 2; - if (ig->readcb(ig, line, read_size) != read_size) { + if (i_io_read(ig, line, read_size) != read_size) { myfree(line); - i_push_error(0, "missing data during decompression"); - i_img_destroy(im); - return NULL; + if (allow_incomplete) { + i_tags_setn(&im->tags, "i_incomplete", 1); + i_tags_setn(&im->tags, "i_lines_read", abs(start_y-y)); + return im; + } + else { + i_push_error(0, "missing data during decompression"); + i_img_destroy(im); + return NULL; + } } i_ppal(im, x, x+count, y, line); x += count; @@ -1100,7 +1271,7 @@ read_8bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, } else { myfree(line); - i_push_errorf(0, "unknown 8-bit BMP compression (%d)", compression); + im_push_errorf(aIMCTX, 0, "unknown 8-bit BMP compression (%d)", compression); i_img_destroy(im); return NULL; } @@ -1111,25 +1282,44 @@ read_8bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, struct bm_masks { unsigned masks[3]; int shifts[3]; + int bits[3]; }; static struct bm_masks std_masks[] = { { /* 16-bit */ - { 0770000, 00007700, 00000077, }, - { 10, 4, -2, }, + { 0076000, 00001740, 00000037, }, + { 10, 5, 0, }, + { 5, 5, 5, } }, { /* 24-bit */ { 0xFF0000, 0x00FF00, 0x0000FF, }, { 16, 8, 0, }, + { 8, 8, 8, }, }, { /* 32-bit */ { 0xFF0000, 0x00FF00, 0x0000FF, }, { 16, 8, 0, }, + { 8, 8, 8, }, }, }; +/* multiplier and shift for converting from N bits to 8 bits */ +struct bm_sampconverts { + int mult; + int shift; +}; +static struct bm_sampconverts samp_converts[] = { + { 0xff, 0 }, /* 1 bit samples */ + { 0x55, 0 }, + { 0111, 1 }, + { 0x11, 0 }, + { 0x21, 2 }, + { 0x41, 4 }, + { 0x81, 6 } /* 7 bit samples */ +}; + /* -=item read_direct_bmp(ig, xsize, ysize, bit_count, clr_used, compression) +=item read_direct_bmp(ig, xsize, ysize, bit_count, clr_used, compression, allow_incomplete) Skips the palette and reads in the image data for a direct colour image. @@ -1139,9 +1329,10 @@ Returns the image or NULL. */ static i_img * read_direct_bmp(io_glue *ig, int xsize, int ysize, int bit_count, - int clr_used, int compression, long offbits) { + int clr_used, int compression, long offbits, + int allow_incomplete) { i_img *im; - int x, y, lasty, yinc; + int x, y, starty, lasty, yinc; i_color *line, *p; int pix_size = bit_count / 8; int line_size = xsize * pix_size; @@ -1153,6 +1344,7 @@ read_direct_bmp(io_glue *ig, int xsize, int ysize, int bit_count, const char *compression_name; int bytes; long base_offset = FILEHEAD_SIZE + INFOHEAD_SIZE; + dIMCTXio(ig); unpack_code[0] = *("v3V"+pix_size-2); unpack_code[1] = '\0'; @@ -1161,17 +1353,18 @@ read_direct_bmp(io_glue *ig, int xsize, int ysize, int bit_count, extras = line_size - xsize * pix_size; if (ysize > 0) { - y = ysize-1; + starty = ysize-1; lasty = -1; yinc = -1; } else { /* when ysize is -ve it's a top-down image */ ysize = -ysize; - y = 0; + starty = 0; lasty = ysize; yinc = 1; } + y = starty; if (compression == BI_RGB) { compression_name = "BI_RGB"; masks = std_masks[pix_size-2]; @@ -1179,7 +1372,7 @@ read_direct_bmp(io_glue *ig, int xsize, int ysize, int bit_count, /* 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) { + if (i_io_read(ig, buf, 4) != 4) { i_push_error(0, "skipping colors"); return 0; } @@ -1187,27 +1380,47 @@ read_direct_bmp(io_glue *ig, int xsize, int ysize, int bit_count, } } else if (compression == BI_BITFIELDS) { - int pos, bit; + int pos; + unsigned bits; compression_name = "BI_BITFIELDS"; for (i = 0; i < 3; ++i) { - if (!read_packed(ig, "V", masks.masks+i)) { + i_packed_t rmask; + if (!read_packed(ig, "V", &rmask)) { i_push_error(0, "reading pixel masks"); return 0; } + if (rmask == 0) { + im_push_errorf(aIMCTX, 0, "Zero mask for channel %d", i); + return NULL; + } + masks.masks[i] = rmask; /* work out a shift for the mask */ pos = 0; - bit = masks.masks[i] & -masks.masks[i]; - while (bit) { + bits = masks.masks[i]; + while (!(bits & 1)) { ++pos; - bit >>= 1; + bits >>= 1; } - masks.shifts[i] = pos - 8; + masks.shifts[i] = pos; + pos = 0; + while (bits & 1) { + ++pos; + bits >>= 1; + } + masks.bits[i] = pos; + /*fprintf(stderr, "%d: mask %08x shift %d bits %d\n", i, masks.masks[i], masks.shifts[i], masks.bits[i]);*/ } - base_offset += 4 * 4; + /* account for the masks */ + base_offset += 3 * 4; } else { - i_push_errorf(0, "unknown 24-bit BMP compression (%d)", compression); + im_push_errorf(aIMCTX, 0, "unknown 24-bit BMP compression (%d)", compression); + return NULL; + } + + if (offbits < base_offset) { + im_push_errorf(aIMCTX, 0, "image data offset too small (%ld)", offbits); return NULL; } @@ -1216,7 +1429,7 @@ read_direct_bmp(io_glue *ig, int xsize, int ysize, int bit_count, rare */ char buffer; while (base_offset < offbits) { - if (ig->readcb(ig, &buffer, 1) != 1) { + if (i_io_read(ig, &buffer, 1) != 1) { i_push_error(0, "failed skipping to image data offset"); return NULL; } @@ -1242,24 +1455,36 @@ read_direct_bmp(io_glue *ig, int xsize, int ysize, int bit_count, while (y != lasty) { p = line; for (x = 0; x < xsize; ++x) { - unsigned pixel; + i_packed_t pixel; if (!read_packed(ig, unpack_code, &pixel)) { - i_push_error(0, "failed reading image data"); myfree(line); - i_img_destroy(im); - return NULL; + if (allow_incomplete) { + i_tags_setn(&im->tags, "i_incomplete", 1); + i_tags_setn(&im->tags, "i_lines_read", abs(starty - y)); + return im; + } + else { + i_push_error(0, "failed reading image data"); + 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]; + int sample = (pixel & masks.masks[i]) >> masks.shifts[i]; + int bits = masks.bits[i]; + if (bits < 8) { + sample = (sample * samp_converts[bits-1].mult) >> samp_converts[bits-1].shift; + } + else if (bits) { + sample >>= bits - 8; + } + p->channel[i] = sample; } ++p; } i_plin(im, 0, xsize, y, line); if (extras) - ig->readcb(ig, junk, extras); + i_io_read(ig, junk, extras); y += yinc; } myfree(line);