/*
Gwenview - A simple image viewer for TDE
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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
*/

#include "imageloader.h"

#include <assert.h>

// TQt
#include <tqtimer.h>
#include <tqwmatrix.h>

// KDE
#include <tdeapplication.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 TQMap< KURL, ImageLoader* > sLoaders;

//---------------------------------------------------------------------
//
// CancellableBuffer
// This class acts like TQBuffer, but will simulates a truncated file if the
// TSThread which was passed to its constructor has been asked for cancellation
//
//---------------------------------------------------------------------
class CancellableBuffer : public TQBuffer {
public:
	CancellableBuffer(TQByteArray buffer, TSThread* thread)
	: TQBuffer(buffer), mThread(thread) {}

	bool atEnd() const {
		if (mThread->testCancel()) {
			LOG("cancel detected");
			return true;
		}
		return TQBuffer::atEnd();
	}
	
	TQ_LONG readBlock(char * data, TQ_ULONG maxlen) {
		if (mThread->testCancel()) {
			LOG("cancel detected");
			return 0;
		}
		return TQBuffer::readBlock(data, maxlen);
	}

	TQ_LONG readLine(char * data, TQ_ULONG maxlen) {
		if (mThread->testCancel()) {
			LOG("cancel detected");
			return 0;
		}
		return TQBuffer::readLine(data, maxlen);
	}

	TQByteArray readAll() {
		if (mThread->testCancel()) {
			LOG("cancel detected");
			return TQByteArray();
		}
		return TQBuffer::readAll();
	}

	int getch() {
		if (mThread->testCancel()) {
			LOG("cancel detected");
			setStatus(IO_ReadError);
			return -1;
		}
		return TQBuffer::getch();
	}

private:
	TSThread* mThread;
};


//---------------------------------------------------------------------
//
// DecoderThread
//
//---------------------------------------------------------------------
void DecoderThread::run() {
	TQMutexLocker locker(&mMutex);
	LOG("");
	
	// This block makes sure imageIO won't access the image after the signal
	// has been posted
	{
		TQImageIO 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, TQ_SIGNAL(failed()) );
			return;
		}
		
		LOG("succeeded");
		mImage=imageIO.image();
	}
	
	LOG("succeeded, emitting signal");
	postSignal( this, TQ_SIGNAL(succeeded()) );
}


void DecoderThread::setRawData(const TQByteArray& data) {
	TQMutexLocker locker(&mMutex);
	mRawData=data.copy();
}


TQImage DecoderThread::popLoadedImage() {
	TQMutexLocker locker(&mMutex);
	TQImage img=mImage;
	mImage=TQImage();
	return img;
}
	


//---------------------------------------------------------------------
//
// ImageLoaderPrivate
//
//---------------------------------------------------------------------
struct OwnerData {
	const TQObject* 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
	TQDateTime mTimestamp;

	// The raw data we get
	TQByteArray mRawData;
	
	// The async decoder and it's waking timer	
	TQImageDecoder mDecoder;
	TQTimer 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
	TQRect mLoadChangedRect; 
	
	// The time since we last emitted the imageChanged() signal
	TQTime mTimeSinceLastUpdate;
	
	// Whether the loading should be suspended
	bool mSuspended;
	
	// Delay used for next frame after it's finished decoding.
	int mNextFrameDelay;

	bool mWasFrameData;

	TQImage mProcessedImage; // image frame currently being decoded

	TQRegion mLoadedRegion; // loaded parts of mProcessedImage

	ImageFrames mFrames;

	TQCString mImageFormat;

	ImageUtils::Orientation mOrientation;
	
	TQString mMimeType;
	MimeTypeUtils::Kind mURLKind;

	TQValueVector< OwnerData > mOwners; // loaders may be shared
	
	
	void determineImageFormat() {
		Q_ASSERT(mRawData.size()>0);
		TQBuffer buffer(mRawData);
		buffer.open(IO_ReadOnly);
		mImageFormat = TQImageIO::imageFormat(&buffer);
	}
};


//---------------------------------------------------------------------
//
// ImageLoader
//
//---------------------------------------------------------------------
ImageLoader::ImageLoader() {
	LOG("");
	d = new ImageLoaderPrivate(this);
	connect( BusyLevelManager::instance(), TQ_SIGNAL( busyLevelChanged(BusyLevel)),
		this, TQ_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, TQ_SIGNAL(timeout()), this, TQ_SLOT(decodeChunk()) );

	connect(&d->mDecoderThread, TQ_SIGNAL(succeeded()),
		this, TQ_SLOT(slotDecoderThreadSucceeded()) );
	connect(&d->mDecoderThread, TQ_SIGNAL(failed()),
		this, TQ_SLOT(slotDecoderThreadFailed()) );

	checkPendingStat();
}

void ImageLoader::checkPendingStat() {
	if( d->mSuspended || d->mGetState != GET_PENDING_STAT ) return;

	TDEIO::Job* job=TDEIO::stat( d->mURL, false );
	job->setWindow(TDEApplication::kApplication()->mainWidget());
	connect(job, TQ_SIGNAL(result(TDEIO::Job*)),
		this, TQ_SLOT(slotStatResult(TDEIO::Job*)) );
	d->mGetState = GET_STATING;
}

