基于QGraphicsView的图像显示控件,支持放大、缩小、鼠标拖动

原链接

前言

这是一个Qt平台的基于QGraphicsView类的图像显示控件,支持输入QPixmap、QImage、opencv的从cv::Mat类。
实现平台:Windows 10 x64 + Qt 6.2.3 + MSVC 2019 + opencv 4.5

先来看演示视频

控件类实现

ImageViewer.h文件

#ifndef IMAGEVIEWER_H
#define IMAGEVIEWER_H

#include <QObject>
#include <QGraphicsItem>
#include <QGraphicsView>
#include <QMouseEvent>
#include <QImage>
#include <QWheelEvent>
#include <QGraphicsScene>
#include "opencv2/opencv.hpp"

class ImageViewer : public QGraphicsView
{
    Q_OBJECT
public:
    explicit ImageViewer(QWidget *parent = nullptr);

signals:


public:
    void setQImage(QImage);
    void setMatImage(cv::Mat );
    void setPixmap(QPixmap);
    void resetImage();
    QImage getQImage();
    cv::Mat getMatImage();
    QPixmap getPixmap();

protected:
    virtual void wheelEvent(QWheelEvent *event) override;
    virtual void keyPressEvent(QKeyEvent *event) override;
    virtual void mousePressEvent(QMouseEvent *event) override;
    virtual void mouseMoveEvent(QMouseEvent *event) override;
    virtual void mouseReleaseEvent(QMouseEvent *event) override;
    void zoom(QPoint factor);
    void togglePan(bool pan, const QPoint &startPos = QPoint());
    void pan(const QPoint &panTo);
    void initShow();

private:
    QPixmap m_image;
    bool m_isPan;
    QPoint m_prevPan;
    QGraphicsScene *scene;

};

#endif // IMAGEVIEWER_H

ImageViewer.cpp 文件

#include "imageviewer.h"
#include <QScrollBar>

ImageViewer::ImageViewer(QWidget *parent)
    : QGraphicsView{parent},
    m_isPan(false),
    m_prevPan(0,0),
    scene(nullptr)
{
    scene = new QGraphicsScene(this);
    this->setScene(scene);

    setDragMode(QGraphicsView::DragMode::NoDrag);
    setInteractive(false);
    setEnabled(false);
}
QImage cvMatToQImage(cv::Mat &src)
{
    if(src.channels() == 1) { // if grayscale image
        return QImage((uchar*)src.data, src.cols, src.rows, static_cast<int>(src.step), QImage::Format_Grayscale8).copy();
    }
    if(src.channels() == 3) { // if 3 channel color image
        cv::Mat rgbMat;
        cv::cvtColor(src, rgbMat, cv::COLOR_BGR2RGB); // invert BGR to RGB
        return QImage((uchar*)rgbMat.data, src.cols, src.rows, static_cast<int>(src.step), QImage::Format_RGB888).copy();
    }
    return QImage();
}

//将QImage转化为Mat
cv::Mat qImageToCvMat( const QImage &inImage)
{
    switch (inImage.format())
    {
    case QImage::Format_RGB32:
    case QImage::Format_RGB888:
    {

        QImage   swapped = inImage;
        if ( inImage.format() == QImage::Format_RGB32 )
        {
            swapped = swapped.convertToFormat( QImage::Format_RGB888 );
        }
        cv::Mat matImg = cv::Mat(swapped.height(), swapped.width(),
                                  CV_8UC3,
                                  const_cast<uchar*>(swapped.bits()),
                                  static_cast<size_t>(swapped.bytesPerLine())
                                  );
        cv::cvtColor(matImg,matImg,cv::COLOR_RGB2BGR);
        return matImg.clone();
    }
    case QImage::Format_Indexed8:
    {
        cv::Mat  mat( inImage.height(), inImage.width(),
                      CV_8UC1,
                      const_cast<uchar*>(inImage.bits()),
                      static_cast<size_t>(inImage.bytesPerLine())
                      );
        return mat.clone();
    }
    default:
        break;
    }
    return cv::Mat();
}

