极客挑战:生成一张 1000 亿像素的 PNG 图片
你是否想过生成一张分辨率达到 1000 亿像素的图片?比如 316230 x 316230 这样巨大的尺寸。
如果直接使用普通的绘图库(如 PIL/Pillow),创建一个这样尺寸的 RGB 图片,未压缩的原始数据将占用约 300GB 的内存(316230 * 316230 * 3 bytes)。如果你的机器内存有限,程序很可能会直接崩溃。
挑战目标
不安装任何第三方库(如 Pillow, NumPy),仅使用 Python 标准库,生成一张 1000 亿像素的 PNG 图片,而且内存占用极低,速度极快。
警告:电脑配置低的同学,请不要尝试打开生成的图片。
核心思路
要解决内存和性能问题,我们不能一次性在内存中创建完整的像素数组。我们需要使用流式处理和格式优化。
PNG 格式本质上是分块的,其中图像数据存储在 IDAT 块中。我们可以:
- 手动构建 PNG 文件头和元数据块 (
IHDR)。 - 选择合适的图像模式:使用 1-bit 灰度模式,数据量极小。
- 高效压缩:使用
zliblevel 1 (最快压缩),因为纯色图片压缩率极高,无需高压缩比。 - 逐行生成像素数据并流式写入。
无论图片多大,我们同一时间只在内存中保存一行的像素数据。
代码实现
下面是完整的 Python 脚本。它利用了 zlib 和 struct 模块来手动封装 PNG 格式。
#!/usr/bin/env python3
"""
生成超大分辨率(1000亿像素)的空白 PNG 图片
特点:
1. 无第三方依赖,仅使用 Python 标准库 (zlib, struct)
2. 极速生成:采用 1-bit 灰度模式 + 低压缩级别
3. 内存友好:流式写入,不占用大量内存
原文: https://www.rehiy.com/post/608/
作者: 若海
"""
import sys
import struct
import zlib
def p32(num):
"""打包32位整数 (大端序)"""
return struct.pack('>I', num)
def write_chunk(f, chunk_type, data):
"""
写入标准的 PNG 数据块 (Chunk)
结构: Length(4) + Type(4) + Data(Length) + CRC(4)
"""
f.write(p32(len(data)))
f.write(chunk_type)
f.write(data)
crc = zlib.crc32(chunk_type)
crc = zlib.crc32(data, crc)
f.write(p32(crc & 0xffffffff))
def generate_large_blank_png():
# 配置参数: 1000亿像素 (316230 x 316230)
width = 316230
height = 316230
output_file = "blank_100billion_pixels.png"
print(f"开始生成 {width}x{height} 像素的空白 PNG 图片")
print("正在计算和写入数据...")
try:
with open(output_file, 'wb') as f:
# 1. PNG 文件签名
f.write(b'\x89PNG\r\n\x1a\n')
# 2. IHDR 图像头
# BitDepth=1 (1-bit), ColorType=0 (Grayscale)
# 使用 1-bit 灰度模式以最小化数据量
ihdr_data = p32(width) + p32(height) + b'\x01\x00\x00\x00\x00'
write_chunk(f, b'IHDR', ihdr_data)
# 3. IDAT 图像数据
# 使用 zlib level=1 (最快压缩)
compressor = zlib.compressobj(level=1)
# 准备单行数据: Filter(0) + Pixels(1-bit grayscale)
# 全白图片: 所有位均为 1 (0xFF)
row_bytes = (width + 7) // 8
raw_row = b'\x00' + b'\xff' * row_bytes
# 数据缓冲区 (64KB chunks)
compressed_buffer = bytearray()
CHUNK_SIZE = 65536
print("正在压缩并写入 IDAT 数据...")
for y in range(height):
# 压缩当前行
if compressed := compressor.compress(raw_row):
compressed_buffer.extend(compressed)
# 缓冲区满则写入文件
while len(compressed_buffer) >= CHUNK_SIZE:
write_chunk(f, b'IDAT', compressed_buffer[:CHUNK_SIZE])
compressed_buffer = compressed_buffer[CHUNK_SIZE:]
# 进度显示
if y % 1000 == 0:
percent = (y / height) * 100
sys.stdout.write(f"\r进度: {percent:.1f}% ({y}/{height})")
sys.stdout.flush()
# 结束压缩,写入剩余数据
compressed_buffer.extend(compressor.flush())
while len(compressed_buffer) > 0:
chunk_len = min(len(compressed_buffer), CHUNK_SIZE)
write_chunk(f, b'IDAT', compressed_buffer[:chunk_len])
compressed_buffer = compressed_buffer[chunk_len:]
sys.stdout.write(f"\r进度: 100.0% ({height}/{height})\n")
# 4. IEND 文件结束
write_chunk(f, b'IEND', b'')
print(f"✓ 成功生成图片: {output_file}")
print("提示: 由于是纯色图片,PNG压缩效率极高,文件体积会很小。")
except Exception as e:
print(f"\n❌ 发生错误: {e}")
if __name__ == "__main__":
generate_large_blank_png()
代码解析
1. PNG 文件结构
PNG 文件由一个 8 字节的签名和一系列数据块(Chunk)组成。每个块包含长度、类型码、数据和 CRC 校验。我们定义了 write_chunk 函数来处理这种结构。
2. 关键策略
这是实现 1000 亿像素生成的关键。
a. 1-bit 灰度模式
使用 BitDepth=1 和 ColorType=0(灰度),8 个像素仅占 1 字节。这极大降低了内存需求和 I/O 压力。
b. 极速压缩
我们使用 zlib.compressobj(level=1)。对于纯色图片,Level 1 的压缩率已经极高,使用默认的 Level 6 只会浪费 CPU 时间而不会显著减小文件体积。
compressor = zlib.compressobj(level=1)
3. 极小的文件体积
因为我们生成的是纯色(空白)图片,PNG 的压缩算法(Deflate)对重复数据的压缩效率极高。虽然图片展开后有 300GB 大小的像素数据,但最终生成的 PNG 文件依然非常小。
学到了新知识 海哥好棒