QT图片查看器封装-鼠标中心缩放、移动、截图、框选、切换播放
Qt图片查看器
目录
1 简介... 1
2 功能实现... 2
2.1 图片以鼠标为中心放大缩小功能... 2
2.2 图片移动功能... 4
2.3 图片框选人脸功能... 6
2.4 图片无感知切换播放界面... 10
2.5 图片截图功能... 11
3 总结... 13
4 封装类压缩文件... 14
1 简介
Qt程序开发中经常需要展示图片,要实现图片的放大缩小,而且放大缩小是以鼠标为中心进行缩放;还要实现图片的截图等功能;左右切换查看,列表选择查看;对图片进行拖动,框选出图片中的人林,图片的截图功能,图片直接无感知切换播放,这些都是常规的图片操作,每次编写图片查看程序时,为了避免重复开发,将图片查看封装成一个类,可以在多个项目中使用;开发效果图如下图所示。
界面的交互界面设计如下图所示:
2 功能实现
2.1 图片以鼠标为中心放大缩小功能
鼠标滚动时,以鼠标所在位置为中心,进行缩放图片;关键是要计算放大缩小之后的中心位置;先按比例缩放图片的宽高,然后再计算图片坐标偏移量,将鼠标移动到新的位置,保证是鼠标位置为中心进行缩放,不会发生偏移;
放大效果图如下所示
缩小效果如下图所示
实现代码如下所示:
void DetailShowSinglePic::wheelEvent(QWheelEvent *event)
{
QRect rect = labelPicLeft.geometry();
QPoint cursorpositon = QCursor().pos();
QPoint pos = labelPicLeft.mapFromGlobal(cursorpositon);
double scale = 0.15;
if (event->delta() > 0)
{
m_zoomscale += scale;
int x = rect.x() - scale*pos.x();
int y = rect.y() - scale*pos.y();
labelPicLeft.hide();//图片移动时,会有闪烁重叠的问题,所以先hide,再show;
labelPicLeft.move(x, y);
labelPicLeft.show();
labelPicLeft.resize(rect.width()*(1 + scale), rect.height()*(1 + scale));
}
else
{
m_zoomscale -= scale;
if (m_zoomscale < scale)
{
m_zoomscale = scale;
return;
}
int x = rect.x() + scale*pos.x();
int y = rect.y() + scale*pos.y();
labelPicLeft.hide();
labelPicLeft.move(x, y);
labelPicLeft.show();
labelPicLeft.resize(rect.width()*(1 - scale), rect.height()*(1 - scale));
}
}
在开发过程中遇到移动Qlabel图片时,出现闪烁和重影问题,经过多次调试,还是有这样的情况,最后是移动之前隐藏,移动之后显示,解决了重影闪烁的问题;
2.2 图片移动功能
要实现图片在窗口中移动,需要实现鼠标按下、移动、释放三个函数;鼠标按下的时候保存起始位置,设置鼠标形状;鼠标移动响应函数中设置图片的位置,并重置起始位置;鼠标释放的时候,恢复鼠标形状;
void DetailShowSinglePic::mousePressEvent(QMouseEvent *event)
{
if (ui.widgetmid->underMouse())
{
m_bmousepressed = true;
QPoint cursorpositon = QCursor().pos();
m_mouseStartPos = ui.widgetmid->mapFromGlobal(cursorpositon);
setCursor(Qt::ClosedHandCursor);
}
}
void DetailShowSinglePic::mouseMoveEvent(QMouseEvent *event)
{
if (ui.widgetmid->underMouse())
{
if (m_bmousepressed)
{
QPoint cursorpositon = QCursor().pos();
QPoint endmouse = ui.widgetmid->mapFromGlobal(cursorpositon);
QPoint labelpicpos = labelPicLeft.pos();
labelpicpos.setX(labelpicpos.x() + endmouse.x() - m_mouseStartPos.x());
labelpicpos.setY(labelpicpos.y() + endmouse.y() - m_mouseStartPos.y());
labelPicLeft.move(labelpicpos);
m_mouseStartPos = endmouse;
}
}
}
void DetailShowSinglePic::mouseReleaseEvent(QMouseEvent *event)
{
setCursor(Qt::ArrowCursor);
m_bmousepressed = false;
}
2.3 图片框选人脸功能
如下图所示,需要在图片中画出一个四角矩形框,把目标的人脸给圈出来;让用户更加醒目的分辨出目标任务;而且能够随着图片的缩放而缩放,不会发生偏移;
实现方式新建一个类继承QLabel,然后实现paintEvent(QPaintEvent *)函数,在paintEvent(QPaintEvent *)函数用Qpaint画出图片,然后再图片上绘制矩形框;画图片要保持图片的宽高比,且最大占据Qlabel的空间;同时计算出人脸坐标再图片中位置,绘制出人脸图片;实现效果如下图所示:
实例代码也封装成了一个类,可以复用与其他项目:
头文件
#ifndef ASPECTRATIOPIXMAPLABEL_H
#define ASPECTRATIOPIXMAPLABEL_H
#include <QLabel>
#include<QPaintEvent>
#include"FaceDefine.h"
class AspectRatioPixmapLabel : public QLabel
{
Q_OBJECT
public:
AspectRatioPixmapLabel(QWidget *parent = 0);
QPixmap scaledPixmap() const;
void setPath(QString strPath)
{
this->clear();
m_strPath = strPath;
pix = QPixmap(m_strPath);
QSize labelsize = this->size();
pix = pix.scaled(this->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
setPixmap(pix);
m_bFaceShow = false;
update();
}
void setFaceRect(FaceRect rect)
{
m_bFaceShow = true;
m_rectFace = rect;
update();
}
void setScalPixmap(const QPixmap p);
QString getPath() { return m_strPath; }
protected:
void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE;
//void resizeEvent(QResizeEvent *event);
private:
QPixmap pix;
QString m_strPath = "";
QRect m_rectPic;//图片在图片区的位置,可能未完全填充 m_picrect包含m_rect
bool m_bFaceShow = false;
FaceRect m_rectFace;//人脸相对值比例
};
#endif // ASPECTRATIOPIXMAPLABEL_H
源文件
#include "aspectratiopixmaplabel.h"
#include<QPainter>
AspectRatioPixmapLabel::AspectRatioPixmapLabel(QWidget *parent) :
QLabel(parent)
{
setWindowModality(Qt::NonModal);
setStyleSheet("font-family: MicrosoftYaHeiUI;font-size: 14px;color:#028EC0;letter-spacing: 0;line-height: 20px;background:#000000;");
setScaledContents(false);
show();
}
void AspectRatioPixmapLabel::setScalPixmap(const QPixmap p)
{
setPixmap(scaledPixmap());
setContentsMargins(0,0,0,0);
}
QPixmap AspectRatioPixmapLabel::scaledPixmap() const
{
int low = this->size().width() < this->size().height() ? this->size().width() : this->size().height();
QPixmap pixw= pix.scaled(low,low, Qt::KeepAspectRatio, Qt::SmoothTransformation);
QSize szie = pixw.size();
return pixw;
}
void AspectRatioPixmapLabel::paintEvent(QPaintEvent *env)
{
//让图片按照Qlabel的大小进行缩放,保持宽高比。
//clear();
QPainter painter(this);
if (m_strPath!="")
{
QSize labelsize = this->size();
pix = QPixmap(m_strPath);
pix = pix.scaled(this->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
QSize pixsize = pix.size();
//根据图片的宽高和QLabel的宽高计算绘图的矩形区域和坐标
QRect rectPic(0, 0, pix.size().width(), pix.size().height());
if (pix.width() < this->width())
{
rectPic.setX((this->width() - pix.width()) / 2);
rectPic.setY(0);
rectPic.setWidth(pix.size().width());
rectPic.setHeight(pix.size().height());
}
if (pix.height() < this->height())
{
rectPic.setX(0);
rectPic.setY((this->height() - pix.height()) / 2);
rectPic.setWidth(pix.size().width());
rectPic.setHeight(pix.size().height());
}
QSize pimageszie = pix.toImage().size();
painter.drawPixmap(rectPic, pix);
//setPixmap(pix);
m_rectPic = rectPic;
//painter.setPen(QPen(Qt::red, 1, Qt::SolidLine, Qt::RoundCap));
//painter.drawRect(QRect(50, 50, 200, 200));
}
if (m_bFaceShow)
{
//QRect FaceRectContrast;
int x = m_rectFace.x * m_rectPic.width() + m_rectPic.left();
int y = m_rectFace.y * m_rectPic.height() + m_rectPic.top();
int width = m_rectFace.width * m_rectPic.width();
int height = m_rectFace.height * m_rectPic.height();
/*FaceRectContrast.setX(x);
FaceRectContrast.setY(y);
FaceRectContrast.setWidth(width);
FaceRectContrast.setHeight(height);*/
painter.setPen(QPen(QColor("#FF9c38"), 2, Qt::SolidLine, Qt::RoundCap));
//左上
painter.drawLine(x, y, x + 5, y);
painter.drawLine(x, y, x, y + 5);
//左下
painter.drawLine(x, y + height, x + 5, y + height);
painter.drawLine(x, y + height, x, y + height - 5);
//右上
painter.drawLine(x + width, y, x + width - 5, y);
painter.drawLine(x + width, y, x + width, y + 5);
//右下
painter.drawLine(x + width, y + height, x + width - 5, y + height);
painter.drawLine(x + width, y + height, x + width, y + height - 5);
}
}
2.4 图片无感知切换播放界面
在图片查看的时候,直接定位到视频中图片播放的位置,进行播放,并做到用户无感知;采用两层结构,上层是图片查看,下层是视频播放,图片是视频抓拍的图片;所以要实现用户无感知的切换,图片显示窗口和视频播放窗口同大小,且图片显示和视频播放都是按照分辨率的宽高比进行同比例缩放,这样图片和视频在窗口中相同的位置显示;上下两次切换时才有直接在图片上播放视频的效果;
void DetailShowSinglePic::SlotStartPlayVideo()
{
QString strvideo = "";
QString time = "";
if (m_currentAttri.contains("location"))
{
strvideo = m_currentAttri.value("location").toString();
}
if (strvideo == "")
{
LOG_ERROR("video path is empty!");
return;
}
if (m_currentAttri.contains("time"))
{
time = m_currentAttri.value("time").toString();
}
m_VedioPlayWidget.StartPlayVedioStrTime(strvideo, time);
int ret=m_VedioPlayWidget.SetScaleType(PLAYM4_ENUM_SCALE_FIT);// PLAYM4_ENUM_SCALE_FILL || type == PLAYM4_ENUM_SCALE_FIT
m_VedioPlayWidget.setGeometry(0, 0, ui.widgetmid->width(), ui.widgetmid->height());
m_VedioPlayWidget.show();
}
m_VedioPlayWidget是封装的一个播放窗口类;鼠标移动到上面之后,会出现标题栏和控制栏;
2.5 图片截图功能
有时需要对图片进行截图,或者在播放界面上进行截图,截图用于图片搜索,保存图片等功能;QT截取子窗口或者播放窗口图片分为两个步骤,先获取子窗口widget在屏幕中的坐标和宽高,然后调用抓取屏幕图片的方法抓取子窗口坐标和宽高的表示的区域;
(1)子窗口获取相对屏幕的坐标
假如要抓取子窗口widgetmid的图片,先计算widgetmid在整个屏幕中的坐标;
QRect widgetRect;
//widgetmid在屏幕中的坐标
QPoint point = ui.widgetmid->mapToGlobal(QPoint(0, 0));
widgetRect.setX(point.x());
widgetRect.setY(point.y());
//widgetmid的宽高
widgetRect.setWidth(ui.widgetmid->width());
widgetRect.setHeight(ui.widgetmid->height());
//抓子窗口区域图片,并显示在CutPicWidget
m_CutPicWidget.CutWidgetPic(widgetRect);
//m_CutPicWidget显示窗口截图,覆盖在ui.widgetmid之上
m_CutPicWidget.setGeometry(0, 0, ui.widgetmid->width(), ui.widgetmid->height());
m_CutPicWidget.show();
m_CutPicWidget.raise();
(2)抓取区域图片并保存
QPixmap m_widgetScreenPic;是一个变量
int CutPicWidget::CutWidgetPic(QRect rect)
{
//抓取区域截图
QScreen *screen = QGuiApplication::primaryScreen();
m_widgetScreenPic = screen->grabWindow(0, rect.x(), rect.y(), rect.width(), rect.height());//抓取widget的图片
m_widgetScreenPic = m_widgetScreenPic.scaled(QSize(rect.width(), rect.height()), Qt::KeepAspectRatio);
//显示图片
ui.labelPic->setPixmap(m_widgetScreenPic);
//保存图片
QString filePathName = "cut-";
filePathName += QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz");
filePathName += ".png";
filePathName = QDir::currentPath() + "/" + filePathName;
m_widgetScreenPic.save(filePathName);
return 0;
}
通过这两个步骤,就可以抓取指定窗口的图片,并显示保存;也可以抓取正在播放的视频画面;
3 总结
综上所述,本文实现了图片的查看的常用功能:缩放、移动、框选人脸、无感切换播放、截图等功能,并且对功能进行了封装,可以进行分类入库,用于其他项目,避免二次开发;
4 封装类压缩文件
将相关的封装类打包如下,可复制到电脑,解压后直接添加下面的文件到指定的工程项目,进行复用;