Qt线程处理图片(绘画、压缩、保存、压缩图片的读取)

1、绘画

Qt提供了QPainter类来对图片进行绘画,提供了QImage类对图片进行压缩和保存。

如果图片显示在UI上,我们就能够很正常的使用QPaintEvent函数对图片进行绘画的操作,但有时候,我们需要批量处理图片,并且在处理这些图片的过程中图片是不可现的。那么我们就需要开启线程去处理这些图片。

前段时间在线程处理图片时踩了一些坑,将这些坑分享一下。

刚开始,按照惯性思维,画图只能在QWidget类中的QPaintEvent中实现,因此在设置画布时继承了QLabel类,毕竟在以前的通过界面画图的时候就是这么干的。。

class PictureHelper : public QLabel
{
	Q_OBJECT
    Q_PROPERTY(PictureType pictureType READ pictureType WRITE setpictureType DESIGNABLE true)
public:
    static PictureHelper* Instance();	    //获取单实例
    static void			  Release();		//释放单实例;
    void setpictureType(PictureType nType);
    PictureType pictureType() const;
protected:
    void paintEvent(QPaintEvent* event) override;
private:
    explicit PictureHelper(QObject *parent = Q_NULLPTR);
    ~PictureHelper();
    void drawPoint(QPainter* pPainter);
    void drawText(QPainter* pPainter, QRect dRect);
private:
	static PictureHelper* m_pSelf;
	QImage m_Image;
	QPoint  m_hot;
  	QColor m_pointColor;
};
void PictureHelper::paintEvent(QPaintEvent *)
{
     QPainter dPainter(this);
     QPen dCapture = QPen(m_pointColor, 1, Qt::SolidLine, Qt::FlatCap);
     dPainter.setPen(dCapture);
     dPainter.drawPixmap(0, 0, m_Image);    //画布

     drawPoint(&dPainter);	//画点
 }

​ 当实现整个工程之后发现,函数 paintEvent(QPaintEvent* event) 根本没有调用,然后开始疯狂的各种找原因,找原因的过程中也了解到了很多知识。

​ 函数 paintEvent(QPaintEvent* event) 调用的时机:

  • 在窗口部件第一次显示时,系统会自动产生一个绘图事件,从而强制绘制这个窗口部件;
  • 重新调整窗口部件的大小时,系统也会产生一个绘制事件;
  • 窗口部件隐藏部分重新显示时,隐藏的部分会被重新产生一个绘制事件;
  • 手动调用QWidget::update() 或QWidget::repaint()强制产生一个重绘事件;两者的区别是:repaint会产生一个即时的事件,而update是在Qt下一次处理事件时才产生事件,因此update可以防窗口抖动。

按照上述方法,需要画图时手动调用了update甚至repaint都不能使paintEvent函数调用,紧接着去找paintEvent函数不调用的原因。
​ 函数 paintEvent(QPaintEvent* event) 不调用的原因有两种:

  • update 是被禁用的
  • 绘画的widget被隐藏了
  • 设置了QWidget 的 setAttribute(Qt::WA_TranslucentBackground,true); 属性,会引起很多刷新问题

​ 逐一排除原因,可以保证update函数没有被禁用,也没有设置 Qt::WA_TranslucentBackground 属性,那么就只剩下第二条了,可是第二条与我们的目标是相冲的,这肯定是不现实的。

​ 到这时候,突然发现paintEvent函数不能调用,那么是不是可以试一下不用这个函数呢?

​ 然后修改了函数:

void PictureHelper::paint()
{
    QPainter dPainter(this);
    QPen dCapture = QPen(m_pointColor, 1, Qt::SolidLine, Qt::FlatCap);
    dPainter.setPen(dCapture);
    dPainter.drawPixmap(0, 0, m_Image);    //画布

    drawPoint(&dPainter);	//画点
}

在需要画图的时候手动调用pait()函数进行画图,是不是就能够保证画图正常运行了。悲催的是修改之后有新问题:

QPainter::begin: Paint device returned engine == 0, type: 3
QPainter::save: Painter not active

qpainter setpen painter not active

报这个错的位置是 QPainter dPainter(this); 然后查找了下这个报错的原因:

没有在QPainterEvent中绘图,而是在其他处(如果想在其他位置使用QPainter,建议使用双缓冲机制,也就是用paintEvent函数)

​ 得,又回到原点了。

查找QPainter的源码发现

d->engine = pd->paintEngine();if (!d->engine) {
	qWarning("QPainter::begin: Paint device returned engine == 0, type: %d", pd->devType());
	return false;
}

查看Qt文档

QPaintEngine *QPainter::paintEngine() const
	Returns the paint engine that the painter is currently operating on if the painter is active; otherwise 0.
	See also isActive().
bool QPainter::isActive() const
	Returns true if begin() has been called and end() has not yet been called; otherwise returns false.
	See also begin() and QPaintDevice::paintingActive().

追踪pd->devType():

