Blob Blame Raw


#ifndef _DEBUG
#undef _STLP_DEBUG
#else
#define _STLP_DEBUG 1

#endif

#ifdef TNZCORE_LIGHT
#ifdef _DEBUGTOONZ
#undef _DEBUGTOONZ
#endif
#else
#ifdef _DEBUG
#define _DEBUGTOONZ _DEBUG
#endif
#endif

#include "timagecache.h"
#include "trasterimage.h"
#ifndef TNZCORE_LIGHT
#include "tvectorimage.h"
#include "trastercm.h"
#include "tropcm.h"
#endif

#include "tcodec.h"
#include "tfilepath_io.h"
#include "tconvert.h"
#include "tsystem.h"

#include "traster.h"

//#include "tstopwatch.h"
#include "tconvert.h"
#include "tbigmemorymanager.h"

#include "tstream.h"
#include "tenv.h"
#include <deque>
#include <numeric>
#include <sstream>
#ifdef _WIN32
#include <crtdbg.h>
#endif

// Qt includes
#include <QThreadStorage>

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

#undef DVAPI
#undef DVVAR
#ifdef TSYSTEM_EXPORTS
#define DVAPI DV_EXPORT_API
#define DVVAR DV_EXPORT_VAR
#else
#define DVAPI DV_IMPORT_API
#define DVVAR DV_IMPORT_VAR
#endif

class ImageBuilder;
class ImageInfo;

//std::ofstream os("C:\\cache.txt");

TUINT32 HistoryCount = 0;
//------------------------------------------------------------------------------

class TheCodec : public TRasterCodecLz4
{
public:
	static TheCodec *instance()
	{
		if (!_instance)
			_instance = new TheCodec();

		return _instance;
	}

	void reset()
	{
		if (_instance)
			_instance->TRasterCodecLz4::reset();
	}

private:
	static TheCodec *_instance;
	TheCodec() : TRasterCodecLz4("Lz4_Codec", false) {}
};

TheCodec *TheCodec::_instance = 0;

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

class CacheItem : public TSmartObject
{
	DECLARE_CLASS_CODE
public:
	CacheItem()
		: m_cantCompress(false), m_builder(0), m_imageInfo(0), m_modified(false) {}

	CacheItem(ImageBuilder *builder, ImageInfo *imageInfo)
		: m_cantCompress(false), m_builder(builder), m_imageInfo(imageInfo), m_historyCount(0), m_modified(false)
	{
	}

	virtual ~CacheItem() {}

	virtual TUINT32 getSize() const = 0;

	// getImage restituisce un'immagine non compressa
	virtual TImageP getImage() const = 0;

	bool m_cantCompress;
	ImageBuilder *m_builder;
	ImageInfo *m_imageInfo;
	std::string m_id;
	TUINT32 m_historyCount;
	bool m_modified;
};

#ifdef _WIN32
template class DVAPI TSmartPointerT<CacheItem>;
#endif
typedef TSmartPointerT<CacheItem> CacheItemP;

DEFINE_CLASS_CODE(CacheItem, 101)

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

class ImageInfo
{

public:
	TDimension m_size;
	ImageInfo(const TDimension &size) : m_size(size) {}
	virtual ~ImageInfo() {}
	virtual ImageInfo *clone() = 0;
};

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

class ImageBuilder
{
public:
	virtual ~ImageBuilder() {}
	virtual ImageBuilder *clone() = 0;
	virtual TImageP build(ImageInfo *info, const TRasterP &ras) = 0;
};

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

class RasterImageInfo : public ImageInfo
{
public:
	RasterImageInfo(const TRasterImageP &ri);

	void setInfo(const TRasterImageP &ri);

	ImageInfo *clone();

	double m_dpix, m_dpiy;
	std::string m_name;
	TRect m_savebox;
	bool m_isOpaque;
	TPoint m_offset;
	int m_subs;
};

RasterImageInfo::RasterImageInfo(const TRasterImageP &ri)
	: ImageInfo(ri->getRaster()->getSize())
{
	ri->getDpi(m_dpix, m_dpiy);
	m_name = ri->getName();
	m_savebox = ri->getSavebox();
	m_isOpaque = ri->isOpaque();
	m_offset = ri->getOffset();
	m_subs = ri->getSubsampling();
}

void RasterImageInfo::setInfo(const TRasterImageP &ri)
{
	ri->setDpi(m_dpix, m_dpiy);
	ri->setName(m_name);
	ri->setSavebox(m_savebox);
	ri->setOpaqueFlag(m_isOpaque);
	ri->setOffset(m_offset);
	ri->setSubsampling(m_subs);
}

