精通Qt4编程
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

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 图像浏览程序