From b22b149f210e0bc4cfe605b5791447d975e11a6e Mon Sep 17 00:00:00 2001 From: Nihal Kumar Date: Mon, 20 Apr 2026 13:00:11 +0530 Subject: [PATCH] terminal_element: Skip contrast adjustment for 24-bit color (#52162) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Context Red and blue have inherently low perceptual luminance in APCA — pure default Lc 45 threshold. This caused ensure_minimum_contrast to lighten them, washing out the color to pink/lavender. When an application explicitly specifies an RGB value via ESC[38;2;R;G;Bm, that color should be rendered as-is. Named and indexed colors continue to be contrast-adjusted since they come from theme mappings [zed-colors-before.webm](https://github.com/user-attachments/assets/1adb8c95-5dec-4744-b6f8-38f085602ef4) [zed-colors-after.webm](https://github.com/user-attachments/assets/1968db26-9c02-48cf-9542-86a6c39899c0) Closes: #50396 ## Self-Review Checklist - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Fixed true-color render in terminal --- crates/terminal_view/src/terminal_element.rs | 42 +++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 0bb0837c6edb926cdcda70a54889de313cbe94f1..d1c6b324498ce50f67ed124ebb133f300bd40c39 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -550,11 +550,13 @@ impl TerminalElement { minimum_contrast: f32, ) -> TextRun { let flags = indexed.cell.flags; + let is_true_color = matches!(fg, terminal::alacritty_terminal::vte::ansi::Color::Spec(_)); let mut fg = convert_color(&fg, colors); let bg = convert_color(&bg, colors); - // Only apply contrast adjustment to non-decorative characters - if !Self::is_decorative_character(indexed.c) { + // Skip contrast adjustment for true-color (24-bit RGB) foregrounds — the + // application chose that exact color. Also skip for decorative characters. + if !is_true_color && !Self::is_decorative_character(indexed.c) { fg = ensure_minimum_contrast(fg, bg, minimum_contrast); } @@ -1848,6 +1850,42 @@ mod tests { ); } + #[test] + fn test_true_color_red_blue_not_washed_out_on_dark_bg() { + // Red and blue have inherently low perceptual luminance in APCA. + // Pure #ff0000 only achieves Lc ~35 against #1e1e1e — below the + // default Lc 45 threshold. ensure_minimum_contrast would lighten + // them, washing out the color. This is why cell_style skips the + // adjustment for Color::Spec (24-bit true color). + let dark_bg = gpui::Hsla { + h: 0.0, + s: 0.0, + l: 0.05, + a: 1.0, + }; + + for (name, r, g, b) in [ + ("red", 225, 80, 80), + ("blue", 80, 80, 225), + ("pure red", 255, 0, 0), + ] { + let color = terminal::rgba_color(r, g, b); + let contrast = apca_contrast(color, dark_bg).abs(); + assert!( + contrast < 45.0, + "{name} should have APCA < 45 on dark bg, got {contrast}", + ); + + let adjusted = ensure_minimum_contrast(color, dark_bg, 45.0); + assert!( + adjusted.l > color.l, + "{name} would be lightened by contrast adjustment (l: {} -> {})", + color.l, + adjusted.l, + ); + } + } + #[test] fn test_white_on_white_contrast_issue() { // This test reproduces the exact issue from the bug report