// **************************************************************************** // // WINIMAGE.CPP : Generic classes for raster images (MSWindows specialization) // // Content: Member definitions for: // - class CAnimatedGif : Storage class for single images // - class CAnimatedGifSet : Storage class for sets of images // // (Includes routines to Load and Save BMP files and to load GIF files into // these classes). // // -------------------------------------------------------------------------- // // Copyright (c) 2000, Juan Soulie // // Permission to use, copy, modify, distribute and sell this software or any // part thereof and/or its documentation for any purpose is granted without fee // provided that the above copyright notice and this permission notice appear // in all copies. // // This software is provided "as is" without express or implied warranty of // any kind. The author shall have no liability with respect to the // infringement of copyrights or patents that any modification to the content // of this file or this file itself may incur. // // **************************************************************************** #include "AnimatedGif.h" #include "FileSystem/SpecialProtocol.h" #include "utils/EndianSwap.h" #ifdef _WIN32 extern "C" FILE *fopen_utf8(const char *_Filename, const char *_Mode); #else #define fopen_utf8 fopen #endif #pragma pack(1) // Error processing macro (NO-OP by default): #define ERRORMSG(PARAM) {} #ifndef BI_RGB #define BI_RGB 0L #define BI_RLE8 1L #define BI_RLE4 2L #define BI_BITFIELDS 3L #endif // Macros to swap data endianness #define SWAP16(X) X=Endian_SwapLE16(X) #define SWAP32(X) X=Endian_SwapLE32(X) // pre-declaration: int LZWDecoder (char*, char*, short, int, int, int, const int); // **************************************************************************** // * CAnimatedGif Member definitions * // **************************************************************************** CAnimatedGif::CAnimatedGif() { Height = Width = 0; Raster = NULL; Palette = NULL; pbmi = NULL; BPP = Transparent = BytesPerRow = 0; xPos = yPos = Delay = Transparency = 0; nLoops = 1; //default=play animation 1 time } CAnimatedGif::~CAnimatedGif() { delete [] pbmi; delete [] Raster; delete [] Palette; } // Init: Allocates space for raster and palette in GDI-compatible structures. void CAnimatedGif::Init(int iWidth, int iHeight, int iBPP, int iLoops) { delete[] Raster; Raster = NULL; delete[] pbmi; pbmi = NULL; delete[] Palette; Palette = NULL; // Standard members setup Transparent = -1; BytesPerRow = Width = iWidth; Height = iHeight; BPP = iBPP; // Animation Extra members setup: xPos = yPos = Delay = Transparency = 0; nLoops = iLoops; if (BPP == 24) { BytesPerRow *= 3; pbmi = (GUIBITMAPINFO*)new char [sizeof(GUIBITMAPINFO)]; } else { pbmi = (GUIBITMAPINFO*)new char[sizeof(GUIBITMAPINFOHEADER)]; Palette = new COLOR[256]; } BytesPerRow += (ALIGN - Width % ALIGN) % ALIGN; // Align BytesPerRow int size = BytesPerRow * Height; Raster = new char [size]; pbmi->bmiHeader.biSize = sizeof (GUIBITMAPINFOHEADER); pbmi->bmiHeader.biWidth = Width; pbmi->bmiHeader.biHeight = -Height; // negative means up-to-bottom pbmi->bmiHeader.biPlanes = 1; pbmi->bmiHeader.biBitCount = (BPP < 8 ? 8 : BPP); // Our raster is byte-aligned pbmi->bmiHeader.biCompression = BI_RGB; pbmi->bmiHeader.biSizeImage = 0; pbmi->bmiHeader.biXPelsPerMeter = 11811; pbmi->bmiHeader.biYPelsPerMeter = 11811; pbmi->bmiHeader.biClrUsed = 0; pbmi->bmiHeader.biClrImportant = 0; } // operator=: copies an object's content to another CAnimatedGif& CAnimatedGif::operator = (CAnimatedGif& rhs) { Init(rhs.Width, rhs.Height, rhs.BPP); // respects virtualization memcpy(Raster, rhs.Raster, BytesPerRow*Height); memcpy(Palette, rhs.Palette, 256*sizeof(COLOR)); return *this; } CAnimatedGifSet::CAnimatedGifSet() { FrameHeight = FrameWidth = 0; nLoops = 1; //default=play animation 1 time } CAnimatedGifSet::~CAnimatedGifSet() { Release(); } void CAnimatedGifSet::Release() { FrameWidth = 0; FrameHeight = 0; for (int i = 0; i < (int)m_vecimg.size(); ++i) { CAnimatedGif* pImage = m_vecimg[i]; delete pImage; } m_vecimg.erase(m_vecimg.begin(), m_vecimg.end()); } // **************************************************************************** // * CAnimatedGifSet Member definitions * // **************************************************************************** // AddImage: Adds an image object to the back of the img vector. void CAnimatedGifSet::AddImage (CAnimatedGif* newimage) { m_vecimg.push_back(newimage); } int CAnimatedGifSet::GetImageCount() const { return m_vecimg.size(); } unsigned char CAnimatedGifSet::getbyte(FILE *fd) { unsigned char uchar; if (fread(&uchar, 1, 1, fd) == 1) return uchar; else return 0; } // **************************************************************************** // * LoadGIF * // * Load a GIF File into the CAnimatedGifSet object * // * (c) Nov 2000, Juan Soulie * // **************************************************************************** int CAnimatedGifSet::LoadGIF (const char * szFileName) { int n; // Global GIF variables: int GlobalBPP; // Bits per Pixel. COLOR * GlobalColorMap; // Global colormap (allocate) struct GIFGCEtag { // GRAPHIC CONTROL EXTENSION unsigned char BlockSize; // Block Size: 4 bytes unsigned char PackedFields; // 3.. Packed Fields. Bits detail: // 0: Transparent Color Flag // 1: User Input Flag // 2-4: Disposal Method unsigned short Delay; // 4..5 Delay Time (1/100 seconds) unsigned char Transparent; // 6.. Transparent Color Index } gifgce; struct GIFNetscapeTag { unsigned char comment[11]; //4...14 NETSCAPE2.0 unsigned char SubBlockLength; //15 0x3 unsigned char reserved; //16 0x1 unsigned short iIterations ; //17..18 number of iterations (lo-hi) } gifnetscape; int GraphicExtensionFound = 0; // OPEN FILE FILE *fd = fopen_utf8(_P(szFileName), "rb"); if (!fd) { return 0; } // *1* READ HEADERBLOCK (6bytes) (SIGNATURE + VERSION) char szSignature[6]; // First 6 bytes (GIF87a or GIF89a) int iRead = fread(szSignature, 1, 6, fd); if (iRead != 6) { fclose(fd); return 0; } if ( memcmp(szSignature, "GIF", 2) != 0) { fclose(fd); return 0; } // *2* READ LOGICAL SCREEN DESCRIPTOR struct GIFLSDtag { unsigned short ScreenWidth; // Logical Screen Width unsigned short ScreenHeight; // Logical Screen Height unsigned char PackedFields; // Packed Fields. Bits detail: // 0-2: Size of Global Color Table // 3: Sort Flag // 4-6: Color Resolution // 7: Global Color Table Flag unsigned char Background; // Background Color Index unsigned char PixelAspectRatio; // Pixel Aspect Ratio } giflsd; iRead = fread(&giflsd, 1, sizeof(giflsd), fd); if (iRead != sizeof(giflsd)) { fclose(fd); return 0; } // endian swap SWAP16(giflsd.ScreenWidth); SWAP16(giflsd.ScreenHeight); GlobalBPP = (giflsd.PackedFields & 0x07) + 1; // fill some animation data: FrameWidth = giflsd.ScreenWidth; FrameHeight = giflsd.ScreenHeight; nLoops = 1; //default=play animation 1 time // *3* READ/GENERATE GLOBAL COLOR MAP GlobalColorMap = new COLOR [1 << GlobalBPP]; if (giflsd.PackedFields & 0x80) // File has global color map? for (n = 0;n < 1 << GlobalBPP;n++) { GlobalColorMap[n].r = getbyte(fd); GlobalColorMap[n].g = getbyte(fd); GlobalColorMap[n].b = getbyte(fd); } else // GIF standard says to provide an internal default Palette: for (n = 0;n < 256;n++) GlobalColorMap[n].r = GlobalColorMap[n].g = GlobalColorMap[n].b = n; // *4* NOW WE HAVE 3 POSSIBILITIES: // 4a) Get and Extension Block (Blocks with additional information) // 4b) Get an Image Separator (Introductor to an image) // 4c) Get the trailer Char (End of GIF File) do { int charGot = getbyte(fd); if (charGot == 0x21) // *A* EXTENSION BLOCK { unsigned char extensionType = getbyte(fd); switch (extensionType) { case 0xF9: // Graphic Control Extension { if (fread((char*)&gifgce, 1, sizeof(gifgce), fd) == sizeof(gifgce)) SWAP16(gifgce.Delay); GraphicExtensionFound++; getbyte(fd); // Block Terminator (always 0) } break; case 0xFE: // Comment Extension: Ignored { while (int nBlockLength = getbyte(fd)) for (n = 0;n < nBlockLength;n++) getbyte(fd); } break; case 0x01: // PlainText Extension: Ignored { while (int nBlockLength = getbyte(fd)) for (n = 0;n < nBlockLength;n++) getbyte(fd); } break; case 0xFF: // Application Extension: Ignored { int nBlockLength = getbyte(fd); if (nBlockLength == 0x0b) { struct GIFNetscapeTag tag; if (fread((char*)&tag, 1, sizeof(gifnetscape), fd) == sizeof(gifnetscape)) { SWAP16(tag.iIterations); nLoops = tag.iIterations; } else nLoops = 0; if (nLoops) nLoops++; getbyte(fd); } else { do { for (n = 0;n < nBlockLength;n++) getbyte(fd); } while ((nBlockLength = getbyte(fd)) != 0); } } break; default: // Unknown Extension: Ignored { // read (and ignore) data sub-blocks while (int nBlockLength = getbyte(fd)) for (n = 0;n < nBlockLength;n++) getbyte(fd); } break; } } else if (charGot == 0x2c) { // *B* IMAGE (0x2c Image Separator) // Create a new Image Object: CAnimatedGif* NextImage = new CAnimatedGif(); // Read Image Descriptor struct GIFIDtag { unsigned short xPos; // Image Left Position unsigned short yPos; // Image Top Position unsigned short Width; // Image Width unsigned short Height; // Image Height unsigned char PackedFields; // Packed Fields. Bits detail: // 0-2: Size of Local Color Table // 3-4: (Reserved) // 5: Sort Flag // 6: Interlace Flag // 7: Local Color Table Flag } gifid; memset(&gifid, 0, sizeof(gifid)); int LocalColorMap = 0; if (fread((char*)&gifid, 1, sizeof(gifid), fd) == sizeof(gifid)) { SWAP16(gifid.xPos); SWAP16(gifid.yPos); SWAP16(gifid.Width); SWAP16(gifid.Height); LocalColorMap = (gifid.PackedFields & 0x08) ? 1 : 0; } NextImage->Init(gifid.Width, gifid.Height, LocalColorMap ? (gifid.PackedFields&7) + 1 : GlobalBPP); // Fill NextImage Data NextImage->xPos = gifid.xPos; NextImage->yPos = gifid.yPos; if (GraphicExtensionFound) { NextImage->Transparent = (gifgce.PackedFields & 0x01) ? gifgce.Transparent : -1; NextImage->Transparency = (gifgce.PackedFields & 0x1c) > 1 ? 1 : 0; NextImage->Delay = gifgce.Delay * 10; } if (NextImage->Transparent != -1) memset(NextImage->Raster, NextImage->Transparent, NextImage->BytesPerRow * NextImage->Height); else memset(NextImage->Raster, giflsd.Background, NextImage->BytesPerRow * NextImage->Height); // Read Color Map (if descriptor says so) size_t palSize = sizeof(COLOR)*(1 << NextImage->BPP); bool isPalRead = false; if (LocalColorMap && fread((char*)NextImage->Palette, 1, palSize, fd) == palSize) isPalRead = true; // Copy global, if no palette if (!isPalRead) memcpy(NextImage->Palette, GlobalColorMap, palSize); short firstbyte = getbyte(fd); // 1st byte of img block (CodeSize) // Calculate compressed image block size // to fix: this allocates an extra byte per block long ImgStart, ImgEnd; ImgEnd = ImgStart = ftell(fd); while ((n = getbyte(fd)) != 0) fseek (fd, ImgEnd += n + 1, SEEK_SET ); fseek (fd, ImgStart, SEEK_SET); // Allocate Space for Compressed Image char * pCompressedImage = new char [ImgEnd - ImgStart + 4]; // Read and store Compressed Image char * pTemp = pCompressedImage; while (int nBlockLength = getbyte(fd)) { if (fread(pTemp, 1, nBlockLength, fd) != (size_t)nBlockLength) { // Error? } pTemp += nBlockLength; } // Call LZW/GIF decompressor n = LZWDecoder( (char*) pCompressedImage, (char*) NextImage->Raster, firstbyte, NextImage->BytesPerRow, //NextImage->AlignedWidth, gifid.Width, gifid.Height, ((gifid.PackedFields & 0x40) ? 1 : 0) //Interlaced? ); if (n) AddImage(NextImage); else { delete NextImage; ERRORMSG("GIF File Corrupt"); } // Some cleanup delete[] pCompressedImage; GraphicExtensionFound = 0; } else if (charGot == 0x3b) { // *C* TRAILER: End of GIF Info break; // Ok. Standard End. } } while ( !feof(fd) ); delete[] GlobalColorMap; fclose(fd); if ( GetImageCount() == 0) ERRORMSG("Premature End Of File"); return GetImageCount(); } // **************************************************************************** // * LZWDecoder (C/C++) * // * Codec to perform LZW (GIF Variant) decompression. * // * (c) Nov2000, Juan Soulie * // **************************************************************************** // // Parameter description: // - bufIn: Input buffer containing a "de-blocked" GIF/LZW compressed image. // - bufOut: Output buffer where result will be stored. // - InitCodeSize: Initial CodeSize to be Used // (GIF files include this as the first byte in a picture block) // - AlignedWidth : Width of a row in memory (including alignment if needed) // - Width, Height: Physical dimensions of image. // - Interlace: 1 for Interlaced GIFs. // int LZWDecoder (char * bufIn, char * bufOut, short InitCodeSize, int AlignedWidth, int Width, int Height, const int Interlace) { int n; int row = 0, col = 0; // used to point output if Interlaced int nPixels, maxPixels; // Output pixel counter short CodeSize; // Current CodeSize (size in bits of codes) short ClearCode; // Clear code : resets decompressor short EndCode; // End code : marks end of information long whichBit; // Index of next bit in bufIn long LongCode; // Temp. var. from which Code is retrieved short Code; // Code extracted short PrevCode; // Previous Code short OutCode; // Code to output // Translation Table: short Prefix[4096]; // Prefix: index of another Code unsigned char Suffix[4096]; // Suffix: terminating character short FirstEntry; // Index of first free entry in table short NextEntry; // Index of next free entry in table unsigned char OutStack[4097]; // Output buffer int OutIndex; // Characters in OutStack int RowOffset; // Offset in output buffer for current row // Set up values that depend on InitCodeSize Parameter. CodeSize = InitCodeSize + 1; ClearCode = (1 << InitCodeSize); EndCode = ClearCode + 1; NextEntry = FirstEntry = ClearCode + 2; whichBit = 0; nPixels = 0; maxPixels = Width * Height; RowOffset = 0; PrevCode = 0; while (nPixels < maxPixels) { OutIndex = 0; // Reset Output Stack // GET NEXT CODE FROM bufIn: // LZW compression uses code items longer than a single byte. // For GIF Files, code sizes are variable between 9 and 12 bits // That's why we must read data (Code) this way: LongCode = *((long*)(bufIn + whichBit / 8)); // Get some bytes from bufIn SWAP32(LongCode); LongCode >>= (whichBit&7); // Discard too low bits Code = (short)((LongCode & ((1 << CodeSize) - 1) )); // Discard too high bits whichBit += CodeSize; // Increase Bit Offset // SWITCH, DIFFERENT POSIBILITIES FOR CODE: if (Code == EndCode) // END CODE break; // Exit LZW Decompression loop if (Code == ClearCode) { // CLEAR CODE: CodeSize = InitCodeSize + 1; // Reset CodeSize NextEntry = FirstEntry; // Reset Translation Table PrevCode = Code; // Prevent next to be added to table. continue; // restart, to get another code } if (Code < NextEntry) // CODE IS IN TABLE OutCode = Code; // Set code to output. else { // CODE IS NOT IN TABLE: OutIndex++; // Keep "first" character of previous output. OutCode = PrevCode; // Set PrevCode to be output } // EXPAND OutCode IN OutStack // - Elements up to FirstEntry are Raw-Codes and are not expanded // - Table Prefices contain indexes to other codes // - Table Suffices contain the raw codes to be output while (OutCode >= FirstEntry) { if (OutIndex > 4096 || OutCode >= 4096) return 0; OutStack[OutIndex++] = Suffix[OutCode]; // Add suffix to Output Stack OutCode = Prefix[OutCode]; // Loop with preffix } // NOW OutCode IS A RAW CODE, ADD IT TO OUTPUT STACK. if (OutIndex > 4096) return 0; OutStack[OutIndex++] = (unsigned char) OutCode; // ADD NEW ENTRY TO TABLE (PrevCode + OutCode) // (EXCEPT IF PREVIOUS CODE WAS A CLEARCODE) if (PrevCode != ClearCode) { Prefix[NextEntry] = PrevCode; Suffix[NextEntry] = (unsigned char) OutCode; NextEntry++; // Prevent Translation table overflow: if (NextEntry >= 4096) return 0; // INCREASE CodeSize IF NextEntry IS INVALID WITH CURRENT CodeSize if (NextEntry >= (1 << CodeSize)) { if (CodeSize < 12) CodeSize++; else { ; } // Do nothing. Maybe next is Clear Code. } } PrevCode = Code; // Avoid the possibility of overflow on 'bufOut'. if (nPixels + OutIndex > maxPixels) OutIndex = maxPixels - nPixels; // OUTPUT OutStack (LAST-IN FIRST-OUT ORDER) for (n = OutIndex - 1; n >= 0; n--) { if (col == Width) // Check if new row. { if (Interlace) { // If interlaced:: if ((row&7) == 0) {row += 8; if (row >= Height) row = 4;} else if ((row&3) == 0) {row += 8; if (row >= Height) row = 2;} else if ((row&1) == 0) {row += 4; if (row >= Height) row = 1;} else row += 2; } else // If not interlaced: row++; RowOffset = row * AlignedWidth; // Set new row offset col = 0; } bufOut[RowOffset + col] = OutStack[n]; // Write output col++; nPixels++; // Increase counters. } } // while (main decompressor loop) return whichBit; } // Refer to WINIMAGE.TXT for copyright and patent notices on GIF and LZW. #pragma pack()