Skip to content

Commit

Permalink
add first basic color palette generator
Browse files Browse the repository at this point in the history
  • Loading branch information
0xk1f0 committed May 27, 2023
1 parent ceb68c3 commit 40c0b2d
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 10 deletions.
43 changes: 41 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ smithay-client-toolkit = { git = "https://github.com/Smithay/client-toolkit", de
wayland-client = "0.30.1"
image = "0.24.6"
toml = "0.7.4"
serde = { version = "1.0.163", features = ["derive"] }
serde_json = "1.0.96"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ rwpspread --help
- [x] Hyprland/wlroots Integration
- [x] wpaperd Integration
- [x] wallpaper caching (don't resplit if we don't need to)
- [ ] color palette generation from wallpaper
- [x] color palette generation from wallpaper
- [ ] watchdog auto-resplit on output change
- [ ] alignment adjust if wallpaper is big enough
- [ ] monitor bezel compensation
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod palette;
mod parser;
mod splitter;
mod wayland;
Expand Down
104 changes: 104 additions & 0 deletions src/palette.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use image::{GenericImageView, Rgba};
use serde::Serialize;
use serde_json::to_writer_pretty;
use std::collections::HashMap;
use std::fs::File;
use std::path::PathBuf;

#[derive(Serialize)]
struct ColorData {
index: usize,
color: String,
}

pub struct Palette {
pixels: Vec<Rgba<u8>>,
colors: Vec<String>,
}

impl Palette {
pub fn new(image_path: &PathBuf) -> Result<Self, String> {
let pixels = Palette::extract_rgba_pixels(image_path).map_err(|err| err.to_string())?;
Ok(Self {
pixels,
colors: Vec::with_capacity(16),
})
}

fn extract_rgba_pixels(image_path: &PathBuf) -> Result<Vec<Rgba<u8>>, String> {
// Load the image
let img = image::open(image_path).map_err(|err| err.to_string())?;

// Resize the image to a small size for faster processing
let small_img = img.resize_exact(16, 16, image::imageops::FilterType::Triangle);

// Collect the RGBA values of each pixel in a vector
let mut pixels = Vec::with_capacity(16 * 16);
for pixel in small_img.pixels() {
pixels.push(pixel.2);
}

// return them
Ok(pixels)
}

pub fn generate_mostused(mut self, save_path: String) -> Result<(), String> {
// Create a HashMap to store the frequency of each color
let mut color_counts: HashMap<Rgba<u8>, usize> = HashMap::new();

// Loop over the vector to analyze pixels
for entry in &self.pixels {
// Count the frequency of each color
let count = color_counts.entry(*entry).or_insert(0);
*count += 1;
}

// Sort the colors by frequency in descending order
let mut sorted_colors: Vec<(Rgba<u8>, usize)> = color_counts.into_iter().collect();
sorted_colors.sort_by_key(|(_, count)| *count);
sorted_colors.reverse();

// Get the top num_colors most used colors in hexadecimal notation
let most_used_colors = sorted_colors
.iter()
.take(16)
.map(|(color, _)| {
let hex_channels = color
.0
.iter()
.take(3)
.map(|channel| format!("{:x}", channel))
.collect::<Vec<String>>();
format!("#{}", hex_channels.join(""))
})
.collect::<Vec<String>>();

// Sort the output values darkest to brightest
let mut sorted_output = most_used_colors.clone();
sorted_output.sort();
self.colors = sorted_output;

// out to json
self.to_json(save_path).map_err(|err| err.to_string())?;

// done
Ok(())
}

fn to_json(&self, path: String) -> Result<(), String> {
let color_data: Vec<ColorData> = self
.colors
.iter()
.enumerate()
.map(|(index, color)| ColorData {
index: index + 1,
color: color.to_string(),
})
.collect();

let file = File::create(path).map_err(|err| err.to_string())?;
to_writer_pretty(file, &color_data).map_err(|err| err.to_string())?;

Ok(())
}
}
6 changes: 6 additions & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ struct Args {
#[arg(short, long)]
wpaperd: bool,

/// Generate a color palette from wallpaper
#[arg(short, long)]
palette: bool,

/// Force Resplit even if cache exists
#[arg(short, long)]
force_resplit: bool,
Expand All @@ -27,6 +31,7 @@ struct Args {
pub struct Config {
pub image_path: PathBuf,
pub with_wpaperd: bool,
pub with_palette: bool,
pub force_resplit: bool,
pub dont_downscale: bool,
}
Expand All @@ -48,6 +53,7 @@ impl Config {
Ok(Self {
image_path: in_path,
with_wpaperd: args.wpaperd,
with_palette: args.palette,
force_resplit: args.force_resplit,
dont_downscale: args.dont_downscale,
})
Expand Down
16 changes: 10 additions & 6 deletions src/splitter.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::palette::Palette;
use crate::wayland::{Monitor, MonitorConfig};
use crate::wpaperd::{CmdWrapper, WpaperdConfig};
use crate::Config;
Expand Down Expand Up @@ -45,6 +46,14 @@ impl Splitter {
config.hash(&mut hasher);
self.hash = format!("{:x}", hasher.finish());

// check for palette bool and do that first
if config.with_palette {
let color_palette = Palette::new(&config.image_path).map_err(|err| err.to_string())?;
color_palette
.generate_mostused(format!("{}/.cache/rwpcolors.json", var("HOME").unwrap()))
.map_err(|err| err.to_string())?;
}

// check if we need to generate wpaperd config
if config.with_wpaperd {
// create new wpaperd instance
Expand Down Expand Up @@ -139,12 +148,7 @@ impl Splitter {
);

// get full image path
let path_image = format!(
"{}/rwps_{}_{}.png",
save_path,
&self.hash,
&monitor.name,
);
let path_image = format!("{}/rwps_{}_{}.png", save_path, &self.hash, &monitor.name,);

// save it
cropped_image
Expand Down
4 changes: 3 additions & 1 deletion src/wpaperd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ impl WpaperdConfig {
}

// write the file
config_file.write(format!("# {}\n", self.config_hash).as_bytes()).unwrap();
config_file
.write(format!("# {}\n", self.config_hash).as_bytes())
.unwrap();
config_file
.write(b"# DO NOT EDIT! AUTOGENERATED CONFIG!\n\n")
.unwrap();
Expand Down

0 comments on commit 40c0b2d

Please sign in to comment.