使用 EasyX 实现半透明文本函数(改进版算法,带抗锯齿)
2025-2-13
(0)
一、说明
随波逐流的半透明文本输出函数,算法存在缺陷:
- 该算法的实际效果,其实是损失了不透明度文本在抗锯齿区域的那部分的像素点。
- 虽视觉上看似半透明,但放大查看时,输出文字与原本不透明文字相比,有缺失部分;缺失的正是原本文字的抗锯齿像素点。
- 输出的半透明文本观感违和,有强烈的割裂感与尖锐感,类似像素字。
该算法原理:
- 先将文字输出到图片对象。
- 进行简单图像比对处理,剥离并存储背景颜色与文本颜色。
- 利用存储的像素点数据,通过半透明算法,把原本不透明像素点,转为半透明像素点输出,从而实现简单文本半透明效果。
我的改进原理:
- 将文本写入图片对象时,把文本模式设置为背景透明输出。
- 这样就只能提取文本像素,同时也保留了,文本原有的抗锯齿那区域的像素点。
- 接着,将两者一起转化为半透明输出,既不损失文字清晰度,又避免了随波逐流算法的割裂感与尖锐感,达到与原本不透明文字的完美效果。
二、不透明文本、随波逐流半透明文本和改进版半透明文本,对比图像如下
原本不透明文本,效果:
随波逐流半透明文本,效果(输出效果不带抗锯齿,半透明度百分之 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;
}
添加评论
取消回复