{
  "lead": "普通缩放把每个像素一视同仁，图片变窄时人物和建筑一起被压扁。Seam Carving（Avidan 与 Shamir，2007）换了个思路：每次从上到下找一条「最不重要」的像素缝，一行删一个像素，删掉它图片就窄了一列。天空、海面、沙滩这些平坦的区域会先被删掉，灯塔、帆船这些有明显边缘的物体保持原样。",
  "formulas": [
    [
      "e(x, y) = |∇<sub>x</sub>I|² + |∇<sub>y</sub>I|²",
      "能量：左右和上下相邻像素的颜色差，边缘处能量高"
    ],
    [
      "M(x, y) = e(x, y) + min(M(x−1, y−1), M(x, y−1), M(x+1, y−1))",
      "动态规划：到达每个像素的最小累计能量，从最后一行的最小值往回走就得到缝"
    ]
  ],
  "terms": [
    [
      "缝",
      "从第一行到最后一行、每行一个像素、相邻两行最多左右错开一格的路径，所以删掉之后每行都少一个像素，图片仍然是规整的矩形。所有这样的路径多达 3<sup>高度</sup> 条，动态规划只需把每个像素算一遍，就能找出累计能量最小的那条。"
    ],
    [
      "预先计算删除顺序",
      "加载图片后连续删掉宽度一半的缝（640 像素宽就是 320 条），每删一条都重新找下一条，并给每个原始像素记下它在第几轮被删掉。之后拖动滑块到任意宽度，只要保留「第 k 轮以后才被删」的像素，瞬间就能得到结果，不必重新计算。「缝的位置」视图就是把前 k 轮的像素涂成红色。"
    ],
    [
      "放大",
      "把图片变宽时，找出前 k 条最不重要的缝，在每条缝旁边插入一个取左右平均色的新像素。平坦区域被拉宽，物体依然保持原样。"
    ],
    [
      "保护与删除",
      "涂成绿色的像素能量加上一个极大值，缝不会经过它们；涂成红色的像素能量减去一个极大值，缝会优先穿过它们。把一个物体涂红，刚好删掉所有红色像素所需的缝数就是它的宽度，页面会自动把尺寸设到这个值，物体就从画面中消失了。"
    ],
    [
      "只更新缝附近",
      "删掉一条缝后，只有它两侧的像素换了邻居，所以只重算这些像素的能量；累计能量表则每次都要整张重算，这是主要开销。"
    ]
  ],
  "flow": [
    [
      "js",
      "示例图由 Canvas 程序化绘制，上传的图片先缩小到不超过 640×480。JS 把像素和涂抹标记传给 <code>sc_load(像素, 宽, 高, 是否改变高度, 标记)</code>。"
    ],
    [
      "rust",
      "JS 每一帧调用 <code>sc_carve(24)</code>，Rust 再找出并删除 24 条缝，记录每个像素的删除轮次，页面同步显示进度。改变高度时，Rust 先把图片转置，把行当成列来处理。"
    ],
    [
      "js",
      "拖动滑块时调用 <code>sc_render(k)</code>（k 为正时删缝，为负时插缝）取回结果，下方用浏览器的普通缩放画出同样尺寸的对比图。"
    ]
  ],
  "perf": [
    "实测（桌面 Chromium）：640×400 的示例图算出全部 320 条缝约 250ms，平均每条不到 1ms（第一批约 2ms，图片越窄越快）；之后拖动滑块，每次生成结果约 1.6ms，所以滑块跟手。",
    "Seam Carving 并不是万能的：画面里没有空白区域可删、或者缝不得不穿过有规则结构的物体（比如直线、人脸）时会出现扭曲；删除物体后，缝经过的地方偶尔能看到细小的接缝（示例中的海浪）。"
  ],
  "src": [
    [
      "crates/media/seam-carving/src/lib.rs",
      ""
    ],
    [
      "www/seam-carving/index.js",
      ""
    ]
  ]
}
