//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<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 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 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 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 string &srcId, const string &dstId)
{
QWriteLocker locker(&m_imp->m_tableLock);
std::map<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 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<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 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<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, vector<TFrameId> fids, vector<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<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<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 string &id)
{
QWriteLocker locker(&m_imp->m_tableLock);
std::map<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 string &id, const TImageP &img)
{
if (!img)
return invalidate(id);
QWriteLocker locker(&m_imp->m_tableLock);
std::map<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 string &id)
{
QWriteLocker locker(&m_imp->m_tableLock);
std::map<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 string &id)
{
QWriteLocker locker(&m_imp->m_tableLock);
std::map<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 string &id)
{
QWriteLocker locker(&m_imp->m_tableLock);
std::map<string, ImageBuilderP>::iterator it = m_imp->m_builders.find(id);
return (it == m_imp->m_builders.end()) ? false : it->second->m_modified;
}