595 lines
16 KiB
C
595 lines
16 KiB
C
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#define GDK_PIXBUF_ENABLE_BACKEND
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <gtk/gtk.h>
|
|
#include <glib/gstdio.h>
|
|
#include <ticalcs.h>
|
|
#include <tilem.h>
|
|
|
|
#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;
|
|
}
|