feat: implement wallpaper settings page
This commit is contained in:
parent
3681a0987e
commit
900bb45758
12 changed files with 1009 additions and 480 deletions
|
|
@ -1,6 +1,8 @@
|
|||
pub use cosmic_bg_config::{Config, Entry, Output, ScalingMode};
|
||||
pub use cosmic_bg_config::{Color, Config, Entry, Gradient, ScalingMode, Source};
|
||||
|
||||
use image::RgbaImage;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::{hash_map::DefaultHasher, HashMap},
|
||||
fs::DirEntry,
|
||||
hash::{Hash, Hasher},
|
||||
|
|
@ -9,6 +11,39 @@ use std::{
|
|||
};
|
||||
use tokio::sync::mpsc::{self, Receiver};
|
||||
|
||||
pub const DEFAULT_COLORS: &[Color] = &[
|
||||
Color::Single([0.580, 0.922, 0.922]),
|
||||
Color::Single([0.000, 0.286, 0.427]),
|
||||
Color::Single([1.000, 0.678, 0.000]),
|
||||
Color::Single([0.282, 0.725, 0.78]),
|
||||
Color::Single([0.333, 0.278, 0.259]),
|
||||
Color::Single([0.969, 0.878, 0.384]),
|
||||
Color::Single([0.063, 0.165, 0.298]),
|
||||
Color::Single([1.000, 0.843, 0.631]),
|
||||
Color::Single([0.976, 0.227, 0.514]),
|
||||
Color::Single([1.000, 0.612, 0.867]),
|
||||
Color::Single([0.812, 0.490, 1.000]),
|
||||
Color::Single([0.835, 0.549, 1.000]),
|
||||
Color::Single([0.243, 0.533, 1.000]),
|
||||
Color::Single([0.584, 0.769, 0.988]),
|
||||
Color::Gradient(Gradient {
|
||||
colors: Cow::Borrowed(&[[1.000, 0.678, 0.000], [0.282, 0.725, 0.78]]),
|
||||
radius: 270.0,
|
||||
}),
|
||||
Color::Gradient(Gradient {
|
||||
colors: Cow::Borrowed(&[[1.000, 0.843, 0.631], [0.58, 0.922, 0.922]]),
|
||||
radius: 270.0,
|
||||
}),
|
||||
Color::Gradient(Gradient {
|
||||
colors: Cow::Borrowed(&[[1.000, 0.612, 0.867], [0.976, 0.29, 0.514]]),
|
||||
radius: 270.0,
|
||||
}),
|
||||
Color::Gradient(Gradient {
|
||||
colors: Cow::Borrowed(&[[0.584, 0.769, 0.988], [0.063, 0.165, 0.298]]),
|
||||
radius: 270.0,
|
||||
}),
|
||||
];
|
||||
|
||||
pub fn config() -> (Config, HashMap<String, String>) {
|
||||
let mut displays = HashMap::new();
|
||||
|
||||
|
|
@ -36,11 +71,13 @@ pub fn config() -> (Config, HashMap<String, String>) {
|
|||
pub fn set(config: &mut Config, entry: Entry) {
|
||||
if let Ok(context) = Config::helper() {
|
||||
tracing::info!(
|
||||
"setting wallpaper for {} to {}",
|
||||
entry.output.to_string(),
|
||||
entry.source.display()
|
||||
output = entry.output.to_string(),
|
||||
source = ?entry.source,
|
||||
"setting wallpaper",
|
||||
);
|
||||
|
||||
let _res = Config::set_same_on_all(&context, config.same_on_all);
|
||||
|
||||
if let Err(why) = config.set_entry(&context, entry) {
|
||||
tracing::error!(?why, "failed to set background");
|
||||
}
|
||||
|
|
@ -59,7 +96,7 @@ pub fn cache_dir() -> Option<PathBuf> {
|
|||
|
||||
/// Loads wallpapers in parallel by spawning tasks with a rayon thread pool.
|
||||
#[must_use]
|
||||
pub fn load_each_from_path(path: PathBuf) -> Receiver<(PathBuf, RgbaImage)> {
|
||||
pub fn load_each_from_path(path: PathBuf) -> Receiver<(PathBuf, RgbaImage, RgbaImage)> {
|
||||
let cache_dir = Arc::new(cache_dir());
|
||||
|
||||
let (tx, rx) = mpsc::channel(1);
|
||||
|
|
@ -82,10 +119,23 @@ pub fn load_each_from_path(path: PathBuf) -> Receiver<(PathBuf, RgbaImage)> {
|
|||
let tx = tx.clone();
|
||||
let cache_dir = cache_dir.clone();
|
||||
rayon::spawn_fifo(move || {
|
||||
let thumbnail =
|
||||
let display_thumbnail =
|
||||
load_thumbnail(cache_dir.as_deref(), &path, &entry, 300, 169);
|
||||
if let Some(image) = thumbnail {
|
||||
let _res = tx.blocking_send((path, image));
|
||||
|
||||
if let Some(display_thumbnail) = display_thumbnail {
|
||||
let mut selection_thumbnail = image::imageops::resize(
|
||||
&display_thumbnail,
|
||||
158,
|
||||
105,
|
||||
image::imageops::FilterType::Lanczos3,
|
||||
);
|
||||
round(&mut selection_thumbnail, [8, 8, 8, 8]);
|
||||
|
||||
let _res = tx.blocking_send((
|
||||
path,
|
||||
display_thumbnail,
|
||||
selection_thumbnail,
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -141,3 +191,120 @@ pub fn load_thumbnail(
|
|||
image.into_rgba8()
|
||||
})
|
||||
}
|
||||
|
||||
// https://users.rust-lang.org/t/how-to-trim-image-to-circle-image-without-jaggy/70374/2
|
||||
fn round(img: &mut image::ImageBuffer<image::Rgba<u8>, Vec<u8>>, radius: [u32; 4]) {
|
||||
let (width, height) = img.dimensions();
|
||||
assert!(radius[0] + radius[1] <= width);
|
||||
assert!(radius[3] + radius[2] <= width);
|
||||
assert!(radius[0] + radius[3] <= height);
|
||||
assert!(radius[1] + radius[2] <= height);
|
||||
|
||||
// top left
|
||||
border_radius(img, radius[0], |x, y| (x - 1, y - 1));
|
||||
// top right
|
||||
border_radius(img, radius[1], |x, y| (width - x, y - 1));
|
||||
// bottom right
|
||||
border_radius(img, radius[2], |x, y| (width - x, height - y));
|
||||
// bottom left
|
||||
border_radius(img, radius[3], |x, y| (x - 1, height - y));
|
||||
}
|
||||
|
||||
fn border_radius(
|
||||
img: &mut image::ImageBuffer<image::Rgba<u8>, Vec<u8>>,
|
||||
r: u32,
|
||||
coordinates: impl Fn(u32, u32) -> (u32, u32),
|
||||
) {
|
||||
if r == 0 {
|
||||
return;
|
||||
}
|
||||
let r0 = r;
|
||||
|
||||
// 16x antialiasing: 16x16 grid creates 256 possible shades, great for u8!
|
||||
let r = 16 * r;
|
||||
|
||||
let mut x = 0;
|
||||
let mut y = r - 1;
|
||||
let mut p: i32 = 2 - r as i32;
|
||||
|
||||
// ...
|
||||
|
||||
let mut alpha: u16 = 0;
|
||||
let mut skip_draw = true;
|
||||
|
||||
let draw = |img: &mut image::ImageBuffer<image::Rgba<u8>, Vec<u8>>, alpha, x, y| {
|
||||
debug_assert!((1..=256).contains(&alpha));
|
||||
let pixel_alpha = &mut img[coordinates(r0 - x, r0 - y)].0[3];
|
||||
*pixel_alpha = ((alpha * *pixel_alpha as u16 + 128) / 256) as u8;
|
||||
};
|
||||
|
||||
'l: loop {
|
||||
// (comments for bottom_right case:)
|
||||
// remove contents below current position
|
||||
{
|
||||
let i = x / 16;
|
||||
for j in y / 16 + 1..r0 {
|
||||
img[coordinates(r0 - i, r0 - j)].0[3] = 0;
|
||||
}
|
||||
}
|
||||
// remove contents right of current position mirrored
|
||||
{
|
||||
let j = x / 16;
|
||||
for i in y / 16 + 1..r0 {
|
||||
img[coordinates(r0 - i, r0 - j)].0[3] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// draw when moving to next pixel in x-direction
|
||||
if !skip_draw {
|
||||
draw(img, alpha, x / 16 - 1, y / 16);
|
||||
draw(img, alpha, y / 16, x / 16 - 1);
|
||||
alpha = 0;
|
||||
}
|
||||
|
||||
for _ in 0..16 {
|
||||
skip_draw = false;
|
||||
|
||||
if x >= y {
|
||||
break 'l;
|
||||
}
|
||||
|
||||
alpha += y as u16 % 16 + 1;
|
||||
if p < 0 {
|
||||
x += 1;
|
||||
p += (2 * x + 2) as i32;
|
||||
} else {
|
||||
// draw when moving to next pixel in y-direction
|
||||
if y % 16 == 0 {
|
||||
draw(img, alpha, x / 16, y / 16);
|
||||
draw(img, alpha, y / 16, x / 16);
|
||||
skip_draw = true;
|
||||
alpha = (x + 1) as u16 % 16 * 16;
|
||||
}
|
||||
|
||||
x += 1;
|
||||
p -= (2 * (y - x) + 2) as i32;
|
||||
y -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// one corner pixel left
|
||||
if x / 16 == y / 16 {
|
||||
// column under current position possibly not yet accounted
|
||||
if x == y {
|
||||
alpha += y as u16 % 16 + 1;
|
||||
}
|
||||
let s = y as u16 % 16 + 1;
|
||||
let alpha = 2 * alpha - s * s;
|
||||
draw(img, alpha, x / 16, y / 16);
|
||||
}
|
||||
|
||||
// remove remaining square of content in the corner
|
||||
let range = y / 16 + 1..r0;
|
||||
for i in range.clone() {
|
||||
for j in range.clone() {
|
||||
img[coordinates(r0 - i, r0 - j)].0[3] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue