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

news2024/9/20 1:16:04

原链接

前言

这是一个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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【SpringMVC】知识汇总

SpringMVC 短暂回来&#xff0c;有时间就会更新博客 文章目录 SpringMVC前言一、第一章 SpingMVC概述二、SpringMVC常用注解1. Controller注解2. RequestMapping注解3. ResponseBody注解4. RequestParam5. EnableWebMvc注解介绍6. RequestBody注解介绍7. RequestBody与RequestP…

(19)C#自动添加微信好友(可批量申请)--微信UI自动化(.Net)

往期知识回顾 (1)C#开启探索微信自动化之路-微信UI自动化 (2)C#创建微信窗体自动化实例-微信UI自动化 (3)C#针对系统热键管理-微信UI自动化 (4)C#采集微信通讯录和联系人-微信UI自动化 (5)C#实现针对微信窗体鼠标静默点击-微信UI自动化 (6)C#搜索微信通讯录联系人-微信UI…

JavaMySQL高级一(下)

目录 1.常用函数 1.字符串函数 2.时间日期函数 3.聚合函数 4.数学函数 2.分布查询 3.子查询基础 1.简单子查询 1.常用函数 在程序开发过程中&#xff0c;除了简单的数据查询&#xff0c;还有基于已数据进行数据的统计分析计算等需求。因此&#xff0c;在SQL中将一…

zookeeper底层细节

zk 临时节点和watch机制实现注册中心自动注册和发现&#xff0c;数据都在内存&#xff0c;nio 多线程模型&#xff1b; cp注重一致性&#xff0c;数据不一致时集群不可用 事务请求处理方式 1.all事务由唯一服务器处理 2.将客户端事务请求转成proposal分发follower 3.等待半…

windows访问远程服务器上容器的几种直接方式

远程服务器 host上有一个docker container&#xff0c;如何通过 client 直接登陆 container 呢&#xff1f; container 使用 host 的网络&#xff0c;即使用了 --networkhost先配置 container的 ssdh&#xff08;/etc/ssh/sshd_config&#xff09;&#xff0c;相关参数设置 po…

使用导入车辆出险报告接口API接口实现车辆出险查询

引言&#xff1a; 现在&#xff0c;汽车事故频发&#xff0c;我们经常听到车辆出险的消息。对于购车用户而言&#xff0c;了解车辆的出险、理赔和事故记录是十分重要的。而如何快速查询到这些信息&#xff0c;成为了用户们在掌握车辆情况上的一个难点。幸运的是&#xff0c;挖数…

高效备考2024年AMC10:吃透2000-2023年1250道真题(限时免费送)

我们今天继续来随机看5道AMC10真题&#xff0c;以及详细解析&#xff0c;这些题目来自1250道完整的官方历年AMC10真题库。通过系统研究和吃透AMC10的历年真题&#xff0c;参加AMC10的竞赛就能拿到好名次。即使不参加AMC10竞赛&#xff0c;掌握了这些知识和解题思路后初中和高中…

【NBUOJ刷题笔记】递推_递归+分治策略1

0. 前言 PS&#xff1a;本人并不是集训队的成员&#xff0c;因此代码写的烂轻点喷。。。本专题一方面是巩固自己的算法知识&#xff0c;另一方面是给NBU学弟学妹们参考解题思路&#xff08;切勿直接搬运抄袭提交作业&#xff01;&#xff01;&#xff01;&#xff09;最后&…

QT5.14.2 Qt布局调和术:精妙UI设计背后的自适应魔法

欢迎来到Qt世界的一角&#xff0c;今天我们要探索的是Qt中的布局管理以及控件自适应大小调整的艺术。在这篇文章中&#xff0c;我们不仅会讨论理论&#xff0c;还会一起动手实践&#xff0c;弄清楚如何打造出既美观又实用的用户界面。 一、布局管理概览 布局管理是Qt中的核心概…

2024腾讯云优惠券领取_云服务器代金券_优惠券查询和使用方法

腾讯云代金券领取渠道有哪些&#xff1f;腾讯云官网可以领取、官方媒体账号可以领取代金券、完成任务可以领取代金券&#xff0c;大家也可以在腾讯云百科蹲守代金券&#xff0c;因为腾讯云代金券领取渠道比较分散&#xff0c;腾讯云百科txybk.com专注汇总优惠代金券领取页面&am…

