use std::io::{self, ErrorKind, Write};
#[cfg(feature = "image")]
use image::{
ExtendedColorType, ImageEncoder, ImageError, ImageResult,
error::{EncodingError, ImageFormatHint},
};
#[derive(Debug)]
pub struct Encoder<W: Write> {
writer: W,
}
impl<W: Write> Encoder<W> {
#[inline]
pub const fn new(writer: W) -> Self {
Self { writer }
}
pub fn encode(
self,
buf: impl AsRef<[u8]>,
name: impl AsRef<str>,
width: u32,
height: u32,
x_hot: Option<u32>,
y_hot: Option<u32>,
) -> Result<(), Error> {
let inner = |mut encoder: Self,
buf: &[u8],
name: &str,
width: u32,
height: u32,
x_hot: Option<u32>,
y_hot: Option<u32>|
-> Result<(), Error> {
let width = usize::try_from(width).expect("width should be in the range of `usize`");
let dimensions = usize::try_from(height).map(|h| width * h);
assert_eq!(
Ok(buf.len()),
dimensions,
"`buf` and the image dimensions are different"
);
if buf.iter().any(|&p| p > 1) {
return Err(Error::new(
ErrorKind::InvalidData,
"`buf` contains values other than `0` and `1`",
));
}
let mut chars = name.chars();
if !chars.next().is_some_and(unicode_ident::is_xid_start)
|| !chars.all(unicode_ident::is_xid_continue)
{
return Err(Error::new(
ErrorKind::InvalidData,
"invalid C identifier prefix",
));
}
if x_hot.is_some() != y_hot.is_some() {
return Err(Error::new(
ErrorKind::InvalidInput,
"only one of `x_hot` and `y_hot` is `Some`",
));
}
writeln!(encoder.writer, "#define {name}_width {width}")?;
writeln!(encoder.writer, "#define {name}_height {height}")?;
if let Some(pos) = x_hot {
writeln!(encoder.writer, "#define {name}_x_hot {pos}")?;
}
if let Some(pos) = y_hot {
writeln!(encoder.writer, "#define {name}_y_hot {pos}")?;
}
writeln!(encoder.writer, "static unsigned char {name}_bits[] = {{")?;
let mut pixels_chunk = Vec::with_capacity(12);
for per_line in buf.chunks(width) {
for chunk in per_line.chunks(8) {
let mut pixels = u8::default();
for (i, pixel) in chunk.iter().enumerate() {
pixels |= pixel << i;
}
pixels_chunk.push(pixels);
if pixels_chunk.len() == 12 {
let line = pixels_chunk
.iter()
.map(|p| format!("{p:#04X}"))
.collect::<Vec<_>>()
.join(", ");
writeln!(encoder.writer, " {line},")?;
pixels_chunk.clear();
}
}
}
if !pixels_chunk.is_empty() {
let line = pixels_chunk
.into_iter()
.map(|p| format!("{p:#04X}"))
.collect::<Vec<_>>()
.join(", ");
writeln!(encoder.writer, " {line},")?;
}
write!(encoder.writer, "}};")
};
inner(
self,
buf.as_ref(),
name.as_ref(),
width,
height,
x_hot,
y_hot,
)
}
}
#[cfg(feature = "image")]
impl<W: Write> ImageEncoder for Encoder<W> {
fn write_image(
self,
buf: &[u8],
width: u32,
height: u32,
color_type: ExtendedColorType,
) -> ImageResult<()> {
let name = "image";
match color_type {
ExtendedColorType::L1 => self
.encode(buf, name, width, height, None, None)
.map_err(ImageError::IoError),
ExtendedColorType::L8 => {
let mut buf = buf.to_vec();
for p in &mut buf {
*p = u8::from(*p <= (u8::MAX / 2));
}
self.encode(buf, name, width, height, None, None)
.map_err(ImageError::IoError)
}
_ => Err(ImageError::Encoding(EncodingError::new(
ImageFormatHint::Name(String::from("XBM")),
format!("unsupported color type `{color_type:?}`"),
))),
}
}
}
pub type Error = io::Error;
#[cfg(test)]
mod tests {
use std::any;
use super::*;
#[test]
fn error_type() {
assert_eq!(any::type_name::<Error>(), any::type_name::<io::Error>());
}
}