forked from 1b-agent/opencadstudio-upstream
fix(color): only colour 7 swaps with the background; true colours draw as authored
Every near-white colour turned black on a light background (and near-black white on a dark one), including true colours. A 255,255,255 solid used as a white mask became a black block. Now only colour 7 swaps: true colours and ACI 255 resolve a hair off pure white/black and keep their value on screen and in plots. Discussion #1500 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
ac2a1e1166
commit
60f35e2b1d
7 files changed
+83
-30
No files matched your search
@@ -1126,9 +1126,14 @@ pub fn line_total_width(
|
||||
pub fn resolve_inline_color(c: &InlineColor) -> Option<[f32; 3]> {
|
||||
match c {
|
||||
InlineColor::Aci(idx) => aci_to_rgb(*idx).map(|(r, g, b)| {
|
||||
[r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0]
|
||||
let rgb = [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0];
|
||||
if *idx == 7 {
|
||||
rgb
|
||||
} else {
|
||||
crate::scene::convert::tess_util::authored_rgb(rgb)
|
||||
}
|
||||
}),
|
||||
InlineColor::True(rgb) => Some(*rgb),
|
||||
InlineColor::True(rgb) => Some(crate::scene::convert::tess_util::authored_rgb(*rgb)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+15
-3
@@ -719,7 +719,11 @@ fn append_pdf_page(
|
||||
// Near-white and near-yellow (viewport active border) → dark grey for print
|
||||
// (only when no CTB override was applied).
|
||||
if !color_overridden {
|
||||
let is_light = r > 0.80 && g > 0.80 && b > 0.80;
|
||||
// An authored white (not colour 7) plots as drawn, like on screen.
|
||||
let is_light = r > 0.80
|
||||
&& g > 0.80
|
||||
&& b > 0.80
|
||||
&& !crate::scene::convert::tess_util::is_authored_white([r, g, b]);
|
||||
let is_yellow = r > 0.80 && g > 0.70 && b < 0.30;
|
||||
let is_cyan = r < 0.30 && g > 0.70 && b > 0.70;
|
||||
if is_light || is_yellow {
|
||||
@@ -1301,7 +1305,11 @@ fn emit_hatch(
|
||||
&& hatch.aci != 0
|
||||
&& !(hatch.aci == 7 && matches!(hatch.pattern, HatchPattern::Solid))
|
||||
{
|
||||
let is_light = r > 0.80 && g > 0.80 && b > 0.80;
|
||||
// An authored white (not colour 7) plots as drawn, like on screen.
|
||||
let is_light = r > 0.80
|
||||
&& g > 0.80
|
||||
&& b > 0.80
|
||||
&& !crate::scene::convert::tess_util::is_authored_white([r, g, b]);
|
||||
let is_yellow = r > 0.80 && g > 0.70 && b < 0.30;
|
||||
let is_cyan = r < 0.30 && g > 0.70 && b > 0.70;
|
||||
if is_light || is_yellow {
|
||||
@@ -1482,7 +1490,11 @@ fn glyph_world_xy(v: &crate::scene::pipeline::text_gpu::TextVertex) -> [f64; 2]
|
||||
/// near-white / near-yellow (colour-7-on-white) → black, near-cyan → dark blue.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn adapt_text_color([r, g, b]: [f32; 3]) -> [f32; 3] {
|
||||
let is_light = r > 0.80 && g > 0.80 && b > 0.80;
|
||||
// An authored white (not colour 7) plots as drawn, like on screen.
|
||||
let is_light = r > 0.80
|
||||
&& g > 0.80
|
||||
&& b > 0.80
|
||||
&& !crate::scene::convert::tess_util::is_authored_white([r, g, b]);
|
||||
let is_yellow = r > 0.80 && g > 0.70 && b < 0.30;
|
||||
let is_cyan = r < 0.30 && g > 0.70 && b > 0.70;
|
||||
if is_light || is_yellow {
|
||||
|
||||
Vendored
+15
-12
@@ -3136,19 +3136,22 @@ mod bg_resolution_tests {
|
||||
assert_eq!(colors(&once), colors(&twice));
|
||||
}
|
||||
|
||||
/// Why the raw colour is stored instead of re-adapting the resolved one.
|
||||
/// If this ever starts passing, `raw_color` could be dropped — and if it
|
||||
/// is removed while this still fails, near-white geometry loses its tint
|
||||
/// on the first layout switch.
|
||||
/// Only colour 7's exact white / black swap with the background; a colour
|
||||
/// drawn as authored — near-white, or a true-colour white mask — keeps its
|
||||
/// value on any background. (#1500)
|
||||
#[test]
|
||||
fn adapt_to_bg_cannot_be_reapplied() {
|
||||
let once = adapt_to_bg(NEAR_WHITE, LIGHT);
|
||||
assert_eq!(once, [0.0, 0.0, 0.0, 1.0], "near-white snaps to pure black");
|
||||
assert_ne!(
|
||||
adapt_to_bg(once, DARK),
|
||||
adapt_to_bg(NEAR_WHITE, DARK),
|
||||
"re-adapting the resolved colour must not equal adapting the raw one",
|
||||
);
|
||||
fn only_colour_seven_swaps_with_the_background() {
|
||||
assert_eq!(adapt_to_bg([1.0, 1.0, 1.0, 1.0], LIGHT), [0.0, 0.0, 0.0, 1.0]);
|
||||
assert_eq!(adapt_to_bg([0.0, 0.0, 0.0, 1.0], DARK), [1.0, 1.0, 1.0, 1.0]);
|
||||
assert_eq!(adapt_to_bg(NEAR_WHITE, LIGHT), NEAR_WHITE);
|
||||
let mask = crate::scene::convert::tess_util::aci_to_rgba(&codec::types::Color::Rgb {
|
||||
r: 255,
|
||||
g: 255,
|
||||
b: 255,
|
||||
});
|
||||
assert_eq!(adapt_to_bg(mask, LIGHT), mask);
|
||||
let seven = crate::scene::convert::tess_util::aci_to_rgba(&codec::types::Color::Index(7));
|
||||
assert_eq!(adapt_to_bg(seven, LIGHT), [0.0, 0.0, 0.0, 1.0]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -794,10 +794,14 @@ fn tessellate_entity_inner(
|
||||
a,
|
||||
),
|
||||
ProxyColor::Rgb(r, g, b) => (
|
||||
view::render::adapt_to_bg(
|
||||
[r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0],
|
||||
bg_color,
|
||||
),
|
||||
{
|
||||
let [r, g, b] = convert::tess_util::authored_rgb([
|
||||
r as f32 / 255.0,
|
||||
g as f32 / 255.0,
|
||||
b as f32 / 255.0,
|
||||
]);
|
||||
[r, g, b, 1.0]
|
||||
},
|
||||
0,
|
||||
),
|
||||
ProxyColor::Inherit => (entity_color, aci),
|
||||
|
||||
@@ -31,8 +31,36 @@ pub type FallbackGeometry = (
|
||||
/// Convert an opencadcodec Color (ACI index or true-color) to a GPU RGBA value.
|
||||
pub fn aci_to_rgba(color: &AcadColor) -> [f32; 4] {
|
||||
if let Some((r, g, b)) = color.rgb() {
|
||||
[r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0]
|
||||
let rgb = [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0];
|
||||
// Only colour 7 swaps white/black with the background; any other
|
||||
// colour — a true colour, or ACI 255 — is drawn as authored.
|
||||
let [r, g, b] = if matches!(color, AcadColor::Index(7)) {
|
||||
rgb
|
||||
} else {
|
||||
authored_rgb(rgb)
|
||||
};
|
||||
[r, g, b, 1.0]
|
||||
} else {
|
||||
WireModel::WHITE
|
||||
}
|
||||
}
|
||||
|
||||
/// Pure white and pure black are what the background adaptation swaps, and
|
||||
/// only colour 7 may be swapped. A colour chosen as-is (255,255,255 used as a
|
||||
/// white mask on a light sheet, say) is kept a hair off them so it is drawn
|
||||
/// exactly as authored; the difference is below one display step.
|
||||
pub fn authored_rgb(rgb: [f32; 3]) -> [f32; 3] {
|
||||
const OFF: f32 = 1.0 / 1024.0;
|
||||
if rgb == [1.0; 3] {
|
||||
[1.0 - OFF; 3]
|
||||
} else if rgb == [0.0; 3] {
|
||||
[OFF; 3]
|
||||
} else {
|
||||
rgb
|
||||
}
|
||||
}
|
||||
|
||||
/// True for a colour [`authored_rgb`] moved off pure white.
|
||||
pub fn is_authored_white(rgb: [f32; 3]) -> bool {
|
||||
rgb == authored_rgb([1.0; 3])
|
||||
}
|
||||
@@ -2868,12 +2868,10 @@ impl DimGeom {
|
||||
/// `ByLayer` / `ByBlock` (assumes those are already resolved upstream).
|
||||
pub(crate) fn color_or_inherit(c: &AcadColor, inherited: [f32; 4]) -> [f32; 4] {
|
||||
match c.rgb() {
|
||||
Some((r, g, b)) => [
|
||||
r as f32 / 255.0,
|
||||
g as f32 / 255.0,
|
||||
b as f32 / 255.0,
|
||||
inherited[3],
|
||||
],
|
||||
Some(_) => {
|
||||
let [r, g, b, _] = crate::scene::convert::tess_util::aci_to_rgba(c);
|
||||
[r, g, b, inherited[3]]
|
||||
}
|
||||
None => inherited,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3774,8 +3774,11 @@ pub(crate) fn render_style_for_block_sub_viewport(
|
||||
/// backgrounds become white. All other colors pass through unchanged.
|
||||
pub(crate) fn adapt_to_bg(color: [f32; 4], bg: [f32; 4]) -> [f32; 4] {
|
||||
let lum = 0.299 * bg[0] + 0.587 * bg[1] + 0.114 * bg[2];
|
||||
let is_white = color[0] > 0.95 && color[1] > 0.95 && color[2] > 0.95;
|
||||
let is_black = color[0] < 0.05 && color[1] < 0.05 && color[2] < 0.05;
|
||||
// Only exact white / black swap: that is colour 7 (and interface colours).
|
||||
// Authored colours reach here nudged off both, so they stay as drawn —
|
||||
// a true-colour white mask on a light sheet remains white. (#1500)
|
||||
let is_white = color[..3] == [1.0; 3];
|
||||
let is_black = color[..3] == [0.0; 3];
|
||||
if is_white && lum > 0.5 {
|
||||
[0.0, 0.0, 0.0, color[3]]
|
||||
} else if is_black && lum <= 0.5 {
|
||||
|
||||
Reference in new issue
Block a user