关于SetMapMode坐标变换以后的BitBlt使用

来源:互联网 发布:心电图模拟软件 编辑:程序博客网 时间:2024/05/16 07:04
目标:现在用一个timer在一个static空间上动态绘制一个sin曲线,希望在大约一秒钟内画完一个sin周期图形

思路:

用timer触发Polyline函数依次连接sin数组内的点,然后动态画出sin曲线

第一次进入OnTimer画点1和点2

第二次进入OnTimer画点1,点2,点3

第三次进入OnTimer画点1,点2,点3,点4

以此类推

m_ArySin是一个Sin数组,X:0~2PI,Y=sin(X)


问题:

在timer内用双缓存实现绘图,由于sin数组内有正有负,为了直观化,引入坐标变化,把(X=0,Y=rect.bottom/2)设为原点,结果画出来的图如附件所示,感觉内存里的DC坐标并没有变化正确,虽然内存DC中的Y轴方向是朝上了,但是Y轴起始点好像还是在Y=0的位置


做了N次实验,发现如下代码能够达到预期目的,但是具体理由不详

CBitmap memBitmap;//内存绘图CBitmap* pOldBmp = NULL;//内存绘图CRect rect; //控件区域//获取空间区域DCCWnd* pWnd = GetDlgItem(IDC_STATIC1);//控件窗口pWnd->GetClientRect(&rect);//控件客户区m_pDC = pWnd->GetDC();//获取控件DC//坐标转换m_pDC->SetMapMode(MM_ANISOTROPIC);//设置map模式m_pDC->SetWindowExt(rect.right, -rect.bottom);//设置窗口尺寸,逻辑单位m_pDC->SetViewportExt(rect.right, rect.bottom);//设置视口尺寸,物理单位m_pDC->SetViewportOrg(0, rect.bottom/2);//设置设备坐标原点//m_pDC->SetWindowOrg(0, rect.bottom/2);//设置逻辑坐标原点//CPoint PtTest;//PtTest = m_pDC->GetWindowOrg();//PtTest = m_pDC->GetViewportOrg();//m_pDC->MoveTo(0,0);//m_pDC->LineTo(10,-10);//pWnd->Invalidate();//pWnd->UpdateWindow();//创建内存绘图设备m_memDC.CreateCompatibleDC(m_pDC);memBitmap.CreateCompatibleBitmap(m_pDC,rect.right,rect.bottom);pOldBmp = m_memDC.SelectObject(&memBitmap);//m_memDC.BitBlt(rect.left,rect.top,rect.right,rect.bottom,m_pDC,0,0,SRCCOPY);//m_memDC.BitBlt(0,-rect.bottom/2,rect.right,rect.bottom,m_pDC,0,-rect.bottom/2,SRCCOPY);//坐标转换m_memDC.SetMapMode(MM_ANISOTROPIC);//设置map模式m_memDC.SetWindowExt(rect.right, rect.bottom);//设置窗口尺寸,逻辑单位m_memDC.SetViewportExt(rect.right, -rect.bottom);//设置视口尺寸,物理单位m_memDC.SetViewportOrg(0, rect.bottom/2);//设置设备坐标原点//m_memDC.SetWindowOrg(0, rect.bottom/2);//设置逻辑坐标原点//先把图拷贝到内存获取底色m_memDC.BitBlt(0,-rect.bottom/2,rect.right,rect.bottom,m_pDC,0,-rect.bottom/2,SRCCOPY);//绘图CPen Pen;CPen* pOldPen;Pen.CreatePen(PS_SOLID, 1, RGB(255,255,0));pOldPen = m_memDC.SelectObject(&Pen);//画线m_memDC.Polyline(m_ArySin, m_nTime1Cnt);//m_memDC.MoveTo(0,0);//m_memDC.LineTo(10,10);m_memDC.SelectObject(pOldPen);Pen.DeleteObject();/*if(m_nTime1Cnt == 0){memDC.MoveTo(0,0);memDC.LineTo(10,10);}else if(m_nTime1Cnt == 1){memDC.MoveTo(10,0);memDC.LineTo(20,10);}*///内存拷贝到屏幕//m_pDC->BitBlt(rect.left,rect.top,rect.right,rect.bottom,&m_memDC,0,0,SRCCOPY);m_pDC->BitBlt(0,-rect.bottom/2,rect.right,rect.bottom,&m_memDC,0,-rect.bottom/2,SRCCOPY);//m_pDC->BitBlt(0,-50,rect.right,rect.bottom,&m_memDC,0,-50,SRCCOPY);//析构m_memDC.SelectObject(pOldBmp);m_memDC.DeleteDC();memBitmap.DeleteObject();

为了弄清坐标变换关系,做了N多实验,得出以下结论。

结论:

1,pDC的逻辑坐标系和设备坐标系之间做了坐标变换以后,memDC最好做相同的坐标变换(为了保持一致),当然如果你对两个DC中的坐标变换很有把握,那也可以分别设置

2,BitBlt中的坐标参数是逻辑坐标系下的坐标

