#include "ImageView.h" #include #include #include #include #include #include #include #include #include namespace { constexpr double kDefaultMinimumZoom = 0.02; constexpr double kDefaultMaximumZoom = 20.0; constexpr double kZoomStep = 1.25; } ImageView::ImageView(QWidget* parent) : QGraphicsView(parent), m_scene(new QGraphicsScene(this)), m_pixmapItem(nullptr), m_zoomLabel(new QLabel(this)), m_zoomRatio(1.0), m_minimumZoomRatio(kDefaultMinimumZoom), m_maximumZoomRatio(kDefaultMaximumZoom), m_imageAlignment(Qt::AlignCenter), m_panning(false), m_needsInitialFit(false) { setScene(m_scene); setBackgroundBrush(QColor(35, 35, 35)); setFrameShape(QFrame::NoFrame); setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); setTransformationAnchor(QGraphicsView::AnchorUnderMouse); setResizeAnchor(QGraphicsView::AnchorViewCenter); setAlignment(m_imageAlignment); setDragMode(QGraphicsView::NoDrag); setCursor(Qt::OpenHandCursor); m_zoomLabel->setObjectName(QStringLiteral("imageViewZoomLabel")); m_zoomLabel->setAlignment(Qt::AlignCenter); m_zoomLabel->setMinimumWidth(58); m_zoomLabel->setAttribute(Qt::WA_TransparentForMouseEvents); m_zoomLabel->setStyleSheet(QStringLiteral( "QLabel { color: white; background: transparent; border: none; }")); updateZoomLabel(); } ImageView::~ImageView() = default; void ImageView::setImage(const QImage& image) { if (image.isNull()) { clearImage(); return; } m_image = image; if (!m_pixmapItem) { m_pixmapItem = m_scene->addPixmap(QPixmap::fromImage(m_image)); } else { m_pixmapItem->setPixmap(QPixmap::fromImage(m_image)); } m_scene->setSceneRect(QRectF(QPointF(0.0, 0.0), QSizeF(m_image.size()))); m_needsInitialFit = true; if (viewport()->width() > 0 && viewport()->height() > 0) { m_needsInitialFit = false; resetView(); } emit imageChanged(); } void ImageView::setImage(const QPixmap& pixmap) { setImage(pixmap.toImage()); } bool ImageView::loadImage(const QString& filePath) { QImage loaded; if (!loaded.load(filePath)) { return false; } setImage(loaded); return true; } void ImageView::clearImage() { m_image = QImage(); m_scene->clear(); m_pixmapItem = nullptr; m_scene->setSceneRect(QRectF()); m_zoomRatio = 1.0; m_needsInitialFit = false; updateZoomLabel(); viewport()->update(); emit imageChanged(); } bool ImageView::hasImage() const { return !m_image.isNull(); } QImage ImageView::image() const { return m_image; } double ImageView::zoomRatio() const { return m_zoomRatio; } void ImageView::setZoomRatio(double ratio) { updateZoomRatio(ratio, true); } void ImageView::zoomIn(double factor) { if (factor <= 1.0) { factor = kZoomStep; } updateZoomRatio(m_zoomRatio * factor, true); } void ImageView::zoomOut(double factor) { if (factor <= 1.0) { factor = kZoomStep; } updateZoomRatio(m_zoomRatio / factor, true); } void ImageView::resetView() { if (!hasImage() || !m_pixmapItem) { return; } const double previousRatio = m_zoomRatio; resetTransform(); fitInView(m_pixmapItem, Qt::KeepAspectRatio); const double fittedRatio = qBound(m_minimumZoomRatio, transform().m11(), m_maximumZoomRatio); resetTransform(); scale(fittedRatio, fittedRatio); m_zoomRatio = fittedRatio; centerOn(m_pixmapItem); updateZoomLabel(); if (!qFuzzyCompare(previousRatio, m_zoomRatio)) { emit zoomRatioChanged(m_zoomRatio); } } void ImageView::setZoomRange(double minimum, double maximum) { if (minimum <= 0.0 || maximum < minimum) { return; } m_minimumZoomRatio = minimum; m_maximumZoomRatio = maximum; updateZoomRatio(m_zoomRatio, true); } double ImageView::minimumZoomRatio() const { return m_minimumZoomRatio; } double ImageView::maximumZoomRatio() const { return m_maximumZoomRatio; } void ImageView::setImageAlignment(Qt::Alignment alignment) { if (!alignment) { alignment = Qt::AlignCenter; } m_imageAlignment = alignment; setAlignment(m_imageAlignment); if (hasImage()) { viewport()->update(); } } Qt::Alignment ImageView::imageAlignment() const { return m_imageAlignment; } void ImageView::resizeEvent(QResizeEvent* event) { QGraphicsView::resizeEvent(event); if (m_needsInitialFit && hasImage() && viewport()->width() > 0 && viewport()->height() > 0) { m_needsInitialFit = false; resetView(); } repositionZoomLabel(); } void ImageView::wheelEvent(QWheelEvent* event) { if (!hasImage() || event->angleDelta().y() == 0) { event->ignore(); return; } const double factor = event->angleDelta().y() > 0 ? kZoomStep : 1.0 / kZoomStep; updateZoomRatio(m_zoomRatio * factor, true); event->accept(); } void ImageView::mousePressEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton && hasImage()) { m_panning = true; m_lastMousePosition = event->pos(); setCursor(Qt::ClosedHandCursor); event->accept(); return; } QGraphicsView::mousePressEvent(event); } void ImageView::mouseMoveEvent(QMouseEvent* event) { if (m_panning) { const QPoint delta = event->pos() - m_lastMousePosition; m_lastMousePosition = event->pos(); horizontalScrollBar()->setValue(horizontalScrollBar()->value() - delta.x()); verticalScrollBar()->setValue(verticalScrollBar()->value() - delta.y()); event->accept(); return; } QGraphicsView::mouseMoveEvent(event); } void ImageView::mouseReleaseEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton && m_panning) { m_panning = false; setCursor(Qt::OpenHandCursor); event->accept(); return; } QGraphicsView::mouseReleaseEvent(event); } void ImageView::mouseDoubleClickEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton && hasImage()) { resetView(); event->accept(); return; } QGraphicsView::mouseDoubleClickEvent(event); } void ImageView::updateZoomLabel() { if (!hasImage()) { m_zoomLabel->setText(QStringLiteral("--")); } else { m_zoomLabel->setText(QStringLiteral("%1%") .arg(m_zoomRatio * 100.0, 0, 'f', 1)); } m_zoomLabel->adjustSize(); repositionZoomLabel(); } void ImageView::updateZoomRatio(double ratio, bool keepCenter) { if (!hasImage()) { return; } ratio = qBound(m_minimumZoomRatio, ratio, m_maximumZoomRatio); if (qFuzzyCompare(ratio, m_zoomRatio) && !transform().isIdentity()) { return; } const QPoint viewportCenter = viewport()->rect().center(); const QPointF sceneCenter = mapToScene(viewportCenter); resetTransform(); scale(ratio, ratio); m_zoomRatio = ratio; if (keepCenter) { centerOn(sceneCenter); } updateZoomLabel(); emit zoomRatioChanged(m_zoomRatio); } void ImageView::repositionZoomLabel() { if (!m_zoomLabel) { return; } const int margin = 8; const int scrollBarHeight = horizontalScrollBar()->isVisible() ? horizontalScrollBar()->height() : 0; m_zoomLabel->move(margin, height() - m_zoomLabel->height() - margin - scrollBarHeight); m_zoomLabel->raise(); }