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