docs(design)+test(palette-extract): 修正「种子一定是原图真实颜色」的过度承诺

自测发现:400×400 红蓝棋盘在 MAX_EDGE=256(适配层 thumbnail 缩放)与 512(design 块平均)
下都取到 #704070,都不在原图上——「≤MAX_SAMPLES ⇒ 真实颜色」只对「传进 analyze_rgba 的
那个缓冲」成立,不是整条链的性质。
- Seed::color 文档写明边界 + 上游缩放同样会破坏该性质(附实测例)
- 结构性断言的注释/断言文案改为「design 不再叠第二层块平均」,不再声称能保住真实颜色
This commit is contained in:
AskaEth committed 2026-11-02 11:05:06 +08:00
1 parent 09b716ae79
commit 8be91a9cc9
2 files changed
+18 -9

No files matched your search

+9 -4
View File
@@ -54,10 +54,15 @@ const ALPHA_THRESHOLD: u8 = 128;
pub struct Seed {
/// 种子色。
///
/// **像素数 ≤ [`MAX_SAMPLES`] 时**它是图上真实存在的颜色(代表色取「簇内像素最多的桶里
/// 像素原值平均」,纯色图逐通道精确);**更大的图会先做块平均降采样**,此时它是块平均后的
/// 颜色——`2×2` 块内红蓝交替会平均成 `#703C75`,这个色值在图上任何位置都不存在。
/// 降采样本来就要平均(顺带降噪),所以这是**有意的行为**,不是「绝不出现平均色」的保证。
/// **只对「传进 [`analyze_rgba`] 的那个缓冲」作保证**:像素数 ≤ [`MAX_SAMPLES`] 时不做块
/// 平均,种子是缓冲里真实存在的颜色(代表色取「簇内像素最多的桶里像素原值平均」,
/// 纯色图逐通道精确);更大的缓冲会先块平均,此时可能是平均出来的新颜色。
///
/// ⚠️ 整条链上**任何一次缩放**都会破坏「真实颜色」,不只是这一层:`crates/palette-extract`
/// 在超过 `MAX_EDGE` 时先用 `thumbnail` 缩放。实测 400×400 红蓝棋盘(每像素交替):
/// `MAX_EDGE = 256`(适配层缩放)与 `MAX_EDGE = 512`(本层块平均)都取到 `#704070`,
/// 这个色值在原图上**一个像素都不是**。降采样本来就要平均(顺带降噪),所以这是
/// **有意的行为**;不要把它当「绝不出现平均色」的保证,也别拿它判断「这个色在壁纸里存在」。
pub color: Color,
/// 种子的 CAM16 色相角(度)。
pub hue: f64,
+9 -5
View File
@@ -158,15 +158,19 @@ fn absurd_dimensions_are_rejected_before_allocating() {
#[test]
fn max_edge_stays_within_the_design_sample_budget() {
// 结构性防线(复审 r2 要求):`unlucky_design` 只在「像素数 ≤ MAX_SAMPLES」时才不做块平均,
// 也只有那时 `Seed::color` 才保证是图上真实存在的颜色。当前 256² = 65536 = MAX_SAMPLES,
// **余量为零**——谁把 MAX_EDGE 调到 512,UI 路径就会开始返回「块平均出来的、图上不存在的
// 颜色」(例如红蓝交替被平均成 #703C75),而没有任何东西会红。这条把两个常数绑在一起。
// 结构性防线(复审 r2 要求):`unlucky_design` 只在「像素数 ≤ MAX_SAMPLES」时才不做块平均。
// 当前 256² = 65536 = MAX_SAMPLES,**余量为零**:谁把 MAX_EDGE 调到 512,本适配层交出去的
// 缓冲就会超过 design 的采样预算,design 会**在适配层已经缩过之后再叠一层块平均**
// (色彩被二次抹平),而这条退化没有任何测试会报错——两个常数此前是各写各的。
//
// 注意别把这条读成「≤ 预算 ⇒ 种子一定是原图真实颜色」:本适配层的 `thumbnail` 本身就会平均
// (400×400 红蓝棋盘在 MAX_EDGE=256 下取到的 `#704070` 也不在原图上)。这条只保证
// 「design 侧不再叠第二层平均」,逐像素语义见 `Seed::color` 的文档。
let budget = unlucky_design::palette::MAX_SAMPLES;
let edge = MAX_EDGE as usize;
assert!(
edge * edge <= budget,
"MAX_EDGE={MAX_EDGE} 的平方({})超过 design 的采样预算 {budget}: 调大它之前先读 `unlucky_design::palette::Seed::color` 的文档——超过预算后种子不再是图上真实存在的颜色",
"MAX_EDGE={MAX_EDGE} 的平方({})超过 design 的采样预算 {budget}: 调大它之前先读 `unlucky_design::palette::Seed::color` 的文档——超过预算后 design 会在本适配层缩放之上再叠一层块平均",
edge * edge
);
}