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
This commit is contained in:
Albrecht Schlosser 2021-09-27 00:27:28 +02:00
parent 4075a14715
commit 1d847fec00
3 changed files with 179 additions and 88 deletions

View File

@ -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:

View File

@ -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,38 +105,66 @@ 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.
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.
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 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.
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.
The destructor frees all memory and server resources that are used by
the image.
\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.
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.
\see Fl_GIF_Image::Fl_GIF_Image(const char *filename)
\see Fl_Shared_Image
If \p length is not used
- it defaults to -1 (unlimited size)
- buffer overruns will not be checked.
\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.
\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
*/
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);
}
}
/*
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
@ -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();
CHECK_ERROR
if (ch==0xF9 && blocklen==4) { // Netscape animation extension
char bits;
bits = rdr.read_byte();
rdr.read_word(); // GETSHORT(delay);
transparent_pixel = rdr.read_byte();
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_()

View File

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