怎样去除图片上的背景颜色实现透明贴图?
查了一些资料并参考一些帖子总结了一下有几种方法
由简单到复杂:
方法一:
使用TransparentBlt;
void
CGdiDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // 用于绘制的设备上下文
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// 使图标在工作矩形中居中
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// 绘制图标
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CPaintDC dc(this);
CDC memDc;
memDc.CreateCompatibleDC(&dc);
BITMAP bm;
m_bitmap.GetBitmap(&bm);
memDc.SelectObject(&m_bitmap);
dc.TransparentBlt(10, 10, bm.bmWidth, bm.bmHeight, &memDc, 0, 0, bm.bmWidth, bm.bmHeight, RGB(255, 255, 255));
}
}
方法二:
使用MaskBlt;
{
......
CPaintDC dc(this);
CDC memDc;
memDc.CreateCompatibleDC(&dc);
//CBitmap memBitmap;
BITMAP bm;
m_bitmap.GetBitmap(&bm);
memDc.SelectObject(&m_bitmap);
CDC dcmask;
dcmask.CreateCompatibleDC(&dc);
CBitmap bmpmask;
bmpmask.CreateBitmap(bm.bmWidth, bm.bmHeight, 1, 1, NULL);
dcmask.SelectObject(&bmpmask);
dcmask.FillSolidRect(0, 0, bm.bmWidth, bm.bmHeight, RGB(255, 255, 255));
dc.MaskBlt(0, 0, bm.bmWidth, bm.bmHeight, &memDc, 0, 0, bmpmask, 0, 0, MAKEROP4(SRCAND, SRCINVERT));
......
}
方法三:
使用BitBlt的光栅操作
{
.....
CPaintDC dc(this);
CDC memDc;
memDc.CreateCompatibleDC(&dc);
BITMAP bm;
m_bitmap.GetBitmap(&bm);
memDc.SelectObject(&m_bitmap);
CDC dcmask;
dcmask.CreateCompatibleDC(&dc);
CBitmap bmpmask;
bmpmask.CreateBitmap(bm.bmWidth, bm.bmHeight, 1, 1, NULL);
dcmask.SelectObject(&bmpmask);
memDc.SetBkColor(RGB(255, 255, 255));
dcmask.BitBlt(0, 0, bm.bmWidth, bm.bmHeight, &memDc, 0, 0, SRCCOPY);
dc.BitBlt(0, 0, bm.bmWidth, bm.bmHeight, &memDc, 0, 0, SRCINVERT);
dc.BitBlt(0, 0, bm.bmWidth, bm.bmHeight, &dcmask, 0, 0, SRCAND);
dc.BitBlt(0, 0, bm.bmWidth, bm.bmHeight, &memDc, 0, 0, SRCINVERT);
.....
}
上述代码的原理,直观的说,目标就是,把memDc绘制到dc上的时候,
不绘制跟背景相同的颜色的部分。
1.用BitBlt API进行透明显示的步骤:
①处理dcmask为黑白DC,使memDc上颜色为背景的部分在dcmask显示为白色,其余地方显示为黑色。
②将memDc用BitBlt绘制到dc上,使用SRCINVERT方式
③将dcmask用BitBlt绘制到dc上,使用SRCAND方式
④再将memDc用BitBlt绘制到dc上,使用SRCINVERT方式
ROP中,SRCINVERT是图像间异或处理,SRCAND是图像间与处理。可以简单证明上述的
操作过程会得到我们想要的结果:
对于某一个位置,dc上颜色为B,memDc上颜色为A。
当A == 背景色的时候,dcmask上这个位置的颜色M为白色。则上面的②~④步可以表示为:
((B xor A) and M) xor A
⇔ (B xor A) xor A
⇔ B
当A != 背景色的时候,dcmask上这个位置的颜色M为黑色。则上面的②~④步可以表示为:
((B xor A) and M) xor A
⇔ 0 xor A
⇔ A
前两种方法TransparentBlt和MaskBlt API windows 95/98/me不支持,只支持NT/2000/XP及以后版本
在CDialog类中进行贴图,一般放在OnPaint()函数中,因为窗口更新时,使用它来进行重绘。在OnPain()中贴图的源码如下:
void C***Dialog::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: Add your message handler code here
// CPaintDC dc(this);
CRect rect;
GetClientRect(&rect);
CDC dcMem;
dcMem.CreateCompatibleDC(&dc);///建立关联DC
CBitmap bmpBackground;
bmpBackground.LoadBitmap(IDB_BITMAP_BKK); //IDB_BITMAP是你自己的图对应的ID
BITMAP bitmap;
bmpBackground.GetBitmap(&bitmap);
CBitmap *pbmpOld=dcMem.SelectObject(&bmpBackground);
dc.StretchBlt(0,0,rect.Width(),rect.Height(),&dcMem,0,0,
bitmap.bmWidth,bitmap.bmHeight,SRCCOPY);
// Do not call CDialog::OnPaint() for painting messages
}
当你贴图完毕后,会发现很多地方出现了原来的底色,这是因为这是你的其他东东即控件,的画刷没有设置透明背景色。所有下一步就是设置透明背景色,代码如下:
HBRUSH C***Dialog::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CGraphDialog::OnCtlColor(pDC, pWnd, nCtlColor);
// TODO: Change any attributes of the DC here
if( nCtlColor == CTLCOLOR_STATIC)
{
pDC->SetBkMode(TRANSPARENT); //设置背景透明
return HBRUSH(GetStockObject(HOLLOW_BRUSH));
}
// TODO: Return a different brush if the default is not desired
return hbr;
}
这两个函数均为消息响应函数,所以还需要添加消息响应
.H文件中
protected:
// Generated message map functions
//{{AFX_MSG(CCreateArcBy2Point)
afx_msg void OnPaint();
afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
.CPP文件中
BEGIN_MESSAGE_MAP(CAllMaterialDlg, CDialog)
//{{AFX_MSG_MAP(CAllMaterialDlg)
ON_WM_PAINT()
ON_WM_CTLCOLOR()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
这个时候贴图的效果就非常好了,但是你如果动态的修改静态文本框控件内容时候,又发现了新的问题,发现字符可能重叠。这个原因是你在OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 函数中,将画刷设置为透明引起的。那怎么解决内,你就需要,在OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 函数中返回一个具有背景的画刷了。具体的办法可以自己去琢磨,我这里有个比较笨的办法,给大家提供一个借鉴:
HBRUSH CAllMaterialDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
// TODO: Change any attributes of the DC here
if( nCtlColor == CTLCOLOR_STATIC)
{
pDC->SetBkMode(TRANSPARENT); //设置背景透明
// return HBRUSH(GetStockObject(HOLLOW_BRUSH));
hbr=(HBRUSH)(m_brush.GetSafeHandle());
}
// TODO: Return a different brush if the default is not desired
return hbr;
}
IPicture *m_picture;
OLE_XSIZE_HIMETRIC m_width;
OLE_YSIZE_HIMETRIC m_height;
CString m_filename("G:\\my photo\\照片和视频\\DSC_00516.jpg");//文件名
CFile m_file(m_filename,CFile::modeRead );
//获取文件长度
DWORD m_filelen = m_file.GetLength();
//在堆上分配空间
HGLOBAL m_hglobal = GlobalAlloc(GMEM_MOVEABLE,m_filelen);
LPVOID pvdata = NULL;
//锁定堆空间,获取指向堆空间的指针
pvdata = GlobalLock(m_hglobal);
//将文件数据读区到堆中
m_file.Read(pvdata,m_filelen);
IStream* m_stream;
GlobalUnlock(m_hglobal);
//在堆中创建流对象
CreateStreamOnHGlobal(m_hglobal,TRUE,&m_stream);
//利用流加载图像
OleLoadPicture(m_stream,m_filelen,TRUE,IID_IPicture,(LPVOID*)&m_picture);
m_picture->get_Width(&m_width);
m_picture->get_Height(&m_height);
CDC* dc = GetDC();
//m_IsShow = TRUE;
CRect rect;
GetClientRect(rect);
SetScrollRange(SB_VERT,0,(int)(m_height/26.45)-rect.Height());
SetScrollRange(SB_HORZ,0,(int)(m_width/26.45)-rect.Width());
m_picture->Render(*dc,1,50,(int)(m_width/26.45),(int)(m_height/26.45),0,m_height,m_width,-m_height,NULL);