浅析透明图片显示

1、理解图片构成

上面是一个飞机的透明图片,每个图片都是由一个个像素点构成的,每个像素点都是一个颜色,在内存中占4个字节,由透明度、红、绿、蓝构成。如下图:

该飞机图片飞机图片长51像素,宽63像素。就是说图片是有51×63个像素构成,在内存中占有51×63×4字节大小。

2、不透明图片显示

比如说我们有一个白色背景的画布,要在上面把飞机图片显示出来。首先我们要知道在哪开始画,就是指飞机图片针对白色背景的画布的相对坐标位置,也就是飞机图片要显示的左上角坐标,然后从该坐标开始向右数51个像素,向下数63个像素,将该范围内的所有像素点都替换为飞机相对应的像素点,这样飞机就显示出来了。
如下图:

我们把黑方框里的像素替换成飞机图片的像素,图片就显示出来了。

3、透明图片显示

每个像素点都有一个透明度(0~255),0是完全透明,255是完全不透明,其他值是半透明。显示透明图片有2种方法:
1)简单:取一个折中的透明度,比如说100,透明度大于100的我们就认为是不透明的,白色背景的画布相应位置的像素就替换成飞机图片像素,小于100的就认为是透明的,不替换,还是原来画布的像素,这样的话这块位置的颜色给用户的感觉就是透明了。
2)复杂:我们需要得到白色背景画布像素点的红、绿、蓝,还有飞机图片对应位置像素点的透明度、红、绿、蓝这些值。根据图片的透明度,用一个公式将画布像素点的红绿蓝与图片像素红绿蓝进行换算,得到新的像素点值,再将该像素点替换画布上的像素点即可。

4、内存中像素点位置对应

原理理解了,但内存中是如何把画布的像素点与图片的像素点位置对应上呢?
其实就是画布在内存中的指针是如何定位到图片左上角位置的,再简单一点理解就把指针当成像素点,画布最开始的指针是指向画布的左上角位置,要移动多少个像素点才能到达飞机图片的左上角那个像素点。
看图说话:

图片左上角的坐标(x,y)我们肯定知道,由于图片左上角的坐标是相对应与画布左上角的坐标而定位的。所以我们要在内存中把指针移动到该对应位置上。
即画布指针移动画布宽度×y+x即可。

#include <graphics.h>

