-
-
Save urraka/685d9a6340b26b830d49 to your computer and use it in GitHub Desktop.
#define STB_IMAGE_IMPLEMENTATION | |
#define STB_IMAGE_WRITE_IMPLEMENTATION | |
#define STBI_ONLY_PNG | |
#define STBI_ONLY_JPEG | |
#define STBI_ONLY_BMP | |
#define STBI_ONLY_GIF | |
#include "stb_image.h" | |
#include "stb_image_write.h" | |
#ifdef __cplusplus | |
extern "C" { | |
#endif | |
typedef struct gif_result_t { | |
int delay; | |
unsigned char *data; | |
struct gif_result_t *next; | |
} gif_result; | |
STBIDEF unsigned char *stbi_xload(char const *filename, int *x, int *y, int *frames) | |
{ | |
FILE *f; | |
stbi__context s; | |
unsigned char *result = 0; | |
if (!(f = stbi__fopen(filename, "rb"))) | |
return stbi__errpuc("can't fopen", "Unable to open file"); | |
stbi__start_file(&s, f); | |
if (stbi__gif_test(&s)) | |
{ | |
int c; | |
stbi__gif g; | |
gif_result head; | |
gif_result *prev = 0, *gr = &head; | |
memset(&g, 0, sizeof(g)); | |
memset(&head, 0, sizeof(head)); | |
*frames = 0; | |
while (gr->data = stbi__gif_load_next(&s, &g, &c, 4)) | |
{ | |
if (gr->data == (unsigned char*)&s) | |
{ | |
gr->data = 0; | |
break; | |
} | |
if (prev) prev->next = gr; | |
gr->delay = g.delay; | |
prev = gr; | |
gr = (gif_result*) stbi__malloc(sizeof(gif_result)); | |
memset(gr, 0, sizeof(gif_result)); | |
++(*frames); | |
} | |
STBI_FREE(g.out); | |
if (gr != &head) | |
STBI_FREE(gr); | |
if (*frames > 0) | |
{ | |
*x = g.w; | |
*y = g.h; | |
} | |
result = head.data; | |
if (*frames > 1) | |
{ | |
unsigned int size = 4 * g.w * g.h; | |
unsigned char *p = 0; | |
result = (unsigned char*)stbi__malloc(*frames * (size + 2)); | |
gr = &head; | |
p = result; | |
while (gr) | |
{ | |
prev = gr; | |
memcpy(p, gr->data, size); | |
p += size; | |
*p++ = gr->delay & 0xFF; | |
*p++ = (gr->delay & 0xFF00) >> 8; | |
gr = gr->next; | |
STBI_FREE(prev->data); | |
if (prev != &head) STBI_FREE(prev); | |
} | |
} | |
} | |
else | |
{ | |
result = stbi__load_main(&s, x, y, frames, 4); | |
*frames = !!result; | |
} | |
fclose(f); | |
return result; | |
} | |
#ifdef __cplusplus | |
} | |
#endif |
I'm sorry I didn't reply earlier.
Sorry from me too (same reason).
First, I'm going to assume that where you have gr->out you actually mean gr->data (I'm guessing you renamed it and it's just a typo in the post).
Actually now gr is an instance of gif_result, that derives from:
typedef struct
{
int w,h;
stbi_uc *out, *old_out; // output buffer (always 4 components)
int flags, bgindex, ratio, transparent, eflags, delay;
stbi_uc pal[256][4];
stbi_uc lpal[256][4];
stbi__gif_lzw codes[4096];
stbi_uc *color_table;
int parse, step;
int lflags;
int start_x, start_y;
int max_x, max_y;
int cur_x, cur_y;
int line_size;
} stbi__gif;
So there is gr->out (I'm using stb_image.h version 2.10).
That told, I'm aware that my code might have problems... some of your observations seem correct to me.
The problem is just that with the code you posted I have memory leaks, and I did not find another way to get rid of them :(.
Woops, I totally missed the inheritance you had there...
Anyway, I don't remember exactly how stbi__gif_load_next
works but I looked a bit into it now and it seems there is a leak indeed. As far as I can see all you need is that STBI_FREE(gr->out)
you added after the while loop. Freeing inside the loop still seems incorrect because gr->out
and gr->data
point at the same image data (and that's used later on). Also, in some cases stbi__gif_load_next
will need the data from the previous frame.
I think you can remove the gif_result : stbi__gif
inheritance and just call STBI_FREE(s.out)
after the while loop.
Freeing inside the loop still seems incorrect because gr->out and gr->data point at the same image data (and that's used later on). Also, in some cases stbi__gif_load_next will need the data from the previous frame.
I had the same impression... but in the (2 or 3) .gifs I've tried so far, Valgrind still gives me a memory leak if I comment out the STBI_FREE(gr->out)
inside the loop. However I think that, as you say, there might be cases in which this approach would fail... I don't know how to find a robust solution to this (and actually I don't know why my code seems to work, if I free gr->out that points to gr->data: but Valgrind should tell me if I free memory twice... and it seems to like my code [so far]...).
I think you can remove the
gif_result : stbi__gif
inheritance and just callSTBI_FREE(s.out)
after the while loop.
If s
is the stbi__context
, in my version of stb_image, it has a lot of buffers, but not s.out:
typedef struct
{
stbi__uint32 img_x, img_y;
int img_n, img_out_n;
stbi_io_callbacks io;
void *io_user_data;
int read_from_callbacks;
int buflen;
stbi_uc buffer_start[128];
stbi_uc *img_buffer, *img_buffer_end;
stbi_uc *img_buffer_original, *img_buffer_original_end;
} stbi__context;
So I don't know which one to try...
I'm sorry: on a closer look the memory leaks seem to be still present even with my (horrible and incorrect) 'fix'.
So I don't have any valid solution yet.
Sorry, STBI_FREE(s.out)
is wrong. Should be STBI_FREE(g.out)
instead. I'll take another look to see if I find why it's still leaking. Maybe I'll do some actual testing too instead of just trying to guess what's wrong.
I tested with some sample gifs. Adding STBI_FREE(g.out)
after the while loop seems to fix the leaks here. Make sure you call stbi_image_free
on the result of stbi_xload
at some point too.
I tested with some sample gifs. Adding STBI_FREE(g.out) after the while loop seems to fix the leaks here.
Yes! This worked for me too (tested with 2 different gifs: no more leaks!)
Thanks.
P.S. You should modify your code to reflect the fix.
In any case, for clarity, from Revision 1 of this gist, users should just add STBI_FREE(g.out);
after the loop (before: if (gr != &head)
).
Cool. It's updated now.
This code is outdated. Multiple functions of stbi_image have been changed so this code doesn't work anymore:
In line 44: stbi__gif_load_next(&s, &g, &c, 4)
New definition: static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back)
In line 98: result = stbi__load_main(&s, x, y, frames, 4);
New definition: static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc)
Is there any way to make it work with the current functions? What has changed since 2016? There is still a link to this piece of code as a GIF loader solution.
Hey @Gtoyos, I suggest that you look into stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp)
(https://github.com/nothings/stb/blob/f67165c2bb2af3060ecae7d20d6f731173485ad0/stb_image.h#L1300). It looks like it's been added somewhere along the way and it returns a buffer with all frames and delays in a separate argument.
Note: for an updated version check this version: https://github.com/opensoldat/opensoldat/blob/develop/client/libs/stb/src/stb.c
Hi @urraka, Do u have any tutorials or advice on how to use the updated version? I'm currently stuck on how to access/load the other frames of the gif. Also do you know how to allow stbi_set_flip_vertically_on_load to work on the gifs?
Hello @Platforming-Mayhem.
It goes something like this:
// INPUT:
// unsigned char *buffer;
// int bufferSize;
int width = 0;
int height = 0;
int nFrames = 0;
int *delays = NULL;
unsigned char *data = stbi_xload_mem(buffer, bufferSize, &width, &height, &nFrames, &delays);
unsigned char *get_frame_image(int frame) {
return data + (width * height * 4 * frame);
}
int get_frame_delay(int frame) {
return delays[frame] / 10; // don't remember what the /10 was all about, probably just time unit conversion
}
Here's some reference code using it (PASCAL)
Regarding the stbi_set_flip_vertically_on_load
I wouldn't know. It's been a while since I used this stuff. You could try making that call before the loading happens and see if it makes a difference.
YESSS!!! I WORKED.
@urraka Thank you very much :)
@urraka Sorry to bother you, but do u have any idea as to why this is happening when it loads the next frame?
I also get this exception thrown when loading the next frame on majority of my gifs
Some of them don't get this issue, but those are the ones where the gif is created in a specific program, I think it was Adobe Animate CC.
Animated Gifs created in Krita don't work, Animated Gifs created in Photoshop also don't work.
Nevermind this was my mistake.
Hey @Platforming-Mayhem, looks like you got it working, good to know!
By the way, that last line you're highlighting looks suspicious to me. Isn't stbi_set_flip_vertically_on_load
a function? My C/C++ is rusty but I think that might be interpreting it as a pointer and just testing that it's not zero, so stbi__vertical_flip_slices
is always called.
@urraka I'm not sure, since I just copied it from one of the stb_load functions
@Platforming-Mayhem nevermind what I said, my bad. The function is stbi_set_flip_vertically_on_load
but you have stbi__vertically_flip_on_load
so it's all good ;D
Hi @urraka,
I've been using this code for a while now but recently I found out that it takes up a lot of memory. Is this normal and is there anyway to decrease the amount of memory that is used?
Thanks :)
Hi @Platforming-Mayhem. The way this works is the easy way out: load every frame into memory in RGBA format. If you have big images with many frames it will take a lot of memory (width * height * 4 * number of frames bytes). I suppose, in theory, one could load the compressed file into memory and buffer a few frames at a time (sort of like video preloading)... but that would need a lot more work.
Thanks :)
This was a big help :D
@Flix01
I'm sorry I didn't reply earlier. GitHub didn't notify me of this and I just found your comment randomly. I won't claim that my code is free from memory leaks, but I see some issues with your changes.
First, I'm going to assume that where you have
gr->out
you actually meangr->data
(I'm guessing you renamed it and it's just a typo in the post).Regarding your first "New!" line: You are freeing the image data as soon as you retrieve it. How could you possibly return any (valid) image data that way? Also note that after that line I'm comparing
gr->data
with&s
. That is done becausestbi__gif_load_next
will return a pointer to thestbi__context
when it finds some kind of terminating chunk from the gif format. You're not supposed to free that pointer because it's allocated in the stack at the beggining ofstbi_xload
.Second "New!" line: There are two conditions under which the
while
loop will end. One is whengr->data
is0
(so no need to free it) and another is whengr->data
equals&s
(no need to free it either, and it assigns0
to it before breaking anyway). As far as I can see this line is harmless (free
will accept null pointers just fine, if I recall correctly) but it's also useless (the data is alwaysnull
).Note that you are supposed to call
stbi_image_free
on whatever this function returns. Also, when the gif file has only one frame this function will return the result ofstbi__gif_load_next
directly, so that data shouldn't be freed bystbi_xload
in that case.