From a80ff28480545418cc3d8e38415435dd771d345c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 11 Nov 2024 19:06:29 +0100 Subject: [PATCH 1/2] Rasterize glyphs using white on black (or viceversa) depending on brightness --- crates/gpui/src/color.rs | 6 ++ crates/gpui/src/platform/mac/text_system.rs | 110 ++++++++++++++------ crates/gpui/src/text_system.rs | 2 + crates/gpui/src/window.rs | 2 + 4 files changed, 86 insertions(+), 34 deletions(-) diff --git a/crates/gpui/src/color.rs b/crates/gpui/src/color.rs index 6a1f375b651d4..ae52cb837a54d 100644 --- a/crates/gpui/src/color.rs +++ b/crates/gpui/src/color.rs @@ -433,6 +433,12 @@ impl Hsla { self.a == 0.0 } + /// Returns true if the color is considered "light", false otherwise. + /// A color is considered light if its lightness value is greater than 0.5. + pub fn is_light(&self) -> bool { + self.l > 0.5 + } + /// Blends `other` on top of `self` based on `other`'s alpha value. The resulting color is a combination of `self`'s and `other`'s colors. /// /// If `other`'s alpha value is 1.0 or greater, `other` color is fully opaque, thus `other` is returned as the output color. diff --git a/crates/gpui/src/platform/mac/text_system.rs b/crates/gpui/src/platform/mac/text_system.rs index 3db1bf9bcd52b..953e95eb54afe 100644 --- a/crates/gpui/src/platform/mac/text_system.rs +++ b/crates/gpui/src/platform/mac/text_system.rs @@ -1,3 +1,4 @@ +use super::open_type::apply_features_and_fallbacks; use crate::{ point, px, size, Bounds, DevicePixels, Font, FontFallbacks, FontFeatures, FontId, FontMetrics, FontRun, FontStyle, FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, @@ -13,10 +14,13 @@ use core_foundation::{ string::CFString, }; use core_graphics::{ - base::{kCGImageAlphaPremultipliedLast, CGGlyph}, + base::{ + kCGBitmapByteOrder32Little, kCGImageAlphaNoneSkipLast, kCGImageAlphaPremultipliedLast, + CGGlyph, + }, color_space::CGColorSpace, - context::CGContext, - display::CGPoint, + context::{CGBlendMode, CGContext, CGTextDrawingMode}, + display::{CGPoint, CGRect, CGSize}, }; use core_text::{ font::CTFont, @@ -44,11 +48,6 @@ use pathfinder_geometry::{ use smallvec::SmallVec; use std::{borrow::Cow, char, cmp, convert::TryFrom, sync::Arc}; -use super::open_type::apply_features_and_fallbacks; - -#[allow(non_upper_case_globals)] -const kCGImageAlphaOnly: u32 = 7; - pub(crate) struct MacTextSystem(RwLock); #[derive(Clone, PartialEq, Eq, Hash)] @@ -359,30 +358,63 @@ impl MacTextSystemState { } let bitmap_size = bitmap_size; - let mut bytes; - let cx; - if params.is_emoji { - bytes = vec![0; bitmap_size.width.0 as usize * 4 * bitmap_size.height.0 as usize]; - cx = CGContext::create_bitmap_context( - Some(bytes.as_mut_ptr() as *mut _), - bitmap_size.width.0 as usize, - bitmap_size.height.0 as usize, - 8, - bitmap_size.width.0 as usize * 4, - &CGColorSpace::create_device_rgb(), - kCGImageAlphaPremultipliedLast, - ); + let color_type = if params.is_emoji { + kCGImageAlphaPremultipliedLast } else { - bytes = vec![0; bitmap_size.width.0 as usize * bitmap_size.height.0 as usize]; - cx = CGContext::create_bitmap_context( - Some(bytes.as_mut_ptr() as *mut _), - bitmap_size.width.0 as usize, - bitmap_size.height.0 as usize, - 8, - bitmap_size.width.0 as usize, - &CGColorSpace::create_device_gray(), - kCGImageAlphaOnly, + kCGImageAlphaNoneSkipLast + }; + let mut bytes = + vec![0; bitmap_size.width.0 as usize * 4 * bitmap_size.height.0 as usize]; + let cx = CGContext::create_bitmap_context( + Some(bytes.as_mut_ptr() as *mut _), + bitmap_size.width.0 as usize, + bitmap_size.height.0 as usize, + 8, + bitmap_size.width.0 as usize * 4, + &CGColorSpace::create_device_rgb(), + kCGBitmapByteOrder32Little | color_type, + ); + cx.set_allows_font_subpixel_positioning(true); + cx.set_should_subpixel_position_fonts(true); + cx.set_allows_font_subpixel_quantization(false); + cx.set_should_subpixel_quantize_fonts(false); + if !params.is_emoji { + cx.set_should_smooth_fonts(true); + cx.set_should_antialias(true); + + let background_color; + let foreground_color; + if params.is_light { + background_color = [0., 0., 0.]; + foreground_color = [1., 1., 1.]; + } else { + background_color = [1., 1., 1.]; + foreground_color = [0., 0., 0.]; + } + + cx.set_blend_mode(CGBlendMode::Copy); + cx.set_rgb_fill_color( + background_color[0], + background_color[1], + background_color[2], + 1., + ); + cx.fill_rect(CGRect { + origin: CGPoint { x: 0.0, y: 0.0 }, + size: CGSize { + width: bitmap_size.width.0 as CGFloat, + height: bitmap_size.height.0 as CGFloat, + }, + }); + cx.set_blend_mode(CGBlendMode::Normal); + + cx.set_rgb_fill_color( + foreground_color[0], + foreground_color[1], + foreground_color[2], + 1., ); + cx.set_text_drawing_mode(CGTextDrawingMode::CGTextFill); } // Move the origin to bottom left and account for scaling, this @@ -399,10 +431,7 @@ impl MacTextSystemState { let subpixel_shift = params .subpixel_variant .map(|v| v as f32 / SUBPIXEL_VARIANTS as f32); - cx.set_allows_font_subpixel_positioning(true); - cx.set_should_subpixel_position_fonts(true); - cx.set_allows_font_subpixel_quantization(false); - cx.set_should_subpixel_quantize_fonts(false); + self.fonts[params.font_id.0] .native_font() .clone_with_font_size(f32::from(params.font_size) as CGFloat) @@ -424,6 +453,19 @@ impl MacTextSystemState { pixel[1] = (pixel[1] as f32 / a) as u8; pixel[2] = (pixel[2] as f32 / a) as u8; } + } else { + let mut index = 0; + bytes.retain_mut(|value| { + index += 1; + if index % 4 == 0 { + if !params.is_light { + *value = 255 - *value; + } + true + } else { + false + } + }); } Ok((bitmap_size, bytes)) diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 27e7e55982615..48d4ac62865d7 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -657,6 +657,7 @@ pub(crate) struct RenderGlyphParams { pub(crate) subpixel_variant: Point, pub(crate) scale_factor: f32, pub(crate) is_emoji: bool, + pub(crate) is_light: bool, } impl Eq for RenderGlyphParams {} @@ -669,6 +670,7 @@ impl Hash for RenderGlyphParams { self.subpixel_variant.hash(state); self.scale_factor.to_bits().hash(state); self.is_emoji.hash(state); + self.is_light.hash(state); } } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index e4bea94da04a7..dcda69723b5d8 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -2447,6 +2447,7 @@ impl<'a> WindowContext<'a> { subpixel_variant, scale_factor, is_emoji: false, + is_light: color.is_light(), }; let raster_bounds = self.text_system().raster_bounds(¶ms)?; @@ -2511,6 +2512,7 @@ impl<'a> WindowContext<'a> { subpixel_variant: Default::default(), scale_factor, is_emoji: true, + is_light: false, }; let raster_bounds = self.text_system().raster_bounds(¶ms)?; From b7e76425da1429ead58e8cd2984c375ee2025254 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 12 Nov 2024 16:54:13 +0100 Subject: [PATCH 2/2] Use WebRender's method to determine lightness --- crates/gpui/src/color.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/gpui/src/color.rs b/crates/gpui/src/color.rs index ae52cb837a54d..8e227686554d3 100644 --- a/crates/gpui/src/color.rs +++ b/crates/gpui/src/color.rs @@ -434,9 +434,10 @@ impl Hsla { } /// Returns true if the color is considered "light", false otherwise. - /// A color is considered light if its lightness value is greater than 0.5. pub fn is_light(&self) -> bool { - self.l > 0.5 + const ONE_THIRD: f32 = 1. / 3.; + let Rgba { r, g, b, .. } = self.to_rgb(); + r >= ONE_THIRD && g >= ONE_THIRD && b >= ONE_THIRD && r + g + b >= 2. } /// Blends `other` on top of `self` based on `other`'s alpha value. The resulting color is a combination of `self`'s and `other`'s colors.