这两天一直被如何从一个图象抖动的问题所困扰, 想了两天, 调试了两天终于搞明白了.
问题是这样的,我已知图象每个点的数据, 并且已经有在dc上绘制成图象的代码, 开始我的想法是从DC上得到Bitmap以完成图象抖动. 后来发现这种思路不对. 因为当前只有1个打印机dc, 我无法创建一个24位的dc从而创建内存位图, 选到内存dc中, 然后在内存dc中绘图. 可能你会说为什么不用打印机dc来创建一个内存dc来, 再创建一个24位的位图然后选到内存dc中呢, 事实证明dc与Bitmap不兼容, 是选不进去, 包括颜色位数不同, 区域大小不对. 所以同理用CreateDC创建一个显示dc也是行不通的. 最终的解决办法是创建一个打印机关联的内存dc以及一个内存bitmap, 内存bitmap选入内存dc, 然后根据原来的代码生成24位位图的图象数据, 接着创建一个24位位图,将生成好的图象数据设置进去. 这个位图不用选进dc,当然想选也选不进去. 这时就可以将这个位图进行一下抖动, 将这个24位抖动过的位图转换成与打印机关联的那个内存位图就可以了. 当然别忘了将内存dc拷贝到打印机上噢.
下面说说曾经困扰我多时的几点. 首先我们熟悉的是CreateCompatibleBitmap, 来创建一个内存位图. 但是我们指定不了位图的颜色位数, 这时需要就需要用CreateBitmap了. 第一第二个参数是位图的宽度及高度, 一定注意这两个参数是打印机屏幕上实实在在的点数, 也就是说传过来逻辑坐标的话, 需要用LPtoDP来转换成设备坐标.而调用FillSolidRect以及绘图的时候用到的是逻辑坐标, 也就是说你的内存dc的映射模式一定要设置对. 另外如果你的绘制区域不是从原点开始的话, 记得用在内存dc中调用SetWindowOrg. 遇到另外的一个问题是CreateBitmap会失败. 一种情况是传进来的逻辑坐标top在bottom的下面, 意思说要想创建成功, rect的top要在bottom的上面才行. 比如top 0 bottom -100, top -100 bottom -200. 另一种失败的情况是位图过大, 这个可用下面的解决办法
if (m_bitmap.m_hObject == NULL)
{
m_pFile = new CFile(szVmfile, CFile::modeCreate|CFile::modeReadWrite);
m_pbi = new BITMAPINFOHEADER;
m_pbi->biSize = sizeof (BITMAPINFOHEADER);
m_pbi->biWidth = m_rect.Width(); // 注意m_rect要转换成设备坐标
m_pbi->biHeight = m_rect.Height();
m_pbi->biPlanes = 1;
m_pbi->biBitCount = nBitcount; // 颜色位数
m_pbi->biCompression = BI_RGB;
m_pbi->biSizeImage = 0;
m_pbi->biXPelsPerMeter = 0;
m_pbi->biYPelsPerMeter = 0;
m_pbi->biClrUsed = 0;
m_pbi->biClrImportant = 0;
m_pbi->biSizeImage = ((((m_pbi->biWidth * m_pbi->biBitCount) + 31) & ~31) >> 3) * m_pbi->biHeight;
m_hFile = ::CreateFileMapping((HANDLE)m_pFile->m_hFile,
NULL, PAGE_READWRITE, 0, m_pbi->biSizeImage, NULL);
if (m_hFile == NULL)
{
TRACE("Error : Failed to create file mapping object!/n");
}
HBITMAP hBitmap = ::CreateDIBSection(pDC->GetSafeHdc(),
(LPBITMAPINFO)m_pbi, DIB_RGB_COLORS, &m_pBits, m_hFile, 0);
TRACE("Save BITMAP size %ud, height %d/n", m_pbi->biSizeImage, m_pbi->biHeight);
if (hBitmap == NULL)
{
TRACE("Error : Failed to create bitmap object!/n");
CloseHandle(m_hFile);
m_hFile = NULL;
return;
}
m_bitmap.Attach(hBitmap);
}
还有一个问题是CBitmap中的函数GetBitmapBits, SetBitmapBits的数据缓冲区的大小如何确定, 是这样的
BITMAP bmpInfo;
bitmap.GetBitmap(&bmpInfo);
int nByteCount = bmpInfo.bmHeight * bmpInfo.bmWidthBytes;
将24位位图转换成黑白位图时, 两个位图的bmpInfo.bmBitsPixel分别是24和1. bmpInfo.bmWidthBytes却并不是正比关系. bmpInfo.bmWidthBytes意思是说我图像的一行所占的字节数. 开始的时候老缓冲区越界, 然后我把缓冲区人为的搞大一些,结果呢GetBitmapBits就失败了, 看来GetBitmapBits必须要缓冲区设对才能成功啊. 后来发现循环转换的部分有问题, 重新根据打印机的位图的 bmHeight 和 bmWidthBytes来循环, 结果图象出来是乱的. 想了很久, 试了很久, 调试了很久, 终于找到原因了. 我用的黑白打印机的一个点是用一位来表示的, 但是如果是图象的一行结束并没有用过整一个字节, 或是一个字, 他会补上的. 也就是说用打印机位图的 bmHeight 和 bmWidthBytes来循环会导致从24位位图数据缓冲区取数据的时候, 偏移量算错. 后来我改成用24位位图的 bmHeight 和 bmWidthBytes来循环, 问题终于解决了, 呵呵, 贴下24位位图转成黑白位图的代码.
CDC ptdc; // 打印机DC
CBitmap bitmap;
ptdc.CreateCompatibleDC(m_pDC); // 创建打印机兼容的内存dc, m_pDC是打印机dc
ptdc.SetMapMode(m_pDC->GetMapMode());
bitmap.CreateCompatibleBitmap(m_pDC, m_drect.Width(), m_drect.Height()); // m_drect是设备坐标
ptdc.SelectObject(&bitmap);
ptdc.SetWindowOrg(CPoint(m_rect.left, m_rect.top));
ptdc.FillSolidRect(m_rect, ptdc.GetBkColor());
BITMAP bmpInfo;
bitmap.GetBitmap(&bmpInfo);
int nPrintByteCount = bmpInfo.bmHeight * bmpInfo.bmWidthBytes;
int nPrintColorBit = bmpInfo.bmBitsPixel;
int nPrintHeight = bmpInfo.bmHeight;
int nPrintWidth = bmpInfo.bmWidthBytes;
BYTE* pPrintData = new BYTE[nPrintByteCount]; // 打印机兼容位图的数据缓冲区
ZeroMemory(pPrintData, nPrintByteCount);
m_bitmap.GetBitmap(&bmpInfo); // m_bitmap是用CreateBitmap生成好的24位位图
int nMemColorBit = bmpInfo.bmBitsPixel;
int nMemHeight = bmpInfo.bmHeight;
int nMemWidthBytes = bmpInfo.bmWidthBytes;
int nByteCount = bmpInfo.bmHeight * bmpInfo.bmWidthBytes;
BYTE *pData = new BYTE[nByteCount]; // 24位图的数据缓冲区
m_bitmap.GetBitmapBits (nByteCount , pData);
for (int i = 0; i < nMemHeight; i++)
{
int nPrintPos = i * nPrintWidth; // 一行的起始
for (int j = 0; j < nMemWidthBytes / 3; j++) // 图像以像素点为单位的列序号
{
int nPos = i * nMemWidthBytes + 3 * j;
int nColor = pData[nPos];
int p = j / 8;
int k = j % 8;
int b = 1;
if (nColor > 127)
pPrintData[nPrintPos + p] |= (b << (7 - k));
}
}
bitmap.SetBitmapBits(nPrintByteCount, pPrintData);
m_pDC->BitBlt(m_rect.left, m_rect.top, m_rect.Width(), m_rect.Height(), &ptdc, m_rect.left,
m_rect.top, SRCCOPY);
delete pPrintData;
delete pData;