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