Skip to content

Instantly share code, notes, and snippets.

@jcredmond
Last active June 12, 2018 13:13
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save jcredmond/9ef711b406e42a250daa3797ce96fd26 to your computer and use it in GitHub Desktop.
An example of using APNG support in stb_image.h
/*
Tests APNG support in stb_image.h by rendering the APNG via SDL. No animation occurs if the loaded file is a
normal PNG.
*/
#include <assert.h>
#include <stdio.h>
#include <SDL2/SDL.h>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
// Settings
static const int screen_width = 1024;
static const int screen_height = 768;
static const int y_flip = 0; // Set to 1 to flip vertically on load
static const int draw_bounds = 0; // Set to 1 to draw frame bounds
static const int forced_delay_ms = 0; // Set to >0 to ignore frame times and force the specified delay between frames
static const int print_dbginfo = 1; // Set to 1 to print debug information on load
static int actual_screen_width = 0;
static int actual_screen_height = 0;
static const char *dispose_ops[] = {
"STBI_APNG_dispose_op_none",
"STBI_APNG_dispose_op_background",
"STBI_APNG_dispose_op_previous"
};
static const char *blend_ops[] = {
"STBI_APNG_blend_op_source",
"STBI_APNG_blend_op_over"
};
int main(int argc, char **argv)
{
int ec = -1;
FILE *f = NULL;
int width = 0;
int height = 0;
int orig_format = 0;
int req_format = STBI_rgb_alpha;
int depth = 0;
int pitch_coeff = 0;
unsigned char *data = NULL;
size_t dir_offset = 0;
Uint32 rmask = 0x000000ff;
Uint32 gmask = 0x0000ff00;
Uint32 bmask = 0x00ff0000;
Uint32 amask = (req_format == STBI_rgb) ? 0 : 0xff000000;
Uint32 delay_ms = 0;
Uint32 last_render_ms = 0;
unsigned int current_frame = 0;
unsigned int num_plays = 0;
SDL_Window *win = NULL;
SDL_Renderer *renderer = NULL;
SDL_Surface **surfaces = NULL;
SDL_Texture **textures = NULL;
stbi__apng_directory *dir = NULL;
stbi__uint32 num_frames = 0;
stbi__context s;
stbi__uint32 i;
if (argc < 2) {
fprintf(stderr, "must provide image path\n");
goto out;
}
stbi_set_flip_vertically_on_load(y_flip);
if (!(f = stbi__fopen(argv[1], "rb"))) {
fprintf(stderr, "can't fopen\n");
goto out;
}
stbi__start_file(&s, f);
if (!stbi__png_test(&s)) {
fprintf(stderr, "image isn't png\n");
fclose(f); f = NULL;
goto free_image;
}
data = stbi__apng_load_8bit(&s, &width, &height, &orig_format, req_format, &dir_offset);
// If dir_offset is > 0 then the loaded file is an APNG -- if dir_offset is == 0 then the loaded file is a
// normal PNG with no extra information packed into the data buffer.
if (data && dir_offset > 0) {
dir = (stbi__apng_directory *) (data + dir_offset);
// Sanity check that the data actually came from stbi.
if (dir->type != STBI__STRUCTURE_TYPE_APNG_DIRECTORY) {
fprintf(stderr, "invalid apng directory\n");
fclose(f); f = NULL;
goto free_image;
}
if (print_dbginfo) {
fprintf(stderr, "dir_offset : %lu\n", dir_offset);
fprintf(stderr, "dir.type : %.*s\n", 4, (unsigned char *) &dir->type);
fprintf(stderr, "dir.num_frames : %u\n", dir->num_frames);
fprintf(stderr, "dir.default_image_is_first_frame : %s\n",
dir->default_image_is_first_frame ? "yes" : "no");
fprintf(stderr, "dir.num_plays : %u\n", dir->num_plays);
for (i = 0; i < dir->num_frames; ++i) {
stbi__apng_frame_directory_entry *frame = &dir->frames[i];
fprintf(stderr, "frame : %u\n", i);
fprintf(stderr, " width : %u\n", frame->width);
fprintf(stderr, " height : %u\n", frame->height);
fprintf(stderr, " x_offset : %u\n", frame->x_offset);
fprintf(stderr, " y_offset : %u\n", frame->y_offset);
fprintf(stderr, " delay_num : %u\n", frame->delay_num);
fprintf(stderr, " delay_den : %u\n", frame->delay_den);
fprintf(stderr, " dispose_op : %s\n", dispose_ops[frame->dispose_op]);
fprintf(stderr, " blend_op : %s\n", blend_ops[frame->blend_op]);
}
}
// For simplicity, this sample only supports APNG files where the default image is also the first frame.
assert(dir->default_image_is_first_frame == 1);
}
fclose(f); f = NULL;
if (data == NULL) {
fprintf(stderr, "%s\n", stbi_failure_reason());
goto out;
}
if (print_dbginfo) {
fprintf(stderr, "%s: width=%d height=%d orig_format=%d req_format=%d\n",
argv[1], width, height, orig_format, req_format);
}
if (SDL_Init(SDL_INIT_VIDEO) < 0)
goto free_image;
win = SDL_CreateWindow("stb_image APNG test", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
screen_width, screen_height, SDL_WINDOW_ALLOW_HIGHDPI);
if (win == NULL) {
fprintf(stderr, "failed to create window: %s\n", SDL_GetError());
goto free_image;
}
renderer = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED);
if (renderer == NULL) {
fprintf(stderr, "failed to create renderer: %s\n", SDL_GetError());
goto destroy_window;
}
// Get the actual screen resolution (to support HiDPI displays).
SDL_GL_GetDrawableSize(win, &actual_screen_width, &actual_screen_height);
SDL_RenderSetScale(renderer, (float) actual_screen_width / width, (float) actual_screen_height / height);
if (req_format == STBI_rgb) {
depth = 24;
pitch_coeff = 3;
} else {
depth = 32;
pitch_coeff = 4;
}
num_frames = dir ? dir->num_frames : 1;
surfaces = malloc(num_frames * sizeof(surfaces[0]));
if (surfaces == NULL) {
fprintf(stderr, "out of memory\n");
goto destroy_window;
}
if (dir) {
size_t frame_offset = 0;
for (i = 0; i < num_frames; ++i) {
size_t frame_size = dir->frames[i].width * dir->frames[i].height * pitch_coeff;
int pitch = pitch_coeff * dir->frames[i].width;
surfaces[i] = SDL_CreateRGBSurfaceFrom((void *) (data + frame_offset),
dir->frames[i].width, dir->frames[i].height, depth,
pitch, rmask, gmask, bmask, amask);
frame_offset += frame_size;
if (surfaces[i] == NULL) {
fprintf(stderr, "failed to create surface: %s\n", SDL_GetError());
goto free_surfaces;
}
}
} else {
int pitch = pitch_coeff * width;
surfaces[0] = SDL_CreateRGBSurfaceFrom((void *) data, width, height, depth, pitch,
rmask, gmask, bmask, amask);
if (surfaces[0] == NULL) {
fprintf(stderr, "failed to create surface: %s\n", SDL_GetError());
goto free_surfaces;
}
}
textures = malloc(num_frames * sizeof(textures[0]));
if (textures == NULL) {
fprintf(stderr, "out of memory\n");
goto free_surfaces;
}
for (i = 0; i < num_frames; ++i) {
textures[i] = SDL_CreateTextureFromSurface(renderer, surfaces[i]);
if (textures[i] == NULL) {
fprintf(stderr, "failed to create texture: %s\n", SDL_GetError());
goto free_textures;
}
}
while (1) {
stbi__apng_frame_directory_entry *render_frame = dir ? &dir->frames[current_frame] : NULL;
SDL_Event e;
SDL_Rect dstrect;
dstrect.x = 0;
dstrect.y = 0;
dstrect.w = width;
dstrect.h = height;
if (render_frame) {
dstrect.x = render_frame->x_offset;
dstrect.y = render_frame->y_offset;
dstrect.w = render_frame->width;
dstrect.h = render_frame->height;
if (render_frame->delay_den == 0) // delay_num specifies 1/100ths of a second
delay_ms = (Uint32) ((render_frame->delay_num / 100.0f) * 1000.0f);
else // delay_num / delay_den specifies seconds
delay_ms = (Uint32) (((float) render_frame->delay_num / render_frame->delay_den) * 1000.0f);
if (render_frame->delay_num == 0) // Render ASAP
delay_ms = 0;
}
if (forced_delay_ms)
delay_ms = forced_delay_ms;
if (render_frame && last_render_ms != 0 && SDL_GetTicks() - last_render_ms > delay_ms) {
// For simplicity, this sample only implements STBI_APNG_dispose_op_none,
// STBI_APNG_dispose_op_background, and STBI_APNG_blend_op_source.
assert(render_frame->dispose_op == STBI_APNG_dispose_op_none ||
render_frame->dispose_op == STBI_APNG_dispose_op_background);
assert(render_frame->blend_op == STBI_APNG_blend_op_source);
if (render_frame->dispose_op == STBI_APNG_dispose_op_background) {
SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_TRANSPARENT);
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
SDL_RenderFillRect(renderer, &dstrect);
}
current_frame = (current_frame + 1) % num_frames;
render_frame = &dir->frames[current_frame];
dstrect.x = render_frame->x_offset;
dstrect.y = render_frame->y_offset;
dstrect.w = render_frame->width;
dstrect.h = render_frame->height;
last_render_ms = SDL_GetTicks();
if (current_frame == 0)
++num_plays;
// If the frame counter wraps around and the number of plays should be limited, simply set
// dir to NULL to switch off playback and "stick" the first frame.
if (dir->num_plays > 0 && num_plays == dir->num_plays)
dir = NULL;
}
if (SDL_PollEvent(&e)) {
if (e.type == SDL_QUIT)
break;
else if (e.type == SDL_KEYUP && e.key.keysym.sym == SDLK_ESCAPE)
break;
}
SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, textures[current_frame], NULL, &dstrect);
if (draw_bounds) {
SDL_SetRenderDrawColor(renderer, 0, 255, 0, SDL_ALPHA_OPAQUE);
SDL_RenderDrawRect(renderer, &dstrect);
}
SDL_RenderPresent(renderer);
if (last_render_ms == 0)
last_render_ms = SDL_GetTicks();
}
ec = 0;
free_textures:
for (i = 0; i < num_frames; ++i)
SDL_DestroyTexture(textures[i]);
free(textures);
free_surfaces:
for (i = 0; i < num_frames; ++i)
SDL_FreeSurface(surfaces[i]);
free(surfaces);
SDL_DestroyRenderer(renderer);
destroy_window:
SDL_DestroyWindow(win);
free_image:
stbi_image_free(data);
out:
return ec;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment