Repository: j0ru/kickoff (language: rust)

Issue:
Text is pixellated
Love this project, have been using it a lot! I'm using a 2160p display at 2x scaling in sway, and I notice the menu entry text is fairly pixelated. This is also noticeable in the demo screenshot in the readme. Do you think it would be an easy fix to get nice smooth text or would it be quite involved?


Discussion / hints:
Hey, thanks for the interest!
The pixelation seems to be a scaling problem and I was able to reproduce it on my system. It should be fixable by telling the compositor that the program is scaling aware, to avoid being upscaled.
The pixelation in the screenshot is misleading. The text behind it is just as pixelated as kickoff, its due to the resolution of the screenshot. If there are no major roadblockers I should have fixed it by the end of the week.

Fix patch:
diff --git a/src/font.rs b/src/font.rs
index 2ac91b5..8218449 100644
--- a/src/font.rs
+++ b/src/font.rs
@@ -17,7 +17,8 @@ use image::{Pixel, RgbaImage};
 pub struct Font {
     fonts: Vec<fontdue::Font>,
     layout: RefCell<Layout>,
-    scale: f32,
+    size: f32,
+    scale: i32,
     glyph_cache: RefCell<HashMap<GlyphRasterConfig, (Metrics, Vec<u8>)>>,
 }
 
@@ -49,11 +50,16 @@ impl Font {
         Ok(Font {
             fonts: font_data,
             layout: RefCell::new(Layout::new(CoordinateSystem::PositiveYDown)),
-            scale: size,
+            size,
+            scale: 1,
             glyph_cache: RefCell::new(HashMap::new()),
         })
     }
 
+    pub fn set_scale(&mut self, scale: i32) {
+        self.scale = scale;
+    }
+
     fn render_glyph(&self, conf: GlyphRasterConfig) -> (Metrics, Vec<u8>) {
         let mut glyph_cache = self.glyph_cache.borrow_mut();
         if let Some(bitmap) = glyph_cache.get(&conf) {
@@ -90,7 +96,7 @@ impl Font {
             }
             layout.append(
                 &self.fonts,
-                &TextStyle::new(&c.to_string(), self.scale, font_index),
+                &TextStyle::new(&c.to_string(), self.size * self.scale as f32, font_index),
             );
         }
 
diff --git a/src/gui.rs b/src/gui.rs
index f952ffc..123bcc3 100644
--- a/src/gui.rs
+++ b/src/gui.rs
@@ -1,4 +1,5 @@
 use smithay_client_toolkit::{
+    get_surface_scale_factor,
     reexports::{
         calloop,
         client::protocol::{
@@ -25,7 +26,7 @@ use smithay_clipboard::Clipboard;
 
 use log::*;
 use std::cell::Cell;
-use std::io::{BufWriter, ErrorKind, Seek, SeekFrom, Write};
+use std::io::{BufWriter, ErrorKind, Seek, Write};
 use std::rc::Rc;
 
 use image::{Pixel, Rgba, RgbaImage};
@@ -92,7 +93,7 @@ impl Surface {
                     layer_surface.ack_configure(serial);
                     next_render_event_handle.set(Some(RenderEvent::Configure { width, height }));
                 }
-                (_, _) => {}
+                _ => todo!(),
             }
         });
 
@@ -108,11 +109,11 @@ impl Surface {
         }
     }
 