void ImageViewer::setQImage(QImage image)
{   if(image.isNull())
        return;
    m_image = QPixmap::fromImage(image);
    initShow();
}
void ImageViewer::setMatImage(cv::Mat src)
{
    if(src.empty())
        return;
    m_image = QPixmap::fromImage(cvMatToQImage(src));
    initShow();
}
void ImageViewer::setPixmap(QPixmap pixmap)
{
    m_image = pixmap.copy();
    initShow();
}
void ImageViewer::resetImage()
{
    if(m_image.isNull())
        return;
    scene->clear();
    setEnabled(false);
}
QImage  ImageViewer::getQImage()
{
    return m_image.toImage();
}
cv::Mat ImageViewer::getMatImage()
{
    return qImageToCvMat(m_image.toImage());
}
QPixmap ImageViewer::getPixmap()
{
    return m_image;
}
void ImageViewer::initShow()
{
    setEnabled(true);
    setMouseTracking(true);
    scene->clear();
    scene->addPixmap(m_image);
    scene->update();
    this->resetTransform();
    this->setSceneRect(m_image.rect());
    this->fitInView(QRect(0, 0, m_image.width(), m_image.height()), Qt::KeepAspectRatio);
}
void ImageViewer::mousePressEvent(QMouseEvent *event)
{
    if(event->button() == Qt::LeftButton){
        togglePan(true, event->pos());
        event->accept();
        return;
    }
    event->ignore();
}

void ImageViewer::mouseMoveEvent(QMouseEvent *event)
{
    if(m_isPan) {
        pan(event->pos());
        event->accept();
        return;
    }
    event->ignore();
}


void ImageViewer::mouseReleaseEvent(QMouseEvent *event)
{
    if(event->button() == Qt::LeftButton){
        togglePan(false);
        event->accept();
        return;
    }
    event->ignore();
}

void ImageViewer::zoom(QPoint factor)
{
    QRectF FOV = this->mapToScene(this->rect()).boundingRect();
    QRectF FOVImage = QRectF(FOV.left(), FOV.top(), FOV.width(), FOV.height());
    float scaleX = static_cast<float>(m_image.width()) / FOVImage.width();
    float scaleY = static_cast<float>(m_image.height()) / FOVImage.height();
    float minScale = scaleX > scaleY ? scaleY : scaleX;
    float maxScale = scaleX > scaleY ? scaleX : scaleY;
    if ((factor.y() > 0 && minScale > 100) || (factor.y() < 0 && maxScale < 1 )) {
      return;
    }
    if(factor.y()>0)
        scale(1.2, 1.2);
    else
        scale(0.8, 0.8);
}
void ImageViewer::keyPressEvent(QKeyEvent *event)
{
    if(event->key() == Qt::Key_O) {
        this->resetTransform();
        this->setSceneRect(m_image.rect());
        this->fitInView(QRect(0, 0, m_image.width(), m_image.height()), Qt::KeepAspectRatio);
    }
}

void ImageViewer::pan(const QPoint &panTo)
{
    auto hBar = horizontalScrollBar();
    auto vBar = verticalScrollBar();
    auto delta = panTo - m_prevPan;
    m_prevPan = panTo;
    hBar->setValue(hBar->value() - delta.x());
    vBar->setValue(vBar->value() - delta.y());
}

void ImageViewer::wheelEvent(QWheelEvent *event)
{
    if(m_image.isNull())
        return;
    QPoint numDegrees = event->angleDelta() / 8;
    if (!numDegrees.isNull()) {
        QPoint numSteps = numDegrees / 15;
        zoom(numSteps);
    }
    event->accept();
}


void ImageViewer::togglePan(bool pan, const QPoint &startPos)
{
    if(pan){
        if(m_isPan) {
            return;
        }
        m_isPan = true;
        m_prevPan = startPos;
        setCursor(Qt::ClosedHandCursor);
    } else {
        if(!m_isPan) {
            return;
        }
        m_isPan = false;
        m_prevPan = QPoint(0,0);
        setCursor(Qt::ArrowCursor);
    }
}

控件类的使用

