src/plugins/imageformats/gif/qgifhandler.cpp
branchRCL_3
changeset 8 3f74d0d4af4c
parent 4 3b1da2848fc7
--- a/src/plugins/imageformats/gif/qgifhandler.cpp	Mon Mar 15 12:43:09 2010 +0200
+++ b/src/plugins/imageformats/gif/qgifhandler.cpp	Thu Apr 08 14:19:33 2010 +0300
@@ -71,7 +71,8 @@
     ~QGIFFormat();
 
     int decode(QImage *image, const uchar* buffer, int length,
-               int *nextFrameDelay, int *loopCount, QSize *nextSize);
+               int *nextFrameDelay, int *loopCount);
+    static void scan(QIODevice *device, QVector<QSize> *imageSizes, int *loopCount);
 
     bool newFrame;
     bool partialNewFrame;
@@ -229,7 +230,7 @@
     Returns the number of bytes consumed.
 */
 int QGIFFormat::decode(QImage *image, const uchar *buffer, int length,
-                       int *nextFrameDelay, int *loopCount, QSize *nextSize)
+                       int *nextFrameDelay, int *loopCount)
 {
     // We are required to state that
     //    "The Graphics Interchange Format(c) is the Copyright property of
@@ -346,10 +347,6 @@
                     bpl = image->bytesPerLine();
                     bits = image->bits();
                     memset(bits, 0, image->byteCount());
-
-                    // ### size of the upcoming frame, should rather
-                    // be known before decoding it.
-                    *nextSize = QSize(swidth, sheight);
                 }
 
                 disposePrevious(image);
@@ -645,6 +642,273 @@
     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->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) {
@@ -763,9 +1027,9 @@
 {
     gifFormat = new QGIFFormat;
     nextDelay = 0;
-    loopCnt = 0;
+    loopCnt = 1;
     frameNumber = -1;
-    nextSize = QSize();
+    scanIsCached = false;
 }
 
 QGifHandler::~QGifHandler()
@@ -787,7 +1051,7 @@
         }
 
         int decoded = gifFormat->decode(&lastImage, (const uchar *)buffer.constData(), buffer.size(),
-                                        &nextDelay, &loopCnt, &nextSize);
+                                        &nextDelay, &loopCnt);
         if (decoded == -1)
             break;
         buffer.remove(0, decoded);
@@ -831,7 +1095,7 @@
         }
 
         int decoded = gifFormat->decode(&lastImage, (const uchar *)buffer.constData(), buffer.size(),
-                                        &nextDelay, &loopCnt, &nextSize);
+                                        &nextDelay, &loopCnt);
         if (decoded == -1)
             break;
         buffer.remove(0, decoded);
@@ -862,8 +1126,18 @@
 QVariant QGifHandler::option(ImageOption option) const
 {
     if (option == Size) {
-        if (imageIsComing())
-            return nextSize;
+        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;
     }
@@ -883,11 +1157,19 @@
 
 int QGifHandler::imageCount() const
 {
-    return 0; // Don't know
+    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;
+    }
     return loopCnt-1; // In GIF, loop count is iteration count, so subtract one
 }