diff options
Diffstat (limited to 'src/gvcore/imageloader.cpp')
-rw-r--r-- | src/gvcore/imageloader.cpp | 917 |
1 files changed, 917 insertions, 0 deletions
diff --git a/src/gvcore/imageloader.cpp b/src/gvcore/imageloader.cpp new file mode 100644 index 0000000..e1be4e8 --- /dev/null +++ b/src/gvcore/imageloader.cpp @@ -0,0 +1,917 @@ +// vim: set tabstop=4 shiftwidth=4 noexpandtab +/* +Gwenview - A simple image viewer for KDE +Copyright 2000-2004 Aur�lien G�teau + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "imageloader.h" + +#include <assert.h> + +// Qt +#include <qtimer.h> +#include <qwmatrix.h> + +// KDE +#include <kapplication.h> +#include <kimageio.h> +#include <kmimetype.h> + +// Local +#include "cache.h" +#include "miscconfig.h" +#include "imageutils/imageutils.h" +#include "imageutils/jpegcontent.h" + +#include "imageloader.moc" +namespace Gwenview { + +const unsigned int DECODE_CHUNK_SIZE=4096; + +/** Interval between image updates, in milli seconds */ +const int IMAGE_UPDATE_INTERVAL=100; + +#undef ENABLE_LOG +#undef LOG +#undef LOG2 + +#define ENABLE_LOG 0 + +#if ENABLE_LOG >= 1 +#define LOG(x) kdDebug() << k_funcinfo << x << endl +#else +#define LOG(x) ; +#endif + +#if ENABLE_LOG >= 2 +#define LOG2(x) kdDebug() << k_funcinfo << x << endl +#else +#define LOG2(x) ; +#endif + +static QMap< KURL, ImageLoader* > sLoaders; + +//--------------------------------------------------------------------- +// +// CancellableBuffer +// This class acts like QBuffer, but will simulates a truncated file if the +// TSThread which was passed to its constructor has been asked for cancellation +// +//--------------------------------------------------------------------- +class CancellableBuffer : public QBuffer { +public: + CancellableBuffer(QByteArray buffer, TSThread* thread) + : QBuffer(buffer), mThread(thread) {} + + bool atEnd() const { + if (mThread->testCancel()) { + LOG("cancel detected"); + return true; + } + return QBuffer::atEnd(); + } + + Q_LONG readBlock(char * data, Q_ULONG maxlen) { + if (mThread->testCancel()) { + LOG("cancel detected"); + return 0; + } + return QBuffer::readBlock(data, maxlen); + } + + Q_LONG readLine(char * data, Q_ULONG maxlen) { + if (mThread->testCancel()) { + LOG("cancel detected"); + return 0; + } + return QBuffer::readLine(data, maxlen); + } + + QByteArray readAll() { + if (mThread->testCancel()) { + LOG("cancel detected"); + return QByteArray(); + } + return QBuffer::readAll(); + } + + int getch() { + if (mThread->testCancel()) { + LOG("cancel detected"); + setStatus(IO_ReadError); + return -1; + } + return QBuffer::getch(); + } + +private: + TSThread* mThread; +}; + + +//--------------------------------------------------------------------- +// +// DecoderThread +// +//--------------------------------------------------------------------- +void DecoderThread::run() { + QMutexLocker locker(&mMutex); + LOG(""); + + // This block makes sure imageIO won't access the image after the signal + // has been posted + { + QImageIO imageIO; + + CancellableBuffer buffer(mRawData, this); + buffer.open(IO_ReadOnly); + imageIO.setIODevice(&buffer); + bool ok=imageIO.read(); + if (testCancel()) { + LOG("cancelled"); + return; + } + + if (!ok) { + LOG("failed"); + postSignal( this, SIGNAL(failed()) ); + return; + } + + LOG("succeeded"); + mImage=imageIO.image(); + } + + LOG("succeeded, emitting signal"); + postSignal( this, SIGNAL(succeeded()) ); +} + + +void DecoderThread::setRawData(const QByteArray& data) { + QMutexLocker locker(&mMutex); + mRawData=data.copy(); +} + + +QImage DecoderThread::popLoadedImage() { + QMutexLocker locker(&mMutex); + QImage img=mImage; + mImage=QImage(); + return img; +} + + + +//--------------------------------------------------------------------- +// +// ImageLoaderPrivate +// +//--------------------------------------------------------------------- +struct OwnerData { + const QObject* owner; + BusyLevel priority; +}; + +enum GetState { + GET_PENDING_STAT, // Stat has not been started + GET_STATING, // Stat has been started + GET_PENDING_GET, // Stat is done, get has not been started + GET_GETTING, // Get has been started + GET_DONE, // All data has been received +}; + + +enum DecodeState { + DECODE_WAITING, // No data to decode yet + DECODE_PENDING_THREADED_DECODING, // Waiting for all data to start threaded decoding + DECODE_THREADED_DECODING, // Threaded decoder is running + DECODE_INCREMENTAL_DECODING, // Incremental decoder is running + DECODE_INCREMENTAL_DECODING_DONE, // Incremental decoder is done + DECODE_CACHED, // Image has been obtained from cache, but raw data was missing. Wait for get to finish. + DECODE_DONE, // All done +}; + +class ImageLoaderPrivate { +public: + ImageLoaderPrivate(ImageLoader* impl) + : mDecodedSize(0) + , mGetState(GET_PENDING_STAT) + , mDecodeState(DECODE_WAITING) + , mDecoder(impl) + , mSuspended(false) + , mNextFrameDelay(0) + , mWasFrameData(false) + , mOrientation(ImageUtils::NOT_AVAILABLE) + , mURLKind(MimeTypeUtils::KIND_UNKNOWN) + {} + + // How many of the raw data we have already decoded + unsigned int mDecodedSize; + + GetState mGetState; + DecodeState mDecodeState; + + KURL mURL; + + // The file timestamp + QDateTime mTimestamp; + + // The raw data we get + QByteArray mRawData; + + // The async decoder and it's waking timer + QImageDecoder mDecoder; + QTimer mDecoderTimer; + + // The decoder thread + DecoderThread mDecoderThread; + + // A rect of recently loaded pixels that the rest of the application has + // not been notified about with the imageChanged() signal + QRect mLoadChangedRect; + + // The time since we last emitted the imageChanged() signal + QTime mTimeSinceLastUpdate; + + // Whether the loading should be suspended + bool mSuspended; + + // Delay used for next frame after it's finished decoding. + int mNextFrameDelay; + + bool mWasFrameData; + + QImage mProcessedImage; // image frame currently being decoded + + QRegion mLoadedRegion; // loaded parts of mProcessedImage + + ImageFrames mFrames; + + QCString mImageFormat; + + ImageUtils::Orientation mOrientation; + + QString mMimeType; + MimeTypeUtils::Kind mURLKind; + + QValueVector< OwnerData > mOwners; // loaders may be shared + + + void determineImageFormat() { + Q_ASSERT(mRawData.size()>0); + QBuffer buffer(mRawData); + buffer.open(IO_ReadOnly); + mImageFormat = QImageIO::imageFormat(&buffer); + } +}; + + +//--------------------------------------------------------------------- +// +// ImageLoader +// +//--------------------------------------------------------------------- +ImageLoader::ImageLoader() { + LOG(""); + d = new ImageLoaderPrivate(this); + connect( BusyLevelManager::instance(), SIGNAL( busyLevelChanged(BusyLevel)), + this, SLOT( slotBusyLevelChanged(BusyLevel))); +} + + +ImageLoader::~ImageLoader() { + LOG(""); + if (d->mDecoderThread.running()) { + d->mDecoderThread.cancel(); + d->mDecoderThread.wait(); + } + delete d; +} + + +void ImageLoader::setURL( const KURL& url ) { + assert( d->mURL.isEmpty()); + d->mURL = url; +} + +void ImageLoader::startLoading() { + d->mTimestamp = Cache::instance()->timestamp( d->mURL ); + slotBusyLevelChanged( BusyLevelManager::instance()->busyLevel()); + + connect(&d->mDecoderTimer, SIGNAL(timeout()), this, SLOT(decodeChunk()) ); + + connect(&d->mDecoderThread, SIGNAL(succeeded()), + this, SLOT(slotDecoderThreadSucceeded()) ); + connect(&d->mDecoderThread, SIGNAL(failed()), + this, SLOT(slotDecoderThreadFailed()) ); + + checkPendingStat(); +} + +void ImageLoader::checkPendingStat() { + if( d->mSuspended || d->mGetState != GET_PENDING_STAT ) return; + + KIO::Job* job=KIO::stat( d->mURL, false ); + job->setWindow(KApplication::kApplication()->mainWidget()); + connect(job, SIGNAL(result(KIO::Job*)), + this, SLOT(slotStatResult(KIO::Job*)) ); + d->mGetState = GET_STATING; +} + +void ImageLoader::slotStatResult(KIO::Job* job) { + LOG("error code: " << job->error()); + + // Get modification time of the original file + KIO::UDSEntry entry = static_cast<KIO::StatJob*>(job)->statResult(); + KIO::UDSEntry::ConstIterator it= entry.begin(); + QDateTime urlTimestamp; + for (; it!=entry.end(); it++) { + if ((*it).m_uds == KIO::UDS_MODIFICATION_TIME) { + urlTimestamp.setTime_t( (*it).m_long ); + break; + } + } + + if( d->mTimestamp.isValid() && urlTimestamp == d->mTimestamp ) { + // We have the image in cache + LOG(d->mURL << ", We have the image in cache"); + d->mRawData = Cache::instance()->file( d->mURL ); + Cache::instance()->getFrames(d->mURL, &d->mFrames, &d->mImageFormat); + + if( !d->mFrames.isEmpty()) { + LOG("The image in cache can be used"); + d->mProcessedImage = d->mFrames[0].image; + emit sizeLoaded(d->mProcessedImage.width(), d->mProcessedImage.height()); + emit imageChanged(d->mProcessedImage.rect()); + + if (d->mRawData.isNull() && d->mImageFormat=="JPEG") { + // Raw data is needed for JPEG, wait for it to be downloaded + LOG("Wait for raw data to be downloaded"); + d->mDecodeState = DECODE_CACHED; + } else { + // We don't care about raw data + finish(true); + return; + } + } else { + // Image in cache is broken + LOG("The image in cache cannot be used"); + if( !d->mRawData.isNull()) { + LOG("Using cached raw data"); + // Raw data is ok, skip get step and decode it + d->mGetState = GET_DONE; + d->mTimeSinceLastUpdate.start(); + d->mDecoderTimer.start(0, false); + return; + } + } + } + + d->mTimestamp = urlTimestamp; + d->mRawData.resize(0); + d->mGetState = GET_PENDING_GET; + checkPendingGet(); +} + +void ImageLoader::checkPendingGet() { + if( d->mSuspended || d->mGetState != GET_PENDING_GET ) return; + + // Start loading the image + KIO::Job* getJob=KIO::get( d->mURL, false, false); + getJob->setWindow(KApplication::kApplication()->mainWidget()); + + connect(getJob, SIGNAL(data(KIO::Job*, const QByteArray&)), + this, SLOT(slotDataReceived(KIO::Job*, const QByteArray&)) ); + + connect(getJob, SIGNAL(result(KIO::Job*)), + this, SLOT(slotGetResult(KIO::Job*)) ); + + d->mTimeSinceLastUpdate.start(); + d->mGetState = GET_GETTING; +} + + +void ImageLoader::slotGetResult(KIO::Job* job) { + LOG("error code: " << job->error()); + if( job->error() != 0 ) { + // failed + finish( false ); + return; + } + + d->mGetState = GET_DONE; + + // Store raw data in cache + // Note: Cache will give high cost to non-JPEG raw data. + Cache::instance()->addFile( d->mURL, d->mRawData, d->mTimestamp ); + + + switch (d->mDecodeState) { + case DECODE_CACHED: + // image was in cache, but not raw data + finish( true ); + break; + + case DECODE_PENDING_THREADED_DECODING: + // Start the decoder thread if needed + startThread(); + break; + + default: + // Finish decoding if needed + if (!d->mDecoderTimer.isActive()) d->mDecoderTimer.start(0); + } +} + +// There is no way in KImageIO to get the mimeType from the image format. +// This function assumes KImageIO::types and KImageIO::mimeTypes return items +// in the same order (which they do, according to the source code). +static QString mimeTypeFromFormat(const char* format) { + QStringList formats = KImageIO::types(KImageIO::Reading); + QStringList mimeTypes = KImageIO::mimeTypes(KImageIO::Reading); + int pos = formats.findIndex(QString::fromAscii(format)); + Q_ASSERT(pos != -1); + return mimeTypes[pos]; +} + +void ImageLoader::slotDataReceived(KIO::Job* job, const QByteArray& chunk) { + LOG2("size: " << chunk.size()); + if (chunk.size()<=0) return; + + int oldSize=d->mRawData.size(); + d->mRawData.resize(oldSize + chunk.size()); + memcpy(d->mRawData.data()+oldSize, chunk.data(), chunk.size() ); + + if (oldSize==0) { + // Try to determine the data type + QBuffer buffer(d->mRawData); + buffer.open(IO_ReadOnly); + const char* format = QImageIO::imageFormat(&buffer); + if (format) { + // This is a raster image, get the mime type now + d->mURLKind = MimeTypeUtils::KIND_RASTER_IMAGE; + d->mMimeType = mimeTypeFromFormat(format); + } else { + KMimeType::Ptr ptr = KMimeType::findByContent(d->mRawData); + d->mMimeType = ptr->name(); + d->mURLKind = MimeTypeUtils::mimeTypeKind(d->mMimeType); + } + if (d->mURLKind!=MimeTypeUtils::KIND_RASTER_IMAGE) { + Q_ASSERT(!d->mDecoderTimer.isActive()); + job->kill(true /* quietly */); + LOG("emit urlKindDetermined(!raster)"); + emit urlKindDetermined(); + return; + } + LOG("emit urlKindDetermined(raster)"); + emit urlKindDetermined(); + } + + // Decode the received data + if( !d->mDecoderTimer.isActive() && + (d->mDecodeState==DECODE_WAITING || d->mDecodeState==DECODE_INCREMENTAL_DECODING) + ) { + d->mDecoderTimer.start(0); + } +} + + +void ImageLoader::decodeChunk() { + if( d->mSuspended ) { + LOG("suspended"); + d->mDecoderTimer.stop(); + return; + } + + int chunkSize = QMIN(DECODE_CHUNK_SIZE, int(d->mRawData.size())-d->mDecodedSize); + int decodedSize = 0; + if (chunkSize>0) { + decodedSize = d->mDecoder.decode( + (const uchar*)(d->mRawData.data()+d->mDecodedSize), + chunkSize); + + if (decodedSize<0) { + // We can't use incremental decoding, switch to threaded decoding + d->mDecoderTimer.stop(); + if (d->mGetState == GET_DONE) { + startThread(); + } else { + d->mDecodeState = DECODE_PENDING_THREADED_DECODING; + } + return; + } + + // We just decoded some data + if (d->mDecodeState == DECODE_WAITING) { + d->mDecodeState = DECODE_INCREMENTAL_DECODING; + } + d->mDecodedSize+=decodedSize; + } + + if (decodedSize == 0) { + // We decoded as much as possible from the buffer, wait to receive + // more data before coming again in decodeChunk + d->mDecoderTimer.stop(); + + if (d->mGetState == GET_DONE) { + // All available data has been received. + if (d->mDecodeState == DECODE_INCREMENTAL_DECODING) { + // Decoder is not finished, the image must be truncated, + // let's simulate its end + kdWarning() << "ImageLoader::decodeChunk(): image '" << d->mURL.prettyURL() << "' is truncated.\n"; + + if (d->mProcessedImage.isNull()) { + d->mProcessedImage = d->mDecoder.image(); + } + emit imageChanged(d->mProcessedImage.rect()); + end(); + } + } + } +} + + +void ImageLoader::startThread() { + LOG("starting decoder thread"); + d->mDecodeState = DECODE_THREADED_DECODING; + d->mDecoderThread.setRawData(d->mRawData); + d->mDecoderThread.start(); +} + + +void ImageLoader::slotDecoderThreadFailed() { + LOG(""); + // Image can't be loaded + finish( false ); +} + + +void ImageLoader::slotDecoderThreadSucceeded() { + LOG(""); + d->mProcessedImage = d->mDecoderThread.popLoadedImage(); + d->mFrames.append( ImageFrame( d->mProcessedImage, 0 )); + emit sizeLoaded(d->mProcessedImage.width(), d->mProcessedImage.height()); + emit imageChanged(d->mProcessedImage.rect()); + finish(true); +} + + +/** + * Cache image and emit imageLoaded + */ +void ImageLoader::finish( bool ok ) { + LOG(""); + + d->mDecodeState = DECODE_DONE; + + if (!ok) { + d->mFrames.clear(); + d->mRawData = QByteArray(); + d->mImageFormat = QCString(); + d->mProcessedImage = QImage(); + emit imageLoaded( false ); + return; + } + + if (d->mImageFormat.isEmpty()) { + d->determineImageFormat(); + } + Q_ASSERT(d->mFrames.count() > 0); + Cache::instance()->addImage( d->mURL, d->mFrames, d->mImageFormat, d->mTimestamp ); + emit imageLoaded( true ); +} + + +BusyLevel ImageLoader::priority() const { + BusyLevel mylevel = BUSY_NONE; + for( QValueVector< OwnerData >::ConstIterator it = d->mOwners.begin(); + it != d->mOwners.end(); + ++it ) { + mylevel = QMAX( mylevel, (*it).priority ); + } + return mylevel; +} + +void ImageLoader::slotBusyLevelChanged( BusyLevel level ) { + // this loader may be needed for normal loading (BUSY_LOADING), or + // only for prefetching + BusyLevel mylevel = priority(); + if( level > mylevel ) { + suspendLoading(); + } else { + resumeLoading(); + } +} + +void ImageLoader::suspendLoading() { + d->mDecoderTimer.stop(); + d->mSuspended = true; +} + +void ImageLoader::resumeLoading() { + d->mSuspended = false; + d->mDecoderTimer.start(0, false); + checkPendingGet(); + checkPendingStat(); +} + + +//--------------------------------------------------------------------- +// +// QImageConsumer +// +//--------------------------------------------------------------------- +void ImageLoader::end() { + LOG(""); + + // Notify about the last loaded rectangle + LOG("mLoadChangedRect " << d->mLoadChangedRect); + if (!d->mLoadChangedRect.isEmpty()) { + emit imageChanged( d->mLoadChangedRect ); + } + + d->mDecoderTimer.stop(); + d->mDecodeState = DECODE_INCREMENTAL_DECODING_DONE; + + // We are done + if( d->mFrames.count() == 0 ) { + d->mFrames.append( ImageFrame( d->mProcessedImage, 0 )); + } + // The image has been totally decoded, we delay the call to finish because + // when we return from this function we will be in decodeChunk(), after the + // call to decode(), so we don't want to switch to a new impl yet, since + // this means deleting "this". + QTimer::singleShot(0, this, SLOT(callFinish()) ); +} + + +void ImageLoader::callFinish() { + finish(true); +} + + +void ImageLoader::changed(const QRect& constRect) { + LOG2(""); + QRect rect = constRect; + + if (d->mLoadedRegion.isEmpty()) { + // This is the first time we get called. Init mProcessedImage and emit + // sizeLoaded. + LOG("mLoadedRegion is empty"); + + // By default, mProcessedImage should use the image from mDecoder + d->mProcessedImage = d->mDecoder.image(); + + if (d->mImageFormat.isEmpty()) { + d->determineImageFormat(); + } + Q_ASSERT(!d->mImageFormat.isEmpty()); + if (d->mImageFormat == "JPEG") { + // This is a JPEG, extract orientation and adjust mProcessedImage + // if necessary according to misc options + ImageUtils::JPEGContent content; + + if (content.loadFromData(d->mRawData)) { + d->mOrientation = content.orientation(); + if (MiscConfig::autoRotateImages() && + d->mOrientation != ImageUtils::NOT_AVAILABLE && d->mOrientation != ImageUtils::NORMAL) { + QSize size = content.size(); + d->mProcessedImage = QImage(size, d->mDecoder.image().depth()); + } + d->mProcessedImage.setDotsPerMeterX(content.dotsPerMeterX()); + d->mProcessedImage.setDotsPerMeterY(content.dotsPerMeterY()); + } else { + kdWarning() << "ImageLoader::changed(): JPEGContent could not load '" << d->mURL.prettyURL() << "'\n"; + } + } + + LOG("emit sizeLoaded " << d->mProcessedImage.size()); + emit sizeLoaded(d->mProcessedImage.width(), d->mProcessedImage.height()); + } + + // Apply orientation if necessary and if wanted by user settings (misc options) + if (MiscConfig::autoRotateImages() && + d->mOrientation != ImageUtils::NOT_AVAILABLE && d->mOrientation != ImageUtils::NORMAL) { + // We can only rotate whole images, so copy the loaded rect in a temp + // image, rotate the temp image and copy it to mProcessedImage + + // Copy loaded rect + QImage temp(rect.size(), d->mProcessedImage.depth()); + bitBlt(&temp, 0, 0, + &d->mDecoder.image(), rect.left(), rect.top(), rect.width(), rect.height()); + + // Rotate + temp = ImageUtils::transform(temp, d->mOrientation); + + // Compute destination rect + QWMatrix matrix = ImageUtils::transformMatrix(d->mOrientation); + + QRect imageRect = d->mDecoder.image().rect(); + imageRect = matrix.mapRect(imageRect); + + rect = matrix.mapRect(rect); + rect.moveBy(-imageRect.left(), -imageRect.top()); + + // copy temp to mProcessedImage + bitBlt(&d->mProcessedImage, rect.left(), rect.top(), + &temp, 0, 0, temp.width(), temp.height()); + } + + // Update state tracking vars + d->mWasFrameData = true; + d->mLoadChangedRect |= rect; + d->mLoadedRegion |= rect; + if( d->mTimeSinceLastUpdate.elapsed() > IMAGE_UPDATE_INTERVAL ) { + LOG("emitting imageChanged " << d->mLoadChangedRect); + d->mTimeSinceLastUpdate.start(); + emit imageChanged(d->mLoadChangedRect); + d->mLoadChangedRect = QRect(); + } +} + +void ImageLoader::frameDone() { + frameDone( QPoint( 0, 0 ), d->mDecoder.image().rect()); +} + +void ImageLoader::frameDone(const QPoint& offset, const QRect& rect) { + LOG(""); + // Another case where the image loading in Qt's is a bit borken. + // It's possible to get several notes about a frame being done for one frame (with MNG). + if( !d->mWasFrameData ) { + // To make it even more fun, with MNG the sequence is actually + // setFramePeriod( 0 ) + // frameDone() + // setFramePeriod( delay ) + // frameDone() + // Therefore ignore the second frameDone(), but fix the delay that should be + // after the frame. + if( d->mFrames.count() > 0 ) { + d->mFrames.last().delay = d->mNextFrameDelay; + d->mNextFrameDelay = 0; + } + return; + } + d->mWasFrameData = false; + if( !d->mLoadChangedRect.isEmpty()) { + emit imageChanged(d->mLoadChangedRect); + d->mLoadChangedRect = QRect(); + d->mTimeSinceLastUpdate.start(); + } + d->mLoadedRegion = QRegion(); + + QImage image; + if (d->mProcessedImage.isNull()) { + image = d->mDecoder.image().copy(); + } else { + image = d->mProcessedImage.copy(); + } + + if( offset != QPoint( 0, 0 ) || rect != image.rect()) { + // Blit last frame below 'image' + if( !d->mFrames.isEmpty()) { + QImage im = d->mFrames.last().image.copy(); + bitBlt( &im, offset.x(), offset.y(), &image, rect.x(), rect.y(), rect.width(), rect.height()); + image = im; + } + } + d->mFrames.append( ImageFrame( image, d->mNextFrameDelay )); + d->mNextFrameDelay = 0; +} + +void ImageLoader::setLooping(int) { +} + +void ImageLoader::setFramePeriod(int milliseconds) { + if( milliseconds < 0 ) milliseconds = 0; // -1 means showing immediately + if( d->mNextFrameDelay == 0 || milliseconds != 0 ) { + d->mNextFrameDelay = milliseconds; + } +} + +void ImageLoader::setSize(int, int) { + // Do nothing, size is handled when ::changed() is called for the first + // time +} + + +QImage ImageLoader::processedImage() const { + return d->mProcessedImage; +} + + +ImageFrames ImageLoader::frames() const { + return d->mFrames; +} + + +QCString ImageLoader::imageFormat() const { + return d->mImageFormat; +} + + +QByteArray ImageLoader::rawData() const { + return d->mRawData; +} + + +QString ImageLoader::mimeType() const { + return d->mMimeType; +} + + +MimeTypeUtils::Kind ImageLoader::urlKind() const { + return d->mURLKind; +} + + +KURL ImageLoader::url() const { + return d->mURL; +} + + +QRegion ImageLoader::loadedRegion() const { + return d->mLoadedRegion; +} + + +bool ImageLoader::completed() const { + return d->mDecodeState == DECODE_DONE; +} + + +void ImageLoader::ref( const QObject* owner, BusyLevel priority ) { + OwnerData data; + data.owner = owner; + data.priority = priority; + d->mOwners.append( data ); + connect( owner, SIGNAL( destroyed()), SLOT( ownerDestroyed())); +} + +void ImageLoader::deref( const QObject* owner ) { + for( QValueVector< OwnerData >::Iterator it = d->mOwners.begin(); + it != d->mOwners.end(); + ++it ) { + if( (*it).owner == owner ) { + d->mOwners.erase( it ); + if( d->mOwners.count() == 0 ) { + sLoaders.remove( d->mURL ); + delete this; + } + return; + } + } + assert( false ); +} + +void ImageLoader::release( const QObject* owner ) { + disconnect( owner ); + deref( owner ); +} + +void ImageLoader::ownerDestroyed() { + deref( sender()); +} + +//--------------------------------------------------------------------- +// +// Managing loaders +// +//--------------------------------------------------------------------- + +ImageLoader* ImageLoader::loader( const KURL& url, const QObject* owner, BusyLevel priority ) { + if( sLoaders.contains( url )) { + ImageLoader* loader = sLoaders[ url ]; + loader->ref( owner, priority ); + // resume if this owner has high priority + loader->slotBusyLevelChanged( BusyLevelManager::instance()->busyLevel()); + return loader; + } + ImageLoader* loader = new ImageLoader; + loader->ref( owner, priority ); + sLoaders[ url ] = loader; + loader->setURL( url ); + // Code using a loader first calls loader() to get ImageLoader* and only after that it can + // connect to its signals etc., so don't start loading immediately. + // This also helps with preloading jobs, since BUSY_LOADING busy level is not entered immediately + // when a new picture is selected, so preloading jobs without this delay could start working + // immediately. + QTimer::singleShot( priority >= BUSY_LOADING ? 0 : 10, loader, SLOT( startLoading())); + return loader; +} + +} // namespace |