3,由pDC创建出来的memDC的逻辑坐标系和设备坐标系的设定和pDC本身的坐标系的设定没有关系,比如pDC先做了坐标变换,然后由pDC再创建memDC,那么memDC默认是没有坐标变换的

4,pDC和memDC之间并不存在坐标变换,所谓坐标变换指的是逻辑坐标系和设备坐标系之间的关系,pDC和memDC他们各自有各自的逻辑坐标系和设备坐标系


很多人大概会问,为什么BitBlt里面的坐标是负的

m_pDC->BitBlt(0,-rect.bottom/2,rect.right,rect.bottom,&m_memDC,0,-rect.bottom/2,SRCCOPY);


为了说明这个问题,我做个简单的实验


在没做坐标变换的时候先用BitBlt画一条(0,0)到(10,10)的线


CDC*         pDC;CDC          memDC;CBitmap      memBitmap;//内存绘图CBitmap*     pOldBmp = NULL;//内存绘图CRect        rect; //控件区域//获取空间区域DCCWnd* pWnd = GetDlgItem(IDC_STATIC1);//控件窗口pWnd->GetClientRect(&rect);//控件客户区pDC = pWnd->GetDC();//获取控件DC//创建内存绘图设备memDC.CreateCompatibleDC(pDC);memBitmap.CreateCompatibleBitmap(pDC,rect.right,rect.bottom);pOldBmp = memDC.SelectObject(&memBitmap);//绘图CPen Pen;CPen* pOldPen;Pen.CreatePen(PS_SOLID, 3, RGB(0,255,0));pOldPen = memDC.SelectObject(&Pen);//画线memDC.MoveTo(0,0);memDC.LineTo(20,20);memDC.SelectObject(pOldPen);Pen.DeleteObject();//内存拷贝到屏幕pDC->BitBlt(0,0,rect.right,rect.bottom,&memDC,0,0,SRCCOPY);//析构memDC.SelectObject(pOldBmp);memDC.DeleteDC();memBitmap.DeleteObject();


得到的图片如下图



我们没做任何坐标变换,所以这个现象很好理解。由于画线之类的GDI函数都是基于逻辑坐标系的,而我画的是(0,0)到(20,20)的线,且没做坐标变换和坐标平移,所以很直观的就可以知道短线的左上角就是逻辑坐标原点(0,0),右下角就是逻辑坐标(20,20),由于没做坐标转换,所以很显然,逻辑坐标系的X轴的0点在图片的左上角,向右增大;Y轴0点也在左上角并向下增大。pDC的设备坐标系和逻辑坐标系重合。整张BitMap图的左上角的逻辑坐标无疑就是(0,0)。对于memDC也是一样,所以BitBlt的时候,src和des的左上角坐标都是(0,0)。


此时,如果我想把这条线做一个以X为轴的镜像(往上翘),并显示在下图一样的位置




那么问题就来了,坐标转换怎么做?BitBlt怎么写?(我们假定这个图为正方形,长宽各150)


首先,对于X轴镜像,一个简单的方法就是把设备坐标系和逻辑坐标系做一个转换。公式:Y' = -Y。所以有:

pDC->SetMapMode(MM_ANISOTROPIC);//设置map模式pDC->SetWindowExt(rect.right, rect.bottom);//设置窗口尺寸,逻辑单位pDC->SetViewportExt(rect.right, -rect.bottom);//设置视口尺寸,物理单位

其次,BitBlt的参数问题,如果你正过来想不出BitBlt的参数怎么设,那我们不如反过来思考,现在我们知道pDC的逻辑坐标系和设备坐标系是以逻辑坐标系的X轴(图上的红线)互相为镜像的,所以,如果要像上图一样的显示,那么pDC中的图像必定是在X轴(红线)的上方,而在pDC的逻辑坐标系中,红线的上方都是负数,且向上递减,所以可想而知,BitBlt中目标DC的左上角的Y坐标应该是"-150"。BitBlt应写成

pDC->BitBlt(0,-150,rect.right,rect.bottom,&memDC,0,0,SRCCOPY);

通过这个实验应该就能理解为什么BitBlt的参数会是负数的缘故了。同样,这个道理也适用于第二个参数的设置上,因为“拷贝到哪里去”和“从哪里拷贝来”道理其实是一样的,也就是说,如果memDC做过镜像,那么第二个参数也应该去负数。




再说一下关于坐标平移,再来看两个简单的实验


不用BitBlt,纯用pDC话(0,0)到(50,50)的线

