blob: a6029b691c1ba7eca9e2316bc38d54ca599535c6 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
** WARNING:
** A separate license from Unisys may be required to use the gif
** reader. See http://www.unisys.com/about__unisys/lzw/
** for information from Unisys
**
****************************************************************************/
#include "qgifhandler_p.h"
#include <qimage.h>
#include <qiodevice.h>
#include <qvariant.h>
QT_BEGIN_NAMESPACE
#define Q_TRANSPARENT 0x00ffffff
// avoid going through QImage::scanLine() which calls detach
#define FAST_SCAN_LINE(bits, bpl, y) (bits + (y) * bpl)
/*
Incremental image decoder for GIF image format.
This subclass of QImageFormat decodes GIF format images,
including animated GIFs. Internally in
*/
class QGIFFormat {
public:
QGIFFormat();
~QGIFFormat();
int decode(QImage *image, const uchar* buffer, int length,
int *nextFrameDelay, int *loopCount);
static void scan(QIODevice *device, QVector<QSize> *imageSizes, int *loopCount);
bool newFrame;
bool partialNewFrame;
private:
void fillRect(QImage *image, int x, int y, int w, int h, QRgb col);
inline QRgb color(uchar index) const;
// GIF specific stuff
QRgb* globalcmap;
QRgb* localcmap;
QImage backingstore;
unsigned char hold[16];
bool gif89;
int count;
int ccount;
int expectcount;
enum State {
Header,
LogicalScreenDescriptor,
GlobalColorMap,
LocalColorMap,
Introducer,
ImageDescriptor,
TableImageLZWSize,
ImageDataBlockSize,
ImageDataBlock,
ExtensionLabel,
GraphicControlExtension,
ApplicationExtension,
NetscapeExtensionBlockSize,
NetscapeExtensionBlock,
SkipBlockSize,
SkipBlock,
Done,
Error
} state;
int gncols;
int lncols;
int ncols;
int lzwsize;
bool lcmap;
int swidth, sheight;
int width, height;
int left, top, right, bottom;
enum Disposal { NoDisposal, DoNotChange, RestoreBackground, RestoreImage };
Disposal disposal;
bool disposed;
int trans_index;
bool gcmap;
int bgcol;
int interlace;
int accum;
int bitcount;
enum { max_lzw_bits=12 }; // (poor-compiler's static const int)
int code_size, clear_code, end_code, max_code_size, max_code;
int firstcode, oldcode, incode;
short* table[2];
short* stack;
short *sp;
bool needfirst;
int x, y;
int frame;
bool out_of_bounds;
bool digress;
void nextY(unsigned char *bits, int bpl);
void disposePrevious(QImage *image);
};
/*!
Constructs a QGIFFormat.
*/
QGIFFormat::QGIFFormat()
{
globalcmap = 0;
localcmap = 0;
lncols = 0;
gncols = 0;
disposal = NoDisposal;
out_of_bounds = false;
disposed = true;
frame = -1;
state = Header;
count = 0;
lcmap = false;
newFrame = false;
partialNewFrame = false;
table[0] = 0;
table[1] = 0;
stack = 0;
}
/*!
Destroys a QGIFFormat.
*/
QGIFFormat::~QGIFFormat()
{
if (globalcmap) delete[] globalcmap;
if (localcmap) delete[] localcmap;
delete [] stack;
}
void QGIFFormat::disposePrevious(QImage *image)
{
if (out_of_bounds) {
// flush anything that survived
// ### Changed: QRect(0, 0, swidth, sheight)
}
// Handle disposal of previous image before processing next one
if (disposed) return;
int l = qMin(swidth-1,left);
int r = qMin(swidth-1,right);
int t = qMin(sheight-1,top);
int b = qMin(sheight-1,bottom);
switch (disposal) {
case NoDisposal:
break;
case DoNotChange:
break;
case RestoreBackground:
if (trans_index>=0) {
// Easy: we use the transparent color
fillRect(image, l, t, r-l+1, b-t+1, Q_TRANSPARENT);
} else if (bgcol>=0) {
// Easy: we use the bgcol given
fillRect(image, l, t, r-l+1, b-t+1, color(bgcol));
} else {
// Impossible: We don't know of a bgcol - use pixel 0
const QRgb *bits = reinterpret_cast<const QRgb *>(image->constBits());
fillRect(image, l, t, r-l+1, b-t+1, bits[0]);
}
// ### Changed: QRect(l, t, r-l+1, b-t+1)
break;
case RestoreImage: {
if (frame >= 0) {
for (int ln=t; ln<=b; ln++) {
memcpy(image->scanLine(ln)+l*sizeof(QRgb),
backingstore.constScanLine(ln-t),
(r-l+1)*sizeof(QRgb));
}
// ### Changed: QRect(l, t, r-l+1, b-t+1)
}
}
}
disposal = NoDisposal; // Until an extension says otherwise.
disposed = true;
}
/*!
This function decodes some data into image changes.
Returns the number of bytes consumed.
*/
int QGIFFormat::decode(QImage *image, const uchar *buffer, int length,
int *nextFrameDelay, int *loopCount)
{
// We are required to state that
// "The Graphics Interchange Format(c) is the Copyright property of
// CompuServe Incorporated. GIF(sm) is a Service Mark property of
// CompuServe Incorporated."
if (!stack) {
stack = new short[(1 << max_lzw_bits) * 4];
table[0] = &stack[(1 << max_lzw_bits) * 2];
table[1] = &stack[(1 << max_lzw_bits) * 3];
}
image->detach();
int bpl = image->bytesPerLine();
unsigned char *bits = image->bits();
#define LM(l, m) (((m)<<8)|l)
digress = false;
const int initial = length;
while (!digress && length) {
length--;
unsigned char ch=*buffer++;
switch (state) {
case Header:
hold[count++]=ch;
if (count==6) {
// Header
gif89=(hold[3]!='8' || hold[4]!='7');
state=LogicalScreenDescriptor;
count=0;
}
break;
case LogicalScreenDescriptor:
hold[count++]=ch;
if (count==7) {
// Logical Screen Descriptor
swidth=LM(hold[0], hold[1]);
sheight=LM(hold[2], hold[3]);
gcmap=!!(hold[4]&0x80);
//UNUSED: bpchan=(((hold[4]&0x70)>>3)+1);
//UNUSED: gcmsortflag=!!(hold[4]&0x08);
gncols=2<<(hold[4]&0x7);
bgcol=(gcmap) ? hold[5] : -1;
//aspect=hold[6] ? double(hold[6]+15)/64.0 : 1.0;
trans_index = -1;
count=0;
ncols=gncols;
if (gcmap) {
ccount=0;
state=GlobalColorMap;
globalcmap = new QRgb[gncols+1]; // +1 for trans_index
globalcmap[gncols] = Q_TRANSPARENT;
} else {
state=Introducer;
}
}
break;
case GlobalColorMap: case LocalColorMap:
hold[count++]=ch;
if (count==3) {
QRgb rgb = qRgb(hold[0], hold[1], hold[2]);
if (state == LocalColorMap) {
if (ccount < lncols)
localcmap[ccount] = rgb;
} else {
globalcmap[ccount] = rgb;
}
if (++ccount >= ncols) {
if (state == LocalColorMap)
state=TableImageLZWSize;
else
state=Introducer;
}
count=0;
}
break;
case Introducer:
hold[count++]=ch;
switch (ch) {
case ',':
state=ImageDescriptor;
break;
case '!':
state=ExtensionLabel;
break;
case ';':
// ### Changed: QRect(0, 0, swidth, sheight)
state=Done;
break;
default:
digress=true;
// Unexpected Introducer - ignore block
state=Error;
}
break;
case ImageDescriptor:
hold[count++]=ch;
if (count==10) {
int newleft=LM(hold[1], hold[2]);
int newtop=LM(hold[3], hold[4]);
int newwidth=LM(hold[5], hold[6]);
int newheight=LM(hold[7], hold[8]);
// disbelieve ridiculous logical screen sizes,
// unless the image frames are also large.
if (swidth/10 > qMax(newwidth,200))
swidth = -1;
if (sheight/10 > qMax(newheight,200))
sheight = -1;
if (swidth <= 0)
swidth = newleft + newwidth;
if (sheight <= 0)
sheight = newtop + newheight;
QImage::Format format = trans_index >= 0 ? QImage::Format_ARGB32 : QImage::Format_RGB32;
if (image->isNull()) {
(*image) = QImage(swidth, sheight, format);
bpl = image->bytesPerLine();
bits = image->bits();
if (bits)
memset(bits, 0, image->sizeInBytes());
}
// Check if the previous attempt to create the image failed. If it
// did then the image is broken and we should give up.
if (image->isNull()) {
state = Error;
return -1;
}
disposePrevious(image);
disposed = false;
left = newleft;
top = newtop;
width = newwidth;
height = newheight;
right=qMax(0, qMin(left+width, swidth)-1);
bottom=qMax(0, qMin(top+height, sheight)-1);
lcmap=!!(hold[9]&0x80);
interlace=!!(hold[9]&0x40);
//bool lcmsortflag=!!(hold[9]&0x20);
lncols=lcmap ? (2<<(hold[9]&0x7)) : 0;
if (lncols) {
if (localcmap)
delete [] localcmap;
localcmap = new QRgb[lncols+1];
localcmap[lncols] = Q_TRANSPARENT;
ncols = lncols;
} else {
ncols = gncols;
}
frame++;
if (frame == 0) {
if (left || top || width<swidth || height<sheight) {
// Not full-size image - erase with bg or transparent
if (trans_index >= 0) {
fillRect(image, 0, 0, swidth, sheight, color(trans_index));
// ### Changed: QRect(0, 0, swidth, sheight)
} else if (bgcol>=0) {
fillRect(image, 0, 0, swidth, sheight, color(bgcol));
// ### Changed: QRect(0, 0, swidth, sheight)
}
}
}
if (disposal == RestoreImage) {
int l = qMin(swidth-1,left);
int r = qMin(swidth-1,right);
int t = qMin(sheight-1,top);
int b = qMin(sheight-1,bottom);
int w = r-l+1;
int h = b-t+1;
if (backingstore.width() < w
|| backingstore.height() < h) {
// We just use the backing store as a byte array
backingstore = QImage(qMax(backingstore.width(), w),
qMax(backingstore.height(), h),
QImage::Format_RGB32);
if (backingstore.isNull()) {
state = Error;
return -1;
}
memset(backingstore.bits(), 0, backingstore.sizeInBytes());
}
const int dest_bpl = backingstore.bytesPerLine();
unsigned char *dest_data = backingstore.bits();
for (int ln=0; ln<h; ln++) {
memcpy(FAST_SCAN_LINE(dest_data, dest_bpl, ln),
FAST_SCAN_LINE(bits, bpl, t+ln) + l*sizeof(QRgb), w*sizeof(QRgb));
}
}
count=0;
if (lcmap) {
ccount=0;
state=LocalColorMap;
} else {
state=TableImageLZWSize;
}
x = left;
y = top;
accum = 0;
bitcount = 0;
sp = stack;
firstcode = oldcode = 0;
needfirst = true;
out_of_bounds = left>=swidth || y>=sheight;
}
break;
case TableImageLZWSize: {
lzwsize=ch;
if (lzwsize > max_lzw_bits) {
state=Error;
} else {
code_size=lzwsize+1;
clear_code=1<<lzwsize;
end_code=clear_code+1;
max_code_size=2*clear_code;
max_code=clear_code+2;
int i;
for (i=0; i<clear_code; i++) {
table[0][i]=0;
table[1][i]=i;
}
state=ImageDataBlockSize;
}
count=0;
break;
} case ImageDataBlockSize:
expectcount=ch;
if (expectcount) {
state=ImageDataBlock;
} else {
state=Introducer;
digress = true;
newFrame = true;
}
break;
case ImageDataBlock:
count++;
accum|=(ch<<bitcount);
bitcount+=8;
while (bitcount>=code_size && state==ImageDataBlock) {
int code=accum&((1<<code_size)-1);
bitcount-=code_size;
accum>>=code_size;
if (code==clear_code) {
if (!needfirst) {
code_size=lzwsize+1;
max_code_size=2*clear_code;
max_code=clear_code+2;
}
needfirst=true;
} else if (code==end_code) {
bitcount = -32768;
// Left the block end arrive
} else {
if (needfirst) {
firstcode=oldcode=code;
if (!out_of_bounds && image->height() > y && ((frame == 0) || (firstcode != trans_index)))
((QRgb*)FAST_SCAN_LINE(bits, bpl, y))[x] = color(firstcode);
x++;
if (x>=swidth) out_of_bounds = true;
needfirst=false;
if (x>=left+width) {
x=left;
out_of_bounds = left>=swidth || y>=sheight;
nextY(bits, bpl);
}
} else {
incode=code;
if (code>=max_code) {
*sp++=firstcode;
code=oldcode;
}
while (code>=clear_code+2) {
if (code >= max_code) {
state = Error;
return -1;
}
*sp++=table[1][code];
if (code==table[0][code]) {
state=Error;
return -1;
}
if (sp-stack>=(1<<(max_lzw_bits))*2) {
state=Error;
return -1;
}
code=table[0][code];
}
if (code < 0) {
state = Error;
return -1;
}
*sp++=firstcode=table[1][code];
code=max_code;
if (code<(1<<max_lzw_bits)) {
table[0][code]=oldcode;
table[1][code]=firstcode;
max_code++;
if ((max_code>=max_code_size)
&& (max_code_size<(1<<max_lzw_bits)))
{
max_code_size*=2;
code_size++;
}
}
oldcode=incode;
const int h = image->height();
QRgb *line = 0;
if (!out_of_bounds && h > y)
line = (QRgb*)FAST_SCAN_LINE(bits, bpl, y);
while (sp>stack) {
const uchar index = *(--sp);
if (!out_of_bounds && h > y && ((frame == 0) || (index != trans_index))) {
line[x] = color(index);
}
x++;
if (x>=swidth) out_of_bounds = true;
if (x>=left+width) {
x=left;
out_of_bounds = left>=swidth || y>=sheight;
nextY(bits, bpl);
if (!out_of_bounds && h > y)
line = (QRgb*)FAST_SCAN_LINE(bits, bpl, y);
}
}
}
}
}
partialNewFrame = true;
if (count==expectcount) {
count=0;
state=ImageDataBlockSize;
}
break;
case ExtensionLabel:
switch (ch) {
case 0xf9:
state=GraphicControlExtension;
break;
case 0xff:
state=ApplicationExtension;
break;
#if 0
case 0xfe:
state=CommentExtension;
break;
case 0x01:
break;
#endif
default:
state=SkipBlockSize;
}
count=0;
break;
case ApplicationExtension:
if (count<11) hold[count]=ch;
count++;
if (count==hold[0]+1) {
if (qstrncmp((char*)(hold+1), "NETSCAPE", 8)==0) {
// Looping extension
state=NetscapeExtensionBlockSize;
} else {
state=SkipBlockSize;
}
count=0;
}
break;
case NetscapeExtensionBlockSize:
expectcount=ch;
count=0;
if (expectcount) state=NetscapeExtensionBlock;
else state=Introducer;
break;
case NetscapeExtensionBlock:
if (count<3) hold[count]=ch;
count++;
if (count==expectcount) {
*loopCount = hold[1]+hold[2]*256;
state=SkipBlockSize; // Ignore further blocks
}
break;
case GraphicControlExtension:
if (count<5) hold[count]=ch;
count++;
if (count==hold[0]+1) {
disposePrevious(image);
disposal=Disposal((hold[1]>>2)&0x7);
//UNUSED: waitforuser=!!((hold[1]>>1)&0x1);
int delay=count>3 ? LM(hold[2], hold[3]) : 1;
// IE and mozilla use a minimum delay of 10. With the minimum delay of 10
// we are compatible to them and avoid huge loads on the app and xserver.
*nextFrameDelay = (delay < 2 ? 10 : delay) * 10;
bool havetrans=hold[1]&0x1;
trans_index = havetrans ? hold[4] : -1;
count=0;
state=SkipBlockSize;
}
break;
case SkipBlockSize:
expectcount=ch;
count=0;
if (expectcount) state=SkipBlock;
else state=Introducer;
break;
case SkipBlock:
count++;
if (count==expectcount) state=SkipBlockSize;
break;
case Done:
digress=true;
/* Netscape ignores the junk, so we do too.
length++; // Unget
state=Error; // More calls to this is an error
*/
break;
case Error:
return -1; // Called again after done.
}
}
return initial-length;
}
/*!
Scans through the data stream defined by \a device and returns the image
sizes found in the stream in the \a imageSizes vector.
*/
void QGIFFormat::scan(QIODevice *device, QVector<QSize> *imageSizes, int *loopCount)
{
if (!device)
return;
qint64 oldPos = device->pos();
if (device->isSequential() || !device->seek(0))
return;
int colorCount = 0;
int localColorCount = 0;
int globalColorCount = 0;
int colorReadCount = 0;
bool localColormap = false;
bool globalColormap = false;
int count = 0;
int blockSize = 0;
int imageWidth = 0;
int imageHeight = 0;
bool done = false;
uchar hold[16];
State state = Header;
const int readBufferSize = 40960; // 40k read buffer
QByteArray readBuffer(device->read(readBufferSize));
if (readBuffer.isEmpty()) {
device->seek(oldPos);
return;
}
// This is a specialized version of the state machine from decode(),
// which doesn't do any image decoding or mallocing, and has an
// optimized way of skipping SkipBlocks, ImageDataBlocks and
// Global/LocalColorMaps.
while (!readBuffer.isEmpty()) {
int length = readBuffer.size();
const uchar *buffer = (const uchar *) readBuffer.constData();
while (!done && length) {
length--;
uchar ch = *buffer++;
switch (state) {
case Header:
hold[count++] = ch;
if (count == 6) {
state = LogicalScreenDescriptor;
count = 0;
}
break;
case LogicalScreenDescriptor:
hold[count++] = ch;
if (count == 7) {
imageWidth = LM(hold[0], hold[1]);
imageHeight = LM(hold[2], hold[3]);
globalColormap = !!(hold[4] & 0x80);
globalColorCount = 2 << (hold[4] & 0x7);
count = 0;
colorCount = globalColorCount;
if (globalColormap) {
int colorTableSize = 3 * globalColorCount;
if (length >= colorTableSize) {
// skip the global color table in one go
length -= colorTableSize;
buffer += colorTableSize;
state = Introducer;
} else {
colorReadCount = 0;
state = GlobalColorMap;
}
} else {
state=Introducer;
}
}
break;
case GlobalColorMap:
case LocalColorMap:
hold[count++] = ch;
if (count == 3) {
if (++colorReadCount >= colorCount) {
if (state == LocalColorMap)
state = TableImageLZWSize;
else
state = Introducer;
}
count = 0;
}
break;
case Introducer:
hold[count++] = ch;
switch (ch) {
case 0x2c:
state = ImageDescriptor;
break;
case 0x21:
state = ExtensionLabel;
break;
case 0x3b:
state = Done;
break;
default:
done = true;
state = Error;
}
break;
case ImageDescriptor:
hold[count++] = ch;
if (count == 10) {
int newLeft = LM(hold[1], hold[2]);
int newTop = LM(hold[3], hold[4]);
int newWidth = LM(hold[5], hold[6]);
int newHeight = LM(hold[7], hold[8]);
if (imageWidth/10 > qMax(newWidth,200))
imageWidth = -1;
if (imageHeight/10 > qMax(newHeight,200))
imageHeight = -1;
if (imageWidth <= 0)
imageWidth = newLeft + newWidth;
if (imageHeight <= 0)
imageHeight = newTop + newHeight;
*imageSizes << QSize(imageWidth, imageHeight);
localColormap = !!(hold[9] & 0x80);
localColorCount = localColormap ? (2 << (hold[9] & 0x7)) : 0;
if (localColorCount)
colorCount = localColorCount;
else
colorCount = globalColorCount;
count = 0;
if (localColormap) {
int colorTableSize = 3 * localColorCount;
if (length >= colorTableSize) {
// skip the local color table in one go
length -= colorTableSize;
buffer += colorTableSize;
state = TableImageLZWSize;
} else {
colorReadCount = 0;
state = LocalColorMap;
}
} else {
state = TableImageLZWSize;
}
}
break;
case TableImageLZWSize:
if (ch > max_lzw_bits)
state = Error;
else
state = ImageDataBlockSize;
count = 0;
break;
case ImageDataBlockSize:
blockSize = ch;
if (blockSize) {
if (length >= blockSize) {
// we can skip the block in one go
length -= blockSize;
buffer += blockSize;
count = 0;
} else {
state = ImageDataBlock;
}
} else {
state = Introducer;
}
break;
case ImageDataBlock:
++count;
if (count == blockSize) {
count = 0;
state = ImageDataBlockSize;
}
break;
case ExtensionLabel:
switch (ch) {
case 0xf9:
state = GraphicControlExtension;
break;
case 0xff:
state = ApplicationExtension;
break;
default:
state = SkipBlockSize;
}
count = 0;
break;
case ApplicationExtension:
if (count < 11)
hold[count] = ch;
++count;
if (count == hold[0] + 1) {
if (qstrncmp((char*)(hold+1), "NETSCAPE", 8) == 0)
state=NetscapeExtensionBlockSize;
else
state=SkipBlockSize;
count = 0;
}
break;
case GraphicControlExtension:
if (count < 5)
hold[count] = ch;
++count;
if (count == hold[0] + 1) {
count = 0;
state = SkipBlockSize;
}
break;
case NetscapeExtensionBlockSize:
blockSize = ch;
count = 0;
if (blockSize)
state = NetscapeExtensionBlock;
else
state = Introducer;
break;
case NetscapeExtensionBlock:
if (count < 3)
hold[count] = ch;
count++;
if (count == blockSize) {
*loopCount = LM(hold[1], hold[2]);
state = SkipBlockSize;
}
break;
case SkipBlockSize:
blockSize = ch;
count = 0;
if (blockSize) {
if (length >= blockSize) {
// we can skip the block in one go
length -= blockSize;
buffer += blockSize;
} else {
state = SkipBlock;
}
} else {
state = Introducer;
}
break;
case SkipBlock:
++count;
if (count == blockSize)
state = SkipBlockSize;
break;
case Done:
done = true;
break;
case Error:
device->seek(oldPos);
return;
}
}
readBuffer = device->read(readBufferSize);
}
device->seek(oldPos);
return;
}
void QGIFFormat::fillRect(QImage *image, int col, int row, int w, int h, QRgb color)
{
if (w>0) {
for (int j=0; j<h; j++) {
QRgb *line = (QRgb*)image->scanLine(j+row);
for (int i=0; i<w; i++)
*(line+col+i) = color;
}
}
}
void QGIFFormat::nextY(unsigned char *bits, int bpl)
{
if (out_of_bounds)
return;
int my;
switch (interlace) {
case 0: // Non-interlaced
// if (!out_of_bounds) {
// ### Changed: QRect(left, y, right - left + 1, 1);
// }
y++;
break;
case 1: {
int i;
my = qMin(7, bottom-y);
// Don't dup with transparency
if (trans_index < 0) {
for (i=1; i<=my; i++) {
memcpy(FAST_SCAN_LINE(bits, bpl, y+i)+left*sizeof(QRgb), FAST_SCAN_LINE(bits, bpl, y)+left*sizeof(QRgb),
(right-left+1)*sizeof(QRgb));
}
}
// if (!out_of_bounds) {
// ### Changed: QRect(left, y, right - left + 1, my + 1);
// }
// if (!out_of_bounds)
// qDebug("consumer->changed(QRect(%d, %d, %d, %d))", left, y, right-left+1, my+1);
y+=8;
if (y>bottom) {
interlace++; y=top+4;
if (y > bottom) { // for really broken GIFs with bottom < 5
interlace=2;
y = top + 2;
if (y > bottom) { // for really broken GIF with bottom < 3
interlace = 0;
y = top + 1;
}
}
}
} break;
case 2: {
int i;
my = qMin(3, bottom-y);
// Don't dup with transparency
if (trans_index < 0) {
for (i=1; i<=my; i++) {
memcpy(FAST_SCAN_LINE(bits, bpl, y+i)+left*sizeof(QRgb), FAST_SCAN_LINE(bits, bpl, y)+left*sizeof(QRgb),
(right-left+1)*sizeof(QRgb));
}
}
// if (!out_of_bounds) {
// ### Changed: QRect(left, y, right - left + 1, my + 1);
// }
y+=8;
if (y>bottom) {
interlace++; y=top+2;
// handle broken GIF with bottom < 3
if (y > bottom) {
interlace = 3;
y = top + 1;
}
}
} break;
case 3: {
int i;
my = qMin(1, bottom-y);
// Don't dup with transparency
if (trans_index < 0) {
for (i=1; i<=my; i++) {
memcpy(FAST_SCAN_LINE(bits, bpl, y+i)+left*sizeof(QRgb), FAST_SCAN_LINE(bits, bpl, y)+left*sizeof(QRgb),
(right-left+1)*sizeof(QRgb));
}
}
// if (!out_of_bounds) {
// ### Changed: QRect(left, y, right - left + 1, my + 1);
// }
y+=4;
if (y>bottom) { interlace++; y=top+1; }
} break;
case 4:
// if (!out_of_bounds) {
// ### Changed: QRect(left, y, right - left + 1, 1);
// }
y+=2;
}
// Consume bogus extra lines
if (y >= sheight) out_of_bounds=true; //y=bottom;
}
inline QRgb QGIFFormat::color(uchar index) const
{
if (index > ncols)
return Q_TRANSPARENT;
QRgb *map = lcmap ? localcmap : globalcmap;
QRgb col = map ? map[index] : 0;
return index == trans_index ? col & Q_TRANSPARENT : col;
}
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
QGifHandler::QGifHandler()
{
gifFormat = new QGIFFormat;
nextDelay = 100;
loopCnt = -1;
frameNumber = -1;
scanIsCached = false;
}
QGifHandler::~QGifHandler()
{
delete gifFormat;
}
// Does partial decode if necessary, just to see if an image is coming
bool QGifHandler::imageIsComing() const
{
const int GifChunkSize = 4096;
while (!gifFormat->partialNewFrame) {
if (buffer.isEmpty()) {
buffer += device()->read(GifChunkSize);
if (buffer.isEmpty())
break;
}
int decoded = gifFormat->decode(&lastImage, (const uchar *)buffer.constData(), buffer.size(),
&nextDelay, &loopCnt);
if (decoded == -1)
break;
buffer.remove(0, decoded);
}
return gifFormat->partialNewFrame;
}
bool QGifHandler::canRead() const
{
if (canRead(device()) || imageIsComing()) {
setFormat("gif");
return true;
}
return false;
}
bool QGifHandler::canRead(QIODevice *device)
{
if (!device) {
qWarning("QGifHandler::canRead() called with no device");
return false;
}
char head[6];
if (device->peek(head, sizeof(head)) == sizeof(head))
return qstrncmp(head, "GIF87a", 6) == 0
|| qstrncmp(head, "GIF89a", 6) == 0;
return false;
}
bool QGifHandler::read(QImage *image)
{
const int GifChunkSize = 4096;
while (!gifFormat->newFrame) {
if (buffer.isEmpty()) {
buffer += device()->read(GifChunkSize);
if (buffer.isEmpty())
break;
}
int decoded = gifFormat->decode(&lastImage, (const uchar *)buffer.constData(), buffer.size(),
&nextDelay, &loopCnt);
if (decoded == -1)
break;
buffer.remove(0, decoded);
}
if (gifFormat->newFrame || (gifFormat->partialNewFrame && device()->atEnd())) {
*image = lastImage;
++frameNumber;
gifFormat->newFrame = false;
gifFormat->partialNewFrame = false;
return true;
}
return false;
}
bool QGifHandler::write(const QImage &image)
{
Q_UNUSED(image);
return false;
}
bool QGifHandler::supportsOption(ImageOption option) const
{
if (!device() || device()->isSequential())
return option == Animation;
else
return option == Size
|| option == Animation;
}
QVariant QGifHandler::option(ImageOption option) const
{
if (option == Size) {
if (!scanIsCached) {
QGIFFormat::scan(device(), &imageSizes, &loopCnt);
scanIsCached = true;
}
// before the first frame is read, or we have an empty data stream
if (frameNumber == -1)
return (imageSizes.count() > 0) ? QVariant(imageSizes.at(0)) : QVariant();
// after the last frame has been read, the next size is undefined
if (frameNumber >= imageSizes.count() - 1)
return QVariant();
// and the last case: the size of the next frame
return imageSizes.at(frameNumber + 1);
} else if (option == Animation) {
return true;
}
return QVariant();
}
void QGifHandler::setOption(ImageOption option, const QVariant &value)
{
Q_UNUSED(option);
Q_UNUSED(value);
}
int QGifHandler::nextImageDelay() const
{
return nextDelay;
}
int QGifHandler::imageCount() const
{
if (!scanIsCached) {
QGIFFormat::scan(device(), &imageSizes, &loopCnt);
scanIsCached = true;
}
return imageSizes.count();
}
int QGifHandler::loopCount() const
{
if (!scanIsCached) {
QGIFFormat::scan(device(), &imageSizes, &loopCnt);
scanIsCached = true;
}
if (loopCnt == 0)
return -1;
else if (loopCnt == -1)
return 0;
else
return loopCnt;
}
int QGifHandler::currentImageNumber() const
{
return frameNumber;
}
#if QT_DEPRECATED_SINCE(5, 13)
QByteArray QGifHandler::name() const
{
return "gif";
}
#endif
QT_END_NAMESPACE