Files
chengnan/MainWindow.cpp
2026-05-30 17:18:55 +08:00

414 lines
12 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "MainWindow.h"
#include <QAction>
#include <QApplication>
#include <QFileDialog>
#include <QFileInfo>
#include <QImageReader>
#include <QImageWriter>
#include <QKeySequence>
#include <QMenuBar>
#include <QMessageBox>
#include <QMouseEvent>
#include <QSlider>
#include <QStatusBar>
#include <QToolBar>
#include <algorithm>
#include <cmath>
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(1100, 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);
}
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_);
}
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_);
}
void MainWindow::createCentralWidget()
{
imageLabel_ = new ImageLabel(this);
connect(imageLabel_, &ImageLabel::doubleClicked, this, &MainWindow::fitToWindow);
scrollArea_ = new QScrollArea(this);
scrollArea_->setBackgroundRole(QPalette::Dark);
scrollArea_->setWidget(imageLabel_);
scrollArea_->setWidgetResizable(true);
setCentralWidget(scrollArea_);
}
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 = scrollArea_->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()
{
if (!ensureImageLoaded(tr("灰度化"))) {
return;
}
applyTransformedImage(currentImage_.convertToFormat(QImage::Format_Grayscale8));
}
void MainWindow::invertColors()
{
if (!ensureImageLoaded(tr("反色"))) {
return;
}
QImage image = currentImage_.convertToFormat(QImage::Format_ARGB32);
image.invertPixels(QImage::InvertRgb);
applyTransformedImage(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)
{
currentImage_ = image.convertToFormat(QImage::Format_ARGB32);
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()) {
imageLabel_->setPixmap(QPixmap());
imageLabel_->setText(tr("打开一张图片开始查看"));
return;
}
const QSize scaledSize = currentImage_.size() * zoomFactor_;
const QPixmap pixmap = QPixmap::fromImage(currentImage_).scaled(
scaledSize,
Qt::KeepAspectRatio,
Qt::SmoothTransformation);
imageLabel_->setText(QString());
imageLabel_->setPixmap(pixmap);
imageLabel_->resize(pixmap.size());
if (zoomSlider_->value() != static_cast<int>(zoomFactor_ * 100.0)) {
zoomSlider_->blockSignals(true);
zoomSlider_->setValue(static_cast<int>(zoomFactor_ * 100.0));
zoomSlider_->blockSignals(false);
}
statusBar()->showMessage(tr("缩放:%1%").arg(static_cast<int>(zoomFactor_ * 100.0)));
}
void MainWindow::setZoomFactor(double factor)
{
zoomFactor_ = std::clamp(factor, 0.10, 4.00);
updateImageView();
}
void MainWindow::applyTransformedImage(const QImage &image)
{
currentImage_ = image.convertToFormat(QImage::Format_ARGB32);
updateImageView();
updateActions();
statusBar()->showMessage(tr("处理完成,可保存结果"), 3000);
}
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);
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;
}