QPixmap和QImage的区别及剖析 (转载) (2011-10-25 14:51:25)

本文详细解析了Qt中QPixmap和QImage的区别及其应用场景,特别是在Symbian平台上的使用技巧,介绍了如何避免因资源限制导致的大图片显示失败问题。

(一)QPixmap和QImage的区别

http://www.thisisqt.com/forum/viewthread.php?tid=267

QPixmap是专门为绘图而生,当需要绘制图片时你需要使用QPixmap。QImage则是为I/O,为图片像素访问以及修改而设计的。如果你想访问图片的像素或是修改图片像素,则需要使用QImage,或者借助于QPainter来操作像素。另外跟QImage不同是,QPixmap跟硬件是相关的,如X11, Mac 以及 Symbian平台上,QPixmap 是存储在服务器端,而QImage则是存储在客户端,在Windows平台上,QPixmap和QImage都是存储在客户端,并不使用任何的GDI资源。

相信大家更关心的是谁比较快,哈哈,现在来总结一下:
在X11, Mac 以及 Symbian平台上,QImage: 因为它是存储在客户端,往QImage上绘图比较快,但显示它则比较慢。QPixmap: 因为它是存储在服务器端,往QPixmap上绘图比较慢,但显示它则比较快。但在Windows平台上则是是一样的,因为它们都存储在客户端。

Qt上图片处理使用QPixmap和QImage时最多了,不过既然谈到图片了,我们把其他几个图片处理类也说一下:
QBitmap只是一个继承于QPixmap的简单类,它可以确保图片深度为1。
QBitmap是QPixmap的子类,提供单色图像,可以用来制作游标(QCursor)或者笔刷(QBrush)。

QPicture是一个绘画设备类,它记录了并可以重演QPainter的命令。你可以使用QPainter的begin()方法,指定在QPicture上绘图,使用end()方法结束绘图,使用QPicture的save()方法將QPainter所使用过的绘图指令存至档案。要重播绘图指令的话,建立一個QPicture,使用load()方法载入绘图指令的档案,然后在指定的绘图裝置上绘制QPicture:

(二)QImage与QPixmap完全解析

http://www.civilnet.cn/bbs/browse.php?topicno=4691

用Qt程序在手机上显示一幅图片对编程人员来说是再基础不过的一件事情了。那么先让大家看两段代码:

//dangerous should not be used, cannot display earth.png,   
//but if we change earth.png to a smaller image e.g. apple.png, apple.png can be displayed
QPixmap pixmap;
pixmap.load( ":/pics/earth.png" );
label->setPixmap( pixmap );


//dangerous should not be used, cannot display earth.png,   
//but if we change earth.png to a smaller image e.g. apple.png, apple.png can be displayed
QPixmap pixmap;
pixmap.load( ":/pics/earth.png" );
QPainter painter(this);
painter.drawPixmap(0,0, pixmap);
大家认为这两段代码有什么问题吗? 看起来好像没什么问题啊。是的,在Windows操作系统上是没有问题的。问题是我们做的是Qt for Symbian! 手机上的资源本来就是比较紧缺的,所以我们使用的时候就需要更加注意。 Qt 为我们提供了四个处理图像的类:QImage,QPixmap,QBitmap 和QPicture。其中前两个是最常使用的。

本文就通过一个例子,一步一步为大家讲解QImage与QPixmap的使用奥秘,在此过程中为大家揭示以上代码存在的缺陷。
QPixmap依赖于硬件

首先需要知道的是QPixmap的具体实现是依赖于系统的。在Symbian系统上QPixmap是被存放在Server端的。 
目前的Qt会把QPixmap都存储在graphics memory中,这明显是依赖硬件的。因此我们对QPixmap的使用需要格外注意。这也正是以上两段代码存在问题的根源。
那么Qt为什么要这么做呢?很简单,设计之初QPixmap就是用来加速显示的,例如我们在paint的时候用QPixmap就会比用其他类的效果好许多。

现在回到我们最初的问题,以上代码到底有什么问题呢?我们可以先用本文提供的例子程序做个试验。当使用上述代码显示较小图片的时候(比如例子程序中的background.png 和apple.png)是没有问题的,图片都能在手机上正确显示。
但是当我们把图片换成一副较大图片287KB,1058 x 1058的“earth.png”的时候就出现问题了,图片无法显示,程序的界面是一片空白。

