如何实现一个词云 (3)

下图为放置"L"单词时的简单示意图,左侧为board数组,右侧为单词的sprite数组。首先需要根据文字布局函数找到要放置的点。如第一个点根据布局函数,在画布的中心。

如何实现一个词云

红点为尝试在画布中放置的位置,根据放置的坐标与文字的宽高等信息,可以计算出board中对应像素范围(绿色框内),遍历sprite数组,将sprite中的像素与board绿色框中的像素一一做比较,若结果为两者不同时为1,则不重叠。显然,在下面的图中单词"L"与画布中已存在的"H"有重叠,则"L"不能放置在红点处,调用布局函数寻找下一个放置点尝试。

如何实现一个词云

经过多次尝试失败后找到下图红点位置,经过对比发现没有重叠,则认为红点处可以放置下单词"L",更新"L"单词的最终绘制坐标x, y。

如何实现一个词云

更新"L"坐标信息后,意味着单词"L"已经确定在画布最终绘制时的位置,这时将"L"的像素占用信息更新到board数组中。随后开始进行下一个单词的放置尝试,直到所有单词放置完毕。

像素数据的存储方式

由于画布上的像素点是二维信息,而我们使用一维数组进行存储,所以在保存时需要记录宽度信息,即几个元素为一行,用以还原它在二维画布上的位置信息,使用1表示已占用,0表示未占用。

以一个"L"字母为例,假设"L"单词的包围盒宽为8,高为11,则可以新建一个长度为 88 (8 * 11)的一维数组来存储像素占用情况,同时记录该单词宽度为8。如下图:

如何实现一个词云

此时检测一次的时间复杂度为:$$wordWidth * wordHeight$$

使用二进制存储像素信息

一个canvas画布上的像素点数量可能非常庞大,如一个分辨率为1,500 * 500的画布上,有250000个像素点,如果使用一个整数存储一个像素信息的方法,需要一个长度为250000的数组来存储整个画布的信息。操作一个大数组会导致内存占用变大,同时遍历效率也会变低。

在碰撞检测的场景中,对于每一个像素,我们只需要记录"已占用"和"未占用"两种情况。这两种状态可以使用二进制的1和0来表示,因此我们可以使用整数表示一个32位的二进制数,其中1表示已占用,0表示未占用。

对于500 * 500的画布,只需要一个长度为7812的数组即可保存。以同一个"L"字母为例,优化后只需要一个长度为8的数组就可以存储下单词"L"的sprite信息。如下图:

如何实现一个词云

此时放置检测的时间复杂度为:$$wordWidth * wordHeight / 32$$

可视化查看像素信息

为了更直观的观察数组中存储的像素占用情况,编写一个printPixelArray函数来将数组中的数值以二维的形式打印出来。

数组打印函数实现如下:

/** * 打印像素占用数组 * @param {*} board * @param {*} w * @returns */ export const printPixelArray = (board, w) => { let bitStr = '' let intStr = '' for (let i = 0; i < board.length / w; i++) { for (let j = 0; j < w; j++) { bitStr += `${(board[i * w + j] >>> 0).toString(2).padStart(32,'0')}|` intStr += `${board[i * w + j].toString(10).padEnd(32)}|` } // 整数格式 bitStr += '\n' // 二进制格式 intStr += '\n' } return { bitStr, intStr } }

以单词"螺狮粉"为例,下图是将"螺蛳粉"的sprite数组的值打印出来的结果,根据单词的宽度进行换行,每个整数之间使用|分割。可以看到一维数组已经还原成了二维的平面,"螺蛳粉"一行使用六个整数来记录像素信息。

如何实现一个词云

将整数转换为二进制的格式进行显示,可以更直观地观察到像素的占用情况。

如何实现一个词云

将整数转换为二进制可以清楚的看到每个像素的占用情况。然而由于字符串占据面积太大,不方便整体调试,所以我们再编写一个paint函数来将数组中的像素占用情况绘制到一个等比例的canvas中。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zgjjpy.html