From 56091be65418b5675984a9abb1d2d80d3256ad06 Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Sat, 14 Sep 2024 10:17:58 +0900 Subject: [PATCH 1/7] Implement parse_rgba_color --- Cargo.lock | 209 +++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/color.rs | 42 ++++++++++- 3 files changed, 251 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9b5bba6..ae6a09e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,6 +29,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "aligned-vec" version = "0.5.0" @@ -759,6 +768,101 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "fxhash" version = "0.2.1" @@ -805,6 +909,12 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "half" version = "2.4.1" @@ -1377,6 +1487,12 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.30" @@ -1402,6 +1518,15 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -1590,6 +1715,41 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + [[package]] name = "resvg" version = "0.19.0" @@ -1623,12 +1783,51 @@ dependencies = [ "xmlparser", ] +[[package]] +name = "rstest" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b423f0e62bdd61734b67cd21ff50871dfaeb9cc74f869dcd6af974fbcb19936" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e1711e7d14f74b12a58411c542185ef7fb7f2e7f8ee6e2940a883628522b42" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.71", + "unicode-ident", +] + [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.34" @@ -1756,6 +1955,7 @@ dependencies = [ "image", "ratatui", "rayon", + "rstest", "semver", "serde", "serde_json", @@ -1836,6 +2036,15 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "smallvec" version = "1.13.2" diff --git a/Cargo.toml b/Cargo.toml index 591541d..ed60e23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,5 +35,6 @@ xdg = "2.5.2" [dev-dependencies] dircpy = "0.3.19" +rstest = "0.22.0" tempfile = "3.12.0" text-to-png = "0.2.0" diff --git a/src/color.rs b/src/color.rs index d3d3192..500927c 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,4 +1,4 @@ -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Color { r: u8, g: u8, @@ -49,3 +49,43 @@ impl Default for ColorSet { } } } + +fn parse_rgba_color(s: &str) -> Option { + if !s.starts_with("#") { + return None; + } + + let s = &s[1..]; + let l = s.len(); + if l != 6 && l != 8 { + return None; + } + + let r = u8::from_str_radix(&s[0..2], 16).ok()?; + let g = u8::from_str_radix(&s[2..4], 16).ok()?; + let b = u8::from_str_radix(&s[4..6], 16).ok()?; + if l == 6 { + Some(Color::from_rgb(r, g, b)) + } else { + let a = u8::from_str_radix(&s[6..8], 16).ok()?; + Some(Color::from_rgba(r, g, b, a)) + } +} + +#[cfg(test)] +mod tests { + use rstest::rstest; + + use super::*; + + #[rstest] + #[case("#ff0000", Some(Color { r: 255, g: 0, b: 0, a: 255}))] + #[case("#AABBCCDD", Some(Color { r: 170, g: 187, b: 204, a: 221}))] + #[case("#ff000", None)] + #[case("#fff", None)] + #[case("000000", None)] + #[case("##123456", None)] + fn test_parse_rgba_color(#[case] input: &str, #[case] expected: Option) { + assert_eq!(parse_rgba_color(input), expected); + } +} From 48d9e62d2a8d56b1552e1498efcc5e660685d7b6 Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Sat, 14 Sep 2024 10:47:49 +0900 Subject: [PATCH 2/7] Add graph.color config --- README.md | 20 +++++++++++ src/config.rs | 92 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 110 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 808cca5..952b493 100644 --- a/README.md +++ b/README.md @@ -241,6 +241,26 @@ date_local = true # type: u16 width = 26 +[graph.color] +# Colors should be specified in the format #RRGGBB or #RRGGBBAA. + +# Array of colors used for the commit graph. +# type: array of strings +branches = [ + "#E06C76", + "#98C379", + "#E5C07B", + "#61AFEF", + "#C678DD", + "#56B6C2", +] +# Color of the edge surrounding the commit circles in the graph. +# type: string +edge = "#00000000" +# Background color of the commit graph. +# type: string +background = "#00000000" + [keybind] # See ./assets/default-keybind.toml for a specific example configuration. # ... diff --git a/src/config.rs b/src/config.rs index 4bf22c6..632d9e0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -21,6 +21,12 @@ const DEFAULT_DETAIL_DATE_LOCAL: bool = true; const DEFAULT_DETAIL_HEIGHT: u16 = 20; const DEFAULT_REFS_WIDTH: u16 = 26; +const DEFAULT_GRAPH_COLOR_BRANCHES: [&str; 6] = [ + "#E06C76", "#98C379", "#E5C07B", "#61AFEF", "#C678DD", "#56B6C2", +]; +const DEFAULT_GRAPH_COLOR_EDGE: &str = "#00000000"; +const DEFAULT_GRAPH_COLOR_BACKGROUND: &str = "#00000000"; + pub fn load() -> (UiConfig, Option) { let config = match config_file_path_from_env() { Some(user_path) => { @@ -60,6 +66,8 @@ fn read_config_from_path(path: &Path) -> Config { struct Config { #[serde(default)] ui: UiConfig, + #[serde(default)] + graph: GraphConfig, // The user customed keybinds, please ref `assets/default-keybind.toml` keybind: Option, } @@ -170,6 +178,47 @@ fn ui_refs_width_default() -> u16 { DEFAULT_REFS_WIDTH } +#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize)] +pub struct GraphConfig { + #[serde(default)] + pub color: GraphColorConfig, +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct GraphColorConfig { + #[serde(default = "graph_color_branches_default")] + pub branches: Vec, + #[serde(default = "graph_color_edge_default")] + pub edge: String, + #[serde(default = "graph_color_background_default")] + pub background: String, +} + +impl Default for GraphColorConfig { + fn default() -> Self { + Self { + branches: graph_color_branches_default(), + edge: graph_color_edge_default(), + background: graph_color_background_default(), + } + } +} + +fn graph_color_branches_default() -> Vec { + DEFAULT_GRAPH_COLOR_BRANCHES + .iter() + .map(|s| s.to_string()) + .collect() +} + +fn graph_color_edge_default() -> String { + DEFAULT_GRAPH_COLOR_EDGE.into() +} + +fn graph_color_background_default() -> String { + DEFAULT_GRAPH_COLOR_BACKGROUND.into() +} + #[cfg(test)] mod tests { use super::*; @@ -193,6 +242,20 @@ mod tests { }, refs: UiRefsConfig { width: 26 }, }, + graph: GraphConfig { + color: GraphColorConfig { + branches: vec![ + "#E06C76".into(), + "#98C379".into(), + "#E5C07B".into(), + "#61AFEF".into(), + "#C678DD".into(), + "#56B6C2".into(), + ], + edge: "#00000000".into(), + background: "#00000000".into(), + }, + }, keybind: None, }; assert_eq!(actual, expected); @@ -200,7 +263,7 @@ mod tests { #[test] fn test_config_complete_toml() { - let toml = r#" + let toml = r##" [ui.list] subject_min_width = 40 date_format = "%Y/%m/%d" @@ -213,7 +276,11 @@ mod tests { date_local = false [ui.refs] width = 40 - "#; + [graph.color] + branches = ["#ff0000", "#00ff00", "#0000ff"] + edge = "#000000" + background = "#ffffff" + "##; let actual: Config = toml::from_str(toml).unwrap(); let expected = Config { ui: UiConfig { @@ -231,6 +298,13 @@ mod tests { }, refs: UiRefsConfig { width: 40 }, }, + graph: GraphConfig { + color: GraphColorConfig { + branches: vec!["#ff0000".into(), "#00ff00".into(), "#0000ff".into()], + edge: "#000000".into(), + background: "#ffffff".into(), + }, + }, keybind: None, }; assert_eq!(actual, expected); @@ -259,6 +333,20 @@ mod tests { }, refs: UiRefsConfig { width: 26 }, }, + graph: GraphConfig { + color: GraphColorConfig { + branches: vec![ + "#E06C76".into(), + "#98C379".into(), + "#E5C07B".into(), + "#61AFEF".into(), + "#C678DD".into(), + "#56B6C2".into(), + ], + edge: "#00000000".into(), + background: "#00000000".into(), + }, + }, keybind: None, }; assert_eq!(actual, expected); From a7bfe75bcfa34a4c7b58a368975e04b01ef8cea1 Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Sat, 14 Sep 2024 11:10:04 +0900 Subject: [PATCH 3/7] Fix ColorSet to build from GraphColorConfig --- src/color.rs | 37 +++++++++++++++++++++++-------------- src/config.rs | 4 ++-- src/graph/image.rs | 31 +++++++++++++++++++------------ src/lib.rs | 6 +++--- tests/graph.rs | 5 +++-- 5 files changed, 50 insertions(+), 33 deletions(-) diff --git a/src/color.rs b/src/color.rs index 500927c..ecd59e9 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,3 +1,5 @@ +use crate::config::GraphColorConfig; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Color { r: u8, @@ -22,32 +24,39 @@ impl Color { pub fn to_ratatui_color(self) -> ratatui::style::Color { ratatui::style::Color::Rgb(self.r, self.g, self.b) } + + fn transparent() -> Self { + Self::from_rgba(0, 0, 0, 0) + } } #[derive(Debug, Clone)] pub struct ColorSet { pub colors: Vec, + pub edge_color: Color, + pub background_color: Color, } impl ColorSet { - pub fn get(&self, index: usize) -> Color { - self.colors[index % self.colors.len()] - } -} + pub fn new(config: &GraphColorConfig) -> Self { + let colors = config + .branches + .iter() + .filter_map(|s| parse_rgba_color(s)) + .collect(); + let edge_color = parse_rgba_color(&config.edge).unwrap_or(Color::transparent()); + let background_color = parse_rgba_color(&config.background).unwrap_or(Color::transparent()); -impl Default for ColorSet { - fn default() -> Self { Self { - colors: vec![ - Color::from_rgb(224, 108, 118), - Color::from_rgb(152, 195, 121), - Color::from_rgb(229, 192, 123), - Color::from_rgb(97, 175, 239), - Color::from_rgb(198, 120, 221), - Color::from_rgb(86, 182, 194), - ], + colors, + edge_color, + background_color, } } + + pub fn get(&self, index: usize) -> Color { + self.colors[index % self.colors.len()] + } } fn parse_rgba_color(s: &str) -> Option { diff --git a/src/config.rs b/src/config.rs index 632d9e0..9cd5a68 100644 --- a/src/config.rs +++ b/src/config.rs @@ -27,7 +27,7 @@ const DEFAULT_GRAPH_COLOR_BRANCHES: [&str; 6] = [ const DEFAULT_GRAPH_COLOR_EDGE: &str = "#00000000"; const DEFAULT_GRAPH_COLOR_BACKGROUND: &str = "#00000000"; -pub fn load() -> (UiConfig, Option) { +pub fn load() -> (UiConfig, GraphConfig, Option) { let config = match config_file_path_from_env() { Some(user_path) => { if !user_path.exists() { @@ -44,7 +44,7 @@ pub fn load() -> (UiConfig, Option) { } } }; - (config.ui, config.keybind) + (config.ui, config.graph, config.keybind) } fn config_file_path_from_env() -> Option { diff --git a/src/graph/image.rs b/src/graph/image.rs index 5be6e39..6b21545 100644 --- a/src/graph/image.rs +++ b/src/graph/image.rs @@ -633,7 +633,7 @@ mod tests { use image::GenericImage; - use crate::color::Color; + use crate::config::GraphColorConfig; use super::*; use EdgeType::*; @@ -648,7 +648,8 @@ mod tests { fn test_calc_graph_row_image_default_params() { let params = simple_test_params(); let cell_count = 4; - let color_set = ColorSet::default(); + let graph_color_config = GraphColorConfig::default(); + let color_set = ColorSet::new(&graph_color_config); let image_params = ImageParams::new(&color_set); let drawing_pixels = DrawingPixels::new(&image_params); let file_name = "default_params"; @@ -660,7 +661,8 @@ mod tests { fn test_calc_graph_row_image_wide_image() { let params = simple_test_params(); let cell_count = 4; - let color_set = ColorSet::default(); + let graph_color_config = GraphColorConfig::default(); + let color_set = ColorSet::new(&graph_color_config); let mut image_params = ImageParams::new(&color_set); image_params.width = 100; let drawing_pixels = DrawingPixels::new(&image_params); @@ -673,7 +675,8 @@ mod tests { fn test_calc_graph_row_image_tall_image() { let params = simple_test_params(); let cell_count = 4; - let color_set = ColorSet::default(); + let graph_color_config = GraphColorConfig::default(); + let color_set = ColorSet::new(&graph_color_config); let mut image_params = ImageParams::new(&color_set); image_params.height = 100; let drawing_pixels = DrawingPixels::new(&image_params); @@ -686,7 +689,8 @@ mod tests { fn test_calc_graph_row_image_circle_radius() { let params = straight_test_params(); let cell_count = 2; - let color_set = ColorSet::default(); + let graph_color_config = GraphColorConfig::default(); + let color_set = ColorSet::new(&graph_color_config); let mut image_params = ImageParams::new(&color_set); image_params.circle_inner_radius = 5; image_params.circle_outer_radius = 12; @@ -700,7 +704,8 @@ mod tests { fn test_calc_graph_row_image_line_width() { let params = straight_test_params(); let cell_count = 2; - let color_set = ColorSet::default(); + let graph_color_config = GraphColorConfig::default(); + let color_set = ColorSet::new(&graph_color_config); let mut image_params = ImageParams::new(&color_set); image_params.line_width = 1; let drawing_pixels = DrawingPixels::new(&image_params); @@ -713,14 +718,16 @@ mod tests { fn test_calc_graph_row_image_color() { let params = branches_test_params(); let cell_count = 7; - let color_set = ColorSet { - colors: vec![ - Color::from_rgb(200, 200, 100), - Color::from_rgb(100, 200, 200), - Color::from_rgb(100, 100, 100), - Color::from_rgb(200, 100, 200), + let graph_color_config = GraphColorConfig { + branches: vec![ + "#c8c864".into(), + "#64c8c8".into(), + "#646464".into(), + "#c864c8".into(), ], + ..Default::default() }; + let color_set = ColorSet::new(&graph_color_config); let image_params = ImageParams::new(&color_set); let drawing_pixels = DrawingPixels::new(&image_params); let file_name = "color"; diff --git a/src/lib.rs b/src/lib.rs index 29d1cae..3a30455 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,11 @@ pub mod color; +pub mod config; pub mod git; pub mod graph; pub mod protocol; mod app; mod check; -mod config; mod event; mod external; mod keybind; @@ -71,10 +71,10 @@ impl From for graph::SortCommit { pub fn run() -> std::io::Result<()> { color_eyre::install().unwrap(); let args = Args::parse(); - let (ui_config, key_bind_patch) = config::load(); + let (ui_config, graph_config, key_bind_patch) = config::load(); let key_bind = keybind::KeyBind::new(key_bind_patch); - let color_set = color::ColorSet::default(); + let color_set = color::ColorSet::new(&graph_config.color); let image_protocol = args.protocol.into(); let repository = git::Repository::load(Path::new("."), args.order.into()); diff --git a/tests/graph.rs b/tests/graph.rs index 0ab28de..5a645b6 100644 --- a/tests/graph.rs +++ b/tests/graph.rs @@ -2,7 +2,7 @@ use std::{path::Path, process::Command}; use chrono::{DateTime, Days, NaiveDate, TimeZone, Utc}; use image::{GenericImage, GenericImageView}; -use serie::{color, git, graph}; +use serie::{color, config::GraphColorConfig, git, graph}; type TestResult = Result<(), Box>; @@ -1076,7 +1076,8 @@ fn generate_and_output_graph_images(repo_path: &Path, options: &[GenerateGraphOp fn generate_and_output_graph_image>(path: P, option: &GenerateGraphOption) { // Build graphs in the same way as application - let color_set = color::ColorSet::default(); + let graph_color_config = GraphColorConfig::default(); + let color_set = color::ColorSet::new(&graph_color_config); let repository = git::Repository::load(path.as_ref(), option.sort); let graph = graph::calc_graph(&repository); let image_params = graph::ImageParams::new(&color_set); From a39d5abc760c09b6db39b530b22512b0dc499ac9 Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Sat, 14 Sep 2024 13:19:16 +0900 Subject: [PATCH 4/7] Draw circle edge and background --- src/graph/image.rs | 48 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/src/graph/image.rs b/src/graph/image.rs index 6b21545..b89e417 100644 --- a/src/graph/image.rs +++ b/src/graph/image.rs @@ -116,6 +116,8 @@ pub struct ImageParams { circle_inner_radius: u16, circle_outer_radius: u16, edge_colors: Vec>, + circle_edge_color: image::Rgba, + background_color: image::Rgba, } impl ImageParams { @@ -130,6 +132,8 @@ impl ImageParams { .iter() .map(|c| c.to_image_color()) .collect(); + let circle_edge_color = color_set.edge_color.to_image_color(); + let background_color = color_set.background_color.to_image_color(); Self { width, height, @@ -137,6 +141,8 @@ impl ImageParams { circle_inner_radius, circle_outer_radius, edge_colors, + circle_edge_color, + background_color, } } @@ -212,6 +218,7 @@ type Pixels = FxHashSet<(i32, i32)>; #[derive(Debug)] pub struct DrawingPixels { circle: Pixels, + circle_edge: Pixels, vertical_edge: Pixels, horizontal_edge: Pixels, up_edge: Pixels, @@ -227,6 +234,7 @@ pub struct DrawingPixels { impl DrawingPixels { pub fn new(image_params: &ImageParams) -> Self { let circle = calc_commit_circle_drawing_pixels(image_params); + let circle_edge = calc_circle_edge_drawing_pixels(image_params); let vertical_edge = calc_vertical_edge_drawing_pixels(image_params); let horizontal_edge = calc_horizontal_edge_drawing_pixels(image_params); let up_edge = calc_up_edge_drawing_pixels(image_params); @@ -240,6 +248,7 @@ impl DrawingPixels { Self { circle, + circle_edge, vertical_edge, horizontal_edge, up_edge, @@ -255,10 +264,20 @@ impl DrawingPixels { } fn calc_commit_circle_drawing_pixels(image_params: &ImageParams) -> Pixels { + calc_circle_drawing_pixels(image_params, image_params.circle_inner_radius as i32) +} + +fn calc_circle_edge_drawing_pixels(image_params: &ImageParams) -> Pixels { + let inner = calc_circle_drawing_pixels(image_params, image_params.circle_inner_radius as i32); + let outer = calc_circle_drawing_pixels(image_params, image_params.circle_outer_radius as i32); + + outer.difference(&inner).cloned().collect() +} + +fn calc_circle_drawing_pixels(image_params: &ImageParams, radius: i32) -> Pixels { // Bresenham's circle algorithm let center_x = (image_params.width / 2) as i32; let center_y = (image_params.height / 2) as i32; - let radius = image_params.circle_inner_radius as i32; let mut x = radius; let mut y = 0; @@ -553,6 +572,7 @@ fn calc_graph_row_image( let mut img_buf = image::ImageBuffer::new(image_width, image_height); + draw_background(&mut img_buf, image_params); draw_commit_circle(&mut img_buf, commit_pos_x, image_params, drawing_pixels); for edge in edges { @@ -564,6 +584,19 @@ fn calc_graph_row_image( GraphRowImage { bytes, cell_count } } +fn draw_background( + img_buf: &mut image::ImageBuffer, Vec>, + image_params: &ImageParams, +) { + if image_params.background_color[3] == 0 { + // If the alpha value is 0, the background is transparent, so we don't need to draw it. + return; + } + for pixel in img_buf.pixels_mut() { + *pixel = image_params.background_color; + } +} + fn draw_commit_circle( img_buf: &mut image::ImageBuffer, Vec>, circle_pos_x: usize, @@ -580,6 +613,19 @@ fn draw_commit_circle( let pixel = img_buf.get_pixel_mut(x, y); *pixel = color; } + + if image_params.circle_edge_color[3] == 0 { + // If the alpha value is 0, the circle edge is transparent, so we don't need to draw it. + return; + } + + for (x, y) in &drawing_pixels.circle_edge { + let x = (*x + x_offset) as u32; + let y = *y as u32; + + let pixel = img_buf.get_pixel_mut(x, y); + *pixel = image_params.circle_edge_color; + } } fn draw_edge( From e1d1453d5cc6d675977471ba94383740db5dd0da Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Sat, 14 Sep 2024 13:23:03 +0900 Subject: [PATCH 5/7] Fix graph::image test --- src/graph/image.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/graph/image.rs b/src/graph/image.rs index b89e417..d83540c 100644 --- a/src/graph/image.rs +++ b/src/graph/image.rs @@ -771,7 +771,8 @@ mod tests { "#646464".into(), "#c864c8".into(), ], - ..Default::default() + edge: "#ffffff".into(), + background: "#00ff0070".into(), }; let color_set = ColorSet::new(&graph_color_config); let image_params = ImageParams::new(&color_set); From e77605b9ff15c3fab607f7149dc5a6f8c5efa6ff Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Sat, 14 Sep 2024 14:56:04 +0900 Subject: [PATCH 6/7] Fix edge calculation --- src/graph/image.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/graph/image.rs b/src/graph/image.rs index d83540c..6e6a8b2 100644 --- a/src/graph/image.rs +++ b/src/graph/image.rs @@ -126,7 +126,7 @@ impl ImageParams { let height = 50; let line_width = 5; let circle_inner_radius = 10; - let circle_outer_radius = 14; + let circle_outer_radius = 13; let edge_colors = color_set .colors .iter() @@ -340,7 +340,7 @@ fn calc_up_edge_drawing_pixels(image_params: &ImageParams) -> Pixels { let circle_outer_radius = image_params.circle_outer_radius as i32; let mut pixels = Pixels::default(); - for y in 0..=(circle_center_y - circle_outer_radius) { + for y in 0..(circle_center_y - circle_outer_radius) { for x in (center_x - half_line_width)..=(center_x + half_line_width) { pixels.insert((x, y)); } @@ -355,7 +355,7 @@ fn calc_down_edge_drawing_pixels(image_params: &ImageParams) -> Pixels { let circle_outer_radius = image_params.circle_outer_radius as i32; let mut pixels = Pixels::default(); - for y in (circle_center_y + circle_outer_radius)..(image_params.height as i32) { + for y in (circle_center_y + circle_outer_radius + 1)..(image_params.height as i32) { for x in (center_x - half_line_width)..=(center_x + half_line_width) { pixels.insert((x, y)); } @@ -371,7 +371,7 @@ fn calc_left_edge_drawing_pixels(image_params: &ImageParams) -> Pixels { let mut pixels = Pixels::default(); for y in (center_y - half_line_width)..=(center_y + half_line_width) { - for x in 0..=(circle_center_x - circle_outer_radius) { + for x in 0..(circle_center_x - circle_outer_radius) { pixels.insert((x, y)); } } @@ -386,7 +386,7 @@ fn calc_right_edge_drawing_pixels(image_params: &ImageParams) -> Pixels { let mut pixels = Pixels::default(); for y in (center_y - half_line_width)..=(center_y + half_line_width) { - for x in (circle_center_x + circle_outer_radius)..=(image_params.width as i32) { + for x in (circle_center_x + circle_outer_radius + 1)..=(image_params.width as i32) { pixels.insert((x, y)); } } From e3196165ca4edd472013e4574d45ee6421b9014a Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Sat, 14 Sep 2024 15:23:13 +0900 Subject: [PATCH 7/7] Fix to use single quote instead of double quote --- src/color.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/color.rs b/src/color.rs index ecd59e9..4350250 100644 --- a/src/color.rs +++ b/src/color.rs @@ -60,7 +60,7 @@ impl ColorSet { } fn parse_rgba_color(s: &str) -> Option { - if !s.starts_with("#") { + if !s.starts_with('#') { return None; }