299 lines
8.5 KiB
C++
299 lines
8.5 KiB
C++
#include "BitmapImage.h"
|
|
|
|
void *BI_def_bitmap_create(int width, int height) {return Allocate(width * height * 4);}
|
|
void BI_def_bitmap_set_opaque(void *bitmap, BOOL opaque) {}
|
|
BOOL BI_def_bitmap_test_opaque(void *bitmap) {return false;}
|
|
unsigned char *BI_def_bitmap_get_buffer(void *bitmap) {return (unsigned char*)bitmap;}
|
|
void BI_def_bitmap_destroy(void *bitmap) {Free(bitmap);}
|
|
void BI_def_bitmap_modified(void *bitmap) {}
|
|
|
|
|
|
BitmapImage::BitmapImage()
|
|
{
|
|
bitmap_callbacks.bitmap_create = BI_def_bitmap_create;
|
|
bitmap_callbacks.bitmap_destroy = BI_def_bitmap_destroy;
|
|
bitmap_callbacks.bitmap_get_buffer = BI_def_bitmap_get_buffer;
|
|
bitmap_callbacks.bitmap_modified = BI_def_bitmap_modified;
|
|
bitmap_callbacks.bitmap_set_opaque = BI_def_bitmap_set_opaque;
|
|
bitmap_callbacks.bitmap_test_opaque = BI_def_bitmap_test_opaque;
|
|
}
|
|
|
|
BitmapImage::~BitmapImage()
|
|
{
|
|
if(bIsAnimatedGif)
|
|
{
|
|
gif_finalise(&gif);
|
|
Free(animationFrameCache);
|
|
Free(animationFrameData);
|
|
}
|
|
|
|
if(lpGifData)
|
|
Free(lpGifData);
|
|
|
|
EnableFileMonitor(false);
|
|
|
|
delete texture;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void BitmapImage::CreateErrorTexture(void)
|
|
{
|
|
LPBYTE textureData = (LPBYTE)Allocate(32*32*4);
|
|
msetd(textureData, 0xFF0000FF, 32*32*4);
|
|
|
|
texture = CreateTexture(32, 32, GS_RGB, textureData, FALSE);
|
|
fullSize.Set(32.0f, 32.0f);
|
|
|
|
Free(textureData);
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
void BitmapImage::SetPath(String path)
|
|
{
|
|
filePath = path;
|
|
}
|
|
|
|
void BitmapImage::EnableFileMonitor(bool bMonitor)
|
|
{
|
|
if (changeMonitor)
|
|
{
|
|
OSMonitorFileDestroy(changeMonitor);
|
|
changeMonitor = NULL;
|
|
}
|
|
|
|
if (bMonitor)
|
|
changeMonitor = OSMonitorFileStart(filePath);
|
|
}
|
|
|
|
void BitmapImage::Init(void)
|
|
{
|
|
if(bIsAnimatedGif)
|
|
{
|
|
bIsAnimatedGif = false;
|
|
gif_finalise(&gif);
|
|
|
|
Free(animationFrameCache);
|
|
animationFrameCache = NULL;
|
|
Free(animationFrameData);
|
|
animationFrameData = NULL;
|
|
}
|
|
|
|
if(lpGifData)
|
|
{
|
|
Free(lpGifData);
|
|
lpGifData = NULL;
|
|
}
|
|
|
|
animationTimes.Clear();
|
|
|
|
delete texture;
|
|
texture = NULL;
|
|
|
|
CTSTR lpBitmap = filePath;
|
|
if(!lpBitmap || !*lpBitmap)
|
|
{
|
|
AppWarning(TEXT("BitmapImage::Init: Empty path"));
|
|
CreateErrorTexture();
|
|
return;
|
|
}
|
|
|
|
//------------------------------------
|
|
|
|
if(GetPathExtension(lpBitmap).CompareI(TEXT("gif")))
|
|
{
|
|
gif_create(&gif, &bitmap_callbacks);
|
|
|
|
XFile gifFile;
|
|
if(!gifFile.Open(lpBitmap, XFILE_READ, XFILE_OPENEXISTING))
|
|
{
|
|
AppWarning(TEXT("BitmapImage::Init: could not open gif file '%s'"), lpBitmap);
|
|
CreateErrorTexture();
|
|
return;
|
|
}
|
|
|
|
|
|
DWORD fileSize = (DWORD)gifFile.GetFileSize();
|
|
lpGifData = (LPBYTE)Allocate(fileSize);
|
|
gifFile.Read(lpGifData, fileSize);
|
|
|
|
gif_result result;
|
|
do
|
|
{
|
|
result = gif_initialise(&gif, fileSize, lpGifData);
|
|
if(result != GIF_OK && result != GIF_WORKING)
|
|
{
|
|
Log(TEXT("BitmapImage: Warning, couldn't initialise gif %s, it is likely corrupt"), lpBitmap);
|
|
CreateErrorTexture();
|
|
return;
|
|
}
|
|
}while(result != GIF_OK);
|
|
|
|
if (gif.width > 4096 || gif.height > 4096)
|
|
{
|
|
Log(TEXT("BitmapImage: Warning, bad texture dimensions %d x %d in %s"), gif.width, gif.height, lpBitmap);
|
|
CreateErrorTexture();
|
|
return;
|
|
}
|
|
|
|
unsigned long long max_size = (unsigned long long)gif.width * (unsigned long long)gif.height * 4LLU * (unsigned long long)gif.frame_count;
|
|
if (gif.width * gif.height * 4 * gif.frame_count != max_size)
|
|
{
|
|
Log(TEXT("BitmapImage: Warning, gif %s overflowed maximum pointer size and was not loaded (%llu > %u)"), lpBitmap, max_size, gif.width * gif.height * 4 * gif.frame_count);
|
|
CreateErrorTexture();
|
|
return;
|
|
}
|
|
|
|
if(gif.frame_count > 1)
|
|
{
|
|
if(result == GIF_OK || result == GIF_WORKING)
|
|
bIsAnimatedGif = true;
|
|
}
|
|
|
|
if(bIsAnimatedGif)
|
|
{
|
|
gif_decode_frame(&gif, 0);
|
|
texture = CreateTexture(gif.width, gif.height, GS_RGBA, gif.frame_image, FALSE, FALSE);
|
|
|
|
animationFrameCache = (BYTE **)Allocate(gif.frame_count * sizeof(BYTE *));
|
|
memset(animationFrameCache, 0, gif.frame_count * sizeof(BYTE *));
|
|
|
|
animationFrameData = (BYTE *)Allocate(gif.frame_count * gif.width * gif.height * 4);
|
|
memset(animationFrameData, 0, gif.frame_count * gif.width * gif.height * 4);
|
|
|
|
for(UINT i=0; i<gif.frame_count; i++)
|
|
{
|
|
float frameTime = float(gif.frames[i].frame_delay)*0.01f;
|
|
if (frameTime == 0.0f)
|
|
frameTime = 0.1f;
|
|
animationTimes << frameTime;
|
|
|
|
if (gif_decode_frame(&gif, i) != GIF_OK)
|
|
Log (TEXT("BitmapImage: Warning, couldn't decode frame %d of %s"), i, lpBitmap);
|
|
}
|
|
|
|
gif_decode_frame(&gif, 0);
|
|
|
|
fullSize.x = float(gif.width);
|
|
fullSize.y = float(gif.height);
|
|
|
|
curTime = 0.0f;
|
|
curFrame = 0;
|
|
lastDecodedFrame = 0;
|
|
}
|
|
else
|
|
{
|
|
gif_finalise(&gif);
|
|
Free(lpGifData);
|
|
lpGifData = NULL;
|
|
}
|
|
}
|
|
|
|
if(!bIsAnimatedGif)
|
|
{
|
|
texture = GS->CreateTextureFromFile(lpBitmap, TRUE);
|
|
if(!texture)
|
|
{
|
|
AppWarning(TEXT("BitmapImage::Init: could not create texture '%s'"), lpBitmap);
|
|
CreateErrorTexture();
|
|
return;
|
|
}
|
|
|
|
fullSize.x = float(texture->Width());
|
|
fullSize.y = float(texture->Height());
|
|
}
|
|
|
|
}
|
|
|
|
Vect2 BitmapImage::GetSize(void) const
|
|
{
|
|
return fullSize;
|
|
}
|
|
|
|
Texture* BitmapImage::GetTexture(void) const
|
|
{
|
|
return texture;
|
|
}
|
|
|
|
void BitmapImage::Tick(float fSeconds)
|
|
{
|
|
if(bIsAnimatedGif)
|
|
{
|
|
UINT totalLoops = (UINT)gif.loop_count;
|
|
if(totalLoops >= 0xFFFF)
|
|
totalLoops = 0;
|
|
|
|
if(!totalLoops || curLoop < totalLoops)
|
|
{
|
|
UINT newFrame = curFrame;
|
|
|
|
curTime += fSeconds;
|
|
while(curTime > animationTimes[newFrame])
|
|
{
|
|
curTime -= animationTimes[newFrame];
|
|
if(++newFrame == animationTimes.Num())
|
|
{
|
|
if(!totalLoops || ++curLoop < totalLoops)
|
|
newFrame = 0;
|
|
else if (curLoop == totalLoops)
|
|
{
|
|
newFrame--;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(newFrame != curFrame)
|
|
{
|
|
UINT lastFrame;
|
|
|
|
if (!animationFrameCache[newFrame])
|
|
{
|
|
//animation might have looped, if so make sure we decode from frame 0
|
|
if (newFrame < lastDecodedFrame)
|
|
lastFrame = 0;
|
|
else
|
|
lastFrame = lastDecodedFrame + 1;
|
|
|
|
//we need to decode any frames we missed for consistency
|
|
for (UINT i = lastFrame; i < newFrame; i++)
|
|
{
|
|
if (gif_decode_frame(&gif, i) != GIF_OK)
|
|
return;
|
|
}
|
|
|
|
//now decode and display the actual frame we want
|
|
int ret = gif_decode_frame(&gif, newFrame);
|
|
if (ret == GIF_OK)
|
|
{
|
|
animationFrameCache[newFrame] = animationFrameData + (newFrame * (gif.width * gif.height * 4));
|
|
memcpy(animationFrameCache[newFrame], gif.frame_image, gif.width * gif.height * 4);
|
|
}
|
|
|
|
lastDecodedFrame = newFrame;
|
|
}
|
|
|
|
if (animationFrameCache[newFrame])
|
|
texture->SetImage(animationFrameCache[newFrame], GS_IMAGEFORMAT_RGBA, gif.width*4);
|
|
|
|
curFrame = newFrame;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (updateImageTime)
|
|
{
|
|
updateImageTime -= fSeconds;
|
|
if (updateImageTime <= 0.0f)
|
|
{
|
|
updateImageTime = 0.0f;
|
|
Init();
|
|
}
|
|
}
|
|
|
|
if (changeMonitor && OSFileHasChanged(changeMonitor))
|
|
updateImageTime = 1.0f;
|
|
}
|