1 /* === S Y N F I G ========================================================= */
2 /*!	\file trgt_gif.cpp
3 **	\brief BMP Target Module
4 **
5 **	$Id$
6 **
7 **	\legal
8 **	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
9 **	Copyright (c) 2007 Chris Moore
10 **
11 **	This package is free software; you can redistribute it and/or
12 **	modify it under the terms of the GNU General Public License as
13 **	published by the Free Software Foundation; either version 2 of
14 **	the License, or (at your option) any later version.
15 **
16 **	This package is distributed in the hope that it will be useful,
17 **	but WITHOUT ANY WARRANTY; without even the implied warranty of
18 **	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 **	General Public License for more details.
20 **	\endlegal
21 **
22 ** === N O T E S ===========================================================
23 **
24 ** ========================================================================= */
25 
26 /* === H E A D E R S ======================================================= */
27 
28 #ifdef USING_PCH
29 #	include "pch.h"
30 #else
31 #ifdef HAVE_CONFIG_H
32 #	include <config.h>
33 #endif
34 
35 #include <synfig/localization.h>
36 #include <synfig/general.h>
37 
38 #include <ETL/stringf>
39 #include "trgt_gif.h"
40 #include <cstdio>
41 #endif
42 
43 /* === M A C R O S ========================================================= */
44 
45 using namespace synfig;
46 using namespace std;
47 using namespace etl;
48 
49 #define MAX_FRAME_RATE	(20.0)
50 
51 /* === G L O B A L S ======================================================= */
52 
53 SYNFIG_TARGET_INIT(gif);
54 SYNFIG_TARGET_SET_NAME(gif,"gif");
55 SYNFIG_TARGET_SET_EXT(gif,"gif");
56 SYNFIG_TARGET_SET_VERSION(gif,"0.1");
57 SYNFIG_TARGET_SET_CVS_ID(gif,"$Id$");
58 
59 /* === M E T H O D S ======================================================= */
60 
gif(const char * filename_,const synfig::TargetParam &)61 gif::gif(const char *filename_, const synfig::TargetParam & /* params */):
62 	bs(),
63 	filename(filename_),
64 	file( (filename=="-")?stdout:fopen(filename_,POPEN_BINARY_WRITE_TYPE) ),
65 	i(),
66 	codesize(),
67 	rootsize(),
68 	nextcode(),
69 	table(NULL),
70 	next(NULL),
71 	node(NULL),
72 	imagecount(0),
73 	cur_scanline(),
74 	lossy(true),
75 	multi_image(false),
76 	dithering(true),
77 	color_bits(8),
78 	iframe_density(30),
79 	loop_count(0x7fff),
80 	local_palette(true)
81 { }
82 
~gif()83 gif::~gif()
84 {
85 	if(file)
86 		fputc(';',file.get());	// Image terminator
87 }
88 
89 bool
set_rend_desc(RendDesc * given_desc)90 gif::set_rend_desc(RendDesc *given_desc)
91 {
92 	if(given_desc->get_frame_rate()>MAX_FRAME_RATE)
93 		given_desc->set_frame_rate(MAX_FRAME_RATE);
94 
95 	desc=*given_desc;
96 
97 	if(desc.get_frame_end()-desc.get_frame_start()>0)
98 	{
99 		multi_image=true;
100 		//set_remove_alpha();
101 		imagecount=desc.get_frame_end()-desc.get_frame_start();
102 	}
103 	else
104 		multi_image=false;
105 	return true;
106 }
107 
108 bool
init(synfig::ProgressCallback *)109 gif::init(synfig::ProgressCallback * /* cb */)
110 {
111 	int w=desc.get_w(),h=desc.get_h();
112 
113 	if(!file)
114 	{
115 		synfig::error(strprintf(_("Unable to open \"%s\" for write access!"),filename.c_str()));
116 		return false;
117 	}
118 
119 	rootsize=color_bits;	// Size of pixel bits
120 
121 	curr_frame.set_wh(w,h);
122 	prev_frame.set_wh(w,h);
123 	curr_surface.set_wh(w,h);
124 	curr_frame.clear();
125 	prev_frame.clear();
126 	curr_surface.clear();
127 
128 	if(get_quality()>5)
129 		lossy=true;
130 	else
131 		lossy=false;
132 
133 	// Output the header
134 	fprintf(file.get(),"GIF89a");
135 	fputc(w&0x000000ff,file.get());
136 	fputc((w&0x0000ff00)>>8,file.get());
137 	fputc(h&0x000000ff,file.get());
138 	fputc((h&0x0000ff00)>>8,file.get());
139 	if(!local_palette)
140 		fputc(0xF0+(rootsize-1),file.get());	// flags
141 	else
142 		fputc((0xF0+(rootsize-1))&~(1<<7),file.get());	// flags
143 
144 	fputc(0,file.get());		// background color
145 	fputc(0,file.get());		// Pixel Aspect Ratio
146 
147 	if(!local_palette)
148 	{
149 		curr_palette=Palette::grayscale(256/(1<<(8-rootsize))-1);
150 		output_curr_palette();
151 	}
152 
153 	if(loop_count && multi_image)
154 	{
155 		fputc(33,file.get()); // 33 (hex 0x21) GIF Extension code
156 		fputc(255,file.get()); // 255 (hex 0xFF) Application Extension Label
157 		fputc(11,file.get()); // 11 (hex (0x0B) Length of Application Block
158 		fprintf(file.get(),"NETSCAPE2.0");
159 		fputc(3,file.get()); // 3 (hex 0x03) Length of Data Sub-Block
160 		fputc(1,file.get()); // 1 (hex 0x01)
161 		fputc(loop_count&0x000000ff,file.get());
162 		fputc((loop_count&0x0000ff00)>>8,file.get());
163 		fputc(0,file.get()); // 0 (hex 0x00) a Data Sub-block Terminator.
164 	}
165 
166 	return true;
167 }
168 
169 void
output_curr_palette()170 gif::output_curr_palette()
171 {
172 	// Output the color table
173 	for(i=0;i<256/(1<<(8-rootsize));i++)
174 	{
175 		if(i<(signed)curr_palette.size())
176 		{
177 			Color color(curr_palette[i].color.clamped());
178 			//fputc(i*(1<<(8-rootsize)),file.get());
179 			//fputc(i*(1<<(8-rootsize)),file.get());
180 			//fputc(i*(1<<(8-rootsize)),file.get());
181 			fputc(gamma().r_F32_to_U8(color.get_r()),file.get());
182 			fputc(gamma().g_F32_to_U8(color.get_g()),file.get());
183 			fputc(gamma().b_F32_to_U8(color.get_b()),file.get());
184 		}
185 		else
186 		{
187 			fputc(255,file.get());
188 			fputc(0,file.get());
189 			fputc(255,file.get());
190 		}
191 	}
192 }
193 
194 bool
start_frame(synfig::ProgressCallback * callback)195 gif::start_frame(synfig::ProgressCallback *callback)
196 {
197 //	int
198 //		w=desc.get_w(),
199 //		h=desc.get_h();
200 
201 	if(!file)
202 	{
203 		if(callback)callback->error(string("BUG:")+_("Description not set!"));
204 		return false;
205 	}
206 
207 	if(callback)callback->task(filename+strprintf(" %d",imagecount));
208 
209 
210 
211 	return true;
212 }
213 
214 void
end_frame()215 gif::end_frame()
216 {
217 	int w=desc.get_w(),h=desc.get_h(),i;
218 	unsigned int value;
219 	int
220 		delaytime=round_to_int(100.0/desc.get_frame_rate());
221 
222 	bool build_off_previous(multi_image);
223 
224 	Palette prev_palette(curr_palette);
225 
226 	// Fill in the background color
227 	if(get_alpha_mode()==TARGET_ALPHA_MODE_KEEP)
228 	{
229 		Surface::alpha_pen pen(curr_surface.begin(),1.0,Color::BLEND_BEHIND);
230 		pen.set_value(get_canvas()->rend_desc().get_bg_color());
231 		for(int y=0;y<curr_surface.get_h();y++,pen.inc_y())
232 		{
233 			int x;
234 			for(x=0;x<curr_surface.get_w();x++,pen.inc_x())
235 			{
236 				if(pen.get_value().get_a()>0.1)
237 					pen.put_value();
238 				else
239 					pen[0][0]=Color::alpha();
240 			}
241 			pen.dec_x(x);
242 		}
243 	}
244 
245 	if(local_palette)
246 	{
247 		curr_palette=Palette(curr_surface,256/(1<<(8-rootsize))-build_off_previous-1);
248 		synfig::info("curr_palette.size()=%d",curr_palette.size());
249 	}
250 
251 	int transparent_index(curr_palette.find_closest(Color(1,0,1,0))-curr_palette.begin());
252 	bool has_transparency(curr_palette[transparent_index].color.get_a()<=0.00001);
253 
254 	if(has_transparency)
255 		build_off_previous=false;
256 
257 	if(build_off_previous)
258 	{
259 		transparent_index=0;
260 		has_transparency=true;
261 	}
262 
263 #define DISPOSE_UNDEFINED			(0)
264 #define DISPOSE_NONE				(1<<2)
265 #define DISPOSE_RESTORE_BGCOLOR		(2<<2)
266 #define DISPOSE_RESTORE_PREVIOUS	(3<<2)
267 	int gec_flags(0);
268 	if(build_off_previous)
269 		gec_flags|=DISPOSE_NONE;
270 	else
271 		gec_flags|=DISPOSE_RESTORE_PREVIOUS;
272 	if(has_transparency)
273 		gec_flags|=1;
274 
275 	// output the Graphic Control Extension
276 	fputc(0x21,file.get()); // Extension introducer
277 	fputc(0xF9,file.get()); // Graphic Control Label
278 	fputc(4,file.get()); // Block Size
279 	fputc(gec_flags,file.get()); // Flags (Packed Fields)
280 	fputc(delaytime&0x000000ff,file.get()); // Delay Time (MSB)
281 	fputc((delaytime&0x0000ff00)>>8,file.get()); // Delay Time (LSB)
282 	fputc(transparent_index,file.get()); // Transparent Color Index
283 	fputc(0,file.get()); // Block Terminator
284 
285 	// output the image header
286 	fputc(',',file.get());
287 	fputc(0,file.get());	// image left
288 	fputc(0,file.get());	// image left
289 	fputc(0,file.get());	// image top
290 	fputc(0,file.get());	// image top
291 	fputc(w&0x000000ff,file.get());
292 	fputc((w&0x0000ff00)>>8,file.get());
293 	fputc(h&0x000000ff,file.get());
294 	fputc((h&0x0000ff00)>>8,file.get());
295 	if(local_palette)
296 		fputc(0x80|(rootsize-1),file.get());	// flags
297 	else
298 		fputc(0x00+ rootsize-1,file.get());	// flags
299 
300 
301 	if(local_palette)
302 	{
303 		Palette out(curr_palette);
304 
305 		if(build_off_previous)
306 			curr_palette.insert(curr_palette.begin(),Color(1,0,1,0));
307 		output_curr_palette();
308 		curr_palette=out;
309 	}
310 
311 	bs=bitstream(file);
312 
313 	// Prepare ourselves for LZW compression
314 	codesize=rootsize+1;
315 	nextcode=(1<<rootsize)+2;
316 	table=lzwcode::NewTable((1<<rootsize));
317 	node=table;
318 
319 	// Output the rootsize
320 	fputc(rootsize,file.get());	// rootsize;
321 
322 	// Push a table reset into the bitstream
323 	bs.push_value(1<<rootsize,codesize);
324 
325 	for(int cur_scanline=0;cur_scanline<desc.get_h();cur_scanline++)
326 	{
327 		//convert_color_format(curr_frame[cur_scanline], curr_surface[cur_scanline], desc.get_w(), PF_GRAY, gamma());
328 
329 		// Now we compress it!
330 		for(i=0;i<w;i++)
331 		{
332 			Color color(curr_surface[cur_scanline][i].clamped());
333 			Palette::iterator iter(curr_palette.find_closest(color));
334 
335 			if(dithering)
336 			{
337 				Color error(color-iter->color);
338 				//error*=0.25;
339 				if(curr_surface.get_h()>cur_scanline+1)
340 				{
341 					curr_surface[cur_scanline+1][i-1]  += error * ((float)3/(float)16);
342 					curr_surface[cur_scanline+1][i]    += error * ((float)5/(float)16);
343 					if(curr_surface.get_w()>i+1)
344 						curr_surface[cur_scanline+1][i+1]  += error * ((float)1/(float)16);
345 				}
346 				if(curr_surface.get_w()>i+1)
347 					curr_surface[cur_scanline][i+1]    += error * ((float)7/(float)16);
348 			}
349 
350 			curr_frame[cur_scanline][i]=iter-curr_palette.begin();
351 
352 			value=curr_frame[cur_scanline][i];
353 			if(build_off_previous)
354 				value++;
355 			if(value>(unsigned)(1<<rootsize)-1)
356 				value=(1<<rootsize)-1;
357 
358 			// If the pixel is the same as the one that
359 			// is already there, then we should make it
360 			// transparent
361 			if(build_off_previous)
362 			{
363 				if(lossy)
364 				{
365 
366 					// Lossy
367 					if(
368 						abs( ( iter->color-prev_palette[prev_frame[cur_scanline][i]-1].color ).get_y() ) > (1.0/16.0) ||
369 //						abs((int)value-(int)prev_frame[cur_scanline][i])>2||
370 //						(value<=2 && value!=prev_frame[cur_scanline][i]) ||
371 						(imagecount%iframe_density)==0 || imagecount==desc.get_frame_end()-1 ) // lossy version
372 						prev_frame[cur_scanline][i]=value;
373 					else
374 					{
375 						prev_frame[cur_scanline][i]=value;
376 						value=0;
377 					}
378 				}
379 				else
380 				{
381 					// lossless version
382 					if(value!=prev_frame[cur_scanline][i])
383 						prev_frame[cur_scanline][i]=value;
384 					else
385 						value=0;
386 				}
387 			}
388 			else
389 			prev_frame[cur_scanline][i]=value;
390 
391 			next=node->FindCode(value);
392 			if(next)
393 				node=next;
394 			else
395 			{
396 				node->AddNode(nextcode, value);
397 				bs.push_value(node->code, codesize);
398 				node = table->FindCode(value);
399 
400 				// Check to see if we need to increase the codesize
401 				if (nextcode == ( 1 << codesize))
402 					codesize += 1;
403 
404 				nextcode += 1;
405 
406 				// check to see if we have filled up the table
407 				if (nextcode == 4096)
408 				{
409 					// output the clear code: make sure to use the current
410 					// codesize
411 					bs.push_value((unsigned) 1 << rootsize, codesize);
412 
413 					delete table;
414 					table = lzwcode::NewTable((1<<rootsize));
415 					codesize = rootsize + 1;
416 					nextcode = (1 << rootsize) + 2;
417 
418 					// since we have a new table, need the correct prefix
419 					node = table->FindCode(value);
420 				}
421 			}
422 		}
423 	}
424 
425 
426 
427 
428 
429 	// Push the last code onto the bitstream
430 	bs.push_value(node->code,codesize);
431 
432 	// Push a end-of-stream code onto the bitstream
433 	bs.push_value((1<<rootsize)+1,codesize);
434 
435 	// Make sure everything is dumped out
436 	bs.dump();
437 
438 	delete table;
439 
440 	fputc(0,file.get());		// Block terminator
441 
442 	fflush(file.get());
443 	imagecount++;
444 }
445 
446 synfig::Color*
start_scanline(int scanline)447 gif::start_scanline(int scanline)
448 {
449 	cur_scanline=scanline;
450 	return curr_surface[scanline];
451 }
452 
453 bool
end_scanline()454 gif::end_scanline()
455 {
456 	if(!file)
457 		return false;
458 
459 //	int w=desc.get_w(),i;
460 //	unsigned int value;
461 
462 
463 	return true;
464 }
465