- read paletted tiff images into Imager paletted images
authorTony Cook <tony@develop=help.com>
Sun, 25 Nov 2001 03:45:42 +0000 (03:45 +0000)
committerTony Cook <tony@develop=help.com>
Sun, 25 Nov 2001 03:45:42 +0000 (03:45 +0000)
- on partial tiff image reads, set the i_incomplete tag
- tiff reading now uses the error stack
- use the error stack value from reading bmp files
- fix an error message in bmp.c

Changes
Imager.pm
MANIFEST
bmp.c
t/t106tiff.t
testimg/comp4bad.tif [new file with mode: 0755]
testimg/comp8.tif
tiff.c

diff --git a/Changes b/Changes
index 49c8624..ec35eaa 100644 (file)
--- a/Changes
+++ b/Changes
@@ -552,6 +552,11 @@ Revision history for Perl extension Imager.
         - make color values smarter for the drawing functions
         - implemented reading and writing the TIFF text tags
         - added prototypes for some of the derivative tags functions
+        - read paletted tiff images into Imager paletted images
+        - on partial tiff image reads, set the i_incomplete tag
+        - tiff reading now uses the error stack
+        - use the error stack value from reading bmp files
+        - fix an error message in bmp.c
 
 =================================================================
 
index 1f1e510..f5444a5 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -913,7 +913,7 @@ sub read {
     if ( $input{'type'} eq 'tiff' ) {
       $self->{IMG}=i_readtiff_wiol( $IO, -1 ); # Fixme, check if that length parameter is ever needed
       if ( !defined($self->{IMG}) ) {
-       $self->{ERRSTR}='unable to read tiff image'; return undef;
+       $self->{ERRSTR}=$self->_error_as_msg(); return undef;
       }
       $self->{DEBUG} && print "loading a tiff file\n";
       return $self;
@@ -940,7 +940,7 @@ sub read {
     if ( $input{'type'} eq 'bmp' ) {
       $self->{IMG}=i_readbmp_wiol( $IO );
       if ( !defined($self->{IMG}) ) {
-       $self->{ERRSTR}='unable to read bmp image';
+       $self->{ERRSTR}=$self->_error_as_msg();
        return undef;
       }
       $self->{DEBUG} && print "loading a bmp file\n";
@@ -4144,6 +4144,11 @@ 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 i_incomplete
+
+If this tag is present then the whole image could not be read.  This
+isn't implemented for all images yet.
+
 =back
 
 =head1 BUGS
index 00d5343..c715a05 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -115,7 +115,10 @@ t/t75polyaa.t
 t/t90cc.t
 testimg/bandw.gif
 testimg/comp4.bmp       Compressed 4-bit/pixel BMP
+testimg/comp4.tif       4-bit/pixel paletted TIFF
+testimg/comp4bad.tif    corrupted 4-bit/pixel paletted TIFF
 testimg/comp8.bmp       Compressed 8-bit/pixel BMP
+testimg/comp8.tif       8-bit/pixel paletted TIFF
 testimg/expected.gif
 testimg/gimpgrad        A GIMP gradient file
 testimg/junk.ppm
diff --git a/bmp.c b/bmp.c
index fdc542c..2b1fd34 100644 (file)
--- a/bmp.c
+++ b/bmp.c
@@ -278,7 +278,7 @@ write_packed(io_glue *ig, char *format, ...) {
       break;
 
     default:
-      m_fatal(1, "Unknown read_packed format code 0x%02x", *format);
+      m_fatal(1, "Unknown write_packed format code 0x%02x", *format);
     }
     ++format;
   }
index 2b4e9fd..4c6b7b1 100644 (file)
@@ -1,5 +1,5 @@
 #!perl -w
-print "1..21\n";
+print "1..33\n";
 use Imager qw(:all);
 $^W=1; # warnings during command-line tests
 $|=1;  # give us some progress in the test harness
@@ -21,8 +21,10 @@ my $trans = i_color_new(255, 0, 0, 127);
 i_box_filled($timg, 0, 0, 20, 20, $green);
 i_box_filled($timg, 2, 2, 18, 18, $trans);
 
+my $test_num;
+
 if (!i_has_format("tiff")) {
-  for (1..21) {
+  for (1..33) {
     print "ok $_ # skip no tiff support\n";
   }
 } else {
@@ -154,5 +156,47 @@ if (!i_has_format("tiff")) {
   $oofim->write(file=>'testout/t106_oo_faxlo.tiff', class=>'fax', fax_fine=>0)
     or print "not ";
   print "ok 21\n";
+
+  # paletted reads
+  my $img4 = Imager->new;
+  $test_num = 22;
+  ok($img4->read(file=>'testimg/comp4.tif'), "reading 4-bit paletted");
+  ok($img4->type eq 'paletted', "image isn't paletted");
+  print "# colors: ", $img4->colorcount,"\n";
+  ok($img4->colorcount <= 16, "more than 16 colors!");
+  #ok($img4->write(file=>'testout/t106_was4.ppm'),
+  #   "Cannot write img4");
+  # I know I'm using BMP before it's test, but comp4.tif started life 
+  # as comp4.bmp
+  my $bmp4 = Imager->new;
+  ok($bmp4->read(file=>'testimg/comp4.bmp'), "reading 4-bit bmp!");
+  $diff = i_img_diff($img4->{IMG}, $bmp4->{IMG});
+  print "# diff $diff\n";
+  ok($diff == 0, "image mismatch");
+  my $img8 = Imager->new;
+  ok($img8->read(file=>'testimg/comp8.tif'), "reading 8-bit paletted");
+  ok($img8->type eq 'paletted', "image isn't paletted");
+  print "# colors: ", $img8->colorcount,"\n";
+  #ok($img8->write(file=>'testout/t106_was8.ppm'),
+  #   "Cannot write img8");
+  ok($img8->colorcount == 256, "more colors than expected");
+  my $bmp8 = Imager->new;
+  ok($bmp8->read(file=>'testimg/comp8.bmp'), "reading 8-bit bmp!");
+  $diff = i_img_diff($img8->{IMG}, $bmp8->{IMG});
+  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(scalar $bad->tags(name=>'i_incomplete'), "incomplete tag not set");
 }
 
+sub ok {
+  my ($ok, $msg) = @_;
+
+  if ($ok) {
+    print "ok ",$test_num++,"\n";
+  }
+  else {
+    print "not ok ", $test_num++," # line ",(caller)[2]," $msg\n";
+  }
+}
diff --git a/testimg/comp4bad.tif b/testimg/comp4bad.tif
new file mode 100755 (executable)
index 0000000..015ec3f
Binary files /dev/null and b/testimg/comp4bad.tif differ
index e8debfe..5dc4f32 100755 (executable)
Binary files a/testimg/comp8.tif and b/testimg/comp8.tif differ
diff --git a/tiff.c b/tiff.c
index ec4e27b..bd6d8cd 100644 (file)
--- a/tiff.c
+++ b/tiff.c
@@ -1,7 +1,7 @@
 #include "image.h"
 #include "tiffio.h"
 #include "iolayer.h"
-
+#include "imagei.h"
 
 /*
 =head1 NAME
@@ -57,6 +57,14 @@ static struct tag_name text_tag_names[] =
 static const int text_tag_count = 
   sizeof(text_tag_names) / sizeof(*text_tag_names);
 
+static void error_handler(char const *module, char const *fmt, va_list ap) {
+  i_push_errorvf(0, fmt, ap);
+}
+
+static int save_tiff_tags(TIFF *tif, i_img *im);
+
+static void expand_4bit_hl(unsigned char *buf, int count);
+
 /*
 =item comp_seek(h, o, w)
 
@@ -93,14 +101,20 @@ i_readtiff_wiol(io_glue *ig, int length) {
   i_img *im;
   uint32 width, height;
   uint16 channels;
-  uint32* raster;
+  uint32* raster = NULL;
   int tiled, error;
   TIFF* tif;
   float xres, yres;
   uint16 resunit;
   int gotXres, gotYres;
   uint16 photometric;
+  uint16 bits_per_sample;
   int i;
+  int ch;
+  TIFFErrorHandler old_handler;
+
+  i_clear_error();
+  old_handler = TIFFSetErrorHandler(error_handler);
 
   error = 0;
 
@@ -110,7 +124,7 @@ i_readtiff_wiol(io_glue *ig, int length) {
   io_glue_commit_types(ig);
   mm_log((1, "i_readtiff_wiol(ig %p, length %d)\n", ig, length));
   
-  tif = TIFFClientOpen("Iolayer: FIXME", 
+  tif = TIFFClientOpen("(Iolayer)", 
                       "rm", 
                       (thandle_t) ig,
                       (TIFFReadWriteProc) ig->readcb,
@@ -123,24 +137,32 @@ i_readtiff_wiol(io_glue *ig, int length) {
   
   if (!tif) {
     mm_log((1, "i_readtiff_wiol: Unable to open tif file\n"));
+    i_push_error(0, "opening file");
+    TIFFSetErrorHandler(old_handler);
     return NULL;
   }
 
   TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width);
   TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height);
-  TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &channels);
+  TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &channels);
   tiled = TIFFIsTiled(tif);
-  TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometric);
+  TIFFGetFieldDefaulted(tif, TIFFTAG_PHOTOMETRIC, &photometric);
+  TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &bits_per_sample);
 
   mm_log((1, "i_readtiff_wiol: width=%d, height=%d, channels=%d\n", width, height, channels));
   mm_log((1, "i_readtiff_wiol: %stiled\n", tiled?"":"not "));
   mm_log((1, "i_readtiff_wiol: %sbyte swapped\n", TIFFIsByteSwapped(tif)?"":"not "));
-  
-  im = i_img_empty_ch(NULL, width, height, channels);
 
+  if (photometric == PHOTOMETRIC_PALETTE && bits_per_sample <= 8) {
+    channels = 3;
+    im = i_img_pal_new(width, height, channels, 256);
+  }
+  else {
+    im = i_img_empty_ch(NULL, width, height, channels);
+  }
+    
   /* resolution tags */
-  if (!TIFFGetField(tif, TIFFTAG_RESOLUTIONUNIT, &resunit))
-    resunit = RESUNIT_INCH;
+  TIFFGetFieldDefaulted(tif, TIFFTAG_RESOLUTIONUNIT, &resunit);
   gotXres = TIFFGetField(tif, TIFFTAG_XRESOLUTION, &xres);
   gotYres = TIFFGetField(tif, TIFFTAG_YRESOLUTION, &yres);
   if (gotXres || gotYres) {
@@ -172,89 +194,159 @@ i_readtiff_wiol(io_glue *ig, int length) {
   }
   
   /*   TIFFPrintDirectory(tif, stdout, 0); good for debugging */
-  
-  if (tiled) {
-    int ok = 1;
-    uint32 row, col;
-    uint32 tile_width, tile_height;
 
-    TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tile_width);
-    TIFFGetField(tif, TIFFTAG_TILELENGTH, &tile_height);
-    mm_log((1, "i_readtiff_wiol: tile_width=%d, tile_height=%d\n", tile_width, tile_height));
+  if (photometric == PHOTOMETRIC_PALETTE &&
+      (bits_per_sample == 4 || bits_per_sample == 8)) {
+    uint16 *maps[3];
+    char used[256];
+    int maxused;
+    uint32 row, col;
+    unsigned char *buffer;
 
-    raster = (uint32*)_TIFFmalloc(tile_width * tile_height * sizeof (uint32));
-    if (!raster) {
-      TIFFError(TIFFFileName(tif), "No space for raster buffer");
+    if (!TIFFGetField(tif, TIFFTAG_COLORMAP, maps+0, maps+1, maps+2)) {
+      i_push_error(0, "Cannot get colormap for paletted image");
+      TIFFSetErrorHandler(old_handler);
+      i_img_destroy(im);
+      TIFFClose(tif);
       return NULL;
     }
-    
-    for( row = 0; row < height; row += tile_height ) {
-      for( col = 0; ok && col < width; col += tile_width ) {
-       uint32 i_row, x, newrows, newcols;
-
-       /* Read the tile into an RGBA array */
-       if (!TIFFReadRGBATile(tif, col, row, raster)) {
-         ok = 0;
-         break;
-       }
-       newrows = (row+tile_height > height) ? height-row : tile_height;
-       mm_log((1, "i_readtiff_wiol: newrows=%d\n", newrows));
-       newcols = (col+tile_width  > width ) ? width-row  : tile_width;
-       for( i_row = 0; i_row < tile_height; i_row++ ) {
-         for(x = 0; x < newcols; x++) {
-           i_color val;
-           uint32 temp = raster[x+tile_width*(tile_height-i_row-1)];
-           val.rgba.r = TIFFGetR(temp);
-           val.rgba.g = TIFFGetG(temp);
-           val.rgba.b = TIFFGetB(temp);
-           val.rgba.a = TIFFGetA(temp);
-           i_ppix(im, col+x, row+i_row, &val);
-         }
-       }
+    buffer = (unsigned char *)_TIFFmalloc(width+2);
+    if (!buffer) {
+      i_push_error(0, "out of memory");
+      TIFFSetErrorHandler(old_handler);
+      i_img_destroy(im);
+      TIFFClose(tif);
+      return NULL;
+    }
+    row = 0;
+    memset(used, 0, sizeof(used));
+    while (row < height && TIFFReadScanline(tif, buffer, row, 0) > 0) {
+      if (bits_per_sample == 4)
+        expand_4bit_hl(buffer, (width+1)/2);
+      for (col = 0; col < width; ++col) {
+        used[buffer[col]] = 1;
       }
+      i_ppal(im, 0, width, row, buffer);
+      ++row;
     }
-  } else {
-    uint32 rowsperstrip, row;
-    TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
-    mm_log((1, "i_readtiff_wiol: rowsperstrip=%d\n", rowsperstrip));
-    
-    raster = (uint32*)_TIFFmalloc(width * rowsperstrip * sizeof (uint32));
-    if (!raster) {
-      TIFFError(TIFFFileName(tif), "No space for raster buffer");
-      return NULL;
+    if (row < height) {
+      error = 1;
     }
-    
-    for( row = 0; row < height; row += rowsperstrip ) {
-      uint32 newrows, i_row;
+    /* Ideally we'd optimize the palette, but that could be expensive
+       since we'd have to re-index every pixel.
+
+       Optimizing the palette (even at this level) might not 
+       be what the user wants, so I don't do it.
+
+       We'll add a function to optimize a paletted image instead.
+    */
+    maxused = (1 << bits_per_sample)-1;
+    if (!error) {
+      while (maxused >= 0 && !used[maxused])
+        --maxused;
+    }
+    for (i = 0; i < 1 << bits_per_sample; ++i) {
+      i_color c;
+      for (ch = 0; ch < 3; ++ch) {
+        c.channel[ch] = Sample16To8(maps[ch][i]);
+      }
+      i_addcolors(im, &c, 1);
+    }
+    _TIFFfree(buffer);
+  }
+  else {
+    if (tiled) {
+      int ok = 1;
+      uint32 row, col;
+      uint32 tile_width, tile_height;
+      
+      TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tile_width);
+      TIFFGetField(tif, TIFFTAG_TILELENGTH, &tile_height);
+      mm_log((1, "i_readtiff_wiol: tile_width=%d, tile_height=%d\n", tile_width, tile_height));
       
-      if (!TIFFReadRGBAStrip(tif, row, raster)) {
-       error++;
-       break;
+      raster = (uint32*)_TIFFmalloc(tile_width * tile_height * sizeof (uint32));
+      if (!raster) {
+        i_img_destroy(im);
+        i_push_error(0, "No space for raster buffer");
+        TIFFSetErrorHandler(old_handler);
+        TIFFClose(tif);
+        return NULL;
       }
       
-      newrows = (row+rowsperstrip > height) ? height-row : rowsperstrip;
-      mm_log((1, "newrows=%d\n", newrows));
+      for( row = 0; row < height; row += tile_height ) {
+        for( col = 0; ok && col < width; col += tile_width ) {
+          uint32 i_row, x, newrows, newcols;
+          
+          /* Read the tile into an RGBA array */
+          if (!TIFFReadRGBATile(tif, col, row, raster)) {
+            ok = 0;
+            break;
+          }
+          newrows = (row+tile_height > height) ? height-row : tile_height;
+          mm_log((1, "i_readtiff_wiol: newrows=%d\n", newrows));
+          newcols = (col+tile_width  > width ) ? width-row  : tile_width;
+          for( i_row = 0; i_row < tile_height; i_row++ ) {
+            for(x = 0; x < newcols; x++) {
+              i_color val;
+              uint32 temp = raster[x+tile_width*(tile_height-i_row-1)];
+              val.rgba.r = TIFFGetR(temp);
+              val.rgba.g = TIFFGetG(temp);
+              val.rgba.b = TIFFGetB(temp);
+              val.rgba.a = TIFFGetA(temp);
+              i_ppix(im, col+x, row+i_row, &val);
+            }
+          }
+        }
+      }
+    } else {
+      uint32 rowsperstrip, row;
+      TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip);
+      mm_log((1, "i_readtiff_wiol: rowsperstrip=%d\n", rowsperstrip));
+      
+      raster = (uint32*)_TIFFmalloc(width * rowsperstrip * sizeof (uint32));
+      if (!raster) {
+        i_img_destroy(im);
+        i_push_error(0, "No space for raster buffer");
+        TIFFSetErrorHandler(old_handler);
+        TIFFClose(tif);
+        return NULL;
+      }
       
-      for( i_row = 0; i_row < newrows; i_row++ ) { 
-       uint32 x;
-       for(x = 0; x<width; x++) {
-         i_color val;
-         uint32 temp = raster[x+width*(newrows-i_row-1)];
-         val.rgba.r = TIFFGetR(temp);
-         val.rgba.g = TIFFGetG(temp);
-         val.rgba.b = TIFFGetB(temp);
-         val.rgba.a = TIFFGetA(temp);
-         i_ppix(im, x, i_row+row, &val);
-       }
+      for( row = 0; row < height; row += rowsperstrip ) {
+        uint32 newrows, i_row;
+        
+        if (!TIFFReadRGBAStrip(tif, row, raster)) {
+          error++;
+          break;
+        }
+        
+        newrows = (row+rowsperstrip > height) ? height-row : rowsperstrip;
+        mm_log((1, "newrows=%d\n", newrows));
+        
+        for( i_row = 0; i_row < newrows; i_row++ ) { 
+          uint32 x;
+          for(x = 0; x<width; x++) {
+            i_color val;
+            uint32 temp = raster[x+width*(newrows-i_row-1)];
+            val.rgba.r = TIFFGetR(temp);
+            val.rgba.g = TIFFGetG(temp);
+            val.rgba.b = TIFFGetB(temp);
+            val.rgba.a = TIFFGetA(temp);
+            i_ppix(im, x, i_row+row, &val);
+          }
+        }
       }
     }
-
   }
   if (error) {
     mm_log((1, "i_readtiff_wiol: error during reading\n"));
+    i_tags_addn(&im->tags, "i_incomplete", 0, 1);
   }
-  _TIFFfree( raster );
+  if (raster)
+    _TIFFfree( raster );
   if (TIFFLastDirectory(tif)) mm_log((1, "Last directory of tiff file\n"));
+  TIFFSetErrorHandler(old_handler);
+  TIFFClose(tif);
   return im;
 }
 
@@ -271,8 +363,6 @@ Stores an image in the iolayer object.
 =cut 
 */
 
-static int save_tiff_tags(TIFF *tif, i_img *im);
-
 /* FIXME: Add an options array in here soonish */
 
 undef_int
@@ -611,6 +701,27 @@ static int save_tiff_tags(TIFF *tif, i_img *im) {
   return 1;
 }
 
+/*
+=item expand_4bit_hl(buf, count)
+
+Expands 4-bit/entry packed data into 1 byte/entry.
+
+buf must contain count bytes to be expanded and have 2*count bytes total 
+space.
+
+The data is expanded in place.
+
+=cut
+*/
+
+static void expand_4bit_hl(unsigned char *buf, int count) {
+  while (--count >= 0) {
+    buf[count*2+1] = buf[count] & 0xF;
+    buf[count*2] = buf[count] >> 4;
+  }
+}
+
+
 /*
 =back