Skip to content

Commit

Permalink
automatic color mapping
Browse files Browse the repository at this point in the history
  • Loading branch information
actioninja committed May 25, 2023
1 parent 4406d5f commit eae6bed
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 1 deletion.
4 changes: 4 additions & 0 deletions examples/bitmask-slice.toml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ delays = [10, 20]
[map_icon]
# The name of the icon_state the resulting generated icon will use
icon_state_name = "map_icon"
# Attempt to automatically derive colors from the input icon
# if true, base_color, text_color, and outer_border will be ignored
# Optional, defaults to false if omitted
automatic = false
# The base color to use for the icon
# Accepts any hex color
base_color = "#FFFFFF"
Expand Down
19 changes: 19 additions & 0 deletions hypnagogic_core/src/config/blocks/generators.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::generation::rect::{Border, BorderStyle};
use crate::generation::text::Alignment;
use crate::util::color::Color;
use crate::util::icon_ops::pick_contrasting_colors;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, Serialize, Deserialize)]
Expand Down Expand Up @@ -41,6 +42,8 @@ fn default_alignment() -> Alignment {
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct MapIcon {
pub icon_state_name: String,
#[serde(default)]
pub automatic: bool,
#[serde(default = "white")]
pub base_color: Color,
#[serde(default)]
Expand All @@ -61,6 +64,7 @@ impl Default for MapIcon {
fn default() -> Self {
Self {
icon_state_name: "map_icon".to_string(),
automatic: false,
base_color: Color::new(255, 255, 255, 255),
text: Some("DEF".to_string()),
text_color: Color::new(0, 0, 0, 255),
Expand All @@ -74,3 +78,18 @@ impl Default for MapIcon {
}
}
}

impl MapIcon {
pub fn gen_colors(&mut self, colors: &[Color]) {
if !self.automatic {
return;
}
let sorted_colors = pick_contrasting_colors(colors);
self.base_color = sorted_colors.0;
self.text_color = sorted_colors.1;
self.outer_border = Some(Border {
style: BorderStyle::Solid,
color: sorted_colors.1,
});
}
}
5 changes: 5 additions & 0 deletions hypnagogic_core/src/util/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ impl Color {
self.red, self.green, self.blue, self.alpha
)
}

#[must_use]
pub fn luminance(&self) -> f32 {
(0.299 * self.red as f32 + 0.587 * self.green as f32 + 0.114 * self.blue as f32) / 255.0
}
}

impl Serialize for Color {
Expand Down
34 changes: 33 additions & 1 deletion hypnagogic_core/src/util/icon_ops.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::util::color::Color;
use dmi::icon::IconState;
use image::DynamicImage;
use image::{DynamicImage, GenericImageView};

// Removes duplicate frames from the icon state's animation, if it has any
#[must_use]
Expand Down Expand Up @@ -54,3 +55,34 @@ pub fn dedupe_frames(icon_state: IconState) -> IconState {
..icon_state
}
}

#[must_use]
pub fn colors_in_image(image: &DynamicImage) -> Vec<Color> {
let mut colors = Vec::new();
for pixel in image.pixels() {
let color = pixel.2;
if !colors.contains(&color) {
colors.push(color);
}
}
colors
.iter()
.map(|c| Color::new(c.0[0], c.0[1], c.0[2], c.0[3]))
.collect()
}

pub fn sort_colors_by_luminance(colors: &mut Vec<Color>) {
colors.sort_by(|a, b| a.luminance().partial_cmp(&b.luminance()).unwrap());
}

#[must_use]
pub fn pick_contrasting_colors(colors: &[Color]) -> (Color, Color) {
let mut sorted_colors = colors.to_vec();
sort_colors_by_luminance(&mut sorted_colors);
let len_as_f32 = colors.len() as f32;
let first = 0.10 * len_as_f32;
let first_index = (first.floor() as usize).saturating_sub(1);
let second = 0.90 * len_as_f32;
let second_index = (second.floor() as usize).saturating_sub(1);
(sorted_colors[first_index], sorted_colors[second_index])
}

0 comments on commit eae6bed

Please sign in to comment.