1 /*
2 * TilEm II
3 *
4 * Copyright (c) 2011 Benjamin Moody
5 *
6 * This program is free software: you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation, either version 3 of the
9 * License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23
24 #define GDK_PIXBUF_ENABLE_BACKEND
25
26 #include <stdio.h>
27 #include <string.h>
28 #include <errno.h>
29 #include <gtk/gtk.h>
30 #include <glib/gstdio.h>
31 #include <ticalcs.h>
32 #include <tilem.h>
33
34 #include "gui.h"
35
36 #define GAMMA 2.2
37
38 struct _TilemAnimFrame {
39 struct _TilemAnimFrame *next;
40 unsigned duration : 24;
41 unsigned contrast : 8;
42 byte data[1];
43 };
44
45 struct _TilemAnimation {
46 GdkPixbufAnimation parent;
47
48 int num_frames;
49 TilemAnimFrame *start;
50 TilemAnimFrame *end;
51 dword last_stamp;
52
53 TilemLCDBuffer *temp_buffer;
54
55 GdkPixbuf *static_pixbuf;
56
57 int base_contrast;
58 int display_width;
59 int display_height;
60 int frame_rowstride;
61 int frame_size;
62 int image_width;
63 int image_height;
64 dword *palette;
65 gdouble speed;
66 gdouble time_stretch;
67
68 gboolean out_of_memory;
69 };
70
71 struct _TilemAnimationClass {
72 GdkPixbufAnimationClass parent_class;
73 };
74
75 #define TILEM_TYPE_ANIM_ITER (tilem_anim_iter_get_type())
76 #define TILEM_ANIM_ITER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TILEM_TYPE_ANIM_ITER, TilemAnimIter))
77 #define TILEM_ANIM_ITER_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST((cls), TILEM_TYPE_ANIM_ITER, TilemAnimIterClass))
78 #define TILEM_IS_ANIM_ITER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TILEM_TYPE_ANIM_ITER))
79 #define TILEM_IS_ANIM_ITER_CLASS(cls) (G_TYPE_CHECK_CLASS_TYPE((cls), TILEM_TYPE_ANIM_ITER))
80 #define TILEM_ANIM_ITER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TILEM_TYPE_ANIM_ITER, TilemAnimIterClass))
81
82 typedef struct _TilemAnimIter {
83 GdkPixbufAnimationIter parent;
84 GTimeVal current_time;
85 int time_elapsed;
86 TilemAnimation *anim;
87 TilemAnimFrame *frame;
88 GdkPixbuf *pixbuf;
89 } TilemAnimIter;
90
91 typedef struct _TilemAnimIterClass {
92 GdkPixbufAnimationIterClass parent_class;
93 } TilemAnimIterClass;
94
95 G_DEFINE_TYPE(TilemAnimation, tilem_animation,
96 GDK_TYPE_PIXBUF_ANIMATION);
97
98 G_DEFINE_TYPE(TilemAnimIter, tilem_anim_iter,
99 GDK_TYPE_PIXBUF_ANIMATION_ITER);
100
alloc_frame(int bufsize)101 static TilemAnimFrame * alloc_frame(int bufsize)
102 {
103 TilemAnimFrame *frm;
104
105 frm = g_try_malloc(sizeof(TilemAnimFrame) + bufsize - 1);
106 if (!frm)
107 return NULL;
108 frm->next = NULL;
109 return frm;
110 }
111
free_frame(TilemAnimFrame * frm)112 static void free_frame(TilemAnimFrame *frm)
113 {
114 g_free(frm);
115 }
116
adjust_contrast(TilemAnimation * anim,int contrast)117 static int adjust_contrast(TilemAnimation *anim, int contrast)
118 {
119 TilemAnimFrame *frm;
120
121 if (!contrast)
122 return 0;
123
124 if (!anim->base_contrast) {
125 for (frm = anim->start; frm; frm = frm->next) {
126 if (frm->contrast != 0) {
127 anim->base_contrast = frm->contrast;
128 break;
129 }
130 }
131 }
132
133 contrast = (contrast - anim->base_contrast + 32);
134 return CLAMP(contrast, 0, 63);
135 }
136
set_lcdbuf_from_frame(TilemAnimation * anim,TilemLCDBuffer * buf,const TilemAnimFrame * frm)137 static void set_lcdbuf_from_frame(TilemAnimation *anim,
138 TilemLCDBuffer *buf,
139 const TilemAnimFrame *frm)
140 {
141 buf->width = anim->display_width;
142 buf->height = anim->display_height;
143 buf->rowstride = anim->frame_rowstride;
144 buf->contrast = adjust_contrast(anim, frm->contrast);
145 buf->data = (byte *) frm->data;
146 }
147
frame_to_pixbuf(TilemAnimation * anim,const TilemAnimFrame * frm)148 static GdkPixbuf * frame_to_pixbuf(TilemAnimation *anim,
149 const TilemAnimFrame *frm)
150 {
151 GdkPixbuf *pb;
152
153 pb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8,
154 anim->image_width, anim->image_height);
155
156 set_lcdbuf_from_frame(anim, anim->temp_buffer, frm);
157 tilem_draw_lcd_image_rgb(anim->temp_buffer,
158 gdk_pixbuf_get_pixels(pb),
159 anim->image_width,
160 anim->image_height,
161 gdk_pixbuf_get_rowstride(pb),
162 3, anim->palette,
163 TILEM_SCALE_SMOOTH);
164 anim->temp_buffer->data = NULL;
165
166 return pb;
167 }
168
tilem_animation_is_static_image(GdkPixbufAnimation * ganim)169 static gboolean tilem_animation_is_static_image(GdkPixbufAnimation *ganim)
170 {
171 TilemAnimation *anim = TILEM_ANIMATION(ganim);
172
173 g_return_val_if_fail(TILEM_IS_ANIMATION(ganim), FALSE);
174
175 if (anim->start == anim->end)
176 return TRUE;
177 else
178 return FALSE;
179 }
180
tilem_animation_get_static_image(GdkPixbufAnimation * ganim)181 static GdkPixbuf * tilem_animation_get_static_image(GdkPixbufAnimation *ganim)
182 {
183 TilemAnimation *anim = TILEM_ANIMATION(ganim);
184
185 g_return_val_if_fail(TILEM_IS_ANIMATION(anim), NULL);
186 g_return_val_if_fail(anim->start != NULL, NULL);
187
188 if (!anim->static_pixbuf)
189 anim->static_pixbuf = frame_to_pixbuf(anim, anim->start);
190
191 return anim->static_pixbuf;
192 }
193
tilem_animation_get_size(GdkPixbufAnimation * ganim,int * width,int * height)194 static void tilem_animation_get_size(GdkPixbufAnimation *ganim,
195 int *width, int *height)
196 {
197 TilemAnimation *anim = TILEM_ANIMATION(ganim);
198
199 g_return_if_fail(TILEM_IS_ANIMATION(anim));
200
201 if (width) *width = anim->image_width;
202 if (height) *height = anim->image_height;
203 }
204
205 static GdkPixbufAnimationIter *
tilem_animation_get_iter(GdkPixbufAnimation * ganim,const GTimeVal * start_time)206 tilem_animation_get_iter(GdkPixbufAnimation *ganim,
207 const GTimeVal *start_time)
208 {
209 TilemAnimation *anim = TILEM_ANIMATION(ganim);
210 TilemAnimIter *iter;
211
212 g_return_val_if_fail(TILEM_IS_ANIMATION(anim), NULL);
213
214 iter = g_object_new(TILEM_TYPE_ANIM_ITER, NULL);
215 iter->anim = anim;
216 iter->frame = anim->start;
217 iter->current_time = *start_time;
218
219 g_object_ref(anim);
220
221 return GDK_PIXBUF_ANIMATION_ITER(iter);
222 }
223
tilem_animation_init(G_GNUC_UNUSED TilemAnimation * anim)224 static void tilem_animation_init(G_GNUC_UNUSED TilemAnimation *anim)
225 {
226 }
227
tilem_animation_finalize(GObject * obj)228 static void tilem_animation_finalize(GObject *obj)
229 {
230 TilemAnimation *anim = TILEM_ANIMATION(obj);
231 TilemAnimFrame *frm;
232
233 g_return_if_fail(TILEM_IS_ANIMATION(anim));
234
235 while (anim->start) {
236 frm = anim->start;
237 anim->start = frm->next;
238 free_frame(frm);
239 }
240
241 anim->start = anim->end = NULL;
242
243 if (anim->temp_buffer)
244 tilem_lcd_buffer_free(anim->temp_buffer);
245 anim->temp_buffer = NULL;
246
247 if (anim->palette)
248 tilem_free(anim->palette);
249 anim->palette = NULL;
250
251 if (anim->static_pixbuf)
252 g_object_unref(anim->static_pixbuf);
253 anim->static_pixbuf = NULL;
254
255 if (G_OBJECT_CLASS(tilem_animation_parent_class)->finalize)
256 (*G_OBJECT_CLASS(tilem_animation_parent_class)->finalize)(obj);
257 }
258
tilem_animation_class_init(TilemAnimationClass * klass)259 static void tilem_animation_class_init(TilemAnimationClass *klass)
260 {
261 GdkPixbufAnimationClass *aclass = GDK_PIXBUF_ANIMATION_CLASS(klass);
262 GObjectClass *oclass = G_OBJECT_CLASS(klass);
263
264 aclass->is_static_image = tilem_animation_is_static_image;
265 aclass->get_static_image = tilem_animation_get_static_image;
266 aclass->get_size = tilem_animation_get_size;
267 aclass->get_iter = tilem_animation_get_iter;
268
269 oclass->finalize = tilem_animation_finalize;
270 }
271
tilem_anim_iter_get_delay_time(GdkPixbufAnimationIter * giter)272 static int tilem_anim_iter_get_delay_time(GdkPixbufAnimationIter *giter)
273 {
274 TilemAnimIter *iter = TILEM_ANIM_ITER(giter);
275
276 g_return_val_if_fail(TILEM_IS_ANIM_ITER(iter), 0);
277 g_return_val_if_fail(iter->anim != NULL, 0);
278 g_return_val_if_fail(iter->frame != NULL, 0);
279
280 if (iter->anim->start == iter->anim->end)
281 return -1;
282 else
283 return ((iter->frame->duration - iter->time_elapsed)
284 * iter->anim->time_stretch);
285 }
286
tilem_anim_iter_get_pixbuf(GdkPixbufAnimationIter * giter)287 static GdkPixbuf * tilem_anim_iter_get_pixbuf(GdkPixbufAnimationIter *giter)
288 {
289 TilemAnimIter *iter = TILEM_ANIM_ITER(giter);
290
291 g_return_val_if_fail(TILEM_IS_ANIM_ITER(iter), NULL);
292 g_return_val_if_fail(iter->anim != NULL, NULL);
293 g_return_val_if_fail(iter->frame != NULL, NULL);
294
295 if (!iter->pixbuf)
296 iter->pixbuf = frame_to_pixbuf(iter->anim, iter->frame);
297
298 return iter->pixbuf;
299 }
300
tilem_anim_iter_on_currently_loading_frame(G_GNUC_UNUSED GdkPixbufAnimationIter * giter)301 static gboolean tilem_anim_iter_on_currently_loading_frame(G_GNUC_UNUSED GdkPixbufAnimationIter *giter)
302 {
303 return FALSE;
304 }
305
306 static gboolean
tilem_anim_iter_advance(GdkPixbufAnimationIter * giter,const GTimeVal * current_time)307 tilem_anim_iter_advance(GdkPixbufAnimationIter *giter,
308 const GTimeVal *current_time)
309 {
310 TilemAnimIter *iter = TILEM_ANIM_ITER(giter);
311 int ms;
312
313 g_return_val_if_fail(TILEM_IS_ANIM_ITER(iter), FALSE);
314 g_return_val_if_fail(iter->anim != NULL, FALSE);
315 g_return_val_if_fail(iter->frame != NULL, FALSE);
316
317 ms = ((current_time->tv_usec - iter->current_time.tv_usec) / 1000
318 + (current_time->tv_sec - iter->current_time.tv_sec) * 1000);
319
320 g_time_val_add(&iter->current_time, ms * 1000);
321
322 ms *= iter->anim->speed;
323
324 ms += iter->time_elapsed;
325 if (ms < iter->frame->duration) {
326 iter->time_elapsed = ms;
327 return FALSE;
328 }
329
330 if (iter->pixbuf)
331 g_object_unref(iter->pixbuf);
332 iter->pixbuf = NULL;
333
334 while (ms >= iter->frame->duration) {
335 ms -= iter->frame->duration;
336 if (iter->frame->next)
337 iter->frame = iter->frame->next;
338 else
339 iter->frame = iter->anim->start;
340 }
341
342 iter->time_elapsed = ms;
343 return TRUE;
344 }
345
tilem_anim_iter_init(G_GNUC_UNUSED TilemAnimIter * iter)346 static void tilem_anim_iter_init(G_GNUC_UNUSED TilemAnimIter *iter)
347 {
348 }
349
tilem_anim_iter_finalize(GObject * obj)350 static void tilem_anim_iter_finalize(GObject *obj)
351 {
352 TilemAnimIter *iter = TILEM_ANIM_ITER(obj);
353
354 g_return_if_fail(TILEM_IS_ANIM_ITER(obj));
355
356 if (iter->anim)
357 g_object_unref(iter->anim);
358 iter->anim = NULL;
359
360 if (iter->pixbuf)
361 g_object_unref(iter->pixbuf);
362 iter->pixbuf = NULL;
363
364 if (G_OBJECT_CLASS(tilem_anim_iter_parent_class)->finalize)
365 (*G_OBJECT_CLASS(tilem_anim_iter_parent_class)->finalize)(obj);
366 }
367
tilem_anim_iter_class_init(TilemAnimIterClass * klass)368 static void tilem_anim_iter_class_init(TilemAnimIterClass *klass)
369 {
370 GdkPixbufAnimationIterClass *iclass = GDK_PIXBUF_ANIMATION_ITER_CLASS(klass);
371 GObjectClass *oclass = G_OBJECT_CLASS(klass);
372
373 iclass->get_delay_time = tilem_anim_iter_get_delay_time;
374 iclass->get_pixbuf = tilem_anim_iter_get_pixbuf;
375 iclass->on_currently_loading_frame = tilem_anim_iter_on_currently_loading_frame;
376 iclass->advance = tilem_anim_iter_advance;
377
378 oclass->finalize = tilem_anim_iter_finalize;
379 }
380
tilem_animation_new(int display_width,int display_height)381 TilemAnimation * tilem_animation_new(int display_width, int display_height)
382 {
383 TilemAnimation *anim;
384 TilemAnimFrame *dummy_frame;
385
386 g_return_val_if_fail(display_width > 0, NULL);
387 g_return_val_if_fail(display_height > 0, NULL);
388
389 anim = g_object_new(TILEM_TYPE_ANIMATION, NULL);
390 anim->display_width = display_width;
391 anim->display_height = display_height;
392 anim->frame_rowstride = (display_width + 7) & ~7;
393 anim->frame_size = anim->frame_rowstride * display_height;
394
395 anim->image_width = display_width;
396 anim->image_height = display_height;
397 anim->speed = 1.0;
398 anim->time_stretch = 1.0;
399
400 anim->temp_buffer = tilem_lcd_buffer_new();
401 anim->palette = tilem_color_palette_new(255, 255, 255, 0, 0, 0, GAMMA);
402
403 dummy_frame = alloc_frame(anim->frame_size);
404 dummy_frame->duration = 0;
405 dummy_frame->contrast = 0;
406 anim->start = anim->end = dummy_frame;
407
408 return anim;
409 }
410
tilem_animation_append_frame(TilemAnimation * anim,const TilemLCDBuffer * buf,int duration)411 gboolean tilem_animation_append_frame(TilemAnimation *anim,
412 const TilemLCDBuffer *buf,
413 int duration)
414 {
415 TilemAnimFrame *frm;
416
417 g_return_val_if_fail(TILEM_IS_ANIMATION(anim), FALSE);
418 g_return_val_if_fail(anim->end != NULL, FALSE);
419 g_return_val_if_fail(buf != NULL, FALSE);
420 g_return_val_if_fail(buf->data != NULL, FALSE);
421 g_return_val_if_fail(buf->height == anim->display_height, FALSE);
422 g_return_val_if_fail(buf->rowstride == anim->frame_rowstride, FALSE);
423
424 if (anim->out_of_memory)
425 return FALSE;
426
427 if (anim->end->contrast == buf->contrast
428 && (anim->last_stamp == buf->stamp
429 || !memcmp(anim->end->data, buf->data, anim->frame_size))) {
430 anim->end->duration += duration;
431 }
432 else {
433 if (anim->end->duration == 0) {
434 frm = anim->end;
435 }
436 else {
437 frm = alloc_frame(anim->frame_size);
438 if (!frm) {
439 anim->out_of_memory = TRUE;
440 return FALSE;
441 }
442 anim->end->next = frm;
443 anim->end = frm;
444 }
445
446 frm->contrast = buf->contrast;
447 frm->duration = duration;
448 memcpy(frm->data, buf->data, anim->frame_size);
449 }
450
451 anim->last_stamp = buf->stamp;
452 return TRUE;
453 }
454
tilem_animation_set_size(TilemAnimation * anim,int width,int height)455 void tilem_animation_set_size(TilemAnimation *anim, int width, int height)
456 {
457 g_return_if_fail(TILEM_IS_ANIMATION(anim));
458 anim->image_width = width;
459 anim->image_height = height;
460 }
461
tilem_animation_set_colors(TilemAnimation * anim,const GdkColor * foreground,const GdkColor * background)462 void tilem_animation_set_colors(TilemAnimation *anim,
463 const GdkColor *foreground,
464 const GdkColor *background)
465 {
466 g_return_if_fail(TILEM_IS_ANIMATION(anim));
467 g_return_if_fail(foreground != NULL);
468 g_return_if_fail(background != NULL);
469
470 if (anim->palette)
471 tilem_free(anim->palette);
472
473 anim->palette = tilem_color_palette_new(background->red >> 8,
474 background->green >> 8,
475 background->blue >> 8,
476 foreground->red >> 8,
477 foreground->green >> 8,
478 foreground->blue >> 8,
479 GAMMA);
480 }
481
tilem_animation_set_speed(TilemAnimation * anim,gdouble factor)482 void tilem_animation_set_speed(TilemAnimation *anim, gdouble factor)
483 {
484 g_return_if_fail(TILEM_IS_ANIMATION(anim));
485 g_return_if_fail(factor > 0.0);
486 anim->speed = factor;
487 anim->time_stretch = 1.0 / factor;
488 }
489
tilem_animation_get_speed(TilemAnimation * anim)490 gdouble tilem_animation_get_speed(TilemAnimation *anim)
491 {
492 g_return_val_if_fail(TILEM_IS_ANIMATION(anim), 1.0);
493 return anim->speed;
494 }
495
tilem_animation_next_frame(TilemAnimation * anim,TilemAnimFrame * frm)496 TilemAnimFrame *tilem_animation_next_frame(TilemAnimation *anim,
497 TilemAnimFrame *frm)
498 {
499 g_return_val_if_fail(TILEM_IS_ANIMATION(anim), NULL);
500 if (frm)
501 return frm->next;
502 else
503 return anim->start;
504 }
505
tilem_anim_frame_get_duration(TilemAnimFrame * frm)506 int tilem_anim_frame_get_duration(TilemAnimFrame *frm)
507 {
508 g_return_val_if_fail(frm != NULL, 0);
509 return frm->duration;
510 }
511
tilem_animation_get_indexed_image(TilemAnimation * anim,TilemAnimFrame * frm,byte ** buffer,int * width,int * height)512 void tilem_animation_get_indexed_image(TilemAnimation *anim,
513 TilemAnimFrame *frm,
514 byte **buffer,
515 int *width, int *height)
516 {
517 g_return_if_fail(TILEM_IS_ANIMATION(anim));
518 g_return_if_fail(frm != NULL);
519 g_return_if_fail(buffer != NULL);
520 g_return_if_fail(width != NULL);
521 g_return_if_fail(height != NULL);
522
523 *width = anim->image_width;
524 *height = anim->image_height;
525 *buffer = g_new(byte, anim->image_width * anim->image_height);
526
527 set_lcdbuf_from_frame(anim, anim->temp_buffer, frm);
528 tilem_draw_lcd_image_indexed(anim->temp_buffer, *buffer,
529 anim->image_width, anim->image_height,
530 anim->image_width,
531 TILEM_SCALE_SMOOTH);
532 anim->temp_buffer->data = NULL;
533 }
534
tilem_animation_save(TilemAnimation * anim,const char * fname,const char * type,char ** option_keys,char ** option_values,GError ** err)535 gboolean tilem_animation_save(TilemAnimation *anim,
536 const char *fname, const char *type,
537 char **option_keys, char **option_values,
538 GError **err)
539 {
540 FILE *fp;
541 char *dname;
542 int errnum;
543 GdkPixbuf *pb;
544 gboolean status;
545 byte palette[768];
546 int i;
547
548 g_return_val_if_fail(TILEM_IS_ANIMATION(anim), FALSE);
549 g_return_val_if_fail(fname != NULL, FALSE);
550 g_return_val_if_fail(type != NULL, FALSE);
551 g_return_val_if_fail(err == NULL || *err == NULL, FALSE);
552
553 if (strcmp(type, "gif") != 0) {
554 pb = gdk_pixbuf_animation_get_static_image
555 (GDK_PIXBUF_ANIMATION(anim));
556 status = gdk_pixbuf_savev(pb, fname, type,
557 option_keys, option_values,
558 err);
559 return status;
560 }
561
562 fp = g_fopen(fname, "wb");
563 if (!fp) {
564 errnum = errno;
565 dname = g_filename_display_name(fname);
566 g_set_error(err, G_FILE_ERROR,
567 g_file_error_from_errno(errnum),
568 "Failed to open '%s' for writing: %s",
569 dname, g_strerror(errnum));
570 g_free(dname);
571 return FALSE;
572 }
573
574 for (i = 0; i < 256; i++) {
575 palette[3 * i] = anim->palette[i] >> 16;
576 palette[3 * i + 1] = anim->palette[i] >> 8;
577 palette[3 * i + 2] = anim->palette[i];
578 }
579
580 tilem_animation_write_gif(anim, palette, 256, fp);
581
582 if (fclose(fp)) {
583 errnum = errno;
584 dname = g_filename_display_name(fname);
585 g_set_error(err, G_FILE_ERROR,
586 g_file_error_from_errno(errnum),
587 "Error while closing '%s': %s",
588 dname, g_strerror(errnum));
589 g_free(dname);
590 return FALSE;
591 }
592
593 return TRUE;
594 }
595