//+----------------------------------------------------------------------------------- // // Microsoft // Copyright (c) Microsoft Corporation, 1999 // // File: src\time\src\loadgif.cpp // // Contents: gif decoder, copied from direct animation source: danim\src\appel\util\loadgif.cpp // //------------------------------------------------------------------------------------ //#include //bw #include "headers.h" // #define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include #include #include /*lint ++flb*/ //bw DeclareTag(tagImageDecode, "Image Decode", "Image Decode Filters"); const long COLORKEY_NOT_SET = -1; /*-- Structs from IE img.hxx --*/ void * __cdecl _calloc(size_t num, size_t size) { void * pv = malloc(num * size); if (NULL == pv) { return NULL; } ZeroMemory(pv, num * size); return pv; } enum { gifNoneSpecified = 0, // no disposal method specified gifNoDispose = 1, // do not dispose, leave the bits there gifRestoreBkgnd = 2, // replace the image with the background color gifRestorePrev = 3 // replace the image with the previous pixels }; typedef struct _GCEDATA // data from GIF Graphic control extension { unsigned int uiDelayTime; // frame duration, initialy 1/100ths seconds // converted to milliseconds unsigned int uiDisposalMethod; // 0 - none specified. // 1 - do not dispose - leave bits in place. // 2 - replace with background color. // 3 - restore previous bits // >3 - not yet defined BOOL fTransparent; // TRUE is ucTransIndex describes transparent color unsigned char ucTransIndex; // transparent index } GCEDATA; typedef struct _GIFFRAME { struct _GIFFRAME *pgfNext; GCEDATA gced; // animation parameters for frame. int top; // bounds relative to the GIF logical screen int left; int width; int height; unsigned char *ppixels; // pointer to image pixel data int cColors; // number of entries in pcolors PALETTEENTRY *pcolors; PBITMAPINFO pbmi; HRGN hrgnVis; // region describing currently visible portion of the frame int iRgnKind; // region type for hrgnVis } GIFFRAME, *PGIFFRAME; typedef struct { BOOL fAnimating; // TRUE if animation is (still) running DWORD dwLoopIter; // current iteration of looped animation, not actually used for Netscape compliance reasons _GIFFRAME * pgfDraw; // last frame we need to draw DWORD dwNextTimeMS; // Time to display pgfDraw->pgfNext, or next iteration } GIFANIMATIONSTATE, *PGIFANIMATIONSTATE; #define dwGIFVerUnknown ((DWORD)0) // unknown version of GIF file #define dwGIFVer87a ((DWORD)87) // GIF87a file format #define dwGIFVer89a ((DWORD)89) // GIF89a file format. typedef struct _GIFANIMDATA { BOOL fAnimated; // TRUE if cFrames and pgf define a GIF animation BOOL fLooped; // TRUE if we've seen a Netscape loop block BOOL fHasTransparency; // TRUE if a frame is transparent, or if a frame does // not cover the entire logical screen. BOOL fNoBWMapping; // TRUE if we saw more than two colors in anywhere in the file. DWORD dwGIFVer; // GIF Version we need to special case 87a backgrounds unsigned short cLoops; // A la Netscape, we will treat this as // "loop forever" if it is zero. PGIFFRAME pgf; // animation frame entries PALETTEENTRY *pcolorsGlobal; // GIF global colors - NULL after GIF prepared for screen PGIFFRAME pgfLastProg; // remember the last frame to be drawn during decoding DWORD dwLastProgTimeMS; // time at which pgfLastProg was displayed. } GIFANIMDATA, *PGIFANIMDATA; /** End Structs **/ #define MAXCOLORMAPSIZE 256 #define TRUE 1 #define FALSE 0 #define CM_RED 0 #define CM_GREEN 1 #define CM_BLUE 2 #define MAX_LWZ_BITS 12 #define INTERLACE 0x40 #define LOCALCOLORMAP 0x80 #define BitSet(byte, bit) (((byte) & (bit)) == (bit)) #define LM_to_uint(a,b) ((((unsigned int) b)<<8)|((unsigned int)a)) #define dwIndefiniteGIFThreshold 300 // 300 seconds == 5 minutes // If the GIF runs longer than // this, we will assume the author // intended an indefinite run. #define dwMaxGIFBits 13107200 // keep corrupted GIFs from causing // us to allocate _too_ big a buffer. // This one is 1280 X 1024 X 10. typedef struct _GIFSCREEN { unsigned long Width; unsigned long Height; unsigned char ColorMap[3][MAXCOLORMAPSIZE]; unsigned long BitPixel; unsigned long ColorResolution; unsigned long Background; unsigned long AspectRatio; } GIFSCREEN; typedef struct _GIF89 { long transparent; long delayTime; long inputFlag; long disposal; } GIF89; #define MAX_STACK_SIZE ((1 << (MAX_LWZ_BITS)) * 2) #define MAX_TABLE_SIZE (1 << MAX_LWZ_BITS) typedef struct _GIFINFO { IStream *stream; GIF89 Gif89; long lGifLoc; long ZeroDataBlock; /* ** Pulled out of nextCode */ long curbit, lastbit, get_done; long last_byte; long return_clear; /* ** Out of nextLWZ */ unsigned short *pstack, *sp; long stacksize; long code_size, set_code_size; long max_code, max_code_size; long clear_code, end_code; /* * Were statics in procedures */ unsigned char buf[280]; unsigned short *table[2]; long tablesize; long firstcode, oldcode; } GIFINFO,*PGIFINFO; /* DirectAnimation wrapper class for GIF info */ class CImgGif { // Class methods public: CImgGif(); ~CImgGif(); unsigned char * ReadGIFMaster(); BOOL Read(unsigned char *buffer, long len); long ReadColorMap(long number, unsigned char buffer[3][MAXCOLORMAPSIZE]); long DoExtension(long label); long GetDataBlock(unsigned char *buf); unsigned char * ReadImage(long len, long height, BOOL fInterlace, BOOL fGIFFrame); long readLWZ(); long nextLWZ(); long nextCode(long code_size); BOOL initLWZ(long input_code_size); unsigned short * growStack(); BOOL growTables(); BITMAPINFO * FinishDithering(); // Data members public: LPCSTR _szFileName; BOOL _fInterleaved; BOOL _fInvalidateAll; int _yLogRow; GIFINFO _gifinfo; GIFANIMATIONSTATE _gas; GIFANIMDATA _gad; PALETTEENTRY _ape[256]; int _xWidth; int _yHeight; LONG _lTrans; BYTE * _pbBits; } GIFIMAGE; CImgGif::CImgGif() { _gifinfo.pstack = NULL; _gifinfo.table[0] = NULL; _gifinfo.table[1] = NULL; } CImgGif::~CImgGif() { free(_gifinfo.pstack); free(_gifinfo.table[0]); free(_gifinfo.table[1]); PGIFFRAME nextPgf, curPgf; curPgf = _gad.pgf; while(curPgf != NULL) { nextPgf = curPgf->pgfNext; free(curPgf->ppixels); free(curPgf->pcolors); free(curPgf->pbmi); free(curPgf); curPgf = nextPgf; } } static int GetColorMode() { return 0; }; #ifndef DEBUG #pragma optimize("t",on) #endif BOOL CImgGif::Read(unsigned char *buffer, long len) { DWORD lenout = 0; /* read len characters into buffer */ _gifinfo.stream->Read(buffer,len,&lenout); return ((long)lenout == len); } long CImgGif::ReadColorMap(long number, unsigned char buffer[3][MAXCOLORMAPSIZE]) { long i; unsigned char rgb[3]; for (i = 0; i < number; ++i) { if (!Read(rgb, sizeof(rgb))) { //bw //bw TraceTag((tagImageDecode, "bad gif colormap.")); return (TRUE); } buffer[CM_RED][i] = rgb[0]; buffer[CM_GREEN][i] = rgb[1]; buffer[CM_BLUE][i] = rgb[2]; } return FALSE; } long CImgGif::GetDataBlock(unsigned char *buf) { unsigned char count; count = 0; if (!Read(&count, 1)) { return -1; } _gifinfo.ZeroDataBlock = count == 0; if ((count != 0) && (!Read(buf, count))) { return -1; } return ((long) count); } #define MIN_CODE_BITS 5 #define MIN_STACK_SIZE 64 #define MINIMUM_CODE_SIZE 2 BOOL CImgGif::initLWZ(long input_code_size) { if(input_code_size < MINIMUM_CODE_SIZE) return FALSE; _gifinfo.set_code_size = input_code_size; _gifinfo.code_size = _gifinfo.set_code_size + 1; _gifinfo.clear_code = 1 << _gifinfo.set_code_size; _gifinfo.end_code = _gifinfo.clear_code + 1; _gifinfo.max_code_size = 2 * _gifinfo.clear_code; _gifinfo.max_code = _gifinfo.clear_code + 2; _gifinfo.curbit = _gifinfo.lastbit = 0; _gifinfo.last_byte = 2; _gifinfo.get_done = FALSE; _gifinfo.return_clear = TRUE; if(input_code_size >= MIN_CODE_BITS) _gifinfo.stacksize = ((1 << (input_code_size)) * 2); else _gifinfo.stacksize = MIN_STACK_SIZE; if ( _gifinfo.pstack != NULL ) free( _gifinfo.pstack ); if ( _gifinfo.table[0] != NULL ) free( _gifinfo.table[0] ); if ( _gifinfo.table[1] != NULL ) free( _gifinfo.table[1] ); _gifinfo.table[0] = 0; _gifinfo.table[1] = 0; _gifinfo.pstack = 0; _gifinfo.pstack = (unsigned short *) malloc((_gifinfo.stacksize)*sizeof(unsigned short)); if(_gifinfo.pstack == 0){ goto ErrorExit; } _gifinfo.sp = _gifinfo.pstack; // Initialize the two tables. _gifinfo.tablesize = (_gifinfo.max_code_size); _gifinfo.table[0] = (unsigned short *) malloc((_gifinfo.tablesize)*sizeof(unsigned short)); _gifinfo.table[1] = (unsigned short *) malloc((_gifinfo.tablesize)*sizeof(unsigned short)); if((_gifinfo.table[0] == 0) || (_gifinfo.table[1] == 0)){ goto ErrorExit; } return TRUE; ErrorExit: if(_gifinfo.pstack){ free(_gifinfo.pstack); _gifinfo.pstack = 0; } if(_gifinfo.table[0]){ free(_gifinfo.table[0]); _gifinfo.table[0] = 0; } if(_gifinfo.table[1]){ free(_gifinfo.table[1]); _gifinfo.table[1] = 0; } return FALSE; } long CImgGif::nextCode(long code_size) { static const long maskTbl[16] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, }; long i, j, ret, end; unsigned char *buf = &_gifinfo.buf[0]; if (_gifinfo.return_clear) { _gifinfo.return_clear = FALSE; return _gifinfo.clear_code; } end = _gifinfo.curbit + code_size; if (end >= _gifinfo.lastbit) { long count; if (_gifinfo.get_done) { return -1; } buf[0] = buf[_gifinfo.last_byte - 2]; buf[1] = buf[_gifinfo.last_byte - 1]; if ((count = GetDataBlock(&buf[2])) == 0) _gifinfo.get_done = TRUE; if (count < 0) { return -1; } _gifinfo.last_byte = 2 + count; _gifinfo.curbit = (_gifinfo.curbit - _gifinfo.lastbit) + 16; _gifinfo.lastbit = (2 + count) * 8; end = _gifinfo.curbit + code_size; // Okay, bug 30784 time. It's possible that we only got 1 // measly byte in the last data block. Rare, but it does happen. // In that case, the additional byte may still not supply us with // enough bits for the next code, so, as Mars Needs Women, IE // Needs Data. if ( end >= _gifinfo.lastbit && !_gifinfo.get_done ) { // protect ourselve from the ( theoretically impossible ) // case where between the last data block, the 2 bytes from // the block preceding that, and the potential 0xFF bytes in // the next block, we overflow the buffer. // Since count should always be 1, //bw Assert ( count == 1 ); // there should be enough room in the buffer, so long as someone // doesn't shrink it. if ( count + 0x101 >= sizeof( _gifinfo.buf ) ) { //bw Assert ( FALSE ); // return -1; } if ((count = GetDataBlock(&buf[2 + count])) == 0) _gifinfo.get_done = TRUE; if (count < 0) { return -1; } _gifinfo.last_byte += count; _gifinfo.lastbit = _gifinfo.last_byte * 8; end = _gifinfo.curbit + code_size; } } j = end / 8; i = _gifinfo.curbit / 8; if (i == j) ret = buf[i]; else if (i + 1 == j) ret = buf[i] | (((long) buf[i + 1]) << 8); else ret = buf[i] | (((long) buf[i + 1]) << 8) | (((long) buf[i + 2]) << 16); ret = (ret >> (_gifinfo.curbit % 8)) & maskTbl[code_size]; _gifinfo.curbit += code_size; return ret; } // Grows the stack and returns the top of the stack. unsigned short * CImgGif::growStack() { long index; unsigned short *lp; if (_gifinfo.stacksize >= MAX_STACK_SIZE) return 0; index = long(_gifinfo.sp - _gifinfo.pstack); lp = (unsigned short *)realloc(_gifinfo.pstack, (_gifinfo.stacksize)*2*sizeof(unsigned short)); if(lp == 0) return 0; _gifinfo.pstack = lp; _gifinfo.sp = &(_gifinfo.pstack[index]); _gifinfo.stacksize = (_gifinfo.stacksize)*2; lp = &(_gifinfo.pstack[_gifinfo.stacksize]); return lp; } BOOL CImgGif::growTables() { unsigned short *lp; lp = (unsigned short *) realloc(_gifinfo.table[0], (_gifinfo.max_code_size)*sizeof(unsigned short)); if(lp == 0){ return FALSE; } _gifinfo.table[0] = lp; lp = (unsigned short *) realloc(_gifinfo.table[1], (_gifinfo.max_code_size)*sizeof(unsigned short)); if(lp == 0){ return FALSE; } _gifinfo.table[1] = lp; return TRUE; } inline long CImgGif::readLWZ() { return((_gifinfo.sp > _gifinfo.pstack) ? *--(_gifinfo.sp) : nextLWZ()); } #define CODE_MASK 0xffff long CImgGif::nextLWZ() { long code, incode; unsigned short usi; unsigned short *table0 = _gifinfo.table[0]; unsigned short *table1 = _gifinfo.table[1]; unsigned short *pstacktop = &(_gifinfo.pstack[_gifinfo.stacksize]); while ((code = nextCode(_gifinfo.code_size)) >= 0) { if (code == _gifinfo.clear_code) { /* corrupt GIFs can make this happen */ if (_gifinfo.clear_code >= (1 << MAX_LWZ_BITS)) { return -2; } _gifinfo.code_size = _gifinfo.set_code_size + 1; _gifinfo.max_code_size = 2 * _gifinfo.clear_code; _gifinfo.max_code = _gifinfo.clear_code + 2; if(!growTables()) return -2; table0 = _gifinfo.table[0]; table1 = _gifinfo.table[1]; _gifinfo.tablesize = _gifinfo.max_code_size; for (usi = 0; usi < _gifinfo.clear_code; ++usi) { table1[usi] = usi; } memset(table0,0,sizeof(unsigned short )*(_gifinfo.tablesize)); memset(&table1[_gifinfo.clear_code],0,sizeof(unsigned short)*((_gifinfo.tablesize)-_gifinfo.clear_code)); _gifinfo.sp = _gifinfo.pstack; do { _gifinfo.firstcode = _gifinfo.oldcode = nextCode(_gifinfo.code_size); } while (_gifinfo.firstcode == _gifinfo.clear_code); return _gifinfo.firstcode; } if (code == _gifinfo.end_code) { long count; unsigned char buf[260]; if (_gifinfo.ZeroDataBlock) { return -2; } while ((count = GetDataBlock(buf)) > 0) ; if (count != 0) return -2; } incode = code; if (code >= _gifinfo.max_code) { if (_gifinfo.sp >= pstacktop){ pstacktop = growStack(); if(pstacktop == 0) return -2; } *(_gifinfo.sp)++ = (unsigned short)((CODE_MASK ) & (_gifinfo.firstcode)); code = _gifinfo.oldcode; } #if FEATURE_FAST // (andyp) easy speedup here for ie3.1 (too late for ie3.0): // // 1. move growStack code out of loop (use max 12-bit/4k slop). // 2. do "sp = _gifinfo.sp" so it will get enreg'ed. // 3. un-inline growStack (and growTables). // 4. change short's to int's (benefits win32) (esp. table1 & table2) // (n.b. int not long, so we'll keep win3.1 perf) // 5. change long's to int's (benefits win16) (esp. code). // // together these will make the loop very tight w/ everything kept // enregistered and no 66 overrides. // // one caveat is that on average this loop iterates 4x so it's // not clear how much the speedup will really gain us until we // look at the outer loop as well. #endif while (code >= _gifinfo.clear_code) { if (_gifinfo.sp >= pstacktop){ pstacktop = growStack(); if(pstacktop == 0) return -2; } *(_gifinfo.sp)++ = table1[code]; if (code == (long)(table0[code])) { return (code); } code = (long)(table0[code]); } if (_gifinfo.sp >= pstacktop){ pstacktop = growStack(); if(pstacktop == 0) return -2; } _gifinfo.firstcode = (long)table1[code]; *(_gifinfo.sp)++ = table1[code]; if ((code = _gifinfo.max_code) < (1 << MAX_LWZ_BITS)) { table0[code] = (USHORT)(_gifinfo.oldcode) & CODE_MASK; table1[code] = (USHORT)(_gifinfo.firstcode) & CODE_MASK; ++_gifinfo.max_code; if ((_gifinfo.max_code >= _gifinfo.max_code_size) && (_gifinfo.max_code_size < ((1 << MAX_LWZ_BITS)))) { _gifinfo.max_code_size *= 2; ++_gifinfo.code_size; if(!growTables()) return -2; table0 = _gifinfo.table[0]; table1 = _gifinfo.table[1]; // Tables have been reallocated to the correct size but initialization // still remains to be done. This initialization is different from // the first time initialization of these tables. memset(&(table0[_gifinfo.tablesize]),0, sizeof(unsigned short )*(_gifinfo.max_code_size - _gifinfo.tablesize)); memset(&(table1[_gifinfo.tablesize]),0, sizeof(unsigned short )*(_gifinfo.max_code_size - _gifinfo.tablesize)); _gifinfo.tablesize = (_gifinfo.max_code_size); } } _gifinfo.oldcode = incode; if (_gifinfo.sp > _gifinfo.pstack) return ((long)(*--(_gifinfo.sp))); } return code; } #ifndef DEBUG // Return to default optimization flags #pragma optimize("",on) #endif unsigned char * CImgGif::ReadImage(long len, long height, BOOL fInterlace, BOOL fGIFFrame) { unsigned char *dp, c; long v; long xpos = 0, ypos = 0, pass = 0; unsigned char *image; long padlen = ((len + 3) / 4) * 4; DWORD cbImage = 0; char buf[256]; // need a buffer to read trailing blocks ( up to terminator ) into //ULONG ulCoversImg = IMGBITS_PARTIAL; /* ** Initialize the Compression routines */ if (!Read(&c, 1)) { return (NULL); } /* ** If this is an "uninteresting picture" ignore it. */ cbImage = padlen * height * sizeof(char); if ( cbImage > dwMaxGIFBits || (image = (unsigned char *) _calloc(1, cbImage)) == NULL) { //bw TraceTag((tagImageDecode, "Cannot allocate space for gif image data\n")); return (NULL); } if (c == 1) { // Netscape seems to field these bogus GIFs by filling treating them // as transparent. While not the optimal way to simulate this effect, // we'll fake it by pushing the initial code size up to a safe value, // consuming the input, and returning a buffer full of the transparent // color or zero, if no transparency is indicated. if (initLWZ(MINIMUM_CODE_SIZE)) while (readLWZ() >= 0); else { //bw TraceTag((tagImageDecode, "GIF: failed LZW decode.\n")); free(image); return (NULL); } if (_gifinfo.Gif89.transparent != -1) FillMemory(image, cbImage, (BYTE)_gifinfo.Gif89.transparent); else // fall back on the background color FillMemory(image, cbImage, 0); return image; } else if (initLWZ(c) == FALSE) { free(image); //bw TraceTag((tagImageDecode, "GIF: failed LZW decode.\n")); return NULL; } if (!fGIFFrame) _pbBits = image; if (fInterlace) { long i; long pass = 0, step = 8; if (!fGIFFrame && (height > 4)) _fInterleaved = TRUE; for (i = 0; i < height; i++) { // message("readimage, logical=%d, offset=%d\n", i, padlen * ((height-1) - ypos)); dp = &image[padlen * ((height-1) - ypos)]; for (xpos = 0; xpos < len; xpos++) { if ((v = readLWZ()) < 0) goto abort; *dp++ = (unsigned char) v; } ypos += step; while (ypos >= height) { if (pass++ > 0) step /= 2; ypos = step / 2; /*if (!fGIFFrame && pass == 1) { ulCoversImg = IMGBITS_TOTAL; }*/ } if (!fGIFFrame) { _yLogRow = i; /*if ((i & PROG_INTERVAL) == 0) { // Post ProgDraw (IE code has delay-logic) OnProg(FALSE, ulCoversImg); }*/ } } /*if (!fGIFFrame) { OnProg(TRUE, ulCoversImg); }*/ if (!fGIFFrame && height <= 4) { _yLogRow = height-1; } } else { if (!fGIFFrame) _yLogRow = -1; for (ypos = height-1; ypos >= 0; ypos--) { dp = &image[padlen * ypos]; for (xpos = 0; xpos < len; xpos++) { if ((v = readLWZ()) < 0) goto abort; *dp++ = (unsigned char) v; } if (!fGIFFrame) { _yLogRow++; // message("readimage, logical=%d, offset=%d\n", _yLogRow, padlen * ypos); /*if ((_yLogRow & PROG_INTERVAL) == 0) { // Post ProgDraw (IE code has delay-logic) OnProg(FALSE, ulCoversImg); }*/ } } /*if (!fGIFFrame) { OnProg(TRUE, ulCoversImg); }*/ } // consume blocks up to image block terminator so we can proceed to the next image while (GetDataBlock((unsigned char *) buf) > 0) ; return (image); abort: /*if (!fGIFFrame) OnProg(TRUE, ulCoversImg);*/ return NULL; } long CImgGif::DoExtension(long label) { unsigned char buf[256]; int count; switch (label) { case 0x01: /* Plain Text Extension */ break; case 0xff: /* Application Extension */ // Is it the Netscape looping extension count = GetDataBlock((unsigned char *) buf); if (count >= 11) { char *szNSExt = "NETSCAPE2.0"; if ( memcmp( buf, szNSExt, strlen( szNSExt ) ) == 0 ) { // if it has their signature, get the data subblock with the iter count count = GetDataBlock((unsigned char *) buf); if ( count >= 3 ) { _gad.fLooped = TRUE; _gad.cLoops = (buf[2] << 8) | buf[1]; } } } while (GetDataBlock((unsigned char *) buf) > 0) ; return FALSE; break; case 0xfe: /* Comment Extension */ while (GetDataBlock((unsigned char *) buf) > 0) { //bw TraceTag((tagImageDecode, "GIF comment: %s\n", buf)); } return FALSE; case 0xf9: /* Graphic Control Extension */ count = GetDataBlock((unsigned char *) buf); if (count >= 3) { _gifinfo.Gif89.disposal = (buf[0] >> 2) & 0x7; _gifinfo.Gif89.inputFlag = (buf[0] >> 1) & 0x1; _gifinfo.Gif89.delayTime = LM_to_uint(buf[1], buf[2]); if ((buf[0] & 0x1) != 0) _gifinfo.Gif89.transparent = buf[3]; else _gifinfo.Gif89.transparent = -1; } while (GetDataBlock((unsigned char *) buf) > 0) ; return FALSE; default: break; } while (GetDataBlock((unsigned char *) buf) > 0) ; return FALSE; } BOOL IsGifHdr(BYTE * pb) { return(pb[0] == 'G' && pb[1] == 'I' && pb[2] == 'F' && pb[3] == '8' && (pb[4] == '7' || pb[4] == '9') && pb[5] == 'a'); } PBITMAPINFO x_8BPIBitmap(int xsize, int ysize) { PBITMAPINFO pbmi; if (GetColorMode() == 8) { pbmi = (PBITMAPINFO) _calloc(1, sizeof(BITMAPINFOHEADER) + 256 * sizeof(WORD)); if (!pbmi) { return NULL; } pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); pbmi->bmiHeader.biWidth = xsize; pbmi->bmiHeader.biHeight = ysize; pbmi->bmiHeader.biPlanes = 1; pbmi->bmiHeader.biBitCount = 8; pbmi->bmiHeader.biCompression = BI_RGB; /* no compression */ pbmi->bmiHeader.biSizeImage = 0; /* not needed when not compressed */ pbmi->bmiHeader.biXPelsPerMeter = 0; pbmi->bmiHeader.biYPelsPerMeter = 0; pbmi->bmiHeader.biClrUsed = 256; pbmi->bmiHeader.biClrImportant = 0; } else { pbmi = (PBITMAPINFO) _calloc(1, sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)); if (!pbmi) { return NULL; } pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); pbmi->bmiHeader.biWidth = xsize; pbmi->bmiHeader.biHeight = ysize; pbmi->bmiHeader.biPlanes = 1; pbmi->bmiHeader.biBitCount = 8; pbmi->bmiHeader.biCompression = BI_RGB; /* no compression */ pbmi->bmiHeader.biSizeImage = 0; /* not needed when not compressed */ pbmi->bmiHeader.biXPelsPerMeter = 0; pbmi->bmiHeader.biYPelsPerMeter = 0; pbmi->bmiHeader.biClrUsed = 256; pbmi->bmiHeader.biClrImportant = 0; } return pbmi; } /* For color images. This routine should only be used when drawing to an 8 bit palette screen. It always creates a DIB in DIB_PAL_COLORS format. */ PBITMAPINFO BIT_Make_DIB_PAL_Header(int xsize, int ysize) { int i; PBITMAPINFO pbmi; WORD *pw; pbmi = x_8BPIBitmap(xsize, ysize); if (!pbmi) { return NULL; } pw = (WORD *) pbmi->bmiColors; for (i = 0; i < 256; i++) { pw[i] = (WORD)i; } return pbmi; } /* For color images. This routine is used when drawing to the nonpalette screens. It always creates DIBs in DIB_RGB_COLORS format. If there is a transparent color, it is modified in the palette to be the background color for the window. */ PBITMAPINFO BIT_Make_DIB_RGB_Header_Screen(int xsize, int ysize, int cEntries, PALETTEENTRY * rgpe, int transparent) { int i; PBITMAPINFO pbmi; pbmi = x_8BPIBitmap(xsize, ysize); if (!pbmi) { return NULL; } for (i = 0; i < cEntries; i++) { pbmi->bmiColors[i].rgbRed = rgpe[i].peRed; pbmi->bmiColors[i].rgbGreen = rgpe[i].peGreen; pbmi->bmiColors[i].rgbBlue = rgpe[i].peBlue; pbmi->bmiColors[i].rgbReserved = 0; } /* if (transparent != -1) { COLORREF color; color = PREF_GetBackgroundColor(); pbmi->bmiColors[transparent].rgbRed = GetRValue(color); pbmi->bmiColors[transparent].rgbGreen = GetGValue(color); pbmi->bmiColors[transparent].rgbBlue = GetBValue(color); } */ return pbmi; } unsigned char * CImgGif::ReadGIFMaster() { HRESULT hr = S_OK; unsigned char buf[16]; unsigned char c; unsigned char localColorMap[3][MAXCOLORMAPSIZE]; long useGlobalColormap; long imageCount = 0; long imageNumber = 1; unsigned char *image = NULL; unsigned long i; long bitPixel; PGIFFRAME pgfLast = NULL; PGIFFRAME pgfNew; GIFSCREEN* GifScreen = new GIFSCREEN; if (GifScreen == NULL) { hr = E_FAIL; goto done; } _gifinfo.ZeroDataBlock = 0; /* * Initialize GIF89 extensions */ _gifinfo.Gif89.transparent = -1; _gifinfo.Gif89.delayTime = 5; _gifinfo.Gif89.inputFlag = -1; _gifinfo.Gif89.disposal = 0; _gifinfo.lGifLoc = 0; // initialize our animation fields _gad.fAnimated = FALSE; // set to TRUE if we see more than one image _gad.fLooped = FALSE; // TRUE if we've seen a Netscape loop block _gad.fHasTransparency = FALSE; // until proven otherwise _gad.fNoBWMapping = FALSE; _gad.dwGIFVer = dwGIFVerUnknown; _gad.cLoops = 0; _gad.pgf = NULL; _gad.pcolorsGlobal = NULL; if (!Read(buf, 6)) { //bw TraceTag((tagImageDecode, "GIF: error reading magic number\n")); hr = E_FAIL; goto done; } if (!IsGifHdr(buf)) { //bw TraceTag((tagImageDecode, "GIF: Malformed header\n")); hr = E_FAIL; goto done; } _gad.dwGIFVer = (buf[4] == '7') ? dwGIFVer87a : dwGIFVer89a; if (!Read(buf, 7)) { //bw TraceTag((tagImageDecode, "GIF: failed to read screen descriptor\n")); hr = E_FAIL; goto done; } GifScreen->Width = LM_to_uint(buf[0], buf[1]); GifScreen->Height = LM_to_uint(buf[2], buf[3]); GifScreen->BitPixel = 2 << (buf[4] & 0x07); GifScreen->ColorResolution = (((buf[4] & 0x70) >> 3) + 1); GifScreen->Background = buf[5]; GifScreen->AspectRatio = buf[6]; if (BitSet(buf[4], LOCALCOLORMAP)) { /* Global Colormap */ int scale = 65536 / MAXCOLORMAPSIZE; if (ReadColorMap(GifScreen->BitPixel, GifScreen->ColorMap)) { //bw TraceTag((tagImageDecode, "error reading global colormap\n")); hr = E_FAIL; goto done; } for (i = 0; i < GifScreen->BitPixel; i++) { int tmp; tmp = (BYTE) (GifScreen->ColorMap[0][i]); _ape[i].peRed = (BYTE) (GifScreen->ColorMap[0][i]); _ape[i].peGreen = (BYTE) (GifScreen->ColorMap[1][i]); _ape[i].peBlue = (BYTE) (GifScreen->ColorMap[2][i]); _ape[i].peFlags = (BYTE) 0; } for (i = GifScreen->BitPixel; i < MAXCOLORMAPSIZE; i++) { _ape[i].peRed = (BYTE) 0; _ape[i].peGreen = (BYTE) 0; _ape[i].peBlue = (BYTE) 0; _ape[i].peFlags = (BYTE) 0; } } if (GifScreen->AspectRatio != 0 && GifScreen->AspectRatio != 49) { float r; r = ((float) (GifScreen->AspectRatio) + (float) 15.0) / (float) 64.0; //bw TraceTag((tagImageDecode, "Warning: non-square pixels!\n")); } for (;; ) // our appetite now knows no bounds save termination or error { if (!Read(&c, 1)) { //bw TraceTag((tagImageDecode, "EOF / read error on image data\n")); hr = E_FAIL; goto done; } if (c == ';') { /* GIF terminator */ if (imageCount < imageNumber) { //bw TraceTag((tagImageDecode, "No images found in file\n")); hr = E_FAIL; goto done; } break; } if (c == '!') { /* Extension */ if (!Read(&c, 1)) { //bw TraceTag((tagImageDecode, "EOF / read error on extension function code\n")); hr = E_FAIL; goto done; } DoExtension(c); continue; } if (c != ',') { /* Not a valid start character */ break; } ++imageCount; if (!Read(buf, 9)) { //bw TraceTag((tagImageDecode, "couldn't read left/top/width/height\n")); hr = E_FAIL; goto done; } useGlobalColormap = !BitSet(buf[8], LOCALCOLORMAP); bitPixel = 1 << ((buf[8] & 0x07) + 1); /* * We only want to set width and height for the imageNumber * we are requesting. */ if (imageCount == imageNumber) { // Replicate some of Netscape's special cases: // Don't use the logical screen if it's a GIF87a and the topLeft of the first image is at the origin. // Don't use the logical screen if the first image spills out of the logical screen. // These are artifacts of primitive authoring tools falling into the hands of hapless users. RECT rectImage; // rect defining bounds of GIF RECT rectLS; // rect defining bounds of GIF logical screen. RECT rectSect; // intersection of image an logical screen BOOL fNoSpill; // True if the image doesn't spill out of the logical screen BOOL fGoofy87a; // TRUE if its one of the 87a pathologies that Netscape special cases rectImage.left = LM_to_uint(buf[0], buf[1]); rectImage.top = LM_to_uint(buf[2], buf[3]); rectImage.right = rectImage.left + LM_to_uint(buf[4], buf[5]); rectImage.bottom = rectImage.top + LM_to_uint(buf[6], buf[7]); rectLS.left = rectLS.top = 0; rectLS.right = GifScreen->Width; rectLS.bottom = GifScreen->Height; IntersectRect( &rectSect, &rectImage, &rectLS ); fNoSpill = EqualRect( &rectImage, &rectSect ); fGoofy87a = FALSE; if (_gad.dwGIFVer == dwGIFVer87a) { // netscape ignores the logical screen if the image is flush against // either the upper left or lower right corner fGoofy87a = (rectImage.top == 0 && rectImage.left == 0) || (rectImage.bottom == rectLS.bottom && rectImage.right == rectLS.right); } if (!fGoofy87a && fNoSpill) { _xWidth = GifScreen->Width; _yHeight = GifScreen->Height; } else { // Something is amiss. Fall back to the image's dimensions. // If the sizes match, but the image is offset, or we're ignoring // the logical screen cuz it's a goofy 87a, then pull it back to // to the origin if ((LM_to_uint(buf[4], buf[5]) == GifScreen->Width && LM_to_uint(buf[6], buf[7]) == GifScreen->Height) || fGoofy87a) { buf[0] = buf[1] = 0; // left corner to zero buf[2] = buf[3] = 0; // top to zero. } _xWidth = LM_to_uint(buf[4], buf[5]); _yHeight = LM_to_uint(buf[6], buf[7]); } _lTrans = _gifinfo.Gif89.transparent; // Post WHKNOWN //OnSize(_xWidth, _yHeight, _lTrans); } if (!useGlobalColormap) { if (ReadColorMap(bitPixel, localColorMap)) { //bw TraceTag((tagImageDecode, "error reading local colormap\n")); hr = E_FAIL; goto done; } } // We allocate a frame record for each imag in the GIF stream, including // the first/primary image. pgfNew = (PGIFFRAME) _calloc(1, sizeof(GIFFRAME)); if ( pgfNew == NULL ) { //bw TraceTag((tagImageDecode, "not enough memory for GIF frame\n")); hr = E_FAIL; goto done; } if ( _gifinfo.Gif89.delayTime != -1 ) { // we have a fresh control extension for this block // convert to milliseconds pgfNew->gced.uiDelayTime = _gifinfo.Gif89.delayTime * 10; //REVIEW(seanf): crude hack to cope with 'degenerate animations' whose timing is set to some // small value becaue of the delays imposed by Netscape's animation process if ( pgfNew->gced.uiDelayTime <= 50 ) // assume these small values imply Netscape encoding delay pgfNew->gced.uiDelayTime = 100; // pick a larger value s.t. the frame will be visible pgfNew->gced.uiDisposalMethod = _gifinfo.Gif89.disposal; pgfNew->gced.fTransparent = _gifinfo.Gif89.transparent != -1; pgfNew->gced.ucTransIndex = (unsigned char)_gifinfo.Gif89.transparent; } else { // fake one up s.t. GIFs that rely solely on Netscape's delay to time their animations will play // The spec says that the scope of one of these blocks is the image after the block. // Netscape says 'until further notice'. So we play it their way up to a point. We // propagate the disposal method and transparency. Since Netscape doesn't honor the timing // we use our default timing for these images. pgfNew->gced.uiDelayTime = 100; pgfNew->gced.uiDisposalMethod = _gifinfo.Gif89.disposal; pgfNew->gced.fTransparent = _gifinfo.Gif89.transparent != -1; pgfNew->gced.ucTransIndex = (unsigned char)_gifinfo.Gif89.transparent; } pgfNew->top = LM_to_uint(buf[2], buf[3]); // bounds relative to the GIF logical screen pgfNew->left = LM_to_uint(buf[0], buf[1]); pgfNew->width = LM_to_uint(buf[4], buf[5]); pgfNew->height = LM_to_uint(buf[6], buf[7]); // Images that are offset, or do not cover the full logical screen are 'transparent' in the // sense that they require us to matte the frame onto the background. if (!_gad.fHasTransparency && (pgfNew->gced.fTransparent || pgfNew->top != 0 || pgfNew->left != 0 || (UINT)pgfNew->width != (UINT)GifScreen->Width || (UINT)pgfNew->height != (UINT)GifScreen->Height)) { _gad.fHasTransparency = TRUE; //if (_lTrans == -1) // OnTrans(0); } // We don't need to allocate a handle for the simple region case. // FrancisH says Windows is too much of a cheapskate to allow us the simplicity // of allocating the region once and modifying as needed. Well, okay, he didn't // put it that way... pgfNew->hrgnVis = NULL; pgfNew->iRgnKind = NULLREGION; if (!useGlobalColormap) { // remember that we saw a local color table and only map two-color images // if we have a homogenous color environment _gad.fNoBWMapping = _gad.fNoBWMapping || bitPixel > 2; // CALLOC will set unused colors to <0,0,0,0> pgfNew->pcolors = (PALETTEENTRY *) _calloc(MAXCOLORMAPSIZE, sizeof(PALETTEENTRY)); if ( pgfNew->pcolors == NULL ) { DeleteRgn( pgfNew->hrgnVis ); free( pgfNew ); //bw TraceTag((tagImageDecode, "not enough memory for GIF frame colors\n")); hr = E_FAIL; goto done; } else { for (i = 0; i < (ULONG)bitPixel; ++i) { pgfNew->pcolors[i].peRed = localColorMap[CM_RED][i]; pgfNew->pcolors[i].peGreen = localColorMap[CM_GREEN][i]; pgfNew->pcolors[i].peBlue = localColorMap[CM_BLUE][i]; } pgfNew->cColors = bitPixel; } } else { if ( _gad.pcolorsGlobal == NULL ) { // Whoa! Somebody's interested in the global color table // CALLOC will set unused colors to <0,0,0,0> _gad.pcolorsGlobal = (PALETTEENTRY *) _calloc(MAXCOLORMAPSIZE, sizeof(PALETTEENTRY)); _gad.fNoBWMapping = _gad.fNoBWMapping || GifScreen->BitPixel > 2; if ( _gad.pcolorsGlobal != NULL ) { CopyMemory(_gad.pcolorsGlobal, _ape, GifScreen->BitPixel * sizeof(PALETTEENTRY) ); } else { DeleteRgn( pgfNew->hrgnVis ); free( pgfNew ); //bw TraceTag((tagImageDecode, "not enough memory for GIF frame colors\n")); hr = E_FAIL; goto done; } } pgfNew->cColors = GifScreen->BitPixel; pgfNew->pcolors = _gad.pcolorsGlobal; } // Get this in here so that GifStrectchDIBits can use it during progressive // rendering. if ( _gad.pgf == NULL ) _gad.pgf = pgfNew; pgfNew->ppixels = ReadImage(LM_to_uint(buf[4], buf[5]), // width LM_to_uint(buf[6], buf[7]), // height BitSet(buf[8], INTERLACE), imageCount != imageNumber); if ( pgfNew->ppixels != NULL ) { // Oh JOY of JOYS! We got the pixels! if (pgfLast != NULL) { int transparent = (pgfNew->gced.fTransparent) ? (int) pgfNew->gced.ucTransIndex : -1; _gad.fAnimated = TRUE; // say multi-image == animated if (GetColorMode() == 8) // palettized, use DIB_PAL_COLORS { // This will also dither the bits to the screen palette pgfNew->pbmi = BIT_Make_DIB_PAL_Header(pgfNew->width, pgfNew->height); //if (x_Dither(pgfNew->ppixels, pgfNew->pcolors, pgfNew->width, pgfNew->height, transparent)) // goto exitPoint; } else // give it an RGB header { pgfNew->pbmi = BIT_Make_DIB_RGB_Header_Screen( pgfNew->width, pgfNew->height, pgfNew->cColors, pgfNew->pcolors, transparent); } // Okay, so we've done any mapping on the GIFFRAME, so there's // no need to keep the pcolors around. Let's go can clear out // the pcolors. // REVIEW(seanf): This assumes a common palette is used by all // clients of the image if ( pgfNew->pcolors != NULL && pgfNew->pcolors != _gad.pcolorsGlobal ) free( pgfNew->pcolors ); pgfNew->pcolors = NULL; pgfLast->pgfNext = pgfNew; // Do something to here to get the new frame on the screen. _fInvalidateAll = TRUE; //super::OnProg(FALSE, IMGBITS_TOTAL); } else { // first frame _gad.pgf = pgfNew; _gad.pgfLastProg = pgfNew; _gad.dwLastProgTimeMS = 0; // set up a temporary animation state for use in progressive draw _gas.fAnimating = TRUE; _gas.dwLoopIter = 0; _gas.pgfDraw = pgfNew; if ( imageCount == imageNumber ) image = pgfNew->ppixels; } pgfLast = pgfNew; } // make the _gifinfo.Gif89.delayTime stale, so we know if we got a new // GCE for the next image _gifinfo.Gif89.delayTime = -1; } if ( imageCount > imageNumber ) _gad.fAnimated = TRUE; // say multi-image == animated #ifdef FEATURE_GIF_ANIMATION_LONG_LOOP_GOES_INFINITE // RAID #23709 - If an animation is sufficiently long, we treat it as indefinite... // Indefinite stays indefinite. // 5/29/96 - JCordell sez we shouldn't introduce this gratuitous NS incompatibility. // We'll keep it around inside this ifdef in case we decide we want it. if ( _gad.fLooped && (_gad.dwLoopDurMS * _gad.cLoops) / 1000 > dwIndefiniteGIFThreshold ) // if longer than five minutes _gad.cLoops = 0; // set to indefinite looping. #endif // FEATURE_GIF_ANIMATION_LONG_LOOP_GOES_INFINITE done: if (GifScreen) { delete GifScreen; } return image; } BITMAPINFO * CImgGif::FinishDithering() { BITMAPINFO * pbmi; if (GetColorMode() == 8) { pbmi = BIT_Make_DIB_PAL_Header(_gad.pgf->width, _gad.pgf->height); } else { pbmi = BIT_Make_DIB_RGB_Header_Screen(_gad.pgf->width, _gad.pgf->height, _gad.pgf->cColors, _gad.pgf->pcolors, _lTrans); } return pbmi; } //#include //#define vector std::vector //+----------------------------------------------------------------------- // // Member: LoadGifImage // // Overview: Given an IStream, decode an image into an array of bitmaps // // Arguments: pStream data source // colorKeys pointer to where to store colorKey data // numBitmaps where to store number of bitmaps // delays where to store delay array // loop where to store number of times to loop // ppBitMaps where to store bitmaps // // Returns: S_OK on success otherwise error code // //------------------------------------------------------------------------ HRESULT LoadGifImage(IStream *stream, COLORREF **colorKeys, int *numBitmaps, int **delays, double *loop, HBITMAP *phBitmap) { HRESULT hr = S_OK; /* The odd approach here lets us keep the original IE GIF code unchanged while removing DA specific inserts (except error reporting). The progressive rendering and palette dithering found in the IE code is also not supported yet. */ CImgGif* gifimage = new CImgGif; if (gifimage == NULL) { hr = E_FAIL; goto done; } gifimage->_szFileName = NULL; gifimage->_gifinfo.stream = stream; BYTE *pbBits = gifimage->ReadGIFMaster(); if (pbBits) { gifimage->_pbBits = pbBits; gifimage->_gad.pgf->pbmi = gifimage->FinishDithering(); } /* Extract information from GIF decoder, and format it into an array of bitmaps. */ *delays = NULL; /*vector<>*/HBITMAP vhbmp; /*vector<>*/COLORREF vcolorKey; /*vector<>*/int vdelay; LPVOID image = NULL; LPVOID lastBits = pbBits; LPVOID bitsBeforeLastBits = NULL; PBITMAPINFO pbmi = NULL; HBITMAP hbm; PGIFFRAME pgf = gifimage->_gad.pgf; PGIFFRAME pgfOld = NULL; bool fUseOffset = false; bool fFirstFrame = true; long pgfWidth,pgfHeight, // animation frame dims fullWidth,fullHeight, // main frame dims fullPad, pgfPad, // row padding vals fullSize, pgfSize; unsigned int disp = 0; int i = 0; // TODO: Dither global palette to display palette fullWidth = gifimage->_xWidth; fullHeight = gifimage->_yHeight; fullPad = (((fullWidth + 3) / 4) * 4) - fullWidth; fullSize = (fullPad+fullWidth)*fullHeight; if (NULL == pgf) { hr = E_FAIL; goto done; } while(1) { // Assert(pgf); pbmi = pgf->pbmi; if (pbmi == NULL) { hr = E_FAIL; goto done; } // TODO: It would be nice to pass local palettes up so they could // be mapped to system palettes. // Check to see if frame is offset from logical frame if(pgf->top != 0 || pgf->left != 0 || pgf->width != fullWidth || pgf->height != fullHeight) { fUseOffset = true; pgfWidth = pbmi->bmiHeader.biWidth; pgfHeight = pbmi->bmiHeader.biHeight; pbmi->bmiHeader.biWidth = fullWidth; pbmi->bmiHeader.biHeight = fullHeight; pgfPad = (((pgfWidth + 3) / 4) * 4) - pgfWidth; pgfSize = (pgfPad+pgfWidth)*pgfHeight; } hbm = CreateDIBSection(NULL, pbmi, DIB_RGB_COLORS, (LPVOID *) &image, NULL, 0); if(!hbm) { hr = E_OUTOFMEMORY; goto done; } // Correctly composite bitmaps based on disposal method specified disp = pgf->gced.uiDisposalMethod; // If the frame is offset, fill it with if( (disp == gifRestorePrev) && (bitsBeforeLastBits != NULL) ) memcpy(image, bitsBeforeLastBits, fullSize); else if( (disp == gifRestoreBkgnd) || (disp == gifRestorePrev) || fFirstFrame ) // fill with bgColor memset(image, pgf->gced.ucTransIndex, fullSize); else // fill with last frames data memcpy(image, lastBits, fullSize); // For offset gifs allocate an image the size of the first frame // and then fill in the bits at the offset location. if(fUseOffset) { for(i=0; itop; dst = (BYTE*)image + ( ((topOffset + i) *(fullPad+fullWidth)) + pgf->left ); // copy from the frame's nth row src = pgf->ppixels + i*(pgfPad+pgfWidth); for(int j=0; jgced.ucTransIndex) dst[j] = src[j]; } } } else { // Overwritten accumulated bits with current bits. If the // new image contains transparency we need to take it into // account. Since this is slower, special case it. if(pgf->gced.fTransparent) { for(i=0; i<((fullPad+fullWidth)*fullHeight); i++) { if(pgf->ppixels[i] != pgf->gced.ucTransIndex) ((BYTE*)image)[i] = ((BYTE*)pgf->ppixels)[i]; } } else // Otherwise, just copy over the offset window's bytes memcpy(image, pgf->ppixels, (fullPad+fullWidth)*fullHeight); } /* If we got a transparent color extension, convert it to a COLORREF */ COLORREF colorKey = COLORKEY_NOT_SET; if (pgf->gced.fTransparent) { int transparent = pgf->gced.ucTransIndex; colorKey = RGB(pgf->pbmi->bmiColors[transparent].rgbRed, pgf->pbmi->bmiColors[transparent].rgbGreen, pgf->pbmi->bmiColors[transparent].rgbBlue); } // vcolorKey.push_back(colorKey); vcolorKey = colorKey; // biao change : vhbmp.push_back(hbm); vhbmp = hbm; /* The delay times are frame specific and can be different, these should be propagated as an array to the sampling code. */ // vdelay.push_back(pgf->gced.uiDelayTime); vdelay = pgf->gced.uiDelayTime; bitsBeforeLastBits = lastBits; lastBits = image; fUseOffset = false; if(pgf->pgfNext == NULL) break; pgfOld = pgf; pgf = pgf->pgfNext; fFirstFrame = FALSE; } // The number of times to loop are also propagated. Note we add one because // all other GIF decoders appear to treat the loop as the number of times to // loop AFTER the first run through the frames. if (gifimage->_gad.cLoops == 0 && gifimage->_gad.fLooped != 0) { *loop = 0; // HUGE_VAL; } else { *loop = gifimage->_gad.cLoops; } *numBitmaps = 1; // Since the vector will go out of scope, move contents over to heap *delays = (int*)malloc(1 * sizeof(int)); if (NULL == *delays) { hr = E_OUTOFMEMORY; goto done; } *colorKeys = (COLORREF*)malloc( sizeof(COLORREF) * 1 ); if (NULL == *colorKeys) { hr = E_OUTOFMEMORY; goto done; } *phBitmap = vhbmp; // biao fix [i]; (*colorKeys)[0] = vcolorKey; // [i]; (*delays)[0] = vdelay; //[i]; hr = S_OK; done: if (FAILED(hr)) { free(*delays); free(*colorKeys); if (gifimage) { delete gifimage; } } return hr; } /*lint --flb*/ BOOL Gif2Bmp(LPSTREAM pStream, HBITMAP* phBmp) { HRESULT hr; int numGifs = 0; double loop = 0; int * pDelays = NULL; COLORREF * pColorKeys = NULL; hr = LoadGifImage(pStream, &pColorKeys, &numGifs, &pDelays, &loop, phBmp); if (FAILED(hr)) { return FALSE; } return TRUE; }