int QImage::devType() const
{
    return QInternal::Image;
}
class Q_CORE_EXPORT QInternal {
public:
     enum PaintDeviceFlags {
         UnknownDevice = 0x00,
         Widget        = 0x01,
         Pixmap        = 0x02,
         Image         = 0x03,
         Printer       = 0x04,
         Picture       = 0x05,
         Pbuffer       = 0x06,    // GL pbuffer
         FramebufferObject = 0x07, // GL framebuffer object
         CustomRaster  = 0x08,
         MacQuartz     = 0x09,
         PaintBuffer   = 0x0a,
         OpenGL        = 0x0b
     };
 };

至此,说明painter是为激活的,后面的type表明了绘画的基础类型,没什么具体的含义。
到现在,感觉已经无力回天了,那么,就需要换个方向重新走走看。所以回到了最初的设定,查看了QPainter的构造:

QPainter::QPainter(QPaintDevice *device)
Constructs a painter that begins painting the paint device immediately.
 This constructor is convenient for short-lived painters, e.g. in a QWidget::paintEvent() and should be used only once. The constructor calls begin() for you and the QPainter destructor automatically calls end().
 Here's an example using begin() and end():

再看看QPaintDevice类:

Header:
	#include <QPaintDevice> 
	qmake:
	QT += gui
	Inherited By:
	QImage, QOpenGLPaintDevice, QPagedPaintDevice, QPaintDeviceWindow, QPicture, and QPixmap
	
	List of all members, including inherited members

QPaintDevice继承了QImage,那么我们为什么需要多加一步QPainter dPainter(this);呢?
马上修改自定义类,取消继承QLabel,然后画图函数通过手动调用。

class PictureHelper
{
    Q_OBJECT
    ...
};
void PictureHelper::paint()
{
    QPainter dPainter(m_Image);
    QPen dCapture = QPen(m_pointColor, 1, Qt::SolidLine, Qt::FlatCap);
    dPainter.setPen(dCapture);

    drawPoint(&dPainter);	//画点
}

发现图片能够被正常绘画。

**总结一下:**出现问题时,我们总会在自己的舒适圈中找问题的答案,经常是撞得头破血流,如果跳出舒适圈,就会发现答案可能就在离你3米远的臭水沟。。。

2、压缩

下面说下图片的压缩,大多都知道,Qt提供了图片的压缩接口,使用QImage或者QPixmap的scaled函数能够实现图片的压缩,首先通过Qt文档查看下scaled这个函数:

QImage QImage::scaled(int width, int height, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio, Qt::TransformationMode transformMode = Qt::FastTransformation) const

This is an overloaded function.
Returns a copy of the image scaled to a rectangle with the given width and height according to the given aspectRatioMode and transformMode.
If either the width or the height is zero or negative, this function returns a null image.

下面看下enum Qt::AspectRatioMode:
This enum type defines what happens to the aspect ratio when scaling an rectangle.

typevalueinfomationinfomation
Qt::IgnoreAspectRatio0The size is scaled freely. The aspect ratio is not preserved.大小自由缩放,长宽比不保留
Qt::KeepAspectRatio1The size is scaled to a rectangle as large as possible inside a given rectangle, preserving the aspect ratio.在给定的矩形内,大小被缩放到一个尽可能大的矩形,保持高宽比。
Qt::KeepAspectRatioByExpanding2The size is scaled to a rectangle as small as possible outside a given rectangle, preserving the aspect ratio.大小被缩放到一个矩形,在给定的矩形外尽可能小,保持高宽比。

具体的效果请看下图:
在这里插入图片描述

下面看下enum Qt::TransformationMode:
This enum type defines whether image transformations (e.g., scaling) should be smooth or not.

typevalueinfomationinfomation
Qt::FastTransformation0The transformation is performed quickly, with no smoothing.快速压缩,图片质量不高
Qt::SmoothTransformation1The resulting image is transformed using bilinear filtering.平滑压缩,图片质量较高

3、保存

通过上面的函数,按照一定的大小压缩,可以压缩图片,但压缩比例可能会不尽人意。(亲测1.25M的bmp图片按照原先长宽的1/2压缩之后的大小为960K)
因此我们还需要QImage类的save函数进行二次压缩。

bool QImage::save(const QString &fileName, const char *format = Q_NULLPTR, int quality = -1) const

最后一个参数quality的取值决定保存的图片的大小,取值范围[0-100],值越小,压缩比例越大。保存图片的格式也会影响压缩的比例。

4、读取

下面再看下压缩之后图片的读取,以1280 * 1024的图片为例,压缩之后的大小640 * 512,文件大小114K。

假设我们使用QImage类将图片读到内存:

QImage image(filepath);

uchar* ba = image.bits();

 int size = image.byteCount();

得到的结果为:size = 640 * 512 = 327680,但其实图片的文件大小为:114 * 1024 = 116736,并且重新将ba中的数据保存为图片时,图片是损坏的。

因此压缩之后图片是有部分失真的,我们不能按照正常的图片读取,应该按照文件二进制流的形式读取。

QFile file(pic.path);	
if (!file.open(QIODevice::ReadOnly))
{
    return;
}
char *pBuff = Q_NULLPTR;
do
{
    pBuff = new char[pic.size + 1];
} while (pBuff == Q_NULLPTR);

QDataStream in(&file);
int buffSize = in.readRawData(pBuff, pic.size);

...	//to do something

if(pBuff != Q_NULLPTR)
{
	delete[] pBuff;
	pBuff = Q_NULLPTR;
}
  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值