GDI透明贴图

网上下载的图片,有一些会有水印。水印的实现可以用透明贴图来做。透明贴图就是让两张图片合并的时候,重叠的部分中使一些颜色不显示,从而达到透明的效果。

如果用GDI来实现的话,具体方法步骤可多可少,少的可以使用一个SDK函数就解决。

工程下载地址:点击打开链接

背景图片:

前景图片:

实现效果:

成功把星星画上去了。

先看下面的资料:

//如果一个单色位图向彩色位图转换,那么单色位图为1的部分(也就是白色部分),会转换为彩色位图的背景色,单色位图为0的部分(黑色部分),会转换为彩色位图的前景色。
//如果一个彩色位图向单色位图转换,那么彩色位图的背景色转换到单色位图中则为1(白色),其他的则转换为0(黑色)。
//当然上面的1和0都是指bit位的值,还有这些转换是在设备上下文间的块数据操作前就进行的。
//由于windows上所有的位图操作都是基于内存设备上下文的,所以我们还需要创建两个设备上下文分别用于存放源位图和“掩码”位图。
//位图在设备上下文之间块数据传递需要通过Biltblt实现,对于这个函数和关于bitblt的最后一个参数的光栅操作的具体含义的介绍,这里就不再赘述,具体可看MSDN上关于bilblt的描述。
//对一数据做两次异或操作,那么数据将恢复到原数据,跟没操作一样,透明贴图就是运用该原理。
/*核心代码:
dcImage.SetBkColor(crColour);
dcMask.BitBlt(0, 0, nWidth, nHeight, &dcImage, 0, 0, SRCCOPY);
pDC->BitBlt(x, y, nWidth, nHeight, &dcImage, 0, 0, SRCINVERT);
pDC->BitBlt(x, y, nWidth, nHeight, &dcMask, 0, 0, SRCAND);
pDC->BitBlt(x, y, nWidth, nHeight, &dcImage, 0, 0, SRCINVERT); 
*/
	CBitmap btfile;
	btfile.LoadBitmap(IDB_BITMAP1);
	BITMAP btinfo;
	btfile.GetBitmap(&btinfo); //获取源位图的大小等信息
	CClientDC dcClient(this); // 当前绘画DC,以下表述中可能会称之为“底图”
	CDC dcImage, dcMask; // 创建两个用于处理位图的兼容内存DC,MSDN指出图片处理需要在内存DC中进行
    // dcImage 该兼容DC用于处理源彩色位图,而dcMask用于生成跟源图对应的黑白掩码位图
	dcImage.CreateCompatibleDC(&dcClient); // 跟dcClient兼容
	dcImage.SelectObject(&btfile); // 将源彩色位图选人“源图处理dc”
	dcImage.SetBkColor(RGB(255, 255, 255));
    // 设置背景色,也就是源位图的透明色,假设为白色,我们一般做素材时,将背景做成白色的,
    // 在下面,生成掩码图时,跟背景相同的会转换为白色,其他颜色转换为黑色
	CBitmap btSingleColor; // 声明位图对象
	btSingleColor.CreateBitmap(btinfo.bmWidth, btinfo.bmHeight, 1, 1, NULL);// 设定该内存位图的大小,并设置为单色位图
	dcMask.CreateCompatibleDC(&dcClient); // 跟dcClient兼容
	dcMask.SelectObject(&btSingleColor); // 将黑白位图选人“掩码处理dc”
	dcMask.BitBlt(0, 0, btinfo.bmWidth, btinfo.bmHeight, &dcImage, 0, 0, SRCCOPY);
   //这里DC之间的块数据拷贝就用到了开头提到的知识点,从彩色位图向单色位图转换,
   //源位图的背景图拷到掩码DC中后变为白色,其他则为黑色
	dcClient.BitBlt(0, 0, btinfo.bmWidth, btinfo.bmHeight, &dcImage, 0, 0, SRCINVERT);
   // 源图跟底图进行异或
   // SRCINVERT:这个光栅操作码代表“异或”操作,SRCAND:代表“与”操作
   // 如果对一数据做两次异或操作,那么数据将恢复到原数据,跟没操作一样
   // 透明位图就是利用这个特性,将源位图异或的方式拷贝到显示DC两次
   // 但是异或拷贝两次的话,那么源位图将不显示,换种说法就是整张位图都被透明了
   // 这可不是我们想要的结果,我们的目的只是将背景色透明,所以我们还需要在这两次
   // 异或操作中加入其他操作来实现我们想要的结果,这时候就需要上面准备的“掩码”DC来帮忙了
	dcClient.BitBlt(0, 0, btinfo.bmWidth, btinfo.bmHeight, &dcMask, 0, 0, SRCAND);
   // 掩码DC内的单色位图跟上面的结果进行与操作,目的是将需要透明的地方保留不变,这样下次再次用源图异或时
   // 该部分就应为执行了两次异或而透明了;而最终结果中需要显示的部分,在本次掩码与操作中,
   // 应当为清除为0,即黑色,这样下次异或时,将会原封不动的显示出来。
   //掩码DC内的单色位图为1的部分,将会保留显示DC的原图,为0的部分将会清除对应区域为0,即为黑色
	dcClient.BitBlt(0, 0, btinfo.bmWidth, btinfo.bmHeight, &dcImage, 0, 0, SRCINVERT);
   // 最后在上面步骤的基础上,再次用源数据DC进行一下异或操作,
   // 最终的结果,需要透明的部分,源图因为执行了两次异或,而透明了,直接显示底图的图案
   // 需要显示的部分,因为是跟0异或,将得到完全的保留。
   // 异或操作:(0^1 = 1) (0^0 = 0)

