diff options
-rw-r--r-- | src/backend_freeimage.c | 114 | ||||
-rw-r--r-- | src/imv.c | 98 | ||||
-rw-r--r-- | src/source.h | 13 |
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; @@ -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. */ |