# 图片 → CSS 艺术：工具化技术计划

> 本文所有数字都是 **Codex（gpt-6-astra）在本机实测**的，不是估算。
> 测量对象是成品 `css-whale-girl.html` 的真实数据：
> 7,348 个多边形、232,517 个顶点、210 种颜色、743×1095 画布。

---

## 一、体积预算（核心结论）

同一份几何数据，六种编码方式的实测结果：

| 方案 | 原始字节 | gzip -9 | brotli q=11 | b64+gzip | b64+brotli |
|---|---:|---:|---:|---:|---:|
| 原始 HTML（2–3 位小数百分比） | 4,222,646 | 1,418,243 | 799,425 | 1,890,992 | 1,065,900 |
| 坐标降到 2 位小数 | 3,700,731 | 1,105,565 | 621,381 | 1,474,088 | 828,508 |
| 坐标降到 1 位小数 | 3,198,929 | 635,106 | 362,620 | 846,808 | 483,496 |
| 差分 + varint（0.001% 精度） | 744,781 | 547,678 | 463,296 | 730,240 | 617,728 |
| **差分 + varint（0.01% 精度）** | **498,183** | **257,890** | **236,209** | **343,856** | 314,948 |

**两条关键发现：**

1. **差分编码是收益最大的一步**。原始 420 万 → 差分 49 万（降 88%），
   再 gzip 到 25 万（降 94%）。原因是相邻顶点坐标只差一点点，
   转成小整数增量后大量重复字节。
2. **brotli 比 gzip 小 8%，但浏览器原生不能解压**。
   `DecompressionStream` 只支持 `gzip` / `deflate` / `deflate-raw`。
   所以 **B 档必须用 gzip**，不能用 brotli。

## 二、A 档（开源版）：纯静态 HTML

目标：下载一个 `.html`，双击就能看，无 JS。

RDP 轮廓简化实验（保留 2 位小数百分比）：

| ε (px) | 顶点数 | HTML | gzip |
|---:|---:|---:|---:|
| 0（不简化） | 232,517 | 3,515,146 | 1,055,417 |
| 0.3 | 82,969 | 1,654,612 | 467,574 |
| 0.6 | 56,984 | 1,298,590 | 350,079 |
| **0.9** | **47,020** | **1,162,214** | **300,911** |
| 1.2 | 42,796 | 1,104,529 | 278,884 |

再配极简模板（颜色进 class、模板文字压到最短）：

| 模板写法 | 原始 | gzip |
|---|---:|---:|
| `style="clip-path:polygon(...)"` 直写 | 974,938 | 254,081 |
| `style="--p:..."` + 统一规则 | **864,746** | **251,012** |

**A 档结论：`< 1 MB` 可以做到（裸 HTML），gzip 后约 250 KB。**

⚠️ **但 ε=0.9 的画质还没做浏览器回归**。原始 232,517 顶点时 MAE 是 9.69，
简化到 47,020 顶点之后是多少 —— **必须重测才能写进文章**。

## 三、B 档（自用版）：压缩 + 运行时展开

**实测：343,856 字节（336 KB），保留全部 232,517 顶点，零精度损失。**

格式（往返已验证）：

```
魔数 "CSSA" + 版本 1
uint16 画布宽 743
uint16 画布高 1095
uint32 坐标刻度（×100 表示 0.01% 精度）
uint32 颜色数 210
uint32 多边形数 7348
颜色表：[颜色数 × 3 字节 RGB]
每个多边形：
  varint 颜色索引
  varint 顶点数
  每个顶点：varint zigzag(Δx), varint zigzag(Δy)
```

验证输出：`ROUNDTRIP_OK rows=7348 vertices=232517`

**体积对比：4.04 MB → 336 KB（降 92%），画质完全不变。**

运行时方案（待实现）：

1. 页面里放 `<script type="application/octet-stream">` 或 `<template>` 存 base64
2. `fetch` 自身 → `DecompressionStream('gzip')` → `ArrayBuffer`
3. 解析 varint → 拼 `clip-path:polygon(...)` 字符串
4. 分块插入 DOM（每 500 个一批，`requestIdleCallback` 之间）
5. 样式用**外置 `<style>` 一次注入**，不要逐个 `style` 属性

