使用 EasyX 实现半透明文本函数(改进版算法,带抗锯齿)

一、说明

随波逐流的半透明文本输出函数,算法存在缺陷:

  1. 该算法的实际效果,其实是损失了不透明度文本在抗锯齿区域的那部分的像素点。
  2. 虽视觉上看似半透明,但放大查看时,输出文字与原本不透明文字相比,有缺失部分;缺失的正是原本文字的抗锯齿像素点。
  3. 输出的半透明文本观感违和,有强烈的割裂感与尖锐感,类似像素字。

该算法原理:

  1. 先将文字输出到图片对象。
  2. 进行简单图像比对处理,剥离并存储背景颜色与文本颜色。
  3. 利用存储的像素点数据,通过半透明算法,把原本不透明像素点,转为半透明像素点输出,从而实现简单文本半透明效果。

我的改进原理:

  1. 将文本写入图片对象时,把文本模式设置为背景透明输出。
  2. 这样就只能提取文本像素,同时也保留了,文本原有的抗锯齿那区域的像素点。
  3. 接着,将两者一起转化为半透明输出,既不损失文字清晰度,又避免了随波逐流算法的割裂感与尖锐感,达到与原本不透明文字的完美效果。

二、不透明文本、随波逐流半透明文本和改进版半透明文本,对比图像如下

原本不透明文本,效果:

随波逐流半透明文本,效果(输出效果不带抗锯齿,半透明度百分之 50):

改进版半透明文本,输出效果带抗锯齿(半透明度百分之 50):

随波逐流半透明文本与改进版半透明文本,对比(半透明度百分之 50):

三、改进版半透明文本函数使用方式

示例代码:

	
	……

	// 第一步打开半透明缓冲区绘图(必需要这样,否则会塴)。
	alpha.g_pBuf11 = GetImageBuffer();
	
	// 第二步设置你需要的透明度(60 为百分之六十的文本透明度)。
	alpha.setalpha1(60);

	// 第三步使用半透明文本函数(半透明文本函数输出,参数对应:文本 x 坐标,文本 y 坐标,要输出的文本,输出文本颜色,输出文本大小。)
	alpha.outtext(0, 0, _T("EasyX 半透明文本"), BLACK, 100);
	alpha.outtext(0, 150, _T("	(半透明度 60%)"), RED, 100);

	……

代码截屏效果:

四、改进版半透明文本源码(完整版输出效果带抗锯齿)

// 改良一个 EasyX 绘图库的半透明文字输出函数。让你的绘图文字支持任意的透明度,达到让大家推波推润的效果。
// 日期:2023-7-21
// 原作者:随波逐流
// 原作者联系:2963787923@qq.com
// 改良作者:窝补药开学 QQ 1655897146@qq.com

#include <graphics.h>
#include <stdio.h>
#include <conio.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>

// 半透明文本函数类封装。
class alpha
{
		// 私有变量。
	private:

		// 绘图窗口大小。
		int WIDTH2 = 0, HEIGHT2 = 0;
		// 半透明文本高宽大小。
		int w = 0, h = 0;

		// 存入字体的像素值。
		int tex[2000][2000][1] = { 0 };
		COLORREF rgb1[2000][2000][10] = { 0 };
		COLORREF rgb2[2000][2000][10] = { 0 };

		// 公用变量。
	public:

		// 初始换透明度为百分比 50。
		int alpha1 = 50;
		// 设置半透明度函数。
		void setalpha1(int al)
		{
			alpha1 = al * 2.56;
		}

		// 绘图缓冲区。
		DWORD *g_pBuf11;

		/* 设置字体大小的函数 */
		int settextzise(int zise)
		{
			LOGFONT f;
			gettextstyle(&f);									// 获取当前字体设置。
			f.lfHeight = zise;									// 设置字体高度为 48。
			_tcscpy_s(f.lfFaceName, _T("黑体"));
			f.lfQuality = ANTIALIASED_QUALITY;					// 设置输出效果为抗锯齿。
			settextstyle(&f);
			return zise;
		}

