diff --git a/crates/design/src/palette/seed/mod.rs b/crates/design/src/palette/seed/mod.rs index ce06dab..a2c577a 100644 --- a/crates/design/src/palette/seed/mod.rs +++ b/crates/design/src/palette/seed/mod.rs @@ -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, diff --git a/crates/palette-extract/src/tests.rs b/crates/palette-extract/src/tests.rs index 6f57b6d..dc17050 100644 --- a/crates/palette-extract/src/tests.rs +++ b/crates/palette-extract/src/tests.rs @@ -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 ); }