6.5 QImage与QPixmap绘图设备
6.5.1 QImage
Qt提供了4个处理图像的类:QImage、QPixmap、QBitmap和QPicture。它们有着各自的特点,QImage优化了I/O操作,可以直接存取操纵像素数据;QPixmap主要用来在屏幕上显示图像;QBitmap从QPixmap继承,只能表示两种颜色;QPicture是可以记录和重放QPainter命令的类。
QImage类提供了与硬件无关的图像表示方法,通过QImage可以直接存取像素数据,QImage也可以用作绘图设备。QImage支持的图像颜色可以是单色、8位、32位和Alpha混合格式。
因为QImage从QPaintDevice继承,所以QPainter可以直接在QImage上绘图。除了绘制文字外(QFont依赖于底层GUI),其他的绘制操作可以在任意线程中完成。如果要在其他线程中绘制文字,可以使用QPainterPath。QImage对象具有隐式共享(在第13章中会详细介绍),作为传值参数,可以使用数据流及进行比较等特性。
读入图像文件数据可以通过QImage的构造函数、load()函数、loadFromData()几种方法完成。还可以通过QImage的静态函数fromData()由指定数据构造一个QImage对象。既可以从文件系统装入,也可以从Qt应用程序的嵌入式资源中读取。使用save()函数可以保存QImage对象。
可以通过QImageReader::supportedImageFormats()和QImageWriter::supportedImage Formats()函数获取QImage支持的所有文件格式列表。Qt支持如表6-3所示格式的图像文件。
表6-3 QImage支持的图像文件
如果要支持新的图像格式,可以编写相应的插件并加载。QImage的函数很丰富,可以分为如表6-4所示的几类。
表6-4 QImage函数概览
QImage的8位和单色图像采用颜色索引表的方式存取,32位的图像则直接存储ARGB值,因此它们像素的操作函数也不相同。对32位图像,setPixel()函数可以改变指定像素的QRgb颜色值。对8 位和单色图像,setPixel()改变在预定义颜色表中的索引值。如果要改变颜色表,可以使用setColor()函数。
QImage提供scanLine()函数返回指定行的数据。bits()函数返回第一个像素的指针。每个像素在QImage中都使用整数表示。单色图像使用一位的索引指向只有两种颜色的调色板。有两种类型的单色图像,即big endian(MSB)和little endian(LSB)。
256色的图像使用8位索引的调色板,调色板的数据类型是QVector<QRgb>,QRgb实际上是无符号整型数,存储ARGB的格式是0xAARRGGBB。32位的图像直接存储。有三种类型的存储格式:RGB,ARGB和已预乘(premultiplied)的ARGB值。在已预乘ARGB类型中,红绿蓝三色已经和Alpha相乘并模除255。
allGray()和isGrayscale()函数可以判断一个彩色图像能否安全地转化为灰度图像。图像的格式可以用format()函数取出。convertToFormat()可以进行图像格式转换。QImage支持的存储格式如表6-5所示。
表6-5 QImage图像存储格式
6.5.2 Pixmap
QPixmap主要完成屏幕后台(off-screen)缓冲区绘图。QPixmap对象可以使用QLabel或QAbstractButton子类(QPushButton和QToolButton)显示。QLabel通过设置pixmap属性,QAbstractButton通过设置icon属性来完成。
除了使用构造函数来初始化,QPixmap对象还可以使用静态函数grabWidget()和grabWindow()函数创建,并绘制指定的窗口和窗口部件。
QPixmap中的像素数据是内部的,并且由底层的窗口系统进行管理。如果要存取像素,只有通过QPainter函数或将QPixmap对象转换为QImage对象。根据底层系统的不同,QPixmap可以RGB32或混合alpha格式存储。如果图像有Alpha通道且底层系统允许,则优先使用混合alpha格式。因此QPixmap是依赖于底层系统的。在X11和Mac系统上,QPixmap存储在服务器端,QImage存储在客户端。在Windows系统上,这两个类是用相同方式表示的。
QImage和QPixmap可以相互转换。通常QImage载入图像并进行直接操作,然后转换为QPixmap在屏幕上显示。如果不需要操作像素,就直接使用QPixmap。在Windows上,QPixmap还可以与HBITMAP之间互相转换。QPixmap同QImage一样也使用隐式共享,也能够使用数据流。
下面通过实现图像浏览器来学习在Qt中如何处理图像。这个图像浏览器能够实现图像的浏览、旋转和放大等简单功能。
用来显示图像的窗口部件定义如下:
#ifndef IMAGEWIDGET_H_ #define IMAGEWIDGET_H_ #include <QtGui> class ImageWidget : public QWidget { Q_OBJECT public: bool bFit; // 图像是否匹配窗口尺寸 qreal scale; // 图像缩放值 ImageWidget(QWidget *parent = 0); void setPixmap(QString fileName); QPixmap getPixmap(); void setAngle(qreal rotateAngle); protected: void paintEvent(QPaintEvent *event); private: QPixmap pixmap; qreal angle; }; #endif /*IMAGEWIDGET_H_*/
ImageWidget类定义中的setAngle()函数用来设置图像旋转的角度。
ImageWidget类实现文件如下:
#include <QtCore> #include "imagewidget.h" ImageWidget::ImageWidget(QWidget *parent) : QWidget(parent) { QDesktopWidget desktop; pixmap = QPixmap(desktop.width(), desktop.height()); scale = 1; angle = 0; bFit = true; }
在构造函数中设置了变量的初始值。
绘图函数paintEvent()完成图像的显示。
void ImageWidget::paintEvent(QPaintEvent *event) { QPainter painter(this); if(angle) { QPointF center(width()/2.0,height()/2.0); painter.translate(center); painter.rotate(angle); painter.translate(-center); } if(bFit) { QPixmap fitPixmap = pixmap.scaled(width(),height(), Qt::KeepAspectRatio); painter.drawPixmap(0, 0, fitPixmap); } else painter.drawPixmap(0, 0, pixmap); }
在绘图函数中判断图像是否要旋转,是否要按实际大小显示。
设置和获取当前显示的图像的函数setPixmap()和getPixmap():
void ImageWidget::setPixmap(QString fileName) { pixmap.load(fileName); update(); } QPixmap ImageWidget::getPixmap() { return pixmap; }
设置旋转角度的函数setAngle():
void ImageWidget::setAngle(qreal rotateAngle) { angle += rotateAngle; update(); }
主窗口的头文件如下:
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QScrollArea> #include <QDir> #include "imagewidget.h" class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(); public slots: void selectDir(); void next(); void prev(); void rotateLeft(); void rotateRight(); void zoomIn(); void zoomOut(); void actualSize(); void fitSize(); void copy(); void print(); void present(); protected: void resizeEvent(QResizeEvent * event); private: void createActions(); void createMenus(); void createToolBars(); void createStatusBar(); QScrollArea *scrollArea; ImageWidget *imageWidget; QMenu *naviMenu; QMenu *operMenu; QToolBar *naviToolBar; QToolBar *operToolBar; QAction *dirAct; QAction *nextAct; QAction *prevAct; QAction *leftAct; QAction *rightAct; QAction *zoomInAct; QAction *zoomOutAct; QAction *actualSizeAct; QAction *fitSizeAct; …… QStringList imageList; int index; QDir imageDir; QClipboard *clipboard; }; #endif
主要在头文件中定义了菜单、按钮及对应的QAction。
主窗口的实现文件如下:
#include <QtGui> #include "mainwindow.h" MainWindow::MainWindow() { imageWidget = new ImageWidget; scrollArea = new QScrollArea; scrollArea->setBackgroundRole(QPalette::Dark); imageWidget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); scrollArea->setWidget(imageWidget); scrollArea->widget()->setMinimumSize(320, 240); setCentralWidget(scrollArea); createActions(); createMenus(); createToolBars(); createStatusBar(); setWindowTitle(tr("zeki")); setFocusPolicy(Qt::StrongFocus); index = 0; imageDir.setPath("/windows/E/Wife/USA photo/5.3"); QStringList filter; filter << "*.jpg" << "*.bmp" << "*.jpeg" << "*.png" << "*.xpm"; imageList = imageDir.entryList ( filter, QDir::Files ); next(); } void MainWindow::resizeEvent(QResizeEvent * event) { QRect childRect = scrollArea->childrenRect(); imageWidget->resize(childRect.size()); } void MainWindow::createActions() { dirAct = new QAction(QIcon(":/images/open.png"), tr("Open"), this); dirAct->setShortcut(QKeySequence::Open); connect(dirAct, SIGNAL(triggered()), this, SLOT(selectDir())); prevAct = new QAction(QIcon(":/images/previous.png"), tr("Previous"), this); prevAct->setShortcut(QKeySequence::Back); connect(prevAct, SIGNAL(triggered()), this, SLOT(prev())); nextAct = new QAction(QIcon(":/images/next.png"), tr("Next"), this); nextAct->setShortcut(QKeySequence::Forward); connect(nextAct, SIGNAL(triggered()), this, SLOT(next())); leftAct = new QAction(QIcon(":/images/rotate_left.png"), tr("Left"), this); leftAct->setShortcut(tr("Ctrl+L")); connect(leftAct, SIGNAL(triggered()), this, SLOT(rotateLeft())); rightAct = new QAction(QIcon(":/images/rotate_right.png"), tr("Right"), this); rightAct->setShortcut(tr("Ctrl+R")); connect(rightAct, SIGNAL(triggered()), this, SLOT(rotateRight())); zoomInAct = new QAction(QIcon(":/images/zoomin.png"), tr("ZoomIn"), this); zoomInAct->setShortcut(QKeySequence::ZoomIn); connect(zoomInAct, SIGNAL(triggered()), this, SLOT(zoomIn())); zoomOutAct = new QAction(QIcon(":/images/zoomout.png"), tr("ZoomOut"), this); zoomOutAct->setShortcut(QKeySequence::ZoomOut); connect(zoomOutAct, SIGNAL(triggered()), this, SLOT(zoomOut())); actualSizeAct = new QAction(QIcon(":/images/actualsize.png"), tr("Actual"), this); actualSizeAct->setShortcut(Qt::Key_Home); connect(actualSizeAct, SIGNAL(triggered()), this, SLOT(actualSize())); fitSizeAct = new QAction(QIcon(":/images/fitwindow.png"), tr("Fit"), this); fitSizeAct->setShortcut(Qt::Key_End); connect(fitSizeAct, SIGNAL(triggered()), this, SLOT(fitSize())); …… } void MainWindow::createMenus() { naviMenu = menuBar()->addMenu(tr("Navigation")); naviMenu->addAction(prevAct); naviMenu->addAction(nextAct); operMenu = menuBar()->addMenu(tr("Operation")); operMenu->addAction(leftAct); operMenu->addAction(rightAct); operMenu->addAction(zoomInAct); operMenu->addAction(zoomOutAct); operMenu->addAction(actualSizeAct); operMenu->addAction(fitSizeAct); …… } void MainWindow::createToolBars() { naviToolBar = addToolBar(tr("Navigation")); naviToolBar->addAction(dirAct); naviToolBar->addSeparator(); naviToolBar->addAction(prevAct); naviToolBar->addAction(nextAct); operToolBar = addToolBar(tr("Operation")); operToolBar->addAction(leftAct); operToolBar->addAction(rightAct); operToolBar->addAction(zoomInAct); operToolBar->addAction(zoomOutAct); operToolBar->addAction(actualSizeAct); operToolBar->addAction(fitSizeAct); …… } void MainWindow::createStatusBar() { statusBar()->showMessage(tr("Ready")); } // 选择浏览的目录 void MainWindow::selectDir() { QString dir = QFileDialog::getExistingDirectory(this, tr("Open Directory"), QDir::currentPath(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); if(dir.isEmpty()) return; imageDir.setPath(dir); QStringList filter; filter << "*.jpg" << "*.bmp" << "*.jpeg" << "*.png" << "*.xpm"; imageList = imageDir.entryList ( filter, QDir::Files ); next(); } // 下一幅图像 void MainWindow::next() { if(index < imageList.size()) { imageWidget->setPixmap(imageDir.absolutePath() + QDir::separator() + imageList.at(index)); statusBar()->showMessage(imageList.at(index) index++; } } // 前一幅图像 void MainWindow::prev() { if(index > 0) { imageWidget->setPixmap(imageDir.absolutePath() + QDir::separator() + imageList.at(index)); statusBar()->showMessage(imageList.at(index)); index--; } } // 左转90° void MainWindow::rotateLeft() { imageWidget->setAngle(-90); } // 右转90° void MainWindow::rotateRight() { imageWidget->setAngle(90); } // 放大图像 void MainWindow::zoomIn() { imageWidget->scale *= 1.25; zoomInAct->setEnabled(imageWidget->scale < 3); zoomOutAct->setEnabled(imageWidget->scale > 0.333); imageWidget->resize(imageWidget->scale * scrollArea->size()); } // 缩小图像 void MainWindow::zoomOut() { imageWidget->scale *= 0.8; zoomInAct->setEnabled(imageWidget->scale < 3); imageWidget->resize(imageWidget->scale * scrollArea->size()); } // 显示图像实际大小 void MainWindow::actualSize() { imageWidget->scale = 1; imageWidget->bFit = false; imageWidget->update(); } // 匹配窗口大小 void MainWindow::fitSize() { imageWidget->scale = 1; imageWidget->bFit = true; imageWidget->update(); } // 全屏显示 void MainWindow::present() { statusBar()->hide(); menuBar()->hide(); naviToolBar->hide(); operToolBar->hide(); showFullScreen(); }
程序的运行效果如图6-15所示。
图6-15 图像浏览程序