ImageInfo *RasterImageInfo::clone()
{
	return new RasterImageInfo(*this);
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
#ifndef TNZCORE_LIGHT

#include "tpalette.h"
#include "ttoonzimage.h"

class ToonzImageInfo : public ImageInfo
{
public:
	ToonzImageInfo(const TToonzImageP &ti);
	~ToonzImageInfo()
	{
		if (m_palette)
			m_palette->release();
	}

	ImageInfo *clone()
	{
		ToonzImageInfo *ret = new ToonzImageInfo(*this);
		if (ret->m_palette)
			ret->m_palette->addRef();
		return ret;
	}

	void setInfo(const TToonzImageP &ti);

	double m_dpix, m_dpiy;
	std::string m_name;
	TRect m_savebox;
	TPoint m_offset;
	int m_subs;
	TPalette *m_palette;
};

ToonzImageInfo::ToonzImageInfo(const TToonzImageP &ti) : ImageInfo(ti->getSize())
{
	m_palette = ti->getPalette();
	if (m_palette)
		m_palette->addRef();

	ti->getDpi(m_dpix, m_dpiy);
	m_savebox = ti->getSavebox();
	m_offset = ti->getOffset();
	m_subs = ti->getSubsampling();
}

void ToonzImageInfo::setInfo(const TToonzImageP &ti)
{
	ti->setPalette(m_palette);
	ti->setDpi(m_dpix, m_dpiy);
	ti->setOffset(m_offset);
	ti->setSubsampling(m_subs);
}

#endif

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

class RasterImageBuilder : public ImageBuilder
{
public:
	ImageBuilder *clone()
	{
		return new RasterImageBuilder(*this);
	}

	TImageP build(ImageInfo *info, const TRasterP &ras);
};

TImageP RasterImageBuilder::build(ImageInfo *info, const TRasterP &ras)
{
	RasterImageInfo *riInfo = dynamic_cast<RasterImageInfo *>(info);
	assert(riInfo);

	int rcount = ras->getRefCount();
	TRasterImageP ri = new TRasterImage();
#ifdef _DEBUGTOONZ
	ras->m_cashed = true;
#endif
	ri->setRaster(ras);
	riInfo->setInfo(ri);
	assert(ras->getRefCount() > rcount);
	return ri;
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
#ifndef TNZCORE_LIGHT

class ToonzImageBuilder : public ImageBuilder
{
public:
	ImageBuilder *clone()
	{
		return new ToonzImageBuilder(*this);
	}

	TImageP build(ImageInfo *info, const TRasterP &ras);
};

TImageP ToonzImageBuilder::build(ImageInfo *info, const TRasterP &ras)
{
	ToonzImageInfo *tiInfo = dynamic_cast<ToonzImageInfo *>(info);
	assert(tiInfo);

	TRasterCM32P rasCM32 = ras;
	assert(rasCM32);

	TRasterCM32P imgRasCM32;

	assert(TRect(tiInfo->m_size).contains(tiInfo->m_savebox));

	if (ras->getSize() != tiInfo->m_size) {
		TRasterCM32P fullRas(tiInfo->m_size);
		TRect rectToExtract(tiInfo->m_savebox);
		TPixelCM32 bgColor;
		fullRas->fillOutside(tiInfo->m_savebox, bgColor);
		fullRas->extractT(rectToExtract)->copy(ras);
		assert(rectToExtract == tiInfo->m_savebox);
		imgRasCM32 = fullRas;
	} else
		imgRasCM32 = rasCM32;
#ifdef _DEBUG
	imgRasCM32->m_cashed = true;
#endif
	TToonzImageP ti = new TToonzImage(imgRasCM32, tiInfo->m_savebox);
	tiInfo->setInfo(ti);
	return ti;
}
#endif

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

class UncompressedOnMemoryCacheItem : public CacheItem
{
public:
	UncompressedOnMemoryCacheItem(const TImageP &image) : m_image(image)
	{
		TRasterImageP ri = m_image;

		if (ri)
			m_imageInfo = new RasterImageInfo(ri);
#ifndef TNZCORE_LIGHT
		else {
			TToonzImageP ti = m_image;
			if (ti)
				m_imageInfo = new ToonzImageInfo(ti);
			else
				m_imageInfo = 0;
		}
#else
		else
			m_imageInfo = 0;
#endif
	}

	~UncompressedOnMemoryCacheItem()
	{
		if (m_imageInfo)
			delete m_imageInfo;
	}

	TUINT32 getSize() const;
	TImageP getImage() const { return m_image; }

	TImageP m_image;
};

#ifdef _WIN32
template class DVAPI TSmartPointerT<UncompressedOnMemoryCacheItem>;
template class DVAPI TDerivedSmartPointerT<UncompressedOnMemoryCacheItem, CacheItem>;
#endif
typedef TDerivedSmartPointerT<UncompressedOnMemoryCacheItem, CacheItem> UncompressedOnMemoryCacheItemP;

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

TUINT32 UncompressedOnMemoryCacheItem::getSize() const
{
	TRasterImageP ri = m_image;
	if (ri) {
		TRasterP ras = ri->getRaster();
		if (ras)
			return ras->getLy() * ras->getRowSize();
		else
			return 0;
	} else {
#ifndef TNZCORE_LIGHT
		TToonzImageP ti = m_image;
		if (ti) {
			TDimension size = ti->getSize();
			return size.lx * size.ly * sizeof(TPixelCM32);
		}
#endif
	}

	return 0;
}

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

class CompressedOnMemoryCacheItem : public CacheItem
{
public:
	CompressedOnMemoryCacheItem(const TImageP &img);

	CompressedOnMemoryCacheItem(const TRasterP &compressedRas,
								ImageBuilder *builder,
								ImageInfo *info);

	~CompressedOnMemoryCacheItem();

	TUINT32 getSize() const;
	TImageP getImage() const;

	TRasterP m_compressedRas;
};

#ifdef _WIN32
template class DVAPI TSmartPointerT<CompressedOnMemoryCacheItem>;
template class DVAPI TDerivedSmartPointerT<CompressedOnMemoryCacheItem, CacheItem>;
#endif
typedef TDerivedSmartPointerT<CompressedOnMemoryCacheItem, CacheItem> CompressedOnMemoryCacheItemP;

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

CompressedOnMemoryCacheItem::CompressedOnMemoryCacheItem(const TImageP &img)
	: m_compressedRas()
{
	TRasterImageP ri = img;
	if (ri) {
		m_imageInfo = new RasterImageInfo(ri);
		m_builder = new RasterImageBuilder();
		TINT32 buffSize = 0;
		m_compressedRas = TheCodec::instance()->compress(ri->getRaster(), 1, buffSize);
	}
#ifndef TNZCORE_LIGHT
	else {
		TToonzImageP ti = img;
		if (ti) {
			m_imageInfo = new ToonzImageInfo(ti);
			m_builder = new ToonzImageBuilder();
			TRasterCM32P rasCM32 = ti->getRaster();
			TINT32 buffSize = 0;
			m_compressedRas = TheCodec::instance()->compress(rasCM32, 1, buffSize);
		} else
			assert(false);
	}
#else
	else
		assert(false);
#endif
}

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

CompressedOnMemoryCacheItem::CompressedOnMemoryCacheItem(const TRasterP &ras,
														 ImageBuilder *builder,
														 ImageInfo *info)
	: CacheItem(builder, info), m_compressedRas(ras)
{
}

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

CompressedOnMemoryCacheItem::~CompressedOnMemoryCacheItem()
{
	delete m_imageInfo;
}

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

TUINT32 CompressedOnMemoryCacheItem::getSize() const
{
	if (m_compressedRas)
		return m_compressedRas->getLx();
	else
		return 0;
}

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

TImageP CompressedOnMemoryCacheItem::getImage() const
{
	assert(m_compressedRas);

	// PER IL MOMENTO DISCRIMINO: DA ELIMINARE
	TRasterP ras;

	TheCodec::instance()->decompress(m_compressedRas, ras);
#ifdef _DEBUGTOONZ
	ras->m_cashed = true;
#endif
#ifndef TNZCORE_LIGHT
	ToonzImageBuilder *tibuilder = dynamic_cast<ToonzImageBuilder *>(m_builder);
	if (tibuilder)
		return tibuilder->build(m_imageInfo, ras);
	else
#endif
		return m_builder->build(m_imageInfo, ras);
}

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

class CompressedOnDiskCacheItem : public CacheItem
{
public:
	CompressedOnDiskCacheItem(const TFilePath &fp,
							  const TRasterP &compressedRas,
							  ImageBuilder *builder,
							  ImageInfo *info);

	~CompressedOnDiskCacheItem();

	TUINT32 getSize() const { return 0; }
	TImageP getImage() const;
	TFilePath m_fp;
};

#ifdef _WIN32
template class DVAPI TSmartPointerT<CompressedOnDiskCacheItem>;
template class DVAPI TDerivedSmartPointerT<CompressedOnDiskCacheItem, CacheItem>;
#endif
typedef TDerivedSmartPointerT<CompressedOnDiskCacheItem, CacheItem> CompressedOnDiskCacheItemP;

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

CompressedOnDiskCacheItem::CompressedOnDiskCacheItem(const TFilePath &fp,
													 const TRasterP &compressedRas,
													 ImageBuilder *builder,
													 ImageInfo *info)
	: CacheItem(builder, info), m_fp(fp)
{
	compressedRas->lock();

	Tofstream oss(m_fp);
	assert(compressedRas->getLy() == 1 && compressedRas->getPixelSize() == 1);
	TUINT32 size = compressedRas->getLx();
	oss.write((char *)&size, sizeof(TUINT32));
	oss.write((char *)compressedRas->getRawData(), size);
	assert(!oss.fail());

	compressedRas->unlock();
}

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

CompressedOnDiskCacheItem::~CompressedOnDiskCacheItem()
{
	delete m_imageInfo;
	TSystem::deleteFile(m_fp);
}

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

TImageP CompressedOnDiskCacheItem::getImage() const
{
	Tifstream is(m_fp);
	TUINT32 dataSize;
	is.read((char *)&dataSize, sizeof(TUINT32));
	TRasterGR8P ras(dataSize, 1);
	ras->lock();
	UCHAR *data = ras->getRawData();
	is.read((char *)data, dataSize);
	assert(!is.fail());
	ras->unlock();
	CompressedOnMemoryCacheItem item(ras, m_builder->clone(), m_imageInfo->clone());
	return item.getImage();
}

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

class UncompressedOnDiskCacheItem : public CacheItem
{
	int m_pixelsize;

public:
	UncompressedOnDiskCacheItem(const TFilePath &fp,
								const TImageP &img);

	~UncompressedOnDiskCacheItem();

	TUINT32 getSize() const { return 0; }
	TImageP getImage() const;
	//TRaster32P getRaster32() const;

	TFilePath m_fp;
};
#ifdef _WIN32
template class DVAPI TSmartPointerT<UncompressedOnDiskCacheItem>;
template class DVAPI TDerivedSmartPointerT<UncompressedOnDiskCacheItem, CacheItem>;
#endif
typedef TDerivedSmartPointerT<UncompressedOnDiskCacheItem, CacheItem> UncompressedOnDiskCacheItemP;

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

UncompressedOnDiskCacheItem::UncompressedOnDiskCacheItem(const TFilePath &fp,
														 const TImageP &image)
	: CacheItem(0, 0), m_fp(fp)
{
	TRasterImageP ri = image;

	TRasterP ras;
	if (ri) {
		m_imageInfo = new RasterImageInfo(ri);
		ras = ri->getRaster();
	}
#ifndef TNZCORE_LIGHT
	else {
		TToonzImageP ti = image;
		if (ti) {
			m_imageInfo = new ToonzImageInfo(ti);
			ras = ti->getRaster();
		} else
			assert(false);
	}
#else
	else
		assert(false);
#endif

	m_builder = 0;

	int dataSize = ras->getLx() * ras->getLy() * ras->getPixelSize();

	int lx = ras->getLx();
	int ly = ras->getLy();
	int wrap = ras->getWrap();
	m_pixelsize = ras->getPixelSize();

	Tofstream oss(m_fp);
	//oss.write((char*)&dataSize, sizeof(TUINT32));
	//assert(!oss.fail());
	ras->lock();
	if (lx == wrap) {
		oss.write((char *)ras->getRawData(), dataSize);
		assert(!oss.fail());
	} else {
		char *buf = (char *)ras->getRawData();
		for (int i = 0; i < ly; i++, buf += wrap) {
			oss.write(buf, lx * m_pixelsize);
			assert(!oss.fail());
		}
	}
	ras->unlock();
}

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

UncompressedOnDiskCacheItem::~UncompressedOnDiskCacheItem()
{
	delete m_imageInfo;
	TSystem::deleteFile(m_fp);
}

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

TImageP UncompressedOnDiskCacheItem::getImage() const
{
	Tifstream is(m_fp);
	TUINT32 dataSize = m_imageInfo->m_size.lx * m_imageInfo->m_size.ly * m_pixelsize;

	//is.read((char*)&dataSize, sizeof(TUINT32));
	//assert(unsigned(m_lx*m_ly*m_pixelsize)==dataSize);

	TRasterP ras;

	RasterImageInfo *rii = dynamic_cast<RasterImageInfo *>(m_imageInfo);

	if (rii) {
		if (m_pixelsize == 4)
			ras = (TRasterP)(TRaster32P(rii->m_size));
		else if (m_pixelsize == 8)
			ras = (TRasterP)(TRaster64P(rii->m_size));
		else if (m_pixelsize == 1)
			ras = (TRasterP)(TRasterGR8P(rii->m_size));
		else if (m_pixelsize == 2)
			ras = (TRasterP)(TRasterGR16P(rii->m_size));
		else
			assert(false);
		ras->lock();
		char *data = (char *)ras->getRawData();
		is.read(data, dataSize);
		ras->unlock();
#ifdef _DEBUGTOONZ
		ras->m_cashed = true;
#endif

		return RasterImageBuilder().build(m_imageInfo, ras);
	}
#ifndef TNZCORE_LIGHT
	else {
		ToonzImageInfo *tii = dynamic_cast<ToonzImageInfo *>(m_imageInfo);
		if (tii) {
			ras = (TRasterP)(TRasterCM32P(tii->m_size));
			ras->lock();
			char *data = (char *)ras->getRawData();
			is.read(data, dataSize);
			ras->unlock();
#ifdef _DEBUG
			ras->m_cashed = true;
#endif

			return ToonzImageBuilder().build(m_imageInfo, ras);
		} else {
			assert(false);
			return 0;
		}
	}
#else
	else {
		assert(false);
		return 0;
	}
#endif
}

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

std::string TImageCache::getUniqueId(void)
{
	static TAtomicVar count;
	std::stringstream ss;
	ss << ++count;
	return "IMAGECACHEUNIQUEID" + ss.str();
}

class TImageCache::Imp
{
public:
	Imp() : m_rootDir()
	{
		//ATTENZIONE: e' molto piu' veloce se si usa memoria fisica
		//invece che virtuale: la virtuale e' tanta, non c'e' quindi bisogno
		//di comprimere le immagini, che grandi come sono vengono swappate su disco
		if (TBigMemoryManager::instance()->isActive())
			return;

		m_reservedMemory = (TINT64)(TSystem::getMemorySize(true) * 0.10);
		if (m_reservedMemory < 64 * 1024)
			m_reservedMemory = 64 * 1024;
	}

	~Imp()
	{
		if (m_rootDir != TFilePath())
			TSystem::rmDirTree(m_rootDir);
	}

	bool inline notEnoughMemory()
	{
		if (TBigMemoryManager::instance()->isActive())
			return TBigMemoryManager::instance()->getAvailableMemoryinKb() < 50 * 1024;
		else
			return TSystem::memoryShortage();
	}

	void doCompress();
	void doCompress(std::string id);
	UCHAR *compressAndMalloc(TUINT32 requestedSize); // compress in the cache till it can nallocate the requested memory
	void outputMap(UINT chunkRequested, std::string filename);
	void remove(const std::string &id);
	void remap(const std::string &dstId, const std::string &srcId);
	TImageP get(const std::string &id, bool toBeModified);
	void add(const std::string &id, const TImageP &img, bool overwrite);
	TFilePath m_rootDir;

#ifndef TNZCORE_LIGHT
	QThreadStorage<bool *> m_isEnabled;
#else
	bool m_isEnabled;
#endif

	std::map<std::string, CacheItemP> m_uncompressedItems;
	std::map<TUINT32, std::string> m_itemHistory;
	std::map<std::string, CacheItemP> m_compressedItems;
	std::map<void *, std::string> m_itemsByImagePointer; //items ordered by ImageP.getPointer()
	std::map<std::string, std::string> m_duplicatedItems;	 //for duplicated items (when id1!=id2 but image1==image2) in the map: key is dup id, value is main id
											   //memoria fisica totale della macchina che non puo' essere utilizzata;
	TINT64 m_reservedMemory;
	TThread::Mutex m_mutex;

	static int m_fileid;
};

int TImageCache::Imp::m_fileid;

//------------------------------------------------------------------------------
namespace
{
inline void *getPointer(const TImageP &img)
{
	TRasterImageP rimg = img;
	if (rimg)
		return rimg->getRaster().getPointer();
#ifndef TNZCORE_LIGHT
	TToonzImageP timg = img;
	if (timg)
		return timg->getRaster().getPointer();
#endif

	return img.getPointer();
}

// Returns true or false whether the image or its eventual raster are
// referenced by someone other than Toonz cache.
inline TINT32 hasExternalReferences(const TImageP &img)
{
	int refCount;

	{
		TRasterImageP rimg = img;
		if (rimg)
			refCount = rimg->getRaster()->getRefCount();
	}

#ifndef TNZCORE_LIGHT
	{
		TToonzImageP timg = img;
		if (timg)
			refCount = timg->getRaster()->getRefCount() - 1; //!!! the TToonzImage::getRaster method increments raster refCount!(the TRasterImage::getRaster don't)
	}
#endif

	return std::max(refCount, img->getRefCount()) > 1;
}
}
//------------------------------------------------------------------------------

void TImageCache::Imp::doCompress()
{
	// se la memoria usata per mantenere le immagini decompresse e' superiore
	// a un dato valore, comprimo alcune immagini non compresse non checked-out
	// in modo da liberare memoria

	// per il momento scorre tutte le immagini alla ricerca di immagini
	// non compresse non checked-out

	TThread::MutexLocker sl(&m_mutex);

	std::map<TUINT32, std::string>::iterator itu = m_itemHistory.begin();

	for (; itu != m_itemHistory.end() && notEnoughMemory();) {
		std::map<std::string, CacheItemP>::iterator it = m_uncompressedItems.find(itu->second);
		assert(it != m_uncompressedItems.end());
		CacheItemP item = it->second;

		UncompressedOnMemoryCacheItemP uitem = item;
		if (item->m_cantCompress ||
			(uitem && (!uitem->m_image || hasExternalReferences(uitem->m_image)))) {
			++itu;
			continue;
		}
		std::string id = it->first;

#ifdef _WIN32
		assert(itu->first == it->second->m_historyCount);
		itu = m_itemHistory.erase(itu);
		m_itemsByImagePointer.erase(getPointer(item->getImage()));
		m_uncompressedItems.erase(it);
#else
		std::map<TUINT32, std::string>::iterator itu2 = itu;
		itu++;
		m_itemHistory.erase(itu2);
		m_itemsByImagePointer.erase(item->getImage().getPointer());
		m_uncompressedItems.erase(it);
#endif

		if (m_compressedItems.find(id) == m_compressedItems.end()) {
			assert(uitem);
			item->m_cantCompress = true;
			CacheItemP newItem = new CompressedOnMemoryCacheItem(item->getImage()); //WARNING the codec buffer  allocation can CHANGE the cache.
			item->m_cantCompress = false;
			if (newItem->getSize() == 0) ///non c'era memoria sufficiente per il buffer compresso....
			{
				assert(m_rootDir != TFilePath());
				TFilePath fp = m_rootDir + TFilePath(std::to_string(TImageCache::Imp::m_fileid++));
				newItem = new UncompressedOnDiskCacheItem(fp, item->getImage());
			}
			m_compressedItems[id] = newItem;
			item = CacheItemP();
			uitem = UncompressedOnMemoryCacheItemP();
			//doCompress();//restart, since interators can have been changed (see comment above)
			//return;
			itu = m_itemHistory.begin();
		}
	}

	// se il quantitativo di memoria utilizzata e' superiore a un dato valore, sposto
	// su disco alcune immagini compresse in modo da liberare memoria

	if (itu != m_itemHistory.end()) //memory is enough!
		return;

	std::map<std::string, CacheItemP>::iterator itc = m_compressedItems.begin();
	for (; itc != m_compressedItems.end() && notEnoughMemory(); ++itc) {
		CacheItemP item = itc->second;
		if (item->m_cantCompress)
			continue;

		CompressedOnMemoryCacheItemP citem = itc->second;
		if (citem) {
			assert(m_rootDir != TFilePath());
			TFilePath fp = m_rootDir + TFilePath(std::to_string(TImageCache::Imp::m_fileid++));

			CacheItemP newItem = new CompressedOnDiskCacheItem(fp, citem->m_compressedRas,
															   citem->m_builder->clone(), citem->m_imageInfo->clone());

			itc->second = 0;
			m_compressedItems[itc->first] = newItem;
		}
	}
}

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

void TImageCache::Imp::doCompress(std::string id)
{
	TThread::MutexLocker sl(&m_mutex);

	// search id in m_uncompressedItems
	std::map<std::string, CacheItemP>::iterator it = m_uncompressedItems.find(id);
	if (it == m_uncompressedItems.end())
		return; // id not found: return

	// is item suitable for compression ?
	CacheItemP item = it->second;
	UncompressedOnMemoryCacheItemP uitem = item;
	if (item->m_cantCompress ||
		(uitem && (!uitem->m_image || hasExternalReferences(uitem->m_image))))
		return;

	// search id in m_itemHistory
	std::map<TUINT32, std::string>::iterator itu = m_itemHistory.begin();
	while (itu != m_itemHistory.end() && itu->second != id)
		++itu;
	if (itu == m_itemHistory.end())
		return; // id not found: return

// delete itu from m_itemHistory
#ifdef _WIN32
	assert(itu->first == it->second->m_historyCount);
	itu = m_itemHistory.erase(itu);
	m_itemsByImagePointer.erase(getPointer(item->getImage()));
#else
	std::map<TUINT32, std::string>::iterator itu2 = itu;
	itu++;
	m_itemHistory.erase(itu2);
	m_itemsByImagePointer.erase(item->getImage().getPointer());
#endif

	// delete item from m_uncompressedItems
	m_uncompressedItems.erase(it);

	// check if item has been already compressed. this should never happen
	if (m_compressedItems.find(id) != m_compressedItems.end())
		return;

	assert(uitem);
	item->m_cantCompress = true;											// ??
	CacheItemP newItem = new CompressedOnMemoryCacheItem(item->getImage()); //WARNING the codec buffer  allocation can CHANGE the cache.
	item->m_cantCompress = false;											// ??
	if (newItem->getSize() == 0)											///non c'era memoria sufficiente per il buffer compresso....
	{
		assert(m_rootDir != TFilePath());
		TFilePath fp = m_rootDir + TFilePath(std::to_string(TImageCache::Imp::m_fileid++));
		newItem = new UncompressedOnDiskCacheItem(fp, item->getImage());
	}
	m_compressedItems[id] = newItem;
	item = CacheItemP();
	uitem = UncompressedOnMemoryCacheItemP();
}

/*
  // se il quantitativo di memoria utilizzata e' superiore a un dato valore, sposto
  // su disco alcune immagini compresse in modo da liberare memoria

  if (itu != m_itemHistory.end()) //memory is enough!
    return;
    
  std::map<std::string, CacheItemP>::iterator itc = m_compressedItems.begin();
  for ( ; itc != m_compressedItems.end() && notEnoughMemory(); ++itc)
	{
	  CacheItemP item = itc->second;
	  if (item->m_cantCompress)
		  continue;

	  CompressedOnMemoryCacheItemP citem = itc->second;
	  if (citem)
		{
		  assert(m_rootDir!=TFilePath());
		  TFilePath fp = m_rootDir + TFilePath(toString(TImageCache::Imp::m_fileid++));
  	  
		  CacheItemP newItem = new CompressedOnDiskCacheItem(fp, citem->m_compressedRas, 
																											   citem->m_builder->clone(), citem->m_imageInfo->clone());
  	  
		  itc->second = 0;
		  m_compressedItems[itc->first] = newItem; 
		}
	}
  */

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

UCHAR *TImageCache::Imp::compressAndMalloc(TUINT32 size)
{
	UCHAR *buf = 0;

	TThread::MutexLocker sl(&m_mutex);

	TheCodec::instance()->reset();

	//if (size!=0)
	//  size = size>>10;

	//assert(size==0 || TBigMemoryManager::instance()->isActive());

	std::map<TUINT32, std::string>::iterator itu = m_itemHistory.begin();
	while ((buf = TBigMemoryManager::instance()->getBuffer(size)) == 0 &&
		   itu != m_itemHistory.end()) //>TBigMemoryManager::instance()->getAvailableMemoryinKb()))
	{
		std::map<std::string, CacheItemP>::iterator it = m_uncompressedItems.find(itu->second);
		assert(it != m_uncompressedItems.end());
		CacheItemP item = it->second;

		UncompressedOnMemoryCacheItemP uitem = item;
		if (item->m_cantCompress || (uitem && (!uitem->m_image || hasExternalReferences(uitem->m_image)))) {
			++itu;
			continue;
		}

		if (m_compressedItems.find(it->first) == m_compressedItems.end()) {
			assert(uitem);
			CacheItemP newItem;
			//newItem = new CompressedOnMemoryCacheItem(item->getImage());
			//if (newItem->getSize()==0)
			//  {
			assert(m_rootDir != TFilePath());
			TFilePath fp = m_rootDir + TFilePath(std::to_string(TImageCache::Imp::m_fileid++));
			newItem = new UncompressedOnDiskCacheItem(fp, item->getImage());
			//  }

			m_compressedItems[it->first] = newItem;
		}

#ifdef _WIN32
		assert(itu->first == it->second->m_historyCount);
		itu = m_itemHistory.erase(itu);
		m_itemsByImagePointer.erase(getPointer(item->getImage()));
		m_uncompressedItems.erase(it);
#else
		std::map<TUINT32, std::string>::iterator itu2 = itu;
		itu++;
		m_itemHistory.erase(itu2);
		m_itemsByImagePointer.erase(item->getImage().getPointer());
		m_uncompressedItems.erase(it);
#endif
	}

	if (buf != 0)
		return buf;

	std::map<std::string, CacheItemP>::iterator itc = m_compressedItems.begin();
	for (; itc != m_compressedItems.end() &&
		   (buf = TBigMemoryManager::instance()->getBuffer(size)) == 0;
		 ++itc) {
		CacheItemP item = itc->second;
		if (item->m_cantCompress)
			continue;

		CompressedOnMemoryCacheItemP citem = itc->second;
		if (citem) {
			assert(m_rootDir != TFilePath());
			TFilePath fp = m_rootDir + TFilePath(std::to_string(TImageCache::Imp::m_fileid++));

			CacheItemP newItem = new CompressedOnDiskCacheItem(
				fp, citem->m_compressedRas,
				citem->m_builder->clone(), citem->m_imageInfo->clone());

			itc->second = 0;
			m_compressedItems[itc->first] = newItem;
		}
	}

	return buf;
}

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

namespace
{

int check = 0;
const int magic = 123456;
}

static TImageCache *CacheInstance = 0;

TImageCache *TImageCache::instance()
{
	if (CacheInstance == 0)
		CacheInstance = new TImageCache();
	return CacheInstance;
	/*
  if (!ImageCache::m_instance)
  {
    ImageCache::m_instance = new ImageCache;
    ImageCache::m_destroyer.m_imageCache = ImageCache::m_instance;
  }
  return ImageCache::m_instance;
	*/
}

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

TImageCache::TImageCache()
	: m_imp(new Imp())
{
	assert(check == 0);
	check = magic;
}

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

TImageCache::~TImageCache()
{
	assert(check == magic);
	check = -1;
	CacheInstance = 0;
}

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

void TImageCache::setEnabled(bool isEnabled)
{
#ifndef TNZCORE_LIGHT
	QThreadStorage<bool *> &storage = m_imp->m_isEnabled;

	if (storage.hasLocalData() && *(storage.localData()) == isEnabled)
		return;
	if (!storage.hasLocalData())
		storage.setLocalData(new bool(isEnabled));
	else
		*(storage.localData()) = isEnabled;
#else
	m_imp->m_isEnabled = isEnabled;
#endif
}

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

bool TImageCache::isEnabled()
{
#ifndef TNZCORE_LIGHT
	QThreadStorage<bool *> &storage = m_imp->m_isEnabled;

	if (!storage.hasLocalData())
		return true;
	return *(storage.localData());
#else
	return m_isEnabled;
#endif
}

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

void TImageCache::setRootDir(const TFilePath &cacheDir)
{
	if (m_imp->m_rootDir != TFilePath())
		return;

	m_imp->m_rootDir = cacheDir + TFilePath(std::to_string(TSystem::getProcessId()));

#ifndef TNZCORE_LIGHT
	TFileStatus fs1(m_imp->m_rootDir);

	if (!fs1.doesExist())
		TSystem::mkDir(m_imp->m_rootDir);

#endif
}

//------------------------------------------------------------------------------
/*
TFilePath TImageCache::getRootDir() const
{
return m_imp->m_rootDir;
}
*/
//------------------------------------------------------------------------------

UCHAR *TImageCache::compressAndMalloc(TUINT32 requestedSize)
{
	return m_imp->compressAndMalloc(requestedSize);
}

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

void TImageCache::add(const std::string &id, const TImageP &img, bool overwrite)
{
	if (!isEnabled())
		return;
	m_imp->add(id, img, overwrite);
}

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

void TImageCache::Imp::add(const std::string &id, const TImageP &img, bool overwrite)
{

	TThread::MutexLocker sl(&m_mutex);

#ifdef LEVO
	std::map<std::string, CacheItemP>::iterator it1 = m_uncompressedItems.begin();

	for (; it1 != m_uncompressedItems.end(); ++it1) {
		UncompressedOnMemoryCacheItemP item = (UncompressedOnMemoryCacheItemP)it1->second;
		//m_memUsage -= item->getSize();
		assert(item);
		TImageP refImg = item->getImage();
		if (refImg.getPointer() == img.getPointer() && it1->first != id)
			assert(!"opps gia' esiste in cache!");
	}
#endif

	std::map<std::string, CacheItemP>::iterator itUncompr = m_uncompressedItems.find(id);
	std::map<std::string, CacheItemP>::iterator itCompr = m_compressedItems.find(id);

#ifdef _DEBUGTOONZ
	TRasterImageP rimg = (TRasterImageP)img;
	TToonzImageP timg = (TToonzImageP)img;
#endif

	if (itUncompr != m_uncompressedItems.end() ||
		itCompr != m_compressedItems.end()) //already present in cache with same id...
	{
		if (overwrite) {
#ifdef _DEBUGTOONZ
			if (rimg)
				rimg->getRaster()->m_cashed = true;
			else if (timg)
				timg->getRaster()->m_cashed = true;
#endif
			std::map<std::string, CacheItemP>::iterator it;

			if (itUncompr != m_uncompressedItems.end()) {
				assert(m_itemHistory.find(itUncompr->second->m_historyCount) != m_itemHistory.end());
				m_itemHistory.erase(itUncompr->second->m_historyCount);
				m_itemsByImagePointer.erase(getPointer(itUncompr->second->getImage()));
				m_uncompressedItems.erase(itUncompr);
			}
			if (itCompr != m_compressedItems.end())
				m_compressedItems.erase(id);
		} else
			return;
	} else {
		std::map<std::string, std::string>::iterator dt = m_duplicatedItems.find(id);
		if ((dt != m_duplicatedItems.end()) && !overwrite)
			return;

		std::map<void *, std::string>::iterator it;
		if ((it = m_itemsByImagePointer.find(getPointer(img))) != m_itemsByImagePointer.end()) //already present in cache with another id...
		{
			m_duplicatedItems[id] = it->second;
			return;
		}

		if (dt != m_duplicatedItems.end())
			m_duplicatedItems.erase(dt);
	}

	CacheItemP item;

#ifdef _DEBUGTOONZ
	if (rimg)
		rimg->getRaster()->m_cashed = true;
	else if (timg)
		timg->getRaster()->m_cashed = true;
#endif

	item = new UncompressedOnMemoryCacheItem(img);
#ifdef TNZCORE_LIGHT
	item->m_cantCompress = false;
#else
	item->m_cantCompress = (TVectorImageP(img) ? true : false);
#endif
	item->m_id = id;
	m_uncompressedItems[id] = item;
	m_itemsByImagePointer[getPointer(img)] = id;
	item->m_historyCount = HistoryCount;
	m_itemHistory[HistoryCount] = id;
	HistoryCount++;

	doCompress();

#ifdef _DEBUGTOONZ
//int itemCount = m_imp->m_uncompressedItems.size()+m_imp->m_compressedItems.size();
//m_imp->outputDebug();
#endif
}

void TImageCache::remove(const std::string &id)
{
	m_imp->remove(id);
}

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

void TImageCache::Imp::remove(const std::string &id)
{
	if (CacheInstance == 0)
		return; //the remove can be called when exiting from toonz...after the imagecache was already freed!

	assert(check == magic);
	TThread::MutexLocker sl(&m_mutex);

	std::map<std::string, std::string>::iterator it1;
	if ((it1 = m_duplicatedItems.find(id)) != m_duplicatedItems.end()) //it's a duplicated id...
	{
		m_duplicatedItems.erase(it1);
		return;
	}

	for (it1 = m_duplicatedItems.begin(); it1 != m_duplicatedItems.end(); ++it1)
		if (it1->second == id)
			break;

	if (it1 != m_duplicatedItems.end()) //it has duplicated, so cannot erase it; I erase the duplicate, and assign its id has the main id
	{
		std::string sonId = it1->first;
		m_duplicatedItems.erase(it1);
		remap(sonId, id);
		return;
	}

	std::map<std::string, CacheItemP>::iterator it = m_uncompressedItems.find(id);
	std::map<std::string, CacheItemP>::iterator itc = m_compressedItems.find(id);
	if (it != m_uncompressedItems.end()) {
		const CacheItemP &item = it->second;
		assert((UncompressedOnMemoryCacheItemP)item);
		assert(m_itemHistory.find(it->second->m_historyCount) != m_itemHistory.end());
		m_itemHistory.erase(it->second->m_historyCount);
		m_itemsByImagePointer.erase(getPointer(it->second->getImage()));

#ifdef _DEBUGTOONZ
		if ((TRasterImageP)it->second->getImage())
			((TRasterImageP)it->second->getImage())->getRaster()->m_cashed = false;
		else if ((TToonzImageP)it->second->getImage())
			((TToonzImageP)it->second->getImage())->getRaster()->m_cashed = false;
#endif

		m_uncompressedItems.erase(it);
	}
	if (itc != m_compressedItems.end())
		m_compressedItems.erase(itc);
}

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

void TImageCache::remap(const std::string &dstId, const std::string &srcId)
{
	m_imp->remap(dstId, srcId);
}

void TImageCache::Imp::remap(const std::string &dstId, const std::string &srcId)
{

	TThread::MutexLocker sl(&m_mutex);
	std::map<std::string, CacheItemP>::iterator it = m_uncompressedItems.find(srcId);
	if (it != m_uncompressedItems.end()) {
		CacheItemP citem = it->second;
		assert(m_itemHistory.find(citem->m_historyCount) != m_itemHistory.end());
		m_itemHistory.erase(citem->m_historyCount);
		m_itemsByImagePointer.erase(getPointer(citem->getImage()));
		m_uncompressedItems.erase(it);

		m_uncompressedItems[dstId] = citem;
		m_itemHistory[citem->m_historyCount] = dstId;
		m_itemsByImagePointer[getPointer(citem->getImage())] = dstId;
	}
	it = m_compressedItems.find(srcId);
	if (it != m_compressedItems.end()) {
		CacheItemP citem = it->second;
		m_compressedItems.erase(it);
		m_compressedItems[dstId] = citem;
	}
	std::map<std::string, std::string>::iterator it2 = m_duplicatedItems.find(srcId);
	if (it2 != m_duplicatedItems.end()) {
		std::string id = it2->second;
		m_duplicatedItems.erase(it2);
		m_duplicatedItems[dstId] = id;
	}
	for (it2 = m_duplicatedItems.begin(); it2 != m_duplicatedItems.end(); ++it2)
		if (it2->second == srcId)
			it2->second = dstId;
}

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

void TImageCache::remapIcons(const std::string &dstId, const std::string &srcId)
{
	std::map<std::string, CacheItemP>::iterator it;
	std::map<std::string, std::string> table;
	std::string prefix = srcId + ":";
	int j = (int)prefix.length();
	for (it = m_imp->m_uncompressedItems.begin(); it != m_imp->m_uncompressedItems.end(); ++it) {
		std::string id = it->first;
		if (id.find(prefix) == 0)
			table[id] = dstId + ":" + id.substr(j);
	}
	for (std::map<std::string, std::string>::iterator it2 = table.begin();
		 it2 != table.end(); ++it2) {
		remap(it2->second, it2->first);
	}
}

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

void TImageCache::clear(bool deleteFolder)
{
	TThread::MutexLocker sl(&m_imp->m_mutex);
	m_imp->m_uncompressedItems.clear();
	m_imp->m_itemHistory.clear();
	m_imp->m_compressedItems.clear();
	m_imp->m_duplicatedItems.clear();
	m_imp->m_itemsByImagePointer.clear();
	if (deleteFolder && m_imp->m_rootDir != TFilePath())
		TSystem::rmDirTree(m_imp->m_rootDir);
}

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

void TImageCache::clearSceneImages()
{
	TThread::MutexLocker sl(&m_imp->m_mutex);

	//note the ';' - which follows ':' in the ascii table
	m_imp->m_uncompressedItems.erase(m_imp->m_uncompressedItems.begin(), m_imp->m_uncompressedItems.lower_bound("$:"));
	m_imp->m_uncompressedItems.erase(m_imp->m_uncompressedItems.lower_bound("$;"), m_imp->m_uncompressedItems.end());

	m_imp->m_compressedItems.erase(m_imp->m_compressedItems.begin(), m_imp->m_compressedItems.lower_bound("$:"));
	m_imp->m_compressedItems.erase(m_imp->m_compressedItems.lower_bound("$;"), m_imp->m_compressedItems.end());

	m_imp->m_duplicatedItems.erase(m_imp->m_duplicatedItems.begin(), m_imp->m_duplicatedItems.lower_bound("$:"));
	m_imp->m_duplicatedItems.erase(m_imp->m_duplicatedItems.lower_bound("$;"), m_imp->m_duplicatedItems.end());

	//Clear maps whose id is on the second of map pairs.

	std::map<TUINT32, std::string>::iterator it;
	for (it = m_imp->m_itemHistory.begin(); it != m_imp->m_itemHistory.end();) {
		if (it->second.size() >= 2 && it->second[0] == '$' && it->second[1] == ':')
			++it;
		else {
			std::map<TUINT32, std::string>::iterator app = it;
			app++;
			m_imp->m_itemHistory.erase(it);
			it = app;
		}
	}

	std::map<void *, std::string>::iterator jt;
	for (jt = m_imp->m_itemsByImagePointer.begin(); jt != m_imp->m_itemsByImagePointer.end();) {
		if (jt->second.size() >= 2 && jt->second[0] == '$' && jt->second[1] == ':')
			++jt;
		else {
			std::map<void *, std::string>::iterator app = jt;
			app++;
			m_imp->m_itemsByImagePointer.erase(jt);
			jt = app;
		}
	}
}

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

bool TImageCache::isCached(const std::string &id) const
{
	TThread::MutexLocker sl(&m_imp->m_mutex);
	return (m_imp->m_uncompressedItems.find(id) != m_imp->m_uncompressedItems.end() ||
			m_imp->m_compressedItems.find(id) != m_imp->m_compressedItems.end() ||
			m_imp->m_duplicatedItems.find(id) != m_imp->m_duplicatedItems.end());
}

//------------------------------------------------------------------------------
#ifdef LEVO

bool TImageCache::getSize(const std::string &id, TDimension &size) const
{
	QMutexLocker sl(&m_imp->m_mutex);

	std::map<std::string, CacheItemP>::iterator it = m_imp->m_uncompressedItems.find(id);
	if (it != m_imp->m_uncompressedItems.end()) {

		UncompressedOnMemoryCacheItemP uncompressed = it->second;
		assert(uncompressed);
		TToonzImageP ti = uncompressed->getImage();
		if (ti) {
			size = ti->getSize();
			return true;
		}

		TRasterImageP ri = uncompressed->getImage();
		if (ri && ri->getRaster()) {
			size = ri->getRaster()->getSize();
			return true;
		}
		return false;
	}
	std::map<std::string, CacheItemP>::iterator itc = m_imp->m_compressedItems.find(id);
	if (itc == m_imp->m_compressedItems.end())
		return false;
	CacheItemP cacheItem = itc->second;

	if (cacheItem->m_imageInfo) {
		RasterImageInfo *rimageInfo = dynamic_cast<RasterImageInfo *>(cacheItem->m_imageInfo);
		if (rimageInfo) {
			size = rimageInfo->m_size;
			return true;
		}
		ToonzImageInfo *timageInfo = dynamic_cast<ToonzImageInfo *>(cacheItem->m_imageInfo);
		if (timageInfo) {
			size = timageInfo->m_size;
			return true;
		}
	}

	return false;
}

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

bool TImageCache::getSavebox(const std::string &id, TRect &savebox) const
{
	QMutexLocker sl(&m_imp->m_mutex);

	std::map<std::string, CacheItemP>::iterator it = m_imp->m_uncompressedItems.find(id);
	if (it != m_imp->m_uncompressedItems.end()) {

		UncompressedOnMemoryCacheItemP uncompressed = it->second;
		assert(uncompressed);

		TToonzImageP ti = uncompressed->getImage();
		if (ti) {
			savebox = ti->getSavebox();
			return true;
		}
		TRasterImageP ri = uncompressed->getImage();
		if (ri) {
			savebox = ri->getSavebox();
			return true;
		}
		return false;
	}
	std::map<std::string, CacheItemP>::iterator itc = m_imp->m_compressedItems.find(id);
	if (itc == m_imp->m_compressedItems.end())
		return false;

	CacheItemP cacheItem = itc->second;

	assert(cacheItem->m_imageInfo);

	RasterImageInfo *rimageInfo = dynamic_cast<RasterImageInfo *>(cacheItem->m_imageInfo);
	if (rimageInfo) {
		savebox = rimageInfo->m_savebox;
		return true;
	}
#ifndef TNZCORE_LIGHT
	ToonzImageInfo *timageInfo = dynamic_cast<ToonzImageInfo *>(cacheItem->m_imageInfo);
	if (timageInfo) {
		savebox = timageInfo->m_savebox;
		return true;
	}
#endif
	return false;
}

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

bool TImageCache::getDpi(const std::string &id, double &dpiX, double &dpiY) const
{
	QMutexLocker sl(&m_imp->m_mutex);

	std::map<std::string, CacheItemP>::iterator it = m_imp->m_uncompressedItems.find(id);
	if (it != m_imp->m_uncompressedItems.end()) {

		UncompressedOnMemoryCacheItemP uncompressed = it->second;
		assert(uncompressed);
		TToonzImageP ti = uncompressed->getImage();
		if (ti) {
			ti->getDpi(dpiX, dpiY);
			return true;
		}
		TRasterImageP ri = uncompressed->getImage();
		if (ri) {
			ri->getDpi(dpiX, dpiY);
			return true;
		}
		return false;
	}
	std::map<std::string, CacheItemP>::iterator itc = m_imp->m_compressedItems.find(id);
	if (itc == m_imp->m_compressedItems.end())
		return false;
	CacheItemP cacheItem = itc->second;
	assert(cacheItem->m_imageInfo);

	RasterImageInfo *rimageInfo = dynamic_cast<RasterImageInfo *>(cacheItem->m_imageInfo);
	if (rimageInfo) {
		dpiX = rimageInfo->m_dpix;
		dpiY = rimageInfo->m_dpiy;
		return true;
	}
#ifndef TNZCORE_LIGHT
	ToonzImageInfo *timageInfo = dynamic_cast<ToonzImageInfo *>(cacheItem->m_imageInfo);
	if (timageInfo) {
		dpiX = timageInfo->m_dpix;
		dpiY = timageInfo->m_dpiy;
		return true;
	}
#endif
	return false;
}

//------------------------------------------------------------------------------
#endif

bool TImageCache::getSubsampling(const std::string &id, int &subs) const
{

	TThread::MutexLocker sl(&m_imp->m_mutex);

	std::map<std::string, std::string>::iterator it1;
	if ((it1 = m_imp->m_duplicatedItems.find(id)) != m_imp->m_duplicatedItems.end()) {
		assert(m_imp->m_duplicatedItems.find(it1->second) == m_imp->m_duplicatedItems.end());
		return getSubsampling(it1->second, subs);
	}

	std::map<std::string, CacheItemP>::iterator it = m_imp->m_uncompressedItems.find(id);
	if (it != m_imp->m_uncompressedItems.end()) {
		UncompressedOnMemoryCacheItemP uncompressed = it->second;
		assert(uncompressed);
#ifndef TNZCORE_LIGHT
		if (TToonzImageP ti = uncompressed->getImage()) {
			subs = ti->getSubsampling();
			return true;
		}

		else
#endif
			if (TRasterImageP ri = uncompressed->getImage()) {
			subs = ri->getSubsampling();
			return true;
		} else
			return false;
	}
	std::map<std::string, CacheItemP>::iterator itc = m_imp->m_compressedItems.find(id);
	if (itc == m_imp->m_compressedItems.end())
		return false;
	CacheItemP cacheItem = itc->second;
	assert(cacheItem->m_imageInfo);
	if (RasterImageInfo *rimageInfo = dynamic_cast<RasterImageInfo *>(cacheItem->m_imageInfo)) {
		subs = rimageInfo->m_subs;
		return true;
	}
#ifndef TNZCORE_LIGHT
	else if (ToonzImageInfo *timageInfo = dynamic_cast<ToonzImageInfo *>(cacheItem->m_imageInfo)) {
		subs = timageInfo->m_subs;
		return true;
	}
#endif
	else
		return false;
}

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

bool TImageCache::hasBeenModified(const std::string &id, bool reset) const
{
	TThread::MutexLocker sl(&m_imp->m_mutex);

	std::map<std::string, std::string>::iterator it;
	if ((it = m_imp->m_duplicatedItems.find(id)) != m_imp->m_duplicatedItems.end()) {
		assert(m_imp->m_duplicatedItems.find(it->second) == m_imp->m_duplicatedItems.end());
		return hasBeenModified(it->second, reset);
	}

	TImageP img;

	std::map<std::string, CacheItemP>::iterator itu = m_imp->m_uncompressedItems.find(id);
	if (itu != m_imp->m_uncompressedItems.end()) {
		if (reset && itu->second->m_modified) {
			itu->second->m_modified = false;
			return true;
		} else
			return itu->second->m_modified;
	}
	return true; //not present in cache==modified (for particle purposes...)
}

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

TImageP TImageCache::get(const std::string &id, bool toBeModified) const
{
	return m_imp->get(id, toBeModified);
}

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

TImageP TImageCache::Imp::get(const std::string &id, bool toBeModified)
{
	TThread::MutexLocker sl(&m_mutex);

	std::map<std::string, std::string>::const_iterator it;
	if ((it = m_duplicatedItems.find(id)) != m_duplicatedItems.end()) {
		assert(m_duplicatedItems.find(it->second) == m_duplicatedItems.end());
		return get(it->second, toBeModified);
	}

	TImageP img;

	std::map<std::string, CacheItemP>::iterator itu = m_uncompressedItems.find(id);
	if (itu != m_uncompressedItems.end()) {
		img = itu->second->getImage();
		if (itu->second->m_historyCount != HistoryCount - 1) //significa che l'ultimo get non era sulla stessa immagine, quindi  serve aggiornare l'history!
		{
			assert(m_itemHistory.find(itu->second->m_historyCount) != m_itemHistory.end());
			m_itemHistory.erase(itu->second->m_historyCount);
			m_itemHistory[HistoryCount] = id;
			itu->second->m_historyCount = HistoryCount;
			HistoryCount++;
		}
		if (toBeModified) {
			itu->second->m_modified = true;
			std::map<std::string, CacheItemP>::iterator itc = m_compressedItems.find(id);
			if (itc != m_compressedItems.end())
				m_compressedItems.erase(itc);
		}
		return img;
	}

	std::map<std::string, CacheItemP>::iterator itc = m_compressedItems.find(id);
	if (itc == m_compressedItems.end())
		return 0;

	CacheItemP cacheItem = itc->second;

	img = cacheItem->getImage();

	CacheItemP uncompressed;
	uncompressed = new UncompressedOnMemoryCacheItem(img);
	m_uncompressedItems[itc->first] = uncompressed;
	m_itemsByImagePointer[getPointer(img)] = itc->first;

	m_itemHistory[HistoryCount] = itc->first;
	uncompressed->m_historyCount = HistoryCount;
	HistoryCount++;

	if (CompressedOnMemoryCacheItemP(cacheItem))
	//l'immagine compressa non la tengo insieme alla
	//uncompressa se e' troppo grande
	{
		if (10 * cacheItem->getSize() > uncompressed->getSize()) {
			m_compressedItems.erase(itc);
			itc = m_compressedItems.end();
		}
	} else
		assert((CompressedOnDiskCacheItemP)cacheItem || (UncompressedOnDiskCacheItemP)cacheItem); //deve essere compressa!

	if (toBeModified && itc != m_compressedItems.end()) {
		uncompressed->m_modified = true;
		m_compressedItems.erase(itc);
	}

	uncompressed->m_cantCompress = toBeModified;
	// se la memoria utilizzata e' superiore al massimo consentito, comprime
	doCompress();

	uncompressed->m_cantCompress = false;

//#define DO_MEMCHECK
#ifdef DO_MEMCHECK
	assert(_CrtCheckMemory());
#endif

	return img;
}

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

namespace
{

class AccumulateMemUsage
{
public:
	int operator()(int oldValue, std::pair<std::string, CacheItemP> item)
	{
		return oldValue + item.second->getSize();
	}
};
}

UINT TImageCache::getMemUsage() const
{
	TThread::MutexLocker sl(&m_imp->m_mutex);

	int ret = std::accumulate(
		m_imp->m_uncompressedItems.begin(),
		m_imp->m_uncompressedItems.end(),
		0,
		AccumulateMemUsage());

	return ret + std::accumulate(
					 m_imp->m_compressedItems.begin(),
					 m_imp->m_compressedItems.end(),
					 0,
					 AccumulateMemUsage());
}

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

UINT TImageCache::getDiskUsage() const
{
	return 0;
}

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

UINT TImageCache::getMemUsage(const std::string &id) const
{
	std::map<std::string, CacheItemP>::iterator it = m_imp->m_uncompressedItems.find(id);
	if (it != m_imp->m_uncompressedItems.end())
		return it->second->getSize();

	it = m_imp->m_compressedItems.find(id);
	if (it != m_imp->m_compressedItems.end())
		return it->second->getSize();
	return 0;
}

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

//! Returns the uncompressed image size (in KB) of the image associated with
//! passd id, or 0 if none was found.
UINT TImageCache::getUncompressedMemUsage(const std::string &id) const
{
	std::map<std::string, CacheItemP>::iterator it = m_imp->m_uncompressedItems.find(id);
	if (it != m_imp->m_uncompressedItems.end())
		return it->second->getSize();

	it = m_imp->m_compressedItems.find(id);
	if (it != m_imp->m_compressedItems.end())
		return it->second->getSize();

	return 0;
}

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

/*
int TImageCache::getItemCount() const 
{
  return m_imp->m_uncompressedItems.size()+m_imp->m_compressedItems.size();
}
*/
//------------------------------------------------------------------------------

UINT TImageCache::getDiskUsage(const std::string &id) const
{
	return 0;
}

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

void TImageCache::dump(std::ostream &os) const
{
	os << "mem: " << getMemUsage() << std::endl;
	std::map<std::string, CacheItemP>::iterator it = m_imp->m_uncompressedItems.begin();
	for (; it != m_imp->m_uncompressedItems.end(); ++it) {
		os << it->first << std::endl;
	}
}

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

void TImageCache::outputMap(UINT chunkRequested, std::string filename)
{
	m_imp->outputMap(chunkRequested, filename);
}

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

void TImageCache::Imp::outputMap(UINT chunkRequested, std::string filename)
{
	TThread::MutexLocker sl(&m_mutex);
	//#ifdef _DEBUG
	//static int Count = 0;

	std::string st = filename /*+toString(Count++)*/ + ".txt";
	TFilePath fp(st);
	Tofstream os(fp);

	int umcount1 = 0;
	int umcount2 = 0;
	int umcount3 = 0;
	int cmcount = 0;
	int cdcount = 0;
	int umcount = 0;
	int udcount = 0;

	TUINT64 umsize1 = 0;
	TUINT64 umsize2 = 0;
	TUINT64 umsize3 = 0;
	TUINT64 cmsize = 0;
	TUINT64 cdsize = 0;
	TUINT64 umsize = 0;
	TUINT64 udsize = 0;

	std::map<std::string, CacheItemP>::iterator itu = m_uncompressedItems.begin();

	for (; itu != m_uncompressedItems.end(); ++itu) {
		UncompressedOnMemoryCacheItemP uitem = itu->second;
		if (uitem->m_image && hasExternalReferences(uitem->m_image)) {
			umcount1++;
			umsize1 += (TUINT64)(itu->second->getSize() / 1024.0);
		} else if (uitem->m_cantCompress) {
			umcount2++;
			umsize2 += (TUINT64)(itu->second->getSize() / 1024.0);
		} else {
			umcount3++;
			umsize3 += (TUINT64)(itu->second->getSize() / 1024.0);
		}
	}
	std::map<std::string, CacheItemP>::iterator itc = m_compressedItems.begin();
	for (; itc != m_compressedItems.end(); ++itc) {
		CacheItemP boh = itc->second;
		CompressedOnMemoryCacheItemP cmitem = itc->second;
		CompressedOnDiskCacheItemP cditem = itc->second;
		UncompressedOnDiskCacheItemP uditem = itc->second;
		if (cmitem) {
			cmcount++;
			cmsize += cmitem->getSize();
		} else if (cditem) {
			cdcount++;
			cdsize += cditem->getSize();
		} else {
			assert(uditem);
			udcount++;
			udsize += uditem->getSize();
		}
	}

	TUINT64 currPhisMemoryAvail = (TUINT64)(TSystem::getFreeMemorySize(true) / 1024.0);
	//TUINT64 currVirtualMemoryAvail = TSystem::getFreeMemorySize(false)/1024.0;

	os << "************************************************************\n";

	os << "***requested memory: " + std::to_string((int)chunkRequested / 1048576.0) + " MB\n";
	//os<<"*** memory in rasters: " + std::to_string((int)TRaster::getTotalMemoryInKB()/1024.0) + " MB\n";
	//os<<"***virtualmem " + std::to_string((int)currVirtualMemoryAvail) + " MB\n";
	os << "***phismem " + std::to_string((int)currPhisMemoryAvail) + " MB; percent of tot:" + std::to_string((int)((currPhisMemoryAvail * 100) / m_reservedMemory)) + "\n";
	//os<<"***bigmem available" + std::to_string((int)TBigMemoryManager::instance()->getAvailableMemoryinKb());
	os << "***uncompressed NOT compressable(refcount>1)   " + std::to_string(umcount1) + " " + std::to_string(umsize1 / 1024.0) + " MB\n";
	os << "***uncompressed NOT compressable(cantCompress)   " + std::to_string(umcount2) + " " + std::to_string(umsize2 / 1024.0) + " MB\n";
	os << "***uncompressed compressable   " + std::to_string(umcount3) + " " + std::to_string(umsize3 / 1024.0) + " MB\n";
	os << "***compressed on mem  " + std::to_string(cmcount) + " " + std::to_string((int)cmsize / 1048576.0) + " MB\n";
	os << "***compressed on disk " + std::to_string(cdcount) + " " + std::to_string((int)cdsize / 1048576.0) + " MB\n";
	os << "***uncompressed on disk " + std::to_string(udcount) + " " + std::to_string((int)udsize / 1048576.0) + " MB\n";

	//TBigMemoryManager::instance()->printMap();

	//#endif
}

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

void TImageCache::compress(const std::string &id)
{
	m_imp->doCompress(id);
}

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

#ifndef TNZCORE_LIGHT

void TImageCache::add(const QString &id, const TImageP &img, bool overwrite)
{
	if (!isEnabled())
		return;
	m_imp->add(id.toStdString(), img, overwrite);
}

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

void TImageCache::remove(const QString &id)
{
	m_imp->remove(id.toStdString());
}

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

TImageP TImageCache::get(const QString &id, bool toBeModified) const
{
	return get(id.toStdString(), toBeModified);
}

#endif

//*************************************************************************************
//    TCachedImage implementation
//*************************************************************************************

DEFINE_CLASS_CODE(TCachedImage, 103)

TCachedImage::TCachedImage()
	: TSmartObject(m_classCode), m_ref(TImageCache::instance()->getUniqueId())
{
}

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

TCachedImage::TCachedImage(const TImageP &img)
	: TSmartObject(m_classCode), m_ref(TImageCache::instance()->getUniqueId())
{
	setImage(img);
}

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

TCachedImage::~TCachedImage()
{
	TImageCache::instance()->remove(m_ref);
}

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

void TCachedImage::setImage(const TImageP &img, bool overwrite)
{
	TImageCache::instance()->add(m_ref, img, overwrite);
}

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

TImageP TCachedImage::image(bool toBeModified)
{
	return TImageCache::instance()->get(m_ref, toBeModified);
}