HTML5语法总结

文章目录 一.HTML基本框架二.标题标签三.段落标签四.换行与水平线标签五.文本格式化标签(加粗、倾斜、下划线、删除线)六.图像标签扩展&#xff1a;相对路径,绝对路径与在线网址 七.超链接标签八.音频标签九.视频标签十.列表标签十一.表格标签扩展&#xff1a;表格结构标签合并…

表情生成器微信小程序版

1.纯前端输出&#xff0c;无需后台&#xff0c;无需域名&#xff0c;速度杠杠快&#xff01; 2.完美支持微信端和抖音端&#xff1b; 3.双端均支持配置开启流量主广告&#xff0c;包括&#xff1a;激励视频广告、插屏广告、banner广告、原生广告、封面广告等&#xff1b; 4.…

【蓝桥杯入门记录】继电器、蜂鸣器及原理图分析

一、继电器、继电器概述 &#xff08;1&#xff09;蜂鸣器原理 蜂鸣器的发声原理由振动装置和谐振装置组成&#xff0c;而蜂鸣器又分为无源他激型与有源自激型&#xff0c;蜂鸣器的发声原理为: 1、无源他激型蜂鸣器的工作发声原理是&#xff1a;方波信号输入谐振装置转换为声…

阿里云服务器2核4G服务器收费价格表,1个月和一年报价

阿里云2核4G服务器多少钱一年&#xff1f;2核4G服务器1个月费用多少&#xff1f;2核4G服务器30元3个月、85元一年&#xff0c;轻量应用服务器2核4G4M带宽165元一年&#xff0c;企业用户2核4G5M带宽199元一年。本文阿里云服务器网整理的2核4G参加活动的主机是ECS经济型e实例和u1…

【unity实战】时间控制 昼夜交替 四季变化 天气变化效果

最终效果 文章目录 最终效果日期季节控制时间昼夜交替素材如果没有天空盒&#xff0c;需要自己配置新增SkyboxBlendingShader.shader&#xff0c;控制天空盒平滑过渡交替变化 下雨下雨粒子效果控制雨一直跟随玩家&#xff0c;但是旋转不跟随控制不同天气 源码完结 日期季节控制…

C++ —— 内存管理

目录 1. C内存分布 2. C 内存管理方式 2.1 new 和 delete 操作内置类型 2.2 new 和 delete 操作自定义类型 3. operator new与operator delete函数 4. new和delete的实现原理 5. malloc/free 和 new/delete 的区别 1. C内存分布 首先看一段代码&#xff1a; int globalV…

短视频矩阵系统源头技术开发--每一次技术迭代

短视频矩阵系统源头开发3年的我们&#xff0c;肯定是需求不断的迭代更新的&#xff0c;目前我们已经迭代了3年之久&#xff0c;写技术文章已经写了短视频矩阵系统&#xff0c;写了3年了&#xff0c;开发了3年了 短视频矩阵获客系统是一种基于短视频平台的获客游戏。短视频矩阵系…

18个惊艳的可视化大屏(第29辑):机械自动化设备仪器

当涉及到机械自动化生产管理时&#xff0c;可视化大屏可以提供以下九个价值&#xff1a; 实时监控 可视化大屏可以实时显示生产线上的各个环节和设备的运行状态。运营人员可以通过大屏实时监控生产线的生产效率、设备运行状况等关键指标&#xff0c;及时发现并解决问题&#…

面试笔记——Redis(集群方案:主从复制、哨兵模式和分片集群)

主从复制 在 Redis 主从集群中&#xff0c;一个主节点&#xff08;Master&#xff09;负责处理客户端的读写请求&#xff0c;而多个从节点&#xff08;Slave&#xff09;则负责复制主节点的数据&#xff0c;并对外提供读取服务——解决高并发问题。 主节点&#xff08;Master&…

echart多折线图堆叠 y轴和实际数据不对应

当使用 ECharts 绘制堆叠折线图时&#xff0c;有时会遇到 y 轴与实际数据不对应的问题。 比如明明值是50&#xff0c;但折线点在y轴的对应点却飙升到了二百多 解决办法&#xff1a; 查看了前端代码发现在echart的图表中有一个‘stack’的属性&#xff0c;尝试把他删除之后y轴的…