@这两天弄了拆分窗口和图片的显示,还有把前面的基础又弄了下。
@之前界面这块用的是动态拆分窗口,可是考虑到别的操作,包括图片的显示区域以及具体操作的时候,窗口的索引等信息会出现莫名其妙现在不理解的问题。而如果用静态窗口,可以方便定制不同类别的视图,也不会有那些问题。动态拆分也可以定制不同的视图,方法好像是重构那个拆分条。
@图片读取这块,要能处理多种图片类型(JPEG,BMP,PNG,,,),然后发现了CImage这个好东东。
@一堆琐碎的细节问题。
1. OpenGL/MFC
OpenGL在MFC上使用的关键就是构建一个能够给OpenGL使用的环境,包括下面几步:
- 往View中增加一个OpenGL渲染句柄变量
- 在OnCreate函数中使用SetPixelFormat设置窗口的像素格式,返回最匹配该像素格式的像素格式索引值,然后依此用wglCreateContext建立RC句柄
- 在OnSize和OnPaint/OnDraw中每次要画的时候,用wglMakeCurrent把View中的句柄赋到当前DC中,然后绘制,并且要记住释放相关的资源
- 这个过程不常用的话,肯定不熟悉,那么找本书或者上网现查
下面几点注意:
- CPaintDC只能用在响应WM_PAINT事件,CClientDC只能用在响应非WM_PAINT事件,关于DC(Device Context),详细见此
- HDC的获取并不只有一种方式
//法1
HWND hWnd = GetSafeHwnd();
HDC hDC = ::GetDC(hWnd);
m_hRC = wglCreateContext(hDC);
//法2
CClientDC dc(this);
m_hRC = wglCreateContext(dc.m_hDC);
P.S. : 这里只是简单记录,具体操作的时候,再细查。
2. 窗口拆分
MFC窗口拆分,《深入解析MFC》(George Shepherd, Scot Wingo)第九章讲的不错,用CSplitterWnd,这里也讲的不错,看了就懂了。对于多窗口的,大致包括以下几步:
- 在CChildFrame中新增一个CSplitterWnd变量
- 重写CChildFrame的OnCreateClient方法
- 选择静态的还是动态的拆分
- 继续细化的操作
//静态拆分
BOOL CChildFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
CRect rect;
GetClientRect(&rect);
if(!m_staticSplitter.CreateStatic(this, 1, 2)) return FALSE;
m_staticSplitter.CreateView(0, 0, RUNTIME_CLASS(COpenGLPlatView), CSize(rect.Width()/2,rect.Height()), pContext);
m_staticSplitter.CreateView(0, 1, RUNTIME_CLASS(MView), CSize(rect.Width()/2,rect.Height()), pContext);
return TRUE;
}
//动态拆分
BOOL CChildFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
return m_wndSplitter.Create(this, 2, 2, CSize(10, 10), pContext);
}
3. CImage
图像这块,CImage的使用是在这里(39/73)看到的,通过具体的代码来记录,
void CTexture::SetData(char *name)
{
CImage* img = new CImage;
img->Load(name);
m_width = img->GetWidth();
m_height = img->GetHeight();
TRACE("w = %d, h = %d\n", m_width, m_height);
m_pData = new unsigned char[m_width * m_height * 3];
for(int j = 0; j < m_height; j++)
{
for(int i = 0; i < m_width; i++)
{
int index = (j * m_width + i) * 3;
COLORREF rgb = img->GetPixel(i, m_height - 1 - j);
m_pData[index] = GetRValue(rgb);
m_pData[index + 1] = GetGValue(rgb);
m_pData[index + 2] = GetBValue(rgb);
}
}
//m_pData = auxDIBImageLoad(_T(name))->data;
n_width = 1 << 8;
n_height = 1 << 8;
n_pData = new unsigned char[n_width * n_height * 3];
//查看最大的纹理大小
GLint max;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max);
TRACE("max = %d\n", max);
//放缩图片
gluScaleImage(
GL_RGB,
m_width,
m_height,
GL_UNSIGNED_BYTE,
m_pData,
n_width,
n_height,
GL_UNSIGNED_BYTE,
n_pData
);
TRACE("error=%s\n", gluErrorString(glGetError())); //查看错误
delete img;
}
用到的思路,就是把CImage中的像素数据放到数组里去,因为我要通过纹理映射的方式来显示图片,要注意的是,CImage类中的GetPixel(i, j)方法,(i,j)的索引指的是以图片的左下角为原点的笛卡尔坐标系下的点,而纹理映射要求的数据的排放方式是行优先存储。上面的代码用到了别的函数,出的问题在遇到的琐碎的问题中扯。
4. 遇到的琐碎问题
4.1. 闪啊闪
写好动态拆分窗口后,发现一个很奇怪的问题,就是拖动窗口时,发现画的图形一直在闪,原来是MFC的WM_ERASEBKGND消息在默认操作,一直画啊画,并且被你给捕捉到了,要这样处理下,这个操作很基本了,初学的飘过~
BOOL MView::OnEraseBkgnd(CDC* pDC)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
//return CView::OnEraseBkgnd(pDC);
return TRUE;
}
这儿引用这里的一段话:
其实防止“闪烁”不在于什么双缓存,这是因为你没有理解正确什么是闪烁以及它出现的原因。闪烁本质上是显存数据在发生变化时被人的视觉捕获到了就有了闪烁感,也就是有短暂的一个“非最终结果”曾经被扫描输出到屏幕上。要避免闪烁,主要方法是“快速”的更新显存数据以及很多情况下,是通过屏蔽“WM_ERASEBKGND"消息去防止那种刷新窗口时的闪烁,换句话说,防止系统使用默认行为在你的绘制方法之前为你刷背景(这是造成闪烁的最常见因素)。所谓双缓存,主要是考虑在内存中准备结果数据,而不是直接在目标DC上设置像素,由此防止影响处理图像时计算速度。
4.2. 就是不更新
把闪啊闪解决了之后,随之而来的是个诡异的问题,横向拖动的窗口时,可以重新绘制,可是当纵向拖动窗口和最大化窗口的时候,绘制的内容就是不更新。害我还以为是静态分割窗口后,加载的两个视图类是不一样的,要是那样的话,就不知道怎么搞了呢。这个地方Debug了蛮久的,我把原先的那个View的方法都复制到新建的View里面,看看到底是哪个出问题了。结果表明居然是OnSize出问题了,现在还具体不知道本质是什么原因,但看起来是因为没有提供OpenGL渲染的环境,导致glMatrixMode、glLoadIdentity、glViewport这些函数的异常,glViewport这个函数直接影响到最后的显示效果。可是可是,水平移动的话就没有这个问题,诡异,修改的部分在代码中标出:
void MView::OnSize(UINT nType, int cx, int cy)
{
TRACE("MView::OnSize\n");
CView::OnSize(nType, cx, cy);
// TODO: 在此处添加消息处理程序代码
CClientDC dc(this);
wglMakeCurrent(dc.m_hDC, m_hRC); //如果不加这句,会“就是不更新”
glMatrixMode(GL_PROJECTION);
glLoadIdentity( );
glViewport(0, 0, cx, cy);
glMatrixMode(GL_MODELVIEW);
}
4.3. Unicode?
在上述3的代码中,写的是void CTexture::SetData(char *name)这个函数,功能是把name这个路径的图片相应的像素数据加载到CTexture相应的区域,在调用这个函数时,要传递char*的参数,要归结于这样个问题,就是CString类型的,转成char*类型的:
CString filepath = f.GetPathName();
char* name = filepath.GetBuffer();
上面代码会报出这样的错误:error C2440: “初始化”: 无法从“wchar_t *”转换为“char *”。这个问题CSDN上有各种各样的帮助,试了之后都不行,后来知道wchar是宽字节,在这里想到去改字符集什么的,大概意思是因为支持Unicode才会出现这个问题,字符集改动位置:工程属性->配置属性->常规->字符集,从“使用 Unicode 字符集”改成“未设置”。
4.4. 一些函数注意事项
- gluScaleImage函数使用的时候,有可能因为自己的操作不当,导致出现难以发现的错误,可以用gluErrorString(glGetError())来查看错误,例如我这次把32位的数定义成了8位的数了,大大的溢出,导致传递的参数不合法。
- OpenGL中支持的纹理大小是有一定的限制的,可以通过glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max)来获得,例如我的那个版本最大是1024。glTexImage2D函数要求宽度和高度要是2的次幂,但现在的版本没这个限制了,可以通过添加扩展的库或用gluScaleImage先放缩成2的次幂相关的图片来实现,详见这里。
5. 爽图
贴下静态拆分窗口下成功读取PNG后的爽图吧:
两天虽然没做什么出来,但遇到的问题还真不少,插一句,用WLW写这篇随笔的时候,崩溃了3次,囧。接下来把这两个视图联系起来,把选框这个对象做好,现在的思路也比较简单:把右边的图片做下矩形选择,然后跑到左边来做做简单变形,然后贴下,一步一步来,要做的还好多额。