1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
use crate::SdfGlyphError;

/// A raw bitmap containing only the alpha channel.
#[derive(Debug, PartialEq, Eq)]
pub struct BitmapGlyph {
    /// The rendered, buffered glyph bitmap, flattened into a 1D array consisting only of only the
    /// alpha channel.
    pub(crate) alpha: Vec<u8>,

    /// The unbuffered width of the glyph in px.
    pub(crate) width: usize,

    /// The unbuffered height of the glyph in px.
    pub(crate) height: usize,

    /// The number of pixels buffering the glyph on all sides. You can add a buffer to a
    /// raw bitmap using the [`Self::from_unbuffered()`] constructor.
    pub(crate) buffer: usize,
}

impl BitmapGlyph {
    /// Creates a new bitmap from scratch.
    ///
    /// This constructor is useful when you already have a buffered glyph. If you have the alpha
    /// bitmap but still need to buffer it (what you expect from most font renderers, for example),
    /// use [`Self::from_unbuffered()`] instead.
    ///
    /// The dimensions provided is expected to describe the input data.
    pub fn new(
        alpha: Vec<u8>,
        width: usize,
        height: usize,
        buffer: usize,
    ) -> Result<BitmapGlyph, SdfGlyphError> {
        let expected = (width + buffer * 2) * (height + buffer * 2);
        if alpha.len() != expected {
            return Err(SdfGlyphError::InvalidDataDimensions(
                "(width + buffer * 2) * (height + buffer * 2)",
                expected,
                alpha.len(),
            ));
        }

        Ok(BitmapGlyph {
            alpha,
            width,
            height,
            buffer,
        })
    }

    /// Creates a new bitmap from a raw data source, buffered by a given amount.
    ///
    /// Most SDF glyphs are buffered a bit so that the outer edges can be properly captured.
    /// This constructor does the buffering for you.
    ///
    /// The dimensions provided is expected to describe the input data.
    pub fn from_unbuffered(
        alpha: &[u8],
        width: usize,
        height: usize,
        buffer: usize,
    ) -> Result<BitmapGlyph, SdfGlyphError> {
        let expected = width * height;
        if alpha.len() != expected {
            return Err(SdfGlyphError::InvalidDataDimensions(
                "width * height",
                expected,
                alpha.len(),
            ));
        }

        let double_buffer = buffer + buffer;

        // Create an larger bitmap that includes a buffer
        let mut buffered_data = vec![0u8; (width + double_buffer) * (height + double_buffer)];

        // Copy the bitmap
        for x in 0..width {
            for y in 0..height {
                buffered_data[(y + buffer) * (width + double_buffer) + x + buffer] =
                    alpha[y * width + x];
            }
        }

        Ok(BitmapGlyph {
            alpha: buffered_data,
            width,
            height,
            buffer,
        })
    }

    /// Render a signed distance field for the given bitmap, recording distances
    /// out to `radius` pixels from the shape outline (the rest will be clamped).
    /// The range of the output field is [-1.0, 1.0], normalised to units of `radius`.
    #[must_use]
    pub fn render_sdf(&self, radius: usize) -> Vec<f64> {
        // Create two bitmaps, one for the pixels outside the filled area, and another for
        // values inside it.
        let mut outer_df: Vec<f64> = self
            .alpha
            .iter()
            .map(|alpha| {
                if *alpha == 0 {
                    f64::MAX // Perfectly outside the shape
                } else {
                    // Values with alpha < 50% will be treated as progressively
                    // further "outside" the shape as their alpha decreases. Alpha > 50%
                    // will be treated as "inside" the shape and get a zero value here.
                    0f64.max(0.5 - (*alpha as f64 / 255.0)).powi(2)
                }
            })
            .collect();

        let mut inner_df: Vec<f64> = self
            .alpha
            .iter()
            .map(|alpha| {
                if *alpha == 255 {
                    f64::MAX // Perfectly inside the shape
                } else {
                    // Values with alpha > 50% will be treated as progressively
                    // further "inside" the shape as their alpha decreases. Alpha < 50%
                    // will be treated as "outside" the shape and get a zero value here.
                    0f64.max((*alpha as f64 / 255.0) - 0.5).powi(2)
                }
            })
            .collect();

        let buffered_width = self.width + self.buffer + self.buffer;
        let buffered_height = self.height + self.buffer + self.buffer;

        // Per page 8 (422), the 2D distance transform can be obtained by computing the
        // one-dimensional distance transform along each column first and then computing
        // the transform along each row of the result. We run the transform over both the
        // outer and inner to get the respective Euclidean squared distances
        // (the math is much easier this way).
        for col in 0..buffered_width {
            dt(&mut outer_df, col, buffered_width, buffered_height);
            dt(&mut inner_df, col, buffered_width, buffered_height);
        }

        for row in 0..buffered_height {
            dt(&mut outer_df, row * buffered_width, 1, buffered_width);
            dt(&mut inner_df, row * buffered_width, 1, buffered_width);
        }

        outer_df
            .iter()
            .zip(inner_df.iter())
            .map(|(outer_df, inner_df)| {
                // Determine the euclidean distance inside or outside the alpha mask, then
                // clamp the range according to the radius so that the overall range of the
                // output field is [-1, 1] as a percentage of the radius.
                ((outer_df.sqrt() - inner_df.sqrt()) / radius as f64)
                    .min(1.0)
                    .max(-1.0)
            })
            .collect()
    }
}

