1use crate::prelude::*;
37
38use serde::Serialize;
39
40use crate::error::ContentError;
41use crate::format::{AssetKind, AssetPath, DecodeCx, Encode, Format};
42use crate::image::ImageInfo;
43
44#[cfg(feature = "std")]
45pub mod convert;
46pub mod dither;
47
48pub const MAX_BYTES: usize = 16 << 20;
52
53#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
59pub struct OneBitInfo;
60
61impl Format for OneBitInfo {
62 type Asset = ImageInfo;
63
64 fn id(&self) -> &'static str {
65 "one-bit"
66 }
67
68 fn extensions(&self) -> &'static [&'static str] {
69 &[]
70 }
71
72 fn sniff(&self, bytes: &[u8]) -> bool {
73 Bitmap::is_bitmap(bytes)
74 }
75
76 fn max_bytes(&self) -> usize {
77 MAX_BYTES
78 }
79
80 fn decode(
81 &self,
82 file: &AssetPath,
83 bytes: &[u8],
84 _cx: &DecodeCx<'_>,
85 ) -> Result<ImageInfo, Vec<ContentError>> {
86 source_info(bytes).map_err(|message| vec![ContentError::new(file.shown(), message)])
87 }
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
95pub struct OneBitPicture {
96 file: Vec<u8>,
97}
98
99impl OneBitPicture {
100 pub fn new(file: Vec<u8>) -> Result<Self, &'static str> {
103 Bitmap::parse(&file)?;
104 Ok(Self { file })
105 }
106
107 pub fn file(&self) -> &[u8] {
109 &self.file
110 }
111
112 pub fn into_file(self) -> Vec<u8> {
114 self.file
115 }
116}
117
118impl AssetKind for OneBitPicture {
119 const NAME: &'static str = "one-bit picture";
120}
121
122#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
129pub struct OneBitFile;
130
131impl Format for OneBitFile {
132 type Asset = OneBitPicture;
133
134 fn id(&self) -> &'static str {
135 "one-bit"
136 }
137
138 fn extensions(&self) -> &'static [&'static str] {
139 &[]
140 }
141
142 fn sniff(&self, bytes: &[u8]) -> bool {
143 Bitmap::is_bitmap(bytes)
144 }
145
146 fn max_bytes(&self) -> usize {
147 MAX_BYTES
148 }
149
150 fn decode(
151 &self,
152 file: &AssetPath,
153 bytes: &[u8],
154 _cx: &DecodeCx<'_>,
155 ) -> Result<OneBitPicture, Vec<ContentError>> {
156 OneBitPicture::new(bytes.to_vec())
157 .map_err(|message| vec![ContentError::new(file.shown(), message)])
158 }
159}
160
161impl Encode for OneBitFile {
162 fn encode(&self, asset: &OneBitPicture) -> Vec<u8> {
163 asset.file.clone()
164 }
165}
166
167pub(crate) fn source_info(bytes: &[u8]) -> Result<ImageInfo, String> {
170 let (width, height) = Bitmap::parse(bytes)?.source_size();
171 ImageInfo::checked(u32::from(width), u32::from(height))
172}
173
174pub const MAGIC: [u8; 4] = *b"1BIT";
176
177pub const VERSION: u8 = 1;
179
180pub const HEADER_BYTES: usize = 16;
182
183pub const EXTENSION: &str = "1bit";
185
186const HAS_MASK: u8 = 1;
188
189#[derive(Copy, Clone, Debug, PartialEq, Eq)]
191pub struct Bitmap<'a> {
192 width: u16,
193 height: u16,
194 source_width: u16,
195 source_height: u16,
196 styles: u8,
197 planes: &'a [u8],
199 mask: Option<&'a [u8]>,
200}
201
202impl<'a> Bitmap<'a> {
203 pub fn is_bitmap(bytes: &[u8]) -> bool {
205 bytes.starts_with(&MAGIC)
206 }
207
208 pub fn parse(bytes: &'a [u8]) -> Result<Self, &'static str> {
210 let header = bytes
211 .get(..HEADER_BYTES)
212 .ok_or("too short for a one-bit picture's header")?;
213 if !Self::is_bitmap(header) {
214 return Err("not a one-bit picture file");
215 }
216 if header[4] != VERSION {
217 return Err("a one-bit picture of an unknown version");
218 }
219 let flags = header[5];
220 if flags & !HAS_MASK != 0 || header[7] != 0 {
221 return Err("a one-bit picture with unknown flags");
222 }
223 let styles = header[6];
224 let word = |at: usize| u16::from_le_bytes([header[at], header[at + 1]]);
225 let (width, height) = (word(8), word(10));
226 let (source_width, source_height) = (word(12), word(14));
227 if styles == 0 || width == 0 || height == 0 || source_width == 0 || source_height == 0 {
228 return Err("a one-bit picture with no styles or no pixels");
229 }
230 let plane = Self::plane_bytes(width, height);
231 let has_mask = flags & HAS_MASK != 0;
232 let body = &bytes[HEADER_BYTES..];
233 let mask_bytes = if has_mask { plane } else { 0 };
236 let Some(planes_bytes) = plane
237 .checked_mul(usize::from(styles))
238 .filter(|&n| n.checked_add(mask_bytes) == Some(body.len()))
239 else {
240 return Err("a one-bit picture's size doesn't match its header");
241 };
242 let (planes, mask) = body.split_at(planes_bytes);
243 Ok(Self {
244 width,
245 height,
246 source_width,
247 source_height,
248 styles,
249 planes,
250 mask: has_mask.then_some(mask),
251 })
252 }
253
254 pub const fn stride(width: u16) -> usize {
256 (width as usize).div_ceil(8)
257 }
258
259 pub const fn plane_bytes(width: u16, height: u16) -> usize {
261 Self::stride(width) * height as usize
262 }
263
264 pub const fn width(&self) -> u16 {
266 self.width
267 }
268
269 pub const fn height(&self) -> u16 {
271 self.height
272 }
273
274 pub const fn source_size(&self) -> (u16, u16) {
276 (self.source_width, self.source_height)
277 }
278
279 pub const fn styles(&self) -> usize {
281 self.styles as usize
282 }
283
284 pub fn style(&self, n: usize) -> Option<&'a [u8]> {
286 let plane = Self::plane_bytes(self.width, self.height);
287 (n < self.styles()).then(|| &self.planes[n * plane..(n + 1) * plane])
288 }
289
290 pub const fn mask(&self) -> Option<&'a [u8]> {
292 self.mask
293 }
294
295 pub fn pixel(&self, n: usize, x: u16, y: u16) -> Option<bool> {
298 if x >= self.width || y >= self.height {
299 return None;
300 }
301 let at = usize::from(y) * Self::stride(self.width) + usize::from(x / 8);
302 let bit = |rows: &[u8]| rows[at] >> (7 - x % 8) & 1 == 1;
303 let rows = self.style(n)?;
304 match self.mask {
305 Some(mask) if !bit(mask) => None,
306 _ => Some(bit(rows)),
307 }
308 }
309}
310
311pub fn bitmap_sizes(bytes: &[u8]) -> Option<((u16, u16), (u16, u16))> {
314 let bitmap = Bitmap::parse(bytes).ok()?;
315 Some(((bitmap.width, bitmap.height), bitmap.source_size()))
316}
317
318pub fn encode(
322 (width, height): (u16, u16),
323 source: (u16, u16),
324 styles: &[&[u8]],
325 mask: Option<&[u8]>,
326) -> Result<Vec<u8>, &'static str> {
327 let plane = Bitmap::plane_bytes(width, height);
328 let count = u8::try_from(styles.len()).map_err(|_| "more than 255 styles")?;
329 if count == 0 || width == 0 || height == 0 || source.0 == 0 || source.1 == 0 {
330 return Err("a one-bit picture needs a style and pixels");
331 }
332 if styles
333 .iter()
334 .chain(mask.as_ref())
335 .any(|rows| rows.len() != plane)
336 {
337 return Err("a style or the mask isn't the picture's size");
338 }
339 let mut out = Vec::with_capacity(HEADER_BYTES + plane * (styles.len() + 1));
340 out.extend(MAGIC);
341 out.extend([VERSION, if mask.is_some() { HAS_MASK } else { 0 }, count, 0]);
342 for word in [width, height, source.0, source.1] {
343 out.extend(word.to_le_bytes());
344 }
345 for rows in styles.iter().chain(mask.as_ref()) {
346 out.extend_from_slice(rows);
347 }
348 Ok(out)
349}
350
351#[cfg(test)]
352pub(crate) mod tests {
353 use super::*;
354 use proptest::prelude::*;
355
356 pub(crate) fn sample() -> Vec<u8> {
358 let a: &[u8] = &[0b1000_0000, 0b1000_0000, 0, 0];
359 let b: &[u8] = &[0b0111_1111, 0, 0xff, 0x80];
360 let mask: &[u8] = &[0xff, 0x80, 0b0111_1111, 0];
361 encode((9, 2), (3, 1), &[a, b], Some(mask)).unwrap()
362 }
363
364 #[test]
365 fn the_header_is_as_documented() {
366 let file = sample();
367 assert_eq!(&file[..4], b"1BIT");
368 assert_eq!(file[4..8], [1, 1, 2, 0]);
369 assert_eq!(file[8..16], [9, 0, 2, 0, 3, 0, 1, 0]);
370 assert_eq!(file.len(), HEADER_BYTES + 3 * 4);
371 let plain = encode((8, 1), (8, 1), &[&[0x0f]], None).unwrap();
372 assert_eq!(plain[5], 0);
373 assert_eq!(plain[16..], [0x0f]);
374 }
375
376 #[test]
377 fn sizes_past_255_read_both_bytes() {
378 let plane = vec![0u8; Bitmap::plane_bytes(300, 1)];
379 let file = encode((300, 1), (258, 513), &[&plane], None).unwrap();
380 assert_eq!(file[8..16], [0x2c, 1, 1, 0, 2, 1, 1, 2]);
381 let bitmap = Bitmap::parse(&file).unwrap();
382 assert_eq!((bitmap.width(), bitmap.height()), (300, 1));
383 assert_eq!(bitmap.source_size(), (258, 513));
384 }
385
386 #[test]
387 fn a_file_reads_back_as_written() {
388 let file = sample();
389 let bitmap = Bitmap::parse(&file).unwrap();
390 assert_eq!((bitmap.width(), bitmap.height()), (9, 2));
391 assert_eq!(bitmap.source_size(), (3, 1));
392 assert_eq!(bitmap.styles(), 2);
393 assert_eq!(bitmap.style(0), Some(&[0x80, 0x80, 0, 0][..]));
394 assert_eq!(bitmap.style(1), Some(&[0x7f, 0, 0xff, 0x80][..]));
395 assert_eq!(bitmap.style(2), None);
396 assert_eq!(bitmap.mask(), Some(&[0xff, 0x80, 0x7f, 0][..]));
397 assert_eq!(bitmap_sizes(&file), Some(((9, 2), (3, 1))));
398 assert!(Bitmap::is_bitmap(&file));
399 assert!(!Bitmap::is_bitmap(b"\x89PNG"));
400 }
401
402 #[test]
403 fn pixels_are_read_highest_bit_first_and_masked() {
404 let file = sample();
405 let bitmap = Bitmap::parse(&file).unwrap();
406 assert_eq!(bitmap.pixel(0, 0, 0), Some(true));
407 assert_eq!(bitmap.pixel(0, 1, 0), Some(false));
408 assert_eq!(bitmap.pixel(0, 8, 0), Some(true));
409 assert_eq!(bitmap.pixel(1, 0, 0), Some(false));
410 assert_eq!(bitmap.pixel(1, 1, 0), Some(true));
411 assert_eq!(bitmap.pixel(1, 0, 1), None);
413 assert_eq!(bitmap.pixel(1, 1, 1), Some(true));
414 assert_eq!(bitmap.pixel(1, 8, 1), None);
415 assert_eq!(bitmap.pixel(0, 9, 0), None);
416 assert_eq!(bitmap.pixel(0, 0, 2), None);
417 assert_eq!(bitmap.pixel(2, 0, 0), None);
418 }
419
420 #[test]
421 fn bad_files_say_what_is_wrong() {
422 let file = sample();
423 let error = |bytes: &[u8]| Bitmap::parse(bytes).err().unwrap_or_default();
424 assert_eq!(
425 error(&file[..15]),
426 "too short for a one-bit picture's header"
427 );
428 let mut png = file.clone();
429 png[..4].copy_from_slice(b"\x89PNG");
430 assert_eq!(error(&png), "not a one-bit picture file");
431 for (at, value, message) in [
432 (4, 2, "a one-bit picture of an unknown version"),
433 (5, 3, "a one-bit picture with unknown flags"),
434 (7, 1, "a one-bit picture with unknown flags"),
435 (6, 0, "a one-bit picture with no styles or no pixels"),
436 (8, 0, "a one-bit picture with no styles or no pixels"),
437 (10, 0, "a one-bit picture with no styles or no pixels"),
438 (12, 0, "a one-bit picture with no styles or no pixels"),
439 (14, 0, "a one-bit picture with no styles or no pixels"),
440 (6, 3, "a one-bit picture's size doesn't match its header"),
441 (5, 0, "a one-bit picture's size doesn't match its header"),
442 ] {
443 let mut bad = file.clone();
444 bad[at] = value;
445 if at == 8 || at == 10 || at == 12 || at == 14 {
446 bad[at + 1] = 0;
447 }
448 assert_eq!(error(&bad), message, "byte {at} = {value}");
449 }
450 let mut long = file.clone();
451 long.push(0);
452 assert_eq!(
453 error(&long),
454 "a one-bit picture's size doesn't match its header"
455 );
456 assert_eq!(bitmap_sizes(&long), None);
457 }
458
459 #[test]
460 fn encode_refuses_what_it_cannot_write() {
461 let row: &[u8] = &[0];
462 assert!(encode((8, 1), (8, 1), &[], None).is_err());
463 assert!(encode((0, 1), (8, 1), &[&[]], None).is_err());
464 assert!(encode((8, 0), (8, 1), &[&[]], None).is_err());
465 assert!(encode((8, 1), (0, 1), &[row], None).is_err());
466 assert!(encode((8, 1), (8, 0), &[row], None).is_err());
467 assert!(encode((8, 1), (8, 1), &[&[0, 0]], None).is_err());
468 assert!(encode((8, 1), (8, 1), &[row], Some(&[])).is_err());
469 assert!(encode((8, 1), (8, 1), &[row; 256], None).is_err());
470 assert!(encode((8, 1), (8, 1), &[row; 255], None).is_ok());
471 }
472
473 proptest! {
474 #[test]
476 fn any_picture_round_trips(
477 width in 1u16..20,
478 height in 1u16..6,
479 source in (1u16..30, 1u16..30),
480 styles in 1usize..4,
481 masked: bool,
482 seed: u64,
483 ) {
484 let plane = Bitmap::plane_bytes(width, height);
485 let byte = |n: usize| seed.rotate_left(u32::try_from(n % 64).unwrap() * 7).to_le_bytes()[0] ^ n.to_le_bytes()[0];
486 let planes: Vec<Vec<u8>> = (0..=styles)
487 .map(|s| (0..plane).map(|i| byte(s * plane + i)).collect())
488 .collect();
489 let refs: Vec<&[u8]> = planes[..styles].iter().map(Vec::as_slice).collect();
490 let mask = masked.then_some(planes[styles].as_slice());
491 let file = encode((width, height), source, &refs, mask).unwrap();
492 let bitmap = Bitmap::parse(&file).unwrap();
493 prop_assert_eq!(bitmap.source_size(), source);
494 for (n, rows) in refs.iter().enumerate() {
495 prop_assert_eq!(bitmap.style(n), Some(*rows));
496 }
497 prop_assert_eq!(bitmap.mask(), mask);
498 }
499 }
500}