// 绘制透明图片
void putimagepng(int dstX, int dstY, IMAGE* pSrcImg)
{
	DWORD* pGph = GetImageBuffer();				// 得到默认图形设备缓冲区指针,图片要绘制到该设备上(DWORD -> unsigned long)
	DWORD* pImg = GetImageBuffer(pSrcImg);		// 得到图片缓冲区指针
	int gphWidth = getwidth();					// 得到绘图区宽度
	int gphHeight = getheight();				// 得到绘图区高度
	int imgWidth = pSrcImg->getwidth();			// 得到图片宽度
	int imgHeight = pSrcImg->getheight();		// 得到图片高度

	// 计算图片真实大小与位置(图片可能有部分在绘图区外面,这部分必须舍去,否则会导致不可预知错误,因为我们要用图片某些位置的像素点替换绘图区某些位置的像素点,这位置要是在绘图区范围外就不是绘图区管理的内存范围了)
	/*
	图片是由多个像素点构成,类似下图
	 0  1  2  3  4  5  6  7  8  9   ->每一行有imgWidth个像素点
	10 11 12 13 14 15 16 17 18 19
	20 21 22 23 24 25 26 27 28 29
	30 31 32 33 34 35 36 37 38 39
	40 41 42 43 44 45 46 47 48 49
			  |
			该位置计算公式为(imgWidth * y + x = imgWidth * 5 + 4)
	*/
	int imgNewWidth = (dstX + imgWidth > gphWidth) ? (gphWidth - dstX) : imgWidth;		// 超出右边界
	int imgNewHeight = (dstY + imgHeight > gphHeight) ? (gphHeight - dstY) : imgHeight;	// 超出右边界
	if (dstX < 0)																		// 超出左边界
	{
		pImg += -dstX;					// 图片指针重新定位(向右移动-dstX列,即-dstX个像素点)
		imgNewWidth += dstX;			// 图片宽度变小
		dstX = 0;						// X坐标改为0
	}
	if (dstY < 0)																		// 超出上边界
	{
		pImg += (imgWidth * (-dstY));	// 图片指针重新定位(向下移动-dstY行,每一行有imgWidth个像素点)
		imgNewHeight += dstY;			// 图片高度变小
		dstY = 0;						// Y坐标改为0
	}

	// 将绘图区指针移动到图片区左上角位置
	pGph += gphWidth * dstY + dstX;

	// 图片所有像素点循序,随时根据透明度将绘图区的像素点替换成图片区的像素点,这就是透明图片实现的原理
	for (int y = 0; y < imgNewHeight; y++)
	{
		for (int x = 0; x < imgNewWidth; x++)
		{
			/*
			用GetImageBuffer()得到的缓冲区的像素点构成:
			1个像素点占4个字节(32bit),所以缓冲区大小=图片宽度*图片高度*4
			这4个字节具体含义:透明度 + 红色 + 绿色 + 蓝色,各占1字节,我们用16进制来表示这4个字节的像素点。
			比如说某像素点(int a) 透明度=58,红色=120,绿色=70,蓝色=142
			该像素点用16进制表示:3A78468E
			二进制表示:00111010 01111000 01000110 10001110
			如何得到透明度:将该二进制进行位右移(>>)24个位置,左边补零
			int b = a >> 24 得到 00000000 00000000 00000000 00111010
			printf("%d\n",b); 得到58
			如何得到红色:首先我只要红色,其他的值都得变为0,这就得先进行与运算(&)
			我们常用的条件与是针对字节的(&&),比如说if(1==1 && 2==3),二者全为真结果才是真,否则返回假。
			这里针对位的与条件(&)也是一样道理.
			1 & 1 => 1
			1 & 0 => 0
			0 & 0 => 0
			因此针对上面像素a的二进制来说,其他位置为0,红色部分为1进行与计算即可。
			代码:
			int red = (a & 0x00ff0000) >> 16;
			printf("%d\n",red); 得到120
			二进制与运算解析
			a -> 00111010 01111000 01000110 10001110
			   & 00000000 11111111 00000000 00000000
			结果: 00000000 01111000 00000000 00000000
			再将此结果右移16位,左边补零,得到
				  00000000 00000000 00000000 01111000
			红色值就得到了。
			*/
			int a = pImg[x] >> 24;	// 得到图片该像素点得透明度(0-完全透明,其他-部分透明,255-完全不透明)
			if (a > 100)				// 这里随便定义一个透明度,超过它就进行替换,图片透明效果只能说够用了,要说特别完美那这种方法肯定不行,必须根据透明度将双方的红绿蓝值再进行一番计算才行,即用贝叶斯定理来进行点颜色的概率计算,那就有点复杂了。
			{
				pGph[x] = pImg[x];	// 将绘图区的像素点替换成图片区的像素点来实现透明效果
			}
		}
		// 指针指向下一行
		pGph += gphWidth;
		pImg += imgWidth;
	}
}


int main()
{
	initgraph(480, 600);

	// 绘制背景图片
	IMAGE imgBG;
	loadimage(&imgBG, "./images/background.jpg");	// vs的项目属性中改字符集为【使用多字节字符集】
	putimage(0, 0, &imgBG);

	// 绘制飞机(不透明)
	IMAGE imgPlane;
	loadimage(&imgPlane, "./images/plane.png");
	putimage(100, 50, &imgPlane);

	// 绘制飞机(透明)
	putimagepng(200, 50, &imgPlane);

	system("pause");

	closegraph();

	return 0;
}

效果如图:

备注:这里用的是easyx图形库,该软件要求C++,开发软件用的是VS,项目属性中的字符集改为【使用多字节字符集】。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值