1use crate::strips::fill_rows;
2
3pub(crate) const WORD_BITS: usize = u64::BITS as usize;
5
6#[derive(Clone, PartialEq, Eq)]
9pub struct Bitmap {
10 words: Vec<u64>,
11 width: usize,
12 height: usize,
13 words_per_row: usize,
14}
15
16impl Bitmap {
17 pub fn zeros(width: usize, height: usize) -> Self {
18 let words_per_row = width.div_ceil(WORD_BITS);
19 Self {
20 words: vec![0; words_per_row * height],
21 width,
22 height,
23 words_per_row,
24 }
25 }
26
27 pub fn width(&self) -> usize {
28 self.width
29 }
30
31 pub fn height(&self) -> usize {
32 self.height
33 }
34
35 pub fn get(&self, x: usize, y: usize) -> bool {
37 let (word, bit) = self.locate(x, y);
38 self.words[word] >> bit & 1 == 1
39 }
40
41 pub fn set(&mut self, x: usize, y: usize, value: bool) {
43 let (word, bit) = self.locate(x, y);
44 if value {
45 self.words[word] |= 1 << bit;
46 } else {
47 self.words[word] &= !(1 << bit);
48 }
49 }
50
51 pub fn count_ones(&self) -> u64 {
53 self.words.iter().map(|word| word.count_ones() as u64).sum()
54 }
55
56 pub(crate) fn packed(
59 width: usize,
60 height: usize,
61 bit: impl Fn(usize, usize) -> bool + Send + Sync,
62 ) -> Self {
63 let mut bitmap = Self::zeros(width, height);
64 let words_per_row = bitmap.words_per_row;
65
66 fill_rows(&mut bitmap.words, words_per_row, |y, row| {
67 for (index, word) in row.iter_mut().enumerate() {
68 let base = index * WORD_BITS;
69 let span = WORD_BITS.min(width - base); let mut packed = 0;
72 for offset in 0..span {
73 packed |= (bit(base + offset, y) as u64) << offset;
74 }
75
76 *word = packed;
77 }
78 });
79
80 bitmap
81 }
82
83 pub fn words_per_row(&self) -> usize {
85 self.words_per_row
86 }
87
88 pub fn row(&self, y: usize) -> &[u64] {
89 &self.words[y * self.words_per_row..(y + 1) * self.words_per_row]
90 }
91
92 fn locate(&self, x: usize, y: usize) -> (usize, usize) {
93 assert!(
94 x < self.width && y < self.height,
95 "({x}, {y}) is outside a {}x{} bitmap",
96 self.width,
97 self.height
98 );
99 (y * self.words_per_row + x / WORD_BITS, x % WORD_BITS)
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106 use proptest::prelude::*;
107
108 #[test]
109 fn a_new_bitmap_holds_no_bits() {
110 let bitmap = Bitmap::zeros(100, 40);
111
112 assert_eq!(bitmap.width(), 100);
113 assert_eq!(bitmap.height(), 40);
114 assert_eq!(bitmap.count_ones(), 0);
115 }
116
117 #[test]
118 fn a_set_bit_reads_back() {
119 let mut bitmap = Bitmap::zeros(100, 40);
120
121 bitmap.set(70, 3, true);
122
123 assert!(bitmap.get(70, 3));
124 assert!(!bitmap.get(69, 3));
125 assert!(!bitmap.get(70, 2));
126 assert_eq!(bitmap.count_ones(), 1);
127 }
128
129 #[test]
130 fn clearing_a_bit_removes_it() {
131 let mut bitmap = Bitmap::zeros(8, 2);
132
133 bitmap.set(5, 1, true);
134 bitmap.set(5, 1, false);
135
136 assert!(!bitmap.get(5, 1));
137 assert_eq!(bitmap.count_ones(), 0);
138 }
139
140 #[test]
141 fn the_padding_past_the_width_never_counts() {
142 let mut bitmap = Bitmap::zeros(3, 4);
143
144 for y in 0..4 {
145 for x in 0..3 {
146 bitmap.set(x, y, true);
147 }
148 }
149
150 assert_eq!(bitmap.count_ones(), 12);
151 }
152
153 #[test]
154 fn each_row_starts_on_a_word_boundary() {
155 let bitmap = Bitmap::zeros(3, 4);
156
157 assert_eq!(bitmap.words_per_row(), 1);
158 assert_eq!(bitmap.row(0).len(), 1);
159 assert_eq!(bitmap.row(3).len(), 1);
160 }
161
162 #[test]
163 fn a_row_wider_than_one_word_spans_several() {
164 let mut bitmap = Bitmap::zeros(130, 2);
165
166 assert_eq!(bitmap.words_per_row(), 3);
167
168 bitmap.set(0, 0, true);
169 bitmap.set(64, 0, true);
170 bitmap.set(129, 0, true);
171
172 assert_eq!(bitmap.row(0), &[1, 1, 1 << 1]);
173 assert_eq!(bitmap.count_ones(), 3);
174 }
175
176 #[test]
177 #[should_panic(expected = "(3, 0) is outside a 3x4 bitmap")]
178 fn reading_past_the_width_panics() {
179 Bitmap::zeros(3, 4).get(3, 0);
180 }
181
182 #[test]
183 #[should_panic(expected = "(0, 4) is outside a 3x4 bitmap")]
184 fn writing_past_the_height_panics() {
185 Bitmap::zeros(3, 4).set(0, 4, true);
186 }
187
188 proptest! {
189 #[test]
191 fn packing_a_word_at_a_time_matches_setting_bit_by_bit(
192 width in 1usize..200,
193 height in 1usize..8,
194 seed in any::<u64>(),
195 ) {
196 let bit = |x: usize, y: usize| {
197 (seed ^ (x as u64) << 7 ^ (y as u64)).wrapping_mul(0x2545F491_4F6CDD1D).count_ones().is_multiple_of(3)
198 };
199
200 let mut one_at_a_time = Bitmap::zeros(width, height);
201 for y in 0..height {
202 for x in 0..width {
203 one_at_a_time.set(x, y, bit(x, y));
204 }
205 }
206
207 prop_assert!(Bitmap::packed(width, height, bit) == one_at_a_time);
208 }
209
210 #[test]
212 fn the_count_matches_the_bits_actually_set(
213 width in 1usize..200,
214 height in 1usize..20,
215 positions in prop::collection::vec((0usize..200, 0usize..20), 0..50),
216 ) {
217 let mut bitmap = Bitmap::zeros(width, height);
218 let inside: std::collections::BTreeSet<_> = positions
219 .into_iter()
220 .filter(|&(x, y)| x < width && y < height)
221 .collect();
222
223 for &(x, y) in &inside {
224 bitmap.set(x, y, true);
225 }
226
227 prop_assert_eq!(bitmap.count_ones(), inside.len() as u64);
228 for &(x, y) in &inside {
229 prop_assert!(bitmap.get(x, y));
230 }
231 }
232
233 #[test]
235 fn setting_one_bit_leaves_every_other_clear(
236 width in 1usize..140,
237 height in 1usize..10,
238 index in 0usize..1400,
239 ) {
240 let (x, y) = (index % width, index / width % height);
241 let mut bitmap = Bitmap::zeros(width, height);
242
243 bitmap.set(x, y, true);
244
245 for probe_y in 0..height {
246 for probe_x in 0..width {
247 prop_assert_eq!(bitmap.get(probe_x, probe_y), (probe_x, probe_y) == (x, y));
248 }
249 }
250 }
251 }
252}