ecolor-0.33.0/.cargo_vcs_info.json0000644000000001530000000000100124010ustar { "git": { "sha1": "430a3fbc7872a64ad607e95fa87a566c8e4f1e87" }, "path_in_vcs": "crates/ecolor" }ecolor-0.33.0/Cargo.lock0000644000000064360000000000100103660ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "bytemuck" version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "cint" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a0e87cdf78571d9fbeff16861c37a006cd718d2433dc6d5b80beaae367d899a" [[package]] name = "color-hex" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecdffb913a326b6c642290a0d0ec8e8d6597291acdc07cc4c9cb4b3635d44cf9" [[package]] name = "document-features" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" dependencies = [ "litrs", ] [[package]] name = "ecolor" version = "0.33.0" dependencies = [ "bytemuck", "cint", "color-hex", "document-features", "emath", "serde", ] [[package]] name = "emath" version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c615516cdceec867065f20d7db13d8eb8aedd65c9e32cc0c7c379380fa42e6e8" [[package]] name = "litrs" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "proc-macro2" version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] [[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", ] [[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "syn" version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" ecolor-0.33.0/Cargo.toml0000644000000170650000000000100104110ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2024" rust-version = "1.88" name = "ecolor" version = "0.33.0" authors = [ "Emil Ernerfeldt ", "Andreas Reich ", ] build = false include = [ "../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Color structs and color conversion utilities" homepage = "https://github.com/emilk/egui" readme = "README.md" keywords = [ "gui", "color", "conversion", "gamedev", "images", ] categories = [ "mathematics", "encoding", ] license = "MIT OR Apache-2.0" repository = "https://github.com/emilk/egui" resolver = "2" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--generate-link-to-definition"] [features] default = [] [lib] name = "ecolor" path = "src/lib.rs" [dependencies.bytemuck] version = "1.24.0" features = ["derive"] optional = true [dependencies.cint] version = "0.3.1" optional = true [dependencies.color-hex] version = "0.2.0" optional = true [dependencies.document-features] version = "0.2.11" optional = true [dependencies.emath] version = "0.33.0" default-features = false [dependencies.serde] version = "1.0.228" features = ["derive"] optional = true [lints.clippy] allow_attributes = "warn" as_ptr_cast_mut = "warn" assigning_clones = "allow" await_holding_lock = "warn" bool_to_int_with_if = "warn" branches_sharing_code = "warn" char_lit_as_u8 = "warn" checked_conversions = "warn" clear_with_drain = "warn" cloned_instead_of_copied = "warn" comparison_chain = "allow" dbg_macro = "warn" debug_assert_with_mut_call = "warn" default_union_representation = "warn" derive_partial_eq_without_eq = "warn" disallowed_macros = "warn" disallowed_methods = "warn" disallowed_names = "warn" disallowed_script_idents = "warn" disallowed_types = "warn" doc_comment_double_space_linebreaks = "warn" doc_link_with_quotes = "warn" doc_markdown = "warn" elidable_lifetime_names = "warn" empty_enum = "warn" empty_enum_variants_with_brackets = "warn" empty_line_after_outer_attr = "warn" enum_glob_use = "warn" equatable_if_let = "warn" exit = "warn" expl_impl_clone_on_copy = "warn" explicit_deref_methods = "warn" explicit_into_iter_loop = "warn" explicit_iter_loop = "warn" fallible_impl_from = "warn" filter_map_next = "warn" flat_map_option = "warn" float_cmp_const = "warn" fn_params_excessive_bools = "warn" fn_to_numeric_cast_any = "warn" from_iter_instead_of_collect = "warn" get_unwrap = "warn" if_let_mutex = "warn" ignore_without_reason = "warn" implicit_clone = "warn" implied_bounds_in_impls = "warn" imprecise_flops = "warn" inconsistent_struct_constructor = "warn" index_refutable_slice = "warn" inefficient_to_string = "warn" infinite_loop = "warn" into_iter_without_iter = "warn" invalid_upcast_comparisons = "warn" iter_filter_is_ok = "warn" iter_filter_is_some = "warn" iter_not_returning_iterator = "warn" iter_on_empty_collections = "warn" iter_on_single_items = "warn" iter_over_hash_type = "warn" iter_without_into_iter = "warn" large_digit_groups = "warn" large_include_file = "warn" large_stack_arrays = "warn" large_stack_frames = "warn" large_types_passed_by_value = "warn" let_underscore_must_use = "allow" let_underscore_untyped = "allow" let_unit_value = "warn" linkedlist = "warn" literal_string_with_formatting_args = "warn" lossy_float_literal = "warn" macro_use_imports = "warn" manual_assert = "warn" manual_clamp = "warn" manual_instant_elapsed = "warn" manual_is_power_of_two = "warn" manual_is_variant_and = "warn" manual_let_else = "warn" manual_midpoint = "warn" manual_ok_or = "warn" manual_range_contains = "allow" manual_string_new = "warn" map_err_ignore = "warn" map_flatten = "warn" map_unwrap_or = "allow" match_bool = "warn" match_same_arms = "warn" match_wild_err_arm = "warn" match_wildcard_for_single_variants = "warn" mem_forget = "warn" mismatching_type_param_order = "warn" missing_assert_message = "warn" missing_enforced_import_renames = "warn" missing_errors_doc = "warn" missing_safety_doc = "warn" mixed_attributes_style = "warn" mut_mut = "warn" mutex_integer = "warn" needless_borrow = "warn" needless_continue = "warn" needless_for_each = "warn" needless_pass_by_ref_mut = "warn" needless_pass_by_value = "warn" negative_feature_names = "warn" non_std_lazy_statics = "warn" non_zero_suggestions = "warn" nonstandard_macro_braces = "warn" option_as_ref_cloned = "warn" option_option = "warn" path_buf_push_overwrite = "warn" pathbuf_init_then_push = "warn" precedence_bits = "warn" print_stderr = "warn" print_stdout = "warn" ptr_as_ptr = "warn" ptr_cast_constness = "warn" pub_underscore_fields = "warn" pub_without_shorthand = "warn" rc_mutex = "warn" readonly_write_lock = "warn" redundant_type_annotations = "warn" ref_as_ptr = "warn" ref_option_ref = "warn" ref_patterns = "warn" rest_pat_in_fully_bound_structs = "warn" return_and_then = "warn" same_functions_in_if_condition = "warn" self_named_module_files = "allow" semicolon_if_nothing_returned = "warn" set_contains_or_insert = "warn" should_panic_without_expect = "allow" significant_drop_tightening = "allow" single_char_pattern = "warn" single_match_else = "warn" single_option_map = "warn" str_split_at_newline = "warn" str_to_string = "warn" string_add = "warn" string_add_assign = "warn" string_lit_as_bytes = "warn" string_lit_chars_any = "warn" string_to_string = "warn" suspicious_command_arg_space = "warn" suspicious_xor_used_as_pow = "warn" todo = "warn" too_long_first_doc_paragraph = "warn" too_many_lines = "allow" trailing_empty_array = "warn" trait_duplication_in_bounds = "warn" transmute_ptr_to_ptr = "warn" tuple_array_conversions = "warn" unchecked_duration_subtraction = "warn" undocumented_unsafe_blocks = "warn" unimplemented = "warn" uninhabited_references = "warn" uninlined_format_args = "warn" unnecessary_box_returns = "warn" unnecessary_debug_formatting = "warn" unnecessary_literal_bound = "warn" unnecessary_safety_comment = "warn" unnecessary_safety_doc = "warn" unnecessary_self_imports = "warn" unnecessary_semicolon = "warn" unnecessary_struct_initialization = "warn" unnecessary_wraps = "warn" unnested_or_patterns = "warn" unused_peekable = "warn" unused_rounding = "warn" unused_self = "warn" unused_trait_names = "warn" unwrap_used = "allow" use_self = "warn" useless_let_if_seq = "warn" useless_transmute = "warn" verbose_file_reads = "warn" wildcard_dependencies = "warn" wildcard_imports = "allow" zero_sized_map_values = "warn" [lints.clippy.all] level = "warn" priority = -1 [lints.rust] elided_lifetimes_in_paths = "warn" rust_2021_prelude_collisions = "warn" semicolon_in_expressions_from_macros = "warn" trivial_casts = "allow" trivial_numeric_casts = "warn" unexpected_cfgs = "warn" unsafe_code = "deny" unsafe_op_in_unsafe_fn = "warn" unused_extern_crates = "warn" unused_import_braces = "warn" unused_lifetimes = "warn" unused_qualifications = "allow" [lints.rust.future_incompatible] level = "warn" priority = -1 [lints.rust.nonstandard_style] level = "warn" priority = -1 [lints.rust.rust_2018_idioms] level = "warn" priority = -1 [lints.rustdoc] all = "warn" broken_intra_doc_links = "warn" missing_crate_level_docs = "warn" ecolor-0.33.0/Cargo.toml.orig000064400000000000000000000025561046102023000140710ustar 00000000000000[package] name = "ecolor" version.workspace = true authors = ["Emil Ernerfeldt ", "Andreas Reich "] description = "Color structs and color conversion utilities" edition.workspace = true rust-version.workspace = true homepage = "https://github.com/emilk/egui" license.workspace = true readme = "README.md" repository = "https://github.com/emilk/egui" categories = ["mathematics", "encoding"] keywords = ["gui", "color", "conversion", "gamedev", "images"] include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] [lints] workspace = true [package.metadata.docs.rs] all-features = true rustdoc-args = ["--generate-link-to-definition"] [lib] [features] default = [] [dependencies] emath.workspace = true #! ### Optional dependencies ## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast `ecolor` types to `&[u8]`. bytemuck = { workspace = true, optional = true, features = ["derive"] } ## [`cint`](https://docs.rs/cint) enables interoperability with other color libraries. cint = { workspace = true, optional = true } ## Enable the [`hex_color`] macro. color-hex = { workspace = true, optional = true } ## Enable this when generating docs. document-features = { workspace = true, optional = true } ## Allow serialization using [`serde`](https://docs.rs/serde). serde = { workspace = true, optional = true } ecolor-0.33.0/README.md000064400000000000000000000012671046102023000124570ustar 00000000000000# ecolor - egui color library [![Latest version](https://img.shields.io/crates/v/ecolor.svg)](https://crates.io/crates/ecolor) [![Documentation](https://docs.rs/ecolor/badge.svg)](https://docs.rs/ecolor) [![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) ![MIT](https://img.shields.io/badge/license-MIT-blue.svg) ![Apache](https://img.shields.io/badge/license-Apache-blue.svg) A simple color storage and conversion library. This crate is built for the wants and needs of [`egui`](https://github.com/emilk/egui/). If you want an actual _good_ color crate, use [`color`](https://crates.io/crates/color) instead. ecolor-0.33.0/src/cint_impl.rs000064400000000000000000000075401046102023000143130ustar 00000000000000use super::{Color32, Hsva, HsvaGamma, Rgba, linear_f32_from_linear_u8, linear_u8_from_linear_f32}; use cint::{Alpha, ColorInterop, EncodedSrgb, Hsv, LinearSrgb, PremultipliedAlpha}; // ---- Color32 ---- impl From>> for Color32 { fn from(srgba: Alpha>) -> Self { let Alpha { color: EncodedSrgb { r, g, b }, alpha: a, } = srgba; Self::from_rgba_unmultiplied(r, g, b, a) } } // No From for Alpha<_> because Color32 is premultiplied impl From>> for Color32 { fn from(srgba: PremultipliedAlpha>) -> Self { let PremultipliedAlpha { color: EncodedSrgb { r, g, b }, alpha: a, } = srgba; Self::from_rgba_premultiplied(r, g, b, a) } } impl From for PremultipliedAlpha> { fn from(col: Color32) -> Self { let (r, g, b, a) = col.to_tuple(); Self { color: EncodedSrgb { r, g, b }, alpha: a, } } } impl From>> for Color32 { fn from(srgba: PremultipliedAlpha>) -> Self { let PremultipliedAlpha { color: EncodedSrgb { r, g, b }, alpha: a, } = srgba; // This is a bit of an abuse of the function name but it does what we want. let r = linear_u8_from_linear_f32(r); let g = linear_u8_from_linear_f32(g); let b = linear_u8_from_linear_f32(b); let a = linear_u8_from_linear_f32(a); Self::from_rgba_premultiplied(r, g, b, a) } } impl From for PremultipliedAlpha> { fn from(col: Color32) -> Self { let (r, g, b, a) = col.to_tuple(); // This is a bit of an abuse of the function name but it does what we want. let r = linear_f32_from_linear_u8(r); let g = linear_f32_from_linear_u8(g); let b = linear_f32_from_linear_u8(b); let a = linear_f32_from_linear_u8(a); Self { color: EncodedSrgb { r, g, b }, alpha: a, } } } impl ColorInterop for Color32 { type CintTy = PremultipliedAlpha>; } // ---- Rgba ---- impl From>> for Rgba { fn from(srgba: PremultipliedAlpha>) -> Self { let PremultipliedAlpha { color: LinearSrgb { r, g, b }, alpha: a, } = srgba; Self([r, g, b, a]) } } impl From for PremultipliedAlpha> { fn from(col: Rgba) -> Self { let (r, g, b, a) = col.to_tuple(); Self { color: LinearSrgb { r, g, b }, alpha: a, } } } impl ColorInterop for Rgba { type CintTy = PremultipliedAlpha>; } // ---- Hsva ---- impl From>> for Hsva { fn from(srgba: Alpha>) -> Self { let Alpha { color: Hsv { h, s, v }, alpha: a, } = srgba; Self::new(h, s, v, a) } } impl From for Alpha> { fn from(col: Hsva) -> Self { let Hsva { h, s, v, a } = col; Self { color: Hsv { h, s, v }, alpha: a, } } } impl ColorInterop for Hsva { type CintTy = Alpha>; } // ---- HsvaGamma ---- impl ColorInterop for HsvaGamma { type CintTy = Alpha>; } impl From>> for HsvaGamma { fn from(srgba: Alpha>) -> Self { let Alpha { color: Hsv { h, s, v }, alpha: a, } = srgba; Hsva::new(h, s, v, a).into() } } impl From for Alpha> { fn from(col: HsvaGamma) -> Self { let Hsva { h, s, v, a } = col.into(); Self { color: Hsv { h, s, v }, alpha: a, } } } ecolor-0.33.0/src/color32.rs000064400000000000000000000425471046102023000136260ustar 00000000000000use crate::{Rgba, fast_round, linear_f32_from_linear_u8}; /// This format is used for space-efficient color representation (32 bits). /// /// Instead of manipulating this directly it is often better /// to first convert it to either [`Rgba`] or [`crate::Hsva`]. /// /// Internally this uses 0-255 gamma space `sRGBA` color with _premultiplied alpha_. /// /// It's the non-linear ("gamma") values that are multiplied with the alpha. /// /// Premultiplied alpha means that the color values have been pre-multiplied with the alpha (opacity). /// This is in contrast with "normal" RGBA, where the alpha is _separate_ (or "unmultiplied"). /// Using premultiplied alpha has some advantages: /// * It allows encoding additive colors /// * It is the better way to blend colors, e.g. when filtering texture colors /// * Because the above, it is the better way to encode colors in a GPU texture /// /// The color space is assumed to be [sRGB](https://en.wikipedia.org/wiki/SRGB). /// /// All operations on `Color32` are done in "gamma space" (see ). /// This is not physically correct, but it is fast and sometimes more perceptually even than linear space. /// If you instead want to perform these operations in linear-space color, use [`Rgba`]. /// /// An `alpha=0` means the color is to be treated as an additive color. #[repr(C)] #[repr(align(4))] #[derive(Clone, Copy, Default, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))] pub struct Color32(pub(crate) [u8; 4]); impl std::fmt::Debug for Color32 { /// Prints the contents with premultiplied alpha! fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let [r, g, b, a] = self.0; write!(f, "#{r:02X}_{g:02X}_{b:02X}_{a:02X}") } } impl std::ops::Index for Color32 { type Output = u8; #[inline] fn index(&self, index: usize) -> &u8 { &self.0[index] } } impl std::ops::IndexMut for Color32 { #[inline] fn index_mut(&mut self, index: usize) -> &mut u8 { &mut self.0[index] } } impl Color32 { // Mostly follows CSS names: pub const TRANSPARENT: Self = Self::from_rgba_premultiplied(0, 0, 0, 0); pub const BLACK: Self = Self::from_rgb(0, 0, 0); #[doc(alias = "DARK_GREY")] pub const DARK_GRAY: Self = Self::from_rgb(96, 96, 96); #[doc(alias = "GREY")] pub const GRAY: Self = Self::from_rgb(160, 160, 160); #[doc(alias = "LIGHT_GREY")] pub const LIGHT_GRAY: Self = Self::from_rgb(220, 220, 220); pub const WHITE: Self = Self::from_rgb(255, 255, 255); pub const BROWN: Self = Self::from_rgb(165, 42, 42); pub const DARK_RED: Self = Self::from_rgb(0x8B, 0, 0); pub const RED: Self = Self::from_rgb(255, 0, 0); pub const LIGHT_RED: Self = Self::from_rgb(255, 128, 128); pub const CYAN: Self = Self::from_rgb(0, 255, 255); pub const MAGENTA: Self = Self::from_rgb(255, 0, 255); pub const YELLOW: Self = Self::from_rgb(255, 255, 0); pub const ORANGE: Self = Self::from_rgb(255, 165, 0); pub const LIGHT_YELLOW: Self = Self::from_rgb(255, 255, 0xE0); pub const KHAKI: Self = Self::from_rgb(240, 230, 140); pub const DARK_GREEN: Self = Self::from_rgb(0, 0x64, 0); pub const GREEN: Self = Self::from_rgb(0, 255, 0); pub const LIGHT_GREEN: Self = Self::from_rgb(0x90, 0xEE, 0x90); pub const DARK_BLUE: Self = Self::from_rgb(0, 0, 0x8B); pub const BLUE: Self = Self::from_rgb(0, 0, 255); pub const LIGHT_BLUE: Self = Self::from_rgb(0xAD, 0xD8, 0xE6); pub const PURPLE: Self = Self::from_rgb(0x80, 0, 0x80); pub const GOLD: Self = Self::from_rgb(255, 215, 0); pub const DEBUG_COLOR: Self = Self::from_rgba_premultiplied(0, 200, 0, 128); /// An ugly color that is planned to be replaced before making it to the screen. /// /// This is an invalid color, in that it does not correspond to a valid multiplied color, /// nor to an additive color. /// /// This is used as a special color key, /// i.e. often taken to mean "no color". pub const PLACEHOLDER: Self = Self::from_rgba_premultiplied(64, 254, 0, 128); /// From RGB with alpha of 255 (opaque). #[inline] pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self { Self([r, g, b, 255]) } /// From RGB into an additive color (will make everything it blend with brighter). #[inline] pub const fn from_rgb_additive(r: u8, g: u8, b: u8) -> Self { Self([r, g, b, 0]) } /// From `sRGBA` with premultiplied alpha. /// /// You likely want to use [`Self::from_rgba_unmultiplied`] instead. #[inline] pub const fn from_rgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self { Self([r, g, b, a]) } /// From `sRGBA` with separate alpha. /// /// This is a "normal" RGBA value that you would find in a color picker or a table somewhere. /// /// You can use [`Self::to_srgba_unmultiplied`] to get back these values, /// but for transparent colors what you get back might be slightly different (rounding errors). #[inline] pub fn from_rgba_unmultiplied(r: u8, g: u8, b: u8, a: u8) -> Self { use std::sync::OnceLock; match a { // common-case optimization: 0 => Self::TRANSPARENT, // common-case optimization: 255 => Self::from_rgb(r, g, b), a => { static LOOKUP_TABLE: OnceLock> = OnceLock::new(); let lut = LOOKUP_TABLE.get_or_init(|| { (0..=u16::MAX) .map(|i| { let [value, alpha] = i.to_ne_bytes(); fast_round(value as f32 * linear_f32_from_linear_u8(alpha)) }) .collect() }); let [r, g, b] = [r, g, b].map(|value| lut[usize::from(u16::from_ne_bytes([value, a]))]); Self::from_rgba_premultiplied(r, g, b, a) } } } /// Same as [`Self::from_rgba_unmultiplied`], but can be used in a const context. /// /// It is slightly slower when operating on non-const data. #[inline] pub const fn from_rgba_unmultiplied_const(r: u8, g: u8, b: u8, a: u8) -> Self { match a { // common-case optimization: 0 => Self::TRANSPARENT, // common-case optimization: 255 => Self::from_rgb(r, g, b), a => { let r = fast_round(r as f32 * linear_f32_from_linear_u8(a)); let g = fast_round(g as f32 * linear_f32_from_linear_u8(a)); let b = fast_round(b as f32 * linear_f32_from_linear_u8(a)); Self::from_rgba_premultiplied(r, g, b, a) } } } /// Opaque gray. #[doc(alias = "from_grey")] #[inline] pub const fn from_gray(l: u8) -> Self { Self([l, l, l, 255]) } /// Black with the given opacity. #[inline] pub const fn from_black_alpha(a: u8) -> Self { Self([0, 0, 0, a]) } /// White with the given opacity. #[inline] pub fn from_white_alpha(a: u8) -> Self { Self([a, a, a, a]) } /// Additive white. #[inline] pub const fn from_additive_luminance(l: u8) -> Self { Self([l, l, l, 0]) } #[inline] pub const fn is_opaque(&self) -> bool { self.a() == 255 } /// Red component multiplied by alpha. #[inline] pub const fn r(&self) -> u8 { self.0[0] } /// Green component multiplied by alpha. #[inline] pub const fn g(&self) -> u8 { self.0[1] } /// Blue component multiplied by alpha. #[inline] pub const fn b(&self) -> u8 { self.0[2] } /// Alpha (opacity). #[inline] pub const fn a(&self) -> u8 { self.0[3] } /// Returns an opaque version of self #[inline] pub fn to_opaque(self) -> Self { Rgba::from(self).to_opaque().into() } /// Returns an additive version of self #[inline] pub const fn additive(self) -> Self { let [r, g, b, _] = self.to_array(); Self([r, g, b, 0]) } /// Is the alpha=0 ? #[inline] pub fn is_additive(self) -> bool { self.a() == 0 } /// Premultiplied RGBA #[inline] pub const fn to_array(&self) -> [u8; 4] { [self.r(), self.g(), self.b(), self.a()] } /// Premultiplied RGBA #[inline] pub const fn to_tuple(&self) -> (u8, u8, u8, u8) { (self.r(), self.g(), self.b(), self.a()) } /// Convert to a normal "unmultiplied" RGBA color (i.e. with separate alpha). /// /// This will unmultiply the alpha. /// /// This is the inverse of [`Self::from_rgba_unmultiplied`], /// but due to precision problems it may return slightly different values for transparent colors. #[inline] pub fn to_srgba_unmultiplied(&self) -> [u8; 4] { let [r, g, b, a] = self.to_array(); match a { // Common-case optimization. 0 | 255 => self.to_array(), a => { let factor = 255.0 / a as f32; let r = fast_round(factor * r as f32); let g = fast_round(factor * g as f32); let b = fast_round(factor * b as f32); [r, g, b, a] } } } /// Multiply with 0.5 to make color half as opaque, perceptually. /// /// Fast multiplication in gamma-space. /// /// This is perceptually even, and faster that [`Self::linear_multiply`]. #[inline] pub fn gamma_multiply(self, factor: f32) -> Self { debug_assert!( 0.0 <= factor && factor.is_finite(), "factor should be finite, but was {factor}" ); let Self([r, g, b, a]) = self; Self([ (r as f32 * factor + 0.5) as u8, (g as f32 * factor + 0.5) as u8, (b as f32 * factor + 0.5) as u8, (a as f32 * factor + 0.5) as u8, ]) } /// Multiply with 127 to make color half as opaque, perceptually. /// /// Fast multiplication in gamma-space. /// /// This is perceptually even, and faster that [`Self::linear_multiply`]. #[inline] pub fn gamma_multiply_u8(self, factor: u8) -> Self { let Self([r, g, b, a]) = self; let factor = factor as u32; Self([ ((r as u32 * factor + 127) / 255) as u8, ((g as u32 * factor + 127) / 255) as u8, ((b as u32 * factor + 127) / 255) as u8, ((a as u32 * factor + 127) / 255) as u8, ]) } /// Multiply with 0.5 to make color half as opaque in linear space. /// /// This is using linear space, which is not perceptually even. /// You likely want to use [`Self::gamma_multiply`] instead. #[inline] pub fn linear_multiply(self, factor: f32) -> Self { debug_assert!( 0.0 <= factor && factor.is_finite(), "factor should be finite, but was {factor}" ); // As an unfortunate side-effect of using premultiplied alpha // we need a somewhat expensive conversion to linear space and back. Rgba::from(self).multiply(factor).into() } /// Converts to floating point values in the range 0-1 without any gamma space conversion. /// /// Use this with great care! In almost all cases, you want to convert to [`crate::Rgba`] instead /// in order to obtain linear space color values. #[inline] pub fn to_normalized_gamma_f32(self) -> [f32; 4] { let Self([r, g, b, a]) = self; [ r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, a as f32 / 255.0, ] } /// Lerp this color towards `other` by `t` in gamma space. pub fn lerp_to_gamma(&self, other: Self, t: f32) -> Self { use emath::lerp; Self::from_rgba_premultiplied( fast_round(lerp((self[0] as f32)..=(other[0] as f32), t)), fast_round(lerp((self[1] as f32)..=(other[1] as f32), t)), fast_round(lerp((self[2] as f32)..=(other[2] as f32), t)), fast_round(lerp((self[3] as f32)..=(other[3] as f32), t)), ) } /// Blend two colors in gamma space, so that `self` is behind the argument. pub fn blend(self, on_top: Self) -> Self { self.gamma_multiply_u8(255 - on_top.a()) + on_top } /// Intensity of the color. /// /// Returns a value in the range 0-1. /// The brighter the color, the closer to 1. pub fn intensity(&self) -> f32 { (self.r() as f32 * 0.299 + self.g() as f32 * 0.587 + self.b() as f32 * 0.114) / 255.0 } } impl std::ops::Mul for Color32 { type Output = Self; /// Fast gamma-space multiplication. #[inline] fn mul(self, other: Self) -> Self { Self([ fast_round(self[0] as f32 * other[0] as f32 / 255.0), fast_round(self[1] as f32 * other[1] as f32 / 255.0), fast_round(self[2] as f32 * other[2] as f32 / 255.0), fast_round(self[3] as f32 * other[3] as f32 / 255.0), ]) } } impl std::ops::Add for Color32 { type Output = Self; #[inline] fn add(self, other: Self) -> Self { Self([ self[0].saturating_add(other[0]), self[1].saturating_add(other[1]), self[2].saturating_add(other[2]), self[3].saturating_add(other[3]), ]) } } #[cfg(test)] mod test { use super::*; fn test_rgba() -> impl Iterator { [ [0, 0, 0, 0], [0, 0, 0, 255], [10, 0, 30, 0], [10, 0, 30, 40], [10, 100, 200, 0], [10, 100, 200, 100], [10, 100, 200, 200], [10, 100, 200, 255], [10, 100, 200, 40], [10, 20, 0, 0], [10, 20, 0, 255], [10, 20, 30, 255], [10, 20, 30, 40], [255, 255, 255, 0], [255, 255, 255, 255], ] .into_iter() } #[test] fn test_color32_additive() { let opaque = Color32::from_rgb(40, 50, 60); let additive = Color32::from_rgb(255, 127, 10).additive(); assert_eq!(additive.blend(opaque), opaque, "opaque on top of additive"); assert_eq!( opaque.blend(additive), Color32::from_rgb(255, 177, 70), "additive on top of opaque" ); } #[test] fn test_color32_blend_vs_gamma_blend() { let opaque = Color32::from_rgb(0x60, 0x60, 0x60); let transparent = Color32::from_rgba_unmultiplied(168, 65, 65, 79); assert_eq!( transparent.blend(opaque), opaque, "Opaque on top of transparent" ); // Blending in gamma-space is the de-facto standard almost everywhere. // Browsers and most image editors do it, and so it is what users expect. assert_eq!( opaque.blend(transparent), Color32::from_rgb( blend(0x60, 168, 79), blend(0x60, 65, 79), blend(0x60, 65, 79) ), "Transparent on top of opaque" ); fn blend(dest: u8, src: u8, alpha: u8) -> u8 { let src = src as f32 / 255.0; let dest = dest as f32 / 255.0; let alpha = alpha as f32 / 255.0; fast_round((src * alpha + dest * (1.0 - alpha)) * 255.0) } } #[test] fn color32_unmultiplied_round_trip() { for in_rgba in test_rgba() { let [r, g, b, a] = in_rgba; if a == 0 { continue; } let c = Color32::from_rgba_unmultiplied(r, g, b, a); let out_rgba = c.to_srgba_unmultiplied(); if a == 255 { assert_eq!(in_rgba, out_rgba); } else { // There will be small rounding errors whenever the alpha is not 0 or 255, // because we multiply and then unmultiply the alpha. for (&a, &b) in in_rgba.iter().zip(out_rgba.iter()) { assert!(a.abs_diff(b) <= 3, "{in_rgba:?} != {out_rgba:?}"); } } } } #[test] fn from_black_white_alpha() { for a in 0..=255 { assert_eq!( Color32::from_white_alpha(a), Color32::from_rgba_unmultiplied(255, 255, 255, a) ); assert_eq!( Color32::from_white_alpha(a), Color32::WHITE.gamma_multiply_u8(a) ); assert_eq!( Color32::from_black_alpha(a), Color32::from_rgba_unmultiplied(0, 0, 0, a) ); assert_eq!( Color32::from_black_alpha(a), Color32::BLACK.gamma_multiply_u8(a) ); } } #[test] fn to_from_rgba() { for [r, g, b, a] in test_rgba() { let original = Color32::from_rgba_unmultiplied(r, g, b, a); let constfn = Color32::from_rgba_unmultiplied_const(r, g, b, a); let rgba = Rgba::from(original); let back = Color32::from(rgba); assert_eq!(back, original); assert_eq!(constfn, original); } assert_eq!( Color32::from(Rgba::from_rgba_unmultiplied(1.0, 0.0, 0.0, 0.5)), Color32::from_rgba_unmultiplied(255, 0, 0, 128) ); } } ecolor-0.33.0/src/hex_color_macro.rs000064400000000000000000000044451046102023000155010ustar 00000000000000/// Construct a [`crate::Color32`] from a hex RGB or RGBA string literal. /// /// Requires the "color-hex" feature. /// /// The string is checked at compile time. If the format is invalid, compilation fails. The valid /// format is the one described in . Only 6 (RGB) or 8 (RGBA) /// digits are supported, and the leading `#` character is optional. /// /// Note that despite being checked at compile-time, this macro is not usable in `const` contexts /// because creating the [`crate::Color32`] instance requires floating-point arithmetic. /// /// See also [`crate::Color32::from_hex`] and [`crate::Color32::to_hex`]. /// /// # Examples /// /// ``` /// # use ecolor::{hex_color, Color32}; /// assert_eq!(hex_color!("#202122"), Color32::from_hex("#202122").unwrap()); /// assert_eq!(hex_color!("#202122"), Color32::from_rgb(0x20, 0x21, 0x22)); /// assert_eq!(hex_color!("#202122"), hex_color!("202122")); /// assert_eq!(hex_color!("#abcdef12"), Color32::from_rgba_unmultiplied(0xab, 0xcd, 0xef, 0x12)); /// ``` /// /// If the literal string has the wrong format, the code does not compile. /// /// ```compile_fail /// let _ = ecolor::hex_color!("#abc"); /// ``` /// /// ```compile_fail /// let _ = ecolor::hex_color!("#20212x"); /// ``` /// /// The macro can be used in a `const` context. /// /// ``` /// const COLOR: ecolor::Color32 = ecolor::hex_color!("#202122"); /// assert_eq!(COLOR, ecolor::Color32::from_rgb(0x20, 0x21, 0x22)); /// ``` #[macro_export] macro_rules! hex_color { ($s:literal) => {{ let array = $crate::color_hex::color_from_hex!($s); match array.as_slice() { [r, g, b] => $crate::Color32::from_rgb(*r, *g, *b), [r, g, b, a] => $crate::Color32::from_rgba_unmultiplied_const(*r, *g, *b, *a), _ => panic!("Invalid hex color length: expected 3 (RGB) or 4 (RGBA) bytes"), } }}; } #[test] fn test_from_rgb_hex() { assert_eq!( crate::Color32::from_rgb(0x20, 0x21, 0x22), hex_color!("#202122") ); assert_eq!( crate::Color32::from_rgb_additive(0x20, 0x21, 0x22), hex_color!("#202122").additive() ); } #[test] fn test_from_rgba_hex() { assert_eq!( crate::Color32::from_rgba_unmultiplied(0x20, 0x21, 0x22, 0x50), hex_color!("20212250") ); } ecolor-0.33.0/src/hex_color_runtime.rs000064400000000000000000000175731046102023000160710ustar 00000000000000//! Convert colors to and from the hex-color string format at runtime //! //! Supports the 3, 4, 6, and 8-digit formats, according to the specification in //! use std::{fmt::Display, str::FromStr}; use crate::Color32; #[repr(C)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] /// A wrapper around Color32 that converts to and from a hex-color string /// /// Implements [`Display`] and [`FromStr`] to convert to and from the hex string. pub enum HexColor { /// 3 hexadecimal digits, one for each of the r, g, b channels Hex3(Color32), /// 4 hexadecimal digits, one for each of the r, g, b, a channels Hex4(Color32), /// 6 hexadecimal digits, two for each of the r, g, b channels Hex6(Color32), /// 8 hexadecimal digits, one for each of the r, g, b, a channels Hex8(Color32), } #[derive(Clone, Debug, Eq, PartialEq)] pub enum ParseHexColorError { MissingHash, InvalidLength, InvalidInt(std::num::ParseIntError), } impl FromStr for HexColor { type Err = ParseHexColorError; fn from_str(s: &str) -> Result { s.strip_prefix('#') .ok_or(ParseHexColorError::MissingHash) .and_then(Self::from_str_without_hash) } } impl Display for HexColor { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Hex3(color) => { let [r, g, b, _] = color.to_srgba_unmultiplied().map(|u| u >> 4); f.write_fmt(format_args!("#{r:x}{g:x}{b:x}")) } Self::Hex4(color) => { let [r, g, b, a] = color.to_srgba_unmultiplied().map(|u| u >> 4); f.write_fmt(format_args!("#{r:x}{g:x}{b:x}{a:x}")) } Self::Hex6(color) => { let [r, g, b, _] = color.to_srgba_unmultiplied(); let u = u32::from_be_bytes([0, r, g, b]); f.write_fmt(format_args!("#{u:06x}")) } Self::Hex8(color) => { let [r, g, b, a] = color.to_srgba_unmultiplied(); let u = u32::from_be_bytes([r, g, b, a]); f.write_fmt(format_args!("#{u:08x}")) } } } } impl HexColor { /// Retrieves the inner [`Color32`] #[inline] pub fn color(&self) -> Color32 { match self { Self::Hex3(color) | Self::Hex4(color) | Self::Hex6(color) | Self::Hex8(color) => *color, } } /// Parses a string as a hex color without the leading `#` character /// /// # Errors /// Returns an error if the length of the string does not correspond to one of the standard /// formats (3, 4, 6, or 8), or if it contains non-hex characters. #[inline] pub fn from_str_without_hash(s: &str) -> Result { match s.len() { 3 => { let [r, gb] = u16::from_str_radix(s, 16) .map_err(ParseHexColorError::InvalidInt)? .to_be_bytes(); let [r, g, b] = [r, gb >> 4, gb & 0x0f].map(|u| (u << 4) | u); Ok(Self::Hex3(Color32::from_rgb(r, g, b))) } 4 => { let [r_g, b_a] = u16::from_str_radix(s, 16) .map_err(ParseHexColorError::InvalidInt)? .to_be_bytes(); let [r, g, b, a] = [r_g >> 4, r_g & 0x0f, b_a >> 4, b_a & 0x0f].map(|u| (u << 4) | u); Ok(Self::Hex4(Color32::from_rgba_unmultiplied(r, g, b, a))) } 6 => { let [_, r, g, b] = u32::from_str_radix(s, 16) .map_err(ParseHexColorError::InvalidInt)? .to_be_bytes(); Ok(Self::Hex6(Color32::from_rgb(r, g, b))) } 8 => { let [r, g, b, a] = u32::from_str_radix(s, 16) .map_err(ParseHexColorError::InvalidInt)? .to_be_bytes(); Ok(Self::Hex8(Color32::from_rgba_unmultiplied(r, g, b, a))) } _ => Err(ParseHexColorError::InvalidLength)?, } } } impl Color32 { /// Parses a color from a hex string. /// /// Supports the 3, 4, 6, and 8-digit formats, according to the specification in /// /// /// To parse hex colors from string literals with compile-time checking, use the macro /// [`crate::hex_color!`] instead. /// /// # Example /// ```rust /// use ecolor::Color32; /// assert_eq!(Ok(Color32::RED), Color32::from_hex("#ff0000")); /// assert_eq!(Ok(Color32::GREEN), Color32::from_hex("#00ff00ff")); /// assert_eq!(Ok(Color32::BLUE), Color32::from_hex("#00f")); /// assert_eq!(Ok(Color32::TRANSPARENT), Color32::from_hex("#0000")); /// ``` /// /// # Errors /// Returns an error if the string doesn't start with the hash `#` character, if the remaining /// length does not correspond to one of the standard formats (3, 4, 6, or 8), if it contains /// non-hex characters. pub fn from_hex(hex: &str) -> Result { HexColor::from_str(hex).map(|h| h.color()) } /// Formats the color as a hex string. /// /// # Example /// ```rust /// use ecolor::Color32; /// assert_eq!(Color32::RED.to_hex(), "#ff0000ff"); /// assert_eq!(Color32::GREEN.to_hex(), "#00ff00ff"); /// assert_eq!(Color32::BLUE.to_hex(), "#0000ffff"); /// assert_eq!(Color32::TRANSPARENT.to_hex(), "#00000000"); /// ``` /// /// Uses the 8-digit format described in , /// as that is the only format that is lossless. /// For other formats, see [`HexColor`]. #[inline] pub fn to_hex(&self) -> String { HexColor::Hex8(*self).to_string() } } #[cfg(test)] mod tests { use super::*; #[test] fn hex_string_formats() { use Color32 as C; use HexColor as H; let cases = [ (H::Hex3(C::RED), "#f00"), (H::Hex4(C::RED), "#f00f"), (H::Hex6(C::RED), "#ff0000"), (H::Hex8(C::RED), "#ff0000ff"), (H::Hex3(C::GREEN), "#0f0"), (H::Hex4(C::GREEN), "#0f0f"), (H::Hex6(C::GREEN), "#00ff00"), (H::Hex8(C::GREEN), "#00ff00ff"), (H::Hex3(C::BLUE), "#00f"), (H::Hex4(C::BLUE), "#00ff"), (H::Hex6(C::BLUE), "#0000ff"), (H::Hex8(C::BLUE), "#0000ffff"), (H::Hex3(C::WHITE), "#fff"), (H::Hex4(C::WHITE), "#ffff"), (H::Hex6(C::WHITE), "#ffffff"), (H::Hex8(C::WHITE), "#ffffffff"), (H::Hex3(C::BLACK), "#000"), (H::Hex4(C::BLACK), "#000f"), (H::Hex6(C::BLACK), "#000000"), (H::Hex8(C::BLACK), "#000000ff"), (H::Hex4(C::TRANSPARENT), "#0000"), (H::Hex8(C::TRANSPARENT), "#00000000"), ]; for (color, string) in cases { assert_eq!(color.to_string(), string, "{color:?} <=> {string}"); assert_eq!( H::from_str(string).unwrap(), color, "{color:?} <=> {string}" ); } } #[test] fn hex_string_round_trip() { let cases = [ [0, 20, 30, 0], [10, 0, 30, 40], [10, 100, 200, 0], [10, 100, 200, 100], [10, 100, 200, 200], [10, 100, 200, 255], [10, 100, 200, 40], [10, 20, 0, 255], [10, 20, 30, 0], [10, 20, 30, 255], [10, 20, 30, 40], ]; for [r, g, b, a] in cases { let color = Color32::from_rgba_unmultiplied(r, g, b, a); assert_eq!(Color32::from_hex(color.to_hex().as_str()), Ok(color)); } } } ecolor-0.33.0/src/hsva.rs000064400000000000000000000144331046102023000132750ustar 00000000000000use crate::{ Color32, Rgba, gamma_u8_from_linear_f32, linear_f32_from_gamma_u8, linear_u8_from_linear_f32, }; /// Hue, saturation, value, alpha. All in the range [0, 1]. /// No premultiplied alpha. #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[derive(Clone, Copy, Debug, Default, PartialEq)] pub struct Hsva { /// hue 0-1 pub h: f32, /// saturation 0-1 pub s: f32, /// value 0-1 pub v: f32, /// alpha 0-1. A negative value signifies an additive color (and alpha is ignored). pub a: f32, } impl Hsva { #[inline] pub fn new(h: f32, s: f32, v: f32, a: f32) -> Self { Self { h, s, v, a } } /// From `sRGBA` with premultiplied alpha #[inline] pub fn from_srgba_premultiplied([r, g, b, a]: [u8; 4]) -> Self { Self::from(Color32::from_rgba_premultiplied(r, g, b, a)) } /// From `sRGBA` without premultiplied alpha #[inline] pub fn from_srgba_unmultiplied([r, g, b, a]: [u8; 4]) -> Self { Self::from(Color32::from_rgba_unmultiplied(r, g, b, a)) } /// From linear RGBA with premultiplied alpha #[inline] pub fn from_rgba_premultiplied(r: f32, g: f32, b: f32, a: f32) -> Self { #![allow(clippy::many_single_char_names)] if a <= 0.0 { if r == 0.0 && b == 0.0 && a == 0.0 { Self::default() } else { Self::from_additive_rgb([r, g, b]) } } else { let (h, s, v) = hsv_from_rgb([r / a, g / a, b / a]); Self { h, s, v, a } } } /// From linear RGBA without premultiplied alpha #[inline] pub fn from_rgba_unmultiplied(r: f32, g: f32, b: f32, a: f32) -> Self { #![allow(clippy::many_single_char_names)] let (h, s, v) = hsv_from_rgb([r, g, b]); Self { h, s, v, a } } #[inline] pub fn from_additive_rgb(rgb: [f32; 3]) -> Self { let (h, s, v) = hsv_from_rgb(rgb); Self { h, s, v, a: -0.5, // anything negative is treated as additive } } #[inline] pub fn from_additive_srgb([r, g, b]: [u8; 3]) -> Self { Self::from_additive_rgb([ linear_f32_from_gamma_u8(r), linear_f32_from_gamma_u8(g), linear_f32_from_gamma_u8(b), ]) } #[inline] pub fn from_rgb(rgb: [f32; 3]) -> Self { let (h, s, v) = hsv_from_rgb(rgb); Self { h, s, v, a: 1.0 } } #[inline] pub fn from_srgb([r, g, b]: [u8; 3]) -> Self { Self::from_rgb([ linear_f32_from_gamma_u8(r), linear_f32_from_gamma_u8(g), linear_f32_from_gamma_u8(b), ]) } // ------------------------------------------------------------------------ #[inline] pub fn to_opaque(self) -> Self { Self { a: 1.0, ..self } } #[inline] pub fn to_rgb(&self) -> [f32; 3] { rgb_from_hsv((self.h, self.s, self.v)) } #[inline] pub fn to_srgb(&self) -> [u8; 3] { let [r, g, b] = self.to_rgb(); [ gamma_u8_from_linear_f32(r), gamma_u8_from_linear_f32(g), gamma_u8_from_linear_f32(b), ] } #[inline] pub fn to_rgba_premultiplied(&self) -> [f32; 4] { let [r, g, b, a] = self.to_rgba_unmultiplied(); let additive = a < 0.0; if additive { [r, g, b, 0.0] } else { [a * r, a * g, a * b, a] } } /// To linear space rgba in 0-1 range. /// /// Represents additive colors using a negative alpha. #[inline] pub fn to_rgba_unmultiplied(&self) -> [f32; 4] { let Self { h, s, v, a } = *self; let [r, g, b] = rgb_from_hsv((h, s, v)); [r, g, b, a] } #[inline] pub fn to_srgba_premultiplied(&self) -> [u8; 4] { Color32::from(*self).to_array() } /// To gamma-space 0-255. #[inline] pub fn to_srgba_unmultiplied(&self) -> [u8; 4] { let [r, g, b, a] = self.to_rgba_unmultiplied(); [ gamma_u8_from_linear_f32(r), gamma_u8_from_linear_f32(g), gamma_u8_from_linear_f32(b), linear_u8_from_linear_f32(a.abs()), ] } } impl From for Rgba { #[inline] fn from(hsva: Hsva) -> Self { Self(hsva.to_rgba_premultiplied()) } } impl From for Hsva { #[inline] fn from(rgba: Rgba) -> Self { Self::from_rgba_premultiplied(rgba.0[0], rgba.0[1], rgba.0[2], rgba.0[3]) } } impl From for Color32 { #[inline] fn from(hsva: Hsva) -> Self { Self::from(Rgba::from(hsva)) } } impl From for Hsva { #[inline] fn from(srgba: Color32) -> Self { Self::from(Rgba::from(srgba)) } } /// All ranges in 0-1, rgb is linear. #[inline] pub fn hsv_from_rgb([r, g, b]: [f32; 3]) -> (f32, f32, f32) { #![allow(clippy::many_single_char_names)] let min = r.min(g.min(b)); let max = r.max(g.max(b)); // value let range = max - min; let h = if max == min { 0.0 // hue is undefined } else if max == r { (g - b) / (6.0 * range) } else if max == g { (b - r) / (6.0 * range) + 1.0 / 3.0 } else { // max == b (r - g) / (6.0 * range) + 2.0 / 3.0 }; let h = (h + 1.0).fract(); // wrap let s = if max == 0.0 { 0.0 } else { 1.0 - min / max }; (h, s, max) } /// All ranges in 0-1, rgb is linear. #[inline] pub fn rgb_from_hsv((h, s, v): (f32, f32, f32)) -> [f32; 3] { #![allow(clippy::many_single_char_names)] let h = (h.fract() + 1.0).fract(); // wrap let s = s.clamp(0.0, 1.0); let f = h * 6.0 - (h * 6.0).floor(); let p = v * (1.0 - s); let q = v * (1.0 - f * s); let t = v * (1.0 - (1.0 - f) * s); match (h * 6.0).floor() as i32 % 6 { 0 => [v, t, p], 1 => [q, v, p], 2 => [p, v, t], 3 => [p, q, v], 4 => [t, p, v], 5 => [v, p, q], _ => unreachable!(), } } #[test] #[ignore = "too expensive"] fn test_hsv_roundtrip() { for r in 0..=255 { for g in 0..=255 { for b in 0..=255 { let srgba = Color32::from_rgb(r, g, b); let hsva = Hsva::from(srgba); assert_eq!(srgba, Color32::from(hsva)); } } } } ecolor-0.33.0/src/hsva_gamma.rs000064400000000000000000000026201046102023000144320ustar 00000000000000use crate::{Color32, Hsva, Rgba, gamma_from_linear, linear_from_gamma}; /// Like Hsva but with the `v` value (brightness) being gamma corrected /// so that it is somewhat perceptually even. #[derive(Clone, Copy, Debug, Default, PartialEq)] pub struct HsvaGamma { /// hue 0-1 pub h: f32, /// saturation 0-1 pub s: f32, /// value 0-1, in gamma-space (~perceptually even) pub v: f32, /// alpha 0-1. A negative value signifies an additive color (and alpha is ignored). pub a: f32, } impl From for Rgba { fn from(hsvag: HsvaGamma) -> Self { Hsva::from(hsvag).into() } } impl From for Color32 { fn from(hsvag: HsvaGamma) -> Self { Rgba::from(hsvag).into() } } impl From for Hsva { fn from(hsvag: HsvaGamma) -> Self { let HsvaGamma { h, s, v, a } = hsvag; Self { h, s, v: linear_from_gamma(v), a, } } } impl From for HsvaGamma { fn from(rgba: Rgba) -> Self { Hsva::from(rgba).into() } } impl From for HsvaGamma { fn from(srgba: Color32) -> Self { Hsva::from(srgba).into() } } impl From for HsvaGamma { fn from(hsva: Hsva) -> Self { let Hsva { h, s, v, a } = hsva; Self { h, s, v: gamma_from_linear(v), a, } } } ecolor-0.33.0/src/lib.rs000064400000000000000000000125471046102023000131060ustar 00000000000000//! Color conversions and types. //! //! This crate is built for the wants and needs of [`egui`](https://github.com/emilk/egui/). //! //! If you want an actual _good_ color crate, use [`color`](https://crates.io/crates/color) instead. //! //! If you want a compact color representation, use [`Color32`]. //! If you want to manipulate RGBA colors in linear space use [`Rgba`]. //! If you want to manipulate colors in a way closer to how humans think about colors, use [`HsvaGamma`]. //! //! ## Conventions //! The word "gamma" or "srgb" is used to refer to values in the non-linear space defined by //! [the sRGB transfer function](https://en.wikipedia.org/wiki/SRGB). //! We use `u8` for anything in the "gamma" space. //! //! We use `f32` in 0-1 range for anything in the linear space. //! //! ## Feature flags #![cfg_attr(feature = "document-features", doc = document_features::document_features!())] //! #![allow(clippy::wrong_self_convention)] #[cfg(feature = "cint")] mod cint_impl; mod color32; pub use color32::*; mod hsva_gamma; pub use hsva_gamma::*; mod hsva; pub use hsva::*; #[cfg(feature = "color-hex")] mod hex_color_macro; #[cfg(feature = "color-hex")] #[doc(hidden)] pub use color_hex; mod rgba; pub use rgba::*; mod hex_color_runtime; pub use hex_color_runtime::*; // ---------------------------------------------------------------------------- // Color conversion: impl From for Rgba { fn from(srgba: Color32) -> Self { let [r, g, b, a] = srgba.to_array(); if a == 0 { // Additive, or completely transparent Self([ linear_f32_from_gamma_u8(r), linear_f32_from_gamma_u8(g), linear_f32_from_gamma_u8(b), 0.0, ]) } else { let a = linear_f32_from_linear_u8(a); Self([ linear_from_gamma(r as f32 / (255.0 * a)) * a, linear_from_gamma(g as f32 / (255.0 * a)) * a, linear_from_gamma(b as f32 / (255.0 * a)) * a, a, ]) } } } impl From for Color32 { fn from(rgba: Rgba) -> Self { let [r, g, b, a] = rgba.to_array(); if a == 0.0 { // Additive, or completely transparent Self([ gamma_u8_from_linear_f32(r), gamma_u8_from_linear_f32(g), gamma_u8_from_linear_f32(b), 0, ]) } else { Self([ fast_round(gamma_u8_from_linear_f32(r / a) as f32 * a), fast_round(gamma_u8_from_linear_f32(g / a) as f32 * a), fast_round(gamma_u8_from_linear_f32(b / a) as f32 * a), linear_u8_from_linear_f32(a), ]) } } } /// gamma [0, 255] -> linear [0, 1]. pub fn linear_f32_from_gamma_u8(s: u8) -> f32 { if s <= 10 { s as f32 / 3294.6 } else { ((s as f32 + 14.025) / 269.025).powf(2.4) } } /// linear [0, 255] -> linear [0, 1]. /// Useful for alpha-channel. #[inline(always)] pub const fn linear_f32_from_linear_u8(a: u8) -> f32 { a as f32 / 255.0 } /// linear [0, 1] -> gamma [0, 255] (clamped). /// Values outside this range will be clamped to the range. pub fn gamma_u8_from_linear_f32(l: f32) -> u8 { if l <= 0.0 { 0 } else if l <= 0.0031308 { fast_round(3294.6 * l) } else if l <= 1.0 { fast_round(269.025 * l.powf(1.0 / 2.4) - 14.025) } else { 255 } } /// linear [0, 1] -> linear [0, 255] (clamped). /// Useful for alpha-channel. #[inline(always)] pub fn linear_u8_from_linear_f32(a: f32) -> u8 { fast_round(a * 255.0) } const fn fast_round(r: f32) -> u8 { (r + 0.5) as _ // rust does a saturating cast since 1.45 } #[test] pub fn test_srgba_conversion() { for b in 0..=255 { let l = linear_f32_from_gamma_u8(b); assert!(0.0 <= l && l <= 1.0); assert_eq!(gamma_u8_from_linear_f32(l), b); } } /// gamma [0, 1] -> linear [0, 1] (not clamped). /// Works for numbers outside this range (e.g. negative numbers). pub fn linear_from_gamma(gamma: f32) -> f32 { if gamma < 0.0 { -linear_from_gamma(-gamma) } else if gamma <= 0.04045 { gamma / 12.92 } else { ((gamma + 0.055) / 1.055).powf(2.4) } } /// linear [0, 1] -> gamma [0, 1] (not clamped). /// Works for numbers outside this range (e.g. negative numbers). pub fn gamma_from_linear(linear: f32) -> f32 { if linear < 0.0 { -gamma_from_linear(-linear) } else if linear <= 0.0031308 { 12.92 * linear } else { 1.055 * linear.powf(1.0 / 2.4) - 0.055 } } // ---------------------------------------------------------------------------- /// Cheap and ugly. /// Made for graying out disabled `Ui`s. pub fn tint_color_towards(color: Color32, target: Color32) -> Color32 { let [mut r, mut g, mut b, mut a] = color.to_array(); if a == 0 { r /= 2; g /= 2; b /= 2; } else if a < 170 { // Cheapish and looks ok. // Works for e.g. grid stripes. let div = (2 * 255 / a as i32) as u8; r = r / 2 + target.r() / div; g = g / 2 + target.g() / div; b = b / 2 + target.b() / div; a /= 2; } else { r = r / 2 + target.r() / 2; g = g / 2 + target.g() / 2; b = b / 2 + target.b() / 2; } Color32::from_rgba_premultiplied(r, g, b, a) } ecolor-0.33.0/src/rgba.rs000064400000000000000000000211231046102023000132410ustar 00000000000000use crate::Color32; /// 0-1 linear space `RGBA` color with premultiplied alpha. /// /// See [`crate::Color32`] for explanation of what "premultiplied alpha" means. #[repr(C)] #[derive(Clone, Copy, Debug, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))] pub struct Rgba(pub(crate) [f32; 4]); impl std::ops::Index for Rgba { type Output = f32; #[inline] fn index(&self, index: usize) -> &f32 { &self.0[index] } } impl std::ops::IndexMut for Rgba { #[inline] fn index_mut(&mut self, index: usize) -> &mut f32 { &mut self.0[index] } } /// Deterministically hash an `f32`, treating all NANs as equal, and ignoring the sign of zero. #[inline] pub(crate) fn f32_hash(state: &mut H, f: f32) { if f == 0.0 { state.write_u8(0); } else if f.is_nan() { state.write_u8(1); } else { use std::hash::Hash as _; f.to_bits().hash(state); } } impl std::hash::Hash for Rgba { #[inline] fn hash(&self, state: &mut H) { crate::f32_hash(state, self.0[0]); crate::f32_hash(state, self.0[1]); crate::f32_hash(state, self.0[2]); crate::f32_hash(state, self.0[3]); } } impl Rgba { pub const TRANSPARENT: Self = Self::from_rgba_premultiplied(0.0, 0.0, 0.0, 0.0); pub const BLACK: Self = Self::from_rgb(0.0, 0.0, 0.0); pub const WHITE: Self = Self::from_rgb(1.0, 1.0, 1.0); pub const RED: Self = Self::from_rgb(1.0, 0.0, 0.0); pub const GREEN: Self = Self::from_rgb(0.0, 1.0, 0.0); pub const BLUE: Self = Self::from_rgb(0.0, 0.0, 1.0); #[inline] pub const fn from_rgba_premultiplied(r: f32, g: f32, b: f32, a: f32) -> Self { Self([r, g, b, a]) } #[inline] pub fn from_rgba_unmultiplied(r: f32, g: f32, b: f32, a: f32) -> Self { Self([r * a, g * a, b * a, a]) } #[inline] pub fn from_srgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self { Self::from(Color32::from_rgba_premultiplied(r, g, b, a)) } #[inline] pub fn from_srgba_unmultiplied(r: u8, g: u8, b: u8, a: u8) -> Self { Self::from(Color32::from_rgba_unmultiplied(r, g, b, a)) } #[inline] pub const fn from_rgb(r: f32, g: f32, b: f32) -> Self { Self([r, g, b, 1.0]) } #[doc(alias = "from_grey")] #[inline] pub const fn from_gray(l: f32) -> Self { Self([l, l, l, 1.0]) } #[inline] pub fn from_luminance_alpha(l: f32, a: f32) -> Self { debug_assert!( 0.0 <= l && l <= 1.0, "l should be in the range [0, 1], but was {l}" ); debug_assert!( 0.0 <= a && a <= 1.0, "a should be in the range [0, 1], but was {a}" ); Self([l * a, l * a, l * a, a]) } /// Transparent black #[inline] pub fn from_black_alpha(a: f32) -> Self { debug_assert!( 0.0 <= a && a <= 1.0, "a should be in the range [0, 1], but was {a}" ); Self([0.0, 0.0, 0.0, a]) } /// Transparent white #[inline] pub fn from_white_alpha(a: f32) -> Self { debug_assert!(0.0 <= a && a <= 1.0, "a: {a}"); Self([a, a, a, a]) } /// Return an additive version of this color (alpha = 0) #[inline] pub fn additive(self) -> Self { let [r, g, b, _] = self.0; Self([r, g, b, 0.0]) } /// Is the alpha=0 ? #[inline] pub fn is_additive(self) -> bool { self.a() == 0.0 } /// Multiply with e.g. 0.5 to make us half transparent #[inline] pub fn multiply(self, alpha: f32) -> Self { Self([ alpha * self[0], alpha * self[1], alpha * self[2], alpha * self[3], ]) } #[inline] pub fn r(&self) -> f32 { self.0[0] } #[inline] pub fn g(&self) -> f32 { self.0[1] } #[inline] pub fn b(&self) -> f32 { self.0[2] } #[inline] pub fn a(&self) -> f32 { self.0[3] } /// How perceptually intense (bright) is the color? #[inline] pub fn intensity(&self) -> f32 { 0.3 * self.r() + 0.59 * self.g() + 0.11 * self.b() } /// Returns an opaque version of self #[inline] pub fn to_opaque(&self) -> Self { if self.a() == 0.0 { // Additive or fully transparent black. Self::from_rgb(self.r(), self.g(), self.b()) } else { // un-multiply alpha: Self::from_rgb( self.r() / self.a(), self.g() / self.a(), self.b() / self.a(), ) } } /// Premultiplied RGBA #[inline] pub fn to_array(&self) -> [f32; 4] { [self.r(), self.g(), self.b(), self.a()] } /// Premultiplied RGBA #[inline] pub fn to_tuple(&self) -> (f32, f32, f32, f32) { (self.r(), self.g(), self.b(), self.a()) } /// unmultiply the alpha #[inline] pub fn to_rgba_unmultiplied(&self) -> [f32; 4] { let a = self.a(); if a == 0.0 { // Additive, let's assume we are black self.0 } else { [self.r() / a, self.g() / a, self.b() / a, a] } } /// unmultiply the alpha #[inline] pub fn to_srgba_unmultiplied(&self) -> [u8; 4] { crate::Color32::from(*self).to_srgba_unmultiplied() } /// Blend two colors in linear space, so that `self` is behind the argument. pub fn blend(self, on_top: Self) -> Self { self.multiply(1.0 - on_top.a()) + on_top } } impl std::ops::Add for Rgba { type Output = Self; #[inline] fn add(self, rhs: Self) -> Self { Self([ self[0] + rhs[0], self[1] + rhs[1], self[2] + rhs[2], self[3] + rhs[3], ]) } } impl std::ops::Mul for Rgba { type Output = Self; #[inline] fn mul(self, other: Self) -> Self { Self([ self[0] * other[0], self[1] * other[1], self[2] * other[2], self[3] * other[3], ]) } } impl std::ops::Mul for Rgba { type Output = Self; #[inline] fn mul(self, factor: f32) -> Self { Self([ self[0] * factor, self[1] * factor, self[2] * factor, self[3] * factor, ]) } } impl std::ops::Mul for f32 { type Output = Rgba; #[inline] fn mul(self, rgba: Rgba) -> Rgba { Rgba([ self * rgba[0], self * rgba[1], self * rgba[2], self * rgba[3], ]) } } #[cfg(test)] mod test { use super::*; fn test_rgba() -> impl Iterator { [ [0, 0, 0, 0], [0, 0, 0, 255], [10, 0, 30, 0], [10, 0, 30, 40], [10, 100, 200, 0], [10, 100, 200, 100], [10, 100, 200, 200], [10, 100, 200, 255], [10, 100, 200, 40], [10, 20, 0, 0], [10, 20, 0, 255], [10, 20, 30, 255], [10, 20, 30, 40], [255, 255, 255, 0], [255, 255, 255, 255], ] .into_iter() } #[test] fn test_rgba_blend() { let opaque = Rgba::from_rgb(0.4, 0.5, 0.6); let transparent = Rgba::from_rgb(1.0, 0.5, 0.0).multiply(0.3); assert_eq!( transparent.blend(opaque), opaque, "Opaque on top of transparent" ); assert_eq!( opaque.blend(transparent), Rgba::from_rgb( 0.7 * 0.4 + 0.3 * 1.0, 0.7 * 0.5 + 0.3 * 0.5, 0.7 * 0.6 + 0.3 * 0.0 ), "Transparent on top of opaque" ); } #[test] fn test_rgba_roundtrip() { for in_rgba in test_rgba() { let [r, g, b, a] = in_rgba; if a == 0 { continue; } let rgba = Rgba::from_srgba_unmultiplied(r, g, b, a); let out_rgba = rgba.to_srgba_unmultiplied(); if a == 255 { assert_eq!(in_rgba, out_rgba); } else { // There will be small rounding errors whenever the alpha is not 0 or 255, // because we multiply and then unmultiply the alpha. for (&a, &b) in in_rgba.iter().zip(out_rgba.iter()) { assert!(a.abs_diff(b) <= 3, "{in_rgba:?} != {out_rgba:?}"); } } } } }