/
SM_DAMIR
/
limb
Обзор
Документация
Войти
/
SM_DAMIR
/
limb
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/imageprocessor.cpp
550 строк
16 KB
DamirSM
chore: init
16 июл 2026, 12:14
16 июл 2026, 12:14
bb10094
Код
Авторство
О чём код?
#include "imageprocessor.h" #include <QQmlEngine> #include <QDebug> #include <QDateTime> #include <QDir> #include <QStandardPaths> ImageProcessor::ImageProcessor(QObject *parent) : QObject(parent) , m_provider(new ResultImageProvider) { m_worker = new ProcessingWorker; m_worker->moveToThread(&m_thread); connect(&m_thread, &QThread::finished, m_worker, &QObject::deleteLater); connect(this, &ImageProcessor::loadImageRequested, m_worker, &ProcessingWorker::loadImage); connect(this, &ImageProcessor::removeBackgroundRequested, m_worker, &ProcessingWorker::removeBackground); connect(this, &ImageProcessor::blurBackgroundRequested, m_worker, &ProcessingWorker::blurBackground); connect(this, &ImageProcessor::colorBackgroundRequested, m_worker, &ProcessingWorker::colorBackground); connect(this, &ImageProcessor::gradientBackgroundRequested, m_worker, &ProcessingWorker::gradientBackground); connect(this, &ImageProcessor::enhancementRequested, m_worker, &ProcessingWorker::applyEnhancement); connect(this, &ImageProcessor::zeroDCERequested, m_worker, &ProcessingWorker::applyZeroDCE); connect(this, &ImageProcessor::styleRequested, m_worker, &ProcessingWorker::applyStyle); connect(this, &ImageProcessor::resetRequested, m_worker, &ProcessingWorker::resetToOriginal); connect(this, &ImageProcessor::detectObjectRequested, m_worker, &ProcessingWorker::detectObject); connect(this, &ImageProcessor::cancelRequested, m_worker, &ProcessingWorker::cancelOperation); connect(this, &ImageProcessor::restoreStateRequested, m_worker, &ProcessingWorker::restoreState); connect(m_worker, &ProcessingWorker::imageLoaded, this, &ImageProcessor::handleImageLoaded); connect(m_worker, &ProcessingWorker::processingFinished, this, &ImageProcessor::handleProcessingFinished); connect(m_worker, &ProcessingWorker::previewFinished, this, &ImageProcessor::handlePreviewFinished); connect(m_worker, &ProcessingWorker::processingProgress, this, &ImageProcessor::handleProgress); connect(m_worker, &ProcessingWorker::errorOccurred, this, &ImageProcessor::handleError); connect(m_worker, &ProcessingWorker::timingMeasured, this, &ImageProcessor::handleTiming); m_thread.start(); } ImageProcessor::~ImageProcessor() { emit cancelRequested(QImage(), 0, 0, 0, -1, 100, false); m_thread.quit(); m_thread.wait(3000); } void ImageProcessor::registerProvider(QQmlEngine *engine) { engine->addImageProvider( QStringLiteral("result"), m_provider); } void ImageProcessor::emitCurrentParameters() { emit parametersRestored( m_brightness, m_contrast, m_whiteBalance, m_currentStyleIndex, m_styleStrength, m_useZeroDCE); } void ImageProcessor::setHasChanges(bool value) { if (m_hasChanges == value) return; m_hasChanges = value; emit hasChangesChanged(); } void ImageProcessor::loadImage(const QString &filePath) { if (filePath.isEmpty()) return; QString path = filePath; if (path.startsWith(QStringLiteral("file://"))) path = path.mid(7); m_imagePath = path; emit imagePathChanged(); m_history.clear(); m_redoStack.clear(); m_restoringCount = 0; m_cancelled = false; m_brightness = 0; m_contrast = 0; m_whiteBalance = 0; m_currentStyleIndex = -1; m_currentStyleStrength = 100; m_styleStrength = 100; m_useZeroDCE = false; setHasChanges(false); emit canUndoChanged(); emit canRedoChanged(); emitCurrentParameters(); emit styleIndexChanged(-1); emit styleStrengthChanged(); setProcessing(true); emit loadImageRequested(path); } void ImageProcessor::resetToOriginal() { m_history.clear(); m_redoStack.clear(); m_restoringCount = 0; m_cancelled = false; m_brightness = 0; m_contrast = 0; m_whiteBalance = 0; m_currentStyleIndex = -1; m_currentStyleStrength = 100; m_styleStrength = 100; m_useZeroDCE = false; setHasChanges(false); emit canUndoChanged(); emit canRedoChanged(); emitCurrentParameters(); emit styleIndexChanged(-1); emit styleStrengthChanged(); setProcessing(true); emit resetRequested(); } void ImageProcessor::cancelProcessing() { qDebug() << "=== cancelProcessing ==="; m_cancelled = true; // Отправляем worker'у cancel вместе с последним подтверждённым // состоянием, чтобы он синхронизировал свои поля // (иначе отменённый стиль/ZeroDCE «залипнет» и вылезет // при следующем applyEnhancement). if (!m_history.isEmpty()) { const HistoryState &s = m_history.top(); emit cancelRequested(s.base, s.brightness, s.contrast, s.whiteBalance, s.styleIndex, s.styleStrength, s.useZeroDCE); // Возвращаем UI и картинку к последнему подтверждённому // состоянию — чтобы убрать промежуточный контур detectObject // и восстановить корректные значения ползунков. m_provider->setImage(s.image); updateResultSource(); m_brightness = s.brightness; m_contrast = s.contrast; m_whiteBalance = s.whiteBalance; m_currentStyleIndex = s.styleIndex; m_currentStyleStrength = s.styleStrength; m_styleStrength = s.styleStrength; m_useZeroDCE = s.useZeroDCE; emitCurrentParameters(); emit styleIndexChanged(m_currentStyleIndex); emit styleStrengthChanged(); } else { emit cancelRequested(QImage(), 0, 0, 0, -1, 100, false); } // Гасим индикатор и прогресс. setProcessing(false); m_progress = 0; emit progressChanged(); // canUndo/canRedo/hasChanges НЕ трогаем — // отменённая операция не должна попадать в redo-стек. emit canUndoChanged(); emit canRedoChanged(); } void ImageProcessor::undoLastAction() { if (m_processing || m_history.size() <= 1) return; HistoryState current = m_history.pop(); m_redoStack.push(current); const HistoryState &state = m_history.top(); restoreStateToUiAndWorker(state); emit canUndoChanged(); emit canRedoChanged(); setHasChanges(m_history.size() > 1); } void ImageProcessor::redoLastAction() { if (m_processing || m_redoStack.isEmpty()) return; HistoryState state = m_redoStack.pop(); m_history.push(state); restoreStateToUiAndWorker(state); emit canUndoChanged(); emit canRedoChanged(); setHasChanges(true); } void ImageProcessor::setStyleStrength(int value) { value = qBound(0, value, 100); if (m_styleStrength == value) return; m_styleStrength = value; emit styleStrengthChanged(); } void ImageProcessor::applyStyle(int styleIndex) { if (styleIndex == m_currentStyleIndex && m_styleStrength == m_currentStyleStrength) { qDebug() << "applyStyle: no-op, skip"; return; } m_cancelled = false; m_currentStyleIndex = styleIndex; m_currentStyleStrength = m_styleStrength; emit styleIndexChanged(styleIndex); setProcessing(true); emit styleRequested(styleIndex, m_styleStrength); } void ImageProcessor::removeBackground(int objectType) { m_cancelled = false; setProcessing(true); emit removeBackgroundRequested(objectType); } void ImageProcessor::blurBackground( int objectType, int radius) { m_cancelled = false; setProcessing(true); emit blurBackgroundRequested(objectType, radius); } void ImageProcessor::colorBackground( int objectType, int r, int g, int b) { m_cancelled = false; setProcessing(true); emit colorBackgroundRequested(objectType, r, g, b); } void ImageProcessor::gradientBackground( int objectType, int r1, int g1, int b1, int r2, int g2, int b2) { m_cancelled = false; setProcessing(true); emit gradientBackgroundRequested( objectType, r1, g1, b1, r2, g2, b2); } void ImageProcessor::applyEnhancement( int brightness, int contrast, int whiteBalance) { if (!m_useZeroDCE && m_brightness == brightness && m_contrast == contrast && m_whiteBalance == whiteBalance) { qDebug() << "applyEnhancement: no-op, skip"; return; } m_cancelled = false; m_brightness = brightness; m_contrast = contrast; m_whiteBalance = whiteBalance; m_useZeroDCE = false; setProcessing(true); emit enhancementRequested(brightness, contrast, whiteBalance); } void ImageProcessor::applyZeroDCE() { m_cancelled = false; m_brightness = 0; m_contrast = 0; m_whiteBalance = 0; m_useZeroDCE = true; setProcessing(true); emit zeroDCERequested(); } bool ImageProcessor::saveToGallery(const QString &destPath) { if (!m_hasChanges) { qDebug() << "saveToGallery: no changes, skip"; return false; } QImage img = m_provider->currentImage(); if (img.isNull()) { m_errorMessage = tr("Нет изображения для сохранения"); emit errorMessageChanged(); return false; } QString savePath = destPath; if (savePath.isEmpty()) { QString picDir = QStandardPaths::writableLocation( QStandardPaths::PicturesLocation) + "/limb"; QDir().mkpath(picDir); savePath = picDir + "/limb_" + QDateTime::currentDateTime() .toString("yyyyMMdd_HHmmss") + ".jpg"; } bool ok = img.save(savePath, "JPEG", 95); if (ok) { qDebug() << "Saved to:" << savePath; emit savedToGallery(savePath); } else { m_errorMessage = tr("Ошибка сохранения: ") + savePath; emit errorMessageChanged(); } return ok; } void ImageProcessor::detectObject(int objectType) { m_cancelled = false; setProcessing(true); emit detectObjectRequested(objectType); } void ImageProcessor::handleImageLoaded(const QImage &image) { if (image.isNull()) { setProcessing(false); return; } m_provider->setOriginalImage(image); } void ImageProcessor::handleProcessingFinished( const QImage &result, const QImage &base) { if (m_cancelled) { qDebug() << "handleProcessingFinished: cancelled, ignoring"; if (m_processing) setProcessing(false); return; } if (result.isNull()) { setProcessing(false); return; } m_provider->setImage(result); if (m_restoringCount > 0) { m_restoringCount--; // При undo/redo hasChanges выставляется в самих // undoLastAction()/redoLastAction() — здесь не трогаем. } else { bool pushed = pushHistory(result, base); if (pushed && m_history.size() > 1) { setHasChanges(true); } } setProcessing(false); updateResultSource(); emitCurrentParameters(); } void ImageProcessor::handlePreviewFinished( const QImage &preview) { if (m_cancelled) { qDebug() << "handlePreviewFinished: cancelled, ignoring"; if (m_processing) setProcessing(false); // m_cancelled НЕ сбрасываем — см. правку 4.3. return; } if (preview.isNull()) { setProcessing(false); return; } m_provider->setImage(preview); setProcessing(false); updateResultSource(); } void ImageProcessor::handleProgress(int percent) { if (m_cancelled) return; m_progress = percent; emit progressChanged(); } void ImageProcessor::handleError(const QString &message) { qDebug() << "Worker error:" << message; // Ошибка из отменённой операции — молча игнорируем. if (m_cancelled) { if (m_processing) setProcessing(false); return; } m_errorMessage = message; emit errorMessageChanged(); setProcessing(false); } void ImageProcessor::handleTiming(const QString &stage, qint64 ms) { qDebug() << "[TIMING]" << stage << ":" << ms << "ms"; // Маппинг технических названий в универсальные для UI QString genericStage = stage; if (stage.contains("preprocess", Qt::CaseInsensitive) || stage.contains("load", Qt::CaseInsensitive)) { genericStage = "Препроцесс"; } else if (stage.contains("inference", Qt::CaseInsensitive)) { genericStage = "Инференс"; } else if (stage.contains("postprocess", Qt::CaseInsensitive)) { genericStage = "Постпроцесс"; } else if (stage.contains("enhancement", Qt::CaseInsensitive)) { genericStage = "Улучшение"; } // Формируем строку с HTML-разметкой для лучшей читаемости // <b> делает цифры жирными, <br> делает аккуратный перенос строки QString newEntry = QString("• %1: <b>%2</b> мс").arg(genericStage).arg(ms); if (!m_lastTiming.isEmpty()) { m_lastTiming += "<br>" + newEntry; } else { m_lastTiming = newEntry; } // Ограничиваем вывод строго последними 3-мя этапами QStringList lines = m_lastTiming.split("<br>"); if (lines.size() > 3) { m_lastTiming = lines.mid(lines.size() - 3).join("<br>"); } emit lastTimingChanged(); } void ImageProcessor::setProcessing(bool value) { if (m_processing == value) return; m_processing = value; emit processingChanged(); } void ImageProcessor::updateResultSource() { ++m_version; m_resultSource = QStringLiteral("image://result/img/%1") .arg(m_version); emit resultSourceChanged(); } bool ImageProcessor::pushHistory( const QImage &image, const QImage &base) { if (image.isNull() || base.isNull()) return false; if (m_history.size() >= MAX_HISTORY) m_history.removeFirst(); HistoryState state; state.image = image; state.base = base; state.brightness = m_brightness; state.contrast = m_contrast; state.whiteBalance = m_whiteBalance; state.styleIndex = m_currentStyleIndex; state.styleStrength = m_styleStrength; state.useZeroDCE = m_useZeroDCE; m_history.push(state); m_redoStack.clear(); emit canUndoChanged(); emit canRedoChanged(); return true; } void ImageProcessor::restoreStateToUiAndWorker( const HistoryState &state) { m_cancelled = false; m_provider->setImage(state.image); updateResultSource(); m_brightness = state.brightness; m_contrast = state.contrast; m_whiteBalance = state.whiteBalance; m_currentStyleIndex = state.styleIndex; m_currentStyleStrength = state.styleStrength; m_styleStrength = state.styleStrength; m_useZeroDCE = state.useZeroDCE; emitCurrentParameters(); emit styleIndexChanged(m_currentStyleIndex); emit styleStrengthChanged(); m_restoringCount++; setProcessing(true); emit restoreStateRequested( state.base, state.brightness, state.contrast, state.whiteBalance, state.styleIndex, state.styleStrength, state.useZeroDCE); }