Generate a signed distance field from a bitmap glyph. `bitmap` is the alpha channel, `w`/`h` are dimensions. `spread` is the distance range in pixels. Returns an RGBA SDF texture (white + SDF in alpha).
(bitmap: &[u8], w: u32, h: u32, spread: f32)
| 19 | /// `spread` is the distance range in pixels. |
| 20 | /// Returns an RGBA SDF texture (white + SDF in alpha). |
| 21 | fn generate_sdf(bitmap: &[u8], w: u32, h: u32, spread: f32) -> Vec<u8> { |
| 22 | let ww = w as i32; |
| 23 | let hh = h as i32; |
| 24 | let spread_i = spread as i32; |
| 25 | let mut sdf = vec![0u8; (w * h * 4) as usize]; |
| 26 | |
| 27 | for y in 0..hh { |
| 28 | for x in 0..ww { |
| 29 | let inside = bitmap[(y * ww + x) as usize] > 127; |
| 30 | let mut min_dist = spread; |
| 31 | |
| 32 | // Search in a window around the pixel for the nearest edge |
| 33 | let x0 = (x - spread_i).max(0); |
| 34 | let x1 = (x + spread_i).min(ww - 1); |
| 35 | let y0 = (y - spread_i).max(0); |
| 36 | let y1 = (y + spread_i).min(hh - 1); |
| 37 | |
| 38 | for sy in y0..=y1 { |
| 39 | for sx in x0..=x1 { |
| 40 | let other_inside = bitmap[(sy * ww + sx) as usize] > 127; |
| 41 | if other_inside != inside { |
| 42 | let dx = (sx - x) as f32; |
| 43 | let dy = (sy - y) as f32; |
| 44 | let dist = (dx * dx + dy * dy).sqrt(); |
| 45 | if dist < min_dist { min_dist = dist; } |
| 46 | } |
| 47 | } |
| 48 | } |
| 49 | |
| 50 | let normalized = if inside { |
| 51 | 0.5 + 0.5 * (min_dist / spread) |
| 52 | } else { |
| 53 | 0.5 - 0.5 * (min_dist / spread) |
| 54 | }; |
| 55 | let alpha = (normalized.clamp(0.0, 1.0) * 255.0) as u8; |
| 56 | let idx = ((y * ww + x) * 4) as usize; |
| 57 | sdf[idx] = 255; |
| 58 | sdf[idx + 1] = 255; |
| 59 | sdf[idx + 2] = 255; |
| 60 | sdf[idx + 3] = alpha; |
| 61 | } |
| 62 | } |
| 63 | sdf |
| 64 | } |
| 65 | |
| 66 | pub struct TextRenderer { |
| 67 | fonts: Vec<Font>, |