/* Cockos WDL - LICE - Lightweight Image Compositing Engine Copyright (C) 2007 and later, Cockos Incorporated File: lice_gif.cpp (GIF loading for LICE) See lice.h for license and other information */ #include "lice.h" #include "../heapbuf.h" #include "../fileread.h" #include extern "C" { #include "../giflib/gif_lib.h" int _GifError; }; static void applyGifFrameToBitmap(LICE_IBitmap *bmp, GifFileType *fp, int transparent_pix, bool clear) { const int width=fp->Image.Width,height=fp->Image.Height; LICE_pixel cmap[256]; GifPixelType _linebuf[2048]; GifPixelType *linebuf=width > 2048 ? (GifPixelType*)malloc(width*sizeof(GifPixelType)) : _linebuf; int y; if (bmp) { y=0; if (fp->Image.ColorMap && fp->Image.ColorMap->Colors) for (;y<256 && y < fp->Image.ColorMap->ColorCount;y++) { GifColorType *ct=fp->Image.ColorMap->Colors+y; cmap[y]=LICE_RGBA(ct->Red,ct->Green,ct->Blue,255); } if (fp->SColorMap && fp->SColorMap->Colors) for (;y<256 && y < fp->SColorMap->ColorCount;y++) { GifColorType *ct=fp->SColorMap->Colors+y; cmap[y]=LICE_RGBA(ct->Red,ct->Green,ct->Blue,255); } for (;y<256;y++) cmap[y]=0; if (clear) { LICE_pixel col = 0; if (fp->SColorMap && fp->SColorMap->Colors && fp->SBackGroundColor >= 0 && fp->SBackGroundColor < fp->SColorMap->ColorCount) { GifColorType *ct=fp->SColorMap->Colors+fp->SBackGroundColor; col = LICE_RGBA(ct->Red,ct->Green,ct->Blue,0); } else if (fp->SBackGroundColor>=0 && fp->SBackGroundColor<256) { col = cmap[fp->SBackGroundColor] & LICE_RGBA(255,255,255,0); } LICE_Clear(bmp,col); } } const int bmp_h = bmp ? bmp->getHeight() : 0; const int bmp_w = bmp ? bmp->getWidth() : 0; LICE_pixel *bmp_ptr = bmp ? bmp->getBits() : NULL; int bmp_span = bmp ? bmp->getRowSpan() : 0; if (bmp && bmp->isFlipped() ) { bmp_ptr += (bmp_h-1)*bmp_span; bmp_span = -bmp_span; } const int xpos = fp->Image.Left; int skip=0; int use_width = width; if (xpos < 0) skip = -xpos; if (use_width > bmp_w - xpos) use_width = bmp_w - xpos; int ystate = 0, ypass=0; for (y=0; y < height; y ++) { if (DGifGetLine(fp,linebuf,width)==GIF_ERROR) break; int ypos; if (fp->Image.Interlace) { if (ypass == 0) { ypos = ystate++ * 8; if (ypos >= height) { ypass++; ystate=0; } } if (ypass == 1) { ypos = ystate++ * 8 + 4; if (ypos >= height) { ypass++; ystate=0; } } if (ypass == 2) { ypos = ystate++ * 4 + 2; if (ypos >= height) { ypass++; ystate=0; } } if (ypass==3) ypos = ystate++ * 2 + 1; } else { ypos = ystate++; } ypos += fp->Image.Top; if (ypos < 0 || ypos >= bmp_h) continue; LICE_pixel *p = bmp_ptr + ypos*bmp_span + xpos; int x; for (x = skip; x < use_width; x ++) { const int a = (int) linebuf[x]; if (a != transparent_pix) p[x]=cmap[a]; } } if (linebuf != _linebuf) free(linebuf); } static int readfunc_fh(GifFileType *fh, GifByteType *buf, int sz) { return ((WDL_FileRead*)fh->UserData)->Read(buf,sz); } LICE_IBitmap *LICE_LoadGIF(const char *filename, LICE_IBitmap *bmp, int *nframes) { WDL_FileRead fpp(filename,0,65536); if (nframes) *nframes=0; if (!fpp.IsOpen()) return 0; GifFileType *fp=DGifOpen(&fpp, readfunc_fh); if (!fp) return 0; int transparent_pix = -1; bool had_image = false; bool had_delay = false; bool need_new_frame = false; const bool own_bmp = !bmp; GifRecordType RecordType; GifByteType *Extension; int ExtCode; LICE_WrapperBitmap workbm(NULL,0,0,0,false); WDL_HeapBuf workbuf(128*1024); do { if (DGifGetRecordType(fp, &RecordType) == GIF_ERROR) break; switch (RecordType) { case IMAGE_DESC_RECORD_TYPE: if (DGifGetImageDesc(fp) == GIF_ERROR) { RecordType = TERMINATE_RECORD_TYPE; break; } if (!bmp) bmp=new WDL_NEW LICE_MemBitmap; if (!had_image || (nframes && need_new_frame)) { if (had_image) { // implies nframes const int ht = (*nframes+1) * (int)fp->SHeight; workbm.m_h = ht; workbm.m_w = (int)fp->SWidth; workbm.m_span = (workbm.m_w+3)&~3; workbm.m_buf = (LICE_pixel *) workbuf.ResizeOK(ht * sizeof(LICE_pixel) * workbm.m_span); if (!workbm.m_buf) { RecordType = TERMINATE_RECORD_TYPE; break; } } else { if (bmp) bmp->resize(fp->SWidth,fp->SHeight); if (!bmp || bmp->getWidth() != (int)fp->SWidth || bmp->getHeight() != (int)fp->SHeight) { RecordType = TERMINATE_RECORD_TYPE; break; } } if (nframes) { if (*nframes > 0) { if (*nframes == 1) LICE_Blit(&workbm,bmp,0,0,0,0,bmp->getWidth(),bmp->getHeight(),1.0,LICE_BLIT_MODE_COPY); LICE_Blit(&workbm,&workbm,0,workbm.getHeight()-(int)fp->SHeight,0, workbm.getHeight()-(int)fp->SHeight*2, workbm.getWidth(), (int)fp->SHeight,1.0f,LICE_BLIT_MODE_COPY); } *nframes += 1; } } if (nframes) { LICE_SubBitmap tmp(&workbm, 0,workbm.getHeight() - (int) fp->SHeight, workbm.getWidth(), (int)fp->SHeight); applyGifFrameToBitmap(&tmp,fp,transparent_pix,!had_image); } else applyGifFrameToBitmap(bmp,fp,transparent_pix,!had_image); had_image = true; need_new_frame = false; transparent_pix = -1; if (had_delay) { if (!nframes) { RecordType = TERMINATE_RECORD_TYPE; // finish up if first frame is finished } else { need_new_frame = true; } had_delay = false; } break; case EXTENSION_RECORD_TYPE: if (DGifGetExtension(fp, &ExtCode, &Extension) == GIF_ERROR) { RecordType = TERMINATE_RECORD_TYPE; } else { while (Extension != NULL) { if (ExtCode == 0xF9 && *Extension >= 4) { transparent_pix = -1; if (Extension[1]&1) { transparent_pix = Extension[4]; } if (Extension[2] || Extension[3]) had_delay=true; } if (DGifGetExtensionNext(fp, &Extension) == GIF_ERROR) { RecordType = TERMINATE_RECORD_TYPE; break; } } } break; case TERMINATE_RECORD_TYPE: break; default: /* Should be traps by DGifGetRecordType. */ break; } } while (RecordType != TERMINATE_RECORD_TYPE); DGifCloseFile(fp); if (had_image) { if (workbm.getWidth()) LICE_Copy(bmp,&workbm); return bmp; } if (own_bmp) delete bmp; return NULL; } class LICE_GIFLoader { public: _LICE_ImageLoader_rec rec; LICE_GIFLoader() { rec.loadfunc = loadfunc; rec.get_extlist = get_extlist; rec._next = LICE_ImageLoader_list; LICE_ImageLoader_list = &rec; } static LICE_IBitmap *loadfunc(const char *filename, bool checkFileName, LICE_IBitmap *bmpbase) { if (checkFileName) { const char *p=filename; while (*p)p++; while (p>filename && *p != '\\' && *p != '/' && *p != '.') p--; if (stricmp(p,".gif")) return 0; } return LICE_LoadGIF(filename,bmpbase,NULL); } static const char *get_extlist() { return "GIF files (*.GIF)\0*.GIF\0"; } }; LICE_GIFLoader LICE_gifldr; struct lice_gif_read_ctx { WDL_FileRead *fh; GifFileType *gif; int msecpos; int state; WDL_FILEREAD_POSTYPE ipos; }; void *LICE_GIF_LoadEx(const char *filename) { WDL_FileRead *fpp = new WDL_FileRead(filename,0,32768); if (!fpp->IsOpen()) { delete fpp; return 0; } GifFileType *gif=DGifOpen(fpp, readfunc_fh); if (!gif) { delete fpp; return 0; } lice_gif_read_ctx *ret = (lice_gif_read_ctx*)calloc(sizeof(lice_gif_read_ctx), 1); if (!ret) { DGifCloseFile(gif); delete fpp; return NULL; } ret->fh = fpp; ret->gif = gif; ret->msecpos = 0; ret->state = 0; ret->ipos = fpp->GetPosition(); return ret; } void LICE_GIF_Close(void *handle) { lice_gif_read_ctx *h = (lice_gif_read_ctx*)handle; if (h) { DGifCloseFile(h->gif); delete h->fh; free(h); } } void LICE_GIF_Rewind(void *handle) { lice_gif_read_ctx *h = (lice_gif_read_ctx*)handle; if (h) { h->state = 0; h->msecpos = 0; h->fh->SetPosition(h->ipos); // todo: flush giflib too } } unsigned int LICE_GIF_GetFilePos(void *handle) { // only used for accounting at the moment, so meh lice_gif_read_ctx *h = (lice_gif_read_ctx*)handle; if (h && h->fh) { return (unsigned int) h->fh->GetPosition(); } return 0; } int LICE_GIF_UpdateFrame(void *handle, LICE_IBitmap *bm) { lice_gif_read_ctx *h = (lice_gif_read_ctx*)handle; if (!h||h->state<0) return -2; GifFileType *fp = h->gif; if (bm) { bm->resize(fp->SWidth, fp->SHeight); if (bm->getWidth() != (int)fp->SWidth || bm->getHeight() != (int)fp->SHeight) return -3; } int has_delay = 0; int transparent_pix = -1; GifRecordType RecordType; GifByteType *Extension; int ExtCode; do { if (DGifGetRecordType(fp, &RecordType) == GIF_ERROR) return h->state = -5; switch (RecordType) { case IMAGE_DESC_RECORD_TYPE: if (DGifGetImageDesc(fp) == GIF_ERROR) return h->state = -4; applyGifFrameToBitmap(bm,fp,transparent_pix,!h->state); h->state += 1; h->msecpos += has_delay*10; return has_delay*10; case EXTENSION_RECORD_TYPE: if (DGifGetExtension(fp, &ExtCode, &Extension) == GIF_ERROR) { return h->state = -9; } else { while (Extension != NULL) { if (ExtCode == 0xF9 && *Extension >= 4) { transparent_pix = -1; if (Extension[1]&1) { transparent_pix = Extension[4]; } has_delay = ((int)Extension[3] << 8) + Extension[2]; if (has_delay == 1) has_delay = 10; // https://www.biphelps.com/blog/The-Fastest-GIF-Does-Not-Exist } if (DGifGetExtensionNext(fp, &Extension) == GIF_ERROR) return h->state = -8; } } break; case TERMINATE_RECORD_TYPE: break; default: /* Should be traps by DGifGetRecordType. */ break; } } while (RecordType != TERMINATE_RECORD_TYPE); return h->state = -10; }