terminal_element: Skip contrast adjustment for 24-bit color (#52162)

Nihal Kumar created

## 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 

<!-- What does this PR do, and why? How is it expected to impact users?
     Not just what changed, but what motivated it and why this approach.

Link to Linear issue (e.g., ENG-123) or GitHub issue (e.g., Closes #456)
     if one exists — helps with traceability. -->

## Self-Review Checklist

<!-- Check before requesting review: -->
- [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

Change summary

crates/terminal_view/src/terminal_element.rs | 42 ++++++++++++++++++++-
1 file changed, 40 insertions(+), 2 deletions(-)

Detailed changes

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