]>
Commit | Line | Data |
---|---|---|
b59bf2db | 1 | ///////////////////////////////////////////////////////////////////////////// |
8898456d | 2 | // Name: src/common/imaggif.cpp |
b59bf2db | 3 | // Purpose: wxGIFHandler |
77b83d0a | 4 | // Author: Vaclav Slavik, Guillermo Rodriguez Garcia, Gershon Elber, Troels K |
77b83d0a | 5 | // Copyright: (c) 1999-2011 Vaclav Slavik, Guillermo Rodriguez Garcia, Gershon Elber, Troels K |
65571936 | 6 | // Licence: wxWindows licence |
b59bf2db JS |
7 | ///////////////////////////////////////////////////////////////////////////// |
8 | ||
b59bf2db | 9 | // For compilers that support precompilation, includes "wx.h". |
3096bd2f | 10 | #include "wx/wxprec.h" |
b59bf2db JS |
11 | |
12 | #ifdef __BORLANDC__ | |
8898456d | 13 | #pragma hdrstop |
b59bf2db JS |
14 | #endif |
15 | ||
8898456d WS |
16 | #if wxUSE_IMAGE && wxUSE_GIF |
17 | ||
ce4169a4 | 18 | #ifndef WX_PRECOMP |
8898456d WS |
19 | #include "wx/intl.h" |
20 | #include "wx/log.h" | |
77b83d0a DS |
21 | #include "wx/palette.h" |
22 | #include "wx/utils.h" | |
ce4169a4 | 23 | #endif |
b59bf2db | 24 | |
8f493002 | 25 | #include "wx/imaggif.h" |
464122b6 | 26 | #include "wx/gifdecod.h" |
77b83d0a DS |
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 | ||
ce4169a4 RR |
71 | IMPLEMENT_DYNAMIC_CLASS(wxGIFHandler,wxImageHandler) |
72 | ||
77b83d0a DS |
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, | |
64a3ee76 | 94 | bool loop, const wxRGB *pal, int palCount); |
77b83d0a DS |
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 | ||
b59bf2db JS |
110 | //----------------------------------------------------------------------------- |
111 | // wxGIFHandler | |
112 | //----------------------------------------------------------------------------- | |
113 | ||
9ab6ee85 GRG |
114 | #if wxUSE_STREAMS |
115 | ||
b931f7ee | 116 | bool wxGIFHandler::LoadFile(wxImage *image, wxInputStream& stream, |
77b83d0a | 117 | bool verbose, int index) |
b59bf2db | 118 | { |
464122b6 | 119 | wxGIFDecoder *decod; |
72045d57 | 120 | wxGIFErrorCode error; |
7beb59f3 | 121 | bool ok = true; |
b59bf2db | 122 | |
0141d2c9 | 123 | // image->Destroy(); |
72045d57 VZ |
124 | decod = new wxGIFDecoder(); |
125 | error = decod->LoadGIF(stream); | |
8a68d8b6 | 126 | |
8141573c | 127 | if ((error != wxGIF_OK) && (error != wxGIF_TRUNCATED)) |
995612e2 | 128 | { |
e08e239b GRG |
129 | if (verbose) |
130 | { | |
131 | switch (error) | |
132 | { | |
8141573c | 133 | case wxGIF_INVFORMAT: |
add95ac3 | 134 | wxLogError(_("GIF: error in GIF image format.")); |
8141573c GRG |
135 | break; |
136 | case wxGIF_MEMERR: | |
add95ac3 | 137 | wxLogError(_("GIF: not enough memory.")); |
8141573c GRG |
138 | break; |
139 | default: | |
add95ac3 | 140 | wxLogError(_("GIF: unknown error!!!")); |
8141573c | 141 | break; |
e08e239b GRG |
142 | } |
143 | } | |
b59bf2db | 144 | delete decod; |
7beb59f3 | 145 | return false; |
b59bf2db | 146 | } |
b59bf2db | 147 | |
8141573c GRG |
148 | if ((error == wxGIF_TRUNCATED) && verbose) |
149 | { | |
add95ac3 | 150 | wxLogError(_("GIF: data stream seems to be truncated.")); |
77b83d0a | 151 | // go on; image data is OK |
8141573c GRG |
152 | } |
153 | ||
b931f7ee VS |
154 | if (ok) |
155 | { | |
72045d57 | 156 | ok = decod->ConvertToImage(index != -1 ? (size_t)index : 0, image); |
b931f7ee VS |
157 | } |
158 | else | |
159 | { | |
160 | wxLogError(_("GIF: Invalid gif index.")); | |
161 | } | |
162 | ||
464122b6 | 163 | delete decod; |
0141d2c9 | 164 | |
464122b6 | 165 | return ok; |
b59bf2db JS |
166 | } |
167 | ||
77b83d0a DS |
168 | bool wxGIFHandler::SaveFile(wxImage *image, |
169 | wxOutputStream& stream, bool verbose) | |
b59bf2db | 170 | { |
77b83d0a DS |
171 | #if wxUSE_PALETTE |
172 | wxRGB pal[256]; | |
173 | int palCount; | |
174 | int maskIndex; | |
8a68d8b6 | 175 | |
77b83d0a DS |
176 | return wxGIFHandler_GetPalette(*image, pal, &palCount, &maskIndex) |
177 | && DoSaveFile(*image, &stream, verbose, true /*first?*/, 0, | |
491da411 | 178 | false /*loop?*/, pal, palCount, maskIndex) |
77b83d0a DS |
179 | && wxGIFHandler_WriteTerm(&stream); |
180 | #else | |
181 | wxUnusedVar(image); | |
182 | wxUnusedVar(stream); | |
183 | wxUnusedVar(verbose); | |
7beb59f3 | 184 | return false; |
77b83d0a | 185 | #endif |
b59bf2db JS |
186 | } |
187 | ||
995612e2 | 188 | bool wxGIFHandler::DoCanRead( wxInputStream& stream ) |
0828c087 | 189 | { |
72045d57 VZ |
190 | wxGIFDecoder decod; |
191 | return decod.CanRead(stream); | |
8faef7cc | 192 | // it's ok to modify the stream position here |
0828c087 VS |
193 | } |
194 | ||
8faef7cc | 195 | int wxGIFHandler::DoGetImageCount( wxInputStream& stream ) |
85fcb94f VZ |
196 | { |
197 | wxGIFDecoder decod; | |
198 | wxGIFErrorCode error = decod.LoadGIF(stream); | |
199 | if ( (error != wxGIF_OK) && (error != wxGIF_TRUNCATED) ) | |
200 | return -1; | |
03647350 | 201 | |
8faef7cc FM |
202 | // NOTE: this function modifies the current stream position but it's ok |
203 | // (see wxImageHandler::GetImageCount) | |
85fcb94f VZ |
204 | |
205 | return decod.GetFrameCount(); | |
206 | } | |
207 | ||
77b83d0a DS |
208 | bool wxGIFHandler::DoSaveFile(const wxImage& image, wxOutputStream *stream, |
209 | bool WXUNUSED(verbose), bool first, int delayMilliSecs, bool loop, | |
491da411 | 210 | const wxRGB *pal, int palCount, int maskIndex) |
77b83d0a DS |
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, | |
64a3ee76 | 226 | pal, palCount); |
77b83d0a DS |
227 | } |
228 | ||
64a3ee76 DS |
229 | ok = ok |
230 | && wxGIFHandler_WriteComment(stream, | |
231 | image.GetOption(wxIMAGE_OPTION_GIF_COMMENT)) | |
232 | && wxGIFHandler_WriteControl(stream, maskIndex, delayMilliSecs) | |
77b83d0a DS |
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, | |
491da411 | 303 | wxOutputStream *stream, bool verbose, int delayMilliSecs) |
77b83d0a DS |
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, | |
491da411 | 335 | true /*loop?*/, pal, palCount, maskIndex); |
77b83d0a DS |
336 | } |
337 | ||
338 | return ok && wxGIFHandler_WriteTerm(stream); | |
339 | #else | |
340 | wxUnusedVar(images); | |
341 | wxUnusedVar(stream); | |
342 | wxUnusedVar(verbose); | |
343 | wxUnusedVar(delayMilliSecs); | |
77b83d0a DS |
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 | ||
9ab6ee85 GRG |
488 | #endif // wxUSE_STREAMS |
489 | ||
77b83d0a DS |
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; | |
ff5bf31c | 645 | return wxGIFHandler_Write(stream, &buf, sizeof(buf)); |
77b83d0a DS |
646 | } |
647 | ||
648 | bool wxGIFHandler_WriteHeader(wxOutputStream *stream, int width, int height, | |
64a3ee76 | 649 | bool loop, const wxRGB *pal, int palCount) |
77b83d0a DS |
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 | ||
64a3ee76 | 671 | return ok; |
77b83d0a DS |
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 | { | |
da60600b DS |
738 | if ( comment.empty() ) |
739 | { | |
740 | return true; | |
741 | } | |
77b83d0a | 742 | |
da60600b DS |
743 | // Write comment header. |
744 | wxUint8 buf[2]; | |
77b83d0a DS |
745 | buf[0] = GIF_MARKER_EXT; |
746 | buf[1] = GIF_MARKER_EXT_COMMENT; | |
da60600b DS |
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 | ||
77b83d0a | 773 | |
da60600b DS |
774 | // Write comment footer. |
775 | return wxGIFHandler_WriteZero(stream); | |
77b83d0a DS |
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 |