#include "preview-dialog.hpp" #include "opencv-helpers.hpp" #include "ui-helpers.hpp" #include #include namespace advss { PreviewDialog::PreviewDialog(QWidget *parent) : QDialog(parent), _scrollArea(new QScrollArea), _imageLabel(new QLabel(this)), _rubberBand(new QRubberBand(QRubberBand::Rectangle, this)) { setWindowTitle("Advanced Scene Switcher"); setWindowFlags(windowFlags() | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint); _valueLabel = new QLabel(""); _statusLabel = new QLabel(obs_module_text( "AdvSceneSwitcher.condition.video.showMatch.loading")); resize(parent->window()->size()); // Center image auto wrapper = new QWidget(); auto wrapperHLayout = new QHBoxLayout(); wrapperHLayout->addStretch(); wrapperHLayout->addWidget(_imageLabel); wrapperHLayout->addStretch(); auto wrapperVLayout = new QVBoxLayout(); wrapperVLayout->addStretch(); wrapperVLayout->addLayout(wrapperHLayout); wrapperVLayout->addStretch(); wrapper->setLayout(wrapperVLayout); _scrollArea->setBackgroundRole(QPalette::Dark); _scrollArea->setWidget(wrapper); _scrollArea->setWidgetResizable(true); QVBoxLayout *layout = new QVBoxLayout; layout->addWidget(_valueLabel); layout->addWidget(_statusLabel); layout->addWidget(_scrollArea); setLayout(layout); } void PreviewDialog::mousePressEvent(QMouseEvent *event) { _selectingArea = true; if (_type == PreviewType::SHOW_MATCH) { return; } _origin = event->pos(); _rubberBand->setGeometry(QRect(_origin, QSize())); _rubberBand->show(); } void PreviewDialog::mouseMoveEvent(QMouseEvent *event) { if (_type == PreviewType::SHOW_MATCH) { return; } _rubberBand->setGeometry(QRect(_origin, event->pos()).normalized()); } void PreviewDialog::mouseReleaseEvent(QMouseEvent *) { if (_type == PreviewType::SHOW_MATCH) { return; } auto selectionStart = _rubberBand->mapToGlobal(_rubberBand->rect().topLeft()); QRect selectionArea(selectionStart, _rubberBand->size()); auto imageStart = _imageLabel->mapToGlobal(_imageLabel->rect().topLeft()); QRect imageArea(imageStart, _imageLabel->size()); auto intersected = imageArea.intersected(selectionArea); QRect checksize(QPoint(intersected.topLeft() - imageStart), intersected.size()); if (checksize.x() >= 0 && checksize.y() >= 0 && checksize.width() > 0 && checksize.height() > 0) { emit SelectionAreaChanged(checksize); } _selectingArea = false; } PreviewDialog::~PreviewDialog() { Stop(); } void PreviewDialog::ShowMatch() { _type = PreviewType::SHOW_MATCH; _rubberBand->hide(); _valueLabel->show(); Start(); _statusLabel->setText(""); } void PreviewDialog::SelectArea() { _selectingArea = false; _type = PreviewType::SELECT_AREA; _rubberBand->show(); _valueLabel->hide(); Start(); DrawFrame(); _statusLabel->setText(obs_module_text( "AdvSceneSwitcher.condition.video.selectArea.status")); } void PreviewDialog::Stop() { _thread.quit(); _thread.wait(); } void PreviewDialog::closeEvent(QCloseEvent *) { Stop(); } void PreviewDialog::PatternMatchParametersChanged( const PatternMatchParameters ¶ms) { std::unique_lock lock(_mtx); _patternMatchParams = params; _patternImageData = CreatePatternData(_patternMatchParams.image); } void PreviewDialog::ObjDetectParametersChanged(const ObjDetectParameters ¶ms) { std::unique_lock lock(_mtx); _objDetectParams = std::make_shared(params); } void PreviewDialog::OCRParametersChanged(const OCRParameters ¶ms) { std::unique_lock lock(_mtx); _ocrParams = std::make_shared(params); } void PreviewDialog::VideoSelectionChanged(const VideoInput &video) { std::unique_lock lock(_mtx); _video = video; } void PreviewDialog::AreaParametersChanged(const AreaParameters ¶ms) { std::unique_lock lock(_mtx); _areaParams = params; } void PreviewDialog::ConditionChanged(int cond) { Stop(); close(); std::unique_lock lock(_mtx); _condition = static_cast(cond); } void PreviewDialog::UpdateValue(double matchValue) { std::string temp = obs_module_text( "AdvSceneSwitcher.condition.video.patternMatchValue"); temp += "%.3f"; _valueLabel->setText(QString::asprintf(temp.c_str(), matchValue)); } void PreviewDialog::UpdateStatus(const QString &status) { _statusLabel->setText(status); } void PreviewDialog::UpdateImage(const QPixmap &image) { _imageLabel->setPixmap(image); _imageLabel->adjustSize(); if (_type == PreviewType::SELECT_AREA && !_selectingArea) { DrawFrame(); } emit NeedImage(_video, _type, _patternMatchParams, _patternImageData, _objDetectParams, _ocrParams, _areaParams, _condition); } void PreviewDialog::Start() { if (!_video.ValidSelection()) { DisplayMessage(obs_module_text( "AdvSceneSwitcher.condition.video.screenshotFail")); close(); return; } if (_thread.isRunning()) { return; } auto worker = new PreviewImage(_mtx); worker->moveToThread(&_thread); connect(&_thread, &QThread::finished, worker, &QObject::deleteLater); connect(worker, &PreviewImage::ImageReady, this, &PreviewDialog::UpdateImage); connect(worker, &PreviewImage::StatusUpdate, this, &PreviewDialog::UpdateStatus); connect(worker, &PreviewImage::ValueUpdate, this, &PreviewDialog::UpdateValue); connect(this, &PreviewDialog::NeedImage, worker, &PreviewImage::CreateImage); _thread.start(); emit NeedImage(_video, _type, _patternMatchParams, _patternImageData, _objDetectParams, _ocrParams, _areaParams, _condition); } void PreviewDialog::DrawFrame() { if (!_video.ValidSelection()) { return; } auto imageStart = _imageLabel->mapToGlobal(_imageLabel->rect().topLeft()); auto windowStart = mapToGlobal(rect().topLeft()); _rubberBand->resize(_areaParams.area.width, _areaParams.area.height); _rubberBand->move( _areaParams.area.x + (imageStart.x() - windowStart.x()), _areaParams.area.y + (imageStart.y() - windowStart.y())); _rubberBand->show(); } static void markPatterns(cv::Mat &matchResult, QImage &image, const cv::Mat &pattern) { auto matchImg = QImageToMat(image); for (int row = 0; row < matchResult.rows; row++) { for (int col = 0; col < matchResult.cols; col++) { if (matchResult.at(row, col) != 0.0) { rectangle(matchImg, {col, row}, cv::Point(col + pattern.cols, row + pattern.rows), cv::Scalar(255, 0, 0, 255), 2, 8, 0); } } } } static void markObjects(QImage &image, std::vector &objects) { auto frame = QImageToMat(image); for (size_t i = 0; i < objects.size(); i++) { rectangle(frame, cv::Point(objects[i].x, objects[i].y), cv::Point(objects[i].x + objects[i].width, objects[i].y + objects[i].height), cv::Scalar(255, 0, 0, 255), 2, 8, 0); } } PreviewImage::PreviewImage(std::mutex &mtx) : _mtx(mtx) {} void PreviewImage::CreateImage( const VideoInput &video, PreviewType type, const PatternMatchParameters &patternMatchParams, const PatternImageData &patternImageData, std::shared_ptr objDetectParams, std::shared_ptr ocrParams, const AreaParameters &areaParams, VideoCondition condition) { auto source = obs_weak_source_get_source(video.GetVideo()); QRect screenshotArea; if (areaParams.enable && type == PreviewType::SHOW_MATCH) { screenshotArea.setRect(areaParams.area.x, areaParams.area.y, areaParams.area.width, areaParams.area.height); } Screenshot screenshot(source, screenshotArea, true); obs_source_release(source); if (!video.ValidSelection() || !screenshot.IsDone()) { emit StatusUpdate(obs_module_text( "AdvSceneSwitcher.condition.video.screenshotFail")); emit ImageReady(QPixmap()); return; } if (screenshot.GetImage().width() == 0 || screenshot.GetImage().height() == 0) { emit StatusUpdate(obs_module_text( "AdvSceneSwitcher.condition.video.screenshotEmpty")); emit ImageReady(QPixmap()); return; } if (type == PreviewType::SHOW_MATCH) { std::unique_lock lock(_mtx); // Will emit status label update MarkMatch(screenshot.GetImage(), patternMatchParams, patternImageData, objDetectParams, ocrParams, condition); } else { emit StatusUpdate(obs_module_text( "AdvSceneSwitcher.condition.video.selectArea.status")); } emit ImageReady(QPixmap::fromImage(screenshot.GetImage())); } void PreviewImage::MarkMatch( QImage &screenshot, const PatternMatchParameters &patternMatchParams, const PatternImageData &patternImageData, std::shared_ptr objDetectParams, std::shared_ptr ocrParams, VideoCondition condition) { if (condition == VideoCondition::PATTERN) { MarkPatternMatch(screenshot, patternMatchParams, patternImageData); } else if (condition == VideoCondition::OBJECT) { MarkObjectMatch(screenshot, objDetectParams); } else if (condition == VideoCondition::OCR) { MarkOCRMatch(screenshot, ocrParams); } } void PreviewImage::MarkPatternMatch( QImage &screenshot, const PatternMatchParameters &patternMatchParams, const PatternImageData &patternImageData) { cv::Mat result; double matchValue = MatchPattern(screenshot, patternImageData, patternMatchParams.threshold, result, patternMatchParams.useAlphaAsMask, patternMatchParams.matchMode); emit ValueUpdate(matchValue); if (result.total() == 0 || countNonZero(result) == 0) { emit StatusUpdate(obs_module_text( "AdvSceneSwitcher.condition.video.patternMatchFail")); } else if (result.cols == 1 && result.rows == 1) { emit StatusUpdate(obs_module_text( "AdvSceneSwitcher.condition.video.patternMatchSuccessFullImage")); } else { emit StatusUpdate(obs_module_text( "AdvSceneSwitcher.condition.video.patternMatchSuccess")); markPatterns(result, screenshot, patternImageData.rgbaPattern); } } void PreviewImage::MarkObjectMatch( QImage &screenshot, const std::shared_ptr &objDetectParams) { if (!objDetectParams) { emit StatusUpdate(obs_module_text( "AdvSceneSwitcher.condition.video.objectMatchFail")); return; } auto model = objDetectParams->GetModel(); if (!model) { emit StatusUpdate(obs_module_text( "AdvSceneSwitcher.condition.video.objectMatchFail")); return; } auto objects = MatchObject(screenshot, *model, objDetectParams->scaleFactor, objDetectParams->minNeighbors, objDetectParams->minSize.CV(), objDetectParams->maxSize.CV()); if (objects.empty()) { emit StatusUpdate(obs_module_text( "AdvSceneSwitcher.condition.video.objectMatchFail")); } else { emit StatusUpdate(obs_module_text( "AdvSceneSwitcher.condition.video.objectMatchSuccess")); markObjects(screenshot, objects); } } void PreviewImage::MarkOCRMatch(QImage &screenshot, const std::shared_ptr &ocrParams) { if (!ocrParams) { emit StatusUpdate(obs_module_text( "AdvSceneSwitcher.condition.video.ocrMatchFail")); return; } auto text = RunOCR(ocrParams->GetOCR(), screenshot, ocrParams->color, ocrParams->colorThreshold); if (!text) { emit StatusUpdate(obs_module_text( "AdvSceneSwitcher.condition.video.ocrMatchFail")); } QString status(obs_module_text( "AdvSceneSwitcher.condition.video.ocrMatchSuccess")); emit StatusUpdate(status.arg(QString::fromStdString(*text))); } } // namespace advss