此文章為數(shù)組圖像處理課程設(shè)計的筆記弛车。項目我放在了我的GitHub上脓豪。這個系統(tǒng)要求用純VC++實現(xiàn)以下功能:
Open BMP file
打開一個BMP文件取劫,并在窗口中顯示出來匆笤。Save to new BMP file
將當(dāng)前視圖保存為一個新的BMP文件(先彈出一個對話框,輸入一個BMP文件名)谱邪。-
Display file header
按如下的格式顯示文件頭信息:
頭信息格式 Get pixel value
取某個位置像素的顏色值炮捧,并顯示出來。Set pixel value
設(shè)置某個位置像素的顏色值惦银,并顯示出來咆课。
功能(4)和(5)所需的參數(shù)從對話框中獲取。前面5個功能對灰度圖像和彩色圖像都適用扯俱,后面的功能僅要求針對灰度圖像书蚪。
- Image interpolation
圖像縮放:x和y方向的縮放因子、插值算法選擇(最鄰近和雙線性)迅栅,從對話框中獲取殊校。需要將圖像縮放的結(jié)果顯示出來。 - Median filtering
實現(xiàn)3x3的中值濾波读存,并將結(jié)果顯示出來为流。 - Gaussian smoothing
從對話框中獲取高斯函數(shù)的均方差,對圖像做高斯平滑让簿,并將結(jié)果顯示出來敬察。
功能(9) ~ (13)是選做題。
- Histogram equalization
直方圖均衡化尔当,并將結(jié)果顯示出來莲祸。 - Sharpening by gradient
實現(xiàn)基于梯度的圖像銳化,所需參數(shù)從對話框中獲取椭迎,將銳化結(jié)果顯示出來虫给。 - Bilateral filtering
實現(xiàn)雙邊濾波,參數(shù)sigma_d和sigma_R從對話框中獲取侠碧,并將結(jié)果顯示出來抹估。 - Add impulse noise
在圖像中加入脈沖噪聲,噪聲密度和類型從對話框中獲取弄兜,將噪聲圖像顯示出來药蜻。 - Canny edge detection
實現(xiàn)Canny算子邊緣檢測瓷式,并將結(jié)果顯示出來。
由于Qt把HWND和HDC這種平臺相關(guān)的數(shù)據(jù)類型全都封裝好了语泽,沒有提供直接的操作接口贸典,我決定用Qt自己的繪圖設(shè)備比如QImage實現(xiàn)這些功能。雖然用的數(shù)據(jù)類型是封裝好的踱卵,但是圖片讀取廊驼、顯示和處理算法都在像素級別處理。主要是MFC真的苦手啊惋砂。
以下分功能進(jìn)行記錄妒挎。
0 主界面和數(shù)據(jù)結(jié)構(gòu)設(shè)計
本圖像處理系統(tǒng)需要一個用戶友好的可操作界面。我們可以用Qt自帶的設(shè)計功能來實現(xiàn)這個界面西饵。新建一個MainWindow工程酝掩,Qt會自動給你生成mainwindow.h
,mainwindow.cpp
和mainwindow.ui
眷柔。我在新建工程時期虾,為了和以后可能新建的mianwindow窗口區(qū)分,將這個mainwindow類改名成了psmainwindow類驯嘱。我們先打開mainwindow.ui
镶苞,進(jìn)行界面的設(shè)計。
設(shè)計過程中鞠评,為了能夠進(jìn)行良好的布局宾尚、且能夠讓所有控件隨窗口大小改變而改變,我們需要用到Qt的布局管理器谢澈,具體參見這篇文章:
我做出的界面是這個樣子的:
其中煌贴,每個菜單項下拉內(nèi)容如下:
About菜單沒有下拉項。
界面設(shè)計結(jié)構(gòu)如下:
接下來需要進(jìn)行數(shù)據(jù)結(jié)構(gòu)的設(shè)計锥忿。為了打開一張BMP圖片牛郑,我們首先要知道BMP圖片是如何被存儲的,之后才能夠?qū)ζ湓O(shè)計數(shù)據(jù)結(jié)構(gòu)敬鬓,讀出圖像放入內(nèi)存淹朋。
為了學(xué)習(xí)BMP圖像的存儲,有兩個資料可供參考钉答。一個是B站上的一個數(shù)字圖像處理課:
數(shù)字圖像處理-Digital Image Processing (DIP)
還有一篇博客:
圖像識別_2010暑期實訓(xùn)有感【二】
BMP圖像文件的數(shù)據(jù)結(jié)構(gòu)如下:
//bmpfile.h
#ifndef BMPFILE_H
#define BMPFILE_H
#include <QtGlobal>
#include <QDebug>
typedef unsigned char BYTE;
typedef unsigned short WORD;//2byte
typedef unsigned int DWORD;//4byte
typedef qint32 LONG;//long 32bit
//位圖文件頭定義;
//其中不包含文件類型信息(由于結(jié)構(gòu)體的內(nèi)存結(jié)構(gòu)決定础芍,
//要是加了的話將不能正確讀取文件信息)
typedef struct tagBITMAPFILEHEADER{
WORD bfType;//文件類型,必須是0x424D数尿,即字符“BM”
DWORD bfSize;//文件大小
WORD bfReserved1;//保留字
WORD bfReserved2;//保留字
DWORD bfOffBits;//從文件頭到實際位圖數(shù)據(jù)的偏移字節(jié)數(shù)
}BITMAPFILEHEADER;
typedef struct tagBITMAPINFOHEADER{
DWORD biSize;//信息頭大小
LONG biWidth;//圖像寬度
LONG biHeight;//圖像高度
WORD biPlanes;//位平面數(shù)仑性,必須為1
WORD biBitCount;//每像素位數(shù):1,4,8,24
DWORD biCompression; //壓縮類型
DWORD biSizeImage; //壓縮圖像大小字節(jié)數(shù)
LONG biXPelsPerMeter; //水平分辨率
LONG biYPelsPerMeter; //垂直分辨率
DWORD biClrUsed; //位圖實際用到的色彩數(shù)
DWORD biClrImportant; //本位圖中重要的色彩數(shù)
}BITMAPINFOHEADER; //位圖信息頭定義
typedef struct tagRGBQUAD{//24位時沒有這個
BYTE rgbBlue; //該顏色的藍(lán)色分量
BYTE rgbGreen; //該顏色的綠色分量
BYTE rgbRed; //該顏色的紅色分量
BYTE rgbReserved; //保留值
}RGBQUAD;//調(diào)色板定義
//像素信息:24位
typedef struct tagIMAGEDATA
{
BYTE blue;//當(dāng)1,4右蹦,8時诊杆,用blue存儲信息
BYTE green;
BYTE red;
}IMAGEDATA;
#endif // BMPFILE_H
為了方便對這個數(shù)據(jù)結(jié)構(gòu)進(jìn)行操作歼捐,我定義了BMPIMG
類,用于存儲整張圖像:
#ifndef BMPIMG_H
#define BMPIMG_H
#include <QtCore/QString>
#include <QFile>
#include <QMessageBox>
#include <QDataStream>
#include <QImage>
#include <bmpfile.h>
class BMPIMG
{
private:
BITMAPFILEHEADER fileHeader;
BITMAPINFOHEADER infoHeader;
RGBQUAD *rgbQuad;
IMAGEDATA *imgData;
};
#endif // BMPIMG_H
1 Open BMP file
功能說明:打開一個BMP文件晨汹,并在窗口中顯示出來豹储。
為了實現(xiàn)這個功能,首先需要能夠獲得圖片的路徑地址淘这,這里我們需要使用到Qt的文件對話框類剥扣,具體使用說明參見這個文章:
在Action Editor中,右鍵單擊actionOpen_BMP_file
動作铝穷,點擊“轉(zhuǎn)到槽”钠怯,即可跳轉(zhuǎn)到槽函數(shù),也就是Open BMP File項被點擊之后會運行的函數(shù)氧骤。
在槽函數(shù)中添加添加獲取圖片地址的邏輯:
void PSMainWindow::on_actionOpen_BMP_file_triggered()
{
QString path = QFileDialog::getOpenFileName(this, tr("Open Image"), ".", tr("Image Files(*.bmp)"));
if(path.length() == 0) {
QMessageBox::information(this, tr("Path"), tr("You didn't select any files."));
}
}
之后我們需要通過圖片路徑讀取圖片。我將這個邏輯放在了BMPIMG
類中吃引,通過構(gòu)造函數(shù)實現(xiàn)讀取一張圖片筹陵、并把圖片信息保存在BMPIMG
對象中,然后才能顯示镊尺。
1.1 讀取圖片
定義bool getImage(QString filename)
函數(shù)朦佩。
用QFile
打開路徑文件,并用QDataStream
進(jìn)行數(shù)據(jù)讀取庐氮。參考文章:
這部分代碼如下
bool BMPIMG::getImage(QString filename)
{
qDebug()<<"open file: " + filename;
//open file
QFile file(filename);
if(!file.open(QIODevice::ReadOnly)){//open file failed
QMessageBox::warning(0, "Waring", "open file " + filename + " failed!", QMessageBox::Yes);
return false;
}
QDataStream dataStream(&file);
}
之后開始讀取文件內(nèi)容语稠。參考文章如下:
首先讀取開頭2個字節(jié)弄砍,即fileHeader.bfType
仙畦,判斷是不是“BM”,即16進(jìn)制的424D音婶,從而判斷選中的是否是BMP圖像慨畸。如果是則進(jìn)行后續(xù)操作,不是則彈出警告對話框衣式,并返回false
寸士。
bool BMPIMG::getImage(QString filename)
{
//前面代碼省略
//read file header
dataStream>>fileHeader.bfType;
if(fileHeader.bfType != 0x424D){//bfType != "BM"
QMessageBox::warning(0, "Waring", "file " + filename + " is not a BMP image!", QMessageBox::Yes);
return false;
}
}
之后開始讀取圖片的其余頭數(shù)據(jù),即fileHeader
和infoHeader
碴卧。要注意的是弱卡,BMP文件是小端存儲,即低位在前住册,高位在后婶博,比如讀取數(shù)據(jù)3A 00 00 00
,實際上是00 00 00 3A
荧飞,也就是3A
凡蜻;而Qt的QDataStream
類則是默認(rèn)的大端讀取搭综,所以我們要先設(shè)置一下大小端,再進(jìn)行數(shù)據(jù)的讀取划栓。參考文章:
bool BMPIMG::getImage(QString filename)
{
//前略
dataStream.setByteOrder(QDataStream::LittleEndian);
dataStream>>fileHeader.bfSize;
dataStream>>fileHeader.bfReserved1;
dataStream>>fileHeader.bfReserved2;
dataStream>>fileHeader.bfOffBits;
//read info header
qDebug()<<"reading info header...";
dataStream>>infoHeader.biSize;
dataStream>>infoHeader.biWidth;
dataStream>>infoHeader.biHeight;
dataStream>>infoHeader.biPlanes;
dataStream>>infoHeader.biBitCount;
dataStream>>infoHeader.biCompression; //壓縮類型
dataStream>>infoHeader.biSizeImage; //壓縮圖像大小字節(jié)數(shù)
dataStream>>infoHeader.biXPelsPerMeter; //水平分辨率
dataStream>>infoHeader.biYPelsPerMeter; //垂直分辨率
dataStream>>infoHeader.biClrUsed; //位圖實際用到的色彩數(shù)
}
這里有一個坑:256色bmp位圖兑巾,有時biClrUsed
會被存儲為0。所以我在函數(shù)中添加了一個判斷:如果是8位存儲的圖像且biClrUsed = 0
忠荞,則將biClrUsed
賦值為256蒋歌。
bool BMPIMG::getImage(QString filename)
{
//前略
if(infoHeader.biClrUsed == 0 && infoHeader.biBitCount == 8)
infoHeader.biClrUsed = 256;
dataStream>>infoHeader.biClrImportant; //本位圖中重要的色彩數(shù)
}
接下來讀取調(diào)色盤數(shù)據(jù)。需要注意的是委煤,如果圖片是24位存儲的話堂油,則沒有調(diào)色盤數(shù)據(jù),而直接在image data中存儲RGB值碧绞,所以需要做一下判斷府框。
bool BMPIMG::getImage(QString filename)
{
//前略
if(infoHeader.biBitCount != 24){
//read rgbquad
qDebug()<<"reading rgbquad...";
rgbQuad = (RGBQUAD*)malloc(sizeof(RGBQUAD) * infoHeader.biClrUsed);
for(unsigned int nCounti=0; nCounti<infoHeader.biClrUsed; nCounti++){
dataStream>>(*(rgbQuad + nCounti)).rgbBlue;
dataStream>>(*(rgbQuad + nCounti)).rgbGreen;
dataStream>>(*(rgbQuad + nCounti)).rgbRed;
dataStream>>(*(rgbQuad + nCounti)).rgbReserved;
}
}
}
同樣的,在讀取image data的時候也要對存儲深度讥邻、即biBitCount
做一下判斷迫靖。如果是24位真彩色圖像,則RGB三個變量都需要用到兴使;如果是8位的話只需要存儲到rgbBlue
里就可以系宜。
這里需要注意的是,需要把位數(shù)對齊7⑵恰m锬痢!励幼!bmp位圖的數(shù)據(jù)在存儲時汰寓,會把每一行的數(shù)據(jù)對齊到4的倍數(shù),比如一個寬為7的圖像苹粟,每行會存儲8個IMAGEDATA
數(shù)據(jù)踩寇,最后一個全都是0。
bool BMPIMG::getImage(QString filename)
{
//前略
//read image data
imgData = (IMAGEDATA*)malloc(sizeof(IMAGEDATA) * infoHeader.biWidth * infoHeader.biHeight);
int cnt = 0;
int align = (4 - (int)infoHeader.biWidth % 4) % 4;
IMAGEDATA temp;
switch (infoHeader.biBitCount) {
case 8:
for(int i = 0; i < infoHeader.biHeight; i++){
for(int j = 0; j < infoHeader.biWidth; j++){
dataStream>>(*(imgData + cnt)).blue;
cnt++;
}
if(align!=0){
for(int k=0; k<align; k++){
dataStream >> temp.blue;
}
}
}
break;
case 24:
for(int i = 0; i < infoHeader.biHeight; i++){
for(int j = 0; j < infoHeader.biWidth; j++){
dataStream>>(*(imgData + cnt)).blue;
dataStream>>(*(imgData + cnt)).green;
dataStream>>(*(imgData + cnt)).red;
cnt++;
}
if(align!=0){
for(int k=0; k<align; k++){
dataStream >> temp.blue;
dataStream >> temp.green;
dataStream >> temp.red;
}
}
}
break;
}
file.close();
return true;
}
至此文件讀取完畢六水。
1.2 顯示圖片
在顯示圖片之前首先了解一下坐標(biāo)系的問題俺孙。根據(jù)上面提到的文章圖像識別_2010暑期實訓(xùn)有感【二】:
一般來說,.bmp文件的數(shù)據(jù)從下到上掷贾,從左到右的睛榄。也就是說,從文件中最先讀到的是圖象最下面一行的左邊第一個象素想帅,然后是左邊第二個象素……接下來是倒數(shù)第二行左邊第一個象素场靴,左邊第二個象素……依次類推 ,最后得到的是最上面一行的最右一個象素。
而QImage
的坐標(biāo)系是反著的旨剥,左上角才是原點咧欣,所以在輸出(setPixel)的時候,需要從大數(shù)開始輸出轨帜∑枪荆基本流程就是,按順序讀取每一個像素的rgb值蚌父, 然后設(shè)置這個位置的像素為這個顏色哮兰。
要顯示圖片,要先將BMPIMG
對象里存儲的IMAGEDATA
信息輸出為QImage
苟弛。由于我不希望調(diào)用QImage自帶的打開圖片接口喝滞,而想在像素級別進(jìn)行操作,所以我需要QImage像素級操作的接口膏秫。參考:
我們需要用到void QImage::setPixelColor(int x, int y, const [QColor](qcolor.html) &color)
這個API右遭。
在BMPIMG
類中添加QImage toQImage()
函數(shù):
QImage BMPIMG::toQImage()
{
int cnt = 0;
QImage outputImg = QImage(infoHeader.biWidth, infoHeader.biHeight, QImage::Format_ARGB32);
QPoint pos;
QColor color;
BYTE rgb;
if(infoHeader.biBitCount == 24){
for(int i=infoHeader.biHeight-1; i>=0; i--){
for(int j=0; j<infoHeader.biWidth; j++){
pos = QPoint(j,i);
color = QColor((imgData + cnt)->red, (imgData + cnt)->green, (imgData + cnt)->blue);
outputImg.setPixelColor(pos, color);
cnt++;
}
}
}
else{
for(int i=infoHeader.biHeight-1; i>=0; i--){
for(int j=0; j<infoHeader.biWidth; j++){
pos = QPoint(j,i);
rgb = (imgData + cnt)->blue;
color = QColor((rgbQuad + rgb)->rgbRed, (rgbQuad + rgb)->rgbGreen, (rgbQuad + rgb)->rgbBlue);
outputImg.setPixelColor(pos, color);
cnt++;
}
}
}
return outputImg;
}
這樣一來,要顯示圖片所需的功能就都有了缤削。接下來我們在Open BMP file項的槽函數(shù)中繼續(xù)添加讀取文件窘哈、顯示圖像的邏輯,也就是調(diào)用我們剛寫好的BMPIMG
類的函數(shù)僻他。
void PSMainWindow::on_actionOpen_BMP_file_triggered()
{
//前略
BMPIMG image(path);
setImg(image);
qDebug()<<"here";
QImage qImage = image.toQImage();
QGraphicsScene *scene = new QGraphicsScene();
scene->addPixmap(QPixmap::fromImage(qImage));
qDebug()<<qImage;
ui->graphicsView->setScene(scene);
ui->graphicsView->show();
qDebug()<<"here";
return;
}
這樣一來宵距,點擊Open BMP file項之后就可以看到圖片了腊尚。
2 Save to new BMP file
功能要求:將當(dāng)前視圖保存為一個新的BMP文件(先彈出一個對話框吨拗,輸入一個BMP文件名)。
和打開文件一樣婿斥,為了獲取保存的路徑劝篷,我們依然需要用到文件對話框,只不過用法和之前不太一樣民宿。參考 Qt入門-打開和保存文件對話框 娇妓。除此之外,在保存之前活鹰,我們需要判斷是否有已經(jīng)打開的圖片哈恰,不然對空對象進(jìn)行操作會報錯。對于判斷是否打開了圖片志群,我是通過fileHeader.bfType
這一項來判斷的着绷。如果打開的文件是bmp文件,那么這個變量的值應(yīng)該是0x424D锌云,我修改了BMPIMG
類的構(gòu)造函數(shù)荠医,使得一個BMPIMG
對象剛被構(gòu)造出來時,fileHeader.bfType
會被賦值為0:
BMPIMG::BMPIMG()
{
fileHeader.bfType = 0;
}
同時添加BMPIMG
判空函數(shù)bool BMPIMG::isEmpty()
,若對象為空則返回true
彬向,否則返回false
:
bool BMPIMG::isEmpty()
{
if(fileHeader.bfType == 0x424D){
return false;
}
else{
return true;
}
}
接下來編寫保存圖像的邏輯兼贡。保存圖像實際上就是打開圖像的逆操作,怎么讀出來的就怎么寫回去娃胆,同樣要注意對齊的問題遍希,用0將每行不足4的倍數(shù)的位置補齊。這里還有一點要注意的是缕棵,對齊時是以BYTE
為單位孵班,而非以IMAGEDATA
為單位。當(dāng)補齊24位圖像時招驴,不需要補幾個IMAGEDATA
篙程,只需要補幾個BYTE
。
bool BMPIMG::saveImage(QString path){
QFile newImg(path);
if(!newImg.open(QIODevice::WriteOnly)){
QMessageBox::warning(0, "Waring", "save file " + path + " failed!", QMessageBox::Yes);
return false;
}
QDataStream dataStream(&newImg);
dataStream<<fileHeader.bfType;
dataStream.setByteOrder(QDataStream::LittleEndian);
dataStream<<fileHeader.bfSize;
dataStream<<fileHeader.bfReserved1;
dataStream<<fileHeader.bfReserved2;
dataStream<<fileHeader.bfOffBits;
//write info header
dataStream<<infoHeader.biSize;
dataStream<<infoHeader.biWidth;
dataStream<<infoHeader.biHeight;
dataStream<<infoHeader.biPlanes;
dataStream<<infoHeader.biBitCount;
dataStream<<infoHeader.biCompression; //壓縮類型
dataStream<<infoHeader.biSizeImage; //壓縮圖像大小字節(jié)數(shù)
dataStream<<infoHeader.biXPelsPerMeter; //水平分辨率
dataStream<<infoHeader.biYPelsPerMeter; //垂直分辨率
dataStream<<infoHeader.biClrUsed; //位圖實際用到的色彩數(shù)
dataStream<<infoHeader.biClrImportant; //本位圖中重要的色彩數(shù)
if(infoHeader.biBitCount != 24){
//write rgbquad
for(unsigned int nCounti=0; nCounti<infoHeader.biClrUsed; nCounti++){
dataStream<<(*(rgbQuad + nCounti)).rgbBlue;
dataStream<<(*(rgbQuad + nCounti)).rgbGreen;
dataStream<<(*(rgbQuad + nCounti)).rgbRed;
dataStream<<(*(rgbQuad + nCounti)).rgbReserved;
}
}
//write image data
int cnt = 0;
int alignByte = (4 - (int)infoHeader.biWidth % 4) % 4;
IMAGEDATA align;
align.blue = 0;
align.green = 0;
align.red = 0;
switch (infoHeader.biBitCount) {
case 8:
for(int i = 0; i < infoHeader.biHeight; i++){
for(int j = 0; j < infoHeader.biWidth; j++){
dataStream<<(imgData+cnt)->blue;
cnt++;
}
if(alignByte != 0){
for(int k = 0; k < alignByte; k++){
dataStream<<align.blue;
}
}
}
break;
case 24:
for(int i = 0; i < infoHeader.biHeight; i++){
for(int j = 0; j < infoHeader.biWidth; j++){
dataStream<<(imgData+cnt)->blue;
dataStream<<(imgData+cnt)->green;
dataStream<<(imgData+cnt)->red;
cnt++;
}
if(alignByte != 0){
for(int k = 0; k < alignByte; k++){
dataStream<<align.blue;
}
}
}
break;
}
newImg.close();
return true;
}
現(xiàn)在我們有了所有的模塊别厘,可以將他們拼在一起了虱饿。右鍵添加Save to new BMP file的槽函數(shù),先圖片判空触趴,再地址判空氮发,最后寫圖片:
void PSMainWindow::on_actionSave_to_new_BMP_file_triggered()
{
if(image.isEmpty()){
QMessageBox::information(this, tr("warning"), tr("Please open an image first."));
return;
}
QString path = QFileDialog::getSaveFileName(this, tr("Save Image"), " ", tr("Image Files(*.bmp)"));
if(!path.isNull()){
image.saveImage(path);
}
else{
QMessageBox::information(this, tr("Path"), tr("You didn't input a file name."));
}
}
保存功能完成。
3 Display file header
首先設(shè)計顯示文件頭信息的窗口headerInfoDialog
冗懦。在普通的Dialog中加入一個List Widget控件和一個按鈕爽冕,用于顯示文件頭信息。
在headerInfoDialog
類中添加顯示信息的接口:
void HeaderInfoDialog::setInfo(BITMAPFILEHEADER fileHeader, BITMAPINFOHEADER infoHeader)
按要求的格式披蕉,將說明和值拼接成字符串颈畸,調(diào)用List Widget控件的setItem函數(shù)即可添加信息。每次展示信息時没讲,先將控件內(nèi)容清空眯娱,再依次添加信息。有一些需要用到數(shù)學(xué)計算的信息爬凑,計算函數(shù)可參考Qt下常用的數(shù)值計算(絕對值qAbs徙缴,最大qMax,最小qMin嘁信,開根號Sqrt于样,N次方是pow,斷言宏Q_ASSERT和Q_ASSERT_X )潘靖。
void HeaderInfoDialog::setInfo(BITMAPFILEHEADER fileHeader, BITMAPINFOHEADER infoHeader)
{
ui->listWidget->clear();
QString line;
line = "btType (file type) = " + QString::number(fileHeader.bfType);
ui->listWidget->addItem(line);
line = "bfSize (file length) = " + QString::number(fileHeader.bfSize);
ui->listWidget->addItem(line);
line = "bfOffBits (offset of bit map data in bytes) = " + QString::number(fileHeader.bfOffBits);
ui->listWidget->addItem(line);
line = "biSize (header structure length shoud be 40 or 0x28) = " + QString::number(infoHeader.biSize);
ui->listWidget->addItem(line);
line = "biWidth (image width) = " + QString::number(infoHeader.biWidth);
ui->listWidget->addItem(line);
line = "biHeight (image height) = " + QString::number(infoHeader.biHeight);
ui->listWidget->addItem(line);
line = "biPlanes (must be eaual to 1) = " + QString::number(infoHeader.biPlanes);
ui->listWidget->addItem(line);
line = "biBitCount (color/pixel bits) = " + QString::number(infoHeader.biBitCount);
ui->listWidget->addItem(line);
line = "biCompression (compressed?) = " + QString::number(infoHeader.biCompression);
ui->listWidget->addItem(line);
line = "biSizeImage (length of bit map data in bytes must be the times of 4) = " + QString::number(infoHeader.biSizeImage);
ui->listWidget->addItem(line);
line = "biXPelsPerMeter (horizontal resolution of target device in pixels/metre) = " + QString::number(infoHeader.biXPelsPerMeter);
ui->listWidget->addItem(line);
line = "biYpelsPerMeter (vertical resolution of target device in pixels/metre) = " + QString::number(infoHeader.biYPelsPerMeter);
ui->listWidget->addItem(line);
line = "biColorUsed (number of colors used in bitmap, 0 = 2**biBitCount) = " + QString::number(infoHeader.biClrUsed);
ui->listWidget->addItem(line);
line = "biColorImportant (number of important colors, 0 = all colors are impretant) = " + QString::number(infoHeader.biClrImportant);
ui->listWidget->addItem(line);
line = "";
ui->listWidget->addItem(line);
line = "The following is additional information:";
ui->listWidget->addItem(line);
line = "Bytes per row in bitmap (nBytesPerRow) = " + QString::number(infoHeader.biWidth * (infoHeader.biBitCount/8));
ui->listWidget->addItem(line);
line = "Total bytes of bitmap (nImageSizeInByte) = " + QString::number(fileHeader.bfSize - fileHeader.bfOffBits);
ui->listWidget->addItem(line);
line = "Actual pixels per row in bitmap (nPixelsPerRpe)= " + QString::number(infoHeader.biWidth);
ui->listWidget->addItem(line);
line = "Total rows of bitmap (nTotalRows) = " + QString::number(infoHeader.biHeight);
ui->listWidget->addItem(line);
line = "Total colors (2**biBitCount)(nTotalColors) = " + QString::number(pow(2, infoHeader.biBitCount));
ui->listWidget->addItem(line);
line = "Used colors (biColorUsed)(nUsedolors) = " + QString::number(infoHeader.biClrUsed);
ui->listWidget->addItem(line);
}
為了將信息傳到頭信息展示窗口中捐康,我們還需要獲取圖片的fileHeader
和infoHeader
這兩個私有成員忠聚。在BMPIMG
類中添加相應(yīng)接口:
BITMAPFILEHEADER BMPIMG::getFileHeader()
{
return fileHeader;
}
BITMAPINFOHEADER BMPIMG::getInfoHeader()
{
return infoHeader;
}
接下來編寫Display file header槽函數(shù)中的邏輯。先定義一個新的信息展示窗口,獲取文件頭信息和信息頭信息蚀乔,設(shè)置展示內(nèi)容,最后調(diào)用show()函數(shù)運行窗口即可。
這里需要注意的是,如果直接定義窗口涮坐、調(diào)用show()函數(shù),窗口運行之后會馬上自動退出誓军,解決方法見:關(guān)于窗口閃退的解決袱讹。我采取了樓主所說的第一種方法:
將dlg作為類的成員,而不是在函數(shù)內(nèi)部昵时。
在psmainwindow.h中添加展示窗口的定義:
//psmainwindow.h
#ifndef PSMAINWINDOW_H
#define PSMAINWINDOW_H
//頭文件略
namespace Ui {
class PSMainWindow;
}
class PSMainWindow : public QMainWindow
{
Q_OBJECT
//前略
public:
explicit PSMainWindow(QWidget *parent = nullptr);
~PSMainWindow();
private:
HeaderInfoDialog headerInfoDialog;
};
#endif // PSMAINWINDOW_H
右鍵添加槽函數(shù):
void PSMainWindow::on_actionDisplay_file_header_triggered()
{
if(image.isEmpty()){
QMessageBox::information(this, tr("warning"), tr("You didn't open any image, please open an image first."));
return;
}
BITMAPFILEHEADER fileHeader = image.getFileHeader();
BITMAPINFOHEADER infoHeader = image.getInfoHeader();
headerInfoDialog.setInfo(fileHeader, infoHeader);
headerInfoDialog.show();
}
4 Get pixel value
功能要求:取某個位置像素的顏色值捷雕,并顯示出來。
先實現(xiàn)獲取顏色值的邏輯壹甥。
首先要實現(xiàn)定位讀取IMAGEDATA
救巷。在BMPIMG
類中添加:
IMAGEDATA BMPIMG::getPixelData(int x, int y)
{
IMAGEDATA pix = *(imgData + y * infoHeader.biWidth + x);
return pix;
}
直接返回特定位置的IMAGEDATA
信息。接下來判斷biBitCount
句柠,分情況解析顏色信息:
QColor BMPIMG::getPixel(int x, int y)
{
IMAGEDATA pix = getPixelData(x, y);
QColor color;
switch(infoHeader.biBitCount){
case 8:
color.setRed((rgbQuad + pix.blue)->rgbRed);
color.setBlue((rgbQuad + pix.blue)->rgbBlue);
color.setGreen((rgbQuad + pix.blue)->rgbGreen);
break;
case 24:
color.setRed(pix.red);
color.setBlue(pix.blue);
color.setGreen(pix.green);
break;
}
return color;
}
設(shè)計獲取坐標(biāo)的position
窗口浦译。新建一個Dialog
,在對話框中添加兩組標(biāo)簽和Spin Box(整型數(shù)輸入框)溯职,和一個OK按鈕精盅。
此處我們需要將子窗口position
中獲取的值傳回父窗口psmainwindow
,傳值方法可以參考Qt窗體之間相互傳值的三種方式谜酒。這里我采用了信號和槽的方法叹俏。
編輯position
類的頭文件position.h,定義信號函數(shù):
#ifndef POSITION_H
#define POSITION_H
#include <QDialog>
namespace Ui {
class position;
}
class position : public QDialog
{
Q_OBJECT
//前略
signals:
void send_position(QPoint p);
};
#endif // POSITION_H
定義好之信號后只需emit send_position(p);
即可發(fā)射信號僻族。
編輯psmainwindow
類粘驰,定義對應(yīng)的槽函數(shù)用于接收信號,并定義QPoint p
成員用于存儲接收到的坐標(biāo):
#ifndef PSMAINWINDOW_H
#define PSMAINWINDOW_H
//頭文件略
namespace Ui {
class PSMainWindow;
}
class PSMainWindow : public QMainWindow
{
Q_OBJECT
//前略
private slots:
void receivePosition(QPoint p);
private:
QPoint p;
};
#endif // PSMAINWINDOW_H
在position
的設(shè)計界面右鍵點擊OK按鈕添加槽函數(shù)鹰贵,獲取坐標(biāo)并發(fā)射信號:
void position::on_pushButton_clicked()
{
int x = ui->xInput->value();
int y = ui->yInput->value();
QPoint p(x,y);
emit send_position(p);
this->close();
}
編輯psmainwindow.cpp晴氨,完成槽函數(shù)void receivePosition(QPoint p)
:
void PSMainWindow::receivePosition(QPoint p)
{
this->p = p;
}
接下來設(shè)計展示顏色的窗口colorDisplay
康嘉。用Label
來展示顏色值碉输,再添加一個小塊的GraphicsView
來直觀地顯示顏色:
定義接口void colorDisplay::setInfo(QColor color)
來設(shè)置展示的顏色:
void colorDisplay::setInfo(QColor color){
ui->Red->setText(QString::number(color.red()));
ui->Green->setText(QString::number(color.green()));
ui->Blue->setText(QString::number(color.blue()));
QImage *colorBlock = new QImage(40, 40, QImage::Format_A2BGR30_Premultiplied);
colorBlock->fill(color);
QGraphicsScene *scene = new QGraphicsScene();
scene->addPixmap(QPixmap::fromImage(*colorBlock));
ui->graphicsView->setScene(scene);
}
之后我們就可以寫Get pixel value本體了。在設(shè)計界面添加Get pixel value的槽函數(shù):
void PSMainWindow::on_actionGet_pixel_value_triggered()
{
if(image.isEmpty()){
QMessageBox::information(this, tr("warning"), tr("Please open an image first."));
return;
}
position *dlg = new position(this);
connect(dlg, SIGNAL(send_position(QPoint)), this, SLOT(receivePosition(QPoint)));
dlg->setMax(image.getInfoHeader().biWidth, image.getInfoHeader().biHeight);
dlg->exec();
QColor pix = image.getPixel(p.x(), p.y());
colorDisplay *displayDialog = new colorDisplay();
displayDialog->setInfo(pix);
displayDialog->show();
}
這里有一點需要注意的是亭珍,Dialog::shwo()
函數(shù)是展示子窗口之后敷钾,父窗口還會繼續(xù)運行;而Dialog::exec()
函數(shù)是等待子窗口運行結(jié)束(即關(guān)閉)之后肄梨,父窗口才會繼續(xù)運行阻荒。我們需要獲取到坐標(biāo)之后才能讀取像素值,所以獲取坐標(biāo)的窗口position *dlg
要用exec()
運行众羡,而父窗口不依賴展示窗口colorDisplay *displayDialog
侨赡,直接調(diào)用show()
就可以。
5 Set pixel value
功能要求:設(shè)置某個位置像素的顏色值,并顯示出來羊壹。
在BMPIMG
類中添加void setPixel(int r, int b, int g, int x, int y)
函數(shù):
void BMPIMG::setPixel(int r, int b, int g, int x, int y)
{
if(infoHeader.biBitCount == 8){
BYTE color = getColor(r,b,g);
(imgData + y * infoHeader.biWidth + x)->blue = color;
}
else{
(imgData + y * infoHeader.biWidth + x)->blue = b;
(imgData + y * infoHeader.biWidth + x)->green = g;
(imgData + y * infoHeader.biWidth + x)->red = r;
}
return;
}
這里有一個敗筆:我之前寫的getPixelData(int x, int y)
函數(shù)返回值不是指針蓖宦,所以當(dāng)需要修改像素顏色值時,這個函數(shù)無法被復(fù)用油猫。