-
2018-04-06 10:05:14
1、前言
在机器视觉系统中,图像处理是必不可少的一环。其中设置图像ROI(Region of interest)是基本操作,例如在模板匹配、直线拟合、圆拟合等功能中均会用到。当MFC对话框中的图片控件尺寸有限,而图像尺寸相对较大时,为了能精准设置ROI,此时需要实现图像在窗口内缩放、平移功能。
2、实现
2.1原理
在MFC图片控件中利用Halcon函数库实现图片读取、保存是比较基本的操作。将图片控件句柄传入Halcon打开窗口"open_window"函数后,MFC无法正常响应鼠标消息。此时需要使用MFC“PreTranslateMessage”来获取鼠标坐标。利用Halcon "SetPart" 来设置显示的图像显示范围。
2.2实现
2.2.1图片控件窗口初始化
bool CImageProcess::InitWnd(UINT iWidth,UINT iHeight,CWnd * hWnd, HTuple &hWindow) { try { CRect rPic; hWnd->GetClientRect(rPic); SetWindowAttr("background_color","gray"); //设置图片窗口背景颜色为灰色 OpenWindow(0, 0, rPic.Width(), rPic.Height(), (Hlong)hWnd->GetSafeHwnd(), "visible", "", &hWindow); if (SetHWindowSize(iWidth,iHeight ,rPic,hWindow )) { return false; } SetDraw(hWindow,"margin"); SetColor(hWindow,"red"); SetLineWidth(hWindow,1); } catch(HException &except) { return false; } return true; }
bool CImageProcess::SetHWindowSize(UINT iWidth,UINT iHeight ,CRect rPic,HTuple &hWindow ) { try { //设置halcon内部处理的图像的宽度和高度 SetSystem("tsp_width",(int)iWidth); SetSystem("tsp_height",(int)iHeight); //计算图像缩放倍数 CPoint pointSizeImg,pointSizeImgNew,pointWinShowImgPos;//读取后经过缩放图像的尺寸 pointSizeImg.x =iWidth; pointSizeImg.y = iHeight; m_dScaleWidth = (double)rPic.Width() /pointSizeImg.x ; m_dScaleHeight = (double)rPic.Height() /pointSizeImg.y ; m_dScale = m_dScaleWidth < m_dScaleHeight ? m_dScaleWidth : m_dScaleHeight; //取较小的缩放倍数 //计算图像窗口左上角坐标 pointSizeImgNew.x = pointSizeImg.x*m_dScale; pointSizeImgNew.y = pointSizeImg.y*m_dScale; m_pointOrigin.x = (rPic.Width() - pointSizeImgNew.x) / 2; m_pointOrigin.y = (rPic.Height() - pointSizeImgNew.y) / 2; m_pointDestination.x=m_pointOrigin.x+pointSizeImgNew.x; m_pointDestination.y=m_pointOrigin.y+pointSizeImgNew.y; m_dDispImagePartRow0=0; m_dDispImagePartCol0=0; m_dDispImagePartRow1=iHeight-1; m_dDispImagePartCol1=iWidth-1; //设置显示的图像范围 SetPart(hWindow,m_dDispImagePartRow0,m_dDispImagePartCol0,m_dDispImagePartRow1,m_dDispImagePartCol1); SetWindowExtents(hWindow, m_pointOrigin.y, m_pointOrigin.x, pointSizeImgNew.x, pointSizeImgNew.y); //刷新Pictrue Control } catch(HException &except) { return false; } return true; }
2.2.2鼠标响应
//响应Halcon窗口内,鼠标事件 BOOL CDlgLocation::PreTranslateMessage(MSG* pMsg) { // TODO: 在此添加专用代码和/或调用基类 CPoint point; GetCursorPos(&point); CWnd * phWnd=GetDlgItem(IDC_LOCATIONPICSHOW); phWnd->GetClientRect(m_rPic); phWnd->ClientToScreen(m_rPic); point.x=point.x-m_rPic.left; point.y=point.y-m_rPic.top; phWnd->GetClientRect(m_rPic); if (m_rPic.PtInRect(point)) //鼠标移动在窗口内 { CPoint point2; point2=point; PicControlToImage2(point2,m_pHGMachineVisionDoc->m_iImageWidth,m_pHGMachineVisionDoc->m_iImageHeight); if (pMsg->message==WM_LBUTTONDOWN) //鼠标左键单击 { if (3==m_iCursor) //图像放大 { if (m_dShowScale<6) //最大放大6倍 { m_dShowScale=m_dShowScale*1.1; DisPlay(m_srcImg,m_hWindow); } } else if (4==m_iCursor) //图像缩小 { if (m_dShowScale>0) { m_dShowScale=m_dShowScale/1.1; DisPlay(m_srcImg,m_hWindow); } } else if (5==m_iCursor) //图像移动 { m_dXO=point2.x; m_dYO=point2.y; m_bImgMove=true; } } else if (pMsg->message==WM_LBUTTONUP) { if (5==m_iCursor) //图像移动 { m_bImgMove=false; MoveWnd(point2,m_srcImg,m_hWindow); m_dXO=0; m_dYO=0; } } } return __super::PreTranslateMessage(pMsg); }
2.2.3图像缩放
//图像缩放显示 void CImageProcess::DisPlay(HImage srcImg,HTuple hWindow ) { double dWidth=srcImg.Width().I(); double dHeight=srcImg.Height().I(); double dWidth2=dWidth/m_dShowScale; double dHeight2=dHeight/m_dShowScale; m_dDispImagePartRow0=dHeight/2-dHeight2/2-m_dYOffset; m_dDispImagePartCol0=dWidth/2-dWidth2/2-m_dXOffset; m_dDispImagePartRow1=dHeight/2+dHeight2/2-m_dYOffset; m_dDispImagePartCol1=dWidth/2+dWidth2/2-m_dXOffset; ClearWindow(hWindow); SetPart(hWindow,m_dDispImagePartRow0,m_dDispImagePartCol0,m_dDispImagePartRow1,m_dDispImagePartCol1); DispImage(srcImg, hWindow); }
2.2.4图像平移
void CImageProcess::MoveWnd(CPoint point,HImage srcImg,HTuple hWindow) { double xOffset=point.x-m_dXO; double yOffset=point.y-m_dYO; m_dXOffset=m_dXOffset+point.x-m_dXO; m_dYOffset=m_dYOffset+point.y-m_dYO; m_dDispImagePartRow0=m_dDispImagePartRow0-yOffset; m_dDispImagePartCol0=m_dDispImagePartCol0-xOffset; m_dDispImagePartRow1=m_dDispImagePartRow1-yOffset; m_dDispImagePartCol1=m_dDispImagePartCol1-xOffset; ClearWindow(hWindow); SetPart(hWindow,m_dDispImagePartRow0,m_dDispImagePartCol0,m_dDispImagePartRow1,m_dDispImagePartCol1); DispImage(srcImg, hWindow); }
更多相关内容 -
Halcon C#实现图像的灰度化鼠标放大缩小与平移
2016-10-29 08:59:59③图像平移 ④图像缩放 ⑥重置 移动和缩放功能采用官方的一个类,在halcon控件上移动和缩放很平滑。 此DMOE适合新手学习C#调用HALCON的基本操作,老鸟可以略过了 为了大家多多传源码,不要注册只为了下载走人,... -
MFC空间几何变换之图像平移、镜像、旋转、缩放
2018-07-23 17:06:41本文主要讲述基于VC++6.0 MFC图像处理的应用知识,主要结合自己大三所学课程《数字图像处理》及课件进行讲解,主要通过MFC单文档视图实现显示BMP图片空间几何变换,包括图像平移、图形旋转、图像反转倒置镜像和图像...本文主要讲述基于VC++6.0 MFC图像处理的应用知识,主要结合自己大三所学课程《数字图像处理》及课件进行讲解,主要通过MFC单文档视图实现显示BMP图片空间几何变换,包括图像平移、图形旋转、图像反转倒置镜像和图像缩放的知识。同时文章比较详细基础,没有采用GDI+获取矩阵,而是通过读取BMP图片信息头和矩阵像素实现变换,希望该篇文章对你有所帮助,尤其是初学者和学习图像处理的学生。
【数字图像处理】一.MFC详解显示BMP格式图片
【数字图像处理】二.MFC单文档分割窗口显示图片
【数字图像处理】三.MFC实现图像灰度、采样和量化功能详解
【数字图像处理】四.MFC对话框绘制灰度直方图
【数字图像处理】五.MFC图像点运算之灰度线性变化、灰度非线性变化、阈值化和均衡化处理详解
免费资源下载地址:
http://download.csdn.net/detail/eastmount/8772951
一. 图像平移
前一篇文章讲述了图像点运算(基于像素的图像变换),这篇文章讲述的是图像几何变换:在不改变图像内容的情况下对图像像素进行空间几何变换的处理方式。
点运算对单幅图像做处理,不改变像素的空间位置;代数运算对多幅图像做处理,也不改变像素的空间位置;几何运算对单幅图像做处理,改变像素的空间位置,几何运算包括两个独立的算法:空间变换算法和灰度级插值算法。空间变换操作包括简单空间变换、多项式卷绕和几何校正、控制栅格插值和图像卷绕,这里主要讲述简单的空间变换,如图像平移、镜像、缩放和旋转。主要是通过线性代数中的齐次坐标变换。
图像平移坐标变换如下:
运行效果如下图所示,其中BMP图片(0,0)像素点为左下角。
其代码核心算法:
1.在对话框中输入平移坐标(x,y) m_xPY=x,m_yPY=y
2.定义Place=dlg.m_yPY*m_nWidth*3 表示当前m_yPY行需要填充为黑色
3.新建一个像素矩阵 ImageSize=new unsigned char[m_nImage]
4.循环整个像素矩阵处理
for(int i=0 ; i<m_nImage ; i++ ){
if(i<Place) {ImageSize[i]=black; continue;} //黑色填充底部 从小往上绘图
else if(i>=Place && countWidth<dlg.m_xPY*3) {//黑色填充左部分
ImageSize[i]=black; countWidth++; continue;
}
else if(i>=Place && countWidth>=dlg.m_xPY*3) {//图像像素平移区域
ImageSize[i]=m_pImage[m_pImagePlace];//原(0,0)像素赋值过去
m_pImagePlace++; countWidth++;
if(countWidth==m_nWidth*3) { //一行填满 m_pImagePlace走到(0,1)
number++; m_pImagePlace=number*m_nWidth*3;
}
}
}
5.写文件绘图fwrite(ImageSize,m_nImage,1,fpw)
第一步:在ResourceView资源视图中,添加Menu子菜单如下:(注意ID号)第二步:设置平移对话框。将试图切换到ResourceView界面--选中Dialog,右键鼠标新建一个Dialog,并新建一个名为IDD_DIALOG_PY。编辑框(X)IDC_EDIT_PYX 和 (Y)IDC_EDIT_PYY,确定为默认按钮。设置成下图对话框:
第三步:在对话框资源模板空白区域双击鼠标—Create a new class创建一个新类--命名为CImagePYDlg。会自动生成它的.h和.cpp文件。打开类向导(Ctrl W),选择类名:CImagePYDlg添加成员变量如下图所示,同时在Message Maps中生成ID_JHBH_PY实现函数。
第四步:在CImageProcessingView.cpp中添加头文件#include "ImagePYDlg.h",并实现平移。-
/********************************************************/
-
/* 图像空间几何变换:图像平移 ID_JHBH_PY(几何变换-平移)
-
/* 使用平移对话框:CImagePYDlg dlg
-
/* 算法:f(x,y)=f(x+x0,y+y0)图像所有点平移,空的补黑'0'
-
/* 注意该图像平移方法只是从左上角(0,0)处开始平移
-
/* 其他方向原理相同 自己去实现
-
/********************************************************/
-
void CImageProcessingView::OnJhbhPy()
-
{
-
if(numPicture==0) {
-
AfxMessageBox("载入图片后才能空间平移!",MB_OK,0);
-
return;
-
}
-
//定义采样对话框也是用来空间变换平移的坐标
-
CImagePYDlg dlg;
-
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);
-
/************************************************************/
-
/* 图片空间变换-平移
-
/* 坐标(dlg.m_xPY,dlg.m_yPY)表示图像平移的坐标
-
/* 先用Plave计算出平移后的起始坐标,其他的坐标赋值为'0'黑色
-
/* 然后依次平移坐标,空的赋为黑色,否则填充
-
/************************************************************/
-
/******************************************************************/
-
/* 严重错误1:数组变量赋值相等
-
/* 在View.h中定义变量 BYTE *m_pImage 读入图片数据后的指针
-
/* 建立临时变量数组,让它平移变换 unsigned char *ImageSize
-
/* ImageSize=m_pImage(错误)
-
/* 会导致ImageSize赋值变换时m_pImage也产生了变换,所以输出全为黑色
-
/* 因为它俩指向了相同的数组地址
-
/* 解决方法:使用下面C++的new方法动态分配或for循环i=m_nImage赋值
-
/******************************************************************/
-
/*临时变量存储的像素与m_pImage相同,便于处理图像*/
-
unsigned char *ImageSize;
-
ImageSize=new unsigned char[m_nImage]; //new和delete有效的进行动态内存的分配和释放
-
int Place; //建立临时坐标 记录起始坐标(0,0)平移过来的位置
-
int m_pImagePlace; //原始图像平移为(0,0) 图像把它平移到Place位置
-
unsigned char black; //填充黑色='0'
-
/************************************************************/
-
/* for(int i=0 ; i<m_nHeight ; i++ )
-
/* for(int j=0 ; j<m_nWidth ; j++ )
-
/* 不能使用的上面的因为可能图像的最后一行没有完整的一行像素
-
/* 这样会出现exe报错,使用m_nImage读写所有像素比较正确
-
/************************************************************/
-
Place=dlg.m_yPY*m_nWidth*3; //前m_yPY行都要填充为黑色
-
black=0; //颜色为黑色
-
m_pImagePlace=0; //图像处事位置为(0,0),把该点像素平移过去
-
int countWidth=0; //记录每行的像素个数,满行时变回0
-
int number=0; //数字记录使用的像素行数,平移时使用
-
for(int i=0 ; i<m_nImage ; i++ )
-
{
-
/*如果每行的像素填满时清为0*/
-
if(countWidth==m_nWidth*3) {
-
countWidth=0;
-
}
-
/*第一部分:到平移后像素位置前面的所有像素点赋值为黑色*/
-
if(i<Place) {
-
ImageSize[i]=black; //赋值为黑色
-
continue;
-
}
-
/*第二部分:平移区域的左边部分赋值为黑色*/
-
else if(i>=Place && countWidth<dlg.m_xPY*3) { //RGB乘3
-
ImageSize[i]=black; //赋值为黑色
-
countWidth++;
-
continue;
-
}
-
/****************************/
-
/* 各部分如图所示:
-
/* 000000000000000000000000
-
/* 000000000000000000000000
-
/* 0000000.................
-
/* 0000000.................
-
/* 0000000.................
-
/* 0000000.................
-
/* 点表示像素部分,0为黑色
-
/****************************/
-
/* 重点错误提示:由于bmp图像显示是从左下角开始存储(0,0)点所以输出图像为 */
-
/* bmp图像是从左下角到右上角排列的 */
-
/****************************/
-
/* 各部分如图所示:
-
/* 0000000.................
-
/* 0000000.................
-
/* 0000000.................
-
/* 0000000.................
-
/* 000000000000000000000000
-
/* 000000000000000000000000
-
/* 点表示像素部分,0为黑色
-
/****************************/
-
/*第三部分:图像像素平移区域*/
-
else if(i>=Place && countWidth>=dlg.m_xPY*3)
-
{
-
ImageSize[i]=m_pImage[m_pImagePlace];
-
m_pImagePlace++;
-
countWidth++;
-
if(countWidth==m_nWidth*3)
-
{
-
number++;
-
m_pImagePlace=number*m_nWidth*3;
-
}
-
}
-
}
-
fwrite(ImageSize,m_nImage,1,fpw);
-
fclose(fpo);
-
fclose(fpw);
-
numPicture = 2;
-
level=200; //200表示几何变换
-
Invalidate();
-
}
-
}
同时在ShowBitmap中添加level标记重新绘制图片,代码如下:
-
else //图像几何变换
-
if(level=200)
-
{
-
m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,
-
LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);
-
}
运行时需要注意一点:BMP图像在处理过程中可能会出现一些斜线,而平移(40,60)位移量时可能出现如下。他是因为BMP格式有个非常重要的规定,要求每一扫描的字节数据必须能被4整除,也就是Dword对齐(长度4字节),如果图像的一行字节数不能被4整除,就需要在每行末尾不起0达到标准。
例如一行像素为97字节,我们就需要补3个字节吗,数值可以是0,但是我们在BMP格式的信息头里说明了其宽度,所以补齐后对我们没有影响,所以后面补若干个字节的0即可直到被4整除。
通过后面的图像缩放后,我从学做了一遍这个补齐的缩放。代码如下,能够实现完美平移。nice啊~-
void CImageProcessingView::OnJhbhPy()
-
{
-
if(numPicture==0) {
-
AfxMessageBox("载入图片后才能空间平移!",MB_OK,0);
-
return;
-
}
-
//定义采样对话框也是用来空间变换平移的坐标
-
CImagePYDlg dlg;
-
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);
-
int num; //记录每行多余的图像素数个数
-
int sfSize; //补齐后的图像大小
-
//重点:图像的每行像素都必须是4的倍数:1*1的图像为 r g b 00H
-
if(m_nWidth*3%4!=0)
-
{
-
num=(4-m_nWidth*3%4);
-
sfSize=(m_nWidth*3+num)*m_nHeight; //每行多number个
-
}
-
else
-
{
-
num=0;
-
sfSize=m_nWidth*m_nHeight*3;
-
}
-
//注意:假如最后一行像素不足,我默认处理为完整的一行,不足补00H
-
//总之处理后的图像总是m*n且为4倍数,每行都完整存在
-
/*更改文件头信息 定义临时文件头结构变量*/
-
BITMAPFILEHEADER bfhsf;
-
BITMAPINFOHEADER bihsf;
-
bfhsf=bfh;
-
bihsf=bih;
-
bfhsf.bfSize=sfSize+54;
-
fwrite(&bfhsf,sizeof(BITMAPFILEHEADER),1,fpw);
-
fwrite(&bihsf,sizeof(BITMAPINFOHEADER),1,fpw);
-
fread(m_pImage,m_nImage,1,fpo);
-
CString str;
-
str.Format("补齐=%d",num);
-
AfxMessageBox(str);
-
/*临时变量存储的像素与sfSize相同 new和delete有效的进行动态内存的分配和释放*/
-
unsigned char *ImageSize;
-
ImageSize=new unsigned char[sfSize];
-
int Place; //建立临时坐标 记录起始坐标(0,0)平移过来的位置
-
int m_pImagePlace; //原始图像平移为(0,0) 图像把它平移到Place位置
-
unsigned char black=0; //填充黑色='0'
-
unsigned char other=0; //补码00H='\0'
-
Place=dlg.m_yPY*(m_nWidth*3+num); //前m_yPY行都要填充为黑色
-
m_pImagePlace=0; //图像处事位置为(0,0),把该点像素平移过去
-
int countWidth=0; //记录每行的像素个数,满行时变回0
-
int number=0; //数字记录使用的像素行数,平移时使用
-
for(int i=0 ; i<sfSize ; i++ )
-
{
-
/*第一部分:到平移后像素位置前面的所有像素点赋值为黑色*/
-
if(i<Place)
-
{
-
ImageSize[i]=black; //赋值为黑色
-
continue;
-
}
-
/*第二部分:平移区域的左边部分赋值为黑色*/
-
else if(i>=Place && countWidth<dlg.m_xPY*3) //RGB乘3
-
{
-
ImageSize[i]=black; //赋值为黑色
-
countWidth++;
-
continue;
-
}
-
/*第三部分:图像像素平移区域*/
-
else if(i>=Place && countWidth>=dlg.m_xPY*3)
-
{
-
ImageSize[i]=m_pImage[m_pImagePlace];
-
m_pImagePlace++;
-
countWidth++;
-
if(countWidth==m_nWidth*3)
-
{
-
if(num==0)
-
{
-
countWidth=0;
-
number++;
-
m_pImagePlace=number*m_nWidth*3;
-
}
-
else //num为补0
-
{
-
for(int j=0;j<num;j++)
-
{
-
i++;
-
ImageSize[i]=other;
-
}
-
countWidth=0;
-
number++;
-
m_pImagePlace=number*(m_nWidth*3+num); //重点:添加Num
-
}
-
}
-
}
-
}
-
fwrite(ImageSize,sfSize,1,fpw);
-
fclose(fpo);
-
fclose(fpw);
-
numPicture = 2;
-
level=200; //200表示几何变换
-
Invalidate();
-
}
-
}
运行效果如下图所示,完美平移,其他算法遇到斜线问题类似补齐即可。
二. 图像镜像
1.水平镜像翻转
其变换矩阵如下:
X=width-X0-1 (width为图像宽度)
Y=Y0
打开类向导,在CImageProcessingView中添加IDs为ID_JHBH_FZ,生成函数,代码如下:-
/* 几何变换 图像翻转:自己对这个功能比较感兴趣,做个图像反转 */
-
void CImageProcessingView::OnJhbhFz()
-
{
-
if(numPicture==0) {
-
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);
-
/*new和delete有效的进行动态内存的分配和释放*/
-
unsigned char *ImageSize;
-
ImageSize=new unsigned char[m_nImage];
-
int countWidth=0; //记录每行的像素个数,满行时变回0
-
int Place; //记录图像每行的位置,便于图像反转
-
int number=0; //数字记录使用的像素行数
-
Place=m_nWidth*3-1;
-
//翻转矩阵: y=y0 x=width-x0-1
-
for(int i=0 ; i<m_nImage ; i++ )
-
{
-
if(countWidth==m_nWidth*3)
-
{
-
countWidth=0;
-
}
-
ImageSize[i]=m_pImage[Place]; //(0,0)赋值(0,width*3-1)像素
-
Place--;
-
countWidth++;
-
if(countWidth==m_nWidth*3)
-
{
-
number++;
-
Place=number*m_nWidth*3-1;
-
}
-
}
-
fwrite(ImageSize,m_nImage,1,fpw);
-
fclose(fpo);
-
fclose(fpw);
-
numPicture = 2;
-
level=200;
-
Invalidate();
-
}
运行效果如下图所示,其中还是存在一些小BUG,如前面的BMP图补0凑齐4整数倍宽度或颜色失帧。
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::OnJhbhDz()
-
{
-
if(numPicture==0) {
-
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);
-
/*new和delete有效的进行动态内存的分配和释放*/
-
unsigned char *ImageSize;
-
ImageSize=new unsigned char[m_nImage];
-
int countWidth=0; //记录每行像素个数,满行时变回0
-
int Place; //每列位置
-
int number=0; //像素行数
-
Place=(m_nWidth*3)*(m_nHeight-1-1); //0行存储
-
//翻转矩阵: x=x0 y=height-y0-1
-
for(int i=0 ; i<m_nImage ; i++ )
-
{
-
ImageSize[i]=m_pImage[Place]; //(0,0)赋值(0,0)像素
-
Place++;
-
countWidth++;
-
if(countWidth==m_nWidth*3)
-
{
-
countWidth=0;
-
number++;
-
Place=(m_nWidth*3)*(m_nHeight-number-1);
-
}
-
}
-
fwrite(ImageSize,m_nImage,1,fpw);
-
fclose(fpo);
-
fclose(fpw);
-
numPicture = 2;
-
level=200;
-
Invalidate();
-
}
运行结果如下图所示,第二张图颜色没有失帧或变灰,这完全可以怀疑在翻转过程中RGB像素编程BGR后导致的结果,最终实现了翻转图像,但灰度存在一定;所以如果改为RBG顺序不变化即可原图颜色显示。
三. 图像旋转
图像饶原点旋转顺时针theta角矩阵变换如下:注意BMP图像(0,0)左下角
写到这里真心觉得写底层的代码非常困难啊!尤其是以为像素转换二维像素,同时也觉得当时的自己算法部分还是很强大的,也感觉到如果采用GDI+操作像素矩阵Matrix或ColorMatrix是多么的方便,因为它定义好了X和Y向量,这就是为什么Android前面写的图像处理要容易得多。但是效率高~
好像利用GDI+旋转通过几句代码即可:
matrix.Rotate(15); //矩阵旋转15度
graph.SetTransform(&matrix);
graph.DrawImage(&image,points,3);
下面这部分代码是实现Android旋转的:参考我的博客-
//旋转图片
-
private void TurnPicture() {
-
Matrix matrix = new Matrix();
-
turnRotate=turnRotate+15;
-
//选择角度 饶(0,0)点选择 正数顺时针 负数逆时针 中心旋转
-
matrix.setRotate(turnRotate,bmp.getWidth()/2,bmp.getHeight()/2);
-
Bitmap createBmp = Bitmap.createBitmap(bmp.getWidth(), bmp.getHeight(), bmp.getConfig());
-
Canvas canvas = new Canvas(createBmp);
-
Paint paint = new Paint();
-
canvas.drawBitmap(bmp, matrix, paint);
-
imageCreate.setBackgroundColor(Color.RED);
-
imageCreate.setImageBitmap(createBmp);
-
textview2.setVisibility(View.VISIBLE);
-
}
实现效果如下图所示:
言归正传,新建Dialog如下图所示,设置ID_DIALOG_XZ和变量:
再点击空白处创建CImageXZDlg类(旋转),它会自动生成.h和.cpp文件。打开类向导生成CImageXZDlg类的成员变量m_xzds(旋转度数),并设置其为int型(最大值360 最小值0)。
在类向导(Ctrl+W)选择类CImageProcessingView,为ID_JHBH_TXXZ(图像旋转)添加函数,同时添加头文件#include "ImageXZDlg.h"-
/**********************************************************/
-
/* 几何变换:图片旋转
-
/* 先添加对话框:IDD_JHBH_TXXZ(图像旋转),创建新类CImageXZDlg
-
/* 创建输入度数的:m_xzds Member variables 为int 0-360间
-
/**********************************************************/
-
void CImageProcessingView::OnJhbhTxxz()
-
{
-
if(numPicture==0) {
-
AfxMessageBox("载入图片后才能空间旋转!",MB_OK,0);
-
return;
-
}
-
//定义对话框并调用对话框
-
CImageXZDlg dlg;
-
if( dlg.DoModal()==IDOK ) //显示对话框
-
{
-
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);
-
/*new和delete有效的进行动态内存的分配和释放*/
-
unsigned char *ImageSize;
-
ImageSize=new unsigned char[m_nImage];
-
int Place; //记录图像每行的位置,便于图像旋转
-
/*定义PA=3.14时使用的方法是arcsin(1.0/2)*6即为π*/
-
double PA;
-
PA=asin(0.5)*6;
-
/*把输入的0-360的正整数度数转换为角度,30度=π/6*/
-
double degree;
-
degree=PA*dlg.m_xzds/180; //调用dlg.m_xzds(旋转度数)
-
//对应的二维矩阵 注意图像矩阵从左下角开始处理 它最终要转换成一维存储
-
int X,Y; //图像变换前通过一维矩阵转换为二维
-
int XPlace,YPlace;
-
//输出转换为的角度
-
CString str;
-
str.Format("转换后的角度=%f",degree);
-
AfxMessageBox(str);
-
//图像旋转处理
-
for(int i=0 ; i<m_nImage ; i++ )
-
{
-
//原图:一维矩阵转换为二维矩阵
-
X=(i/3)%m_nWidth;
-
Y=(i/3)/m_nWidth;
-
//注意错误:X=i/m_nHeight Y=i%m_nWidth; 只输出最后1/3
-
//图像旋转为:a(x,y)=x*cos-y*sin b(x,y)=x*sin+y*cos
-
XPlace=(int)(X*cos(degree)-Y*sin(degree));
-
YPlace=(int)(X*sin(degree)+Y*cos(degree));
-
//在转换为一维图想输出
-
if( (XPlace>=0 && XPlace<=m_nWidth) && (YPlace>=0 && YPlace<=m_nHeight) )
-
{
-
Place=YPlace*m_nWidth*3+XPlace*3;
-
//在图像范围内赋值为该像素
-
if(Place+2<m_nImage)
-
{
-
ImageSize[i]=m_pImage[Place];
-
i++;
-
ImageSize[i]=m_pImage[Place+1];
-
i++;
-
ImageSize[i]=m_pImage[Place+2];
-
}
-
//否则赋值为黑色
-
else
-
{
-
ImageSize[i]=0;
-
i++;
-
ImageSize[i]=0;
-
i++;
-
ImageSize[i]=0;
-
}
-
}
-
//否则赋值为黑色
-
else
-
{
-
ImageSize[i]=0;
-
i++;
-
ImageSize[i]=0;
-
i++;
-
ImageSize[i]=0;
-
}
-
}
-
fwrite(ImageSize,m_nImage,1,fpw);
-
fclose(fpo);
-
fclose(fpw);
-
numPicture = 2;
-
level=200; //几何变换
-
Invalidate();
-
}
-
}
运行效果如下图所示,中心旋转太难了!找到中心那个位置就不太容易,我做不下去了,fuck~同时旋转过程中,由于是饶左下角(0,0)实现,故有的角度会到界面外显示全黑。下图分别旋转15度和355度。
四. 图像缩放
图像缩放主要有两种方法:
1.最近邻插值:向后映射时,输出图像的灰度等于离它所映射位置最近的输入图像的灰度值。其中向前映射和向后映射如下:对于向前映射每个输出图像的灰度要经过多次运算,对于向后映射,每个输出图像的灰度只经过一次运算。在实际应用中,更多的是采用向后映射法,其中根据四个相邻像素灰度值计算某个位置的像素灰度值即为灰度级插值。
2.双线性插值:四点确定一个平面函数,属于过约束问题。即单位正方形顶点已知,求正方形内任一点的f(x,y)值。
换个通熟的说法,如下图所示。采用最近邻插值法就是P(x,y)像素值采用四舍五入等于离它最近的输入图像像素值。分别计算它到四个顶点之间的距离,但是这样会造成图像的马赛克、锯齿等现象。而采用双线性插值法,主要通过该坐标周围的四个像素值,按照比例混合计算器近似值。比例混合的依据是离哪个像素近,哪个像素的比例越大。
下面是采用最近邻插值法的过程,注意BMP图缩放还需修改头文件信息。
第一步:在资源视图中添加“图像缩放”Dialog
第二步:点击空白处创建对话框的类CImageSFDlg,同时打开类向导为其添加成员变量m_sfbs(缩放倍数),其为int型在0-200之间。
第三步:打开类向导为其添加成员函数void CImageProcessingView::OnJhbhSf() 并实现缩放。同时添加头文件#include "ImageSFDlg.h"。-
/*******************************************************************/
-
/* ID_JHBH_SF: 几何运算-缩放-最近邻插值算法
-
/* 算法思想:输出图像的灰度等于离它所映射位置最近的输入图像的灰度值
-
/* 先计算出放大缩小后的长宽,根据它计算找原图中的点灰度,四舍五入
-
/*******************************************************************/
-
void CImageProcessingView::OnJhbhSf()
-
{
-
if(numPicture==0) {
-
AfxMessageBox("载入图片后才能几何缩放图像!",MB_OK,0);
-
return;
-
}
-
CImageSFDlg dlg; //定义缩放对话框
-
if( dlg.DoModal()==IDOK )
-
{
-
//采样坐标最初为图片的自身像素 m_sfbs(缩放倍数)
-
if( dlg.m_sfbs==0 ) {
-
AfxMessageBox("输入图片缩放倍数不能为0!",MB_OK,0);
-
return;
-
}
-
FILE *fpo = fopen(BmpName,"rb");
-
FILE *fpw = fopen(BmpNameLin,"wb+");
-
fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);
-
fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);
-
/*先求缩放后的长宽*/
-
int sfWidth,sfHeight; //缩放后的长宽
-
int sfSize; //缩放后的图像大小
-
sfWidth=(int)(m_nWidth*(dlg.m_sfbs*1.0)/100); //24位图像RGB必须是3倍数 循环读取时为RGB
-
sfHeight=(int)(m_nHeight*(dlg.m_sfbs*1.0)/100);
-
int number; //记录每行多余的图像素数个数
-
//重点:图像的每行像素都必须是4的倍数:1*1的图像为 r g b 00H
-
if(sfWidth*3%4!=0) {
-
number=(4-sfWidth*3%4);
-
sfSize=(sfWidth*3+(4-sfWidth*3%4))*sfHeight;
-
}
-
else {
-
number=0;
-
sfSize=sfWidth*sfHeight*3;
-
}
-
//注意:假如最后一行像素不足,我默认处理为完整的一行,不足补00H
-
//总之处理后的图像总是m*n且为4倍数,每行都完整存在
-
/*更改文件头信息 定义临时文件头结构变量*/
-
BITMAPFILEHEADER bfhsf;
-
BITMAPINFOHEADER bihsf; //缩放(sf)
-
bfhsf=bfh;
-
bihsf=bih;
-
bfhsf.bfSize=sfSize+54;
-
bihsf.biWidth=sfWidth;
-
bihsf.biHeight=sfHeight;
-
//显示部分m_nDrawWidth<650显示原图,否则显示
-
flagSF=1; //图像缩放为1标识变量
-
m_nDrawWidthSF=sfWidth;
-
m_nDrawHeightSF=sfHeight;
-
fwrite(&bfhsf,sizeof(BITMAPFILEHEADER),1,fpw);
-
fwrite(&bihsf,sizeof(BITMAPINFOHEADER),1,fpw);
-
fread(m_pImage,m_nImage,1,fpo);
-
unsigned char red,green,blue;
-
unsigned char other=0; //补码00H='\0'
-
int placeX; //记录在原图中的第几行的位置
-
int placeY; //记录在原图中的位置(x,y)
-
int placeBH; //记录变换后在变换图中的位置
-
/*new和delete有效的进行动态内存的分配和释放*/
-
unsigned char *ImageSize;
-
ImageSize=new unsigned char[sfSize];
-
/*读取文件像素信息 缩放注意:1.找最近灰度 2.四舍五入法(算法+0.5)*/
-
for(int i=0; i<sfHeight ; i++ ) //行
-
{
-
placeX=(int)(i/(dlg.m_sfbs*1.0/100)+0.5)*bih.biWidth*3;
-
for(int j=0; j<sfWidth ; j++ ) //列
-
{
-
red=green=blue=0;
-
//放大倍数为(dlg.m_sfbs*1.0/100)
-
placeY=placeX+(int)(j/(dlg.m_sfbs*1.0/100)+0.5)*3;
-
//重点是:number*i补充00H,如果是numer图像会被切成2块
-
placeBH=(i*sfWidth*3+number*i)+j*3;
-
if(placeY+2<m_nImage)
-
{
-
ImageSize[placeBH]=m_pImage[placeY];
-
ImageSize[placeBH+1]=m_pImage[placeY+1];
-
ImageSize[placeBH+2]=m_pImage[placeY+2];
-
}
-
else
-
{
-
ImageSize[placeBH]=0;
-
ImageSize[placeBH+1]=0;
-
ImageSize[placeBH+2]=0;
-
}
-
}
-
}
-
fwrite(ImageSize,sfSize,1,fpw);
-
fclose(fpo);
-
fclose(fpw);
-
numPicture = 2;
-
level=200;
-
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);
-
}
运行效果如下图所示,采用最近邻插值法缩放大了会出现失帧。
但是同时当图片缩小是总是报错,图片缩放确实有点难,因为像素需要补齐4整数倍,同时需要修改消息头,同时像素矩阵的变换都非常复杂。
//
-
-
【数字图像处理】六.MFC空间几何变换之图像平移、镜像、旋转、缩放详解
2015-06-04 17:28:35本文主要讲述基于VC++6.0 MFC图像处理的应用知识,主要结合自己大三所学课程《数字图像处理》及课件进行讲解,主要通过MFC单文档视图实现显示BMP图片空间几何变换,包括图像平移、图形旋转、图像反转倒置镜像和图像...本文主要讲述基于VC++6.0 MFC图像处理的应用知识,主要结合自己大三所学课程《数字图像处理》及课件进行讲解,主要通过MFC单文档视图实现显示BMP图片空间几何变换,包括图像平移、图形旋转、图像反转倒置镜像和图像缩放的知识。同时文章比较详细基础,没有采用GDI+获取矩阵,而是通过读取BMP图片信息头和矩阵像素实现变换,希望该篇文章对你有所帮助,尤其是初学者和学习图像处理的学生。
【数字图像处理】一.MFC详解显示BMP格式图片
【数字图像处理】二.MFC单文档分割窗口显示图片
【数字图像处理】三.MFC实现图像灰度、采样和量化功能详解
【数字图像处理】四.MFC对话框绘制灰度直方图
【数字图像处理】五.MFC图像点运算之灰度线性变化、灰度非线性变化、阈值化和均衡化处理详解
免费资源下载地址:
http://download.csdn.net/detail/eastmount/8772951
一. 图像平移
前一篇文章讲述了图像点运算(基于像素的图像变换),这篇文章讲述的是图像几何变换:在不改变图像内容的情况下对图像像素进行空间几何变换的处理方式。
点运算对单幅图像做处理,不改变像素的空间位置; 代数运算 对多幅图像做处理,也不改变像素的空间位置; 几何运算 对单幅图像做处理,改变像素的空间位置,几何运算包括两个独立的算法:空间变换算法和灰度级插值算法。空间变换操作包括简单空间变换、多项式卷绕和几何校正、控制栅格插值和图像卷绕,这里主要讲述简单的空间变换,如图像平移、镜像、缩放和旋转。主要是通过线性代数中的齐次坐标变换。
图像平移坐标变换如下:
运行效果如下图所示,其中BMP图片(0,0)像素点为左下角。
其代码核心算法:
1.在对话框中输入平移坐标(x,y) m_xPY=x,m_yPY=y
2.定义Place=dlg.m_yPY*m_nWidth*3 表示当前m_yPY行需要填充为黑色
3.新建一个像素矩阵 ImageSize=new unsigned char[m_nImage]
4.循环整个像素矩阵处理
for(int i=0 ; i<m_nImage ; i++ ){
if(i<Place) {ImageSize[i]=black; continue;} //黑色填充底部 从小往上绘图
else if(i>=Place && countWidth<dlg.m_xPY*3) {//黑色填充左部分
ImageSize[i]=black; countWidth++; continue;
}
else if(i>=Place && countWidth>=dlg.m_xPY*3) {//图像像素平移区域
ImageSize[i]=m_pImage[m_pImagePlace];//原(0,0)像素赋值过去
m_pImagePlace++; countWidth++;
if(countWidth==m_nWidth*3) { //一行填满 m_pImagePlace走到(0,1)
number++; m_pImagePlace=number*m_nWidth*3;
}
}
}
5.写文件绘图fwrite(ImageSize,m_nImage,1,fpw)
第一步:在ResourceView资源视图中,添加Menu子菜单如下:(注意ID号)
第二步:设置平移对话框。将试图切换到ResourceView界面--选中Dialog,右键鼠标新建一个Dialog,并新建一个名为IDD_DIALOG_PY。编辑框(X)IDC_EDIT_PYX 和 (Y)IDC_EDIT_PYY,确定为默认按钮。设置成下图对话框:
第三步:在对话框资源模板空白区域双击鼠标—Create a new class创建一个新类--命名为CImagePYDlg。会自动生成它的.h和.cpp文件。打开类向导(Ctrl W),选择类名:CImagePYDlg添加成员变量如下图所示,同时在Message Maps中生成ID_JHBH_PY实现函数。
第四步:在CImageProcessingView.cpp中添加头文件#include "ImagePYDlg.h",并实现平移。
同时在ShowBitmap中添加level标记重新绘制图片,代码如下:/********************************************************/ /* 图像空间几何变换:图像平移 ID_JHBH_PY(几何变换-平移) /* 使用平移对话框:CImagePYDlg dlg /* 算法:f(x,y)=f(x+x0,y+y0)图像所有点平移,空的补黑'0' /* 注意该图像平移方法只是从左上角(0,0)处开始平移 /* 其他方向原理相同 自己去实现 /********************************************************/ void CImageProcessingView::OnJhbhPy() { if(numPicture==0) { AfxMessageBox("载入图片后才能空间平移!",MB_OK,0); return; } //定义采样对话框也是用来空间变换平移的坐标 CImagePYDlg dlg; 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); /************************************************************/ /* 图片空间变换-平移 /* 坐标(dlg.m_xPY,dlg.m_yPY)表示图像平移的坐标 /* 先用Plave计算出平移后的起始坐标,其他的坐标赋值为'0'黑色 /* 然后依次平移坐标,空的赋为黑色,否则填充 /************************************************************/ /******************************************************************/ /* 严重错误1:数组变量赋值相等 /* 在View.h中定义变量 BYTE *m_pImage 读入图片数据后的指针 /* 建立临时变量数组,让它平移变换 unsigned char *ImageSize /* ImageSize=m_pImage(错误) /* 会导致ImageSize赋值变换时m_pImage也产生了变换,所以输出全为黑色 /* 因为它俩指向了相同的数组地址 /* 解决方法:使用下面C++的new方法动态分配或for循环i=m_nImage赋值 /******************************************************************/ /*临时变量存储的像素与m_pImage相同,便于处理图像*/ unsigned char *ImageSize; ImageSize=new unsigned char[m_nImage]; //new和delete有效的进行动态内存的分配和释放 int Place; //建立临时坐标 记录起始坐标(0,0)平移过来的位置 int m_pImagePlace; //原始图像平移为(0,0) 图像把它平移到Place位置 unsigned char black; //填充黑色='0' /************************************************************/ /* for(int i=0 ; i<m_nHeight ; i++ ) /* for(int j=0 ; j<m_nWidth ; j++ ) /* 不能使用的上面的因为可能图像的最后一行没有完整的一行像素 /* 这样会出现exe报错,使用m_nImage读写所有像素比较正确 /************************************************************/ Place=dlg.m_yPY*m_nWidth*3; //前m_yPY行都要填充为黑色 black=0; //颜色为黑色 m_pImagePlace=0; //图像处事位置为(0,0),把该点像素平移过去 int countWidth=0; //记录每行的像素个数,满行时变回0 int number=0; //数字记录使用的像素行数,平移时使用 for(int i=0 ; i<m_nImage ; i++ ) { /*如果每行的像素填满时清为0*/ if(countWidth==m_nWidth*3) { countWidth=0; } /*第一部分:到平移后像素位置前面的所有像素点赋值为黑色*/ if(i<Place) { ImageSize[i]=black; //赋值为黑色 continue; } /*第二部分:平移区域的左边部分赋值为黑色*/ else if(i>=Place && countWidth<dlg.m_xPY*3) { //RGB乘3 ImageSize[i]=black; //赋值为黑色 countWidth++; continue; } /****************************/ /* 各部分如图所示: /* 000000000000000000000000 /* 000000000000000000000000 /* 0000000................. /* 0000000................. /* 0000000................. /* 0000000................. /* 点表示像素部分,0为黑色 /****************************/ /* 重点错误提示:由于bmp图像显示是从左下角开始存储(0,0)点所以输出图像为 */ /* bmp图像是从左下角到右上角排列的 */ /****************************/ /* 各部分如图所示: /* 0000000................. /* 0000000................. /* 0000000................. /* 0000000................. /* 000000000000000000000000 /* 000000000000000000000000 /* 点表示像素部分,0为黑色 /****************************/ /*第三部分:图像像素平移区域*/ else if(i>=Place && countWidth>=dlg.m_xPY*3) { ImageSize[i]=m_pImage[m_pImagePlace]; m_pImagePlace++; countWidth++; if(countWidth==m_nWidth*3) { number++; m_pImagePlace=number*m_nWidth*3; } } } fwrite(ImageSize,m_nImage,1,fpw); fclose(fpo); fclose(fpw); numPicture = 2; level=200; //200表示几何变换 Invalidate(); } }
运行时需要注意一点:BMP图像在处理过程中可能会出现一些斜线,而平移(40,60)位移量时可能出现如下。他是因为BMP格式有个非常重要的规定,要求每一扫描的字节数据必须能被4整除,也就是Dword对齐(长度4字节),如果图像的一行字节数不能被4整除,就需要在每行末尾不起0达到标准。else //图像几何变换 if(level=200) { m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0, LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION); }
例如一行像素为97字节,我们就需要补3个字节吗,数值可以是0,但是我们在BMP格式的信息头里说明了其宽度,所以补齐后对我们没有影响,所以后面补若干个字节的0即可直到被4整除。
通过后面的图像缩放后,我从学做了一遍这个补齐的缩放。代码如下,能够实现完美平移。nice啊~
运行效果如下图所示,完美平移,其他算法遇到斜线问题类似补齐即可。void CImageProcessingView::OnJhbhPy() { if(numPicture==0) { AfxMessageBox("载入图片后才能空间平移!",MB_OK,0); return; } //定义采样对话框也是用来空间变换平移的坐标 CImagePYDlg dlg; 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); int num; //记录每行多余的图像素数个数 int sfSize; //补齐后的图像大小 //重点:图像的每行像素都必须是4的倍数:1*1的图像为 r g b 00H if(m_nWidth*3%4!=0) { num=(4-m_nWidth*3%4); sfSize=(m_nWidth*3+num)*m_nHeight; //每行多number个 } else { num=0; sfSize=m_nWidth*m_nHeight*3; } //注意:假如最后一行像素不足,我默认处理为完整的一行,不足补00H //总之处理后的图像总是m*n且为4倍数,每行都完整存在 /*更改文件头信息 定义临时文件头结构变量*/ BITMAPFILEHEADER bfhsf; BITMAPINFOHEADER bihsf; bfhsf=bfh; bihsf=bih; bfhsf.bfSize=sfSize+54; fwrite(&bfhsf,sizeof(BITMAPFILEHEADER),1,fpw); fwrite(&bihsf,sizeof(BITMAPINFOHEADER),1,fpw); fread(m_pImage,m_nImage,1,fpo); CString str; str.Format("补齐=%d",num); AfxMessageBox(str); /*临时变量存储的像素与sfSize相同 new和delete有效的进行动态内存的分配和释放*/ unsigned char *ImageSize; ImageSize=new unsigned char[sfSize]; int Place; //建立临时坐标 记录起始坐标(0,0)平移过来的位置 int m_pImagePlace; //原始图像平移为(0,0) 图像把它平移到Place位置 unsigned char black=0; //填充黑色='0' unsigned char other=0; //补码00H='\0' Place=dlg.m_yPY*(m_nWidth*3+num); //前m_yPY行都要填充为黑色 m_pImagePlace=0; //图像处事位置为(0,0),把该点像素平移过去 int countWidth=0; //记录每行的像素个数,满行时变回0 int number=0; //数字记录使用的像素行数,平移时使用 for(int i=0 ; i<sfSize ; i++ ) { /*第一部分:到平移后像素位置前面的所有像素点赋值为黑色*/ if(i<Place) { ImageSize[i]=black; //赋值为黑色 continue; } /*第二部分:平移区域的左边部分赋值为黑色*/ else if(i>=Place && countWidth<dlg.m_xPY*3) //RGB乘3 { ImageSize[i]=black; //赋值为黑色 countWidth++; continue; } /*第三部分:图像像素平移区域*/ else if(i>=Place && countWidth>=dlg.m_xPY*3) { ImageSize[i]=m_pImage[m_pImagePlace]; m_pImagePlace++; countWidth++; if(countWidth==m_nWidth*3) { if(num==0) { countWidth=0; number++; m_pImagePlace=number*m_nWidth*3; } else //num为补0 { for(int j=0;j<num;j++) { i++; ImageSize[i]=other; } countWidth=0; number++; m_pImagePlace=number*(m_nWidth*3+num); //重点:添加Num } } } } fwrite(ImageSize,sfSize,1,fpw); fclose(fpo); fclose(fpw); numPicture = 2; level=200; //200表示几何变换 Invalidate(); } }
二. 图像镜像
1.水平镜像翻转
其变换矩阵如下:
X=width-X0-1 (width为图像宽度)
Y=Y0
打开类向导,在CImageProcessingView中添加IDs为ID_JHBH_FZ,生成函数,代码如下:
运行效果如下图所示,其中还是存在一些小BUG,如前面的BMP图补0凑齐4整数倍宽度或颜色失帧。/* 几何变换 图像翻转:自己对这个功能比较感兴趣,做个图像反转 */ void CImageProcessingView::OnJhbhFz() { if(numPicture==0) { 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); /*new和delete有效的进行动态内存的分配和释放*/ unsigned char *ImageSize; ImageSize=new unsigned char[m_nImage]; int countWidth=0; //记录每行的像素个数,满行时变回0 int Place; //记录图像每行的位置,便于图像反转 int number=0; //数字记录使用的像素行数 Place=m_nWidth*3-1; //翻转矩阵: y=y0 x=width-x0-1 for(int i=0 ; i<m_nImage ; i++ ) { if(countWidth==m_nWidth*3) { countWidth=0; } ImageSize[i]=m_pImage[Place]; //(0,0)赋值(0,width*3-1)像素 Place--; countWidth++; if(countWidth==m_nWidth*3) { number++; Place=number*m_nWidth*3-1; } } fwrite(ImageSize,m_nImage,1,fpw); fclose(fpo); fclose(fpw); numPicture = 2; level=200; 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)找到每行的第一个像素点。
运行结果如下图所示,第二张图颜色没有失帧或变灰,这完全可以怀疑在翻转过程中RGB像素编程BGR后导致的结果,最终实现了翻转图像,但灰度存在一定;所以如果改为RBG顺序不变化即可原图颜色显示。/* 几何变换 图像倒转 */ void CImageProcessingView::OnJhbhDz() { if(numPicture==0) { 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); /*new和delete有效的进行动态内存的分配和释放*/ unsigned char *ImageSize; ImageSize=new unsigned char[m_nImage]; int countWidth=0; //记录每行像素个数,满行时变回0 int Place; //每列位置 int number=0; //像素行数 Place=(m_nWidth*3)*(m_nHeight-1-1); //0行存储 //翻转矩阵: x=x0 y=height-y0-1 for(int i=0 ; i<m_nImage ; i++ ) { ImageSize[i]=m_pImage[Place]; //(0,0)赋值(0,0)像素 Place++; countWidth++; if(countWidth==m_nWidth*3) { countWidth=0; number++; Place=(m_nWidth*3)*(m_nHeight-number-1); } } fwrite(ImageSize,m_nImage,1,fpw); fclose(fpo); fclose(fpw); numPicture = 2; level=200; Invalidate(); }
三. 图像旋转
图像饶原点旋转顺时针theta角矩阵变换如下:注意BMP图像(0,0)左下角
写到这里真心觉得写底层的代码非常困难啊!尤其是以为像素转换二维像素,同时也觉得当时的自己算法部分还是很强大的,也感觉到如果采用GDI+操作像素矩阵Matrix或ColorMatrix是多么的方便,因为它定义好了X和Y向量,这就是为什么Android前面写的图像处理要容易得多。但是效率高~
好像利用GDI+旋转通过几句代码即可:
matrix.Rotate(15); //矩阵旋转15度
graph.SetTransform(&matrix);
graph.DrawImage(&image,points,3);
下面这部分代码是实现Android旋转的:参考我的博客
实现效果如下图所示://旋转图片 private void TurnPicture() { Matrix matrix = new Matrix(); turnRotate=turnRotate+15; //选择角度 饶(0,0)点选择 正数顺时针 负数逆时针 中心旋转 matrix.setRotate(turnRotate,bmp.getWidth()/2,bmp.getHeight()/2); Bitmap createBmp = Bitmap.createBitmap(bmp.getWidth(), bmp.getHeight(), bmp.getConfig()); Canvas canvas = new Canvas(createBmp); Paint paint = new Paint(); canvas.drawBitmap(bmp, matrix, paint); imageCreate.setBackgroundColor(Color.RED); imageCreate.setImageBitmap(createBmp); textview2.setVisibility(View.VISIBLE); }
言归正传,新建Dialog如下图所示,设置ID_DIALOG_XZ和变量:
再点击空白处创建CImageXZDlg类(旋转),它会自动生成.h和.cpp文件。打开类向导生成CImageXZDlg类的成员变量m_xzds(旋转度数),并设置其为int型(最大值360 最小值0)。
在类向导(Ctrl+W)选择类CImageProcessingView,为ID_JHBH_TXXZ(图像旋转)添加函数,同时添加头文件#include "ImageXZDlg.h"
运行效果如下图所示,中心旋转太难了!找到中心那个位置就不太容易,我做不下去了,fuck~同时旋转过程中,由于是饶左下角(0,0)实现,故有的角度会到界面外显示全黑。下图分别旋转15度和355度。/**********************************************************/ /* 几何变换:图片旋转 /* 先添加对话框:IDD_JHBH_TXXZ(图像旋转),创建新类CImageXZDlg /* 创建输入度数的:m_xzds Member variables 为int 0-360间 /**********************************************************/ void CImageProcessingView::OnJhbhTxxz() { if(numPicture==0) { AfxMessageBox("载入图片后才能空间旋转!",MB_OK,0); return; } //定义对话框并调用对话框 CImageXZDlg dlg; if( dlg.DoModal()==IDOK ) //显示对话框 { 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); /*new和delete有效的进行动态内存的分配和释放*/ unsigned char *ImageSize; ImageSize=new unsigned char[m_nImage]; int Place; //记录图像每行的位置,便于图像旋转 /*定义PA=3.14时使用的方法是arcsin(1.0/2)*6即为π*/ double PA; PA=asin(0.5)*6; /*把输入的0-360的正整数度数转换为角度,30度=π/6*/ double degree; degree=PA*dlg.m_xzds/180; //调用dlg.m_xzds(旋转度数) //对应的二维矩阵 注意图像矩阵从左下角开始处理 它最终要转换成一维存储 int X,Y; //图像变换前通过一维矩阵转换为二维 int XPlace,YPlace; //输出转换为的角度 CString str; str.Format("转换后的角度=%f",degree); AfxMessageBox(str); //图像旋转处理 for(int i=0 ; i<m_nImage ; i++ ) { //原图:一维矩阵转换为二维矩阵 X=(i/3)%m_nWidth; Y=(i/3)/m_nWidth; //注意错误:X=i/m_nHeight Y=i%m_nWidth; 只输出最后1/3 //图像旋转为:a(x,y)=x*cos-y*sin b(x,y)=x*sin+y*cos XPlace=(int)(X*cos(degree)-Y*sin(degree)); YPlace=(int)(X*sin(degree)+Y*cos(degree)); //在转换为一维图想输出 if( (XPlace>=0 && XPlace<=m_nWidth) && (YPlace>=0 && YPlace<=m_nHeight) ) { Place=YPlace*m_nWidth*3+XPlace*3; //在图像范围内赋值为该像素 if(Place+2<m_nImage) { ImageSize[i]=m_pImage[Place]; i++; ImageSize[i]=m_pImage[Place+1]; i++; ImageSize[i]=m_pImage[Place+2]; } //否则赋值为黑色 else { ImageSize[i]=0; i++; ImageSize[i]=0; i++; ImageSize[i]=0; } } //否则赋值为黑色 else { ImageSize[i]=0; i++; ImageSize[i]=0; i++; ImageSize[i]=0; } } fwrite(ImageSize,m_nImage,1,fpw); fclose(fpo); fclose(fpw); numPicture = 2; level=200; //几何变换 Invalidate(); } }
四. 图像缩放
图像缩放主要有两种方法:
1.最近邻插值:向后映射时,输出图像的灰度等于离它所映射位置最近的输入图像的灰度值。其中向前映射和向后映射如下:
对于向前映射每个输出图像的灰度要经过多次运算,对于向后映射,每个输出图像的灰度只经过一次运算。在实际应用中,更多的是采用向后映射法,其中根据四个相邻像素灰度值计算某个位置的像素灰度值即为灰度级插值。
2.双线性插值:四点确定一个平面函数,属于过约束问题。即单位正方形顶点已知,求正方形内任一点的f(x,y)值。
换个通熟的说法,如下图所示。采用最近邻插值法就是P(x,y)像素值采用四舍五入等于离它最近的输入图像像素值。分别计算它到四个顶点之间的距离,但是这样会造成图像的马赛克、锯齿等现象。而采用双线性插值法,主要通过该坐标周围的四个像素值,按照比例混合计算器近似值。比例混合的依据是离哪个像素近,哪个像素的比例越大。
下面是采用最近邻插值法的过程,注意BMP图缩放还需修改头文件信息。
第一步:在资源视图中添加“图像缩放”Dialog
第二步:点击空白处创建对话框的类CImageSFDlg,同时打开类向导为其添加成员变量m_sfbs(缩放倍数),其为int型在0-200之间。
第三步:打开类向导为其添加成员函数void CImageProcessingView::OnJhbhSf() 并实现缩放。同时添加头文件#include "ImageSFDlg.h"。
第四步:因为图像缩放修改BMP图片头信息,所以需要修改ShowBitmap中的显示第二张图片时的部分代码。如下所示:添加变量flagSF、m_nDrawWidthSF和m_nDrawHeightSF。/*******************************************************************/ /* ID_JHBH_SF: 几何运算-缩放-最近邻插值算法 /* 算法思想:输出图像的灰度等于离它所映射位置最近的输入图像的灰度值 /* 先计算出放大缩小后的长宽,根据它计算找原图中的点灰度,四舍五入 /*******************************************************************/ void CImageProcessingView::OnJhbhSf() { if(numPicture==0) { AfxMessageBox("载入图片后才能几何缩放图像!",MB_OK,0); return; } CImageSFDlg dlg; //定义缩放对话框 if( dlg.DoModal()==IDOK ) { //采样坐标最初为图片的自身像素 m_sfbs(缩放倍数) if( dlg.m_sfbs==0 ) { AfxMessageBox("输入图片缩放倍数不能为0!",MB_OK,0); return; } FILE *fpo = fopen(BmpName,"rb"); FILE *fpw = fopen(BmpNameLin,"wb+"); fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo); fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo); /*先求缩放后的长宽*/ int sfWidth,sfHeight; //缩放后的长宽 int sfSize; //缩放后的图像大小 sfWidth=(int)(m_nWidth*(dlg.m_sfbs*1.0)/100); //24位图像RGB必须是3倍数 循环读取时为RGB sfHeight=(int)(m_nHeight*(dlg.m_sfbs*1.0)/100); int number; //记录每行多余的图像素数个数 //重点:图像的每行像素都必须是4的倍数:1*1的图像为 r g b 00H if(sfWidth*3%4!=0) { number=(4-sfWidth*3%4); sfSize=(sfWidth*3+(4-sfWidth*3%4))*sfHeight; } else { number=0; sfSize=sfWidth*sfHeight*3; } //注意:假如最后一行像素不足,我默认处理为完整的一行,不足补00H //总之处理后的图像总是m*n且为4倍数,每行都完整存在 /*更改文件头信息 定义临时文件头结构变量*/ BITMAPFILEHEADER bfhsf; BITMAPINFOHEADER bihsf; //缩放(sf) bfhsf=bfh; bihsf=bih; bfhsf.bfSize=sfSize+54; bihsf.biWidth=sfWidth; bihsf.biHeight=sfHeight; //显示部分m_nDrawWidth<650显示原图,否则显示 flagSF=1; //图像缩放为1标识变量 m_nDrawWidthSF=sfWidth; m_nDrawHeightSF=sfHeight; fwrite(&bfhsf,sizeof(BITMAPFILEHEADER),1,fpw); fwrite(&bihsf,sizeof(BITMAPINFOHEADER),1,fpw); fread(m_pImage,m_nImage,1,fpo); unsigned char red,green,blue; unsigned char other=0; //补码00H='\0' int placeX; //记录在原图中的第几行的位置 int placeY; //记录在原图中的位置(x,y) int placeBH; //记录变换后在变换图中的位置 /*new和delete有效的进行动态内存的分配和释放*/ unsigned char *ImageSize; ImageSize=new unsigned char[sfSize]; /*读取文件像素信息 缩放注意:1.找最近灰度 2.四舍五入法(算法+0.5)*/ for(int i=0; i<sfHeight ; i++ ) //行 { placeX=(int)(i/(dlg.m_sfbs*1.0/100)+0.5)*bih.biWidth*3; for(int j=0; j<sfWidth ; j++ ) //列 { red=green=blue=0; //放大倍数为(dlg.m_sfbs*1.0/100) placeY=placeX+(int)(j/(dlg.m_sfbs*1.0/100)+0.5)*3; //重点是:number*i补充00H,如果是numer图像会被切成2块 placeBH=(i*sfWidth*3+number*i)+j*3; if(placeY+2<m_nImage) { ImageSize[placeBH]=m_pImage[placeY]; ImageSize[placeBH+1]=m_pImage[placeY+1]; ImageSize[placeBH+2]=m_pImage[placeY+2]; } else { ImageSize[placeBH]=0; ImageSize[placeBH+1]=0; ImageSize[placeBH+2]=0; } } } fwrite(ImageSize,sfSize,1,fpw); fclose(fpo); fclose(fpw); numPicture = 2; level=200; Invalidate(); } }
运行效果如下图所示,采用最近邻插值法缩放大了会出现失帧。/*定义显示图像缩放时的长宽与标记*/ 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); }
但是同时当图片缩小是总是报错,图片缩放确实有点难,因为像素需要补齐4整数倍,同时需要修改消息头,同时像素矩阵的变换都非常复杂。
最后还是希望文章对你有所帮助,如果文章有不足或错误之处,请海涵。自己给自己点个赞,挺不容易的,但还会继续写完~(By:Eastmount 2015-06-04 下午5点 http://blog.csdn.net/eastmount/)
-
衣柜移门 创盈吊趟门移门设计优化管理系统 v2015
2020-10-23 02:06:22创盈移门下料优化管理系统涵括了所有门窗系列和门窗款式,如吊趟门、推拉门、平开门、拼格门、艺术门、小折叠、大折叠、封边门、推拉窗、平开窗、悬开窗、组合窗等,还自带了 -
openGL-读取off、stl、obj文件并旋转平移缩放操作
2018-05-05 10:59:08最近图形学实验要求做一个off、obj文件并旋转平移缩放操作的练习,我顺手把stl(二进制)也做了一下。 支持操作:旋转、平移、缩放、改变散射光、改变光源位置。而且可以在显示点、线、面三者切换 注意我没有...说明
很多朋友反馈,加载完毕后是一片空白~不用担心。我认为你很大程度上已经配置成功。这时候你可以试一试点击数字1、2、3进行查看。
此外我建议你使用点面数目规模较小的模型例如bunny等而不是使用kitten 这类点面数目较多的模型。综述
最近图形学实验要求做一个off、obj文件并旋转平移缩放操作的练习,我顺手把stl(二进制)也做了一下。
**支持操作:**旋转、平移、缩放、改变散射光、改变光源位置。而且可以在显示点、线、面三者切换
注意我没有使用半边结构,暴力存储的。文件读取说明
不多说废话,代码写的很清楚。注意off和obj文件的格式很相似。然而stl二进制读取需要注意我是四个字节读三次来搞的。
操作说明
1、2、3分别控制显示和关闭 点、线、面 鼠标左键旋转 鼠标右键缩放 按下‘z’后,会切换到平移操作,鼠标左键平移 (下面是一些额外的功能,你可以选择去除) ‘4’‘5’‘6’‘7’控制两个光源的强度和位置信息 ‘q’‘w’‘e’‘r’分别控制散射情况(增加) ‘a’‘s’‘d’‘f’控制散射情况(降低)
编译环境
xcode
不过你不必担心,你只需要改变一下glut的include路径即可展示效果
点展示
线展示
面展示
改变散射颜色
代码
追求优秀是一种情怀
// //计算机图形学实验2 // 作者:山东大学计算机基地班frankdura // Standard include files #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <math.h> #include <assert.h> #include<string> #include<vector> #include<iostream> #include <fstream> #include <GLUT/GLUT.h> using namespace std; //只使用glut足够 //支持平移旋转缩放 //支持调整材质色彩和环境色彩 //支持off/obj/stl int if_face=0, if_line=0, if_point=0; //判断三种几何信息是否展示 static GLfloat my_set_material[] = { 1.0, 1.0, 0.1, 0.8}; int if_control_move = 0 ; static char *name2 = "/Users/frankdura/Desktop/CG_playground/b.stl"; string fname = "/Users/frankdura/Desktop/CG_playground/ddd.obj"; static char *filename = "/Users/frankdura/Desktop/CG_playground/bunny.off"; //展示目录清单: /* obj:ddd.obj bunny.obj off:bunny.off stl:b.stl */ //设置光源的位置 static GLfloat light0_position[] = { 4.0, 4.0, 4.0, 0.0 }; static GLfloat light1_position[] = { -3.0, -3.0, -3.0, 0.0 }; typedef struct Vertex { //定义三维图形的 //用于face结构体中 float x, y, z; } Vertex; typedef struct Face { //多边形(三角形)面的结构体 Face(void) : vert_number(0), verts(0) {}; int vert_number; //记录顶点的个数 Vertex **verts; //这是一个面的所有 顶点数组(含有坐标) float normal[3]; //记录点的法向量,分别是x,y,z三个方向 //注意点的法向量通过顶点的信息计算得到! //对于obj模型如果我们已经得到了法线的信息 //那么就直接拿来用就好! } Face; typedef struct myMesh { //自定义mesh的结构体 myMesh(void) : vert_number(0), verts(0), face_number(0), faces(0) {}; //自定义构造器 int vert_number; //总的顶点个数 Vertex *verts; //定点数组 int face_number; //面的数目 Face *faces; vector<Vertex>point; } myMesh; //输入指定的文件名 static int GLUTwindow = 0; static int GLUTwindow_height = 800; //设置窗口的大小 static int GLUTwindow_width = 800; static int GLUTmouse[2] = { 0, 0 }; static int GLUTbutton[3] = { 0, 0, 0 }; static int GLUTarrows[4] = { 0, 0, 0, 0 }; static int GLUTmodifiers = 0; static int scaling = 0; static int translating = 0; static int rotating = 0; static float scale = 1.0; static float center[3] = { 0.0, 0.0, 0.0 }; static float rotation[3] = { 0.0, 0.0, 0.0 }; static float translation[3] = { 0.0, 0.0, -4.0 }; static myMesh *mesh = NULL; void get_normal( Face& face){ //计算面法线! //计算面的法线 //通过公式计算: face.normal[0] = face.normal[1] = face.normal[2] = 0; Vertex *v1 = face.verts[face.vert_number-1]; for (int i = 0; i < face.vert_number; i++) { Vertex *v2 = face.verts[i]; //新建所有的点 face.normal[0] += (v1->y - v2->y) * (v1->z + v2->z); face.normal[1] += (v1->z - v2->z) * (v1->x + v2->x); face.normal[2] += (v1->x - v2->x) * (v1->y + v2->y); //首先完成叉乘的工作 v1 = v2; } //计算归一化法线 float squared_normal_length = 0.0; squared_normal_length += face.normal[0]*face.normal[0]; squared_normal_length += face.normal[1]*face.normal[1]; squared_normal_length += face.normal[2]*face.normal[2]; float normal_length = sqrt(squared_normal_length); //得到归一化长度 if (normal_length > 1.0E-6) { face.normal[0] /= normal_length; face.normal[1] /= normal_length; face.normal[2] /= normal_length; } //然后完成归一化任务 } vector<string> split(const string &str,const string &pattern) { //进行字符串的切割 //const char* convert to char* char * strc = new char[strlen(str.c_str())+1]; strcpy(strc, str.c_str()); vector<string> resultVec; char* tmpStr = strtok(strc, pattern.c_str()); while (tmpStr != NULL) { resultVec.push_back(string(tmpStr)); tmpStr = strtok(NULL, pattern.c_str()); } delete[] strc; return resultVec; } myMesh * ReadASCII(const char *cfilename); myMesh * ReadBinary(const char *cfilename); myMesh * ReadSTLFile(const char *cfilename) { //只处理三角形足够了! if (cfilename == NULL) return 0; std::ifstream in(cfilename, std::ifstream::in); if (!in) return 0; std::string headStr; getline(in, headStr, ' '); in.close(); if (headStr.empty()) return 0; if (headStr[0] == 's') return ReadASCII(cfilename); else return ReadBinary(cfilename); } myMesh * ReadASCII(const char *cfilename) { std::vector<float> coorX; std::vector<float> coorY; std::vector<float> coorZ; int i = 0, j = 0, cnt = 0, pCnt = 4; char a[100]; char str[100]; double x = 0, y = 0, z = 0; std::ifstream in(cfilename, std::ifstream::in); if (!in) return 0; do { i = 0; cnt = 0; in.getline(a, 100, '\n'); while (a[i] != '\0') { if (!islower((int)a[i]) && !isupper((int)a[i]) && a[i] != ' ') break; cnt++; i++; } while (a[cnt] != '\0') { str[j] = a[cnt]; cnt++; j++; } str[j] = '\0'; j = 0; if (sscanf(str, "%lf%lf%lf", &x, &y, &z) == 3) { coorX.push_back(x); coorY.push_back(y); coorZ.push_back(z); } pCnt++; } while (!in.eof()); return 0; } myMesh * ReadBinary(const char *cfilename) { std::vector<GLfloat> coorX; std::vector<GLfloat> coorY; std::vector<GLfloat> coorZ; char str[80]; std::ifstream in(cfilename, std::ifstream::in | std::ifstream::binary); if (!in) return 0; //首先使用二进制读入文件 myMesh *meshs = new myMesh(); //建立我们的mesh类 in.read(str, 80); int triangles; //triangles 记录了三角面的数目 in.read((char*)&triangles, sizeof(int)); if (triangles == 0) return 0; for (int i = 0; i < triangles; i++) { //开始处理三角片 所以面的数量计数器++ float coorXYZ[12]; in.read((char*)coorXYZ, 12 * sizeof(float)); for (int j = 1; j < 4; j++) { //三个四字节信息 //分别处理每个坐标点 coorX.push_back(coorXYZ[j * 3]); coorY.push_back(coorXYZ[j * 3 + 1]); coorZ.push_back(coorXYZ[j * 3 + 2]); //将点的信息压入暂存数组 //由于stl文件特性只需要保持三个一组进行最后的划分即可! } in.read((char*)coorXYZ, 2); } in.close(); //向结构体进行转换 int vert_number = coorX.size(); int face_number = triangles; meshs->verts = new Vertex [vert_number+1]; assert(meshs->verts); //处理点的信息 for(int i = 0 ; i < vert_number;i++) { Vertex& vert = meshs->verts[meshs->vert_number++]; vert.x =coorX[i]; vert.y =coorY[i]; vert.z =coorZ[i]; } //处理面的信息 meshs->faces = new Face [face_number]; assert(meshs->faces); int index=0; for(int i = 0; i < face_number;i++) { Face fa ; fa.vert_number = 3; //这里直接设置为定3即可! STL三角片决定的! fa.verts =new Vertex* [fa.vert_number]; for (int j = 0 ; j < 3; j++) { fa.verts[j] = &meshs->verts[index++]; } get_normal(fa); meshs->faces[meshs->face_number++] = fa; //首先分配第一维数组 } return meshs; } //读取obj文件 myMesh * ReaderOBj(string fname2) { string line; fstream f; f.open(fname2, ios::in); if (!f.is_open()) { cout << "文件打开出错" << endl; } vector<vector<GLfloat>> vset; vector<vector<GLint>> fset; int v_counter = 1; int f_counter = 1; while (!f.eof()) { getline(f, line);//拿到obj文件中一行,作为一个字符串 vector<string>parameters; string tailMark = " "; string ans = ""; line = line.append(tailMark); if(line[0]!='v'&&line[0]!='f'){ continue; } for (int i = 0; i < line.length(); i++) { char ch = line[i]; if (ch != ' ') { ans += ch; } else { if(ans!=""){ parameters.push_back(ans); //取出字符串中的元素,以空格切分 ans = "";} } } cout << endl; if (parameters[0] == "v") { //如果是顶点的话 vector<GLfloat>Point; v_counter++; // cout <<atof( parameters[1].c_str()) << "--" << atof( parameters[2].c_str()) <<" -- " << atof( parameters[3].c_str()); Point.push_back(atof( parameters[1].c_str())); Point.push_back(atof( parameters[2].c_str())); Point.push_back(atof( parameters[3].c_str())); vset.push_back(Point); } else if (parameters[0] == "f") { //如果是面的话,存放顶点的索引 vector<GLint>vIndexSets; //临时存放点的集合 for (int i = 1; i < 4; i++) { string x = parameters[i]; string ans = ""; for (int j = 0; j < x.length(); j++) { //跳过‘/’ char ch = x[j]; if (ch != '/') { ans += ch; } else { break; } } vector<string >res = split(ans,"/"); int index = atof(res[0].c_str()); index--;//因为顶点索引在obj文件中是从1开始的,而我们存放的顶点vector是从0开始的,因此要减1 vIndexSets.push_back(index); } fset.push_back(vIndexSets); } } f.close(); myMesh *meshs = new myMesh(); //建立我们的mesh类 // cout << fset.size() << endl; // cout << vset.size() << endl; //向结构体进行转换 int vert_number = vset.size(); int face_number = fset.size(); meshs->verts = new Vertex [vert_number+1]; assert(meshs->verts); //处理mesh的信息 //----------------处理点的信息 -------------- for(int i = 0 ; i < vset.size();i++) { Vertex& vert = meshs->verts[meshs->vert_number++]; vert.x = vset[i][0]; vert.y = vset[i][1]; vert.z = vset[i][2]; } //----------------处理面信息 -------------- meshs->faces = new Face [face_number]; assert(meshs->faces); for(int i = 0; i < fset.size();i++) { Face a ; a.vert_number = fset[i].size(); a.verts =new Vertex* [fset[i].size()]; for (int j = 0 ; j < fset[i].size(); j++) { a.verts[j] = &meshs->verts[fset[i][j]]; } get_normal(a); meshs->faces[meshs->face_number++] = a; //首先分配第一维数组 } return meshs; } //读取off文件 myMesh * ReadOffFile(const char *filename) { /* 函数说明: 对文件进行读取,读取的是off文件 */ int i; FILE *fp; //开始读入文件 if (!(fp = fopen(filename, "r"))) { cout << "无法打开文件" << endl; return 0; } myMesh *mesh = new myMesh(); //建立我们的mesh类 //进行读入文件的操作 int vert_number = 0; //记录顶点个数 int face_number = 0; //记录面的个数 int line_number = 0; //记录边的个数 int line_count = 0; //这个是我读入了几行 char buffer[1024]; while (fgets(buffer, 1023, fp)) { line_count++; char *bufferp = buffer; while (isspace(*bufferp)) bufferp++; if (*bufferp == '#') continue; if (*bufferp == '\0') continue; if (vert_number == 0) { if (!strstr(bufferp, "OFF")) { if ((sscanf(bufferp, "%d%d%d", &vert_number, &face_number, &line_number) != 3) || (vert_number == 0)) { cout << "存在语法错误!" << endl; fclose(fp); return NULL; } //存储顶点的信息 cout << "aa" << vert_number << "--" << face_number << endl; mesh->verts = new Vertex [vert_number]; assert(mesh->verts); //存储面的信息 mesh->faces = new Face [face_number]; assert(mesh->faces); //如果头文件信息合适那么开辟内存空间 } } else if (mesh->vert_number < vert_number) { Vertex& vert = mesh->verts[mesh->vert_number++]; if (sscanf(bufferp, "%f%f%f", &(vert.x), &(vert.y), &(vert.z)) != 3) { cout << "点的信息中,数据量不足(3个)" << endl; fclose(fp); return NULL; } } else if (mesh->face_number < face_number) { Face& face = mesh->faces[mesh->face_number++]; //新建一个face对象 bufferp = strtok(bufferp, " \t"); if (bufferp) face.vert_number = atoi(bufferp); else { fclose(fp); return NULL; } face.verts = new Vertex *[face.vert_number]; //建立面的数组 assert(face.verts); for (i = 0; i < face.vert_number; i++) { bufferp = strtok(NULL, " \t"); if (bufferp) face.verts[i] = &(mesh->verts[atoi(bufferp)]); else { fprintf(stderr, "Syntax error with face on line %d in file %s\n", line_count, filename); fclose(fp); return NULL; } } get_normal(face); } else { cout << "格式存在错误!" << endl; break; } } //判断实际的 面 的数目是否和要求数目一样! if (face_number != mesh->face_number) { cout << "面的数目与实际不符"<<endl; } fclose(fp); return mesh; } void draw_faces(){ for (int i = 0; i < mesh->face_number; i++) { //注意我们的操作都是 Face& face = mesh->faces[i]; glBegin(GL_POLYGON); //绘制多边形即可! //在绘制面的过程中载入我们已经计算好的法线量信息 glNormal3fv(face.normal); //在绘制面的时候同时载入法向量信息 for (int j = 0; j < face.vert_number; j++) { Vertex *vert = face.verts[j]; glVertex3f(vert->x, vert->y, vert->z); } glEnd(); } } void draw_points(){ //下面绘制点的信息 //依次将面的信息点进行遍历 glColor3f(0.0, 1.0, 0.0); glPointSize(2); glBegin(GL_POINTS); for (int j = 0 ; j < mesh->vert_number; j++) { glVertex3f(mesh->verts[j].x, mesh->verts[j].y,mesh->verts[j].z); } glEnd(); // for (int i = 0; i < mesh->face_number; i++) { // Face& face = mesh->faces[i]; // glColor3f(0.0, 1.0, 0.0); // glPointSize(1); // glBegin(GL_POINTS); // // for (int j = 0; j < face.vert_number; j++) { // Vertex *vert = face.verts[j]; cout << vert->x << " " << vert->y <<" "<<vert ->z << endl; // glVertex3f(vert->x, vert->y, vert->z); // } // glEnd(); // } } void draw_lines(){ double temp_x,temp_y,temp_z; for (int i = 0; i < mesh->face_number; i++) { Face& face = mesh->faces[i]; glColor3f(0, 0, 1); glBegin(GL_LINES); for (int j = 0; j < face.vert_number; j++) { Vertex *vert = face.verts[j]; if(j==0){ temp_x = vert->x; temp_y = vert->y; temp_z = vert->z; continue; } glVertex3f(temp_x, temp_y, temp_z); glVertex3f(vert->x, vert->y, vert->z); temp_x = vert->x; temp_y = vert->y; temp_z = vert->z; } glEnd(); } } void GLUTRedraw(void) { //进行空间的重绘 glLoadIdentity(); glScalef(scale, scale, scale); glTranslatef(translation[0], translation[1], 0.0); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(45.0, (GLfloat) GLUTwindow_width /(GLfloat) GLUTwindow_height, 0.1, 100.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(translation[0], translation[1], translation[2]); glScalef(scale, scale, scale); //刷新放缩的大小 glRotatef(rotation[0], 1.0, 0.0, 0.0); glRotatef(rotation[1], 0.0, 1.0, 0.0); //控制不同角度 glRotatef(rotation[2], 0.0, 0.0, 1.0); glTranslatef(-center[0], -center[1], -center[2]); //改变旋转中心 glClearColor(1.0, 1.0, 1.0, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //设置光照 //载入不同光源的位置 glLightfv(GL_LIGHT0, GL_POSITION, light0_position); glLightfv(GL_LIGHT1, GL_POSITION, light1_position); //定义材料信息 //这里可以调整环境颜色和散射颜色数组 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, my_set_material); // 下面开始绘制表面 if(if_face==1) draw_faces(); if(if_line==1) draw_lines(); if(if_point==1) draw_points(); glutSwapBuffers(); } void GLUTResize(int w, int h) { glViewport(0, 0, w, h); //当用户拖拽之后用获取的高度和宽度信息去更新全局变量 GLUTwindow_width = w; GLUTwindow_height = h; glutPostRedisplay(); } void GLUTMotion(int x, int y) { //通过鼠标事件控制所调用的活动类型! //真正的鼠标控制事件的执行者 y = GLUTwindow_height - y; if (rotating) { //控制旋转的信号 rotation[0] += -0.5 * (y - GLUTmouse[1]); rotation[2] += 0.5 * (x - GLUTmouse[0]); //rotation[1] += 0.5 * (x - GLUTmouse[0]); //这样可以面向你进行旋转 //在重绘的时候就可以了 } else if (scaling) { // 控制缩放信号 //GLUTmouse存储了之前记录的点信息 scale *= exp(2.0 * (float)( (x- GLUTmouse[0])) / (float) GLUTwindow_width); //如果想调成按照y方向控制可以: // scale *= exp(2.0 * (float)( (y- GLUTmouse[1])) / (float) GLUTwindow_width); } else if (translating) { // 控制平移信号 translation[0] += 2.0 * (float) (x - GLUTmouse[0]) / (float) GLUTwindow_width; translation[1] += 2.0 * (float) (y - GLUTmouse[1]) / (float) GLUTwindow_height; } //我们在拖拽旋转的过程中需要设置定点中心 GLUTmouse[0] = x; GLUTmouse[1] = y; //刷新定点 } void GLUTMouse(int button, int state, int x, int y) { cout << "控制键的情况: " << if_control_move << endl; y = GLUTwindow_height - y; int kind = 0; if(button == GLUT_LEFT_BUTTON&&if_control_move){ translating = 1; cout << "double" << endl; kind=2; rotating=0; scaling = 0; }else{if(button == GLUT_LEFT_BUTTON&&button != GLUT_RIGHT_BUTTON){ kind = 0; rotating =1; scaling = 0; translating = 0; }else if(button != GLUT_LEFT_BUTTON&&button == GLUT_RIGHT_BUTTON){ kind = 1; scaling = 1; rotating =0; translating = 0; }} if (rotating || scaling || translating) glutIdleFunc(GLUTRedraw); else glutIdleFunc(0); cout << "此时的B 选择的操作:"<< kind<< endl; GLUTbutton[kind] = (state == GLUT_DOWN) ? 1 : 0; GLUTmodifiers = glutGetModifiers(); GLUTmouse[0] = x; GLUTmouse[1] = y; } void GLUTSpecial(int key, int x, int y) { y = GLUTwindow_height - y; //记录当下鼠标点击的位置 GLUTmouse[0] = x; GLUTmouse[1] = y; // Remember modifiers GLUTmodifiers = glutGetModifiers(); glutPostRedisplay(); } void GLUTKeyboard(unsigned char key, int x, int y) { // Process keyboard button event //处理鼠标事件 switch (key) { case '1': cout << "打开/关闭点的信息" << endl; if_point = 1 - if_point; break; case '2': cout << "打开/关闭线的信息" << endl; if_line = 1 - if_line; break; case '3': cout << "打开/关闭面的信息" << endl; if_face = 1 - if_face; break; //调节表面的材质的信息 case 'z': if_control_move = 1 - if_control_move; break; case 'q': my_set_material[0] += 0.1; break; case 'w': my_set_material[1] += 0.1; break; case 'e': my_set_material[2] += 0.1; break; case 'r': my_set_material[3] += 0.1; break; case 'a': my_set_material[0] -= 0.1; break; case 's': my_set_material[1] -= 0.1; break; case 'd': my_set_material[2] -= 0.1; break; case 'f': my_set_material[3] -= 0.1; break; case '4': for(int i = 0 ; i < 4 ;i ++){ light0_position [i]+=0.01; } break; case '5': for(int i = 0 ; i < 4 ;i ++){ light0_position [i]-=0.01; if(light0_position[i]<=0) light0_position[i] = 0; } break; case '6': for(int i = 0 ; i < 4 ;i ++){ cout <<light1_position [i] << " ~~ "<< endl; light1_position [i]+=0.01; } break; case '7': for(int i = 0 ; i < 4 ;i ++){ light1_position [i]-=0.01; cout <<light1_position [i] << " ~~ "<< endl; if(light1_position[i]<=0) light1_position[i] = 0; } break; } // Remember mouse position GLUTmouse[0] = x; GLUTmouse[1] = GLUTwindow_height - y; // Remember modifiers GLUTmodifiers = glutGetModifiers(); } void GLUTInit(int *argc, char **argv) { // Open window glutInit(argc, argv); glutInitWindowPosition(100, 100); glutInitWindowSize(GLUTwindow_width, GLUTwindow_height); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); // | GLUT_STENCIL GLUTwindow = glutCreateWindow("powerful_reader~"); glutReshapeFunc(GLUTResize); //设置重绘信息 glutDisplayFunc(GLUTRedraw); //注册键盘事件 glutKeyboardFunc(GLUTKeyboard); glutSpecialFunc(GLUTSpecial); glutMouseFunc(GLUTMouse); glutMotionFunc(GLUTMotion); glutIdleFunc(0); // 设置光照信息 static GLfloat lmodel_ambient[] = { 0.2, 0.2, 0.2, 1.0 }; glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient); glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE); static GLfloat light0_diffuse[] = { 1.0, 1.0, 1.0, 1.0 }; //设置满散射 glLightfv(GL_LIGHT0, GL_DIFFUSE, light0_diffuse); glEnable(GL_LIGHT0); static GLfloat light1_diffuse[] = { 0.5, 0.5, 0.5, 1.0 }; glLightfv(GL_LIGHT1, GL_DIFFUSE, light1_diffuse); glEnable(GL_LIGHT1); glEnable(GL_NORMALIZE); glEnable(GL_LIGHTING); glEnable(GL_DEPTH_TEST); } void GLUTMainLoop(void) { float bbox[2][3] = { { 1.0E30F, 1.0E30F, 1.0E30F }, { -1.0E30F, -1.0E30F, -1.0E30F } }; for (int i = 0; i < mesh->vert_number; i++) { Vertex& vert = mesh->verts[i]; if (vert.x < bbox[0][0]) bbox[0][0] = vert.x; else if (vert.x > bbox[1][0]) bbox[1][0] = vert.x; if (vert.y < bbox[0][1]) bbox[0][1] = vert.y; else if (vert.y > bbox[1][1]) bbox[1][1] = vert.y; if (vert.z < bbox[0][2]) bbox[0][2] = vert.z; else if (vert.z > bbox[1][2]) bbox[1][2] = vert.z; } // Setup initial viewing scale float dx = bbox[1][0] - bbox[0][0]; float dy = bbox[1][1] - bbox[0][1]; float dz = bbox[1][2] - bbox[0][2]; scale = 2.0 / sqrt(dx*dx + dy*dy + dz*dz); // Setup initial viewing center center[0] = 0.5 * (bbox[1][0] + bbox[0][0]); center[1] = 0.5 * (bbox[1][1] + bbox[0][1]); center[2] = 0.5 * (bbox[1][2] + bbox[0][2]); glutMainLoop(); //计算并更新视角边框以及中心 } int main(int argc, char **argv) { GLUTInit(&argc, argv); mesh = ReaderOBj(fname); //objreader; //obj和off很相似 // mesh = ReadSTLFile(name2); // mesh = ReadOffFile(filename); GLUTMainLoop(); return 0; }
-
【OpenGL学习笔记⑦】——键盘控制镜头的平移【3D正方体 透视投影 观察矩阵 对LookAt的理解】
2021-10-23 22:59:353.1 前后左右上下移动的实现 3.2 在顶点着色器里的处理 四、Camera 类 五、完整代码 六、参考附录: 移动的镜头 上一篇文章链接:【OpenGL学习笔记⑥】——3D变换【旋转的正方体 ⭐实现地月系统⭐ 旋转+平移+缩放】.... -
MFC+OPENGL配置+显示三维图形实现 旋转平移缩放+光照效果[对话框篇]
2019-11-10 10:47:58MFC+OPENGL配置+显示三维图形实现 旋转平移缩放+光照效果[对话框篇] 一、开发环境说明 操作系统:windows 开发软件:VS2017 编程语言:基于MFC对话框下的opengl 最终效果图: 二、配置操作 配置opengl... -
unity Camera相机组件 和 cinemachine摄像机组件 功能实现 镜头角度旋转、平移、缩放、位置重置、自动避障...
2021-01-11 11:17:09} 四、平移视角 所谓平移视角,就是在当前摄像机的视角下,对摄像机进行相对平移操作。 Camera组件实现 public float moveSpeedXY = 10f; // 以前后移动速度 public float moveSpeedZ = 60f; void Update() { // ... -
对pandas中时间窗函数rolling的使用详解
2021-03-07 01:16:27函数原型和参数说明 dataframe.rolling(window, min_periods=none, freq=none, center=false, win_type=none, on=none, axis=0, closed=none) window:表示时间窗的大小,注意有两种形式(int or offset)。... -
编程实现多边形的平移、比例(缩放)、旋转、对称和错切等二维仿射变换(大有门道)
2020-04-13 23:36:16平移: (1)数学原理: (2)实现: //平移(用键盘的上下左右键控制) void translate(GLfloat Tx,GLfloat Ty) { for(int i=0;i;i++){ square[i][0]+=Tx; square[i][1]+=Ty; } } 2.比例(缩放): (1)原理... -
控制台GLUT+OPENGL配置+显示三维图形实现 旋转 平移 滚轮缩放+光照效果
2019-11-15 07:17:09A 控制台GLUT+OPENGL配置+显示三维图形实现 旋转 平移 滚轮缩放+光照效果 一、开发环境说明 操作系统:windows 开发软件:Visual Studio 2017 编程语言:基于控制台下的opengl 二、配置操作说明 配置opengl,并搭建... -
Photoshop 2022下缩放和平移图像
2022-02-13 11:51:56小编将为您介绍 Photoshop 工作区,并向您展示缩放和平移图像的使用方法。 了解如何缩放和平移图像 更改图像的视图。 在 Photoshop 中处理图像时,经常需要缩放和平移图像,接下来我们学习如何使用缩放和平移控件... -
C#通过Windows API捕获窗,获取窗口文本(FindWindow、GetWindowText),附录:Windows窗口消息大全、...
2021-01-21 15:03:56文章目录一、前言二、使用Spy++工具分析窗口三、C#通过Windows API捕获窗口,获取窗口文本四、附录:Windows窗口消息 一、前言 项目是Unity开发的,上架了QQ游戏大厅,需要兼容XP系统。 QQ游戏大厅启动游戏的流程是... -
C#图像处理初学之平移和镜像
2017-02-09 21:21:01//得到两个方向的图像平移量 int x = Convert.ToInt32(traForm.GetXOffset); int y = Convert.ToInt32(traForm.GetYOffset); byte[] tempArray = new byte[bytes]; //临时数组初始化为白色255像素 for(int i=0... -
常用窗函数的特点
2020-12-22 19:54:072.汉宁窗汉宁窗的频谱时间上是由三个矩形窗经相互平移叠加二乘,汉宁窗的第一旁瓣幅值是主瓣的0.027%,这样旁瓣可以最大限度地互相抵消,从而达到加强主瓣的作用,使泄漏得到较为有效的抑制。采用汉... -
Opencv图像识别从零到精通(7)----图像平移、旋转、镜像
2016-07-16 16:49:23根据vc6.0c++的学习经验,如果可以很好的自己编程,让图像进行平移旋转这些操作,那么就好像能够清楚的看见图像的内部结构,当然这里你怎么访问像素,这个可以自己选一种适合的,最多的是ptr指针,at也是挺多的。... -
使用opengl 和 openmesh 读取obj文件,显示3d模型,并可以进行旋转、平移、缩放
2019-06-06 20:56:57本博客是使用opengl 和 openmesh 读取obj文件,显示3d模型,并可以进行旋转、平移、缩放,并加入了环境光 一、操作 鼠标控制物体旋转移动,滚轮缩放,上下左右键可以控制模型的移动 F1,F2,F3,F4,F5,F6,F7,F8可以... -
python + pyqt +opencv 有界面,对lable中的图片进行图像旋转,向右平移,向下平移,二值化,灰度,边缘...
2019-08-20 11:35:00对lable中的图片进行图像旋转,向右平移,向下平移,二值化,灰度,边缘检测 import cv2 import numpy as np import sys from PyQt5 import QtWidgets, QtCore, QtGui from PyQt5.QtGui import * from PyQt5.... -
-
为什么这么多人说 IDEA 比 Eclipse 更好?
2019-10-15 08:47:00点击上方“黄小斜”,选择“置顶或者星标”一起成为更好的自己!作者:彭博来源:http://1t.click/asZu# 争论有一些没有唯一正确答案的“永恒”的问题,... -
超硬核十万字!全网最全 数据结构 代码,随便秒杀老师/面试官,我说的
2021-04-11 01:11:23(数组中保存起始位置就好了,结束位置一定是最后) AC自动机 数组缺失 二叉树遍历 前序 中序 后序 进一步思考 二叉树序列化/反序列化 先序中序后序两两结合重建二叉树 先序遍历 中序遍历 后序遍历 层次遍历 输入某... -
关于参考图管理神器 PureRef 的一些快捷键
2021-01-28 14:50:55框选窗口边鼠标左键 调整窗口大小鼠标中键 或 按住Alt 移动画布鼠标滚轮 或 按住Z 缩放画布按住S 查看目标位置颜色信息(可复制16进制颜色代码)按住D 查看目标位置在图片窗的位置信息按住Ctrl 旋转按住Ctrl+... -
Android之项目级悬浮窗开发教程
2016-08-05 10:46:56在我们玩手机游戏时能看到,很多游戏的登录界面两侧往往会有一个小小的悬浮窗,可以提供相应功能菜单项,简洁实用且不影响游戏体验。具体效果如下图所示。这篇博客将带大家开发一个可以直接用在项目中的悬浮窗实例。 -
限流算法之固定窗口与滑动窗口
2019-08-30 11:23:43由上图我们可以看出每次窗口只是做了平移,舍弃第一个窗口,将最新的请求放置到最后的窗口,这样就保证了这滑动窗口的时间内对流量的监控。 java代码实现如下: package com.example.demo; import java... -
Android 悬浮窗功能实现(微信语音通话悬浮窗效果实现)
2020-05-11 14:33:59悬浮窗功能实现,微信语音通话悬浮窗效果实现 -
计算机图形学(一)——opengl实现三维立方体添加纹理光照与材质、键盘鼠标控制平移旋转和放大缩小
2019-12-27 12:57:501.实验要求: 用OpenGL和C语言编写一个带纹理和材质的一个立方体的交互式程序。 1)要求生成一个在立方体,并...2)可以利用鼠标和键盘进行交互,实现该立方体的旋转、平移和缩放。 2.实验效果图如下: 正常情况下 加... -
IDA-数据显示窗口(反汇编窗口、函数窗口、十六进制窗口)
2017-11-09 17:57:303.平移操作: 除了使用“图形概况”窗口迅速定位图形外,你还可以通过单击和拖动图形窗口的 背景 来定位图形, 注意是背景,不要点到自身上了 4.重新调整块位置: 通过单击指定块的标题栏... -
opengl绘制桌子(平移、旋转、缩放)
2016-10-18 00:15:19其中左边的桌子向上平移到一定的位置回到原位置继续重复动作,中间的桌子绕y轴旋转,右边的桌子逐渐缩小到一定程度回到原大小继续重复动作。 如有错误,请批评指正(*/ω╲*) -
【转】 Qt绘图,显示图片图像,平移,缩放,旋转和扭曲图片的方法 声明:本
2015-11-11 17:37:57现在我们来实现在窗口上显示图片,并学习怎样将图片进行平移,缩放,旋转和扭曲。这里我们是利用QPixmap类来实现图片显示的。 一、利用QPixmap显示图片。 1.将以前的工程文件夹进行复制备份,我们这里将工程...