void ImageLoader::slotStatResult(TDEIO::Job* job) {
	LOG("error code: " << job->error());

	// Get modification time of the original file
	TDEIO::UDSEntry entry = static_cast<TDEIO::StatJob*>(job)->statResult();
	TDEIO::UDSEntry::ConstIterator it= entry.begin();
	TQDateTime urlTimestamp;
	for (; it!=entry.end(); it++) {
		if ((*it).m_uds == TDEIO::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
	TDEIO::Job* getJob=TDEIO::get( d->mURL, false, false);
	getJob->setWindow(TDEApplication::kApplication()->mainWidget());

	connect(getJob, TQ_SIGNAL(data(TDEIO::Job*, const TQByteArray&)),
		this, TQ_SLOT(slotDataReceived(TDEIO::Job*, const TQByteArray&)) );
	
	connect(getJob, TQ_SIGNAL(result(TDEIO::Job*)),
		this, TQ_SLOT(slotGetResult(TDEIO::Job*)) );

	d->mTimeSinceLastUpdate.start();
	d->mGetState = GET_GETTING;
}
	

void ImageLoader::slotGetResult(TDEIO::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 TQString mimeTypeFromFormat(const char* format) {
	TQStringList formats = KImageIO::types(KImageIO::Reading);
	TQStringList mimeTypes = KImageIO::mimeTypes(KImageIO::Reading);
	int pos = formats.findIndex(TQString::fromAscii(format));
	if(pos < 0) {
		// TQImageIO::imageFormat() nay return some different types than listed by
		// TQImage::inputFormats(), e.g. "PPMRAW". Also TDE might simly be ignorant
		// abous some types known to TQt.
		return TQString::null;
	}
	return mimeTypes[pos];
}

void ImageLoader::slotDataReceived(TDEIO::Job* job, const TQByteArray& 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
		TQBuffer buffer(d->mRawData);
		buffer.open(IO_ReadOnly);
		const char* format = TQImageIO::imageFormat(&buffer);
		if (format) {
			// This is a raster image, get the mime type now
			d->mMimeType = mimeTypeFromFormat(format);
			if(d->mMimeType.isNull()) { // fall back to TDE's autoguess of mime by content
				KMimeType::Ptr ptr = KMimeType::findByContent(d->mRawData);
				d->mMimeType = ptr->name();
			}
			d->mURLKind = MimeTypeUtils::KIND_RASTER_IMAGE;
		} 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 = TQMIN(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 = TQByteArray();
		d->mImageFormat = TQCString();
		d->mProcessedImage = TQImage();
		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( TQValueVector< OwnerData >::ConstIterator it = d->mOwners.begin();
			it != d->mOwners.end();
			++it ) {
		mylevel = TQMAX( 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();
}


//---------------------------------------------------------------------
//
// TQImageConsumer
//
//---------------------------------------------------------------------
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".
	TQTimer::singleShot(0, this, TQ_SLOT(callFinish()) );
}


void ImageLoader::callFinish() {
	finish(true);
}


void ImageLoader::changed(const TQRect& constRect) {
	LOG2("");
	TQRect 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) {
					TQSize size = content.size();
					d->mProcessedImage = TQImage(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
		TQImage 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
		TQWMatrix matrix = ImageUtils::transformMatrix(d->mOrientation);
		
		TQRect 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 = TQRect();
	}
}

void ImageLoader::frameDone() {
	frameDone( TQPoint( 0, 0 ), d->mDecoder.image().rect());
}

void ImageLoader::frameDone(const TQPoint& offset, const TQRect& rect) {
	LOG("");
	// Another case where the image loading in TQt'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 = TQRect();
		d->mTimeSinceLastUpdate.start();
	}
	d->mLoadedRegion = TQRegion();
	
	TQImage image;
	if (d->mProcessedImage.isNull()) {
		image = d->mDecoder.image().copy();
	} else {
		image = d->mProcessedImage.copy();
	}
	
	if( offset != TQPoint( 0, 0 ) || rect != image.rect()) {
		// Blit last frame below 'image'
		if( !d->mFrames.isEmpty()) {
			TQImage 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
}


TQImage ImageLoader::processedImage() const {
	return d->mProcessedImage;
}


ImageFrames ImageLoader::frames() const {
	return d->mFrames;
}


TQCString ImageLoader::imageFormat() const {
	return d->mImageFormat;
}


TQByteArray ImageLoader::rawData() const {
	return d->mRawData;
}


TQString ImageLoader::mimeType() const {
	return d->mMimeType;
}


MimeTypeUtils::Kind ImageLoader::urlKind() const {
	return d->mURLKind;
}


KURL ImageLoader::url() const {
	return d->mURL;
}


TQRegion ImageLoader::loadedRegion() const {
	return d->mLoadedRegion;
}


bool ImageLoader::completed() const {
	return d->mDecodeState == DECODE_DONE;
}


void ImageLoader::ref( const TQObject* owner, BusyLevel priority ) {
	OwnerData data;
	data.owner = owner;
	data.priority = priority;
	d->mOwners.append( data );
	connect( owner, TQ_SIGNAL( destroyed()), TQ_SLOT( ownerDestroyed()));
}

void ImageLoader::deref( const TQObject* owner ) {
	for( TQValueVector< 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 TQObject* owner ) {
	disconnect( owner );
	deref( owner );
}

void ImageLoader::ownerDestroyed() {
	deref( sender() );
}

//---------------------------------------------------------------------
//
// Managing loaders
//
//---------------------------------------------------------------------

ImageLoader* ImageLoader::loader( const KURL& url, const TQObject* 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.
	TQTimer::singleShot( priority >= BUSY_LOADING ? 0 : 10, loader, TQ_SLOT( startLoading()));
	return loader;
}

} // namespace