		// 半透明字体输出函数 X_Y 字体坐标,str 输入的字符患,rgb 字体颜色,size 字体大小。
		void outtext(int x, int y, LPCTSTR str, const COLORREF rgb, int size)
		{

			IMAGE img(textwidth(str), textheight(str));
			settextzise(size);

			w = textwidth(str);
			h = textheight(str);

			getimage(&img, x, y, w, h);

			// 把文字写入图片对像中。
			SetWorkingImage(&img);
			// 设置背景透明文本。
			setbkmode(TRANSPARENT);
			// 设置字体大小。
			settextzise(size);

			settextcolor(rgb);
			// 把文字绘制在图片里。
			outtextxy(0, 0, str);
			// 将 img 对象显示在绘图窗口中。
			SetWorkingImage();

			// 用图片缓冲区打开文字。
			DWORD* pMem = GetImageBuffer(&img);

			// 获取图片文字的高宽。
			int WIDTH1 = img.getwidth();
			int HEIGHT1 = img.getheight();

			// 读取图片中无背景的文字。
			for (int i = 0; i < WIDTH1; i++)
			{
				for (int j = 0; j < HEIGHT1; j++)
				{
					int xy = j * WIDTH1 + i;
					if (xy < 0 || xy >= (WIDTH1 * HEIGHT1))
					{
						return ;
					}

					int r = GetRValue(pMem[xy]);
					int g = GetGValue(pMem[xy]);
					int b = GetBValue(pMem[xy]);

					rgb1[i][j][1] = RGB(r, g, b);

					COLORREF c = BGR(rgb1[i ][j ][1]);
					WIDTH2 = getwidth(), HEIGHT2 = getheight();

					int puti = 0;

					if (i + x < WIDTH2)

						puti = (j + y ) * WIDTH2 + (i + x);
					// 如果 puti 超出缓冲区范围,直接返回。
					if (puti < 0 || puti >= (WIDTH2 * HEIGHT2))
					{
						continue ;
					}

					COLORREF backdropcolor, bkcolor = getbkcolor();
					// 读取背景上的颜色点。
					COLORREF c2 = 0;
					if ((i + x) < WIDTH2)
						c2 = g_pBuf11[(j + y) * WIDTH2 + (i + x)];

					backdropcolor = BGR(c2);
					// 声明定义背景颜色和绘图的颜色。

					int r2 = GetRValue(c), g2 = GetGValue(c), b2 = GetBValue(c),
						r1 = GetRValue(backdropcolor), g1 = GetGValue(backdropcolor), b1 = GetBValue(backdropcolor);

					// 如果获取的颜色等于背景色不进行透明计算。
					if (bkcolor == backdropcolor)
					{
						g_pBuf11[puti] = BGR(c);
					}
					// 透明颜色混合并输出。
					if (backdropcolor != bkcolor)
					{

						int	alp = alpha1;
						g_pBuf11[puti] =
										BGR
										(
												RGB
												(
													(r2 *alp + r1 * (256 - alp)) >> 8,
													(g2 *alp + g1 * (256 - alp)) >> 8,
													(b2 *alp + b1 * (256 - alp)) >> 8
												)
										);
					}
				}
			}
		}

} alpha;


class RandomMotion
{

		// 私有变量。
	private:

		// 公用变量。
	public:
		struct linexy
		{
			// 存放一条直线的绘制过程的 x_y 坐标二次使用。
			int line_xy2[9][3000][2] = { 0 };
		} lixy;

		// 缓冲区画点。
		void putpixelqBuffer(DWORD *pibu, int x, int y, int w, int h, COLORREF color)
		{
			long zb = (h *y), zb1 = (w - x), ab2 = zb - zb1;
			pibu[abs(-ab2)] = BGR(color);
		}

		// 使用中点算法画任意斜率的开放式直线(包括起始点,不包括终止点,同时返回该直线的所有 X_Y 坐标与线长度)。
		int line_s1(int x1, int y1, int x2, int y2, int len[][2])
		{
			int w1 = getwidth(), h1 = getheight();
			if (x1 >= w1 || x2 >= w1 || x1 <= 0 || x2 <= 0 || y1 >= h1 || y2 >= h1 || y1 <= 0 || y2 <= 0)
				return 0;

			DWORD* p = GetImageBuffer();
			int x = x1, y = y1;
			int a = y1 - y2, b = x2 - x1;
			int cx = (b >= 0 ? 1 : (b = -b, -1));
			int cy = (a <= 0 ? 1 : (a = -a, -1));
			int ii = 0, iii = 0;
			int d, d1, d2;

			if (-a <= b)
			{

				// 斜率绝对值 <= 1。
				d = 2 * a + b;
				d1 = 2 * a;
				d2 = 2 * (a + b);

				while (x != x2)
				{
					putpixelqBuffer(p, len[ii][0] = x, len[ii][1] = y, w1, h1, RED);
					ii++;
					if (d < 0)
						y += cy, d += d2;
					else
						d += d1;
					x += cx;
				}
				return ii;
			}
			else
			{

				// 斜率绝对值 > 1。
				d = 2 * b + a;
				d1 = 2 * b;
				d2 = 2 * (a + b);

				while (y != y2)
				{
					putpixelqBuffer(p, len[iii][0] = x, len[iii][1] = y, w1, h1, RED);
					iii++;
					if (d < 0)
						d += d1;
					else
						x += cx, d += d2;
					y += cy;
				}
				return iii;
			}
		}