CDC*         pDC;CDC          memDC;CBitmap      memBitmap;//内存绘图CBitmap*     pOldBmp = NULL;//内存绘图CRect        rect; //控件区域//获取空间区域DCCWnd* pWnd = GetDlgItem(IDC_STATIC1);//控件窗口pWnd->GetClientRect(&rect);//控件客户区pDC = pWnd->GetDC();//获取控件DC//坐标转换pDC->SetMapMode(MM_ANISOTROPIC);//设置map模式pDC->SetWindowExt(rect.right, rect.bottom);//设置窗口尺寸,逻辑单位pDC->SetViewportExt(rect.right, rect.bottom);//设置视口尺寸,物理单位//pDC->SetWindowOrg(0, 75);//设置逻辑坐标原点//pDC->SetViewportOrg(0, 75);//设置设备坐标原点//CSize WndSize = pDC->GetWindowExt();//CSize VewSize = pDC->GetViewportExt();CPoint PtTest;PtTest = pDC->GetWindowOrg();PtTest = pDC->GetViewportOrg();//pDC->MoveTo(0,0);//pDC->LineTo(10,-10);//pWnd->Invalidate();//pWnd->UpdateWindow();//绘图CPen Pen;CPen* pOldPen;Pen.CreatePen(PS_SOLID, 3, RGB(0,255,0));//pOldPen = memDC.SelectObject(&Pen);pOldPen = pDC->SelectObject(&Pen);//画线pDC->MoveTo(0,0);pDC->LineTo(50,50);pDC->SelectObject(pOldPen);Pen.DeleteObject();//析构//pDC.DeleteDC();

显示如下图


然后分别用

pDC->SetWindowOrg(0, 75);//设置逻辑坐标原点pDC->SetViewportOrg(0, 75);//设置设备坐标原点

对逻辑坐标系原点和设备坐标系原点做设定,看看效果

首先是

pDC->SetWindowOrg(0, 75);//设置逻辑坐标原点

得到下图

可以看到,短线跑到上面去了,也就是说,如果把逻辑坐标系原点往Y轴正方向平移,那么实际显示的图像就会往反方向平移。


再来看设备坐标系,同样往Y轴正方向平移

pDC->SetViewportOrg(0, 75);//设置设备坐标原点

得到下图

可以看到,短线跑到下面去了,也就是说,如果把设备坐标原点往Y轴正方向平移,那么实际显示的图像就会往同方向平移。

总结一下,移动逻辑坐标,实际图像往反方向移动,移动设备坐标,实际图像往同方向移动。

其实这两句话是一个意思,归根结底,看的还是逻辑坐标系和设备坐标系之间的转换,简单来说,不管使用SetWindowOrg把逻辑坐标往上移还是用SeViewportOrg把设备坐标往下移,其实都是一回事情,把逻辑坐标上移其实就等于把设备坐标下移

现在我们用公式表达式来说明问题。

做了变换以后的两个坐标系下的坐标对应关系是:

公式1: Y(设备)=Y(逻辑)+75

公式2: X(设备)=X(设备)

如果搞不清坐标系之间的变换公式,可以看两个原点之间偏差,这样就很容易找出两个坐标系之间公式关系,比如把逻辑坐标系往下移,那么Y(设备)=Y(逻辑)+50

那么假设在逻辑坐标系下有一条线,它的公式是Y(逻辑)=2X(逻辑)

那么把公式1和公式2代入之后,在设备坐标系下就是,Y(设备)=2X(设备)+50

有了设备坐标系下的图像函数,我们就可以显示图像了。

当然,我们现在的逻辑坐标系在内存里,设备坐标系在那个Static控件上。所以,在设备坐标系下显示出来的最终结果就是一条短线向Y轴正方向平移了50个单位。也就是上图标识的结果。

这里额外提一句,所有你人眼看得到的图像都是设备坐标系下显示出来的东西,所以只要找到设备坐标系下的图像函数(由逻辑坐标系下的函数转换而来),就可以得到最终显示结果了。


有了上述这些分析,再回到最初的问题,我们要把一个sin曲线显示出来,应该怎么做?

首先,为了让图像更符合我们常识中的坐标系,先对pDC做一个以X为轴的镜像,这样Y轴就可以向上了

m_pDC->SetWindowExt(rect.right, rect.bottom);//设置窗口尺寸,逻辑单位m_pDC->SetViewportExt(rect.right, -rect.bottom);//设置视口尺寸,物理单位

既然Y轴做了镜像,那么按照我们前面说的BitBlt的第一个参数(des)肯定就是负的,(0,-150)

然后,在memDC中绘制sin曲线,由于sin的正半部分超过了memDC中的设备坐标系的上限,所以是显示不出来的,所以要对memDC做一个坐标平移,移逻辑或者移设备都可以,只要你掌握了规律,这里我们移逻辑

m_memDC.SetWindowOrg(0, -rect.bottom/2);//设置逻辑坐标原点

逻辑往上移,等于设备往下移,等于实际图像往下移,所以sin的正负部分都可以被显示了,但是BitBlt的第二个参数(src)要注意了,由于平移过memDC的逻辑坐标系,所以在memDC所对应的设备坐标系下显示的位图的Y坐标范围就不再是0~150了。因为逻辑网上移了75,等于设备往下移了75,所以设备中显示的位图的左上角的逻辑坐标应该是-75。所以BitBlt的第二个参数应该是(0,-75)

m_pDC->BitBlt(0,-150,rect.right,rect.bottom,&m_memDC,0,-75,SRCCOPY);

最后的这些参数设定和文章一开始的代码里的参数多少有点不一样,那是因为一开始是试出来的,后面的是自己分析出来的,效果都是一样的,呵呵


原创粉丝点击