**待验证**：7348 个元素的分块插入在手机上会不会卡、样式重算的耗时。

## 四、算法移植（Python → Rust/WASM）

管线五步，难度分布：

| 步骤 | Python 现在用的 | Rust 替代 | 难度 |
|---|---|---|---|
| K-means 量化 | `sklearn.MiniBatchKMeans` | 自己写（`linfa` 或手写 k-means++） | **中** |
| 轮廓提取 | `skimage.measure.find_contours` | `imageproc` + `contour` crate | **低** |
| 周期高斯平滑 | `scipy.ndimage.gaussian_filter1d` | `gaussian-filter` / 手写 | **低** |
| RDP 简化 | `approximate_polygon` | `geo` / 手写（200 行） | **低** |
| 深度排序 | 自写的 point-in-polygon | 照搬 | **低** |

**真正的工作量在 K-means**：sklearn 的 MiniBatch 实现有收敛优化，
手写要保证质量和速度都对得起。

**WASM 体积估算**：用 `wasm-bindgen` + `opt-level="z"` + LTO，
纯算法部分大概 **150–250 KB**（含 image 解码要另算）。
如果自己写 K-means 而不用 `linfa`（它有大量泛型代码），能压到 150 KB 以内。

**性能估算**（未实测，谨慎看）：1000×1000 = 100 万像素，
K=72、10 轮迭代 ≈ 7 亿次距离计算。WASM 大约比 JS 快 3–8 倍，
估计 **2–5 秒**。需要实测确认。

## 五、开源策略（用户定的）

- **A 档开源**：纯静态 HTML 生成器，MIT
- **B 档不开源**：压缩格式 + 运行时展开，自己部署用

这个切分合理：A 档是「能看懂、能学」的算法演示，
B 档是「工程优化」的部分 —— 后者开源价值低但维护成本高。

**类似项目（Grok 提到，但那是另一个产品）**：
`abi/screenshot-to-code`、`wandb/openui`、`microsoft/OmniParser` 都是
「截图 → 可编辑前端代码」，和本工具「图片 → CSS 艺术」不是一回事。
真正接近的是 `potrace`（位图 → 矢量），但它输出 SVG 不是 clip-path。

## 六、里程碑

| 阶段 | 内容 | 验收 | 人日 |
|---|---|---|---|
| **M1** | Python 管线搬进浏览器（JS 版，不上 WASM） | 上传图 → 下载 HTML，能跑通 | 2–3 |
| **M2** | 体积优化：差分 + gzip + base64 | A 档 < 1 MB，B 档 < 400 KB | 2 |
| **M3** | Rust/WASM 重写算法 | 1000×1000 图 < 5 秒 | 4–6 |
| **M4** | 开源准备 + B 档运行时 | README、示例、CI；B 档手机上不卡 | 2–3 |

**M1 先做 JS 版**是刻意的：先用最笨的办法跑通端到端，
拿到真实的耗时和体积数字，再决定 WASM 值不值得。

## 七、最大的风险

**① 画质回归没人验证。**
ε=0.9 把 232,517 顶点砍到 47,020（降 80%），MAE 必然上升，
但上升多少没测。**上线前必须重测 MAE，否则不能写「9.69」这个数。**

**② 超大图降级策略没定。**
3000×3000 的照片按当前密度外推是 ~88,000 多边形、48 MB。
必须在输入端就限制：**最长边 ≤ 1200，超出先等比缩小**。

**③ 手机上的 DOM 插入没验证。**
7348 个元素分块插入在低端机上会不会卡 3 秒 —— 只能真机测。

**④ K-means 的随机性。**
同一张图两次生成结果不同，用户会觉得「不靠谱」。
必须固定 seed，并在 UI 上说明「同样参数结果一致」。

---

*本计划由 Codex（gpt-6-astra）在本机实测生成测量数据，2026-10-25。*
