Files
tlib/oversampling/WDL/lice/lice_gif.cpp
2024-05-24 13:28:31 +02:00

463 lines
11 KiB
C++

/*
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 <stdio.h>
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;
}