From fc9450659aa3d7c9264502b76bfbfc9230a4b57c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trond=20Kjern=C3=A5sen?= Date: Thu, 4 Feb 2010 11:44:44 +0100 Subject: Cache the sizes of the images in an animated GIF. Rework the previous commit a bit and include caching of image sizes. Task-number: QTBUG-6696 Reviewed-by: Kim (cherry picked from commit 6d1d425e219b0a5e03603f7d708cd740b7d3a3f4) --- src/plugins/imageformats/gif/qgifhandler.cpp | 85 ++++++++++++++++++---------- src/plugins/imageformats/gif/qgifhandler.h | 4 +- tests/auto/qimagereader/tst_qimagereader.cpp | 1 + 3 files changed, 59 insertions(+), 31 deletions(-) diff --git a/src/plugins/imageformats/gif/qgifhandler.cpp b/src/plugins/imageformats/gif/qgifhandler.cpp index 57fae1f..fa9ad0b 100644 --- a/src/plugins/imageformats/gif/qgifhandler.cpp +++ b/src/plugins/imageformats/gif/qgifhandler.cpp @@ -71,8 +71,8 @@ public: ~QGIFFormat(); int decode(QImage *image, const uchar* buffer, int length, - int *nextFrameDelay, int *loopCount, QSize *nextSize); - static int imageCount(QIODevice *device); + int *nextFrameDelay, int *loopCount); + static void scan(QIODevice *device, QVector *imageSizes); bool newFrame; bool partialNewFrame; @@ -230,7 +230,7 @@ void QGIFFormat::disposePrevious(QImage *image) 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 @@ -347,10 +347,6 @@ int QGIFFormat::decode(QImage *image, const uchar *buffer, int length, 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); @@ -647,17 +643,17 @@ int QGIFFormat::decode(QImage *image, const uchar *buffer, int length, } /*! - Returns the number of images that can be read from \a device. + Scans through the data stream defined by \a device and returns the image + sizes found in the stream in the \a imageSizes vector. */ - -int QGIFFormat::imageCount(QIODevice *device) +void QGIFFormat::scan(QIODevice *device, QVector *imageSizes) { if (!device) - return 0; + return; qint64 oldPos = device->pos(); if (!device->seek(0)) - return 0; + return; int colorCount = 0; int localColorCount = 0; @@ -667,18 +663,21 @@ int QGIFFormat::imageCount(QIODevice *device) bool globalColormap = false; int count = 0; int blockSize = 0; + int imageWidth = 0; + int imageHeight = 0; bool done = false; uchar hold[16]; - int imageCount = 0; State state = Header; const int readBufferSize = 40960; // 40k read buffer QByteArray readBuffer(device->read(readBufferSize)); - if (readBuffer.isEmpty()) - return 0; + if (readBuffer.isEmpty()) { + device->seek(oldPos); + return; + } - // this is a specialized version of the state machine from decode(), + // 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. @@ -700,6 +699,8 @@ int QGIFFormat::imageCount(QIODevice *device) 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; @@ -753,13 +754,29 @@ int QGIFFormat::imageCount(QIODevice *device) 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; - imageCount++; count = 0; if (localColormap) { @@ -865,13 +882,13 @@ int QGIFFormat::imageCount(QIODevice *device) break; case Error: device->seek(oldPos); - return 0; + return; } } readBuffer = device->read(readBufferSize); } device->seek(oldPos); - return imageCount; + return; } void QGIFFormat::fillRect(QImage *image, int col, int row, int w, int h, QRgb color) @@ -994,8 +1011,7 @@ QGifHandler::QGifHandler() nextDelay = 0; loopCnt = 0; frameNumber = -1; - nextSize = QSize(); - imageCnt = -1; + scanIsCached = false; } QGifHandler::~QGifHandler() @@ -1017,7 +1033,7 @@ bool QGifHandler::imageIsComing() const } int decoded = gifFormat->decode(&lastImage, (const uchar *)buffer.constData(), buffer.size(), - &nextDelay, &loopCnt, &nextSize); + &nextDelay, &loopCnt); if (decoded == -1) break; buffer.remove(0, decoded); @@ -1061,7 +1077,7 @@ bool QGifHandler::read(QImage *image) } int decoded = gifFormat->decode(&lastImage, (const uchar *)buffer.constData(), buffer.size(), - &nextDelay, &loopCnt, &nextSize); + &nextDelay, &loopCnt); if (decoded == -1) break; buffer.remove(0, decoded); @@ -1092,8 +1108,18 @@ bool QGifHandler::supportsOption(ImageOption option) const QVariant QGifHandler::option(ImageOption option) const { if (option == Size) { - if (imageIsComing()) - return nextSize; + if (!scanIsCached) { + QGIFFormat::scan(device(), &imageSizes); + 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; } @@ -1113,10 +1139,11 @@ int QGifHandler::nextImageDelay() const int QGifHandler::imageCount() const { - if (imageCnt != -1) - return imageCnt; - imageCnt = QGIFFormat::imageCount(device()); - return imageCnt; + if (!scanIsCached) { + QGIFFormat::scan(device(), &imageSizes); + scanIsCached = true; + } + return imageSizes.count(); } int QGifHandler::loopCount() const diff --git a/src/plugins/imageformats/gif/qgifhandler.h b/src/plugins/imageformats/gif/qgifhandler.h index db03806..e5b1041 100644 --- a/src/plugins/imageformats/gif/qgifhandler.h +++ b/src/plugins/imageformats/gif/qgifhandler.h @@ -87,8 +87,8 @@ private: mutable int nextDelay; mutable int loopCnt; int frameNumber; - mutable QSize nextSize; - mutable int imageCnt; + mutable QVector imageSizes; + mutable bool scanIsCached; }; QT_END_NAMESPACE diff --git a/tests/auto/qimagereader/tst_qimagereader.cpp b/tests/auto/qimagereader/tst_qimagereader.cpp index 1f7b081..e3b21c8 100644 --- a/tests/auto/qimagereader/tst_qimagereader.cpp +++ b/tests/auto/qimagereader/tst_qimagereader.cpp @@ -881,6 +881,7 @@ void tst_QImageReader::gifImageCount() { QImageReader io(":images/trolltech.gif"); QVERIFY(io.imageCount() == 34); + QVERIFY(io.size() == QSize(128,64)); } } #endif -- cgit v0.12 NSLINKMODULE_OPTION_RETURN_ON_ERROR|NSLINKMODULE_OPTION_PRIVATE #endif dl_funcptr _PyImport_GetDynLoadFunc(const char *fqname, const char *shortname, const char *pathname, FILE *fp) { dl_funcptr p = NULL; char funcname[258]; NSObjectFileImageReturnCode rc; NSObjectFileImage image; NSModule newModule; NSSymbol theSym; const char *errString; char errBuf[512]; PyOS_snprintf(funcname, sizeof(funcname), "_PyInit_%.200s", shortname); #ifdef USE_DYLD_GLOBAL_NAMESPACE if (NSIsSymbolNameDefined(funcname)) { theSym = NSLookupAndBindSymbol(funcname); p = (dl_funcptr)NSAddressOfSymbol(theSym); return p; } #endif rc = NSCreateObjectFileImageFromFile(pathname, &image); switch(rc) { default: case NSObjectFileImageFailure: case NSObjectFileImageFormat: /* for these a message is printed on stderr by dyld */ errString = "Can't create object file image"; break; case NSObjectFileImageSuccess: errString = NULL; break; case NSObjectFileImageInappropriateFile: errString = "Inappropriate file type for dynamic loading"; break; case NSObjectFileImageArch: errString = "Wrong CPU type in object file"; break; case NSObjectFileImageAccess: errString = "Can't read object file (no access)"; break; } if (errString == NULL) { newModule = NSLinkModule(image, pathname, LINKOPTIONS); if (newModule == NULL) { int errNo; const char *fileName, *moreErrorStr; NSLinkEditErrors c; NSLinkEditError( &c, &errNo, &fileName, &moreErrorStr ); PyOS_snprintf(errBuf, 512, "Failure linking new module: %s: %s", fileName, moreErrorStr); errString = errBuf; } } if (errString != NULL) { PyErr_SetString(PyExc_ImportError, errString); return NULL; } #ifdef USE_DYLD_GLOBAL_NAMESPACE if (!NSIsSymbolNameDefined(funcname)) { /* UnlinkModule() isn't implemented in current versions, but calling it does no harm */ /* NSUnLinkModule(newModule, FALSE); removed: causes problems for ObjC code */ PyErr_Format(PyExc_ImportError, "Loaded module does not contain symbol %.200s", funcname); return NULL; } theSym = NSLookupAndBindSymbol(funcname); #else theSym = NSLookupSymbolInModule(newModule, funcname); if ( theSym == NULL ) { /* NSUnLinkModule(newModule, FALSE); removed: causes problems for ObjC code */ PyErr_Format(PyExc_ImportError, "Loaded module does not contain symbol %.200s", funcname); return NULL; } #endif p = (dl_funcptr)NSAddressOfSymbol(theSym); return p; }