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