Various changes:
authorTony Cook <tony@develop=help.com>
Sat, 30 Dec 2006 21:44:33 +0000 (21:44 +0000)
committerTony Cook <tony@develop=help.com>
Sat, 30 Dec 2006 21:44:33 +0000 (21:44 +0000)
 - 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

36 files changed:
Imager.pm
Imager.xs
MANIFEST
Makefile.PL
TODO
bmp.c
freetyp2.c
image.c
imager.h
imdatatypes.h
imrender.h [new file with mode: 0644]
lib/Imager/Files.pod
lib/Imager/ImageTypes.pod
lib/Imager/Test.pm
pnm.c
quant.c
render.im [new file with mode: 0644]
rendert.h [new file with mode: 0644]
t/t104ppm.t
t/t106tiff.t
t/t15color.t
testimg/bad_asc.pbm [new file with mode: 0644]
testimg/bad_asc.pgm [new file with mode: 0644]
testimg/bad_asc.ppm [new file with mode: 0644]
testimg/maxval_256.ppm
testimg/pbm_base.pgm [new file with mode: 0644]
testimg/pgm.pgm [new file with mode: 0644]
testimg/short_asc.pbm [new file with mode: 0644]
testimg/short_asc.pgm [new file with mode: 0644]
testimg/short_asc.ppm [new file with mode: 0644]
testimg/short_bin.pbm [new file with mode: 0644]
testimg/short_bin.pgm [new file with mode: 0644]
testimg/short_bin.ppm [new file with mode: 0644]
testimg/short_bin16.pgm [new file with mode: 0644]
testimg/short_bin16.ppm [new file with mode: 0644]
tiff.c

index 3bfbad1..bde1258 100644 (file)
--- 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;
index d2ea2cf..3997a1e 100644 (file)
--- 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
index 9a17165..52e2886 100644 (file)
--- 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
index e748d23..f55e7d3 100644 (file)
@@ -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 f1d2081..dd09fdd 100644 (file)
--- 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 0e8dc62..94fe6ff 100644 (file)
--- 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)
index 6fc3caa..e7625b9 100644 (file)
@@ -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 316982a..03a06f4 100644 (file)
--- 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
index 613777b..5608411 100644 (file)
--- 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
index 44d8538..0cc440c 100644 (file)
@@ -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 (file)
index 0000000..213362d
--- /dev/null
@@ -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
index 08f96a1..6089b38 100644 (file)
@@ -52,6 +52,10 @@ supply the filename:
   $img->read(file => $filename)
     or die "Cannot read $filename: ", $img->errstr;
 
+The read() method accepts the C<allow_partial> parameter.  If this is
+non-zero then read() can return true on an incomplete image and set
+the C<i_incomplete> tag.
+
 =item write
 
 and the C<write()> 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 *
+
+X<pnm_maxval>pnm_maxval - the maxvals number from the PGM/PPM header.
+Always set to 2 for a PBM file.
+
+=item *
+
+X<pnm_type>pnm_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 *
+
+X<pnm_write_wide_data>pnm_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<jpegquality> 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<i_aspect_only> tag is non-zero.
 
 The C<i_xres> and C<i_yres> tags are expressed in pixels per inch no
 matter the value of this tag, they will be converted to/from the value
