|
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 ] | |