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