图像表示和图像处理概述
颜色数据格式
图像数据可以看作是二维数组,数组每个元素就是1 像素的颜色数据,在绘图设备上显示图像就是设置每个像素的颜色。任何颜色在显像时都是红色、绿色、蓝色三原色的组合。
颜色数据表示格式有以下几种:
RGB32:用 32 位无符号整数表示颜色,数据格式为 0xffRRGGBB,其中最高字节的 ff 是 无意义的,实际是用 24 位有效数据表示颜色。因为 32 位无符号整数(quint32)是标准的整数格式,所以在计算机上存储的图片文件一般采用这种格式表示像素的颜色。
RGB888:即红色、绿色、蓝色各用 1 字节表示,数据格式为 0xRRGGBB,1 像素的颜色占用 3 字节。但是 3 字节数据不是标准类型的整数,所以即使在内存中表示颜色使用了 RGB888 格式,在计算机上保存图片文件时也会使用 RGB32 格式。
ARGB32:在 RGB32 的基础上用 1 字节表示 Alpha 值,数据格式为 0xAARRGGBB。Alpha 值一般作为不透明度参数,Alpha 为 0 表示完全透明,Alpha 为 100 表示完全不透明。
RGBA32:与 ARGB32 类似,只是存储格式是 0xRRGGBBAA。
RGB565:用 16 位无符号整数(quint16)表示 1 像素的颜色,其中红色占 5 位,绿色占 6 位,蓝色占 5 位,1 像素只需用 2 字节就可以表示。一些嵌入式设备的 TFT-LCD 会用 RGB565 表示像素的颜色,这样可以节省存储空间,提高处理效率。
Grayscale8:用 8 位无符号整数(quint8)表示 1 像素的灰度颜色。
Grayscale16:用 16 位无符号整数(quint16)表示 1 像素的灰度颜色。
图像文件格式
图像数据可以保存为不同格式的图片文件,常见的图片文件格式有 BMP、JPG、PNG、SVG 等。这些文件格式是在像素数据的基础上附加一些头部控制信息(如图像高度、宽度、颜色格式等),根据遵循的协议,封装成一个带后缀的文件。显示时根据图像的拆包协议可以提取压缩/非压缩的图像数据。
BMP 是位图文件格式,其文件头存储图像的一些信息,如图像宽度、高度、颜色数据格式等,图像中所有像素的颜色数据被无修改地保存在文件里,例如每个像素的颜色是一个RGB32 的数据。BMP 是一种无损图片文件格式,保留了图像的原始颜色数据。
JPG 是使用了联合图像专家组(joint photographic experts group,JPEG)图像压缩算法的图片文件格式,是一种有损压缩格式,可以在保持较高图像质量的情况下使文件大小减小很多,从而节省存储空间。
PNG便携式网络图形(portable network graphics)是一种无损压缩图片文件格式,它具有一定的压缩率,文件解压后就是真实的原图。PNG 图像的颜色数据可以有 Alpha 通道,例如颜色数据格式可以是 ARGB32。
SVG 是基于 XML,描述图像绘制方法的图片文件格式。SVG 文件存储的是绘制图像的过程,而不是图像的像素颜色数据。
BMP、JPG 和 PNG 都是基于图像中所有像素颜色数据的文件格式,BMP 是无压缩的位图文件格式,JPG 是有损压缩文件格式,PNG 是无损压缩文件格式。使用 QImage 和 QPixmap 类可以直接加载这 3 种格式的文件。
SVG 是基于 XML 的矢量图文件格式,不能用 QImage 和 QPixmap 类处理。要读取和显示 SVG 图片文件,需要使用 QSvgRenderer 和 QSvgWidget 类。
QImage 类与QPixmap类
QImage 是一种绘图设备类它可以读取 BMP、JPG、PNG 等格式的图片文件,存储图像中所有像素的颜色数据。QImage 的接口函数可以实现图像的缩放、旋转、镜像翻转等简单处理,可以转换颜色数据格式。因为 QImage 可以读写图像中每个像素的颜色数据,所以结合图像处理算法,我们可以对图像进行各种处理,例如调整亮度、调整对比度、模糊化处理等。
QPixmap是为实现在屏幕上显示图像而优化设计的类。QPixmap主要用于在界面上显示图像,它可以对图像 进行缩放,但是不能像 QImage 那样对图像进 行像素级的处理。
QImage 类
加载与保存图像数据
QImage(const QString &fileName, const char *format = nullptr) //指定图片文件名
QImage(int width, int height, QImage::Format format) //设置图像大小
QImage() //不设置任何参数
第一种指定图片文件名,创建的 QImage 对象会加载图片文件内的图像数据。参数 format 是图片文件的格式,用“BMP”“JPG”等字符串表示。如果不设置参数 format,程序会根据文件名的后缀自动判断图片文件格式。
第二种创建指定宽度和高度的图像,宽度和高度的单位是像素,参数 format 是像素的颜色数据的格式,是枚举类型 QImage::Format。创建后可以用来绘图(结合QPainter)和图像处理,类似于OpenCV中的Mat。
第三种创建 QImage 对象,之后一般会用函数 load()加载图片文件。可以从文件或其他 I/O 设备加载图像数据,也可以将图像数据保存为文件或保存到其他 I/O 设备中。
bool QImage::load(const QString &fileName, const char *format = nullptr)
bool QImage::save(const QString &fileName, const char *format = nullptr, int quality = -1)
其中,参数 fileName 是图片文件名。参数 format 是图片文件格式,参数 quality 是保存为有损压缩图片文件(如 JPG 图片文件)时的品质参数,取值为 0 表示最低品质,压缩后文件最小;取值为 100 表示最高品质,图像无压缩;取值为-1 表示使用默认的品质参数。
另一组 load()和 save()函数可以从其他 I/O 设备加载数据和保存数据:
QImage image("Save_as.png");
QByteArray ba; //字节数组
QBuffer buffer(&ba); //缓冲区对象
buffer.open(QIODevice::WriteOnly); //以只读模式打开缓冲区
image.save(&buffer, "PNG"); //将图像以 PNG 格式写入缓冲区,也就是写到字节数组 ba 里
函数 loadFromData()可以从字节数组中加载图像数据,其中一种定义如下:
bool QImage::loadFromData(const QByteArray &data, const char *format = nullptr)
其中,参数 data 是字节数组;format 是图片文件格式,这个函数中的参数 data 可以是用QFile::readAll()函数读取的图片文件的全部内容,也可以是用 QImage::save()保存到缓冲区中的数据。如果是在内存中复制 QImage 对象的图像数据,使用缓冲区是最高效的。
图像信息
QImage 提供的一些接口函数可以获取图像的一些信息:
图像格式:返回值是枚举类型 QImage::Format。详细查看qimage.h中关于Format的定义。
图像深度和位平面数:图像深度就是指 1 像素的颜色数据的位数,它与图像格式有关,例如RGB32 格式是 32 位,RGB565 格式是 16 位。位平面数就是指 1 像素的颜色和透明度数据的有效位数,它的值小于或等于图像深度值。例如对于 RGB32 格式,数据是 0xffRRGGBB,其图像深度是 32 位,但是位平面数是 24 位,因为只有 24 位数据有效。
图像的大小:QImage 的函数 width()和 height()分别返回图像的宽度和高度,单位是像素。函数 sizeInBytes()返回图像中所有像素的颜色数据所占用的字节数。其值等于width()×height()×depth()/8。
图像的分辨率:函数 dotsPerMeterX()和 dotsPerMeterY()分别返回图像在水平和垂直方向上的 DPM 分辨率。(1 DPI = 0.0254 DPM)通过设置图像的水平和垂直方向上的分辨率,可以调整图像大小,也可以改变长宽比。
图像数据访问
通过 QImage 的接口函数可以读写图像中每个像素的颜色数据,可以进行像素级的图像处理。像素颜色数据表示有直接和间接两种方式:8 位格式图像有颜色表,是间接方式;其他格式图像中每个像素直接用一个 QRgb 数据表示颜色。
读取像素颜色
使用函数 pixel()
QRgb QImage::pixel(int x, int y) //返回像素(x, y)的颜色数据
返回的颜色数据是 QRgb 类型的,QRgb 就是无符号 32 位整数,它以 0xAARRGGBB 的格式表示颜色的 Alpha 通道以及红色、绿色、蓝色对应的数值,我们用(a, r, g, b)表示这 4 种成分的数值。对于QRgb 类型的处理可以使用以下接口:
使用函数 pixelColor()
QColor QImage::pixelColor(int x, int y) //返回像素(x, y)的颜色数据
返回数据类型是 QColor,QColor 是表示颜色的类,它有一些接口函数可以获取颜色成分数值,也可以将 QColor 表示的颜色转换为 QRgb 类型的颜色:
QRgb QColor::rgb() //返回颜色的 QRgb 类型数据,Alpha 成分值是 255
QRgb QColor::rgba() //返回颜色的 QRgb 类型数据,包括 Alpha 通道的值
int QColor::red() //返回颜色中红色成分的数值
int QColor::green() //返回颜色中绿色成分的数值
int QColor::blue() //返回颜色中蓝色成分的数值
int QColor::alpha() //返回颜色中 Alpha 通道的数值
QColor 有丰富的接口函数用于颜色数据处理,它可以用 RGB、HSV、CMYK 等模式表示颜色数据,可以修改颜色的饱和度和亮度。
设置像素颜色
使用函数 setPixel()和 setPixelColor()可以设置某个像素的颜色
void QImage::setPixel(int x, int y, uint index_or_rgb)
void QImage::setPixelColor(int x, int y, const QColor &color)
函数 setPixel()设置像素(x, y)的颜色索引或 QRgb 颜色数据。对于带颜色表的图像,参数 index_or_rgb 是颜色索引;对于不带颜色表的图像,参数 index_or_rgb 是 QRgb 颜色数据值。 函数 setPixelColor()设置的颜色是 QColor 类型的,所以这个函数不适用于 8 位图像。
图像处理
QImage 提供了一些接口函数来对图像进行处理,例如镜像翻转、缩放、图像格式转换等,这
些接口函数如下表所示,表中省略了函数的输入参数。
示例程序解读
主窗口头文件
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>#include <QImage>
#include <QPrinter>QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTprivate:QString m_filename; //当前图片文件名QImage m_image; //原始图像void showImageFeatures(bool formatChanged=true); //显示图像属性void imageModified(bool modified=true); //图像被修改了,改变actions状态void printImage(QPainter *painter, QPrinter *printer); //打印图像void printRGB565Data(QPainter *painter, QPrinter *printer); //打印RGB565数据public:MainWindow(QWidget *parent = nullptr);~MainWindow();private slots:void do_paintRequestedImage(QPrinter *printer); //用于打印图片void do_paintRequestedText(QPrinter *printer); //用于打印文本void on_actFile_Open_triggered(); //打开图像文件void on_actImg_RotateLeft_triggered(); //左旋void on_actImg_FlipUD_triggered(); //上下翻转void on_actImg_FlipLR_triggered(); //左右翻转void on_actImg_RotateRight_triggered(); //右旋void on_actFile_Reload_triggered(); //重新加载void on_btnFormatConvert_clicked(); //格式转换void on_btnGetRGB565_clicked(); //RGB565格式数据void on_btnSaveDataFile_clicked(); //保存RGB565void on_actFile_Save_triggered(); //保存图像void on_actFile_SaveAs_triggered(); //另存为void on_actFile_Print_triggered(); //打印void on_actFile_Preview_triggered(); //打印预览void on_actImg_ZoomIn_triggered(); //放大void on_actImg_ZoomOut_triggered(); //缩小private:Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
变量 m_filename 用于保存当前打开的图片文件名,变量 m_image 是当前图像对象。函数 printImage()用于打印图片,函数 printRGB565Data()用于打印文本数据。这里注意用到了打印功能,需要在项目配置文件(.pro 文件)中加入语句:QT += printsupport
打开文件
通过对话框选择一个图片文件后, 使用 QImage::load()函数加载图片文件。QImage 会自动解析图片文件格式,读取文件内的数据,在内存中存储图像中所有像素的颜色数据。为了在界面上的QLabel 组件上显示图像,程序还创建了一个 QPixmap 对象。QPixmap 是适合在界面上显示图片的绘图设备类。
void MainWindow::on_actFile_Open_triggered()
{//”打开”按钮QString curPath= QDir::currentPath(); //应用程序当前目录QString filter="图片文件(*.bmp *.jpg *.png);;""BMP文件(*.bmp);;JPG文件(*.jpg);;PNG文件(*.png)";QString fileName=QFileDialog::getOpenFileName(this,"选择图片文件",curPath,filter);if (fileName.isEmpty())return;ui->statusbar->showMessage(fileName);m_filename=fileName; //保存当前图片文件名QFileInfo fileInfo(fileName);QDir::setCurrent(fileInfo.absolutePath()); //设置应用程序当前目录m_image.load(fileName); //载入图片文件QPixmap pixmap=QPixmap::fromImage(m_image);ui->labPic->setPixmap(pixmap); //在QLabel组件上显示图片ui->tabWidget->setCurrentIndex(0);showImageFeatures(); //显示图片属性ui->frameLeft->setEnabled(true);ui->actFile_SaveAs->setEnabled(true);ui->actImg_ZoomIn->setEnabled(true);ui->actImg_ZoomOut->setEnabled(true);ui->actImg_FlipLR->setEnabled(true);ui->actImg_FlipUD->setEnabled(true);ui->actImg_RotateLeft->setEnabled(true);ui->actImg_RotateRight->setEnabled(true);ui->actFile_Print->setEnabled(true);ui->actFile_Preview->setEnabled(true);
}
打开文件后,通过QImage类的format()函数获取图像的格式,然后读取深度(depth()函数)、位平面(bitPlaneCount()函数)、图像字节数(sizeInBytes()函数)、长宽(height()与width()函数)、DPM(dotsPerMeterX()与dotsPerMeterY()函数)等信息。并显示在视图组件中。
输入参数 formatChanged,其默认值为 true。当这个参数值为true 时,表示需要获取与图像格式相关的信息,包括图像格式、图像深度、是不是灰度图等信息,在图像格式变化后才需要获取这些信息。当参数 formatChanged 值为 false 时,只获取图像的宽度、高度、数据字节数等信息,在对图像进行翻转、缩放等处理后只需刷新显示这些信息。
//formatChanged, 改变了图像格式
void MainWindow::showImageFeatures(bool formatChanged)
{if (formatChanged) //格式转换后需要显示全部信息{QImage::Format fmt=m_image.format(); //图像格式if (fmt == QImage::Format_RGB32)ui->editImg_Format->setText("32-bit RGB(0xffRRGGBB)");else if (fmt == QImage::Format_RGB16)ui->editImg_Format->setText("16-bit RGB565");else if (fmt == QImage::Format_RGB888)ui->editImg_Format->setText("24-bit RGB888");else if (fmt == QImage::Format_Grayscale8)ui->editImg_Format->setText("8-bit grayscale");else if (fmt == QImage::Format_Grayscale16)ui->editImg_Format->setText("16-bit grayscale");else if (fmt == QImage::Format_ARGB32)ui->editImg_Format->setText("32-bit ARGB(0xAARRGGBB)");else if (fmt == QImage::Format_Indexed8)ui->editImg_Format->setText("8-bit indexes into a colormap");elseui->editImg_Format->setText(QString("Format= %1,其他格式").arg(fmt));ui->editImg_Depth->setText(QString("%1 bits/pixel").arg(m_image.depth()));ui->editImg_BitPlane->setText(QString("%1 bits").arg(m_image.bitPlaneCount()));ui->chkBox_Alpha->setChecked(m_image.hasAlphaChannel());ui->chkBox_GrayScale->setChecked(m_image.isGrayscale());}//缩放,或旋转之后显示大小信息ui->editImg_Height->setText(QString("%1 像素").arg(m_image.height()));ui->editImg_Width->setText(QString("%1 像素").arg(m_image.width()));qsizetype sz =m_image.sizeInBytes(); //图像数据字节数if (sz<1024*9)ui->editImg_SizeByte->setText(QString("%1 Bytes").arg(sz));elseui->editImg_SizeByte->setText(QString("%1 KB").arg(sz/1024));QString dpi=QString::asprintf("DPI_X=%.0f, DPI_Y=%.0f",m_image.dotsPerMeterX()*0.0254,m_image.dotsPerMeterY()*0.0254);ui->editImg_DPM->setText(dpi); //DPI分辨率}
图像保存、另存为、重载
保存选项是将当前处理的图像保存到源文件中(之前的m_filename保存了打开的文件路径名),会覆盖保存;另存为会打开一个文件会话框,可以设置与原文件名后缀不同的文件名,例如原来是BMP 文件,可以另存为 JPG 文件,QImage::save()函数会自动进行文件格式转换。重载图像就是放弃当前的图像处理结果,重新载入图像文件(通过m_filename文件路径名);
void MainWindow::on_actFile_Save_triggered()
{m_image.save(m_filename); //保存到当前文件imageModified(false);
// ui->actFile_Save->setEnabled(false);
// ui->actFile_Reload->setEnabled(false);
}void MainWindow::on_actFile_SaveAs_triggered()
{
// QString filter="图片文件(*.bmp *.jpg *.png);;BMP文件(*.bmp);;JPG文件(*.jpg);;PNG文件(*.png)";QString filter= "图片文件(*.bmp *.jpg *.png);;""BMP文件(*.bmp);;JPG文件(*.jpg);;PNG文件(*.png)";QString fileName=QFileDialog::getSaveFileName(this,"保存文件",m_filename,filter);if (fileName.isEmpty())return;m_image.save(fileName); //保存到新的文件m_filename= fileName; //重新设置当前文件名ui->statusbar->showMessage(fileName);imageModified(false);
}void MainWindow::on_actFile_Reload_triggered()
{//重新载入QString fileName =m_filename;m_image.load(fileName); //从当前文件重新载入QPixmap pixmap=QPixmap::fromImage(m_image);ui->labPic->setPixmap(pixmap); //刷新图像显示ui->tabWidget->setCurrentIndex(0);showImageFeatures(true); //显示全部属性imageModified(false); //设置按钮状态
}
图像格式转换
对图像进行图像格式转换,也就是改变像素颜色数据的表示格式。从下拉列表框中选择目标格式,然后点击“图像格式转换”按钮就可以进行图像格式转换。对应的槽函数如下:
void MainWindow::on_btnFormatConvert_clicked()
{//图像格式转换
// QImage newImage;int index=ui->comboFormat->currentIndex();if (index ==0)m_image.convertTo(QImage::Format_RGB16); //RGB565else if (index ==1)m_image.convertTo(QImage::Format_RGB888); //RGB888else if (index ==2)m_image.convertTo(QImage::Format_RGB32); //RGBx888else if (index ==3)
// newImage = image.convertToFormat(QImage::Format_Grayscale8); //不改变原图
// newImage = image.convertedTo(QImage::Format_Grayscale8); //不改变原图像m_image.convertTo(QImage::Format_Grayscale8); //8位灰度else if (index ==4)m_image.convertTo(QImage::Format_Grayscale16);//16位灰度else if (index ==5)m_image.convertTo(QImage::Format_Indexed8); //8位索引elsereturn;QPixmap pixmap=QPixmap::fromImage(m_image); //刷新界面的图像显示ui->labPic->setPixmap(pixmap);showImageFeatures(true); //显示全部信息imageModified(true); //图像被修改了
}
程序里使用 QImage::convertTo()函数进行图像格式转换,这个函数的原型定义如下:
void QImage::convertTo(QImage::Format format, Qt::ImageConversionFlags flags = Qt::AutoColor)
其中,参数 format 是需要转换的目标格式,参数 flags 控制格式转换的处理方法,一般使用默认值 即可。函数 convertTo()没有返回值,转换后的图像会覆盖原来的图像。
图像处理
工具栏上有一些按钮可以对图像进行缩放、旋转或翻转处理。
图像缩放:图像缩放可以使用函数 scaled()来实现,该函数原型定义如下:
QImage QImage::scaled(int width, int height,Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio, Qt::TransformationMode transformMode = Qt::FastTransformation)
其中,参数 width 和 height 分别是缩放后的新图像的宽度和高度,单位是像素;参数aspectRatioMode 控制是否保持图像的长宽比,默认值 Qt::IgnoreAspectRatio 表示忽略长宽比,也可以设置为保持长宽比, 也就是设置值为 Qt::KeepAspectRatio;参数 transformMode 表示变换模式,默认值 Qt::FastTransformation表示快速转换,不做平滑处理,也可以设置值为 Qt::SmoothTransformation,表示进行平滑处理。
void MainWindow::on_actImg_ZoomIn_triggered()
{//放大int W=m_image.width();int H=m_image.height();m_image=m_image.scaled(1.1*W, 1.1*H,Qt::KeepAspectRatio); //放大QPixmap pixmap=QPixmap::fromImage(m_image);ui->labPic->setPixmap(pixmap); //重新设置pixmap,将清除之前的内容showImageFeatures(false);imageModified(true);
}void MainWindow::on_actImg_ZoomOut_triggered()
{//缩小int W=m_image.width();int H=m_image.height();m_image=m_image.scaled(0.9*W, 0.9*H,Qt::KeepAspectRatio); //缩小QPixmap pixmap=QPixmap::fromImage(m_image);ui->labPic->setPixmap(pixmap); //刷新界面的图像显示showImageFeatures(false);imageModified(true);
}
函数 scaled()返回缩放后的图像副本,原图像不变。示例程序中将缩放后的图像又保存到原图
图像旋转
函数 transformed()可以通过一个变换矩阵对图像进行任意的变换,其函数原 型定义如下:
QImage QImage::transformed(const QTransform &matrix, Qt::TransformationMode mode = Qt::FastTransformation)
参数 matrix 是 QTransform 类型的变换矩阵。变换矩阵是一个 3×3 的矩阵,可以表示坐标系的平移、缩放、旋转等坐标变换运算关系。
void MainWindow::on_actImg_RotateLeft_triggered()
{//左旋90度QTransform matrix;matrix.reset(); //单位矩阵matrix.rotate(-90); //默认Qt::ZAxism_image=m_image.transformed(matrix); //使用变换矩阵matrix进行图像变换QPixmap pixmap=QPixmap::fromImage(m_image);ui->labPic->setPixmap(pixmap); //刷新界面的图像显示showImageFeatures(false); //只刷新显示图像尺寸相关信息imageModified(true);
}void MainWindow::on_actImg_RotateRight_triggered()
{//右旋90度QTransform matrix;matrix.reset(); //单位矩阵matrix.rotate(90); //默认Qt::ZAxism_image=m_image.transformed(matrix); //使用变换矩阵matrix进行图像变换QPixmap pixmap=QPixmap::fromImage(m_image);ui->labPic->setPixmap(pixmap); //刷新界面的图像显示showImageFeatures(false); //只刷新显示图像尺寸相关信息imageModified(true);
}
void MainWindow::on_actImg_RotateLeft_triggered()
{//左旋90度QTransform matrix;matrix.reset(); //单位矩阵matrix.rotate(-90); //默认Qt::ZAxism_image=m_image.transformed(matrix); //使用变换矩阵matrix进行图像变换QPixmap pixmap=QPixmap::fromImage(m_image);ui->labPic->setPixmap(pixmap); //刷新界面的图像显示showImageFeatures(false); //只刷新显示图像尺寸相关信息imageModified(true);
}void MainWindow::on_actImg_RotateRight_triggered()
{//右旋90度QTransform matrix;matrix.reset(); //单位矩阵matrix.rotate(90); //默认Qt::ZAxism_image=m_image.transformed(matrix); //使用变换矩阵matrix进行图像变换QPixmap pixmap=QPixmap::fromImage(m_image);ui->labPic->setPixmap(pixmap); //刷新界面的图像显示showImageFeatures(false); //只刷新显示图像尺寸相关信息imageModified(true);
}
图像镜像
函数 mirror()可以对图像进行镜像处理,该函数定义如下:
void QImage::mirror(bool horizontal = false, bool vertical = true)
其中,参数 horizontal 表示是否进行水平镜像,参数 vertical 表示是否进行垂直镜像。函数没有返 回值,直接修改原图像。
还有一个函数 mirrored()可以对图像进行镜像处理,它返回处理后的图像副本,不修改原图像。
QImage QImage::mirrored(bool horizontal = false, bool vertical = true)
void MainWindow::on_actImg_FlipUD_triggered()
{//上下翻转bool horizontal=false;bool vertical=true;m_image.mirror(horizontal,vertical); //图像镜像处理QPixmap pixmap=QPixmap::fromImage(m_image);ui->labPic->setPixmap(pixmap);imageModified(true);
}void MainWindow::on_actImg_FlipLR_triggered()
{//左右翻转bool horizontal=true;bool vertical=false;m_image.mirror(horizontal,vertical); //图像镜像处理QPixmap pixmap=QPixmap::fromImage(m_image);ui->labPic->setPixmap(pixmap);imageModified(true);
}
生成 与保存RGB565 数据
对图像从上到下、从左到右进行处理,读取每个像素的颜色数据,然后将其转换成十六进制字符串。通过 QImage::pixel()函数获取某个像素的颜色,无论图像是什么格式,函数 pixel() 返回的数据类型 QRgb 的格式都是 0xAARRGGBB。通过 qRed()、qGreen()和 qBlue()函数分别获取 QRgb 数据中的红色、绿色和蓝色成分,并分别取其有效的高 5 位、高 6 位、高 5 位。最后提取RGB565 数据的低字节和高字节数据。
在将 byteLSB 和 byteMSB 组合成字符串时,还会根据界面上单选按钮的选择,设置为低字节在前或高字节在前。可以点击保存位C语言头文件。
void MainWindow::on_btnGetRGB565_clicked()
{ui->plainText->clear();int W=m_image.width();int H=m_image.height();int total=2*W*H; //总数据字节数QFileInfo fileInfo(m_filename);QString arrayName=fileInfo.baseName(); //不带后缀的文件名QString aLine=QString("const unsigned char RGB565_%1[%2] = {").arg(arrayName).arg(total);ui->plainText->appendPlainText(aLine);QString onePixel; //一个像素的2字节16进制数据字符串QChar ch0('0'); //用于填充的字符int base=16; //16进制int count=0; //单行像素个数计数for (int y=0; y<H; y++) //从上到下逐行处理{QApplication::processEvents();for (int x=0; x<W; x++) //从左到右逐个像素处理{QRgb rgb=m_image.pixel(x,y); //一个像素的RGB颜色, 格式 0xAARRGGBB/自己处理,有效
// quint32 tmp32 = rgb & 0x00F80000; //取red高5位
// rgb565 = tmp32>>8; //Red5
// tmp32 = rgb & 0x0000FC00; //取green高6位
// rgb565 = rgb565 | (tmp32>>5); //R5G6
// tmp32 = rgb & 0x000000F8; //取blue高5位
// rgb565 =rgb565 | (tmp32>>3); //RGB565
// quint8 byteLSB = rgb565 & 0x00FF;
// quint8 byteMSB = rgb565>>8;//使用qRed()等函数,有效quint16 red =qRed(rgb) & 0x00F8; //取高5位quint16 green=qGreen(rgb) & 0x00FC; //取高6位quint16 blue =qBlue(rgb) & 0x00F8; //取高5位quint16 rgb565=(red<<8) | (green <<3) | (blue>>3); //RGB565数据quint8 byteLSB = rgb565 & 0x00FF; //低字节quint8 byteMSB = rgb565>>8; //高字节//if (ui->radioLSB->isChecked()) //低字节在前//2为字宽,例如0xff字宽为2,0x0ff字宽为3,base为基数(进制),ch0为填充的字符onePixel += QString("0x%1,0x%2,").arg(byteLSB, 2, base,ch0).arg(byteMSB, 2, base,ch0);
// onePixel=onePixel+QString("0x%1,0x%2,").arg(byteLSB, 2, base,ch0).arg(byteMSB, 2, base,ch0);elseonePixel += QString("0x%1,0x%2,").arg(byteMSB, 2, base,ch0).arg(byteLSB, 2, base,ch0);
// onePixel=onePixel+QString("0x%1,0x%2,").arg(byteMSB, 2, base,ch0).arg(byteLSB, 2, base,ch0);count++;if (count==8) //每行只填8个像素的数据{onePixel = onePixel.toUpper();onePixel = onePixel.replace(QChar('X'),"x");//使用小写的0x为16进制前缀ui->plainText->appendPlainText(onePixel);onePixel="";count=0;}}}if (count>0) //最后不足8个像素的数据{onePixel = onePixel.toUpper();onePixel = onePixel.replace(QChar('X'),"x");ui->plainText->appendPlainText(onePixel);}ui->plainText->appendPlainText("};"); //数组结尾ui->tabWidget->setCurrentIndex(1); //切换tabui->btnSaveDataFile->setEnabled(true);QMessageBox::information(this,"提示","RGB565数据生成已完成");
}void MainWindow::on_btnSaveDataFile_clicked()
{QFileInfo fileInfo(m_filename);QString newName=fileInfo.baseName()+".h"; //更改文件后缀QString filter="C语言头文件(*.h);;C语言程序文件(*.c);;文本文件(*.txt)";QString fileName=QFileDialog::getSaveFileName(this,"保存文件",newName,filter);if (fileName.isEmpty())return;// QFileInfo fileInfo(imageFilename);
// QString newName=fileInfo.fileName(); //去除路径的文件名
// fileInfo.setFile(newName);
// newName=fileInfo.baseName()+".h"; //更改文件后缀
// newName=QDir::currentPath()+"\\"+newName; //应用程序当前目录
// QString filter="C语言头文件(*.h);;C语言程序文件(*.c);;文本文件(*.txt)";
// QString fileName=QFileDialog::getSaveFileName(this,"保存文件",newName,filter);// if (fileName.isEmpty())
// return;
// fileInfo.setFile(fileName);
// QDir::setCurrent(fileInfo.absolutePath()); //设置应用程序当前目录QFile aFile(fileName);if (aFile.open(QIODevice::WriteOnly | QIODevice::Text)){QString str=ui->plainText->toPlainText(); //整个内容作为字符串QByteArray strBytes=str.toUtf8(); //转换为字节数组, UTF-8编码aFile.write(strBytes,strBytes.length()); //写入文件aFile.close();}
}
打印功能
打印相关的类
QPrinter (父类为QPagedPaintDevice)是实现打印功能的绘图设备类,打印输出实际上就是在 QPrinter 上用 QPainter 绘制各种图形和文字。QPagedPaintDevice 还有另一个子类QPdfWriter,使用 QPdfWriter 类可以创建 PDF 文件,将内容打印到 PDF 文件里。
QPrintDialog 是打印设置对话框类,使用这个对话框类可以对 QPrinter 对象的各种主要属性进行设置,包括选择打印机,设置纸张大小、纸张方向、打印页面范围、打印份数、是否双面打印等。还有一个打印预览对话框类 QPrintPreviewDialog,它可以实现打印预览功能。
QPagedPaintDevice 类定义了多页打印输出的一些基本函数如下(表中省略了函数的输入参数):
这些接口函数用于设置页面布局,包括纸张大小、纸张方向、页边距等,还可以设置打印页面范围,在新建页面之前需要设置好页面布局。
使用 QPrintDialog 对话框类可以对一个 QPrinter 对象进行设置,包括打印机和打印页面的设置。在打印对话框确认后,就可以为 QPrinter 对象设置 QPainter 画笔,实际上打印就是在页面上用 QPainter 输出文字或绘制图像。
打印预览功能
Qt 中有一个打印预览对话框类 QPrintPreviewDialog,它可以实现打印预览功能。它的父类是
QDialog,所以它是一个独立的对话框。要实现打印预览,关键是要为 QPrintPreviewDialog 的
paintRequested()信号关联一个槽函数,信号函数的输入参数 printer 是当前使用的打印机。在实现槽函数时,可以为这个 printer 创建QPainter 对象,然后在 printer 上输出打印内容。
void MainWindow::on_actFile_Preview_triggered()
{//”打印预览”按钮QPrintPreviewDialog previewDlg(this); //打印预览对话框previewDlg.setWindowFlag(Qt::WindowMaximizeButtonHint); //具有最大化按钮if (ui->tabWidget->currentIndex() == 0)connect(&previewDlg, SIGNAL(paintRequested(QPrinter *)),this, SLOT(do_paintRequestedImage(QPrinter *)));elseconnect(&previewDlg, SIGNAL(paintRequested(QPrinter *)),this, SLOT(do_paintRequestedText(QPrinter *)));previewDlg.exec(); //以模态方式显示对话框
}void MainWindow::do_paintRequestedImage(QPrinter *printer)
{QPainter painter(printer); //打印机的画笔printImage(&painter, printer);
}void MainWindow::do_paintRequestedText(QPrinter *printer)
{QPainter painter(printer); //打印机的画笔printRGB565Data(&painter, printer);
}
上述代码中分别为打印图片和打印RGB565数据设置了预览对话框,并根据当前的tab页面分别连接槽函数,由于这里QPrintPreviewDialog对话框是局部创建的。所以每次点击Action时,才会连接创建的对话框中信号与槽函数,执行exec()函数后,该对话框会同时发送paintRequested信号,槽函数便会运行。
void MainWindow::printImage(QPainter *painter, QPrinter *printer)
{//打印图像QMargins margin(20,40,20,40); //上下左右4个边距,单位:像素QRectF pageRect=printer->pageRect(QPrinter::DevicePixel); //单位:设备像素int pageW=pageRect.width(); //打印页面的宽度int pageH=pageRect.height();const int lineInc=20; //一行文字所占的行高度,单位:像素int curX=margin.left(); //当前X坐标int curY=margin.top(); //当前Y坐标painter->drawText(curX,curY,m_filename); //打印图片文件名curY += lineInc; //移到下一行painter->drawText(curX,curY,QString("Page width =%1 像素").arg(pageW));painter->drawText(200,curY,QString("Image width =%1 像素").arg(m_image.width()));curY += lineInc;painter->drawText(curX,curY,QString("Page height=%1 像素").arg(pageH));painter->drawText(200,curY,QString("Image height=%1 像素").arg(m_image.height()));curY += lineInc;int spaceH= pageH-curY; //页面剩余的高度//图像未超过页面范围,居中显示实际大小的图片if ((pageW >m_image.width()) && (spaceH >m_image.height())){curX =(pageW-m_image.width())/2; //使水平居中painter->drawImage(curX,curY,m_image); //打印图像return;}//否则图像高度或宽度超过了页面剩余空间,缩放后打印QImage newImg;if (m_image.height() > m_image.width())newImg =m_image.scaledToHeight(spaceH); //按高度缩放elsenewImg =m_image.scaledToWidth(pageW); //按宽度缩放curX =(pageW-newImg.width())/2; //使水平居中painter->drawImage(curX,curY,newImg); //打印图像
}void MainWindow::printRGB565Data(QPainter *painter, QPrinter *printer)
{//打印文档QMargins margin(20,40,20,40); //上下左右4个边距,单位:像素QRectF pageRect=printer->pageRect(QPrinter::DevicePixel); //单位:设备像素int pageW=pageRect.width(); //打印页面的宽度,像素int pageH=pageRect.height();const int lineInc=25; //一行文字所占的行高度,单位:像素int curX=margin.left(); //当前X坐标int curY=margin.top(); //当前Y坐标QFont font=ui->plainText->font();painter->setFont(font); //设置打印字体int pageNum=1; //打印页面编号painter->drawLine(margin.left(), pageH- margin.bottom()+1, //页脚划线pageW-margin.right(), pageH- margin.bottom()+1);painter->drawText(pageW-5*margin.right(),pageH-margin.bottom()+20, //页脚页面编号QString("第 %1 页").arg(pageNum));QTextDocument* doc=ui->plainText->document(); //文本对象int cnt=doc->blockCount(); //回车符是一个blockfor (int i=0; i<cnt; i++) //逐行读取文字,逐行打印{QTextBlock textLine =doc->findBlockByNumber(i); // 文本中的一段QString str=textLine.text(); //一行文字painter->drawText(curX,curY,str); //打印文字curY += lineInc; //换到下一行if (curY>= (pageH-margin.bottom())) //需要换页{printer->newPage(); //新建一个打印页curY=margin.top(); //一页的首行位置pageNum++; //页面编号++painter->drawLine(margin.left(), pageH- margin.bottom()+1, //页脚划线pageW-margin.right(), pageH- margin.bottom()+1);painter->drawText(pageW-5*margin.right(),pageH-margin.bottom()+20,QString("第 %1 页").arg(pageNum)); //页脚页面编号}}
}
打印图像关键点是要设置好图像的宽高以适配打印纸张的大小,打印文本关键点是设置好合适的换行和换页。
打印
打印与打印预览的区别主要是对话框的区别,打印预览使用的是QPrintPreviewDialog对话框,而打印使用的是QPrintDialog对话框;最终的打印函数与打印预览部分是共用的。
void MainWindow::on_actFile_Print_triggered()
{//"打印"按钮QPrinter printer;QPrintDialog pritnDialog(&printer,this); //打印设置对话框if (pritnDialog.exec()==QDialog::Accepted){QPainter painter(&printer); //打印机的画笔,并传入打印机对象if (ui->tabWidget->currentIndex() == 0)printImage(&painter, &printer); //打印图像elseprintRGB565Data(&painter, &printer); //打印文本}
}
打印相关的内容仅是基本演示,实际的设计还是比较复杂的,更专业的设计软件才需要。
参考
Qt 6 C++开发指南