也就是说用对两个图片进行比较复杂的位操作(数学不好,真心觉得很难)。

下面是我的实现,与上面的资料略有不同。

	//1 bk背景 fore前景 mask掩码
	CDC* dc = GetDC();
	CDC bk_DC,fore_DC,mask_DC;

	bk_DC.CreateCompatibleDC(dc);
	fore_DC.CreateCompatibleDC(dc);
	mask_DC.CreateCompatibleDC(dc);

	int j = SaveDC(bk_DC.m_hDC);
	int i = SaveDC(fore_DC.m_hDC);
	int k = SaveDC(mask_DC.m_hDC);

	CBitmap bk_bmp,fore_bmp;
	bk_bmp.LoadBitmap(IDB_BITMAP1);
	fore_bmp.LoadBitmap(IDB_BITMAP2);

	bk_DC.SelectObject(bk_bmp);
	fore_DC.SelectObject(fore_bmp);
	fore_DC.SetBkColor(fore_DC.GetPixel(2,2)); //设置背景颜色

	HBITMAP mask_bmp = CreateBitmap(512,384,1,1,NULL);
	mask_DC.SelectObject(mask_bmp);

	//彩色位图贴到单色位图,彩色位图的背景色变成白色(1),其他变成黑色(0)
	mask_DC.BitBlt(0,0,512,384,&fore_DC,0,0,SRCCOPY); 
//	dc->BitBlt(0,0,512,384,&mask_DC,0,0,SRCCOPY);  //白底黑字

	fore_DC.SetBkColor(RGB(0,0,0));   //设置背景颜色黑
	fore_DC.SetTextColor(RGB(255,255,255));  //设置前景颜色白
	//1(白) & x = x; 
	//0(黑) & x = 0; 背景(黑色)保留了
	fore_DC.BitBlt(0,0,512,384,&mask_DC,0,0,SRCAND);
//	dc->BitBlt(0,0,512,384,&fore_DC,0,0,SRCCOPY);  //背景变为黑,前景没变

	bk_DC.SetBkColor(RGB(255,255,255));  //设置背景颜色白
	bk_DC.SetTextColor(RGB(0,0,0));   //设置前景颜色黑

	bk_DC.BitBlt(0,0,512,384,&mask_DC,0,0,SRCAND);
//	dc->BitBlt(0,0,512,384,&bk_DC,0,0,SRCCOPY);

	bk_DC.BitBlt(0,0,512,384,&fore_DC,0,0,SRCPAINT);
	dc->BitBlt(0,0,512,384,&bk_DC,0,0,SRCCOPY);
	RestoreDC(fore_DC.m_hDC,i);
	RestoreDC(bk_DC.m_hDC,j);
	DeleteObject(mask_bmp);
	DeleteDC(bk_DC);
	DeleteDC(mask_DC);
	DeleteDC(fore_DC);

