文章目录
1、绘画2、压缩3、保存4、读取1、绘画
Qt提供了QPainter类来对图片进行绘画,提供了QImage类对图片进行压缩和保存。
如果图片显示在UI上,我们就能够很正常的使用QPaintEvent函数对图片进行绘画的操作,但有时候,我们需要批量处理图片,并且在处理这些图片的过程中图片是不可现的。那么我们就需要开启线程去处理这些图片。
前段时间在线程处理图片时踩了一些坑,将这些坑分享一下。
刚开始,按照惯性思维,画图只能在QWidget类中的QPaintEvent中实现,因此在设置画布时继承了QLabel类,毕竟在以前的通过界面画图的时候就是这么干的。。
class PictureHelper : public QLabel{Q_OBJECTQ_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: 3QPainter::save: Painter not activeqpainter 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() constReturns the paint engine that the painter is currently operating on if the painter is active; otherwise 0.See also isActive().
bool QPainter::isActive() constReturns 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 pbufferFramebufferObject = 0x07, // GL framebuffer objectCustomRaster = 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 += guiInherited By:QImage, QOpenGLPaintDevice, QPagedPaintDevice, QPaintDeviceWindow, QPicture, and QPixmapList 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) constThis 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.
具体的效果请看下图:
下面看下enum Qt::TransformationMode:
This enum type defines whether image transformations (e.g., scaling) should be smooth or not.
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 somethingif(pBuff != Q_NULLPTR){delete[] pBuff;pBuff = Q_NULLPTR;}
如果觉得《Qt线程处理图片(绘画 压缩 保存 压缩图片的读取)》对你有帮助,请点赞、收藏,并留下你的观点哦!