feat: add simplified Qt image viewer Dev branch

This commit is contained in:
luochen570
2026-05-30 17:18:55 +08:00
parent 4832838a0d
commit 4627343e31
21 changed files with 591 additions and 1623 deletions

413
MainWindow.cpp Normal file
View File

@@ -0,0 +1,413 @@
#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;
}