写完这个代码之后,还不是很理解,尤其是SetBkColor、SetTextColor两个函数与BltBit的SRCAND操作的地方。暂且做个标记吧。

其实,实现透明贴图还可以用MaskBlt和TransparentBlt。MaskBlt最后的参数比较复杂,我还没搞懂就先放过了。而TransParentBlt的话,简单来说就是最快捷方便的方式了。

TransParentBlt可以指定要透明的颜色。

	//2
	CDC* dc = GetDC();
	CDC bk_DC,fore_DC,mask_DC;

	bk_DC.CreateCompatibleDC(dc);
	fore_DC.CreateCompatibleDC(dc);

	int j = SaveDC(bk_DC.m_hDC);
	int i = SaveDC(fore_DC.m_hDC);

	CBitmap bk_bmp,fore_bmp;
	bk_bmp.LoadBitmap(IDB_BITMAP1);
	fore_bmp.LoadBitmap(IDB_BITMAP2);
	bk_DC.SelectObject(bk_bmp);
	fore_DC.SelectObject(fore_bmp);

	dc->BitBlt(0,0,512,384,&bk_DC,0,0,SRCCOPY);
	dc->TransparentBlt(0,0,512,384,&fore_DC,0,0,510,380,fore_DC.GetPixel(2,2));

	RestoreDC(fore_DC.m_hDC,i);
	RestoreDC(bk_DC.m_hDC,j);
	DeleteDC(bk_DC);
	DeleteDC(fore_DC);

是不是觉得比第一种方法简单了十几万倍!

最后,希望看到文章的朋友留下你宝贵的意见或者经验,不胜感激

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Vamei实验室

Python小题目 针对快速教程

作业的目的是帮助熟悉之前学习的内容:  1. 写一个程序,判断2008年是否是闰年。 写一个程序,用于计算2008年10月1日是这一年的第几天?(2008年1月...

1875
来自专栏Nian糕的私人厨房

腾讯课堂 IMWeb 七天前端求职提升营 Day 1

本次的系列博文主要是针对 腾讯课堂七天前端求职提升营 课程中,所推送的面试题目及编程练习的一次汇总,期间还包括三次直播课的分享,均由腾讯导师给大家讲解,该系列博...

353
来自专栏Crossin的编程教室

用 Python 跟自己下棋

今天,李世乭终于在与 AlphaGo 的人机大战中扳回一局。但计算机 AI 可以在围棋上战胜人类顶尖棋手的时代已经到来。可以预见,人工智能和机器人将会在更多领域...

2869
来自专栏JackieZheng

漫谈可视化Prefuse(六)---改动源码定制边粗细

可视化一路走来,体会很多;博客一路写来,收获颇丰;代码一路码来,思路越来越清晰。终究还是明白了一句古话:纸上得来终觉浅,绝知此事要躬行。   跌跌撞撞整合了个...

1828
来自专栏章鱼的慢慢技术路

CodeCombat地牢关卡Python代码

1588
来自专栏点滴积累

ANSJ中文分词使用方法

一、前言 之前做solr索引的时候就使用了ANSJ进行中文分词,用着挺好,然而当时没有写博客记录的习惯。最近又尝试了好几种JAVA下的中文分词库,个人感觉还是A...

3489
来自专栏PPV课数据科学社区

【工具】用R软件绘制中国分省市地图

【注】新版本的maptools包对很多函数进行了修改,对于修改的内容,文章中用红色的文字进行了说明。 鉴于最近有不少人在讨论用R软件绘制地图的问题,我也就跟着凑...

3069
来自专栏葡萄城控件技术团队

Visual Studio 2015速递(1)——C#6.0新特性怎么用

系列文章 Visual Studio 2015速递(1)——C#6.0新特性怎么用 Visual Studio 2015速递(2)——提升效率和质量(VS20...

1728
来自专栏数据小魔方

创意雷达图(Round Rador Chart)

今天给大家分享的图表是创意雷达图! ▽▼▽ 既然是创意雷达图,肯定是有难度的啦,单纯的雷达图太没有挑战了! 首先看成品,怎么样,还不错吧,想不想自己也做一个,如...

3835
来自专栏月色的自留地

Grapher--寂寞无名的神器

1637

扫描关注云+社区