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