From 3d5503bac68a563e550a13cfde329a1676f318d6 Mon Sep 17 00:00:00 2001 From: Nimetu Date: Tue, 3 Nov 2015 18:50:31 +0200 Subject: [PATCH] Add basic gif support to CBitmap (issue #238) --- code/CMakeLists.txt | 1 + code/nel/include/nel/misc/bitmap.h | 10 + code/nel/src/misc/CMakeLists.txt | 6 + code/nel/src/misc/bitmap.cpp | 32 +++ code/nel/src/misc/bitmap_gif.cpp | 315 +++++++++++++++++++++++++++++ 5 files changed, 364 insertions(+) create mode 100644 code/nel/src/misc/bitmap_gif.cpp 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