smol bot
Diffstat (limited to 'src/bot/sorter.rs')
| -rw-r--r-- | src/bot/sorter.rs | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/src/bot/sorter.rs b/src/bot/sorter.rs new file mode 100644 index 0000000..c35f1a3 --- /dev/null +++ b/src/bot/sorter.rs @@ -0,0 +1,176 @@ +use anyhow::Result; +use atools::prelude::*; +use block::SORTER; +use exoquant::{ + ditherer::{self, Ditherer}, + Color, Remapper, SimpleColorSpace, +}; +use fimg::Image; +use mindus::*; +use poise::{serenity_prelude::*, ChoiceParameter}; + +#[derive(ChoiceParameter)] +enum Scaling { + /// dumbest, jaggedest, scaling algorithm + #[name = "nearest"] + Nearest, + /// prettiest scaling algorithm + #[name = "lanczos 3"] + Lanczos3, + #[name = "box"] + Box, + #[name = "bilinear"] + Bilinear, + #[name = "hamming"] + Hamming, + #[name = "catmull rom"] + CatmullRom, + #[name = "mitchell"] + Mitchell, +} + +#[derive(ChoiceParameter)] +enum Dithering { + #[name = "floyd steinberg"] + FloydSteinberg, + #[name = "ordered"] + /// A 2x2 ordered dithering. + Ordered, +} + +fn sort( + mut x: Image<Box<[u8]>, 4>, + height: Option<u8>, + width: Option<u8>, + algorithm: Option<Scaling>, + dithered: Option<Dithering>, +) -> (Image<Box<[u8]>, 4>, Schematic) { + const PAL: [Color; 23] = car::map!( + [0, 0, 0, 0].join(car::map!( + car::map!(mindus::item::Type::ALL, |i| i.color()), + |(r, g, b)| [r, g, b, 255] + )), + |[r, g, b, a]| Color { r, g, b, a } + ); + + if width.is_some() || height.is_some() { + let width = width.map(|x| x as u32).unwrap_or(x.width()); + let height = height.map(|x| x as u32).unwrap_or(x.height()); + x = match algorithm.unwrap_or(Scaling::Nearest) { + Scaling::Nearest => x.scale::<fimg::scale::Nearest>(width, height), + Scaling::Lanczos3 => x.scale::<fimg::scale::Lanczos3>(width, height), + Scaling::Box => x.scale::<fimg::scale::Box>(width, height), + Scaling::Bilinear => x.scale::<fimg::scale::Bilinear>(width, height), + Scaling::Hamming => x.scale::<fimg::scale::Hamming>(width, height), + Scaling::CatmullRom => x.scale::<fimg::scale::CatmullRom>(width, height), + Scaling::Mitchell => x.scale::<fimg::scale::Mitchell>(width, height), + }; + }; + + fn quant(x: Image<&[u8], 4>, d: impl Ditherer) -> Vec<u8> { + Remapper::new(&PAL, &SimpleColorSpace::default(), &d).remap( + &x.chunked() + .map(|&[r, g, b, a]| Color::new(r, g, b, a)) + .collect::<Vec<_>>(), + x.width() as usize, + ) + } + + let quant = match dithered { + Some(Dithering::FloydSteinberg) => quant(x.as_ref(), ditherer::FloydSteinberg::vanilla()), + Some(Dithering::Ordered) => quant(x.as_ref(), ditherer::Ordered), + None => quant(x.as_ref(), ditherer::None), + }; + + let (width, height) = (x.width() as usize, x.height() as usize); + let mut s = Schematic::new(width, height); + let pixels = (0..width) + .flat_map(|x_| (0..height).map(move |y| (x_, y))) + .filter_map( + move |(x_, y_)| match quant[(height - y_ - 1) * width + x_] { + 0 => None, + x => Some(((x_, y_), x - 1)), + }, + ); + for ((x, y), i) in pixels.clone() { + s.set( + x, + y, + &SORTER, + data::dynamic::DynData::Content(mindus::content::Type::Item, i as _), + block::Rotation::Up, + ) + .unwrap(); + } + let mut preview = Image::build(x.width(), x.height()).alloc(); + for ((x, y), i) in pixels { + unsafe { + preview.set_pixel( + x as _, + (height - y - 1) as _, + mindus::item::Type::ALL[i as usize] + .color() + .array() + .join(255), + ) + }; + } + ( + preview.scale::<fimg::scale::Nearest>(preview.width() * 4, preview.height() * 4), + s, + ) +} + +#[poise::command(slash_command)] +/// Create sorter representations of images. +pub async fn sorter( + c: super::Context<'_>, + #[description = "image: png, webp, jpg"] i: Attachment, + #[description = "height in blocks"] height: Option<u8>, + #[description = "height in blocks"] width: Option<u8>, + #[description = "scaling algorithm, defaults to nearest"] algorithm: Option<Scaling>, + #[description = "dithering algorithm, defaults to none"] dithered: Option<Dithering>, +) -> Result<()> { + c.defer().await?; + let image = i.download().await?; + match image::load_from_memory(&image) { + Ok(x) => { + let x = x.to_rgba8(); + let (preview, mut schem) = sort( + Image::<_, 4>::build(x.width(), x.height()) + .buf(x.into_vec()) + .boxed(), + width, + height, + algorithm, + dithered, + ); + use crate::emoji::to_mindustry::named::*; + schem + .tags + .insert("labels".to_string(), format!(r#"["{SORTER}"]"#)); + let mut h = std::hash::DefaultHasher::default(); + std::hash::Hasher::write(&mut h, preview.bytes()); + let h = std::hash::Hasher::finish(&h) as u32; + schem + .tags + .insert("name".to_string(), format!("{SORTER} #{h:x}")); + let mut buff = data::DataWrite::default(); + schem.serialize(&mut buff)?; + let buff = buff.consume(); + let mut preview_png = Vec::with_capacity(1 << 11); + fimg::WritePng::write(&preview, &mut preview_png).unwrap(); + poise::send_reply( + c, + poise::CreateReply::default() + .attachment(CreateAttachment::bytes(preview_png, "preview.png")) + .attachment(CreateAttachment::bytes(buff, format!("sorter{h:x}.msch"))), + ) + .await?; + } + Err(e) => { + c.reply(e.to_string()).await?; + } + } + Ok(()) +} |