460 lines
15 KiB
C++
460 lines
15 KiB
C++
|
|
|
|
#include "drawingdata.h"
|
|
#include "filmstripcommand.h"
|
|
#include "timagecache.h"
|
|
#include "tpaletteutil.h"
|
|
#include "ttoonzimage.h"
|
|
#include "tvectorimage.h"
|
|
#include "trasterimage.h"
|
|
#include "trop.h"
|
|
#include "tstroke.h"
|
|
#include "toonzqt/icongenerator.h"
|
|
#include "toonzqt/dvdialog.h"
|
|
#include "toonz/txshsimplelevel.h"
|
|
#include "toonz/toonzimageutils.h"
|
|
#include "toonz/txshleveltypes.h"
|
|
#include "toonz/stage.h"
|
|
#include "toonz/tcenterlinevectorizer.h"
|
|
#include "toonz/toonzscene.h"
|
|
#include "toonz/sceneproperties.h"
|
|
#include "vectorizerpopup.h"
|
|
|
|
#include <QApplication>
|
|
|
|
//=============================================================================
|
|
// DrawingData
|
|
//-----------------------------------------------------------------------------
|
|
namespace {
|
|
|
|
// aggiorna gli hook: sono stati inseriti shiftRange (>0) drawings a partire da
|
|
// startFrameId
|
|
// quindi tutti i riferimenti a frameId >= startFrameId devono essere aggiornati
|
|
|
|
void shiftHooks(TXshSimpleLevel *sl, const TFrameId &startFrameId,
|
|
int shiftRange) {
|
|
int i, startIndex = sl->guessIndex(startFrameId);
|
|
assert(startIndex >= 0);
|
|
if (startIndex < 0) return;
|
|
HookSet *levelHooks = sl->getHookSet();
|
|
for (i = sl->getFrameCount() - 1; i >= startIndex + shiftRange; i--) {
|
|
TFrameId fid = sl->index2fid(i);
|
|
TFrameId precFid = sl->index2fid(i - shiftRange);
|
|
assert(precFid >= startFrameId);
|
|
if (precFid == TFrameId::NO_FRAME) break;
|
|
int j, hookCount = levelHooks->getHookCount();
|
|
for (j = 0; j < hookCount; j++) {
|
|
Hook *hook = levelHooks->getHook(j);
|
|
if (hook) {
|
|
hook->setAPos(fid, hook->getAPos(precFid));
|
|
hook->setBPos(fid, hook->getBPos(precFid));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
QString makeCacheId(UINT id, const TFrameId &fid) {
|
|
return "DrawingData" + QString::number(id) + "-" +
|
|
QString::number(fid.getNumber());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//
|
|
// frames[] is replaced by a sequence of imageSet.size() consecutive TFrameId's
|
|
// frames[0] is used to determine the 'pastePosition', i.e. the starting frame
|
|
// of the new sequence
|
|
|
|
void getFrameIdToInsert(std::set<TFrameId> &frames, TXshSimpleLevel *sl,
|
|
const std::map<TFrameId, QString> &imageSet) {
|
|
assert(!frames.empty());
|
|
int count = (int)imageSet.size();
|
|
TFrameId pastePosition = *frames.begin();
|
|
// devo inserire prima di pastePosition. ci puo' essere un "buco".
|
|
// es: frames=[1,3,5] selected=[3] => devo inserire in 2 (creando un nuovo
|
|
// frameid).
|
|
|
|
std::vector<TFrameId> fids;
|
|
sl->getFids(fids);
|
|
// cerca fids[j] <= pastePosition
|
|
// fids.beginからfids.endまで、frames.begin()に比べFrameIdが同じか大きいところまでjを進める
|
|
//要するに、ペースト場所を選択フレーム範囲の一番上の部分にする。
|
|
std::vector<TFrameId>::iterator j =
|
|
std::lower_bound(fids.begin(), fids.end(), pastePosition);
|
|
if (j != fids.begin()) {
|
|
// pastePosition is not the first frameid of the level
|
|
if (j != fids.end() && frames.find(*j) != frames.end()) {
|
|
// Se il frameId e' gia' presente nel livello lo modifico.
|
|
|
|
--j;
|
|
pastePosition = *j + 1;
|
|
}
|
|
} else {
|
|
// pastePosition e' il primo frame (o perche' fids[0]==pastePosition
|
|
// o perche' pastePosition < fids[j], per ogni j
|
|
if (pastePosition.getNumber() > 1)
|
|
pastePosition = TFrameId(pastePosition.getNumber() - 1);
|
|
}
|
|
|
|
// pastePositionからコピーされたフレームの枚数分framesに挿入する。
|
|
// frames <- i frames da inserire
|
|
frames.clear();
|
|
int i;
|
|
for (i = 0; i < count; i++) frames.insert(pastePosition + i);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void rasterize(TToonzImageP &target, const TVectorImageP &source,
|
|
const std::map<int, int> &styleTable) {
|
|
double dpix, dpiy;
|
|
target->getDpi(dpix, dpiy);
|
|
assert(dpix != 0 && dpiy != 0);
|
|
TScale sc(dpix / Stage::inch, dpiy / Stage::inch);
|
|
|
|
TRectD bbox = sc * source->getBBox();
|
|
bbox.x0 = tfloor(bbox.x0);
|
|
bbox.y0 = tfloor(bbox.y0);
|
|
bbox.x1 = tceil(bbox.x1);
|
|
bbox.y1 = tceil(bbox.y1);
|
|
TDimension size(bbox.getLx(), bbox.getLy());
|
|
TToonzImageP app = ToonzImageUtils::vectorToToonzImage(
|
|
source, sc, source->getPalette(), bbox.getP00(), size, 0, true);
|
|
TRect rBbox = ToonzImageUtils::convertWorldToRaster(bbox, target);
|
|
target->getCMapped()->copy(app->getCMapped(), rBbox.getP00());
|
|
|
|
ToonzImageUtils::scrambleStyles(target, styleTable);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void vectorize(TVectorImageP &target, const TToonzImageP &source,
|
|
const std::map<int, int> &styleTable,
|
|
const VectorizerConfiguration &config) {
|
|
VectorizerCore vc;
|
|
TVectorImageP vi =
|
|
vc.vectorize(source.getPointer(), config, source->getPalette());
|
|
assert(vi);
|
|
vi->setPalette(source->getPalette());
|
|
|
|
double dpiX, dpiY;
|
|
source->getDpi(dpiX, dpiY);
|
|
|
|
TScale sc(dpiX / Stage::inch, dpiY / Stage::inch);
|
|
TRect rBox = source->getCMapped()->getBounds();
|
|
TRectD wBbox = ToonzImageUtils::convertRasterToWorld(rBox, source);
|
|
TTranslation tr(wBbox.getP00());
|
|
|
|
int i;
|
|
for (i = 0; i < (int)vi->getStrokeCount(); i++) {
|
|
TStroke *stroke = vi->getStroke(i);
|
|
stroke->transform(sc.inv() * tr, true);
|
|
}
|
|
target->mergeImage(vi, TAffine(), styleTable);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
DrawingData *DrawingData::clone() const { return new DrawingData(this); }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void DrawingData::setLevelFrames(TXshSimpleLevel *sl,
|
|
std::set<TFrameId> &frames) {
|
|
if (!sl || frames.empty()) return;
|
|
|
|
m_level = sl;
|
|
m_imageSet.clear();
|
|
|
|
std::set<TFrameId>::iterator it;
|
|
|
|
HookSet *levelHooks = m_level->getHookSet();
|
|
int i, hookCount = levelHooks->getHookCount();
|
|
|
|
for (it = frames.begin(); it != frames.end(); ++it) {
|
|
TFrameId frameId = *it;
|
|
|
|
// Retrieve the image to be stored in this instance
|
|
TImageP img =
|
|
sl->getFullsampledFrame( // Subsampling is explicitly removed, here.
|
|
frameId, ImageManager::dontPutInCache); // Furthermore, will not
|
|
// force the IM to cache
|
|
// it.
|
|
if (!img) continue;
|
|
|
|
// Clone the image and store it in the image cache
|
|
QString id = makeCacheId(
|
|
(uintptr_t) this,
|
|
it->getNumber()); // Cloning is necessary since the user may
|
|
TImageCache::instance()->add(
|
|
id, img->cloneImage()); // modify and save the original AFTER the copy
|
|
m_imageSet[frameId] = id; // has been done.
|
|
|
|
for (i = 0; i < hookCount; i++) {
|
|
Hook *levelHook = levelHooks->getHook(i);
|
|
if (!levelHook || levelHook->isEmpty()) continue;
|
|
|
|
Hook *copiedHook = m_levelHooks.getHook(i);
|
|
if (!copiedHook) copiedHook = m_levelHooks.addHook();
|
|
|
|
copiedHook->setAPos(frameId, levelHook->getAPos(frameId));
|
|
copiedHook->setBPos(frameId, levelHook->getBPos(frameId));
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//
|
|
// sl <= frames
|
|
//
|
|
// setType = INSERT | OVER_FRAMEID | OVER_SELECTION
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// paste DrawingData to sl.
|
|
// frames: selected frames
|
|
// setType : INSERT->InsertPaste, OVER_FRAMEID->Merge,
|
|
// OVER_SELECTION->PasteInto
|
|
|
|
bool DrawingData::getLevelFrames(TXshSimpleLevel *sl,
|
|
std::set<TFrameId> &frames,
|
|
ImageSetType setType, bool cloneImages,
|
|
bool &keepOriginalPalette, bool isRedo) const {
|
|
int slType = m_level->getType();
|
|
if (m_imageSet.empty() || !sl ||
|
|
(frames.empty() &&
|
|
setType != DrawingData::OVER_FRAMEID) || // If setType == OVER_FRAMEID
|
|
// ignore frames
|
|
(sl->getType() != slType && // Level types must be compatible...
|
|
(sl->getType() != TZP_XSHLEVEL ||
|
|
slType != PLI_XSHLEVEL) && // ...unless they are of type PLI and TLV
|
|
(sl->getType() != PLI_XSHLEVEL || slType != TZP_XSHLEVEL))) //
|
|
return false;
|
|
|
|
if (m_level.getPointer() == sl || isRedo) {
|
|
}
|
|
// when pasting the frame to different level
|
|
else {
|
|
QString question;
|
|
question = "Replace palette ?";
|
|
int ret = DVGui::MsgBox(
|
|
question, QObject::tr("Replace with copied palette"),
|
|
QObject::tr("Keep original palette"), QObject::tr("Cancel"), 0);
|
|
|
|
if (ret == 0 || ret == 3)
|
|
return false;
|
|
else if (ret == 1)
|
|
keepOriginalPalette = false;
|
|
else
|
|
keepOriginalPalette = true;
|
|
}
|
|
|
|
QApplication::setOverrideCursor(Qt::WaitCursor);
|
|
|
|
// oldFids = old frame ids
|
|
// renumberTable = old -> new
|
|
// framesToInsert = new frames
|
|
std::vector<TFrameId> oldFids;
|
|
sl->getFids(oldFids);
|
|
|
|
std::set<TFrameId> framesToInsert;
|
|
if (setType == INSERT) {
|
|
framesToInsert = frames;
|
|
getFrameIdToInsert(framesToInsert, sl, m_imageSet);
|
|
// faccio posto (se serve)
|
|
makeSpaceForFids(sl, framesToInsert);
|
|
assert(sl->getFrameCount() == (int)oldFids.size());
|
|
} else if (setType == OVER_SELECTION)
|
|
framesToInsert = frames;
|
|
|
|
frames.clear();
|
|
|
|
std::map<TFrameId, QString> usedImageSet;
|
|
TPaletteP imgPlt;
|
|
// selected frames
|
|
std::set<TFrameId>::iterator it = framesToInsert.begin();
|
|
|
|
// Preprocessing to keep used styles
|
|
for (auto const &image : m_imageSet) {
|
|
QString imageId = image.second;
|
|
// paste destination
|
|
TFrameId frameId;
|
|
|
|
// merge
|
|
if (setType == OVER_FRAMEID) // If setType == OVER_FRAMEID ignore frames
|
|
frameId = image.first;
|
|
else {
|
|
// if type == OVER_SELECTION, pasting ends at the end of selected range
|
|
if (it == framesToInsert.end())
|
|
continue; // If setType == INSERT this is not possible.
|
|
|
|
frameId = *it;
|
|
}
|
|
frames.insert(frameId);
|
|
usedImageSet[frameId] = imageId;
|
|
|
|
if (!imgPlt.getPointer()) {
|
|
TImageP img = TImageCache::instance()->get(imageId, false);
|
|
if (img) imgPlt = img->getPalette();
|
|
}
|
|
|
|
if (setType != OVER_FRAMEID) ++it;
|
|
}
|
|
|
|
// Merge Palette :
|
|
TPalette *slPlt = sl->getPalette();
|
|
bool styleAdded = mergePalette_Overlap(slPlt, imgPlt, keepOriginalPalette);
|
|
|
|
int styleCount = slPlt ? slPlt->getStyleCount() : 0;
|
|
std::map<int, int> styleTable;
|
|
for (int s = 0; s < styleCount; s++) styleTable[s] = s;
|
|
|
|
// Merge Image
|
|
for (auto const &image : usedImageSet) {
|
|
QString imageId = image.second;
|
|
TImageP img = getImage(imageId, sl, styleTable, m_keepVectorFills);
|
|
if (!cloneImages) TImageCache::instance()->remove(imageId);
|
|
sl->setFrame(image.first, cloneImages ? img->cloneImage() : img);
|
|
}
|
|
|
|
// merge Hooks
|
|
HookSet *levelHooks = sl->getHookSet();
|
|
|
|
int hookCount = m_levelHooks.getHookCount();
|
|
// shiftHooks(sl,usedImageSet.begin()->first,framesToInsert.size());
|
|
|
|
auto frameIt = m_imageSet.begin();
|
|
for (auto const &image : usedImageSet) {
|
|
for (int i = 0; i < hookCount; i++) {
|
|
Hook *levelHook = levelHooks->getHook(i);
|
|
if (!levelHook) levelHook = levelHooks->addHook();
|
|
Hook *copiedHook = m_levelHooks.getHook(i);
|
|
assert(copiedHook);
|
|
levelHook->setAPos(image.first, copiedHook->getAPos((*frameIt).first));
|
|
levelHook->setBPos(image.first, copiedHook->getBPos((*frameIt).first));
|
|
}
|
|
++frameIt;
|
|
}
|
|
|
|
sl->setDirtyFlag(true);
|
|
|
|
// notify if there are any modifications to the palette
|
|
if (styleAdded && !isRedo) {
|
|
QString message;
|
|
if (keepOriginalPalette)
|
|
message = "NOTICE: Some styles were added from copied palette.";
|
|
else
|
|
message = "NOTICE: Some styles were added from original palette.";
|
|
DVGui::info(message);
|
|
}
|
|
|
|
QApplication::restoreOverrideCursor();
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TImageP DrawingData::getImage(QString imageId, TXshSimpleLevel *sl,
|
|
const std::map<int, int> &styleTable,
|
|
bool keepVectorFills) const {
|
|
TImageP img = TImageCache::instance()->get(imageId, false);
|
|
int slType = m_level->getType();
|
|
if (TToonzImageP ti = img) {
|
|
assert(slType == PLI_XSHLEVEL || slType == TZP_XSHLEVEL);
|
|
TImageP slImg = sl->createEmptyFrame();
|
|
slImg->setPalette(sl->getPalette());
|
|
// raster -> raster
|
|
if (sl->getType() == TZP_XSHLEVEL) {
|
|
TToonzImageP slTi = slImg;
|
|
// Immagine di appoggio per effettuare lo scramble
|
|
TToonzImageP newImg = ti->clone();
|
|
ToonzImageUtils::scrambleStyles(newImg, styleTable);
|
|
TRasterCM32P slRaster = slTi->getRaster();
|
|
TRasterCM32P imgRaster = newImg->getRaster();
|
|
TRop::over(slRaster, imgRaster, TTranslation(slRaster->getCenterD() -
|
|
imgRaster->getCenterD()));
|
|
TRect savebox;
|
|
TRop::computeBBox(slTi->getRaster(), savebox);
|
|
slTi->setSavebox(savebox);
|
|
img = slTi;
|
|
}
|
|
// raster -> vector
|
|
else if (sl->getType() == PLI_XSHLEVEL) {
|
|
TVectorImageP slVi = slImg;
|
|
|
|
ToonzScene *scene = sl->getScene();
|
|
assert(scene);
|
|
|
|
std::unique_ptr<VectorizerConfiguration> config(
|
|
scene->getProperties()
|
|
->getVectorizerParameters()
|
|
->getCurrentConfiguration(0.0));
|
|
bool leaveUnpainted = config->m_leaveUnpainted;
|
|
if (keepVectorFills) config->m_leaveUnpainted = false;
|
|
vectorize(slVi, ti, styleTable, *config);
|
|
config->m_leaveUnpainted = leaveUnpainted;
|
|
img = slVi;
|
|
}
|
|
} else if (TVectorImageP vi = img) {
|
|
assert(slType == PLI_XSHLEVEL || slType == TZP_XSHLEVEL);
|
|
TImageP slImg = sl->createEmptyFrame();
|
|
slImg->setPalette(sl->getPalette());
|
|
// vector -> vector
|
|
if (sl->getType() == PLI_XSHLEVEL) {
|
|
TVectorImageP slVi = slImg;
|
|
slVi->mergeImage(vi, TAffine(), styleTable);
|
|
img = slVi;
|
|
}
|
|
// vector -> raster
|
|
else if (sl->getType() == TZP_XSHLEVEL) {
|
|
TToonzImageP slTi = slImg;
|
|
rasterize(slTi, vi, styleTable);
|
|
TRect savebox;
|
|
TRop::computeBBox(slTi->getRaster(), savebox);
|
|
slTi->setSavebox(savebox);
|
|
img = slTi;
|
|
}
|
|
}
|
|
return img;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void DrawingData::setFrames(const std::map<TFrameId, QString> &imageSet,
|
|
TXshSimpleLevel *level, const HookSet &levelHooks) {
|
|
m_levelHooks = levelHooks;
|
|
m_imageSet.clear();
|
|
|
|
assert(!imageSet.empty());
|
|
|
|
m_imageSet = imageSet;
|
|
m_level = level;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void DrawingData::getFrames(std::set<TFrameId> &frames) const {
|
|
for (auto const &image : m_imageSet) frames.insert(image.first);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
DrawingData::~DrawingData() {
|
|
// cannot do that here! if you have cloned this class, the images in the cache
|
|
// are still used...
|
|
// int i;
|
|
// for(i=0; i<m_imageSet.size(); i++)
|
|
// TImageCache::instance()->remove(m_imageSet[i]);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void DrawingData::releaseData() {
|
|
// do it when you're sure you no t need images anymore... (for example in an
|
|
// undo destructor)
|
|
// int i;
|
|
// for(i=0; i<m_imageSet.size(); i++)
|
|
// TImageCache::instance()->remove(m_imageSet[i]);
|
|
}
|