feat(exporters): bitmap font, colors, drawing primitives
This commit is contained in:
@@ -1,7 +1,217 @@
|
||||
use image::{ImageFormat, Rgba, RgbaImage};
|
||||
use lib::{BlockPalette, StructureExporter, VoxelGrid, VoxelType};
|
||||
|
||||
const GAP: u32 = 1;
|
||||
|
||||
const COLOR_BODY: [u8; 4] = [0x4A, 0x90, 0xD9, 0xFF];
|
||||
const COLOR_OUTLINE: [u8; 4] = [0x88, 0x88, 0x88, 0xFF];
|
||||
const COLOR_SHADOW: [u8; 4] = [0xC0, 0xA0, 0x60, 0xFF];
|
||||
const COLOR_BG: [u8; 4] = [0x22, 0x22, 0x22, 0xFF];
|
||||
const COLOR_TEXT: [u8; 4] = [0xFF, 0xFF, 0xFF, 0xFF];
|
||||
|
||||
fn voxel_color(vt: VoxelType) -> [u8; 4] {
|
||||
match vt {
|
||||
VoxelType::Body => COLOR_BODY,
|
||||
VoxelType::Outline => COLOR_OUTLINE,
|
||||
VoxelType::Shadow => COLOR_SHADOW,
|
||||
}
|
||||
}
|
||||
|
||||
fn voxel_letter(vt: VoxelType) -> char {
|
||||
match vt {
|
||||
VoxelType::Body => 'B',
|
||||
VoxelType::Outline => 'O',
|
||||
VoxelType::Shadow => 'S',
|
||||
}
|
||||
}
|
||||
|
||||
/// 5×7 bitmap font indexed by ASCII code. Bit4=left, Bit0=right per row.
|
||||
static FONT_5X7: [[u8; 7]; 128] = {
|
||||
const __: [u8; 7] = [0; 7];
|
||||
[
|
||||
// 0-31: control chars (blank)
|
||||
__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,
|
||||
__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,
|
||||
// 32 ' '
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
|
||||
// 33 '!'
|
||||
[0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x04],
|
||||
// 34-47: unused (blank)
|
||||
__, __, __, __, __, __, __, __, __, __, __, __, __, __,
|
||||
// 48 '0'
|
||||
[0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E],
|
||||
// 49 '1'
|
||||
[0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E],
|
||||
// 50 '2'
|
||||
[0x0E, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1F],
|
||||
// 51 '3'
|
||||
[0x0E, 0x11, 0x01, 0x06, 0x01, 0x11, 0x0E],
|
||||
// 52 '4'
|
||||
[0x02, 0x06, 0x0A, 0x12, 0x1F, 0x02, 0x02],
|
||||
// 53 '5'
|
||||
[0x1F, 0x10, 0x1E, 0x01, 0x01, 0x11, 0x0E],
|
||||
// 54 '6'
|
||||
[0x06, 0x08, 0x10, 0x1E, 0x11, 0x11, 0x0E],
|
||||
// 55 '7'
|
||||
[0x1F, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08],
|
||||
// 56 '8'
|
||||
[0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E],
|
||||
// 57 '9'
|
||||
[0x0E, 0x11, 0x11, 0x0F, 0x01, 0x02, 0x0C],
|
||||
// 58 ':'
|
||||
[0x00, 0x04, 0x04, 0x00, 0x04, 0x04, 0x00],
|
||||
// 59-64: blank
|
||||
__, __, __, __, __, __,
|
||||
// 65 'A'
|
||||
[0x04, 0x0A, 0x11, 0x11, 0x1F, 0x11, 0x11],
|
||||
// 66 'B'
|
||||
[0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E],
|
||||
// 67 'C'
|
||||
[0x0E, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0E],
|
||||
// 68 'D'
|
||||
[0x1C, 0x12, 0x11, 0x11, 0x11, 0x12, 0x1C],
|
||||
// 69 'E'
|
||||
[0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x1F],
|
||||
// 70 'F'
|
||||
[0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x10],
|
||||
// 71 'G'
|
||||
[0x0E, 0x11, 0x10, 0x17, 0x11, 0x11, 0x0F],
|
||||
// 72 'H'
|
||||
[0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11],
|
||||
// 73 'I'
|
||||
[0x0E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E],
|
||||
// 74 'J'
|
||||
[0x07, 0x02, 0x02, 0x02, 0x02, 0x12, 0x0C],
|
||||
// 75 'K'
|
||||
[0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11],
|
||||
// 76 'L'
|
||||
[0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F],
|
||||
// 77 'M'
|
||||
[0x11, 0x1B, 0x15, 0x11, 0x11, 0x11, 0x11],
|
||||
// 78 'N'
|
||||
[0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x11],
|
||||
// 79 'O'
|
||||
[0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E],
|
||||
// 80 'P'
|
||||
[0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10, 0x10],
|
||||
// 81 'Q'
|
||||
[0x0E, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0D],
|
||||
// 82 'R'
|
||||
[0x1E, 0x11, 0x11, 0x1E, 0x14, 0x12, 0x11],
|
||||
// 83 'S'
|
||||
[0x0E, 0x11, 0x10, 0x0E, 0x01, 0x11, 0x0E],
|
||||
// 84 'T'
|
||||
[0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04],
|
||||
// 85 'U'
|
||||
[0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E],
|
||||
// 86 'V'
|
||||
[0x11, 0x11, 0x11, 0x11, 0x11, 0x0A, 0x04],
|
||||
// 87 'W'
|
||||
[0x11, 0x11, 0x11, 0x15, 0x15, 0x1B, 0x11],
|
||||
// 88 'X'
|
||||
[0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11],
|
||||
// 89 'Y'
|
||||
[0x11, 0x11, 0x0A, 0x04, 0x04, 0x04, 0x04],
|
||||
// 90 'Z'
|
||||
[0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1F],
|
||||
// 91-94: blank
|
||||
__, __, __, __,
|
||||
// 95 '_'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F],
|
||||
// 96: blank
|
||||
__,
|
||||
// 97 'a'
|
||||
[0x00, 0x00, 0x0E, 0x01, 0x0F, 0x11, 0x0F],
|
||||
// 98 'b'
|
||||
[0x10, 0x10, 0x1E, 0x11, 0x11, 0x11, 0x1E],
|
||||
// 99 'c'
|
||||
[0x00, 0x00, 0x0E, 0x10, 0x10, 0x10, 0x0E],
|
||||
// 100 'd'
|
||||
[0x01, 0x01, 0x0F, 0x11, 0x11, 0x11, 0x0F],
|
||||
// 101 'e'
|
||||
[0x00, 0x00, 0x0E, 0x11, 0x1F, 0x10, 0x0E],
|
||||
// 102 'f'
|
||||
[0x06, 0x09, 0x08, 0x1E, 0x08, 0x08, 0x08],
|
||||
// 103 'g'
|
||||
[0x00, 0x0F, 0x11, 0x11, 0x0F, 0x01, 0x0E],
|
||||
// 104 'h'
|
||||
[0x10, 0x10, 0x1E, 0x11, 0x11, 0x11, 0x11],
|
||||
// 105 'i'
|
||||
[0x04, 0x00, 0x0C, 0x04, 0x04, 0x04, 0x0E],
|
||||
// 106 'j'
|
||||
[0x02, 0x00, 0x06, 0x02, 0x02, 0x12, 0x0C],
|
||||
// 107 'k'
|
||||
[0x10, 0x10, 0x12, 0x14, 0x18, 0x14, 0x12],
|
||||
// 108 'l'
|
||||
[0x0C, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E],
|
||||
// 109 'm'
|
||||
[0x00, 0x00, 0x1A, 0x15, 0x15, 0x11, 0x11],
|
||||
// 110 'n'
|
||||
[0x00, 0x00, 0x16, 0x19, 0x11, 0x11, 0x11],
|
||||
// 111 'o'
|
||||
[0x00, 0x00, 0x0E, 0x11, 0x11, 0x11, 0x0E],
|
||||
// 112 'p'
|
||||
[0x00, 0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10],
|
||||
// 113 'q'
|
||||
[0x00, 0x0F, 0x11, 0x11, 0x0F, 0x01, 0x01],
|
||||
// 114 'r'
|
||||
[0x00, 0x00, 0x16, 0x19, 0x10, 0x10, 0x10],
|
||||
// 115 's'
|
||||
[0x00, 0x00, 0x0E, 0x10, 0x0E, 0x01, 0x0E],
|
||||
// 116 't'
|
||||
[0x08, 0x08, 0x1E, 0x08, 0x08, 0x09, 0x06],
|
||||
// 117 'u'
|
||||
[0x00, 0x00, 0x11, 0x11, 0x11, 0x13, 0x0D],
|
||||
// 118 'v'
|
||||
[0x00, 0x00, 0x11, 0x11, 0x11, 0x0A, 0x04],
|
||||
// 119 'w'
|
||||
[0x00, 0x00, 0x11, 0x11, 0x15, 0x15, 0x0A],
|
||||
// 120 'x'
|
||||
[0x00, 0x00, 0x11, 0x0A, 0x04, 0x0A, 0x11],
|
||||
// 121 'y'
|
||||
[0x00, 0x11, 0x11, 0x0F, 0x01, 0x11, 0x0E],
|
||||
// 122 'z'
|
||||
[0x00, 0x00, 0x1F, 0x02, 0x04, 0x08, 0x1F],
|
||||
// 123-127: blank
|
||||
__, __, __, __, __,
|
||||
]
|
||||
};
|
||||
|
||||
fn draw_rect(img: &mut RgbaImage, x: u32, y: u32, w: u32, h: u32, color: [u8; 4]) {
|
||||
let c = Rgba(color);
|
||||
for dy in 0..h {
|
||||
for dx in 0..w {
|
||||
let px = x + dx;
|
||||
let py = y + dy;
|
||||
if px < img.width() && py < img.height() {
|
||||
img.put_pixel(px, py, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Render one char at (x, y). Each font pixel = scale×scale actual pixels.
|
||||
fn draw_char(img: &mut RgbaImage, x: u32, y: u32, ch: char, scale: u32, color: [u8; 4]) {
|
||||
let code = ch as usize;
|
||||
if code >= 128 { return; }
|
||||
let glyph = FONT_5X7[code];
|
||||
for (row, &bits) in glyph.iter().enumerate() {
|
||||
for col in 0..5u32 {
|
||||
if bits & (1 << (4 - col)) != 0 {
|
||||
draw_rect(img, x + col * scale, y + row as u32 * scale, scale, scale, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Render a string left-to-right. Each char is 6*scale px wide (5px + 1px gap).
|
||||
fn draw_text(img: &mut RgbaImage, x: u32, y: u32, text: &str, scale: u32, color: [u8; 4]) {
|
||||
let char_w = 6 * scale;
|
||||
for (i, ch) in text.chars().enumerate() {
|
||||
draw_char(img, x + i as u32 * char_w, y, ch, scale, color);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns VoxelTypes that (1) are configured in the palette AND (2) appear in the grid.
|
||||
fn active_voxel_types(palette: &BlockPalette, grid: &VoxelGrid) -> Vec<VoxelType> {
|
||||
let mut candidates = vec![VoxelType::Body];
|
||||
@@ -93,6 +303,13 @@ mod tests {
|
||||
}"#).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn letter_for_each_voxel_type() {
|
||||
assert_eq!(voxel_letter(VoxelType::Body), 'B');
|
||||
assert_eq!(voxel_letter(VoxelType::Outline), 'O');
|
||||
assert_eq!(voxel_letter(VoxelType::Shadow), 'S');
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dimensions_single_layer() {
|
||||
let grid = VoxelGrid::new(4, 3, 1);
|
||||
|
||||
Reference in New Issue
Block a user