		// 多做线 X_Y 容器。
		int line_xy[6][3000][2] = { 0 };

		// 开放式多段线绘图函数,通过指定 5 点坐标,返回 5 点坐标上的所有 X_Y 坐标与多段线长度。
		int pl(int l, int p[], int line_xy1[][2])
		{
			for (int g = 0; g < 6; g++)
				for (int i = 0; i < 3000; i++)
				{
					for (int j = 0; j < 2; j++)
					{
						line_xy[g][i][j] = 0;
					}
				}
			// tr 是奇数偶数开关
			int tr = 0;
			// 是五段线分别每一段直线的长度容器。
			int s[10][1] = { 0 };
			// 累加
			int iii = 0;
			// 累加

			// 特殊法配合奇偶开关绘制多段线
			for (int i = 0; i < l * 2; i++)
			{
				// 奇偶开关
				if ((tr % 2) == 0)
				{
					// 核心绘制坐标点的线段并依次返回坐标与线长度
					s[iii][1] = line_s1(p[i], p[i + 1], p[i + 2], p[i + 3], line_xy[iii]);
					iii++;
					tr++;
				} else if ((tr % 2) != 0)
				{
					tr++;
				}
			}
			// 绘制末段线。
			s[4][1] = line_s1(p[l * 2 - 2], p[l * 2 - 1], p[0], p[1], line_xy[4]);

			// 把三维数组里的五段线 X_Y 坐标存入二级数组。
			int ss = 0;
			for (int g = 0; g < iii + 1; g++)
				for (int i = 0; i < s[g][1]; i++)
				{
					for (int j = 0; j < 2; j++)
					{
						line_xy1[ss][j] = line_xy[g][i][j];
					}
					ss++;
				}
			// 把五段线长度统一成完整的多线线长度。
			int ssg = 0;
			for (int i = 0; i < iii + 1; i++)
			{
				ssg = ssg + s[i][1];
			}
			// 返回多段线长度。
			return ssg;
		}

} randommotion;

