316 lines
7.6 KiB
C++
316 lines
7.6 KiB
C++
#include "ImageView.h"
|
|
|
|
#include <QGraphicsPixmapItem>
|
|
#include <QGraphicsScene>
|
|
#include <QLabel>
|
|
#include <QMouseEvent>
|
|
#include <QResizeEvent>
|
|
#include <QtWidgets/QScrollBar>
|
|
#include <QStyle>
|
|
#include <QWheelEvent>
|
|
#include <QtMath>
|
|
|
|
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();
|
|
}
|