FLTK logo

[master] 1d847fe - Fix and improve Fl_GIF_Image (issue #271, #274)

FLTK matrix user chat room
(using Element browser app)   FLTK gitter user chat room   GitHub FLTK Project   FLTK News RSS Feed  
  FLTK Apps      FLTK Library      Forums      Links     Login 
 All Forums  |  Back to fltk.commit  ]
 
Previous Message ]Next Message ]

[master] 1d847fe - Fix and improve Fl_GIF_Image (issue #271, #274) "Albrecht Schlosser" Sep 27, 2021  
 
commit 1d847fec00cbcfc82e4864802c2686575101f1ee
Author:     Albrecht Schlosser <albrechts.fltk@online.de>
AuthorDate: Mon Sep 27 00:27:28 2021 +0200
Commit:     Albrecht Schlosser <albrechts.fltk@online.de>
CommitDate: Mon Sep 27 19:22:48 2021 +0200

    Fix and improve Fl_GIF_Image (issue #271, #274)
    
    - add error and EOF checks
    - fix transparent pixel index outside ColorMap (#271)
    - fix Fl_GIF_Image decoder bug (#274)
    - add Fl_Image_Reader::skip(unsigned int)
    - use new skip() method in GIF reader

 FL/Fl_GIF_Image.H     |   4 +-
 src/Fl_GIF_Image.cxx  | 262 +++++++++++++++++++++++++++++++++-----------------
 src/Fl_Image_Reader.h |   3 +
 3 files changed, 180 insertions(+), 89 deletions(-)

diff --git FL/Fl_GIF_Image.H FL/Fl_GIF_Image.H
index 673e9c0..274af9a 100644
--- FL/Fl_GIF_Image.H
+++ FL/Fl_GIF_Image.H
@@ -1,7 +1,7 @@
 //
 // GIF image header file for the Fast Light Tool Kit (FLTK).
 //
-// Copyright 1998-2020 by Bill Spitzak and others.
+// Copyright 1998-2021 by Bill Spitzak and others.
 //
 // This library is free software. Distribution and use rights are outlined in
 // the file "COPYING" which should have been included with this file.  If this
@@ -31,7 +31,7 @@ class FL_EXPORT Fl_GIF_Image : public Fl_Pixmap {
 public:
 
   Fl_GIF_Image(const char* filename);
-  Fl_GIF_Image(const char* imagename, const unsigned char *data);
+  Fl_GIF_Image(const char* imagename, const unsigned char *data, const long length = -1);
 
 protected:
 
diff --git src/Fl_GIF_Image.cxx src/Fl_GIF_Image.cxx
index d747ac4..32a1dab 100644
--- src/Fl_GIF_Image.cxx
+++ src/Fl_GIF_Image.cxx
@@ -1,7 +1,7 @@
 //
 // Fl_GIF_Image routines.
 //
-// Copyright 1997-2020 by Bill Spitzak and others.
+// Copyright 1997-2021 by Bill Spitzak and others.
 //
 // This library is free software. Distribution and use rights are outlined in
 // the file "COPYING" which should have been included with this file.  If this
@@ -74,23 +74,23 @@
 
 
 /**
- \brief The constructor loads the named GIF image.
+  This constructor loads a GIF image from the given file.
 
- IF a GIF is animated, Fl_GIF_Image will only read and display the first frame
- of the animation.
+  IF a GIF image is animated, Fl_GIF_Image will only read and display the
+  first frame of the animation.
 
- The destructor frees all memory and server resources that are used by
- the image.
+  The destructor frees all memory and server resources that are used by
+  the image.
 
- Use Fl_Image::fail() to check if Fl_GIF_Image failed to load. fail() returns
- ERR_FILE_ACCESS if the file could not be opened or read, ERR_FORMAT if the
- GIF format could not be decoded, and ERR_NO_IMAGE if the image could not
- be loaded for another reason.
+  Use Fl_Image::fail() to check if Fl_GIF_Image failed to load. fail() returns
+  ERR_FILE_ACCESS if the file could not be opened or read, ERR_FORMAT if the
+  GIF format could not be decoded, and ERR_NO_IMAGE if the image could not
+  be loaded for another reason.
 
- \param[in] filename a full path and name pointing to a valid GIF file.
+  \param[in] filename a full path and name pointing to a GIF image file.
 
- \see Fl_GIF_Image::Fl_GIF_Image(const char *imagename, const unsigned char *data)
- */
+  \see Fl_GIF_Image::Fl_GIF_Image(const char *imagename, const unsigned char *data, const long length)
+*/
 Fl_GIF_Image::Fl_GIF_Image(const char *filename) :
   Fl_Pixmap((char *const*)0)
 {
@@ -105,32 +105,47 @@ Fl_GIF_Image::Fl_GIF_Image(const char *filename) :
 
 
 /**
- \brief The constructor loads a GIF image from memory.
+  This constructor loads a GIF image from memory.
+
+  Construct an image from a block of memory inside the application. Fluid offers
+  "binary data" chunks as a great way to add image data into the C++ source code.
+  \p imagename can be NULL. If a name is given, the image is added to the list of
+  shared images and will be available by that name.
+
+  IF a GIF image is animated, Fl_GIF_Image will only read and display the
+  first frame of the animation.
+
+  The destructor frees all memory and server resources that are used by
+  the image.
+
+  The (new and optional) third parameter \p length \b should be used so buffer
+  overruns (i.e. truncated images) can be checked. See note below.
 
- Construct an image from a block of memory inside the application. Fluid offers
- "binary Data" chunks as a great way to add image data into the C++ source code.
- imagename can be NULL. If a name is given, the image is added to the list of
- shared images and will be available by that name.
+  If \p length is not used
+  - it defaults to -1 (unlimited size)
+  - buffer overruns will not be checked.
 
- IF a GIF is animated, Fl_GIF_Image will only read and display the first frame
- of the animation.
+  \note The optional parameter \p length is available since FLTK 1.4.0.
+    Not using it is deprecated and old code should be modified to use it.
+    This parameter will likely become mandatory in a future FLTK version.
 
- Use Fl_Image::fail() to check if Fl_GIF_Image failed to load. fail() returns
- ERR_FILE_ACCESS if the file could not be opened or read, ERR_FORMAT if the
- GIF format could not be decoded, and ERR_NO_IMAGE if the image could not
- be loaded for another reason.
+  Use Fl_Image::fail() to check if Fl_GIF_Image failed to load. fail() returns
+  ERR_FILE_ACCESS if the file could not be opened or read, ERR_FORMAT if the
+  GIF format could not be decoded, and ERR_NO_IMAGE if the image could not
+  be loaded for another reason.
 
- \param[in] imagename  A name given to this image or NULL
- \param[in] data       Pointer to the start of the GIF image in memory. This code will not check for buffer overruns.
+  \param[in] imagename  A name given to this image or NULL
+  \param[in] data       Pointer to the start of the GIF image in memory.
+  \param[in] length     Length of the GIF image in memory.
 
- \see Fl_GIF_Image::Fl_GIF_Image(const char *filename)
- \see Fl_Shared_Image
+  \see Fl_GIF_Image::Fl_GIF_Image(const char *filename)
+  \see Fl_Shared_Image
 */
-Fl_GIF_Image::Fl_GIF_Image(const char *imagename, const unsigned char *data) :
+Fl_GIF_Image::Fl_GIF_Image(const char *imagename, const unsigned char *data, const long length) :
   Fl_Pixmap((char *const*)0)
 {
   Fl_Image_Reader rdr;
-  if (rdr.open(imagename, data)==-1) {
+  if (rdr.open(imagename, data, length) == -1) {
     ld(ERR_FILE_ACCESS);
   } else {
     load_gif_(rdr);
@@ -138,6 +153,19 @@ Fl_GIF_Image::Fl_GIF_Image(const char *imagename, const unsigned char *data) :
 }
 
 /*
+  This macro can be used to check for end of file (EOF) or other read errors.
+  In case of an error or EOF an error message is issued and the image loading
+  is terminated with error code ERR_FORMAT.
+*/
+#define CHECK_ERROR \
+  if (rdr.error()) { \
+    Fl::error("[%d] Fl_GIF_Image: %s - unexpected EOF or read error at offset %ld", \
+              __LINE__, rdr.name(), rdr.tell()); \
+    ld(ERR_FORMAT); \
+    return; \
+  }
+
+/*
  This method reads GIF image data and creates an RGB or RGBA image. The GIF
  format supports only 1 bit for alpha. To avoid code duplication, we use
  an Fl_Image_Reader that reads data from either a file or from memory.
@@ -145,6 +173,9 @@ Fl_GIF_Image::Fl_GIF_Image(const char *imagename, const unsigned char *data) :
 void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr)
 {
   char **new_data;      // Data array
+  w(0); h(0);
+
+  // printf("\nFl_GIF_Image::load_gif_ : %s\n", rdr.name());
 
   {char b[6] = { 0 };
     for (int i=0; i<6; ++i) b[i] = rdr.read_byte();
@@ -161,6 +192,7 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr)
   int Height = rdr.read_word();
 
   uchar ch = rdr.read_byte();
+  CHECK_ERROR
   char HasColormap = ((ch & 0x80) != 0);
   int BitsPerPixel = (ch & 7) + 1;
   int ColorMapSize;
@@ -173,6 +205,7 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr)
   // int SortedTable = (ch&8)!=0;
   ch = rdr.read_byte(); // Background Color index
   ch = rdr.read_byte(); // Aspect ratio is N/64
+  CHECK_ERROR
 
   // Read in global colormap:
   uchar transparent_pixel = 0;
@@ -185,6 +218,7 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr)
       Blue[i] = rdr.read_byte();
     }
   }
+  CHECK_ERROR
 
   int CodeSize;         /* Code size, init from GIF header, increases... */
   char Interlace;
@@ -192,44 +226,52 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr)
   for (;;) {
 
     int i = rdr.read_byte();
-    if (i<0) {
-      Fl::error("Fl_GIF_Image: %s - unexpected EOF", rdr.name());
-      w(0); h(0); d(0); ld(ERR_FORMAT);
-      return;
-    }
+    CHECK_ERROR
     int blocklen;
 
-    //  if (i == 0x3B) return 0;  eof code
-
-    if (i == 0x21) {            // a "gif extension"
-
-      ch = rdr.read_byte();
+    if (i == 0x21) {                          // a "gif extension"
+      ch = rdr.read_byte();                   // extension type
       blocklen = rdr.read_byte();
-
-      if (ch==0xF9 && blocklen==4) { // Netscape animation extension
-
-        char bits;
-        bits = rdr.read_byte();
-        rdr.read_word(); // GETSHORT(delay);
-        transparent_pixel = rdr.read_byte();
+      CHECK_ERROR
+
+      if (ch == 0xF9 && blocklen == 4) {      // Graphic Control Extension
+        // printf("Graphic Control Extension at offset %ld\n", rdr.tell()-2);
+        char bits = rdr.read_byte();          // Packed Fields
+        rdr.read_word();                      // Delay Time
+        transparent_pixel = rdr.read_byte();  // Transparent Color Index
+        blocklen = rdr.read_byte();           // Block Terminator (must be zero)
+        CHECK_ERROR
         if (bits & 1) has_transparent = 1;
-        blocklen = rdr.read_byte();
-
-      } else if (ch == 0xFF) { // Netscape repeat count
-        ;
-
-      } else if (ch != 0xFE) { //Gif Comment
-        Fl::warning("%s: unknown gif extension 0x%02x.", rdr.name(), ch);
       }
-    } else if (i == 0x2c) {     // an image
-
-      ch = rdr.read_byte(); ch = rdr.read_byte(); // GETSHORT(x_position);
-      ch = rdr.read_byte(); ch = rdr.read_byte(); // GETSHORT(y_position);
-      Width = rdr.read_word();
-      Height = rdr.read_word();
-      ch = rdr.read_byte();
+      else if (ch == 0xFF) {                  // Application Extension
+        // printf("Application Extension at offset %ld, length = %d\n", rdr.tell()-3, blocklen);
+        ; // skip data
+      }
+      else if (ch == 0xFE) {                  // Comment Extension
+        // printf("Comment Extension at offset %ld, length = %d\n", rdr.tell()-3, blocklen);
+        ; // skip data
+      }
+      else if (ch == 0x01) {                  // Plain Text Extension
+        // printf("Plain Text Extension at offset %ld, length = %d\n", rdr.tell()-3, blocklen);
+        ; // skip data
+      }
+      else {
+        Fl::warning("%s: unknown GIF extension 0x%02x at offset %ld, length = %d",
+                    rdr.name(), ch, rdr.tell()-3, blocklen);
+        ; // skip data
+      }
+    }
+    else if (i == 0x2c) {       // an image: Image Descriptor follows
+      // printf("Image Descriptor at offset %ld\n", rdr.tell());
+      rdr.read_word();          // Image Left Position
+      rdr.read_word();          // Image Top Position
+      Width = rdr.read_word();  // Image Width
+      Height = rdr.read_word(); // Image Height
+      ch = rdr.read_byte();     // Packed Fields
+      CHECK_ERROR
       Interlace = ((ch & 0x40) != 0);
-      if (ch & 0x80) { // image has local color table
+      if (ch & 0x80) {          // image has local color table
+        // printf("Local Color Table at offset %ld\n", rdr.tell());
         BitsPerPixel = (ch & 7) + 1;
         ColorMapSize = 2 << (ch & 7);
         for (i=0; i < ColorMapSize; i++) {
@@ -238,20 +280,30 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr)
           Blue[i] = rdr.read_byte();
         }
       }
-      CodeSize = rdr.read_byte()+1;
+      CHECK_ERROR
       break; // okay, this is the image we want
     } else {
-      Fl::warning("%s: unknown gif code 0x%02x", rdr.name(), i);
+      Fl::warning("%s: unknown GIF code 0x%02x at offset %ld", rdr.name(), i, rdr.tell()-1);
       blocklen = 0;
     }
+    CHECK_ERROR
 
-    // skip the data:
-    while (blocklen>0) {while (blocklen--) {ch = rdr.read_byte();} blocklen = rdr.read_byte();}
+    // skip all the data subblocks:
+    while (blocklen > 0) {
+      rdr.skip(blocklen);
+      blocklen = rdr.read_byte();
+    }
+    // printf("End of data at offset %ld\n", rdr.tell());
   }
 
-  if (BitsPerPixel >= CodeSize)
-  {
-    // Workaround for broken GIF files...
+  // read image data
+
+  // printf("Image Data at offset %ld\n", rdr.tell());
+
+  CodeSize = rdr.read_byte() + 1; // LZW Minimum Code Size
+  CHECK_ERROR
+
+  if (BitsPerPixel >= CodeSize) { // Workaround for broken GIF files...
     BitsPerPixel = CodeSize - 1;
     ColorMapSize = 1 << BitsPerPixel;
   }
@@ -269,14 +321,25 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr)
     for (int i = 2; i < ColorMapSize; i++) {
       Red[i] = Green[i] = Blue[i] = (uchar)(255 * i / (ColorMapSize - 1));
     }
-#if (0)
-    // fill color table to maximum size
-    for (int i = ColorMapSize; i < 256; i++) {
-      Red[i] = Green[i] = Blue[i] = 0; // black
-    }
-#endif
   }
 
+  // Fix transparent pixel index outside ColorMap (Issue #271)
+  if (has_transparent && transparent_pixel >= ColorMapSize) {
+    for (int k = ColorMapSize; k <= transparent_pixel; k++)
+      Red[k] = Green[k] = Blue[k] = 0xff; // white (color is irrelevant)
+    ColorMapSize = transparent_pixel + 1;
+  }
+
+#if (0) // TEST/DEBUG: fill color table to maximum size
+  for (int i = ColorMapSize; i < 256; i++) {
+    Red[i] = Green[i] = Blue[i] = 0; // black
+  }
+#endif
+
+  CHECK_ERROR
+
+  // now read the LZW compressed image data
+
   uchar *Image = new uchar[Width*Height];
 
   int YC = 0, Pass = 0; /* Used to de-interlace the picture */
@@ -292,12 +355,13 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr)
   int FreeCode = FirstFree;
   int OldCode = ClearCode;
 
-  // tables used by LZW decompresser:
+  // tables used by LZW decompressor:
   short int Prefix[4096];
   uchar Suffix[4096];
 
   int blocklen = rdr.read_byte();
   uchar thisbyte = rdr.read_byte(); blocklen--;
+  CHECK_ERROR
   int frombit = 0;
 
   for (;;) {
@@ -311,17 +375,21 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr)
     if (frombit+CodeSize > 7) {
       if (blocklen <= 0) {
         blocklen = rdr.read_byte();
+        CHECK_ERROR
         if (blocklen <= 0) break;
       }
       thisbyte = rdr.read_byte(); blocklen--;
+      CHECK_ERROR
       CurCode |= thisbyte<<8;
     }
     if (frombit+CodeSize > 15) {
       if (blocklen <= 0) {
         blocklen = rdr.read_byte();
+        CHECK_ERROR
         if (blocklen <= 0) break;
       }
       thisbyte = rdr.read_byte(); blocklen--;
+      CHECK_ERROR
       CurCode |= thisbyte<<16;
     }
     CurCode = (CurCode>>frombit)&ReadMask;
@@ -335,16 +403,34 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr)
       continue;
     }
 
-    if (CurCode == EOFCode) break;
+    if (CurCode == EOFCode)
+      break;
 
     uchar OutCode[4097]; // temporary array for reversing codes
     uchar *tp = OutCode;
     int i;
-    if (CurCode < FreeCode) i = CurCode;
-    else if (CurCode == FreeCode) {*tp++ = (uchar)FinChar; i = OldCode;}
-    else {Fl::error("Fl_GIF_Image: %s - LZW Barf!", rdr.name()); break;}
+    if (CurCode < FreeCode) {
+      i = CurCode;
+    } else if (CurCode == FreeCode) {
+      *tp++ = (uchar)FinChar;
+      i = OldCode;
+    } else {
+      Fl::error("Fl_GIF_Image: %s - LZW Barf at offset %ld", rdr.name(), rdr.tell());
+      break;
+    }
 
-    while (i >= ColorMapSize) {*tp++ = Suffix[i]; i = Prefix[i];}
+    while (i >= ColorMapSize) {
+      if (i < FreeCode) {
+        *tp++ = Suffix[i];
+        i = Prefix[i];
+      } else { // FIXME - should never happen (?)
+        Fl::error("Fl_GIF_Image: %s - i(%d) >= FreeCode (%d) at offset %ld",
+                  rdr.name(), i, FreeCode, rdr.tell());
+        // NOTREACHED
+        i = FreeCode - 1; // fix broken index ???
+        break;
+      }
+    }
     *tp++ = FinChar = i;
     do {
       *p++ = *--tp;
@@ -363,21 +449,22 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr)
     } while (tp > OutCode);
 
     if (OldCode != ClearCode) {
-      Prefix[FreeCode] = (short)OldCode;
-      Suffix[FreeCode] = FinChar;
-      FreeCode++;
+      if (FreeCode < 4096) {
+        Prefix[FreeCode] = (short)OldCode;
+        Suffix[FreeCode] = FinChar;
+        FreeCode++;
+      }
       if (FreeCode > ReadMask) {
         if (CodeSize < 12) {
           CodeSize++;
           ReadMask = (1 << CodeSize) - 1;
         }
-        else FreeCode--;
       }
     }
     OldCode = CurCode;
   }
 
-  // We are done reading the file, now convert to xpm:
+  // We are done reading the image, now convert to xpm:
 
   // allocate line pointer arrays:
   w(Width);
@@ -452,4 +539,5 @@ void Fl_GIF_Image::load_gif_(Fl_Image_Reader &rdr)
   alloc_data = 1;
 
   delete[] Image;
-}
+
+} // load_gif_()
diff --git src/Fl_Image_Reader.h src/Fl_Image_Reader.h
index 7d176ac..2c62e4a 100644
--- src/Fl_Image_Reader.h
+++ src/Fl_Image_Reader.h
@@ -82,6 +82,9 @@ public:
   // return the name or filename for this reader
   const char *name() { return pName; }
 
+  // skip a given number of bytes
+  void skip(unsigned int n) { seek(tell() + n); }
+
 private:
 
   // open() sets this if we read from a file
Direct Link to Message ]
 
     
Previous Message ]Next Message ]
 
 

Comments are owned by the poster. All other content is copyright 1998-2024 by Bill Spitzak and others. This project is hosted by The FLTK Team. Please report site problems to 'erco@seriss.com'.