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