/* * TilEm II * * Copyright (c) 2011 Benjamin Moody * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifdef HAVE_CONFIG_H # include #endif #define GDK_PIXBUF_ENABLE_BACKEND #include #include #include #include #include #include #include #include "gui.h" #define GAMMA 2.2 struct _TilemAnimFrame { struct _TilemAnimFrame *next; unsigned duration : 24; unsigned contrast : 8; byte data[1]; }; struct _TilemAnimation { GdkPixbufAnimation parent; int num_frames; TilemAnimFrame *start; TilemAnimFrame *end; dword last_stamp; TilemLCDBuffer *temp_buffer; GdkPixbuf *static_pixbuf; int base_contrast; int display_width; int display_height; int frame_rowstride; int frame_size; int image_width; int image_height; dword *palette; gdouble speed; gdouble time_stretch; gboolean out_of_memory; }; struct _TilemAnimationClass { GdkPixbufAnimationClass parent_class; }; #define TILEM_TYPE_ANIM_ITER (tilem_anim_iter_get_type()) #define TILEM_ANIM_ITER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TILEM_TYPE_ANIM_ITER, TilemAnimIter)) #define TILEM_ANIM_ITER_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST((cls), TILEM_TYPE_ANIM_ITER, TilemAnimIterClass)) #define TILEM_IS_ANIM_ITER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TILEM_TYPE_ANIM_ITER)) #define TILEM_IS_ANIM_ITER_CLASS(cls) (G_TYPE_CHECK_CLASS_TYPE((cls), TILEM_TYPE_ANIM_ITER)) #define TILEM_ANIM_ITER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TILEM_TYPE_ANIM_ITER, TilemAnimIterClass)) typedef struct _TilemAnimIter { GdkPixbufAnimationIter parent; GTimeVal current_time; int time_elapsed; TilemAnimation *anim; TilemAnimFrame *frame; GdkPixbuf *pixbuf; } TilemAnimIter; typedef struct _TilemAnimIterClass { GdkPixbufAnimationIterClass parent_class; } TilemAnimIterClass; G_DEFINE_TYPE(TilemAnimation, tilem_animation, GDK_TYPE_PIXBUF_ANIMATION); G_DEFINE_TYPE(TilemAnimIter, tilem_anim_iter, GDK_TYPE_PIXBUF_ANIMATION_ITER); static TilemAnimFrame * alloc_frame(int bufsize) { TilemAnimFrame *frm; frm = g_try_malloc(sizeof(TilemAnimFrame) + bufsize - 1); if (!frm) return NULL; frm->next = NULL; return frm; } static void free_frame(TilemAnimFrame *frm) { g_free(frm); } static int adjust_contrast(TilemAnimation *anim, int contrast) { TilemAnimFrame *frm; if (!contrast) return 0; if (!anim->base_contrast) { for (frm = anim->start; frm; frm = frm->next) { if (frm->contrast != 0) { anim->base_contrast = frm->contrast; break; } } } contrast = (contrast - anim->base_contrast + 32); return CLAMP(contrast, 0, 63); } static void set_lcdbuf_from_frame(TilemAnimation *anim, TilemLCDBuffer *buf, const TilemAnimFrame *frm) { buf->width = anim->display_width; buf->height = anim->display_height; buf->rowstride = anim->frame_rowstride; buf->contrast = adjust_contrast(anim, frm->contrast); buf->data = (byte *) frm->data; } static GdkPixbuf * frame_to_pixbuf(TilemAnimation *anim, const TilemAnimFrame *frm) { GdkPixbuf *pb; pb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, anim->image_width, anim->image_height); set_lcdbuf_from_frame(anim, anim->temp_buffer, frm); tilem_draw_lcd_image_rgb(anim->temp_buffer, gdk_pixbuf_get_pixels(pb), anim->image_width, anim->image_height, gdk_pixbuf_get_rowstride(pb), 3, anim->palette, TILEM_SCALE_SMOOTH); anim->temp_buffer->data = NULL; return pb; } static gboolean tilem_animation_is_static_image(GdkPixbufAnimation *ganim) { TilemAnimation *anim = TILEM_ANIMATION(ganim); g_return_val_if_fail(TILEM_IS_ANIMATION(ganim), FALSE); if (anim->start == anim->end) return TRUE; else return FALSE; } static GdkPixbuf * tilem_animation_get_static_image(GdkPixbufAnimation *ganim) { TilemAnimation *anim = TILEM_ANIMATION(ganim); g_return_val_if_fail(TILEM_IS_ANIMATION(anim), NULL); g_return_val_if_fail(anim->start != NULL, NULL); if (!anim->static_pixbuf) anim->static_pixbuf = frame_to_pixbuf(anim, anim->start); return anim->static_pixbuf; } static void tilem_animation_get_size(GdkPixbufAnimation *ganim, int *width, int *height) { TilemAnimation *anim = TILEM_ANIMATION(ganim); g_return_if_fail(TILEM_IS_ANIMATION(anim)); if (width) *width = anim->image_width; if (height) *height = anim->image_height; } static GdkPixbufAnimationIter * tilem_animation_get_iter(GdkPixbufAnimation *ganim, const GTimeVal *start_time) { TilemAnimation *anim = TILEM_ANIMATION(ganim); TilemAnimIter *iter; g_return_val_if_fail(TILEM_IS_ANIMATION(anim), NULL); iter = g_object_new(TILEM_TYPE_ANIM_ITER, NULL); iter->anim = anim; iter->frame = anim->start; iter->current_time = *start_time; g_object_ref(anim); return GDK_PIXBUF_ANIMATION_ITER(iter); } static void tilem_animation_init(G_GNUC_UNUSED TilemAnimation *anim) { } static void tilem_animation_finalize(GObject *obj) { TilemAnimation *anim = TILEM_ANIMATION(obj); TilemAnimFrame *frm; g_return_if_fail(TILEM_IS_ANIMATION(anim)); while (anim->start) { frm = anim->start; anim->start = frm->next; free_frame(frm); } anim->start = anim->end = NULL; if (anim->temp_buffer) tilem_lcd_buffer_free(anim->temp_buffer); anim->temp_buffer = NULL; if (anim->palette) tilem_free(anim->palette); anim->palette = NULL; if (anim->static_pixbuf) g_object_unref(anim->static_pixbuf); anim->static_pixbuf = NULL; if (G_OBJECT_CLASS(tilem_animation_parent_class)->finalize) (*G_OBJECT_CLASS(tilem_animation_parent_class)->finalize)(obj); } static void tilem_animation_class_init(TilemAnimationClass *klass) { GdkPixbufAnimationClass *aclass = GDK_PIXBUF_ANIMATION_CLASS(klass); GObjectClass *oclass = G_OBJECT_CLASS(klass); aclass->is_static_image = tilem_animation_is_static_image; aclass->get_static_image = tilem_animation_get_static_image; aclass->get_size = tilem_animation_get_size; aclass->get_iter = tilem_animation_get_iter; oclass->finalize = tilem_animation_finalize; } static int tilem_anim_iter_get_delay_time(GdkPixbufAnimationIter *giter) { TilemAnimIter *iter = TILEM_ANIM_ITER(giter); g_return_val_if_fail(TILEM_IS_ANIM_ITER(iter), 0); g_return_val_if_fail(iter->anim != NULL, 0); g_return_val_if_fail(iter->frame != NULL, 0); if (iter->anim->start == iter->anim->end) return -1; else return ((iter->frame->duration - iter->time_elapsed) * iter->anim->time_stretch); } static GdkPixbuf * tilem_anim_iter_get_pixbuf(GdkPixbufAnimationIter *giter) { TilemAnimIter *iter = TILEM_ANIM_ITER(giter); g_return_val_if_fail(TILEM_IS_ANIM_ITER(iter), NULL); g_return_val_if_fail(iter->anim != NULL, NULL); g_return_val_if_fail(iter->frame != NULL, NULL); if (!iter->pixbuf) iter->pixbuf = frame_to_pixbuf(iter->anim, iter->frame); return iter->pixbuf; } static gboolean tilem_anim_iter_on_currently_loading_frame(G_GNUC_UNUSED GdkPixbufAnimationIter *giter) { return FALSE; } static gboolean tilem_anim_iter_advance(GdkPixbufAnimationIter *giter, const GTimeVal *current_time) { TilemAnimIter *iter = TILEM_ANIM_ITER(giter); int ms; g_return_val_if_fail(TILEM_IS_ANIM_ITER(iter), FALSE); g_return_val_if_fail(iter->anim != NULL, FALSE); g_return_val_if_fail(iter->frame != NULL, FALSE); ms = ((current_time->tv_usec - iter->current_time.tv_usec) / 1000 + (current_time->tv_sec - iter->current_time.tv_sec) * 1000); g_time_val_add(&iter->current_time, ms * 1000); ms *= iter->anim->speed; ms += iter->time_elapsed; if (ms < iter->frame->duration) { iter->time_elapsed = ms; return FALSE; } if (iter->pixbuf) g_object_unref(iter->pixbuf); iter->pixbuf = NULL; while (ms >= iter->frame->duration) { ms -= iter->frame->duration; if (iter->frame->next) iter->frame = iter->frame->next; else iter->frame = iter->anim->start; } iter->time_elapsed = ms; return TRUE; } static void tilem_anim_iter_init(G_GNUC_UNUSED TilemAnimIter *iter) { } static void tilem_anim_iter_finalize(GObject *obj) { TilemAnimIter *iter = TILEM_ANIM_ITER(obj); g_return_if_fail(TILEM_IS_ANIM_ITER(obj)); if (iter->anim) g_object_unref(iter->anim); iter->anim = NULL; if (iter->pixbuf) g_object_unref(iter->pixbuf); iter->pixbuf = NULL; if (G_OBJECT_CLASS(tilem_anim_iter_parent_class)->finalize) (*G_OBJECT_CLASS(tilem_anim_iter_parent_class)->finalize)(obj); } static void tilem_anim_iter_class_init(TilemAnimIterClass *klass) { GdkPixbufAnimationIterClass *iclass = GDK_PIXBUF_ANIMATION_ITER_CLASS(klass); GObjectClass *oclass = G_OBJECT_CLASS(klass); iclass->get_delay_time = tilem_anim_iter_get_delay_time; iclass->get_pixbuf = tilem_anim_iter_get_pixbuf; iclass->on_currently_loading_frame = tilem_anim_iter_on_currently_loading_frame; iclass->advance = tilem_anim_iter_advance; oclass->finalize = tilem_anim_iter_finalize; } TilemAnimation * tilem_animation_new(int display_width, int display_height) { TilemAnimation *anim; TilemAnimFrame *dummy_frame; g_return_val_if_fail(display_width > 0, NULL); g_return_val_if_fail(display_height > 0, NULL); anim = g_object_new(TILEM_TYPE_ANIMATION, NULL); anim->display_width = display_width; anim->display_height = display_height; anim->frame_rowstride = (display_width + 7) & ~7; anim->frame_size = anim->frame_rowstride * display_height; anim->image_width = display_width; anim->image_height = display_height; anim->speed = 1.0; anim->time_stretch = 1.0; anim->temp_buffer = tilem_lcd_buffer_new(); anim->palette = tilem_color_palette_new(255, 255, 255, 0, 0, 0, GAMMA); dummy_frame = alloc_frame(anim->frame_size); dummy_frame->duration = 0; dummy_frame->contrast = 0; anim->start = anim->end = dummy_frame; return anim; } gboolean tilem_animation_append_frame(TilemAnimation *anim, const TilemLCDBuffer *buf, int duration) { TilemAnimFrame *frm; g_return_val_if_fail(TILEM_IS_ANIMATION(anim), FALSE); g_return_val_if_fail(anim->end != NULL, FALSE); g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(buf->data != NULL, FALSE); g_return_val_if_fail(buf->height == anim->display_height, FALSE); g_return_val_if_fail(buf->rowstride == anim->frame_rowstride, FALSE); if (anim->out_of_memory) return FALSE; if (anim->end->contrast == buf->contrast && (anim->last_stamp == buf->stamp || !memcmp(anim->end->data, buf->data, anim->frame_size))) { anim->end->duration += duration; } else { if (anim->end->duration == 0) { frm = anim->end; } else { frm = alloc_frame(anim->frame_size); if (!frm) { anim->out_of_memory = TRUE; return FALSE; } anim->end->next = frm; anim->end = frm; } frm->contrast = buf->contrast; frm->duration = duration; memcpy(frm->data, buf->data, anim->frame_size); } anim->last_stamp = buf->stamp; return TRUE; } void tilem_animation_set_size(TilemAnimation *anim, int width, int height) { g_return_if_fail(TILEM_IS_ANIMATION(anim)); anim->image_width = width; anim->image_height = height; } void tilem_animation_set_colors(TilemAnimation *anim, const GdkColor *foreground, const GdkColor *background) { g_return_if_fail(TILEM_IS_ANIMATION(anim)); g_return_if_fail(foreground != NULL); g_return_if_fail(background != NULL); if (anim->palette) tilem_free(anim->palette); anim->palette = tilem_color_palette_new(background->red >> 8, background->green >> 8, background->blue >> 8, foreground->red >> 8, foreground->green >> 8, foreground->blue >> 8, GAMMA); } void tilem_animation_set_speed(TilemAnimation *anim, gdouble factor) { g_return_if_fail(TILEM_IS_ANIMATION(anim)); g_return_if_fail(factor > 0.0); anim->speed = factor; anim->time_stretch = 1.0 / factor; } gdouble tilem_animation_get_speed(TilemAnimation *anim) { g_return_val_if_fail(TILEM_IS_ANIMATION(anim), 1.0); return anim->speed; } TilemAnimFrame *tilem_animation_next_frame(TilemAnimation *anim, TilemAnimFrame *frm) { g_return_val_if_fail(TILEM_IS_ANIMATION(anim), NULL); if (frm) return frm->next; else return anim->start; } int tilem_anim_frame_get_duration(TilemAnimFrame *frm) { g_return_val_if_fail(frm != NULL, 0); return frm->duration; } void tilem_animation_get_indexed_image(TilemAnimation *anim, TilemAnimFrame *frm, byte **buffer, int *width, int *height) { g_return_if_fail(TILEM_IS_ANIMATION(anim)); g_return_if_fail(frm != NULL); g_return_if_fail(buffer != NULL); g_return_if_fail(width != NULL); g_return_if_fail(height != NULL); *width = anim->image_width; *height = anim->image_height; *buffer = g_new(byte, anim->image_width * anim->image_height); set_lcdbuf_from_frame(anim, anim->temp_buffer, frm); tilem_draw_lcd_image_indexed(anim->temp_buffer, *buffer, anim->image_width, anim->image_height, anim->image_width, TILEM_SCALE_SMOOTH); anim->temp_buffer->data = NULL; } gboolean tilem_animation_save(TilemAnimation *anim, const char *fname, const char *type, char **option_keys, char **option_values, GError **err) { FILE *fp; char *dname; int errnum; GdkPixbuf *pb; gboolean status; byte palette[768]; int i; g_return_val_if_fail(TILEM_IS_ANIMATION(anim), FALSE); g_return_val_if_fail(fname != NULL, FALSE); g_return_val_if_fail(type != NULL, FALSE); g_return_val_if_fail(err == NULL || *err == NULL, FALSE); if (strcmp(type, "gif") != 0) { pb = gdk_pixbuf_animation_get_static_image (GDK_PIXBUF_ANIMATION(anim)); status = gdk_pixbuf_savev(pb, fname, type, option_keys, option_values, err); return status; } fp = g_fopen(fname, "wb"); if (!fp) { errnum = errno; dname = g_filename_display_name(fname); g_set_error(err, G_FILE_ERROR, g_file_error_from_errno(errnum), "Failed to open '%s' for writing: %s", dname, g_strerror(errnum)); g_free(dname); return FALSE; } for (i = 0; i < 256; i++) { palette[3 * i] = anim->palette[i] >> 16; palette[3 * i + 1] = anim->palette[i] >> 8; palette[3 * i + 2] = anim->palette[i]; } tilem_animation_write_gif(anim, palette, 256, fp); if (fclose(fp)) { errnum = errno; dname = g_filename_display_name(fname); g_set_error(err, G_FILE_ERROR, g_file_error_from_errno(errnum), "Error while closing '%s': %s", dname, g_strerror(errnum)); g_free(dname); return FALSE; } return TRUE; }