@@ -118,6 +118,7 @@ constexpr int kDefaultFrameHeight = 32;
118118constexpr int kDefaultSpriteWidth = 64;
119119constexpr int kDefaultSpriteHeight = 64;
120120constexpr int kPreviewIconWidth = 160;
121+ constexpr int kMonochromeTriggerId = 65432;
121122constexpr int kPreviewIconHeight = 120;
122123constexpr int kPreviewItemWidth = 180;
123124constexpr int kPreviewItemHeight = 150;
@@ -1179,6 +1180,7 @@ MainWindow::MainWindow(QWidget* parent)
11791180 m_frameStore->clear();
11801181 m_spriteStore->clear();
11811182 m_frameDurations.clear();
1183+ m_frameTriggerIds.clear();
11821184 m_spriteNames.clear();
11831185 m_spriteColored.clear();
11841186 m_spriteColoredX.clear();
@@ -1296,6 +1298,10 @@ MainWindow::MainWindow(QWidget* parent)
12961298 m_serumData.nframes = static_cast<uint32_t>(m_frameStore->count());
12971299 }
12981300 m_state->addFrame();
1301+ m_frameTriggerIds.push_back(0xffffffffu);
1302+ if (m_hasLegacyRoundTrip) {
1303+ m_legacyRoundTrip.trigger_ids = m_frameTriggerIds;
1304+ }
12991305 ensureUndoStacksSize();
13001306 if (m_framesList->count() > 0) {
13011307 m_framesList->setCurrentRow(m_framesList->count() - 1);
@@ -1400,6 +1406,12 @@ MainWindow::MainWindow(QWidget* parent)
14001406 if (row >= 0 && row < static_cast<int>(m_frameDynamicMaskMapsX.size())) {
14011407 m_frameDynamicMaskMapsX.erase(m_frameDynamicMaskMapsX.begin() + row);
14021408 }
1409+ if (row >= 0 && row < static_cast<int>(m_frameTriggerIds.size())) {
1410+ m_frameTriggerIds.erase(m_frameTriggerIds.begin() + row);
1411+ if (m_hasLegacyRoundTrip) {
1412+ m_legacyRoundTrip.trigger_ids = m_frameTriggerIds;
1413+ }
1414+ }
14031415 ensureUndoStacksSize();
14041416 } else if (m_spritesList->hasFocus()) {
14051417 const int row = m_spritesList->currentRow();
@@ -2370,6 +2382,14 @@ MainWindow::MainWindow(QWidget* parent)
23702382 m_frameBackgroundAssign = new QComboBox(inspectorWidget);
23712383 m_backgroundAssignLabel = new QLabel("Background", inspectorWidget);
23722384 m_shapeCompToggle = new QCheckBox("Shape comparison", inspectorWidget);
2385+ m_triggerIdSpin = new QSpinBox(inspectorWidget);
2386+ m_triggerIdSpin->setMinimum(-1);
2387+ m_triggerIdSpin->setMaximum(kMonochromeTriggerId);
2388+ m_triggerIdSpin->setSpecialValueText("None");
2389+ m_triggerIdSpin->setKeyboardTracking(false);
2390+ m_triggerIdSpin->setEnabled(false);
2391+ m_triggerMonochromeCheck = new QCheckBox("Switch to Monochrome", inspectorWidget);
2392+ m_triggerMonochromeCheck->setEnabled(false);
23732393 m_hdSourceCombo = new QComboBox(inspectorWidget);
23742394 m_hdScaleCombo = new QComboBox(inspectorWidget);
23752395 m_hdCreateButton = new QPushButton("Create HD", inspectorWidget);
@@ -2385,6 +2405,8 @@ MainWindow::MainWindow(QWidget* parent)
23852405 inspectorLayout->addRow("Counts", m_countsLabel);
23862406 inspectorLayout->addRow("Selection", m_selectionLabel);
23872407 inspectorLayout->addRow("Frame info", m_frameMetaLabel);
2408+ inspectorLayout->addRow("Trigger ID", m_triggerIdSpin);
2409+ inspectorLayout->addRow(m_triggerMonochromeCheck);
23882410 inspectorLayout->addRow("Mask", m_frameMaskAssign);
23892411 inspectorLayout->addRow("Dynamic mask", m_frameDynamicMaskAssign);
23902412 inspectorLayout->addRow("Dynamic copy", m_frameDynamicCopyButton);
@@ -2590,6 +2612,73 @@ MainWindow::MainWindow(QWidget* parent)
25902612 m_frameShapeCompModes[static_cast<std::size_t>(row)] = enabled ? 1 : 0;
25912613 }
25922614 });
2615+ connect(m_triggerMonochromeCheck, &QCheckBox::toggled, this, [this](bool enabled) {
2616+ if (!m_triggerIdSpin) {
2617+ return;
2618+ }
2619+ const int frameCount = m_frameStore ? m_frameStore->count() : 0;
2620+ if (frameCount <= 0) {
2621+ return;
2622+ }
2623+ if (m_frameTriggerIds.size() < static_cast<std::size_t>(frameCount)) {
2624+ m_frameTriggerIds.resize(static_cast<std::size_t>(frameCount), 0xffffffffu);
2625+ }
2626+ const uint32_t triggerValue = enabled ? static_cast<uint32_t>(kMonochromeTriggerId) : 0xffffffffu;
2627+ const std::vector<int> targets = targetFrameIndices();
2628+ if (targets.empty()) {
2629+ return;
2630+ }
2631+ for (int row : targets) {
2632+ if (row < 0 || row >= static_cast<int>(m_frameTriggerIds.size())) {
2633+ continue;
2634+ }
2635+ m_frameTriggerIds[static_cast<std::size_t>(row)] = triggerValue;
2636+ }
2637+ if (m_hasLegacyRoundTrip) {
2638+ m_legacyRoundTrip.trigger_ids = m_frameTriggerIds;
2639+ }
2640+ QSignalBlocker blockSpin(m_triggerIdSpin);
2641+ if (enabled) {
2642+ m_triggerIdSpin->setValue(kMonochromeTriggerId);
2643+ m_triggerIdSpin->setReadOnly(true);
2644+ } else {
2645+ m_triggerIdSpin->setReadOnly(false);
2646+ if (m_triggerIdSpin->value() >= kMonochromeTriggerId) {
2647+ m_triggerIdSpin->setValue(-1);
2648+ }
2649+ }
2650+ });
2651+ connect(m_triggerIdSpin, QOverload<int>::of(&QSpinBox::valueChanged), this, [this](int value) {
2652+ if (!m_triggerMonochromeCheck || m_triggerMonochromeCheck->isChecked()) {
2653+ return;
2654+ }
2655+ if (value >= kMonochromeTriggerId) {
2656+ QSignalBlocker blockSpin(m_triggerIdSpin);
2657+ m_triggerIdSpin->setValue(kMonochromeTriggerId - 1);
2658+ value = kMonochromeTriggerId - 1;
2659+ }
2660+ const int frameCount = m_frameStore ? m_frameStore->count() : 0;
2661+ if (frameCount <= 0) {
2662+ return;
2663+ }
2664+ if (m_frameTriggerIds.size() < static_cast<std::size_t>(frameCount)) {
2665+ m_frameTriggerIds.resize(static_cast<std::size_t>(frameCount), 0xffffffffu);
2666+ }
2667+ const uint32_t triggerValue = (value < 0) ? 0xffffffffu : static_cast<uint32_t>(value);
2668+ const std::vector<int> targets = targetFrameIndices();
2669+ if (targets.empty()) {
2670+ return;
2671+ }
2672+ for (int row : targets) {
2673+ if (row < 0 || row >= static_cast<int>(m_frameTriggerIds.size())) {
2674+ continue;
2675+ }
2676+ m_frameTriggerIds[static_cast<std::size_t>(row)] = triggerValue;
2677+ }
2678+ if (m_hasLegacyRoundTrip) {
2679+ m_legacyRoundTrip.trigger_ids = m_frameTriggerIds;
2680+ }
2681+ });
25932682 connect(m_spriteDetAreaCombo, &QComboBox::currentIndexChanged, this, [this](int index) {
25942683 if (index < 0 || index >= MAX_SPRITE_DETECT_AREAS) {
25952684 return;
@@ -4751,6 +4840,7 @@ void MainWindow::openProjectFile(const QString& filename)
47514840 m_spriteStore->clear();
47524841 m_backgroundStore->clear();
47534842 m_frameDurations.clear();
4843+ m_frameTriggerIds.clear();
47544844 m_spriteNames.clear();
47554845 m_spriteColored.clear();
47564846 m_spriteColoredX.clear();
@@ -4814,6 +4904,15 @@ void MainWindow::openProjectFile(const QString& filename)
48144904 }
48154905 }
48164906 m_frameDurations = legacy.frame_durations;
4907+ m_frameTriggerIds = legacy.trigger_ids;
4908+ if (m_frameTriggerIds.size() < legacy.frames.size()) {
4909+ m_frameTriggerIds.resize(legacy.frames.size(), 0xffffffffu);
4910+ } else if (m_frameTriggerIds.size() > legacy.frames.size()) {
4911+ m_frameTriggerIds.resize(legacy.frames.size());
4912+ }
4913+ if (m_hasLegacyRoundTrip) {
4914+ m_legacyRoundTrip.trigger_ids = m_frameTriggerIds;
4915+ }
48174916 m_spriteNames = legacy.sprite_labels;
48184917 if (!m_serumDataLoaded) {
48194918 m_spriteColored = legacy.sprite_colored;
@@ -5118,11 +5217,14 @@ LegacyProject MainWindow::buildLegacyProject(const QString& baseName) const
51185217 }
51195218
51205219 const int frameCount = m_frameStore->count();
5220+ if (!m_frameTriggerIds.empty()) {
5221+ project.trigger_ids = m_frameTriggerIds;
5222+ }
51215223 if (m_hasLegacyRoundTrip) {
51225224 project.hash_codes.resize(static_cast<std::size_t>(frameCount), 0);
51235225 project.active_frames.resize(static_cast<std::size_t>(frameCount), 0);
5124- project.trigger_ids.resize(static_cast<std::size_t>(frameCount), 0xffffffffu);
51255226 }
5227+ project.trigger_ids.resize(static_cast<std::size_t>(frameCount), 0xffffffffu);
51265228 project.frames.resize(static_cast<std::size_t>(frameCount));
51275229 cv::Size baseSize(kDefaultFrameWidth, kDefaultFrameHeight);
51285230 for (int i = 0; i < frameCount; ++i) {
@@ -5723,6 +5825,60 @@ void MainWindow::jumpPlayback(int index)
57235825 }
57245826}
57255827
5828+ void MainWindow::updatePlaybackIdleFrame(int index)
5829+ {
5830+ if (!m_playbackCanvas || m_playbackActive) {
5831+ return;
5832+ }
5833+ if (index < 0) {
5834+ m_playbackCanvas->setTitle("Playback");
5835+ m_playbackCanvas->setImage(cv::Mat());
5836+ m_playbackCanvas->canvas()->setGridSegments(0, 0, 0);
5837+ m_playbackCanvas->canvas()->setGridScales(1, 1);
5838+ m_playbackCanvas->canvas()->setGridRegions(QRect(), QRect());
5839+ return;
5840+ }
5841+ const bool useHd = m_useHdFrame && hasHdFrame(index);
5842+ const cv::Mat composed = renderFrameWithSerum(index, useHd);
5843+ if (composed.empty()) {
5844+ m_playbackCanvas->setImage(cv::Mat());
5845+ m_playbackCanvas->canvas()->setGridSegments(0, 0, 0);
5846+ m_playbackCanvas->canvas()->setGridScales(1, 1);
5847+ m_playbackCanvas->canvas()->setGridRegions(QRect(), QRect());
5848+ return;
5849+ }
5850+ cv::Mat reference = buildOriginalPreviewForIndex(index);
5851+ cv::Mat original;
5852+ if (m_playbackShowOriginal && !reference.empty()) {
5853+ original = buildOriginalFrame(reference);
5854+ }
5855+ const QColor gap = m_playbackCanvas
5856+ ? m_playbackCanvas->palette().color(QPalette::Window)
5857+ : QApplication::palette().color(QPalette::Window);
5858+ const cv::Scalar gapColor(gap.blue(), gap.green(), gap.red());
5859+ const cv::Mat combined = (!original.empty())
5860+ ? buildCombinedFrame(composed, original, gapColor)
5861+ : EnsureBgr(composed);
5862+ m_playbackCanvas->setTitle(QString("Playback (frame %1)").arg(index + 1));
5863+ m_playbackCanvas->setImage(combined);
5864+ if (!original.empty()) {
5865+ const int gapPixels = FrameGapForWidth(composed.cols);
5866+ m_playbackCanvas->canvas()->setGridSegments(composed.rows, gapPixels, original.rows);
5867+ m_playbackCanvas->canvas()->setGridScales(1, 1);
5868+ FrameLayout layout = BuildFrameLayout(composed, original);
5869+ const QRect topRegion(layout.topX, 0, layout.topWidth, layout.topHeight);
5870+ const QRect bottomRegion(layout.bottomX,
5871+ layout.topHeight + gapPixels,
5872+ layout.bottomWidth,
5873+ layout.bottomHeight);
5874+ m_playbackCanvas->canvas()->setGridRegions(topRegion, bottomRegion);
5875+ } else {
5876+ m_playbackCanvas->canvas()->setGridSegments(0, 0, 0);
5877+ m_playbackCanvas->canvas()->setGridScales(1, 1);
5878+ m_playbackCanvas->canvas()->setGridRegions(QRect(), QRect());
5879+ }
5880+ }
5881+
57265882void MainWindow::renderPlaybackFrame()
57275883{
57285884 if (!m_playbackActive || m_playbackFrames.empty() || !m_playbackCanvas) {
@@ -5867,7 +6023,7 @@ void MainWindow::advancePlaybackFrame()
58676023
58686024void MainWindow::updatePlaybackButtons()
58696025{
5870- const bool hasFrames = !m_frameDurations.empty() ;
6026+ const bool hasFrames = m_frameStore && m_frameStore->count() > 0 ;
58716027 if (m_previewPlayButton) {
58726028 m_previewPlayButton->setEnabled(hasFrames);
58736029 }
@@ -9474,6 +9630,12 @@ void MainWindow::ensureMaskDataSize()
94749630 m_frameDynamicCopyButton->setEnabled(hasFrames);
94759631 }
94769632 m_shapeCompToggle->setEnabled(hasFrames);
9633+ if (m_triggerIdSpin) {
9634+ m_triggerIdSpin->setEnabled(hasFrames);
9635+ }
9636+ if (m_triggerMonochromeCheck) {
9637+ m_triggerMonochromeCheck->setEnabled(hasFrames);
9638+ }
94779639 if (m_framesCanvas) {
94789640 m_framesCanvas->setMaskButtonsEnabled(hasFrames);
94799641 m_framesCanvas->setBackgroundMaskEnabled(hasFrames);
@@ -14539,8 +14701,8 @@ void MainWindow::refreshFrameSpriteLists()
1453914701 const QColor gap = m_spritesList->palette().color(QPalette::Window);
1454014702 const QStringList spriteNames = m_state->sprites();
1454114703 for (int i = 0; i < spriteNames.size(); ++i) {
14542- const cv::Mat* image = m_spriteStore->at (i);
14543- if (! image || image-> empty()) {
14704+ const cv::Mat image = m_spriteStore->loadCopy (i);
14705+ if (image. empty()) {
1454414706 continue;
1454514707 }
1454614708 cv::Mat hd;
@@ -14549,7 +14711,7 @@ void MainWindow::refreshFrameSpriteLists()
1454914711 hd = *hdSprite;
1455014712 }
1455114713 }
14552- cv::Mat previewMat = BuildBackgroundPreview(* image,
14714+ cv::Mat previewMat = BuildBackgroundPreview(image,
1455314715 hd,
1455414716 cv::Scalar(gap.blue(), gap.green(), gap.red()));
1455514717 if (previewMat.empty()) {
@@ -15151,6 +15313,7 @@ void MainWindow::showFrameAtIndex(int index)
1515115313 if (image && !image->empty()) {
1515215314 m_framesCanvas->canvas()->clearPreviewImage();
1515315315 updateFrameCanvasImage(index);
15316+ updatePlaybackIdleFrame(index);
1515415317 if (index == 0 && m_drawPointEnabled == false) {
1515515318 QTimer::singleShot(0, this, [this]() {
1515615319 m_framesCanvas->canvas()->requestFitOnResize(true);
@@ -15176,6 +15339,31 @@ void MainWindow::showFrameAtIndex(int index)
1517615339 const uint8_t value = m_frameShapeCompModes[static_cast<std::size_t>(index)];
1517715340 m_shapeCompToggle->setChecked(value != 0);
1517815341 }
15342+ if (m_triggerIdSpin && m_triggerMonochromeCheck) {
15343+ QSignalBlocker blockSpin(m_triggerIdSpin);
15344+ QSignalBlocker blockCheck(m_triggerMonochromeCheck);
15345+ if (index >= 0 && index < static_cast<int>(m_frameTriggerIds.size())) {
15346+ const uint32_t trigger = m_frameTriggerIds[static_cast<std::size_t>(index)];
15347+ const bool isMono = trigger == static_cast<uint32_t>(kMonochromeTriggerId);
15348+ m_triggerMonochromeCheck->setChecked(isMono);
15349+ m_triggerMonochromeCheck->setEnabled(true);
15350+ m_triggerIdSpin->setEnabled(true);
15351+ m_triggerIdSpin->setReadOnly(isMono);
15352+ if (isMono) {
15353+ m_triggerIdSpin->setValue(kMonochromeTriggerId);
15354+ } else if (trigger == 0xffffffffu) {
15355+ m_triggerIdSpin->setValue(-1);
15356+ } else {
15357+ m_triggerIdSpin->setValue(static_cast<int>(trigger));
15358+ }
15359+ } else {
15360+ m_triggerMonochromeCheck->setChecked(false);
15361+ m_triggerMonochromeCheck->setEnabled(false);
15362+ m_triggerIdSpin->setEnabled(false);
15363+ m_triggerIdSpin->setReadOnly(false);
15364+ m_triggerIdSpin->setValue(-1);
15365+ }
15366+ }
1517915367 const bool hasHd = hasHdFrame(index);
1518015368 if (!hasHd && m_useHdFrame) {
1518115369 m_useHdFrame = false;
@@ -15194,11 +15382,23 @@ void MainWindow::showFrameAtIndex(int index)
1519415382 refreshDynamicPaletteButtons();
1519515383 refreshRotationEditor();
1519615384 updateFrameUsageHighlights(index);
15385+ updatePlaybackButtons();
1519715386 } else {
1519815387 updateFrameCanvasImage(-1);
15388+ updatePlaybackIdleFrame(-1);
15389+ if (m_triggerIdSpin && m_triggerMonochromeCheck) {
15390+ QSignalBlocker blockSpin(m_triggerIdSpin);
15391+ QSignalBlocker blockCheck(m_triggerMonochromeCheck);
15392+ m_triggerMonochromeCheck->setChecked(false);
15393+ m_triggerMonochromeCheck->setEnabled(false);
15394+ m_triggerIdSpin->setEnabled(false);
15395+ m_triggerIdSpin->setReadOnly(false);
15396+ m_triggerIdSpin->setValue(-1);
15397+ }
1519915398 refreshFrameSpriteSlotCombo();
1520015399 refreshRotationEditor();
1520115400 updateFrameUsageHighlights(-1);
15401+ updatePlaybackButtons();
1520215402 }
1520315403}
1520415404
0 commit comments