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