数字图像处理(五)几何变换之图像平移、镜像、绕中心点旋转、缩放等
本文为参考这位https://blog.csdn.net/eastmount/article/details/46345299所做的一些笔记,文字部分复制粘贴,代码部分有所改进,增加了绕中心点旋转等
本篇博客是自己的理解,如有错误,欢迎指正,图像数据下载地址
https://download.csdn.net/download/hjxu2016/10436834
代数运算
对多幅图像做处理,也不改变像素的空间位置;
几何运算
对单幅图像做处理,改变像素的空间位置,几何运算包括两个独立的算法:空间变换算法和灰度级插值算法。
图像平移坐标变换如下:
第一步:在ResourceView资源视图中,添加Menu子菜单如下:(注意ID号)
void CImageProcessingView::OnJhbhPy()
{
// TODO: 在此添加命令处理程序代码
if (numPicture == 0)
{
AfxMessageBox("请输入一张图像", MB_OK, 0);
return;
}
if (m_nBitCount != 24)
{
AfxMessageBox("输入图片不是24位", MB_OK, 0);
return;
}
CImagePYDlg dlg;
int num;//记录每一行需要填充的字节
if (m_nWidth * 3 % 4 != 0)
{
num = 4 - m_nWidth * 3 % 4;
}
else
{
num = 0;
}
if (dlg.DoModal() == IDOK)
{
if (dlg.m_xPY > m_nWidth || dlg.m_yPY > m_nHeight)
{
AfxMessageBox("图像平移不能超过原始长度:", MB_OK, 0);
return;
}
AfxMessageBox("图像空间变换-平移", MB_OK, 0);
FILE *fpo = fopen(BmpName, "rb");
FILE *fpw = fopen(BmpNameLin, "wb+");
fread(&bfh, sizeof(BITMAPFILEHEADER), 1, fpo);
fread(&bih, sizeof(BITMAPINFOHEADER), 1, fpo);
fwrite(&bfh, sizeof(BITMAPFILEHEADER), 1, fpw);
fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, fpw);
fread(m_pImage, m_nImage, 1, fpo);
unsigned char *ImageSize;
ImageSize = new unsigned char[m_nImage]; //new和delete有效的进行动态内存的分配和释放
unsigned char black; //填充黑色='0'
int x, y;
for (y = 0; y < m_nHeight; y++)
{
if (y < dlg.m_yPY) /*第一部分:到平移后像素位置前面的所有像素点赋值为黑色*/
{
for (x = 0; x < m_nWidth; x++)
{
ImageSize[(y*m_nWidth + x) * 3 + y*num] = black;
ImageSize[(y*m_nWidth + x) * 3 + 1 + y*num] = black;
ImageSize[(y*m_nWidth + x) * 3 + 2 + y*num] = black;
}
}
else if (y >= dlg.m_yPY)
{
for (x = 0; x < m_nWidth; x++)
{
if (x < dlg.m_xPY)
{
ImageSize[(y*m_nWidth + x) * 3 + y*num] = black;
ImageSize[(y*m_nWidth + x) * 3 + 1 + y*num] = black;
ImageSize[(y*m_nWidth + x) * 3 + 2 + y*num] = black;
}
else if (x >= dlg.m_xPY)
{
ImageSize[(y*m_nWidth + x) * 3 + y*num] = m_pImage[((y-dlg.m_yPY)*m_nWidth + x-dlg.m_xPY) * 3 + y*num];
ImageSize[(y*m_nWidth + x) * 3 + 1 + y*num] = m_pImage[((y - dlg.m_yPY)*m_nWidth + x - dlg.m_xPY) * 3+1 + y*num];
ImageSize[(y*m_nWidth + x) * 3 + 2 + y*num] = m_pImage[((y - dlg.m_yPY)*m_nWidth + x - dlg.m_xPY) * 3+1 + y*num];
}
}
}
}
fwrite(ImageSize, m_nImage, 1, fpw);
fclose(fpo);
fclose(fpw);
numPicture = 2;
level = 400;
Invalidate();
}
}
二. 图像镜像
1.水平镜像翻转
其设原图像宽度为lWidth,高度为lHeight,源图像中(x0,y0)经过水平镜像后坐标为
void CImageProcessingView::OnJhbhSpjx()
{
// TODO: 在此添加命令处理程序代码
// TODO: 在此添加命令处理程序代码
if (numPicture == 0)
{
AfxMessageBox("请输入一张图像", MB_OK, 0);
return;
}
if (m_nBitCount != 24)
{
AfxMessageBox("输入图片不是24位", MB_OK, 0);
return;
}
AfxMessageBox("水平镜像!", MB_OK, 0);
int num;//记录每一行需要填充的字节
if (m_nWidth * 3 % 4 != 0)
{
num = 4 - m_nWidth * 3 % 4;
}
else
{
num = 0;
}
//打开临时的图片
FILE *fpo = fopen(BmpName, "rb");
FILE *fpw = fopen(BmpNameLin, "wb+");
fread(&bfh, sizeof(BITMAPFILEHEADER), 1, fpo);
fread(&bih, sizeof(BITMAPINFOHEADER), 1, fpo);
fwrite(&bfh, sizeof(BITMAPFILEHEADER), 1, fpw);
fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, fpw);
fread(m_pImage, m_nImage, 1, fpo);
/*new和delete有效的进行动态内存的分配和释放*/
unsigned char *ImageSize;
ImageSize = new unsigned char[m_nImage];
int x, y;
for (y = 0; y < m_nHeight; y++)
{
for (x = 0; x < m_nWidth; x++)
{
ImageSize[(y*m_nWidth + x)*3 + y*num] = m_pImage[(y*m_nWidth + m_nWidth - x)*3 + y*num];
ImageSize[(y*m_nWidth + x) * 3 + y*num + 1] = m_pImage[(y*m_nWidth + m_nWidth - x)*3 + y*num + 1];
ImageSize[(y*m_nWidth + x) * 3 + y*num + 2] = m_pImage[(y*m_nWidth + m_nWidth - x)*3 + y*num + 2];
}
}
fwrite(ImageSize, m_nImage, 1, fpw);
fclose(fpo);
fclose(fpw);
numPicture = 2;
level = 400;
Invalidate();
}
2.垂直镜像倒转
其中变换矩阵如下:
X=X0
Y=height-Y0-1 (height为图像高度)
它相当于把原图的像素矩阵的最后一行像素值赋值给第一行,首先找到(0,0)对应的(height-1,0)像素值,然后依次赋值该行的像素数据;最后当前行赋值结束,依次下一行。重点是找到每行的第一个像素点即可。
代码中引用两个变量:Place=(m_nWidth*3)*(m_nHeight-1-1)即是(height-1,0)最后一行的第一个像素点;然后是循环中Place=(m_nWidth*3)*(m_nHeight-number-1)找到每行的第一个像素点。
同样通过类向导生成函数void CImageProcessingView::OnJhbhDz(),代码如下:
void CImageProcessingView::OnJhbhCzjx()
{
// TODO: 在此添加命令处理程序代码
// TODO: 在此添加命令处理程序代码
// TODO: 在此添加命令处理程序代码
if (numPicture == 0)
{
AfxMessageBox("请输入一张图像", MB_OK, 0);
return;
}
if (m_nBitCount != 24)
{
AfxMessageBox("输入图片不是24位", MB_OK, 0);
return;
}
AfxMessageBox("垂直镜像!", MB_OK, 0);
int num;//记录每一行需要填充的字节
if (m_nWidth * 3 % 4 != 0)
{
num = 4 - m_nWidth * 3 % 4;
}
else
{
num = 0;
}
//打开临时的图片
FILE *fpo = fopen(BmpName, "rb");
FILE *fpw = fopen(BmpNameLin, "wb+");
fread(&bfh, sizeof(BITMAPFILEHEADER), 1, fpo);
fread(&bih, sizeof(BITMAPINFOHEADER), 1, fpo);
fwrite(&bfh, sizeof(BITMAPFILEHEADER), 1, fpw);
fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, fpw);
fread(m_pImage, m_nImage, 1, fpo);
/*new和delete有效的进行动态内存的分配和释放*/
unsigned char *ImageSize;
ImageSize = new unsigned char[m_nImage];
int x, y;
for (y = 0; y < m_nHeight; y++)
{
for (x = 0; x < m_nWidth; x++)
{
ImageSize[(y*m_nWidth + x) * 3 + y*num] = m_pImage[((m_nHeight - y)*m_nWidth + x) * 3 + (m_nHeight - y)*num];
ImageSize[(y*m_nWidth + x) * 3 + y*num + 1] = m_pImage[((m_nHeight - y)*m_nWidth + x) * 3 + (m_nHeight - y)*num + 1];
ImageSize[(y*m_nWidth + x) * 3 + y*num + 2] = m_pImage[((m_nHeight - y)*m_nWidth + x) * 3 + (m_nHeight - y)*num + 2];
}
}
fwrite(ImageSize, m_nImage, 1, fpw);
fclose(fpo);
fclose(fpw);
numPicture = 2;
level = 400;
Invalidate();
}
三. 图像旋转-绕左下角旋转
图像饶原点旋转顺时针theta角矩阵变换如下:注意BMP图像(0,0)左下角
先来个绕左下角旋转
新建Dialog如下图所示,设置ID_DIALOG_XZ和变量:
在类向导(Ctrl+W)选择类CImageProcessingView,为ID_JHBH_TXXZ(图像旋转)添加函数,同时添加头文件#include "ImageXZDlg.h"
void CImageProcessingView::OnXzzxj()
{
// TODO: 在此添加命令处理程序代码
if (numPicture == 0)
{
AfxMessageBox("请输入一张图像", MB_OK, 0);
return;
}
if (m_nBitCount != 24)
{
AfxMessageBox("输入图片不是24位", MB_OK, 0);
return;
}
AfxMessageBox("左下角旋转!", MB_OK, 0);
CImageXZ dlg;
if (dlg.DoModal() == IDOK)
{
int num;//记录每一行需要填充的字节
if (m_nWidth * 3 % 4 != 0)
{
num = 4 - m_nWidth * 3 % 4;
}
else
{
num = 0;
}
double PI = asin(0.5) * 6;
double degree = 1.0 * dlg.m_xXZJZ * PI / 180;
CString str;
str.Format("转换后的角度=%d", dlg.m_xXZJZ);
AfxMessageBox(str);
//打开临时的图片
FILE *fpo = fopen(BmpName, "rb");
FILE *fpw = fopen(BmpNameLin, "wb+");
fread(&bfh, sizeof(BITMAPFILEHEADER), 1, fpo);
fread(&bih, sizeof(BITMAPINFOHEADER), 1, fpo);
fwrite(&bfh, sizeof(BITMAPFILEHEADER), 1, fpw);
fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, fpw);
fread(m_pImage, m_nImage, 1, fpo);
/*new和delete有效的进行动态内存的分配和释放*/
unsigned char *ImageSize;
ImageSize = new unsigned char[m_nImage];
unsigned char black;
int x, y;
int xPlace, yPlace;
for (y = 0; y < m_nHeight; y++)//高对应着行
{
for (x = 0; x < m_nWidth; x++)//宽对应着列
{
xPlace = (int)(x*cos(degree) - y*sin(degree));//代表原图上xplace的x坐标
yPlace = (int)(x*sin(degree) + y*cos(degree));//代表原图上yplace的y坐标
if (xPlace <= m_nWidth && yPlace <= m_nHeight && xPlace>=0 && yPlace>=0)
{
ImageSize[(y*m_nWidth + x) * 3 + y*num] = m_pImage[(yPlace*m_nWidth+xPlace)*3 + yPlace * num];
ImageSize[(y*m_nWidth + x) * 3 + y*num + 1] = m_pImage[(yPlace*m_nWidth + xPlace) * 3 + yPlace * num + 1];
ImageSize[(y*m_nWidth + x) * 3 + y*num + 2] = m_pImage[(yPlace*m_nWidth + xPlace) * 3 + yPlace * num + 2];
}
else if (xPlace > m_nWidth || yPlace > m_nHeight || xPlace < 0 || yPlace < 0)
{
ImageSize[(y*m_nWidth + x) * 3 + y*num] = black;
ImageSize[(y*m_nWidth + x) * 3 + y*num + 1] = black;
ImageSize[(y*m_nWidth + x) * 3 + y*num + 2] = black;
}
}
}
fwrite(ImageSize, m_nImage, 1, fpw);
fclose(fpo);
fclose(fpw);
numPicture = 2;
level = 400; //几何变换
Invalidate();
}
}
运行效果如下图所示,中心旋转太难了!找到中心那个位置就不太容易,我做不下去了,fuck~同时旋转过程中,由于是饶左下角(0,0)实现,故有的角度会到界面外显示全黑。下图分别旋转15度和355度。
这理论部分就有点复杂,还要找中心点坐标啥的,首先我干脆不找中心点了,默认中心点就是(width/2,height/2)
代码如下
void CImageProcessingView::OnJhzhZxxz()
{
// TODO: 在此添加命令处理程序代码
// TODO: 在此添加命令处理程序代码
if (numPicture == 0)
{
AfxMessageBox("请输入一张图像", MB_OK, 0);
return;
}
if (m_nBitCount != 24)
{
AfxMessageBox("输入图片不是24位", MB_OK, 0);
return;
}
AfxMessageBox("中心点逆时针旋转!", MB_OK, 0);
CImageXZDlg dlg;
if (dlg.DoModal() == IDOK)
{
int num;//记录每一行需要填充的字节
if (m_nWidth * 3 % 4 != 0)
{
num = 4 - m_nWidth * 3 % 4;
}
else
{
num = 0;
}
double PI = asin(0.5) * 6;//定义pi
double degree = 1.0 * dlg.m_nZXJZ * PI / 180;//计算角度
CString str;
str.Format("转换后的角度=%d", dlg.m_nZXJZ);
AfxMessageBox(str);
//打开临时的图片
FILE *fpo = fopen(BmpName, "rb");
FILE *fpw = fopen(BmpNameLin, "wb+");
fread(&bfh, sizeof(BITMAPFILEHEADER), 1, fpo);
fread(&bih, sizeof(BITMAPINFOHEADER), 1, fpo);
fwrite(&bfh, sizeof(BITMAPFILEHEADER), 1, fpw);
fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, fpw);
fread(m_pImage, m_nImage, 1, fpo);
/*new和delete有效的进行动态内存的分配和释放*/
unsigned char *ImageSize;
ImageSize = new unsigned char[m_nImage];
unsigned char black;
int x, y; //旧图像点的坐标
int xPlace, yPlace; //注意,这里xplace和之前的不同,这里是新图像点的坐标
float a0 = (float)(m_nWidth - 1) *0.5;//计算中心点,a0是横坐标,b0是纵坐标
float b0 = (float)(m_nHeight - 1)*0.5;
float varx = -a0*cos(degree) + b0*sin(degree) + a0;//计算两个敞亮
float vary = -a0*sin(degree) - b0*cos(degree) + b0;
for (y = 0; y < m_nHeight; y++)
{
for (x = 0; x < m_nWidth; x++)
{
xPlace = (int)(x*cos(degree) - y*sin(degree) + varx + 0.5);//算出新图像点的坐标
yPlace = (int)(x*sin(degree) + y*cos(degree) + vary + 0.5);
if (xPlace <= m_nWidth && yPlace <= m_nHeight && xPlace >= 0 && yPlace >= 0)
{//替换
ImageSize[(yPlace*m_nWidth + xPlace) * 3 + yPlace * num] = m_pImage[(y*m_nWidth + x) * 3 + y*num];
ImageSize[(yPlace*m_nWidth + xPlace) * 3 + yPlace * num + 1] = m_pImage[(y*m_nWidth + x) * 3 + y*num + 1];
ImageSize[(yPlace*m_nWidth + xPlace) * 3 + yPlace * num + 2] = m_pImage[(y*m_nWidth + x) * 3 + y*num + 2];
}
/*else if (xPlace > m_nWidth || yPlace > m_nHeight || xPlace < 0 || yPlace < 0)
{
ImageSize[(yPlace*m_nWidth + xPlace) * 3 + yPlace * num] = black;
ImageSize[(yPlace*m_nWidth + xPlace) * 3 + yPlace * num + 1] = black;
ImageSize[(yPlace*m_nWidth + xPlace) * 3 + yPlace * num + 2] = black;
}*/
}
}
fwrite(ImageSize, m_nImage, 1, fpw);
fclose(fpo);
fclose(fpw);
numPicture = 2;
level = 400; //几何变换
Invalidate();
}
}
结果很奇怪,旋转90度就不会出现这个问题。后来又写了一个版本的旋转,参考
https://www.cnblogs.com/tingshuo/archive/2011/05/15/2047016.html可以说是复制粘贴哈哈
五. 图像旋转-绕中心旋转(新版本)
图像旋转算法与实现
好吧,先下个定义,图像旋转是指图像以某一点为中心旋转一定的角度,形成一幅新的图像的过程。当然这个点通常就是图像的中心。既然是按照中心旋转,自然会有这样一个属性:旋转前和旋转后的点离中心的位置不变.
根据这个属性,我们可以得到旋转后的点的坐标与原坐标的对应关系。由于原图像的坐标是以左上角为原点的,所以我们先把坐标转换为以图像中心为原点。假设原图像的宽为w,高为h,(x0,y0)为原坐标内的一点,转换坐标后的点为(x1,y1)。那么不难得到:
x1 = x0 - w/2; y1 = -y0 + h/2;
在新的坐标系下,假设点(x
0,y
0)距离原点的距离为r,点与原点之间的连线与x轴的夹角为b,旋转的角度为a,旋转后的点为(x
1,y
1), 如下图所示。
那么有以下结论:
x0=rcosb;y0=rsinb
x1 = rcos(b-a) = rcosbcosa+rsinbsina=x0cosa+y0sina;
y1=rsin(b-a)=rsinbcosa-rcosbsina=-x0sina+y0cosa;
得到了转换后的坐标,我们只需要把这些坐标再转换为原坐标系即可。这里还有一点要注意,旋转后的图像的长和宽会发生变化,因此要计算新图像的长和宽。
代码如下
void CImageProcessingView::OnJhbhGjxz()
{
// TODO: 在此添加命令处理程序代码
if (numPicture == 0)
{
AfxMessageBox("请输入一张图像", MB_OK, 0);
return;
}
if (m_nBitCount != 24)
{
AfxMessageBox("输入图片不是24位", MB_OK, 0);
return;
}
AfxMessageBox("中心点逆时针旋转-该进版本!", MB_OK, 0);
CImageXZDlg dlg;
if (dlg.DoModal() == IDOK)
{
int num;//记录每一行需要填充的字节
if (m_nWidth * 3 % 4 != 0)
{
num = 4 - m_nWidth * 3 % 4;
}
else
{
num = 0;
}
double PI = asin(0.5) * 6;
double degree = 1.0 * dlg.m_nZXJZ * PI / 180;
CString str;
str.Format("转换后的角度=%d", dlg.m_nZXJZ);
AfxMessageBox(str);
//打开临时的图片
FILE *fpo = fopen(BmpName, "rb");
FILE *fpw = fopen(BmpNameLin, "wb+");
fread(&bfh, sizeof(BITMAPFILEHEADER), 1, fpo);
fread(&bih, sizeof(BITMAPINFOHEADER), 1, fpo);
fread(m_pImage, m_nImage, 1, fpo);
//以图像中心为原点左上角,右上角,左下角和右下角的坐标,用于计算旋转后的图像的宽和高
POINT pLT, pRT, pLB, pRB;//分别对应左上点、右上点、左下角、右下角
pLT.x = -m_nWidth / 2; pLT.y = m_nHeight / 2;
pRT.x = m_nWidth / 2; pRT.y = m_nHeight / 2;
pLB.x = -m_nWidth / 2; pLB.y = -m_nHeight / 2;
pRB.x = m_nWidth / 2; pRB.y = -m_nHeight / 2;
//旋转之后的坐标
POINT pLTN, pRTN, pLBN, pRBN;
double sina = sin(degree);
double cosa = cos(degree);
pLTN.x = pLT.x*cosa + pLT.y*sina;
pLTN.y = -pLT.x*sina + pLT.y*cosa;
pRTN.x = pRT.x*cosa + pRT.y*sina;
pRTN.y = -pRT.x*sina + pRT.y*cosa;
pLBN.x = pLB.x*cosa + pLB.y*sina;
pLBN.y = -pLB.x*sina + pLB.y*cosa;
pRBN.x = pRB.x*cosa + pRB.y*sina;
pRBN.y = -pRB.x*sina + pRB.y*cosa;
//旋转后图像宽和高
int desWidth = max(abs(pRBN.x - pLTN.x), abs(pRTN.x - pLBN.x));
int desHeight = max(abs(pRBN.y - pLTN.y), abs(pRTN.y - pLBN.y));
//分配旋转后图像的缓存
int desBufSize = ((desWidth * m_nBitCount + 31) / 32) * 4 * desHeight;
/*new和delete有效的进行动态内存的分配和释放*/
unsigned char *ImageSize;
ImageSize = new unsigned char[desBufSize];
BITMAPFILEHEADER nbmfHeader;
nbmfHeader.bfType = 0x4D42;
nbmfHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)+ desWidth * desHeight * m_nBitCount / 8;
nbmfHeader.bfReserved1 = 0;
nbmfHeader.bfReserved2 = 0;
nbmfHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
//Bitmap头信息
BITMAPINFOHEADER bmi;
bmi.biSize = sizeof(BITMAPINFOHEADER);
bmi.biWidth = desWidth;
bmi.biHeight = desHeight;
bmi.biPlanes = 1;
bmi.biBitCount = m_nBitCount;
bmi.biCompression = BI_RGB;
bmi.biSizeImage = 0;
bmi.biXPelsPerMeter = 0;
bmi.biYPelsPerMeter = 0;
bmi.biClrUsed = 0;
bmi.biClrImportant = 0;
fwrite(&nbmfHeader, sizeof(BITMAPFILEHEADER), 1, fpw);
fwrite(&bmi, sizeof(BITMAPINFOHEADER), 1, fpw);
int num1;//记录每一行需要填充的字节
if (desWidth * 3 % 4 != 0)
{
num1 = 4 - desWidth * 3 % 4;
}
else
{
num1 = 0;
}
for (int i = 0; i < desHeight; i++)
{
for (int j = 0; j < desWidth; j++)
{
//转换到以图像为中心的坐标系,并进行逆旋转
int tX = (j - desWidth / 2)*cos(2*PI - degree) + (-i + desHeight / 2)*sin(2 * PI - degree);
int tY = -(j - desWidth / 2)*sin(2 * PI - degree) + (-i + desHeight / 2)*cos(2 * PI - degree);
//如果这个坐标不在原图像内,则不赋值
if (tX > m_nWidth/ 2 || tX < -m_nWidth/ 2 || tY > m_nHeight / 2 || tY < -m_nHeight / 2)
{
continue;
}
//再转换到原坐标系下
int tXN = tX + m_nWidth/ 2; int tYN = abs(tY - m_nHeight / 2);
//值拷贝
ImageSize[(i*desWidth + j) * 3 + i * num1] = m_pImage[(tYN *m_nWidth + tXN) * 3 + tYN*num];
ImageSize[(i*desWidth + j) * 3 + i * num1 + 1] = m_pImage[(tYN *m_nWidth + tXN) * 3 + tYN*num + 1];
ImageSize[(i*desWidth + j) * 3 + i * num1 + 2] = m_pImage[(tYN *m_nWidth + tXN) * 3 + tYN*num + 2];
}
}
fwrite(ImageSize, desBufSize, 1, fpw);
fclose(fpo);
fclose(fpw);
numPicture = 2;
level = 400; //几何变换
Invalidate();
}
}
看新的结果
六. 图像缩放
下面是采用最近邻插值法的过程,注意BMP图缩放还需修改头文件信息。
第一步:在资源视图中添加“图像缩放”Dialog
void CImageProcessingView::OnJhbhTxsfZjl()
{
// TODO: 在此添加命令处理程序代码
if (numPicture == 0)
{
AfxMessageBox("请输入一张图像", MB_OK, 0);
return;
}
if (m_nBitCount != 24)
{
AfxMessageBox("输入图片不是24位", MB_OK, 0);
return;
}
AfxMessageBox("图像缩放-最近邻!", MB_OK, 0);
CImageSFdlg dlg;
if (dlg.DoModal() == IDOK)
{
int num;//记录每一行需要填充的字节
if (m_nWidth * 3 % 4 != 0)
{
num = 4 - m_nWidth * 3 % 4;
}
else
{
num = 0;
}
//打开临时的图片
FILE *fpo = fopen(BmpName, "rb");
FILE *fpw = fopen(BmpNameLin, "wb+");
fread(&bfh, sizeof(BITMAPFILEHEADER), 1, fpo);
fread(&bih, sizeof(BITMAPINFOHEADER), 1, fpo);
fread(m_pImage, m_nImage, 1, fpo);
float SFX = dlg.SFX;
float SFY = dlg.SFY;
//计算缩放后的宽高
int desWidth = int(m_nWidth * SFX);
int desHeight = int(m_nHeight * SFY);
//记录缩放后每一行需要填充的字节
int num1;
if (desWidth * 3 % 4 != 0)
{
num1 = 4 - m_nWidth * 3 % 4;
}
else
{
num1 = 0;
}
//分配旋转后图像的缓存
int desBufSize = ((desWidth * m_nBitCount + 31) / 32) * 4 * desHeight;
unsigned char *ImageSize;
ImageSize = new unsigned char[desBufSize];
BITMAPFILEHEADER nbmfHeader;
nbmfHeader.bfType = 0x4D42;
nbmfHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + desWidth * desHeight * m_nBitCount / 8;
nbmfHeader.bfReserved1 = 0;
nbmfHeader.bfReserved2 = 0;
nbmfHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
//Bitmap头信息
BITMAPINFOHEADER bmi;
bmi.biSize = sizeof(BITMAPINFOHEADER);
bmi.biWidth = desWidth;
bmi.biHeight = desHeight;
bmi.biPlanes = 1;
bmi.biBitCount = m_nBitCount;
bmi.biCompression = BI_RGB;
bmi.biSizeImage = 0;
bmi.biXPelsPerMeter = 0;
bmi.biYPelsPerMeter = 0;
bmi.biClrUsed = 0;
bmi.biClrImportant = 0;
fwrite(&nbmfHeader, sizeof(BITMAPFILEHEADER), 1, fpw);
fwrite(&bmi, sizeof(BITMAPINFOHEADER), 1, fpw);
for (int i = 0; i < desHeight; i++)
{
for (int j = 0; j < desWidth; j++)
{
int tXN = int(j / SFX);
int tYN = int (i / SFY);
//值拷贝
ImageSize[(i*desWidth + j) * 3 + i * num1] = m_pImage[(tYN *m_nWidth + tXN) * 3 + tYN*num];
ImageSize[(i*desWidth + j) * 3 + i * num1 + 1] = m_pImage[(tYN *m_nWidth + tXN) * 3 + tYN*num + 1];
ImageSize[(i*desWidth + j) * 3 + i * num1 + 2] = m_pImage[(tYN *m_nWidth + tXN) * 3 + tYN*num + 2];
}
}
fwrite(ImageSize, desBufSize, 1, fpw);
fclose(fpo);
fclose(fpw);
numPicture = 2;
flagSF = 1;
m_nDrawWidthSF = desWidth;
m_nDrawHeightSF = desHeight;
level = 500; //几何变换
Invalidate();
}
}
第四步:因为图像缩放修改BMP图片头信息,所以需要修改ShowBitmap中的显示第二张图片时的部分代码。如下所示:添加变量flagSF、m_nDrawWidthSF和m_nDrawHeightSF。
- /*定义显示图像缩放时的长宽与标记*/
- int flagSF=0; //图像几何变换缩放变换
- int m_nDrawWidthSF=0; //图像显示宽度缩放后
- int m_nDrawHeightSF=0; //图像显示高度缩放后
- //****************显示BMP格式图片****************//
- void CImageProcessingView::ShowBitmap(CDC *pDC, CString BmpName)
- {
- ......
- else //图像几何变换
- if(level=200)
- {
- m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,
- LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);
- }
- if( m_bitmap.m_hObject ) {
- m_bitmap.Detach(); //m_bitmap为创建的位图对象
- }
- m_bitmap.Attach(m_hBitmapChange);
- //定义并创建一个内存设备环境
- CDC dcBmp;
- if( !dcBmp.CreateCompatibleDC(pDC) ) //创建兼容性的DC
- return;
- BITMAP m_bmp; //临时bmp图片变量
- m_bitmap.GetBitmap(&m_bmp); //将图片载入位图中
- CBitmap *pbmpOld = NULL;
- dcBmp.SelectObject(&m_bitmap); //将位图选入临时内存设备环境
- //图片显示调用函数StretchBlt
- if(flagSF==1)
- {
- CString str;
- str.Format("缩放长=%d 宽%d 原图长=%d 宽=%d",m_nDrawWidthSF,
- m_nDrawHeightSF,m_nWidth,m_nHeight);
- AfxMessageBox(str);
- flagSF=0;
- //m_nDrawWidthSF缩放此存见函数最近邻插值法中赋值
- if(m_nDrawWidthSF<650 && m_nDrawHeightSF<650)
- pDC->StretchBlt(m_nWindowWidth-m_nDrawWidthSF,0,
- m_nDrawWidthSF,m_nDrawHeightSF,&dcBmp,0,0,m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY);
- else
- pDC->StretchBlt(m_nWindowWidth-640,0,640,640,&dcBmp,0,0,
- m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY); //显示大小为640*640
- }
- else {
- //如果图片太大显示大小为固定640*640 否则显示原图大小
- if(m_nDrawWidth<650 && m_nDrawHeight<650)
- pDC->StretchBlt(m_nWindowWidth-m_nDrawWidth,0,
- m_nDrawWidth,m_nDrawHeight,&dcBmp,0,0,m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY);
- else
- pDC->StretchBlt(m_nWindowWidth-640,0,640,640,&dcBmp,0,0,
- m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY);
- }
- //恢复临时DC的位图
- dcBmp.SelectObject(pbmpOld);
- }
运行效果如下图所示,采用最近邻插值法缩放大了会出现失帧。
双线性插值有点难,后面有时间会写一下