This commit is contained in:
2026-04-09 23:13:33 +08:00
parent d67d7dc0c5
commit 6cb82cec57
24 changed files with 1733 additions and 71 deletions

View File

@@ -0,0 +1,175 @@
#include "dialogs/BlackholeResolveDialog.h"
#include <QDialogButtonBox>
#include <QFrame>
#include <QLabel>
#include <QPushButton>
#include <QStackedWidget>
#include <QVBoxLayout>
namespace {
QPushButton* makeAlgoButton(const QString& title, const QString& subtitle, QWidget* parent) {
auto* btn = new QPushButton(parent);
btn->setCheckable(false);
btn->setCursor(Qt::PointingHandCursor);
btn->setMinimumHeight(86);
btn->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
btn->setText(title + QStringLiteral("\n") + subtitle);
btn->setStyleSheet(
"QPushButton { text-align: left; padding: 10px 12px; border: 1px solid palette(mid); border-radius: 8px; }"
"QPushButton:hover { border-color: palette(highlight); }");
return btn;
}
} // namespace
BlackholeResolveDialog::BlackholeResolveDialog(const QString& blackholeName, QWidget* parent)
: QDialog(parent),
m_blackholeName(blackholeName) {
setModal(true);
setMinimumSize(560, 420);
setWindowTitle(QStringLiteral("黑洞解决"));
auto* root = new QVBoxLayout(this);
m_pages = new QStackedWidget(this);
root->addWidget(m_pages, 1);
buildSelectPage();
buildDetailPage();
m_pages->setCurrentWidget(m_pageSelect);
}
void BlackholeResolveDialog::buildSelectPage() {
m_pageSelect = new QWidget(this);
auto* layout = new QVBoxLayout(m_pageSelect);
layout->setContentsMargins(8, 8, 8, 8);
layout->setSpacing(12);
auto* title = new QLabel(QStringLiteral("第 1 步:选择黑洞解决算法"), m_pageSelect);
auto* sub = new QLabel(QStringLiteral("当前黑洞:%1").arg(m_blackholeName), m_pageSelect);
title->setStyleSheet("font-size: 18px; font-weight: 600;");
sub->setStyleSheet("color: palette(mid);");
layout->addWidget(title);
layout->addWidget(sub);
auto* btnCopy = makeAlgoButton(
QStringLiteral("复制背景其他区域"),
QStringLiteral("进入画布拖动取样框,直观选择复制来源。"),
m_pageSelect);
auto* btnOriginal = makeAlgoButton(
QStringLiteral("使用原始背景"),
QStringLiteral("撤销黑洞显示,恢复抠图前背景区域。"),
m_pageSelect);
layout->addWidget(btnCopy);
layout->addWidget(btnOriginal);
auto* modelNote = new QLabel(
QStringLiteral("模型补全:已预留接口,本版本暂不实现。"),
m_pageSelect);
modelNote->setStyleSheet("color: palette(mid);");
layout->addWidget(modelNote);
layout->addStretch(1);
auto* btns = new QDialogButtonBox(QDialogButtonBox::Cancel, m_pageSelect);
connect(btns, &QDialogButtonBox::rejected, this, &QDialog::reject);
layout->addWidget(btns);
connect(btnCopy, &QPushButton::clicked, this, [this]() {
enterAlgorithmPage(Algorithm::CopyBackgroundRegion);
});
connect(btnOriginal, &QPushButton::clicked, this, [this]() {
enterAlgorithmPage(Algorithm::UseOriginalBackground);
});
m_pages->addWidget(m_pageSelect);
}
void BlackholeResolveDialog::buildDetailPage() {
m_pageDetail = new QWidget(this);
auto* layout = new QVBoxLayout(m_pageDetail);
layout->setContentsMargins(8, 8, 8, 8);
layout->setSpacing(10);
m_detailTitle = new QLabel(m_pageDetail);
m_detailTitle->setStyleSheet("font-size: 18px; font-weight: 600;");
m_detailHint = new QLabel(m_pageDetail);
m_detailHint->setWordWrap(true);
m_detailHint->setStyleSheet("color: palette(mid);");
layout->addWidget(m_detailTitle);
layout->addWidget(m_detailHint);
m_algoDetails = new QStackedWidget(m_pageDetail);
// 详情页 A复制背景其他区域交互布局
m_copyDetail = new QWidget(m_algoDetails);
{
auto* cLay = new QVBoxLayout(m_copyDetail);
cLay->setSpacing(8);
auto* panel = new QFrame(m_copyDetail);
panel->setFrameShape(QFrame::StyledPanel);
auto* pLay = new QVBoxLayout(panel);
pLay->setSpacing(8);
auto* tip = new QLabel(
QStringLiteral("说明:点击“应用”后进入画布拖动模式。\n在画布中拖动青色取样框,松开鼠标即可将该区域复制到黑洞位置并自动移除黑洞。"),
panel);
tip->setWordWrap(true);
tip->setStyleSheet("color: palette(mid);");
pLay->addWidget(tip);
cLay->addWidget(panel);
cLay->addStretch(1);
}
// 详情页 B使用原始背景确认布局
m_originalDetail = new QWidget(m_algoDetails);
{
auto* oLay = new QVBoxLayout(m_originalDetail);
oLay->setSpacing(8);
auto* desc = new QLabel(
QStringLiteral("该方案不会改动背景像素文件,只会将黑洞切换为不显示,从而恢复原始背景区域。"),
m_originalDetail);
desc->setWordWrap(true);
auto* note = new QLabel(
QStringLiteral("适用场景:当前黑洞区域无需二次修补,只需恢复抠图前背景;应用后黑洞会自动移除。"),
m_originalDetail);
note->setWordWrap(true);
note->setStyleSheet("color: palette(mid);");
oLay->addWidget(desc);
oLay->addWidget(note);
oLay->addStretch(1);
}
m_algoDetails->addWidget(m_copyDetail);
m_algoDetails->addWidget(m_originalDetail);
layout->addWidget(m_algoDetails, 1);
auto* btns = new QDialogButtonBox(m_pageDetail);
auto* btnBack = btns->addButton(QStringLiteral("上一步"), QDialogButtonBox::ActionRole);
auto* btnApply = btns->addButton(QStringLiteral("应用"), QDialogButtonBox::AcceptRole);
auto* btnCancel = btns->addButton(QDialogButtonBox::Cancel);
connect(btnBack, &QPushButton::clicked, this, [this]() {
m_pages->setCurrentWidget(m_pageSelect);
});
connect(btnApply, &QPushButton::clicked, this, &QDialog::accept);
connect(btnCancel, &QPushButton::clicked, this, &QDialog::reject);
layout->addWidget(btns);
m_pages->addWidget(m_pageDetail);
}
void BlackholeResolveDialog::enterAlgorithmPage(Algorithm algo) {
m_selectedAlgorithm = algo;
if (algo == Algorithm::CopyBackgroundRegion) {
m_detailTitle->setText(QStringLiteral("第 2 步:复制背景其他区域"));
m_detailHint->setText(QStringLiteral("准备进入画布拖动取样框模式。"));
m_algoDetails->setCurrentWidget(m_copyDetail);
} else {
m_detailTitle->setText(QStringLiteral("第 2 步:使用原始背景"));
m_detailHint->setText(QStringLiteral("确认后将切换为原始背景显示。"));
m_algoDetails->setCurrentWidget(m_originalDetail);
}
m_pages->setCurrentWidget(m_pageDetail);
}

