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