具体使用代码如下
1、初始化类对象,并加入界面布局中

	//初始化图片浏览控件,并添加到布局
  	m_view = new ImageViewer(this);
   	//在界面的一个布局中加入控件
   	ui->horizontalLayout_3->addWidget(m_view);

2、打开图像,并加载到控件中

QString filename = getFilePath(QDir::currentPath(),this);
    qDebug() << filename;
    if(filename == "")
        return;
    //读取为QPixmap
    QPixmap mapImg(filename);
    m_view->setPixmap(mapImg);

    //读取为QImage
//    QImage img(filename);
//    m_view->setQImage(img);

    //读取为cv::Mat
//    cv::Mat matImg = cv::imread(filename.toLocal8Bit().data(),cv::IMREAD_UNCHANGED);
//    m_view->setMatImage(matImg);

 3、从控件中卸载图片

m_view->resetImage();

如果还是看不懂、建议直接下载源代码

源码链接:https://download.csdn.net/download/xiaohuihuihuige/87239431

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/473082.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

力扣刷题Days23-35.搜索插入的位置(js)

1&#xff0c;题目 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。请必须使用时间复杂度为 O(log n) 的算法。 2&#xff0c;代码 /*** param {number[]} nums*…

Autosar Crypto Driver学习笔记(二)

文章目录 Crypto DriverFunction definitionsGeneral APICrypto_InitCrypto_GetVersionInfo Job Processing InterfaceCrypto_ProcessJob Job Cancellation InterfaceKey Management InterfaceKey Setting Interface密钥设置接口Crypto_KeyElementSetCrypto_KeySetValid Key Ex…

206.翻转链表

给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 输入&#xff1a;head [1,2] 输出&#xff1a;[2,1]示例 3&#xff1a; 输入&#xff1a;head [] 输…

软件设计师:03 - 数据库系统

一、数据模型的分类 1.1、概念数据模型 1.2、结构数据模型 1.3 真题 二、三级模式 概念模式对应的是基本表&#xff0c;概念模式也称为模式 外模式对应的是视图&#xff0c;也称用户模式或者子模式 内模式对应的是数据库里面的存储文件&#xff0c;也称存储模式 真题 三、两级…

使用Dockerfile打包java项目生成镜像部署到Linux

1、Dockerfile 介绍 如果说容器就是“小板房”&#xff0c;镜像就是“样板间”。那么&#xff0c;要造出这个“样板间”&#xff0c;就必然要有一个“施工图纸”&#xff0c;由它来规定如何建造地基、铺设水电、开窗搭门等动作。这个“施工图纸”就是“Dockerfile”。 比起容…

探秘ChatGPT:智能聊天系统的真实面貌

最近&#xff0c;“ChatGPT”这款聊天机器人在网上广受追捧。 该软件被众多人誉为“时代的里程碑”、“科技领域的新冠军”&#xff0c;上次有如此形容的技术便是互联网。 微软创始人比尔盖茨对ChatGPT的描述是&#xff0c;你可能预想出五年后的形态&#xff0c;但是对于十年…

sqlalchemy和moke生成实体类(一)

前言 如果通过java生成实体类&#xff0c;可以通过mybatis或者mybatis-plus的generator。 而sqlalchemy也可以生成实体类&#xff0c;通过sqlalcodegen或者flask-sqlalcodegen。 使用flask-sqlalcodegen生成实体类 建表 建立学生表&#xff0c;如下。 create table stude…

redis关联和非关联

1.1.2.关联和非关联 传统数据库的表与表之间往往存在关联&#xff0c;例如外键&#xff1a; 而非关系型数据库不存在关联关系&#xff0c;要维护关系要么靠代码中的业务逻辑&#xff0c;要么靠数据之间的耦合&#xff1a; {id: 1,name: "张三",orders: [{id: 1,ite…

基于Spring Boot+Vue的社区医院管理系统

末尾获取源码作者介绍&#xff1a;大家好&#xff0c;我是墨韵&#xff0c;本人4年开发经验&#xff0c;专注定制项目开发 更多项目&#xff1a;CSDN主页YAML墨韵 学如逆水行舟&#xff0c;不进则退。学习如赶路&#xff0c;不能慢一步。 目录 一、项目简介 一、研究背景 二…

