SuperAPI AI 工具集

The Art of PNG Glitch

《The Art of PNG Glitch》是一篇关于PNG图像格式故障艺术(glitch art)的深度技术文章与开源库介绍。文章从PNG格式的校验机制出发,解释为何直接编辑PNG文件通常无法产生故障效果,因为CRC32校验和会阻止损坏图像被正常显示。作者梳理了PNG编码流程中可被利用的环节,重点分析Filtered Data(过滤后数据)和Compressed Data(压缩后数据)两类故障目标,并说明Sub、Up、Average、Paeth、None五种滤波器各自在数据受损时产生的不同视觉效果,如花瓣状扩散、向右雪崩、对角流星尾迹、复杂破坏等。文章还涉及透明像素、Adam7隔行扫描等对故障外观的影响。作者提供了Ruby开源库PNGlitch,用于在重新计算CRC32后对PNG内部数据进行故障处理,并给出简单用法、复杂操作、错误滤波器等示例代码,以及由该库生成的大量故障图案目录。

访问官网

工具介绍

PNG是一种自1995年开始发展、至今仍广泛使用的图像格式,以无损压缩和透明像素支持著称。然而,本文并不从常规视角看待图像格式,而是从“故障”(glitch)的角度审视PNG,探究它有哪些可被利用的特性。

首先需要理解PNG的CRC32校验和机制。该校验用于确认图像是否损坏,一旦检测到损坏,普通查看器会拒绝显示图像。因此,直接用文本或二进制编辑器改写部分二进制数据这种简单方法无法生成故障(会完全失败)。也就是说,PNG格式本身难以被故障化。必须按照PNG规范来制造故障,才能避免失败。这意味着需要在解码CRC32后重写数据,重新计算并附加校验和到编辑后的数据上。

其次需要关注PNG的转码过程。编码流程可简化为四个状态,每个状态都可能成为故障目标。但故障化第一个“Raw Data”状态等同于故障化BMP,严格来说不属于PNG故障;最后一个“Formatted PNG”状态因校验和机制而无法生效。因此,PNG故障可以在“Filtered Data”或“Compressed Data”被操作时产生。

Filtered Data发生故障时,会呈现出独特效果:类似花瓣的图案散布在图像周围。当同一滤波器应用于每条扫描线时,不同滤波器之间的差异会变得明显。Compressed Data故障则带有Deflate压缩算法自身的特点,呈现类似雪花噪声的效果。

除了转码过程,透明像素和隔行扫描等元素也会影响故障外观。最能决定故障外观的因素是滤波器(filter)。滤波器通过特定算法转换每条扫描线的未压缩像素数据,以提高压缩效率。共有五种滤波器:Sub、Up、Average、Paeth四种算法,以及None(不应用滤波器)。PNG图像通常对每条扫描线应用最合适的滤波器后再压缩,因此实际PNG会组合使用全部五种滤波器。这些滤波器通常只影响压缩效率,输出结果总是一样。但当过滤后数据受损时,输出结果会出现明显差异。对于优化过、组合了全部五种滤波器的图像,很难辨认滤波器差异;而当同一种单一滤波器应用于每条扫描线时再故障化,差异就会变得非常明显。

各滤波器故障效果不同:应用“None”(无滤波器)时,是原始数据故障,每个像素孤立,改写单个字节不会产生大范围影响。应用“Sub”时,目标像素参考右侧像素,故障图案向右雪崩。应用“Up”时,参考方向为上下。应用“Average”时,参考对角线方向,呈现从受损像素开始的流星状尾迹,并带有柔和渐变,其结果缺乏故障感,是PNG故障中最精细的部分。应用“Paeth”时,算法最复杂,故障效果也最复杂:即使改写最少字节,影响范围也很大,原始图像中的人物轮廓会保留但被强烈破坏,这是PNG故障的主调效果。

