initial color emoji implementation, currently only monochrome, still

Kate created

figuring out why it doesn't render even though it rasterizes to the
bitmap correctly

Change summary

crates/gpui/src/platform/windows/direct_write.rs | 267 ++++++++++++++---
1 file changed, 208 insertions(+), 59 deletions(-)

Detailed changes

crates/gpui/src/platform/windows/direct_write.rs 🔗

@@ -805,75 +805,224 @@ impl DirectWriteState {
 
         let mut bitmap_data;
         if params.is_emoji {
-            bitmap_data =
-                vec![0u8; bitmap_size.width.0 as usize * bitmap_size.height.0 as usize * 4];
-
-            let mut rgba_data = vec![0u8; (texture_width * texture_height * 4) as usize];
-            unsafe {
-                glyph_analysis.CreateAlphaTexture(texture_type, &texture_bounds, &mut rgba_data)?;
-            }
-
-            // Copy texture data into bitmap at correct position
-            let offset_x = texture_bounds.left.max(0) as usize;
-            let offset_y = texture_bounds.top.max(0) as usize;
-            for y in 0..texture_height as usize {
-                for x in 0..texture_width as usize {
-                    let bitmap_x = offset_x + x;
-                    let bitmap_y = offset_y + y;
-
-                    if bitmap_x < bitmap_size.width.0 as usize
-                        && bitmap_y < bitmap_size.height.0 as usize
-                    {
-                        let texture_idx = (y * texture_width as usize + x) * 4;
-                        let bitmap_idx = (bitmap_y * bitmap_size.width.0 as usize + bitmap_x) * 4;
+            // todo: support more glyph image formats for more exotic fonts, for now it should fallback to monochrome rendering
+            let color_enumerator = unsafe {
+                self.components.factory.TranslateColorGlyphRun(
+                    Vector2::new(baseline_origin_x, baseline_origin_y),
+                    &glyph_run,
+                    None,
+                    DWRITE_GLYPH_IMAGE_FORMATS_COLR
+                        | DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8,
+                    measuring_mode,
+                    Some(&transform),
+                    0,
+                )
+            };
 
-                        if texture_idx + 3 < rgba_data.len() && bitmap_idx + 3 < bitmap_data.len() {
-                            bitmap_data[bitmap_idx..bitmap_idx + 4]
-                                .copy_from_slice(&rgba_data[texture_idx..texture_idx + 4]);
-                        }
-                    }
-                }
-            }
+            // if let Ok(color_enumerator) = color_enumerator {
+            //     loop {
+            //         let color_run = unsafe { color_enumerator.GetCurrentRun() };
+            //         if let Ok(color_run) = color_run {
+            //             let color_glyph_run = unsafe { &*color_run };
+            //             let color_value = color_glyph_run.Base.runColor;
+
+            //             // Create analysis for this color layer
+            //             let color_analysis = unsafe {
+            //                 self.components.factory.CreateGlyphRunAnalysis(
+            //                     &color_glyph_run.Base.glyphRun as *const _,
+            //                     Some(&transform),
+            //                     rendering_mode,
+            //                     measuring_mode,
+            //                     DWRITE_GRID_FIT_MODE_DEFAULT,
+            //                     DWRITE_TEXT_ANTIALIAS_MODE_CLEARTYPE,
+            //                     baseline_origin_x,
+            //                     baseline_origin_y,
+            //                 )
+            //             };
+
+            //             // todo: move this block completely to the gpu
+            //             // this is important because fonts can bundle quite large icons
+            //             // and compositing them on the cpu is quite expensive
+            //             // also the code is ugly
+            //             if let Ok(color_analysis) = color_analysis {
+            //                 let color_bounds =
+            //                     unsafe { color_analysis.GetAlphaTextureBounds(texture_type) };
+            //                 if let Ok(color_bounds) = color_bounds {
+            //                     let color_width = (color_bounds.right - color_bounds.left) as u32;
+            //                     let color_height = (color_bounds.bottom - color_bounds.top) as u32;
+
+            //                     if color_width > 0 && color_height > 0 {
+            //                         let mut alpha_data =
+            //                             vec![0u8; (color_width * color_height * 3) as usize];
+            //                         if unsafe {
+            //                             color_analysis.CreateAlphaTexture(
+            //                                 texture_type,
+            //                                 &color_bounds,
+            //                                 &mut alpha_data,
+            //                             )
+            //                         }
+            //                         .is_ok()
+            //                         {
+            //                             let r = (color_value.r * 255.0) as u8;
+            //                             let g = (color_value.g * 255.0) as u8;
+            //                             let b = (color_value.b * 255.0) as u8;
+            //                             let a = (color_value.a * 255.0) as u8;
+
+            //                             let offset_x = color_bounds.left.max(0) as usize;
+            //                             let offset_y = color_bounds.top.max(0) as usize;
+
+            //                             for y in 0..color_height as usize {
+            //                                 for x in 0..color_width as usize {
+            //                                     let bitmap_x = offset_x + x;
+            //                                     let bitmap_y = offset_y + y;
+
+            //                                     if bitmap_x < bitmap_size.width.0 as usize
+            //                                         && bitmap_y < bitmap_size.height.0 as usize
+            //                                     {
+            //                                         let alpha_idx =
+            //                                             (y * color_width as usize + x) * 3;
+            //                                         let bitmap_idx = (bitmap_y
+            //                                             * bitmap_size.width.0 as usize
+            //                                             + bitmap_x)
+            //                                             * 4;
+
+            //                                         if alpha_idx + 2 < alpha_data.len()
+            //                                             && bitmap_idx + 3 < bitmap_data.len()
+            //                                         {
+            //                                             let alpha_value = (alpha_data[alpha_idx]
+            //                                                 as u32
+            //                                                 + alpha_data[alpha_idx + 1] as u32
+            //                                                 + alpha_data[alpha_idx + 2] as u32)
+            //                                                 / 3;
+            //                                             let final_alpha =
+            //                                                 ((alpha_value * a as u32) / 255) as u8;
+
+            //                                             if final_alpha > 0 {
+            //                                                 let existing_r =
+            //                                                     bitmap_data[bitmap_idx];
+            //                                                 let existing_g =
+            //                                                     bitmap_data[bitmap_idx + 1];
+            //                                                 let existing_b =
+            //                                                     bitmap_data[bitmap_idx + 2];
+            //                                                 let existing_a =
+            //                                                     bitmap_data[bitmap_idx + 3];
+
+            //                                                 let src_alpha =
+            //                                                     final_alpha as f32 / 255.0;
+            //                                                 let dst_alpha =
+            //                                                     existing_a as f32 / 255.0;
+            //                                                 let out_alpha = src_alpha
+            //                                                     + dst_alpha * (1.0 - src_alpha);
+
+            //                                                 if out_alpha > 0.0 {
+            //                                                     bitmap_data[bitmap_idx] =
+            //                                                         ((r as f32 * src_alpha
+            //                                                             + existing_r as f32
+            //                                                                 * dst_alpha
+            //                                                                 * (1.0 - src_alpha))
+            //                                                             / out_alpha)
+            //                                                             as u8;
+            //                                                     bitmap_data[bitmap_idx + 1] =
+            //                                                         ((g as f32 * src_alpha
+            //                                                             + existing_g as f32
+            //                                                                 * dst_alpha
+            //                                                                 * (1.0 - src_alpha))
+            //                                                             / out_alpha)
+            //                                                             as u8;
+            //                                                     bitmap_data[bitmap_idx + 2] =
+            //                                                         ((b as f32 * src_alpha
+            //                                                             + existing_b as f32
+            //                                                                 * dst_alpha
+            //                                                                 * (1.0 - src_alpha))
+            //                                                             / out_alpha)
+            //                                                             as u8;
+            //                                                     bitmap_data[bitmap_idx + 3] =
+            //                                                         (out_alpha * 255.0) as u8;
+            //                                                 }
+            //                                             }
+            //                                         }
+            //                                     }
+            //                                 }
+            //                             }
+            //                         }
+            //                     }
+            //                 }
+            //             }
+            //         }
+
+            //         if !unsafe { color_enumerator.MoveNext() }?.as_bool() {
+            //             break;
+            //         }
+            //     }
+            // } else {
+            // }
+            let monochrome_data = Self::rasterize_monochrome(
+                &glyph_analysis,
+                bitmap_size,
+                size(texture_width, texture_height),
+                &texture_bounds,
+            )?;
+            bitmap_data = monochrome_data
+                .into_iter()
+                .flat_map(|e| [e, e, e, 255])
+                .collect::<Vec<u8>>();
         } else {
-            bitmap_data = vec![0u8; bitmap_size.width.0 as usize * bitmap_size.height.0 as usize];
-
-            let mut alpha_data = vec![0u8; (texture_width * texture_height * 3) as usize];
-            unsafe {
-                glyph_analysis.CreateAlphaTexture(
-                    texture_type,
-                    &texture_bounds,
-                    &mut alpha_data,
-                )?;
-            }
+            bitmap_data = Self::rasterize_monochrome(
+                &glyph_analysis,
+                bitmap_size,
+                size(texture_width, texture_height),
+                &texture_bounds,
+            )?;
+        }
 
-            // Convert ClearType RGB data to grayscale and place in bitmap
-            let offset_x = texture_bounds.left.max(0) as usize;
-            let offset_y = texture_bounds.top.max(0) as usize;
+        Ok((bitmap_size, bitmap_data))
+    }
 
