Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qgifhandler.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
6
7#include <qimage.h>
8#include <qiodevice.h>
9#include <qloggingcategory.h>
10#include <qvariant.h>
11
13
14Q_LOGGING_CATEGORY(lcGif, "qt.gui.imageio.gif")
15
16#define Q_TRANSPARENT 0x00ffffff
17
18// avoid going through QImage::scanLine() which calls detach
19#define FAST_SCAN_LINE(bits, bpl, y) (bits + qptrdiff(y) * bpl)
20
21/*
22 Incremental image decoder for GIF image format.
23
24 This subclass of QImageFormat decodes GIF format images,
25 including animated GIFs. Internally in
26*/
27
29public:
32
33 int decode(QImage *image, const uchar* buffer, int length,
34 int *nextFrameDelay, int *loopCount);
35 static void scan(QIODevice *device, QList<QSize> *imageSizes, int *loopCount);
36
39
40private:
41 void fillRect(QImage *image, int x, int y, int w, int h, QRgb col);
42 inline QRgb color(uchar index) const;
43 static bool withinSizeLimit(int width, int height)
44 {
45 return quint64(width) * height < 16384 * 16384; // Reject unreasonable header values
46 }
47
48 // GIF specific stuff
49 QRgb* globalcmap;
50 QRgb* localcmap;
51 QImage backingstore;
52 unsigned char hold[16];
53 bool gif89;
54 int count;
55 int ccount;
56 int expectcount;
57 enum State {
58 Header,
59 LogicalScreenDescriptor,
60 GlobalColorMap,
61 LocalColorMap,
62 Introducer,
63 ImageDescriptor,
64 TableImageLZWSize,
65 ImageDataBlockSize,
66 ImageDataBlock,
67 ExtensionLabel,
68 GraphicControlExtension,
69 ApplicationExtension,
70 NetscapeExtensionBlockSize,
71 NetscapeExtensionBlock,
72 SkipBlockSize,
73 SkipBlock,
74 Done,
75 Error
76 } state;
77 int gncols;
78 int lncols;
79 int ncols;
80 int lzwsize;
81 bool lcmap;
82 int swidth, sheight;
83 int width, height;
84 int left, top, right, bottom;
85 enum Disposal { NoDisposal, DoNotChange, RestoreBackground, RestoreImage };
86 Disposal disposal;
87 bool disposed;
88 int trans_index;
89 bool gcmap;
90 int bgcol;
91 int interlace;
92 int accum;
93 int bitcount;
94
95 enum { max_lzw_bits=12 }; // (poor-compiler's static const int)
96
97 int code_size, clear_code, end_code, max_code_size, max_code;
98 int firstcode, oldcode, incode;
99 short* table[2];
100 short* stack;
101 short *sp;
102 bool needfirst;
103 int x, y;
104 int frame;
105 bool out_of_bounds;
106 bool digress;
107 void nextY(unsigned char *bits, int bpl);
108 void disposePrevious(QImage *image);
109};
110
111/*!
112 Constructs a QGIFFormat.
113*/
115{
116 globalcmap = nullptr;
117 localcmap = nullptr;
118 lncols = 0;
119 gncols = 0;
120 disposal = NoDisposal;
121 out_of_bounds = false;
122 disposed = true;
123 frame = -1;
124 state = Header;
125 count = 0;
126 lcmap = false;
127 newFrame = false;
128 partialNewFrame = false;
129 table[0] = nullptr;
130 table[1] = nullptr;
131 stack = nullptr;
132}
133
134/*!
135 Destroys a QGIFFormat.
136*/
138{
139 if (globalcmap) delete[] globalcmap;
140 if (localcmap) delete[] localcmap;
141 delete [] stack;
142}
143
144void QGIFFormat::disposePrevious(QImage *image)
145{
146 if (out_of_bounds) {
147 // flush anything that survived
148 // ### Changed: QRect(0, 0, swidth, sheight)
149 }
150
151 // Handle disposal of previous image before processing next one
152
153 if (disposed) return;
154
155 int l = qMin(swidth-1,left);
156 int r = qMin(swidth-1,right);
157 int t = qMin(sheight-1,top);
158 int b = qMin(sheight-1,bottom);
159
160 switch (disposal) {
161 case NoDisposal:
162 break;
163 case DoNotChange:
164 break;
165 case RestoreBackground:
166 if (trans_index>=0) {
167 // Easy: we use the transparent color
168 fillRect(image, l, t, r-l+1, b-t+1, Q_TRANSPARENT);
169 } else if (bgcol>=0) {
170 // Easy: we use the bgcol given
171 fillRect(image, l, t, r-l+1, b-t+1, color(bgcol));
172 } else {
173 // Impossible: We don't know of a bgcol - use pixel 0
174 const QRgb *bits = reinterpret_cast<const QRgb *>(image->constBits());
175 fillRect(image, l, t, r-l+1, b-t+1, bits[0]);
176 }
177 // ### Changed: QRect(l, t, r-l+1, b-t+1)
178 break;
179 case RestoreImage: {
180 if (frame >= 0) {
181 for (int ln=t; ln<=b; ln++) {
182 memcpy(image->scanLine(ln)+l*sizeof(QRgb),
183 backingstore.constScanLine(ln-t),
184 (r-l+1)*sizeof(QRgb));
185 }
186 // ### Changed: QRect(l, t, r-l+1, b-t+1)
187 }
188 }
189 }
190 disposal = NoDisposal; // Until an extension says otherwise.
191
192 disposed = true;
193}
194
195/*!
196 This function decodes some data into image changes.
197
198 Returns the number of bytes consumed.
199*/
200int QGIFFormat::decode(QImage *image, const uchar *buffer, int length,
201 int *nextFrameDelay, int *loopCount)
202{
203 // We are required to state that
204 // "The Graphics Interchange Format(c) is the Copyright property of
205 // CompuServe Incorporated. GIF(sm) is a Service Mark property of
206 // CompuServe Incorporated."
207
208 if (!stack) {
209 stack = new short[(1 << max_lzw_bits) * 4];
210 table[0] = &stack[(1 << max_lzw_bits) * 2];
211 table[1] = &stack[(1 << max_lzw_bits) * 3];
212 }
213
214 image->detach();
215 qsizetype bpl = image->bytesPerLine();
216 unsigned char *bits = image->bits();
217
218#define LM(l, m) (((m)<<8)|l)
219 digress = false;
220 const int initial = length;
221 while (!digress && length) {
222 length--;
223 unsigned char ch=*buffer++;
224 switch (state) {
225 case Header:
226 hold[count++]=ch;
227 if (count==6) {
228 // Header
229 gif89=(hold[3]!='8' || hold[4]!='7');
230 state=LogicalScreenDescriptor;
231 count=0;
232 }
233 break;
234 case LogicalScreenDescriptor:
235 hold[count++]=ch;
236 if (count==7) {
237 // Logical Screen Descriptor
238 swidth=LM(hold[0], hold[1]);
239 sheight=LM(hold[2], hold[3]);
240 gcmap=!!(hold[4]&0x80);
241 //UNUSED: bpchan=(((hold[4]&0x70)>>3)+1);
242 //UNUSED: gcmsortflag=!!(hold[4]&0x08);
243 gncols=2<<(hold[4]&0x7);
244 bgcol=(gcmap) ? hold[5] : -1;
245 //aspect=hold[6] ? double(hold[6]+15)/64.0 : 1.0;
246
247 trans_index = -1;
248 count=0;
249 ncols=gncols;
250 if (gcmap) {
251 ccount=0;
252 state=GlobalColorMap;
253 globalcmap = new QRgb[gncols+1]; // +1 for trans_index
254 globalcmap[gncols] = Q_TRANSPARENT;
255 } else {
256 state=Introducer;
257 }
258 }
259 break;
260 case GlobalColorMap: case LocalColorMap:
261 hold[count++]=ch;
262 if (count==3) {
263 QRgb rgb = qRgb(hold[0], hold[1], hold[2]);
264 if (state == LocalColorMap) {
265 if (ccount < lncols)
266 localcmap[ccount] = rgb;
267 } else {
268 globalcmap[ccount] = rgb;
269 }
270 if (++ccount >= ncols) {
271 if (state == LocalColorMap)
272 state=TableImageLZWSize;
273 else
274 state=Introducer;
275 }
276 count=0;
277 }
278 break;
279 case Introducer:
280 hold[count++]=ch;
281 switch (ch) {
282 case ',':
283 state=ImageDescriptor;
284 break;
285 case '!':
286 state=ExtensionLabel;
287 break;
288 case ';':
289 // ### Changed: QRect(0, 0, swidth, sheight)
290 state=Done;
291 break;
292 default:
293 digress=true;
294 // Unexpected Introducer - ignore block
295 state=Error;
296 }
297 break;
298 case ImageDescriptor:
299 hold[count++]=ch;
300 if (count==10) {
301 int newleft=LM(hold[1], hold[2]);
302 int newtop=LM(hold[3], hold[4]);
303 int newwidth=LM(hold[5], hold[6]);
304 int newheight=LM(hold[7], hold[8]);
305
306 // disbelieve ridiculous logical screen sizes,
307 // unless the image frames are also large.
308 if (swidth/10 > qMax(newwidth,16384))
309 swidth = -1;
310 if (sheight/10 > qMax(newheight,16384))
311 sheight = -1;
312
313 if (swidth <= 0)
314 swidth = newleft + newwidth;
315 if (sheight <= 0)
316 sheight = newtop + newheight;
317
318 QImage::Format format = trans_index >= 0 ? QImage::Format_ARGB32 : QImage::Format_RGB32;
319 if (image->isNull()) {
320 if (!withinSizeLimit(swidth, sheight)) {
321 state = Error;
322 return -1;
323 }
324 if (!QImageIOHandler::allocateImage(QSize(swidth, sheight), format, image)) {
325 state = Error;
326 return -1;
327 }
328 bpl = image->bytesPerLine();
329 bits = image->bits();
330 if (bits)
331 memset(bits, 0, image->sizeInBytes());
332 }
333
334 // Check if the previous attempt to create the image failed. If it
335 // did then the image is broken and we should give up.
336 if (image->isNull()) {
337 state = Error;
338 return -1;
339 }
340
341 disposePrevious(image);
342 disposed = false;
343
344 left = newleft;
345 top = newtop;
346 width = newwidth;
347 height = newheight;
348
349 right=qMax(0, qMin(left+width, swidth)-1);
350 bottom=qMax(0, qMin(top+height, sheight)-1);
351 lcmap=!!(hold[9]&0x80);
352 interlace=!!(hold[9]&0x40);
353 //bool lcmsortflag=!!(hold[9]&0x20);
354 lncols=lcmap ? (2<<(hold[9]&0x7)) : 0;
355 if (lncols) {
356 if (localcmap)
357 delete [] localcmap;
358 localcmap = new QRgb[lncols+1];
359 localcmap[lncols] = Q_TRANSPARENT;
360 ncols = lncols;
361 } else {
362 ncols = gncols;
363 }
364 frame++;
365 if (frame == 0) {
366 if (left || top || width<swidth || height<sheight) {
367 // Not full-size image - erase with bg or transparent
368 if (trans_index >= 0) {
369 fillRect(image, 0, 0, swidth, sheight, color(trans_index));
370 // ### Changed: QRect(0, 0, swidth, sheight)
371 } else if (bgcol>=0) {
372 fillRect(image, 0, 0, swidth, sheight, color(bgcol));
373 // ### Changed: QRect(0, 0, swidth, sheight)
374 }
375 }
376 }
377
378 if (disposal == RestoreImage) {
379 int l = qMin(swidth-1,left);
380 int r = qMin(swidth-1,right);
381 int t = qMin(sheight-1,top);
382 int b = qMin(sheight-1,bottom);
383 int w = r-l+1;
384 int h = b-t+1;
385
386 if (backingstore.width() < w
387 || backingstore.height() < h) {
388
389 if (!withinSizeLimit(w, h)) {
390 state = Error;
391 return -1;
392 }
393 // We just use the backing store as a byte array
394 QSize bsSize(qMax(backingstore.width(), w), qMax(backingstore.height(), h));
395 if (!QImageIOHandler::allocateImage(bsSize, QImage::Format_RGB32,
396 &backingstore)) {
397 state = Error;
398 return -1;
399 }
400 memset(backingstore.bits(), 0, backingstore.sizeInBytes());
401 }
402 const qsizetype dest_bpl = backingstore.bytesPerLine();
403 unsigned char *dest_data = backingstore.bits();
404 for (int ln=0; ln<h; ln++) {
405 memcpy(FAST_SCAN_LINE(dest_data, dest_bpl, ln),
406 FAST_SCAN_LINE(bits, bpl, t+ln) + l*sizeof(QRgb), w*sizeof(QRgb));
407 }
408 }
409
410 count=0;
411 if (lcmap) {
412 ccount=0;
413 state=LocalColorMap;
414 } else {
415 state=TableImageLZWSize;
416 }
417 x = left;
418 y = top;
419 accum = 0;
420 bitcount = 0;
421 sp = stack;
422 firstcode = oldcode = 0;
423 needfirst = true;
424 out_of_bounds = left>=swidth || y>=sheight;
425 }
426 break;
427 case TableImageLZWSize: {
428 lzwsize=ch;
429 if (lzwsize > max_lzw_bits) {
430 state=Error;
431 } else {
432 code_size=lzwsize+1;
433 clear_code=1<<lzwsize;
434 end_code=clear_code+1;
435 max_code_size=2*clear_code;
436 max_code=clear_code+2;
437 int i;
438 for (i=0; i<clear_code; i++) {
439 table[0][i]=0;
440 table[1][i]=i;
441 }
442 state=ImageDataBlockSize;
443 }
444 count=0;
445 break;
446 } case ImageDataBlockSize:
447 expectcount=ch;
448 if (expectcount) {
449 state=ImageDataBlock;
450 } else {
451 state=Introducer;
452 digress = true;
453 newFrame = true;
454 }
455 break;
456 case ImageDataBlock:
457 count++;
458 if (bitcount != -32768) {
459 if (bitcount < 0 || bitcount > 31) {
460 state = Error;
461 return -1;
462 }
463 accum |= (ch << bitcount);
464 bitcount += 8;
465 }
466 while (bitcount>=code_size && state==ImageDataBlock) {
467 int code=accum&((1<<code_size)-1);
468 bitcount-=code_size;
469 accum>>=code_size;
470
471 if (code==clear_code) {
472 if (!needfirst) {
473 code_size=lzwsize+1;
474 max_code_size=2*clear_code;
475 max_code=clear_code+2;
476 }
477 needfirst=true;
478 } else if (code==end_code) {
479 bitcount = -32768;
480 // Left the block end arrive
481 } else {
482 if (needfirst) {
483 firstcode=oldcode=code;
484 if (!out_of_bounds && image->height() > y && ((frame == 0) || (firstcode != trans_index)))
485 ((QRgb*)FAST_SCAN_LINE(bits, bpl, y))[x] = color(firstcode);
486 x++;
487 if (x>=swidth) out_of_bounds = true;
488 needfirst=false;
489 if (x>=left+width) {
490 x=left;
491 out_of_bounds = left>=swidth || y>=sheight;
492 nextY(bits, bpl);
493 }
494 } else {
495 incode=code;
496 if (code>=max_code) {
497 *sp++=firstcode;
498 code=oldcode;
499 }
500 while (code>=clear_code+2) {
501 if (code >= max_code) {
502 state = Error;
503 return -1;
504 }
505 *sp++=table[1][code];
506 if (code==table[0][code]) {
507 state=Error;
508 return -1;
509 }
510 if (sp-stack>=(1<<(max_lzw_bits))*2) {
511 state=Error;
512 return -1;
513 }
514 code=table[0][code];
515 }
516 if (code < 0) {
517 state = Error;
518 return -1;
519 }
520
521 *sp++=firstcode=table[1][code];
522 code=max_code;
523 if (code<(1<<max_lzw_bits)) {
524 table[0][code]=oldcode;
525 table[1][code]=firstcode;
526 max_code++;
527 if ((max_code>=max_code_size)
528 && (max_code_size<(1<<max_lzw_bits)))
529 {
530 max_code_size*=2;
531 code_size++;
532 }
533 }
534 oldcode=incode;
535 const int h = image->height();
536 QRgb *line = nullptr;
537 if (!out_of_bounds && h > y)
538 line = (QRgb*)FAST_SCAN_LINE(bits, bpl, y);
539 while (sp>stack) {
540 const uchar index = *(--sp);
541 if (!out_of_bounds && h > y && ((frame == 0) || (index != trans_index))) {
542 line[x] = color(index);
543 }
544 x++;
545 if (x>=swidth) out_of_bounds = true;
546 if (x>=left+width) {
547 x=left;
548 out_of_bounds = left>=swidth || y>=sheight;
549 nextY(bits, bpl);
550 if (!out_of_bounds && h > y)
551 line = (QRgb*)FAST_SCAN_LINE(bits, bpl, y);
552 }
553 }
554 }
555 }
556 }
557 partialNewFrame = true;
558 if (count==expectcount) {
559 count=0;
560 state=ImageDataBlockSize;
561 }
562 break;
563 case ExtensionLabel:
564 switch (ch) {
565 case 0xf9:
566 state=GraphicControlExtension;
567 break;
568 case 0xff:
569 state=ApplicationExtension;
570 break;
571#if 0
572 case 0xfe:
573 state=CommentExtension;
574 break;
575 case 0x01:
576 break;
577#endif
578 default:
579 state=SkipBlockSize;
580 }
581 count=0;
582 break;
583 case ApplicationExtension:
584 if (count<11) hold[count]=ch;
585 count++;
586 if (count==hold[0]+1) {
587 if (qstrncmp((char*)(hold+1), "NETSCAPE", 8)==0) {
588 // Looping extension
589 state=NetscapeExtensionBlockSize;
590 } else {
591 state=SkipBlockSize;
592 }
593 count=0;
594 }
595 break;
596 case NetscapeExtensionBlockSize:
597 expectcount=ch;
598 count=0;
599 if (expectcount) state=NetscapeExtensionBlock;
600 else state=Introducer;
601 break;
602 case NetscapeExtensionBlock:
603 if (count<3) hold[count]=ch;
604 count++;
605 if (count==expectcount) {
606 *loopCount = hold[1]+hold[2]*256;
607 state=SkipBlockSize; // Ignore further blocks
608 }
609 break;
610 case GraphicControlExtension:
611 if (count<5) hold[count]=ch;
612 count++;
613 if (count==hold[0]+1) {
614 disposePrevious(image);
615 uint dBits = (hold[1] >> 2) & 0x7;
616 disposal = (dBits <= RestoreImage) ? Disposal(dBits) : NoDisposal;
617 //UNUSED: waitforuser=!!((hold[1]>>1)&0x1);
618 int delay=count>3 ? LM(hold[2], hold[3]) : 1;
619 // IE and mozilla use a minimum delay of 10. With the minimum delay of 10
620 // we are compatible to them and avoid huge loads on the app and xserver.
621 *nextFrameDelay = (delay < 2 ? 10 : delay) * 10;
622
623 bool havetrans=hold[1]&0x1;
624 trans_index = havetrans ? hold[4] : -1;
625
626 count=0;
627 state=SkipBlockSize;
628 }
629 break;
630 case SkipBlockSize:
631 expectcount=ch;
632 count=0;
633 if (expectcount) state=SkipBlock;
634 else state=Introducer;
635 break;
636 case SkipBlock:
637 count++;
638 if (count==expectcount) state=SkipBlockSize;
639 break;
640 case Done:
641 digress=true;
642 /* Netscape ignores the junk, so we do too.
643 length++; // Unget
644 state=Error; // More calls to this is an error
645 */
646 break;
647 case Error:
648 return -1; // Called again after done.
649 }
650 }
651 return initial-length;
652}
653
654/*!
655 Scans through the data stream defined by \a device and returns the image
656 sizes found in the stream in the \a imageSizes list.
657*/
658void QGIFFormat::scan(QIODevice *device, QList<QSize> *imageSizes, int *loopCount)
659{
660 if (!device)
661 return;
662
663 qint64 oldPos = device->pos();
664 if (device->isSequential() || !device->seek(0))
665 return;
666
667 int colorCount = 0;
668 int localColorCount = 0;
669 int globalColorCount = 0;
670 int colorReadCount = 0;
671 bool localColormap = false;
672 bool globalColormap = false;
673 int count = 0;
674 int blockSize = 0;
675 int imageWidth = 0;
676 int imageHeight = 0;
677 bool done = false;
678 uchar hold[16];
679 State state = Header;
680
681 const int readBufferSize = 40960; // 40k read buffer
682 QByteArray readBuffer(device->read(readBufferSize));
683
684 if (readBuffer.isEmpty()) {
685 device->seek(oldPos);
686 return;
687 }
688
689 // This is a specialized version of the state machine from decode(),
690 // which doesn't do any image decoding or mallocing, and has an
691 // optimized way of skipping SkipBlocks, ImageDataBlocks and
692 // Global/LocalColorMaps.
693
694 while (!readBuffer.isEmpty()) {
695 int length = readBuffer.size();
696 const uchar *buffer = (const uchar *) readBuffer.constData();
697 while (!done && length) {
698 length--;
699 uchar ch = *buffer++;
700 switch (state) {
701 case Header:
702 hold[count++] = ch;
703 if (count == 6) {
704 state = LogicalScreenDescriptor;
705 count = 0;
706 }
707 break;
708 case LogicalScreenDescriptor:
709 hold[count++] = ch;
710 if (count == 7) {
711 imageWidth = LM(hold[0], hold[1]);
712 imageHeight = LM(hold[2], hold[3]);
713 globalColormap = !!(hold[4] & 0x80);
714 globalColorCount = 2 << (hold[4] & 0x7);
715 count = 0;
716 colorCount = globalColorCount;
717 if (globalColormap) {
718 int colorTableSize = 3 * globalColorCount;
719 if (length >= colorTableSize) {
720 // skip the global color table in one go
721 length -= colorTableSize;
722 buffer += colorTableSize;
723 state = Introducer;
724 } else {
725 colorReadCount = 0;
726 state = GlobalColorMap;
727 }
728 } else {
729 state=Introducer;
730 }
731 }
732 break;
733 case GlobalColorMap:
734 case LocalColorMap:
735 hold[count++] = ch;
736 if (count == 3) {
737 if (++colorReadCount >= colorCount) {
738 if (state == LocalColorMap)
739 state = TableImageLZWSize;
740 else
741 state = Introducer;
742 }
743 count = 0;
744 }
745 break;
746 case Introducer:
747 hold[count++] = ch;
748 switch (ch) {
749 case 0x2c:
750 state = ImageDescriptor;
751 break;
752 case 0x21:
753 state = ExtensionLabel;
754 break;
755 case 0x3b:
756 state = Done;
757 break;
758 default:
759 done = true;
760 state = Error;
761 }
762 break;
763 case ImageDescriptor:
764 hold[count++] = ch;
765 if (count == 10) {
766 int newLeft = LM(hold[1], hold[2]);
767 int newTop = LM(hold[3], hold[4]);
768 int newWidth = LM(hold[5], hold[6]);
769 int newHeight = LM(hold[7], hold[8]);
770
771 if (imageWidth/10 > qMax(newWidth,200))
772 imageWidth = -1;
773 if (imageHeight/10 > qMax(newHeight,200))
774 imageHeight = -1;
775
776 if (imageWidth <= 0)
777 imageWidth = newLeft + newWidth;
778 if (imageHeight <= 0)
779 imageHeight = newTop + newHeight;
780
781 *imageSizes << QSize(imageWidth, imageHeight);
782
783 localColormap = !!(hold[9] & 0x80);
784 localColorCount = localColormap ? (2 << (hold[9] & 0x7)) : 0;
785 if (localColorCount)
786 colorCount = localColorCount;
787 else
788 colorCount = globalColorCount;
789
790 count = 0;
791 if (localColormap) {
792 int colorTableSize = 3 * localColorCount;
793 if (length >= colorTableSize) {
794 // skip the local color table in one go
795 length -= colorTableSize;
796 buffer += colorTableSize;
797 state = TableImageLZWSize;
798 } else {
799 colorReadCount = 0;
800 state = LocalColorMap;
801 }
802 } else {
803 state = TableImageLZWSize;
804 }
805 }
806 break;
807 case TableImageLZWSize:
808 if (ch > max_lzw_bits)
809 state = Error;
810 else
811 state = ImageDataBlockSize;
812 count = 0;
813 break;
814 case ImageDataBlockSize:
815 blockSize = ch;
816 if (blockSize) {
817 if (length >= blockSize) {
818 // we can skip the block in one go
819 length -= blockSize;
820 buffer += blockSize;
821 count = 0;
822 } else {
823 state = ImageDataBlock;
824 }
825 } else {
826 state = Introducer;
827 }
828 break;
829 case ImageDataBlock:
830 ++count;
831 if (count == blockSize) {
832 count = 0;
833 state = ImageDataBlockSize;
834 }
835 break;
836 case ExtensionLabel:
837 switch (ch) {
838 case 0xf9:
839 state = GraphicControlExtension;
840 break;
841 case 0xff:
842 state = ApplicationExtension;
843 break;
844 default:
845 state = SkipBlockSize;
846 }
847 count = 0;
848 break;
849 case ApplicationExtension:
850 if (count < 11)
851 hold[count] = ch;
852 ++count;
853 if (count == hold[0] + 1) {
854 if (qstrncmp((char*)(hold+1), "NETSCAPE", 8) == 0)
855 state=NetscapeExtensionBlockSize;
856 else
857 state=SkipBlockSize;
858 count = 0;
859 }
860 break;
861 case GraphicControlExtension:
862 if (count < 5)
863 hold[count] = ch;
864 ++count;
865 if (count == hold[0] + 1) {
866 count = 0;
867 state = SkipBlockSize;
868 }
869 break;
870 case NetscapeExtensionBlockSize:
871 blockSize = ch;
872 count = 0;
873 if (blockSize)
874 state = NetscapeExtensionBlock;
875 else
876 state = Introducer;
877 break;
878 case NetscapeExtensionBlock:
879 if (count < 3)
880 hold[count] = ch;
881 count++;
882 if (count == blockSize) {
883 *loopCount = LM(hold[1], hold[2]);
884 state = SkipBlockSize;
885 }
886 break;
887 case SkipBlockSize:
888 blockSize = ch;
889 count = 0;
890 if (blockSize) {
891 if (length >= blockSize) {
892 // we can skip the block in one go
893 length -= blockSize;
894 buffer += blockSize;
895 } else {
896 state = SkipBlock;
897 }
898 } else {
899 state = Introducer;
900 }
901 break;
902 case SkipBlock:
903 ++count;
904 if (count == blockSize)
905 state = SkipBlockSize;
906 break;
907 case Done:
908 done = true;
909 break;
910 case Error:
911 device->seek(oldPos);
912 return;
913 }
914 }
915 readBuffer = device->read(readBufferSize);
916 }
917 device->seek(oldPos);
918 return;
919}
920
921void QGIFFormat::fillRect(QImage *image, int col, int row, int w, int h, QRgb color)
922{
923 if (w>0) {
924 for (int j=0; j<h; j++) {
925 QRgb *line = (QRgb*)image->scanLine(j+row);
926 for (int i=0; i<w; i++)
927 *(line+col+i) = color;
928 }
929 }
930}
931
932void QGIFFormat::nextY(unsigned char *bits, int bpl)
933{
934 if (out_of_bounds)
935 return;
936 int my;
937 switch (interlace) {
938 case 0: // Non-interlaced
939 // if (!out_of_bounds) {
940 // ### Changed: QRect(left, y, right - left + 1, 1);
941 // }
942 y++;
943 break;
944 case 1: {
945 int i;
946 my = qMin(7, bottom-y);
947 // Don't dup with transparency
948 if (trans_index < 0) {
949 for (i=1; i<=my; i++) {
950 memcpy(FAST_SCAN_LINE(bits, bpl, y+i)+left*sizeof(QRgb), FAST_SCAN_LINE(bits, bpl, y)+left*sizeof(QRgb),
951 (right-left+1)*sizeof(QRgb));
952 }
953 }
954
955 // if (!out_of_bounds) {
956 // ### Changed: QRect(left, y, right - left + 1, my + 1);
957 // }
958// if (!out_of_bounds)
959// qDebug("consumer->changed(QRect(%d, %d, %d, %d))", left, y, right-left+1, my+1);
960 y+=8;
961 if (y>bottom) {
962 interlace++; y=top+4;
963 if (y > bottom) { // for really broken GIFs with bottom < 5
964 interlace=2;
965 y = top + 2;
966 if (y > bottom) { // for really broken GIF with bottom < 3
967 interlace = 0;
968 y = top + 1;
969 }
970 }
971 }
972 } break;
973 case 2: {
974 int i;
975 my = qMin(3, bottom-y);
976 // Don't dup with transparency
977 if (trans_index < 0) {
978 for (i=1; i<=my; i++) {
979 memcpy(FAST_SCAN_LINE(bits, bpl, y+i)+left*sizeof(QRgb), FAST_SCAN_LINE(bits, bpl, y)+left*sizeof(QRgb),
980 (right-left+1)*sizeof(QRgb));
981 }
982 }
983
984 // if (!out_of_bounds) {
985 // ### Changed: QRect(left, y, right - left + 1, my + 1);
986 // }
987 y+=8;
988 if (y>bottom) {
989 interlace++; y=top+2;
990 // handle broken GIF with bottom < 3
991 if (y > bottom) {
992 interlace = 3;
993 y = top + 1;
994 }
995 }
996 } break;
997 case 3: {
998 int i;
999 my = qMin(1, bottom-y);
1000 // Don't dup with transparency
1001 if (trans_index < 0) {
1002 for (i=1; i<=my; i++) {
1003 memcpy(FAST_SCAN_LINE(bits, bpl, y+i)+left*sizeof(QRgb), FAST_SCAN_LINE(bits, bpl, y)+left*sizeof(QRgb),
1004 (right-left+1)*sizeof(QRgb));
1005 }
1006 }
1007 // if (!out_of_bounds) {
1008 // ### Changed: QRect(left, y, right - left + 1, my + 1);
1009 // }
1010 y+=4;
1011 if (y>bottom) { interlace++; y=top+1; }
1012 } break;
1013 case 4:
1014 // if (!out_of_bounds) {
1015 // ### Changed: QRect(left, y, right - left + 1, 1);
1016 // }
1017 y+=2;
1018 }
1019
1020 // Consume bogus extra lines
1021 if (y >= sheight) out_of_bounds=true; //y=bottom;
1022}
1023
1024inline QRgb QGIFFormat::color(uchar index) const
1025{
1026 if (index > ncols)
1027 return Q_TRANSPARENT;
1028
1029 QRgb *map = lcmap ? localcmap : globalcmap;
1030 QRgb col = map ? map[index] : 0;
1031 return index == trans_index ? col & Q_TRANSPARENT : col;
1032}
1033
1034//-------------------------------------------------------------------------
1035//-------------------------------------------------------------------------
1036//-------------------------------------------------------------------------
1037
1039{
1040 gifFormat = new QGIFFormat;
1041 nextDelay = 100;
1042 loopCnt = -1;
1043 frameNumber = -1;
1044 scanIsCached = false;
1045}
1046
1048{
1049 delete gifFormat;
1050}
1051
1052// Does partial decode if necessary, just to see if an image is coming
1053
1054bool QGifHandler::imageIsComing() const
1055{
1056 const int GifChunkSize = 4096;
1057
1058 while (!gifFormat->partialNewFrame) {
1059 if (buffer.isEmpty()) {
1060 buffer += device()->read(GifChunkSize);
1061 if (buffer.isEmpty())
1062 break;
1063 }
1064
1065 int decoded = gifFormat->decode(&lastImage, (const uchar *)buffer.constData(), buffer.size(),
1066 &nextDelay, &loopCnt);
1067 if (decoded == -1)
1068 break;
1069 buffer.remove(0, decoded);
1070 }
1071 return gifFormat->partialNewFrame;
1072}
1073
1075{
1076 if (canRead(device()) || imageIsComing()) {
1077 setFormat("gif");
1078 return true;
1079 }
1080
1081 return false;
1082}
1083
1084bool QGifHandler::canRead(QIODevice *device)
1085{
1086 if (!device) {
1087 qCWarning(lcGif, "QGifHandler::canRead() called with no device");
1088 return false;
1089 }
1090
1091 char head[6];
1092 if (device->peek(head, sizeof(head)) == sizeof(head))
1093 return qstrncmp(head, "GIF87a", 6) == 0
1094 || qstrncmp(head, "GIF89a", 6) == 0;
1095 return false;
1096}
1097
1098bool QGifHandler::read(QImage *image)
1099{
1100 const int GifChunkSize = 4096;
1101
1102 while (!gifFormat->newFrame) {
1103 if (buffer.isEmpty()) {
1104 buffer += device()->read(GifChunkSize);
1105 if (buffer.isEmpty())
1106 break;
1107 }
1108
1109 int decoded = gifFormat->decode(&lastImage, (const uchar *)buffer.constData(), buffer.size(),
1110 &nextDelay, &loopCnt);
1111 if (decoded == -1)
1112 break;
1113 buffer.remove(0, decoded);
1114 }
1115 if (gifFormat->newFrame || (gifFormat->partialNewFrame && device()->atEnd())) {
1116 *image = lastImage;
1117 ++frameNumber;
1118 gifFormat->newFrame = false;
1119 gifFormat->partialNewFrame = false;
1120 return true;
1121 }
1122
1123 return false;
1124}
1125
1126bool QGifHandler::write(const QImage &image)
1127{
1128 Q_UNUSED(image);
1129 return false;
1130}
1131
1132bool QGifHandler::supportsOption(ImageOption option) const
1133{
1134 if (!device() || device()->isSequential())
1135 return option == Animation;
1136 else
1137 return option == Size
1138 || option == Animation;
1139}
1140
1141QVariant QGifHandler::option(ImageOption option) const
1142{
1143 if (option == Size) {
1144 if (!scanIsCached) {
1145 QGIFFormat::scan(device(), &imageSizes, &loopCnt);
1146 scanIsCached = true;
1147 }
1148 // before the first frame is read, or we have an empty data stream
1149 if (frameNumber == -1)
1150 return (imageSizes.size() > 0) ? QVariant(imageSizes.at(0)) : QVariant();
1151 // after the last frame has been read, the next size is undefined
1152 if (frameNumber >= imageSizes.size() - 1)
1153 return QVariant();
1154 // and the last case: the size of the next frame
1155 return imageSizes.at(frameNumber + 1);
1156 } else if (option == Animation) {
1157 return true;
1158 }
1159 return QVariant();
1160}
1161
1162void QGifHandler::setOption(ImageOption option, const QVariant &value)
1163{
1164 Q_UNUSED(option);
1165 Q_UNUSED(value);
1166}
1167
1169{
1170 return nextDelay;
1171}
1172
1174{
1175 if (!scanIsCached) {
1176 QGIFFormat::scan(device(), &imageSizes, &loopCnt);
1177 scanIsCached = true;
1178 }
1179 return imageSizes.size();
1180}
1181
1183{
1184 if (!scanIsCached) {
1185 QGIFFormat::scan(device(), &imageSizes, &loopCnt);
1186 scanIsCached = true;
1187 }
1188
1189 if (loopCnt == 0)
1190 return -1;
1191 else if (loopCnt == -1)
1192 return 0;
1193 else
1194 return loopCnt;
1195}
1196
1198{
1199 return frameNumber;
1200}
1201
1202QT_END_NAMESPACE
~QGIFFormat()
Destroys a QGIFFormat.
bool partialNewFrame
static void scan(QIODevice *device, QList< QSize > *imageSizes, int *loopCount)
Scans through the data stream defined by device and returns the image sizes found in the stream in th...
int decode(QImage *image, const uchar *buffer, int length, int *nextFrameDelay, int *loopCount)
This function decodes some data into image changes.
QGIFFormat()
Constructs a QGIFFormat.
int nextImageDelay() const override
For image formats that support animation, this function returns the number of milliseconds to wait un...
int currentImageNumber() const override
For image formats that support animation, this function returns the sequence number of the current im...
int loopCount() const override
For image formats that support animation, this function returns the number of times the animation sho...
bool canRead() const override
Returns true if an image can be read from the device (i.e., the image format is supported,...
int imageCount() const override
For image formats that support animation, this function returns the number of images in the animation...
#define Q_TRANSPARENT
#define LM(l, m)
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define FAST_SCAN_LINE(data, bpl, y)