// 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(sint e = 0; e < curFrame->ExtensionBlockCount; 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 ((sint32)index != transparency) { // make sure color index is not outside colormap if (ColorMap) { if ((sint)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 ((sint32)index != transparency) { // make sure color index is not outside colormap if (ColorMap) { if ((sint)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