-            for y in 0..texture_height as usize {
-                for x in 0..texture_width as usize {
-                    let bitmap_x = offset_x + x;
-                    let bitmap_y = offset_y + y;
+    fn rasterize_monochrome(
+        glyph_analysis: &IDWriteGlyphRunAnalysis,
+        bitmap_size: Size<DevicePixels>,
+        texture_size: Size<u32>,
+        texture_bounds: &RECT,
+    ) -> Result<Vec<u8>> {
+        let mut bitmap_data =
+            vec![0u8; bitmap_size.width.0 as usize * bitmap_size.height.0 as usize];
 
-                    if bitmap_x < bitmap_size.width.0 as usize
-                        && bitmap_y < bitmap_size.height.0 as usize
-                    {
-                        let texture_idx = (y * texture_width as usize + x) * 3;
-                        let bitmap_idx = bitmap_y * bitmap_size.width.0 as usize + bitmap_x;
-
-                        if texture_idx + 2 < alpha_data.len() && bitmap_idx < bitmap_data.len() {
-                            let avg = (alpha_data[texture_idx] as u32
-                                + alpha_data[texture_idx + 1] as u32
-                                + alpha_data[texture_idx + 2] as u32)
-                                / 3;
-                            bitmap_data[bitmap_idx] = avg as u8;
-                        }
+        let mut alpha_data = vec![0u8; (texture_size.width * texture_size.height * 3) as usize];
+        unsafe {
+            glyph_analysis.CreateAlphaTexture(
+                DWRITE_TEXTURE_CLEARTYPE_3x1,
+                texture_bounds,
+                &mut alpha_data,
+            )?;
+        }
+
+        // Convert ClearType RGB data to grayscale and place in bitmap
+        let offset_x = texture_bounds.left.max(0) as usize;
+        let offset_y = texture_bounds.top.max(0) as usize;
+
+        for y in 0..texture_size.height as usize {
+            for x in 0..texture_size.width as usize {
+                let bitmap_x = offset_x + x;
+                let bitmap_y = offset_y + y;
+
+                if bitmap_x < bitmap_size.width.0 as usize
+                    && bitmap_y < bitmap_size.height.0 as usize
+                {
+                    let texture_idx = (y * texture_size.width as usize + x) * 3;
+                    let bitmap_idx = bitmap_y * bitmap_size.width.0 as usize + bitmap_x;
+
+                    if texture_idx + 2 < alpha_data.len() && bitmap_idx < bitmap_data.len() {
+                        let avg = (alpha_data[texture_idx] as u32
+                            + alpha_data[texture_idx + 1] as u32
+                            + alpha_data[texture_idx + 2] as u32)
+                            / 3;
+                        bitmap_data[bitmap_idx] = avg as u8;
                     }
                 }
             }
         }
 
-        Ok((bitmap_size, bitmap_data))
+        Ok(bitmap_data)
     }
 
     fn get_typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>> {