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