diff --git a/Cargo.lock b/Cargo.lock index d58cf39880839b697745fdfd8ce2649c82521d5f..e8c82f96ca6abfd7089a3fc7671872bbff92daad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1201,6 +1201,7 @@ dependencies = [ "imageproc", "log", "mpris", + "read_color", "reqwest", "serde", "serde_derive", @@ -1649,6 +1650,12 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "read_color" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f4c8858baa4ad3c8bcc156ae91a0ffe22b76a3975c40c49b4f04c15c6bce0da" + [[package]] name = "redox_syscall" version = "0.1.56" diff --git a/Cargo.toml b/Cargo.toml index 521b176dad4504b891865915c672158fb9ded913..846f25c54608df1a36441a66a6e6d286eac29cc1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ version = "0.1.7" [dependencies.log] # A lightweight logging facade for Rust. version = "0.4" + [dependencies.mpris] # Idiomatic MPRIS D-Bus interface library. version = "2.0.0-rc2" @@ -35,6 +36,7 @@ version = "0.5" features = [ "colored" ] + [dependencies.dbus] # Used to listen to D-bus daemon. version = "0.8.1" @@ -44,12 +46,6 @@ features = [ "blocking" ] -#[dependencies.xcb] -#version = "0.9.0" -#features = [ -# "randr" -#] - [dependencies.gtk] version = "0.8.1" @@ -72,4 +68,7 @@ version = "0.3" version = "0.23.1" [dependencies.imageproc] -version = "0.20.0" \ No newline at end of file +version = "0.20.0" + +[dependencies.read_color] +version = "1.0.0" \ No newline at end of file diff --git a/PKGBUILD b/PKGBUILD index 43c55549f83cf316107e11c07191610a7049d352..7e57c068746b97f17c021de757b30120037a5ec0 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -11,12 +11,13 @@ depends=( 'rust' 'systemd' 'git' + 'feh' + 'gtk3' ) makedepends=( 'rust' ) -# source=("") -# sha1sums=('') + build() { cd "$startdir/" diff --git a/src/background.rs b/src/background.rs index bc0016f670fe7bb286db7fff02f75482d46083d1..a0bb337fc8c3a01e298ebac3a993fdf44f3b93de 100644 --- a/src/background.rs +++ b/src/background.rs @@ -40,4 +40,4 @@ pub fn set_wallpaper(path: &str) { if let Err(set_error) = out { error!("Can't set background: {}", set_error.to_string()); } -} \ No newline at end of file +} diff --git a/src/config/image_processors.rs b/src/config/image_processors.rs index 14f7bfc3d167bae7b5c32dd898b2b5247c4a74c2..0645adbb211e919f43263ab6cc7869e34abddb0a 100644 --- a/src/config/image_processors.rs +++ b/src/config/image_processors.rs @@ -1,6 +1,6 @@ use crate::img_processors::Processors; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Clone, Deserialize, Debug)] pub struct ProcessorParams { pub effect: Processors, #[serde(default = "default_strength")] @@ -21,4 +21,4 @@ impl Default for ProcessorParams { layer: 0, } } -} \ No newline at end of file +} diff --git a/src/config/mod.rs b/src/config/mod.rs index 3d4ac635421345de754c929046a0c76824fa9943..b1cde04cc79bc99da9660cab7565d841cf93a4fb 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -5,8 +5,8 @@ use serde_derive::Deserialize; use crate::result::{MBGError, MBGResult}; -pub mod logger; pub mod image_processors; +pub mod logger; #[derive(Serialize, Default, Deserialize, Debug)] pub struct Config { @@ -64,7 +64,9 @@ impl Config { config_path.push(".mbg.toml"); let mut conf_file = File::create(config_path)?; let mut default_conf = Self::default(); - default_conf.processors.push(image_processors::ProcessorParams::default()); + default_conf + .processors + .push(image_processors::ProcessorParams::default()); default_conf.blender.push(0); let contents = toml::to_string_pretty(&default_conf)?; conf_file.write_all(contents.as_bytes())?; diff --git a/src/display.rs b/src/display.rs index 124f5baf86f5d1ac530ca62f95f3ccda6c0b8299..e3c9f3e7ae84b2df4040488f5681b97d158c6d0f 100644 --- a/src/display.rs +++ b/src/display.rs @@ -29,7 +29,6 @@ pub fn get_max_resolution() -> MBGResult<(i32, i32)> { Ok((height, width)) } - // pub fn update_bg(img_bytes: Vec<u8>) -> MBGResult<()> { // debug!("TODO: Change background."); // file.write_all(img_bytes.as_slice())?; @@ -52,4 +51,4 @@ pub fn get_max_resolution() -> MBGResult<(i32, i32)> { // // debug!("{}", count); // // debug!("{}x{}", screen.width_in_pixels(), screen.height_in_pixels()); // Ok(()) -// } \ No newline at end of file +// } diff --git a/src/img_processors/blur.rs b/src/img_processors/blur.rs index 7456e5786492c8815e14c0966a23497a91a9cc69..2432acfb52b85863b36f6b2193286adf7e979dc4 100644 --- a/src/img_processors/blur.rs +++ b/src/img_processors/blur.rs @@ -1,21 +1,21 @@ -use image::{DynamicImage}; +use image::DynamicImage; use imageproc::filter::gaussian_blur_f32; use crate::config::image_processors::ProcessorParams; -use crate::result::{MBGResult}; +use crate::result::MBGResult; use super::ImageProcessor; pub struct BlurProcessor { pub params: ProcessorParams, - pub screen_height: i32, - pub screen_width: i32, } impl ImageProcessor for BlurProcessor { fn process(&self, img: &DynamicImage) -> MBGResult<DynamicImage> { debug!("Blurring image with sigma: {}", self.params.strength); - Ok(DynamicImage::ImageRgba8(gaussian_blur_f32(&img.to_rgba(), self.params.strength))) + Ok(DynamicImage::ImageRgba8(gaussian_blur_f32( + &img.to_rgba(), + self.params.strength, + ))) } } - diff --git a/src/img_processors/border.rs b/src/img_processors/border.rs new file mode 100644 index 0000000000000000000000000000000000000000..b9b41c925bfc11ea5512503f478170aaf6a0d657 --- /dev/null +++ b/src/img_processors/border.rs @@ -0,0 +1,88 @@ +use std::cmp::min; + +use image::{DynamicImage, GenericImageView, Rgba, RgbaImage}; +use image::imageops::{FilterType, overlay}; +use imageproc::drawing::{Blend, Canvas, draw_filled_circle_mut}; + +use crate::config::image_processors::ProcessorParams; +use crate::img_processors::ImageProcessor; +use crate::result::{MBGError, MBGResult}; + +pub struct Border { + pub params: ProcessorParams, + pub circle: bool, + pub color: String, + pub width: u32, +} + + +impl Border { + fn crop_circle(&self, img: &DynamicImage) -> MBGResult<DynamicImage> { + let img_height = image::GenericImageView::height(img); + let img_width = image::GenericImageView::width(img); + let transparent_pixel = Rgba([0u8, 0u8, 0u8, 0u8]); + let mut mask = Blend(RgbaImage::from_pixel(img_width * 3, img_height * 3, transparent_pixel)); + let center = ((mask.width() / 2) as i32, (mask.height() / 2) as i32); + let radius = min(center.0, center.1); + let black_pixel = Rgba([0u8, 0u8, 0u8, 255u8]); + draw_filled_circle_mut(&mut mask, center, radius, black_pixel); + let mut mask = DynamicImage::ImageRgba8(mask.0); + mask = mask.resize(img_width, img_height, FilterType::Nearest); + let mut res = mask.clone(); + for (x, y, pix) in mask.pixels() { + let pixel = image::GenericImageView::get_pixel(img, x, y).0; + res.draw_pixel(x, y, Rgba([pixel[0], pixel[1], pixel[2], pix.0[3]])); + } + Ok(res) + } + + fn draw_border(&self, img: &DynamicImage) -> MBGResult<DynamicImage> { + let img_width = image::GenericImageView::width(img); + let img_height = image::GenericImageView::height(img); + let center = (((img_width / 2) + self.width), ((img_height / 2) + self.width)); + let target_color = read_color::rgb_maybe_a(&mut self.color.chars()); + if target_color.is_none() { + return Err( + MBGError::ConfigErr(String::from("Can't parse border color. Please use hex value.")) + ); + } + let target_color = target_color.unwrap(); + let target_color = match target_color { + ([r, g, b, ], Some(a)) => Rgba([r, g, b, a]), + ([r, g, b, ], None) => Rgba([r, g, b, 255u8]), + }; + if self.circle { + let mut mask = DynamicImage::ImageRgba8(RgbaImage::from_pixel( + img_width + self.width * 2, + img_height + self.width * 2, + Rgba([0u8, 0u8, 0u8, 0u8]), + )); + let radius = min(center.0, center.1); + let cir_center = (center.0 as i32, center.1 as i32); + draw_filled_circle_mut(&mut mask, cir_center, radius as i32, target_color); + overlay(&mut mask, img, self.width, self.width); + Ok(mask) + } else { + let mut mask = DynamicImage::ImageRgba8(RgbaImage::from_pixel( + img_width + self.width * 2, + img_height + self.width * 2, + target_color, + )); + overlay(&mut mask, img, self.width, self.width); + Ok(mask) + } + } +} + +impl ImageProcessor for Border { + fn process(&self, img: &DynamicImage) -> MBGResult<DynamicImage> { + let mut res = img.clone(); + if self.circle { + res = self.crop_circle(&res)?; + } + if self.width > 0 { + res = self.draw_border(&res)?; + } + Ok(res) + } +} diff --git a/src/img_processors/crop.rs b/src/img_processors/crop.rs index 72a5794d256f71a60a5e87f0ba5bcde45d28e129..498b47eb56958a41f9d1e21ccc99b8ead6511f05 100644 --- a/src/img_processors/crop.rs +++ b/src/img_processors/crop.rs @@ -17,11 +17,26 @@ pub struct CropProcessor { impl ImageProcessor for CropProcessor { fn process(&self, img: &DynamicImage) -> MBGResult<DynamicImage> { let mut res = img.clone(); - let requested_width = if self.fit { self.screen_width as u32 } else { self.width }; - let requested_height = if self.fit { self.screen_height as u32 } else { self.height }; - debug!("Cropping image to be {}x{}", requested_width, requested_height); - let start_x = (res.width() / 2).checked_sub(requested_width / 2).unwrap_or_else(|| 0); - let start_y = (res.height() / 2).checked_sub(requested_height / 2).unwrap_or_else(|| 0); + let requested_width = if self.fit { + self.screen_width as u32 + } else { + self.width + }; + let requested_height = if self.fit { + self.screen_height as u32 + } else { + self.height + }; + debug!( + "Cropping image to be {}x{}", + requested_width, requested_height + ); + let start_x = (res.width() / 2) + .checked_sub(requested_width / 2) + .unwrap_or_else(|| 0); + let start_y = (res.height() / 2) + .checked_sub(requested_height / 2) + .unwrap_or_else(|| 0); Ok(res.crop(start_x, start_y, requested_width, requested_height)) } -} \ No newline at end of file +} diff --git a/src/img_processors/mod.rs b/src/img_processors/mod.rs index 4a2c523aeabe323871f9caa20a2dbce7be44a62d..d9d13e38de41a5a7d41e30c39120a11cb1e84d7d 100644 --- a/src/img_processors/mod.rs +++ b/src/img_processors/mod.rs @@ -1,17 +1,18 @@ -use image::{DynamicImage, GenericImageView}; use image::imageops::overlay; +use image::{DynamicImage, GenericImageView}; -use crate::config::Config; use crate::config::image_processors::ProcessorParams; +use crate::config::Config; use crate::display::get_max_resolution; use crate::result::{MBGError, MBGResult}; -pub mod scale; -pub mod none; -pub mod crop; pub mod blur; +pub mod border; +pub mod crop; +pub mod none; +pub mod scale; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Clone, Deserialize, Debug)] pub enum Processors { None, Blur, @@ -24,6 +25,14 @@ pub enum Processors { #[serde(default)] fit: bool, }, + Border { + #[serde(default)] + circle: bool, + #[serde(default)] + color: String, + #[serde(default)] + width: u32, + }, } pub trait ImageProcessor { @@ -70,7 +79,9 @@ pub fn process_image(img_bytes: Vec<u8>) -> MBGResult<DynamicImage> { debug!("Filter applied"); } if layers.is_empty() { - return Err(MBGError::ImageError(String::from("No processors was found."))); + return Err(MBGError::ImageError(String::from( + "No processors was found.", + ))); } if conf.blender.is_empty() { Ok(layers[0].clone()) @@ -79,7 +90,6 @@ pub fn process_image(img_bytes: Vec<u8>) -> MBGResult<DynamicImage> { } } - pub fn blend_layers(layers: Vec<DynamicImage>, blender: Vec<u8>) -> MBGResult<DynamicImage> { let mut res = layers[0].clone(); for index in blender.into_iter() { @@ -96,14 +106,40 @@ impl_params_getter!( none::NoneProcessor, blur::BlurProcessor, scale::ScaleProcessor, - crop::CropProcessor + crop::CropProcessor, + border::Border ); -fn mapper(params: ProcessorParams, screen_height: i32, screen_width: i32) -> Box<dyn ImgFilterTrait> { - match params.effect { - Processors::None => { Box::new(none::NoneProcessor { params, screen_height, screen_width }) } - Processors::Blur => { Box::new(blur::BlurProcessor { params, screen_height, screen_width }) } - Processors::Scale => { Box::new(scale::ScaleProcessor { params, screen_height, screen_width }) } - Processors::Crop { width, height, fit } => { Box::new(crop::CropProcessor { params, screen_height, screen_width, height, width, fit }) } +fn mapper( + params: ProcessorParams, + screen_height: i32, + screen_width: i32, +) -> Box<dyn ImgFilterTrait> { + match params.clone().effect { + Processors::None => Box::new(none::NoneProcessor { params }), + Processors::Blur => Box::new(blur::BlurProcessor { params }), + Processors::Scale => Box::new(scale::ScaleProcessor { + params, + screen_height, + screen_width, + }), + Processors::Crop { width, height, fit } => Box::new(crop::CropProcessor { + params, + screen_height, + screen_width, + height, + width, + fit, + }), + Processors::Border { + circle, + color, + width, + } => Box::new(border::Border { + params, + circle, + color, + width, + }), } } \ No newline at end of file diff --git a/src/img_processors/none.rs b/src/img_processors/none.rs index 6cdee0cdb8f67670cb0f6dd8cc7f8f55c8491613..f19725371c383e3cabcc74082a48b98598290d45 100644 --- a/src/img_processors/none.rs +++ b/src/img_processors/none.rs @@ -6,8 +6,6 @@ use crate::result::MBGResult; pub struct NoneProcessor { pub params: ProcessorParams, - pub screen_height: i32, - pub screen_width: i32, } impl ImageProcessor for NoneProcessor { @@ -15,4 +13,4 @@ impl ImageProcessor for NoneProcessor { debug!("Do nothing with image"); Ok(img.clone()) } -} \ No newline at end of file +} diff --git a/src/img_processors/scale.rs b/src/img_processors/scale.rs index 3b8320de3053bdb4ec86b6b37e9ec725f1e9a7e9..e1c20e3d6748ec21593d9d098110689e79c557ae 100644 --- a/src/img_processors/scale.rs +++ b/src/img_processors/scale.rs @@ -1,5 +1,5 @@ -use image::{DynamicImage, GenericImageView}; use image::imageops::FilterType; +use image::{DynamicImage, GenericImageView}; use crate::config::image_processors::ProcessorParams; use crate::result::MBGResult; @@ -16,6 +16,10 @@ impl super::ImageProcessor for ScaleProcessor { let scale_factor = ((abs_width / img.width()) as f32) * self.params.strength; let scale_factor = scale_factor.ceil() as u32; debug!("Scaling image by {} times", scale_factor); - Ok(img.resize(img.width() * scale_factor, img.height() * scale_factor, FilterType::Nearest)) + Ok(img.resize( + img.width() * scale_factor, + img.height() * scale_factor, + FilterType::Nearest, + )) } } diff --git a/src/main.rs b/src/main.rs index bfeeb16da6a8a1f16c9cf8a669d30cb927e2be8d..af98e4d430e4141194fe76588a6e77980a462d54 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,16 +17,16 @@ use crate::result::{MBGError, MBGResult}; pub mod background; pub mod config; pub mod dbus_interface; +pub mod display; pub mod img_processors; pub mod logging; pub mod player_dbus; -pub mod display; pub mod result; #[derive(Debug, StructOpt)] #[structopt( -name = "music_bg", -about = "Listens to your mpris interface and sets you a cool background." + name = "music_bg", + about = "Listens to your mpris interface and sets you a cool background." )] struct Opt { #[structopt(subcommand)] @@ -38,8 +38,8 @@ enum RunMode { #[structopt(name = "run", about = "Run d-bus listener")] Run, #[structopt( - name = "config", - about = "Create or replace default configuration file." + name = "config", + about = "Create or replace default configuration file." )] GenConf, } diff --git a/src/player_dbus.rs b/src/player_dbus.rs index dabcdf41e0abfc9b21ed13f1ddb68dfa94b0857b..5ece33b29ec71289e3be9e837493371dc2568a44 100644 --- a/src/player_dbus.rs +++ b/src/player_dbus.rs @@ -1,10 +1,10 @@ use std::collections::HashMap; use std::time::Duration; -use dbus::{arg, Message}; use dbus::arg::RefArg; use dbus::blocking::Connection; use dbus::message::{MatchRule, SignalArgs}; +use dbus::{arg, Message}; use crate::background::{nitrogen_restore, process_image_url, reset_background_handler}; use crate::config::Config; diff --git a/src/result.rs b/src/result.rs index ead167a1995afd4bc086199285f1bd1677afd3d9..be9d7f83086a97d5901935a05c370dfa7b76bd43 100644 --- a/src/result.rs +++ b/src/result.rs @@ -1,5 +1,3 @@ - - pub type MBGResult<T> = Result<T, MBGError>; #[derive(Debug, Fail)] diff --git a/systemd/music_bg.service b/systemd/music_bg.service index 738fedf4ab6e38860df321166dee7fd727511f7c..c21e9b7ea06eeb6cb7d82f9d4d6d202996ff291b 100644 --- a/systemd/music_bg.service +++ b/systemd/music_bg.service @@ -1,6 +1,6 @@ [Unit] Description=Dynamic music background -After=network.target +After=graphical-session.target [Service] Type=simple