Added support for writing a long comment to a GIF image.
[wxWidgets.git] / src / common / imaggif.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/common/imaggif.cpp
3 // Purpose: wxGIFHandler
4 // Author: Vaclav Slavik, Guillermo Rodriguez Garcia, Gershon Elber, Troels K
5 // RCS-ID: $Id$
6 // Copyright: (c) 1999-2011 Vaclav Slavik, Guillermo Rodriguez Garcia, Gershon Elber, Troels K
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
9
10 // For compilers that support precompilation, includes "wx.h".
11 #include "wx/wxprec.h"
12
13 #ifdef __BORLANDC__
14 #pragma hdrstop
15 #endif
16
17 #if wxUSE_IMAGE && wxUSE_GIF
18
19 #ifndef WX_PRECOMP
20 #include "wx/intl.h"
21 #include "wx/log.h"
22 #include "wx/palette.h"
23 #include "wx/utils.h"
24 #endif
25
26 #include "wx/imaggif.h"
27 #include "wx/gifdecod.h"
28 #include "wx/stream.h"
29 #include "wx/anidecod.h" // wxImageArray
30
31 #define GIF89_HDR "GIF89a"
32 #define NETSCAPE_LOOP "NETSCAPE2.0"
33
34 // see members.aol.com/royalef/gifabout.htm
35 // members.aol.com/royalef/gif89a.txt
36
37 enum
38 {
39 GIF_MARKER_EXT = '!', // 0x21
40 GIF_MARKER_SEP = ',', // 0x2C
41 GIF_MARKER_ENDOFDATA = ';', // 0x3B
42
43 GIF_MARKER_EXT_GRAPHICS_CONTROL = 0xF9,
44 GIF_MARKER_EXT_COMMENT = 0xFE,
45 GIF_MARKER_EXT_APP = 0xFF
46 };
47
48 #define LZ_MAX_CODE 4095 // Biggest code possible in 12 bits.
49 #define FLUSH_OUTPUT 4096 // Impossible code, to signal flush.
50 #define FIRST_CODE 4097 // Impossible code, to signal first.
51
52 #define HT_SIZE 8192 // 12bits = 4096 or twice as big!
53 #define HT_KEY_MASK 0x1FFF // 13bits keys
54
55 #define HT_GET_KEY(l) (l >> 12)
56 #define HT_GET_CODE(l) (l & 0x0FFF)
57 #define HT_PUT_KEY(l) (l << 12)
58 #define HT_PUT_CODE(l) (l & 0x0FFF)
59
60 struct wxRGB
61 {
62 wxUint8 red;
63 wxUint8 green;
64 wxUint8 blue;
65 };
66
67 struct GifHashTableType
68 {
69 wxUint32 HTable[HT_SIZE];
70 };
71
72 IMPLEMENT_DYNAMIC_CLASS(wxGIFHandler,wxImageHandler)
73
74 //----------------------------------------------------------------------------
75 // Forward declarations
76 //----------------------------------------------------------------------------
77
78 static int wxGIFHandler_KeyItem(unsigned long item);
79
80 #if wxUSE_STREAMS
81
82 static int wxGIFHandler_BitSize(int n);
83
84 #if wxUSE_PALETTE
85 static bool wxGIFHandler_GetPalette(const wxImage& image,
86 wxRGB *pal, int *palCount, int *mask_index);
87 #endif
88 static
89 int wxGIFHandler_PaletteFind(const wxRGB& clr, const wxRGB *array, int count);
90
91 static bool wxGIFHandler_Write(wxOutputStream *, const void *buf, size_t len);
92 static bool wxGIFHandler_WriteByte(wxOutputStream *, wxUint8);
93 static bool wxGIFHandler_WriteWord(wxOutputStream *, wxUint16);
94 static bool wxGIFHandler_WriteHeader(wxOutputStream *, int width, int height,
95 bool loop, const wxRGB *pal, int palCount,
96 const wxString& comment = wxEmptyString);
97 static bool wxGIFHandler_WriteRect(wxOutputStream *, int width, int height);
98 #if wxUSE_PALETTE
99 static bool wxGIFHandler_WriteTerm(wxOutputStream *);
100 #endif
101 static bool wxGIFHandler_WriteZero(wxOutputStream *);
102 static bool wxGIFHandler_WritePalette(wxOutputStream *,
103 const wxRGB *pal, size_t palCount, int bpp);
104 static bool wxGIFHandler_WriteControl(wxOutputStream *,
105 int maskIndex, int delayMilliSecs);
106 static bool wxGIFHandler_WriteComment(wxOutputStream *, const wxString&);
107 static bool wxGIFHandler_WriteLoop(wxOutputStream *);
108
109 static bool wxGIFHandler_BufferedOutput(wxOutputStream *, wxUint8 *buf, int c);
110 #endif // wxUSE_STREAMS
111
112 //-----------------------------------------------------------------------------
113 // wxGIFHandler
114 //-----------------------------------------------------------------------------
115
116 #if wxUSE_STREAMS
117
118 bool wxGIFHandler::LoadFile(wxImage *image, wxInputStream& stream,
119 bool verbose, int index)
120 {
121 wxGIFDecoder *decod;
122 wxGIFErrorCode error;
123 bool ok = true;
124
125 // image->Destroy();
126 decod = new wxGIFDecoder();
127 error = decod->LoadGIF(stream);
128
129 if ((error != wxGIF_OK) && (error != wxGIF_TRUNCATED))
130 {
131 if (verbose)
132 {
133 switch (error)
134 {
135 case wxGIF_INVFORMAT:
136 wxLogError(_("GIF: error in GIF image format."));
137 break;
138 case wxGIF_MEMERR:
139 wxLogError(_("GIF: not enough memory."));
140 break;
141 default:
142 wxLogError(_("GIF: unknown error!!!"));
143 break;
144 }
145 }
146 delete decod;
147 return false;
148 }
149
150 if ((error == wxGIF_TRUNCATED) && verbose)
151 {
152 wxLogError(_("GIF: data stream seems to be truncated."));
153 // go on; image data is OK
154 }
155
156 if (ok)
157 {
158 ok = decod->ConvertToImage(index != -1 ? (size_t)index : 0, image);
159 }
160 else
161 {
162 wxLogError(_("GIF: Invalid gif index."));
163 }
164
165 delete decod;
166
167 return ok;
168 }
169
170 bool wxGIFHandler::SaveFile(wxImage *image,
171 wxOutputStream& stream, bool verbose)
172 {
173 #if wxUSE_PALETTE
174 wxRGB pal[256];
175 int palCount;
176 int maskIndex;
177
178 return wxGIFHandler_GetPalette(*image, pal, &palCount, &maskIndex)
179 && DoSaveFile(*image, &stream, verbose, true /*first?*/, 0,
180 false /*loop?*/, pal, palCount, maskIndex)
181 && wxGIFHandler_WriteTerm(&stream);
182 #else
183 wxUnusedVar(image);
184 wxUnusedVar(stream);
185 wxUnusedVar(verbose);
186 return false;
187 #endif
188 }
189
190 bool wxGIFHandler::DoCanRead( wxInputStream& stream )
191 {
192 wxGIFDecoder decod;
193 return decod.CanRead(stream);
194 // it's ok to modify the stream position here
195 }
196
197 int wxGIFHandler::DoGetImageCount( wxInputStream& stream )
198 {
199 wxGIFDecoder decod;
200 wxGIFErrorCode error = decod.LoadGIF(stream);
201 if ( (error != wxGIF_OK) && (error != wxGIF_TRUNCATED) )
202 return -1;
203
204 // NOTE: this function modifies the current stream position but it's ok
205 // (see wxImageHandler::GetImageCount)
206
207 return decod.GetFrameCount();
208 }
209
210 bool wxGIFHandler::DoSaveFile(const wxImage& image, wxOutputStream *stream,
211 bool WXUNUSED(verbose), bool first, int delayMilliSecs, bool loop,
212 const wxRGB *pal, int palCount, int maskIndex)
213 {
214 const unsigned long colorcount = image.CountColours(256+1);
215 bool ok = colorcount && (colorcount <= 256);
216 if (!ok)
217 {
218 return false;
219 }
220
221 int width = image.GetWidth();
222 int height = image.GetHeight();
223 int width_even = width + ((width % 2) ? 1 : 0);
224
225 if (first)
226 {
227 ok = wxGIFHandler_WriteHeader(stream, width, height, loop,
228 pal, palCount, image.GetOption(wxIMAGE_OPTION_GIF_COMMENT));
229 }
230
231 ok = ok && wxGIFHandler_WriteControl(stream, maskIndex, delayMilliSecs)
232 && wxGIFHandler_WriteByte(stream, GIF_MARKER_SEP)
233 && wxGIFHandler_WriteRect(stream, width, height);
234
235 // local palette
236 if (first)
237 {
238 // we already saved the (global) palette
239 ok = ok && wxGIFHandler_WriteZero(stream);
240 }
241 else
242 {
243 const int bpp = wxGIFHandler_BitSize(palCount);
244 wxUint8 b;
245
246 b = 0x80;
247 b |=(bpp - 1) << 5;
248 b |=(bpp - 1);
249 b &=~0x40; // clear interlaced
250
251 ok = ok && wxGIFHandler_WriteByte(stream, b)
252 && wxGIFHandler_WritePalette(stream, pal, palCount, bpp);
253 }
254
255 if (!ok)
256 {
257 return false;
258 }
259
260 if (!InitHashTable())
261 {
262 wxLogError(_("Couldn't initialize GIF hash table."));
263 return false;
264 }
265
266 const wxUint8 *src = image.GetData();
267 wxUint8 *eightBitData = new wxUint8[width];
268
269 SetupCompress(stream, 8);
270
271 m_pixelCount = height * width_even;
272 for (int y = 0; y < height; y++)
273 {
274 m_pixelCount -= width_even;
275 for (int x = 0; x < width; x++)
276 {
277 wxRGB rgb;
278 rgb.red = src[0];
279 rgb.green = src[1];
280 rgb.blue = src[2];
281 int index = wxGIFHandler_PaletteFind(rgb, pal, palCount);
282 wxASSERT(index != wxNOT_FOUND);
283 eightBitData[x] = (wxUint8)index;
284 src+=3;
285 }
286
287 ok = CompressLine(stream, eightBitData, width);
288 if (!ok)
289 {
290 break;
291 }
292 }
293
294 delete [] eightBitData;
295
296 wxDELETE(m_hashTable);
297
298 return ok;
299 }
300
301 bool wxGIFHandler::SaveAnimation(const wxImageArray& images,
302 wxOutputStream *stream, bool verbose, int delayMilliSecs)
303 {
304 #if wxUSE_PALETTE
305 bool ok = true;
306 size_t i;
307
308 wxSize size(0,0);
309 for (i = 0; (i < images.GetCount()) && ok; i++)
310 {
311 const wxImage& image = images.Item(i);
312 wxSize temp(image.GetWidth(), image.GetHeight());
313 ok = ok && image.HasPalette();
314 if (i)
315 {
316 ok = ok && (size == temp);
317 }
318 else
319 {
320 size = temp;
321 }
322 }
323
324 for (i = 0; (i < images.GetCount()) && ok; i++)
325 {
326 const wxImage& image = images.Item(i);
327
328 wxRGB pal[256];
329 int palCount;
330 int maskIndex;
331
332 ok = wxGIFHandler_GetPalette(image, pal, &palCount, &maskIndex)
333 && DoSaveFile(image, stream, verbose, i == 0 /*first?*/, delayMilliSecs,
334 true /*loop?*/, pal, palCount, maskIndex);
335 }
336
337 return ok && wxGIFHandler_WriteTerm(stream);
338 #else
339 wxUnusedVar(images);
340 wxUnusedVar(stream);
341 wxUnusedVar(verbose);
342 wxUnusedVar(delayMilliSecs);
343
344 return false;
345 #endif
346 }
347
348 bool wxGIFHandler::CompressOutput(wxOutputStream *stream, int code)
349 {
350 if (code == FLUSH_OUTPUT)
351 {
352 while (m_crntShiftState > 0)
353 {
354 // Get rid of what is left in DWord, and flush it.
355 if (!wxGIFHandler_BufferedOutput(stream, m_LZBuf,
356 m_crntShiftDWord & 0xff))
357 {
358 return false;
359 }
360 m_crntShiftDWord >>= 8;
361 m_crntShiftState -= 8;
362 }
363 m_crntShiftState = 0; // For next time.
364 if (!wxGIFHandler_BufferedOutput(stream, m_LZBuf, FLUSH_OUTPUT))
365 {
366 return false;
367 }
368 }
369 else
370 {
371 m_crntShiftDWord |= ((long) code) << m_crntShiftState;
372 m_crntShiftState += m_runningBits;
373 while (m_crntShiftState >= 8)
374 {
375 // Dump out full bytes:
376 if (!wxGIFHandler_BufferedOutput(stream, m_LZBuf,
377 m_crntShiftDWord & 0xff))
378 {
379 return false;
380 }
381 m_crntShiftDWord >>= 8;
382 m_crntShiftState -= 8;
383 }
384 }
385
386 // If code can't fit into RunningBits bits, must raise its size. Note
387 // however that codes above LZ_MAX_CODE are used for special signaling.
388 if ( (m_runningCode >= m_maxCode1) && (code <= LZ_MAX_CODE))
389 {
390 m_maxCode1 = 1 << ++m_runningBits;
391 }
392 return true;
393 }
394
395 bool wxGIFHandler::SetupCompress(wxOutputStream *stream, int bpp)
396 {
397 m_LZBuf[0] = 0; // Nothing was output yet.
398 m_clearCode = (1 << bpp);
399 m_EOFCode = m_clearCode + 1;
400 m_runningCode = m_EOFCode + 1;
401 m_runningBits = bpp + 1; // Number of bits per code.
402 m_maxCode1 = 1 << m_runningBits; // Max. code + 1.
403 m_crntCode = FIRST_CODE; // Signal that this is first one!
404 m_crntShiftState = 0; // No information in CrntShiftDWord.
405 m_crntShiftDWord = 0;
406
407 // Clear hash table and send Clear to make sure the decoder does the same.
408 ClearHashTable();
409
410 return wxGIFHandler_WriteByte(stream, (wxUint8)bpp)
411 && CompressOutput(stream, m_clearCode);
412 }
413
414 bool wxGIFHandler::CompressLine(wxOutputStream *stream,
415 const wxUint8 *line, int lineLen)
416 {
417 int i = 0, crntCode, newCode;
418 unsigned long newKey;
419 wxUint8 pixel;
420 if (m_crntCode == FIRST_CODE) // It's first time!
421 crntCode = line[i++];
422 else
423 crntCode = m_crntCode; // Get last code in compression.
424
425 while (i < lineLen)
426 {
427 // Decode lineLen items.
428 pixel = line[i++]; // Get next pixel from stream.
429 // Form a new unique key to search hash table for the code combines
430 // crntCode as Prefix string with Pixel as postfix char.
431 newKey = (((unsigned long) crntCode) << 8) + pixel;
432 if ((newCode = ExistsHashTable(newKey)) >= 0)
433 {
434 // This Key is already there, or the string is old one, so
435 // simply take new code as our crntCode:
436 crntCode = newCode;
437 }
438 else
439 {
440 // Put it in hash table, output the prefix code, and make our
441 // crntCode equal to Pixel.
442 if (!CompressOutput(stream, crntCode))
443 {
444 return false;
445 }
446
447 crntCode = pixel;
448
449 // If however the HashTable is full, we send a clear first and
450 // Clear the hash table.
451 if (m_runningCode >= LZ_MAX_CODE)
452 {
453 // Time to do some clearance:
454 if (!CompressOutput(stream, m_clearCode))
455 {
456 return false;
457 }
458
459 m_runningCode = m_EOFCode + 1;
460 m_runningBits = 8 + 1;
461 m_maxCode1 = 1 << m_runningBits;
462 ClearHashTable();
463 }
464 else
465 {
466 // Put this unique key with its relative Code in hash table:
467 InsertHashTable(newKey, m_runningCode++);
468 }
469 }
470 }
471 // Preserve the current state of the compression algorithm:
472 m_crntCode = crntCode;
473 if (m_pixelCount == 0)
474 {
475 // We are done - output last Code and flush output buffers:
476 if (!CompressOutput(stream, crntCode)
477 || !CompressOutput(stream, m_EOFCode)
478 || !CompressOutput(stream, FLUSH_OUTPUT))
479 {
480 return false;
481 }
482 }
483
484 return true;
485 }
486
487 #endif // wxUSE_STREAMS
488
489 bool wxGIFHandler::InitHashTable()
490 {
491 if (!m_hashTable)
492 {
493 m_hashTable = new GifHashTableType();
494 }
495
496 if (!m_hashTable)
497 {
498 return false;
499 }
500
501 ClearHashTable();
502
503 return true;
504 }
505
506 void wxGIFHandler::ClearHashTable()
507 {
508 int index = HT_SIZE;
509 wxUint32 *HTable = m_hashTable->HTable;
510
511 while (--index>=0)
512 {
513 HTable[index] = 0xfffffffful;
514 }
515 }
516
517 void wxGIFHandler::InsertHashTable(unsigned long key, int code)
518 {
519 int hKey = wxGIFHandler_KeyItem(key);
520 wxUint32 *HTable = m_hashTable->HTable;
521
522 while (HT_GET_KEY(HTable[hKey]) != 0xFFFFFL)
523 {
524 hKey = (hKey + 1) & HT_KEY_MASK;
525 }
526 HTable[hKey] = HT_PUT_KEY(key) | HT_PUT_CODE(code);
527 }
528
529
530 int wxGIFHandler::ExistsHashTable(unsigned long key)
531 {
532 int hKey = wxGIFHandler_KeyItem(key);
533 wxUint32 *HTable = m_hashTable->HTable, HTKey;
534
535 while ((HTKey = HT_GET_KEY(HTable[hKey])) != 0xFFFFFL)
536 {
537 if (key == HTKey)
538 {
539 return HT_GET_CODE(HTable[hKey]);
540 }
541 hKey = (hKey + 1) & HT_KEY_MASK;
542 }
543 return -1;
544 }
545
546 // ---------------------------------------------------------------------------
547 // implementation of global private functions
548 // ---------------------------------------------------------------------------
549
550 int wxGIFHandler_KeyItem(unsigned long item)
551 {
552 return ((item >> 12) ^ item) & HT_KEY_MASK;
553 }
554
555 #if wxUSE_STREAMS
556
557 int wxGIFHandler_BitSize(int n)
558 {
559 int i;
560 for (i = 1; i <= 8; i++)
561 {
562 if ((1 << i) >= n)
563 {
564 break;
565 }
566 }
567 return i;
568 }
569
570 #if wxUSE_PALETTE
571 bool wxGIFHandler_GetPalette(const wxImage& image,
572 wxRGB *pal, int *pPalCount, int *pMaskIndex)
573 {
574 if (!image.HasPalette())
575 {
576 return false;
577 }
578
579 const wxPalette& palette = image.GetPalette();
580 int palCount = palette.GetColoursCount();
581
582 for (int i = 0; i < palCount; ++i)
583 {
584 if (!palette.GetRGB(i, &pal[i].red, &pal[i].green, &pal[i].blue))
585 {
586 break;
587 }
588 }
589 if (image.HasMask())
590 {
591 wxRGB mask;
592
593 mask.red = image.GetMaskRed();
594 mask.green = image.GetMaskGreen();
595 mask.blue = image.GetMaskBlue();
596 *pMaskIndex = wxGIFHandler_PaletteFind(mask, pal, palCount);
597 if ( (*pMaskIndex == wxNOT_FOUND) && (palCount < 256))
598 {
599 *pMaskIndex = palCount;
600 pal[palCount++] = mask;
601 }
602 }
603 else
604 {
605 *pMaskIndex = wxNOT_FOUND;
606 }
607 *pPalCount = palCount;
608
609 return true;
610 }
611 #endif // wxUSE_PALETTE
612
613 int wxGIFHandler_PaletteFind(const wxRGB& clr, const wxRGB *array, int count)
614 {
615 for (int i = 0; i < count; i++)
616 {
617 if ( (clr.red == array[i].red)
618 && (clr.green == array[i].green)
619 && (clr.blue == array[i].blue))
620 {
621 return i;
622 }
623 }
624
625 return wxNOT_FOUND;
626 }
627
628 bool wxGIFHandler_Write(wxOutputStream *stream, const void *buf, size_t len)
629 {
630 return (len == stream->Write(buf, len).LastWrite());
631 }
632
633 bool wxGIFHandler_WriteByte(wxOutputStream *stream, wxUint8 byte)
634 {
635 return wxGIFHandler_Write(stream, &byte, sizeof(byte));
636 }
637
638 bool wxGIFHandler_WriteWord(wxOutputStream *stream, wxUint16 word)
639 {
640 wxUint8 buf[2];
641
642 buf[0] = word & 0xff;
643 buf[1] = (word >> 8) & 0xff;
644 return wxGIFHandler_Write(stream, &word, sizeof(word));
645 }
646
647 bool wxGIFHandler_WriteHeader(wxOutputStream *stream, int width, int height,
648 bool loop, const wxRGB *pal, int palCount, const wxString& comment)
649 {
650 const int bpp = wxGIFHandler_BitSize(palCount);
651 wxUint8 buf[3];
652
653 bool ok = wxGIFHandler_Write(stream, GIF89_HDR, sizeof(GIF89_HDR)-1)
654 && wxGIFHandler_WriteWord(stream, (wxUint16) width)
655 && wxGIFHandler_WriteWord(stream, (wxUint16) height);
656
657 buf[0] = 0x80;
658 buf[0] |=(bpp - 1) << 5;
659 buf[0] |=(bpp - 1);
660 buf[1] = 0; // background color == entry 0
661 buf[2] = 0; // aspect ratio 1:1
662 ok = ok && wxGIFHandler_Write(stream, buf, sizeof(buf))
663 && wxGIFHandler_WritePalette(stream, pal, palCount, bpp);
664
665 if (loop)
666 {
667 ok = ok && wxGIFHandler_WriteLoop(stream);
668 }
669
670 return ok && wxGIFHandler_WriteComment(stream, comment);
671 }
672
673 bool wxGIFHandler_WriteRect(wxOutputStream *stream, int width, int height)
674 {
675 return wxGIFHandler_WriteWord(stream, 0) // left
676 && wxGIFHandler_WriteWord(stream, 0) // top
677 && wxGIFHandler_WriteWord(stream, (wxUint16) width)
678 && wxGIFHandler_WriteWord(stream, (wxUint16) height);
679 }
680
681 #if wxUSE_PALETTE
682 bool wxGIFHandler_WriteTerm(wxOutputStream *stream)
683 {
684 return wxGIFHandler_WriteByte(stream, GIF_MARKER_ENDOFDATA);
685 }
686 #endif
687
688 bool wxGIFHandler_WriteZero(wxOutputStream *stream)
689 {
690 return wxGIFHandler_WriteByte(stream, 0);
691 }
692
693 bool wxGIFHandler_WritePalette(wxOutputStream *stream,
694 const wxRGB *array, size_t count, int bpp)
695 {
696 wxUint8 buf[3];
697 for (int i = 0; (i < (1 << bpp)); i++)
698 {
699 if (i < (int)count)
700 {
701 buf[0] = array[i].red;
702 buf[1] = array[i].green;
703 buf[2] = array[i].blue;
704 }
705 else
706 {
707 buf[0] = buf[1] = buf[2] = 0;
708 }
709
710 if ( !wxGIFHandler_Write(stream, buf, sizeof(buf)) )
711 {
712 return false;
713 }
714 }
715
716 return true;
717 }
718
719 bool wxGIFHandler_WriteControl(wxOutputStream *stream,
720 int maskIndex, int delayMilliSecs)
721 {
722 wxUint8 buf[8];
723
724 buf[0] = GIF_MARKER_EXT; // extension marker
725 buf[1] = GIF_MARKER_EXT_GRAPHICS_CONTROL;
726 buf[2] = 4; // length of block
727 buf[3] = (maskIndex != wxNOT_FOUND) ? 1 : 0; // has transparency
728 buf[4] = delayMilliSecs / 10; // delay time
729 buf[5] = 0;
730 buf[6] = (maskIndex != wxNOT_FOUND) ? (wxUint8) maskIndex : 0;
731 buf[7] = 0;
732 return wxGIFHandler_Write(stream, buf, sizeof(buf));
733 }
734
735 bool wxGIFHandler_WriteComment(wxOutputStream *stream, const wxString& comment)
736 {
737 if ( comment.empty() )
738 {
739 return true;
740 }
741
742 // Write comment header.
743 wxUint8 buf[2];
744 buf[0] = GIF_MARKER_EXT;
745 buf[1] = GIF_MARKER_EXT_COMMENT;
746 if ( !wxGIFHandler_Write(stream, buf, sizeof(buf)) )
747 {
748 return false;
749 }
750
751 /*
752 If comment is longer than 255 bytes write it in blocks of maximum 255
753 bytes each.
754 */
755 wxCharBuffer text( comment.mb_str() );
756
757 size_t pos = 0, fullLength = text.length();
758
759 do
760 {
761 size_t blockLength = wxMin(fullLength - pos, 255);
762
763 if ( !wxGIFHandler_WriteByte(stream, (wxUint8) blockLength)
764 || !wxGIFHandler_Write(stream, &text.data()[pos], blockLength) )
765 {
766 return false;
767 }
768
769 pos += blockLength;
770 }while (pos < fullLength);
771
772
773 // Write comment footer.
774 return wxGIFHandler_WriteZero(stream);
775 }
776
777 bool wxGIFHandler_WriteLoop(wxOutputStream *stream)
778 {
779 wxUint8 buf[4];
780 const int loopcount = 0; // infinite
781
782 buf[0] = GIF_MARKER_EXT;
783 buf[1] = GIF_MARKER_EXT_APP;
784 buf[2] = 0x0B;
785 bool ok = wxGIFHandler_Write(stream, buf, 3)
786 && wxGIFHandler_Write(stream, NETSCAPE_LOOP, sizeof(NETSCAPE_LOOP)-1);
787
788 buf[0] = 3;
789 buf[1] = 1;
790 buf[2] = loopcount & 0xFF;
791 buf[3] = loopcount >> 8;
792
793 return ok && wxGIFHandler_Write(stream, buf, 4)
794 && wxGIFHandler_WriteZero(stream);
795 }
796
797 bool wxGIFHandler_BufferedOutput(wxOutputStream *stream, wxUint8 *buf, int c)
798 {
799 bool ok = true;
800
801 if (c == FLUSH_OUTPUT)
802 {
803 // Flush everything out.
804 if (buf[0])
805 {
806 ok = wxGIFHandler_Write(stream, buf, buf[0]+1);
807 }
808 // Mark end of compressed data, by an empty block (see GIF doc):
809 wxGIFHandler_WriteZero(stream);
810 }
811 else
812 {
813 if (buf[0] == 255)
814 {
815 // Dump out this buffer - it is full:
816 ok = wxGIFHandler_Write(stream, buf, buf[0] + 1);
817 buf[0] = 0;
818 }
819 buf[++buf[0]] = c;
820 }
821
822 return ok;
823 }
824
825 #endif // wxUSE_STREAMS
826
827 #endif // wxUSE_IMAGE && wxUSE_GIF