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