diff --git a/Cargo.lock b/Cargo.lock index 2bb44fc..ebaf3d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -243,29 +243,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bindgen" -version = "0.69.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" -dependencies = [ - "bitflags 2.9.1", - "cexpr", - "clang-sys", - "itertools", - "lazy_static", - "lazycell", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.101", - "which", -] - [[package]] name = "bit_field" version = "0.10.2" @@ -419,15 +396,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - [[package]] name = "cfg-expr" version = "0.15.8" @@ -459,17 +427,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", -] - [[package]] name = "color_quant" version = "1.1.0" @@ -1653,12 +1610,6 @@ dependencies = [ "spin", ] -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "lebe" version = "0.5.2" @@ -1687,16 +1638,6 @@ dependencies = [ "cc", ] -[[package]] -name = "libloading" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c" -dependencies = [ - "cfg-if", - "windows-targets 0.53.0", -] - [[package]] name = "libm" version = "0.2.15" @@ -1714,12 +1655,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - [[package]] name = "linux-raw-sys" version = "0.9.4" @@ -1757,15 +1692,6 @@ dependencies = [ "imgref", ] -[[package]] -name = "magick_rust" -version = "1.0.0" -dependencies = [ - "bindgen", - "libc", - "pkg-config", -] - [[package]] name = "maybe-rayon" version = "0.1.1" @@ -2266,16 +2192,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "prettyplease" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" -dependencies = [ - "proc-macro2", - "syn 2.0.101", -] - [[package]] name = "proc-macro2" version = "1.0.95" @@ -2462,7 +2378,7 @@ checksum = "78c81d000a2c524133cc00d2f92f019d399e57906c3b7119271a2495354fe895" dependencies = [ "cfg-if", "libc", - "rustix 1.0.7", + "rustix", "windows", ] @@ -2644,25 +2560,6 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.9.1", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - [[package]] name = "rustix" version = "1.0.7" @@ -2672,7 +2569,7 @@ dependencies = [ "bitflags 2.9.1", "errno", "libc", - "linux-raw-sys 0.9.4", + "linux-raw-sys", "windows-sys 0.59.0", ] @@ -3477,7 +3374,7 @@ dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", - "rustix 1.0.7", + "rustix", "windows-sys 0.59.0", ] @@ -4153,7 +4050,7 @@ dependencies = [ [[package]] name = "watcat" -version = "0.1.0" +version = "0.2.0" dependencies = [ "arabic_reshaper", "dotenvy", @@ -4161,12 +4058,12 @@ dependencies = [ "image", "imagetext", "lastfm", - "magick_rust", "palette", "reqwest 0.12.15", "reqwest-middleware", "serenity", "sqlx", + "tiny-skia", "tokio", "unicode-bidi", ] @@ -4211,18 +4108,6 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix 0.38.44", -] - [[package]] name = "whoami" version = "1.6.0" diff --git a/Cargo.toml b/Cargo.toml index dd8a8f1..26e9b6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,14 +11,15 @@ reqwest = "0.12" reqwest-middleware = "0.4.2" http-cache-reqwest = "0.15.1" palette = "0.7.6" +tiny-skia = "0.11.4" # text rendering dependencies arabic_reshaper = "0.4.2" unicode-bidi = "0.3.18" image = "0.25.6" imagetext = { git = "https://github.com/nathanielfernandes/imagetext" } -[dependencies.magick_rust] -path = "./magick-rust" +#[dependencies.magick_rust] +#path = "./magick-rust" [dependencies.tokio] version = "1.21.2" diff --git a/src/img.rs b/src/img.rs new file mode 100644 index 0000000..7d319ad --- /dev/null +++ b/src/img.rs @@ -0,0 +1,21 @@ + +use image::{RgbaImage, ExtendedColorType, ImageEncoder, load_from_memory}; +use image::codecs::png::PngEncoder; + +pub fn save_to_memory_png(p: &RgbaImage) -> Result, String> { + let (width, height) = p.dimensions(); + let mut blob = Vec::::new(); + let png_encoder = PngEncoder::new(&mut blob); + match png_encoder.write_image(p, width, height, ExtendedColorType::Rgba8) { + Ok(()) => Ok(blob), + Err(_) => Err("Failed to encode text png".to_string()), + } +} + +pub fn ensure_32_bit_png(p: &[u8]) -> Result, String> { + let img = match load_from_memory(p) { + Ok(i) => i, + Err(e) => return Err(format!("Failed to load png: {e}")), + }; + save_to_memory_png(&img.into_rgba8()) +} diff --git a/src/main.rs b/src/main.rs index 9fbadac..64bba08 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,7 @@ +#![allow(dead_code)] +#![allow(unused_variables)] +#![allow(unused_imports)] +#![allow(unused_macros)] extern crate dotenvy; extern crate lastfm; extern crate reqwest; @@ -10,8 +14,11 @@ use serenity::model::channel::Message; use serenity::model::id::UserId; use serenity::prelude::*; -extern crate magick_rust; -use magick_rust::{ColorspaceType, CompositeOperator, MagickWand, PixelWand, magick_wand_genesis, FilterType}; +extern crate tiny_skia; +extern crate image; + +//extern crate magick_rust; +//use magick_rust::{ColorspaceType, CompositeOperator, MagickWand, PixelWand, magick_wand_genesis, FilterType}; use std::env; use std::sync::Once; @@ -27,6 +34,7 @@ use imagetext::fontdb::FontDB; use imagetext::superfont::SuperFont; mod text; +mod img; static START: Once = Once::new(); @@ -60,18 +68,19 @@ macro_rules! log { }; } -macro_rules! handle_magick_result { +macro_rules! handle_result { ($a: expr, $b: literal) => { match $a { - Ok(_) => { + Ok(a) => { log!("{} run successfully", stringify!($a)); + a } Err(e) => return Reply::Text(format!("Error: {} {}", $b, e)), } }; } -macro_rules! handle_magick_option { +macro_rules! handle_option { ($a: expr, $b: literal) => { match $a { Some(res) => res, @@ -102,7 +111,7 @@ fn parse_cielab(s: String) -> Result<(f32, f32, f32), &'static str> { } } -fn validate_color(col: &PixelWand) -> Option { +/*fn validate_color(col: &PixelWand) -> Option { let color_str = match col.get_color_as_string() { Ok(s) => s, Err(_) => return None, @@ -112,17 +121,18 @@ fn validate_color(col: &PixelWand) -> Option { Err(_) => return None, }; Some(Lab::::from_components(color_raw)) -} +}*/ + +const FMI_WIDTH: u32 = 548; +const FMI_HEIGHT: u32 = 147; +const FMI_GAP: i32 = 12; async fn fmi(ctx: &Context, arg: &str, id: UserId, avatar: Option) -> Reply { let lastfm_user = match arg { "" => get_lastfm_username(ctx, id).await, _ => Some(arg.to_string()), }; - let lastfm_client = match lastfm_user { - Some(s) => lastfm::Client::::from_env(s), - None => return Reply::Text("No last.fm username set.".to_string()), - }; + let lastfm_client = lastfm::Client::::from_env(handle_option!(lastfm_user, "No last.fm username set.")); let now_playing = match lastfm_client.now_playing().await { Ok(np) => np, Err(e) => return Reply::Text(format!("Error: grabbing last.fm user data failed {e}")), @@ -141,19 +151,34 @@ async fn fmi(ctx: &Context, arg: &str, id: UserId, avatar: Option) -> Re None => return Reply::Text("Error: getting image uri failed".to_string()), }; - let image = match get_image(ctx, image_uri.as_str()).await { + let image_base = match get_image(ctx, image_uri.as_str()).await { Ok(i) => i, - Err(e) => return Reply::Text(format!("{}", e)), + Err(e) => return Reply::Text(e.to_string()), }; - let mut base_color = PixelWand::new(); + //let image_base_raw = Vec::::new(); + //let png_decoder = image::codecs::png::PngDecoder::new(&mut image_base); + //let mut image_rgba8 = Vec::::new(); + //let png_encoder = image::codecs::png::PngEncoder::new(&mut image_rgba8); + //png_encoder.write_image(&image_base, image) + /*let mut base_color = PixelWand::new(); let mut white = PixelWand::new(); let main_wand = MagickWand::new(); let art_wand = MagickWand::new(); let mut art_wand_cluster = MagickWand::new(); let mask_wand = MagickWand::new(); - let text_image_wand = MagickWand::new(); - - handle_magick_result!( + let text_image_wand = MagickWand::new();*/ + let mut main_image = handle_option!(tiny_skia::Pixmap::new(FMI_WIDTH, FMI_HEIGHT), "Failed to load main image"); + let album_image = handle_result!(tiny_skia::Pixmap::decode_png(&image_base), "Failed to load album art image"); + //let album_image = handle_result!(tiny_skia::Pixmap::decode_png(&image_rgba8), "Failed to decode album art"); + let art_size = (FMI_HEIGHT as i32 - 2_i32 * FMI_GAP) as f32 / 300.0; + let scale_matrix = tiny_skia::Transform::from_scale(art_size, art_size); + let paint = tiny_skia::PixmapPaint { + opacity: 1.0, + blend_mode: tiny_skia::BlendMode::Source, + quality: tiny_skia::FilterQuality::Bilinear, + }; + main_image.draw_pixmap(FMI_GAP, FMI_GAP, album_image.as_ref(), &paint, scale_matrix, None); + /*handle_magick_result!( base_color.set_color("#7f7f7f"), "Failed to set init base color" ); @@ -329,8 +354,9 @@ async fn fmi(ctx: &Context, arg: &str, id: UserId, avatar: Option) -> Re handle_magick_result!( main_wand.compose_images(&avatar_wand, CompositeOperator::SrcOver, false, 473, 73), "Failed to combine avatar image" - ); - Reply::Image(main_wand.write_image_blob("png").unwrap()) + );*/ + //Reply::Image(main_wand.write_image_blob("png").unwrap()) + Reply::Image(main_image.encode_png().unwrap()) } async fn set(ctx: &Context, arg: &str, id: UserId) -> Reply { @@ -438,7 +464,7 @@ async fn set_lastfm_username(ctx: &Context, id: UserId, user: String) { .expect("Failed to insert"); } -async fn get_image(ctx: &Context, url: &str) -> Result, reqwest_middleware::Error> { +async fn get_image(ctx: &Context, url: &str) -> Result, String> { log!("get {}", url); let data = ctx.data.write().await; let http = data @@ -447,13 +473,14 @@ async fn get_image(ctx: &Context, url: &str) -> Result, reqwest_middlewa match http.get(url).send().await { Ok(resp) => { log!("response received"); - Ok(resp + let img_raw = resp .bytes() .await .expect("Unable to resolve bytes") - .to_vec()) + .to_vec(); + img::ensure_32_bit_png(&img_raw) } - Err(e) => Err(e), + Err(e) => Err(format!("{e}")), } } @@ -461,7 +488,7 @@ async fn get_image(ctx: &Context, url: &str) -> Result, reqwest_middlewa async fn main() { START.call_once(|| { dotenvy::dotenv().expect("Failed to load .env"); - magick_wand_genesis(); + //magick_wand_genesis(); }); let db_url = env::var("DATABASE_URL").expect("Failed to load DATABASE_URL"); diff --git a/src/text.rs b/src/text.rs index 269e315..b2e74f6 100644 --- a/src/text.rs +++ b/src/text.rs @@ -11,6 +11,8 @@ use image::{RgbaImage, ExtendedColorType, ImageEncoder}; use image::codecs::png::PngEncoder; use unicode_bidi::BidiInfo; +use crate::img; + enum TextField { Title, Artist, @@ -112,10 +114,5 @@ pub fn fmi_text(width: u32, height: u32, track: TrackInfo, fonts: &(SuperFont<'s draw_info(&mut img, track.title, TextField::Title, &fonts.1.clone(), font_size, dark_text)?; draw_info(&mut img, track.artist, TextField::Artist, &fonts.0.clone(), font_size, dark_text)?; draw_info(&mut img, track.album, TextField::Album, &fonts.0.clone(), font_size, dark_text)?; - let mut blob = Vec::::new(); - let png_encoder = PngEncoder::new(&mut blob); - match png_encoder.write_image(&img, width, height, ExtendedColorType::Rgba8) { - Ok(()) => Ok(blob), - Err(_) => Err("Failed to encode text png".to_string()), - } + img::save_to_memory_png(&img) }