据测算,“earth.png”被完全解码后存储在graphics memory中会占用大约4.3MB的空间。如果此时还有其他加载的窗口和QPixmap,很可能就没有空间了。
使用QImage加载后转换成QPixmap 显示

那么安全和正确的方法应该是什么呢?答案是我们需要用QImage做一下预处理:

//correct and recommended way
QImage image;
image.load( ":/pics/earth.png" );

QPainter painter(this);
QPixmap pixmapToShow = QPixmap::fromImage( image.scaled(size(), Qt::KeepAspectRatio) );
painter.drawPixmap(0,0, pixmapToShow);
和QPixmap 不同,QImage是独立于硬件的,它可以同时被另一个线程访问。QImage是存储在客户端的,对QImage的使用是非常方便和安全的。 又由于 QImage 也是一种QPaintDevice,因此我们可以在另一个线程中对其进行绘制,而不需要在GUI 线程中处理,使用这一方式可以很大幅度提高UI响应速度。 因此当图片较大时,我们可以先通过QImage将图片加载进来,然后把图片缩放成需要的尺寸,最后转换成QPixmap 进行显示。

其中需要注意的是Qt::KeepAspectRatio的使用,默认参数是Qt::IgnoreAspectRatio,如果我们在程序中这么写:QPixmap pixmapToShow = QPixmap::fromImage( image.scaled(size(), Qt::IgnoreAspectRatio) );

我们也可以直接使用QImage做显示,而不转换成QPixmap ,这要根据我们应用的具体需求来决定,如果需要的话我们可以这么写:

//correct, some times may be needed
QImage image;
image.load( ":/pics/earth.png" );

