- INCOMPATIBLE CHANGE:
authorTony Cook <tony@develop=help.com>
Thu, 27 Oct 2005 04:51:38 +0000 (04:51 +0000)
committerTony Cook <tony@develop=help.com>
Thu, 27 Oct 2005 04:51:38 +0000 (04:51 +0000)
  reading a gif file will now read the first image from the file rather
  than the a consolidated image.  If you want the old behaviour supply
  C<< gif_consolidate=>1 >> to the read() method.  Older versions of
  Imager will accept this parameter and produce the same result.
- you can now supply a page parameter to read() to read a given page
  from a GIF file.
- reading a multi-image GIF was leaking memory (a line buffer per file)

Changes
Imager.pm
Imager.xs
TODO
gif.c
image.h
lib/Imager/Files.pod
t/t105gif.t

diff --git a/Changes b/Changes
index 9e023c3..731333d 100644 (file)
--- a/Changes
+++ b/Changes
@@ -1154,6 +1154,14 @@ Revision history for Perl extension Imager.
   Imager::Cookbook.
 - added sampled/slant_text.pl, and notes on shearing/rotating text in
   Imager::Cookbook.
+- INCOMPATIBLE CHANGE:
+  reading a gif file will now read the first image from the file rather 
+  than the a consolidated image.  If you want the old behaviour supply
+  C<< gif_consolidate=>1 >> to the read() method.  Older versions of 
+  Imager will accept this parameter and produce the same result.
+- you can now supply a page parameter to read() to read a given page
+  from a GIF file.
+- reading a multi-image GIF was leaking memory (a line buffer per file)
 
 =================================================================
 
index 5a7b77a..a2a6af8 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -1193,16 +1193,28 @@ sub read {
       $self->{ERRSTR} = "option 'colors' must be a scalar reference";
       return undef;
     }
