From 9c106321e22c5a74d0e5d946b452db24e1c9d6f7 Mon Sep 17 00:00:00 2001 From: Tony Cook Date: Sat, 30 Dec 2006 21:44:33 +0000 Subject: [PATCH 1/1] Various changes: - fix drawing text on 2/4 channel images for FT2 - reading ASCII PBMs was broken, it assumed there was whitespace between samples but there doesn't need to be - add makemap type of mono/monochrome for producing monochrome images - roughly tripled speed of reading any sort of PNM - reading a pnm can now return a partial image if you set allow_partial - reading a bmp can now return a partial image if you set allow_partial - we can now read 16-bit/sample binary PGM/PPM images - we can now write 16-bit/sample binary PGM/PPM files if explicitly requested (since GIMP can't read them) - reading a tiff will now only return an incomplete image if you set allow_partial - some documentation reformatting --- Imager.pm | 10 +- Imager.xs | 13 +- MANIFEST | 1 + Makefile.PL | 2 +- TODO | 8 +- bmp.c | 184 ++++++++---- freetyp2.c | 31 +- image.c | 69 +++++ imager.h | 9 +- imdatatypes.h | 3 + imrender.h | 14 + lib/Imager/Files.pod | 38 ++- lib/Imager/ImageTypes.pod | 68 +++-- lib/Imager/Test.pm | 155 +++++++++- pnm.c | 616 ++++++++++++++++++++++++++++++-------- quant.c | 18 ++ render.im | 204 +++++++++++++ rendert.h | 12 + t/t104ppm.t | 308 +++++++++++++++++-- t/t106tiff.t | 3 +- t/t15color.t | 2 - testimg/bad_asc.pbm | 4 + testimg/bad_asc.pgm | 5 + testimg/bad_asc.ppm | 5 + testimg/maxval_256.ppm | Bin 69 -> 75 bytes testimg/pbm_base.pgm | Bin 0 -> 89 bytes testimg/pgm.pgm | 5 + testimg/short_asc.pbm | 4 + testimg/short_asc.pgm | 5 + testimg/short_asc.ppm | 5 + testimg/short_bin.pbm | 3 + testimg/short_bin.pgm | 5 + testimg/short_bin.ppm | 5 + testimg/short_bin16.pgm | 5 + testimg/short_bin16.ppm | 5 + tiff.c | 33 +- 36 files changed, 1577 insertions(+), 280 deletions(-) create mode 100644 imrender.h create mode 100644 render.im create mode 100644 rendert.h create mode 100644 testimg/bad_asc.pbm create mode 100644 testimg/bad_asc.pgm create mode 100644 testimg/bad_asc.ppm create mode 100644 testimg/pbm_base.pgm create mode 100644 testimg/pgm.pgm create mode 100644 testimg/short_asc.pbm create mode 100644 testimg/short_asc.pgm create mode 100644 testimg/short_asc.ppm create mode 100644 testimg/short_bin.pbm create mode 100644 testimg/short_bin.pgm create mode 100644 testimg/short_bin.ppm create mode 100644 testimg/short_bin16.pgm create mode 100644 testimg/short_bin16.ppm diff --git a/Imager.pm b/Imager.pm index 3bfbad14..bde12587 100644 --- a/Imager.pm +++ b/Imager.pm @@ -1269,11 +1269,13 @@ sub read { return $self; } + my $allow_partial = $input{allow_partial}; + defined $allow_partial or $allow_partial = 0; + if ( $input{'type'} eq 'tiff' ) { my $page = $input{'page'}; defined $page or $page = 0; - # Fixme, check if that length parameter is ever needed - $self->{IMG}=i_readtiff_wiol( $IO, -1, $page ); + $self->{IMG}=i_readtiff_wiol( $IO, $allow_partial, $page ); if ( !defined($self->{IMG}) ) { $self->{ERRSTR}=$self->_error_as_msg(); return undef; } @@ -1282,7 +1284,7 @@ sub read { } if ( $input{'type'} eq 'pnm' ) { - $self->{IMG}=i_readpnm_wiol( $IO, -1 ); # Fixme, check if that length parameter is ever needed + $self->{IMG}=i_readpnm_wiol( $IO, $allow_partial ); if ( !defined($self->{IMG}) ) { $self->{ERRSTR}='unable to read pnm image: '._error_as_msg(); return undef; @@ -1301,7 +1303,7 @@ sub read { } if ( $input{'type'} eq 'bmp' ) { - $self->{IMG}=i_readbmp_wiol( $IO ); + $self->{IMG}=i_readbmp_wiol( $IO, $allow_partial ); if ( !defined($self->{IMG}) ) { $self->{ERRSTR}=$self->_error_as_msg(); return undef; diff --git a/Imager.xs b/Imager.xs index d2ea2cf7..3997a1e5 100644 --- a/Imager.xs +++ b/Imager.xs @@ -535,6 +535,8 @@ static struct value_name make_color_names[] = { "webmap", mc_web_map, }, { "addi", mc_addi, }, { "mediancut", mc_median_cut, }, + { "mono", mc_mono, }, + { "monochrome", mc_mono, }, }; static struct value_name translate_names[] = @@ -2250,9 +2252,9 @@ i_test_format_probe(ig, length) #ifdef HAVE_LIBTIFF Imager::ImgRaw -i_readtiff_wiol(ig, length, page=0) +i_readtiff_wiol(ig, allow_partial, page=0) Imager::IO ig - int length + int allow_partial int page void @@ -2881,9 +2883,9 @@ i_readgif_multi_wiol(ig) Imager::ImgRaw -i_readpnm_wiol(ig, length) +i_readpnm_wiol(ig, allow_partial) Imager::IO ig - int length + int allow_partial undef_int @@ -2912,8 +2914,9 @@ i_writebmp_wiol(im,ig) Imager::IO ig Imager::ImgRaw -i_readbmp_wiol(ig) +i_readbmp_wiol(ig, allow_partial=0) Imager::IO ig + int allow_partial undef_int diff --git a/MANIFEST b/MANIFEST index 9a171659..52e28861 100644 --- a/MANIFEST +++ b/MANIFEST @@ -173,6 +173,7 @@ raw.c regmach.c regmach.h regops.perl +render.im rgb.c Reading and writing SGI rgb files rotate.c rubthru.im diff --git a/Makefile.PL b/Makefile.PL index e748d233..f55e7d30 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -157,7 +157,7 @@ my @objs = qw(Imager.o draw.o polygon.o image.o io.o iolayer.o regmach.o trans2.o quant.o error.o convert.o map.o tags.o palimg.o maskimg.o img16.o rotate.o bmp.o tga.o rgb.o color.o fills.o imgdouble.o limits.o hlines.o - imext.o scale.o rubthru.o); + imext.o scale.o rubthru.o render.o); $Recommends{Imager} = { 'Parse::RecDescent' => 0 }; diff --git a/TODO b/TODO index f1d20815..dd09fddd 100644 --- a/TODO +++ b/TODO @@ -187,7 +187,7 @@ MultiImage & metadata support: New Features: - Add mng support, pcx and aalib support. - - Windows icon files (.ico) + - Windows icon files (.ico) (done) - ILBM (Amiga) images - photoshop files (I think I've seen docs) - XBM @@ -209,7 +209,7 @@ New Features: (or even from an existing bold or slanted font) - utf8 support for text output - (available for FT1, freetype2, should be easy for Win32) + (available for FT1, freetype2, T1, Win32) - easy interfaces for text output: - align text around point, including: @@ -304,7 +304,7 @@ Format specific issues: - provide patches for libgif and libungif that fix their bugs and give a useful extension interface. Probe for the installation of the patches in Makefile.PL to let gif.c - know what features it can use. + know what features it can use. (no need anymore) - Add options for pnm writer to save in any of the p1..P6 formats. Even if the input has 1 channel, write 3 and such @@ -322,7 +322,7 @@ Format specific issues: - read more metadata from images, esp tiff tags, EXIF format information from TIFF and JPEG. -- handle 16-bit/sample pgm/ppm files +- handle 16-bit/sample pgm/ppm files (done) - "jpeg lossless rotation" - directly manipulates the JPEG representation to rotate, scale or in some limited cases, crop an diff --git a/bmp.c b/bmp.c index 0e8dc62c..94fe6ffb 100644 --- a/bmp.c +++ b/bmp.c @@ -45,14 +45,14 @@ 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_partial); 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_partial); 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_partial); 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_partial); /* =item i_writebmp_wiol(im, io_glue) @@ -102,7 +102,7 @@ BI_BITFIELDS images too, but I need a test image. */ i_img * -i_readbmp_wiol(io_glue *ig) { +i_readbmp_wiol(io_glue *ig, int allow_partial) { 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; @@ -140,22 +140,25 @@ i_readbmp_wiol(io_glue *ig) { 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_partial); 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_partial); 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_partial); break; case 32: case 24: case 16: im = read_direct_bmp(ig, xsize, ysize, bit_count, clr_used, compression, - offbits); + offbits, allow_partial); break; default: @@ -663,9 +666,9 @@ 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_partial) { 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; @@ -690,17 +693,18 @@ 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) { @@ -744,9 +748,16 @@ read_1bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, if (ig->readcb(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_partial) { + 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 +793,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_partial) { i_img *im; int x, y, lasty, yinc; i_palidx *line, *p; @@ -791,23 +802,25 @@ read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, unsigned char *in; int size, i; long base_offset; + int starty; /* 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; @@ -856,9 +869,16 @@ read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, if (ig->readcb(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_partial) { + 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; @@ -884,9 +904,16 @@ read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, 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; + if (allow_partial) { + 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; @@ -914,9 +941,16 @@ read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, 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; + if (allow_partial) { + 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]; @@ -929,9 +963,16 @@ read_4bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, 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; + if (allow_partial) { + 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; @@ -956,7 +997,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_partial) Reads in the palette and image data for a 8-bit/pixel image. @@ -966,9 +1007,9 @@ 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_partial) { 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; @@ -980,17 +1021,18 @@ 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) { @@ -1032,9 +1074,16 @@ read_8bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, while (y != lasty) { if (ig->readcb(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_partial) { + 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; @@ -1052,9 +1101,16 @@ read_8bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, /* 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 (allow_partial) { + 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]) { memset(line, packed[1], packed[0]); @@ -1074,9 +1130,16 @@ read_8bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, 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; + if (allow_partial) { + 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]; @@ -1087,9 +1150,16 @@ read_8bit_bmp(io_glue *ig, int xsize, int ysize, int clr_used, 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; + if (allow_partial) { + 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; @@ -1129,7 +1199,7 @@ static struct bm_masks std_masks[] = }; /* -=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_partial) Skips the palette and reads in the image data for a direct colour image. @@ -1139,7 +1209,8 @@ 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_partial) { i_img *im; int x, y, lasty, yinc; i_color *line, *p; @@ -1244,10 +1315,17 @@ read_direct_bmp(io_glue *ig, int xsize, int ysize, int bit_count, for (x = 0; x < xsize; ++x) { unsigned 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_partial) { + i_tags_setn(&im->tags, "i_incomplete", 1); + i_tags_setn(&im->tags, "i_lines_read", lasty - 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) diff --git a/freetyp2.c b/freetyp2.c index 6fc3caab..e7625b9a 100644 --- a/freetyp2.c +++ b/freetyp2.c @@ -645,6 +645,7 @@ i_ft2_text(FT2_Fonthandle *handle, i_img *im, int tx, int ty, const i_color *cl, int ch; i_color pel; int loadFlags = FT_LOAD_DEFAULT; + i_render render; mm_log((1, "i_ft2_text(handle %p, im %p, tx %d, ty %d, cl %p, cheight %f, cwidth %f, text %p, len %d, align %d, aa %d)\n", handle, im, tx, ty, cl, cheight, cwidth, text, align, aa)); @@ -663,6 +664,9 @@ i_ft2_text(FT2_Fonthandle *handle, i_img *im, int tx, int ty, const i_color *cl, if (!i_ft2_bbox(handle, cheight, cwidth, text, len, bbox, utf8)) return 0; + if (aa) + i_render_init(&render, im, bbox[BBOX_POS_WIDTH] - bbox[BBOX_NEG_WIDTH]); + if (!align) { /* this may need adjustment */ tx -= bbox[0] * handle->matrix[0] + bbox[5] * handle->matrix[1] + handle->matrix[2]; @@ -688,6 +692,8 @@ i_ft2_text(FT2_Fonthandle *handle, i_img *im, int tx, int ty, const i_color *cl, ft2_push_message(error); i_push_errorf(0, "loading glyph for character \\x%02x (glyph 0x%04X)", c, index); + if (aa) + i_render_done(&render); return 0; } slot = handle->face->glyph; @@ -698,6 +704,8 @@ i_ft2_text(FT2_Fonthandle *handle, i_img *im, int tx, int ty, const i_color *cl, if (error) { ft2_push_message(error); i_push_errorf(0, "rendering glyph 0x%04X (character \\x%02X)"); + if (aa) + i_render_done(&render); return 0; } if (slot->bitmap.pixel_mode == ft_pixel_mode_mono) { @@ -728,20 +736,16 @@ i_ft2_text(FT2_Fonthandle *handle, i_img *im, int tx, int ty, const i_color *cl, last_mode = slot->bitmap.pixel_mode; last_grays = slot->bitmap.num_grays; } - + bmp = slot->bitmap.buffer; for (y = 0; y < slot->bitmap.rows; ++y) { - for (x = 0; x < slot->bitmap.width; ++x) { - int value = map[bmp[x]]; - if (value) { - i_gpix(im, tx+x+slot->bitmap_left, ty+y-slot->bitmap_top, &pel); - for (ch = 0; ch < im->channels; ++ch) { - pel.channel[ch] = - ((255-value)*pel.channel[ch] + value * cl->channel[ch]) / 255; - } - i_ppix(im, tx+x+slot->bitmap_left, ty+y-slot->bitmap_top, &pel); - } - } + if (last_mode == ft_pixel_mode_grays && + last_grays != 255) { + for (x = 0; x < slot->bitmap.width; ++x) + bmp[x] = map[bmp[x]]; + } + i_render_color(&render, tx + slot->bitmap_left, ty-slot->bitmap_top+y, + slot->bitmap.width, bmp, cl); bmp += slot->bitmap.pitch; } } @@ -751,6 +755,9 @@ i_ft2_text(FT2_Fonthandle *handle, i_img *im, int tx, int ty, const i_color *cl, ty -= slot->advance.y / 64; } + if (aa) + i_render_done(&render); + return 1; } diff --git a/image.c b/image.c index 316982af..03a06f44 100644 --- a/image.c +++ b/image.c @@ -2153,8 +2153,77 @@ i_test_format_probe(io_glue *data, int length) { return NULL; } +/* +=item i_img_is_monochrome(img, &zero_is_white) + +Tests an image to check it meets our monochrome tests. + +The idea is that a file writer can use this to test where it should +write the image in whatever bi-level format it uses, eg. pbm for pnm. + +For performance of encoders we require monochrome images: + +=over + +=item * +be paletted +=item * + +have a palette of two colors, containing only (0,0,0) and +(255,255,255) in either order. + +=back + +zero_is_white is set to non-zero iff the first palette entry is white. + +=cut +*/ + +int +i_img_is_monochrome(i_img *im, int *zero_is_white) { + if (im->type == i_palette_type + && i_colorcount(im) == 2) { + i_color colors[2]; + i_getcolors(im, 0, colors, 2); + if (im->channels == 3) { + if (colors[0].rgb.r == 255 && + colors[0].rgb.g == 255 && + colors[0].rgb.b == 255 && + colors[1].rgb.r == 0 && + colors[1].rgb.g == 0 && + colors[1].rgb.b == 0) { + *zero_is_white = 0; + return 1; + } + else if (colors[0].rgb.r == 0 && + colors[0].rgb.g == 0 && + colors[0].rgb.b == 0 && + colors[1].rgb.r == 255 && + colors[1].rgb.g == 255 && + colors[1].rgb.b == 255) { + *zero_is_white = 1; + return 1; + } + } + else if (im->channels == 1) { + if (colors[0].channel[0] == 255 && + colors[1].channel[1] == 0) { + *zero_is_white = 0; + return 1; + } + else if (colors[0].channel[0] == 0 && + colors[0].channel[0] == 255) { + *zero_is_white = 1; + return 1; + } + } + } + + *zero_is_white = 0; + return 0; +} /* =back diff --git a/imager.h b/imager.h index 613777b8..5608411f 100644 --- a/imager.h +++ b/imager.h @@ -352,6 +352,7 @@ extern i_img *i_img_16_new_low(i_img *im, int x, int y, int ch); extern i_img *i_img_double_new(int x, int y, int ch); extern i_img *i_img_double_new_low(i_img *im, int x, int y, int ch); +extern int i_img_is_monochrome(i_img *im, int *zero_is_white); const char * i_test_format_probe(io_glue *data, int length); @@ -363,7 +364,7 @@ undef_int i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor); #endif /* HAVE_LIBJPEG */ #ifdef HAVE_LIBTIFF -i_img * i_readtiff_wiol(io_glue *ig, int length, int page); +i_img * i_readtiff_wiol(io_glue *ig, int allow_partial, int page); i_img ** i_readtiff_multi_wiol(io_glue *ig, int length, int *count); undef_int i_writetiff_wiol(i_img *im, io_glue *ig); undef_int i_writetiff_multi_wiol(io_glue *ig, i_img **imgs, int count); @@ -401,11 +402,11 @@ void i_qdist(i_img *im); i_img * i_readraw_wiol(io_glue *ig, int x, int y, int datachannels, int storechannels, int intrl); undef_int i_writeraw_wiol(i_img* im, io_glue *ig); -i_img * i_readpnm_wiol(io_glue *ig, int length); +i_img * i_readpnm_wiol(io_glue *ig, int allow_partial); undef_int i_writeppm_wiol(i_img *im, io_glue *ig); extern int i_writebmp_wiol(i_img *im, io_glue *ig); -extern i_img *i_readbmp_wiol(io_glue *ig); +extern i_img *i_readbmp_wiol(io_glue *ig, int allow_partial); int tga_header_verify(unsigned char headbuf[18]); @@ -560,4 +561,6 @@ void malloc_state(void); #endif /* IMAGER_MALLOC_DEBUG */ +#include "imrender.h" + #endif diff --git a/imdatatypes.h b/imdatatypes.h index 44d85381..0cc440c6 100644 --- a/imdatatypes.h +++ b/imdatatypes.h @@ -387,6 +387,7 @@ typedef enum i_make_colors_tag { mc_web_map, /* Use the 216 colour web colour map */ mc_addi, /* Addi's algorithm */ mc_median_cut, /* median cut - similar to giflib, hopefully */ + mc_mono, /* fixed mono color map */ mc_mask = 0xFF /* (mask for generator) */ } i_make_colors; @@ -518,5 +519,7 @@ enum { #include "iolayert.h" +#include "rendert.h" + #endif diff --git a/imrender.h b/imrender.h new file mode 100644 index 00000000..213362d1 --- /dev/null +++ b/imrender.h @@ -0,0 +1,14 @@ +#ifndef IMAGER_IMRENDER_H +#define IMAGER_IMRENDER_H + +#include "rendert.h" + +extern void +i_render_init(i_render *r, i_img *im, int width); +extern void +i_render_done(i_render *r); +extern void +i_render_color(i_render *r, int x, int y, int width, unsigned char const *src, + i_color const *color); + +#endif diff --git a/lib/Imager/Files.pod b/lib/Imager/Files.pod index 08f96a14..6089b384 100644 --- a/lib/Imager/Files.pod +++ b/lib/Imager/Files.pod @@ -52,6 +52,10 @@ supply the filename: $img->read(file => $filename) or die "Cannot read $filename: ", $img->errstr; +The read() method accepts the C parameter. If this is +non-zero then read() can return true on an incomplete image and set +the C tag. + =item write and the C method to write an image: @@ -362,6 +366,38 @@ Imager can read both the ASCII and binary versions of each of the PBM PNM does not support the spatial resolution tags. +The following tags are set when reading a PNM file: + +=over + +=item * + +Xpnm_maxval - the maxvals number from the PGM/PPM header. +Always set to 2 for a PBM file. + +=item * + +Xpnm_type - the type number from the PNM header, 1 for ASCII +PBM files, 2 for ASCII PGM files, 3 for ASCII PPM files, 4 for binary +PBM files, 5 for binary PGM files, 6 for binary PPM files. + +=back + +The following tag is checked when writing an image with more than +8-bits/sample: + +=over + +=item * + +Xpnm_write_wide_data - if this is non-zero then +write() can write PGM/PPM files with 16-bits/sample. Some +applications, for example GIMP 2.2, and tools can only read +8-bit/sample binary PNM files, so Imager will only write a 16-bit +image when this tag is non-zero. + +=back + =head2 JPEG You can supply a C parameter (0-100) when writing a JPEG @@ -383,7 +419,7 @@ to control output: =item jpeg_density_unit The value of the density unit field in the JFIF header. This is -ignored on writing if the i_aspect_only tag is non-zero. +ignored on writing if the C tag is non-zero. The C and C tags are expressed in pixels per inch no matter the value of this tag, they will be converted to/from the value diff --git a/lib/Imager/ImageTypes.pod b/lib/Imager/ImageTypes.pod index b888e37c..f6a2ecdc 100644 --- a/lib/Imager/ImageTypes.pod +++ b/lib/Imager/ImageTypes.pod @@ -679,13 +679,12 @@ some standard information. =over -=item i_xres - -=item i_yres +=item * -The spatial resolution of the image in pixels per inch. If the image -format uses a different scale, eg. pixels per meter, then this value -is converted. A floating point number stored as a string. +XXXXi_xres, i_yres +- The spatial resolution of the image in pixels per inch. If the +image format uses a different scale, eg. pixels per meter, then this +value is converted. A floating point number stored as a string. # our image was generated as a 300 dpi image $img->settag(name => 'i_xres', value => 300); @@ -697,28 +696,39 @@ is converted. A floating point number stored as a string. $img->settag(name => 'i_xres', value => 100 * 2.54); $img->settag(name => 'i_yres', value => 100 * 2.54); -=item i_aspect_only +=item * + +XXi_aspect_only - If this is +non-zero then the values in i_xres and i_yres are treated as a ratio +only. If the image format does not support aspect ratios then this is +scaled so the smaller value is 72dpi. -If this is non-zero then the values in i_xres and i_yres are treated -as a ratio only. If the image format does not support aspect ratios -then this is scaled so the smaller value is 72dpi. +=item * + +XXi_incomplete - If this tag is +present then the whole image could not be read. This isn't +implemented for all images yet, and may not be. -=item i_incomplete +=item * -If this tag is present then the whole image could not be read. This -isn't implemented for all images yet, and may not be. +XXi_lines_read - If +C is set then this tag may be set to the number of +scanlines successfully read from the file. This can be used to decide +whether an image is worth processing. -=item i_format +=item * -The file format this file was read from. +XXi_format - The file format this file +was read from. =back =head2 Quantization options -These options can be specified when calling write_multi() for gif -files, when writing a single image with the gifquant option set to -'gen', or for direct calls to i_writegif_gen and i_writegif_callback. +These options can be specified when calling +L, write_multi() for gif files, when +writing a single image with the gifquant option set to 'gen', or for +direct calls to i_writegif_gen and i_writegif_callback. =over @@ -836,23 +846,29 @@ change. Possible values are: =over -=item none +=item * -Only colors supplied in 'colors' are used. +none - only colors supplied in 'colors' are used. -=item webmap +=item * -The web color map is used (need url here.) +webmap - the web color map is used (need url here.) -=item addi +=item * -The original code for generating the color map (Addi's code) is used. +addi - The original code for generating the color map (Addi's code) is +used. -=item mediancut +=item * -Uses a mediancut algorithm, faster than 'addi', but not as good a +mediancut - Uses a mediancut algorithm, faster than 'addi', but not as good a result. +=item * + +mono, monochrome - a fixed black and white palette, suitable for +producing bi-level images (eg. facsimile) + =back Other methods may be added in the future. diff --git a/lib/Imager/Test.pm b/lib/Imager/Test.pm index 485b0f6e..5d960f9b 100644 --- a/lib/Imager/Test.pm +++ b/lib/Imager/Test.pm @@ -4,7 +4,7 @@ use Test::Builder; require Exporter; use vars qw(@ISA @EXPORT_OK); @ISA = qw(Exporter); -@EXPORT_OK = qw(diff_text_with_nul); +@EXPORT_OK = qw(diff_text_with_nul test_image_raw test_image_16 is_color3 is_color1 is_image); sub diff_text_with_nul { my ($desc, $text1, $text2, @params) = @_; @@ -25,6 +25,145 @@ sub diff_text_with_nul { "$desc - check result different"); } +sub is_color3($$$$$) { + my ($color, $red, $green, $blue, $comment) = @_; + + my $builder = Test::Builder->new; + + unless (defined $color) { + $builder->ok(0, $comment); + $builder->diag("color is undef"); + return; + } + unless ($color->can('rgba')) { + $builder->ok(0, $comment); + $builder->diag("color is not a color object"); + return; + } + + my ($cr, $cg, $cb) = $color->rgba; + unless ($builder->ok($cr == $red && $cg == $green && $cb == $blue, $comment)) { + $builder->diag(<new; + + unless (defined $color) { + $builder->ok(0, $comment); + $builder->diag("color is undef"); + return; + } + unless ($color->can('rgba')) { + $builder->ok(0, $comment); + $builder->diag("color is not a color object"); + return; + } + + my ($cgrey) = $color->rgba; + unless ($builder->ok($cgrey == $grey, $comment)) { + $builder->diag(<new(0, 255, 0, 255); + my $blue = Imager::Color->new(0, 0, 255, 255); + my $red = Imager::Color->new(255, 0, 0, 255); + my $img = Imager->new(xsize => 150, ysize => 150, bits => 16); + $img->box(filled => 1, color => $green, box => [ 70, 25, 130, 125 ]); + $img->box(filled => 1, color => $blue, box => [ 20, 25, 80, 125 ]); + $img->arc(x => 75, y => 75, r => 30, color => $red); + $img->filter(type => 'conv', coef => [ 0.1, 0.2, 0.4, 0.2, 0.1 ]); + + $img; +} + +sub is_image($$$) { + my ($left, $right, $comment) = @_; + + my $builder = Test::Builder->new; + + unless (defined $left) { + $builder->ok(0, $comment); + $builder->diag("left is undef"); + return; + } + unless (defined $right) { + $builder->ok(0, $comment); + $builder->diag("right is undef"); + return; + } + unless ($left->{IMG}) { + $builder->ok(0, $comment); + $builder->diag("left image has no low level object"); + return; + } + unless ($right->{IMG}) { + $builder->ok(0, $comment); + $builder->diag("right image has no low level object"); + return; + } + unless ($left->getwidth == $right->getwidth) { + $builder->ok(0, $comment); + $builder->diag("left width " . $left->getwidth . " vs right width " + . $right->getwidth); + return; + } + unless ($left->getheight == $right->getheight) { + $builder->ok(0, $comment); + $builder->diag("left height " . $left->getheight . " vs right height " + . $right->getheight); + return; + } + unless ($left->getchannels == $right->getchannels) { + $builder->ok(0, $comment); + $builder->diag("left channels " . $left->getchannels . " vs right channels " + . $right->getchannels); + return; + } + my $diff = Imager::i_img_diff($left->{IMG}, $right->{IMG}); + unless ($diff == 0) { + $builder->ok(0, $comment); + $builder->diag("image data different - $diff"); + return; + } + + return $builder->ok(1, $comment); +} + 1; __END__ @@ -51,7 +190,19 @@ No functions are exported by default. =over -=item diff_text_with_nul($test_name, $text1, $text2, @optios) +=item is_color3($color, $red, $blue, $green, $comment) + +Tests is $color matches the given ($red, $blue, $green) + +=item test_image_raw() + +Returns a 150x150x3 Imager::ImgRaw test image. + +=item test_image_16() + +Returns a 150x150x3 16-bit/sample OO test image. + +=item diff_text_with_nul($test_name, $text1, $text2, @options) Creates 2 test images and writes $text1 to the first image and $text2 to the second image with the string() method. Each call adds 3 ok/not diff --git a/pnm.c b/pnm.c index e5d30504..d777e21d 100644 --- a/pnm.c +++ b/pnm.c @@ -15,7 +15,7 @@ pnm.c - implements reading and writing ppm/pnm/pbm files, uses io layer. =head1 SYNOPSIS io_glue *ig = io_new_fd( fd ); - i_img *im = i_readpnm_wiol(ig, -1); // no limit on how much is read + i_img *im = i_readpnm_wiol(ig, 0); // no limit on how much is read // or io_glue *ig = io_new_fd( fd ); return_code = i_writepnm_wiol(im, ig); @@ -75,9 +75,11 @@ Returns a pointer to the byte or NULL on failure (internal). =cut */ +#define gnext(mb) (((mb)->cp == (mb)->len) ? gnextf(mb) : (mb)->buf + (mb)->cp++) + static char * -gnext(mbuf *mb) { +gnextf(mbuf *mb) { io_glue *ig = mb->ig; if (mb->cp == mb->len) { mb->cp = 0; @@ -88,7 +90,6 @@ gnext(mbuf *mb) { return NULL; } if (mb->len == 0) { - i_push_error(errno, "unexpected end of file"); mm_log((1, "i_readpnm: end of file\n")); return NULL; } @@ -108,9 +109,11 @@ the byte or NULL on failure (internal). =cut */ +#define gpeek(mb) ((mb)->cp == (mb)->len ? gpeekf(mb) : (mb)->buf + (mb)->cp) + static char * -gpeek(mbuf *mb) { +gpeekf(mbuf *mb) { io_glue *ig = mb->ig; if (mb->cp == mb->len) { mb->cp = 0; @@ -121,7 +124,6 @@ gpeek(mbuf *mb) { return NULL; } if (mb->len == 0) { - i_push_error(0, "unexpected end of file"); mm_log((1, "i_readpnm: end of file\n")); return NULL; } @@ -129,7 +131,27 @@ gpeek(mbuf *mb) { return &mb->buf[mb->cp]; } - +int +gread(mbuf *mb, unsigned char *buf, size_t read_size) { + int total_read = 0; + if (mb->cp != mb->len) { + int avail_size = mb->len - mb->cp; + int use_size = read_size > avail_size ? avail_size : read_size; + memcpy(buf, mb->buf+mb->cp, use_size); + mb->cp += use_size; + total_read += use_size; + read_size -= use_size; + buf += use_size; + } + if (read_size) { + io_glue *ig = mb->ig; + int read_res = i_io_read(ig, buf, read_size); + if (read_res >= 0) { + total_read += read_res; + } + } + return total_read; +} /* @@ -202,6 +224,10 @@ gnum(mbuf *mb, int *i) { if (!skip_spaces(mb)) return 0; + if (!(cp = gpeek(mb))) + return 0; + if (!misnumber(*cp)) + return 0; while( (cp = gpeek(mb)) && misnumber(*cp) ) { *i = *i*10+(*cp-'0'); cp = gnext(mb); @@ -209,35 +235,307 @@ gnum(mbuf *mb, int *i) { return 1; } +static +i_img * +read_pgm_ppm_bin8(mbuf *mb, i_img *im, int width, int height, + int channels, int maxval, int allow_partial) { + i_color *line, *linep; + int read_size; + unsigned char *read_buf, *readp; + int x, y, ch; + int rounder = maxval / 2; + + line = mymalloc(width * sizeof(i_color)); + read_size = channels * width; + read_buf = mymalloc(read_size); + for(y=0;ytags, "i_incomplete", 1); + i_tags_setn(&im->tags, "i_lines_read", y); + return im; + } + else { + i_push_error(0, "short read - file truncated?"); + i_img_destroy(im); + return NULL; + } + } + if (maxval == 255) { + for(x=0; xchannel[ch] = *readp++; + } + ++linep; + } + } + else { + for(x=0; x maxval) + sample = maxval; + linep->channel[ch] = (sample * 255 + rounder) / maxval; + } + ++linep; + } + } + i_plin(im, 0, width, y, line); + } + myfree(read_buf); + myfree(line); + + return im; +} + +static +i_img * +read_pgm_ppm_bin16(mbuf *mb, i_img *im, int width, int height, + int channels, int maxval, int allow_partial) { + i_fcolor *line, *linep; + int read_size; + unsigned char *read_buf, *readp; + int x, y, ch; + double maxvalf = maxval; + + line = mymalloc(width * sizeof(i_fcolor)); + read_size = channels * width * 2; + read_buf = mymalloc(read_size); + for(y=0;ytags, "i_incomplete", 1); + i_tags_setn(&im->tags, "i_lines_read", y); + return im; + } + else { + i_push_error(0, "short read - file truncated?"); + i_img_destroy(im); + return NULL; + } + } + for(x=0; x maxval) + sample = maxval; + readp += 2; + linep->channel[ch] = sample / maxvalf; + } + ++linep; + } + i_plinf(im, 0, width, y, line); + } + myfree(read_buf); + myfree(line); + + return im; +} + +static +i_img * +read_pbm_bin(mbuf *mb, i_img *im, int width, int height, int allow_partial) { + i_palidx *line, *linep; + int read_size; + unsigned char *read_buf, *readp; + int x, y; + unsigned mask; + + line = mymalloc(width * sizeof(i_palidx)); + read_size = (width + 7) / 8; + read_buf = mymalloc(read_size); + for(y = 0; y < height; y++) { + if (gread(mb, read_buf, read_size) != read_size) { + myfree(line); + myfree(read_buf); + if (allow_partial) { + i_tags_setn(&im->tags, "i_incomplete", 1); + i_tags_setn(&im->tags, "i_lines_read", y); + return im; + } + else { + i_push_error(0, "short read - file truncated?"); + i_img_destroy(im); + return NULL; + } + } + linep = line; + readp = read_buf; + mask = 0x80; + for(x = 0; x < width; ++x) { + *linep++ = *readp & mask ? 1 : 0; + mask >>= 1; + if (mask == 0) { + ++readp; + mask = 0x80; + } + } + i_ppal(im, 0, width, y, line); + } + myfree(read_buf); + myfree(line); + + return im; +} + +/* unlike pgm/ppm pbm: + - doesn't require spaces between samples (bits) + - 1 (maxval) is black instead of white +*/ +static +i_img * +read_pbm_ascii(mbuf *mb, i_img *im, int width, int height, int allow_partial) { + i_palidx *line, *linep; + int x, y; + + line = mymalloc(width * sizeof(i_palidx)); + for(y = 0; y < height; y++) { + linep = line; + for(x = 0; x < width; ++x) { + char *cp; + skip_spaces(mb); + if (!(cp = gnext(mb)) || (*cp != '0' && *cp != '1')) { + myfree(line); + if (allow_partial) { + i_tags_setn(&im->tags, "i_incomplete", 1); + i_tags_setn(&im->tags, "i_lines_read", y); + return im; + } + else { + if (cp) + i_push_error(0, "invalid data for ascii pnm"); + else + i_push_error(0, "short read - file truncated?"); + i_img_destroy(im); + return NULL; + } + } + *linep++ = *cp == '0' ? 0 : 1; + } + i_ppal(im, 0, width, y, line); + } + myfree(line); + + return im; +} + +static +i_img * +read_pgm_ppm_ascii(mbuf *mb, i_img *im, int width, int height, int channels, + int maxval, int allow_partial) { + i_color *line, *linep; + int x, y, ch; + int rounder = maxval / 2; + + line = mymalloc(width * sizeof(i_color)); + for(y=0;ytags, "i_incomplete", 1); + i_tags_setn(&im->tags, "i_lines_read", 1); + return im; + } + else { + if (gpeek(mb)) + i_push_error(0, "invalid data for ascii pnm"); + else + i_push_error(0, "short read - file truncated?"); + i_img_destroy(im); + return NULL; + } + } + if (sample > maxval) + sample = maxval; + linep->channel[ch] = (sample * 255 + rounder) / maxval; + } + ++linep; + } + i_plin(im, 0, width, y, line); + } + myfree(line); + + return im; +} + +static +i_img * +read_pgm_ppm_ascii_16(mbuf *mb, i_img *im, int width, int height, + int channels, int maxval, int allow_partial) { + i_fcolor *line, *linep; + int x, y, ch; + double maxvalf = maxval; + + line = mymalloc(width * sizeof(i_fcolor)); + for(y=0;y maxval) + sample = maxval; + linep->channel[ch] = sample / maxvalf; + } + ++linep; + } + i_plinf(im, 0, width, y, line); + } + myfree(line); + + return im; +} /* -=item i_readpnm_wiol(ig, length) +=item i_readpnm_wiol(ig, allow_partial) Retrieve an image and stores in the iolayer object. Returns NULL on fatal error. ig - io_glue object - length - maximum length to read from data source, before closing it -1 - signifies no limit. + allow_partial - allows a partial file to be read successfully =cut */ i_img * -i_readpnm_wiol(io_glue *ig, int length) { +i_readpnm_wiol(io_glue *ig, int allow_partial) { i_img* im; int type; - int x, y, ch; int width, height, maxval, channels, pcount; int rounder; char *cp; - unsigned char *uc; mbuf buf; - i_color val; i_clear_error(); - - mm_log((1,"i_readpnm(ig %p, length %d)\n", ig, length)); + mm_log((1,"i_readpnm(ig %p, allow_partial %d)\n", ig, allow_partial)); io_glue_commit_types(ig); init_buf(&buf, ig); @@ -327,11 +625,6 @@ i_readpnm_wiol(io_glue *ig, int length) { mm_log((1, "i_readpnm: maxval of %d is over 65535 - invalid pnm file\n")); return NULL; } - else if (type >= 4 && maxval > 255) { - i_push_errorf(0, "maxval of %d is over 255 - not currently supported by Imager for binary pnm", maxval); - mm_log((1, "i_readpnm: maxval of %d is over 255 - not currently supported by Imager for binary pnm\n", maxval)); - return NULL; - } } else maxval=1; rounder = maxval / 2; @@ -350,71 +643,172 @@ i_readpnm_wiol(io_glue *ig, int length) { } mm_log((1, "i_readpnm: (%d x %d), channels = %d, maxval = %d\n", width, height, channels, maxval)); - - im = i_img_empty_ch(NULL, width, height, channels); - i_tags_add(&im->tags, "i_format", 0, "pnm", -1, 0); + if (type == 1 || type == 4) { + i_color pbm_pal[2]; + pbm_pal[0].channel[0] = 255; + pbm_pal[1].channel[0] = 0; + + im = i_img_pal_new(width, height, 1, 256); + i_addcolors(im, pbm_pal, 2); + } + else { + if (maxval > 255) + im = i_img_16_new(width, height, channels); + else + im = i_img_8_new(width, height, channels); + } switch (type) { case 1: /* Ascii types */ + im = read_pbm_ascii(&buf, im, width, height, allow_partial); + break; + case 2: case 3: - for(y=0;y 255) + im = read_pgm_ppm_ascii_16(&buf, im, width, height, channels, maxval, allow_partial); + else + im = read_pgm_ppm_ascii(&buf, im, width, height, channels, maxval, allow_partial); break; case 4: /* binary pbm */ - for(y=0;y>xt)) ? 0 : 255; - i_ppix(im, x+xt, y, &val); - } - } else { - mm_log((1,"i_readpnm: gnext() returned false in data\n")); - return im; - } - } + im = read_pbm_bin(&buf, im, width, height, allow_partial); break; case 5: /* binary pgm */ case 6: /* binary ppm */ - for(y=0;y 255) + im = read_pgm_ppm_bin16(&buf, im, width, height, channels, maxval, allow_partial); + else + im = read_pgm_ppm_bin8(&buf, im, width, height, channels, maxval, allow_partial); break; + default: mm_log((1, "type %s [P%d] unsupported\n", typenames[type-1], type)); return NULL; } + + if (!im) + return NULL; + + i_tags_add(&im->tags, "i_format", 0, "pnm", -1, 0); + i_tags_setn(&im->tags, "pnm_maxval", maxval); + i_tags_setn(&im->tags, "pnm_type", type); + return im; } +static +int +write_pbm(i_img *im, io_glue *ig, int zero_is_white) { + int x, y; + i_palidx *line; + int write_size; + unsigned char *write_buf; + unsigned char *writep; + char header[255]; + unsigned mask; + + sprintf(header, "P4\012# CREATOR: Imager\012%d %d\012", + im->xsize, im->ysize); + if (i_io_write(ig, header, strlen(header)) < 0) { + i_push_error(0, "could not write pbm header"); + return 0; + } + write_size = (im->xsize + 7) / 8; + line = mymalloc(sizeof(i_palidx) * im->xsize); + write_buf = mymalloc(write_size); + for (y = 0; y < im->ysize; ++y) { + i_gpal(im, 0, im->xsize, y, line); + mask = 0x80; + writep = write_buf; + memset(write_buf, 0, write_size); + for (x = 0; x < im->xsize; ++x) { + if (zero_is_white ? line[x] : !line[x]) + *writep |= mask; + mask >>= 1; + if (!mask) { + ++writep; + mask = 0x80; + } + } + if (i_io_write(ig, write_buf, write_size) != write_size) { + i_push_error(0, "write failure"); + myfree(write_buf); + myfree(line); + return 0; + } + } + myfree(write_buf); + myfree(line); + + return 1; +} + +static +int +write_ppm_data_8(i_img *im, io_glue *ig) { + int write_size = im->xsize * im->channels; + unsigned char *data = mymalloc(write_size); + int y = 0; + int rc = 1; + + while (y < im->ysize && rc >= 0) { + i_gsamp(im, 0, im->xsize, y, data, NULL, im->channels); + if (i_io_write(ig, data, write_size) != write_size) { + i_push_error(errno, "could not write ppm data"); + rc = 0; + break; + } + ++y; + } + myfree(data); + + return rc; +} + +static +int +write_ppm_data_16(i_img *im, io_glue *ig) { + int sample_count = im->channels * im->xsize; + int write_size = sample_count * 2; + int line_size = sample_count * sizeof(i_fsample_t); + i_fsample_t *line_buf = mymalloc(line_size); + i_fsample_t *samplep; + unsigned char *write_buf = mymalloc(write_size); + unsigned char *writep; + int sample_num; + int y = 0; + int rc = 1; + + while (y < im->ysize) { + i_gsampf(im, 0, im->xsize, y, line_buf, NULL, im->channels); + samplep = line_buf; + writep = write_buf; + for (sample_num = 0; sample_num < sample_count; ++sample_num) { + unsigned sample16 = SampleFTo16(*samplep++); + *writep++ = sample16 >> 8; + *writep++ = sample16 & 0xFF; + } + if (i_io_write(ig, write_buf, write_size) != write_size) { + i_push_error(errno, "could not write ppm data"); + rc = 0; + break; + } + ++y; + } + myfree(line_buf); + myfree(write_buf); + + return rc; +} undef_int i_writeppm_wiol(i_img *im, io_glue *ig) { char header[255]; - int rc; + int zero_is_white; + int wide_data; mm_log((1,"i_writeppm(im %p, ig %p)\n", im, ig)); i_clear_error(); @@ -424,83 +818,55 @@ i_writeppm_wiol(i_img *im, io_glue *ig) { io_glue_commit_types(ig); - if (im->channels == 3) { - sprintf(header,"P6\n#CREATOR: Imager\n%d %d\n255\n",im->xsize,im->ysize); - if (ig->writecb(ig,header,strlen(header))<0) { - i_push_error(errno, "could not write ppm header"); - mm_log((1,"i_writeppm: unable to write ppm header.\n")); - return(0); - } + if (i_img_is_monochrome(im, &zero_is_white)) { + return write_pbm(im, ig, zero_is_white); + } + else { + int type; + int maxval; - if (!im->virtual && im->bits == i_8_bits && im->type == i_direct_type) { - rc = ig->writecb(ig,im->idata,im->bytes); + if (!i_tags_get_int(&im->tags, "pnm_write_wide_data", 0, &wide_data)) + wide_data = 0; + + if (im->channels == 3) { + type = 6; } - else { - unsigned char *data = mymalloc(3 * im->xsize); - if (data != NULL) { - int y = 0; - static int rgb_chan[3] = { 0, 1, 2 }; - - rc = 0; - while (y < im->ysize && rc >= 0) { - i_gsamp(im, 0, im->xsize, y, data, rgb_chan, 3); - rc = ig->writecb(ig, data, im->xsize * 3); - ++y; - } - myfree(data); - } - else { - i_push_error(0, "Out of memory"); - return 0; - } + else if (im->channels == 1) { + type = 5; } - if (rc<0) { - i_push_error(errno, "could not write ppm data"); - mm_log((1,"i_writeppm: unable to write ppm data.\n")); + else { + i_push_error(0, "can only save 1 or 3 channel images to pnm"); + mm_log((1,"i_writeppm: ppm/pgm is 1 or 3 channel only (current image is %d)\n",im->channels)); return(0); } - } - else if (im->channels == 1) { - sprintf(header, "P5\n#CREATOR: Imager\n%d %d\n255\n", - im->xsize, im->ysize); - if (ig->writecb(ig,header, strlen(header)) < 0) { - i_push_error(errno, "could not write pgm header"); - mm_log((1,"i_writeppm: unable to write pgm header.\n")); + if (im->bits <= 8 || !wide_data) + maxval = 255; + else + maxval = 65535; + + sprintf(header,"P%d\n#CREATOR: Imager\n%d %d\n%d\n", + type, im->xsize, im->ysize, maxval); + + if (ig->writecb(ig,header,strlen(header)) != strlen(header)) { + i_push_error(errno, "could not write ppm header"); + mm_log((1,"i_writeppm: unable to write ppm header.\n")); return(0); } if (!im->virtual && im->bits == i_8_bits && im->type == i_direct_type) { - rc=ig->writecb(ig,im->idata,im->bytes); - } - else { - unsigned char *data = mymalloc(im->xsize); - if (data != NULL) { - int y = 0; - int chan = 0; - - rc = 0; - while (y < im->ysize && rc >= 0) { - i_gsamp(im, 0, im->xsize, y, data, &chan, 1); - rc = ig->writecb(ig, data, im->xsize); - ++y; - } - myfree(data); - } - else { - i_push_error(0, "Out of memory"); + if (ig->writecb(ig,im->idata,im->bytes) != im->bytes) { + i_push_error(errno, "could not write ppm data"); return 0; } } - if (rc<0) { - i_push_error(errno, "could not write pgm data"); - mm_log((1,"i_writeppm: unable to write pgm data.\n")); - return(0); + else if (maxval == 255) { + if (!write_ppm_data_8(im, ig)) + return 0; + } + else { + if (!write_ppm_data_16(im, ig)) + return 0; } - } - else { - i_push_error(0, "can only save 1 or 3 channel images to pnm"); - mm_log((1,"i_writeppm: ppm/pgm is 1 or 3 channel only (current image is %d)\n",im->channels)); - return(0); } ig->closecb(ig); @@ -512,7 +878,7 @@ i_writeppm_wiol(i_img *im, io_glue *ig) { =head1 AUTHOR -Arnar M. Hrafnkelsson +Arnar M. Hrafnkelsson , Tony Cook =head1 SEE ALSO diff --git a/quant.c b/quant.c index 9d3abc50..52942d10 100644 --- a/quant.c +++ b/quant.c @@ -6,6 +6,7 @@ static void makemap_addi(i_quantize *, i_img **imgs, int count); static void makemap_mediancut(i_quantize *, i_img **imgs, int count); +static void makemap_mono(i_quantize *); static void @@ -71,6 +72,10 @@ i_quant_makemap(i_quantize *quant, i_img **imgs, int count) { makemap_mediancut(quant, imgs, count); break; + case mc_mono: + makemap_mono(quant); + break; + case mc_addi: default: makemap_addi(quant, imgs, count); @@ -697,6 +702,19 @@ makemap_mediancut(i_quantize *quant, i_img **imgs, int count) { i_mempool_destroy(&mp); } +static void +makemap_mono(i_quantize *quant) { + quant->mc_colors[0].rgba.r = 0; + quant->mc_colors[0].rgba.g = 0; + quant->mc_colors[0].rgba.b = 0; + quant->mc_colors[0].rgba.a = 255; + quant->mc_colors[1].rgba.r = 255; + quant->mc_colors[1].rgba.g = 255; + quant->mc_colors[1].rgba.b = 255; + quant->mc_colors[1].rgba.a = 255; + quant->mc_count = 2; +} + #define pboxjump 32 /* Define one of the following 4 symbols to choose a colour search method diff --git a/render.im b/render.im new file mode 100644 index 00000000..c096ec92 --- /dev/null +++ b/render.im @@ -0,0 +1,204 @@ +/* +Render utilities +*/ +#include "imager.h" + +#define RENDER_MAGIC 0x765AE + +typedef void (*render_color_f)(i_render *, int, int, int, unsigned char const *src, i_color const *color); + +#code + +static void IM_SUFFIX(render_color_alpha)(i_render *r, int x, int y, int width, unsigned char const *src, i_color const *color); +static void IM_SUFFIX(render_color_13)(i_render *r, int x, int y, int width, unsigned char const *src, i_color const *color); + +static render_color_f IM_SUFFIX(render_color_tab)[] = + { + NULL, + IM_SUFFIX(render_color_13), + IM_SUFFIX(render_color_alpha), + IM_SUFFIX(render_color_13), + IM_SUFFIX(render_color_alpha), + }; + +#/code + +void +i_render_init(i_render *r, i_img *im, int width) { + r->magic = RENDER_MAGIC; + r->im = im; + r->width = width; + r->line_8 = NULL; + r->line_double = NULL; +#code im->bits <= 8 + r->IM_SUFFIX(line) = mymalloc(sizeof(i_fcolor) * width); +#/code +} + +void +i_render_done(i_render *r) { + if (r->line_8) + myfree(r->line_8); + else + myfree(r->line_double); + r->magic = 0; +} + +void +i_render_color(i_render *r, int x, int y, int width, unsigned char const *src, + i_color const *color) { + i_img *im = r->im; + if (y < 0 || y >= im->ysize) + return; + if (x < 0) { + width += x; + src -= x; + x = 0; + } + if (x + width > im->xsize) { + width = im->xsize - x; + } + if (x >= im->xsize || x + width <= 0 || width <= 0) + return; + + /* avoid as much work as we can */ + while (width > 0 && *src == 0) { + --width; + ++src; + ++x; + } + while (width > 0 && src[width-1] == 0) { + --width; + } + if (!width) + return; + + /* make sure our line buffer is big enough */ + if (width > r->width) { + int new_width = r->width * 2; + if (new_width < width) + new_width = width; + + if (r->line_8) + r->line_8 = myrealloc(r->line_8, sizeof(i_color) * new_width); + else + r->line_double = myrealloc(r->line_double, sizeof(i_fcolor) * new_width); + } + +#code r->im->bits <= 8 + (IM_SUFFIX(render_color_tab)[im->channels])(r, x, y, width, src, color); +#/code +} + +static void +dump_src(const char *note, unsigned char const *src, int width) { + int i; + printf("%s - %p/%d\n", note, src, width); + for (i = 0; i < width; ++i) { + printf("%02x ", src[i]); + } + putchar('\n'); +} + +#code + +static +void +IM_SUFFIX(render_color_13)(i_render *r, int x, int y, int width, + unsigned char const *src, i_color const *color) { + i_img *im = r->im; + IM_COLOR *linep = r->IM_SUFFIX(line); + int ch, channels = im->channels; + int fetch_offset; +#undef STORE_COLOR +#ifdef IM_EIGHT_BIT +#define STORE_COLOR (*color) +#else + i_fcolor fcolor; + + for (ch = 0; ch < channels; ++ch) { + fcolor.channel[ch] = color->channel[ch] / 255.0; + } +#define STORE_COLOR fcolor +#endif + + fetch_offset = 0; + while (fetch_offset < width && *src == 0xFF) { + *linep++ = STORE_COLOR; + ++src; + ++fetch_offset; + } + IM_GLIN(im, x+fetch_offset, x+width, y, linep); + while (fetch_offset < width) { +#ifdef IM_EIGHT_BIT + IM_WORK_T alpha = *src++; +#else + IM_WORK_T alpha = *src++ / 255.0; +#endif + if (alpha == IM_SAMPLE_MAX) + *linep = STORE_COLOR; + else if (alpha) { + for (ch = 0; ch < channels; ++ch) { + linep->channel[ch] = (linep->channel[ch] * (IM_SAMPLE_MAX - alpha) + + STORE_COLOR.channel[ch] * alpha) / IM_SAMPLE_MAX; + } + } + ++linep; + ++fetch_offset; + } + IM_PLIN(im, x, x+width, y, r->IM_SUFFIX(line)); +} + +static +void +IM_SUFFIX(render_color_alpha)(i_render *r, int x, int y, int width, + unsigned char const *src, i_color const *color) { + IM_COLOR *linep = r->IM_SUFFIX(line); + int ch; + int alpha_channel = r->im->channels - 1; + int fetch_offset; +#undef STORE_COLOR +#ifdef IM_EIGHT_BIT +#define STORE_COLOR (*color) +#else + i_fcolor fcolor; + + for (ch = 0; ch < r->im->channels; ++ch) { + fcolor.channel[ch] = color->channel[ch] / 255.0; + } +#define STORE_COLOR fcolor +#endif + + fetch_offset = 0; + while (fetch_offset < width && *src == 0xFF) { + *linep++ = STORE_COLOR; + ++src; + ++fetch_offset; + } + IM_GLIN(r->im, x+fetch_offset, x+width, y, linep); + while (fetch_offset < width) { +#ifdef IM_EIGHT_BIT + IM_WORK_T src_alpha = *src++; +#else + IM_WORK_T src_alpha = *src++ / 255.0; +#endif + if (src_alpha == IM_SAMPLE_MAX) + *linep = STORE_COLOR; + else if (src_alpha) { + IM_WORK_T remains = - src_alpha; + IM_WORK_T orig_alpha = linep->channel[alpha_channel]; + IM_WORK_T dest_alpha = src_alpha + (remains * orig_alpha) / IM_SAMPLE_MAX; + for (ch = 0; ch < alpha_channel; ++ch) { + linep->channel[ch] = ( src_alpha * STORE_COLOR.channel[ch] + + remains * linep->channel[ch] * orig_alpha / IM_SAMPLE_MAX + ) / dest_alpha; + } + linep->channel[alpha_channel] = dest_alpha; + } + ++linep; + ++fetch_offset; + } + IM_PLIN(r->im, x, x+width, y, r->IM_SUFFIX(line)); +} + +#/code diff --git a/rendert.h b/rendert.h new file mode 100644 index 00000000..8b34a97a --- /dev/null +++ b/rendert.h @@ -0,0 +1,12 @@ +#ifndef IMAGER_RENDERT_H +#define IMAGER_RENDERT_H + +typedef struct { + int magic; + i_img *im; + i_color *line_8; + i_fcolor *line_double; + int width; +} i_render; + +#endif diff --git a/t/t104ppm.t b/t/t104ppm.t index 546aa1cb..9813afc6 100644 --- a/t/t104ppm.t +++ b/t/t104ppm.t @@ -1,7 +1,8 @@ #!perl -w use Imager ':all'; -use Test::More tests => 64; +use Test::More tests => 143; use strict; +use Imager::Test qw(test_image_raw test_image_16 is_color3 is_color1 is_image); init_log("testout/t104ppm.log",1); @@ -9,12 +10,7 @@ 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); -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(); my $fh = openimage(">testout/t104.ppm"); my $IO = Imager::io_new_fd(fileno($fh)); @@ -65,10 +61,12 @@ is(i_img_diff($gimg, $gcmpimg), 0, my $ooim = Imager->new; ok($ooim->read(file=>"testimg/simple.pbm"), "read simple pbm, via OO"); -check_gray(Imager::i_get_pixel($ooim->{IMG}, 0, 0), 255); -check_gray(Imager::i_get_pixel($ooim->{IMG}, 0, 1), 0); -check_gray(Imager::i_get_pixel($ooim->{IMG}, 1, 0), 0); -check_gray(Imager::i_get_pixel($ooim->{IMG}, 1, 1), 255); +check_gray(Imager::i_get_pixel($ooim->{IMG}, 0, 0), 0); +check_gray(Imager::i_get_pixel($ooim->{IMG}, 0, 1), 255); +check_gray(Imager::i_get_pixel($ooim->{IMG}, 1, 0), 255); +check_gray(Imager::i_get_pixel($ooim->{IMG}, 1, 1), 0); +is($ooim->type, 'paletted', "check pbm read as paletted"); +is($ooim->tags(name=>'pnm_type'), 1, "check pnm_type tag"); { # https://rt.cpan.org/Ticket/Display.html?id=7465 @@ -87,9 +85,10 @@ check_gray(Imager::i_get_pixel($ooim->{IMG}, 1, 1), 255); # check the pixels ok(my ($white, $grey, $green) = $maxval->getpixel('x'=>[0,1,2], 'y'=>[0,0,0]), "fetch pixels"); - check_color($white, 255, 255, 255, "white pixel"); - check_color($grey, 130, 130, 130, "grey pixel"); - check_color($green, 125, 125, 0, "green pixel"); + is_color3($white, 255, 255, 255, "white pixel"); + is_color3($grey, 130, 130, 130, "grey pixel"); + is_color3($green, 125, 125, 0, "green pixel"); + is($maxval->tags(name=>'pnm_type'), 6, "check pnm_type tag on maxval"); # and do the same for ASCII images my $maxval_asc = Imager->new; @@ -103,12 +102,14 @@ check_gray(Imager::i_get_pixel($ooim->{IMG}, 1, 1), 255); is($maxval_asc->getchannels, 3, "channel count"); is($maxval_asc->getwidth, 3, "width"); is($maxval_asc->getheight, 1, "height"); + + is($maxval->tags(name=>'pnm_type'), 6, "check pnm_type tag on maxval"); # check the pixels ok(my ($white_asc, $grey_asc, $green_asc) = $maxval_asc->getpixel('x'=>[0,1,2], 'y'=>[0,0,0]), "fetch pixels"); - check_color($white_asc, 255, 255, 255, "white asc pixel"); - check_color($grey_asc, 130, 130, 130, "grey asc pixel"); - check_color($green_asc, 125, 125, 0, "green asc pixel"); + is_color3($white_asc, 255, 255, 255, "white asc pixel"); + is_color3($grey_asc, 130, 130, 130, "grey asc pixel"); + is_color3($green_asc, 125, 125, 0, "green asc pixel"); } { # previously we didn't validate maxval at all, make sure it's @@ -127,13 +128,15 @@ check_gray(Imager::i_get_pixel($ooim->{IMG}, 1, 1), 255); like($maxval65536->errstr, qr/maxval of 65536 is over 65535 - invalid pnm file/, "error expected from reading maxval_65536.ppm"); - # maxval of 256 is valid, but Imager can't handle it yet in binary files + # maxval of 256 is valid, and handled as of 0.56 my $maxval256 = Imager->new; - ok(!$maxval256->read(file=>'testimg/maxval_256.ppm'), - "should fail reading maxval 256 image"); - print "# ",$maxval256->errstr,"\n"; - like($maxval256->errstr, qr/maxval of 256 is over 255 - not currently supported by Imager/, - "error expected from reading maxval_256.ppm"); + ok($maxval256->read(file=>'testimg/maxval_256.ppm'), + "should succeed reading maxval 256 image"); + is_color3($maxval256->getpixel(x => 0, 'y' => 0), + 0, 0, 0, "check black in maxval_256"); + is_color3($maxval256->getpixel(x => 0, 'y' => 1), + 255, 255, 255, "check white in maxval_256"); + is($maxval256->bits, 16, "check bits/sample on maxval 256"); # make sure we handle maxval > 255 for ascii my $maxval4095asc = Imager->new; @@ -142,11 +145,12 @@ check_gray(Imager::i_get_pixel($ooim->{IMG}, 1, 1), 255); is($maxval4095asc->getchannels, 3, "channels"); is($maxval4095asc->getwidth, 3, "width"); is($maxval4095asc->getheight, 1, "height"); + is($maxval4095asc->bits, 16, "check bits/sample on maxval 4095"); ok(my ($white, $grey, $green) = $maxval4095asc->getpixel('x'=>[0,1,2], 'y'=>[0,0,0]), "fetch pixels"); - check_color($white, 255, 255, 255, "white 4095 pixel"); - check_color($grey, 128, 128, 128, "grey 4095 pixel"); - check_color($green, 127, 127, 0, "green 4095 pixel"); + is_color3($white, 255, 255, 255, "white 4095 pixel"); + is_color3($grey, 128, 128, 128, "grey 4095 pixel"); + is_color3($green, 127, 127, 0, "green 4095 pixel"); } { # check i_format is set when reading a pnm file @@ -194,6 +198,15 @@ check_gray(Imager::i_get_pixel($ooim->{IMG}, 1, 1), 255); Imager->set_file_limits(reset=>1); } +{ + # check we correctly sync with the data stream + my $im = Imager->new; + ok($im->read(file => 'testimg/pgm.pgm', type => 'pnm'), + "read pgm.pgm"); + print "# ", $im->getsamples('y' => 0), "\n"; + is_color1($im->getpixel(x=>0, 'y' => 0), 254, "check top left"); +} + { # check error messages set correctly my $im = Imager->new(xsize=>100, ysize=>100, channels=>4); ok(!$im->write(file=>"testout/t104_fail.ppm", type=>'pnm'), @@ -206,6 +219,242 @@ check_gray(Imager::i_get_pixel($ooim->{IMG}, 1, 1), 255); "check error message"); } +# various bad input files +print "# check error handling\n"; +{ + my $im = Imager->new; + ok(!$im->read(file => 'testimg/short_bin.ppm', type=>'pnm'), + "fail to read short bin ppm"); + cmp_ok($im->errstr, '=~', 'short read - file truncated', + "check error message"); +} + +{ + my $im = Imager->new; + ok(!$im->read(file => 'testimg/short_bin16.ppm', type=>'pnm'), + "fail to read short bin ppm (maxval 65535)"); + cmp_ok($im->errstr, '=~', 'short read - file truncated', + "check error message"); +} + +{ + my $im = Imager->new; + ok(!$im->read(file => 'testimg/short_bin.pgm', type=>'pnm'), + "fail to read short bin pgm"); + cmp_ok($im->errstr, '=~', 'short read - file truncated', + "check error message"); +} + +{ + my $im = Imager->new; + ok(!$im->read(file => 'testimg/short_bin16.pgm', type=>'pnm'), + "fail to read short bin pgm (maxval 65535)"); + cmp_ok($im->errstr, '=~', 'short read - file truncated', + "check error message"); +} + +{ + my $im = Imager->new; + ok(!$im->read(file => 'testimg/short_bin.pbm', type => 'pnm'), + "fail to read a short bin pbm"); + cmp_ok($im->errstr, '=~', 'short read - file truncated', + "check error message"); +} + +{ + my $im = Imager->new; + ok(!$im->read(file => 'testimg/short_asc.ppm', type => 'pnm'), + "fail to read a short asc ppm"); + cmp_ok($im->errstr, '=~', 'short read - file truncated', + "check error message"); +} + +{ + my $im = Imager->new; + ok(!$im->read(file => 'testimg/short_asc.pgm', type => 'pnm'), + "fail to read a short asc pgm"); + cmp_ok($im->errstr, '=~', 'short read - file truncated', + "check error message"); +} + +{ + my $im = Imager->new; + ok(!$im->read(file => 'testimg/short_asc.pbm', type => 'pnm'), + "fail to read a short asc pbm"); + cmp_ok($im->errstr, '=~', 'short read - file truncated', + "check error message"); +} + +{ + my $im = Imager->new; + ok(!$im->read(file => 'testimg/bad_asc.ppm', type => 'pnm'), + "fail to read a bad asc ppm"); + cmp_ok($im->errstr, '=~', 'invalid data for ascii pnm', + "check error message"); +} + +{ + my $im = Imager->new; + ok(!$im->read(file => 'testimg/bad_asc.pgm', type => 'pnm'), + "fail to read a bad asc pgm"); + cmp_ok($im->errstr, '=~', 'invalid data for ascii pnm', + "check error message"); +} + +{ + my $im = Imager->new; + ok(!$im->read(file => 'testimg/bad_asc.pbm', type => 'pnm'), + "fail to read a bad asc pbm"); + cmp_ok($im->errstr, '=~', 'invalid data for ascii pnm', + "check error message"); +} + +{ + my $im = Imager->new; + ok($im->read(file => 'testimg/short_bin.ppm', type => 'pnm', + allow_partial => 1), + "partial read bin ppm"); + is($im->tags(name => 'i_incomplete'), 1, "partial flag set"); + is($im->tags(name => 'i_lines_read'), 1, "lines_read set"); +} + +{ + my $im = Imager->new; + ok($im->read(file => 'testimg/short_bin16.ppm', type => 'pnm', + allow_partial => 1), + "partial read bin16 ppm"); + is($im->tags(name => 'i_incomplete'), 1, "partial flag set"); + is($im->tags(name => 'i_lines_read'), 1, "lines_read set"); + is($im->bits, 16, "check correct bits"); +} + +{ + my $im = Imager->new; + ok($im->read(file => 'testimg/short_bin.pgm', type => 'pnm', + allow_partial => 1), + "partial read bin pgm"); + is($im->tags(name => 'i_incomplete'), 1, "partial flag set"); + is($im->tags(name => 'i_lines_read'), 1, "lines_read set"); +} + +{ + my $im = Imager->new; + ok($im->read(file => 'testimg/short_bin16.pgm', type => 'pnm', + allow_partial => 1), + "partial read bin16 pgm"); + is($im->tags(name => 'i_incomplete'), 1, "partial flag set"); + is($im->tags(name => 'i_lines_read'), 1, "lines_read set"); +} + +{ + my $im = Imager->new; + ok($im->read(file => 'testimg/short_bin.pbm', type => 'pnm', + allow_partial => 1), + "partial read bin pbm"); + is($im->tags(name => 'i_incomplete'), 1, "partial flag set"); + is($im->tags(name => 'i_lines_read'), 1, "lines_read set"); +} + +{ + my $im = Imager->new; + ok($im->read(file => 'testimg/short_asc.ppm', type => 'pnm', + allow_partial => 1), + "partial read asc ppm"); + is($im->tags(name => 'i_incomplete'), 1, "partial flag set"); + is($im->tags(name => 'i_lines_read'), 1, "lines_read set"); +} + +{ + my $im = Imager->new; + ok($im->read(file => 'testimg/short_asc.pgm', type => 'pnm', + allow_partial => 1), + "partial read asc pgm"); + is($im->tags(name => 'i_incomplete'), 1, "partial flag set"); + is($im->tags(name => 'i_lines_read'), 1, "lines_read set"); +} + +{ + my $im = Imager->new; + ok($im->read(file => 'testimg/short_asc.pbm', type => 'pnm', + allow_partial => 1), + "partial read asc pbm"); + is($im->tags(name => 'i_incomplete'), 1, "partial flag set"); + is($im->tags(name => 'i_lines_read'), 1, "lines_read set"); +} + +{ + my $im = Imager->new; + ok($im->read(file => 'testimg/bad_asc.ppm', type => 'pnm', + allow_partial => 1), + "partial read bad asc ppm"); + is($im->tags(name => 'i_incomplete'), 1, "partial flag set"); + is($im->tags(name => 'i_lines_read'), 1, "lines_read set"); +} + +{ + my $im = Imager->new; + ok($im->read(file => 'testimg/bad_asc.pgm', type => 'pnm', + allow_partial => 1), + "partial read bad asc pgm"); + is($im->tags(name => 'i_incomplete'), 1, "partial flag set"); + is($im->tags(name => 'i_lines_read'), 1, "lines_read set"); +} + +{ + my $im = Imager->new; + ok($im->read(file => 'testimg/bad_asc.pbm', type => 'pnm', + allow_partial => 1), + "partial read bad asc pbm"); + is($im->tags(name => 'i_incomplete'), 1, "partial flag set"); + is($im->tags(name => 'i_lines_read'), 1, "lines_read set"); +} + +{ + print "# monochrome output\n"; + my $im = Imager->new(xsize => 10, ysize => 10, type => 'paletted'); + ok($im->addcolors(colors => [ '#000000', '#FFFFFF' ]), + "add black and white"); + $im->box(filled => 1, xmax => 4, color => '#000000'); + $im->box(filled => 1, xmin => 5, color => '#FFFFFF'); + is($im->type, 'paletted', 'mono still paletted'); + ok($im->write(file => 'testout/t104_mono.pbm', type => 'pnm'), + "save as pbm"); + + # check it + my $imread = Imager->new; + ok($imread->read(file => 'testout/t104_mono.pbm', type=>'pnm'), + "read it back in") + or print "# ", $imread->errstr, "\n"; + is($imread->type, 'paletted', "check result is paletted"); + is($imread->tags(name => 'pnm_type'), 4, "check type"); +} + +{ + print "# 16-bit output\n"; + my $data; + my $im = test_image_16(); + + # without tag, it should do 8-bit output + ok($im->write(data => \$data, type => 'pnm'), + "write 16-bit image as 8-bit/sample ppm"); + my $im8 = Imager->new; + ok($im8->read(data => $data), "read it back"); + is($im8->tags(name => 'pnm_maxval'), 255, "check maxval"); + is_image($im, $im8, "check image matches"); + + # try 16-bit output + $im->settag(name => 'pnm_write_wide_data', value => 1); + $data = ''; + ok($im->write(data => \$data, type => 'pnm'), + "write 16-bit image as 16-bit/sample ppm"); + $im->write(file=>'testout/t104_16.ppm'); + my $im16 = Imager->new; + ok($im16->read(data => $data), "read it back"); + is($im16->tags(name => 'pnm_maxval'), 65535, "check maxval"); + $im16->write(file=>'testout/t104_16b.ppm'); + is_image($im, $im16, "check image matches"); +} + sub openimage { my $fname = shift; local(*FH); @@ -229,10 +478,3 @@ sub check_gray { is($g, $gray, "compare gray"); } -sub check_color { - my ($c, $red, $green, $blue, $note) = @_; - - my ($r, $g, $b) = $c->rgba; - is_deeply([ $r, $g, $b], [ $red, $green, $blue ], - "$note ($r, $g, $b) compared to ($red, $green, $blue)"); -} diff --git a/t/t106tiff.t b/t/t106tiff.t index f4aed661..6d9d952b 100644 --- a/t/t106tiff.t +++ b/t/t106tiff.t @@ -166,7 +166,8 @@ SKIP: print "# diff $diff\n"; ok($diff == 0, "image mismatch"); my $bad = Imager->new; - ok($bad->read(file=>'testimg/comp4bad.tif'), "bad image not returned"); + ok($bad->read(file=>'testimg/comp4bad.tif', + allow_partial=>1), "bad image not returned"); ok(scalar $bad->tags(name=>'i_incomplete'), "incomplete tag not set"); ok($img8->write(file=>'testout/t106_pal8.tif'), "writing 8-bit paletted"); my $cmp8 = Imager->new; diff --git a/t/t15color.t b/t/t15color.t index c4019e18..04200c0e 100644 --- a/t/t15color.t +++ b/t/t15color.t @@ -11,8 +11,6 @@ use Test::More tests => 47; BEGIN { use_ok('Imager'); }; -require "t/testtools.pl"; - init_log("testout/t15color.log",1); my $c1 = Imager::Color->new(100, 150, 200, 250); diff --git a/testimg/bad_asc.pbm b/testimg/bad_asc.pbm new file mode 100644 index 00000000..2f37aed7 --- /dev/null +++ b/testimg/bad_asc.pbm @@ -0,0 +1,4 @@ +P1 +2 2 +10 +0x \ No newline at end of file diff --git a/testimg/bad_asc.pgm b/testimg/bad_asc.pgm new file mode 100644 index 00000000..2663babd --- /dev/null +++ b/testimg/bad_asc.pgm @@ -0,0 +1,5 @@ +P2 +2 2 +255 +255 255 +255 x \ No newline at end of file diff --git a/testimg/bad_asc.ppm b/testimg/bad_asc.ppm new file mode 100644 index 00000000..1fe06726 --- /dev/null +++ b/testimg/bad_asc.ppm @@ -0,0 +1,5 @@ +P3 +2 2 +255 +255 255 255 255 255 255 +255 255 x \ No newline at end of file diff --git a/testimg/maxval_256.ppm b/testimg/maxval_256.ppm index a9f89b1ce18bfd8b9f37acf535d93c27b269ede3..8e9c8f56ec7d70ecade166a06ef8cc4598c650c6 100644 GIT binary patch delta 23 YcmZ?uo?t0t#ARe^#>D^zj0_+M04zZQQUCw| delta 17 UcmeZvonXmh$Yo?|#>D^z03HVdNB{r; diff --git a/testimg/pbm_base.pgm b/testimg/pbm_base.pgm new file mode 100644 index 0000000000000000000000000000000000000000..e45a1be7e7957dee77dec09e82ab89799e46409d GIT binary patch literal 89 zcmWGAkeGvG2b sQ!wH(GBxF5;E~m}^NY)Gn6O~O!HbVR0r`4%e)0JY{R=l9y!7Z30BQ~$>;M1& literal 0 HcmV?d00001 diff --git a/testimg/pgm.pgm b/testimg/pgm.pgm new file mode 100644 index 00000000..2bff2c14 --- /dev/null +++ b/testimg/pgm.pgm @@ -0,0 +1,5 @@ +P5 +# CREATOR: The GIMP's PNM Filter Version 1.0 +2 2 +255 +þýüû \ No newline at end of file diff --git a/testimg/short_asc.pbm b/testimg/short_asc.pbm new file mode 100644 index 00000000..a45b270c --- /dev/null +++ b/testimg/short_asc.pbm @@ -0,0 +1,4 @@ +P1 +2 2 +01 +1 \ No newline at end of file diff --git a/testimg/short_asc.pgm b/testimg/short_asc.pgm new file mode 100644 index 00000000..0e3fe441 --- /dev/null +++ b/testimg/short_asc.pgm @@ -0,0 +1,5 @@ +P2 +2 2 +255 +255 255 +255 \ No newline at end of file diff --git a/testimg/short_asc.ppm b/testimg/short_asc.ppm new file mode 100644 index 00000000..e47e6537 --- /dev/null +++ b/testimg/short_asc.ppm @@ -0,0 +1,5 @@ +P3 +2 2 +255 +255 255 255 255 255 255 +255 255 255 \ No newline at end of file diff --git a/testimg/short_bin.pbm b/testimg/short_bin.pbm new file mode 100644 index 00000000..5afe60e9 --- /dev/null +++ b/testimg/short_bin.pbm @@ -0,0 +1,3 @@ +P4 +16 2 +ú€þ \ No newline at end of file diff --git a/testimg/short_bin.pgm b/testimg/short_bin.pgm new file mode 100644 index 00000000..8550f7e9 --- /dev/null +++ b/testimg/short_bin.pgm @@ -0,0 +1,5 @@ +P5 +# CREATOR: The GIMP's PNM Filter Version 1.0 +2 2 +255 +ÿÿÿ \ No newline at end of file diff --git a/testimg/short_bin.ppm b/testimg/short_bin.ppm new file mode 100644 index 00000000..b07b37db --- /dev/null +++ b/testimg/short_bin.ppm @@ -0,0 +1,5 @@ +P6 +# CREATOR: The GIMP's PNM Filter Version 1.0 +2 2 +255 +ÿÿÿÿÿÿÿÿÿ \ No newline at end of file diff --git a/testimg/short_bin16.pgm b/testimg/short_bin16.pgm new file mode 100644 index 00000000..757ed59a --- /dev/null +++ b/testimg/short_bin16.pgm @@ -0,0 +1,5 @@ +P5 +# CREATOR: The GIMP's PNM Filter Version 1.0 +2 2 +65535 +ÿÿÿÿÿÿ \ No newline at end of file diff --git a/testimg/short_bin16.ppm b/testimg/short_bin16.ppm new file mode 100644 index 00000000..e5384d4e --- /dev/null +++ b/testimg/short_bin16.ppm @@ -0,0 +1,5 @@ +P6 +# CREATOR: The GIMP's PNM Filter Version 1.0 +2 2 +65535 +ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ \ No newline at end of file diff --git a/tiff.c b/tiff.c index 48f48702..ccbcd554 100644 --- a/tiff.c +++ b/tiff.c @@ -151,7 +151,7 @@ comp_munmap(thandle_t h, tdata_t p, toff_t off) { /* do nothing */ } -static i_img *read_one_tiff(TIFF *tif) { +static i_img *read_one_tiff(TIFF *tif, int allow_partial) { i_img *im; uint32 width, height; uint16 channels; @@ -296,6 +296,14 @@ static i_img *read_one_tiff(TIFF *tif) { ++row; } if (row < height) { + if (allow_partial) { + i_tags_setn(&im->tags, "i_lines_read", row); + } + else { + i_img_destroy(im); + _TIFFfree(buffer); + return NULL; + } error = 1; } /* Ideally we'd optimize the palette, but that could be expensive @@ -382,8 +390,17 @@ static i_img *read_one_tiff(TIFF *tif) { uint32 newrows, i_row; if (!TIFFReadRGBAStrip(tif, row, raster)) { - error++; - break; + if (allow_partial) { + i_tags_setn(&im->tags, "i_lines_read", row); + error++; + break; + } + else { + i_push_error(0, "could not read TIFF image strip"); + _TIFFfree(raster); + i_img_destroy(im); + return NULL; + } } newrows = (row+rowsperstrip > height) ? height-row : rowsperstrip; @@ -406,7 +423,7 @@ static i_img *read_one_tiff(TIFF *tif) { } if (error) { mm_log((1, "i_readtiff_wiol: error during reading\n")); - i_tags_addn(&im->tags, "i_incomplete", 0, 1); + i_tags_setn(&im->tags, "i_incomplete", 1); } if (raster) _TIFFfree( raster ); @@ -420,7 +437,7 @@ static i_img *read_one_tiff(TIFF *tif) { =cut */ i_img* -i_readtiff_wiol(io_glue *ig, int length, int page) { +i_readtiff_wiol(io_glue *ig, int allow_partial, int page) { TIFF* tif; TIFFErrorHandler old_handler; TIFFErrorHandler old_warn_handler; @@ -436,7 +453,7 @@ i_readtiff_wiol(io_glue *ig, int length, int page) { /* Also add code to check for mmapped code */ io_glue_commit_types(ig); - mm_log((1, "i_readtiff_wiol(ig %p, length %d)\n", ig, length)); + mm_log((1, "i_readtiff_wiol(ig %p, allow_partial %d, page %d)\n", ig, allow_partial, page)); tif = TIFFClientOpen("(Iolayer)", "rm", @@ -468,7 +485,7 @@ i_readtiff_wiol(io_glue *ig, int length, int page) { } } - im = read_one_tiff(tif); + im = read_one_tiff(tif, allow_partial); if (TIFFLastDirectory(tif)) mm_log((1, "Last directory of tiff file\n")); TIFFSetErrorHandler(old_handler); @@ -526,7 +543,7 @@ i_readtiff_multi_wiol(io_glue *ig, int length, int *count) { *count = 0; do { - i_img *im = read_one_tiff(tif); + i_img *im = read_one_tiff(tif, 0); if (!im) break; if (++*count > result_alloc) { -- 2.39.2