diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt
index 440a5fcd3..848403148 100644
--- a/code/CMakeLists.txt
+++ b/code/CMakeLists.txt
@@ -106,6 +106,7 @@ ENDIF(WIN32)
FIND_PACKAGE(LibXml2 REQUIRED)
FIND_PACKAGE(PNG REQUIRED)
+FIND_PACKAGE(GIF)
FIND_PACKAGE(Jpeg)
IF(WITH_STATIC_LIBXML2)
diff --git a/code/nel/include/nel/misc/bitmap.h b/code/nel/include/nel/misc/bitmap.h
index 0b6940629..408b2fd27 100644
--- a/code/nel/include/nel/misc/bitmap.h
+++ b/code/nel/include/nel/misc/bitmap.h
@@ -44,6 +44,7 @@ const uint32 DDS_HEADER = NL_MAKEFOURCC('D', 'D', 'S', ' ');
const uint32 DXT_HEADER = NL_MAKEFOURCC('D', 'X', 'T', '\0');
const uint32 PNG_HEADER = NL_MAKEFOURCC(0x89, 'P', 'N', 'G');
const uint32 JPG_HEADER = NL_MAKEFOURCC(0xff, 0xd8, 0xff, 0xe0);
+const uint32 GIF_HEADER = NL_MAKEFOURCC('G', 'I', 'F', '8');
// dwLinearSize is valid
@@ -132,6 +133,15 @@ private :
uint8 readJPG( NLMISC::IStream &f );
+ /**
+ * Read a GIF from an IStream.
+ * GIF pictures are all converted to 32bit
+ * \param f IStream (must be a reading stream)
+ * \return image depth if succeed, 0 else
+ */
+ uint8 readGIF( NLMISC::IStream &f );
+
+
/**
* Change bitmap format
*
diff --git a/code/nel/src/misc/CMakeLists.txt b/code/nel/src/misc/CMakeLists.txt
index 8cdbf0fe6..4805209cb 100644
--- a/code/nel/src/misc/CMakeLists.txt
+++ b/code/nel/src/misc/CMakeLists.txt
@@ -187,6 +187,12 @@ IF(JPEG_FOUND)
TARGET_LINK_LIBRARIES(nelmisc ${JPEG_LIBRARY})
ENDIF(JPEG_FOUND)
+IF(GIF_FOUND)
+ INCLUDE_DIRECTORIES(${GIF_INCLUDE_DIR})
+ ADD_DEFINITIONS(-DUSE_GIF)
+ TARGET_LINK_LIBRARIES(nelmisc ${GIF_LIBRARY})
+ENDIF(GIF_FOUND)
+
IF(WITH_STATIC OR WIN32)
TARGET_LINK_LIBRARIES(nelmisc ${PNG_LIBRARIES})
ELSE(WITH_STATIC OR WIN32)
diff --git a/code/nel/src/misc/bitmap.cpp b/code/nel/src/misc/bitmap.cpp
index f32980508..30c708929 100644
--- a/code/nel/src/misc/bitmap.cpp
+++ b/code/nel/src/misc/bitmap.cpp
@@ -134,6 +134,19 @@ uint8 CBitmap::load(NLMISC::IStream &f, uint mipMapSkip)
}
#endif // USE_JPEG
+#ifdef USE_GIF
+ if (fileType == GIF_HEADER)
+ {
+#ifdef NEL_ALL_BITMAP_WHITE
+ uint8 result = readGIF(f);
+ MakeWhite (*this);
+ return result;
+#else // NEL_ALL_BITMAP_WHITE
+ return readGIF(f);
+#endif // NEL_ALL_BITMAP_WHITE
+ }
+#endif // USE_GIF
+
// assuming it's TGA
NLMISC::IStream::TSeekOrigin origin= f.begin;
if(!f.seek (0, origin))
@@ -3149,6 +3162,25 @@ void CBitmap::loadSize(NLMISC::IStream &f, uint32 &retWidth, uint32 &retHeight)
}
while(!eof);
}
+ else if(fileType == GIF_HEADER)
+ {
+ // check second part of header ("7a" or "9a" in 'GIF89a')
+ uint16 s;
+ f.serial(s);
+ if (s != 0x6137 && s != 0x6139)
+ {
+ nlwarning("Invalid GIF header, expected GIF87a or GIF89a");
+ return;
+ }
+
+ uint16 lsWidth;
+ uint16 lsHeight;
+ f.serial(lsWidth);
+ f.serial(lsHeight);
+
+ retWidth = lsWidth;
+ retHeight = lsHeight;
+ }
// assuming it's TGA
else
{
diff --git a/code/nel/src/misc/bitmap_gif.cpp b/code/nel/src/misc/bitmap_gif.cpp
new file mode 100644
index 000000000..355e2a2bf
--- /dev/null
+++ b/code/nel/src/misc/bitmap_gif.cpp
@@ -0,0 +1,315 @@
+// NeL - MMORPG Framework
+// Copyright (C) 2010 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include "stdmisc.h"
+#include "nel/misc/bitmap.h"
+
+#ifdef USE_GIF
+#include
+#endif
+
+using namespace std;
+
+namespace NLMISC
+{
+
+#ifdef USE_GIF
+
+// GIFLIB_MAJOR is defined from version 5
+#ifndef GIFLIB_MAJOR
+#define GIFLIB_MAJOR 4
+#endif
+
+static uint8 GIF_TRANSPARENT_MASK = 0x01;
+static uint8 GIF_DISPOSE_MASK = 0x07;
+static sint8 GIF_NOT_TRANSPARENT = -1;
+
+static uint8 GIF_DISPOSE_NONE = 0;
+static uint8 GIF_DISPOSE_LEAVE = 1;
+static uint8 GIF_DISPOSE_BACKGROUND = 2;
+static uint8 GIF_DISPOSE_RESTORE = 3;
+
+static NLMISC::IStream *GIFStream = NULL;
+
+#if GIFLIB_MAJOR < 5
+static uint8 INTERLACED_OFFSET[] = { 0, 4, 2, 1 };
+static uint8 INTERLACED_JUMP[] = { 8, 8, 4, 2 };
+#endif
+
+static int readGIFData(GifFileType *gif, GifByteType *data, int length){
+ NLMISC::IStream *f = static_cast(gif->UserData);
+
+ if(!f->isReading()) return 0;
+
+ try
+ {
+ f->serialBuffer((uint8*) data, length);
+ }
+ catch(...)
+ {
+ nlwarning("error while reading JPEG image");
+
+ return 0;
+ }
+
+ return length;
+}
+
+/*-------------------------------------------------------------------*\
+ readGIF
+\*-------------------------------------------------------------------*/
+uint8 CBitmap::readGIF( NLMISC::IStream &f )
+{
+ if(!f.isReading()) return false;
+
+ {
+ // check gif canvas dimension
+ uint16 ver;
+ uint16 width;
+ uint16 height;
+
+ f.serial(ver);
+ f.serial(width);
+ f.serial(height);
+
+ // limit image size as we are using 32bit pixels
+ // 4000x4000x4 ~ 61MiB
+ if (width*height > 4000*4000)
+ {
+ nlwarning("GIF image size is too big (width=%d, height=%d)", width, height);
+ return 0;
+ }
+
+ // rewind for gif decoder
+ f.seek(-10, IStream::current);
+ }
+
+#if GIFLIB_MAJOR >= 5
+ sint32 errorCode;
+ GifFileType *gif = DGifOpen(&f, readGIFData, &errorCode);
+ if (gif == NULL)
+ {
+ nlwarning("failed to open gif, error=%d", errorCode);
+ return 0;
+ }
+#else
+ GifFileType *gif = DGifOpen(&f, readGIFData);
+ if (gif == NULL)
+ {
+ nlwarning("failed to open gif, error=%d", GifLastError());
+ return 0;
+ }
+#endif
+
+ // this will read and decode all frames
+ sint32 ret = DGifSlurp(gif);
+ if (ret != GIF_OK)
+ {
+ nlwarning("failed to read gif, error=%d", ret);
+#if GIFLIB_MAJOR >= 5 && GIFLIB_MINOR >= 1
+ DGifCloseFile(gif, &errorCode);
+#else
+ DGifCloseFile(gif);
+#endif
+ return 0;
+ }
+
+ // resize target buffer
+ uint32 dstChannels = 4; // RGBA
+ resize (gif->SWidth, gif->SHeight, RGBA);
+
+ // make transparent
+ _Data[0].fill(0);
+
+ // make sure background color index exists in global colormap
+ if (gif->SColorMap && gif->SColorMap->ColorCount < gif->SBackGroundColor)
+ {
+ gif->SBackGroundColor = 0;
+ }
+
+ // merge all frames one by one into single target
+ ColorMapObject *ColorMap;
+ sint32 transparency = GIF_NOT_TRANSPARENT;
+ uint8 r, g, b, a;
+ uint32 offset_x, offset_y, width, height;
+
+ // disable loop as we only interested in first frame
+ // for (uint32 frame = 0; frame < gif->ImageCount; frame++)
+ {
+ uint32 frame = 0;
+ SavedImage *curFrame = &gif->SavedImages[frame];
+
+ if (curFrame->ExtensionBlockCount > 0)
+ {
+ for(uint e=0; eExtensionBlockCount; e++){
+ ExtensionBlock *ext = &curFrame->ExtensionBlocks[e];
+
+ if (ext->Function == GRAPHICS_EXT_FUNC_CODE) {
+ uint8 flag = ext->Bytes[0];
+ //delay = (ext.Bytes[1] << 8) | ext.Bytes[2];
+ transparency = (flag & GIF_TRANSPARENT_MASK) ? ext->Bytes[3] : GIF_NOT_TRANSPARENT;
+ //dispose = ((flag >> 2) & GIF_DISPOSE_MASK);
+ }
+ }
+ }
+
+ // select color map for frame
+ if (curFrame->ImageDesc.ColorMap)
+ {
+ ColorMap = curFrame->ImageDesc.ColorMap;
+ }
+ else
+ if (gif->SColorMap)
+ {
+ ColorMap = gif->SColorMap;
+ }
+ else
+ {
+ nlwarning("GIF has no global or local color map");
+ ColorMap = NULL;
+ }
+
+ // copy frame to canvas
+ offset_x = curFrame->ImageDesc.Left;
+ offset_y = curFrame->ImageDesc.Top;
+ width = curFrame->ImageDesc.Width;
+ height = curFrame->ImageDesc.Height;
+
+#if GIFLIB_MAJOR < 5
+ // giflib 4 does not handle interlaced images, so we must do it
+ if (curFrame->ImageDesc.Interlace)
+ {
+ uint32 srcOffset = 0;
+ for (uint8 pass = 0; pass < 4; pass++)
+ {
+ uint32 nextLine = INTERLACED_OFFSET[pass];
+
+ // y is destination row
+ for (uint32 y = 0; y < height; y++)
+ {
+ if (y != nextLine)
+ continue;
+
+ uint32 dstOffset = (y + offset_y)*gif->SWidth*dstChannels + offset_x*dstChannels;
+ nextLine += INTERLACED_JUMP[pass];
+
+ for (uint32 x = 0; x < width; x++)
+ {
+ srcOffset++;
+ dstOffset+= dstChannels;
+
+ uint32 index = curFrame->RasterBits[srcOffset];
+ if (index != transparency)
+ {
+ // make sure color index is not outside colormap
+ if (ColorMap)
+ {
+ if (index > ColorMap->ColorCount)
+ {
+ index = 0;
+ }
+ r = ColorMap->Colors[index].Red;
+ g = ColorMap->Colors[index].Green;
+ b = ColorMap->Colors[index].Blue;
+ }
+ else
+ {
+ // broken gif, no colormap
+ r = g = b = 0;
+ }
+ a = 255;
+ }
+ else
+ {
+ // transparent
+ r = g = b = a = 0;
+ }
+
+ _Data[0][dstOffset] = r;
+ _Data[0][dstOffset+1] = g;
+ _Data[0][dstOffset+2] = b;
+ _Data[0][dstOffset+3] = a;
+ } // x loop
+ } // y loop
+ } // pass loop
+ }
+ else
+#endif
+ for (uint32 y = 0; y < height; y++)
+ {
+ uint32 srcOffset = y*width;
+ uint32 dstOffset = (y + offset_y)*gif->SWidth*dstChannels + offset_x*dstChannels;
+ for (uint32 x = 0; x < width; x++)
+ {
+ srcOffset++;
+ dstOffset+= dstChannels;
+
+ uint32 index = curFrame->RasterBits[srcOffset];
+ if (index != transparency)
+ {
+ // make sure color index is not outside colormap
+ if (ColorMap)
+ {
+ if (index > ColorMap->ColorCount)
+ {
+ index = 0;
+ }
+ r = ColorMap->Colors[index].Red;
+ g = ColorMap->Colors[index].Green;
+ b = ColorMap->Colors[index].Blue;
+ }
+ else
+ {
+ // broken gif, no colormap
+ r = g = b = 0;
+ }
+ a = 255;
+ }
+ else
+ {
+ // transparent
+ r = g = b = a = 0;
+ }
+
+ _Data[0][dstOffset] = r;
+ _Data[0][dstOffset+1] = g;
+ _Data[0][dstOffset+2] = b;
+ _Data[0][dstOffset+3] = a;
+ } // x loop
+ } // y loop
+ }
+
+ // clean up after the read, and free any memory allocated
+#if GIFLIB_MAJOR >= 5 && GIFLIB_MINOR >= 1
+ DGifCloseFile(gif, &errorCode);
+#else
+ DGifCloseFile(gif);
+#endif
+
+ //return the size of a pixel as 32bits
+ return 32;
+}
+
+#else
+
+uint8 CBitmap::readGIF( NLMISC::IStream &/* f */)
+{
+ nlwarning ("You must compile NLMISC with USE_GIF if you want gif support");
+ return 0;
+}
+
+#endif
+}//namespace