Compressed Data故障会出现暴风雪效果,很难辨认原始图形,偶尔残留滤波器效果,图像常被完全破坏。包含透明像素的图像故障时,透明度会成为一种效果,尤其Average滤波器似乎会逐渐混合透明像素。100%透明像素聚集的处理方式与纯色区域相同。Up滤波器常用于纯色区域。较新的通用图像格式可能会根据图像是纯色区域还是照片等复杂图像来切换压缩方案,因此使用包含纯色区域的图像测试故障是有效方法,WebP就是一例。PNG隔行扫描基于8x8像素的Adam7算法,分为七遍,故障化隔行PNG时可以直观观察到该算法,还能确认拼接效果,且角度在Average滤波器下变窄。

PNG相比JPEG或其他新图像格式非常简单,滤波器算法像玩具,压缩方法与老式Zip压缩相同。但正是这种简单格式展现出惊人的故障变化范围。解释JPEG故障或许只需一个例子,但解释PNG故障需要许多不同类型的样本。PNG本是作为GIF的替代格式开发,但在故障方面,GIF与PNG相比过于贫乏。PNG准备了丰富的结果,长期被校验和壁垒所掩盖。

附录A介绍了PNGlitch库。作者在2010年发布了一个小型PNG故障脚本,当时只是移除CRC32、在内部数据故障后重新添加。此后作者持续改写脚本,为自己的工作制作改进版本,并于2014年决定将经验做成库,Ruby库PNGlitch由此诞生。本文所有故障图像均使用该库制作。使用该库需要用户具备一定Ruby语言知识。

简单用法中,glitch方法将压缩和解压数据作为单个字符串实例处理,方便但内存占用可能巨大。内存有问题时,可以使用IO代替String编写代码。PNGlitch还提供逐扫描线操作方法each_scanline。glitch方法有时会破坏表示滤波器类型的字节,可能输出某些查看器无法打开的文件;each_scanline方法更安全,内存占用也更低,但比glitch方法耗时更长。

复杂操作中,扫描线数据由像素数据和滤波器类型值组成。用户可以用Scanline#replace_data重写像素数据,也可以用Scanline#gsub!像String#gsub!一样处理。运行命令可以确认PNG文件的滤波器类型,内部None、Sub、Up、Average、Paeth分别用0到4的数值表示。也可以用each_scanline检查每种滤波器类型。示例中可将每种滤波器类型改为3(Average),change_filter会正确应用新滤波器类型,由于重新计算滤波器并且PNG被正确格式化,这不会导致故障,结果图像肉眼看起来相同。但滤波器差异对故障影响很大,两个示例输出结果完全不同,差异就在滤波器。

以上代码示例都是对“Filtered Data”状态的操作。当用户想故障化“Compressed Data”时,必须使用glitch_after_compress方法。PNGlitch库以开源形式发布:https://github.com/ucnv/pnglitch

附录B是PNG故障目录,列出了正文未涵盖的故障变体,揭示PNG故障表达的广泛多样性。定义了三种简单破坏数据的方法。列出了五种滤波器类型:Sub、Up、Average、Paeth以及优化组合滤波器。展示了120种组合模式,涵盖是否含alpha、是否隔行、故障化哪个状态。生成脚本附在末尾。

附录C讨论错误滤波器。PNG扫描线由滤波器类型字节和过滤后像素数据组合而成。故意制造错误组合是另一种PNG故障技术。上方图像由代码生成。在PNGlitch中,graft方法用于将错误滤波器类型附加到扫描线。这种技术便于检查各滤波器故障效果的差异。接下来五张图是对每条扫描线应用某一种滤波器类型字节、但不修改扫描线数据的结果。如果实现错误的滤波器会怎样?PNGlitch设计允许用户自由更改滤波器方法,因此可以测试该状态下的结果。使用标准滤波器方法的普通查看器在解码由独特滤波器方法编码的PNG图像,这或许会产生算法性故障。下方图像就是这类生成图像的一部分。