1 /*
2 * TilEm II
3 *
4 * Copyright (c) 2010-2011 Thibault Duponchelle
5 * Copyright (c) 2011 Benjamin Moody
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24
25 #include <stdio.h>
26 #include <gtk/gtk.h>
27 #include <ticalcs.h>
28 #include <tilem.h>
29 #include "gui.h"
30
31
32 static void write_global_header(FILE* fp, int width, int height, byte* palette, int palette_size);
33 static void write_global_footer(FILE* fp);
34 static void write_extension_block(FILE* fout, word delay);
35 static void write_image_block_start(FILE *fp, int width, int height);
36 static void write_image_block_end(FILE *fp);
37 static void write_comment(FILE* fp);
38 static void write_application_extension(FILE * fp) ;
39
write_global_header(FILE * fp,int width,int height,byte * palette,int palette_size)40 static void write_global_header(FILE* fp, int width, int height, byte* palette, int palette_size) {
41
42 /* Magic number for Gif file format */
43 char global_header_magic_number[] = {'G', 'I', 'F', '8', '9', 'a'};
44 /* Size of canvas width on 2 bytes, heigth on 2 bytes */
45 char global_header_canvas[] = {96, 0, 64, 0 };
46
47 global_header_canvas[0] = width;
48 global_header_canvas[1] = (width >> 8) ;
49 global_header_canvas[2] = height;
50 global_header_canvas[3] = (height >> 8);
51
52 /* Flag */
53 /* The 11th byte is a set of flags :
54 bit 0: Global Color Table Flag (GCTF)
55 bit 1..3: Color Resolution
56 bit 4: Sort Flag to Global Color Table
57 bit 5..7: Size of Global Color Table: 2^(1+n)
58 It means "use the GCT wich is given after (from the size bit 5..7) and a resolution bit 1..3
59 The Background color is an index in the Global Color Table
60 */
61 /* FIXME : if we change the palette size, we need to change this flag too and I don't do this currently */
62 char global_header_flag[] = { 0xf7 };
63 /* The index in global color table */
64 char global_header_background_index[] = {0x00};
65 /* Aspect pixel ratio (unknown) */
66 char global_header_aspect_pixel_ratio[] = {0x00};
67
68
69 fwrite(global_header_magic_number, 6, 1, fp);
70 fwrite(global_header_canvas, 4, 1, fp);
71 fwrite(global_header_flag, 1, 1, fp);
72 fwrite(global_header_background_index, 1, 1, fp);
73 fwrite(global_header_aspect_pixel_ratio, 1, 1, fp);
74
75 //byte* palette = tilem_color_palette_new_packed(255, 255, 255, 0, 0, 0, 2.2);
76
77 fwrite(palette, palette_size * 3, 1, fp);
78 }
79
write_global_footer(FILE * fp)80 static void write_global_footer(FILE* fp) {
81
82 /* This value means end of gif file */
83 char footer_trailer[1] = { 0x3b};
84
85 fwrite(footer_trailer, 1, 1,fp);
86 }
87
88
write_extension_block(FILE * fp,word delay)89 static void write_extension_block(FILE* fp, word delay) {
90
91 /* Extension block introduced by 0x21 ('!'), size before extension_block_terminator, flag byte, delay (10/100) 2 bytes */
92 char extension_block_header[2] = {0x21, 0xf9};
93 /* Size before extension_block_terminator */
94 char extension_block_size[1] = { 0x04} ;
95 /* Flag (unknown) */
96 char extension_block_flag[1] = { 0x00} ;
97 /* Delay (x/100 sec) on 2 bytes*/
98 char extension_block_delay[2] = {10, 0} ;
99 extension_block_delay[0] = delay;
100 /* The index designed by this variable become transparent even if palette gives a black(or something else) color. */
101 char extension_block_transparent_index[1] = {0xff};
102 /* End of extension block */
103 char extension_block_terminator[1] = {0x00};
104
105 fwrite(extension_block_header, 2, 1, fp);
106 fwrite(extension_block_size, 1, 1, fp);
107 fwrite(extension_block_flag, 1, 1, fp);
108 fwrite(extension_block_delay, 2, 1, fp);
109 fwrite(extension_block_transparent_index, 1, 1, fp);
110 fwrite(extension_block_terminator, 1, 1, fp);
111
112 }
113
write_image_block_start(FILE * fp,int width,int height)114 static void write_image_block_start(FILE *fp, int width, int height) {
115
116 /* Header */
117 char image_block_header[] = { 0x2c};
118 /* Left corner x (2 bytes), left corner y (2 bytes), width (2 bytes), height (2 bytes) */
119 char image_block_canvas[] = { 0, 0, 0, 0, 96, 0, 64, 0};
120
121 image_block_canvas[4] = width;
122 image_block_canvas[5] = (width >> 8) ;
123 image_block_canvas[6] = height;
124 image_block_canvas[7] = (height >> 8);
125 /* Flag */
126 char image_block_flag[] = { 0x00 };
127
128 fwrite(image_block_header, 1, 1, fp);
129 fwrite(image_block_canvas, 8, 1, fp);
130 fwrite(image_block_flag, 1, 1, fp);
131
132 }
133
write_image_block_end(FILE * fp)134 static void write_image_block_end(FILE *fp) {
135
136 /* Give an end to the image block */
137 char image_block_end[1] = {0x00};
138
139 fwrite(image_block_end, 1, 1,fp);
140 }
141
write_comment(FILE * fp)142 static void write_comment(FILE* fp) {
143
144 char comment[] = {0x21, 0xfe, 8, 'T', 'i', 'l', 'E', 'm', '2', 0, 0, 0};
145 fwrite(comment, 12, 1, fp);
146 }
147
write_application_extension(FILE * fp)148 static void write_application_extension(FILE * fp) {
149
150 /* Magic number to start the block */
151 char application_extension_magic_number[] = { 0x21, 0xff, 0x0b };
152 /* Application name */
153 char application_extension_application_name[] = { 'N', 'E', 'T', 'S', 'C', 'A', 'P', 'E', '2', '.', '0' };
154 /* magic number */
155 char application_extension_data_follow[] = { 0x03, 0x01 };
156 /* 0 to 65535 loop */
157 char application_extension_number_of_loop[] = { 0xff, 0xff};
158 /* the end of the block */
159 char application_extension_terminator[] = { 0x00 };
160
161 //char gif_infos[31] = {
162 //0x21, 0xff, 0x0b, 'N', 'E', 'T', 'S', 'C', 'A', 'P', 'E', '2', '.', '0', 3, 1, 0xff, 0xff, 0x00 };
163
164 //fwrite(gif_infos, 19, 1, fp);
165 fwrite(application_extension_magic_number, 3, 1, fp);
166 fwrite(application_extension_application_name, 11, 1, fp);
167 fwrite(application_extension_data_follow, 2, 1, fp);
168 fwrite(application_extension_number_of_loop, 2, 1, fp);
169 fwrite(application_extension_terminator, 1, 1, fp);
170 }
171
172 /* Apparently, most current web browsers are seriously and
173 deliberately broken in their handling of animated GIFs. Internet
174 Explorer does not allow any frame to be shorter than 60 ms, and
175 Gecko does not allow any frame shorter than 20 ms. Furthermore,
176 rather than simply imposing a lower limit, or skipping frames,
177 these browsers take any frame they deem "too short" and extend it
178 to a full 100 ms out of sheer spite.
179
180 If we want animations to look correct in all web browsers (which
181 is, after all, the main reason for using GIF animations in the
182 first place), we have to limit ourselves to 60-ms frames or
183 longer. */
184 #define MIN_FRAME_DELAY 6
185
tilem_animation_write_gif(TilemAnimation * anim,byte * palette,int palette_size,FILE * fp)186 void tilem_animation_write_gif(TilemAnimation *anim, byte* palette, int palette_size, FILE *fp)
187 {
188 GdkPixbufAnimation *ganim;
189 int width, height, delay, n;
190 gdouble time_stretch, t;
191 byte *image;
192 TilemAnimFrame *frm, *next;
193 gboolean is_static;
194
195 g_return_if_fail(TILEM_IS_ANIMATION(anim));
196 g_return_if_fail(fp != NULL);
197
198 ganim = GDK_PIXBUF_ANIMATION(anim);
199 width = gdk_pixbuf_animation_get_width(ganim);
200 height = gdk_pixbuf_animation_get_height(ganim);
201 is_static = gdk_pixbuf_animation_is_static_image(ganim);
202 time_stretch = 1.0 / tilem_animation_get_speed(anim);
203
204 frm = tilem_animation_next_frame(anim, NULL);
205 g_return_if_fail(frm != NULL);
206
207 write_global_header(fp, width, height, palette, palette_size);
208
209 if (!is_static)
210 write_application_extension(fp);
211
212 write_comment(fp);
213
214 t = MIN_FRAME_DELAY * 5.0;
215
216 /* FIXME: combine multiple frames by averaging rather than
217 simply taking the last one */
218
219 while (frm) {
220 next = tilem_animation_next_frame(anim, frm);
221
222 if (!is_static) {
223 delay = tilem_anim_frame_get_duration(frm);
224 t += delay * time_stretch;
225 n = t / 10.0;
226
227 if (n < MIN_FRAME_DELAY && next != NULL) {
228 frm = next;
229 continue;
230 }
231
232 t -= n * 10.0;
233 if (n > 0xffff)
234 n = 0xffff;
235 else if (n < MIN_FRAME_DELAY)
236 n = MIN_FRAME_DELAY;
237 write_extension_block(fp, n);
238 }
239
240 tilem_animation_get_indexed_image(anim, frm, &image,
241 &width, &height);
242 write_image_block_start(fp, width, height);
243 GifEncode(fp, image, 8, width * height);
244 write_image_block_end(fp);
245 g_free(image);
246
247 frm = next;
248 }
249
250 write_global_footer(fp);
251 }
252