///| A built-in 8x8 bitmap font for the digits `0`-`9`. It serves two roles:
///|
///| as the reference templates for classification, and as a way to render
///| test and demo images. Each glyph is eight rows of eight bits, MSB first
///| (bit 7 is the leftmost pixel); a set bit is ink.
///| The eight row bytes of the glyph for `digit`, or all paper if `digit` is
///
///|
/// outside `0..=9`.
pub fn font_rows(digit : Int) -> Array[Int] {
match digit {
0 => [0x38, 0x6C, 0xC6, 0xC6, 0xC6, 0xC6, 0x6C, 0x38]
1 => [0x18, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E]
2 => [0x3C, 0x66, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x7E]
3 => [0x3C, 0x66, 0x06, 0x1C, 0x06, 0x06, 0x66, 0x3C]
4 => [0x0C, 0x1C, 0x2C, 0x4C, 0xCC, 0xFE, 0x0C, 0x0C]
5 => [0x7E, 0x60, 0x60, 0x7C, 0x06, 0x06, 0x66, 0x3C]
6 => [0x3C, 0x66, 0x60, 0x7C, 0x66, 0x66, 0x66, 0x3C]
7 => [0x7E, 0x06, 0x0C, 0x18, 0x18, 0x18, 0x18, 0x18]
8 => [0x3C, 0x66, 0x66, 0x3C, 0x66, 0x66, 0x66, 0x3C]
9 => [0x3C, 0x66, 0x66, 0x66, 0x3E, 0x06, 0x66, 0x3C]
_ => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
}
}
///| The eight row bytes of the glyph for uppercase letter `c`, or all paper if
///
///|
/// `c` is outside `A`-`Z`. Same bit order and ink convention as `font_rows`.
pub fn font_rows_upper(c : Char) -> Array[Int] {
match c.to_int() {
0x41 => [0x18, 0x3C, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66] // A
0x42 => [0x7C, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x66, 0x7C] // B
0x43 => [0x3C, 0x66, 0x60, 0x60, 0x60, 0x60, 0x66, 0x3C] // C
0x44 => [0x78, 0x6C, 0x66, 0x66, 0x66, 0x66, 0x6C, 0x78] // D
0x45 => [0x7E, 0x60, 0x60, 0x78, 0x60, 0x60, 0x60, 0x7E] // E
0x46 => [0x7E, 0x60, 0x60, 0x78, 0x60, 0x60, 0x60, 0x60] // F
0x47 => [0x3C, 0x66, 0x60, 0x60, 0x6E, 0x66, 0x66, 0x3C] // G
0x48 => [0x66, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x66] // H
0x49 => [0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E] // I
0x4A => [0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x6C, 0x38] // J
0x4B => [0x66, 0x6C, 0x78, 0x70, 0x78, 0x6C, 0x66, 0x66] // K
0x4C => [0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7E] // L
0x4D => [0x63, 0x77, 0x7F, 0x6B, 0x63, 0x63, 0x63, 0x63] // M
0x4E => [0x66, 0x76, 0x7E, 0x7E, 0x6E, 0x66, 0x66, 0x66] // N
0x4F => [0x3C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C] // O
0x50 => [0x7C, 0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x60] // P
0x51 => [0x3C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x0E] // Q
0x52 => [0x7C, 0x66, 0x66, 0x66, 0x7C, 0x78, 0x6C, 0x66] // R
0x53 => [0x3C, 0x66, 0x60, 0x3C, 0x06, 0x06, 0x66, 0x3C] // S
0x54 => [0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18] // T
0x55 => [0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C] // U
0x56 => [0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18] // V
0x57 => [0x63, 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63] // W
0x58 => [0x66, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x66, 0x66] // X
0x59 => [0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x18] // Y
0x5A => [0x7E, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x60, 0x7E] // Z
_ => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
}
}
///| The eight row bytes of the lowercase glyph for `c`, or all paper outside
///| `a`-`z`. `i` and `j` put their dot on the top row, separated from the stem,
///|
/// so those two are two-component glyphs.
pub fn font_rows_lower(c : Char) -> Array[Int] {
match c.to_int() {
0x61 => [0x00, 0x00, 0x3C, 0x06, 0x3E, 0x66, 0x3E, 0x00] // a
0x62 => [0x60, 0x60, 0x7C, 0x66, 0x66, 0x66, 0x7C, 0x00] // b
0x63 => [0x00, 0x00, 0x3C, 0x66, 0x60, 0x60, 0x66, 0x3C] // c
0x64 => [0x06, 0x06, 0x3E, 0x66, 0x66, 0x66, 0x3E, 0x00] // d
0x65 => [0x00, 0x00, 0x3C, 0x66, 0x7E, 0x60, 0x66, 0x3C] // e
0x66 => [0x1C, 0x36, 0x30, 0x7C, 0x30, 0x30, 0x30, 0x30] // f
0x67 => [0x00, 0x00, 0x3E, 0x66, 0x66, 0x3E, 0x06, 0x3C] // g
0x68 => [0x60, 0x60, 0x7C, 0x66, 0x66, 0x66, 0x66, 0x00] // h
0x69 => [0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x3C] // i
0x6A => [0x0C, 0x00, 0x1C, 0x0C, 0x0C, 0x0C, 0x0C, 0x38] // j
0x6B => [0x60, 0x60, 0x66, 0x6C, 0x78, 0x6C, 0x66, 0x00] // k
0x6C => [0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C] // l
0x6D => [0x00, 0x00, 0x63, 0x77, 0x7F, 0x6B, 0x63, 0x00] // m
0x6E => [0x00, 0x00, 0x7C, 0x66, 0x66, 0x66, 0x66, 0x00] // n
0x6F => [0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x66, 0x3C] // o
0x70 => [0x00, 0x00, 0x7C, 0x66, 0x66, 0x7C, 0x60, 0x60] // p
0x71 => [0x00, 0x00, 0x3E, 0x66, 0x66, 0x3E, 0x06, 0x06] // q
0x72 => [0x00, 0x00, 0x7C, 0x66, 0x60, 0x60, 0x60, 0x00] // r
0x73 => [0x00, 0x00, 0x3E, 0x60, 0x3C, 0x06, 0x06, 0x3C] // s
0x74 => [0x30, 0x30, 0x7C, 0x30, 0x30, 0x30, 0x36, 0x1C] // t
0x75 => [0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3E, 0x00] // u
0x76 => [0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18] // v
0x77 => [0x00, 0x00, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63] // w
0x78 => [0x00, 0x00, 0x66, 0x66, 0x3C, 0x18, 0x3C, 0x66] // x
0x79 => [0x00, 0x00, 0x66, 0x66, 0x66, 0x3E, 0x06, 0x3C] // y
0x7A => [0x00, 0x00, 0x7E, 0x0C, 0x18, 0x30, 0x60, 0x7E] // z
_ => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
}
}
///| The eight row bytes for any supported glyph: digits `0`-`9` and upper- or
///
///|
/// lowercase letters `A`-`Z` / `a`-`z`. Any other character yields all paper.
pub fn char_rows(c : Char) -> Array[Int] {
let code = c.to_int()
if code >= 0x30 && code <= 0x39 {
font_rows(code - 0x30)
} else if code >= 0x41 && code <= 0x5A {
font_rows_upper(c)
} else {
font_rows_lower(c)
}
}
///| Expand eight row bytes into a 64-cell feature grid, row-major, where each
///
///| cell is `255` for ink and `0` for paper. This is the reference
///|
/// representation consumed by the classifier.
pub fn rows_to_grid(rows : Array[Int]) -> Array[Int] {
let grid = Array::make(64, 0)
for y = 0; y < 8; y = y + 1 {
let row = rows[y]
for x = 0; x < 8; x = x + 1 {
if ((row >> (7 - x)) & 1) == 1 {
grid[y * 8 + x] = 255
}
}
}
grid
}
///| Render a glyph given as eight row bytes into an `8 * scale` by `8 * scale`
///
///| grayscale image, with ink as `0` and paper as `255`. This is the bridge
///|
/// used to turn font data into images for recognition tests and demos.
pub fn render_rows(rows : Array[Int], scale : Int) -> Image {
let size = 8 * scale
let pixels = Bytes::makei(size * size, fn(i) {
let x = i % size
let y = i / size
let fx = x / scale
let fy = y / scale
if ((rows[fy] >> (7 - fx)) & 1) == 1 {
b'\x00'
} else {
b'\xff'
}
})
Image::new(size, size, pixels)
}
///| Render a string of digits and letters side by side into a single
///|
///| grayscale image, using `scale` pixels per font cell and a `gap`-pixel
///|
///| gutter between glyphs. Characters outside `0`-`9`, `A`-`Z` and `a`-`z` are
///|
/// as paper.
pub fn render_text(text : String, scale : Int, gap : Int) -> Image {
let n = text.length()
let rows_by_slot : Array[Array[Int]] = Array(capacity=n)
for ch in text {
rows_by_slot.push(char_rows(ch))
}
let glyph = 8 * scale
let gutter = if n > 0 { (n - 1) * gap } else { 0 }
let total_w = n * glyph + gutter
let stride_w = glyph + gap
let pixels = Bytes::makei(total_w * glyph, fn(i) {
let x = i % total_w
let y = i / total_w
let slot = x / stride_w
let x_in_slot = x - slot * stride_w
if x_in_slot >= glyph {
b'\xff'
} else {
let fx = x_in_slot / scale
let fy = y / scale
let rows = rows_by_slot[slot]
if ((rows[fy] >> (7 - fx)) & 1) == 1 {
b'\x00'
} else {
b'\xff'
}
}
})
Image::new(total_w, glyph, pixels)
}
///| Render several lines of digits stacked vertically into one image, with
///|
///| `gap_x` pixels between glyphs on a line and `gap_y` pixels between rows.
///| All lines are left-aligned; shorter lines are padded with paper on the
///|
/// right.
pub fn render_text_block(
lines : Array[String],
scale : Int,
gap_x : Int,
gap_y : Int,
) -> Image {
let glyph = 8 * scale
let n = lines.length()
let line_imgs : Array[Image] = Array(capacity=n)
let mut max_w = 0
for i = 0; i < n; i = i + 1 {
let li = render_text(lines[i], scale, gap_x)
if li.width > max_w {
max_w = li.width
}
line_imgs.push(li)
}
let row_h = glyph + gap_y
let gutter = if n > 0 { (n - 1) * gap_y } else { 0 }
let total_h = n * glyph + gutter
let pixels = Bytes::makei(max_w * total_h, fn(i) {
let x = i % max_w
let y = i / max_w
let line_i = y / row_h
let y_in = y - line_i * row_h
if y_in >= glyph {
b'\xff'
} else {
let li = line_imgs[line_i]
if x >= li.width {
b'\xff'
} else {
li.pixels.at(y_in * li.width + x)
}
}
})
Image::new(max_w, total_h, pixels)
}