aboutsummaryrefslogtreecommitdiffstats
//=============================================================================
//
//   File : KviAnimatedPixmapCache.cpp
//   Creation date : Thu Jul 31 2008 01:45:21 CEST by Alexey Uzhva
//
//   This file is part of the KVIrc IRC client distribution
//   Copyright (C) 2008 Alexey Uzhva (wizard at opendoor dot ru)
//
//   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 "KviAnimatedPixmapCache.h"
#include "KviTimeUtils.h"

#include <QImageReader>
#include <QImage>

#define FRAME_DELAY 100

KviAnimatedPixmapCache * KviAnimatedPixmapCache::m_pInstance = nullptr;
static QPixmap * g_pDummyPixmap = nullptr;

KviAnimatedPixmapCache::KviAnimatedPixmapCache()
{
	m_pInstance = this;
	m_animationTimer.setInterval(FRAME_DELAY);
	connect(&m_animationTimer, SIGNAL(timeout()), this, SLOT(timeoutEvent()));
}

KviAnimatedPixmapCache::~KviAnimatedPixmapCache()
{
	if(g_pDummyPixmap)
	{
		delete g_pDummyPixmap;
		g_pDummyPixmap = nullptr;
	}
	m_pInstance = nullptr;
}

void KviAnimatedPixmapCache::init()
{
	if(m_pInstance)
		return;
	m_pInstance = new KviAnimatedPixmapCache();
}

void KviAnimatedPixmapCache::done()
{
	if(!m_pInstance)
		return;
	delete m_pInstance;
	m_pInstance = nullptr;
}

KviAnimatedPixmapCache::Data * KviAnimatedPixmapCache::internalLoad(const QString & szFile, int iWidth, int iHeight)
{
	m_cacheMutex.lock();
	Data * newData = nullptr;

	QMultiHash<QString, Data *>::iterator i = m_hCache.find(szFile);
	while(i != m_hCache.end() && i.key() == szFile && !newData)
	{
		if(!i.value()->resized)
			newData = i.value();
		++i;
	}

	if(!newData)
	{
		newData = new Data(szFile);
		QImageReader reader(szFile);
		newData->size = reader.size();

		QImage buffer;
		QPixmap * framePixmap;
		while(reader.canRead())
		{
			uint delay = reader.nextImageDelay();
			reader.read(&buffer);
			if(!buffer.isNull())
			{
				if(iHeight && iWidth)
					framePixmap = new QPixmap(QPixmap::fromImage(buffer).scaled(iWidth, iHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation));
				else
					framePixmap = new QPixmap(QPixmap::fromImage(buffer));
				newData->append(FrameInfo(framePixmap, delay));
			}
		}
		m_hCache.insert(szFile, newData);
	}
	newData->refs++;

	m_cacheMutex.unlock();

	return newData;
}

KviAnimatedPixmapCache::Data * KviAnimatedPixmapCache::internalResize(Data * data, const QSize & size)
{
	m_cacheMutex.lock();

	bool hasToBeResized = false;

	Data * newData = nullptr;
	QMultiHash<QString, Data *>::iterator i = m_hCache.find(data->file);
	while(i != m_hCache.end() && i.key() == data->file && !newData)
	{
		if(i.value()->size == size)
		{
			newData = i.value();
		}
		++i;
	}

	if(!newData)
	{
		newData = new Data(*data);
		newData->size = size;
		m_hCache.insert(newData->file, newData);
		hasToBeResized = true;
		newData->resized = true;
	}

	newData->refs++;

	m_cacheMutex.unlock();

	internalFree(data);

	if(hasToBeResized)
	{
		for(int i = 0; i < newData->count(); i++)
		{
			QPixmap * old = newData->at(i).pixmap;
			newData->operator[](i).pixmap = new QPixmap(old->scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
			delete old;
		}
	}

	return newData;
}

void KviAnimatedPixmapCache::internalFree(Data * data)
{
	if(!data)
		return;
	m_cacheMutex.lock();
	data->refs--;
	if(data->refs == 0)
	{
		m_hCache.remove(data->file, data);
		for(int i = 0; i < data->count(); i++)
		{
			delete data->operator[](i).pixmap;
		}
		delete data;
	}
	m_cacheMutex.unlock();
}

void KviAnimatedPixmapCache::internalScheduleFrameChange(uint delay, KviAnimatedPixmapInterface * receiver)
{
	//qDebug("Adding %i - %i",(uint)KviTimeUtils::getCurrentTimeMills()+delay,receiver);
	m_timerMutex.lock();
	long long when = KviTimeUtils::getCurrentTimeMills() + delay;

	m_timerData.insert(when, receiver);

	if(!m_animationTimer.isActive())
		m_animationTimer.start();

	m_timerMutex.unlock();
}

void KviAnimatedPixmapCache::timeoutEvent()
{
	/*
	* We are adding 15msecs to the current time. This MAY lead to the situation,
	* when the current frame will be painted a bit earlier, then I should.
	* But we are just playing animated gifs, not a HDTV video. So it should be OK.
	*
	* But it makes good speedup if there will be event, scheduled in such order:
	* 1 event  at time X
	* 3 events at time X+2msec
	* 2 events at time X+6msec
	* 1 event  at time X+8msec
	* 1 event  at time X+9msec
	* 1 event  at time X+12msec
	* ...
	*
	* Such situation is possible if we will have lots of smiles with almost the same
	* frame delays.
	*
	* So it will not emit additional 5 events.
	*/
	long long now = KviTimeUtils::getCurrentTimeMills() + FRAME_DELAY;

	//m_timerMutex.lock();

	QMultiMap<long long, KviAnimatedPixmapInterface *>::iterator i = m_timerData.begin();

	QList<KviAnimatedPixmapInterface *> processed;
	while(i != m_timerData.end() && i.key() <= now)
	{
		if(processed.contains(i.value()))
		{
			// increase the frame index but do not emit the signals as we'll be emitting it later
			i.value()->nextFrame(false);
		}
		else
		{
			// we'll be emitting the signal later
			processed.append(i.value());
		}

		i = m_timerData.erase(i);
	}

	for(auto i : processed)
	{
		// increase the frame index and emit the signal
		i->nextFrame(true);
	}

	// m_timerMutex.unlock();

	if(m_timerData.empty())
		m_animationTimer.stop();
}

QPixmap * KviAnimatedPixmapCache::dummyPixmap()
{
	if(!g_pDummyPixmap)
		g_pDummyPixmap = new QPixmap();
	return g_pDummyPixmap;
}

void KviAnimatedPixmapCache::internalNotifyDelete(
    KviAnimatedPixmapInterface * receiver)
{
	m_timerMutex.lock();

	QMultiMap<long long, KviAnimatedPixmapInterface *>::iterator i = m_timerData.begin();

	while(i != m_timerData.end())
	{
		if(i.value() == receiver)
		{
			i = m_timerData.erase(i);
		}
		else
		{
			++i;
		}
	}

	m_timerMutex.unlock();
}