Some checks failed
Build Windows Release / Build Windows x64 Release on Linux (MinGW + Qt + OpenCV) (push) Failing after 2m15s
609 lines
19 KiB
C++
609 lines
19 KiB
C++
#include "MainWindow.h"
|
||
|
||
#include <QAction>
|
||
#include <QApplication>
|
||
#include <QFileDialog>
|
||
#include <QFileInfo>
|
||
#include <QImageReader>
|
||
#include <QImageWriter>
|
||
#include <QKeySequence>
|
||
#include <QMenuBar>
|
||
#include <QMessageBox>
|
||
#include <QMouseEvent>
|
||
#include <QScrollBar>
|
||
#include <QSlider>
|
||
#include <QSplitter>
|
||
#include <QStatusBar>
|
||
#include <QThread>
|
||
#include <QToolBar>
|
||
#include <QTransform>
|
||
|
||
#include <opencv2/imgproc.hpp>
|
||
|
||
#include <algorithm>
|
||
#include <cmath>
|
||
#include <exception>
|
||
#include <memory>
|
||
|
||
namespace {
|
||
|
||
cv::Mat qImageToMat(const QImage &image)
|
||
{
|
||
const QImage rgba = image.convertToFormat(QImage::Format_RGBA8888);
|
||
cv::Mat mat(rgba.height(), rgba.width(), CV_8UC4, const_cast<uchar *>(rgba.constBits()), rgba.bytesPerLine());
|
||
return mat.clone();
|
||
}
|
||
|
||
QImage matToQImage(const cv::Mat &mat)
|
||
{
|
||
if (mat.empty()) {
|
||
return {};
|
||
}
|
||
|
||
cv::Mat rgba;
|
||
if (mat.channels() == 1) {
|
||
cv::cvtColor(mat, rgba, cv::COLOR_GRAY2RGBA);
|
||
} else if (mat.channels() == 3) {
|
||
cv::cvtColor(mat, rgba, cv::COLOR_BGR2RGBA);
|
||
} else {
|
||
cv::cvtColor(mat, rgba, cv::COLOR_BGRA2RGBA);
|
||
}
|
||
|
||
return QImage(rgba.data, rgba.cols, rgba.rows, static_cast<int>(rgba.step), QImage::Format_RGBA8888).copy();
|
||
}
|
||
|
||
QImage extractObjectImage(const QImage &image)
|
||
{
|
||
const cv::Mat rgba = qImageToMat(image);
|
||
cv::Mat gray;
|
||
cv::cvtColor(rgba, gray, cv::COLOR_RGBA2GRAY);
|
||
cv::GaussianBlur(gray, gray, cv::Size(5, 5), 0.0);
|
||
|
||
cv::Mat edges;
|
||
cv::Canny(gray, edges, 60, 160);
|
||
|
||
std::vector<std::vector<cv::Point>> contours;
|
||
cv::findContours(edges, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
|
||
|
||
cv::Mat output = rgba.clone();
|
||
cv::cvtColor(output, output, cv::COLOR_RGBA2BGRA);
|
||
int count = 0;
|
||
for (const auto &contour : contours) {
|
||
const double area = cv::contourArea(contour);
|
||
if (area < 120.0) {
|
||
continue;
|
||
}
|
||
const cv::Rect rect = cv::boundingRect(contour);
|
||
cv::rectangle(output, rect, cv::Scalar(0, 0, 255, 255), 2);
|
||
++count;
|
||
}
|
||
|
||
cv::putText(output,
|
||
"objects: " + std::to_string(count),
|
||
cv::Point(16, 32),
|
||
cv::FONT_HERSHEY_SIMPLEX,
|
||
0.9,
|
||
cv::Scalar(0, 0, 255, 255),
|
||
2);
|
||
return matToQImage(output);
|
||
}
|
||
|
||
QImage classifyImageByOpenCv(const QImage &image)
|
||
{
|
||
const cv::Mat rgba = qImageToMat(image);
|
||
cv::Mat gray;
|
||
cv::cvtColor(rgba, gray, cv::COLOR_RGBA2GRAY);
|
||
|
||
cv::Mat edges;
|
||
cv::Canny(gray, edges, 80, 180);
|
||
const double brightness = cv::mean(gray)[0];
|
||
const double edgeRatio = static_cast<double>(cv::countNonZero(edges)) / static_cast<double>(gray.total());
|
||
|
||
std::string label;
|
||
if (brightness < 80.0) {
|
||
label = "class: dark image";
|
||
} else if (edgeRatio > 0.12) {
|
||
label = "class: rich edges";
|
||
} else {
|
||
label = "class: bright smooth image";
|
||
}
|
||
|
||
cv::Mat output = rgba.clone();
|
||
cv::cvtColor(output, output, cv::COLOR_RGBA2BGRA);
|
||
const int boxWidth = std::max(1, std::min(520, output.cols - 16));
|
||
cv::rectangle(output, cv::Rect(8, 8, boxWidth, 52), cv::Scalar(0, 0, 0, 180), cv::FILLED);
|
||
cv::putText(output,
|
||
label,
|
||
cv::Point(18, 42),
|
||
cv::FONT_HERSHEY_SIMPLEX,
|
||
0.8,
|
||
cv::Scalar(0, 255, 255, 255),
|
||
2);
|
||
return matToQImage(output);
|
||
}
|
||
|
||
} // namespace
|
||
|
||
ImageLabel::ImageLabel(QWidget *parent)
|
||
: QLabel(parent)
|
||
{
|
||
setAlignment(Qt::AlignCenter);
|
||
setBackgroundRole(QPalette::Base);
|
||
setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
|
||
setScaledContents(false);
|
||
setMinimumSize(320, 240);
|
||
setText(tr("打开一张图片开始查看"));
|
||
}
|
||
|
||
void ImageLabel::mouseDoubleClickEvent(QMouseEvent *event)
|
||
{
|
||
if (event->button() == Qt::LeftButton) {
|
||
Q_EMIT doubleClicked();
|
||
}
|
||
QLabel::mouseDoubleClickEvent(event);
|
||
}
|
||
|
||
MainWindow::MainWindow(QWidget *parent)
|
||
: QMainWindow(parent)
|
||
{
|
||
createActions();
|
||
createMenus();
|
||
createToolbar();
|
||
createCentralWidget();
|
||
updateActions();
|
||
|
||
setWindowTitle(tr("chengnan 图片处理工具"));
|
||
resize(1200, 720);
|
||
statusBar()->showMessage(tr("就绪"));
|
||
}
|
||
|
||
void MainWindow::createActions()
|
||
{
|
||
openAction_ = new QAction(tr("打开(&O)"), this);
|
||
openAction_->setShortcut(QKeySequence::Open);
|
||
connect(openAction_, &QAction::triggered, this, &MainWindow::openImage);
|
||
|
||
saveAction_ = new QAction(tr("保存(&S)"), this);
|
||
saveAction_->setShortcut(QKeySequence::Save);
|
||
connect(saveAction_, &QAction::triggered, this, &MainWindow::saveImage);
|
||
|
||
saveAsAction_ = new QAction(tr("另存为(&A)"), this);
|
||
saveAsAction_->setShortcut(QKeySequence::SaveAs);
|
||
connect(saveAsAction_, &QAction::triggered, this, &MainWindow::saveImageAs);
|
||
|
||
exitAction_ = new QAction(tr("退出(&X)"), this);
|
||
exitAction_->setShortcut(QKeySequence::Quit);
|
||
connect(exitAction_, &QAction::triggered, this, &QWidget::close);
|
||
|
||
zoomInAction_ = new QAction(tr("放大"), this);
|
||
zoomInAction_->setShortcut(QKeySequence::ZoomIn);
|
||
connect(zoomInAction_, &QAction::triggered, this, &MainWindow::zoomIn);
|
||
|
||
zoomOutAction_ = new QAction(tr("缩小"), this);
|
||
zoomOutAction_->setShortcut(QKeySequence::ZoomOut);
|
||
connect(zoomOutAction_, &QAction::triggered, this, &MainWindow::zoomOut);
|
||
|
||
fitAction_ = new QAction(tr("适配窗口"), this);
|
||
fitAction_->setShortcut(Qt::CTRL | Qt::Key_F);
|
||
connect(fitAction_, &QAction::triggered, this, &MainWindow::fitToWindow);
|
||
|
||
resetAction_ = new QAction(tr("原始大小"), this);
|
||
resetAction_->setShortcut(Qt::CTRL | Qt::Key_0);
|
||
connect(resetAction_, &QAction::triggered, this, &MainWindow::resetView);
|
||
|
||
rotateLeftAction_ = new QAction(tr("左旋 90°"), this);
|
||
connect(rotateLeftAction_, &QAction::triggered, this, &MainWindow::rotateLeft);
|
||
|
||
rotateRightAction_ = new QAction(tr("右旋 90°"), this);
|
||
connect(rotateRightAction_, &QAction::triggered, this, &MainWindow::rotateRight);
|
||
|
||
flipHorizontalAction_ = new QAction(tr("水平翻转"), this);
|
||
connect(flipHorizontalAction_, &QAction::triggered, this, &MainWindow::flipHorizontal);
|
||
|
||
flipVerticalAction_ = new QAction(tr("垂直翻转"), this);
|
||
connect(flipVerticalAction_, &QAction::triggered, this, &MainWindow::flipVertical);
|
||
|
||
grayscaleAction_ = new QAction(tr("灰度化"), this);
|
||
connect(grayscaleAction_, &QAction::triggered, this, &MainWindow::grayscale);
|
||
|
||
invertAction_ = new QAction(tr("反色"), this);
|
||
connect(invertAction_, &QAction::triggered, this, &MainWindow::invertColors);
|
||
|
||
extractAction_ = new QAction(tr("目标提取"), this);
|
||
connect(extractAction_, &QAction::triggered, this, &MainWindow::extractObjects);
|
||
|
||
classifyAction_ = new QAction(tr("简单分类"), this);
|
||
connect(classifyAction_, &QAction::triggered, this, &MainWindow::classifyImage);
|
||
}
|
||
|
||
void MainWindow::createMenus()
|
||
{
|
||
QMenu *fileMenu = menuBar()->addMenu(tr("文件(&F)"));
|
||
fileMenu->addAction(openAction_);
|
||
fileMenu->addAction(saveAction_);
|
||
fileMenu->addAction(saveAsAction_);
|
||
fileMenu->addSeparator();
|
||
fileMenu->addAction(exitAction_);
|
||
|
||
QMenu *viewMenu = menuBar()->addMenu(tr("查看(&V)"));
|
||
viewMenu->addAction(zoomInAction_);
|
||
viewMenu->addAction(zoomOutAction_);
|
||
viewMenu->addAction(fitAction_);
|
||
viewMenu->addAction(resetAction_);
|
||
|
||
QMenu *processMenu = menuBar()->addMenu(tr("处理(&P)"));
|
||
processMenu->addAction(rotateLeftAction_);
|
||
processMenu->addAction(rotateRightAction_);
|
||
processMenu->addAction(flipHorizontalAction_);
|
||
processMenu->addAction(flipVerticalAction_);
|
||
processMenu->addSeparator();
|
||
processMenu->addAction(grayscaleAction_);
|
||
processMenu->addAction(invertAction_);
|
||
processMenu->addSeparator();
|
||
processMenu->addAction(extractAction_);
|
||
processMenu->addAction(classifyAction_);
|
||
}
|
||
|
||
void MainWindow::createToolbar()
|
||
{
|
||
QToolBar *toolbar = addToolBar(tr("主工具栏"));
|
||
toolbar->setMovable(false);
|
||
toolbar->addAction(openAction_);
|
||
toolbar->addAction(saveAction_);
|
||
toolbar->addSeparator();
|
||
toolbar->addAction(zoomOutAction_);
|
||
|
||
zoomSlider_ = new QSlider(Qt::Horizontal, this);
|
||
zoomSlider_->setRange(10, 400);
|
||
zoomSlider_->setValue(100);
|
||
zoomSlider_->setFixedWidth(180);
|
||
zoomSlider_->setToolTip(tr("缩放比例"));
|
||
connect(zoomSlider_, &QSlider::valueChanged, this, &MainWindow::updateZoomFromSlider);
|
||
toolbar->addWidget(zoomSlider_);
|
||
|
||
toolbar->addAction(zoomInAction_);
|
||
toolbar->addAction(fitAction_);
|
||
toolbar->addSeparator();
|
||
toolbar->addAction(rotateLeftAction_);
|
||
toolbar->addAction(rotateRightAction_);
|
||
toolbar->addAction(grayscaleAction_);
|
||
toolbar->addAction(invertAction_);
|
||
toolbar->addAction(extractAction_);
|
||
toolbar->addAction(classifyAction_);
|
||
}
|
||
|
||
void MainWindow::createCentralWidget()
|
||
{
|
||
originalLabel_ = new ImageLabel(this);
|
||
processedLabel_ = new ImageLabel(this);
|
||
originalLabel_->setText(tr("处理前图像"));
|
||
processedLabel_->setText(tr("处理后图像"));
|
||
connect(originalLabel_, &ImageLabel::doubleClicked, this, &MainWindow::fitToWindow);
|
||
connect(processedLabel_, &ImageLabel::doubleClicked, this, &MainWindow::fitToWindow);
|
||
|
||
originalScrollArea_ = new QScrollArea(this);
|
||
originalScrollArea_->setBackgroundRole(QPalette::Dark);
|
||
originalScrollArea_->setWidget(originalLabel_);
|
||
originalScrollArea_->setWidgetResizable(true);
|
||
|
||
processedScrollArea_ = new QScrollArea(this);
|
||
processedScrollArea_->setBackgroundRole(QPalette::Dark);
|
||
processedScrollArea_->setWidget(processedLabel_);
|
||
processedScrollArea_->setWidgetResizable(true);
|
||
|
||
auto *splitter = new QSplitter(Qt::Horizontal, this);
|
||
splitter->addWidget(originalScrollArea_);
|
||
splitter->addWidget(processedScrollArea_);
|
||
splitter->setStretchFactor(0, 1);
|
||
splitter->setStretchFactor(1, 1);
|
||
setCentralWidget(splitter);
|
||
}
|
||
|
||
void MainWindow::openImage()
|
||
{
|
||
const QString fileName = QFileDialog::getOpenFileName(
|
||
this,
|
||
tr("打开图片"),
|
||
QString(),
|
||
tr("图片文件 (*.png *.jpg *.jpeg *.bmp *.gif *.webp);;所有文件 (*.*)"));
|
||
|
||
if (fileName.isEmpty()) {
|
||
return;
|
||
}
|
||
|
||
QImageReader reader(fileName);
|
||
reader.setAutoTransform(true);
|
||
const QImage image = reader.read();
|
||
if (image.isNull()) {
|
||
QMessageBox::warning(this, tr("打开失败"), tr("无法读取图片:%1").arg(reader.errorString()));
|
||
return;
|
||
}
|
||
|
||
setImage(image, fileName);
|
||
}
|
||
|
||
void MainWindow::saveImage()
|
||
{
|
||
if (!ensureImageLoaded(tr("保存"))) {
|
||
return;
|
||
}
|
||
|
||
if (currentPath_.isEmpty()) {
|
||
saveImageAs();
|
||
return;
|
||
}
|
||
|
||
writeImageToPath(currentPath_);
|
||
}
|
||
|
||
void MainWindow::saveImageAs()
|
||
{
|
||
if (!ensureImageLoaded(tr("另存为"))) {
|
||
return;
|
||
}
|
||
|
||
const QString fileName = QFileDialog::getSaveFileName(
|
||
this,
|
||
tr("另存为"),
|
||
currentPath_.isEmpty() ? QStringLiteral("chengnan.png") : currentPath_,
|
||
tr("PNG 图片 (*.png);;JPEG 图片 (*.jpg *.jpeg);;BMP 图片 (*.bmp);;所有文件 (*.*)"));
|
||
|
||
if (!fileName.isEmpty()) {
|
||
writeImageToPath(fileName);
|
||
}
|
||
}
|
||
|
||
void MainWindow::zoomIn()
|
||
{
|
||
setZoomFactor(zoomFactor_ * 1.25);
|
||
}
|
||
|
||
void MainWindow::zoomOut()
|
||
{
|
||
setZoomFactor(zoomFactor_ / 1.25);
|
||
}
|
||
|
||
void MainWindow::fitToWindow()
|
||
{
|
||
if (!ensureImageLoaded(tr("适配窗口"))) {
|
||
return;
|
||
}
|
||
|
||
const QSize viewportSize = processedScrollArea_->viewport()->size();
|
||
const QSize imageSize = currentImage_.size();
|
||
if (imageSize.isEmpty()) {
|
||
return;
|
||
}
|
||
|
||
const double xRatio = static_cast<double>(viewportSize.width()) / imageSize.width();
|
||
const double yRatio = static_cast<double>(viewportSize.height()) / imageSize.height();
|
||
setZoomFactor(std::min(xRatio, yRatio));
|
||
}
|
||
|
||
void MainWindow::resetView()
|
||
{
|
||
setZoomFactor(1.0);
|
||
}
|
||
|
||
void MainWindow::rotateLeft()
|
||
{
|
||
if (!ensureImageLoaded(tr("旋转"))) {
|
||
return;
|
||
}
|
||
QTransform transform;
|
||
transform.rotate(-90);
|
||
applyTransformedImage(currentImage_.transformed(transform, Qt::SmoothTransformation));
|
||
}
|
||
|
||
void MainWindow::rotateRight()
|
||
{
|
||
if (!ensureImageLoaded(tr("旋转"))) {
|
||
return;
|
||
}
|
||
QTransform transform;
|
||
transform.rotate(90);
|
||
applyTransformedImage(currentImage_.transformed(transform, Qt::SmoothTransformation));
|
||
}
|
||
|
||
void MainWindow::flipHorizontal()
|
||
{
|
||
if (!ensureImageLoaded(tr("翻转"))) {
|
||
return;
|
||
}
|
||
applyTransformedImage(currentImage_.mirrored(true, false));
|
||
}
|
||
|
||
void MainWindow::flipVertical()
|
||
{
|
||
if (!ensureImageLoaded(tr("翻转"))) {
|
||
return;
|
||
}
|
||
applyTransformedImage(currentImage_.mirrored(false, true));
|
||
}
|
||
|
||
void MainWindow::grayscale()
|
||
{
|
||
processImageInThread(tr("灰度化"), tr("灰度化完成,可保存结果"), [](QImage image) {
|
||
cv::Mat rgba = qImageToMat(image);
|
||
cv::Mat gray;
|
||
cv::cvtColor(rgba, gray, cv::COLOR_RGBA2GRAY);
|
||
return matToQImage(gray);
|
||
});
|
||
}
|
||
|
||
void MainWindow::invertColors()
|
||
{
|
||
processImageInThread(tr("反色"), tr("反色完成,可保存结果"), [](QImage image) {
|
||
QImage result = image.convertToFormat(QImage::Format_ARGB32);
|
||
result.invertPixels(QImage::InvertRgb);
|
||
return result;
|
||
});
|
||
}
|
||
|
||
void MainWindow::extractObjects()
|
||
{
|
||
processImageInThread(tr("目标提取"), tr("目标提取完成,红框为提取到的目标"), [](QImage image) {
|
||
return extractObjectImage(image);
|
||
});
|
||
}
|
||
|
||
void MainWindow::classifyImage()
|
||
{
|
||
processImageInThread(tr("简单分类"), tr("简单分类完成,结果已标注在图像上"), [](QImage image) {
|
||
return classifyImageByOpenCv(image);
|
||
});
|
||
}
|
||
|
||
void MainWindow::updateZoomFromSlider(int value)
|
||
{
|
||
const double factor = static_cast<double>(value) / 100.0;
|
||
if (std::abs(factor - zoomFactor_) > 0.001) {
|
||
zoomFactor_ = factor;
|
||
updateImageView();
|
||
}
|
||
}
|
||
|
||
void MainWindow::setImage(const QImage &image, const QString &path)
|
||
{
|
||
originalImage_ = image.convertToFormat(QImage::Format_ARGB32);
|
||
currentImage_ = originalImage_;
|
||
currentPath_ = path;
|
||
setZoomFactor(1.0);
|
||
updateActions();
|
||
|
||
const QFileInfo info(path);
|
||
setWindowTitle(path.isEmpty()
|
||
? tr("chengnan 图片处理工具")
|
||
: tr("%1 - chengnan 图片处理工具").arg(info.fileName()));
|
||
statusBar()->showMessage(tr("已加载:%1 × %2").arg(currentImage_.width()).arg(currentImage_.height()), 5000);
|
||
}
|
||
|
||
void MainWindow::updateImageView()
|
||
{
|
||
if (currentImage_.isNull()) {
|
||
originalLabel_->setPixmap(QPixmap());
|
||
processedLabel_->setPixmap(QPixmap());
|
||
originalLabel_->setText(tr("处理前图像"));
|
||
processedLabel_->setText(tr("处理后图像"));
|
||
return;
|
||
}
|
||
|
||
updateSingleView(originalLabel_, originalImage_);
|
||
updateSingleView(processedLabel_, currentImage_);
|
||
|
||
const int sliderValue = static_cast<int>(zoomFactor_ * 100.0);
|
||
if (zoomSlider_->value() != sliderValue) {
|
||
zoomSlider_->blockSignals(true);
|
||
zoomSlider_->setValue(sliderValue);
|
||
zoomSlider_->blockSignals(false);
|
||
}
|
||
|
||
statusBar()->showMessage(tr("缩放:%1%").arg(sliderValue));
|
||
}
|
||
|
||
void MainWindow::updateSingleView(ImageLabel *label, const QImage &image)
|
||
{
|
||
const QSize scaledSize(
|
||
std::max(1, static_cast<int>(std::round(image.width() * zoomFactor_))),
|
||
std::max(1, static_cast<int>(std::round(image.height() * zoomFactor_))));
|
||
const QPixmap pixmap = QPixmap::fromImage(image).scaled(
|
||
scaledSize,
|
||
Qt::KeepAspectRatio,
|
||
Qt::SmoothTransformation);
|
||
|
||
label->setText(QString());
|
||
label->setPixmap(pixmap);
|
||
label->resize(pixmap.size());
|
||
}
|
||
|
||
void MainWindow::setZoomFactor(double factor)
|
||
{
|
||
zoomFactor_ = std::clamp(factor, 0.10, 4.00);
|
||
updateImageView();
|
||
}
|
||
|
||
void MainWindow::applyTransformedImage(const QImage &image, const QString &message)
|
||
{
|
||
currentImage_ = image.convertToFormat(QImage::Format_ARGB32);
|
||
updateImageView();
|
||
updateActions();
|
||
statusBar()->showMessage(message.isEmpty() ? tr("处理完成,可保存结果") : message, 3000);
|
||
}
|
||
|
||
void MainWindow::processImageInThread(const QString &actionName,
|
||
const QString &doneMessage,
|
||
const std::function<QImage(QImage)> &processor)
|
||
{
|
||
if (!ensureImageLoaded(actionName)) {
|
||
return;
|
||
}
|
||
|
||
const QImage input = currentImage_;
|
||
auto result = std::make_shared<QImage>();
|
||
auto error = std::make_shared<QString>();
|
||
QThread *thread = QThread::create([input, result, error, processor]() {
|
||
try {
|
||
*result = processor(input);
|
||
} catch (const cv::Exception &ex) {
|
||
*error = QString::fromLocal8Bit(ex.what());
|
||
} catch (const std::exception &ex) {
|
||
*error = QString::fromLocal8Bit(ex.what());
|
||
}
|
||
});
|
||
|
||
statusBar()->showMessage(tr("正在处理:%1 ...").arg(actionName));
|
||
connect(thread, &QThread::finished, this, [this, thread, result, error, doneMessage]() {
|
||
thread->deleteLater();
|
||
if (!error->isEmpty() || result->isNull()) {
|
||
QMessageBox::warning(this, tr("处理失败"), error->isEmpty() ? tr("处理结果为空。") : *error);
|
||
return;
|
||
}
|
||
applyTransformedImage(*result, doneMessage);
|
||
});
|
||
thread->start();
|
||
}
|
||
|
||
void MainWindow::updateActions()
|
||
{
|
||
const bool hasImage = !currentImage_.isNull();
|
||
saveAction_->setEnabled(hasImage);
|
||
saveAsAction_->setEnabled(hasImage);
|
||
zoomInAction_->setEnabled(hasImage);
|
||
zoomOutAction_->setEnabled(hasImage);
|
||
fitAction_->setEnabled(hasImage);
|
||
resetAction_->setEnabled(hasImage);
|
||
rotateLeftAction_->setEnabled(hasImage);
|
||
rotateRightAction_->setEnabled(hasImage);
|
||
flipHorizontalAction_->setEnabled(hasImage);
|
||
flipVerticalAction_->setEnabled(hasImage);
|
||
grayscaleAction_->setEnabled(hasImage);
|
||
invertAction_->setEnabled(hasImage);
|
||
extractAction_->setEnabled(hasImage);
|
||
classifyAction_->setEnabled(hasImage);
|
||
zoomSlider_->setEnabled(hasImage);
|
||
}
|
||
|
||
bool MainWindow::ensureImageLoaded(const QString &actionName) const
|
||
{
|
||
if (!currentImage_.isNull()) {
|
||
return true;
|
||
}
|
||
|
||
QMessageBox::information(nullptr, actionName, tr("请先打开一张图片。"));
|
||
return false;
|
||
}
|
||
|
||
bool MainWindow::writeImageToPath(const QString &path)
|
||
{
|
||
QImageWriter writer(path);
|
||
if (!writer.write(currentImage_)) {
|
||
QMessageBox::warning(this, tr("保存失败"), tr("无法保存图片:%1").arg(writer.errorString()));
|
||
return false;
|
||
}
|
||
|
||
currentPath_ = path;
|
||
setWindowTitle(tr("%1 - chengnan 图片处理工具").arg(QFileInfo(path).fileName()));
|
||
statusBar()->showMessage(tr("已保存:%1").arg(path), 5000);
|
||
return true;
|
||
}
|