画布与词云
图像是压缩预算,词云数的是字频
画布上的预算与词云的谎言
图像能被显示、能被分享,靠的是压缩,压缩的代价落在哪里,哪种图像风格就在哪里长出来。另一批被归入创意的工具则一个都不生产内容,只生产结构,而结构一旦算错——比如中文词云按字频冒充词频——图越好看,误导越深。这篇把两条线放在一起看:前一半是画布上的预算怎么花,后一半是工具产出的结构怎么骗人。
图像风格背后的压缩预算
一张 4000×3000 的照片有 1200 万个像素,每像素 3 个字节,一共 36MB,它能显示、能被分享,全靠压缩。像素画、8 位调色板、GIF、ASCII 艺术被笼统归为复古,技术上其实是同一个动作的不同变体:主动选一种限制,然后在限制里做最优解。
工具入口散在像素画编辑器、ASCII 艺术字、GIF 制作、图片工具、NES 调色板、颜色工具、万花筒几处。
一张图有多少信息
位图的信息量是 位,RGB 各 8 位。压缩只有三条路,而且它们互斥地对应三种美学。
空间冗余。相邻像素常常相似,用差分加熵编码压掉。PNG 走这条路,无损,风格中性。色数。把 1600 万色砍成 256 色、16 色、4 色,损失的是渐变,得到的是块面感。频域有损。JPEG 把人眼不敏感的细节直接扔掉,尤其色度,损失的是锐度,得到的是轻。
像素艺术是主动选第二条加第一条。它不追求看起来像照片,它追求在色数极低的条件下仍然清晰。这个目标反过来决定了画法。
像素画,网格就是坐标系
像素画编辑器的核心状态只有一个二维颜色数组,画布尺寸可选,最终按当前网格尺寸导出 PNG。实现里有个细节很能说明问题,显示时的像素尺寸按 计算。网格越密,单格越小,但整个画布在屏幕上的物理大小几乎恒定。
这保证了 8×8 和 64×64 的画看起来一样大。你评估的是构图,不是画布尺寸。
真正的约束在这里,低分辨率下没有半像素可用。于是所有高阶技巧都退化成明度控制。
明度阶梯,不用色相区分形体,用三四级明度区分,因为色相在 16px 内根本分辨不出来。轮廓线,外轮廓不用纯黑,用主体色的深色版,这样轮廓会融进去而不显脏。抗锯齿是奢侈品,平滑边缘要额外颜色,16 色里拿不出来,于是锯齿本身就是风格的一部分。
换句话说,像素画的训练价值不是练手稳,是练取舍。你必须在动手之前想清楚,这块画布最多能承载几个信息层级。
NES,每屏 25 色是怎么来的
NES 调色板列的是 2C02 PPU 的主调色板,64 项索引,去重后 55 个不同颜色,其中还有一项是比黑更黑,在复合视频上几乎看不见。
很多人以为这就意味着 NES 能显示 55 色。完全不是。
硬件真正的限制是这样。一整屏只能选 1 个全局背景色。背景有 4 组调色板,每组 3 色,加上全局色共 13 色。精灵有 4 组调色板,每组 3 色,加上透明共 12 色。
所以同屏最多 色,而且每组只有 3 个自由位置。这才是 NES 风格的技术内核。不是低饱和的绿红蓝,是调色板槽位极度稀缺。
理解了这一点,两个现象就能解释。为什么 NES 游戏的角色常常换色即是新敌人,因为复用同一套精灵调色板,只改索引。为什么背景的水面、天空、地面必须共享那 3 个颜色,因为同屏同调色板。
美术风格是硬件预算的可视化结果,不是审美选择。
Game Boy,四档灰阶和无背光
Game Boy 滤镜做的事比 NES 更极端:把任意图片的每个像素按亮度映射到 DMG 屏的四档绿灰阶,原理是最近邻量化,全程不做抖动,渐变被硬切成一条条色带。
这套限制即美学的完整推导——亮度权重公式、四档 hex 调色板、为什么刻意不做抖动、以及 BT.709 与 BT.601 两套权重的对照——都写在复古信号那篇的 Game Boy 节,此处不重复。放在这张预算表里只为记一笔:色数从 NES 的 55 个索引一路压到 4 档,是这张表上压得最狠的一格。
GIF,256 色、LZW 和那个 216 色立方
GIF 制作把多张图片串成动图,看着只是循环播放,实际碰上了图像编码里最老的一套工程,索引色。
GIF 的格式约束只有两条,但都很硬。一帧最多 256 色,编码用 LZW。本站的实现是一个手写的 GIF89a 编码器,处理流程能清楚看到三步。
建调色板,不做中位切分那类自适应量化,取一个均匀的 色立方,每个通道取 0、51、102、153、204、255 六档。逐像素量化,对每个像素在 216 色里找欧氏距离最近的一格,写入索引。LZW 编码加帧延迟,延迟字段的单位是百分之一秒,所以 20ms 会被写成 2。
这三个选择各自有代价。
均匀立方体简单、可预测,但对渐变极不友好,天空和皮肤这类低对比区域会明显分层。自适应量化把颜色集中到实际出现的区域,效果好得多,计算量大得多。
逐像素最近邻搜索是 ,一张 800×600 的图就是 1 亿次距离计算,所以实践里必须预建查找表,把 RGB 各压到 6 档直接索引。
延迟单位是百分之一秒,意味着 GIF 的时间分辨率天生是 10ms,任何 16.7ms 的帧时序都会被取整成 20ms。这解释了为什么 GIF 播放速度不对是常态而不是 bug。
还有一条绕不过去的账。GIF 的帧间压缩很弱,早期只有只存变化矩形这一招,所以文件体积大致与帧数乘画面面积成正比。想让 GIF 变小,减面积比减帧数更有效。这也是为什么表情包总是方方正正一小块。
对称群,万花筒和它的近似
万花筒的效果看着像数学魔法,随手画一笔,屏幕上长出繁复的放射花纹。实现其实极简,把画笔位置绕画布中心旋转 ,,各画一次,八个副本自然拼成一张对称图。
但这里有个容易忽略的精确差别。这是 8 重旋转对称,属于循环群 。真实万花筒是 8 面镜子,属于二面体群 。两者的区别只在于有没有镜像反射。
这个差别能肉眼验证。把一个只用旋转生成的图案拿到镜子前,它不会和自己重合,真实的万花筒图案会。所以更真的做法是同时施加 8 次旋转和它们的镜像,代价是每次落笔多一倍绘制。这也解释了一个常见的观感差异,很多网页万花筒看起来太规整、不够碎,原因就是少了镜像那一半操作。
对称群的好处是可以拿来压缩。 有 16 个对称操作,理论上只需要存 1/16 的画面,其余全部可推导。壁纸、纹理、装饰图案设计用的都是这个手法。
对比度,可计算的可读性
颜色工具里最不好玩但最该被认真用的是对比度检查。它的公式完全确定,没有主观成分。
第一步,把 sRGB 值转成相对亮度。先反伽马, 时用 ,否则用 ;再按 Rec.709 权重加权。
第二步,对比度就是两色亮度的比值。
WCAG 的门槛是正文 AA 需 4.5:1、AAA 需 7:1,大字,也就是 18.66px 粗体或 24px,AA 放宽到 3:1。这套数字能当标准,是因为它对应可测量的阅读表现,不是设计师的口味。
最容易踩的坑也在这。对比度是亮度比,不是色相差异。红底绿字看着颜色差得很开,但两者的相对亮度可能只差 1.5 倍,色盲用户看到的几乎是同一片灰。红绿色觉异常在男性里约占 8%。工具里的色觉模拟就是靠色彩矩阵变换实现的,它比我觉得挺清楚的可靠得多。
ASCII 艺术,两套完全不同的原理
ASCII 艺术字实现的是点阵字模。内置一套手写的 5×5 字符点阵表,还有 10×6 的大号版本,把输入文字逐字查表、按行拼出字符矩阵。输出是用字符画出来的字,每个字母的形状是人工设计的。
这跟另一种同名技术完全不同,亮度斜坡映射:把图片降采样,按每块平均亮度挑一个墨量合适的字符填进去。坡度表、行列粒度、对比度拉伸的完整流水线见复古信号那篇的 ASCII 节。前者保形,形状重要;后者保调,明暗重要。
为什么这个区别重要。前者只能画有限字符集,手写每一格的代价太高,所以只覆盖 A-Z、0-9 和少量符号。后者能画任何图片,但完全不能读文字。很多 ASCII 工具的真实实现是两者混合,图片用斜坡,标题用点阵。
看一个 ASCII 工具的输出,就能猜出它用的是哪套原理。
限制就是预算
做图时第一个决定往往跟风格无关,是先定放弃什么。下次打开像素画编辑器之前,先定一个色数上限,比如 8 色,再动手。
图像这边的限制明码标价,写进了参数;下面这批工具的限制藏在算法里,不读实现根本看不见。
创意工具不生产内容,只生产结构
词云给你一张注意力分布图,占位文和占位图给你一个可以量尺寸的壳,角色生成器给你一个不用从零开始的起点——这类被归进创意的工具,其实一个都不生产内容,只生产结构。用它们的方式如果是让工具告诉我答案,一定失望;如果是让工具先把地基铺好,效率翻倍。
工具入口在词云生成器、占位文本、占位图、RPG 角色生成器。
分词粒度决定一切
词云生成器的流程可以写成三步,分词、计频、布局。前两步看着是体力活,整张图的可信度全押在这。
本站的实现用一条正则切分文本。把所有非汉字、非字母数字的字符换成空格,再按空白切块。然后对每个块继续拆,中文按单字,英文按整词,长度至少 2,统一小写。最后统计频次,按次数降序取前 100 个。
这个切法有个必须知道的后果。中文词云统计的是字频,不是词频。
于是「的」「是」「不」「了」这类虚词毫无悬念地占据榜首,而「人工智能」会被拆成「人」「工」「智」「能」四个条目,各自计数。图看着很像词云,结论基本是噪声。
要做对,得补上两个处理。
一是中文分词。用词典或者统计模型把「人工智能」识别成一个词。没有分词器的纯前端工具做不到,只能退化成字频。二是停用词表,把「的、了、是、在、和」这类高频功能词剔掉。英文同理,还要做词形还原,running、runs、ran 归到 run,否则同一个概念被拆成三份,频次全被稀释。
所以看到一张中文词云,第一个该问的问题不是这图真好看,是它分词了吗。没有分词的词云,最好只当装饰。
布局,一个 NP 难问题被贪心解决
计频之后是排版,而排版是矩形打包问题,把 N 个不同大小的矩形塞进一块画布,互不重叠。这个问题属于 NP 难,精确解不可行,所以所有词云库都用同一类贪心近似。
字号按频次线性映射,,本站取 、。从画布中心出发,沿阿基米德螺旋逐步试位,步长是角度 0.3 rad、半径 1.5 px。每试一个位置,跟已放置的所有矩形做包围盒相交测试,留 4px 间距。找到不冲突的位置就落子,否则继续螺旋。
这里有三个工程事实,得分开看。
中心优先等于重要性优先。螺旋从中心向外,高频词天然占据视觉中心。这是设计的副产品,不是算法的目标。
放不下就丢。本站给每个词最多 800 步试探,走完还没位置就静默丢弃。也就是说,一张词云显示的从来不是全部词。你以为看到了全貌,其实看到的是成功挤进来的那部分。
包围盒是近似的。文字的实际墨迹不是矩形,「一」和「國」的墨量差得多,用矩形判定会留出难看的大空隙。真正讲究的排版要用字形轮廓做多边形碰撞,代价是慢一到两个数量级。
面积编码,人眼真的估不准
即使分词完美、布局紧凑,词云还有个根本问题。它用面积编码数值,而人眼对面积的估计很差。
人眼比较长度的误差通常在 10% 以内,比较面积的误差能到 30% 以上。这是 Cleveland 和 McGill 在 1980 年代就系统验证过的结论,也是柱状图优于饼图、饼图优于气泡图的实证来源。词云更糟,它的面积还随字符宽度变化。一个 40px 的「我」和一个 40px 的「W」占的墨量完全不同,你根本没法比。
所以词云的定位应该非常明确。它是一张注意力分布示意图,不是数据图表。适合回答哪些概念反复出现,不适合回答出现次数是几倍。要精确比较,把这些词做成排序条形图,那才是能读出量级的画法。在这批工具里,也只有词云生成器的输出会被当作结论传播,而它恰恰是最容易失真的一种视觉编码——用它找线索可以,下判断不行,想知道一个词出现了几次,去数。
它的正当用途反而在别处。把一年的日记丢进去,看哪些词反复冒出来,这是自我观察,不是数据分析。图不好看没关系,被自己看见的那个词才是收获。
占位文为什么必须没有意义
占位文本生成的是那段著名的拉丁文,Lorem ipsum dolor sit amet。它常被当成看起来高级的填充物,实际的设计意图恰好相反,它必须没有语义。
道理很简单。设计页面时你在同时做两件事,定版式、写内容。这两件事争夺同一块注意力。如果占位文字是可读的中文,评审时的注意力会不可避免地滑向这句话写得对不对,而不是这栏宽合不合适。用一段谁也读不懂的拉丁文,等于给内容贴了一个此处无信息的标签,把注意力强行锁回排版本身。
它还有第二个作用,量尺寸。排版的基本参数是行宽,西文排版的经验值是一行 45 到 75 个字符,Bringhurst 的建议,超出会掉行,低于会碎。占位文提供稳定的字符密度,你可以在没有真实内容的情况下把栏宽、行高、字号配比调到正确,等真内容进来时版式不会崩。
但占位文会掩盖一个真实风险。真内容的长度分布和占位文完全不同。占位文没有超长 URL,没有连续英文单词,没有 C++ 模板符号,没有 CJK 混排。很多人用占位文调好的版式,一上真内容就被一个长链接撑爆。
所以专业的做法是用接近真实的极值去测,最长的标题、最长的无空格串、最短的一段。
占位图,锁住比例防住抖动
占位图按指定尺寸返回一张图,它的工程价值比占位两个字大得多,它锁住了宽高比。
网页里图片最常见的性能问题不是加载慢,是加载时的布局位移。图片没预留高度,加载完成瞬间把下面的内容全部往下推,读者正好在读的那一行突然跑掉。修法只有一个,在图片加载前就占住它最终要占的空间,也就是显式声明宽高比。
第二个用途是比例校验。设计稿里的 16:9 和代码里的 16:9 是不是同一个。用一张 1600×900 的占位图贴上去,一眼就能看出裁切和留白对不对。这比随便搜一张图先放着可靠,因为随便找的图会带入它自己的构图,把比例问题掩盖掉。
随机角色,分布比取值范围重要
RPG 角色生成器看起来最玩具,但它是这四个工具里唯一涉及生成质量的。
属性用 4d6 弃最低。掷四个六面骰,去掉最小的一个,剩下三个求和。结果范围是 3 到 18,但分布不是均匀的,是向右偏的钟形。这意味着高属性是常见的好运,全 18 依然稀有。这跟 D&D 的经典做法一致。要是直接 randInt(3, 18) 均匀取值,角色会显得又平又怪,既不会有明显的强项,也不会有致命的短板。
名字用前缀加中缀加后缀拼接,再做一次连续重复字母去重。组合数只有几百到几千,但读起来确实像名字,因为音节结构被模板约束住了。
种族、职业、六维属性的组合空间足够大,10 种族乘 12 职业,保证每次生成不重样。
三条背后是同一条原则,随机生成的质量不取决于取值范围,取决于分布形状。均匀抽样是最省事的写法,也是最差的写法。它让所有结果等概率,于是既没有平庸的大多数,也没有值得炫耀的极少数,整个生成器就失去了叙事张力。
这条原则可以平移到所有生成式工具上。地图生成要控制噪声的八度叠加,掉落系统要控制稀有度权重,文本生成要控制温度的锐度。只要你在做随机产出内容,第一个该问的就是我要的分布长什么样。