ti83-sdk/tool/tilem-src/gui/animation.c

595 lines
16 KiB
C
Raw Normal View History

2023-10-08 16:33:37 +00:00
/*
* 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;
}