QPainter painter(this);
painter.drawImage(0,0, image);
// zyapp.cpp #include "zyapp.h" #include <QMessageBox> static zyapp* g_app = nullptr; // 解码回调 void CALLBACK MyDecCBFun(long nPort, char* pBuf, long nSize, FRAME_INFO* pFrameInfo, long nReserved1, long long nReserved2) { qDebug() << "Frame type:" << pFrameInfo->nType; if (pFrameInfo->nType == T_RGB32) { int width = pFrameInfo->nWidth; int height = pFrameInfo->nHeight; QImage img((uchar*)pBuf, width, height, QImage::Format_RGB32); if (g_app) { QMetaObject::invokeMethod(g_app, [img]() { g_app->videoLabel->setPixmap(QPixmap::fromImage(img).scaled( g_app->videoLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); }, Qt::QueuedConnection); } } } bool CALLBACK MyRealDataCallback(unsigned long lRealHandle, unsigned long dwDataType, unsigned char* pBuffer, unsigned long dwBufSize, int width, int height, int ra, void* dwUser) { qDebug() << "First bytes:" << QByteArray((const char*)pBuffer, qMin((int)dwBufSize, 16)).toHex(); zyapp* app = reinterpret_cast<zyapp*>(dwUser); if (app && app->g_nPort >= 0) { BOOL ret = VN_PLAY_InputData(app->g_nPort, pBuffer, dwBufSize); if (!ret) { qDebug() << "VN_PLAY_InputData failed, error:" << VN_PLAY_GetLastError(app->g_nPort); } } return true; } zyapp::zyapp(QWidget* parent) : QWidget(parent) { videoLabel = new QLabel(this); videoLabel->setGeometry(10, 10, 640, 480); videoLabel->setStyleSheet("background:black;"); videoLabel->setAttribute(Qt::WA_NativeWindow, true); // 关键:让label有HWND g_app = this; if (!initDevice()) { QMessageBox::critical(this, "错误", "登录或预览失败!"); } } zyapp::~zyapp() { releaseDevice(); } bool zyapp::initDevice() { if (!_VixHz_InitSDK()) { return false; } base::string strOutInfo; lLoginID = _VixHz_LoginDevice(const_cast<char*>(ip), 80, const_cast<char*>(username), const_cast<char*>(password), strOutInfo); if (lLoginID <= 0) { _VixHz_UnInitSDK(); return false; } if (!VN_PLAY_GetPort(&g_nPort)) { _VixHz_Logout(lLoginID); _VixHz_UnInitSDK(); return false; } if (!VN_PLAY_OpenStream(g_nPort, nullptr, 0, SOURCE_BUF_MIN * 1500)) { VN_PLAY_FreePort(g_nPort); _VixHz_Logout(lLoginID); _VixHz_UnInitSDK(); return false; } VN_PLAY_SetDecCallBack(g_nPort, MyDecCBFun); HWND hwnd = (HWND)(videoLabel->winId()); VN_PLAY_Play(g_nPort, hwnd, 0); // 0=显示到窗口 lRealHandle = _VixHz_StartRealPlay(lLoginID, 0, 1, hwnd, MyRealDataCallback, this); if (lRealHandle <= 0) { VN_PLAY_Stop(g_nPort); VN_PLAY_CloseStream(g_nPort); VN_PLAY_FreePort(g_nPort); _VixHz_Logout(lLoginID); _VixHz_UnInitSDK(); return false; } return true; } void zyapp::releaseDevice() { if (lRealHandle > 0) { _VixHz_StopRealPlay(lLoginID, lRealHandle); lRealHandle = -1; } if (g_nPort >= 0) { VN_PLAY_Stop(g_nPort); VN_PLAY_CloseStream(g_nPort); VN_PLAY_FreePort(g_nPort); g_nPort = -1; } if (lLoginID > 0) { _VixHz_Logout(lLoginID); lLoginID = -1; } _VixHz_UnInitSDK(); }这是我写的代码,但是现在无法显示视频,First bytes: "a6c514bbe01bed89c4f348d8c312004c" First bytes: "5f4cc560626a67aa9ff2a11c2c890937" First bytes: "0d817bed0c0c735dacb8d2cb09f62171" First bytes: "fc0ca03ae3251fcab5227f2a91a71966" First bytes: "000001ec05001955800af0050c99a865" First bytes: "bb85b2b2162ebb2ba91c6dc01d532a32" First bytes: "5814b175123815500ae7f16ef74a4a95" First bytes: "d4cabddec59c2b85792b19bf3c0116be" First bytes: "fc7ccb8bd5ff5c4cc340e03926d0aeca" First bytes: "63cf72b07456807e57e23bfbd327be1b" First bytes: "6281d3d40e906b4a22bc2bc3c3a5e95e" First bytes: "000001ec05001956800af0050c99a865" First bytes: "fc5c3a351b24a6701e1be5f474685433" First bytes: "a2af288dd6429aaf9348cd64214ef8fe" First bytes: "83821305f2eed83d7d5a54d9ceaacf9b" First bytes: "e54df768ba95a21663f65f79875a65b4" First bytes: "b5cf93a7613f440fb3bbb8961870fe9d" First bytes: "46b3571cd35396d0c7ad74db78728aa9" First bytes: "000001ec05001957800af0050c99a865" First bytes: "3f999bf80764fa6c057485c4943de388" First bytes: "df9ca219642bf3d26e23354c8cd211ad" First bytes: "1632a4c3ff8e93230f9264d835247793" First bytes: "f5c0492de1248604ad75e247881c3384" First bytes: "e79cc5b20b7ff266bd9729c4e82f40c7" First bytes: "000001ec05001958800af0050c99a865" First bytes: "f334a3691d5f76010066d61d2ed72a58" First bytes: "9509d9fe46dcd3f51607a960d90b4803" First bytes: "bda70be852e10092990520dd8f334dce" First bytes: "000001ec05001959800af0050c99a865" First bytes: "18c22b7644c029ff66935b7f65cb0488" First bytes: "9f51c64468490b053b9e703cc2978436" First bytes: "3a4838a4b8c3d1f25c511ced9febcc28" First bytes: "f440fb70a713de7c28e6a0e7291a6078" First bytes: "cfcf890fc6eea4b73cfde417d05055b4" First bytes: "d60397e4e05ac168b1ef64fdb3f67181" First bytes: "000001ec0500195a800af0050c99a865"这是日志
最新发布
07-01
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值