commit c9105bf4ce7badd7669ed43cde3356f26cf34dc1 Author: Antoine Martin Date: Tue Sep 27 19:57:38 2022 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..273c472 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +/target +.direnv/ +.envrc + + +# Added by cargo +# +# already existing elements were commented out + +#/target +/Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..fd97414 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "unicornlor" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = "0.8.5" diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..f468425 --- /dev/null +++ b/flake.lock @@ -0,0 +1,69 @@ +{ + "nodes": { + "flake-utils": { + "locked": { + "lastModified": 1659877975, + "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0", + "type": "github" + }, + "original": { + "owner": "numtide", + "ref": "master", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1664178928, + "narHash": "sha256-+WVCZH/3Ifef4Da9N1tkGnmfX0QwtkJQz013QuImu10=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b542cc75fa03a3a29350d4c3b69739e946268a93", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-22.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "flake-utils": [ + "flake-utils" + ], + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1664247556, + "narHash": "sha256-J4vazHU3609ekn7dr+3wfqPo5WGlZVAgV7jfux352L0=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "524db9c9ea7bc7743bb74cdd45b6d46ea3fcc2ab", + "type": "github" + }, + "original": { + "owner": "oxalica", + "ref": "master", + "repo": "rust-overlay", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..d227f18 --- /dev/null +++ b/flake.nix @@ -0,0 +1,64 @@ +{ + inputs = { + nixpkgs = { + type = "github"; + owner = "NixOS"; + repo = "nixpkgs"; + ref = "nixos-22.05"; + }; + + flake-utils = { + type = "github"; + owner = "numtide"; + repo = "flake-utils"; + ref = "master"; + }; + + rust-overlay = { + type = "github"; + owner = "oxalica"; + repo = "rust-overlay"; + ref = "master"; + inputs = { + flake-utils.follows = "flake-utils"; + nixpkgs.follows = "nixpkgs"; + }; + }; + }; + + outputs = { + self, + flake-utils, + nixpkgs, + rust-overlay, + }: let + inherit (flake-utils.lib) eachSystem system; + mySystems = [ + system.aarch64-linux + system.x86_64-darwin + system.x86_64-linux + ]; + eachMySystem = eachSystem mySystems; + in + eachMySystem (system: let + overlays = [(import rust-overlay)]; + pkgs = import nixpkgs {inherit overlays system;}; + my-rust = pkgs.rust-bin.stable.latest.default.override { + extensions = ["rust-src"]; + }; + inherit (pkgs) lib; + in { + devShells = { + default = pkgs.mkShell { + + nativeBuildInputs = with pkgs; [ + rust-analyzer + # Clippy, rustfmt, etc... + my-rust + ]; + + RUST_SRC_PATH = "${my-rust}/lib/rustlib/src/rust/library"; + }; + }; + }); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..623126d --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,135 @@ +use rand::Rng; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct HsvColor { + h: f32, + s: f32, + v: f32, +} + +impl HsvColor { + pub fn new(h: f32, s: f32, v: f32) -> Self { + Self { h, s, v } + } + + /// Converts the color to its RGB equivalent. + /// + /// # Panics + /// + /// Panics if the color has a Hue value outside of the range `0..=360`. + pub fn to_rgb(self) -> RgbColor { + // See https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB + let chroma = self.s * self.v; + let h_prime = self.h / 60.0; + // second largest component + let second = chroma * (1.0 - ((h_prime % 2.0) - 1.0).abs()); + let (r, g, b) = match h_prime { + h if (0.0..1.0).contains(&h) => (chroma, second, 0.0), + h if (1.0..2.0).contains(&h) => (second, chroma, 0.0), + h if (2.0..3.0).contains(&h) => (0.0, chroma, second), + h if (3.0..4.0).contains(&h) => (0.0, second, chroma), + h if (4.0..5.0).contains(&h) => (second, 0.0, chroma), + h if (5.0..=6.0).contains(&h) => (chroma, 0.0, second), + _ => panic!( + "Unexpected Hue value during HSV to RGB conversion: {}", + self.h + ), + }; + + let m = self.v - chroma; + let (r, g, b) = (r + m, g + m, b + m); + let (r, g, b) = (r * 255.0, g * 255.0, b * 255.0); + let (r, g, b) = (r.round(), g.round(), b.round()); + debug_assert!((0.0..=255.0).contains(&r)); + debug_assert!((0.0..=255.0).contains(&g)); + debug_assert!((0.0..=255.0).contains(&b)); + let (r, g, b) = (r as u8, g as u8, b as u8); + + RgbColor { r, g, b } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct RgbColor { + r: u8, + g: u8, + b: u8, +} + +impl RgbColor { + pub fn new(r: u8, g: u8, b: u8) -> Self { + Self { r, g, b } + } + + pub fn to_u32(self) -> u32 { + (self.r as u32) << 16 | (self.g as u32) << 8 | (self.b as u32) + } +} + +pub fn not_so_random_color() -> RgbColor { + HsvColor::new(rand::thread_rng().gen_range(0.0..360.0), 0.5, 0.9).to_rgb() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn rgb_to_u32() { + assert_eq!(RgbColor::new(255, 255, 255).to_u32(), 0xFFFFFF); + assert_eq!(RgbColor::new(0, 0, 0).to_u32(), 0x000000); + assert_eq!(RgbColor::new(255, 0, 0).to_u32(), 0xFF0000); + assert_eq!(RgbColor::new(0, 0, 255).to_u32(), 0x0000FF); + assert_eq!(RgbColor::new(0, 255, 0).to_u32(), 0x00FF00); + assert_eq!(RgbColor::new(82, 252, 3).to_u32(), 0x52FC03); + assert_eq!(RgbColor::new(3, 32, 252).to_u32(), 0x0320FC); + assert_eq!(RgbColor::new(86, 3, 252).to_u32(), 0x5603FC); + assert_eq!(RgbColor::new(252, 136, 3).to_u32(), 0xFC8803); + assert_eq!(RgbColor::new(252, 40, 3).to_u32(), 0xFC2803); + assert_eq!(RgbColor::new(3, 252, 211).to_u32(), 0x03FCD3); + } + + #[test] + fn hsv_to_rgb() { + assert_eq!( + HsvColor::new(217.0, 0.73, 0.96).to_rgb(), + RgbColor::new(66, 135, 245) + ); + assert_eq!( + HsvColor::new(122.0, 0.51, 0.61).to_rgb(), + RgbColor::new(76, 156, 79) + ); + assert_eq!( + HsvColor::new(54.0, 0.91, 0.85).to_rgb(), + RgbColor::new(217, 197, 20) + ); + assert_eq!( + HsvColor::new(0.0, 0.73, 0.96).to_rgb(), + RgbColor::new(245, 66, 66) + ); + assert_eq!( + HsvColor::new(360.0, 0.73, 0.96).to_rgb(), + RgbColor::new(245, 66, 66) + ); + assert_eq!( + HsvColor::new(0.0, 0.0, 0.0).to_rgb(), + RgbColor::new(0, 0, 0) + ); + assert_eq!( + HsvColor::new(0.0, 0.0, 1.0).to_rgb(), + RgbColor::new(255, 255, 255) + ); + assert_eq!( + HsvColor::new(0.0, 1.0, 1.0).to_rgb(), + RgbColor::new(255, 0, 0) + ); + assert_eq!( + HsvColor::new(120.0, 1.0, 1.0).to_rgb(), + RgbColor::new(0, 255, 0) + ); + assert_eq!( + HsvColor::new(240.0, 1.0, 1.0).to_rgb(), + RgbColor::new(0, 0, 255) + ); + } +}