aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/backend_freeimage.c114
-rw-r--r--src/imv.c98
-rw-r--r--src/source.h13
3 files changed, 169 insertions, 56 deletions
diff --git a/src/backend_freeimage.c b/src/backend_freeimage.c
index 65a768e..596d450 100644
--- a/src/backend_freeimage.c
+++ b/src/backend_freeimage.c
@@ -12,21 +12,8 @@ struct private {
FREE_IMAGE_FORMAT format;
FIMULTIBITMAP *multibitmap;
FIBITMAP *last_frame;
- int raw_frame_time;
};
-static void time_passed(struct imv_source *src, double dt)
-{
- (void)src;
- (void)dt;
-}
-
-static double time_left(struct imv_source *src)
-{
- (void)src;
- return 0.0;
-}
-
static void source_free(struct imv_source *src)
{
free(src->name);
@@ -78,7 +65,7 @@ static void report_error(struct imv_source *src)
src->callback(&msg);
}
-static void send_bitmap(struct imv_source *src, FIBITMAP *fibitmap)
+static void send_bitmap(struct imv_source *src, FIBITMAP *fibitmap, int frametime)
{
if (!src->callback) {
fprintf(stderr, "imv_source(%s) has no callback configured. "
@@ -90,6 +77,7 @@ static void send_bitmap(struct imv_source *src, FIBITMAP *fibitmap)
msg.source = src;
msg.user_data = src->user_data;
msg.bitmap = to_imv_bitmap(fibitmap);
+ msg.frametime = frametime;
msg.error = NULL;
src->callback(&msg);
@@ -101,6 +89,8 @@ static void first_frame(struct imv_source *src)
struct private *private = src->private;
+ int frametime = 0;
+
if (private->format == FIF_GIF) {
private->multibitmap = FreeImage_OpenMultiBitmap(FIF_GIF, src->name,
/* don't create file */ 0,
@@ -120,9 +110,9 @@ static void first_frame(struct imv_source *src)
FITAG *tag = NULL;
FreeImage_GetMetadata(FIMD_ANIMATION, frame, "FrameTime", &tag);
if(FreeImage_GetTagValue(tag)) {
- private->raw_frame_time = *(int*)FreeImage_GetTagValue(tag);
+ frametime = *(int*)FreeImage_GetTagValue(tag);
} else {
- private->raw_frame_time = 100; /* default value for gifs */
+ frametime = 100; /* default value for gifs */
}
bmp = FreeImage_ConvertTo24Bits(frame);
FreeImage_UnlockPage(private->multibitmap, frame, 0);
@@ -140,8 +130,8 @@ static void first_frame(struct imv_source *src)
}
src->width = FreeImage_GetWidth(bmp);
- src->height = FreeImage_GetWidth(bmp);
- send_bitmap(src, bmp);
+ src->height = FreeImage_GetHeight(bmp);
+ send_bitmap(src, bmp, frametime);
private->last_frame = bmp;
}
@@ -149,11 +139,93 @@ static void next_frame(struct imv_source *src)
{
struct private *private = src->private;
if (src->num_frames == 1) {
- send_bitmap(src, private->last_frame);
+ send_bitmap(src, private->last_frame, 0);
return;
}
- // TODO gifs
+ FITAG *tag = NULL;
+ char disposal_method = 0;
+ int frametime = 0;
+ short top = 0;
+ short left = 0;
+
+ FIBITMAP *frame = FreeImage_LockPage(private->multibitmap, src->next_frame);
+ FIBITMAP *frame32 = FreeImage_ConvertTo32Bits(frame);
+
+ /* First frame is always going to use the raw frame */
+ if(src->next_frame > 0) {
+ FreeImage_GetMetadata(FIMD_ANIMATION, frame, "DisposalMethod", &tag);
+ if(FreeImage_GetTagValue(tag)) {
+ disposal_method = *(char*)FreeImage_GetTagValue(tag);
+ }
+ }
+
+ FreeImage_GetMetadata(FIMD_ANIMATION, frame, "FrameLeft", &tag);
+ if(FreeImage_GetTagValue(tag)) {
+ left = *(short*)FreeImage_GetTagValue(tag);
+ }
+
+ FreeImage_GetMetadata(FIMD_ANIMATION, frame, "FrameTop", &tag);
+ if(FreeImage_GetTagValue(tag)) {
+ top = *(short*)FreeImage_GetTagValue(tag);
+ }
+
+ FreeImage_GetMetadata(FIMD_ANIMATION, frame, "FrameTime", &tag);
+ if(FreeImage_GetTagValue(tag)) {
+ frametime = *(int*)FreeImage_GetTagValue(tag);
+ }
+
+ /* some gifs don't provide a frame time at all */
+ if(frametime == 0) {
+ frametime = 100;
+ }
+
+ FreeImage_UnlockPage(private->multibitmap, frame, 0);
+
+ /* If this frame is inset, we need to expand it for compositing */
+ if(src->width != (int)FreeImage_GetWidth(frame32) ||
+ src->height != (int)FreeImage_GetHeight(frame32)) {
+ FIBITMAP *expanded = FreeImage_Allocate(src->width, src->height, 32, 0,0,0);
+ FreeImage_Paste(expanded, frame32, left, top, 255);
+ FreeImage_Unload(frame32);
+ frame32 = expanded;
+ }
+
+ switch(disposal_method) {
+ case 0: /* nothing specified, fall through to compositing */
+ case 1: /* composite over previous frame */
+ if(private->last_frame && src->next_frame > 0) {
+ FIBITMAP *bg_frame = FreeImage_ConvertTo24Bits(private->last_frame);
+ FreeImage_Unload(private->last_frame);
+ FIBITMAP *comp = FreeImage_Composite(frame32, 1, NULL, bg_frame);
+ FreeImage_Unload(bg_frame);
+ FreeImage_Unload(frame32);
+ private->last_frame = comp;
+ } else {
+ /* No previous frame, just render directly */
+ if(private->last_frame) {
+ FreeImage_Unload(private->last_frame);
+ }
+ private->last_frame = frame32;
+ }
+ break;
+ case 2: /* TODO - set to background, composite over that */
+ if(private->last_frame) {
+ FreeImage_Unload(private->last_frame);
+ }
+ private->last_frame = frame32;
+ break;
+ case 3: /* TODO - restore to previous content */
+ if(private->last_frame) {
+ FreeImage_Unload(private->last_frame);
+ }
+ private->last_frame = frame32;
+ break;
+ }
+
+ src->next_frame = (src->next_frame + 1) % src->num_frames;
+
+ send_bitmap(src, private->last_frame, frametime);
}
static enum backend_result open_path(const char *path, struct imv_source **src)
@@ -176,8 +248,6 @@ static enum backend_result open_path(const char *path, struct imv_source **src)
source->error_event_id = 0;
source->load_first_frame = &first_frame;
source->load_next_frame = &next_frame;
- source->time_passed = &time_passed;
- source->time_left = &time_left;
source->free = &source_free;
source->callback = NULL;
source->user_data = NULL;
diff --git a/src/imv.c b/src/imv.c
index a90082e..2273689 100644
--- a/src/imv.c
+++ b/src/imv.c
@@ -74,6 +74,9 @@ struct imv {
struct { unsigned char r, g, b; } background_color;
unsigned long slideshow_image_duration;
unsigned long slideshow_time_elapsed;
+ unsigned int next_frame_due;
+ int next_frame_duration;
+ struct imv_bitmap *next_frame;
char *font_name;
struct imv_binds *binds;
struct imv_navigator *navigator;
@@ -237,12 +240,14 @@ static void source_callback(struct imv_source_message *msg)
if (msg->bitmap) {
event.type = imv->events.NEW_IMAGE;
event.user.data1 = msg->bitmap;
+ event.user.code = msg->frametime;
/* Keep track of the last source to send us a bitmap in order to detect
* when we're getting a new image, as opposed to a new frame from the
* same image.
*/
- event.user.code = msg->source != imv->last_source;
+ uintptr_t is_new_image = msg->source != imv->last_source;
+ event.user.data2 = (void*)is_new_image;
imv->last_source = msg->source;
} else {
event.type = imv->events.BAD_IMAGE;
@@ -273,6 +278,9 @@ struct imv *imv_create(void)
imv->background_color.r = imv->background_color.g = imv->background_color.b = 0;
imv->slideshow_image_duration = 0;
imv->slideshow_time_elapsed = 0;
+ imv->next_frame_due = 0;
+ imv->next_frame_duration = 0;
+ imv->next_frame = NULL;
imv->font_name = strdup("Monospace:24");
imv->binds = imv_binds_create();
imv->navigator = imv_navigator_create();
@@ -366,6 +374,9 @@ void imv_free(struct imv *imv)
imv_commands_free(imv->commands);
imv_viewport_free(imv->view);
imv_image_free(imv->image);
+ if (imv->next_frame) {
+ imv_bitmap_free(imv->next_frame);
+ }
if(imv->stdin_image_data) {
free(imv->stdin_image_data);
}
@@ -688,6 +699,9 @@ int imv_run(struct imv *imv)
imv->source->callback = &source_callback;
imv->source->user_data = imv;
imv->source->load_first_frame(imv->source);
+ } else {
+ /* Error loading path so remove it from the navigator */
+ imv_navigator_remove(imv->navigator, current_path);
}
// TODO stdin
@@ -718,21 +732,24 @@ int imv_run(struct imv *imv)
}
}
- /* tell the source time has passed (for gifs) */
current_time = SDL_GetTicks();
- /* if we're playing an animated gif, tell the source how much time has
- * passed */
- if(imv_viewport_is_playing(imv->view)) {
- unsigned int dt = current_time - last_time;
- /* We cap the delta-time to 100 ms so that if imv is asleep for several
- * seconds or more (e.g. suspended), upon waking up it doesn't try to
- * catch up all the time it missed by playing through the gif quickly. */
- if(dt > 100) {
- dt = 100;
- }
+ /* Check if a new frame is due */
+ if (imv_viewport_is_playing(imv->view) && imv->next_frame
+ && imv->next_frame_due && imv->next_frame_due <= current_time) {
+ imv_image_set_bitmap(imv->image, imv->next_frame);
+ imv->current_image.width = imv->next_frame->width;
+ imv->current_image.height = imv->next_frame->height;
+ imv_bitmap_free(imv->next_frame);
+ imv->next_frame = NULL;
+ imv->next_frame_due = current_time + imv->next_frame_duration;
+ imv->next_frame_duration = 0;
+
+ imv->need_redraw = true;
+
+ /* Trigger loading of a new frame, now this one's being displayed */
if (imv->source) {
- imv->source->time_passed(imv->source, dt / 1000.0);
+ imv->source->load_next_frame(imv->source);
}
}
@@ -761,14 +778,13 @@ int imv_run(struct imv *imv)
}
/* sleep until we have something to do */
- unsigned int timeout = 1000; /* sleep up to a second */
+ unsigned int timeout = 1000; /* milliseconds */
/* if we need to display the next frame of an animation soon we should
* limit our sleep until the next frame is due */
- const double next_frame_in = imv->source ?
- imv->source->time_left(imv->source) : 0.0;
- if(next_frame_in > 0.0) {
- timeout = (unsigned int)(next_frame_in * 1000.0);
+ if (imv_viewport_is_playing(imv->view)
+ && imv->next_frame_due > current_time) {
+ timeout = imv->next_frame_due - current_time;
}
/* go to sleep until an input event, etc. or the timeout expires */
@@ -852,20 +868,47 @@ static bool setup_window(struct imv *imv)
return true;
}
+
+static void handle_new_image(struct imv *imv, struct imv_bitmap *bitmap, int frametime)
+{
+ imv_image_set_bitmap(imv->image, bitmap);
+ imv->current_image.width = bitmap->width;
+ imv->current_image.height = bitmap->height;
+ imv_bitmap_free(bitmap);
+ imv->need_redraw = true;
+ imv->need_rescale = true;
+ imv->loading = false;
+ imv->next_frame_due = frametime ? SDL_GetTicks() + frametime : 0;
+ imv->next_frame_duration = 0;
+
+ /* If this is an animated image, we should kick off loading the next frame */
+ if (imv->source && frametime) {
+ imv->source->load_next_frame(imv->source);
+ }
+}
+
+static void handle_new_frame(struct imv *imv, struct imv_bitmap *bitmap, int frametime)
+{
+ if (imv->next_frame) {
+ imv_bitmap_free(imv->next_frame);
+ }
+ imv->next_frame = bitmap;
+
+ imv->next_frame_duration = frametime;
+}
+
static void handle_event(struct imv *imv, SDL_Event *event)
{
const int command_buffer_len = 1024;
if(event->type == imv->events.NEW_IMAGE) {
- /* new image to display */
- struct imv_bitmap *bmp = event->user.data1;
- imv_image_set_bitmap(imv->image, bmp);
- imv->current_image.width = bmp->width;
- imv->current_image.height = bmp->height;
- imv_bitmap_free(bmp);
- imv->need_redraw = true;
- imv->need_rescale |= event->user.code;
- imv->loading = false;
+ /* new image vs just a new frame of the same image */
+ bool is_new_image = !!event->user.data2;
+ if (is_new_image) {
+ handle_new_image(imv, event->user.data1, event->user.code);
+ } else {
+ handle_new_frame(imv, event->user.data1, event->user.code);
+ }
return;
} else if(event->type == imv->events.BAD_IMAGE) {
/* an image failed to load, remove it from our image list */
@@ -1362,6 +1405,7 @@ void command_next_frame(struct list *args, const char *argstr, void *data)
struct imv *imv = data;
if (imv->source) {
imv->source->load_next_frame(imv->source);
+ imv->next_frame_due = 1; /* Earliest possible non-zero timestamp */
}
}
diff --git a/src/source.h b/src/source.h
index 80b1e81..2f6b44d 100644
--- a/src/source.h
+++ b/src/source.h
@@ -14,6 +14,9 @@ struct imv_source_message {
/* Receiver is responsible for destroying bitmap */
struct imv_bitmap *bitmap;
+ /* If an animated gif, the frame's duration in milliseconds, else 0 */
+ int frametime;
+
/* Error message if bitmap was NULL */
const char *error;
};
@@ -30,6 +33,9 @@ struct imv_source {
/* Usually 1, more if animated */
int num_frames;
+ /* Next frame to be loaded, 0-indexed */
+ int next_frame;
+
/* Frames are returned using SDL events. These two fields must be
* populated by callers before calling any frame loading functionality
* on the source.
@@ -43,13 +49,6 @@ struct imv_source {
/* Trigger loading of next frame. */
void (*load_next_frame)(struct imv_source *src);
- /* Notify source of time passing, automatically triggers loading of
- * the next frame when due. */
- void (*time_passed)(struct imv_source *src, double dt);
-
- /* Asks the source how long we can sleep for before the next frame is due */
- double (*time_left)(struct imv_source *src);
-
/* Safely free contents of this source. After this returns
* it is safe to dealocate/overwrite the imv_source instance.
*/