Warning: file_get_contents(https://whois.pconline.com.cn/jsLabel.jsp?ip=127.0.0.1) [function.file-get-contents]: failed to open stream: HTTP request failed! HTTP/1.1 503 Service Temporarily Unavailable in D:\wwwroot\huidong\wwwroot\function.inc.php on line 884
赏析村长《艺术字系列:冰封的 EasyX》实现原理 - huidong

huidong

首页 | 会员登录 | 关于争取 2022 寒假做出汇东网 Ver3.0.0 !
搜索文章


这篇文章是我在学校机房写的,时间仓促,简单写一下。


首先放出村长的博客链接《艺术字系列:冰封的 EasyX》:https://codebus.cn/yangw/word-art-freeze 


这个艺术字的原理其实很简单:

首先绘制一段 "EasyX" 字符串到 IMAGE 中,接着收集 IMAGE 中所有有颜色的点,统计这些点的数量,然后将这些坐标存入数组。

接下来还需要生成相同长度的一个 POINT 类型的数组,为这个数组初始化以完全随机的坐标,坐标的范围在目标输出图像的大小范围内(640x480)。

讲到这里大家应该就明白了,之所以要生成一个随机数数组,就是为了将每个有色值的点和随机点进行连线,这样就可以获得文字向外发光的效果,这段“连线”的代码就是 main 函数中的那个 for 循环。

值得注意的是,村长的这段连线是以 x+=2 作为坐标位移的,这样可以突出光的密集和松散的区域,发光效果会更好;但如果直接 i++,就会导致“光线”太密集,无法突出文字主体;而如果 i+=3,则会导致文字整体太松散,不过由于在 main 函数中调用了 Blur 函数渲染了模糊效果,所以他们的最终效果差别并不会很大。

由于单纯的连线是十分生硬的,并且没有颜色强弱的变化,所以 Blur 函数在光感的渲染上起到了很重要的作用。


我觉得这个发光艺术字的实现方式很不错,之前我都觉得要以文字点为中心来向外发散“光芒”,而随机生成点再和文字主体连线的方式让我豁然开朗。


关于模糊效果的原理和实现,可以参考:

https://codebus.cn/yangw/free-dots 

https://codebus.cn/zhaoh/blur-filter 



新增边缘点筛选的代码(dev-cpp 编写):

#include <graphics.h>
#include <conio.h>
#include <time.h>


// 定义全局变量
POINT *g_pDst;		// 点集(目标)
POINT *g_pSrc;		// 点集(源)
int g_nWidth;		// 文字的宽度
int g_nHeight;		// 文字的高度
int g_nCount;		// 点集包含的点的数量

// 是否为边缘点
bool isEdgePoint(int x, int y)
{
	POINT t[4] = { {0,1}, {0,-1}, {1,0}, {-1,0} };
	//POINT t[4] = { {0,2}, {0,-2}, {2,0}, {-2,0} };
	if (getpixel(x, y) != WHITE)
	{
		return false;
	}
	for (int i=0; i<4; i++)
	{
		if (getpixel(x+t[i].x, y+t[i].y) != WHITE)
		{
			return true;
		}
	}
	return false;
}

// 获取目标点集
void GetDstPoints()
{
	// 设置临时绘图对象
	IMAGE img;
	SetWorkingImage(&img);

	// 定义目标字符串
	TCHAR s[] = _T("EasyX");

	// 计算目标字符串的宽高,并调整临时绘图对象的尺寸
	setcolor(WHITE);
	setfont(100, 0, _T("Arial"));
	g_nWidth = textwidth(s);
	g_nHeight = textheight(s);
	Resize(&img, g_nWidth, g_nHeight);

	// 输出目标字符串至 img 对象
	outtextxy(0, 0, s);

	// 计算构成目标字符串的点的数量
	int x, y;
	g_nCount = 0;
	for(x = 0; x < g_nWidth; x++)
		for(y = 0; y < g_nHeight; y++)
			if (isEdgePoint(x, y))
				g_nCount++;

	// 计算目标数据
	g_pDst = new POINT[g_nCount];
	int i = 0;
	for(x = 0; x < g_nWidth; x++)
		for(y = 0; y < g_nHeight; y++)
			if (isEdgePoint(x, y))
			{
				g_pDst[i].x = x + (640 - g_nWidth) / 2;
				g_pDst[i].y = y + (480 - g_nHeight) / 2;
				i++;
			}

	// 恢复对屏幕的绘图操作
	SetWorkingImage(NULL);
}


// 获取源点集
void GetSrcPoints()
{
	// 设置随机种子
	srand((unsigned int)time(NULL));

	// 设置随机的源数据
	g_pSrc = new POINT[g_nCount];
	for(int i = 0; i < g_nCount; i++)
	{
		g_pSrc[i].x = rand() % 640;
		g_pSrc[i].y = rand() % 480;
	}
}


// 全屏模糊处理(忽略屏幕第一行和最后一行)
void Blur(DWORD* pMem)
{
	for(int i = 640; i < 640 * 479; i++)
	{
		pMem[i] = RGB(
		              (GetRValue(pMem[i]) + GetRValue(pMem[i - 640]) + GetRValue(pMem[i - 1]) + GetRValue(pMem[i + 1]) + GetRValue(pMem[i + 640])) / 5,
		              (GetGValue(pMem[i]) + GetGValue(pMem[i - 640]) + GetGValue(pMem[i - 1]) + GetGValue(pMem[i + 1]) + GetGValue(pMem[i + 640])) / 5,
		              (GetBValue(pMem[i]) + GetBValue(pMem[i - 640]) + GetBValue(pMem[i - 1]) + GetBValue(pMem[i + 1]) + GetBValue(pMem[i + 640])) / 5);
	}
}


// 主函数
int main()
{
	// 初始化
	initgraph(640, 480);				// 创建绘图窗口看
	DWORD* pBuf = GetImageBuffer();		// 获取显示缓冲区指针
	GetDstPoints();						// 获取目标点集
	GetSrcPoints();						// 获取源点集

	// 运算
	int x, y;
	for (int i = 2; i <= 256; i += 2)
	{
		COLORREF c = RGB(i-1, i-1, i-1);
		Blur(pBuf);						// 全屏模糊处理

		for (int d = 0; d < g_nCount; d++)
		{
			x = g_pSrc[d].x + (g_pDst[d].x - g_pSrc[d].x) * i / 256;
			y = g_pSrc[d].y + (g_pDst[d].y - g_pSrc[d].y) * i / 256;
			pBuf[y * 640 + x] = c;		// 直接操作显示缓冲区画点
			//putpixel(g_pDst[d].x, g_pDst[d].y, WHITE);
		}

		Sleep(10);						// 延时
	}

	// 清理内存
	delete g_pDst;
	delete g_pSrc;

	// 按任意键退出
	getch();
	closegraph();
}


注:改变 isEdgePoint 函数中的筛选网格,可以获取不一样的效果,很好玩的。比如:

POINT t[4] = { {0,1}, {0,0}, {2,0}, {0,0} };

这样可以有 3D 文字效果~


image.png





返回首页


Copyright (C) 2018-2024 huidong