图像表示和图像处理概述
颜色数据格式
图像数据可以看作是二维数组,数组每个元素就是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 表示像素的颜色,这样可以节省存储空间,提高处理效率。
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 类
加载与保存图像数据
cpp
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 设备中。
cpp
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 设备加载数据和保存数据:
cpp
bool QImage::load(QIODevice *device, const char *format)
bool QImage::save(QIODevice *device, const char *format = nullptr, int quality = -1)
其中,参数 device 是 I/O 设备,参数 format 是图片文件格式,参数 quality 是保存图片的品质参数。device 可以使用 QBuffer 类对象,QBuffer 是为 QByteArray 数据提供读写接口的 I/O 设备类。
cpp
QImage image("Save_as.png");
QByteArray ba; //字节数组
QBuffer buffer(&ba); //缓冲区对象
buffer.open(QIODevice::WriteOnly); //以只读模式打开缓冲区
image.save(&buffer, "PNG"); //将图像以 PNG 格式写入缓冲区,也就是写到字节数组 ba 里
函数 loadFromData()可以从字节数组中加载图像数据,其中一种定义如下:
cpp
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()
cpp
QRgb QImage::pixel(int x, int y) //返回像素(x, y)的颜色数据
返回的颜色数据是 QRgb 类型的,QRgb 就是无符号 32 位整数,它以 0xAARRGGBB 的格式表示颜色的 Alpha 通道以及红色、绿色、蓝色对应的数值,我们用(a, r, g, b)表示这 4 种成分的数值。对于QRgb 类型的处理可以使用以下接口:
使用函数 pixelColor()
cpp
QColor QImage::pixelColor(int x, int y) //返回像素(x, y)的颜色数据
返回数据类型是 QColor,QColor 是表示颜色的类,它有一些接口函数可以获取颜色成分数值,也可以将 QColor 表示的颜色转换为 QRgb 类型的颜色:
cpp
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()可以设置某个像素的颜色
cpp
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 提供了一些接口函数来对图像进行处理,例如镜像翻转、缩放、图像格式转换等,这
些接口函数如下表所示,表中省略了函数的输入参数。
示例程序解读
主窗口头文件
cpp
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QImage>
#include <QPrinter>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
private:
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(); //保存RGB565
void 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 是适合在界面上显示图片的绘图设备类。
cpp
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 时,只获取图像的宽度、高度、数据字节数等信息,在对图像进行翻转、缩放等处理后只需刷新显示这些信息。
cpp
//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");
else
ui->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));
else
ui->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文件路径名);
cpp
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); //设置按钮状态
}
图像格式转换
对图像进行图像格式转换,也就是改变像素颜色数据的表示格式。从下拉列表框中选择目标格式,然后点击"图像格式转换"按钮就可以进行图像格式转换。对应的槽函数如下:
cpp
void MainWindow::on_btnFormatConvert_clicked()
{//图像格式转换
// QImage newImage;
int index=ui->comboFormat->currentIndex();
if (index ==0)
m_image.convertTo(QImage::Format_RGB16); //RGB565
else if (index ==1)
m_image.convertTo(QImage::Format_RGB888); //RGB888
else if (index ==2)
m_image.convertTo(QImage::Format_RGB32); //RGBx888
else 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位索引
else
return;
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,表示进行平滑处理。
cpp
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()返回缩放后的图像副本,原图像不变。示例程序中将缩放后的图像又保存到原图
像,所以会改变图像的物理尺寸。
图像缩放还可以使用函数 scaledToHeight()和 scaledToWidth()来实现,它们分别用于指定高度
或宽度进行缩放,函数原型定义如下:
QImage QImage::scaledToHeight(int height, Qt::TransformationMode mode = Qt::FastTransformation)
QImage QImage::scaledToWidth(int width, Qt::TransformationMode mode = Qt::FastTransformation)
图像旋转
函数 transformed()可以通过一个变换矩阵对图像进行任意的变换,其函数原 型定义如下:
QImage QImage::transformed(const QTransform &matrix, Qt::TransformationMode mode = Qt::FastTransformation)
参数 matrix 是 QTransform 类型的变换矩阵。变换矩阵是一个 3×3 的矩阵,可以表示坐标系的平移、缩放、旋转等坐标变换运算关系。
cpp
void MainWindow::on_actImg_RotateLeft_triggered()
{//左旋90度
QTransform matrix;
matrix.reset(); //单位矩阵
matrix.rotate(-90); //默认Qt::ZAxis
m_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::ZAxis
m_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::ZAxis
m_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::ZAxis
m_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)
cpp
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语言头文件。
cpp
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);
else
onePixel += 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); //切换tab
ui->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 上输出打印内容。
cpp
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 *)));
else
connect(&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信号,槽函数便会运行。
cpp
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); //按高度缩放
else
newImg =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(); //回车符是一个block
for (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对话框;最终的打印函数与打印预览部分是共用的。
cpp
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); //打印图像
else
printRGB565Data(&painter, &printer); //打印文本
}
}
打印相关的内容仅是基本演示,实际的设计还是比较复杂的,更专业的设计软件才需要。
参考
Qt 6 C++开发指南