-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuild.rs
More file actions
172 lines (141 loc) · 4.95 KB
/
build.rs
File metadata and controls
172 lines (141 loc) · 4.95 KB
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
// This build script converts the tiles.png file into a byte array suitable
// for flinging onto the display.
// The pixel data on the display is sent to the display in a packet starting with
// 0x40, then 8 bytes representing the 8 columns.
// The MSB in each byte is the bottom row, LSB is the top.
use image::{GenericImageView, RgbaImage, SubImage};
use std::{
fs::File,
io::{BufWriter, Write},
path::Path,
};
const SSD1306_DATA: u8 = 0x40;
const TILE_SIZE: u32 = 8;
const NUM_TILES: u32 = 15;
const TILES: [&str; 15] = [
"FLOOR", "WALL", "STAIRS", "ENEMY", "PLAYER", "N0", "N1", "N2", "N3", "N4", "N5", "N6", "N7",
"N8", "N9",
];
fn main() {
println!("cargo::rerun-if-changes=tiles.png");
println!("cargo::rerun-if-changes=title_screen.png");
println!("cargo::rerun-if-changes=game_over.png");
let tiles = image::open("assets/tiles.png").unwrap().to_rgba();
assert_eq!(tiles.width(), NUM_TILES * TILE_SIZE);
assert_eq!(tiles.height(), TILE_SIZE);
let tiles: Vec<_> = (0..NUM_TILES)
.map(|i| (i * TILE_SIZE, 0, TILE_SIZE, TILE_SIZE))
.map(|(x, y, width, height)| tiles.view(x, y, width, height))
.collect();
let tiles = process_tiles(tiles);
let path = Path::new("src").join("game").join("tiles.rs");
let file = File::create(path).unwrap();
let mut file = BufWriter::new(file);
writeln!(
&mut file,
"// Generated by the build.rs file during compilation"
)
.unwrap();
writeln!(&mut file, "use crate::hal::progmem::PGMSlice;").unwrap();
for (name, tile) in TILES.iter().zip(tiles) {
writeln!(&mut file, "#[link_section = \".text\"]").unwrap();
write!(&mut file, "static {}_DATA: [u8; {}] = [", name, tile.len()).unwrap();
// Fortunately, rust doesn't care about trailing commas!
for b in tile {
write!(&mut file, "{},", b).unwrap();
}
writeln!(&mut file, "];").unwrap();
write_getter(&mut file, name);
}
write_splash(&mut file, "TITLE_SCREEN", "title_screen.png");
write_splash(&mut file, "GAME_OVER", "game_over.png");
}
fn write_getter(file: &mut BufWriter<File>, name: &str) {
writeln!(
file,
"
#[allow(non_snake_case)]
#[inline(always)]
pub fn {0}() -> PGMSlice {{
unsafe {{ PGMSlice::from_raw_parts(&{0}_DATA as *const u8, {0}_DATA.len()) }}
}}",
name
)
.unwrap();
}
fn write_splash(file: &mut BufWriter<File>, splash_name: &str, filename: &str) {
let title_screen = process_splash(filename);
writeln!(file, "#[link_section = \".text\"]").unwrap();
writeln!(
file,
"static {}_DATA: [u8; {}] = [",
splash_name,
title_screen.len()
)
.unwrap();
// We'll be nice, and chunk the output...
for chunk in title_screen.chunks(32) {
write!(file, " ").unwrap();
for b in chunk {
write!(file, "{},", b).unwrap();
}
writeln!(file).unwrap();
}
writeln!(file, "];").unwrap();
write_getter(file, splash_name);
}
fn process_splash(filename: &str) -> Vec<u8> {
let path = Path::new("assets").join(filename);
let img = image::open(path).unwrap().to_rgba();
// This won't be process as a set of tiles. Instead, we'll output this so the entire thing can
// be thrown onto the screen in one go.
// The screen will take a single 8-pixel column, starting from the upper left, and when it reaches
// the end of the column, it will wrap around to the next 8-pixel column.
// So we need to chunk the image into 8-pixel wide rows.
// The processing is otherwise basically the same as for the tiles.
// One thing we need to concern ourselves with is that my TWI buffer length is 32 bytes.
// This means that we need to insert the SSD1306_DATA byte every 31st byte.
let mut bytes = Vec::new();
let mut count = 0;
let mut push = |b| {
if count == 0 {
bytes.push(SSD1306_DATA);
}
count += 1;
count %= 31;
bytes.push(b);
};
for row in 0..8 {
let row = img.view(0, row * TILE_SIZE, 128, TILE_SIZE);
for x in 0..128 {
let mut column = 0;
for y in (0..TILE_SIZE).rev() {
column <<= 1;
if row.get_pixel(x, y)[0] == 255 {
column |= 1;
}
}
push(column);
}
}
bytes
}
fn process_tiles(tiles: Vec<SubImage<&RgbaImage>>) -> Vec<Vec<u8>> {
tiles
.into_iter()
.map(|tile| {
let mut bytes = vec![SSD1306_DATA];
for x in 0..TILE_SIZE {
let mut column = 0;
for y in (0..TILE_SIZE).rev() {
column <<= 1;
if tile.get_pixel(x, y)[0] == 255 {
column |= 1;
}
}
bytes.push(column);
}
bytes
})
.collect()
}