-    pub fn draw(&mut self, mut image: RgbaImage) -> Result<(), std::io::Error> {
+    pub fn draw(&mut self, mut image: RgbaImage, scale: i32) -> Result<(), std::io::Error> {
         if let Some(pool) = self.pools.pool() {
-            let stride = 4 * self.dimensions.0 as i32;
-            let width = self.dimensions.0 as i32;
-            let height = self.dimensions.1 as i32;
+            let width = self.dimensions.0 as i32 * scale;
+            let height = self.dimensions.1 as i32 * scale;
+            let stride = 4 * width;
 
             // First make sure the pool is the right size
             pool.resize((stride * height) as usize)?;
@@ -125,7 +126,7 @@ impl Surface {
             });
 
             // Write the color to all bytes of the pool
-            pool.seek(SeekFrom::Start(0))?;
+            pool.rewind()?;
             {
                 let mut writer = BufWriter::new(&mut *pool);
                 writer.write_all(image.as_raw())?;
@@ -134,8 +135,7 @@ impl Surface {
 
             // Attach the buffer to the surface and mark the entire surface as damaged
             self.surface.attach(Some(&buffer), 0, 0);
-            self.surface
-                .damage_buffer(0, 0, width as i32, height as i32);
+            self.surface.damage_buffer(0, 0, width, height);
 
             // Finally, commit the surface
             self.surface.commit();
@@ -147,6 +147,14 @@ impl Surface {
             ))
         }
     }
+
+    pub fn get_scale(&self) -> i32 {
+        get_surface_scale_factor(&self.surface)
+    }
+
+    pub fn set_scale(&mut self, scale: i32) {
+        self.surface.set_buffer_scale(scale);
+    }
 }
 
 impl Drop for Surface {
diff --git a/src/main.rs b/src/main.rs
index d1f2122..ae9eb51 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -157,7 +157,8 @@ async fn run() -> Result<Option<JoinHandle<()>>, Box<dyn Error>> {
         match surface.next_render_event.take() {
             Some(RenderEvent::Closed) => break,
             Some(RenderEvent::Configure { width, height }) => {
-                need_redraw = surface.set_dimensions(width, height);
+                need_redraw = true;
+                surface.set_dimensions(width, height);
             }
             None => {}
         }
@@ -232,18 +233,26 @@ async fn run() -> Result<Option<JoinHandle<()>>, Box<dyn Error>> {
         if need_redraw {
             need_redraw = false;
 
-            let mut img = ImageBuffer::from_pixel(
-                surface.dimensions.0,
-                surface.dimensions.1,
-                config.colors.background.to_rgba(),
+            // adjust all components for Hidpi
+            let scale = surface.get_scale();
+            surface.set_scale(scale);
+            font.set_scale(scale);
+            let (width, height) = (
+                surface.dimensions.0 * scale as u32,
+                surface.dimensions.1 * scale as u32,
             );
+            let padding = config.padding * scale as u32;
+            let font_size = config.font_size * scale as f32;
+
+            let mut img =
+                ImageBuffer::from_pixel(width, height, config.colors.background.to_rgba());
             let prompt_width = if !config.prompt.is_empty() {
                 let (width, _) = font.render(
                     &config.prompt,
                     &config.colors.prompt,
                     &mut img,
-                    config.padding,
-                    config.padding,
+                    padding,
+                    padding,
                 );
                 width
             } else {
@@ -256,20 +265,13 @@ async fn run() -> Result<Option<JoinHandle<()>>, Box<dyn Error>> {
                 } else {
                     &config.colors.text_query
                 };
-                font.render(
-                    query,
-                    color,
-                    &mut img,
-                    config.padding + prompt_width,
-                    config.padding,
-                );
+                font.render(query, color, &mut img, padding + prompt_width, padding);
             }
 
-            let spacer = (1.5 * config.font_size) as u32;
-            let max_entries = ((surface.dimensions.1 - 2 * config.padding - spacer) as f32
-                / (config.font_size * 1.2)) as usize;
+            let spacer = (1.5 * font_size) as u32;
+            let max_entries = ((height - 2 * padding - spacer) as f32 / (font_size * 1.2)) as usize;
             let offset = if selection > (max_entries / 2) {
-                (selection - max_entries / 2) as usize
+                selection - max_entries / 2
             } else {
                 0
             };
@@ -289,15 +291,12 @@ async fn run() -> Result<Option<JoinHandle<()>>, Box<dyn Error>> {
                     &matched.name,
                     color,
                     &mut img,
-                    config.padding,
-                    (config.padding
-                        + spacer
-                        + (i - offset) as u32 * (config.font_size * 1.2) as u32)
-                        as u32,
+                    padding,
+                    padding + spacer + (i - offset) as u32 * (font_size * 1.2) as u32,
                 );
             }
 
-            match surface.draw(img) {
+            match surface.draw(img, scale) {
                 Ok(_) => {}
                 Err(e) => {
                     error!("{}", e);


Test patch: