Blob Blame Raw


//Toonz core includes
#include "timagecache.h"
#include "trasterimage.h"
#include "ttoonzimage.h"
#include "tmeshimage.h"
#include "timage_io.h"

//Qt includes (mutexing classes)
#include <QMutex>
#include <QMutexLocker>
#include <QReadWriteLock>
#include <QReadLocker>
#include <QWriteLocker>

#include "toonz/imagemanager.h"
#include "toonz/txshsimplelevel.h"

/* EXPLANATION (by Daniele):

  Images / Image Infos retrieval is quite a frequent task throughout Toonz - in particular,
  as Render operations tend to be multithreaded, it is important to ensure that the
  ImageManager treats these operations efficiently.

  Most of the image manager's job is that of caching hard-built image data so that successive
  queries avoid rebuilding the same images again.
  
  In a multithreaded environment, we must make sure that multiple threads block each other out
  as little as possible.

  Here are the main performance and threading notes:

    - Image infos are completely cached, while image cachability is user-specified.
      This is needed as some images must be loaded only temporarily.

    - One mutex will be used to protect the bindings table. It is the outermost mutex.

    - One mutex (read/write lock) will be used to protect access to EACH individual image.
      Testing should be required. If file access is found to be too strictly sequential,
      perhaps a single mutex could suffice.

    - Having different mutexes to protect images and image infos is currently not implemented,
      but could be. Testing required.
*/

/*
  TODO: TXshSimpleLevel::setFrame(...) usa aggiunte/rimozioni manuali di immagini associate
  a binding nell'ImageManager - aspettandosi che poiche' l'immagine e' presente in cache,
  verra' beccata...
*/

//************************************************************************************
//    Image Builder implementation
//************************************************************************************

DEFINE_CLASS_CODE(ImageBuilder, 100)

//-----------------------------------------------------------------------------

ImageBuilder::ImageBuilder()
	: TSmartObject(m_classCode), m_imageBuildingLock(QReadWriteLock::Recursive), m_cached(false), m_modified(false), m_imFlags(ImageManager::none)
{
}

//-----------------------------------------------------------------------------

ImageBuilder::~ImageBuilder()
{
}

//-----------------------------------------------------------------------------

bool ImageBuilder::areInfosCompatible(int imFlags, void *extData)
{
	return m_info.m_valid;
}

//-----------------------------------------------------------------------------

bool ImageBuilder::isImageCompatible(int imFlags, void *extData)
{
	return m_info.m_valid;
}

//-----------------------------------------------------------------------------

bool ImageBuilder::setImageInfo(TImageInfo &info, const TDimension &size)
{
	info = TImageInfo();
	info.m_lx = size.lx;
	info.m_ly = size.ly;
	info.m_x0 = 0;
	info.m_y0 = 0;
	info.m_x1 = size.lx - 1;
	info.m_y1 = size.ly - 1;
	info.m_valid = true;

	return true;
}

//-----------------------------------------------------------------------------

bool ImageBuilder::setImageInfo(TImageInfo &info, TImage *img)
{
	info = TImageInfo();
	if (TRasterImageP ri = TRasterImageP(img)) {
		TRasterP ras = ri->getRaster();
		info.m_lx = ras->getLx();
		info.m_ly = ras->getLy();
		ri->getDpi(info.m_dpix, info.m_dpiy);
		TRect savebox = ri->getSavebox();
		info.m_x0 = savebox.x0;
		info.m_y0 = savebox.y0;
		info.m_x1 = savebox.x1;
		info.m_y1 = savebox.y1;
	} else if (TToonzImageP ti = TToonzImageP(img)) {
		TRasterP ras = ti->getRaster();
		info.m_lx = ras->getLx();
		info.m_ly = ras->getLy();
		ti->getDpi(info.m_dpix, info.m_dpiy);
		TRect savebox = ti->getSavebox();
		info.m_x0 = savebox.x0;
		info.m_y0 = savebox.y0;
		info.m_x1 = savebox.x1;
		info.m_y1 = savebox.y1;
	} else if (TMeshImageP mi = TMeshImageP(img)) {
		mi->getDpi(info.m_dpix, info.m_dpiy);
	}

	info.m_valid = true;
	return true;
}

//-----------------------------------------------------------------------------

bool ImageBuilder::setImageInfo(TImageInfo &info, TImageReader *ir)
{
	info = TImageInfo();

	const TImageInfo *tmp = ir->getImageInfo();
	if (tmp) {
		info = *tmp;
		if (info.m_x1 < info.m_x0 || info.m_y1 < info.m_y0) {
			info.m_x0 = info.m_y0 = 0;
			info.m_x1 = info.m_lx - 1;
			info.m_y1 = info.m_ly - 1;
		}

		info.m_valid = true;
		return true;
	}

	return false;
}

//************************************************************************************
//    Image Manager Privates implementation
//************************************************************************************

