/
ka_pie
/
PeakOff
Обзор
Документация
Войти
/
ka_pie
/
PeakOff
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/imageprocessor.cpp
656 строк
18 KB
Kate_Pirogova
top secret hihi michael jackson
14 июл 2026, 12:08
14 июл 2026, 12:08
c92103b
Код
Авторство
О чём код?
#include "imageprocessor.h" #include "photomodel.h" #include "historymodel.h" #include "processingworker.h" #include "historymodel.h" #include "projecthistory.h" #include "imageutils.h" #include <QStandardPaths> #include <QDir> #include <QFile> #include <QDateTime> #include <QDebug> #include <QJsonDocument> #include <QJsonObject> #include <QJsonArray> #include <QJsonValue> #include <QRegularExpression> #include <QUrl> ImageProcessor::ImageProcessor(QObject *parent) : QObject(parent) , m_worker(std::make_unique<ProcessingWorker>( "/usr/share/ru.auroragirls.PeakOff/models/u2netp.onnx", "/usr/share/ru.auroragirls.PeakOff/models/super-resolution-10.onnx", "/usr/share/ru.auroragirls.PeakOff/models/rain-princess-9.onnx")) , m_workerThread(std::make_unique<QThread>()) { m_worker->moveToThread(m_workerThread.get()); connect(m_worker.get(), &ProcessingWorker::enhanceFinished, this, [this](const QImage &result) { onProcessingFinished(result, "Улучшение"); }); connect(m_worker.get(), &ProcessingWorker::removeBackgroundFinished, this, [this](const QImage &result) { if (!result.isNull()) { cv::Mat rgbaMat = ImageUtils::qImageToCvMat(result); std::vector<cv::Mat> channels; cv::split(rgbaMat, channels); m_currentMask = channels[3]; } onProcessingFinished(result, "Удаление фона"); }); connect(m_worker.get(), &ProcessingWorker::styleFinished, this, [this](const QImage &result, int) { onProcessingFinished(result, "Стилизация"); }); connect(m_worker.get(), &ProcessingWorker::metricsReady, this, &ImageProcessor::onMetricsReady); connect(m_worker.get(), &ProcessingWorker::errorOccurred, this, &ImageProcessor::onProcessingError); connect(m_worker.get(), &ProcessingWorker::progressChanged, this, &ImageProcessor::onProgressChanged); connect(m_workerThread.get(), &QThread::finished, m_worker.get(), &QObject::deleteLater); connect(m_worker.get(), &ProcessingWorker::correctionFinished, this, &ImageProcessor::onCorrectionFinished); m_workerThread->start(); } ImageProcessor::~ImageProcessor() { if (m_workerThread) { m_workerThread->quit(); m_workerThread->wait(); } } void ImageProcessor::cleanup() { m_busy = false; m_resultSource = ""; m_currentMask = cv::Mat(); if (m_photoModel) m_photoModel->setOriginalPath(""); if (m_historyModel) m_historyModel->clear(); emit busyChanged(); emit resultSourceChanged(); } void ImageProcessor::saveProjectData(QObject *projectHistoryObj, const QString &originalPhotoPath, const QString &projectName) { ProjectHistory *ph = qobject_cast<ProjectHistory*>(projectHistoryObj); if (!ph || !m_historyModel || m_historyModel->rowCount() == 0) return; QString safeName = projectName; safeName.remove(QRegularExpression("[\\\\/:*?\"<>|]")); if (safeName.isEmpty()) { safeName = "Unnamed_Project_" + QString::number(QDateTime::currentMSecsSinceEpoch() % 10000); } QString baseDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/Projects"; QString projId = QString::number(QDateTime::currentMSecsSinceEpoch()); QString projDirPath = baseDir + "/" + projId; QDir().mkpath(projDirPath); QJsonArray stepsArray; for (int i = 0; i < m_historyModel->rowCount(); ++i) { QImage img = m_historyModel->getFullImageAt(i); QString stepImgPath = projDirPath + "/step_" + QString::number(i) + ".png"; img.save(stepImgPath, "PNG"); QJsonObject stepObj; stepObj["operationName"] = m_historyModel->data(m_historyModel->index(i), HistoryModel::OperationNameRole).toString(); stepObj["imagePath"] = stepImgPath; stepsArray.append(stepObj); } QJsonObject projectDoc; projectDoc["photoPath"] = originalPhotoPath; projectDoc["projectName"] = projectName; projectDoc["steps"] = stepsArray; QString jsonFilePath = projDirPath + "/" + safeName + ".json"; QFile file(jsonFilePath); if (file.open(QIODevice::WriteOnly)) { file.write(QJsonDocument(projectDoc).toJson()); file.close(); ph->addProject(jsonFilePath); } } void ImageProcessor::loadProjectFromJson(const QString &path) { QFile file(path); if (!file.open(QIODevice::ReadOnly)) return; QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); QJsonObject root = doc.object(); QJsonArray steps = root["steps"].toArray(); if (m_historyModel) m_historyModel->clear(); QImage lastImg; for (const QJsonValue &val : steps) { QJsonObject stepObj = val.toObject(); QString opName = stepObj["operationName"].toString(); QString imgPath = stepObj["imagePath"].toString(); QImage stepImg(imgPath); if (!stepImg.isNull() && m_historyModel) { m_historyModel->push(stepImg, opName); lastImg = stepImg; } } if (!lastImg.isNull() && m_photoModel) { m_photoModel->setWorkingImage(lastImg); setResult(lastImg); } } void ImageProcessor::setPhotoModel(PhotoModel *model) { if (m_photoModel == model) return; m_photoModel = model; emit photoModelChanged(); updateResultSource(); } void ImageProcessor::setHistoryModel(HistoryModel *model) { if (m_historyModel == model) return; m_historyModel = model; emit historyModelChanged(); } void ImageProcessor::setStyleStrength(float strength) { strength = qBound(0.0f, strength, 1.0f); if (qFuzzyCompare(m_styleStrength, strength)) return; m_styleStrength = strength; emit styleStrengthChanged(); } void ImageProcessor::applyEnhance() { if (!m_photoModel || !m_photoModel->hasPhoto() || m_busy) return; QImage img = m_photoModel->getWorkingImage(); if (img.isNull()) { emit errorOccurred("Нет изображения для обработки"); return; } setBusy(true); m_cancelled = false; QMetaObject::invokeMethod(m_worker.get(), "processEnhance", Qt::QueuedConnection, Q_ARG(QImage, img)); } void ImageProcessor::applyRemoveBackground() { if (!m_worker || (m_workerThread && !m_workerThread->isRunning())) { qWarning() << "Попытка вызова инструмента при выключенном потоке!"; return; } if (!m_photoModel || !m_photoModel->hasPhoto() || m_busy) return; if (!m_photoModel || !m_photoModel->hasPhoto() || m_busy) return; QImage img = m_photoModel->getWorkingImage(); if (img.isNull()) { emit errorOccurred("Нет изображения для обработки"); return; } setBusy(true); m_cancelled = false; QMetaObject::invokeMethod(m_worker.get(), "processRemoveBackground", Qt::QueuedConnection, Q_ARG(QImage, img)); } void ImageProcessor::applyStyle(int styleIndex) { if (!m_photoModel || !m_photoModel->hasPhoto() || m_busy) return; QImage img = m_photoModel->getWorkingImage(); if (img.isNull()) { emit errorOccurred("Нет изображения для обработки"); return; } setBusy(true); m_cancelled = false; QMetaObject::invokeMethod(m_worker.get(), "processStyle", Qt::QueuedConnection, Q_ARG(QImage, img), Q_ARG(int, styleIndex)); } void ImageProcessor::undo() { m_lastBaseImage = QImage(); qDebug() << "[ImageProcessor] undo() ВЫЗВАН"; if (m_busy || !m_historyModel || !m_historyModel->canUndo()) { qDebug() << "[ImageProcessor] undo() - отмена невозможна"; return; } m_historyModel->undo(); QImage img = m_historyModel->getCurrentImage(); if (img.isNull() && m_photoModel) { qDebug() << "[ImageProcessor] undo() - история пуста, возвращаем оригинал"; resetToOriginal(); return; } if (!img.isNull() && m_photoModel) { m_photoModel->setWorkingImage(img); QString cachePath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); QDir dir(cachePath); if (!dir.exists()) dir.mkpath("."); static int counter = 0; counter++; QString filePath = cachePath + "/current_edit_" + QString::number(counter) + ".png"; img.save(filePath, "PNG"); m_resultSource = "file://" + filePath; emit resultSourceChanged(); qDebug() << "[ImageProcessor] undo() - результат обновлён:" << m_resultSource; } } void ImageProcessor::redo() { if (m_busy || !m_historyModel || !m_historyModel->canRedo()) return; m_historyModel->redo(); QImage img = m_historyModel->getCurrentImage(); if (!img.isNull() && m_photoModel) { m_photoModel->setWorkingImage(img); setResult(img); } } void ImageProcessor::resetToOriginal() { m_lastBaseImage = QImage(); m_currentMask = cv::Mat(); if (m_busy || !m_photoModel) return; m_photoModel->resetToOriginal(); if (m_historyModel) m_historyModel->clear(); QImage img = m_photoModel->getWorkingImage(); if (!img.isNull()) setResult(img); emit operationCompleted("reset"); } void ImageProcessor::cancelCorrection() { if (!m_lastBaseImage.isNull()) { m_currentResult = m_lastBaseImage; updateResultSource(); m_lastBaseImage = QImage(); } } void ImageProcessor::saveToGallery() { if (!m_photoModel || !m_photoModel->hasPhoto()) return; QImage img = m_photoModel->getWorkingImage(); if (img.isNull()) { emit errorOccurred("Нет изображения для сохранения"); return; } QString picturesPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); QString fileName = "PeakOff_" + QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss") + ".png"; QString filePath = picturesPath + "/" + fileName; if (img.save(filePath, "PNG")) { emit operationCompleted("save"); } else { emit errorOccurred("Не удалось сохранить изображение"); } } void ImageProcessor::cancelProcessing() { if (m_busy) { m_cancelled = true; setBusy(false); emit operationCompleted("cancel"); } } void ImageProcessor::startCompare() { if (!m_photoModel) return; m_compareOriginal = m_photoModel->getOriginalImage(); if (!m_compareOriginal.isNull()) { setResult(m_compareOriginal); } } void ImageProcessor::stopCompare() { if (!m_photoModel) return; QImage current = m_photoModel->getWorkingImage(); if (!current.isNull()) { setResult(current); } } void ImageProcessor::setBusy(bool busy) { if (m_busy == busy) return; m_busy = busy; emit busyChanged(); } void ImageProcessor::setResult(const QImage &image) { if (image.isNull()) return; m_currentResult = image; updateResultSource(); } void ImageProcessor::updateResultSource() { if (!m_photoModel || !m_photoModel->hasPhoto()) { m_resultSource = ""; emit resultSourceChanged(); return; } QString cachePath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); QDir dir(cachePath); if (!dir.exists()) { dir.mkpath("."); } static int counter = 0; counter++; QString filePath = cachePath + "/current_edit_" + QString::number(QDateTime::currentMSecsSinceEpoch()) + ".png"; bool success = m_currentResult.save(filePath, "PNG"); if (!success) { qWarning() << "Ошибка: Не удалось сохранить файл кэша:" << filePath; emit errorOccurred("Сбой сохранения файла. Проверьте память устройства."); return; } m_resultSource = "file://" + filePath; emit resultSourceChanged(); } void ImageProcessor::pushHistory(const QImage &image, const QString &operation) { if (m_historyModel) { m_historyModel->push(image, operation); } } void ImageProcessor::onProcessingFinished(const QImage &result, const QString &operation) { if (result.isNull()) { setBusy(false); emit errorOccurred("Ошибка обработки"); return; } if (m_photoModel) { m_photoModel->setWorkingImage(result); pushHistory(result, operation); setResult(result); } setBusy(false); emit operationCompleted(operation); } void ImageProcessor::onProcessingError(const QString &error) { setBusy(false); emit errorOccurred(error); } void ImageProcessor::onMetricsReady(const ProcessingMetrics &metrics) { m_processingTime = metrics.toString(); emit processingTimeChanged(); } void ImageProcessor::onProgressChanged(int percent) { Q_UNUSED(percent); } void ImageProcessor::applyHistoryStep(int index) { m_lastBaseImage = QImage(); if (!m_historyModel) return; m_historyModel->jumpTo(index); QImage img = m_historyModel->getCurrentImage(); if (!img.isNull() && m_photoModel) { m_photoModel->setWorkingImage(img); setResult(img); qDebug() << "[ImageProcessor] Прыжок к шагу" << index << "успешен"; } } void ImageProcessor::restoreSteps(const QVariantList &steps) { if (!m_historyModel || !m_photoModel) return; m_historyModel->clear(); QImage lastImage; for (const QVariant &stepVariant : steps) { QVariantMap step = stepVariant.toMap(); m_historyModel->addStep(step); if (step.contains("image")) { lastImage = qvariant_cast<QImage>(step.value("image")); } } if (!lastImage.isNull()) { m_photoModel->setWorkingImage(lastImage); setResult(lastImage); } emit historyApplied(); } void ImageProcessor::processStep(const QString &operation, const QVariant ¶ms) { if (operation == "enhance") { applyEnhance(); } else if (operation == "style") { applyStyle(params.toInt()); } else if (operation == "background") { applyRemoveBackground(); } } void ImageProcessor::applyCorrection(int brightness, float contrast) { if (!m_photoModel || !m_photoModel->hasPhoto()) return; if (m_lastBaseImage.isNull()) { m_lastBaseImage = m_photoModel->getWorkingImage(); } QMetaObject::invokeMethod(m_worker.get(), "processCorrection", Qt::QueuedConnection, Q_ARG(QImage, m_lastBaseImage), Q_ARG(int, brightness), Q_ARG(float, contrast)); } void ImageProcessor::onCorrectionFinished(const QImage &result) { if (result.isNull()) return; m_currentResult = result; updateResultSource(); } void ImageProcessor::commitCorrectionStep() { if (m_currentResult.isNull() || m_lastBaseImage.isNull()) return; if (m_photoModel) { m_photoModel->setWorkingImage(m_currentResult); pushHistory(m_currentResult, "Коррекция"); } m_lastBaseImage = QImage(); } void ImageProcessor::applyCrop(int x, int y, int width, int height) { if (!m_photoModel || !m_photoModel->hasPhoto() || m_busy) return; QImage img = m_photoModel->getWorkingImage(); if (img.isNull()) return; QRect cropRect(x, y, width, height); cropRect = cropRect.intersected(img.rect()); if (cropRect.isEmpty() || cropRect == img.rect()) return; QImage cropped = img.copy(cropRect); m_photoModel->setWorkingImage(cropped); pushHistory(cropped, "Кадрирование"); setResult(cropped); } void ImageProcessor::replaceBackground(const QString &hexColor) { if (!m_photoModel || !m_photoModel->hasPhoto()) return; if (m_currentMask.empty()) { emit errorOccurred("Сначала удалите фон"); return; } QImage img = m_photoModel->getWorkingImage(); cv::Mat rgba = ImageUtils::qImageToCvMat(img); cv::Mat rgb; if (rgba.channels() == 4) { cv::cvtColor(rgba, rgb, cv::COLOR_BGRA2BGR); } else { rgb = rgba; } cv::Mat resultMat = ImageUtils::replaceBackground( rgb, m_currentMask, QColor(hexColor)); QImage result = ImageUtils::cvMatToQImage(resultMat); m_photoModel->setWorkingImage(result); pushHistory(result, "Замена фона"); setResult(result); emit operationCompleted("Замена фона"); } void ImageProcessor::replaceBackgroundWithImage(const QString &imagePath) { if (!m_photoModel || !m_photoModel->hasPhoto()) return; if (m_currentMask.empty()) { emit errorOccurred("Сначала удалите фон"); return; } QImage img = m_photoModel->getWorkingImage(); cv::Mat rgba = ImageUtils::qImageToCvMat(img); cv::Mat src; if (rgba.channels() == 4) { cv::cvtColor(rgba, src, cv::COLOR_BGRA2BGR); } else { src = rgba; } cv::Mat bg = cv::imread(imagePath.toStdString()); if (bg.empty()) { emit errorOccurred("Не удалось открыть изображение фона"); return; } cv::Mat result = ImageUtils::replaceBackgroundImage(src, m_currentMask, bg); QImage out = ImageUtils::cvMatToQImage(result); m_photoModel->setWorkingImage(out); pushHistory(out, "Замена фона изображением"); setResult(out); emit operationCompleted("Замена фона изображением"); } void ImageProcessor::applyManualMask(const QString& maskFileUrl, bool isEraser) { if (m_currentMask.empty()) return; QString localPath = QUrl(maskFileUrl).toLocalFile(); cv::Mat stroke = cv::imread(localPath.toStdString(), cv::IMREAD_GRAYSCALE); if (stroke.empty()) return; if (stroke.size() != m_currentMask.size()) { cv::resize(stroke, stroke, m_currentMask.size()); } m_currentMask = ImageUtils::updateMask(m_currentMask, stroke, isEraser); emit imageProcessed(); }