Decode GIF frames within Bitmap constructor

This commit is contained in:
Struma 2021-05-02 23:05:36 -04:00 committed by Roza
parent 380bd1d249
commit 5c2d0ac167

View file

@ -373,21 +373,25 @@ struct BitmapPrivate
struct BitmapOpenHandler : FileSystem::OpenHandler struct BitmapOpenHandler : FileSystem::OpenHandler
{ {
std::vector<SDL_Surface*> surfaces; // Non-GIF
float animation_rate; SDL_Surface *surface;
// Filled if errors from GIF reading are needed // GIF
std::string error; std::string error;
gif_animation *gif;
unsigned char *gif_data;
size_t gif_data_size;
BitmapOpenHandler() BitmapOpenHandler()
: animation_rate(-1) : surface(0), gif(0), gif_data(0), gif_data_size(0)
{} {}
bool tryRead(SDL_RWops &ops, const char *ext) bool tryRead(SDL_RWops &ops, const char *ext)
{ {
if (IMG_isGIF(&ops)) { if (IMG_isGIF(&ops)) {
// Use libnsgif to initialise the gif data // Use libnsgif to initialise the gif data
gif_animation gif {}; gif = new gif_animation;
gif_bitmap_callback_vt gif_bitmap_callbacks = { gif_bitmap_callback_vt gif_bitmap_callbacks = {
gif_bitmap_create, gif_bitmap_create,
@ -398,30 +402,38 @@ struct BitmapOpenHandler : FileSystem::OpenHandler
gif_bitmap_modified gif_bitmap_modified
}; };
gif_create(&gif, &gif_bitmap_callbacks); gif_create(gif, &gif_bitmap_callbacks);
size_t data_size = ops.size(&ops); gif_data_size = ops.size(&ops);
auto data = new unsigned char[data_size]; gif_data = new unsigned char[gif_data_size];
ops.seek(&ops, 0, RW_SEEK_SET); ops.seek(&ops, 0, RW_SEEK_SET);
ops.read(&ops, data, data_size, 1); ops.read(&ops, gif_data, gif_data_size, 1);
int status; int status;
do { do {
status = gif_initialise(&gif, data_size, data); status = gif_initialise(gif, gif_data_size, gif_data);
if (status != GIF_OK && status != GIF_WORKING) { if (status != GIF_OK && status != GIF_WORKING) {
gif_finalise(&gif); gif_finalise(gif);
delete data; delete gif;
delete gif_data;
error = "Failed to initialize GIF (Error " + std::to_string(status) + ")"; error = "Failed to initialize GIF (Error " + std::to_string(status) + ")";
return false; return false;
} }
} while (status != GIF_OK); } while (status != GIF_OK);
int image_width = -1; // Decode the first frame
int image_height = -1; status = gif_decode_frame(gif, 0);
if (status != GIF_OK && status != GIF_WORKING) {
error = "Failed to decode first GIF frame. (Error " + std::to_string(status) + ")";
gif_finalise(gif);
delete gif;
delete gif_data;
return false;
}
/*
// Read every frame // Read every frame
for (int i = 0; i < gif.frame_count; i++) { for (int i = 0; i < gif->frame_count; i++) {
int status = gif_decode_frame(&gif, i); int status = gif_decode_frame(&gif, i);
if (status != GIF_OK && status != GIF_WORKING) { if (status != GIF_OK && status != GIF_WORKING) {
error = "Failed to read GIF frame " + std::to_string(i + 1) + " (Error " + std::to_string(status) + ")"; error = "Failed to read GIF frame " + std::to_string(i + 1) + " (Error " + std::to_string(status) + ")";
@ -452,10 +464,11 @@ struct BitmapOpenHandler : FileSystem::OpenHandler
gif_finalise(&gif); gif_finalise(&gif);
delete data; delete data;
*/
} else { } else {
surfaces.push_back(IMG_LoadTyped_RW(&ops, 1, ext)); surface = IMG_LoadTyped_RW(&ops, 1, ext);
} }
return (surfaces.size() > 0 && error.empty()); return (surface || gif);
} }
}; };
@ -468,16 +481,16 @@ Bitmap::Bitmap(const char *filename)
// Not loaded with SDL, but I want it to be caught with the same exception type // Not loaded with SDL, but I want it to be caught with the same exception type
throw Exception(Exception::SDLError, "Error loading image '%s': %s", filename, handler.error.c_str()); throw Exception(Exception::SDLError, "Error loading image '%s': %s", filename, handler.error.c_str());
} }
else if (handler.surfaces.size() < 1) { else if (!handler.gif && !handler.surface) {
throw Exception(Exception::SDLError, "Error loading image '%s': %s", throw Exception(Exception::SDLError, "Error loading image '%s': %s",
filename, SDL_GetError()); filename, SDL_GetError());
} }
if (handler.surfaces.size() > 1) { if (handler.gif) {
p = new BitmapPrivate(this); p = new BitmapPrivate(this);
p->animation.enabled = true; p->animation.enabled = true;
p->animation.width = handler.surfaces[0]->w; p->animation.width = handler.gif->width;
p->animation.height = handler.surfaces[0]->h; p->animation.height = handler.gif->height;
if (p->animation.width >= glState.caps.maxTexSize || p->animation.height > glState.caps.maxTexSize) if (p->animation.width >= glState.caps.maxTexSize || p->animation.height > glState.caps.maxTexSize)
{ {
@ -485,35 +498,56 @@ Bitmap::Bitmap(const char *filename)
p->animation.width, p->animation.height, glState.caps.maxTexSize, glState.caps.maxTexSize); p->animation.width, p->animation.height, glState.caps.maxTexSize, glState.caps.maxTexSize);
} }
p->animation.fps = (handler.animation_rate == -1) ? shState->graphics().getFrameRate() : handler.animation_rate; // Guess framerate based on the first frame's delay
p->animation.fps = 1 / ((float)handler.gif->frames[handler.gif->decoded_frame].frame_delay / 100);
if (p->animation.fps < 0) p->animation.fps = shState->graphics().getFrameRate();
for (SDL_Surface* s : handler.surfaces) // Loop gif (Either it's looping or it's not, at the moment)
{ p->animation.loop = handler.gif->loop_count >= 0;
while (handler.gif->decoded_frame < handler.gif->frame_count - 1) {
TEXFBO texfbo; TEXFBO texfbo;
try { try {
texfbo = shState->texPool().request(p->animation.width, p->animation.height); texfbo = shState->texPool().request(p->animation.width, p->animation.height);
} }
catch (const Exception &e) catch (const Exception &e)
{ {
for (SDL_Surface *s : handler.surfaces) for (TEXFBO &frame : p->animation.frames)
SDL_FreeSurface(s); shState->texPool().release(frame);
gif_finalise(handler.gif);
delete handler.gif;
delete handler.gif_data;
throw e; throw e;
} }
TEX::bind(texfbo.tex);
TEX::uploadImage(p->animation.width, p->animation.height, s->pixels, GL_RGBA);
TEX::bind(texfbo.tex);
TEX::uploadImage(p->animation.width, p->animation.height, handler.gif->frame_image, GL_RGBA);
p->animation.frames.push_back(texfbo); p->animation.frames.push_back(texfbo);
int status = gif_decode_frame(handler.gif, handler.gif->decoded_frame + 1);
if (status != GIF_OK && status != GIF_WORKING) {
for (TEXFBO &frame : p->animation.frames)
shState->texPool().release(frame);
gif_finalise(handler.gif);
delete handler.gif;
delete handler.gif_data;
throw Exception(Exception::MKXPError, "Failed to decode frame GIF frame %i out of %i (Error %i)",
handler.gif->decoded_frame, handler.gif->frame_count, status);
}
} }
for (SDL_Surface *s : handler.surfaces) gif_finalise(handler.gif);
SDL_FreeSurface(s); delete handler.gif;
delete handler.gif_data;
p->addTaintedArea(rect()); p->addTaintedArea(rect());
return; return;
} }
SDL_Surface *imgSurf = handler.surfaces[0]; SDL_Surface *imgSurf = handler.surface;
p->ensureFormat(imgSurf, SDL_PIXELFORMAT_ABGR8888); p->ensureFormat(imgSurf, SDL_PIXELFORMAT_ABGR8888);