diff --git a/toonz/sources/toonz/CMakeLists.txt b/toonz/sources/toonz/CMakeLists.txt index 76db34f..2b8ab2e 100644 --- a/toonz/sources/toonz/CMakeLists.txt +++ b/toonz/sources/toonz/CMakeLists.txt @@ -13,7 +13,6 @@ set(MOC_HEADERS boardsettingspopup.h brightnessandcontrastpopup.h cachefxcommand.h - cameracapturelevelcontrol.h camerasettingspopup.h canvassizepopup.h castviewer.h @@ -69,7 +68,6 @@ set(MOC_HEADERS outputsettingspopup.h overwritepopup.h pane.h - penciltestpopup.h pltgizmopopup.h preferencespopup.h previewer.h @@ -129,6 +127,14 @@ set(MOC_HEADERS ${MOC_HEADERS} ../stopmotion/stopmotion.h ../stopmotion/stopmotioncontroller.h + cameracapturelevelcontrol.h + penciltestpopup.h +) +else() +set(MOC_HEADERS + ${MOC_HEADERS} + cameracapturelevelcontrol_qt.h + penciltestpopup_qt.h ) endif() @@ -340,11 +346,9 @@ set(SOURCES comboviewerpane.cpp historypane.cpp cleanupsettingspane.cpp - penciltestpopup.cpp audiorecordingpopup.cpp locatorpopup.cpp styleshortcutswitchablepanel.cpp - cameracapturelevelcontrol.cpp reframepopup.cpp autoinputcellnumberpopup.cpp colormodelbehaviorpopup.cpp @@ -365,6 +369,14 @@ set(SOURCES ${SOURCES} ../stopmotion/stopmotion.cpp ../stopmotion/stopmotioncontroller.cpp + cameracapturelevelcontrol.cpp + penciltestpopup.cpp +) +else() +set(SOURCES + ${SOURCES} + cameracapturelevelcontrol_qt.cpp + penciltestpopup_qt.cpp ) endif() diff --git a/toonz/sources/toonz/autoinputcellnumberpopup.cpp b/toonz/sources/toonz/autoinputcellnumberpopup.cpp index 6b64e09..afb5ff4 100644 --- a/toonz/sources/toonz/autoinputcellnumberpopup.cpp +++ b/toonz/sources/toonz/autoinputcellnumberpopup.cpp @@ -5,7 +5,12 @@ #include "menubarcommandids.h" #include "cellselection.h" #include "columnselection.h" -#include "penciltestpopup.h" // for FrameNumberLineEdit + +#ifdef WITH_STOPMOTION +#include "penciltestpopup.h" +#else +#include "penciltestpopup_qt.h" +#endif // TnzQt includes #include "toonzqt/intfield.h" @@ -31,7 +36,6 @@ #include namespace { - class AutoInputCellNumberUndo final : public TUndo { int m_increment, m_interval, m_step, m_repeat; int m_from, m_to; @@ -64,7 +68,7 @@ public: int rowsCount() { return m_rowsCount; } }; -}; +}; // namespace //----------------------------------------------------------------------------- // executing this on column selection, set r1 = -1. @@ -273,7 +277,7 @@ AutoInputCellNumberPopup::AutoInputCellNumberPopup() bool ret = true; ret = ret && connect(m_overwriteBtn, SIGNAL(clicked()), this, SLOT(onOverwritePressed())); - ret = ret && + ret = ret && connect(m_insertBtn, SIGNAL(clicked()), this, SLOT(onInsertPressed())); ret = ret && connect(cancelBtn, SIGNAL(clicked()), this, SLOT(close())); assert(ret); diff --git a/toonz/sources/toonz/cameracapturelevelcontrol.cpp b/toonz/sources/toonz/cameracapturelevelcontrol.cpp index c138596..cc2a591 100644 --- a/toonz/sources/toonz/cameracapturelevelcontrol.cpp +++ b/toonz/sources/toonz/cameracapturelevelcontrol.cpp @@ -50,12 +50,12 @@ void drawSliderHandle(QPoint pos, QPainter& p, QColor color, bool selected) { p.drawConvexPolygon(points, 5); p.resetTransform(); } -}; +}; // namespace //----------------------------------------------------------------------------- CameraCaptureLevelHistogram::CameraCaptureLevelHistogram(QWidget* parent) : QFrame(parent) - , m_histogramCue(false) + //, m_histogramCue(false) , m_currentItem(None) , m_black(0) , m_white(255) @@ -72,29 +72,27 @@ CameraCaptureLevelHistogram::CameraCaptureLevelHistogram(QWidget* parent) //----------------------------------------------------------------------------- -void CameraCaptureLevelHistogram::updateHistogram(QImage& image) { - // obtain histogram only when clicked - if (!m_histogramCue) return; +void CameraCaptureLevelHistogram::updateHistogram(cv::Mat& image) { + cv::Mat gray, hist; + cv::cvtColor(image, gray, cv::COLOR_RGB2GRAY); - QVector tmpHisto(256); - tmpHisto.fill(0); - for (int y = 0; y < image.height(); y++) { - QRgb* rgb_p = (QRgb*)(image.scanLine(y)); - for (int x = 0; x < image.width(); x++, rgb_p++) { - tmpHisto[qGray(*rgb_p)]++; - } - } - // obtain max value and normalize - int max = 0; - for (int c = 0; c < 256; c++) { - if (tmpHisto.at(c) > max) max = tmpHisto.at(c); - } + const int hdims[] = {256}; // size of histogram + const float hranges[] = {0, 256}; + const float* ranges[] = {hranges}; // min and max values for each bin + double max_val = .0; + + cv::calcHist(&gray, 1, 0, cv::Mat(), hist, 1, hdims, ranges); + // obtain the maximum value + cv::minMaxLoc(hist, 0, &max_val); + // scaling + hist = hist * (max_val ? HISTOGRAM_HEIGHT / max_val : 0.); + + float* histVal = hist.ptr(0); for (int c = 0; c < 256; c++) { - m_histogramData[c] = tmpHisto.at(c) * HISTOGRAM_HEIGHT / max; + m_histogramData[c] = int(histVal[c]); } histogramObtained = true; update(); - m_histogramCue = false; } //----------------------------------------------------------------------------- @@ -106,7 +104,7 @@ void CameraCaptureLevelHistogram::paintEvent(QPaintEvent* event) { // draw histogram p.setPen(Qt::black); - p.setBrush((m_currentItem == Histogram) ? Qt::darkGray : QColor(32, 32, 32)); + p.setBrush(QColor(32, 32, 32)); p.drawRect(histoRect.adjusted(-1, -1, 0, 0)); if (histogramObtained) { @@ -117,10 +115,6 @@ void CameraCaptureLevelHistogram::paintEvent(QPaintEvent* event) { histoRect.bottomLeft() + QPoint(c, -m_histogramData.at(c))); } } - if (!histogramObtained || m_currentItem == Histogram) { - p.setPen(Qt::white); - p.drawText(histoRect, Qt::AlignCenter, tr("Click to Update Histogram")); - } p.setRenderHint(QPainter::Antialiasing); // draw slider handles @@ -147,10 +141,6 @@ void CameraCaptureLevelHistogram::paintEvent(QPaintEvent* event) { void CameraCaptureLevelHistogram::mousePressEvent(QMouseEvent* event) { if (event->button() != Qt::LeftButton) return; - if (m_currentItem == Histogram) { - m_histogramCue = true; - return; - } if (m_currentItem == None) return; QPoint pos = event->pos(); if (m_currentItem == BlackSlider) @@ -168,7 +158,7 @@ void CameraCaptureLevelHistogram::mousePressEvent(QMouseEvent* event) { void CameraCaptureLevelHistogram::mouseMoveEvent(QMouseEvent* event) { QPoint pos = event->pos(); if (event->buttons() & Qt::LeftButton) { - if (m_currentItem == None || m_currentItem == Histogram) return; + if (m_currentItem == None) return; int hPos = pos.x() - SIDE_MARGIN - m_offset; bool changed = false; @@ -185,7 +175,7 @@ void CameraCaptureLevelHistogram::mouseMoveEvent(QMouseEvent* event) { if (hPos < m_black + 1) hPos = m_black + 1; else if (hPos > m_white - 1) - hPos = m_white - 1; + hPos = m_white - 1; float gamma = hPosToGamma(hPos, m_black, m_white); if (gamma != m_gamma) { m_gamma = gamma; @@ -223,8 +213,7 @@ void CameraCaptureLevelHistogram::mouseMoveEvent(QMouseEvent* event) { setToolTip(""); QRect histoRect(5, 1, 256, HISTOGRAM_HEIGHT); if (histoRect.contains(pos)) { - setToolTip(tr("Click to Update Histogram")); - m_currentItem = Histogram; + // do nothing } else { // slider handles QPoint sliderBasePos(SIDE_MARGIN, HISTOGRAM_HEIGHT + 2); @@ -286,6 +275,7 @@ CameraCaptureLevelControl::CameraCaptureLevelControl(QWidget* parent) m_whiteFld = new IntLineEdit(this, m_histogram->white(), 2, 255); m_thresholdFld = new IntLineEdit(this, m_histogram->threshold(), 0, 255); m_gammaFld = new DoubleLineEdit(this, m_histogram->gamma()); + m_lut = cv::Mat(1, 256, CV_8U); m_blackFld->setToolTip(tr("Black Point Value")); m_whiteFld->setToolTip(tr("White Point Value")); @@ -330,6 +320,10 @@ CameraCaptureLevelControl::CameraCaptureLevelControl(QWidget* parent) //----------------------------------------------------------------------------- void CameraCaptureLevelControl::onHistogramValueChanged(int itemId) { + if (itemId == CameraCaptureLevelHistogram::ThresholdSlider) { + m_thresholdFld->setValue(m_histogram->threshold()); + return; + } if (itemId == CameraCaptureLevelHistogram::BlackSlider) { m_blackFld->setValue(m_histogram->black()); m_whiteFld->setRange(m_histogram->black() + 2, 255); @@ -338,9 +332,8 @@ void CameraCaptureLevelControl::onHistogramValueChanged(int itemId) { m_blackFld->setRange(0, m_histogram->white() - 2); } else if (itemId == CameraCaptureLevelHistogram::GammaSlider) { m_gammaFld->setValue(m_histogram->gamma()); - } else if (itemId == CameraCaptureLevelHistogram::ThresholdSlider) { - m_thresholdFld->setValue(m_histogram->threshold()); } + computeLut(); } //----------------------------------------------------------------------------- @@ -367,3 +360,42 @@ void CameraCaptureLevelControl::setMode(bool color_grayscale) { m_thresholdFld->setVisible(!color_grayscale); update(); } + +//----------------------------------------------------------------------------- + +void CameraCaptureLevelControl::adjustLevel(cv::Mat& image) { + int black = m_histogram->black(); + int white = m_histogram->white(); + float gamma = m_histogram->gamma(); + if (black == 0 && white == 255 && gamma == 1.0) return; + + cv::LUT(image, m_lut, image); +} + +void CameraCaptureLevelControl::binarize(cv::Mat& image) { + cv::threshold(image, image, m_histogram->threshold(), 255, cv::THRESH_BINARY); +} + +void CameraCaptureLevelControl::computeLut() { + int black = m_histogram->black(); + int white = m_histogram->white(); + float gamma = m_histogram->gamma(); + + const float maxChannelValueF = 255.0f; + + float value; + + uchar* p = m_lut.data; + for (int i = 0; i < 256; i++) { + if (i <= black) + value = 0.0f; + else if (i >= white) + value = 1.0f; + else { + value = (float)(i - black) / (float)(white - black); + value = std::pow(value, 1.0f / gamma); + } + + p[i] = (uchar)std::floor(value * maxChannelValueF); + } +} \ No newline at end of file diff --git a/toonz/sources/toonz/cameracapturelevelcontrol.h b/toonz/sources/toonz/cameracapturelevelcontrol.h index 4254dd7..5e3c191 100644 --- a/toonz/sources/toonz/cameracapturelevelcontrol.h +++ b/toonz/sources/toonz/cameracapturelevelcontrol.h @@ -5,11 +5,12 @@ #include #include +#include "opencv2/opencv.hpp" namespace DVGui { class IntLineEdit; class DoubleLineEdit; -} +} // namespace DVGui //============================================================================= // CameraCaptureLevelHistogram //----------------------------------------------------------------------------- @@ -22,15 +23,12 @@ public: WhiteSlider, GammaSlider, ThresholdSlider, - Histogram, NumItems }; enum LevelControlMode { Color_GrayScale, BlackAndWhite, NumModes }; private: - bool m_histogramCue; QVector m_histogramData; - LevelControlItem m_currentItem; int m_black, m_white, m_threshold; @@ -42,7 +40,7 @@ private: public: CameraCaptureLevelHistogram(QWidget* parent = 0); - void updateHistogram(QImage& image); + void updateHistogram(cv::Mat& image); int black() { return m_black; } int white() { return m_white; } @@ -78,19 +76,18 @@ class CameraCaptureLevelControl : public QFrame { CameraCaptureLevelHistogram* m_histogram; DVGui::IntLineEdit *m_blackFld, *m_whiteFld, *m_thresholdFld; DVGui::DoubleLineEdit* m_gammaFld; + cv::Mat m_lut; + + void computeLut(); public: CameraCaptureLevelControl(QWidget* parent = 0); - void updateHistogram(QImage& image) { m_histogram->updateHistogram(image); } + void updateHistogram(cv::Mat& image) { m_histogram->updateHistogram(image); } void setMode(bool color_grayscale); - void getValues(int& black, int& white, float& gamma) { - black = m_histogram->black(); - white = m_histogram->white(); - gamma = m_histogram->gamma(); - } - float getThreshold() { return m_histogram->threshold(); } + void adjustLevel(cv::Mat& image); + void binarize(cv::Mat& image); protected slots: void onHistogramValueChanged(int itemId); diff --git a/toonz/sources/toonz/cameracapturelevelcontrol_qt.cpp b/toonz/sources/toonz/cameracapturelevelcontrol_qt.cpp new file mode 100644 index 0000000..54d3eb0 --- /dev/null +++ b/toonz/sources/toonz/cameracapturelevelcontrol_qt.cpp @@ -0,0 +1,369 @@ +#include "cameracapturelevelcontrol_qt.h" + +#include "toonzqt/intfield.h" +#include "toonzqt/doublefield.h" + +#include +#include +#include +#include +#include +#include + +using namespace DVGui; + +namespace { +const int HISTOGRAM_HEIGHT = 50; +const int SIDE_MARGIN = 7; +static bool histogramObtained = false; + +// returns the horizontal position of gamma slider (0-255) +int gammaToHPos(float gamma, int black, int white) { + float ratio = std::pow(0.5f, gamma); + return black + (int)std::round((float)(white - black) * ratio); +} + +// returns the gamma value from the slider position +float hPosToGamma(int hPos, int black, int white) { + if (hPos <= black + 1) + return 9.99f; + else if (hPos >= white - 1) + return 0.01f; + + float ratio = (float)(hPos - black) / (float)(white - black); + + float gamma = std::log(ratio) / std::log(0.5); + int decimals = 2; + gamma = + std::round(gamma * std::pow(10.0, decimals)) / std::pow(10.0, decimals); + return gamma; +} + +void drawSliderHandle(QPoint pos, QPainter& p, QColor color, bool selected) { + p.setPen((selected) ? Qt::yellow : Qt::black); + p.setBrush(color); + + p.translate(pos); + + static const QPoint points[5] = {QPoint(0, 0), QPoint(-6, 8), QPoint(-3, 12), + QPoint(3, 12), QPoint(6, 8)}; + p.drawConvexPolygon(points, 5); + p.resetTransform(); +} +}; // namespace +//----------------------------------------------------------------------------- + +CameraCaptureLevelHistogram::CameraCaptureLevelHistogram(QWidget* parent) + : QFrame(parent) + , m_histogramCue(false) + , m_currentItem(None) + , m_black(0) + , m_white(255) + , m_gamma(1.0) + , m_threshold(128) + , m_offset(0) + , m_mode(Color_GrayScale) + , m_histogramData(256) { + setFixedSize(256 + SIDE_MARGIN * 2, HISTOGRAM_HEIGHT + 15); + setMouseTracking(true); + + m_histogramData.fill(0); +} + +//----------------------------------------------------------------------------- + +void CameraCaptureLevelHistogram::updateHistogram(QImage& image) { + // obtain histogram only when clicked + if (!m_histogramCue) return; + + QVector tmpHisto(256); + tmpHisto.fill(0); + for (int y = 0; y < image.height(); y++) { + QRgb* rgb_p = (QRgb*)(image.scanLine(y)); + for (int x = 0; x < image.width(); x++, rgb_p++) { + tmpHisto[qGray(*rgb_p)]++; + } + } + // obtain max value and normalize + int max = 0; + for (int c = 0; c < 256; c++) { + if (tmpHisto.at(c) > max) max = tmpHisto.at(c); + } + for (int c = 0; c < 256; c++) { + m_histogramData[c] = tmpHisto.at(c) * HISTOGRAM_HEIGHT / max; + } + histogramObtained = true; + update(); + m_histogramCue = false; +} + +//----------------------------------------------------------------------------- + +void CameraCaptureLevelHistogram::paintEvent(QPaintEvent* event) { + QPainter p(this); + + QRect histoRect(SIDE_MARGIN, 1, 256, HISTOGRAM_HEIGHT); + + // draw histogram + p.setPen(Qt::black); + p.setBrush((m_currentItem == Histogram) ? Qt::darkGray : QColor(32, 32, 32)); + p.drawRect(histoRect.adjusted(-1, -1, 0, 0)); + + if (histogramObtained) { + p.setPen(Qt::white); + for (int c = 0; c < 256; c++) { + if (m_histogramData.at(c) == 0) continue; + p.drawLine(histoRect.bottomLeft() + QPoint(c, 0), + histoRect.bottomLeft() + QPoint(c, -m_histogramData.at(c))); + } + } + if (!histogramObtained || m_currentItem == Histogram) { + p.setPen(Qt::white); + p.drawText(histoRect, Qt::AlignCenter, tr("Click to Update Histogram")); + } + + p.setRenderHint(QPainter::Antialiasing); + // draw slider handles + QPoint sliderBasePos(SIDE_MARGIN, HISTOGRAM_HEIGHT + 2); + if (m_mode == Color_GrayScale) { + // black + drawSliderHandle(sliderBasePos + QPoint(m_black, 0), p, QColor(32, 32, 32), + m_currentItem == BlackSlider); + // gamma + drawSliderHandle( + sliderBasePos + QPoint(gammaToHPos(m_gamma, m_black, m_white), 0), p, + Qt::gray, m_currentItem == GammaSlider); + // white + drawSliderHandle(sliderBasePos + QPoint(m_white, 0), p, + QColor(220, 220, 220), m_currentItem == WhiteSlider); + } else if (m_mode == BlackAndWhite) + // threshold + drawSliderHandle(sliderBasePos + QPoint(m_threshold, 0), p, Qt::gray, + m_currentItem == ThresholdSlider); + p.setRenderHint(QPainter::Antialiasing, false); +} + +//----------------------------------------------------------------------------- + +void CameraCaptureLevelHistogram::mousePressEvent(QMouseEvent* event) { + if (event->button() != Qt::LeftButton) return; + if (m_currentItem == Histogram) { + m_histogramCue = true; + return; + } + if (m_currentItem == None) return; + QPoint pos = event->pos(); + if (m_currentItem == BlackSlider) + m_offset = pos.x() - SIDE_MARGIN - m_black; + else if (m_currentItem == GammaSlider) + m_offset = pos.x() - SIDE_MARGIN - gammaToHPos(m_gamma, m_black, m_white); + else if (m_currentItem == BlackSlider) + m_offset = pos.x() - SIDE_MARGIN - m_white; + else if (m_currentItem == ThresholdSlider) + m_offset = pos.x() - SIDE_MARGIN - m_threshold; +} + +//----------------------------------------------------------------------------- + +void CameraCaptureLevelHistogram::mouseMoveEvent(QMouseEvent* event) { + QPoint pos = event->pos(); + if (event->buttons() & Qt::LeftButton) { + if (m_currentItem == None || m_currentItem == Histogram) return; + + int hPos = pos.x() - SIDE_MARGIN - m_offset; + bool changed = false; + if (m_currentItem == BlackSlider) { + if (hPos < 0) + hPos = 0; + else if (hPos > m_white - 2) + hPos = m_white - 2; + if (hPos != m_black) { + m_black = hPos; + changed = true; + } + } else if (m_currentItem == GammaSlider) { + if (hPos < m_black + 1) + hPos = m_black + 1; + else if (hPos > m_white - 1) + hPos = m_white - 1; + float gamma = hPosToGamma(hPos, m_black, m_white); + if (gamma != m_gamma) { + m_gamma = gamma; + changed = true; + } + } else if (m_currentItem == WhiteSlider) { + if (hPos < m_black + 2) + hPos = m_black + 2; + else if (hPos > 255) + hPos = 255; + if (hPos != m_white) { + m_white = hPos; + changed = true; + } + } else if (m_currentItem == ThresholdSlider) { + if (hPos < 0) + hPos = 0; + else if (hPos > 255) + hPos = 255; + if (hPos != m_threshold) { + m_threshold = hPos; + changed = true; + } + } + if (changed) { + update(); + emit valueChange(m_currentItem); + } + return; + } + + // on hover + LevelControlItem oldItem = m_currentItem; + m_currentItem = None; + setToolTip(""); + QRect histoRect(5, 1, 256, HISTOGRAM_HEIGHT); + if (histoRect.contains(pos)) { + setToolTip(tr("Click to Update Histogram")); + m_currentItem = Histogram; + } else { + // slider handles + QPoint sliderBasePos(SIDE_MARGIN, HISTOGRAM_HEIGHT + 2); + QRect sliderRect(-6, 0, 12, 12); + if (m_mode == Color_GrayScale) { + // white + if (sliderRect.translated(sliderBasePos + QPoint(m_white, 0)) + .contains(pos)) { + m_currentItem = WhiteSlider; + setToolTip(tr("Drag to Move White Point")); + } + // gamma + else if (sliderRect + .translated( + sliderBasePos + + QPoint(gammaToHPos(m_gamma, m_black, m_white), 0)) + .contains(pos)) { + m_currentItem = GammaSlider; + setToolTip(tr("Drag to Move Gamma")); + } + // black + else if (sliderRect.translated(sliderBasePos + QPoint(m_black, 0)) + .contains(pos)) { + m_currentItem = BlackSlider; + setToolTip(tr("Drag to Move Black Point")); + } + } else if (m_mode == BlackAndWhite) { + // threshold + if (sliderRect.translated(sliderBasePos + QPoint(m_threshold, 0)) + .contains(pos)) { + m_currentItem = ThresholdSlider; + setToolTip(tr("Drag to Move Threshold Point")); + } + } + } + if (oldItem != m_currentItem) update(); +} + +//----------------------------------------------------------------------------- + +void CameraCaptureLevelHistogram::mouseReleaseEvent(QMouseEvent* event) { + m_offset = 0; +} + +//----------------------------------------------------------------------------- + +void CameraCaptureLevelHistogram::leaveEvent(QEvent* event) { + m_currentItem = None; + m_offset = 0; + update(); +} + +//============================================================================= + +CameraCaptureLevelControl::CameraCaptureLevelControl(QWidget* parent) + : QFrame(parent) { + m_histogram = new CameraCaptureLevelHistogram(this); + m_blackFld = new IntLineEdit(this, m_histogram->black(), 0, 253); + m_whiteFld = new IntLineEdit(this, m_histogram->white(), 2, 255); + m_thresholdFld = new IntLineEdit(this, m_histogram->threshold(), 0, 255); + m_gammaFld = new DoubleLineEdit(this, m_histogram->gamma()); + + m_blackFld->setToolTip(tr("Black Point Value")); + m_whiteFld->setToolTip(tr("White Point Value")); + m_thresholdFld->setToolTip(tr("Threshold Value")); + m_thresholdFld->hide(); + m_gammaFld->setRange(0.01, 9.99); + m_gammaFld->setDecimals(2); + m_gammaFld->setFixedWidth(54); + m_gammaFld->setToolTip(tr("Gamma Value")); + + QVBoxLayout* mainLay = new QVBoxLayout(); + mainLay->setMargin(0); + mainLay->setSpacing(4); + { + mainLay->addWidget(m_histogram, 0, Qt::AlignHCenter); + QHBoxLayout* fieldsLay = new QHBoxLayout(); + fieldsLay->setMargin(1); + fieldsLay->setSpacing(0); + { + fieldsLay->addWidget(m_blackFld, 0); + fieldsLay->addStretch(1); + fieldsLay->addWidget(m_gammaFld, 0); + fieldsLay->addWidget(m_thresholdFld, 0); + fieldsLay->addStretch(1); + fieldsLay->addWidget(m_whiteFld, 0); + } + mainLay->addLayout(fieldsLay, 0); + } + setLayout(mainLay); + + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + + connect(m_histogram, SIGNAL(valueChange(int)), this, + SLOT(onHistogramValueChanged(int))); + connect(m_blackFld, SIGNAL(editingFinished()), this, SLOT(onFieldChanged())); + connect(m_whiteFld, SIGNAL(editingFinished()), this, SLOT(onFieldChanged())); + connect(m_gammaFld, SIGNAL(editingFinished()), this, SLOT(onFieldChanged())); + connect(m_thresholdFld, SIGNAL(editingFinished()), this, + SLOT(onFieldChanged())); +} + +//----------------------------------------------------------------------------- + +void CameraCaptureLevelControl::onHistogramValueChanged(int itemId) { + if (itemId == CameraCaptureLevelHistogram::BlackSlider) { + m_blackFld->setValue(m_histogram->black()); + m_whiteFld->setRange(m_histogram->black() + 2, 255); + } else if (itemId == CameraCaptureLevelHistogram::WhiteSlider) { + m_whiteFld->setValue(m_histogram->white()); + m_blackFld->setRange(0, m_histogram->white() - 2); + } else if (itemId == CameraCaptureLevelHistogram::GammaSlider) { + m_gammaFld->setValue(m_histogram->gamma()); + } else if (itemId == CameraCaptureLevelHistogram::ThresholdSlider) { + m_thresholdFld->setValue(m_histogram->threshold()); + } +} + +//----------------------------------------------------------------------------- + +void CameraCaptureLevelControl::onFieldChanged() { + if (m_histogram->mode() == CameraCaptureLevelHistogram::Color_GrayScale) + m_histogram->setValues(m_blackFld->getValue(), m_whiteFld->getValue(), + m_gammaFld->getValue()); + else if (m_histogram->mode() == CameraCaptureLevelHistogram::BlackAndWhite) + m_histogram->setThreshold(m_thresholdFld->getValue()); + + m_histogram->update(); +} + +//----------------------------------------------------------------------------- + +void CameraCaptureLevelControl::setMode(bool color_grayscale) { + m_histogram->setMode((color_grayscale) + ? CameraCaptureLevelHistogram::Color_GrayScale + : CameraCaptureLevelHistogram::BlackAndWhite); + m_blackFld->setVisible(color_grayscale); + m_whiteFld->setVisible(color_grayscale); + m_gammaFld->setVisible(color_grayscale); + m_thresholdFld->setVisible(!color_grayscale); + update(); +} diff --git a/toonz/sources/toonz/cameracapturelevelcontrol_qt.h b/toonz/sources/toonz/cameracapturelevelcontrol_qt.h new file mode 100644 index 0000000..0495184 --- /dev/null +++ b/toonz/sources/toonz/cameracapturelevelcontrol_qt.h @@ -0,0 +1,100 @@ +#pragma once + +#ifndef CAMERA_CAPTURE_LEVEL_CONTROL_H +#define CAMERA_CAPTURE_LEVEL_CONTROL_H + +#include +#include + +namespace DVGui { +class IntLineEdit; +class DoubleLineEdit; +} // namespace DVGui +//============================================================================= +// CameraCaptureLevelHistogram +//----------------------------------------------------------------------------- +class CameraCaptureLevelHistogram : public QFrame { + Q_OBJECT +public: + enum LevelControlItem { + None = 0, + BlackSlider, + WhiteSlider, + GammaSlider, + ThresholdSlider, + Histogram, + NumItems + }; + enum LevelControlMode { Color_GrayScale, BlackAndWhite, NumModes }; + +private: + bool m_histogramCue; + QVector m_histogramData; + + LevelControlItem m_currentItem; + + int m_black, m_white, m_threshold; + float m_gamma; + int m_offset; // offset between the handle position and the clicked position + + LevelControlMode m_mode; + +public: + CameraCaptureLevelHistogram(QWidget* parent = 0); + + void updateHistogram(QImage& image); + + int black() { return m_black; } + int white() { return m_white; } + float gamma() { return m_gamma; } + int threshold() { return m_threshold; } + LevelControlMode mode() { return m_mode; } + void setValues(int black, int white, float gamma) { + m_black = black; + m_white = white; + m_gamma = gamma; + } + void setThreshold(int thres) { m_threshold = thres; } + void setMode(LevelControlMode mode) { m_mode = mode; } + +protected: + void paintEvent(QPaintEvent* event) override; + + void mousePressEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + + void leaveEvent(QEvent* event) override; + +signals: + void valueChange(int itemId); +}; + +//----------------------------------------------------------------------------- + +class CameraCaptureLevelControl : public QFrame { + Q_OBJECT + + CameraCaptureLevelHistogram* m_histogram; + DVGui::IntLineEdit *m_blackFld, *m_whiteFld, *m_thresholdFld; + DVGui::DoubleLineEdit* m_gammaFld; + +public: + CameraCaptureLevelControl(QWidget* parent = 0); + + void updateHistogram(QImage& image) { m_histogram->updateHistogram(image); } + void setMode(bool color_grayscale); + + void getValues(int& black, int& white, float& gamma) { + black = m_histogram->black(); + white = m_histogram->white(); + gamma = m_histogram->gamma(); + } + float getThreshold() { return m_histogram->threshold(); } + +protected slots: + void onHistogramValueChanged(int itemId); + void onFieldChanged(); +}; + +#endif \ No newline at end of file diff --git a/toonz/sources/toonz/penciltestpopup.cpp b/toonz/sources/toonz/penciltestpopup.cpp index 6909e4a..3e0da9c 100644 --- a/toonz/sources/toonz/penciltestpopup.cpp +++ b/toonz/sources/toonz/penciltestpopup.cpp @@ -1,5 +1,17 @@ #include "penciltestpopup.h" +#ifdef WIN32 +#include +#include +#include +#include +#pragma comment(lib, "Mfplat.lib") +#pragma comment(lib, "Mf.lib") +#pragma comment(lib, "Mfreadwrite.lib") +#pragma comment(lib, "mfuuid.lib") +#pragma comment(lib, "shlwapi.lib") +#endif + // Tnz6 includes #include "tapp.h" #include "menubarcommandids.h" @@ -87,6 +99,10 @@ TEnv::StringVar CamCapCameraName("CamCapCameraName", ""); TEnv::StringVar CamCapCameraResolution("CamCapCameraResolution", ""); // Whether to open save-in popup on launch TEnv::IntVar CamCapOpenSaveInPopupOnLaunch("CamCapOpenSaveInPopupOnLaunch", 0); +TEnv::IntVar CamCapUseMjpg("CamCapUseMjpg", 1); +#ifdef _WIN32 +TEnv::IntVar CamCapUseDirectShow("CamCapUseDirectShow", 1); +#endif // SaveInFolderPopup settings TEnv::StringVar CamCapSaveInParentFolder("CamCapSaveInParentFolder", ""); TEnv::IntVar CamCapSaveInPopupSubFolder("CamCapSaveInPopupSubFolder", 0); @@ -118,7 +134,9 @@ void convertImageToRaster(TRaster32P dstRas, const QImage& srcImg) { dstRas->unlock(); } -void bgReduction(QImage& srcImg, QImage& bgImg, int reduction) { +void bgReduction(cv::Mat& srcImg, cv::Mat& bgImg, int reduction) { + // void bgReduction(QImage& srcImg, QImage& bgImg, int reduction) { + if (srcImg.cols != bgImg.cols || srcImg.rows != bgImg.rows) return; float reductionRatio = (float)reduction / 100.0f; // first, make the reduction table std::vector reductionAmount(256); @@ -126,104 +144,15 @@ void bgReduction(QImage& srcImg, QImage& bgImg, int reduction) { reductionAmount[i] = (int)(std::floor((float)(255 - i) * reductionRatio)); } // then, compute for all pixels - int lx = srcImg.width(); - int ly = srcImg.height(); - for (int j = 0; j < ly; j++) { - // TPixel32 * pix = ras->pixels(j); - QRgb* pix = (QRgb*)srcImg.scanLine(j); - QRgb* bgPix = (QRgb*)bgImg.scanLine(j); - for (int i = 0; i < lx; i++, pix++, bgPix++) { - *pix = qRgb(std::min(255, qRed(*pix) + reductionAmount[qRed(*bgPix)]), - std::min(255, qGreen(*pix) + reductionAmount[qGreen(*bgPix)]), - std::min(255, qBlue(*pix) + reductionAmount[qBlue(*bgPix)])); - } - } -} - -void my_compute_lut(int black, int white, float gamma, std::vector& lut) { - const int maxChannelValue = lut.size() - 1; - const float half_maxChannelValueF = 0.5f * maxChannelValue; - const float maxChannelValueF = maxChannelValue; - - float value; - - int lutSize = lut.size(); - for (int i = 0; i < lutSize; i++) { - if (i <= black) - value = 0.0f; - else if (i >= white) - value = 1.0f; - else { - value = (float)(i - black) / (float)(white - black); - value = std::pow(value, 1.0f / gamma); - } - - lut[i] = (int)std::floor(value * maxChannelValueF); - } -} - -//----------------------------------------------------------------------------- - -inline void doPixGray(QRgb* pix, const std::vector& lut) { - int gray = lut[qGray(*pix)]; - *pix = qRgb(gray, gray, gray); -} - -//----------------------------------------------------------------------------- - -inline void doPixBinary(QRgb* pix, int threshold) { - int gray = qGray(*pix); - if (gray >= threshold) - gray = 255; - else - gray = 0; - *pix = qRgb(gray, gray, gray); -} - -//----------------------------------------------------------------------------- - -inline void doPix(QRgb* pix, const std::vector& lut) { - // The captured image MUST be full opaque! - *pix = qRgb(lut[qRed(*pix)], lut[qGreen(*pix)], lut[qBlue(*pix)]); -} - -//----------------------------------------------------------------------------- - -void onChange(QImage& img, int black, int white, float gamma, bool doGray) { - std::vector lut(TPixel32::maxChannelValue + 1); - my_compute_lut(black, white, gamma, lut); - - int ly = img.height(); - // compute in multi thread - int threadCount = - std::max(1, QThreadPool::globalInstance()->maxThreadCount() / 2); - int tmpStart = 0; - for (int t = 0; t < threadCount; t++) { - int tmpEnd = (int)std::round((float)(ly * (t + 1)) / (float)threadCount); - - QRunnable* task; - if (doGray) - task = new ApplyGrayLutTask(tmpStart, tmpEnd, img, lut); - else - task = new ApplyLutTask(tmpStart, tmpEnd, img, lut); - - QThreadPool::globalInstance()->start(task); - - tmpStart = tmpEnd; - } - - QThreadPool::globalInstance()->waitForDone(); -} - -//----------------------------------------------------------------------------- - -void onChangeBW(QImage& img, int threshold) { - int lx = img.width(), y, ly = img.height(); - for (y = 0; y < ly; ++y) { - QRgb *pix = (QRgb*)img.scanLine(y), *endPix = (QRgb*)(pix + lx); - while (pix < endPix) { - doPixBinary(pix, threshold); - ++pix; + int lx = srcImg.cols; + int ly = srcImg.rows; + for (int j = 0; j < srcImg.rows; j++) { + cv::Vec3b* pix = srcImg.ptr(j); + cv::Vec3b* bgPix = bgImg.ptr(j); + for (int i = 0; i < srcImg.cols; i++, pix++, bgPix++) { + *pix = cv::Vec3b(std::min(255, (*pix)[0] + reductionAmount[(*bgPix)[0]]), + std::min(255, (*pix)[1] + reductionAmount[(*bgPix)[1]]), + std::min(255, (*pix)[2] + reductionAmount[(*bgPix)[2]])); } } } @@ -473,130 +402,9 @@ bool getRasterLevelSize(TXshLevel* level, TDimension& dim) { //============================================================================= -void ApplyLutTask::run() { - int lx = m_img.width(); - for (int y = m_fromY; y < m_toY; ++y) { - QRgb *pix = (QRgb*)m_img.scanLine(y), *endPix = (QRgb*)(pix + lx); - while (pix < endPix) { - doPix(pix, m_lut); - ++pix; - } - } -} - -void ApplyGrayLutTask::run() { - int lx = m_img.width(); - for (int y = m_fromY; y < m_toY; ++y) { - QRgb *pix = (QRgb*)m_img.scanLine(y), *endPix = (QRgb*)(pix + lx); - while (pix < endPix) { - doPixGray(pix, m_lut); - ++pix; - } - } -} - -//============================================================================= - -MyVideoSurface::MyVideoSurface(QWidget* widget, QObject* parent) - : QAbstractVideoSurface(parent) - , m_widget(widget) - , m_imageFormat(QImage::Format_Invalid) {} - -QList MyVideoSurface::supportedPixelFormats( - QAbstractVideoBuffer::HandleType handleType) const { - if (handleType == QAbstractVideoBuffer::NoHandle) { - return QList() - << QVideoFrame::Format_RGB32 << QVideoFrame::Format_ARGB32 - << QVideoFrame::Format_ARGB32_Premultiplied - << QVideoFrame::Format_RGB565 << QVideoFrame::Format_RGB555; - } else { - return QList(); - } -} - -bool MyVideoSurface::isFormatSupported(const QVideoSurfaceFormat& format, - QVideoSurfaceFormat* similar) const { - Q_UNUSED(similar); - - const QImage::Format imageFormat = - QVideoFrame::imageFormatFromPixelFormat(format.pixelFormat()); - const QSize size = format.frameSize(); - - return imageFormat != QImage::Format_Invalid && !size.isEmpty() && - format.handleType() == QAbstractVideoBuffer::NoHandle; -} - -bool MyVideoSurface::start(const QVideoSurfaceFormat& format) { - const QImage::Format imageFormat = - QVideoFrame::imageFormatFromPixelFormat(format.pixelFormat()); - const QSize size = format.frameSize(); - - if (imageFormat != QImage::Format_Invalid && !size.isEmpty()) { - m_imageFormat = imageFormat; - m_imageSize = size; - m_sourceRect = format.viewport(); - - QAbstractVideoSurface::start(format); - - m_widget->updateGeometry(); - updateVideoRect(); - - return true; - } else { - return false; - } -} - -void MyVideoSurface::updateVideoRect() { - QSize size = surfaceFormat().sizeHint(); - size.scale(m_widget->size(), Qt::KeepAspectRatio); - - m_targetRect = QRect(QPoint(0, 0), size); - m_targetRect.moveCenter(m_widget->rect().center()); - - double scale = - (double)m_targetRect.width() / (double)surfaceFormat().sizeHint().width(); - m_S2V_Transform = - QTransform::fromTranslate(m_targetRect.left(), m_targetRect.top()) - .scale(scale, scale); -} - -bool MyVideoSurface::present(const QVideoFrame& frame) { - if (surfaceFormat().pixelFormat() != frame.pixelFormat() || - surfaceFormat().frameSize() != frame.size()) { - setError(IncorrectFormatError); - stop(); - return false; - } else { - m_currentFrame = frame; - - if (m_currentFrame.map(QAbstractVideoBuffer::ReadOnly)) { - QImage image = QImage(m_currentFrame.bits(), m_currentFrame.width(), - m_currentFrame.height(), - m_currentFrame.bytesPerLine(), m_imageFormat); - m_currentFrame.unmap(); - if (!image.isNull()) emit frameCaptured(image); - } - - return true; - } -} - -void MyVideoSurface::stop() { - m_currentFrame = QVideoFrame(); - m_targetRect = QRect(); - - QAbstractVideoSurface::stop(); - - m_widget->update(); -} - -//============================================================================= - MyVideoWidget::MyVideoWidget(QWidget* parent) : QWidget(parent) , m_previousImage(QImage()) - , m_surface(0) , m_showOnionSkin(false) , m_onionOpacity(128) , m_upsideDown(false) @@ -604,44 +412,21 @@ MyVideoWidget::MyVideoWidget(QWidget* parent) , m_subCameraRect(QRect()) { setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); - m_surface = new MyVideoSurface(this); setMouseTracking(true); } -MyVideoWidget::~MyVideoWidget() { delete m_surface; } - -QSize MyVideoWidget::sizeHint() const { - return m_surface->surfaceFormat().sizeHint(); -} - void MyVideoWidget::paintEvent(QPaintEvent* event) { QPainter p(this); p.fillRect(rect(), Qt::black); - if (m_surface->isActive()) { - const QRect videoRect = m_surface->videoRect(); - const QTransform oldTransform = p.transform(); - - if (m_upsideDown) { - p.translate(videoRect.center()); - p.rotate(180); - p.translate(-videoRect.center()); - } - if (m_surface->surfaceFormat().scanLineDirection() == - QVideoSurfaceFormat::BottomToTop) { - p.scale(1, -1); - p.translate(0, -height()); - } - - p.drawImage(videoRect, m_image, m_surface->sourceRect()); - - p.setTransform(oldTransform); + if (!m_image.isNull()) { + p.drawImage(m_targetRect, m_image); if (m_showOnionSkin && m_onionOpacity > 0.0f && !m_previousImage.isNull() && m_previousImage.size() == m_image.size()) { p.setOpacity((qreal)m_onionOpacity / 255.0); - p.drawImage(videoRect, m_previousImage, m_surface->sourceRect()); + p.drawImage(m_targetRect, m_previousImage); p.setOpacity(1.0); } @@ -669,12 +454,23 @@ void MyVideoWidget::paintEvent(QPaintEvent* event) { void MyVideoWidget::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); + computeTransform(m_image.size()); +} + +void MyVideoWidget::computeTransform(QSize imgSize) { + QSize adjustedSize = imgSize; + adjustedSize.scale(size(), Qt::KeepAspectRatio); + m_targetRect = QRect(QPoint(), adjustedSize); + m_targetRect.moveCenter(rect().center()); - m_surface->updateVideoRect(); + double scale = (double)m_targetRect.width() / (double)imgSize.width(); + m_S2V_Transform = + QTransform::fromTranslate(m_targetRect.left(), m_targetRect.top()) + .scale(scale, scale); } void MyVideoWidget::setSubCameraSize(QSize size) { - QSize frameSize = m_surface->surfaceFormat().frameSize(); + QSize frameSize = m_image.size(); assert(frameSize == size.expandedTo(frameSize)); m_subCameraRect.setSize(size); @@ -698,7 +494,7 @@ void MyVideoWidget::drawSubCamera(QPainter& p) { p.drawRect(handleRect); }; - QRect vidSubRect = m_surface->transform().mapRect(m_subCameraRect); + QRect vidSubRect = m_S2V_Transform.mapRect(m_subCameraRect); p.setBrush(Qt::NoBrush); drawSubFrameLine(HandleLeft, vidSubRect.topLeft(), vidSubRect.bottomLeft()); drawSubFrameLine(HandleTop, vidSubRect.topLeft(), vidSubRect.topRight()); @@ -726,11 +522,11 @@ void MyVideoWidget::mouseMoveEvent(QMouseEvent* event) { }; // if the sub camera is not active, do nothing and return - if (!m_surface->isActive() || m_subCameraRect.isNull()) return; + if (m_image.isNull() || m_subCameraRect.isNull()) return; // with no mouse button, update the active handles if (event->buttons() == Qt::NoButton) { - QRect vidSubRect = m_surface->transform().mapRect(m_subCameraRect); + QRect vidSubRect = m_S2V_Transform.mapRect(m_subCameraRect); SUBHANDLE preHandle = m_activeSubHandle; if (!vidSubRect.adjusted(-d, -d, d, d).contains(event->pos())) m_activeSubHandle = HandleNone; @@ -795,7 +591,7 @@ void MyVideoWidget::mouseMoveEvent(QMouseEvent* event) { int minimumSize = 100; QPoint offset = - m_surface->transform().inverted().map(event->pos()) - m_dragStartPos; + m_S2V_Transform.inverted().map(event->pos()) - m_dragStartPos; if (m_activeSubHandle >= HandleTopLeft && m_activeSubHandle <= HandleBottomRight) { QSize offsetSize = m_preSubCameraRect.size(); @@ -808,7 +604,7 @@ void MyVideoWidget::mouseMoveEvent(QMouseEvent* event) { m_activeSubHandle == HandleTopRight) offset.rx() *= -1; } - QSize camSize = m_surface->surfaceFormat().sizeHint(); + QSize camSize = m_image.size(); if (m_activeSubHandle == HandleFrame) { clampPoint(offset, -m_preSubCameraRect.left(), @@ -855,13 +651,13 @@ void MyVideoWidget::mouseMoveEvent(QMouseEvent* event) { void MyVideoWidget::mousePressEvent(QMouseEvent* event) { // if the sub camera is not active, do nothing and return // use left button only and some handle must be active - if (!m_surface->isActive() || m_subCameraRect.isNull() || + if (m_image.isNull() || m_subCameraRect.isNull() || event->button() != Qt::LeftButton || m_activeSubHandle == HandleNone) return; // record the original sub camera size m_preSubCameraRect = m_subCameraRect; - m_dragStartPos = m_surface->transform().inverted().map(event->pos()); + m_dragStartPos = m_S2V_Transform.inverted().map(event->pos()); // temporary stop the camera emit stopCamera(); @@ -870,7 +666,7 @@ void MyVideoWidget::mousePressEvent(QMouseEvent* event) { void MyVideoWidget::mouseReleaseEvent(QMouseEvent* event) { // if the sub camera is not active, do nothing and return // use left button only and some handle must be active - if (!m_surface->isActive() || m_subCameraRect.isNull() || + if (m_image.isNull() || m_subCameraRect.isNull() || event->button() != Qt::LeftButton || m_activeSubHandle == HandleNone) return; @@ -1421,7 +1217,12 @@ PencilTestPopup::PencilTestPopup() : Dialog(0, false, false, "PencilTest") , m_currentCamera(NULL) , m_captureWhiteBGCue(false) - , m_captureCue(false) { + , m_captureCue(false) + , m_useMjpg(CamCapUseMjpg != 0) +#ifdef _WIN32 + , m_useDirectShow(CamCapUseDirectShow != 0) +#endif +{ setWindowTitle(tr("Camera Capture")); // add maximize button to the dialog @@ -1476,15 +1277,12 @@ PencilTestPopup::PencilTestPopup() m_timerIntervalFld = new IntField(this); m_captureTimer = new QTimer(this); m_countdownTimer = new QTimer(this); + m_timer = new QTimer(this); m_captureButton = new QPushButton(tr("Capture\n[Return key]"), this); QPushButton* closeButton = new QPushButton(tr("Close"), this); -#ifdef _WIN32 m_captureFilterSettingsBtn = new QPushButton(this); -#else - m_captureFilterSettingsBtn = 0; -#endif QPushButton* subfolderButton = new QPushButton(tr("Subfolder"), this); @@ -1543,13 +1341,11 @@ PencilTestPopup::PencilTestPopup() m_captureButton->setIcon(style.standardIcon(QStyle::SP_DialogOkButton)); m_captureButton->setIconSize(QSize(30, 30)); - if (m_captureFilterSettingsBtn) { - m_captureFilterSettingsBtn->setObjectName("GearButton"); - m_captureFilterSettingsBtn->setFixedSize(23, 23); - m_captureFilterSettingsBtn->setIconSize(QSize(15, 15)); - m_captureFilterSettingsBtn->setToolTip( - tr("Video Capture Filter Settings...")); - } + m_captureFilterSettingsBtn->setObjectName("GearButton"); + m_captureFilterSettingsBtn->setFixedSize(23, 23); + m_captureFilterSettingsBtn->setIconSize(QSize(15, 15)); + m_captureFilterSettingsBtn->setToolTip(tr("Options")); + m_captureFilterSettingsBtn->setMenu(createOptionsMenu()); subfolderButton->setObjectName("SubfolderButton"); subfolderButton->setIconSize(QSize(15, 15)); @@ -1746,13 +1542,13 @@ PencilTestPopup::PencilTestPopup() SLOT(refreshCameraList())); ret = ret && connect(m_cameraListCombo, SIGNAL(activated(int)), this, SLOT(onCameraListComboActivated(int))); - ret = ret && connect(m_resolutionCombo, SIGNAL(activated(const QString&)), - this, SLOT(onResolutionComboActivated(const QString&))); - ret = ret && connect(m_fileFormatOptionButton, SIGNAL(pressed()), this, + ret = ret && connect(m_resolutionCombo, SIGNAL(activated(int)), this, + SLOT(onResolutionComboActivated())); + ret = ret && connect(m_fileFormatOptionButton, SIGNAL(pressed()), this, SLOT(onFileFormatOptionButtonPressed())); - ret = ret && connect(m_levelNameEdit, SIGNAL(levelNameEdited()), this, + ret = ret && connect(m_levelNameEdit, SIGNAL(levelNameEdited()), this, SLOT(onLevelNameEdited())); - ret = ret && + ret = ret && connect(nextLevelButton, SIGNAL(pressed()), this, SLOT(onNextName())); ret = ret && connect(m_previousLevelButton, SIGNAL(pressed()), this, SLOT(onPreviousName())); @@ -1778,9 +1574,6 @@ PencilTestPopup::PencilTestPopup() ret = ret && connect(closeButton, SIGNAL(clicked()), this, SLOT(reject())); ret = ret && connect(m_captureButton, SIGNAL(clicked(bool)), this, SLOT(onCaptureButtonClicked(bool))); - if (m_captureFilterSettingsBtn) - ret = ret && connect(m_captureFilterSettingsBtn, SIGNAL(pressed()), this, - SLOT(onCaptureFilterSettingsBtnPressed())); ret = ret && connect(subfolderButton, SIGNAL(clicked(bool)), this, SLOT(openSaveInFolderPopup())); ret = ret && connect(m_saveInFileFld, SIGNAL(pathChanged()), this, @@ -1799,15 +1592,10 @@ PencilTestPopup::PencilTestPopup() SLOT(onSubCameraSizeEdited())); ret = ret && connect(m_subHeightFld, SIGNAL(editingFinished()), this, SLOT(onSubCameraSizeEdited())); - ret = ret && connect(m_videoWidget, &MyVideoWidget::startCamera, [&]() { - if (m_currentCamera) m_currentCamera->start(); - }); - ret = ret && connect(m_videoWidget, &MyVideoWidget::stopCamera, [&]() { - if (m_currentCamera) m_currentCamera->stop(); - }); ret = ret && connect(m_videoWidget, SIGNAL(subCameraResized(bool)), this, SLOT(onSubCameraResized(bool))); + ret = ret && connect(m_timer, SIGNAL(timeout()), this, SLOT(onTimeout())); assert(ret); refreshCameraList(); @@ -1824,7 +1612,7 @@ PencilTestPopup::PencilTestPopup() int startupResolutionIndex = m_resolutionCombo->findText(resStr); if (startupResolutionIndex >= 0) { m_resolutionCombo->setCurrentIndex(startupResolutionIndex); - onResolutionComboActivated(resStr); + onResolutionComboActivated(); } } @@ -1833,14 +1621,45 @@ PencilTestPopup::PencilTestPopup() //----------------------------------------------------------------------------- -PencilTestPopup::~PencilTestPopup() { - if (m_currentCamera) { - if (m_currentCamera->state() == QCamera::ActiveState) - m_currentCamera->stop(); - if (m_currentCamera->state() == QCamera::LoadedState) - m_currentCamera->unload(); - delete m_currentCamera; - } +PencilTestPopup::~PencilTestPopup() { m_cvWebcam.release(); } + +//----------------------------------------------------------------------------- + +QMenu* PencilTestPopup::createOptionsMenu() { + QMenu* menu = new QMenu(); + bool ret = true; +#ifdef _WIN32 + QAction* settingsAct = + menu->addAction(tr("Video Capture Filter Settings...")); + ret = ret && connect(settingsAct, SIGNAL(triggered()), this, + SLOT(onCaptureFilterSettingsBtnPressed())); + settingsAct->setIcon(QIcon(":Resources/preferences.svg")); + + menu->addSeparator(); + + QAction* useDShowAct = menu->addAction(tr("Use Direct Show Webcam Drivers")); + useDShowAct->setCheckable(true); + useDShowAct->setChecked(m_useDirectShow); + ret = ret && connect(useDShowAct, &QAction::toggled, [&](bool checked) { + m_cvWebcam.release(); + if (m_timer->isActive()) m_timer->stop(); + m_useDirectShow = checked; + CamCapUseDirectShow = checked; + m_timer->start(40); + }); +#endif + QAction* useMjpgAct = menu->addAction(tr("Use MJPG with Webcam")); + useMjpgAct->setCheckable(true); + useMjpgAct->setChecked(m_useMjpg); + ret = ret && connect(useMjpgAct, &QAction::toggled, [&](bool checked) { + m_cvWebcam.release(); + if (m_timer->isActive()) m_timer->stop(); + m_useMjpg = checked; + CamCapUseMjpg = checked; + m_timer->start(40); + }); + + return menu; } //----------------------------------------------------------------------------- @@ -1874,15 +1693,11 @@ void PencilTestPopup::onCameraListComboActivated(int comboIndex) { QList cameras = QCameraInfo::availableCameras(); if (cameras.size() != m_cameraListCombo->count() - 1) return; + m_cvWebcam.release(); + if (m_timer->isActive()) m_timer->stop(); + // if selected the non-connected state, then disconnect the current camera if (comboIndex == 0) { - m_videoWidget->videoSurface()->stop(); - if (m_currentCamera) { - if (m_currentCamera->state() == QCamera::ActiveState) - m_currentCamera->stop(); - if (m_currentCamera->state() == QCamera::LoadedState) - m_currentCamera->unload(); - } m_deviceName = QString(); m_videoWidget->setImage(QImage()); // update env @@ -1894,9 +1709,6 @@ void PencilTestPopup::onCameraListComboActivated(int comboIndex) { // in case the camera is not changed (just click the combobox) if (cameras.at(index).deviceName() == m_deviceName) return; - QCamera* oldCamera = m_currentCamera; - if (oldCamera) m_videoWidget->videoSurface()->stop(); - m_currentCamera = new QCamera(cameras.at(index), this); m_deviceName = cameras.at(index).deviceName(); @@ -1907,63 +1719,46 @@ void PencilTestPopup::onCameraListComboActivated(int comboIndex) { m_resolutionCombo->clear(); QList sizes = m_currentCamera->supportedViewfinderResolutions(); - for (int s = 0; s < sizes.size(); s++) { + m_currentCamera->unload(); + for (const QSize size : sizes) { m_resolutionCombo->addItem( - QString("%1 x %2").arg(sizes.at(s).width()).arg(sizes.at(s).height())); + QString("%1 x %2").arg(size.width()).arg(size.height()), size); } if (!sizes.isEmpty()) { // select the largest available resolution m_resolutionCombo->setCurrentIndex(m_resolutionCombo->count() - 1); - QCameraViewfinderSettings settings = m_currentCamera->viewfinderSettings(); - settings.setResolution(sizes.last()); - m_currentCamera->setViewfinderSettings(settings); } - m_currentCamera->setViewfinder(m_videoWidget->videoSurface()); - m_videoWidget->videoSurface()->start( - m_videoWidget->videoSurface()->surfaceFormat()); - // deleting old camera - if (oldCamera) { - if (oldCamera->state() == QCamera::ActiveState) oldCamera->stop(); - delete oldCamera; - } - // start new camera - m_currentCamera->start(); m_videoWidget->setImage(QImage()); - + m_timer->start(40); // update env CamCapCameraName = m_cameraListCombo->itemText(comboIndex).toStdString(); } //----------------------------------------------------------------------------- -void PencilTestPopup::onResolutionComboActivated(const QString& itemText) { - // resolution is written in the itemText with the format " x " - // (e.g. "800 x 600") - QStringList texts = itemText.split(' '); - // the splited text must be "" "x" and "" - if (texts.size() != 3) return; +void PencilTestPopup::onResolutionComboActivated() { + m_cvWebcam.release(); + if (m_timer->isActive()) m_timer->stop(); -#ifndef MACOSX - m_currentCamera->stop(); - m_currentCamera->unload(); -#endif + QSize newResolution = m_resolutionCombo->currentData().toSize(); - QCameraViewfinderSettings settings = m_currentCamera->viewfinderSettings(); - QSize newResolution(texts[0].toInt(), texts[2].toInt()); - settings.setResolution(newResolution); - m_currentCamera->setViewfinderSettings(settings); + if (!newResolution.isValid()) return; + if (newResolution == m_resolution) { + m_timer->start(40); + return; + } + + m_resolution = newResolution; // reset white bg - m_whiteBGImg = QImage(); + m_whiteBGImg = cv::Mat(); m_bgReductionFld->setDisabled(true); -#ifndef MACOSX - m_currentCamera->start(); -#endif m_videoWidget->setImage(QImage()); + m_videoWidget->computeTransform(m_resolution); // update env - CamCapCameraResolution = itemText.toStdString(); + CamCapCameraResolution = m_resolutionCombo->currentText().toStdString(); refreshFrameInfo(); @@ -1982,13 +1777,14 @@ void PencilTestPopup::onResolutionComboActivated(const QString& itemText) { TDimension camres = camera->getRes(); newResolution = QSize(camres.lx, camres.ly).scaled(newResolution, Qt::KeepAspectRatio); - // newResolution.scale(QSize(res.lx, res.ly), Qt::KeepAspectRatio); m_subWidthFld->setValue(newResolution.width()); m_subHeightFld->setValue(newResolution.height()); } else { m_subWidthFld->setValue(m_allowedCameraSize.width()); m_subHeightFld->setValue(m_allowedCameraSize.height()); } + + m_timer->start(40); } //----------------------------------------------------------------------------- @@ -2018,7 +1814,6 @@ void PencilTestPopup::onNextName() { } std::wstring levelName = nameCreator->getNext(); - updateLevelNameAndFrame(levelName); } @@ -2125,33 +1920,158 @@ void PencilTestPopup::onColorTypeComboChanged(int index) { //----------------------------------------------------------------------------- -void PencilTestPopup::onFrameCaptured(QImage& image) { - if (!m_videoWidget || image.isNull()) return; +void PencilTestPopup::onTimeout() { getWebcamImage(); } + +//----------------------------------------------------------------------------- + +int PencilTestPopup::translateIndex(int camIndex) { + // We are using Qt to get the camera info and supported resolutions, but + // we are using OpenCV to actually get the images. + // The camera index from OpenCV and from Qt don't always agree, + // So this checks the name against the correct index. + + // Thanks to: + // https://elcharolin.wordpress.com/2017/08/28/webcam-capture-with-the-media-foundation-sdk/ + // for the webcam enumeration here + + std::wstring desc = m_cameraListCombo->currentText().toStdWString(); + +#define CLEAN_ATTRIBUTES() \ + if (attributes) { \ + attributes->Release(); \ + attributes = NULL; \ + } \ + for (DWORD i = 0; i < count; i++) { \ + if (&devices[i]) { \ + devices[i]->Release(); \ + devices[i] = NULL; \ + } \ + } \ + CoTaskMemFree(devices); \ + return camIndex; + + HRESULT hr = S_OK; + + // this is important!! + hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); + + UINT32 count = 0; + IMFAttributes* attributes = NULL; + IMFActivate** devices = NULL; + + if (FAILED(hr)) { + CLEAN_ATTRIBUTES() + } + // Create an attribute store to specify enumeration parameters. + hr = MFCreateAttributes(&attributes, 1); + + if (FAILED(hr)) { + CLEAN_ATTRIBUTES() + } + + // The attribute to be requested is devices that can capture video + hr = attributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID); + if (FAILED(hr)) { + CLEAN_ATTRIBUTES() + } + // Enummerate the video capture devices + hr = MFEnumDeviceSources(attributes, &devices, &count); + + if (FAILED(hr)) { + CLEAN_ATTRIBUTES() + } + // if there are any available devices + if (count > 0) { + WCHAR* nameString = NULL; + // Get the human-friendly name of the device + UINT32 cchName; + + for (int i = 0; i < count; i++) { + hr = devices[i]->GetAllocatedString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, + &nameString, &cchName); + if (nameString == desc) { + return i; + } + // devices[0]->ShutdownObject(); + } + + CoTaskMemFree(nameString); + } + // clean + CLEAN_ATTRIBUTES() +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::getWebcamImage() { + bool error = false; + cv::Mat imgOriginal; + cv::Mat imgCorrected; + + if (m_cvWebcam.isOpened() == false) { + if (m_cameraListCombo->currentIndex() <= 0) return; + int camIndex = m_cameraListCombo->currentIndex() - 1; + if (!m_useDirectShow) { + // the webcam order obtained from Qt isn't always the same order as + // the one obtained from OpenCV without DirectShow + m_cvWebcam.open(translateIndex(camIndex)); + } else { + m_cvWebcam.open(camIndex, cv::CAP_DSHOW); + } + // mjpg is used by many webcams + // opencv runs very slow on some webcams without it. + if (m_useMjpg) { + m_cvWebcam.set(cv::CAP_PROP_FOURCC, + cv::VideoWriter::fourcc('m', 'j', 'p', 'g')); + m_cvWebcam.set(cv::CAP_PROP_FOURCC, + cv::VideoWriter::fourcc('M', 'J', 'P', 'G')); + } + m_cvWebcam.set(cv::CAP_PROP_FRAME_WIDTH, m_resolution.width()); + m_cvWebcam.set(cv::CAP_PROP_FRAME_HEIGHT, m_resolution.height()); + if (!m_cvWebcam.isOpened()) error = true; + } + + bool blnFrameReadSuccessfully = + m_cvWebcam.read(imgOriginal); // get next frame + + if (!blnFrameReadSuccessfully || + imgOriginal.empty()) { // if frame not read successfully + std::cout << "error: frame not read from webcam\n"; + error = true; // print error message to std out + } + + if (!error) { + cv::cvtColor(imgOriginal, imgCorrected, cv::COLOR_BGR2RGB); + onFrameCaptured(imgCorrected); + } +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onFrameCaptured(cv::Mat& image) { + if (!m_videoWidget) return; // capture the white BG if (m_captureWhiteBGCue) { - m_whiteBGImg = image.copy(); + ; + m_whiteBGImg = image.clone(); m_captureWhiteBGCue = false; m_bgReductionFld->setEnabled(true); } processImage(image); - m_videoWidget->setImage(image.copy()); + + QImage::Format format = (m_colorTypeCombo->currentIndex() == 0) + ? QImage::Format_RGB888 + : QImage::Format_Grayscale8; + QImage qimg(image.data, image.cols, image.rows, format); + m_videoWidget->setImage(qimg.copy()); if (m_captureCue) { -#ifndef MACOSX - m_currentCamera->stop(); -#endif m_captureCue = false; - bool scanBtoT = - m_videoWidget->videoSurface()->surfaceFormat().scanLineDirection() == - QVideoSurfaceFormat::BottomToTop; - bool upsideDown = m_upsideDownCB->isChecked(); - - image = image.mirrored(upsideDown, upsideDown != scanBtoT); - - if (importImage(image)) { - m_videoWidget->setPreviousImage(image.copy()); + if (importImage(qimg)) { + m_videoWidget->setPreviousImage(qimg.copy()); if (Preferences::instance()->isShowFrameNumberWithLettersEnabled()) { int f = m_frameNumberEdit->getValue(); if (f % 10 == 0) // next number @@ -2179,34 +2099,18 @@ void PencilTestPopup::onFrameCaptured(QImage& image) { m_captureButton->setChecked(false); onCaptureButtonClicked(false); } -#ifndef MACOSX - m_currentCamera->start(); -#endif } } //----------------------------------------------------------------------------- void PencilTestPopup::showEvent(QShowEvent* event) { - // m_timerId = startTimer(10); - // if there is another action of which "return" key is assigned as short cut // key, // then release the shortcut key temporary while the popup opens QAction* action = CommandManager::instance()->getActionFromShortcut("Return"); if (action) action->setShortcut(QKeySequence("")); - connect(m_videoWidget->videoSurface(), SIGNAL(frameCaptured(QImage&)), this, - SLOT(onFrameCaptured(QImage&))); - - // reload camera - if (m_currentCamera) { - if (m_currentCamera->state() == QCamera::UnloadedState) - m_currentCamera->load(); - if (m_currentCamera->state() == QCamera::LoadedState) - m_currentCamera->start(); - } - TSceneHandle* sceneHandle = TApp::instance()->getCurrentScene(); connect(sceneHandle, SIGNAL(sceneSwitched()), this, SLOT(onSceneSwitched())); connect(sceneHandle, SIGNAL(castChanged()), this, SLOT(refreshFrameInfo())); @@ -2214,6 +2118,9 @@ void PencilTestPopup::showEvent(QShowEvent* event) { bool tmp_alwaysOverwrite = m_alwaysOverwrite; onSceneSwitched(); m_alwaysOverwrite = tmp_alwaysOverwrite; + + onResolutionComboActivated(); + m_videoWidget->computeTransform(m_resolution); } //----------------------------------------------------------------------------- @@ -2223,22 +2130,15 @@ void PencilTestPopup::hideEvent(QHideEvent* event) { QAction* action = CommandManager::instance()->getActionFromShortcut("Return"); if (action) action->setShortcut(QKeySequence("Return")); - disconnect(m_videoWidget->videoSurface(), SIGNAL(frameCaptured(QImage&)), - this, SLOT(onFrameCaptured(QImage&))); - // stop interval timer if it is active if (m_timerCB->isChecked() && m_captureButton->isChecked()) { m_captureButton->setChecked(false); onCaptureButtonClicked(false); } - // release camera - if (m_currentCamera) { - if (m_currentCamera->state() == QCamera::ActiveState) - m_currentCamera->stop(); - if (m_currentCamera->state() == QCamera::LoadedState) - m_currentCamera->unload(); - } + m_cvWebcam.release(); + if (m_timer->isActive()) m_timer->stop(); + Dialog::hideEvent(event); TSceneHandle* sceneHandle = TApp::instance()->getCurrentScene(); @@ -2281,27 +2181,26 @@ bool PencilTestPopup::event(QEvent* event) { } //----------------------------------------------------------------------------- -void PencilTestPopup::processImage(QImage& image) { - /* "upside down" is not executed here. It will be done when capturing the - * image */ +void PencilTestPopup::processImage(cv::Mat& image) { + // void PencilTestPopup::processImage(QImage& image) { + if (m_upsideDownCB->isChecked()) + cv::flip(image, image, -1); // flip in both directions // white bg reduction - if (!m_whiteBGImg.isNull() && m_bgReductionFld->getValue() != 0) { + if (!m_whiteBGImg.empty() && m_bgReductionFld->getValue() != 0) bgReduction(image, m_whiteBGImg, m_bgReductionFld->getValue()); - } + // obtain histogram AFTER bg reduction m_camCapLevelControl->updateHistogram(image); + // change channel + if (m_colorTypeCombo->currentIndex() != 0) + cv::cvtColor(image, image, cv::COLOR_RGB2GRAY); + // color and grayscale mode - if (m_colorTypeCombo->currentIndex() != 2) { - int black, white; - float gamma; - m_camCapLevelControl->getValues(black, white, gamma); - if (black != 0 || white != 255 || gamma != 1.0) - onChange(image, black, white, gamma, - m_colorTypeCombo->currentIndex() != 0); - } else { - onChangeBW(image, m_camCapLevelControl->getThreshold()); - } + if (m_colorTypeCombo->currentIndex() != 2) + m_camCapLevelControl->adjustLevel(image); + else + m_camCapLevelControl->binarize(image); } //----------------------------------------------------------------------------- diff --git a/toonz/sources/toonz/penciltestpopup.h b/toonz/sources/toonz/penciltestpopup.h index 7c27155..389870e 100644 --- a/toonz/sources/toonz/penciltestpopup.h +++ b/toonz/sources/toonz/penciltestpopup.h @@ -6,16 +6,17 @@ #include "toonzqt/dvdialog.h" #include "toonzqt/lineedit.h" #include "toonz/namebuilder.h" +#include "opencv2/opencv.hpp" #include #include +#include // forward decl. class QCamera; class QCameraImageCapture; class QComboBox; -class QLineEdit; class QSlider; class QCheckBox; class QPushButton; @@ -36,70 +37,6 @@ class IntLineEdit; class CameraCaptureLevelControl; -class ApplyLutTask : public QRunnable { -protected: - int m_fromY, m_toY; - QImage& m_img; - std::vector& m_lut; - -public: - ApplyLutTask(int from, int to, QImage& img, std::vector& lut) - : m_fromY(from), m_toY(to), m_img(img), m_lut(lut) {} - -private: - virtual void run() override; -}; - -class ApplyGrayLutTask : public ApplyLutTask { -public: - ApplyGrayLutTask(int from, int to, QImage& img, std::vector& lut) - : ApplyLutTask(from, to, img, lut) {} - -private: - void run() override; -}; - -//============================================================================= -// MyVideoSurface -//----------------------------------------------------------------------------- - -class QVideoSurfaceFormat; -class MyVideoSurface : public QAbstractVideoSurface { - Q_OBJECT -public: - MyVideoSurface(QWidget* widget, QObject* parent = 0); - - QList supportedPixelFormats( - QAbstractVideoBuffer::HandleType handleType = - QAbstractVideoBuffer::NoHandle) const; - bool isFormatSupported(const QVideoSurfaceFormat& format, - QVideoSurfaceFormat* similar) const; - - bool start(const QVideoSurfaceFormat& format); - void stop(); - - bool present(const QVideoFrame& frame); - - QRect videoRect() const { return m_targetRect; } - QRect sourceRect() const { return m_sourceRect; } - void updateVideoRect(); - - QTransform transform() { return m_S2V_Transform; } - -private: - QWidget* m_widget; - QImage::Format m_imageFormat; - QRect m_targetRect; - QSize m_imageSize; - QRect m_sourceRect; - QVideoFrame m_currentFrame; - - QTransform m_S2V_Transform; // surface to video transform - -signals: - void frameCaptured(QImage& image); -}; - //============================================================================= // MyVideoWidget //----------------------------------------------------------------------------- @@ -118,6 +55,8 @@ class MyVideoWidget : public QWidget { QRect m_preSubCameraRect; QPoint m_dragStartPos; + QRect m_targetRect; + QTransform m_S2V_Transform; // surface to video transform enum SUBHANDLE { HandleNone, HandleFrame, @@ -134,15 +73,11 @@ class MyVideoWidget : public QWidget { public: MyVideoWidget(QWidget* parent = 0); - ~MyVideoWidget(); void setImage(const QImage& image) { m_image = image; update(); } - QAbstractVideoSurface* videoSurface() const { return m_surface; } - - QSize sizeHint() const; void setShowOnionSkin(bool on) { m_showOnionSkin = on; } void setOnionOpacity(int value) { m_onionOpacity = value; } @@ -156,6 +91,8 @@ public: void setSubCameraSize(QSize size); QRect subCameraRect() { return m_subCameraRect; } + void computeTransform(QSize imgSize); + protected: void paintEvent(QPaintEvent* event) override; void resizeEvent(QResizeEvent* event) override; @@ -164,9 +101,6 @@ protected: void mousePressEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; -private: - MyVideoSurface* m_surface; - protected slots: void onUpsideDownChecked(bool on) { m_upsideDown = on; } @@ -281,6 +215,12 @@ protected slots: class PencilTestPopup : public DVGui::Dialog { Q_OBJECT + QTimer* m_timer; + cv::VideoCapture m_cvWebcam; + QSize m_resolution; + + //-------- + QCamera* m_currentCamera; QString m_deviceName; MyVideoWidget* m_videoWidget; @@ -297,7 +237,7 @@ class PencilTestPopup : public DVGui::Dialog { QTimer *m_captureTimer, *m_countdownTimer; - QImage m_whiteBGImg; + cv::Mat m_whiteBGImg; // used only for Windows QPushButton* m_captureFilterSettingsBtn; @@ -317,13 +257,23 @@ class PencilTestPopup : public DVGui::Dialog { bool m_captureWhiteBGCue; bool m_captureCue; bool m_alwaysOverwrite = false; + bool m_useMjpg; +#ifdef _WIN32 + bool m_useDirectShow; +#endif - void processImage(QImage& procImage); + void processImage(cv::Mat& procImage); bool importImage(QImage image); void setToNextNewLevel(); void updateLevelNameAndFrame(std::wstring levelName); + void getWebcamImage(); + + QMenu* createOptionsMenu(); + + int translateIndex(int camIndex); + public: PencilTestPopup(); ~PencilTestPopup(); @@ -338,13 +288,13 @@ protected: protected slots: void refreshCameraList(); void onCameraListComboActivated(int index); - void onResolutionComboActivated(const QString&); + void onResolutionComboActivated(); void onFileFormatOptionButtonPressed(); void onLevelNameEdited(); void onNextName(); void onPreviousName(); void onColorTypeComboChanged(int index); - void onFrameCaptured(QImage& image); + void onFrameCaptured(cv::Mat& image); void onCaptureWhiteBGButtonPressed(); void onOnionCBToggled(bool); void onLoadImageButtonPressed(); @@ -365,6 +315,7 @@ protected slots: void onSubCameraResized(bool isDragging); void onSubCameraSizeEdited(); + void onTimeout(); public slots: void openSaveInFolderPopup(); }; diff --git a/toonz/sources/toonz/penciltestpopup_qt.cpp b/toonz/sources/toonz/penciltestpopup_qt.cpp new file mode 100644 index 0000000..1d3b3fc --- /dev/null +++ b/toonz/sources/toonz/penciltestpopup_qt.cpp @@ -0,0 +1,3025 @@ +#include "penciltestpopup_qt.h" + +// Tnz6 includes +#include "tapp.h" +#include "menubarcommandids.h" +#include "formatsettingspopups.h" +#include "filebrowsermodel.h" +#include "cellselection.h" +#include "toonzqt/tselectionhandle.h" +#include "cameracapturelevelcontrol_qt.h" +#include "iocommand.h" + +// TnzQt includes +#include "toonzqt/menubarcommand.h" +#include "toonzqt/filefield.h" +#include "toonzqt/intfield.h" +#include "toonzqt/gutil.h" + +// Tnzlib includes +#include "toonz/tproject.h" +#include "toonz/tscenehandle.h" +#include "toonz/toonzscene.h" +#include "toutputproperties.h" +#include "toonz/sceneproperties.h" +#include "toonz/levelset.h" +#include "toonz/txshleveltypes.h" +#include "toonz/toonzfolders.h" +#include "toonz/tframehandle.h" +#include "toonz/tcolumnhandle.h" +#include "toonz/txsheethandle.h" +#include "toonz/txshsimplelevel.h" +#include "toonz/levelproperties.h" +#include "toonz/tcamera.h" +#include "toonz/preferences.h" + +// TnzCore includes +#include "tsystem.h" +#include "tpixelutils.h" +#include "tenv.h" +#include "tlevel_io.h" + +#include + +// Qt includes +#include +#include +#include +#include +#include +#ifdef MACOSX +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifdef _WIN32 +#include +#endif + +using namespace DVGui; + +// Connected camera +TEnv::StringVar CamCapCameraName("CamCapCameraName", ""); +// Camera resolution +TEnv::StringVar CamCapCameraResolution("CamCapCameraResolution", ""); +// Whether to open save-in popup on launch +TEnv::IntVar CamCapOpenSaveInPopupOnLaunch("CamCapOpenSaveInPopupOnLaunch", 0); +// SaveInFolderPopup settings +TEnv::StringVar CamCapSaveInParentFolder("CamCapSaveInParentFolder", ""); +TEnv::IntVar CamCapSaveInPopupSubFolder("CamCapSaveInPopupSubFolder", 0); +TEnv::StringVar CamCapSaveInPopupProject("CamCapSaveInPopupProject", ""); +TEnv::StringVar CamCapSaveInPopupEpisode("CamCapSaveInPopupEpisode", "1"); +TEnv::StringVar CamCapSaveInPopupSequence("CamCapSaveInPopupSequence", "1"); +TEnv::StringVar CamCapSaveInPopupScene("CamCapSaveInPopupScene", "1"); +TEnv::IntVar CamCapSaveInPopupAutoSubName("CamCapSaveInPopupAutoSubName", 1); +TEnv::IntVar CamCapSaveInPopupCreateSceneInFolder( + "CamCapSaveInPopupCreateSceneInFolder", 0); + +namespace { + +void convertImageToRaster(TRaster32P dstRas, const QImage& srcImg) { + dstRas->lock(); + int lx = dstRas->getLx(); + int ly = dstRas->getLy(); + assert(lx == srcImg.width() && ly == srcImg.height()); + for (int j = 0; j < ly; j++) { + TPixel32* dstPix = dstRas->pixels(j); + for (int i = 0; i < lx; i++, dstPix++) { + QRgb srcPix = srcImg.pixel(lx - 1 - i, j); + dstPix->r = qRed(srcPix); + dstPix->g = qGreen(srcPix); + dstPix->b = qBlue(srcPix); + dstPix->m = TPixel32::maxChannelValue; + } + } + dstRas->unlock(); +} + +void bgReduction(QImage& srcImg, QImage& bgImg, int reduction) { + float reductionRatio = (float)reduction / 100.0f; + // first, make the reduction table + std::vector reductionAmount(256); + for (int i = 0; i < reductionAmount.size(); i++) { + reductionAmount[i] = (int)(std::floor((float)(255 - i) * reductionRatio)); + } + // then, compute for all pixels + int lx = srcImg.width(); + int ly = srcImg.height(); + for (int j = 0; j < ly; j++) { + // TPixel32 * pix = ras->pixels(j); + QRgb* pix = (QRgb*)srcImg.scanLine(j); + QRgb* bgPix = (QRgb*)bgImg.scanLine(j); + for (int i = 0; i < lx; i++, pix++, bgPix++) { + *pix = qRgb(std::min(255, qRed(*pix) + reductionAmount[qRed(*bgPix)]), + std::min(255, qGreen(*pix) + reductionAmount[qGreen(*bgPix)]), + std::min(255, qBlue(*pix) + reductionAmount[qBlue(*bgPix)])); + } + } +} + +void my_compute_lut(int black, int white, float gamma, std::vector& lut) { + const int maxChannelValue = lut.size() - 1; + const float half_maxChannelValueF = 0.5f * maxChannelValue; + const float maxChannelValueF = maxChannelValue; + + float value; + + int lutSize = lut.size(); + for (int i = 0; i < lutSize; i++) { + if (i <= black) + value = 0.0f; + else if (i >= white) + value = 1.0f; + else { + value = (float)(i - black) / (float)(white - black); + value = std::pow(value, 1.0f / gamma); + } + + lut[i] = (int)std::floor(value * maxChannelValueF); + } +} + +//----------------------------------------------------------------------------- + +inline void doPixGray(QRgb* pix, const std::vector& lut) { + int gray = lut[qGray(*pix)]; + *pix = qRgb(gray, gray, gray); +} + +//----------------------------------------------------------------------------- + +inline void doPixBinary(QRgb* pix, int threshold) { + int gray = qGray(*pix); + if (gray >= threshold) + gray = 255; + else + gray = 0; + *pix = qRgb(gray, gray, gray); +} + +//----------------------------------------------------------------------------- + +inline void doPix(QRgb* pix, const std::vector& lut) { + // The captured image MUST be full opaque! + *pix = qRgb(lut[qRed(*pix)], lut[qGreen(*pix)], lut[qBlue(*pix)]); +} + +//----------------------------------------------------------------------------- + +void onChange(QImage& img, int black, int white, float gamma, bool doGray) { + std::vector lut(TPixel32::maxChannelValue + 1); + my_compute_lut(black, white, gamma, lut); + + int ly = img.height(); + // compute in multi thread + int threadCount = + std::max(1, QThreadPool::globalInstance()->maxThreadCount() / 2); + int tmpStart = 0; + for (int t = 0; t < threadCount; t++) { + int tmpEnd = (int)std::round((float)(ly * (t + 1)) / (float)threadCount); + + QRunnable* task; + if (doGray) + task = new ApplyGrayLutTask(tmpStart, tmpEnd, img, lut); + else + task = new ApplyLutTask(tmpStart, tmpEnd, img, lut); + + QThreadPool::globalInstance()->start(task); + + tmpStart = tmpEnd; + } + + QThreadPool::globalInstance()->waitForDone(); +} + +//----------------------------------------------------------------------------- + +void onChangeBW(QImage& img, int threshold) { + int lx = img.width(), y, ly = img.height(); + for (y = 0; y < ly; ++y) { + QRgb *pix = (QRgb*)img.scanLine(y), *endPix = (QRgb*)(pix + lx); + while (pix < endPix) { + doPixBinary(pix, threshold); + ++pix; + } + } +} + +//----------------------------------------------------------------------------- + +TPointD getCurrentCameraDpi() { + TCamera* camera = + TApp::instance()->getCurrentScene()->getScene()->getCurrentCamera(); + TDimensionD size = camera->getSize(); + TDimension res = camera->getRes(); + return TPointD(res.lx / size.lx, res.ly / size.ly); +} + +//----------------------------------------------------------------------------- + +QChar numToLetter(int letterNum) { + switch (letterNum) { + case 0: + return QChar(); + break; + case 1: + return 'A'; + break; + case 2: + return 'B'; + break; + case 3: + return 'C'; + break; + case 4: + return 'D'; + break; + case 5: + return 'E'; + break; + case 6: + return 'F'; + break; + case 7: + return 'G'; + break; + case 8: + return 'H'; + break; + case 9: + return 'I'; + break; + default: + return QChar(); + break; + } +} + +int letterToNum(QChar appendix) { + if (appendix == QChar('A') || appendix == QChar('a')) + return 1; + else if (appendix == QChar('B') || appendix == QChar('b')) + return 2; + else if (appendix == QChar('C') || appendix == QChar('c')) + return 3; + else if (appendix == QChar('D') || appendix == QChar('d')) + return 4; + else if (appendix == QChar('E') || appendix == QChar('e')) + return 5; + else if (appendix == QChar('F') || appendix == QChar('f')) + return 6; + else if (appendix == QChar('G') || appendix == QChar('g')) + return 7; + else if (appendix == QChar('H') || appendix == QChar('h')) + return 8; + else if (appendix == QChar('I') || appendix == QChar('i')) + return 9; + else + return 0; +} + +#ifdef _WIN32 +void openCaptureFilterSettings(const QWidget* parent, + const QString& cameraName) { + HRESULT hr; + + ICreateDevEnum* createDevEnum = NULL; + IEnumMoniker* enumMoniker = NULL; + IMoniker* moniker = NULL; + + IBaseFilter* deviceFilter; + + ISpecifyPropertyPages* specifyPropertyPages; + CAUUID cauuid; + // set parent's window handle in order to make the dialog modal + HWND ghwndApp = (HWND)(parent->winId()); + + // initialize COM + CoInitialize(NULL); + + // get device list + CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, + IID_ICreateDevEnum, (PVOID*)&createDevEnum); + + // create EnumMoniker + createDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, + &enumMoniker, 0); + if (enumMoniker == NULL) { + // if no connected devices found + return; + } + + // reset EnumMoniker + enumMoniker->Reset(); + + // find target camera + ULONG fetched = 0; + bool isCameraFound = false; + while (hr = enumMoniker->Next(1, &moniker, &fetched), hr == S_OK) { + // get friendly name (= device name) of the camera + IPropertyBag* pPropertyBag; + moniker->BindToStorage(0, 0, IID_IPropertyBag, (void**)&pPropertyBag); + VARIANT var; + var.vt = VT_BSTR; + VariantInit(&var); + + pPropertyBag->Read(L"FriendlyName", &var, 0); + + QString deviceName = QString::fromWCharArray(var.bstrVal); + + VariantClear(&var); + + if (deviceName == cameraName) { + // bind monkier to the filter + moniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&deviceFilter); + + // release moniker etc. + moniker->Release(); + enumMoniker->Release(); + createDevEnum->Release(); + + isCameraFound = true; + break; + } + } + + // if no matching camera found + if (!isCameraFound) return; + + // open capture filter popup + hr = deviceFilter->QueryInterface(IID_ISpecifyPropertyPages, + (void**)&specifyPropertyPages); + if (hr == S_OK) { + hr = specifyPropertyPages->GetPages(&cauuid); + + hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1, + (IUnknown**)&deviceFilter, cauuid.cElems, + (GUID*)cauuid.pElems, 0, 0, NULL); + + CoTaskMemFree(cauuid.pElems); + specifyPropertyPages->Release(); + } +} +#endif + +QString convertToFrameWithLetter(int value, int length = -1) { + QString str; + str.setNum((int)(value / 10)); + while (str.length() < length) str.push_front("0"); + QChar letter = numToLetter(value % 10); + if (!letter.isNull()) str.append(letter); + return str; +} + +QString fidsToString(const std::vector& fids, + bool letterOptionEnabled) { + if (fids.empty()) return PencilTestPopup::tr("No", "frame id"); + QString retStr(""); + if (letterOptionEnabled) { + bool beginBlock = true; + for (int f = 0; f < fids.size() - 1; f++) { + int num = fids[f].getNumber(); + int next_num = fids[f + 1].getNumber(); + + if (num % 10 == 0 && num + 10 == next_num) { + if (beginBlock) { + retStr += convertToFrameWithLetter(num) + " - "; + beginBlock = false; + } + } else { + retStr += convertToFrameWithLetter(num) + ", "; + beginBlock = true; + } + } + retStr += convertToFrameWithLetter(fids.back().getNumber()); + } else { + bool beginBlock = true; + for (int f = 0; f < fids.size() - 1; f++) { + int num = fids[f].getNumber(); + int next_num = fids[f + 1].getNumber(); + if (num + 1 == next_num) { + if (beginBlock) { + retStr += QString::number(num) + " - "; + beginBlock = false; + } + } else { + retStr += QString::number(num) + ", "; + beginBlock = true; + } + } + retStr += QString::number(fids.back().getNumber()); + } + return retStr; +} + +bool findCell(TXsheet* xsh, int col, const TXshCell& targetCell, + int& bottomRowWithTheSameLevel) { + bottomRowWithTheSameLevel = -1; + TXshColumnP column = const_cast(xsh)->getColumn(col); + if (!column) return false; + + TXshCellColumn* cellColumn = column->getCellColumn(); + if (!cellColumn) return false; + + int r0, r1; + if (!cellColumn->getRange(r0, r1)) return false; + + for (int r = r0; r <= r1; r++) { + TXshCell cell = cellColumn->getCell(r); + if (cell == targetCell) return true; + if (cell.m_level == targetCell.m_level) bottomRowWithTheSameLevel = r; + } + + return false; +} + +bool getRasterLevelSize(TXshLevel* level, TDimension& dim) { + std::vector fids; + level->getFids(fids); + if (fids.empty()) return false; + TXshSimpleLevel* simpleLevel = level->getSimpleLevel(); + if (!simpleLevel) return false; + TRasterImageP rimg = (TRasterImageP)simpleLevel->getFrame(fids[0], false); + if (!rimg || rimg->isEmpty()) return false; + + dim = rimg->getRaster()->getSize(); + return true; +} + +} // namespace + +//============================================================================= + +void ApplyLutTask::run() { + int lx = m_img.width(); + for (int y = m_fromY; y < m_toY; ++y) { + QRgb *pix = (QRgb*)m_img.scanLine(y), *endPix = (QRgb*)(pix + lx); + while (pix < endPix) { + doPix(pix, m_lut); + ++pix; + } + } +} + +void ApplyGrayLutTask::run() { + int lx = m_img.width(); + for (int y = m_fromY; y < m_toY; ++y) { + QRgb *pix = (QRgb*)m_img.scanLine(y), *endPix = (QRgb*)(pix + lx); + while (pix < endPix) { + doPixGray(pix, m_lut); + ++pix; + } + } +} + +//============================================================================= + +MyVideoSurface::MyVideoSurface(QWidget* widget, QObject* parent) + : QAbstractVideoSurface(parent) + , m_widget(widget) + , m_imageFormat(QImage::Format_Invalid) {} + +QList MyVideoSurface::supportedPixelFormats( + QAbstractVideoBuffer::HandleType handleType) const { + if (handleType == QAbstractVideoBuffer::NoHandle) { + return QList() + << QVideoFrame::Format_RGB32 << QVideoFrame::Format_ARGB32 + << QVideoFrame::Format_ARGB32_Premultiplied + << QVideoFrame::Format_RGB565 << QVideoFrame::Format_RGB555; + } else { + return QList(); + } +} + +bool MyVideoSurface::isFormatSupported(const QVideoSurfaceFormat& format, + QVideoSurfaceFormat* similar) const { + Q_UNUSED(similar); + + const QImage::Format imageFormat = + QVideoFrame::imageFormatFromPixelFormat(format.pixelFormat()); + const QSize size = format.frameSize(); + + return imageFormat != QImage::Format_Invalid && !size.isEmpty() && + format.handleType() == QAbstractVideoBuffer::NoHandle; +} + +bool MyVideoSurface::start(const QVideoSurfaceFormat& format) { + const QImage::Format imageFormat = + QVideoFrame::imageFormatFromPixelFormat(format.pixelFormat()); + const QSize size = format.frameSize(); + + if (imageFormat != QImage::Format_Invalid && !size.isEmpty()) { + m_imageFormat = imageFormat; + m_imageSize = size; + m_sourceRect = format.viewport(); + + QAbstractVideoSurface::start(format); + + m_widget->updateGeometry(); + updateVideoRect(); + + return true; + } else { + return false; + } +} + +void MyVideoSurface::updateVideoRect() { + QSize size = surfaceFormat().sizeHint(); + size.scale(m_widget->size(), Qt::KeepAspectRatio); + + m_targetRect = QRect(QPoint(0, 0), size); + m_targetRect.moveCenter(m_widget->rect().center()); + + double scale = + (double)m_targetRect.width() / (double)surfaceFormat().sizeHint().width(); + m_S2V_Transform = + QTransform::fromTranslate(m_targetRect.left(), m_targetRect.top()) + .scale(scale, scale); +} + +bool MyVideoSurface::present(const QVideoFrame& frame) { + if (surfaceFormat().pixelFormat() != frame.pixelFormat() || + surfaceFormat().frameSize() != frame.size()) { + setError(IncorrectFormatError); + stop(); + return false; + } else { + m_currentFrame = frame; + + if (m_currentFrame.map(QAbstractVideoBuffer::ReadOnly)) { + QImage image = QImage(m_currentFrame.bits(), m_currentFrame.width(), + m_currentFrame.height(), + m_currentFrame.bytesPerLine(), m_imageFormat); + m_currentFrame.unmap(); + if (!image.isNull()) emit frameCaptured(image); + } + + return true; + } +} + +void MyVideoSurface::stop() { + m_currentFrame = QVideoFrame(); + m_targetRect = QRect(); + + QAbstractVideoSurface::stop(); + + m_widget->update(); +} + +//============================================================================= + +MyVideoWidget::MyVideoWidget(QWidget* parent) + : QWidget(parent) + , m_previousImage(QImage()) + , m_surface(0) + , m_showOnionSkin(false) + , m_onionOpacity(128) + , m_upsideDown(false) + , m_countDownTime(0) + , m_subCameraRect(QRect()) { + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + + m_surface = new MyVideoSurface(this); + setMouseTracking(true); +} + +MyVideoWidget::~MyVideoWidget() { delete m_surface; } + +QSize MyVideoWidget::sizeHint() const { + return m_surface->surfaceFormat().sizeHint(); +} + +void MyVideoWidget::paintEvent(QPaintEvent* event) { + QPainter p(this); + + p.fillRect(rect(), Qt::black); + + if (m_surface->isActive()) { + const QRect videoRect = m_surface->videoRect(); + const QTransform oldTransform = p.transform(); + + if (m_upsideDown) { + p.translate(videoRect.center()); + p.rotate(180); + p.translate(-videoRect.center()); + } + if (m_surface->surfaceFormat().scanLineDirection() == + QVideoSurfaceFormat::BottomToTop) { + p.scale(1, -1); + p.translate(0, -height()); + } + + p.drawImage(videoRect, m_image, m_surface->sourceRect()); + + p.setTransform(oldTransform); + + if (m_showOnionSkin && m_onionOpacity > 0.0f && !m_previousImage.isNull() && + m_previousImage.size() == m_image.size()) { + p.setOpacity((qreal)m_onionOpacity / 255.0); + p.drawImage(videoRect, m_previousImage, m_surface->sourceRect()); + p.setOpacity(1.0); + } + + // draw subcamera + if (m_subCameraRect.isValid()) drawSubCamera(p); + + // draw countdown text + if (m_countDownTime > 0) { + QString str = + QTime::fromMSecsSinceStartOfDay(m_countDownTime).toString("s.zzz"); + p.setPen(Qt::yellow); + QFont font = p.font(); + font.setPixelSize(50); + p.setFont(font); + p.drawText(rect(), Qt::AlignRight | Qt::AlignBottom, str); + } + } else { + p.setPen(Qt::white); + QFont font = p.font(); + font.setPixelSize(30); + p.setFont(font); + p.drawText(rect(), Qt::AlignCenter, tr("Camera is not available")); + } +} + +void MyVideoWidget::resizeEvent(QResizeEvent* event) { + QWidget::resizeEvent(event); + + m_surface->updateVideoRect(); +} + +void MyVideoWidget::setSubCameraSize(QSize size) { + QSize frameSize = m_surface->surfaceFormat().frameSize(); + assert(frameSize == size.expandedTo(frameSize)); + + m_subCameraRect.setSize(size); + // make sure the sub camera is inside of the frame + if (!QRect(QPoint(0, 0), frameSize).contains(m_subCameraRect)) + m_subCameraRect.moveCenter(QRect(QPoint(0, 0), frameSize).center()); + + update(); +} + +void MyVideoWidget::drawSubCamera(QPainter& p) { + auto drawSubFrameLine = [&](SUBHANDLE handle, QPoint from, QPoint to) { + p.setPen(QPen(handle == m_activeSubHandle ? Qt::green : Qt::magenta, 2)); + p.drawLine(from, to); + }; + + auto drawHandle = [&](SUBHANDLE handle, QPoint pos) { + p.setPen(handle == m_activeSubHandle ? Qt::green : Qt::magenta); + QRect handleRect(0, 0, 11, 11); + handleRect.moveCenter(pos); + p.drawRect(handleRect); + }; + + QRect vidSubRect = m_surface->transform().mapRect(m_subCameraRect); + p.setBrush(Qt::NoBrush); + drawSubFrameLine(HandleLeft, vidSubRect.topLeft(), vidSubRect.bottomLeft()); + drawSubFrameLine(HandleTop, vidSubRect.topLeft(), vidSubRect.topRight()); + drawSubFrameLine(HandleRight, vidSubRect.topRight(), + vidSubRect.bottomRight()); + drawSubFrameLine(HandleBottom, vidSubRect.bottomLeft(), + vidSubRect.bottomRight()); + + // draw handles + drawHandle(HandleTopLeft, vidSubRect.topLeft()); + drawHandle(HandleTopRight, vidSubRect.topRight()); + drawHandle(HandleBottomLeft, vidSubRect.bottomLeft()); + drawHandle(HandleBottomRight, vidSubRect.bottomRight()); +} + +void MyVideoWidget::mouseMoveEvent(QMouseEvent* event) { + int d = 10; + + auto isNearBy = [&](QPoint handlePos) -> bool { + return (handlePos - event->pos()).manhattanLength() <= d * 2; + }; + + auto isNearEdge = [&](int handlePos, int mousePos) -> bool { + return std::abs(handlePos - mousePos) <= d; + }; + + // if the sub camera is not active, do nothing and return + if (!m_surface->isActive() || m_subCameraRect.isNull()) return; + + // with no mouse button, update the active handles + if (event->buttons() == Qt::NoButton) { + QRect vidSubRect = m_surface->transform().mapRect(m_subCameraRect); + SUBHANDLE preHandle = m_activeSubHandle; + if (!vidSubRect.adjusted(-d, -d, d, d).contains(event->pos())) + m_activeSubHandle = HandleNone; + else if (vidSubRect.adjusted(d, d, -d, -d).contains(event->pos())) + m_activeSubHandle = HandleFrame; + else if (isNearBy(vidSubRect.topLeft())) + m_activeSubHandle = HandleTopLeft; + else if (isNearBy(vidSubRect.topRight())) + m_activeSubHandle = HandleTopRight; + else if (isNearBy(vidSubRect.bottomLeft())) + m_activeSubHandle = HandleBottomLeft; + else if (isNearBy(vidSubRect.bottomRight())) + m_activeSubHandle = HandleBottomRight; + else if (isNearEdge(vidSubRect.left(), event->pos().x())) + m_activeSubHandle = HandleLeft; + else if (isNearEdge(vidSubRect.top(), event->pos().y())) + m_activeSubHandle = HandleTop; + else if (isNearEdge(vidSubRect.right(), event->pos().x())) + m_activeSubHandle = HandleRight; + else if (isNearEdge(vidSubRect.bottom(), event->pos().y())) + m_activeSubHandle = HandleBottom; + else + m_activeSubHandle = HandleNone; + if (preHandle != m_activeSubHandle) { + Qt::CursorShape cursor; + if (m_activeSubHandle == HandleNone) + cursor = Qt::ArrowCursor; + else if (m_activeSubHandle == HandleFrame) + cursor = Qt::SizeAllCursor; + else if (m_activeSubHandle == HandleTopLeft || + m_activeSubHandle == HandleBottomRight) + cursor = Qt::SizeFDiagCursor; + else if (m_activeSubHandle == HandleTopRight || + m_activeSubHandle == HandleBottomLeft) + cursor = Qt::SizeBDiagCursor; + else if (m_activeSubHandle == HandleLeft || + m_activeSubHandle == HandleRight) + cursor = Qt::SplitHCursor; + else // if (m_activeSubHandle == HandleTop || m_activeSubHandle == + // HandleBottom) + cursor = Qt::SplitVCursor; + + setCursor(cursor); + update(); + } + } + // if left button is pressed and some handle is active, transform the + // subcamera + else if (event->buttons() & Qt::LeftButton && + m_activeSubHandle != HandleNone && m_preSubCameraRect.isValid()) { + auto clampVal = [&](int& val, int min, int max) { + if (val < min) + val = min; + else if (val > max) + val = max; + }; + auto clampPoint = [&](QPoint& pos, int xmin, int xmax, int ymin, int ymax) { + clampVal(pos.rx(), xmin, xmax); + clampVal(pos.ry(), ymin, ymax); + }; + + int minimumSize = 100; + + QPoint offset = + m_surface->transform().inverted().map(event->pos()) - m_dragStartPos; + if (m_activeSubHandle >= HandleTopLeft && + m_activeSubHandle <= HandleBottomRight) { + QSize offsetSize = m_preSubCameraRect.size(); + if (m_activeSubHandle == HandleBottomLeft || + m_activeSubHandle == HandleTopRight) + offset.rx() *= -1; + offsetSize.scale(offset.x(), offset.y(), Qt::KeepAspectRatioByExpanding); + offset = QPoint(offsetSize.width(), offsetSize.height()); + if (m_activeSubHandle == HandleBottomLeft || + m_activeSubHandle == HandleTopRight) + offset.rx() *= -1; + } + QSize camSize = m_surface->surfaceFormat().sizeHint(); + + if (m_activeSubHandle == HandleFrame) { + clampPoint(offset, -m_preSubCameraRect.left(), + camSize.width() - m_preSubCameraRect.right(), + -m_preSubCameraRect.top(), + camSize.height() - m_preSubCameraRect.bottom()); + m_subCameraRect = m_preSubCameraRect.translated(offset); + } else { + if (m_activeSubHandle == HandleTopLeft || + m_activeSubHandle == HandleBottomLeft || + m_activeSubHandle == HandleLeft) { + clampVal(offset.rx(), -m_preSubCameraRect.left(), + m_preSubCameraRect.width() - minimumSize); + m_subCameraRect.setLeft(m_preSubCameraRect.left() + offset.x()); + } else if (m_activeSubHandle == HandleTopRight || + m_activeSubHandle == HandleBottomRight || + m_activeSubHandle == HandleRight) { + clampVal(offset.rx(), -m_preSubCameraRect.width() + minimumSize, + camSize.width() - m_preSubCameraRect.right()); + m_subCameraRect.setRight(m_preSubCameraRect.right() + offset.x()); + } + + if (m_activeSubHandle == HandleTopLeft || + m_activeSubHandle == HandleTopRight || + m_activeSubHandle == HandleTop) { + clampVal(offset.ry(), -m_preSubCameraRect.top(), + m_preSubCameraRect.height() - minimumSize); + m_subCameraRect.setTop(m_preSubCameraRect.top() + offset.y()); + } else if (m_activeSubHandle == HandleBottomRight || + m_activeSubHandle == HandleBottomLeft || + m_activeSubHandle == HandleBottom) { + clampVal(offset.ry(), -m_preSubCameraRect.height() + minimumSize, + camSize.height() - m_preSubCameraRect.bottom()); + m_subCameraRect.setBottom(m_preSubCameraRect.bottom() + offset.y()); + } + // if the sub camera size is changed, notify the parent for updating the + // fields + emit subCameraResized(true); + } + update(); + } +} + +void MyVideoWidget::mousePressEvent(QMouseEvent* event) { + // if the sub camera is not active, do nothing and return + // use left button only and some handle must be active + if (!m_surface->isActive() || m_subCameraRect.isNull() || + event->button() != Qt::LeftButton || m_activeSubHandle == HandleNone) + return; + + // record the original sub camera size + m_preSubCameraRect = m_subCameraRect; + m_dragStartPos = m_surface->transform().inverted().map(event->pos()); + + // temporary stop the camera + emit stopCamera(); +} + +void MyVideoWidget::mouseReleaseEvent(QMouseEvent* event) { + // if the sub camera is not active, do nothing and return + // use left button only and some handle must be active + if (!m_surface->isActive() || m_subCameraRect.isNull() || + event->button() != Qt::LeftButton || m_activeSubHandle == HandleNone) + return; + + m_preSubCameraRect = QRect(); + if (m_activeSubHandle != HandleFrame) emit subCameraResized(false); + + // restart the camera + emit startCamera(); +} + +//============================================================================= + +FrameNumberLineEdit::FrameNumberLineEdit(QWidget* parent, int value) + : LineEdit(parent) { + setFixedWidth(54); + m_intValidator = new QIntValidator(this); + setValue(value); + m_intValidator->setRange(1, 9999); + + QRegExp rx("^[0-9]{1,4}[A-Ia-i]?$"); + m_regexpValidator = new QRegExpValidator(rx, this); + + updateValidator(); +} + +//----------------------------------------------------------------------------- + +void FrameNumberLineEdit::updateValidator() { + if (Preferences::instance()->isShowFrameNumberWithLettersEnabled()) + setValidator(m_regexpValidator); + else + setValidator(m_intValidator); +} + +//----------------------------------------------------------------------------- + +void FrameNumberLineEdit::setValue(int value) { + if (value <= 0) + value = 1; + else if (value > 9999) + value = 9999; + + QString str; + if (Preferences::instance()->isShowFrameNumberWithLettersEnabled()) { + str = convertToFrameWithLetter(value, 3); + } else { + str.setNum(value); + while (str.length() < 4) str.push_front("0"); + } + setText(str); + setCursorPosition(0); +} + +//----------------------------------------------------------------------------- + +int FrameNumberLineEdit::getValue() { + if (Preferences::instance()->isShowFrameNumberWithLettersEnabled()) { + QString str = text(); + // if no letters added + if (str.at(str.size() - 1).isDigit()) + return str.toInt() * 10; + else { + return str.left(str.size() - 1).toInt() * 10 + + letterToNum(str.at(str.size() - 1)); + } + } else + return text().toInt(); +} + +//----------------------------------------------------------------------------- + +void FrameNumberLineEdit::focusInEvent(QFocusEvent* e) { + m_textOnFocusIn = text(); +} + +void FrameNumberLineEdit::focusOutEvent(QFocusEvent* e) { + // if the field is empty, then revert the last input + if (text().isEmpty()) setText(m_textOnFocusIn); + + LineEdit::focusOutEvent(e); +} + +//============================================================================= + +LevelNameLineEdit::LevelNameLineEdit(QWidget* parent) + : QLineEdit(parent), m_textOnFocusIn("") { + // Exclude all character which cannot fit in a filepath (Win). + // Dots are also prohibited since they are internally managed by Toonz. + QRegExp rx("[^\\\\/:?*.\"<>|]+"); + setValidator(new QRegExpValidator(rx, this)); + setObjectName("LargeSizedText"); + + connect(this, SIGNAL(editingFinished()), this, SLOT(onEditingFinished())); +} + +void LevelNameLineEdit::focusInEvent(QFocusEvent* e) { + m_textOnFocusIn = text(); +} + +void LevelNameLineEdit::onEditingFinished() { + // if the content is not changed, do nothing. + if (text() == m_textOnFocusIn) return; + + emit levelNameEdited(); +} + +//============================================================================= + +std::wstring FlexibleNameCreator::getPrevious() { + if (m_s.empty() || (m_s[0] == 0 && m_s.size() == 1)) { + m_s.push_back('Z' - 'A'); + m_s.push_back('Z' - 'A'); + return L"ZZ"; + } + int i = 0; + int n = m_s.size(); + while (i < n) { + m_s[i]--; + if (m_s[i] >= 0) break; + m_s[i] = 'Z' - 'A'; + i++; + } + if (i >= n) { + n--; + m_s.pop_back(); + } + std::wstring s; + for (i = n - 1; i >= 0; i--) s.append(1, (wchar_t)(L'A' + m_s[i])); + return s; +} + +//------------------------------------------------------------------- + +bool FlexibleNameCreator::setCurrent(std::wstring name) { + if (name.empty() || name.size() > 2) return false; + std::vector newNameBuf; + for (std::wstring::iterator it = name.begin(); it != name.end(); ++it) { + int s = (int)((*it) - L'A'); + if (s < 0 || s > 'Z' - 'A') return false; + newNameBuf.push_back(s); + } + m_s.clear(); + for (int i = newNameBuf.size() - 1; i >= 0; i--) m_s.push_back(newNameBuf[i]); + return true; +} + +//============================================================================= + +PencilTestSaveInFolderPopup::PencilTestSaveInFolderPopup(QWidget* parent) + : Dialog(parent, true, false, "PencilTestSaveInFolder") { + setWindowTitle(tr("Create the Destination Subfolder to Save")); + + m_parentFolderField = new FileField(this); + + QPushButton* setAsDefaultBtn = new QPushButton(tr("Set As Default"), this); + setAsDefaultBtn->setToolTip( + tr("Set the current \"Save In\" path as the default.")); + + m_subFolderCB = new QCheckBox(tr("Create Subfolder"), this); + + QFrame* subFolderFrame = new QFrame(this); + + QGroupBox* infoGroupBox = new QGroupBox(tr("Infomation"), this); + QGroupBox* subNameGroupBox = new QGroupBox(tr("Subfolder Name"), this); + + m_projectField = new QLineEdit(this); + m_episodeField = new QLineEdit(this); + m_sequenceField = new QLineEdit(this); + m_sceneField = new QLineEdit(this); + + m_autoSubNameCB = new QCheckBox(tr("Auto Format:"), this); + m_subNameFormatCombo = new QComboBox(this); + m_subFolderNameField = new QLineEdit(this); + + QCheckBox* showPopupOnLaunchCB = + new QCheckBox(tr("Show This on Launch of the Camera Capture"), this); + m_createSceneInFolderCB = new QCheckBox(tr("Save Scene in Subfolder"), this); + + QPushButton* okBtn = new QPushButton(tr("OK"), this); + QPushButton* cancelBtn = new QPushButton(tr("Cancel"), this); + + //---- properties + + m_subFolderCB->setChecked(CamCapSaveInPopupSubFolder != 0); + subFolderFrame->setEnabled(CamCapSaveInPopupSubFolder != 0); + + // project name + QString prjName = QString::fromStdString(CamCapSaveInPopupProject.getValue()); + if (prjName.isEmpty()) { + prjName = TProjectManager::instance() + ->getCurrentProject() + ->getName() + .getQString(); + } + m_projectField->setText(prjName); + + m_episodeField->setText( + QString::fromStdString(CamCapSaveInPopupEpisode.getValue())); + m_sequenceField->setText( + QString::fromStdString(CamCapSaveInPopupSequence.getValue())); + m_sceneField->setText( + QString::fromStdString(CamCapSaveInPopupScene.getValue())); + + m_autoSubNameCB->setChecked(CamCapSaveInPopupAutoSubName != 0); + m_subNameFormatCombo->setEnabled(CamCapSaveInPopupAutoSubName != 0); + QStringList items; + items << tr("C- + Sequence + Scene") << tr("Sequence + Scene") + << tr("Episode + Sequence + Scene") + << tr("Project + Episode + Sequence + Scene"); + m_subNameFormatCombo->addItems(items); + m_subNameFormatCombo->setCurrentIndex(CamCapSaveInPopupAutoSubName - 1); + + showPopupOnLaunchCB->setChecked(CamCapOpenSaveInPopupOnLaunch != 0); + m_createSceneInFolderCB->setChecked(CamCapSaveInPopupCreateSceneInFolder != + 0); + m_createSceneInFolderCB->setToolTip( + tr("Save the current scene in the subfolder.\nSet the output folder path " + "to the subfolder as well.")); + + addButtonBarWidget(okBtn, cancelBtn); + + //---- layout + m_topLayout->setMargin(10); + m_topLayout->setSpacing(10); + { + QGridLayout* saveInLay = new QGridLayout(); + saveInLay->setMargin(0); + saveInLay->setHorizontalSpacing(3); + saveInLay->setVerticalSpacing(0); + { + saveInLay->addWidget(new QLabel(tr("Save In:"), this), 0, 0, + Qt::AlignRight | Qt::AlignVCenter); + saveInLay->addWidget(m_parentFolderField, 0, 1); + saveInLay->addWidget(setAsDefaultBtn, 1, 1); + } + saveInLay->setColumnStretch(0, 0); + saveInLay->setColumnStretch(1, 1); + m_topLayout->addLayout(saveInLay); + + m_topLayout->addWidget(m_subFolderCB, 0, Qt::AlignLeft); + + QVBoxLayout* subFolderLay = new QVBoxLayout(); + subFolderLay->setMargin(0); + subFolderLay->setSpacing(10); + { + QGridLayout* infoLay = new QGridLayout(); + infoLay->setMargin(10); + infoLay->setHorizontalSpacing(3); + infoLay->setVerticalSpacing(10); + { + infoLay->addWidget(new QLabel(tr("Project:"), this), 0, 0); + infoLay->addWidget(m_projectField, 0, 1); + + infoLay->addWidget(new QLabel(tr("Episode:"), this), 1, 0); + infoLay->addWidget(m_episodeField, 1, 1); + + infoLay->addWidget(new QLabel(tr("Sequence:"), this), 2, 0); + infoLay->addWidget(m_sequenceField, 2, 1); + + infoLay->addWidget(new QLabel(tr("Scene:"), this), 3, 0); + infoLay->addWidget(m_sceneField, 3, 1); + } + infoLay->setColumnStretch(0, 0); + infoLay->setColumnStretch(1, 1); + infoGroupBox->setLayout(infoLay); + subFolderLay->addWidget(infoGroupBox, 0); + + QGridLayout* subNameLay = new QGridLayout(); + subNameLay->setMargin(10); + subNameLay->setHorizontalSpacing(3); + subNameLay->setVerticalSpacing(10); + { + subNameLay->addWidget(m_autoSubNameCB, 0, 0); + subNameLay->addWidget(m_subNameFormatCombo, 0, 1); + + subNameLay->addWidget(new QLabel(tr("Subfolder Name:"), this), 1, 0); + subNameLay->addWidget(m_subFolderNameField, 1, 1); + } + subNameLay->setColumnStretch(0, 0); + subNameLay->setColumnStretch(1, 1); + subNameGroupBox->setLayout(subNameLay); + subFolderLay->addWidget(subNameGroupBox, 0); + + subFolderLay->addWidget(m_createSceneInFolderCB, 0, Qt::AlignLeft); + } + subFolderFrame->setLayout(subFolderLay); + m_topLayout->addWidget(subFolderFrame); + + m_topLayout->addWidget(showPopupOnLaunchCB, 0, Qt::AlignLeft); + + m_topLayout->addStretch(1); + } + + resize(300, 440); + + //---- signal-slot connection + bool ret = true; + + ret = ret && connect(m_subFolderCB, SIGNAL(clicked(bool)), subFolderFrame, + SLOT(setEnabled(bool))); + ret = ret && connect(m_projectField, SIGNAL(textEdited(const QString&)), this, + SLOT(updateSubFolderName())); + ret = ret && connect(m_episodeField, SIGNAL(textEdited(const QString&)), this, + SLOT(updateSubFolderName())); + ret = ret && connect(m_sequenceField, SIGNAL(textEdited(const QString&)), + this, SLOT(updateSubFolderName())); + ret = ret && connect(m_sceneField, SIGNAL(textEdited(const QString&)), this, + SLOT(updateSubFolderName())); + ret = ret && connect(m_autoSubNameCB, SIGNAL(clicked(bool)), this, + SLOT(onAutoSubNameCBClicked(bool))); + ret = ret && connect(m_subNameFormatCombo, SIGNAL(currentIndexChanged(int)), + this, SLOT(updateSubFolderName())); + + ret = ret && connect(showPopupOnLaunchCB, SIGNAL(clicked(bool)), this, + SLOT(onShowPopupOnLaunchCBClicked(bool))); + ret = ret && connect(m_createSceneInFolderCB, SIGNAL(clicked(bool)), this, + SLOT(onCreateSceneInFolderCBClicked(bool))); + ret = ret && connect(setAsDefaultBtn, SIGNAL(pressed()), this, + SLOT(onSetAsDefaultBtnPressed())); + + ret = ret && connect(okBtn, SIGNAL(clicked(bool)), this, SLOT(onOkPressed())); + ret = ret && connect(cancelBtn, SIGNAL(clicked(bool)), this, SLOT(reject())); + assert(ret); + + updateSubFolderName(); +} + +//----------------------------------------------------------------------------- + +QString PencilTestSaveInFolderPopup::getPath() { + if (!m_subFolderCB->isChecked()) return m_parentFolderField->getPath(); + + // re-code filepath + TFilePath path(m_parentFolderField->getPath() + "\\" + + m_subFolderNameField->text()); + ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene(); + if (scene) { + path = scene->decodeFilePath(path); + path = scene->codeFilePath(path); + } + return path.getQString(); +} + +//----------------------------------------------------------------------------- + +QString PencilTestSaveInFolderPopup::getParentPath() { + return m_parentFolderField->getPath(); +} + +//----------------------------------------------------------------------------- + +void PencilTestSaveInFolderPopup::showEvent(QShowEvent* event) { + // Show "Save the scene" check box only when the scene is untitled + bool isUntitled = + TApp::instance()->getCurrentScene()->getScene()->isUntitled(); + m_createSceneInFolderCB->setVisible(isUntitled); +} + +//----------------------------------------------------------------------------- +namespace { +QString formatString(QString inStr, int charNum) { + if (inStr.isEmpty()) return QString("0").rightJustified(charNum, '0'); + + QString numStr, postStr; + // find the first non-digit character + int index = inStr.indexOf(QRegExp("[^0-9]"), 0); + + if (index == -1) // only digits + numStr = inStr; + else if (index == 0) // only post strings + return inStr; + else { // contains both + numStr = inStr.left(index); + postStr = inStr.right(inStr.length() - index); + } + return numStr.rightJustified(charNum, '0') + postStr; +} +}; // namespace + +void PencilTestSaveInFolderPopup::updateSubFolderName() { + if (!m_autoSubNameCB->isChecked()) return; + + QString episodeStr = formatString(m_episodeField->text(), 3); + QString sequenceStr = formatString(m_sequenceField->text(), 3); + QString sceneStr = formatString(m_sceneField->text(), 4); + + QString str; + + switch (m_subNameFormatCombo->currentIndex()) { + case 0: // C- + Sequence + Scene + str = QString("C-%1-%2").arg(sequenceStr).arg(sceneStr); + break; + case 1: // Sequence + Scene + str = QString("%1-%2").arg(sequenceStr).arg(sceneStr); + break; + case 2: // Episode + Sequence + Scene + str = QString("%1-%2-%3").arg(episodeStr).arg(sequenceStr).arg(sceneStr); + break; + case 3: // Project + Episode + Sequence + Scene + str = QString("%1-%2-%3-%4") + .arg(m_projectField->text()) + .arg(episodeStr) + .arg(sequenceStr) + .arg(sceneStr); + break; + default: + return; + } + m_subFolderNameField->setText(str); +} + +//----------------------------------------------------------------------------- + +void PencilTestSaveInFolderPopup::onAutoSubNameCBClicked(bool on) { + m_subNameFormatCombo->setEnabled(on); + updateSubFolderName(); +} + +//----------------------------------------------------------------------------- + +void PencilTestSaveInFolderPopup::onShowPopupOnLaunchCBClicked(bool on) { + CamCapOpenSaveInPopupOnLaunch = (on) ? 1 : 0; +} + +//----------------------------------------------------------------------------- + +void PencilTestSaveInFolderPopup::onCreateSceneInFolderCBClicked(bool on) { + CamCapSaveInPopupCreateSceneInFolder = (on) ? 1 : 0; +} + +//----------------------------------------------------------------------------- + +void PencilTestSaveInFolderPopup::onSetAsDefaultBtnPressed() { + CamCapSaveInParentFolder = m_parentFolderField->getPath().toStdString(); +} + +//----------------------------------------------------------------------------- + +void PencilTestSaveInFolderPopup::onOkPressed() { + if (!m_subFolderCB->isChecked()) { + accept(); + return; + } + + // check the subFolder value + QString subFolderName = m_subFolderNameField->text(); + if (subFolderName.isEmpty()) { + DVGui::MsgBox(WARNING, tr("Subfolder name should not be empty.")); + return; + } + + int index = subFolderName.indexOf(QRegExp("[\\]:;|=,\\[\\*\\.\"/\\\\]"), 0); + if (index >= 0) { + DVGui::MsgBox(WARNING, tr("Subfolder name should not contain following " + "characters: * . \" / \\ [ ] : ; | = , ")); + return; + } + + TFilePath fp(m_parentFolderField->getPath()); + fp += TFilePath(subFolderName); + TFilePath actualFp = + TApp::instance()->getCurrentScene()->getScene()->decodeFilePath(fp); + + if (QFileInfo::exists(actualFp.getQString())) { + DVGui::MsgBox(WARNING, + tr("Folder %1 already exists.").arg(actualFp.getQString())); + return; + } + + // save the current properties to env data + CamCapSaveInPopupSubFolder = (m_subFolderCB->isChecked()) ? 1 : 0; + CamCapSaveInPopupProject = m_projectField->text().toStdString(); + CamCapSaveInPopupEpisode = m_episodeField->text().toStdString(); + CamCapSaveInPopupSequence = m_sequenceField->text().toStdString(); + CamCapSaveInPopupScene = m_sceneField->text().toStdString(); + CamCapSaveInPopupAutoSubName = (!m_autoSubNameCB->isChecked()) + ? 0 + : m_subNameFormatCombo->currentIndex() + 1; + + // create folder + try { + TSystem::mkDir(actualFp); + } catch (...) { + MsgBox(CRITICAL, tr("It is not possible to create the %1 folder.") + .arg(toQString(actualFp))); + return; + } + + createSceneInFolder(); + accept(); +} + +//----------------------------------------------------------------------------- + +void PencilTestSaveInFolderPopup::createSceneInFolder() { + // make sure that the check box is displayed (= the scene is untitled) and is + // checked. + if (m_createSceneInFolderCB->isHidden() || + !m_createSceneInFolderCB->isChecked()) + return; + // just in case + if (!m_subFolderCB->isChecked()) return; + + // set the output folder + ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene(); + if (!scene) return; + + TFilePath fp(getPath().toStdWString()); + + // for the scene folder mode, output destination must be already set to + // $scenefolder or its subfolder. See TSceneProperties::onInitialize() + if (Preferences::instance()->getPathAliasPriority() != + Preferences::SceneFolderAlias) { + TOutputProperties* prop = scene->getProperties()->getOutputProperties(); + prop->setPath(prop->getPath().withParentDir(fp)); + } + + // save the scene + TFilePath sceneFp = + scene->decodeFilePath(fp) + + TFilePath(m_subFolderNameField->text().toStdWString()).withType("tnz"); + IoCmd::saveScene(sceneFp, 0); +} + +//----------------------------------------------------------------------------- + +void PencilTestSaveInFolderPopup::updateParentFolder() { + // If the parent folder is saved in the scene, use it + ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene(); + QString parentFolder = + scene->getProperties()->cameraCaptureSaveInPath().getQString(); + if (parentFolder.isEmpty()) { + // else then, if the user-env stores the parent folder value, use it + parentFolder = QString::fromStdString(CamCapSaveInParentFolder); + // else, use "+extras" project folder + if (parentFolder.isEmpty()) + parentFolder = + QString("+%1").arg(QString::fromStdString(TProject::Extras)); + } + + m_parentFolderField->setPath(parentFolder); +} + +//============================================================================= + +PencilTestPopup::PencilTestPopup() + // set the parent 0 in order to enable the popup behind the main window + : Dialog(0, false, false, "PencilTest") + , m_currentCamera(NULL) + , m_captureWhiteBGCue(false) + , m_captureCue(false) { + setWindowTitle(tr("Camera Capture")); + + // add maximize button to the dialog + setWindowFlags(windowFlags() | Qt::WindowMaximizeButtonHint); + + layout()->setSizeConstraint(QLayout::SetNoConstraint); + + m_saveInFolderPopup = new PencilTestSaveInFolderPopup(this); + + m_videoWidget = new MyVideoWidget(this); + + m_cameraListCombo = new QComboBox(this); + QPushButton* refreshCamListButton = new QPushButton(tr("Refresh"), this); + m_resolutionCombo = new QComboBox(this); + + QGroupBox* fileFrame = new QGroupBox(tr("File"), this); + m_levelNameEdit = new LevelNameLineEdit(this); + // set the start frame 10 if the option in preferences + // "Show ABC Appendix to the Frame Number in Xsheet Cell" is active. + // (frame 10 is displayed as "1" with this option) + int startFrame = + Preferences::instance()->isShowFrameNumberWithLettersEnabled() ? 10 : 1; + m_frameNumberEdit = new FrameNumberLineEdit(this, startFrame); + m_frameInfoLabel = new QLabel("", this); + m_fileTypeCombo = new QComboBox(this); + m_fileFormatOptionButton = new QPushButton(tr("Options"), this); + + m_saveInFileFld = new FileField(this, m_saveInFolderPopup->getParentPath()); + + QToolButton* nextLevelButton = new QToolButton(this); + m_previousLevelButton = new QToolButton(this); + + m_saveOnCaptureCB = + new QCheckBox(tr("Save images as they are captured"), this); + + QGroupBox* imageFrame = new QGroupBox(tr("Image adjust"), this); + m_colorTypeCombo = new QComboBox(this); + + m_camCapLevelControl = new CameraCaptureLevelControl(this); + m_upsideDownCB = new QCheckBox(tr("Upside down"), this); + + m_bgReductionFld = new IntField(this); + m_captureWhiteBGButton = new QPushButton(tr("Capture white BG"), this); + + QGroupBox* displayFrame = new QGroupBox(tr("Display"), this); + m_onionSkinCB = new QCheckBox(tr("Show onion skin"), this); + m_loadImageButton = new QPushButton(tr("Load Selected Image"), this); + m_onionOpacityFld = new IntField(this); + + QGroupBox* timerFrame = new QGroupBox(tr("Interval timer"), this); + m_timerCB = new QCheckBox(tr("Use interval timer"), this); + m_timerIntervalFld = new IntField(this); + m_captureTimer = new QTimer(this); + m_countdownTimer = new QTimer(this); + + m_captureButton = new QPushButton(tr("Capture\n[Return key]"), this); + QPushButton* closeButton = new QPushButton(tr("Close"), this); + +#ifdef _WIN32 + m_captureFilterSettingsBtn = new QPushButton(this); +#else + m_captureFilterSettingsBtn = 0; +#endif + + QPushButton* subfolderButton = new QPushButton(tr("Subfolder"), this); + + // subcamera + m_subcameraButton = new QPushButton(tr("Subcamera"), this); + m_subWidthFld = new IntLineEdit(this); + m_subHeightFld = new IntLineEdit(this); + QWidget* subCamWidget = new QWidget(this); + + //---- + + m_resolutionCombo->setMaximumWidth(fontMetrics().width("0000 x 0000") + 25); + m_fileTypeCombo->addItems({"jpg", "png", "tga", "tif"}); + m_fileTypeCombo->setCurrentIndex(0); + + fileFrame->setObjectName("CleanupSettingsFrame"); + m_frameNumberEdit->setObjectName("LargeSizedText"); + m_frameInfoLabel->setAlignment(Qt::AlignRight); + nextLevelButton->setFixedSize(24, 24); + nextLevelButton->setArrowType(Qt::RightArrow); + nextLevelButton->setToolTip(tr("Next Level")); + m_previousLevelButton->setFixedSize(24, 24); + m_previousLevelButton->setArrowType(Qt::LeftArrow); + m_previousLevelButton->setToolTip(tr("Previous Level")); + m_saveOnCaptureCB->setChecked(true); + + imageFrame->setObjectName("CleanupSettingsFrame"); + m_colorTypeCombo->addItems( + {tr("Color"), tr("Grayscale"), tr("Black & White")}); + m_colorTypeCombo->setCurrentIndex(0); + m_upsideDownCB->setChecked(false); + + m_bgReductionFld->setRange(0, 100); + m_bgReductionFld->setValue(0); + m_bgReductionFld->setDisabled(true); + + displayFrame->setObjectName("CleanupSettingsFrame"); + m_onionSkinCB->setChecked(false); + m_onionOpacityFld->setRange(1, 100); + m_onionOpacityFld->setValue(50); + m_onionOpacityFld->setDisabled(true); + + timerFrame->setObjectName("CleanupSettingsFrame"); + m_timerCB->setChecked(false); + m_timerIntervalFld->setRange(0, 60); + m_timerIntervalFld->setValue(10); + m_timerIntervalFld->setDisabled(true); + // Make the interval timer single-shot. When the capture finished, restart + // timer for next frame. + // This is because capturing and saving the image needs some time. + m_captureTimer->setSingleShot(true); + + m_captureButton->setObjectName("LargeSizedText"); + m_captureButton->setFixedHeight(75); + QCommonStyle style; + m_captureButton->setIcon(style.standardIcon(QStyle::SP_DialogOkButton)); + m_captureButton->setIconSize(QSize(30, 30)); + + if (m_captureFilterSettingsBtn) { + m_captureFilterSettingsBtn->setObjectName("GearButton"); + m_captureFilterSettingsBtn->setFixedSize(23, 23); + m_captureFilterSettingsBtn->setIconSize(QSize(15, 15)); + m_captureFilterSettingsBtn->setToolTip( + tr("Video Capture Filter Settings...")); + } + + subfolderButton->setObjectName("SubfolderButton"); + subfolderButton->setIconSize(QSize(15, 15)); + m_saveInFileFld->setMaximumWidth(380); + + m_saveInFolderPopup->hide(); + + m_subcameraButton->setObjectName("SubcameraButton"); + m_subcameraButton->setIconSize(QSize(15, 15)); + m_subcameraButton->setCheckable(true); + m_subcameraButton->setChecked(false); + subCamWidget->setHidden(true); + + //---- layout ---- + m_topLayout->setMargin(10); + m_topLayout->setSpacing(10); + { + QHBoxLayout* camLay = new QHBoxLayout(); + camLay->setMargin(0); + camLay->setSpacing(3); + { + camLay->addWidget(new QLabel(tr("Camera:"), this), 0); + camLay->addWidget(m_cameraListCombo, 1); + camLay->addWidget(refreshCamListButton, 0); + camLay->addSpacing(10); + camLay->addWidget(new QLabel(tr("Resolution:"), this), 0); + camLay->addWidget(m_resolutionCombo, 1); + + if (m_captureFilterSettingsBtn) { + camLay->addSpacing(10); + camLay->addWidget(m_captureFilterSettingsBtn); + } + + camLay->addSpacing(10); + camLay->addWidget(m_subcameraButton, 0); + QHBoxLayout* subCamLay = new QHBoxLayout(); + subCamLay->setMargin(0); + subCamLay->setSpacing(3); + { + subCamLay->addWidget(m_subWidthFld, 0); + subCamLay->addWidget(new QLabel("x", this), 0); + subCamLay->addWidget(m_subHeightFld, 0); + subCamLay->addStretch(0); + } + subCamWidget->setLayout(subCamLay); + camLay->addWidget(subCamWidget, 0); + + camLay->addStretch(0); + camLay->addSpacing(15); + camLay->addWidget(new QLabel(tr("Save In:"), this), 0); + camLay->addWidget(m_saveInFileFld, 1); + + camLay->addSpacing(10); + camLay->addWidget(subfolderButton, 0); + } + m_topLayout->addLayout(camLay, 0); + + QHBoxLayout* bottomLay = new QHBoxLayout(); + bottomLay->setMargin(0); + bottomLay->setSpacing(10); + { + bottomLay->addWidget(m_videoWidget, 1); + + QVBoxLayout* rightLay = new QVBoxLayout(); + rightLay->setMargin(0); + rightLay->setSpacing(5); + { + QVBoxLayout* fileLay = new QVBoxLayout(); + fileLay->setMargin(8); + fileLay->setSpacing(5); + { + QGridLayout* levelLay = new QGridLayout(); + levelLay->setMargin(0); + levelLay->setHorizontalSpacing(3); + levelLay->setVerticalSpacing(5); + { + levelLay->addWidget(new QLabel(tr("Name:"), this), 0, 0, + Qt::AlignRight); + QHBoxLayout* nameLay = new QHBoxLayout(); + nameLay->setMargin(0); + nameLay->setSpacing(2); + { + nameLay->addWidget(m_previousLevelButton, 0); + nameLay->addWidget(m_levelNameEdit, 1); + nameLay->addWidget(nextLevelButton, 0); + } + levelLay->addLayout(nameLay, 0, 1); + + levelLay->addWidget(new QLabel(tr("Frame:"), this), 1, 0, + Qt::AlignRight); + + QHBoxLayout* frameLay = new QHBoxLayout(); + frameLay->setMargin(0); + frameLay->setSpacing(2); + { + frameLay->addWidget(m_frameNumberEdit, 1); + frameLay->addWidget(m_frameInfoLabel, 1, Qt::AlignVCenter); + } + levelLay->addLayout(frameLay, 1, 1); + } + levelLay->setColumnStretch(0, 0); + levelLay->setColumnStretch(1, 1); + fileLay->addLayout(levelLay, 0); + + QHBoxLayout* fileTypeLay = new QHBoxLayout(); + fileTypeLay->setMargin(0); + fileTypeLay->setSpacing(3); + { + fileTypeLay->addWidget(new QLabel(tr("File Type:"), this), 0); + fileTypeLay->addWidget(m_fileTypeCombo, 1); + fileTypeLay->addSpacing(10); + fileTypeLay->addWidget(m_fileFormatOptionButton); + } + fileLay->addLayout(fileTypeLay, 0); + + fileLay->addWidget(m_saveOnCaptureCB, 0); + } + fileFrame->setLayout(fileLay); + rightLay->addWidget(fileFrame, 0); + + QGridLayout* imageLay = new QGridLayout(); + imageLay->setMargin(8); + imageLay->setHorizontalSpacing(3); + imageLay->setVerticalSpacing(5); + { + imageLay->addWidget(new QLabel(tr("Color type:"), this), 0, 0, + Qt::AlignRight); + imageLay->addWidget(m_colorTypeCombo, 0, 1); + + imageLay->addWidget(m_camCapLevelControl, 1, 0, 1, 3); + + imageLay->addWidget(m_upsideDownCB, 2, 0, 1, 3, Qt::AlignLeft); + + imageLay->addWidget(new QLabel(tr("BG reduction:"), this), 3, 0, + Qt::AlignRight); + imageLay->addWidget(m_bgReductionFld, 3, 1, 1, 2); + + imageLay->addWidget(m_captureWhiteBGButton, 4, 0, 1, 3); + } + imageLay->setColumnStretch(0, 0); + imageLay->setColumnStretch(1, 0); + imageLay->setColumnStretch(2, 1); + imageFrame->setLayout(imageLay); + rightLay->addWidget(imageFrame, 0); + + QGridLayout* displayLay = new QGridLayout(); + displayLay->setMargin(8); + displayLay->setHorizontalSpacing(3); + displayLay->setVerticalSpacing(5); + { + displayLay->addWidget(m_onionSkinCB, 0, 0, 1, 2); + + displayLay->addWidget(new QLabel(tr("Opacity(%):"), this), 1, 0, + Qt::AlignRight); + displayLay->addWidget(m_onionOpacityFld, 1, 1); + displayLay->addWidget(m_loadImageButton, 2, 0, 1, 2); + } + displayLay->setColumnStretch(0, 0); + displayLay->setColumnStretch(1, 1); + displayFrame->setLayout(displayLay); + rightLay->addWidget(displayFrame); + + QGridLayout* timerLay = new QGridLayout(); + timerLay->setMargin(8); + timerLay->setHorizontalSpacing(3); + timerLay->setVerticalSpacing(5); + { + timerLay->addWidget(m_timerCB, 0, 0, 1, 2); + + timerLay->addWidget(new QLabel(tr("Interval(sec):"), this), 1, 0, + Qt::AlignRight); + timerLay->addWidget(m_timerIntervalFld, 1, 1); + } + timerLay->setColumnStretch(0, 0); + timerLay->setColumnStretch(1, 1); + timerFrame->setLayout(timerLay); + rightLay->addWidget(timerFrame); + + rightLay->addStretch(1); + + rightLay->addWidget(m_captureButton, 0); + rightLay->addSpacing(10); + rightLay->addWidget(closeButton, 0); + rightLay->addSpacing(5); + } + bottomLay->addLayout(rightLay, 0); + } + m_topLayout->addLayout(bottomLay, 1); + } + + //---- signal-slot connections ---- + bool ret = true; + ret = ret && connect(refreshCamListButton, SIGNAL(pressed()), this, + SLOT(refreshCameraList())); + ret = ret && connect(m_cameraListCombo, SIGNAL(activated(int)), this, + SLOT(onCameraListComboActivated(int))); + ret = ret && connect(m_resolutionCombo, SIGNAL(activated(const QString&)), + this, SLOT(onResolutionComboActivated(const QString&))); + ret = ret && connect(m_fileFormatOptionButton, SIGNAL(pressed()), this, + SLOT(onFileFormatOptionButtonPressed())); + ret = ret && connect(m_levelNameEdit, SIGNAL(levelNameEdited()), this, + SLOT(onLevelNameEdited())); + ret = ret && + connect(nextLevelButton, SIGNAL(pressed()), this, SLOT(onNextName())); + ret = ret && connect(m_previousLevelButton, SIGNAL(pressed()), this, + SLOT(onPreviousName())); + ret = ret && connect(m_colorTypeCombo, SIGNAL(currentIndexChanged(int)), this, + SLOT(onColorTypeComboChanged(int))); + ret = ret && connect(m_captureWhiteBGButton, SIGNAL(pressed()), this, + SLOT(onCaptureWhiteBGButtonPressed())); + ret = ret && connect(m_onionSkinCB, SIGNAL(toggled(bool)), this, + SLOT(onOnionCBToggled(bool))); + ret = ret && connect(m_loadImageButton, SIGNAL(pressed()), this, + SLOT(onLoadImageButtonPressed())); + ret = ret && connect(m_onionOpacityFld, SIGNAL(valueEditedByHand()), this, + SLOT(onOnionOpacityFldEdited())); + ret = ret && connect(m_upsideDownCB, SIGNAL(toggled(bool)), m_videoWidget, + SLOT(onUpsideDownChecked(bool))); + ret = ret && connect(m_timerCB, SIGNAL(toggled(bool)), this, + SLOT(onTimerCBToggled(bool))); + ret = ret && connect(m_captureTimer, SIGNAL(timeout()), this, + SLOT(onCaptureTimerTimeout())); + ret = ret && + connect(m_countdownTimer, SIGNAL(timeout()), this, SLOT(onCountDown())); + + ret = ret && connect(closeButton, SIGNAL(clicked()), this, SLOT(reject())); + ret = ret && connect(m_captureButton, SIGNAL(clicked(bool)), this, + SLOT(onCaptureButtonClicked(bool))); + if (m_captureFilterSettingsBtn) + ret = ret && connect(m_captureFilterSettingsBtn, SIGNAL(pressed()), this, + SLOT(onCaptureFilterSettingsBtnPressed())); + ret = ret && connect(subfolderButton, SIGNAL(clicked(bool)), this, + SLOT(openSaveInFolderPopup())); + ret = ret && connect(m_saveInFileFld, SIGNAL(pathChanged()), this, + SLOT(onSaveInPathEdited())); + ret = ret && connect(m_fileTypeCombo, SIGNAL(activated(int)), this, + SLOT(refreshFrameInfo())); + ret = ret && connect(m_frameNumberEdit, SIGNAL(editingFinished()), this, + SLOT(refreshFrameInfo())); + + // sub camera + ret = ret && connect(m_subcameraButton, SIGNAL(toggled(bool)), this, + SLOT(onSubCameraToggled(bool))); + ret = ret && connect(m_subcameraButton, SIGNAL(toggled(bool)), subCamWidget, + SLOT(setVisible(bool))); + ret = ret && connect(m_subWidthFld, SIGNAL(editingFinished()), this, + SLOT(onSubCameraSizeEdited())); + ret = ret && connect(m_subHeightFld, SIGNAL(editingFinished()), this, + SLOT(onSubCameraSizeEdited())); + ret = ret && connect(m_videoWidget, &MyVideoWidget::startCamera, [&]() { + if (m_currentCamera) m_currentCamera->start(); + }); + ret = ret && connect(m_videoWidget, &MyVideoWidget::stopCamera, [&]() { + if (m_currentCamera) m_currentCamera->stop(); + }); + ret = ret && connect(m_videoWidget, SIGNAL(subCameraResized(bool)), this, + SLOT(onSubCameraResized(bool))); + + assert(ret); + + refreshCameraList(); + + int startupCamIndex = m_cameraListCombo->findText( + QString::fromStdString(CamCapCameraName.getValue())); + if (startupCamIndex > 0) { + m_cameraListCombo->setCurrentIndex(startupCamIndex); + onCameraListComboActivated(startupCamIndex); + } + + QString resStr = QString::fromStdString(CamCapCameraResolution.getValue()); + if (m_currentCamera && !resStr.isEmpty()) { + int startupResolutionIndex = m_resolutionCombo->findText(resStr); + if (startupResolutionIndex >= 0) { + m_resolutionCombo->setCurrentIndex(startupResolutionIndex); + onResolutionComboActivated(resStr); + } + } + + setToNextNewLevel(); +} + +//----------------------------------------------------------------------------- + +PencilTestPopup::~PencilTestPopup() { + if (m_currentCamera) { + if (m_currentCamera->state() == QCamera::ActiveState) + m_currentCamera->stop(); + if (m_currentCamera->state() == QCamera::LoadedState) + m_currentCamera->unload(); + delete m_currentCamera; + } +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::refreshCameraList() { + m_cameraListCombo->clear(); + + QList cameras = QCameraInfo::availableCameras(); + if (cameras.empty()) { + m_cameraListCombo->addItem(tr("No camera found")); + m_cameraListCombo->setMaximumWidth(250); + m_cameraListCombo->setDisabled(true); + } + + int maxTextLength = 0; + // add non-connected state as default + m_cameraListCombo->addItem(tr("- Select camera -")); + for (int c = 0; c < cameras.size(); c++) { + QString camDesc = cameras.at(c).description(); + m_cameraListCombo->addItem(camDesc); + maxTextLength = std::max(maxTextLength, fontMetrics().width(camDesc)); + } + m_cameraListCombo->setMaximumWidth(maxTextLength + 25); + m_cameraListCombo->setEnabled(true); + m_cameraListCombo->setCurrentIndex(0); +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onCameraListComboActivated(int comboIndex) { + QList cameras = QCameraInfo::availableCameras(); + if (cameras.size() != m_cameraListCombo->count() - 1) return; + + // if selected the non-connected state, then disconnect the current camera + if (comboIndex == 0) { + m_videoWidget->videoSurface()->stop(); + if (m_currentCamera) { + if (m_currentCamera->state() == QCamera::ActiveState) + m_currentCamera->stop(); + if (m_currentCamera->state() == QCamera::LoadedState) + m_currentCamera->unload(); + } + m_deviceName = QString(); + m_videoWidget->setImage(QImage()); + // update env + CamCapCameraName = ""; + return; + } + + int index = comboIndex - 1; + // in case the camera is not changed (just click the combobox) + if (cameras.at(index).deviceName() == m_deviceName) return; + + QCamera* oldCamera = m_currentCamera; + if (oldCamera) m_videoWidget->videoSurface()->stop(); + + m_currentCamera = new QCamera(cameras.at(index), this); + m_deviceName = cameras.at(index).deviceName(); + + // loading new camera + m_currentCamera->load(); + + // refresh resolution + m_resolutionCombo->clear(); + QList sizes = m_currentCamera->supportedViewfinderResolutions(); + + for (int s = 0; s < sizes.size(); s++) { + m_resolutionCombo->addItem( + QString("%1 x %2").arg(sizes.at(s).width()).arg(sizes.at(s).height())); + } + if (!sizes.isEmpty()) { + // select the largest available resolution + m_resolutionCombo->setCurrentIndex(m_resolutionCombo->count() - 1); + QCameraViewfinderSettings settings = m_currentCamera->viewfinderSettings(); + settings.setResolution(sizes.last()); + m_currentCamera->setViewfinderSettings(settings); + } + m_currentCamera->setViewfinder(m_videoWidget->videoSurface()); + m_videoWidget->videoSurface()->start( + m_videoWidget->videoSurface()->surfaceFormat()); + + // deleting old camera + if (oldCamera) { + if (oldCamera->state() == QCamera::ActiveState) oldCamera->stop(); + delete oldCamera; + } + // start new camera + m_currentCamera->start(); + m_videoWidget->setImage(QImage()); + + // update env + CamCapCameraName = m_cameraListCombo->itemText(comboIndex).toStdString(); +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onResolutionComboActivated(const QString& itemText) { + // resolution is written in the itemText with the format " x " + // (e.g. "800 x 600") + QStringList texts = itemText.split(' '); + // the splited text must be "" "x" and "" + if (texts.size() != 3) return; + +#ifndef MACOSX + m_currentCamera->stop(); + m_currentCamera->unload(); +#endif + + QCameraViewfinderSettings settings = m_currentCamera->viewfinderSettings(); + QSize newResolution(texts[0].toInt(), texts[2].toInt()); + settings.setResolution(newResolution); + m_currentCamera->setViewfinderSettings(settings); + + // reset white bg + m_whiteBGImg = QImage(); + m_bgReductionFld->setDisabled(true); +#ifndef MACOSX + m_currentCamera->start(); +#endif + m_videoWidget->setImage(QImage()); + + // update env + CamCapCameraResolution = itemText.toStdString(); + + refreshFrameInfo(); + + // reset subcamera info + m_subcameraButton->setChecked(false); // this will hide the size fields + m_subWidthFld->setRange(10, newResolution.width()); + m_subHeightFld->setRange(10, newResolution.height()); + // if there is no existing level or its size is larger than the current camera + if (!m_allowedCameraSize.isValid() || + m_allowedCameraSize.width() > newResolution.width() || + m_allowedCameraSize.height() > newResolution.height()) { + // make the initial subcamera size to be with the same aspect ratio as the + // current camera + TCamera* camera = + TApp::instance()->getCurrentScene()->getScene()->getCurrentCamera(); + TDimension camres = camera->getRes(); + newResolution = + QSize(camres.lx, camres.ly).scaled(newResolution, Qt::KeepAspectRatio); + // newResolution.scale(QSize(res.lx, res.ly), Qt::KeepAspectRatio); + m_subWidthFld->setValue(newResolution.width()); + m_subHeightFld->setValue(newResolution.height()); + } else { + m_subWidthFld->setValue(m_allowedCameraSize.width()); + m_subHeightFld->setValue(m_allowedCameraSize.height()); + } +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onFileFormatOptionButtonPressed() { + if (m_fileTypeCombo->currentIndex() == 0) return; + // Tentatively use the preview output settings + ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene(); + if (!scene) return; + TOutputProperties* prop = scene->getProperties()->getPreviewProperties(); + std::string ext = m_fileTypeCombo->currentText().toStdString(); + openFormatSettingsPopup(this, ext, prop->getFileFormatProperties(ext)); +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onLevelNameEdited() { + updateLevelNameAndFrame(m_levelNameEdit->text().toStdWString()); +} + +//----------------------------------------------------------------------------- +void PencilTestPopup::onNextName() { + std::unique_ptr nameCreator(new FlexibleNameCreator()); + if (!nameCreator->setCurrent(m_levelNameEdit->text().toStdWString())) { + setToNextNewLevel(); + return; + } + + std::wstring levelName = nameCreator->getNext(); + + updateLevelNameAndFrame(levelName); +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onPreviousName() { + std::unique_ptr nameCreator(new FlexibleNameCreator()); + + std::wstring levelName; + + // if the current level name is non-sequencial, then try to switch the last + // sequencial level in the scene. + if (!nameCreator->setCurrent(m_levelNameEdit->text().toStdWString())) { + TLevelSet* levelSet = + TApp::instance()->getCurrentScene()->getScene()->getLevelSet(); + nameCreator->setCurrent(L"ZZ"); + for (;;) { + levelName = nameCreator->getPrevious(); + if (levelSet->getLevel(levelName) != 0) break; + if (levelName == L"A") { + setToNextNewLevel(); + return; + } + } + } else + levelName = nameCreator->getPrevious(); + + updateLevelNameAndFrame(levelName); +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::setToNextNewLevel() { + const std::unique_ptr nameBuilder(NameBuilder::getBuilder(L"")); + + TLevelSet* levelSet = + TApp::instance()->getCurrentScene()->getScene()->getLevelSet(); + ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene(); + std::wstring levelName = L""; + + // Select a different unique level name in case it already exists (either in + // scene or on disk) + TFilePath fp; + TFilePath actualFp; + for (;;) { + levelName = nameBuilder->getNext(); + + if (levelSet->getLevel(levelName) != 0) continue; + + fp = TFilePath(m_saveInFileFld->getPath()) + + TFilePath(levelName + L".." + + m_fileTypeCombo->currentText().toStdWString()); + actualFp = scene->decodeFilePath(fp); + + if (TSystem::doesExistFileOrLevel(actualFp)) { + continue; + } + + break; + } + + updateLevelNameAndFrame(levelName); +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::updateLevelNameAndFrame(std::wstring levelName) { + if (levelName != m_levelNameEdit->text().toStdWString()) + m_levelNameEdit->setText(QString::fromStdWString(levelName)); + m_previousLevelButton->setDisabled(levelName == L"A"); + + // set the start frame 10 if the option in preferences + // "Show ABC Appendix to the Frame Number in Xsheet Cell" is active. + // (frame 10 is displayed as "1" with this option) + bool withLetter = + Preferences::instance()->isShowFrameNumberWithLettersEnabled(); + + TLevelSet* levelSet = + TApp::instance()->getCurrentScene()->getScene()->getLevelSet(); + TXshLevel* level_p = levelSet->getLevel(levelName); + int startFrame; + if (!level_p) { + startFrame = withLetter ? 10 : 1; + } else { + std::vector fids; + level_p->getFids(fids); + if (fids.empty()) { + startFrame = withLetter ? 10 : 1; + } else { + int lastNum = fids.back().getNumber(); + startFrame = withLetter ? ((int)(lastNum / 10) + 1) * 10 : lastNum + 1; + } + } + m_frameNumberEdit->setValue(startFrame); + + refreshFrameInfo(); +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onColorTypeComboChanged(int index) { + m_camCapLevelControl->setMode(index != 2); +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onFrameCaptured(QImage& image) { + if (!m_videoWidget || image.isNull()) return; + // capture the white BG + if (m_captureWhiteBGCue) { + m_whiteBGImg = image.copy(); + m_captureWhiteBGCue = false; + m_bgReductionFld->setEnabled(true); + } + + processImage(image); + m_videoWidget->setImage(image.copy()); + + if (m_captureCue) { +#ifndef MACOSX + m_currentCamera->stop(); +#endif + m_captureCue = false; + + bool scanBtoT = + m_videoWidget->videoSurface()->surfaceFormat().scanLineDirection() == + QVideoSurfaceFormat::BottomToTop; + bool upsideDown = m_upsideDownCB->isChecked(); + + image = image.mirrored(upsideDown, upsideDown != scanBtoT); + + if (importImage(image)) { + m_videoWidget->setPreviousImage(image.copy()); + if (Preferences::instance()->isShowFrameNumberWithLettersEnabled()) { + int f = m_frameNumberEdit->getValue(); + if (f % 10 == 0) // next number + m_frameNumberEdit->setValue(((int)(f / 10) + 1) * 10); + else // next alphabet + m_frameNumberEdit->setValue(f + 1); + } else + m_frameNumberEdit->setValue(m_frameNumberEdit->getValue() + 1); + + /* notify */ + TApp::instance()->getCurrentScene()->notifySceneChanged(); + TApp::instance()->getCurrentScene()->notifyCastChange(); + TApp::instance()->getCurrentXsheet()->notifyXsheetChanged(); + + // restart interval timer for capturing next frame (it is single shot) + if (m_timerCB->isChecked() && m_captureButton->isChecked()) { + m_captureTimer->start(m_timerIntervalFld->getValue() * 1000); + // restart the count down as well (for aligning the timing. It is not + // single shot) + if (m_timerIntervalFld->getValue() != 0) m_countdownTimer->start(100); + } + } + // if capture was failed, stop interval capturing + else if (m_timerCB->isChecked()) { + m_captureButton->setChecked(false); + onCaptureButtonClicked(false); + } +#ifndef MACOSX + m_currentCamera->start(); +#endif + } +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::showEvent(QShowEvent* event) { + // m_timerId = startTimer(10); + + // if there is another action of which "return" key is assigned as short cut + // key, + // then release the shortcut key temporary while the popup opens + QAction* action = CommandManager::instance()->getActionFromShortcut("Return"); + if (action) action->setShortcut(QKeySequence("")); + + connect(m_videoWidget->videoSurface(), SIGNAL(frameCaptured(QImage&)), this, + SLOT(onFrameCaptured(QImage&))); + + // reload camera + if (m_currentCamera) { + if (m_currentCamera->state() == QCamera::UnloadedState) + m_currentCamera->load(); + if (m_currentCamera->state() == QCamera::LoadedState) + m_currentCamera->start(); + } + + TSceneHandle* sceneHandle = TApp::instance()->getCurrentScene(); + connect(sceneHandle, SIGNAL(sceneSwitched()), this, SLOT(onSceneSwitched())); + connect(sceneHandle, SIGNAL(castChanged()), this, SLOT(refreshFrameInfo())); + + bool tmp_alwaysOverwrite = m_alwaysOverwrite; + onSceneSwitched(); + m_alwaysOverwrite = tmp_alwaysOverwrite; +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::hideEvent(QHideEvent* event) { + // set back the "return" short cut key + QAction* action = CommandManager::instance()->getActionFromShortcut("Return"); + if (action) action->setShortcut(QKeySequence("Return")); + + disconnect(m_videoWidget->videoSurface(), SIGNAL(frameCaptured(QImage&)), + this, SLOT(onFrameCaptured(QImage&))); + + // stop interval timer if it is active + if (m_timerCB->isChecked() && m_captureButton->isChecked()) { + m_captureButton->setChecked(false); + onCaptureButtonClicked(false); + } + + // release camera + if (m_currentCamera) { + if (m_currentCamera->state() == QCamera::ActiveState) + m_currentCamera->stop(); + if (m_currentCamera->state() == QCamera::LoadedState) + m_currentCamera->unload(); + } + Dialog::hideEvent(event); + + TSceneHandle* sceneHandle = TApp::instance()->getCurrentScene(); + disconnect(sceneHandle, SIGNAL(sceneSwitched()), this, + SLOT(refreshFrameInfo())); + disconnect(sceneHandle, SIGNAL(castChanged()), this, + SLOT(refreshFrameInfo())); +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::keyPressEvent(QKeyEvent* event) { + // override return (or enter) key as shortcut key for capturing + int key = event->key(); + if (key == Qt::Key_Return || key == Qt::Key_Enter) { + // show button-clicking animation followed by calling + // onCaptureButtonClicked() + m_captureButton->animateClick(); + event->accept(); + } else + event->ignore(); +} + +//----------------------------------------------------------------------------- + +bool PencilTestPopup::event(QEvent* event) { + if (event->type() == QEvent::ShortcutOverride) { + QKeyEvent* ke = static_cast(event); + int key = ke->key(); + if (key >= Qt::Key_0 && key <= Qt::Key_9) { + if (!m_frameNumberEdit->hasFocus()) { + m_frameNumberEdit->setFocus(); + m_frameNumberEdit->clear(); + } + event->accept(); + return true; + } + } + return DVGui::Dialog::event(event); +} +//----------------------------------------------------------------------------- + +void PencilTestPopup::processImage(QImage& image) { + /* "upside down" is not executed here. It will be done when capturing the + * image */ + // white bg reduction + if (!m_whiteBGImg.isNull() && m_bgReductionFld->getValue() != 0) { + bgReduction(image, m_whiteBGImg, m_bgReductionFld->getValue()); + } + // obtain histogram AFTER bg reduction + m_camCapLevelControl->updateHistogram(image); + + // color and grayscale mode + if (m_colorTypeCombo->currentIndex() != 2) { + int black, white; + float gamma; + m_camCapLevelControl->getValues(black, white, gamma); + if (black != 0 || white != 255 || gamma != 1.0) + onChange(image, black, white, gamma, + m_colorTypeCombo->currentIndex() != 0); + } else { + onChangeBW(image, m_camCapLevelControl->getThreshold()); + } +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onCaptureWhiteBGButtonPressed() { + m_captureWhiteBGCue = true; +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onOnionCBToggled(bool on) { + m_videoWidget->setShowOnionSkin(on); + m_onionOpacityFld->setEnabled(on); +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onLoadImageButtonPressed() { + TCellSelection* cellSelection = dynamic_cast( + TApp::instance()->getCurrentSelection()->getSelection()); + if (cellSelection) { + int c0; + int r0; + TCellSelection::Range range = cellSelection->getSelectedCells(); + if (range.isEmpty()) { + error(tr("No image selected. Please select an image in the Xsheet.")); + return; + } + c0 = range.m_c0; + r0 = range.m_r0; + TXshCell cell = + TApp::instance()->getCurrentXsheet()->getXsheet()->getCell(r0, c0); + if (cell.getSimpleLevel() == nullptr) { + error(tr("No image selected. Please select an image in the Xsheet.")); + return; + } + TXshSimpleLevel* level = cell.getSimpleLevel(); + int type = level->getType(); + if (type != TXshLevelType::OVL_XSHLEVEL) { + error(tr("The selected image is not in a raster level.")); + return; + } + TImageP origImage = cell.getImage(false); + TRasterImageP tempImage(origImage); + TRasterImage* image = (TRasterImage*)tempImage->cloneImage(); + + int m_lx = image->getRaster()->getLx(); + int m_ly = image->getRaster()->getLy(); + QString res = m_resolutionCombo->currentText(); + QStringList texts = res.split(' '); + if (m_lx != texts[0].toInt() || m_ly != texts[2].toInt()) { + error( + tr("The selected image size does not match the current camera " + "settings.")); + return; + } + int m_bpp = image->getRaster()->getPixelSize(); + int totalBytes = m_lx * m_ly * m_bpp; + image->getRaster()->yMirror(); + + // lock raster to get data + image->getRaster()->lock(); + void* buffin = image->getRaster()->getRawData(); + assert(buffin); + void* buffer = malloc(totalBytes); + memcpy(buffer, buffin, totalBytes); + + image->getRaster()->unlock(); + + QImage qi = QImage((uint8_t*)buffer, m_lx, m_ly, QImage::Format_ARGB32); + QImage qi2(qi.size(), QImage::Format_ARGB32); + qi2.fill(QColor(Qt::white).rgb()); + QPainter painter(&qi2); + painter.drawImage(0, 0, qi); + m_videoWidget->setPreviousImage(qi2); + m_onionSkinCB->setChecked(true); + free(buffer); + } +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onOnionOpacityFldEdited() { + int value = (int)(255.0f * (float)m_onionOpacityFld->getValue() / 100.0f); + m_videoWidget->setOnionOpacity(value); +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onTimerCBToggled(bool on) { + m_timerIntervalFld->setEnabled(on); + m_captureButton->setCheckable(on); + if (on) + m_captureButton->setText(tr("Start Capturing\n[Return key]")); + else + m_captureButton->setText(tr("Capture\n[Return key]")); +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onCaptureButtonClicked(bool on) { + if (m_timerCB->isChecked()) { + m_timerCB->setDisabled(on); + m_timerIntervalFld->setDisabled(on); + // start interval capturing + if (on) { + m_captureButton->setText(tr("Stop Capturing\n[Return key]")); + m_captureTimer->start(m_timerIntervalFld->getValue() * 1000); + if (m_timerIntervalFld->getValue() != 0) m_countdownTimer->start(100); + } + // stop interval capturing + else { + m_captureButton->setText(tr("Start Capturing\n[Return key]")); + m_captureTimer->stop(); + m_countdownTimer->stop(); + // hide the count down text + m_videoWidget->showCountDownTime(0); + } + } + // capture immediately + else + m_captureCue = true; +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onCaptureTimerTimeout() { m_captureCue = true; } + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onCountDown() { + m_videoWidget->showCountDownTime( + m_captureTimer->isActive() ? m_captureTimer->remainingTime() : 0); +} + +//----------------------------------------------------------------------------- +/*! referenced from LevelCreatePopup::apply() + */ +bool PencilTestPopup::importImage(QImage image) { + TApp* app = TApp::instance(); + ToonzScene* scene = app->getCurrentScene()->getScene(); + TXsheet* xsh = scene->getXsheet(); + + std::wstring levelName = m_levelNameEdit->text().toStdWString(); + if (levelName.empty()) { + error(tr("No level name specified: please choose a valid level name")); + return false; + } + + int frameNumber = m_frameNumberEdit->getValue(); + + /* create parent directory if it does not exist */ + TFilePath parentDir = + scene->decodeFilePath(TFilePath(m_saveInFileFld->getPath())); + if (!TFileStatus(parentDir).doesExist()) { + QString question; + question = tr("Folder %1 doesn't exist.\nDo you want to create it?") + .arg(toQString(parentDir)); + int ret = DVGui::MsgBox(question, QObject::tr("Yes"), QObject::tr("No")); + if (ret == 0 || ret == 2) return false; + try { + TSystem::mkDir(parentDir); + DvDirModel::instance()->refreshFolder(parentDir.getParentDir()); + } catch (...) { + error(tr("Unable to create") + toQString(parentDir)); + return false; + } + } + + TFilePath levelFp = TFilePath(m_saveInFileFld->getPath()) + + TFilePath(levelName + L".." + + m_fileTypeCombo->currentText().toStdWString()); + TFilePath actualLevelFp = scene->decodeFilePath(levelFp); + + TXshSimpleLevel* sl = 0; + + TXshLevel* level = scene->getLevelSet()->getLevel(levelName); + enum State { NEWLEVEL = 0, ADDFRAME, OVERWRITE } state; + + // retrieve subcamera image + if (m_subcameraButton->isChecked() && + m_videoWidget->subCameraRect().isValid()) + image = image.copy(m_videoWidget->subCameraRect()); + + /* if the level already exists in the scene cast */ + if (level) { + /* if the existing level is not a raster level, then return */ + if (level->getType() != OVL_XSHLEVEL) { + error( + tr("The level name specified is already used: please choose a " + "different level name.")); + return false; + } + /* if the existing level does not match file path and pixel size, then + * return */ + sl = level->getSimpleLevel(); + if (scene->decodeFilePath(sl->getPath()) != actualLevelFp) { + error( + tr("The save in path specified does not match with the existing " + "level.")); + return false; + } + if (sl->getProperties()->getImageRes() != + TDimension(image.width(), image.height())) { + error(tr( + "The captured image size does not match with the existing level.")); + return false; + } + /* if the level already have the same frame, then ask if overwrite it */ + TFilePath frameFp(actualLevelFp.withFrame(frameNumber)); + if (TFileStatus(frameFp).doesExist()) { + if (!m_alwaysOverwrite) { + QString question = + tr("File %1 does exist.\nDo you want to overwrite it?") + .arg(toQString(frameFp)); + int ret = DVGui::MsgBox(question, QObject::tr("Overwrite"), + QObject::tr("Always Overwrite in This Scene"), + QObject::tr("Cancel")); + if (ret == 0 || ret == 3) return false; + if (ret == 2) m_alwaysOverwrite = true; + } + state = OVERWRITE; + } else + state = ADDFRAME; + } + /* if the level does not exist in the scene cast */ + else { + /* if the file does exist, load it first */ + if (TSystem::doesExistFileOrLevel(actualLevelFp)) { + level = scene->loadLevel(actualLevelFp); + if (!level) { + error(tr("Failed to load %1.").arg(toQString(actualLevelFp))); + return false; + } + + /* if the loaded level does not match in pixel size, then return */ + sl = level->getSimpleLevel(); + if (!sl || sl->getProperties()->getImageRes() != + TDimension(image.width(), image.height())) { + error(tr( + "The captured image size does not match with the existing level.")); + return false; + } + + /* confirm overwrite */ + TFilePath frameFp(actualLevelFp.withFrame(frameNumber)); + if (TFileStatus(frameFp).doesExist()) { + if (!m_alwaysOverwrite) { + QString question = + tr("File %1 does exist.\nDo you want to overwrite it?") + .arg(toQString(frameFp)); + int ret = DVGui::MsgBox(question, QObject::tr("Overwrite"), + QObject::tr("Always Overwrite in This Scene"), + QObject::tr("Cancel")); + if (ret == 0 || ret == 3) return false; + if (ret == 2) m_alwaysOverwrite = true; + } + } + } + /* if the file does not exist, then create a new level */ + else { + TXshLevel* level = scene->createNewLevel(OVL_XSHLEVEL, levelName, + TDimension(), 0, levelFp); + sl = level->getSimpleLevel(); + sl->setPath(levelFp, true); + sl->getProperties()->setDpiPolicy(LevelProperties::DP_CustomDpi); + TPointD dpi; + // if the subcamera is not active or the pixel unit is used, apply the + // current camera dpi + if (!m_subcameraButton->isChecked() || + !m_videoWidget->subCameraRect().isValid() || + Preferences::instance()->getPixelsOnly()) + dpi = getCurrentCameraDpi(); + // if the subcamera is active, compute the dpi so that the image will fit + // to the camera frame + else { + TCamera* camera = + TApp::instance()->getCurrentScene()->getScene()->getCurrentCamera(); + TDimensionD size = camera->getSize(); + double minimumDpi = + std::min(image.width() / size.lx, image.height() / size.ly); + dpi = TPointD(minimumDpi, minimumDpi); + } + sl->getProperties()->setDpi(dpi.x); + sl->getProperties()->setImageDpi(dpi); + sl->getProperties()->setImageRes( + TDimension(image.width(), image.height())); + } + + state = NEWLEVEL; + } + + TFrameId fid(frameNumber); + TPointD levelDpi = sl->getDpi(); + /* create the raster */ + TRaster32P raster(image.width(), image.height()); + convertImageToRaster(raster, image.mirrored(true, true)); + + TRasterImageP ri(raster); + ri->setDpi(levelDpi.x, levelDpi.y); + /* setting the frame */ + sl->setFrame(fid, ri); + + /* set dirty flag */ + sl->getProperties()->setDirtyFlag(true); + + if (m_saveOnCaptureCB->isChecked()) sl->save(); + + /* placement in xsheet */ + + int row = app->getCurrentFrame()->getFrame(); + int col = app->getCurrentColumn()->getColumnIndex(); + + // if the level is newly created or imported, then insert a new column + if (state == NEWLEVEL) { + if (col < 0 || !xsh->isColumnEmpty(col)) { + col += 1; + xsh->insertColumn(col); + } + xsh->setCell(row, col, TXshCell(sl, fid)); + app->getCurrentColumn()->setColumnIndex(col); + return true; + } + + // state == OVERWRITE, ADDFRAME + + // if the same cell is already in the column, then just replace the content + // and do not set a new cell + int foundCol, foundRow = -1; + // most possibly, it's in the current column + int rowCheck; + if (findCell(xsh, col, TXshCell(sl, fid), rowCheck)) return true; + if (rowCheck >= 0) { + foundRow = rowCheck; + foundCol = col; + } + // search entire xsheet + for (int c = 0; c < xsh->getColumnCount(); c++) { + if (c == col) continue; + if (findCell(xsh, c, TXshCell(sl, fid), rowCheck)) return true; + if (rowCheck >= 0) { + foundRow = rowCheck; + foundCol = c; + } + } + // if there is a column containing the same level + if (foundRow >= 0) { + // put the cell at the bottom + int tmpRow = foundRow + 1; + while (1) { + if (xsh->getCell(tmpRow, foundCol).isEmpty()) { + xsh->setCell(tmpRow, foundCol, TXshCell(sl, fid)); + app->getCurrentColumn()->setColumnIndex(foundCol); + break; + } + tmpRow++; + } + } + // if the level is registered in the scene, but is not placed in the xsheet, + // then insert a new column + else { + if (!xsh->isColumnEmpty(col)) { + col += 1; + xsh->insertColumn(col); + } + xsh->setCell(row, col, TXshCell(sl, fid)); + app->getCurrentColumn()->setColumnIndex(col); + } + + return true; +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onCaptureFilterSettingsBtnPressed() { + if (!m_currentCamera || m_deviceName.isNull()) return; + + QList cameras = QCameraInfo::availableCameras(); + for (int c = 0; c < cameras.size(); c++) { + if (cameras.at(c).deviceName() == m_deviceName) { +#ifdef _WIN32 + openCaptureFilterSettings(this, cameras.at(c).description()); +#endif + return; + } + } +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::openSaveInFolderPopup() { + if (m_saveInFolderPopup->exec()) { + QString oldPath = m_saveInFileFld->getPath(); + m_saveInFileFld->setPath(m_saveInFolderPopup->getPath()); + if (oldPath == m_saveInFileFld->getPath()) + setToNextNewLevel(); + else { + onSaveInPathEdited(); + } + } +} + +//----------------------------------------------------------------------------- + +// Refresh information that how many & which frames are saved for the current +// level +void PencilTestPopup::refreshFrameInfo() { + if (!m_currentCamera || m_deviceName.isNull()) { + m_frameInfoLabel->setText(""); + return; + } + + QString tooltipStr, labelStr; + enum InfoType { NEW = 0, ADD, OVERWRITE, WARNING } infoType(WARNING); + + static QColor infoColors[4] = {Qt::cyan, Qt::green, Qt::yellow, Qt::red}; + + ToonzScene* currentScene = TApp::instance()->getCurrentScene()->getScene(); + TLevelSet* levelSet = currentScene->getLevelSet(); + + std::wstring levelName = m_levelNameEdit->text().toStdWString(); + int frameNumber = m_frameNumberEdit->getValue(); + + TDimension camRes; + if (m_subcameraButton->isChecked()) + camRes = TDimension(m_subWidthFld->getValue(), m_subHeightFld->getValue()); + else { + QStringList texts = m_resolutionCombo->currentText().split(' '); + if (texts.size() != 3) return; + camRes = TDimension(texts[0].toInt(), texts[2].toInt()); + } + + bool letterOptionEnabled = + Preferences::instance()->isShowFrameNumberWithLettersEnabled(); + + // level with the same name + TXshLevel* level_sameName = levelSet->getLevel(levelName); + + TFilePath levelFp = TFilePath(m_saveInFileFld->getPath()) + + TFilePath(levelName + L".." + + m_fileTypeCombo->currentText().toStdWString()); + + // level with the same path + TXshLevel* level_samePath = levelSet->getLevel(*(currentScene), levelFp); + + TFilePath actualLevelFp = currentScene->decodeFilePath(levelFp); + + // level existence + bool levelExist = TSystem::doesExistFileOrLevel(actualLevelFp); + + // frame existence + TFilePath frameFp(actualLevelFp.withFrame(frameNumber)); + bool frameExist = false; + if (levelExist) frameExist = TFileStatus(frameFp).doesExist(); + + // reset acceptable camera size + m_allowedCameraSize = QSize(); + + // ### CASE 1 ### + // If there is no same level registered in the scene cast + if (!level_sameName && !level_samePath) { + // If there is a level in the file system + if (levelExist) { + TLevelReaderP lr; + TLevelP level_p; + try { + lr = TLevelReaderP(actualLevelFp); + } catch (...) { + // TODO: output something + m_frameInfoLabel->setText(tr("UNDEFINED WARNING")); + return; + } + if (!lr) { + // TODO: output something + m_frameInfoLabel->setText(tr("UNDEFINED WARNING")); + return; + } + try { + level_p = lr->loadInfo(); + } catch (...) { + // TODO: output something + m_frameInfoLabel->setText(tr("UNDEFINED WARNING")); + return; + } + if (!level_p) { + // TODO: output something + m_frameInfoLabel->setText(tr("UNDEFINED WARNING")); + return; + } + int frameCount = level_p->getFrameCount(); + TLevel::Iterator it = level_p->begin(); + std::vector fids; + for (int i = 0; it != level_p->end(); ++it, ++i) + fids.push_back(it->first); + + tooltipStr += + tr("The level is not registered in the scene, but exists in the file " + "system."); + + // check resolution + const TImageInfo* ii; + try { + ii = lr->getImageInfo(fids[0]); + } catch (...) { + // TODO: output something + m_frameInfoLabel->setText(tr("UNDEFINED WARNING")); + return; + } + TDimension dim(ii->m_lx, ii->m_ly); + // if the saved images has not the same resolution as the current camera + // resolution + if (camRes != dim) { + tooltipStr += tr("\nWARNING : Image size mismatch. The saved image " + "size is %1 x %2.") + .arg(dim.lx) + .arg(dim.ly); + labelStr += tr("WARNING"); + infoType = WARNING; + } + // if the resolutions are matched + else { + if (frameCount == 1) + tooltipStr += tr("\nFrame %1 exists.") + .arg(fidsToString(fids, letterOptionEnabled)); + else + tooltipStr += tr("\nFrames %1 exist.") + .arg(fidsToString(fids, letterOptionEnabled)); + // if the frame exists, then it will be overwritten + if (frameExist) { + labelStr += tr("OVERWRITE 1 of"); + infoType = OVERWRITE; + } else { + labelStr += tr("ADD to"); + infoType = ADD; + } + if (frameCount == 1) + labelStr += tr(" %1 frame").arg(frameCount); + else + labelStr += tr(" %1 frames").arg(frameCount); + } + m_allowedCameraSize = QSize(dim.lx, dim.ly); + } + // If no level exists in the file system, then it will be a new level + else { + tooltipStr += tr("The level will be newly created."); + labelStr += tr("NEW"); + infoType = NEW; + } + } + // ### CASE 2 ### + // If there is already the level registered in the scene cast + else if (level_sameName && level_samePath && + level_sameName == level_samePath) { + tooltipStr += tr("The level is already registered in the scene."); + if (!levelExist) tooltipStr += tr("\nNOTE : The level is not saved."); + + std::vector fids; + level_sameName->getFids(fids); + + // check resolution + TDimension dim; + bool ret = getRasterLevelSize(level_sameName, dim); + if (!ret) { + tooltipStr += + tr("\nWARNING : Failed to get image size of the existing level %1.") + .arg(QString::fromStdWString(levelName)); + labelStr += tr("WARNING"); + infoType = WARNING; + } + // if the saved images has not the same resolution as the current camera + // resolution + else if (camRes != dim) { + tooltipStr += tr("\nWARNING : Image size mismatch. The existing level " + "size is %1 x %2.") + .arg(dim.lx) + .arg(dim.ly); + labelStr += tr("WARNING"); + infoType = WARNING; + } + // if the resolutions are matched + else { + int frameCount = fids.size(); + if (fids.size() == 1) + tooltipStr += tr("\nFrame %1 exists.") + .arg(fidsToString(fids, letterOptionEnabled)); + else + tooltipStr += tr("\nFrames %1 exist.") + .arg(fidsToString(fids, letterOptionEnabled)); + // Check if the target frame already exist in the level + bool hasFrame = false; + for (int f = 0; f < frameCount; f++) { + if (fids.at(f).getNumber() == frameNumber) { + hasFrame = true; + break; + } + } + // If there is already the frame then it will be overwritten + if (hasFrame) { + labelStr += tr("OVERWRITE 1 of"); + infoType = OVERWRITE; + } + // Or, the frame will be added to the level + else { + labelStr += tr("ADD to"); + infoType = ADD; + } + if (frameCount == 1) + labelStr += tr(" %1 frame").arg(frameCount); + else + labelStr += tr(" %1 frames").arg(frameCount); + } + m_allowedCameraSize = QSize(dim.lx, dim.ly); + } + // ### CASE 3 ### + // If there are some conflicts with the existing level. + else { + if (level_sameName) { + TFilePath anotherPath = level_sameName->getPath(); + tooltipStr += + tr("WARNING : Level name conflicts. There already is a level %1 in the scene with the path\ + \n %2.") + .arg(QString::fromStdWString(levelName)) + .arg(toQString(anotherPath)); + // check resolution + TDimension dim; + bool ret = getRasterLevelSize(level_sameName, dim); + if (ret && camRes != dim) + tooltipStr += tr("\nWARNING : Image size mismatch. The size of level " + "with the same name is is %1 x %2.") + .arg(dim.lx) + .arg(dim.ly); + m_allowedCameraSize = QSize(dim.lx, dim.ly); + } + if (level_samePath) { + std::wstring anotherName = level_samePath->getName(); + if (!tooltipStr.isEmpty()) tooltipStr += QString("\n"); + tooltipStr += + tr("WARNING : Level path conflicts. There already is a level with the path %1\ + \n in the scene with the name %2.") + .arg(toQString(levelFp)) + .arg(QString::fromStdWString(anotherName)); + // check resolution + TDimension dim; + bool ret = getRasterLevelSize(level_samePath, dim); + if (ret && camRes != dim) + tooltipStr += tr("\nWARNING : Image size mismatch. The size of level " + "with the same path is %1 x %2.") + .arg(dim.lx) + .arg(dim.ly); + m_allowedCameraSize = QSize(dim.lx, dim.ly); + } + labelStr += tr("WARNING"); + infoType = WARNING; + } + + QColor infoColor = infoColors[(int)infoType]; + m_frameInfoLabel->setStyleSheet(QString("QLabel{color: %1;}\ + QLabel QWidget{ color: black;}") + .arg(infoColor.name())); + m_frameInfoLabel->setText(labelStr); + m_frameInfoLabel->setToolTip(tooltipStr); +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onSaveInPathEdited() { + ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene(); + TFilePath saveInPath(m_saveInFileFld->getPath().toStdWString()); + scene->getProperties()->setCameraCaptureSaveInPath(saveInPath); + refreshFrameInfo(); +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onSceneSwitched() { + m_saveInFolderPopup->updateParentFolder(); + m_saveInFileFld->setPath(m_saveInFolderPopup->getParentPath()); + refreshFrameInfo(); + m_alwaysOverwrite = false; +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onSubCameraToggled(bool on) { + m_videoWidget->setSubCameraSize( + on ? QSize(m_subWidthFld->getValue(), m_subHeightFld->getValue()) + : QSize()); + refreshFrameInfo(); +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onSubCameraResized(bool isDragging) { + QSize subSize = m_videoWidget->subCameraRect().size(); + assert(subSize.isValid()); + m_subWidthFld->setValue(subSize.width()); + m_subHeightFld->setValue(subSize.height()); + if (!isDragging) refreshFrameInfo(); +} + +//----------------------------------------------------------------------------- + +void PencilTestPopup::onSubCameraSizeEdited() { + m_videoWidget->setSubCameraSize( + QSize(m_subWidthFld->getValue(), m_subHeightFld->getValue())); + refreshFrameInfo(); +} + +//----------------------------------------------------------------------------- + +OpenPopupCommandHandler openPencilTestPopup(MI_PencilTest); + +// specialized in order to call openSaveInFolderPopup() +template <> +void OpenPopupCommandHandler::execute() { + if (!m_popup) m_popup = new PencilTestPopup(); + m_popup->show(); + m_popup->raise(); + m_popup->activateWindow(); + if (CamCapOpenSaveInPopupOnLaunch != 0) m_popup->openSaveInFolderPopup(); +} diff --git a/toonz/sources/toonz/penciltestpopup_qt.h b/toonz/sources/toonz/penciltestpopup_qt.h new file mode 100644 index 0000000..7c27155 --- /dev/null +++ b/toonz/sources/toonz/penciltestpopup_qt.h @@ -0,0 +1,372 @@ +#pragma once + +#ifndef PENCILTESTPOPUP_H +#define PENCILTESTPOPUP_H + +#include "toonzqt/dvdialog.h" +#include "toonzqt/lineedit.h" +#include "toonz/namebuilder.h" + +#include +#include + +// forward decl. +class QCamera; +class QCameraImageCapture; + +class QComboBox; +class QLineEdit; +class QSlider; +class QCheckBox; +class QPushButton; +class QVideoFrame; +class QTimer; +class QIntValidator; +class QRegExpValidator; +class QPushButton; +#ifdef MACOSX +class QCameraViewfinder; +#endif + +namespace DVGui { +class FileField; +class IntField; +class IntLineEdit; +} // namespace DVGui + +class CameraCaptureLevelControl; + +class ApplyLutTask : public QRunnable { +protected: + int m_fromY, m_toY; + QImage& m_img; + std::vector& m_lut; + +public: + ApplyLutTask(int from, int to, QImage& img, std::vector& lut) + : m_fromY(from), m_toY(to), m_img(img), m_lut(lut) {} + +private: + virtual void run() override; +}; + +class ApplyGrayLutTask : public ApplyLutTask { +public: + ApplyGrayLutTask(int from, int to, QImage& img, std::vector& lut) + : ApplyLutTask(from, to, img, lut) {} + +private: + void run() override; +}; + +//============================================================================= +// MyVideoSurface +//----------------------------------------------------------------------------- + +class QVideoSurfaceFormat; +class MyVideoSurface : public QAbstractVideoSurface { + Q_OBJECT +public: + MyVideoSurface(QWidget* widget, QObject* parent = 0); + + QList supportedPixelFormats( + QAbstractVideoBuffer::HandleType handleType = + QAbstractVideoBuffer::NoHandle) const; + bool isFormatSupported(const QVideoSurfaceFormat& format, + QVideoSurfaceFormat* similar) const; + + bool start(const QVideoSurfaceFormat& format); + void stop(); + + bool present(const QVideoFrame& frame); + + QRect videoRect() const { return m_targetRect; } + QRect sourceRect() const { return m_sourceRect; } + void updateVideoRect(); + + QTransform transform() { return m_S2V_Transform; } + +private: + QWidget* m_widget; + QImage::Format m_imageFormat; + QRect m_targetRect; + QSize m_imageSize; + QRect m_sourceRect; + QVideoFrame m_currentFrame; + + QTransform m_S2V_Transform; // surface to video transform + +signals: + void frameCaptured(QImage& image); +}; + +//============================================================================= +// MyVideoWidget +//----------------------------------------------------------------------------- + +class MyVideoWidget : public QWidget { + Q_OBJECT + + QImage m_image; + QImage m_previousImage; + int m_countDownTime; + bool m_showOnionSkin; + int m_onionOpacity; + bool m_upsideDown; + + QRect m_subCameraRect; + QRect m_preSubCameraRect; + QPoint m_dragStartPos; + + enum SUBHANDLE { + HandleNone, + HandleFrame, + HandleTopLeft, + HandleTopRight, + HandleBottomLeft, + HandleBottomRight, + HandleLeft, + HandleTop, + HandleRight, + HandleBottom + } m_activeSubHandle = HandleNone; + void drawSubCamera(QPainter&); + +public: + MyVideoWidget(QWidget* parent = 0); + ~MyVideoWidget(); + + void setImage(const QImage& image) { + m_image = image; + update(); + } + QAbstractVideoSurface* videoSurface() const { return m_surface; } + + QSize sizeHint() const; + + void setShowOnionSkin(bool on) { m_showOnionSkin = on; } + void setOnionOpacity(int value) { m_onionOpacity = value; } + void setPreviousImage(QImage prevImage) { m_previousImage = prevImage; } + + void showCountDownTime(int time) { + m_countDownTime = time; + repaint(); + } + + void setSubCameraSize(QSize size); + QRect subCameraRect() { return m_subCameraRect; } + +protected: + void paintEvent(QPaintEvent* event) override; + void resizeEvent(QResizeEvent* event) override; + + void mouseMoveEvent(QMouseEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + +private: + MyVideoSurface* m_surface; + +protected slots: + void onUpsideDownChecked(bool on) { m_upsideDown = on; } + +signals: + void startCamera(); + void stopCamera(); + void subCameraResized(bool isDragging); +}; + +//============================================================================= +// FrameNumberLineEdit +// a special Line Edit which accepts imputting alphabets if the preference +// option +// "Show ABC Appendix to the Frame Number in Xsheet Cell" is active. +//----------------------------------------------------------------------------- + +class FrameNumberLineEdit : public DVGui::LineEdit { + Q_OBJECT + /* having two validators and switch them according to the preferences*/ + QIntValidator* m_intValidator; + QRegExpValidator* m_regexpValidator; + + void updateValidator(); + QString m_textOnFocusIn; + +public: + FrameNumberLineEdit(QWidget* parent = 0, int value = 1); + ~FrameNumberLineEdit() {} + + /*! Set text in field to \b value. */ + void setValue(int value); + /*! Return an integer with text field value. */ + int getValue(); + +protected: + /*! If focus is lost and current text value is out of range emit signal + \b editingFinished.*/ + void focusInEvent(QFocusEvent*) override; + void focusOutEvent(QFocusEvent*) override; + void showEvent(QShowEvent* event) override { updateValidator(); } +}; + +//============================================================================= + +class LevelNameLineEdit : public QLineEdit { + Q_OBJECT + QString m_textOnFocusIn; + +public: + LevelNameLineEdit(QWidget* parent = 0); + +protected: + void focusInEvent(QFocusEvent* e); + +protected slots: + void onEditingFinished(); + +signals: + void levelNameEdited(); +}; + +//============================================================================= +// FlexibleNameCreator +// Inherits NameCreator, added function for obtaining the previous name and +// setting the current name. + +class FlexibleNameCreator final : public NameCreator { +public: + FlexibleNameCreator() {} + std::wstring getPrevious(); + bool setCurrent(std::wstring name); +}; + +//============================================================================= +// PencilTestSaveInFolderPopup +//----------------------------------------------------------------------------- + +class PencilTestSaveInFolderPopup : public DVGui::Dialog { + Q_OBJECT + + DVGui::FileField* m_parentFolderField; + QLineEdit *m_projectField, *m_episodeField, *m_sequenceField, *m_sceneField, + *m_subFolderNameField; + + QCheckBox *m_subFolderCB, *m_autoSubNameCB, *m_createSceneInFolderCB; + QComboBox* m_subNameFormatCombo; + + void createSceneInFolder(); + +public: + PencilTestSaveInFolderPopup(QWidget* parent = 0); + QString getPath(); + QString getParentPath(); + void updateParentFolder(); + +protected: + void showEvent(QShowEvent* event); + +protected slots: + void updateSubFolderName(); + void onAutoSubNameCBClicked(bool); + void onShowPopupOnLaunchCBClicked(bool); + void onCreateSceneInFolderCBClicked(bool); + void onSetAsDefaultBtnPressed(); + void onOkPressed(); +}; + +//============================================================================= +// PencilTestPopup +//----------------------------------------------------------------------------- + +class PencilTestPopup : public DVGui::Dialog { + Q_OBJECT + + QCamera* m_currentCamera; + QString m_deviceName; + MyVideoWidget* m_videoWidget; + + QComboBox *m_cameraListCombo, *m_resolutionCombo, *m_fileTypeCombo, + *m_colorTypeCombo; + LevelNameLineEdit* m_levelNameEdit; + QCheckBox *m_upsideDownCB, *m_onionSkinCB, *m_saveOnCaptureCB, *m_timerCB; + QPushButton *m_fileFormatOptionButton, *m_captureWhiteBGButton, + *m_captureButton, *m_loadImageButton; + DVGui::FileField* m_saveInFileFld; + FrameNumberLineEdit* m_frameNumberEdit; + DVGui::IntField *m_bgReductionFld, *m_onionOpacityFld, *m_timerIntervalFld; + + QTimer *m_captureTimer, *m_countdownTimer; + + QImage m_whiteBGImg; + + // used only for Windows + QPushButton* m_captureFilterSettingsBtn; + + PencilTestSaveInFolderPopup* m_saveInFolderPopup; + + CameraCaptureLevelControl* m_camCapLevelControl; + + QLabel* m_frameInfoLabel; + + QToolButton* m_previousLevelButton; + + QPushButton* m_subcameraButton; + DVGui::IntLineEdit *m_subWidthFld, *m_subHeightFld; + QSize m_allowedCameraSize; + + bool m_captureWhiteBGCue; + bool m_captureCue; + bool m_alwaysOverwrite = false; + + void processImage(QImage& procImage); + bool importImage(QImage image); + + void setToNextNewLevel(); + void updateLevelNameAndFrame(std::wstring levelName); + +public: + PencilTestPopup(); + ~PencilTestPopup(); + +protected: + void showEvent(QShowEvent* event); + void hideEvent(QHideEvent* event); + void keyPressEvent(QKeyEvent* event); + + bool event(QEvent* e) override; + +protected slots: + void refreshCameraList(); + void onCameraListComboActivated(int index); + void onResolutionComboActivated(const QString&); + void onFileFormatOptionButtonPressed(); + void onLevelNameEdited(); + void onNextName(); + void onPreviousName(); + void onColorTypeComboChanged(int index); + void onFrameCaptured(QImage& image); + void onCaptureWhiteBGButtonPressed(); + void onOnionCBToggled(bool); + void onLoadImageButtonPressed(); + void onOnionOpacityFldEdited(); + void onTimerCBToggled(bool); + void onCaptureTimerTimeout(); + void onCountDown(); + + void onCaptureButtonClicked(bool); + void onCaptureFilterSettingsBtnPressed(); + + void refreshFrameInfo(); + + void onSaveInPathEdited(); + void onSceneSwitched(); + + void onSubCameraToggled(bool); + void onSubCameraResized(bool isDragging); + void onSubCameraSizeEdited(); + +public slots: + void openSaveInFolderPopup(); +}; + +#endif \ No newline at end of file