/// An O(n) Euclidean Distance Transform algorithm.
/// See page 6 (420) of [paper](http://cs.brown.edu/people/pfelzens/papers/dt-final.pdf) for details and
/// further discussion of the math behind this.
fn dt(grid: &mut [f64], offset: usize, step_by: usize, size: usize) {
    // For our purposes, f is a one-dimensional slice of the grid
    let f: Vec<f64> = grid.iter().skip(offset).step_by(step_by).copied().collect();

    // It may be possible to make this more functional in style,
    // but for now this is more or less a "dumb" transcription of
    // the algorithm presented in the paper by Felzenszwalb & Huttenlocher.
    let mut k = 0;
    let mut v = vec![0; size];
    let mut z = vec![0f64; size + 1];
    let mut s: f64;

    z[0] = f64::MIN;
    z[1] = f64::MAX;

    for q in 1..size {
        loop {
            let q2 = (q * q) as f64;
            let vk2 = (v[k] * v[k]) as f64;
            let denom = (2 * q - 2 * v[k]) as f64;
            s = ((f[q] + q2) - (f[v[k]] + vk2)) / denom;

            if s <= z[k] {
                k -= 1;
            } else {
                k += 1;
                v[k] = q;
                z[k] = s;
                z[k + 1] = f64::MAX;

                break;
            }
        }
    }

    k = 0;
    for q in 0..size {
        let qf64 = q as f64;
        while z[k + 1] < qf64 {
            k += 1;
        }
        let vkf64 = v[k] as f64;
        grid[offset + q * step_by] = (qf64 - vkf64) * (qf64 - vkf64) + f[v[k]];
    }
}

/// Compresses a `Vec<f64>` into a `Vec<u8>` for efficiency.
///
/// The highest `cutoff` percent of values in the range (0-255) will be used to encode
/// negative values (points inside the glyph). This can be tuned based on the intended
/// application.
///
/// The `cutoff` value must be in the range (0, 1) - non-inclusive on both sides.
/// Values outside this range make no sense and will result in an error.
pub fn clamp_to_u8(sdf: &[f64], cutoff: f64) -> Result<Vec<u8>, SdfGlyphError> {
    if cutoff <= 0.0 || cutoff >= 1.0 {
        return Err(SdfGlyphError::InvalidCutoff(cutoff));
    }
    Ok(sdf
        .iter()
        .map(|v| {
            // Map the values back into the single byte integer range.
            // Note: casting from a float to an integer performs a saturating
            // cast in Rust, removing the need for special logic.
            // See https://doc.rust-lang.org/nomicon/casts.html.
            (255.0 - 255.0 * (v + cutoff)) as u8
        })
        .collect())
}

#[cfg(test)]
mod tests {
    use super::{clamp_to_u8, BitmapGlyph};

    #[test]
    fn test_empty_glyph_unbuffered() {
        // Tests an empty glyph. In this case, we are using the actual bitmap (empty) and metrics
        // for how Open Sans Light encodes a space (0x20).
        let data = Vec::new();
        let bitmap = BitmapGlyph::new(data.clone(), 0, 0, 0).unwrap();
        let sdf = bitmap.render_sdf(8);

        assert_eq!(sdf, Vec::new());
        assert_eq!(clamp_to_u8(&sdf, 0.25).unwrap(), data);
    }

    #[test]
    fn test_empty_glyph_buffered() {
        // Tests an empty glyph. In this case, we are using the actual bitmap (empty) and metrics
        // for how Open Sans Light encodes a space (0x20), plus a 3px buffer we added.
        let data = Vec::from([
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0,
        ]);
        let bitmap = BitmapGlyph::new(data.clone(), 0, 0, 3).unwrap();
        let sdf = bitmap.render_sdf(8);
        let sdf_data_f64 = Vec::from([
            1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
            1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
            1.0, 1.0,
        ]);

        assert_eq!(sdf, sdf_data_f64);
        assert_eq!(clamp_to_u8(&sdf, 0.25).unwrap(), data);
    }

    #[test]
    #[allow(clippy::unreadable_literal)]
    fn test_nontrivial_glyph() {
        // Tests an nontrivial glyph. In this case, we are using the actual bitmap and metrics
        // for how Open Sans Light encodes an ampersand (0x25), plus a 3px buffer we added.
        let alpha = Vec::from(include!("../fixtures/glyph_alpha.json"));
        let sdf_data_f64 = Vec::from(include!("../fixtures/glyph_sdf_f64.json"));
        let sdf_data_u8 = Vec::from(include!("../fixtures/glyph_sdf_u8.json"));
        let bitmap = BitmapGlyph::new(alpha, 16, 19, 3).unwrap();
        let sdf = bitmap.render_sdf(8);

        assert_eq!(sdf, sdf_data_f64);
        assert_eq!(clamp_to_u8(&sdf, 0.25).unwrap(), sdf_data_u8);

        // Sanity check on the clamp_to_u8 function; 191 = 255 (max value of a u8) - 25% of 256 (range)
        assert_eq!(
            clamp_to_u8(&sdf, 0.25)
                .unwrap()
                .into_iter()
                .filter(|x| *x >= 191)
                .count(),
            sdf_data_f64.into_iter().filter(|x| *x < 0.0).count()
        );
    }
}