struct ImageManager::Imp {
	QReadWriteLock m_tableLock;					//!< Lock for the builders table
	std::map<std::string, ImageBuilderP> m_builders; //!< identifier -> ImageBuilder table

public:
	Imp() : m_tableLock(QReadWriteLock::Recursive) {}

	void clear() { m_builders.clear(); }
};

//************************************************************************************
//    Image Manager implementation
//************************************************************************************

ImageManager::ImageManager()
	: m_imp(new Imp)
{
}

//-----------------------------------------------------------------------------

ImageManager::~ImageManager()
{
}

//-----------------------------------------------------------------------------

ImageManager *ImageManager::instance()
{
	// Re-introdotto possibile baco: voglio controllare se esiste ancora
	static ImageManager theInstance;
	return &theInstance;
}

//-----------------------------------------------------------------------------

void ImageManager::bind(const std::string &id, ImageBuilder *builderPtr)
{
	if (!builderPtr) {
		unbind(id);
		return;
	}

	QWriteLocker locker(&m_imp->m_tableLock);

	ImageBuilderP &builderP = m_imp->m_builders[id];
	if (builderP && builderP->m_cached)
		TImageCache::instance()->remove(id);

	builderP = builderPtr;
}

//-----------------------------------------------------------------------------

bool ImageManager::unbind(const std::string &id)
{
	QWriteLocker locker(&m_imp->m_tableLock);

	std::map<std::string, ImageBuilderP>::iterator it = m_imp->m_builders.find(id);
	if (it == m_imp->m_builders.end())
		return false;

	ImageBuilderP &builderP = it->second;
	if (builderP && builderP->m_cached)
		TImageCache::instance()->remove(id);

	m_imp->m_builders.erase(it);
	return true;
}

//-----------------------------------------------------------------------------

bool ImageManager::isBound(const std::string &id) const
{
	QReadLocker locker(&m_imp->m_tableLock);
	return m_imp->m_builders.find(id) != m_imp->m_builders.end();
}

//-----------------------------------------------------------------------------

bool ImageManager::rebind(const std::string &srcId, const std::string &dstId)
{
	QWriteLocker locker(&m_imp->m_tableLock);

	std::map<std::string, ImageBuilderP>::iterator st = m_imp->m_builders.find(srcId);
	if (st == m_imp->m_builders.end())
		return false;

	ImageBuilderP builder = st->second;

	m_imp->m_builders.erase(st);
	m_imp->m_builders[dstId] = builder;

	TImageCache::instance()->remap(dstId, srcId);

	return true;
}

//-----------------------------------------------------------------------------

void ImageManager::clear()
{
	QWriteLocker locker(&m_imp->m_tableLock);

	TImageCache::instance()->clearSceneImages();
	m_imp->clear();
}

//-----------------------------------------------------------------------------

TImageInfo *ImageManager::getInfo(const std::string &id, int imFlags, void *extData)
{
	// Lock for table read and try to find data in the cache
	QReadLocker tableLocker(&m_imp->m_tableLock);

	std::map<std::string, ImageBuilderP>::iterator it = m_imp->m_builders.find(id);
	if (it == m_imp->m_builders.end())
		return 0;

	ImageBuilderP &builder = it->second;

	assert(!((imFlags & ImageManager::toBeModified) && !builder->m_modified));

	// Check cached data
	if (builder->areInfosCompatible(imFlags, extData))
		return &builder->m_info;

	QWriteLocker imageBuildingLocker(&builder->m_imageBuildingLock);

	// Re-check as waiting may have changed the situation
	if (builder->areInfosCompatible(imFlags, extData))
		return &builder->m_info;

	TImageInfo info;
	if (builder->getInfo(info, imFlags, extData)) {
		builder->m_info = info;
		return &builder->m_info;
	}

	return 0;
}

//-----------------------------------------------------------------------------

TImageP ImageManager::getImage(const std::string &id, int imFlags, void *extData)
{
	assert(!((imFlags & ImageManager::toBeModified) && (imFlags & ImageManager::dontPutInCache)));
	assert(!((imFlags & ImageManager::toBeModified) && (imFlags & ImageManager::toBeSaved)));

	// Lock for table read and try to find data in the cache
	QReadLocker tableLocker(&m_imp->m_tableLock);

	std::map<std::string, ImageBuilderP>::iterator it = m_imp->m_builders.find(id);
	if (it == m_imp->m_builders.end())
		return TImageP();

	ImageBuilderP &builder = it->second;
	bool modified = builder->m_modified;

	// Analyze imFlags
	bool _putInCache = TImageCache::instance()->isEnabled() && !(bool)(imFlags & dontPutInCache);
	bool _toBeModified = (imFlags & toBeModified);
	bool _toBeSaved = (imFlags & toBeSaved);

	// Update the modified flag according to the specified flags
	if (_toBeModified)
		builder->m_modified = true;
	else if (_toBeSaved)
		builder->m_modified = false;

	// Now, fetch the image.
	TImageP img;

	if (builder->m_cached) {
		if (modified || builder->isImageCompatible(imFlags, extData)) {
			img = TImageCache::instance()->get(id, _toBeModified);

			assert(img);
			if (img)
				return img;
		}
	}

	//Lock for image building
	QWriteLocker imageBuildingLocker(&builder->m_imageBuildingLock);

	//As multiple threads may block on filesLocker, re-check if the image is now available
	if (builder->m_cached) {
		if (modified || builder->isImageCompatible(imFlags, extData)) {
			img = TImageCache::instance()->get(id, _toBeModified);

			assert(img);
			if (img)
				return img;
		}
	}

	// The image was either not available or not conforming to the required specifications.
	// We have to build it now, then.

	//Build the image
	img = builder->build(imFlags, extData);

	if (img && _putInCache) {
		builder->m_cached = true;
		builder->m_modified = _toBeModified;

		TImageCache::instance()->add(id, img, true);
	}

	return img;
}

