接上一章
一、在文档类中添加两个CDib成员变量:m_dib, m_newDib,一个int 成员变量m_stateDoc和一个CString成员变量m_fileName:
int m_stateDoc; // 用于判断是否打开了文件 CDib m_newDib; // CDib类对象保存图像,用于获取一些必要的信息 CDib m_dib; // CDib类对象保存原图数据,在初始化图像数据时有用 CString m_fileName; // 保存图像名称路径
二、在文档类中添加打开文件菜单的消息处理:
void CScanPictureDoc::OnFileOpen() { // TODO: Add your command handler code here // 创建打开文件对话框 CFileDialog dlg(TRUE, _T("BMP"), _T("*.BMP"), OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, _T("位图文件(*.BMP)|*.BMP|")); if(IDOK == dlg.DoModal()) {// 文件打开成功后,获取文件路径,并把图像载入m_dib和m_newDib两个类中,m_stateDoc = 1表明文件已打开 m_fileName.Format("%s", dlg.GetPathName()); m_dib.LoadFile(m_fileName); m_newDib.LoadFile(m_fileName); m_stateDoc = 1; } }
三、在View窗口类中添加两个CDib *成员变量:m_pDib, m_pNewDib和一个int成员变量m_state,并且添加初始化图像数据的成员函数InitializePictureData:
void CScanPictureView::InitializePictureData() { // 获取保存的文档 CScanPictureDoc * pDoc = GetDocument(); ASSERT_VALID(pDoc); m_state = 1; // 表明已经从文档中获取数据 // 如果文档中已经打开文件,就获取图像数据,否则设为空 if(pDoc->m_stateDoc) { m_pNewDib = &pDoc->m_newDib; m_pDib = &pDoc->m_dib; long int size = m_pDib->GetHeight() * m_pDib->GetWidth(); memcpy(m_pNewDib->m_pData, m_pDib->m_pData, size); } else { m_pNewDib = NULL; m_pDib = NULL; } }
四、在View类中添加CPalette成员变量m_palette和创建调色板成员函数CreateDibPalette:
CPalette * CScanPictureView::CreateDibPalette(CDib *pDib) { // 调色板结构,不可直接用LOGPALETTE palette = {0x300, 256}; // 因为LOGPALETTE的结构是 // typedef struct tagLOGPALETTE { // lgpl // WORD palVersion; // WORD palNumEntries; // PALETTEENTRY palPalEntry[1]; // } LOGPALETTE; struct { WORD Version; WORD NumberOfEntries; PALETTEENTRY aEntries[256]; }palette = {0x300, 256}; // 获取图像颜色表和颜色数量 LPRGBQUAD pRGBTable = pDib->GetRGB(); UINT numberOfColors = pDib->GetNumOfClrs(); // 复制颜色表到调色板 for(UINT x = 0; x != numberOfColors; x++) { palette.aEntries[x].peRed = pRGBTable[x].rgbRed; palette.aEntries[x].peGreen = pRGBTable[x].rgbGreen; palette.aEntries[x].peBlue = pRGBTable[x].rgbBlue; palette.aEntries[x].peFlags = 0; } // 如果已经创建过调色板,先删除掉,重新创建 if(m_palette.m_hObject != NULL) m_palette.DeleteObject(); m_palette.CreatePalette((LPLOGPALETTE) &palette); return &m_palette; // 返回调色板 }
五、在View类头文件中添加宏:
#define DIRECT_NONE1000// 直接显示图像 #define DIRECT_DOWN1001// 向下方向扫描显示图像 #define DIRECT_UP1002// 向上方向扫描显示图像 #define DIRECT_LEFT1003// 向左方向扫描显示图像 #define DIRECT_RIGHT1004// 向右方向扫描显示图像
再添加扫描显示成员函数ScanPicture:
void CScanPictureView::ScanPicture(int whichDirect) { // 获取并初始化图像显示区域 CDC *pDC = GetDC(); CRect rect; InitPictureShowRect(pDC, rect); InitializePictureData(); // 初始化图像数据 // 获取程序保存的文档对象 CScanPictureDoc * pDoc = GetDocument(); ASSERT_VALID(pDoc); // 判断是否打开文件并成功初始化图像数据 if(pDoc->m_stateDoc && m_state == 1) { BYTE * pDibData = m_pNewDib->GetData(); // 获取图像数据 LPBITMAPINFO pDibInfo = m_pNewDib->GetInfo(); // 获取图像信息 int dibHeight = m_pNewDib->GetHeight(); // 获取图像高度 int dibWidth = m_pNewDib->GetWidth(); // 获取图像宽度 // 判断是否有图像颜色表信息 if(m_pNewDib->GetRGB()) { // 创建调色板,并选入设备 CPalette * hPalette = CreateDibPalette(m_pNewDib); CPalette * hOldPalette = pDC->SelectPalette(hPalette, true); pDC->RealizePalette(); switch(whichDirect) { case DIRECT_DOWN: {// 图像从上到下扫描显示的代码块 for(int i = 0; i != dibHeight; i++) { ::StretchDIBits(pDC->GetSafeHdc(), rect.left + (rect.Width() - dibWidth) / 2, rect.top + (rect.Height() - dibHeight) / 2 + i, dibWidth, 1, 0, dibHeight - i, dibWidth, 1, pDibData, pDibInfo, DIB_RGB_COLORS, SRCCOPY); Sleep(5); } } break; case DIRECT_UP: {// 图像从上到下扫描显示的代码块 for(int i = 0; i != dibHeight; i++) { ::StretchDIBits(pDC->GetSafeHdc(), rect.left + (rect.Width() - dibWidth) / 2, rect.top + (rect.Height() + dibHeight) / 2 - i, dibWidth, 1, 0, i, dibWidth, 1, pDibData, pDibInfo, DIB_RGB_COLORS, SRCCOPY); Sleep(5); } } break; case DIRECT_LEFT: {// 图像从左到右扫描显示的代码块 for(int i = 0; i != dibWidth; i++) { ::StretchDIBits(pDC->GetSafeHdc(), rect.right - (rect.Width() - dibWidth) / 2 - i, rect.top + (rect.Height() - dibHeight) / 2, 1, dibHeight, dibWidth - i, 0, 1, dibHeight, pDibData, pDibInfo, DIB_RGB_COLORS, SRCCOPY); Sleep(5); } } break; case DIRECT_RIGHT: {// 图像从右到左扫描显示的代码块 for(int i = 0; i != dibWidth; i++) { ::StretchDIBits(pDC->GetSafeHdc(), rect.left + (rect.Width() - dibWidth) / 2 + i, rect.top + (rect.Height() - dibHeight) / 2, 1, dibHeight, i, 0, 1, dibHeight, pDibData, pDibInfo, DIB_RGB_COLORS, SRCCOPY); Sleep(5); } } break; case DIRECT_NONE: {// 直接将图像拷贝到设备上显示 ::StretchDIBits(pDC->GetSafeHdc(), rect.left + (rect.Width() - dibWidth) / 2, rect.top + (rect.Height() - dibHeight) / 2, dibWidth, dibHeight, 0, 0, dibWidth, dibHeight, pDibData, pDibInfo, DIB_RGB_COLORS, SRCCOPY); } break; } // 选回之前的调色板,并删除新建的调色板 pDC->SelectPalette(hOldPalette, true); ::DeleteObject(hPalette); } } }
图像扫描显示的算法分析:图像扫描显示的效果是先把位图文件拷贝到内存的位图对象中,然后按某个方向一行一行的复制到相应设备上,在复制的同时适当的间隔显示时间,就能看到逐行扫描的效果了。
如向下扫描伪代码:
第一步:×××变量i 赋值 1,执行第二步;
第二步:判断是否小于图像高度dibHeight,是就执行第三步,否则执行第七步;
第三步:把第dibHeight - i行的图像复制到设备的第i行上(注意:图像文件保存的图像数据是从下到上方向保存的),执行第四步;
第四步:变量i加1,执行第五步;
第五步:暂停程序5毫秒(Sleep(5)),执行第六步;
第六步:返回执行第二步;
第七步:算法完成。
六、最后在OnDra和按钮消息处理函数中添加ScanPicture,如下:
void CScanPictureView::OnDraw(CDC* pDC) { CScanPictureDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here // 获取窗口客户区矩形 CRect rcViewClient; GetClientRect(rcViewClient); //绘制分割线和按钮 DrawSeparator(rcViewClient); DrawButtons(rcViewClient); ScanPicture(DIRECT_NONE); }
至此,我们的扫描小程序就完成了,效果如下: