虽然是小项目,但是还是有点挑战性的,因为从来没有做过一个比较正式的项目;之前在编程过程当中,都是比较粗糙,想到什么就写什么,在没有一个系统的架构前提之下,虽可谓倚马可待,但bug很多,多到自己想法都没有了,最后不得不丢下个“四不像”的一堆代码。
找个借口原谅自己就是自觉阅读之前写下的代码,然后认真总结和分析,谈谈自己的收获。
其实也没什么,就一个处理图像的东西,很多的东西已经被sdk封装了,其实你能使用sdk到游刃有余的地步,那也是一种强的表现了,别被别人的闲言冷语冷落到“认为sdk没出息”。
重要的不是你学会了sdk什么的,重要的是你在完成一个任务的过程当中的收获,more or less。
功能列表
文件 |
获取图片信息 |
图像操作 |
打开 |
像素宽度 |
移动 |
*重新加载 |
像素高度 |
旋转 |
另存为 |
两点距离 |
放大 |
保存 |
每行象素所占字节数 |
缩小 |
退出 |
当然还有一些具体的要求。
因为需要用到显示位图,所以我也突发奇想要自定义一个控件,专门用来对付位图的处理,包括移动旋转之类的,感受到了吧,OO。开始的时候不去借助网络资源,自己操手干起来,但是遇到的问题还是蛮多的。
离开了win32一段时间了,来到MFC就忘本了。现在背背,
大概的过程就是这个(其实还是翻了书)。
自定义控件的思路也是这样的,只是createwindow之后的东西(消息处理过程还是要我们操手)IDE帮我们做好了,注意就算在win32下我们还是要对控件createwindow的。上面说消息处理过程还是要自己动手,就是标准控件的消息处理过程已经被包装好了,但是我们自定义的控件会有我们自己想要处理的消息。
在对话框资源窗口添加了Custom Control之后,在属性对话框中要增加Class,在这里要注意填写的是你的注册窗口类而不是你的窗口类,明白人懂的。
添加了一个窗口类(继承自CWnd)之后,里边什么都没有,当然除了动态创建,消息映射,以及CWnd的一些函数还是有的,因为这是CWnd自有的,“爸爸有,儿子也要有”(C++里边的“遗传”好蛋疼)。任何一个窗口都要注册窗口类,所以一定要先在构造函数里面注册好。代码如下:
BOOL
CBMPViewer::RegisterWndClass()
{
WNDCLASS windowclass;
HINSTANCE
hInst = AfxGetInstanceHandle();
windowclass.style = CS_HREDRAW | CS_VREDRAW |CS_OWNDC;
windowclass.lpfnWndProc = ::DefWindowProc;
windowclass.cbClsExtra = windowclass.cbWndExtra = 0;
windowclass.hInstance = hInst;
windowclass.hIcon = NULL;
windowclass.hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
windowclass.hbrBackground =(
HBRUSH
)::CreateSolidBrush(#808a87);
//::GetSysColorBrush(COLOR_WINDOW);
windowclass.lpszMenuName = NULL;
windowclass.lpszClassName = L
"MYCLASS"
;
if
(!AfxRegisterClass(&windowclass))
{
AfxThrowResourceException();
return
FALSE;
}
return
TRUE;
}
|
之后的话就不废话了,想干嘛就干嘛。
在自定义控件当中只有在onpaint()中的设备环境才是有效的,在其他的方法当中你也无法get到,这让我很疑惑,调试了一下,发现连hwnd都是unused的(好奇怪)。如果你发现了,就告诉我。
BitmapSize=bmp.bmWidthBytes*bmp.bmHeight; 在这一次实验当中,涉及到了比较多的BMP图片的学习,包括基本的组成和操作(放大缩小等)。下面是DIB的文件格式:
这就是BMP图片的组成,可以当成是他的数据结构,这里面就存放了显示图片需要的数据。结构体内的数据比较复杂,但是在这里讲几点。
offsetbit 为文件头到像素为的位移;
imagesize为像素位的大小;
size为整个DIB文件的大小。
文件头当中,offsetbit 的计算其实很简单,就是文件头和信息头的大小加起来(当然默认是24位的位图),但是因为他们是固定大小的的所以比较简单。
imagesize计算的方式看起来很复杂:
((((m_bdBmpdata.width*32)+31)/32)*4)*m_bdBmpdata.height,因为所占字节数不足4的倍数的话要用0来补全,慢慢理解一下。
如果你准备的信息比较完善的话还可以用下面的公式来计算:
BitmapSize=bmp.bmWidthBytes*bmp.bmHeight;
第一个是BITMAP结构中的字段,表示每行所占的字节数。
8位位图除了可以索引彩色图像外,也可以是灰阶图像,相信更多的是用于灰度的图像,既然有8位的灰阶图像,也就是说从白到黑分成256种渐变,那16位灰阶图像也是存在的,只不过从白到黑分成2^16种渐变;但这是一种很大的浪费我觉得,因为灰阶图像应用不是非常广,在一些专业领域或许会用到。
而如今PC下的更多的是24位的,32位的,16位的也有。一开始的时候还不知道什么是RGB,其实简单来说就是Red,Green,Blue分别用一定的位数来存储他们的值。16位以上多见RGB格式的图,16位位图图片还可分为R5G6B5,R5G5B5X1(有1位不携带信息,其实就是最高位),R5G5B5A1,R4G4B4A4 等等。
其中RGB555,BITMAPINFOHEADER信息头字段biCompression成员的值是BI_RGB,它的存储格式是:
XRRRRRGG GGGBBBBB,注意它跟24位位图没有颜色表。
24位位图更简单,它的存储格式是:
RRRRRRRR GGGGGGGG BBBBBBBB
而32位的位图,新增了一个透明度,这在XP下很常见了:
AAAAAAAA RRRRRRRR GGGGGGGG BBBBBBBB
图形处理中,通常把RGB三种颜色信息称为红通道、绿通道和蓝通道,相应的把透明度称为Alpha通道。由RGB形成的图像均称做真彩色。
OK,差不多就啰嗦到这里吧。真正在写程序中遇到的问题是位图的转换:将24位真彩位图转换为8位灰阶位图。而没有上面的东西,下面的东西看起来会很吃力。
问题描述的清楚一点吧:原图片是24位位图,而如今项目当中的绝多数操作都是基于8位位图,不可能让客户多一手准备8位的位图,自然而然转换的任务就落在了程序的头上了。
我的解决方法是这样:就用一个方法来实现这个转换的操作,而函数的参数就一个路径(让客户只提供路径,这方便很多了),在转换之后再写为另一个8位位图文件。
或许一开始拿到这样的任务,还是摸不着头脑,不知道从何下手;我画了下面的一张图,
Y是什么,Y就是明度,在灰阶位图当中,只有明度(灰阶值),而没有色度和浓度,它和RGB是有区别的,是两种不同的颜色编码方法,RGB注重的是色彩,而YUV注重的亮度。幸运的是,两者之间可以进行转换,
对,上面的第一个公式正是我们想要的,熟悉DIB格式的童鞋都应该有思路了吧,像素位里面就存着RGB,它与上面的公式能够帮助我们将24位位图转换为8位灰阶位图。
其中有个问题,就是涉及到位图的每行所占字节数的问题,重申一次,Windows规定一个扫描行所占的字节数必须是4的倍数(即以long为单位),不足的以0填充,每行所占字节数的计算公式:((((width*32)+31)/32)*4)。
举一个简单的例子:假如一个8位灰阶位图,它的宽是31,则每一行需要31个字节存储,因为字节数必须是4的整倍数,所以应该是32字节,此时BITMAPINFOHEADER字段当中,biWidth=31,biBitCount=8,但要清楚,每行所占字节是32字节。因此在做每行转换为8位位图的时候,我们需要每个扫描行+32,而不是每个扫描行+31。
所以下面所展示的程序,看到宽度转换可以回到这里找答案。上代码吧,
void
Convertto8Bit()
{
HANDLE
hFile;
//文件句柄
DWORD
dwWritten;
//记录以写入的字节数
hFile = CreateFile(L
"F:\\1.bmp"
,GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
BITMAPFILEHEADER bmfh;
//文件头<
|