From 6cc2a27940fc6ab6759db3c8e0bb367903760351 Mon Sep 17 00:00:00 2001 From: Pavel Kirilin <win10@list.ru> Date: Sat, 14 Mar 2020 21:25:33 +0400 Subject: [PATCH] Added Image processors. Description: - Border processor. Signed-off-by: Pavel Kirilin <win10@list.ru> --- Cargo.lock | 7 +++ Cargo.toml | 13 +++-- PKGBUILD | 5 +- src/background.rs | 2 +- src/config/image_processors.rs | 4 +- src/config/mod.rs | 6 ++- src/display.rs | 3 +- src/img_processors/blur.rs | 12 ++--- src/img_processors/border.rs | 88 ++++++++++++++++++++++++++++++++++ src/img_processors/crop.rs | 27 ++++++++--- src/img_processors/mod.rs | 66 +++++++++++++++++++------ src/img_processors/none.rs | 4 +- src/img_processors/scale.rs | 8 +++- src/main.rs | 10 ++-- src/player_dbus.rs | 2 +- src/result.rs | 2 - systemd/music_bg.service | 2 +- 17 files changed, 204 insertions(+), 57 deletions(-) create mode 100644 src/img_processors/border.rs diff --git a/Cargo.lock b/Cargo.lock index d58cf39..e8c82f9 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 521b176..846f25c 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 43c5554..7e57c06 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 bc0016f..a0bb337 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 14f7bfc..0645adb 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 3d4ac63..b1cde04 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 124f5ba..e3c9f3e 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 7456e57..2432acf 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 0000000..b9b41c9 --- /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 72a5794..498b47e 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 4a2c523..d9d13e3 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 6cdee0c..f197253 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 3b8320d..e1c20e3 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 bfeeb16..af98e4d 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 dabcdf4..5ece33b 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 ead167a..be9d7f8 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 738fedf..c21e9b7 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 -- GitLab