直方图显示应该是图像处理程序必须有的一个功能吧。我的设想是这样,在MFC单文档的菜单上点击【显示直方图】图标,会弹出对话框,显示256色BMP图像的直方图。效果如下:
下面就开始实现这一功能
1、设计对话框
新插入一个资源对话框之后,只在其上添加一个picture控件。picture控件ID为IDC_HIST_PAINT,类型为Rectangle,color改为白色(可以任意)。其实后来才知道,不一定非得在picture控件上画图,其实在对话框上也能直接画图,这就是VC的强大之处的体现!
2、为对话框控件关联类,双击对话框控件会弹出类向导,提示你新建的对话框控件还没有类与之关联,新建类还是。。。在此选择新建类,类名为CHistDialog。在CHistDialog类中新建两个变量 分别用于存储最大灰度值像素数和每个灰度值像素数。重载CHistDialog类的OnPaint函数。重载方法如下图:
我想让直方图显示对话框只负责显示,至于maxcount和 count[256]的统计仍然让CDib类完成,那么,暂时将OnPaint放在一边,处理其他函数。
3、在菜单资源上做如下图所示事情
用类向导建立【查看直方图】菜单的响应函数OnHistLook,具体实现如下
void CReadBMPView::OnHistLook()
{
// TODO: Add your command handler code here
CReadBMPDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if(pDoc->statedoc)
{
if (!pDoc->bmp_file.Is24Bit())
{
if (pDoc->bmp_file.PiexlCount())
{
CHistDialog Histdlg;
Histdlg.DoModal();
}
}
else
{
AfxMessageBox("直方图不支持24位!");
}
}
else
AfxMessageBox("未载入图像!");
}
从上面的代码我们可以知道,要在CDib类中实现完成maxcount和count[256]的统计函数.PiexlCount(),没错!
4、首先为CDib类添加成员int maxcount和int count[256],并在构造函数中初始化为0,注意,VC中数组的初始化与C++中有些不同,最简单的是用memset函数代码如下:
CDib::CDib()
{
。。。。。。
memset(count, 0, sizeof(count));
maxcount=0;
}
为CDib类添加函数.PiexlCount(),代码如下:
BOOL CDib::PiexlCount()
{
if (!Is24Bit())
{ for (UINT h=0;h<Bmphight;h++)
{
for (UINT w=0;w<Bmpbytewidth;w++)
{
count[*(m_pDataCopy+h*Bmphight+w)]++;
}
}
for (int i=0;i<256;i++)
{
if (maxcount<count[i])
{
maxcount=count[i];
}
}
m_valid = TRUE;
return TRUE;
}
else
return FALSE;
}
5,幕后工作完成以后回到CHistDialog的OnPaint函数
为了显示载入图像的直方图,首先应该得到这个对象的指针,而这个对象是在CReadBMPDoc类中生成的,如何在CHistDialog中得到CReadBMPDoc类的指针成了一个关键问题,以上问题也就是:如何从弹出的对话框类中得到文档类的指针。
解决这一问题方法如下:
为CReadBMPDoc类添加成员函数GetDoc(),代码如下:
CReadBMPDoc* CReadBMPDoc::GetDoc()
{
CFrameWnd* pFrame = (CFrameWnd*)(AfxGetApp()->m_pMainWnd);
return (CReadBMPDoc*)pFrame->GetActiveDocument();
}
这样从CHistDialog得到CReadBMPDoc指针就可以用
CReadBMPDoc* pDoc=CReadBMPDoc::GetDoc();
其他已经没有什么大的难点了,OnPaint代码如下:
void CHistDialog::OnPaint()
{
//定义变量
CString str;
CRect rect;
CWnd *pWnd=GetDlgItem(IDC_HIST_PAINT);
CPaintDC dc(pWnd);//
pWnd->GetClientRect(&rect);
CReadBMPDoc* pDoc=CReadBMPDoc::GetDoc();
for (int i=0;i<256;i++)
{
m_count[i]=pDoc->bmp_file.count[i];
}
m_maxcount=pDoc->bmp_file.maxcount;
//创建黑色画笔用于绘制坐标
CPen* pPenblack=new CPen;
pPenblack->CreatePen(PS_SOLID,2,RGB(0,0,0));
CPen* pPengary=new CPen;
pPengary->CreatePen(PS_SOLID,1,RGB(0,255,255));
CGdiObject* pOldPen=dc.SelectObject(pPenblack);
//绘制坐标轴
dc.MoveTo(10,rect.Height()-10);//绘图区左下角定义的原点。
dc.LineTo(10,0);//绘制X轴
dc.MoveTo(10,rect.Height()-10);
dc.LineTo(rect.Width(),rect.Height()-10);//绘制Y轴坐标
//绘制X轴箭头
dc.MoveTo(10,0);
dc.LineTo(4,6);
dc.MoveTo(10,0);
dc.LineTo(16,6);
//绘制Y轴箭头
dc.MoveTo(rect.Width(),rect.Height()-10);
dc.LineTo(rect.Width()-6,rect.Height()-4);
dc.MoveTo(rect.Width(),rect.Height()-10);
dc.LineTo(rect.Width()-6,rect.Height()-16);
//删除画笔
dc.SelectObject(pPengary);
for (int j=1;j<=5;j++)
{
int m=(rect.Height()-20)/5.0*j;
int n=m_maxcount/5.0*j;
str.Format("%d",n);
dc.TextOut(12,rect.Height()-10-m,str);
dc.MoveTo(12,rect.Height()-10-m);
dc.LineTo(rect.Width(),rect.Height()-10-m);
}
dc.SelectObject(pOldPen);
delete pPenblack;
delete pPengary;
CPen* pPenblue=new CPen;
pPenblue->CreatePen(PS_SOLID,1,RGB(0,0,255));
CGdiObject* pOldPen2=dc.SelectObject(pPenblue);
for (int k=0;k<256;k++)
{
int a=(rect.Width()-20)/256.0*k;
int b=(rect.Height()-20)/(m_maxcount+0.0)*m_count[k];
dc.MoveTo(a+15,rect.Height()-10);
dc.LineTo(a+15,rect.Height()-10-b);
}
dc.SelectObject(pOldPen2);
delete pPenblue;
}