//-----------------------------------------------------------------------------
// load icon (and image) data of all frames into cache
void ImageManager::loadAllTlvIconsAndPutInCache(TXshSimpleLevel *level, std::vector<TFrameId> fids, std::vector<std::string> iconIds, bool cacheImagesAsWell)
{
	if (fids.empty() || iconIds.empty())
		return;
	//number of fid and iconId should be the same
	if ((int)fids.size() != (int)iconIds.size())
		return;

	//obtain ImageLoader with the first fId
	TImageInfo info;
	std::map<std::string, ImageBuilderP>::iterator it = m_imp->m_builders.find(level->getImageId(fids[0]));
	if (it != m_imp->m_builders.end()) {
		const ImageBuilderP &builder = it->second;
		assert(builder);
		assert(builder->getRefCount() > 0);

		//this function in reimpremented only in ImageLoader
		builder->buildAllIconsAndPutInCache(level, fids, iconIds, cacheImagesAsWell);
		builder->getInfo(info, ImageManager::none, 0);
	}
	if (cacheImagesAsWell) {
		// reset the savebox
		info.m_x0 = info.m_y0 = 0;
		info.m_x1 = info.m_lx - 1;
		info.m_y1 = info.m_ly - 1;

		// put flags to all builders
		for (int f = 0; f < fids.size(); f++) {
			std::map<std::string, ImageBuilderP>::iterator it = m_imp->m_builders.find(level->getImageId(fids[f]));
			if (it != m_imp->m_builders.end()) {
				const ImageBuilderP &builder = it->second;
				builder->setImageCachedAndModified();
				builder->m_info = info;
			}
		}
	}
}

//-----------------------------------------------------------------------------

bool ImageManager::invalidate(const std::string &id)
{
	QWriteLocker locker(&m_imp->m_tableLock);

	std::map<std::string, ImageBuilderP>::iterator it = m_imp->m_builders.find(id);
	if (it == m_imp->m_builders.end())
		return false;

	ImageBuilderP &builder = it->second;

	builder->invalidate();
	builder->m_cached = builder->m_modified = false;

	TImageCache::instance()->remove(id);

	return true;
}

//-----------------------------------------------------------------------------

bool ImageManager::setImage(const std::string &id, const TImageP &img)
{
	if (!img)
		return invalidate(id);

	QWriteLocker locker(&m_imp->m_tableLock);

	std::map<std::string, ImageBuilderP>::iterator it = m_imp->m_builders.find(id);
	if (it == m_imp->m_builders.end())
		return false;

	ImageBuilderP &builder = it->second;

	builder->invalidate();										   // WARNING: Not all infos are correctly restored
	ImageBuilder::setImageInfo(builder->m_info, img.getPointer()); // from supplied image - must investigate further...

	TImageCache::instance()->add(id, img, true);
	builder->m_cached = builder->m_modified = true;

	return true;
}

//-----------------------------------------------------------------------------

ImageBuilder *ImageManager::getBuilder(const std::string &id)
{
	QWriteLocker locker(&m_imp->m_tableLock);

	std::map<std::string, ImageBuilderP>::iterator it = m_imp->m_builders.find(id);
	return (it == m_imp->m_builders.end()) ? (ImageBuilder *)0 : it->second.getPointer();
}

//-----------------------------------------------------------------------------

bool ImageManager::isCached(const std::string &id)
{
	QWriteLocker locker(&m_imp->m_tableLock);

	std::map<std::string, ImageBuilderP>::iterator it = m_imp->m_builders.find(id);
	return (it == m_imp->m_builders.end()) ? false : it->second->m_cached;
}

//-----------------------------------------------------------------------------

bool ImageManager::isModified(const std::string &id)
{
	QWriteLocker locker(&m_imp->m_tableLock);

	std::map<std::string, ImageBuilderP>::iterator it = m_imp->m_builders.find(id);
	return (it == m_imp->m_builders.end()) ? false : it->second->m_modified;
}