index b888e37..f6a2ecd 100644 (file)
@@ -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.
+X<i_xres tag>X<i_yres tag>X<tags, i_xres>X<tags, i_yres>i_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 *
+
+X<i_aspect_only tag>X<tags, i_aspect_only>i_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 *
+
+X<i_incomplete tag>X<tags, i_incomplete>i_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.
+X<i_lines_read tag>X<tags, i_lines_read>i_lines_read - If
+C<i_incomplete> 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.
+X<i_format tag>X<tags, i_format>i_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<Imager::ImageTypes/to_paletted>, 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.
index 485b0f6..5d960f9 100644 (file)
@@ -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(<<END_DIAG);
+Color mismatch:
+  Red: $red vs $cr
+Green: $green vs $cg
+ Blue: $blue vs $cb
+END_DIAG
+    return;
+  }
+
+  return 1;
+}
+
+sub is_color1($$$) {
+  my ($color, $grey, $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 ($cgrey) = $color->rgba;
+  unless ($builder->ok($cgrey == $grey, $comment)) {
+    $builder->diag(<<END_DIAG);
+Color mismatch:
+  Grey: $grey vs $cgrey
+END_DIAG
+    return;
+  }
+
+  return 1;
+}
+
+sub test_image_raw {
+  my $green=Imager::i_color_new(0,255,0,255);
+  my $blue=Imager::i_color_new(0,0,255,255);
+  my $red=Imager::i_color_new(255,0,0,255);
+  
+  my $img=Imager::ImgRaw::new(150,150,3);
+  
+  Imager::i_box_filled($img,70,25,130,125,$green);
+  Imager::i_box_filled($img,20,25,80,125,$blue);
+  Imager::i_arc($img,75,75,30,0,361,$red);
+  Imager::i_conv($img,[0.1, 0.2, 0.4, 0.2, 0.1]);
+
+  $img;
+}
+
+sub test_image_16 {
+  my $green = Imager::Color->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 e5d3050..d777e21 100644 (file)
--- 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;y<height;y++) {
+    linep = line;
+    readp = read_buf;
+    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;
+      }
+    }
+    if (maxval == 255) {
+      for(x=0; x<width; x++) {
+        for(ch=0; ch<channels; ch++) {
+          linep->channel[ch] = *readp++;
+        }
+        ++linep;
+      }
+    }
+    else {
+      for(x=0; x<width; x++) {
+        for(ch=0; ch<channels; ch++) {
+          /* we just clamp samples to the correct range */
+          unsigned sample = *readp++;
+          if (sample > 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;y<height;y++) {
+    linep = line;
+    readp = read_buf;
+    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;
+      }
+    }
+    for(x=0; x<width; x++) {
+      for(ch=0; ch<channels; ch++) {
+        unsigned sample = (readp[0] << 8) + readp[1];
+        if (sample > 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;y<height;y++) {
+    linep = line;
+    for(x=0; x<width; x++) {
+      for(ch=0; ch<channels; ch++) {
+        int sample;
+        
+        if (!gnum(mb, &sample)) {
+          myfree(line);
+          if (allow_partial) {
+            i_tags_setn(&im->tags, "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<height;y++) {
+    linep = line;
+    for(x=0; x<width; x++) {
+      for(ch=0; ch<channels; ch++) {
+        int sample;
+        
+        if (!gnum(mb, &sample)) {
+          myfree(line);
+          if (allow_partial) {
+          }
+          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 / 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<height;y++) for(x=0; x<width; x++) {
-      for(ch=0; ch<channels; ch++) {
-       int t;
-       if (gnum(&buf, &t)) val.channel[ch] = (t * 255 + rounder) / maxval;
-       else {
-         mm_log((1,"i_readpnm: gnum() returned false in data\n"));
-         return im;
-       }
-      }
-      i_ppix(im, x, y, &val);
-    }
+    if (maxval > 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<height;y++) for(x=0; x<width; x+=8) {
-      if ( (uc = (unsigned char*)gnext(&buf)) ) {
-       int xt;
-       int pc = width-x < 8 ? width-x : 8;
-       /*      mm_log((1,"i_readpnm: y=%d x=%d pc=%d\n", y, x, pc)); */
-       for(xt = 0; xt<pc; xt++) {
-         val.channel[0] = (*uc & (128>>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<height;y++) for(x=0; x<width; x++) {
-      for(ch=0; ch<channels; ch++) {
-       if ( (uc = (unsigned char*)gnext(&buf)) ) 
-         val.channel[ch] = (*uc * 255 + rounder) / maxval;
-       else {
-         mm_log((1,"i_readpnm: gnext() returned false in data\n"));
-         return im;
-       }
-      }
-      i_ppix(im, x, y, &val);
-    }
+    if (maxval > 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 <addi@umich.edu>
+Arnar M. Hrafnkelsson <addi@umich.edu>, Tony Cook<tony@imager.perl.org>
 
 =head1 SEE ALSO
 
diff --git a/quant.c b/quant.c
index 9d3abc5..52942d1 100644 (file)
--- 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 (file)
index 0000000..c096ec9
--- /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 (file)
index 0000000..8b34a97
--- /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
index 546aa1c..9813afc 100644 (file)
@@ -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)");
-}
index f4aed66..6d9d952 100644 (file)
@@ -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;
index c4019e1..04200c0 100644 (file)
@@ -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 (file)
index 0000000..2f37aed
--- /dev/null
@@ -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 (file)
index 0000000..2663bab
--- /dev/null
@@ -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 (file)
index 0000000..1fe0672
--- /dev/null
@@ -0,0 +1,5 @@
+P3
+2 2
+255
+255 255 255 255 255 255
+255 255 x
\ No newline at end of file
index a9f89b1..8e9c8f5 100644 (file)
Binary files a/testimg/maxval_256.ppm and b/testimg/maxval_256.ppm differ
diff --git a/testimg/pbm_base.pgm b/testimg/pbm_base.pgm
new file mode 100644 (file)
index 0000000..e45a1be
Binary files /dev/null and b/testimg/pbm_base.pgm differ
diff --git a/testimg/pgm.pgm b/testimg/pgm.pgm
new file mode 100644 (file)
index 0000000..2bff2c1
--- /dev/null
@@ -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 (file)
index 0000000..a45b270
--- /dev/null
@@ -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 (file)
index 0000000..0e3fe44
--- /dev/null
@@ -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 (file)
index 0000000..e47e653
--- /dev/null
@@ -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 (file)
index 0000000..5afe60e
--- /dev/null
@@ -0,0 +1,3 @@
+P4
+16 2
\80þ
\ No newline at end of file
diff --git a/testimg/short_bin.pgm b/testimg/short_bin.pgm
new file mode 100644 (file)
index 0000000..8550f7e
--- /dev/null
@@ -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 (file)
index 0000000..b07b37d
--- /dev/null
@@ -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 (file)
index 0000000..757ed59
--- /dev/null
@@ -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 (file)
index 0000000..e5384d4
--- /dev/null
@@ -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 48f4870..ccbcd55 100644 (file)
--- 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) {