tahoma2d/toonz/sources/toonzlib/imagemanager.cpp
2023-07-11 22:27:44 -04:00

496 lines
15 KiB
C++

// 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;
m_imp->m_builders[dstId]->m_cached = true;
m_imp->m_builders[dstId]->m_modified = true;
TImageCache::instance()->remap(dstId, srcId);
return true;
}
bool ImageManager::renumber(const std::string &srcId, const TFrameId &fid) {
std::map<std::string, ImageBuilderP>::iterator st =
m_imp->m_builders.find(srcId);
if (st == m_imp->m_builders.end()) return false;
m_imp->m_builders[srcId]->setFid(fid);
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 reimplemented 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->setImageCached();
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;
}