Last active
June 12, 2018 13:13
Star
You must be signed in to star a gist
An example of using APNG support in stb_image.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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