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