smol bot
Diffstat (limited to 'src/bot/sorter.rs')
-rw-r--r--src/bot/sorter.rs176
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(())
+}