View File

@@ -0,0 +1,42 @@
#pragma once
#include <QDialog>
class QLabel;
class QStackedWidget;
class BlackholeResolveDialog final : public QDialog {
Q_OBJECT
public:
enum class Algorithm {
CopyBackgroundRegion,
UseOriginalBackground,
};
explicit BlackholeResolveDialog(const QString& blackholeName, QWidget* parent = nullptr);
Algorithm selectedAlgorithm() const { return m_selectedAlgorithm; }
private:
void buildSelectPage();
void buildDetailPage();
void enterAlgorithmPage(Algorithm algo);
private:
QString m_blackholeName;
Algorithm m_selectedAlgorithm = Algorithm::UseOriginalBackground;
QStackedWidget* m_pages = nullptr;
QWidget* m_pageSelect = nullptr;
QWidget* m_pageDetail = nullptr;
QLabel* m_detailTitle = nullptr;
QLabel* m_detailHint = nullptr;
QStackedWidget* m_algoDetails = nullptr;
QWidget* m_copyDetail = nullptr;
QWidget* m_originalDetail = nullptr;
};

View File

@@ -8,6 +8,7 @@
#include <QFileDialog>
#include <QFileInfo>
#include <QImage>
#include <QImageReader>
#include <QLabel>
#include <QListWidget>
#include <QMessageBox>
@@ -171,7 +172,25 @@ void FrameAnimationDialog::updatePreviewForFrame(int frame) {
}
bool FrameAnimationDialog::applyImageToFrame(int frame, const QString& absImagePath) {
QImage img(absImagePath);
// Qt 默认的 image allocation limit 较小,超大分辨率图可能会被拒绝。
// 这里提高 limit并对极端大图按像素数上限自动缩放避免 OOM。
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QImageReader::setAllocationLimit(1024); // MB
#endif
QImageReader reader(absImagePath);
reader.setAutoTransform(true);
const QSize sz = reader.size();
if (sz.isValid()) {
constexpr qint64 kMaxPixels = 120LL * 1000LL * 1000LL; // 120MP
const qint64 pixels = qint64(sz.width()) * qint64(sz.height());
if (pixels > kMaxPixels) {
const double s = std::sqrt(double(kMaxPixels) / std::max<double>(1.0, double(pixels)));
const int nw = std::max(1, int(std::lround(sz.width() * s)));
const int nh = std::max(1, int(std::lround(sz.height() * s)));
reader.setScaledSize(QSize(nw, nh));
}
}
QImage img = reader.read();
if (img.isNull()) {
return false;
}

View File

@@ -6,6 +6,7 @@
#include <QMouseEvent>
#include <QPainter>
#include <QPushButton>
#include <QImageReader>
#include <QtMath>
class ImageCropDialog::CropView final : public QWidget {
@@ -159,7 +160,25 @@ ImageCropDialog::ImageCropDialog(const QString& imagePath, QWidget* parent)
}
void ImageCropDialog::loadImageOrClose() {
m_image = QImage(m_imagePath);
// Qt 默认的 image allocation limit 较小(常见为 256MB超大分辨率图会被拒绝。
// 这里用 QImageReader 并提高 limit同时对极端大图按像素数上限自动缩放避免 OOM。
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QImageReader::setAllocationLimit(1024); // MB
#endif
QImageReader reader(m_imagePath);
reader.setAutoTransform(true);
const QSize sz = reader.size();
if (sz.isValid()) {
constexpr qint64 kMaxPixels = 120LL * 1000LL * 1000LL; // 120MP
const qint64 pixels = qint64(sz.width()) * qint64(sz.height());
if (pixels > kMaxPixels) {
const double s = std::sqrt(double(kMaxPixels) / std::max<double>(1.0, double(pixels)));
const int nw = std::max(1, int(std::lround(sz.width() * s)));
const int nh = std::max(1, int(std::lround(sz.height() * s)));
reader.setScaledSize(QSize(nw, nh));
}
}
m_image = reader.read();
if (m_image.isNull()) {
reject();
}