BUUCTF-Misc11

[SWPU2019]神奇的二维码1 1.打开附件 2.扫码 提示这里没有flag 3.binwalk 把图片放在kali,用binwalk 发现&#xff1b;里面有好几个压缩包和一张图片 binwalk -e 分离的文件 得到以下内容 4.base64 点开总共有一个加密的图片&#xff0c;和一个加密的音频&#xff0c;还有一…

【算法】差分算法(空调)

可用于求一个数组要变为另一个数组最少要改变多少次的次数 Farmer John 的 N 头奶牛对他们牛棚的室温非常挑剔。 有些奶牛喜欢温度低一些&#xff0c;而有些奶牛则喜欢温度高一些。 Farmer John 的牛棚包含一排 N 个牛栏&#xff0c;编号为 1…N&#xff0c;每个牛栏里有一头…

(一)基于IDEA的JAVA基础3

通过之前的内容&#xff0c;我们在建好的文件夹下建一个java文件&#xff0c;我们来在IDEA中写一下之前用记事本写的helloworld&#xff0c;我们先看一下java代码的规范: 1.java程序文件名一定要有意义&#xff0c;首字母一定要大写。 2.class后面的名字:由大小写字母&#x…

Apipost数据模型上线,解决相似数据结构复用问题

在API设计和开发过程中&#xff0c;存在许多瓶颈&#xff0c;其中一个主要问题是在遇到相似数据结构的API时会产生重复性较多的工作&#xff1a;在每个API中都编写相同的数据&#xff0c;这不仅浪费时间和精力&#xff0c;还容易出错并降低API的可维护性。 为了解决这个问题&a…

【mac M3】idea删除不用或者失效的jdk

【mac M3】idea删除不用或者失效的jdk 不用&#xff08;重复&#xff09;或者失效的jdk如下&#xff1a; 重复或者已失效的JDK版本出现在下拉列表中不仅影响美观&#xff0c;也影响效率&#xff0c;删除jdk的步骤如下&#xff1a; 步骤1.点击File 步骤2.选择Project Structure…

运行jpsall脚本时报命令找不到

1、问题记录 2、解决 进入脚本文件排查问题 [rootnode01 ~]# vim /usr/local/bin/jpsall 错误原因&#xff1a;第四行本来是注释&#xff0c;没有加#&#xff0c;所以总是报这个命令没找到&#xff0c;上一次出现这个问题是因为user打错了&#xff0c;所以一定要细心检查 #…

MySQL - 单表访问

单表访问 查询方式 MySQL查询的执行方式大致分为下边两种&#xff1a; 使用全表扫描进行查询 这种执行方式很好理解&#xff0c;就是把表的每一行记录都扫一遍嘛&#xff0c;把符合搜索条件的记录加入到结果集就完了。不管是啥查询都可以使用这种方式执行&#xff0c;当然&am…

基于springboot+vue的交通管理在线服务系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

代码随想录算法训练营Day52 ||leetCode 300.最长递增子序列 || 674. 最长连续递增序列 || 718. 最长重复子数组

300.最长递增子序列 class Solution { public:int lengthOfLIS(vector<int>& nums) {if (nums.size() < 1) return nums.size();vector<int> dp(nums.size(), 1);int result 0;for (int i 1; i < nums.size(); i) {for (int j 0; j < i; j) {if (…

Jmeter的自动化测试实施方案

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号&#xff1a;互联网杂货铺&#xff0c;回复1 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;薪资嘎嘎涨 Jmeter是目前最流行的一种测试工具&#xff0c;基于此…

JUnit5的条件测试、嵌套测试、重复测试

条件测试 JUnit5支持条件注解&#xff0c;根据布尔值判断是否执行测试。 自定义条件 EnabledIf和DisabledIf注解用来设置自定义条件&#xff0c;示例&#xff1a; Test EnabledIf("customCondition") void enabled() { // ... } Test DisabledIf("cust…