smol bot
Diffstat (limited to 'src/bot/sorter.rs')
| -rw-r--r-- | src/bot/sorter.rs | 183 |
1 files changed, 116 insertions, 67 deletions
diff --git a/src/bot/sorter.rs b/src/bot/sorter.rs index c35f1a3..b0a68f2 100644 --- a/src/bot/sorter.rs +++ b/src/bot/sorter.rs @@ -1,13 +1,10 @@ use anyhow::Result; use atools::prelude::*; use block::SORTER; -use exoquant::{ - ditherer::{self, Ditherer}, - Color, Remapper, SimpleColorSpace, -}; -use fimg::Image; +use fimg::{indexed::IndexedImage, Image}; use mindus::*; use poise::{serenity_prelude::*, ChoiceParameter}; +use remapper::pal; #[derive(ChoiceParameter)] enum Scaling { @@ -30,69 +27,80 @@ enum Scaling { } #[derive(ChoiceParameter)] +/// seeks to reduce banding enum Dithering { - #[name = "floyd steinberg"] - FloydSteinberg, - #[name = "ordered"] - /// A 2x2 ordered dithering. - Ordered, + #[name = "atkinsons"] + /// error diffusion based dithering. + Atkinsons, + #[name = "bayer4x4"] + /// bayer matrix. + Bayer4x4, + #[name = "bayer8x8"] + /// bayer matrix. + Bayer8x8, + #[name = "bayer16x16"] + /// bayer matrix. + Bayer16x16, +} + +fn d<'a>( + x: Image<&[u8], 4>, + d: Option<Dithering>, + p: pal<'a, 4>, +) -> IndexedImage<Box<[u32]>, pal<'a, 4>> { + let x = Image::<Box<[f32]>, 4>::from(x); + match d { + None => remapper::ordered::remap(x.as_ref(), p), + Some(Dithering::Atkinsons) => remapper::diffusion::atkinson(x, p), + Some(Dithering::Bayer4x4) => remapper::ordered::bayer4x4(x.as_ref(), p), + Some(Dithering::Bayer8x8) => remapper::ordered::bayer8x8(x.as_ref(), p), + Some(Dithering::Bayer16x16) => remapper::ordered::bayer16x16(x.as_ref(), p), + } +} + +fn s(mut x: Image<Box<[u8]>, 4>, f: f32, a: Option<Scaling>) -> Image<Box<[u8]>, 4> { + let f = f.min(1.0); + let width = (x.width() as f32 * f).round() as u32; + let height = (x.height() as f32 * f).round() as u32; + match a.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 sort( mut x: Image<Box<[u8]>, 4>, - height: Option<u8>, - width: Option<u8>, + scale_factor: Option<f32>, 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 } - ); + const PAL: [[f32; 4]; 23] = [0.; 4].join(car::map!( + car::map!(mindus::item::Type::ALL, |i| i.color()), + |(r, g, b)| [r as f32 / 256.0, g as f32 / 256.0, b as f32 / 256.0, 1.0] + )); - 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), - }; + if let Some(f) = scale_factor { + x = s(x, f, algorithm); }; - 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 mut quant = d(x.as_ref(), dithered, pal::new(&PAL)); 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_] { + .filter_map(|(x_, y_)| { + match unsafe { quant.raw().buffer() }[(height - y_ - 1) * width + x_] { 0 => None, x => Some(((x_, y_), x - 1)), - }, - ); - for ((x, y), i) in pixels.clone() { + } + }); + for ((x, y), i) in pixels { s.set( x, y, @@ -102,32 +110,35 @@ fn sort( ) .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), - ) - }; - } + let mut preview = quant.to().to_u8(); ( preview.scale::<fimg::scale::Nearest>(preview.width() * 4, preview.height() * 4), s, ) } +fn map( + mut x: Image<Box<[u8]>, 4>, + scale_factor: Option<f32>, + algorithm: Option<Scaling>, + dithered: Option<Dithering>, +) -> Image<Box<[u8]>, 4> { + const PAL: &[[f32; 3]] = unsafe { include!("colors").as_chunks_unchecked::<3>() }; + + if let Some(f) = scale_factor { + x = s(x, f, algorithm); + }; + + let pal = PAL.iter().map(|&x| x.join(1.0)).collect::<Vec<_>>(); + d(x.as_ref(), dithered, pal::new(&pal)).to().to_u8() +} + #[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 factor"] factor: Option<f32>, #[description = "scaling algorithm, defaults to nearest"] algorithm: Option<Scaling>, #[description = "dithering algorithm, defaults to none"] dithered: Option<Dithering>, ) -> Result<()> { @@ -140,8 +151,7 @@ pub async fn sorter( Image::<_, 4>::build(x.width(), x.height()) .buf(x.into_vec()) .boxed(), - width, - height, + factor, algorithm, dithered, ); @@ -174,3 +184,42 @@ pub async fn sorter( } Ok(()) } + +#[poise::command(slash_command)] +/// Create map representations of images. +pub async fn mapper( + c: super::Context<'_>, + #[description = "image: png, webp, jpg"] i: Attachment, + #[description = "scaling factor"] factor: Option<f32>, + #[description = "scaling algorithm, defaults to nearest"] algorithm: Option<Scaling>, + #[description = "dithering algorithm, defaults to none (if you want the map to be playable, go with ordered)"] + 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 = map( + Image::<_, 4>::build(x.width(), x.height()) + .buf(x.into_vec()) + .boxed(), + factor, + algorithm, + dithered, + ); + 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")), + ) + .await?; + } + Err(e) => { + c.reply(e.to_string()).await?; + } + } + Ok(()) +} |