diff --git a/stuff/profiles/layouts/rooms/Default/menubar_template.xml b/stuff/profiles/layouts/rooms/Default/menubar_template.xml index 730a3e1..d17cb34 100644 --- a/stuff/profiles/layouts/rooms/Default/menubar_template.xml +++ b/stuff/profiles/layouts/rooms/Default/menubar_template.xml @@ -33,6 +33,7 @@ MI_StopMotionExportImageSequence MI_ExportTvpJson MI_ExportXsheetPDF + MI_ExportCameraTrack MI_PrintXsheet diff --git a/stuff/profiles/layouts/rooms/StudioGhibli/room4_menubar.xml b/stuff/profiles/layouts/rooms/StudioGhibli/room4_menubar.xml index 86d788e..b268cec 100644 --- a/stuff/profiles/layouts/rooms/StudioGhibli/room4_menubar.xml +++ b/stuff/profiles/layouts/rooms/StudioGhibli/room4_menubar.xml @@ -11,6 +11,7 @@ MI_PrintXsheet MI_Export + MI_ExportCameraTrack MI_Quit diff --git a/toonz/sources/toonz/CMakeLists.txt b/toonz/sources/toonz/CMakeLists.txt index 6499001..770d6b7 100644 --- a/toonz/sources/toonz/CMakeLists.txt +++ b/toonz/sources/toonz/CMakeLists.txt @@ -129,6 +129,7 @@ set(MOC_HEADERS custompanelmanager.h custompaneleditorpopup.h convertfolderpopup.h + exportcameratrackpopup.h ) if(PLATFORM EQUAL 64) @@ -382,6 +383,7 @@ set(SOURCES custompanelmanager.cpp custompaneleditorpopup.cpp convertfolderpopup.cpp + exportcameratrackpopup.cpp # Tracker file dummyprocessor.cpp metnum.cpp diff --git a/toonz/sources/toonz/exportcameratrackpopup.cpp b/toonz/sources/toonz/exportcameratrackpopup.cpp new file mode 100644 index 0000000..9f4610f --- /dev/null +++ b/toonz/sources/toonz/exportcameratrackpopup.cpp @@ -0,0 +1,986 @@ + + +#include "exportcameratrackpopup.h" + +// Tnz6 includes +#include "tapp.h" +#include "mainwindow.h" +#include "filebrowser.h" +#include "menubarcommandids.h" +//// TnzQt includes +#include "toonzqt/colorfield.h" +#include "toonzqt/filefield.h" +#include "toonzqt/doublefield.h" +//// TnzLib includes +#include "toonz/txsheet.h" +#include "toonz/tcamera.h" +#include "toonz/txshlevel.h" +#include "toonz/txshsimplelevel.h" +#include "toonz/txshcell.h" +#include "toonz/tstageobjecttree.h" +#include "toonz/toonzscene.h" +#include "toonz/txshleveltypes.h" +#include "toonz/dpiscale.h" +#include "toonz/tproject.h" +#include "toonz/txsheethandle.h" +#include "toonz/tscenehandle.h" +#include "filebrowserpopup.h" +//// TnzCore includes +#include "trop.h" +#include "tsystem.h" +#include "tenv.h" +#include "tropcm.h" +#include "tpalette.h" +//// Qt includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TEnv::DoubleVar CameraTrackExportBgOpacity("CameraTrackExportBgOpacity", 0.5); +TEnv::StringVar CameraTrackExportLineColor( + "CameraTrackExportLineColor", QColor(Qt::red).name().toStdString()); +TEnv::IntVar CameraTrackExportCamRectOnKeys("CameraTrackExportCamRectOnKeys", + 1); +TEnv::IntVar CameraTrackExportCamRectOnTags("CameraTrackExportCamRectOnTags", + 0); +TEnv::IntVar CameraTrackExportLineTL("CameraTrackExportLineTL", 0); +TEnv::IntVar CameraTrackExportLineTR("CameraTrackExportLineTR", 0); +TEnv::IntVar CameraTrackExportLineCenter("CameraTrackExportLineCenter", 1); +TEnv::IntVar CameraTrackExportLineBL("CameraTrackExportLineBL", 0); +TEnv::IntVar CameraTrackExportLineBR("CameraTrackExportLineBR", 0); +TEnv::IntVar CameraTrackExportGraduationInterval( + "CameraTrackExportGraduationInterval", 1); +TEnv::IntVar CameraTrackExportNumberAt("CameraTrackExportNumberAt", + (int)Qt::TopLeftCorner); +TEnv::IntVar CameraTrackExportNumbersOnLine("CameraTrackExportNumbersOnLine", + 1); +TEnv::StringVar CameraTrackExportFont("CameraTrackExportFont", ""); +TEnv::IntVar CameraTrackExportFontSize("CameraTrackExportFontSize", 30); + +namespace { + +void getCameraPlacement(TAffine& aff, TXsheet* xsh, double row, + const TStageObjectId& objId, + const TStageObjectId& cameraId) { + TStageObject* pegbar = + xsh->getStageObjectTree()->getStageObject(objId, false); + if (!pegbar) return; + + TAffine objAff = pegbar->getPlacement(row); + double objZ = pegbar->getZ(row); + double noScaleZ = pegbar->getGlobalNoScaleZ(); + + TStageObject* camera = xsh->getStageObject(cameraId); + TAffine cameraAff = camera->getPlacement(row); + double cameraZ = camera->getZ(row); + + bool isVisible = TStageObject::perspective(aff, cameraAff, cameraZ, objAff, + objZ, noScaleZ); + + aff = aff.inv() * cameraAff; +} + +// recursively find key frame +bool isKey(int frame, TStageObject* obj, TXsheet* xsh) { + if (obj->isKeyframe(frame)) return true; + if (obj->getParent() != TStageObjectId::NoneId) + return isKey(frame, xsh->getStageObject(obj->getParent()), xsh); + return false; +} + +void drawOutlinedText(QPainter& p, const QPointF& pos, const QString& str) { + QPainterPath path; + path.addText(pos, p.font(), str); + p.setPen(QPen(Qt::white, 3)); + p.drawPath(path); + p.setPen(Qt::NoPen); + p.drawPath(path); +} + +// {0,1,2,3,6,8,9,10} => "1-4,7,9-11" +QString framesToString(const QList& frames) { + QString frameStr; + bool prevIsHyphen = false; + for (int i = 0; i < frames.size(); i++) { + int f = frames.at(i); + if (i == 0) { + frameStr = QString::number(f + 1); + continue; + } + if (i == frames.size() - 1) { + if (prevIsHyphen) + frameStr += QString::number(f + 1); + else + frameStr += ", " + QString::number(f + 1); + break; + } + if (frames.at(i - 1) == f - 1) { + if (frames.at(i + 1) == f + 1) { + if (prevIsHyphen) + continue; + else { + frameStr += "-"; + prevIsHyphen = true; + } + } else { + if (prevIsHyphen) { + frameStr += QString::number(f + 1); + prevIsHyphen = false; + } else { + frameStr += ", " + QString::number(f + 1); + } + } + } else { + frameStr += ", " + QString::number(f + 1); + } + } + return frameStr; +} +} // namespace + +//----------------------------------------------------------------------------- +CameraTrackPreviewPane::CameraTrackPreviewPane(QWidget* parent) + : QWidget(parent), m_scaleFactor(1.0) {} + +void CameraTrackPreviewPane::paintEvent(QPaintEvent* event) { + QPainter painter(this); + painter.setRenderHint(QPainter::SmoothPixmapTransform, true); + painter.setRenderHint(QPainter::Antialiasing, true); + QSize pmSize((double)m_pixmap.width() * m_scaleFactor, + (double)m_pixmap.height() * m_scaleFactor); + painter.drawPixmap( + 0, 0, + m_pixmap.scaled(pmSize, Qt::KeepAspectRatio, Qt::SmoothTransformation)); +} + +void CameraTrackPreviewPane::setPixmap(QPixmap pm) { + m_pixmap = pm; + resize(pm.size() * m_scaleFactor); + update(); +} + +void CameraTrackPreviewPane::doZoom(double d_scale) { + m_scaleFactor += d_scale; + if (m_scaleFactor > 1.0) + m_scaleFactor = 1.0; + else if (m_scaleFactor < 0.1) + m_scaleFactor = 0.1; + + resize(m_pixmap.size() * m_scaleFactor); + update(); +} + +void CameraTrackPreviewPane::fitScaleTo(QSize size) { + double tmp_scaleFactor = + std::min((double)size.width() / (double)m_pixmap.width(), + (double)size.height() / (double)m_pixmap.height()); + + m_scaleFactor = tmp_scaleFactor; + if (m_scaleFactor > 1.0) + m_scaleFactor = 1.0; + else if (m_scaleFactor < 0.1) + m_scaleFactor = 0.1; + + resize(m_pixmap.size() * m_scaleFactor); + update(); +} + +//----------------------------------------------------------------------------- +void CameraTrackPreviewArea::mousePressEvent(QMouseEvent* e) { + m_mousePos = e->pos(); +} + +void CameraTrackPreviewArea::mouseMoveEvent(QMouseEvent* e) { + QPoint d = m_mousePos - e->pos(); + horizontalScrollBar()->setValue(horizontalScrollBar()->value() + d.x()); + verticalScrollBar()->setValue(verticalScrollBar()->value() + d.y()); + m_mousePos = e->pos(); +} + +void CameraTrackPreviewArea::contextMenuEvent(QContextMenuEvent* event) { + QMenu* menu = new QMenu(this); + QAction* fitAction = menu->addAction(tr("Fit To Window")); + connect(fitAction, SIGNAL(triggered()), this, SLOT(fitToWindow())); + menu->exec(event->globalPos()); +} + +void CameraTrackPreviewArea::fitToWindow() { + dynamic_cast(widget())->fitScaleTo(rect().size()); +} + +void CameraTrackPreviewArea::wheelEvent(QWheelEvent* event) { + int delta = 0; + switch (event->source()) { + case Qt::MouseEventNotSynthesized: { + delta = event->angleDelta().y(); + } + case Qt::MouseEventSynthesizedBySystem: { + QPoint numPixels = event->pixelDelta(); + QPoint numDegrees = event->angleDelta() / 8; + if (!numPixels.isNull()) { + delta = event->pixelDelta().y(); + } else if (!numDegrees.isNull()) { + QPoint numSteps = numDegrees / 15; + delta = numSteps.y(); + } + break; + } + + default: // Qt::MouseEventSynthesizedByQt, + // Qt::MouseEventSynthesizedByApplication + { + std::cout << "not supported event: Qt::MouseEventSynthesizedByQt, " + "Qt::MouseEventSynthesizedByApplication" + << std::endl; + break; + } + + } // end switch + + if (delta == 0) { + event->accept(); + return; + } + + dynamic_cast(widget())->doZoom((delta > 0) ? 0.1 + : -0.1); + + event->accept(); +} + +//******************************************************************************** +// ExportCameraTrackPopup implementation +//******************************************************************************** + +ExportCameraTrackPopup::ExportCameraTrackPopup() + : DVGui::Dialog(TApp::instance()->getMainWindow(), false, false, + "ExportCameraTrack") { + setWindowTitle(tr("Export Camera Track")); + + m_previewPane = new CameraTrackPreviewPane(this); + m_previewArea = new CameraTrackPreviewArea(this); + + m_targetColumnCombo = new QComboBox(this); + m_bgOpacityField = new DVGui::DoubleField(this); + m_lineColorFld = new DVGui::ColorField(this, false, TPixel32(255, 0, 0)); + + m_cameraRectOnKeysCB = new QCheckBox(tr("Draw On Keyframes"), this); + m_cameraRectOnTagsCB = new QCheckBox(tr("Draw On Navigation Tags"), this); + m_cameraRectFramesEdit = new QLineEdit(this); + + m_lineTL_CB = new QCheckBox(tr("Top Left"), this); + m_lineTR_CB = new QCheckBox(tr("Top Right"), this); + m_lineCenter_CB = new QCheckBox(tr("Center"), this); + m_lineBL_CB = new QCheckBox(tr("Bottom Left"), this); + m_lineBR_CB = new QCheckBox(tr("Bottom Right"), this); + m_graduationIntervalCombo = new QComboBox(this); + + m_numberAtCombo = new QComboBox(this); + m_numbersOnLineCB = new QCheckBox(tr("Draw Numbers On Track Line"), this); + m_fontCombo = new QFontComboBox(this); + m_fontSizeEdit = new DVGui::IntLineEdit(this, 30, 5, 300); + + QPushButton* exportButton = new QPushButton(tr("Export"), this); + QPushButton* cancelButton = new QPushButton(tr("Cancel"), this); + + //---------- + m_previewArea->setWidget(m_previewPane); + m_previewArea->setAlignment(Qt::AlignCenter); + m_previewArea->setBackgroundRole(QPalette::Dark); + m_previewArea->setStyleSheet("background-color:gray;"); + + m_targetColumnCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents); + m_bgOpacityField->setRange(0.0, 1.0); + m_bgOpacityField->setValue(0.5); + + m_cameraRectFramesEdit->setValidator( + new QRegExpValidator(QRegExp("^(\\d+,)*\\d+$"), this)); + m_cameraRectFramesEdit->setToolTip( + tr("Specify frame numbers where the camera rectangles will be drawn. " + "Separate numbers by comma \",\" .")); + + m_numberAtCombo->addItem(tr("Top Left"), (int)Qt::TopLeftCorner); + m_numberAtCombo->addItem(tr("Top Right"), (int)Qt::TopRightCorner); + m_numberAtCombo->addItem(tr("Bottom Left"), (int)Qt::BottomLeftCorner); + m_numberAtCombo->addItem(tr("Bottom Right"), (int)Qt::BottomRightCorner); + + m_graduationIntervalCombo->addItem(tr("None"), 0); + m_graduationIntervalCombo->addItem(tr("All frames"), 1); + m_graduationIntervalCombo->addItem(tr("Every 2 frames"), 2); + m_graduationIntervalCombo->addItem(tr("Every 3 frames"), 3); + m_graduationIntervalCombo->addItem(tr("Every 4 frames"), 4); + m_graduationIntervalCombo->addItem(tr("Every 5 frames"), 5); + m_graduationIntervalCombo->addItem(tr("Every 6 frames"), 6); + m_graduationIntervalCombo->addItem(tr("Every 8 frames"), 8); + m_graduationIntervalCombo->addItem(tr("Every 10 frames"), 10); + m_graduationIntervalCombo->addItem(tr("Every 12 frames"), 12); + + exportButton->setFocusPolicy(Qt::NoFocus); + cancelButton->setFocusPolicy(Qt::NoFocus); + //---------- + QHBoxLayout* mainLay = new QHBoxLayout(); + mainLay->setMargin(0); + mainLay->setSpacing(5); + { + mainLay->addWidget(m_previewArea, 1); + + QVBoxLayout* rightLay = new QVBoxLayout(); + rightLay->setMargin(10); + rightLay->setSpacing(10); + + QGridLayout* appearanceLay = new QGridLayout(); + appearanceLay->setMargin(0); + appearanceLay->setHorizontalSpacing(5); + appearanceLay->setVerticalSpacing(10); + { + appearanceLay->addWidget(new QLabel(tr("Target Column:"), this), 0, 0, + Qt::AlignRight | Qt::AlignVCenter); + appearanceLay->addWidget(m_targetColumnCombo, 0, 1, Qt::AlignLeft); + + appearanceLay->addWidget(new QLabel(tr("Background:"), this), 1, 0, + Qt::AlignRight | Qt::AlignVCenter); + appearanceLay->addWidget(m_bgOpacityField, 1, 1); + + appearanceLay->addWidget(new QLabel(tr("Line Color:"), this), 2, 0, + Qt::AlignRight | Qt::AlignVCenter); + appearanceLay->addWidget(m_lineColorFld, 2, 1, Qt::AlignLeft); + } + appearanceLay->setColumnStretch(1, 1); + rightLay->addLayout(appearanceLay, 0); + + QGroupBox* cameraRectGB = new QGroupBox(tr("Camera Rectangles"), this); + QGridLayout* cameraRectLay = new QGridLayout(); + cameraRectLay->setMargin(10); + cameraRectLay->setHorizontalSpacing(5); + cameraRectLay->setVerticalSpacing(10); + { + cameraRectLay->addWidget(m_cameraRectOnKeysCB, 0, 0, 1, 2, Qt::AlignLeft); + cameraRectLay->addWidget(m_cameraRectOnTagsCB, 1, 0, 1, 2, Qt::AlignLeft); + + cameraRectLay->addWidget(new QLabel(tr("Specify Frames Manually:"), this), + 2, 0, Qt::AlignRight | Qt::AlignVCenter); + cameraRectLay->addWidget(m_cameraRectFramesEdit, 2, 1); + } + cameraRectLay->setColumnStretch(1, 1); + cameraRectGB->setLayout(cameraRectLay); + rightLay->addWidget(cameraRectGB, 0); + + QGroupBox* trackLineGB = new QGroupBox(tr("Track Lines"), this); + QGridLayout* trackLineLay = new QGridLayout(); + trackLineLay->setMargin(10); + trackLineLay->setHorizontalSpacing(10); + trackLineLay->setVerticalSpacing(10); + { + trackLineLay->addWidget(m_lineTL_CB, 0, 0); + trackLineLay->addWidget(m_lineTR_CB, 0, 1); + trackLineLay->addWidget(m_lineCenter_CB, 1, 0, Qt::AlignRight); + trackLineLay->addWidget(m_lineBL_CB, 2, 0); + trackLineLay->addWidget(m_lineBR_CB, 2, 1); + trackLineLay->addWidget( + new QLabel(tr("Graduation Marks Interval:"), this), 3, 0, + Qt::AlignRight | Qt::AlignVCenter); + trackLineLay->addWidget(m_graduationIntervalCombo, 3, 1, Qt::AlignLeft); + } + trackLineLay->setColumnStretch(1, 1); + trackLineGB->setLayout(trackLineLay); + rightLay->addWidget(trackLineGB, 0); + + QGroupBox* frameNumberGB = new QGroupBox(tr("Frame Numbers"), this); + QGridLayout* frameNumberLay = new QGridLayout(); + frameNumberLay->setMargin(10); + frameNumberLay->setHorizontalSpacing(5); + frameNumberLay->setVerticalSpacing(10); + { + frameNumberLay->addWidget(new QLabel(tr("Camera Rect Corner:"), this), 0, + 0, Qt::AlignRight | Qt::AlignVCenter); + frameNumberLay->addWidget(m_numberAtCombo, 0, 1, Qt::AlignLeft); + + frameNumberLay->addWidget(m_numbersOnLineCB, 1, 0, 1, 2); + + frameNumberLay->addWidget(new QLabel(tr("Font Family:"), this), 2, 0, + Qt::AlignRight | Qt::AlignVCenter); + frameNumberLay->addWidget(m_fontCombo, 2, 1, Qt::AlignLeft); + + frameNumberLay->addWidget(new QLabel(tr("Font Size:"), this), 3, 0, + Qt::AlignRight | Qt::AlignVCenter); + frameNumberLay->addWidget(m_fontSizeEdit, 3, 1, Qt::AlignLeft); + } + frameNumberLay->setColumnStretch(1, 1); + frameNumberGB->setLayout(frameNumberLay); + rightLay->addWidget(frameNumberGB, 0); + + rightLay->addStretch(1); + + QHBoxLayout* buttonsLay = new QHBoxLayout(); + buttonsLay->setMargin(5); + buttonsLay->setSpacing(20); + { + buttonsLay->addWidget(exportButton, 0); + buttonsLay->addWidget(cancelButton, 0); + } + rightLay->setAlignment(Qt::AlignCenter); + rightLay->addLayout(buttonsLay, 0); + + mainLay->addLayout(rightLay, 0); + } + m_topLayout->addLayout(mainLay, 1); + + //---------- + + loadSettings(); + + connect(m_targetColumnCombo, SIGNAL(activated(int)), this, + SLOT(updatePreview())); + connect(m_bgOpacityField, SIGNAL(valueEditedByHand()), this, + SLOT(updatePreview())); + connect(m_lineColorFld, SIGNAL(colorChanged(const TPixel32&, bool)), this, + SLOT(updatePreview())); + connect(m_cameraRectOnKeysCB, SIGNAL(clicked(bool)), this, + SLOT(updatePreview())); + connect(m_cameraRectOnTagsCB, SIGNAL(clicked(bool)), this, + SLOT(updatePreview())); + connect(m_cameraRectFramesEdit, SIGNAL(editingFinished()), this, + SLOT(updatePreview())); + connect(m_lineTL_CB, SIGNAL(clicked(bool)), this, SLOT(updatePreview())); + connect(m_lineTR_CB, SIGNAL(clicked(bool)), this, SLOT(updatePreview())); + connect(m_lineCenter_CB, SIGNAL(clicked(bool)), this, SLOT(updatePreview())); + connect(m_lineBL_CB, SIGNAL(clicked(bool)), this, SLOT(updatePreview())); + connect(m_lineBR_CB, SIGNAL(clicked(bool)), this, SLOT(updatePreview())); + connect(m_graduationIntervalCombo, SIGNAL(activated(int)), this, + SLOT(updatePreview())); + + connect(m_numberAtCombo, SIGNAL(activated(int)), this, SLOT(updatePreview())); + connect(m_numbersOnLineCB, SIGNAL(clicked(bool)), this, + SLOT(updatePreview())); + connect(m_fontCombo, SIGNAL(currentFontChanged(const QFont&)), this, + SLOT(updatePreview())); + connect(m_fontSizeEdit, SIGNAL(editingFinished()), this, + SLOT(updatePreview())); + + connect(cancelButton, SIGNAL(clicked()), this, SLOT(close())); + connect(exportButton, SIGNAL(clicked()), this, SLOT(onExport())); +} + +//-------------------------------------------------------------- + +// register settings to the user env file on close +void ExportCameraTrackPopup::saveSettings() { + CameraTrackExportBgOpacity = m_bgOpacityField->getValue(); + TPixel32 col = m_lineColorFld->getColor(); + CameraTrackExportLineColor = QColor(col.r, col.g, col.b).name().toStdString(); + CameraTrackExportCamRectOnKeys = (m_cameraRectOnKeysCB->isChecked()) ? 1 : 0; + CameraTrackExportCamRectOnTags = (m_cameraRectOnTagsCB->isChecked()) ? 1 : 0; + CameraTrackExportLineTL = (m_lineTL_CB->isChecked()) ? 1 : 0; + CameraTrackExportLineTR = (m_lineTR_CB->isChecked()) ? 1 : 0; + CameraTrackExportLineCenter = (m_lineCenter_CB->isChecked()) ? 1 : 0; + CameraTrackExportLineBL = (m_lineBL_CB->isChecked()) ? 1 : 0; + CameraTrackExportLineBR = (m_lineBR_CB->isChecked()) ? 1 : 0; + CameraTrackExportGraduationInterval = + m_graduationIntervalCombo->currentData().toInt(); + CameraTrackExportNumberAt = m_numberAtCombo->currentData().toInt(); + CameraTrackExportNumbersOnLine = (m_numbersOnLineCB->isChecked()) ? 1 : 0; + CameraTrackExportFont = m_fontCombo->currentFont().family().toStdString(); + CameraTrackExportFontSize = m_fontSizeEdit->getValue(); +} + +//-------------------------------------------------------------- +// +// load settings from the user env file on ctor +void ExportCameraTrackPopup::loadSettings() { + m_bgOpacityField->setValue(CameraTrackExportBgOpacity); + QColor lineColor(QString::fromStdString(CameraTrackExportLineColor)); + m_lineColorFld->setColor( + TPixel32(lineColor.red(), lineColor.green(), lineColor.blue())); + m_cameraRectOnKeysCB->setChecked(CameraTrackExportCamRectOnKeys != 0); + m_cameraRectOnTagsCB->setChecked(CameraTrackExportCamRectOnTags != 0); + m_lineTL_CB->setChecked(CameraTrackExportLineTL != 0); + m_lineTR_CB->setChecked(CameraTrackExportLineTR != 0); + m_lineCenter_CB->setChecked(CameraTrackExportLineCenter != 0); + m_lineBL_CB->setChecked(CameraTrackExportLineBL != 0); + m_lineBR_CB->setChecked(CameraTrackExportLineBR != 0); + m_graduationIntervalCombo->setCurrentIndex( + m_graduationIntervalCombo->findData( + (int)CameraTrackExportGraduationInterval)); + m_numberAtCombo->setCurrentIndex( + m_numberAtCombo->findData((int)CameraTrackExportNumberAt)); + m_numbersOnLineCB->setChecked(CameraTrackExportNumbersOnLine != 0); + QString tmplFont = QString::fromStdString(CameraTrackExportFont); + if (!tmplFont.isEmpty()) m_fontCombo->setCurrentFont(QFont(tmplFont)); + m_fontSizeEdit->setValue(CameraTrackExportFontSize); +} + +//-------------------------------------------------------------- + +void ExportCameraTrackPopup::initialize() { + updateTargetColumnComboItems(); + updatePreview(); +} + +//-------------------------------------------------------------- + +void ExportCameraTrackPopup::updateTargetColumnComboItems() { + m_targetColumnCombo->clear(); + + ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene(); + TXsheet* xsh = TApp::instance()->getCurrentXsheet()->getXsheet(); + + for (int col = 0; col < xsh->getColumnCount(); col++) { + TXshLevelP level; + int r0, r1; + xsh->getCellRange(col, r0, r1); + if (r1 < 0) continue; + for (int r = r0; r <= r1; r++) + if (level = xsh->getCell(r, col).m_level) { + break; + } + + if (!level) continue; + + int type = level->getType(); + if (!(type & RASTER_TYPE)) continue; + + TXshSimpleLevelP sl = level->getSimpleLevel(); + if (!sl) continue; + + QString itemName = tr("Col %1 (%2)") + .arg(col + 1) + .arg(QString::fromStdWString(sl->getName())); + + m_targetColumnCombo->addItem(itemName, col); + } +} + +//-------------------------------------------------------------- + +QImage ExportCameraTrackPopup::generateCameraTrackImg( + const ExportCameraTrackInfo& info, bool isPreview) { + ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene(); + TXsheet* xsh = TApp::instance()->getCurrentXsheet()->getXsheet(); + + // obtain target level + TXshLevelP level; + TFrameId fId; + int r0, r1; + xsh->getCellRange(info.columnId, r0, r1); + if (r1 < 0) return QImage(); + for (int r = r0; r <= r1; r++) + if (level = xsh->getCell(r, info.columnId).m_level) { + fId = xsh->getCell(r, info.columnId).getFrameId(); + break; + } + if (!level) return QImage(); + int type = level->getType(); + if (!(type & RASTER_TYPE)) return QImage(); + TXshSimpleLevelP sl = level->getSimpleLevel(); + if (!sl) return QImage(); + + // construct output image + TStageObjectId cameraId = xsh->getStageObjectTree()->getCurrentCameraId(); + TCamera* camera = xsh->getStageObject(cameraId)->getCamera(); + TDimension camDim = camera->getRes(); + + TDimension imgRes = sl->getResolution(); + + TImageP tImg = sl->getFullsampledFrame(fId, ImageManager::dontPutInCache); + TRaster32P imgRas(tImg->raster()->getSize()); + TRaster32P src32 = tImg->raster(); + TRasterCM32P srcCM = tImg->raster(); + if (src32) + imgRas = src32; + else if (srcCM) + TRop::convert(imgRas, srcCM, tImg->getPalette(), TRect()); + else + TRop::convert(imgRas, tImg->raster()); + + QImage colImg(imgRas->getRawData(), imgRes.lx, imgRes.ly, + QImage::Format_ARGB32_Premultiplied); + + QImage img = colImg.mirrored(false, true); + + TPointD imgDpi = sl->getImageDpi(); + if (imgDpi != TPointD()) { + img.setDotsPerMeterX((int)std::round(imgDpi.x / 0.0254)); + img.setDotsPerMeterY((int)std::round(imgDpi.y / 0.0254)); + } + + // draw + + enum CornerId { + TopLeft = Qt::TopLeftCorner, + TopRight = Qt::TopRightCorner, + BottomLeft = Qt::BottomLeftCorner, + BottomRight = Qt::BottomRightCorner, + Center = Qt::BottomRightCorner + 1 + }; + + QMap trackPaths; // [ CornerId, Path data ] + QList> + cornerPointsTrack; // [ CornerId, Position ] for each frame + + if (info.lineTL) trackPaths.insert((int)TopLeft, QPainterPath()); + if (info.lineTR) trackPaths.insert((int)TopRight, QPainterPath()); + if (info.lineCenter) trackPaths.insert((int)Center, QPainterPath()); + if (info.lineBL) trackPaths.insert((int)BottomLeft, QPainterPath()); + if (info.lineBR) trackPaths.insert((int)BottomRight, QPainterPath()); + + TAffine aff; + TAffine dpiAffInv = getDpiAffine(sl.getPointer(), fId, true).inv(); + TAffine camDpiAff = getDpiAffine(camera); + + TStageObjectId colId = TStageObjectId::ColumnId(info.columnId); + + QMap camCorners = { + {(int)TopLeft, TPointD(-camDim.lx / 2, camDim.ly / 2)}, + {(int)TopRight, TPointD(camDim.lx / 2, camDim.ly / 2)}, + {(int)BottomLeft, TPointD(-camDim.lx / 2, -camDim.ly / 2)}, + {(int)BottomRight, TPointD(camDim.lx / 2, -camDim.ly / 2)}, + {(int)Center, TPointD()}}; + + for (int f = 0; f < scene->getFrameCount(); f++) { + getCameraPlacement(aff, xsh, (double)f, colId, cameraId); + TAffine affTmp = dpiAffInv * aff * camDpiAff; + // corner points + QMap cornerPoints; + for (int c = TopLeft; c <= Center; c++) { + TPointD p = affTmp * camCorners[c]; + cornerPoints.insert(c, QPointF(p.x, -p.y)); + } + cornerPointsTrack.append(cornerPoints); + + if (trackPaths.isEmpty()) continue; + + // track paths will plot every 0.1 frames + for (int df = 0; df < 10; df++) { + double tmpF = (double)f + (double)df * 0.1; + getCameraPlacement(aff, xsh, (double)tmpF, colId, cameraId); + affTmp = dpiAffInv * aff * camDpiAff; + + for (int c = TopLeft; c <= Center; c++) { + if (!trackPaths.contains(c)) continue; + + TPointD p = affTmp * camCorners[c]; + if (f == 0 && df == 0) + trackPaths[c].moveTo(QPointF(p.x, -p.y)); + else + trackPaths[c].lineTo(QPointF(p.x, -p.y)); + } + + if (f == scene->getFrameCount() - 1) break; + } + } + + QPainter p(&img); + p.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing); + + p.setBrush(QColor(255, 255, 255, 255. * (1. - info.bgOpacity))); + p.setPen(Qt::NoPen); + p.drawRect(0, 0, imgRes.lx, imgRes.ly); + p.translate(imgRes.lx / 2.0, imgRes.ly / 2.0); + + // camera rect + QSet rectFrames = info.cameraRectFrames; + if (info.cameraRectOnKeys || info.cameraRectOnTags) { + // check keyframes + for (int f = 0; f < scene->getFrameCount(); f++) { + if (rectFrames.contains(f)) continue; + + if (info.cameraRectOnKeys && + (isKey(f, xsh->getStageObject(cameraId), xsh) || + isKey(f, xsh->getStageObject(colId), xsh))) + rectFrames.insert(f); + + else if (info.cameraRectOnTags && xsh->isFrameTagged(f)) + rectFrames.insert(f); + } + } + + struct cameraRectData { + QPolygonF polygon; + QVector centerCrossPoints; + QPointF textPos; + QVector2D offsetVec; + QList frames; + }; + + QList + camRectDataList; // gather frames with the same camera rectangle + + for (auto rectFrame : rectFrames) { + if (rectFrame < 0 || cornerPointsTrack.size() <= rectFrame) continue; + QMap cornerPoints = cornerPointsTrack.at(rectFrame); + QPolygonF camRectPolygon({cornerPoints[TopLeft], cornerPoints[TopRight], + cornerPoints[BottomRight], + cornerPoints[BottomLeft]}); + QVector centerCrossPoints = { + cornerPoints[TopLeft] * 0.51 + cornerPoints[BottomRight] * 0.49, + cornerPoints[TopLeft] * 0.49 + cornerPoints[BottomRight] * 0.51, + cornerPoints[TopRight] * 0.51 + cornerPoints[BottomLeft] * 0.49, + cornerPoints[TopRight] * 0.49 + cornerPoints[BottomLeft] * 0.51}; + QPointF textPos = cornerPoints[(int)info.numberAt]; + int oppositeId; + switch ((int)info.numberAt) { + case TopLeft: + oppositeId = TopRight; + break; + case TopRight: + oppositeId = TopLeft; + break; + case BottomLeft: + oppositeId = BottomRight; + break; + case BottomRight: + oppositeId = BottomLeft; + break; + } + QVector2D offsetVec = + QVector2D(textPos - cornerPoints[oppositeId]).normalized(); + + bool found = false; + for (int i = 0; i < camRectDataList.size(); i++) + if (camRectDataList[i].polygon == camRectPolygon) { + found = true; + camRectDataList[i].frames.append(rectFrame); + break; + } + if (!found) + camRectDataList.append( + {camRectPolygon, centerCrossPoints, textPos, offsetVec, {rectFrame}}); + } + + p.setFont(info.font); + + for (auto& data : camRectDataList) { + p.setPen(QPen(info.lineColor, 2)); + p.setBrush(Qt::NoBrush); + p.drawPolygon(data.polygon); + + // draw cross mark at center of the frame + bool drawCross = true; + // if the graduation mark is at the camera center, do not draw the cross + if (info.lineCenter && info.graduationInterval > 0) { + for (auto frame : data.frames) + if (frame % info.graduationInterval == 0) { + drawCross = false; + break; + } + } + if (drawCross) { + p.setPen(QPen(info.lineColor, 1)); + p.drawLines(data.centerCrossPoints); + } + + // generate frame number string + std::sort(data.frames.begin(), data.frames.end()); + QString frameStr = framesToString(data.frames); + + // draw frame number string + QFontMetricsF fm(info.font); + QRectF textRect = fm.boundingRect(frameStr).adjusted(-5, 0, 5, 0); + QPointF textPos = data.textPos + QPointF(5, 0); + textRect.translate(textPos); + + while (data.polygon.intersects(textRect)) { + textRect.translate(data.offsetVec.toPointF()); + textPos += QPointF(data.offsetVec.toPointF()); + } + + p.setBrush(info.lineColor); + drawOutlinedText(p, textPos, frameStr); + } + + QFont smallFont(info.font); + smallFont.setPixelSize(info.font.pixelSize() * 2 / 3); + p.setFont(smallFont); + + // track lines + QMap::const_iterator itr = trackPaths.constBegin(); + while (itr != trackPaths.constEnd()) { + if (info.lineCenter && itr.key() != Center) + p.setPen(QPen(info.lineColor, 1, Qt::DashLine)); + else + p.setPen(QPen(info.lineColor, 1)); + p.setBrush(Qt::NoBrush); + + p.drawPath(itr.value()); + + if (info.graduationInterval == 0 || + (info.lineCenter && itr.key() != Center)) { + ++itr; + continue; + } + + // draw graduation + QList>> graduations; + + for (int f = 0; f < scene->getFrameCount(); f++) { + if (f % info.graduationInterval != 0) continue; + + QPointF gPos = cornerPointsTrack[f].value(itr.key()); + QPointF prev = + (f == 0) ? gPos : cornerPointsTrack[f - 1].value(itr.key()); + QPointF next = (f == scene->getFrameCount() - 1) + ? gPos + : cornerPointsTrack[f + 1].value(itr.key()); + + QPointF graduationVec = + QVector2D(next - prev).normalized().toPointF() * 5.0; + graduationVec = QPointF(-graduationVec.y(), graduationVec.x()); + + p.drawLine(gPos - graduationVec, gPos + graduationVec); + + // draw frame + if (!info.numbersOnLine) continue; + if (itr.key() != Center && rectFrames.contains(f)) continue; + + bool found = false; + + for (auto& g : graduations) { + if (g.first == gPos) { + found = true; + g.second.append(f); + break; + } + } + if (!found) graduations.append(QPair>(gPos, {f})); + } + + for (auto& g : graduations) { + std::sort(g.second.begin(), g.second.end()); + QString frameStr = framesToString(g.second); + QFontMetricsF fm(smallFont); + QRectF textRect = fm.boundingRect(frameStr).adjusted(-5, 0, 5, 0); + QPointF pos = g.first + QPointF(5, 0); + if (info.numberAt == Qt::TopLeftCorner || + info.numberAt == Qt::BottomLeftCorner) + pos += QPointF(-textRect.width(), 0); + + p.setBrush(info.lineColor); + drawOutlinedText(p, pos, frameStr); + } + + ++itr; + } + + return img; +} + +//-------------------------------------------------------------- + +void ExportCameraTrackPopup::getInfoFromUI(ExportCameraTrackInfo& info) { + // target column + if (m_targetColumnCombo->count() == 0) return; + info.columnId = m_targetColumnCombo->currentData().toInt(); + // appearance settimgs + info.bgOpacity = m_bgOpacityField->getValue(); + TPixel32 lineTCol = m_lineColorFld->getColor(); + info.lineColor = QColor((int)lineTCol.r, (int)lineTCol.g, (int)lineTCol.b); + // camera rect settings + info.cameraRectOnKeys = m_cameraRectOnKeysCB->isChecked(); + info.cameraRectOnTags = m_cameraRectOnTagsCB->isChecked(); +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + QStringList framesStrList = + m_cameraRectFramesEdit->text().split(",", Qt::SkipEmptyParts); +#else + QStringList framesStrList = + m_cameraRectFramesEdit->text().split(",", QString::SkipEmptyParts); +#endif + for (auto fStr : framesStrList) { + bool ok; + int f = fStr.toInt(&ok); + if (ok) info.cameraRectFrames.insert(f - 1); + } + // track line settings + info.lineTL = m_lineTL_CB->isChecked(); + info.lineTR = m_lineTR_CB->isChecked(); + info.lineCenter = m_lineCenter_CB->isChecked(); + info.lineBL = m_lineBL_CB->isChecked(); + info.lineBR = m_lineBR_CB->isChecked(); + info.graduationInterval = m_graduationIntervalCombo->currentData().toInt(); + // frame number settings + info.numberAt = (Qt::Corner)(m_numberAtCombo->currentData().toInt()); + info.numbersOnLine = m_numbersOnLineCB->isChecked(); + ; + info.font = m_fontCombo->currentFont(); + info.font.setPixelSize(m_fontSizeEdit->getValue()); +} + +//-------------------------------------------------------------- + +void ExportCameraTrackPopup::updatePreview() { + ExportCameraTrackInfo info; + getInfoFromUI(info); + if (info.columnId == -1) return; + + QImage img = generateCameraTrackImg(info, true); + + m_previewPane->setPixmap(QPixmap::fromImage(img)); +} + +//-------------------------------------------------------------- + +void ExportCameraTrackPopup::onExport() { + ExportCameraTrackInfo info; + getInfoFromUI(info); + if (info.columnId == -1) return; + + QImage img = generateCameraTrackImg(info, false); + + ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene(); + + static GenericSaveFilePopup* savePopup = 0; + if (!savePopup) { + savePopup = + new GenericSaveFilePopup(QObject::tr("Export Camera Track Image")); + savePopup->setFilterTypes({"jpg", "jpeg", "bmp", "png", "tif"}); + } + + if (!scene->isUntitled()) + savePopup->setFolder(scene->getScenePath().getParentDir()); + else + savePopup->setFolder( + TProjectManager::instance()->getCurrentProject()->getScenesPath()); + + TXsheet* xsh = TApp::instance()->getCurrentXsheet()->getXsheet(); + TStageObjectId cameraId = xsh->getStageObjectTree()->getCurrentCameraId(); + std::string cameraName = xsh->getStageObject(cameraId)->getName(); + savePopup->setFilename(TFilePath(cameraName + ".tif")); + + TFilePath fp = savePopup->getPath(); + if (fp.isEmpty()) return; + + std::string type = fp.getType(); + if (type == "") + fp = fp.withType("tif"); + else if (type != "jpg" && type != "jpeg" && type != "bmp" && type != "png" && + type != "tif") { + DVGui::MsgBoxInPopup(DVGui::WARNING, + tr("Please specify one of the following file formats; " + "jpg, jpeg, bmp, png, and tif")); + return; + } + + img.save(fp.getQString()); +} + +//******************************************************************************** +// Export Camera Track Command instantiation +//******************************************************************************** + +OpenPopupCommandHandler ExportCameraTrackPopupCommand( + MI_ExportCameraTrack); diff --git a/toonz/sources/toonz/exportcameratrackpopup.h b/toonz/sources/toonz/exportcameratrackpopup.h new file mode 100644 index 0000000..fe2a4d8 --- /dev/null +++ b/toonz/sources/toonz/exportcameratrackpopup.h @@ -0,0 +1,127 @@ +#pragma once + +#ifndef EXPORTCAMERATRACKPOPUP_H +#define EXPORTCAMERATRACKPOPUP_H + +// Tnz6 includes +#include "toonzqt/dvdialog.h" +#include +#include +#include + +namespace DVGui { +class FileField; +class ColorField; +class IntLineEdit; +class DoubleField; +} // namespace DVGui + +class QComboBox; +class QCheckBox; +class QFontComboBox; +class QLineEdit; + +//********************************************************************************* +// Export Camera Track Popup declaration +//********************************************************************************* + +struct ExportCameraTrackInfo { + // target column + int columnId = -1; + // appearance settimgs + double bgOpacity; + QColor lineColor; + // camera rect settings + bool cameraRectOnKeys; + bool cameraRectOnTags; + QSet cameraRectFrames; + // track line settings + bool lineTL, lineTR, lineCenter, lineBL, lineBR; + int graduationInterval; + // frame number settings + Qt::Corner numberAt; + bool numbersOnLine; + QFont font; +}; + +//----------------------------------------------------------------------------- + +class CameraTrackPreviewPane final : public QWidget { + Q_OBJECT + QPixmap m_pixmap; + double m_scaleFactor; + +protected: + void paintEvent(QPaintEvent* event) override; + +public: + CameraTrackPreviewPane(QWidget* parent); + void setPixmap(QPixmap pm); + void doZoom(double d_scale); + void fitScaleTo(QSize size); +}; + +class CameraTrackPreviewArea final : public QScrollArea { + Q_OBJECT + QPoint m_mousePos; + +protected: + void mousePressEvent(QMouseEvent* e) override; + void mouseMoveEvent(QMouseEvent* e) override; + void contextMenuEvent(QContextMenuEvent* event) override; + void wheelEvent(QWheelEvent* event) override; + +public: + CameraTrackPreviewArea(QWidget* parent) : QScrollArea(parent) {} +protected slots: + void fitToWindow(); +}; + +//----------------------------------------------------------------------------- + +class ExportCameraTrackPopup final : public DVGui::Dialog { + Q_OBJECT + CameraTrackPreviewPane* m_previewPane; + CameraTrackPreviewArea* m_previewArea; + + QComboBox* m_targetColumnCombo; + DVGui::DoubleField* m_bgOpacityField; + DVGui::ColorField* m_lineColorFld; + + QCheckBox *m_cameraRectOnKeysCB, *m_cameraRectOnTagsCB; + QLineEdit* m_cameraRectFramesEdit; + + QCheckBox *m_lineTL_CB, *m_lineTR_CB, *m_lineCenter_CB, *m_lineBL_CB, + *m_lineBR_CB; + QComboBox* m_graduationIntervalCombo; + + QComboBox* m_numberAtCombo; + QCheckBox* m_numbersOnLineCB; + QFontComboBox* m_fontCombo; + DVGui::IntLineEdit* m_fontSizeEdit; + +public: + ExportCameraTrackPopup(); + + // bool execute() override; + +private: + void initialize(); + void saveSettings(); + void loadSettings(); + void updateTargetColumnComboItems(); + + QImage generateCameraTrackImg(const ExportCameraTrackInfo& info, + bool isPreview); + void getInfoFromUI(ExportCameraTrackInfo& info); + +protected: + void showEvent(QShowEvent* event) override { initialize(); } + void closeEvent(QCloseEvent* event) override { saveSettings(); } + +private slots: + void updatePreview(); + void onExport(); +}; + +#endif // EXPORTCAMERATRACKPOPUP_H \ No newline at end of file diff --git a/toonz/sources/toonz/mainwindow.cpp b/toonz/sources/toonz/mainwindow.cpp index aca211a..938b692 100644 --- a/toonz/sources/toonz/mainwindow.cpp +++ b/toonz/sources/toonz/mainwindow.cpp @@ -1752,6 +1752,8 @@ void MainWindow::defineActions() { "", "clear_cache"); createMenuFileAction(MI_ExportCurrentScene, QT_TR_NOOP("&Export Current Scene"), ""); + createMenuFileAction(MI_ExportCameraTrack, QT_TR_NOOP("&Export Camera Track"), + ""); // Menu - Edit diff --git a/toonz/sources/toonz/menubar.cpp b/toonz/sources/toonz/menubar.cpp index 885355e..290ad9c 100644 --- a/toonz/sources/toonz/menubar.cpp +++ b/toonz/sources/toonz/menubar.cpp @@ -1117,6 +1117,7 @@ QMenuBar *StackedMenuBar::createFullMenuBar() { addMenuItem(exportMenu, MI_StopMotionExportImageSequence); #endif addMenuItem(exportMenu, MI_ExportTvpJson); + addMenuItem(exportMenu, MI_ExportCameraTrack); } fileMenu->addSeparator(); addMenuItem(fileMenu, MI_PrintXsheet); diff --git a/toonz/sources/toonz/menubarcommandids.h b/toonz/sources/toonz/menubarcommandids.h index 11fe2fe..504a282 100644 --- a/toonz/sources/toonz/menubarcommandids.h +++ b/toonz/sources/toonz/menubarcommandids.h @@ -470,6 +470,7 @@ #define MI_ExportOCA "MI_ExportOCA" #define MI_ExportTvpJson "MI_ExportTvpJson" #define MI_ExportXsheetPDF "MI_ExportXsheetPDF" +#define MI_ExportCameraTrack "MI_ExportCameraTrack" // mark id is added for each actual command (i.g. MI_SetCellMark1) #define MI_SetCellMark "MI_SetCellMark"