-    if ($input{colors}) {
-      my $colors;
-      ($self->{IMG}, $colors) =i_readgif_wiol( $IO );
-      if ($colors) {
-       ${ $input{colors} } = [ map { NC(@$_) } @$colors ];
+    if ($input{'gif_consolidate'}) {
+      if ($input{colors}) {
+       my $colors;
+       ($self->{IMG}, $colors) =i_readgif_wiol( $IO );
+       if ($colors) {
+         ${ $input{colors} } = [ map { NC(@$_) } @$colors ];
+       }
+      }
+      else {
+       $self->{IMG} =i_readgif_wiol( $IO );
       }
     }
     else {
-      $self->{IMG} =i_readgif_wiol( $IO );
+      my $page = $input{'page'};
+      defined $page or $page = 0;
+      $self->{IMG} = i_readgif_single_wiol( $IO, $page );
+      if ($input{colors}) {
+       ${ $input{colors} } =
+         [ i_getcolors($self->{IMG}, 0, i_colorcount($self->{IMG})) ];
+      }
     }
+
     if ( !defined($self->{IMG}) ) {
       $self->{ERRSTR}=$self->_error_as_msg();
       return undef;
index 03082e1..3fafbe8 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -2478,6 +2478,11 @@ i_readgif_wiol(ig)
             PUSHs(newRV_noinc((SV*)ct));
         }
 
+Imager::ImgRaw
+i_readgif_single_wiol(ig, page=0)
+       Imager::IO      ig
+        int            page
+
 void
 i_readgif_scalar(...)
           PROTOTYPE: $
diff --git a/TODO b/TODO
index 8df3329..ac5073d 100644 (file)
--- a/TODO
+++ b/TODO
@@ -52,7 +52,7 @@ not commitments.
     http://nntp.perl.org/group/perl.perl5.porters/102434
   (#1521, #5608, #8231, #11429, #13058)
 
-- have $img->read() act like ($img) = Imager->read_multi() on GIFs
+- have $img->read() act like ($img) = Imager->read_multi() on GIFs (done)
 
 - figure out what the nearest_color filter does, and document it
 
diff --git a/gif.c b/gif.c
index a426f1c..8a538c4 100644 (file)
--- a/gif.c
+++ b/gif.c
@@ -422,13 +422,16 @@ static void free_images(i_img **imgs, int count) {
 }
 
 /*
-=item i_readgif_multi_low(GifFileType *gf, int *count)
+=item i_readgif_multi_low(GifFileType *gf, int *count, int page)
 
 Reads one of more gif images from the given GIF file.
 
 Returns a pointer to an array of i_img *, and puts the count into 
 *count.
 
+If page is not -1 then the given image _only_ is returned from the
+file, where the first image is 0, the second 1 and so on.
+
 Unlike the normal i_readgif*() functions the images are paletted
 images rather than a combined RGB image.
 
@@ -502,7 +505,7 @@ standard.
 =cut
 */
 
-i_img **i_readgif_multi_low(GifFileType *GifFile, int *count) {
+i_img **i_readgif_multi_low(GifFileType *GifFile, int *count, int page) {
   i_img *img;
   int i, j, Size, Width, Height, ExtCode, Count;
   int ImageNum = 0, BackGround = 0, ColorMapSize = 0;
@@ -542,6 +545,7 @@ i_img **i_readgif_multi_low(GifFileType *GifFile, int *count) {
       i_push_error(0, "Unable to get record type");
       free_images(results, *count);
       DGifCloseFile(GifFile);
+      myfree(GifRow);
       return NULL;
     }
     
@@ -552,140 +556,176 @@ i_img **i_readgif_multi_low(GifFileType *GifFile, int *count) {
        i_push_error(0, "Unable to get image descriptor");
         free_images(results, *count);
        DGifCloseFile(GifFile);
+       myfree(GifRow);
        return NULL;
       }
 
-      if (( ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap) )) {
-       mm_log((1, "Adding local colormap\n"));
-       ColorMapSize = ColorMap->ColorCount;
-      } else {
-       /* No colormap and we are about to read in the image - 
-           abandon for now */
-       mm_log((1, "Going in with no colormap\n"));
-       i_push_error(0, "Image does not have a local or a global color map");
-        free_images(results, *count);
-       DGifCloseFile(GifFile);
-       return NULL;
-      }
-      
       Width = GifFile->Image.Width;
       Height = GifFile->Image.Height;
-      channels = 3;
-      if (got_gce && trans_index >= 0)
-        channels = 4;
-      if (!i_int_check_image_file_limits(Width, Height, channels, sizeof(i_sample_t))) {
-       free_images(results, *count);
-       mm_log((1, "i_readgif: image size exceeds limits\n"));
-       return NULL;
-      }
-      img = i_img_pal_new(Width, Height, channels, 256);
-      if (!img) {
-        free_images(results, *count);
-        return NULL;
-      }
-      /* populate the palette of the new image */
-      mm_log((1, "ColorMapSize %d\n", ColorMapSize));
-      for (i = 0; i < ColorMapSize; ++i) {
-        i_color col;
-        col.rgba.r = ColorMap->Colors[i].Red;
-        col.rgba.g = ColorMap->Colors[i].Green;
-        col.rgba.b = ColorMap->Colors[i].Blue;
-        if (channels == 4 && trans_index == i)
-          col.rgba.a = 0;
-        else
-          col.rgba.a = 255;
-       
-        i_addcolors(img, &col, 1);
-      }
-      ++*count;
-      if (*count > result_alloc) {
-        if (result_alloc == 0) {
-          result_alloc = 5;
-          results = mymalloc(result_alloc * sizeof(i_img *));
-        }
-        else {
-          /* myrealloc never fails (it just dies if it can't allocate) */
-          result_alloc *= 2;
-          results = myrealloc(results, result_alloc * sizeof(i_img *));
-        }
-      }
-      results[*count-1] = img;
-      i_tags_add(&img->tags, "i_format", 0, "gif", -1, 0);
-      i_tags_addn(&img->tags, "gif_left", 0, GifFile->Image.Left);
-      /**(char *)0 = 1;*/
-      i_tags_addn(&img->tags, "gif_top",  0, GifFile->Image.Top);
-      i_tags_addn(&img->tags, "gif_interlace", 0, GifFile->Image.Interlace);
-      i_tags_addn(&img->tags, "gif_screen_width", 0, GifFile->SWidth);
-      i_tags_addn(&img->tags, "gif_screen_height", 0, GifFile->SHeight);
-      if (GifFile->SColorMap && !GifFile->Image.ColorMap) {
-        i_tags_addn(&img->tags, "gif_background", 0, 
-                    GifFile->SBackGroundColor);
-      }
-      if (GifFile->Image.ColorMap) {
-        i_tags_addn(&img->tags, "gif_localmap", 0, 1);
-      }
-      if (got_gce) {
-        if (trans_index >= 0) {
-          i_color trans;
-          i_tags_addn(&img->tags, "gif_trans_index", 0, trans_index);
-          i_getcolors(img, trans_index, &trans, 1);
-          i_tags_set_color(&img->tags, "gif_trans_color", 0, &trans);
-        }
-        i_tags_addn(&img->tags, "gif_delay", 0, gif_delay);
-        i_tags_addn(&img->tags, "gif_user_input", 0, user_input);
-        i_tags_addn(&img->tags, "gif_disposal", 0, disposal);
-      }
-      got_gce = 0;
-      if (got_ns_loop)
-        i_tags_addn(&img->tags, "gif_loop", 0, ns_loop);
-      if (comment) {
-        i_tags_add(&img->tags, "gif_comment", 0, comment, strlen(comment), 0);
-        myfree(comment);
-        comment = NULL;
-      }
-
-      ImageNum++;
-      mm_log((1,"i_readgif_multi_low: Image %d at (%d, %d) [%dx%d]: \n",
-             ImageNum, GifFile->Image.Left, GifFile->Image.Top, Width, Height));
+      if (page == -1 || page == ImageNum) {
+       if (( ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap) )) {
+         mm_log((1, "Adding local colormap\n"));
+         ColorMapSize = ColorMap->ColorCount;
+       } else {
+         /* No colormap and we are about to read in the image - 
+            abandon for now */
+         mm_log((1, "Going in with no colormap\n"));
+         i_push_error(0, "Image does not have a local or a global color map");
+         free_images(results, *count);
+         DGifCloseFile(GifFile);
+         myfree(GifRow);
+         return NULL;
+       }
+       
+       channels = 3;
+       if (got_gce && trans_index >= 0)
+         channels = 4;
+       if (!i_int_check_image_file_limits(Width, Height, channels, sizeof(i_sample_t))) {
+         free_images(results, *count);
+         mm_log((1, "i_readgif: image size exceeds limits\n"));
+         myfree(GifRow);
+         return NULL;
+       }
+       img = i_img_pal_new(Width, Height, channels, 256);
+       if (!img) {
+         free_images(results, *count);
+         return NULL;
+       }
+       /* populate the palette of the new image */
+       mm_log((1, "ColorMapSize %d\n", ColorMapSize));
+       for (i = 0; i < ColorMapSize; ++i) {
+         i_color col;
+         col.rgba.r = ColorMap->Colors[i].Red;
+         col.rgba.g = ColorMap->Colors[i].Green;
+         col.rgba.b = ColorMap->Colors[i].Blue;
+         if (channels == 4 && trans_index == i)
+           col.rgba.a = 0;
+         else
+           col.rgba.a = 255;
+         
+         i_addcolors(img, &col, 1);
+       }
+       ++*count;
+       if (*count > result_alloc) {
+         if (result_alloc == 0) {
+           result_alloc = 5;
+           results = mymalloc(result_alloc * sizeof(i_img *));
+         }
+         else {
+           /* myrealloc never fails (it just dies if it can't allocate) */
+           result_alloc *= 2;
+           results = myrealloc(results, result_alloc * sizeof(i_img *));
+         }
+       }
+       results[*count-1] = img;
+       i_tags_add(&img->tags, "i_format", 0, "gif", -1, 0);
+       i_tags_addn(&img->tags, "gif_left", 0, GifFile->Image.Left);
+       /**(char *)0 = 1;*/
+       i_tags_addn(&img->tags, "gif_top",  0, GifFile->Image.Top);
+       i_tags_addn(&img->tags, "gif_interlace", 0, GifFile->Image.Interlace);
+       i_tags_addn(&img->tags, "gif_screen_width", 0, GifFile->SWidth);
+       i_tags_addn(&img->tags, "gif_screen_height", 0, GifFile->SHeight);
+       if (GifFile->SColorMap && !GifFile->Image.ColorMap) {
+         i_tags_addn(&img->tags, "gif_background", 0, 
+                     GifFile->SBackGroundColor);
+       }
+       if (GifFile->Image.ColorMap) {
+         i_tags_addn(&img->tags, "gif_localmap", 0, 1);
+       }
+       if (got_gce) {
+         if (trans_index >= 0) {
+           i_color trans;
+           i_tags_addn(&img->tags, "gif_trans_index", 0, trans_index);
+           i_getcolors(img, trans_index, &trans, 1);
+           i_tags_set_color(&img->tags, "gif_trans_color", 0, &trans);
+         }
+         i_tags_addn(&img->tags, "gif_delay", 0, gif_delay);
+         i_tags_addn(&img->tags, "gif_user_input", 0, user_input);
+         i_tags_addn(&img->tags, "gif_disposal", 0, disposal);
+       }
+       got_gce = 0;
+       if (got_ns_loop)
+         i_tags_addn(&img->tags, "gif_loop", 0, ns_loop);
+       if (comment) {
+         i_tags_add(&img->tags, "gif_comment", 0, comment, strlen(comment), 0);
+         myfree(comment);
+         comment = NULL;
+       }
+       
+       mm_log((1,"i_readgif_multi_low: Image %d at (%d, %d) [%dx%d]: \n",
+               ImageNum, GifFile->Image.Left, GifFile->Image.Top, Width, Height));
+       
+       if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth ||
+           GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) {
+         i_push_errorf(0, "Image %d is not confined to screen dimension, aborted.\n",ImageNum);
+         free_images(results, *count);        
+         DGifCloseFile(GifFile);
+         myfree(GifRow);
+         return(0);
+       }
+       
+       if (GifFile->Image.Interlace) {
+         for (Count = i = 0; i < 4; i++) {
+           for (j = InterlacedOffset[i]; j < Height; 
+                j += InterlacedJumps[i]) {
+             Count++;
+             if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
+               gif_push_error();
+               i_push_error(0, "Reading GIF line");
+               free_images(results, *count);
+               DGifCloseFile(GifFile);
+               myfree(GifRow);
+               return NULL;
+             }
+             
+             i_ppal(img, 0, Width, j, GifRow);
+           }
+         }
+       }
+       else {
+         for (i = 0; i < Height; i++) {
+           if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
+             gif_push_error();
+             i_push_error(0, "Reading GIF line");
+             free_images(results, *count);
+             DGifCloseFile(GifFile);
+             myfree(GifRow);
+             return NULL;
+           }
+           
+           i_ppal(img, 0, Width, i, GifRow);
+         }
+       }
 
-      if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth ||
-         GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) {
-       i_push_errorf(0, "Image %d is not confined to screen dimension, aborted.\n",ImageNum);
-       free_images(results, *count);        
-       DGifCloseFile(GifFile);
-       return(0);
-      }
-            
-      if (GifFile->Image.Interlace) {
-       for (Count = i = 0; i < 4; i++) {
-          for (j = InterlacedOffset[i]; j < Height; 
-               j += InterlacedJumps[i]) {
-            Count++;
-            if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
-              gif_push_error();
-              i_push_error(0, "Reading GIF line");
-              free_images(results, *count);
-              DGifCloseFile(GifFile);
-              return NULL;
-            }
-            
-            i_ppal(img, 0, Width, j, GifRow);
-          }
+       /* must be only one image wanted and that was it */
+       if (page != -1) {
+         myfree(GifRow);
+         DGifCloseFile(GifFile);
+         return results;
        }
       }
       else {
+       /* skip the image */
+       /* whether interlaced or not, it has the same number of lines */
+       /* giflib does't have an interface to skip the image data */
        for (i = 0; i < Height; i++) {
          if (DGifGetLine(GifFile, GifRow, Width) == GIF_ERROR) {
            gif_push_error();
            i_push_error(0, "Reading GIF line");
-            free_images(results, *count);
+           free_images(results, *count);
+           myfree(GifRow);
            DGifCloseFile(GifFile);
            return NULL;
          }
+       }
 
-          i_ppal(img, 0, Width, i, GifRow);
+       /* kill the comment so we get the right comment for the page */
+       if (comment) {
+         myfree(comment);
+         comment = NULL;
        }
       }
