如何从DC中取出Bitmap以完成图象抖动

本文介绍了一种从24位彩色图像转换为黑白图像的方法,详细讲解了使用CreateBitmap创建位图、内存DC的设置、图像数据的转换等步骤,并提供了完整的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这两天一直被如何从一个图象抖动的问题所困扰, 想了两天, 调试了两天终于搞明白了.

      问题是这样的,我已知图象每个点的数据, 并且已经有在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;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值