use std::io::Cursor;
use image::{ImageFormat, Rgb, RgbImage};
use crate::pipeline::PipelineError;
pub struct Drawing<'a> {
pub rows: &'a [Vec<bool>],
pub margin: usize,
pub module_px: u32,
pub row_px: u32,
pub dark: [u8; 3],
pub light: [u8; 3],
}
impl Drawing<'_> {
fn columns(&self) -> usize {
self.rows.first().map_or(0, Vec::len)
}
pub fn width_px(&self) -> u32 {
(self.columns() + 2 * self.margin) as u32 * self.module_px
}
pub fn height_px(&self) -> u32 {
self.rows.len() as u32 * self.row_px + 2 * self.margin as u32 * self.module_px
}
pub fn svg(&self) -> String {
let cols = self.columns() + 2 * self.margin;
let unit_h = self.row_px as f64 / self.module_px as f64;
let rows_h = self.rows.len() as f64 * unit_h + 2.0 * self.margin as f64;
let mut path = String::new();
for (y, row) in self.rows.iter().enumerate() {
let top = self.margin as f64 + y as f64 * unit_h;
let mut x = 0;
while x < row.len() {
if row[x] {
let start = x;
while x < row.len() && row[x] {
x += 1;
}
path.push_str(&format!("M{},{}h{}v{}h-{}z", start + self.margin, trim(top), x - start, trim(unit_h), x - start));
} else {
x += 1;
}
}
}
format!(
"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"{w}\" height=\"{h}\" viewBox=\"0 0 {cols} {vh}\" shape-rendering=\"crispEdges\">\
<rect width=\"100%\" height=\"100%\" fill=\"{light}\"/><path fill=\"{dark}\" d=\"{path}\"/></svg>",
w = self.width_px(),
h = self.height_px(),
vh = trim(rows_h),
light = hex(self.light),
dark = hex(self.dark),
)
}
pub fn png(&self) -> Result<Vec<u8>, PipelineError> {
let (w, h) = crate::pipeline::nodes::shared::limits::raster(self.width_px(), self.height_px(), super::node::SIZE_CODE)?;
let top = self.margin as u32 * self.module_px;
let mut img = RgbImage::from_pixel(w, h, Rgb(self.light));
for (y, row) in self.rows.iter().enumerate() {
for (x, &dark) in row.iter().enumerate() {
if !dark {
continue;
}
let px = (x + self.margin) as u32 * self.module_px;
let py = top + y as u32 * self.row_px;
for dy in 0..self.row_px {
for dx in 0..self.module_px {
img.put_pixel(px + dx, py + dy, Rgb(self.dark));
}
}
}
}
let mut out = Vec::new();
img.write_to(&mut Cursor::new(&mut out), ImageFormat::Png)
.map_err(|e| PipelineError::new(super::node::CODE, format!("PNG encode: {e}")))?;
Ok(out)
}
}
fn trim(v: f64) -> String {
let s = format!("{v:.3}");
s.trim_end_matches('0').trim_end_matches('.').to_string()
}
fn hex(rgb: [u8; 3]) -> String {
format!("#{:02x}{:02x}{:02x}", rgb[0], rgb[1], rgb[2])
}
pub fn parse_colour(raw: &str) -> Option<[u8; 3]> {
let hex = raw.trim().strip_prefix('#')?;
let digits: Vec<u8> = hex.chars().map(|c| c.to_digit(16).map(|d| d as u8)).collect::<Option<_>>()?;
match digits.as_slice() {
[r, g, b] => Some([r * 17, g * 17, b * 17]),
[r1, r2, g1, g2, b1, b2] => Some([r1 * 16 + r2, g1 * 16 + g2, b1 * 16 + b2]),
_ => None,
}
}