+      ImageNum++;
       break;
     case EXTENSION_RECORD_TYPE:
       /* Skip any extension blocks in file: */
@@ -768,6 +808,13 @@ i_img **i_readgif_multi_low(GifFileType *GifFile, int *count) {
     return NULL;
   }
 
+  if (ImageNum && page != -1) {
+    /* there were images, but the page selected wasn't found */
+    i_push_errorf(0, "page %d not found (%d total)", page, ImageNum);
+    free_images(results, *count);
+    return NULL;
+  }
+
   return results;
 }
 
@@ -804,7 +851,7 @@ i_readgif_multi_wiol(io_glue *ig, int *count) {
       return NULL;
     }
     
-    return i_readgif_multi_low(GifFile, count);
+    return i_readgif_multi_low(GifFile, count, -1);
 #else
     i_clear_error();
     i_push_error(0, "callbacks not supported with giflib3");
@@ -834,7 +881,7 @@ i_readgif_multi(int fd, int *count) {
     return NULL;
   }
 
-  return i_readgif_multi_low(GifFile, count);
+  return i_readgif_multi_low(GifFile, count, -1);
 }
 
 /*
@@ -864,7 +911,7 @@ i_readgif_multi_scalar(char *data, int length, int *count) {
     return NULL;
   }
 
-  return i_readgif_multi_low(GifFile, count);
+  return i_readgif_multi_low(GifFile, count, -1);
 #else
   return NULL;
 #endif
@@ -900,7 +947,7 @@ i_readgif_multi_callback(i_read_callback_t cb, char *userdata, int *count) {
     return NULL;
   }
 
-  result = i_readgif_multi_low(GifFile, count);
+  result = i_readgif_multi_low(GifFile, count, -1);
   i_free_gen_read_data(gci);
 
   return result;
@@ -1111,6 +1158,91 @@ i_readgif_wiol(io_glue *ig, int **color_table, int *colors) {
   }
 }
 
+/*
+=item i_readgif_single_low(GifFile, page)
+
+Lower level function to read a single image from a GIF.
+
+page must be non-negative.
+
+=cut
+*/
+static i_img *
+i_readgif_single_low(GifFileType *GifFile, int page) {
+  int count = 0;
+  i_img **imgs;
+
+  imgs = i_readgif_multi_low(GifFile, &count, page);
+
+  if (imgs && count) {
+    i_img *result = imgs[0];
+
+    myfree(imgs);
+    return result;
+  }
+  else {
+    /* i_readgif_multi_low() handles the errors appropriately */
+    return NULL;
+  }
+}
+
+/*
+=item i_readgif_single_wiol(ig, page)
+
+Read a single page from a GIF image file, where the page is indexed
+from 0.
+
+Returns NULL if the page isn't found.
+
+=cut
+*/
+
+i_img *
+i_readgif_single_wiol(io_glue *ig, int page) {
+  io_glue_commit_types(ig);
+
+  i_clear_error();
+
+  if (page < 0) {
+    i_push_error(0, "page must be non-negative");
+    return NULL;
+  }
+
+  if (ig->source.type == FDSEEK || ig->source.type == FDNOSEEK) {
+    GifFileType *GifFile;
+    int fd = dup(ig->source.fdseek.fd);
+    if (fd < 0) {
+      i_push_error(errno, "dup() failed");
+      return NULL;
+    }
+    if ((GifFile = DGifOpenFileHandle(fd)) == NULL) {
+      gif_push_error();
+      i_push_error(0, "Cannot create giflib file object");
+      mm_log((1,"i_readgif: Unable to open file\n"));
+      return NULL;
+    }
+    return i_readgif_single_low(GifFile, page);
+  }
+  else {
+#if IM_GIFMAJOR >= 4
+    GifFileType *GifFile;
+
+    if ((GifFile = DGifOpen((void *)ig, io_glue_read_cb )) == NULL) {
+      gif_push_error();
+      i_push_error(0, "Cannot create giflib callback object");
+      mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n"));
+      return NULL;
+    }
+    
+    return i_readgif_single_low(GifFile, page);
+#else
+    i_push_error(0, "callbacks not supported with giflib3");
+
+    return NULL;
+#endif
+  }
+}
+
 /*
 =item do_write(GifFileType *gf, i_gif_opts *opts, i_img *img, i_palidx *data)
 
diff --git a/image.h b/image.h
index fa8814f..b64477e 100644 (file)
--- a/image.h
+++ b/image.h
@@ -591,6 +591,7 @@ i_img *i_readgif(int fd, int **colour_table, int *colours);
 i_img *i_readgif_wiol(io_glue *ig, int **colour_table, int *colours);
 i_img *i_readgif_scalar(char *data, int length, int **colour_table, int *colours);
 i_img *i_readgif_callback(i_read_callback_t callback, char *userdata, int **colour_table, int *colours);
+i_img *i_readgif_single_wiol(io_glue *ig, int page);
 extern i_img **i_readgif_multi(int fd, int *count);
 extern i_img **i_readgif_multi_scalar(char *data, int length, int *count);
 extern i_img **i_readgif_multi_callback(i_read_callback_t callback, char *userdata, int *count);
index 02708f3..95b7de0 100644 (file)
@@ -434,6 +434,25 @@ This is currently unimplemented due to some limitations in giflib.
 
 =back
 
+You can supply a C<page> parameter to the C<read()> method to read
+some page other than the first.  The page is 0 based:
+
+  # read the second image in the file
+  $image->read(file=>"example.gif", page=>1)
+    or die "Cannot read second page: ",$image->errstr,"\n";
+
+Before release 0.46, Imager would read multi-image GIF image files
+into a single image, overlaying each of the images onto the virtual
+GIF screen.
+
+As of 0.46 the default is to read the first image from the file, as if
+called with C<< page => 0 >>.
+
+You can return to the previous behaviour by calling read with the
+C<gif_consolidate> parameter set to a true value:
+
+  $img->read(file=>$some_gif_file, gif_consolidate=>1);
+
 =head2 TIFF (Tagged Image File Format)
 
 Imager can write images to either paletted or RGB TIFF images,
index 7056862..2c8175f 100644 (file)
@@ -2,7 +2,7 @@
 use strict;
 $|=1;
 use lib 't';
-use Test::More tests => 84;
+use Test::More tests => 107;
 use Imager qw(:all);
 BEGIN { require "t/testtools.pl"; }
 use Carp 'confess';
@@ -38,7 +38,7 @@ SKIP:
     $im = Imager->new(xsize=>2, ysize=>2);
     ok(!$im->write(file=>"testout/nogif.gif"), "should fail to write gif");
     is($im->errstr, 'format not supported', "check no gif message");
-    skip("no gif support", 80);
+    skip("no gif support", 103);
   }
     open(FH,">testout/t105.gif") || die "Cannot open testout/t105.gif\n";
     binmode(FH);
@@ -439,7 +439,8 @@ EOS
       my $ooim = Imager->new;
       ok($ooim->read(file=>"testout/t105.gif"), "read into object");
       ok($ooim->write(file=>"testout/t105_warn.gif", interlace=>1),
-        "save from object");
+        "save from object")
+       or print "# ", $ooim->errstr, "\n";
       ok(grep(/Obsolete .* interlace .* gif_interlace/, @warns),
         "check for warning");
       init(warn_obsolete=>0);
@@ -564,6 +565,77 @@ EOS
        "should succeed - just inside bytes limit");
     Imager->set_file_limits(reset=>1);
   }
+
+  {
+    print "# test OO interface reading of consolidated images\n";
+    my $im = Imager->new;
+    ok($im->read(file=>'testimg/screen2.gif', gif_consolidate=>1),
+       "read image to consolidate");
+    my $expected = Imager->new;
+    ok($expected->read(file=>'testimg/expected.gif'),
+       "read expected via OO");
+    is(i_img_diff($im->{IMG}, $expected->{IMG}), 0,
+       "compare them");
+
+    # check the default read doesn't match
+    ok($im->read(file=>'testimg/screen2.gif'),
+       "read same image without consolidate");
+    isnt(i_img_diff($im->{IMG}, $expected->{IMG}), 0,
+       "compare them - shouldn't include the overlayed second image");
+  }
+  {
+    print "# test the reading of single pages\n";
+    # build a test file
+    my $test_file = 'testout/t105_multi_sing.gif';
+    my $im1 = Imager->new(xsize=>100, ysize=>100);
+    $im1->box(filled=>1, color=>$blue);
+    $im1->addtag(name=>'gif_left', value=>10);
+    $im1->addtag(name=>'gif_top', value=>15);
+    $im1->addtag(name=>'gif_comment', value=>'First page');
+    my $im2 = Imager->new(xsize=>50, ysize=>50);
+    $im2->box(filled=>1, color=>$red);
+    $im2->addtag(name=>'gif_left', value=>30);
+    $im2->addtag(name=>'gif_top', value=>25);
+    $im2->addtag(name=>'gif_comment', value=>'Second page');
+    my $im3 = Imager->new(xsize=>25, ysize=>25);
+    $im3->box(filled=>1, color=>$green);
+    $im3->addtag(name=>'gif_left', value=>35);
+    $im3->addtag(name=>'gif_top', value=>45);
+    # don't set comment for $im3
+    ok(Imager->write_multi({ file=> $test_file}, $im1, $im2, $im3),
+       "write test file for single page reads");
+    
+    my $res = Imager->new;
+    # check we get the first image
+    ok($res->read(file=>$test_file), "read default (first) page");
+    is(i_img_diff($im1->{IMG}, $res->{IMG}), 0, "compare against first");
+    # check tags
+    is($res->tags(name=>'gif_left'), 10, "gif_left");
+    is($res->tags(name=>'gif_top'), 15, "gif_top");
+    is($res->tags(name=>'gif_comment'), 'First page', "gif_comment");
+
+    # get the second image
+    ok($res->read(file=>$test_file, page=>1), "read second page")
+      or print "# ",$res->errstr, "\n";
+    is(i_img_diff($im2->{IMG}, $res->{IMG}), 0, "compare against second");
+    # check tags
+    is($res->tags(name=>'gif_left'), 30, "gif_left");
+    is($res->tags(name=>'gif_top'), 25, "gif_top");
+    is($res->tags(name=>'gif_comment'), 'Second page', "gif_comment");
+
+    # get the third image
+    ok($res->read(file=>$test_file, page=>2), "read third page")
+      or print "# ",$res->errstr, "\n";
+    is(i_img_diff($im3->{IMG}, $res->{IMG}), 0, "compare against third");
+    is($res->tags(name=>'gif_left'), 35, "gif_left");
+    is($res->tags(name=>'gif_top'), 45, "gif_top");
+    is($res->tags(name=>'gif_comment'), undef, 'gif_comment undef');
+
+    # try to read a fourth page
+    ok(!$res->read(file=>$test_file, page=>3), "fail reading fourth page");
+    cmp_ok($res->errstr, "=~", 'page 3 not found',
+          "check error message");
+  }
 }
 
 sub test_readgif_cb {