// 主函数
int main()
{

	// 随机种子。
	srand((unsigned)time(0));
	// 创建绘图窗口

	initgraph(800, 800, 1);
	// 全局缓冲区

	// 打开半透明缓冲区绘图。
	alpha.g_pBuf11 = GetImageBuffer();
	// 设置半透明文本半透明度百分之 50.
	alpha.setalpha1(50);

	// 半透明文本组
	TCHAR str[20][100] =
	{
		_T("EasyX"), _T("绘图库"), _T("半透明"), _T("字体展示"), _T("outtext"), _T("EasyX 绘图库"),
		_T("半透明字体"), _T("!@#$%^^&*()_+{}:"), _T("字体展示"), _T("半透明"), _T("(=^ ^=)"),
		_T("绘图库"), _T("EasyX"), _T("EasyX"), _T("绘图库"), _T("半透明"), _T("字体展示"), _T("EASYX 半透明字体"),
	};

	// 颜色组。
	COLORREF ys[20] =
	{
		{0x4d5562}, {0xe05888}, {0xd8c4c5}, {0xd8c4c5}, {0x473f78}, {0xfef9f0}, {0xd8c4c5}, {0xe16996}, {0xd8c4c5},
	};

	// 容纳透明方格背景图片变量。
	IMAGE IM(800, 800);

	// 使用奇偶开关来绘制透明背景。
	for (int i = 0; i <= 100; i++)
	{
		for (int j = 0; j <= 100; j++)
		{

			// 奇偶开关,偶数绘制灰格了。
			if (((i + j) % 2) == 0)
			{
				// 绘制灰格。
				setfillcolor(RGB(204, 204, 204));

				fillrectangle(i * 8, j * 8, i * 8 + 8, j * 8 + 8);
			}
			// 奇偶开关,奇数绘制白格了。
			else if (((i + j) % 2) != 0)
			{
				// 绘制白格。
				setfillcolor(WHITE);
				fillrectangle(i * 8, j * 8, i * 8 + 8, j * 8 + 8);
			}
		}
	}
	getimage(&IM, 0, 0, 800, 800);

	IMAGE IM2;

	ExMessage m;
	// 可以自己在桌面准备图像,来展示半透明效果。
	loadimage(&IM2, _T("C:\\Users\\Administrator\\Desktop\\2.jpg"));


	// 开启批量绘图防闪。
	BeginBatchDraw();


	while (true)
	{

		// 设置半透文字的的随机运动轨迹坐标。
		for (int i = 0; i < 9; i++)
		{

			int pts[10] = { 0 };
			for (int j = 0; j < 10; j++)
			{
				pts[j] = rand() % 800 + 1;
			}
			randommotion.pl(5, pts, randommotion.lixy.line_xy2[i]);
		}


		// 绘制半透明的文字输出。
		for (int i = 0; i < 2000; i++)
		{

			// 输出国际象棋黑白背景图片。
			putimage(0, 0, &IM);

			// 输出啊然背景图片。
			putimage(0, 200, &IM2);

			for (int j = 0; j < 9; j++)
			{
				// 绘制随机运动的半透明文本。
				alpha.outtext(randommotion.lixy.line_xy2[j][i][0], randommotion.lixy.line_xy2[j][i][1], str[j], ys[j], 80);
			}

			// 半透明文本函数输出,参数对应:文本 x 坐标,文本 y 坐标,要输出的文本,输出文本颜色,输出文本大小。

			alpha.setalpha1(60);	// 设置透明度为百分比 60。
			alpha.outtext(0, 0, _T("EasyX 半透明文本(半透明度 60%)"), RED, 50);

			alpha.setalpha1(50);	// 设置透明度为百分比 50。
			alpha.outtext(2, 20 * 3, _T("璃蔪切裁刀(半透明度 50%)"), BLUE, 65);

			alpha.setalpha1(40);
			alpha.outtext(0, 25 * 3 + 60, _T("稀波光子暗落流(半透明度 40%)"), 0xa20537, 55);

			alpha.setalpha1(30);
			alpha.outtext(0, 25 * 3 * 2 + 45, _T("矢恋颖惯枪(半透明度 30%)"), 0xb167ae, 65);

			alpha.setalpha1(30);
			alpha.outtext(0, 25 * 3 * 2 + 45 + 75, _T("堂莎辐烮格灵花(半透明度 30%)"), 0xa20537, 50);

			alpha.setalpha1(25);
			alpha.outtext(0, 25 * 3 * 2 + 45 + 75 + 40 * 3, _T("啊然很可爱~~!!"), 0xa20537, 100);
			alpha.setalpha1(25);
			alpha.outtext(0, 25 * 3 * 2 + 45 + 75 + 40 * 3 * 2, _T("(半透明度 25%)"), 0xa20537, 100);
			setbkmode(TRANSPARENT);
			settextcolor(BLACK);
			alpha.settextzise(50);
			outtextxy(70, 25 * 3 * 2 + 45 + 75 + 40 * 3 * 3, _T("这是一段不透明文本用来作对比"));

			// 获取鼠标移动消息与键盘按钮消息。
			peekmessage(&m, EX_MOUSE | EX_KEY, true);

			switch (m.message)
			{
				case WM_MOUSEMOVE:

					// 鼠标移动的时候带着半透明文本移动。
					alpha.setalpha1(60);
					alpha.outtext(m.x, m.y, _T("啊然真可爱~!"), RGB(255, 21, 138), 100);
					alpha.outtext(m.x, m.y + 100, _T("(透明度 60)"), RGB(155, 121, 238), 100);


					break;

				case WM_KEYDOWN:
					// 按 ESC 键退出程序
					if (m.vkcode == VK_ESCAPE)
						return 0;
			}
			// 刷新批量绘图图像。
			FlushBatchDraw();
			// 清除前一帧图像。
			cleardevice();
		}
	}

	// 结束缓冲区绘图。
	EndBatchDraw();
	// 接任意键结束程序。
	getmessage(EX_CHAR);
	// 关闭绘图窗口。
	closegraph();

	return 0;
}

添加评论