colorgrad-0.8.0/.cargo_vcs_info.json0000644000000001360000000000100130150ustar { "git": { "sha1": "713ee07392597d281fbd28b74777c3e5a64b92c3" }, "path_in_vcs": "" }colorgrad-0.8.0/CHANGELOG.md000064400000000000000000000031221046102023000134140ustar 00000000000000# Changelog ## [Unreleased](https://github.com/mazznoer/colorgrad-rs/compare/v0.8.0...HEAD) ## [0.8.0](https://github.com/mazznoer/colorgrad-rs/compare/v0.7.2...v0.8.0) ## Added - Support for `no_std`. ## Changed - Removed `preset` and `named-colors` from default features. ## [0.7.2](https://github.com/mazznoer/colorgrad-rs/compare/v0.7.1...v0.7.2) ### Added - `Gradient::colors_iter()` - `GradientColors` ## [0.7.1](https://github.com/mazznoer/colorgrad-rs/compare/v0.7.0...v0.7.1) ### Added - New methods for `Gradient`: `inverse()` and `boxed()`. - impl `Gradient` for `Box`. - impl `Clone` for `Box`. - `GradientBuilder` new method `reset()`. - `InverseGradient`. ### Fixed - `CubehelixGradient` ## [0.7.0](https://github.com/mazznoer/colorgrad-rs/compare/v0.6.2...v0.7.0) ### Added - `BlendMode::Lab`, optional feature, can be enabled using `features = ["lab"]` in Cargo.toml - `GradientBuilder` new method `css()` for parsing css gradient format ### Changed - `f64` -> `f32`. - `GimpGradient` is now a optional feature, can be enabled using `features = ["ggr"]` in Cargo.toml - Preset gradients move to submodule `preset`. - In previous version `Gradient` is a struct holding `LinearGradient`, `BasisGradient`, etc in a `Box`. Now `Gradient` is a trait. `LinearGradient`, `BasisGradient`, etc is now exposed directy, and they are implementing `Gradient` trait. - `CustomGradient` renamed to `GradientBuilder` - `CustomGradientError` renamed to `GradientBuilderError` ### Removed - `BlendMode::Hsv` ### Fixed - Error parsing GIMP gradient with UTF-8 BOM. colorgrad-0.8.0/Cargo.lock0000644000000463310000000000100107770ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "adler2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] name = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bumpalo" version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytemuck" version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" [[package]] name = "byteorder-lite" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "ciborium" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", "serde", ] [[package]] name = "ciborium-io" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", ] [[package]] name = "clap" version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" dependencies = [ "anstyle", "clap_lex", ] [[package]] name = "clap_lex" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "colorgrad" version = "0.8.0" dependencies = [ "criterion", "csscolorparser", "image", "libm", ] [[package]] name = "crc32fast" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] [[package]] name = "criterion" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" dependencies = [ "anes", "cast", "ciborium", "clap", "criterion-plot", "is-terminal", "itertools", "num-traits", "once_cell", "oorandom", "plotters", "rayon", "regex", "serde", "serde_derive", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", "itertools", ] [[package]] name = "crossbeam-deque" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "csscolorparser" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b9e6b904cb9ee4cb0bb93ba2b1bf3dcd2d3fc58c25557934a4b56cbd2ad9f88" dependencies = [ "num-traits", "phf", "uncased", ] [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fdeflate" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" dependencies = [ "simd-adler32", ] [[package]] name = "flate2" version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "half" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", "zerocopy", ] [[package]] name = "hermit-abi" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "image" version = "0.25.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "529feb3e6769d234375c4cf1ee2ce713682b8e76538cb13f9fc23e1400a591e7" dependencies = [ "bytemuck", "byteorder-lite", "moxcms", "num-traits", "png", ] [[package]] name = "is-terminal" version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", "windows-sys", ] [[package]] name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "libc" version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libm" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "miniz_oxide" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", "simd-adler32", ] [[package]] name = "moxcms" version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fbdd3d7436f8b5e892b8b7ea114271ff0fa00bc5acae845d53b07d498616ef6" dependencies = [ "num-traits", "pxfm", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "oorandom" version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "phf" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ "phf_macros", "phf_shared", "serde", ] [[package]] name = "phf_generator" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ "fastrand", "phf_shared", ] [[package]] name = "phf_macros" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", "syn", "uncased", ] [[package]] name = "phf_shared" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" dependencies = [ "siphasher", "uncased", ] [[package]] name = "plotters" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", "plotters-svg", "wasm-bindgen", "web-sys", ] [[package]] name = "plotters-backend" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] [[package]] name = "png" version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" dependencies = [ "bitflags", "crc32fast", "fdeflate", "flate2", "miniz_oxide", ] [[package]] name = "proc-macro2" version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "pxfm" version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3cbdf373972bf78df4d3b518d07003938e2c7d1fb5891e55f9cb6df57009d84" dependencies = [ "num-traits", ] [[package]] name = "quote" version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] [[package]] name = "rayon" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "regex" version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[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 = "serde_json" version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", "serde_core", ] [[package]] name = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "siphasher" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "syn" version = "2.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tinytemplate" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ "serde", "serde_json", ] [[package]] name = "uncased" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" dependencies = [ "version_check", ] [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasm-bindgen" version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ "bumpalo", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "winapi-util" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ "windows-sys", ] [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] [[package]] name = "zerocopy" version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", "syn", ] colorgrad-0.8.0/Cargo.toml0000644000000055610000000000100110220ustar # 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 = "2018" name = "colorgrad" version = "0.8.0" authors = ["Nor Khasyatillah "] build = false exclude = [ ".github/*", ".gitignore", "docs/*", "example_output/*", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Color scales library for data visualization, charts, games, generative art and others." documentation = "https://docs.rs/colorgrad/" readme = "README.md" keywords = [ "color", "colormap", "color-scales", "visualization", "gradient", ] categories = [ "graphics", "visualization", "no-std", ] license = "MIT OR Apache-2.0" repository = "https://github.com/mazznoer/colorgrad-rs" [package.metadata.docs.rs] features = [ "named-colors", "preset", "ggr", "lab", "std", ] [features] default = ["std"] ggr = ["std"] lab = [] named-colors = ["csscolorparser/named-colors"] preset = [] std = ["csscolorparser/std"] [lib] name = "colorgrad" path = "src/lib.rs" [[example]] name = "basic" path = "examples/basic.rs" required-features = ["preset"] [[example]] name = "gradients" path = "examples/gradients/main.rs" required-features = [ "lab", "preset", "ggr", "named-colors", ] [[test]] name = "builder" path = "tests/builder.rs" required-features = ["named-colors"] [[test]] name = "g_basis" path = "tests/g_basis.rs" [[test]] name = "g_catmullrom" path = "tests/g_catmullrom.rs" [[test]] name = "g_gimp" path = "tests/g_gimp.rs" [[test]] name = "g_inverse" path = "tests/g_inverse.rs" [[test]] name = "g_linear" path = "tests/g_linear.rs" [[test]] name = "g_sharp" path = "tests/g_sharp.rs" [[test]] name = "gradient" path = "tests/gradient.rs" [[test]] name = "preset" path = "tests/preset.rs" required-features = ["preset"] [[test]] name = "utils" path = "tests/utils.rs" [[bench]] name = "custom_gradient" path = "benches/custom_gradient.rs" harness = false required-features = ["lab"] [[bench]] name = "gimp" path = "benches/gimp.rs" harness = false required-features = ["ggr"] [[bench]] name = "preset" path = "benches/preset.rs" harness = false required-features = ["preset"] [dependencies.csscolorparser] version = "0.8.0" default-features = false [dependencies.libm] version = "0.2.15" [dev-dependencies.criterion] version = "0.5.1" features = ["html_reports"] [dev-dependencies.image] version = "0.25.2" features = ["png"] default-features = false colorgrad-0.8.0/Cargo.toml.orig000064400000000000000000000030261046102023000144750ustar 00000000000000[package] name = "colorgrad" version = "0.8.0" authors = ["Nor Khasyatillah "] edition = "2018" description = "Color scales library for data visualization, charts, games, generative art and others." readme = "README.md" repository = "https://github.com/mazznoer/colorgrad-rs" documentation = "https://docs.rs/colorgrad/" license = "MIT OR Apache-2.0" keywords = ["color", "colormap", "color-scales", "visualization", "gradient"] categories = ["graphics", "visualization", "no-std"] exclude = [ ".github/*", ".gitignore", "docs/*", "example_output/*", ] [package.metadata.docs.rs] features = ["named-colors", "preset", "ggr", "lab", "std"] [dependencies] csscolorparser = { version = "0.8.0", default-features = false } libm = "0.2.15" [features] default = ["std"] named-colors = ["csscolorparser/named-colors"] lab = [] preset = [] ggr = ["std"] std = ["csscolorparser/std"] [dev-dependencies] criterion = { version = "0.5.1", features = ["html_reports"] } image = { version = "0.25.2", default-features = false, features = ["png"] } [[example]] name = "basic" required-features = ["preset"] [[example]] name = "gradients" required-features = ["lab", "preset", "ggr", "named-colors"] [[test]] name = "preset" required-features = ["preset"] [[test]] name = "builder" required-features = ["named-colors"] [[bench]] name = "custom_gradient" harness = false required-features = ["lab"] [[bench]] name = "preset" harness = false required-features = ["preset"] [[bench]] name = "gimp" harness = false required-features = ["ggr"] colorgrad-0.8.0/LICENSE-APACHE000064400000000000000000000274221046102023000135400ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2020 Nor Khasyatillah Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --- Apache-Style Software License for ColorBrewer software and ColorBrewer Color Schemes Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State University. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. colorgrad-0.8.0/LICENSE-MIT000064400000000000000000000020611046102023000132400ustar 00000000000000MIT License Copyright (c) 2020 Nor Khasyatillah Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. colorgrad-0.8.0/Makefile000064400000000000000000000005701046102023000132470ustar 00000000000000SHELL := /bin/bash .PHONY: all check test all: check test check: cargo build --no-default-features && \ cargo clippy --no-default-features -- -D warnings && \ cargo build --all-features && \ cargo clippy --all-features -- -D warnings && \ cargo build --examples && \ cargo fmt --all -- --check test: cargo test --no-default-features && \ cargo test --all-features colorgrad-0.8.0/PRESET.md000064400000000000000000000060431046102023000131340ustar 00000000000000# Preset Gradients All preset gradients are in the domain [0..1]. Uniform B-splines is used to interpolate the colors. ![img](docs/images/rgb-plot.png) ```rust use colorgrad::Gradient; let g = colorgrad::preset::viridis(); assert_eq!(g.domain(), (0.0, 1.0)); println!("{}", g.at(0.27).to_css_hex()); for color in g.colors_iter(35) { println!("{:?}", color.to_rgba8()); } ``` ## Diverging `colorgrad::preset::br_bg()` ![img](docs/images/preset/br_bg.png) `colorgrad::preset::pr_gn()` ![img](docs/images/preset/pr_gn.png) `colorgrad::preset::pi_yg()` ![img](docs/images/preset/pi_yg.png) `colorgrad::preset::pu_or()` ![img](docs/images/preset/pu_or.png) `colorgrad::preset::rd_bu()` ![img](docs/images/preset/rd_bu.png) `colorgrad::preset::rd_gy()` ![img](docs/images/preset/rd_gy.png) `colorgrad::preset::rd_yl_bu()` ![img](docs/images/preset/rd_yl_bu.png) `colorgrad::preset::rd_yl_gn()` ![img](docs/images/preset/rd_yl_gn.png) `colorgrad::preset::spectral()` ![img](docs/images/preset/spectral.png) ## Sequential (Single Hue) `colorgrad::preset::blues()` ![img](docs/images/preset/blues.png) `colorgrad::preset::greens()` ![img](docs/images/preset/greens.png) `colorgrad::preset::greys()` ![img](docs/images/preset/greys.png) `colorgrad::preset::oranges()` ![img](docs/images/preset/oranges.png) `colorgrad::preset::purples()` ![img](docs/images/preset/purples.png) `colorgrad::preset::reds()` ![img](docs/images/preset/reds.png) ## Sequential (Multi-Hue) `colorgrad::preset::turbo()` ![img](docs/images/preset/turbo.png) `colorgrad::preset::viridis()` ![img](docs/images/preset/viridis.png) `colorgrad::preset::inferno()` ![img](docs/images/preset/inferno.png) `colorgrad::preset::magma()` ![img](docs/images/preset/magma.png) `colorgrad::preset::plasma()` ![img](docs/images/preset/plasma.png) `colorgrad::preset::cividis()` ![img](docs/images/preset/cividis.png) `colorgrad::preset::warm()` ![img](docs/images/preset/warm.png) `colorgrad::preset::cool()` ![img](docs/images/preset/cool.png) `colorgrad::preset::cubehelix_default()` ![img](docs/images/preset/cubehelix_default.png) `colorgrad::preset::bu_gn()` ![img](docs/images/preset/bu_gn.png) `colorgrad::preset::bu_pu()` ![img](docs/images/preset/bu_pu.png) `colorgrad::preset::gn_bu()` ![img](docs/images/preset/gn_bu.png) `colorgrad::preset::or_rd()` ![img](docs/images/preset/or_rd.png) `colorgrad::preset::pu_bu_gn()` ![img](docs/images/preset/pu_bu_gn.png) `colorgrad::preset::pu_bu()` ![img](docs/images/preset/pu_bu.png) `colorgrad::preset::pu_rd()` ![img](docs/images/preset/pu_rd.png) `colorgrad::preset::rd_pu()` ![img](docs/images/preset/rd_pu.png) `colorgrad::preset::yl_gn_bu()` ![img](docs/images/preset/yl_gn_bu.png) `colorgrad::preset::yl_gn()` ![img](docs/images/preset/yl_gn.png) `colorgrad::preset::yl_or_br()` ![img](docs/images/preset/yl_or_br.png) `colorgrad::preset::yl_or_rd()` ![img](docs/images/preset/yl_or_rd.png) ## Cyclical `colorgrad::preset::rainbow()` ![img](docs/images/preset/rainbow.png) `colorgrad::preset::sinebow()` ![img](docs/images/preset/sinebow.png) colorgrad-0.8.0/README.md000064400000000000000000000237171046102023000130760ustar 00000000000000

colorgrad :crab:

Stars License crates.io Documentation Build Status Total Downloads

Rust color scales library for data visualization, charts, games, maps, generative art and others.

DocumentationChangelogDonate


## Index + [Custom Gradient](#custom-gradient) + [Preset Gradients](#preset-gradients) + [Parsing GIMP Gradient](#parsing-gimp-gradient) + [Using the Gradient](#using-the-gradient) + [Examples](#examples) + [Similar Projects](#similar-projects) + [Projects using `colorgrad`](#projects-using-colorgrad) ## Usage Add this to your `Cargo.toml` ```toml colorgrad = "0.8.0" ``` ## Custom Gradient ### Basic ```rust let g = colorgrad::GradientBuilder::new().build::()?; ``` ![img](docs/images/custom-default.png) ### Custom Colors ```rust use colorgrad::Color; let g = colorgrad::GradientBuilder::new() .colors(&[ Color::from_rgba8(0, 206, 209, 255), Color::from_rgba8(255, 105, 180, 255), Color::new(0.274, 0.5, 0.7, 1.0), Color::from_hsva(50.0, 1.0, 1.0, 1.0), Color::from_hsva(348.0, 0.9, 0.8, 1.0), ]) .build::()?; ``` ![img](docs/images/custom-colors.png) ### Using Web Color Format `.html_colors()` method accepts [named colors](https://www.w3.org/TR/css-color-4/#named-colors), hexadecimal (`#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`), `rgb()`, `rgba()`, `hsl()`, `hsla()`, `hwb()`, and `hsv()`. ```rust let g = colorgrad::GradientBuilder::new() .html_colors(&["#C41189", "#00BFFF", "#FFD700"]) .build::()?; ``` ![img](docs/images/custom-hex-colors.png) ```rust let g = colorgrad::GradientBuilder::new() .html_colors(&["gold", "hotpink", "darkturquoise"]) .build::()?; ``` ![img](docs/images/custom-named-colors.png) ```rust let g = colorgrad::GradientBuilder::new() .html_colors(&["rgb(125,110,221)", "rgb(90%,45%,97%)", "hsl(229,79%,85%)"]) .build::()?; ``` ![img](docs/images/custom-css-colors.png) ### Using CSS Gradient Format ```rust let g = colorgrad::GradientBuilder::new() .css("blue, cyan, gold, purple 70%, tomato 70%, 90%, #ff0") .build::()?; ``` ![img](docs/images/css-gradient.png) ### Domain & Color Position Default domain is [0..1]. ```rust let g = colorgrad::GradientBuilder::new() .html_colors(&["deeppink", "gold", "seagreen"]) .build::()?; assert_eq!(g.domain(), (0.0, 1.0)); ``` ![img](docs/images/domain-default.png) Set the domain to [0..100]. ```rust let g = colorgrad::GradientBuilder::new() .html_colors(&["deeppink", "gold", "seagreen"]) .domain(&[0.0, 100.0]) .build::()?; assert_eq!(g.domain(), (0.0, 100.0)); ``` ![img](docs/images/domain-100.png) Set the domain to [-1..1]. ```rust let g = colorgrad::GradientBuilder::new() .html_colors(&["deeppink", "gold", "seagreen"]) .domain(&[-1.0, 1.0]) .build::()?; assert_eq!(g.domain(), (-1.0, 1.0)); ``` ![img](docs/images/domain-neg1-1.png) Set exact position for each color. The domain is [0..1]. ```rust let g = colorgrad::GradientBuilder::new() .html_colors(&["deeppink", "gold", "seagreen"]) .domain(&[0.0, 0.7, 1.0]) .build::()?; assert_eq!(g.domain(), (0.0, 1.0)); ``` ![img](docs/images/color-position-1.png) Set exact position for each color. The domain is [15..80]. ```rust let g = colorgrad::GradientBuilder::new() .html_colors(&["deeppink", "gold", "seagreen"]) .domain(&[15.0, 30.0, 80.0]) .build::()?; assert_eq!(g.domain(), (15.0, 80.0)); ``` ![img](docs/images/color-position-2.png) ### Blending Mode ```rust let g = colorgrad::GradientBuilder::new() .html_colors(&["#FFF", "#00F"]) .mode(colorgrad::BlendMode::Rgb) .build::()?; ``` ![Blending Modes](docs/images/blend-modes-v0.7.png) ### Interpolation Mode ```rust let g = colorgrad::GradientBuilder::new() .html_colors(&["#C41189", "#00BFFF", "#FFD700"]) .build::()?; ``` ![Interpolation Modes](docs/images/interpolation-modes.png) ## Preset Gradients See [PRESET.md](PRESET.md) ## Parsing GIMP Gradient ```rust use colorgrad::{Color, GimpGradient}; use std::fs::File; use std::io::BufReader; let input = File::open("examples/Abstract_1.ggr")?; let buf = BufReader::new(input); let col = Color::default(); let grad = GimpGradient::new(buf, &col, &col)?; assert_eq!(grad.name(), "Abstract 1"); ``` ![img](docs/images/ggr_abstract_1.png) ## Using the Gradient ### Get the domain ```rust let grad = colorgrad::preset::rainbow(); assert_eq!(grad.domain(), (0.0, 1.0)); ``` ### Get single color at certain position ```rust use colorgrad::Gradient; let grad = colorgrad::preset::blues(); assert_eq!(grad.at(0.0).to_rgba8(), [247, 251, 255, 255]); assert_eq!(grad.at(0.5).to_rgba8(), [109, 174, 213, 255]); assert_eq!(grad.at(1.0).to_rgba8(), [8, 48, 107, 255]); assert_eq!(grad.at(0.3).to_rgba8(), grad.repeat_at(0.3).to_rgba8()); assert_eq!(grad.at(0.3).to_rgba8(), grad.reflect_at(0.3).to_rgba8()); assert_eq!(grad.at(0.7).to_rgba8(), grad.repeat_at(0.7).to_rgba8()); assert_eq!(grad.at(0.7).to_rgba8(), grad.reflect_at(0.7).to_rgba8()); ``` The difference of `at()`, `repeat_at()` and `reflect_at()`. ![Spread Modes](docs/images/spread-modes.png) ### Get n colors evenly spaced across gradient ```rust use colorgrad::Gradient; let grad = colorgrad::preset::rainbow(); for c in grad.colors(10) { println!("{}", c.to_css_hex()); } ``` Output: ```console #6e40aa #c83dac #ff5375 #ff8c38 #c9d33a #7cf659 #5dea8d #48b8d0 #4775de #6e40aa ``` ### Hard-Edged Gradient Convert gradient to hard-edged gradient with 11 segments and 0 smoothness. ```rust let g = colorgrad::preset::rainbow().sharp(11, 0.0); ``` ![img](docs/images/rainbow-sharp.png) This is the effect of different smoothness. ![img](docs/images/sharp-gradients.png) ## Examples ### Gradient Image ```rust use colorgrad::Gradient; fn main() -> Result<(), Box> { let width = 1300.0; let height = 70.0; // custom gradient let grad = colorgrad::GradientBuilder::new() .html_colors(&["deeppink", "gold", "seagreen"]) .build::()?; let imgbuf = image::ImageBuffer::from_fn(width as u32, height as u32, |x, _| { image::Rgba(grad.at(x as f32 / width).to_rgba8()) }); imgbuf.save("gradient.png")?; Ok(()) } ``` Example output: ![img](docs/images/example-gradient.png) ### Colored Noise ```rust use colorgrad::Gradient; use noise::NoiseFn; fn main() { let scale = 0.015; let grad = colorgrad::preset::rainbow().sharp(5, 0.15); let ns = noise::OpenSimplex::new(0); let imgbuf = image::ImageBuffer::from_fn(600, 350, |x, y| { let t = ns.get([x as f64 * scale, y as f64 * scale]); image::Rgba(grad.at(norm(t as f32, -0.5, 0.5)).to_rgba8()) }); imgbuf.save("noise.png").unwrap(); } fn norm(t: f32, a: f32, b: f32) -> f32 { (t - a) * (1.0 / (b - a)) } ``` Example output: ![img](docs/images/example-noise.png) ## Features ### Default * __std__: Using the standard library. Can be disabled using `default-features = false`. ### Optional * __named-colors__: Enables parsing from [named colors](https://www.w3.org/TR/css-color-4/#named-colors). * __preset__: Preset gradients. * __lab__: Blending colors in `Lab` colorspace. * __ggr__: Parsing GIMP gradient format. ## Similar Projects * [colorgrad](https://github.com/mazznoer/colorgrad) (Go version of this library) * [colorous](https://github.com/dtolnay/colorous) (Rust) * [chroma.js](https://gka.github.io/chroma.js/#color-scales) (Javascript) * [d3-scale-chromatic](https://github.com/d3/d3-scale-chromatic/) (Javascript) ## Projects using `colorgrad` * [binocle](https://github.com/sharkdp/binocle) - A graphical tool to visualize binary data * [bytehound](https://github.com/koute/bytehound) - A memory profiler for Linux * [cosmic-bg](https://github.com/pop-os/cosmic-bg/) - COSMIC session service which applies backgrounds to displays * [eruption](https://github.com/X3n0m0rph59/eruption) - A Linux user-mode input and LED driver for keyboards, mice and other devices * [gradient](https://github.com/mazznoer/gradient-rs) - A command line tool for playing with color gradient * [lcat](https://github.com/davidkna/lcat-rs) - `lolcat` clone * [lolcrab](https://github.com/mazznoer/lolcrab) - `lolcat` but with noise (`lcat` fork) * [rust-fractal](https://github.com/rust-fractal/rust-fractal-core) - Mandelbrot fractal visualizer * [WezTerm](https://github.com/wez/wezterm) - A GPU-accelerated cross-platform terminal emulator and multiplexer * and [many others..](https://github.com/mazznoer/colorgrad-rs/network/dependents) ## Links * [Color Blindness Simulator](https://www.color-blindness.com/coblis-color-blindness-simulator/) * [Visual System Simulator](https://github.com/UniStuttgart-VISUS/visual-system-simulator) colorgrad-0.8.0/benches/custom_gradient.rs000064400000000000000000000063201046102023000167520ustar 00000000000000use colorgrad::{ BasisGradient, BlendMode, CatmullRomGradient, Gradient, GradientBuilder, LinearGradient, }; use criterion::{black_box, criterion_group, criterion_main, Criterion}; const COLORS: [&'static str; 104] = [ "#87e575", "#e88ef2", "#7398ef", "#65c3f2", "#3e52a0", "#b659db", "#75b7ff", "#7555ba", "#fceac4", "#e8009e", "#cc7c26", "#e175f4", "#f959e7", "#31828e", "#e4bef7", "#a9fcc6", "#c122d6", "#81f9e1", "#caea81", "#47d192", "#db579d", "#ead36b", "#3c2bbc", "#9de544", "#e8e476", "#055d66", "#77c90c", "#bff49a", "#6b76db", "#3cf720", "#61bace", "#aa3405", "#a588d8", "#e2aef9", "#c0eff9", "#9b043b", "#b2ffe0", "#64e092", "#ff4cab", "#56d356", "#e185e2", "#ff72f3", "#ff4fbe", "#0a9366", "#dbc2f9", "#6cbacc", "#893009", "#13afaa", "#5208ad", "#9b1426", "#71e06d", "#c2ff0c", "#ce4244", "#ffebb5", "#169bf9", "#e58eb5", "#3c3ab2", "#2afca5", "#5946c4", "#ea7352", "#f46bbb", "#264daf", "#edaada", "#c6baf4", "#d984e8", "#61dd5f", "#1f26b7", "#f99345", "#b2d624", "#f911e2", "#bf882a", "#81f48b", "#a3ffba", "#13c139", "#dd7752", "#db755c", "#fcbdf2", "#f455b2", "#7414e2", "#074575", "#7cffef", "#dd778a", "#db55cb", "#7aa7cc", "#fcbfd2", "#b7f799", "#a65bc6", "#f242ff", "#f9c0b3", "#9890db", "#d01be8", "#20870e", "#f4426b", "#def260", "#521efc", "#ffbcc6", "#e285b9", "#0ed6f9", "#7825ed", "#f2c6ff", "#cdb2f4", "#5fd374", "#fc838d", "#27bec6", ]; const MODES: [BlendMode; 4] = [ BlendMode::Rgb, BlendMode::LinearRgb, BlendMode::Oklab, BlendMode::Lab, ]; const POSITIONS: [f32; 3] = [0.03, 0.5, 0.97]; fn bench_linear_gradient(c: &mut Criterion) { for mode in MODES { let grad = GradientBuilder::new() .html_colors(&COLORS) .mode(mode) .build::() .unwrap(); for pos in POSITIONS { c.bench_function(&format!("LinearGradient ({mode:?}) t={pos}"), |b| { b.iter(|| { grad.at(black_box(pos)); }) }); } } } fn bench_catmull_rom_gradient(c: &mut Criterion) { for mode in MODES { let grad = GradientBuilder::new() .html_colors(&COLORS) .mode(mode) .build::() .unwrap(); for pos in POSITIONS { c.bench_function(&format!("CatmullRomGradient ({mode:?}) t={pos}"), |b| { b.iter(|| { grad.at(black_box(pos)); }) }); } } } fn bench_basis_gradient(c: &mut Criterion) { for mode in MODES { let grad = GradientBuilder::new() .html_colors(&COLORS) .mode(mode) .build::() .unwrap(); for pos in POSITIONS { c.bench_function(&format!("BasisGradient ({mode:?}) t={pos}"), |b| { b.iter(|| { grad.at(black_box(pos)); }) }); } } } criterion_group!(linear_gradient, bench_linear_gradient,); criterion_group!(catmull_rom_gradient, bench_catmull_rom_gradient,); criterion_group!(basis_gradient, bench_basis_gradient,); criterion_main!(linear_gradient, catmull_rom_gradient, basis_gradient); colorgrad-0.8.0/benches/gimp.rs000064400000000000000000000022301046102023000145130ustar 00000000000000use colorgrad::{Color, GimpGradient, Gradient}; use criterion::{black_box, criterion_group, criterion_main, Criterion}; use std::io::BufReader; const GGR1: &'static str = include_str!("../examples/ggr/My_Gradient.ggr"); const GGR2: &'static str = include_str!("../examples/ggr/test_hsv.ggr"); const POSITIONS: [f32; 4] = [0.03, 0.45, 0.55, 0.97]; fn bench_gimp_gradient(c: &mut Criterion) { let fg_color = Color::new(0.0, 0.0, 0.0, 1.0); let bg_color = Color::new(1.0, 1.0, 1.0, 1.0); let grad1 = GimpGradient::new(BufReader::new(GGR1.as_bytes()), &fg_color, &bg_color).unwrap(); let grad2 = GimpGradient::new(BufReader::new(GGR2.as_bytes()), &fg_color, &bg_color).unwrap(); for pos in POSITIONS { c.bench_function(&format!("GimpGradient RGB t={pos}"), |b| { b.iter(|| { grad1.at(black_box(pos)); }) }); } for pos in POSITIONS { c.bench_function(&format!("GimpGradient HSV t={pos}"), |b| { b.iter(|| { grad2.at(black_box(pos)); }) }); } } criterion_group!(gimp_gradient, bench_gimp_gradient,); criterion_main!(gimp_gradient); colorgrad-0.8.0/benches/preset.rs000064400000000000000000000035561046102023000150750ustar 00000000000000use colorgrad::{preset, Gradient}; use criterion::{black_box, criterion_group, criterion_main, Criterion}; fn bench_sinebow(c: &mut Criterion) { let grad = preset::sinebow(); c.bench_function("preset sinebow", |b| { b.iter(|| { grad.at(black_box(0.6)); }) }); } fn bench_rainbow(c: &mut Criterion) { let grad = preset::rainbow(); c.bench_function("preset rainbow", |b| { b.iter(|| { grad.at(black_box(0.6)); }) }); } fn bench_turbo(c: &mut Criterion) { let grad = preset::turbo(); c.bench_function("preset turbo", |b| { b.iter(|| { grad.at(black_box(0.6)); }) }); } fn bench_cividis(c: &mut Criterion) { let grad = preset::cividis(); c.bench_function("preset cividis", |b| { b.iter(|| { grad.at(black_box(0.6)); }) }); } fn bench_cubehelix(c: &mut Criterion) { let grad = preset::cubehelix_default(); c.bench_function("preset cubehelix_default", |b| { b.iter(|| { grad.at(black_box(0.6)); }) }); } fn bench_warm(c: &mut Criterion) { let grad = preset::warm(); c.bench_function("preset warm", |b| { b.iter(|| { grad.at(black_box(0.6)); }) }); } fn bench_cool(c: &mut Criterion) { let grad = preset::cool(); c.bench_function("preset cool", |b| { b.iter(|| { grad.at(black_box(0.6)); }) }); } fn bench_spectral(c: &mut Criterion) { let grad = preset::spectral(); c.bench_function("preset spectral", |b| { b.iter(|| { grad.at(black_box(0.6)); }) }); } criterion_group!( preset_gradients, bench_sinebow, bench_rainbow, bench_turbo, bench_cividis, bench_cubehelix, bench_warm, bench_cool, bench_spectral, ); criterion_main!(preset_gradients); colorgrad-0.8.0/examples/basic.rs000064400000000000000000000014051046102023000150520ustar 00000000000000use colorgrad::Gradient; fn main() -> Result<(), Box> { let width = 1300.0; let height = 70.0; // preset gradient let grad = colorgrad::preset::rainbow(); let imgbuf = image::ImageBuffer::from_fn(width as u32, height as u32, |x, _| { image::Rgba(grad.at(x as f32 / width).to_rgba8()) }); imgbuf.save("gradient-preset.png")?; // custom gradient let grad = colorgrad::GradientBuilder::new() .html_colors(&["deeppink", "gold", "seagreen"]) .build::()?; let imgbuf = image::ImageBuffer::from_fn(width as u32, height as u32, |x, _| { image::Rgba(grad.at(x as f32 / width).to_rgba8()) }); imgbuf.save("gradient-custom.png")?; Ok(()) } colorgrad-0.8.0/examples/ggr/Abstract_1.ggr000064400000000000000000000012431046102023000166660ustar 00000000000000GIMP Gradient Name: Abstract 1 6 0.000000 0.286311 0.572621 0.269543 0.259267 1.000000 1.000000 0.215635 0.407414 0.984953 1.000000 0 0 0 0 0.572621 0.657763 0.716194 0.215635 0.407414 0.984953 1.000000 0.040368 0.833333 0.619375 1.000000 0 0 0 0 0.716194 0.734558 0.749583 0.040368 0.833333 0.619375 1.000000 0.680490 0.355264 0.977430 1.000000 0 0 0 0 0.749583 0.784641 0.824708 0.680490 0.355264 0.977430 1.000000 0.553909 0.351853 0.977430 1.000000 0 0 0 0 0.824708 0.853088 0.876461 0.553909 0.351853 0.977430 1.000000 1.000000 0.000000 1.000000 1.000000 0 0 0 0 0.876461 0.943172 1.000000 1.000000 0.000000 1.000000 1.000000 1.000000 1.000000 0.000000 1.000000 0 0 0 0 colorgrad-0.8.0/examples/ggr/Full_saturation_spectrum_CW.ggr000064400000000000000000000002311046102023000223650ustar 00000000000000GIMP Gradient Name: Full saturation spectrum CW 1 0.000000 0.500000 1.000000 1.000000 0.000000 0.000000 1.000000 1.000000 0.000000 0.000000 1.000000 0 2 colorgrad-0.8.0/examples/ggr/My_Gradient.ggr000064400000000000000000000247601046102023000171160ustar 00000000000000GIMP Gradient Name: My Gradient 100 0.000000 0.005000 0.010000 1.000000 1.000000 0.000000 1.000000 1.000000 0.963882 0.000575 1.000000 0 0 0 0 0.010000 0.015000 0.020000 1.000000 0.963882 0.000575 1.000000 1.000000 0.927759 0.003080 1.000000 0 0 0 0 0.020000 0.025000 0.030000 1.000000 0.927759 0.003080 1.000000 1.000000 0.891717 0.009192 1.000000 0 0 0 0 0.030000 0.035000 0.040000 1.000000 0.891717 0.009192 1.000000 1.000000 0.855847 0.020315 1.000000 0 0 0 0 0.040000 0.045000 0.050000 1.000000 0.855847 0.020315 1.000000 1.000000 0.820240 0.037596 1.000000 0 0 0 0 0.050000 0.055000 0.060000 1.000000 0.820240 0.037596 1.000000 1.000000 0.784997 0.059007 1.000000 0 0 0 0 0.060000 0.065000 0.070000 1.000000 0.784997 0.059007 1.000000 1.000000 0.750223 0.080719 1.000000 0 0 0 0 0.070000 0.075000 0.080000 1.000000 0.750223 0.080719 1.000000 1.000000 0.716034 0.102592 1.000000 0 0 0 0 0.080000 0.085000 0.090000 1.000000 0.716034 0.102592 1.000000 1.000000 0.682551 0.124545 1.000000 0 0 0 0 0.090000 0.095000 0.100000 1.000000 0.682551 0.124545 1.000000 1.000000 0.649910 0.146528 1.000000 0 0 0 0 0.100000 0.105000 0.110000 1.000000 0.649910 0.146528 1.000000 1.000000 0.618257 0.168523 1.000000 0 0 0 0 0.110000 0.115000 0.120000 1.000000 0.618257 0.168523 1.000000 1.000000 0.587753 0.190532 1.000000 0 0 0 0 0.120000 0.125000 0.130000 1.000000 0.587753 0.190532 1.000000 0.993836 0.558569 0.212579 1.000000 0 0 0 0 0.130000 0.135000 0.140000 0.993836 0.558569 0.212579 1.000000 0.980974 0.530893 0.234712 1.000000 0 0 0 0 0.140000 0.145000 0.150000 0.980974 0.530893 0.234712 1.000000 0.966267 0.504921 0.256991 1.000000 0 0 0 0 0.150000 0.155000 0.160000 0.966267 0.504921 0.256991 1.000000 0.949699 0.480853 0.279496 1.000000 0 0 0 0 0.160000 0.165000 0.170000 0.949699 0.480853 0.279496 1.000000 0.931250 0.458891 0.302328 1.000000 0 0 0 0 0.170000 0.175000 0.180000 0.931250 0.458891 0.302328 1.000000 0.910893 0.439220 0.325599 1.000000 0 0 0 0 0.180000 0.185000 0.190000 0.910893 0.439220 0.325599 1.000000 0.888597 0.421996 0.349444 1.000000 0 0 0 0 0.190000 0.195000 0.200000 0.888597 0.421996 0.349444 1.000000 0.864317 0.407330 0.374009 1.000000 0 0 0 0 0.200000 0.205000 0.210000 0.864317 0.407330 0.374009 1.000000 0.838069 0.395225 0.399362 1.000000 0 0 0 0 0.210000 0.215000 0.220000 0.838069 0.395225 0.399362 1.000000 0.810129 0.385462 0.425243 1.000000 0 0 0 0 0.220000 0.225000 0.230000 0.810129 0.385462 0.425243 1.000000 0.780816 0.377745 0.451403 1.000000 0 0 0 0 0.230000 0.235000 0.240000 0.780816 0.377745 0.451403 1.000000 0.750425 0.371769 0.477670 1.000000 0 0 0 0 0.240000 0.245000 0.250000 0.750425 0.371769 0.477670 1.000000 0.719224 0.367238 0.503914 1.000000 0 0 0 0 0.250000 0.255000 0.260000 0.719224 0.367238 0.503914 1.000000 0.687457 0.363891 0.530025 1.000000 0 0 0 0 0.260000 0.265000 0.270000 0.687457 0.363891 0.530025 1.000000 0.655341 0.361514 0.555900 1.000000 0 0 0 0 0.270000 0.275000 0.280000 0.655341 0.361514 0.555900 1.000000 0.623063 0.359951 0.581432 1.000000 0 0 0 0 0.280000 0.285000 0.290000 0.623063 0.359951 0.581432 1.000000 0.590778 0.359111 0.606505 1.000000 0 0 0 0 0.290000 0.295000 0.300000 0.590778 0.359111 0.606505 1.000000 0.558604 0.358970 0.630988 1.000000 0 0 0 0 0.300000 0.305000 0.310000 0.558604 0.358970 0.630988 1.000000 0.526618 0.359567 0.654737 1.000000 0 0 0 0 0.310000 0.315000 0.320000 0.526618 0.359567 0.654737 1.000000 0.494850 0.361006 0.677589 1.000000 0 0 0 0 0.320000 0.325000 0.330000 0.494850 0.361006 0.677589 1.000000 0.463275 0.363445 0.699366 1.000000 0 0 0 0 0.330000 0.335000 0.340000 0.463275 0.363445 0.699366 1.000000 0.431802 0.367086 0.719870 1.000000 0 0 0 0 0.340000 0.345000 0.350000 0.431802 0.367086 0.719870 1.000000 0.400264 0.372163 0.738887 1.000000 0 0 0 0 0.350000 0.355000 0.360000 0.400264 0.372163 0.738887 1.000000 0.368399 0.378924 0.756186 1.000000 0 0 0 0 0.360000 0.365000 0.370000 0.368399 0.378924 0.756186 1.000000 0.335827 0.387616 0.771519 1.000000 0 0 0 0 0.370000 0.375000 0.380000 0.335827 0.387616 0.771519 1.000000 0.302004 0.398459 0.784617 1.000000 0 0 0 0 0.380000 0.385000 0.390000 0.302004 0.398459 0.784617 1.000000 0.266138 0.411634 0.795195 1.000000 0 0 0 0 0.390000 0.395000 0.400000 0.266138 0.411634 0.795195 1.000000 0.227011 0.427266 0.802945 1.000000 0 0 0 0 0.400000 0.405000 0.410000 0.227011 0.427266 0.802945 1.000000 0.182359 0.445349 0.807616 1.000000 0 0 0 0 0.410000 0.415000 0.420000 0.182359 0.445349 0.807616 1.000000 0.126319 0.465544 0.809257 1.000000 0 0 0 0 0.420000 0.425000 0.430000 0.126319 0.465544 0.809257 1.000000 0.029653 0.487426 0.807983 1.000000 0 0 0 0 0.430000 0.435000 0.440000 0.029653 0.487426 0.807983 1.000000 0.000000 0.510578 0.803899 1.000000 0 0 0 0 0.440000 0.445000 0.450000 0.000000 0.510578 0.803899 1.000000 0.000000 0.534600 0.797102 1.000000 0 0 0 0 0.450000 0.455000 0.460000 0.000000 0.534600 0.797102 1.000000 0.000000 0.559119 0.787684 1.000000 0 0 0 0 0.460000 0.465000 0.470000 0.000000 0.559119 0.787684 1.000000 0.000000 0.583785 0.775735 1.000000 0 0 0 0 0.470000 0.475000 0.480000 0.000000 0.583785 0.775735 1.000000 0.000000 0.608271 0.761341 1.000000 0 0 0 0 0.480000 0.485000 0.490000 0.000000 0.608271 0.761341 1.000000 0.000000 0.632273 0.744590 1.000000 0 0 0 0 0.490000 0.495000 0.500000 0.000000 0.632273 0.744590 1.000000 0.000000 0.655501 0.725570 1.000000 0 0 0 0 0.500000 0.505000 0.510000 0.000000 0.655501 0.725570 1.000000 0.000000 0.677680 0.704374 1.000000 0 0 0 0 0.510000 0.515000 0.520000 0.000000 0.677680 0.704374 1.000000 0.000000 0.698544 0.681102 1.000000 0 0 0 0 0.520000 0.525000 0.530000 0.000000 0.698544 0.681102 1.000000 0.000000 0.717838 0.655863 1.000000 0 0 0 0 0.530000 0.535000 0.540000 0.000000 0.717838 0.655863 1.000000 0.000000 0.735310 0.628782 1.000000 0 0 0 0 0.540000 0.545000 0.550000 0.000000 0.735310 0.628782 1.000000 0.053837 0.750716 0.600001 1.000000 0 0 0 0 0.550000 0.555000 0.560000 0.053837 0.750716 0.600001 1.000000 0.175596 0.763812 0.569693 1.000000 0 0 0 0 0.560000 0.565000 0.570000 0.175596 0.763812 0.569693 1.000000 0.252829 0.774359 0.538069 1.000000 0 0 0 0 0.570000 0.575000 0.580000 0.252829 0.774359 0.538069 1.000000 0.317592 0.782118 0.505394 1.000000 0 0 0 0 0.580000 0.585000 0.590000 0.317592 0.782118 0.505394 1.000000 0.375782 0.786851 0.472009 1.000000 0 0 0 0 0.590000 0.595000 0.600000 0.375782 0.786851 0.472009 1.000000 0.429506 0.788322 0.438362 1.000000 0 0 0 0 0.600000 0.605000 0.610000 0.429506 0.788322 0.438362 1.000000 0.479615 0.786358 0.404945 1.000000 0 0 0 0 0.610000 0.615000 0.620000 0.479615 0.786358 0.404945 1.000000 0.526327 0.781054 0.371930 1.000000 0 0 0 0 0.620000 0.625000 0.630000 0.526327 0.781054 0.371930 1.000000 0.569683 0.772564 0.339402 1.000000 0 0 0 0 0.630000 0.635000 0.640000 0.569683 0.772564 0.339402 1.000000 0.609671 0.761047 0.307444 1.000000 0 0 0 0 0.640000 0.645000 0.650000 0.609671 0.761047 0.307444 1.000000 0.646242 0.746658 0.276138 1.000000 0 0 0 0 0.650000 0.655000 0.660000 0.646242 0.746658 0.276138 1.000000 0.679336 0.729556 0.245569 1.000000 0 0 0 0 0.660000 0.665000 0.670000 0.679336 0.729556 0.245569 1.000000 0.708882 0.709905 0.215819 1.000000 0 0 0 0 0.670000 0.675000 0.680000 0.708882 0.709905 0.215819 1.000000 0.734805 0.687871 0.186972 1.000000 0 0 0 0 0.680000 0.685000 0.690000 0.734805 0.687871 0.186972 1.000000 0.757032 0.663625 0.159114 1.000000 0 0 0 0 0.690000 0.695000 0.700000 0.757032 0.663625 0.159114 1.000000 0.775492 0.637344 0.132330 1.000000 0 0 0 0 0.700000 0.705000 0.710000 0.775492 0.637344 0.132330 1.000000 0.790118 0.609213 0.106712 1.000000 0 0 0 0 0.710000 0.715000 0.720000 0.790118 0.609213 0.106712 1.000000 0.800850 0.579424 0.082352 1.000000 0 0 0 0 0.720000 0.725000 0.730000 0.800850 0.579424 0.082352 1.000000 0.807633 0.548181 0.059355 1.000000 0 0 0 0 0.730000 0.735000 0.740000 0.807633 0.548181 0.059355 1.000000 0.810421 0.515697 0.037851 1.000000 0 0 0 0 0.740000 0.745000 0.750000 0.810421 0.515697 0.037851 1.000000 0.809176 0.482203 0.021235 1.000000 0 0 0 0 0.750000 0.755000 0.760000 0.809176 0.482203 0.021235 1.000000 0.803868 0.447946 0.010757 1.000000 0 0 0 0 0.760000 0.765000 0.770000 0.803868 0.447946 0.010757 1.000000 0.794478 0.413197 0.004787 1.000000 0 0 0 0 0.770000 0.775000 0.780000 0.794478 0.413197 0.004787 1.000000 0.780997 0.378255 0.001887 1.000000 0 0 0 0 0.780000 0.785000 0.790000 0.780997 0.378255 0.001887 1.000000 0.763423 0.343462 0.000826 1.000000 0 0 0 0 0.790000 0.795000 0.800000 0.763423 0.343462 0.000826 1.000000 0.741772 0.309207 0.000587 1.000000 0 0 0 0 0.800000 0.805000 0.810000 0.741772 0.309207 0.000587 1.000000 0.716125 0.275883 0.000482 1.000000 0 0 0 0 0.810000 0.815000 0.820000 0.716125 0.275883 0.000482 1.000000 0.686799 0.243672 0.000388 1.000000 0 0 0 0 0.820000 0.825000 0.830000 0.686799 0.243672 0.000388 1.000000 0.654155 0.212687 0.000307 1.000000 0 0 0 0 0.830000 0.835000 0.840000 0.654155 0.212687 0.000307 1.000000 0.618543 0.183038 0.000237 1.000000 0 0 0 0 0.840000 0.845000 0.850000 0.618543 0.183038 0.000237 1.000000 0.580307 0.154823 0.000179 1.000000 0 0 0 0 0.850000 0.855000 0.860000 0.580307 0.154823 0.000179 1.000000 0.539786 0.128133 0.000132 1.000000 0 0 0 0 0.860000 0.865000 0.870000 0.539786 0.128133 0.000132 1.000000 0.497313 0.103050 0.000094 1.000000 0 0 0 0 0.870000 0.875000 0.880000 0.497313 0.103050 0.000094 1.000000 0.453225 0.079649 0.000065 1.000000 0 0 0 0 0.880000 0.885000 0.890000 0.453225 0.079649 0.000065 1.000000 0.407857 0.057995 0.000043 1.000000 0 0 0 0 0.890000 0.895000 0.900000 0.407857 0.057995 0.000043 1.000000 0.361549 0.038148 0.000028 1.000000 0 0 0 0 0.900000 0.905000 0.910000 0.361549 0.038148 0.000028 1.000000 0.314651 0.022790 0.000017 1.000000 0 0 0 0 0.910000 0.915000 0.920000 0.314651 0.022790 0.000017 1.000000 0.267520 0.012778 0.000009 1.000000 0 0 0 0 0.920000 0.925000 0.930000 0.267520 0.012778 0.000009 1.000000 0.220535 0.006614 0.000005 1.000000 0 0 0 0 0.930000 0.935000 0.940000 0.220535 0.006614 0.000005 1.000000 0.174100 0.003084 0.000002 1.000000 0 0 0 0 0.940000 0.945000 0.950000 0.174100 0.003084 0.000002 1.000000 0.128659 0.001248 0.000001 1.000000 0 0 0 0 0.950000 0.955000 0.960000 0.128659 0.001248 0.000001 1.000000 0.084729 0.000411 0.000000 1.000000 0 0 0 0 0.960000 0.965000 0.970000 0.084729 0.000411 0.000000 1.000000 0.042944 0.000098 0.000000 1.000000 0 0 0 0 0.970000 0.975000 0.980000 0.042944 0.000098 0.000000 1.000000 0.012845 0.000013 0.000000 1.000000 0 0 0 0 0.980000 0.985000 0.990000 0.012845 0.000013 0.000000 1.000000 0.001613 0.000000 0.000000 1.000000 0 0 0 0 0.990000 0.995000 1.000000 0.001613 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 1.000000 0 0 0 0 colorgrad-0.8.0/examples/ggr/UTF_8_BOM.ggr000064400000000000000000000011211046102023000162600ustar 00000000000000GIMP Gradient Name: Pelangi 4 0 0.125 0.25 0.1411764705882353 0.3411764705882353 0.6196078431372549 1 0.7254901960784313 0.9568627450980391 0.6039215686274509 1 0 0 0 0 0.25 0.375 0.5 0.7254901960784313 0.9568627450980391 0.6039215686274509 1 0.9764705882352941 0.5568627450980392 0.2901960784313725 1 0 0 0 0 0.5 0.625 0.75 0.9764705882352941 0.5568627450980392 0.2901960784313725 1 0.5843137254901961 0.2627450980392157 0.9764705882352941 1 0 0 0 0 0.75 0.875 1 0.5843137254901961 0.2627450980392157 0.9764705882352941 1 0.2823529411764706 0.47058823529411764 0.6588235294117647 1 0 0 0 0colorgrad-0.8.0/examples/ggr/test_hsv.ggr000064400000000000000000000001511046102023000165370ustar 00000000000000GIMP Gradient Name: Test HSV 2 0.0 0.25 0.5 1 0 0 1 0 1 1 1 0 2 0 0 0.5 0.75 1.0 1 0 0 1 0 1 1 1 0 1 0 0 colorgrad-0.8.0/examples/gradients/gradients.rs000064400000000000000000000062301046102023000177320ustar 00000000000000use colorgrad::{ BasisGradient, BlendMode, CatmullRomGradient, Color, Gradient, GradientBuilder, LinearGradient, }; macro_rules! preset { ($name:ident) => { (Box::new(colorgrad::preset::$name()), stringify!($name)) }; } pub fn preset() -> Vec<(Box, &'static str)> { vec![ preset!(sinebow), preset!(turbo), preset!(cividis), preset!(rainbow), preset!(cubehelix_default), preset!(warm), preset!(cool), preset!(viridis), preset!(inferno), preset!(magma), preset!(plasma), preset!(bu_gn), preset!(bu_pu), preset!(gn_bu), preset!(or_rd), preset!(pu_bu_gn), preset!(pu_bu), preset!(pu_rd), preset!(rd_pu), preset!(yl_gn_bu), preset!(yl_gn), preset!(yl_or_br), preset!(yl_or_rd), preset!(br_bg), preset!(pr_gn), preset!(pi_yg), preset!(pu_or), preset!(rd_bu), preset!(rd_gy), preset!(rd_yl_bu), preset!(rd_yl_gn), preset!(spectral), preset!(blues), preset!(greens), preset!(greys), preset!(oranges), preset!(purples), preset!(reds), ] } pub fn blend_mode() -> Vec<(Box, &'static str)> { fn grad(mode: BlendMode) -> CatmullRomGradient { GradientBuilder::new() .html_colors(&["#fff", "#00f", "gold", "deeppink", "#000"]) .mode(mode) .build::() .unwrap() } vec![ (Box::new(grad(BlendMode::Rgb)), "Rgb"), (Box::new(grad(BlendMode::LinearRgb)), "LinearRgb"), (Box::new(grad(BlendMode::Oklab)), "Oklab"), (Box::new(grad(BlendMode::Lab)), "Lab"), ] } pub fn interpolation() -> Vec<(Box, String)> { let mut gradients: Vec<(Box, String)> = Vec::new(); let colors = ["#C41189", "#00BFFF", "#FFD700"]; let modes = [ BlendMode::Rgb, BlendMode::LinearRgb, BlendMode::Oklab, BlendMode::Lab, ]; for mode in modes.iter() { let g = GradientBuilder::new() .html_colors(&colors) .mode(*mode) .build::() .unwrap(); gradients.push((Box::new(g), format!("Linear_{mode:?}"))); let g = GradientBuilder::new() .html_colors(&colors) .mode(*mode) .build::() .unwrap(); gradients.push((Box::new(g), format!("CatmullRom_{mode:?}"))); let g = GradientBuilder::new() .html_colors(&colors) .mode(*mode) .build::() .unwrap(); gradients.push((Box::new(g), format!("Basis_{mode:?}"))); } gradients } pub fn sharp() -> Vec<(Box, String)> { let mut gradients: Vec<(Box, String)> = Vec::new(); let grad = colorgrad::preset::rainbow(); for i in 0..=10 { let t = i as f32 / 10.0; let g = grad.sharp(13, t); gradients.push((Box::new(g), format!("sharp_{t}"))); } gradients } colorgrad-0.8.0/examples/gradients/main.rs000064400000000000000000000070541046102023000167030ustar 00000000000000#![allow(unused_imports, unused_variables, dead_code, unreachable_code)] // cargo run --example gradients --all-features --release use std::fs; use std::io::BufReader; use std::path::Path; use colorgrad::{Color, GimpGradient, Gradient}; mod gradients; mod util; use util::{grad_rgb_plot, gradient_image}; fn main() -> Result<(), Box> { let output_dir = Path::new("example_output/"); if !output_dir.exists() { fs::create_dir(output_dir).expect("Failed to create example_output/ directory."); } let css_gradients = [ "red, blue", "red 30%, blue 70%", "red, 75%, blue", "red, yellow, lime, aqua, blue, magenta, red", "tomato 0% 50%, gold 50%, tomato", "purple 50%, deeppink 50%, gold, seagreen", "blue, cyan, gold, purple 50%, tomato 50%", "seagreen 30%, gold 0 70%, deeppink 0", "rgb(40, 230, 65) 10%, hotpink, steelblue", "rgb(255, 0, 0) 0% 50%, rgb(0, 0, 255), red, lime", "red, #f000", "red, 75%, #f000", "red -100, yellow, lime, aqua, blue, magenta, red 100", "red, lime -10, blue 15, gold", ]; println!("--- CSS Gradients"); println!(); for (i, s) in css_gradients.iter().enumerate() { println!("input \"{s}\""); let g = colorgrad::GradientBuilder::new() .css(s) .mode(colorgrad::BlendMode::Rgb) .build::(); if let Ok(grad) = g { println!("domain {:?}", grad.domain()); let imgbuf = grad_rgb_plot(&grad, 1000, 150, 10, None); let file_path = format!("example_output/css_{i}.png"); println!("{file_path}"); imgbuf.save(file_path)?; } else { println!("error"); } println!(); } for (grad, name) in gradients::preset() { let imgbuf = grad_rgb_plot(&*grad, 1000, 150, 10, None); let file_path = format!("example_output/preset_{name}.png"); println!("{file_path}"); imgbuf.save(file_path)?; } for (grad, name) in gradients::blend_mode() { let imgbuf = grad_rgb_plot(&*grad, 1000, 150, 10, None); let file_path = format!("example_output/mode_{name}.png"); println!("{file_path}"); imgbuf.save(file_path)?; } for (grad, name) in gradients::interpolation() { let imgbuf = grad_rgb_plot(&*grad, 1000, 150, 10, None); let file_path = format!("example_output/interpolation_{name}.png"); println!("{file_path}"); imgbuf.save(file_path)?; } for (grad, name) in gradients::sharp() { let imgbuf = grad_rgb_plot(&*grad, 1000, 150, 10, None); let file_path = format!("example_output/{name}.png"); println!("{file_path}"); imgbuf.save(file_path)?; } // GIMP gradients for item in Path::new("examples/ggr/").read_dir()? { let path = item.unwrap().path(); if let Some(ext) = path.extension() { if ext == "ggr" { let fname = path.file_name().unwrap().to_str().unwrap(); let input = fs::File::open(&path)?; let col = Color::default(); let gradient = GimpGradient::new(BufReader::new(input), &col, &col)?; let imgbuf = grad_rgb_plot(&gradient, 1000, 150, 10, None); let file_path = format!("example_output/ggr_{fname}.png"); println!("{file_path} ({})", gradient.name()); imgbuf.save(file_path)?; } } } Ok(()) } colorgrad-0.8.0/examples/gradients/util.rs000064400000000000000000000063341046102023000167340ustar 00000000000000use colorgrad::{Color, Gradient}; use image::{imageops, ImageBuffer, Rgba}; pub fn gradient_image(gradient: &T, width: u32, height: u32) -> ImageBuffer, Vec> where T: Gradient + ?Sized, { let (dmin, dmax) = gradient.domain(); ImageBuffer::from_fn(width, height, |x, _| { let rgba = gradient .at(remap(x as f32, 0.0, width as f32, dmin, dmax)) .to_rgba16(); Rgba(rgba) }) } fn rgb_plot( grad: &T, width: u32, height: u32, pos: Option<&[f32]>, ) -> ImageBuffer, Vec> where T: Gradient + ?Sized, { let mut imgbuf = ImageBuffer::from_pixel( width, height, Rgba(Color::new(0.9, 0.9, 0.9, 1.0).to_rgba16()), ); let (dmin, dmax) = grad.domain(); let fw = width as f32; let y1 = 0.0; let y2 = height as f32; if let Some(pos) = pos { let color = Rgba(Color::new(0.75, 0.75, 0.75, 1.0).to_rgba16()); for t in pos { let x = remap(*t, dmin, dmax, 0.0, fw - 1.0) as u32; for y in 0..height { let pixel = imgbuf.get_pixel_mut(x, y); *pixel = color; } } }; for x in 0..width { let col = grad.at(remap(x as f32, 0.0, fw, dmin, dmax)); let yr = remap(col.r, 0.0, 1.0, y2, y1); let yg = remap(col.g, 0.0, 1.0, y2, y1); let yb = remap(col.b, 0.0, 1.0, y2, y1); let yl = remap(color_luminance(&col), 0.0, 1.0, y2, y1); if (y1..y2).contains(&yr) { let pixel = imgbuf.get_pixel_mut(x, yr as u32); *pixel = Rgba([65535, 0, 0, 65535]); } if (y1..y2).contains(&yg) { let pixel = imgbuf.get_pixel_mut(x, yg as u32); *pixel = Rgba([0, 32767, 0, 65535]); } if (y1..y2).contains(&yb) { let pixel = imgbuf.get_pixel_mut(x, yb as u32); *pixel = Rgba([0, 0, 65535, 65535]); } continue; if (y1..y2).contains(&yl) { let pixel = imgbuf.get_pixel_mut(x, yl as u32); *pixel = Rgba([32767, 32767, 32767, 65535]); } } imgbuf } pub fn grad_rgb_plot( grad: &T, width: u32, height: u32, padding: u32, pos: Option<&[f32]>, ) -> ImageBuffer, Vec> where T: Gradient + ?Sized, { let w = width + padding * 2; let h = height * 2 + padding * 3; let mut imgbuf = ImageBuffer::from_pixel(w, h, Rgba(Color::new(1.0, 1.0, 1.0, 1.0).to_rgba16())); let grad_img = gradient_image(grad, width, height); imageops::replace(&mut imgbuf, &grad_img, padding.into(), padding.into()); let plot_img = rgb_plot(grad, width, height, pos); imageops::replace( &mut imgbuf, &plot_img, padding.into(), (height + padding * 2).into(), ); imgbuf } // Map t in range [a, b] to range [c, d] fn remap(t: f32, a: f32, b: f32, c: f32, d: f32) -> f32 { (t - a) * ((d - c) / (b - a)) + c } fn color_luminance(col: &Color) -> f32 { fn lum(t: f32) -> f32 { if t <= 0.03928 { t / 12.92 } else { ((t + 0.055) / 1.055).powf(2.4) } } 0.2126 * lum(col.r) + 0.7152 * lum(col.g) + 0.0722 * lum(col.b) } colorgrad-0.8.0/src/builder.rs000064400000000000000000000204001046102023000143640ustar 00000000000000use alloc::format; use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; use core::convert::TryFrom; use core::{error, fmt}; use crate::{linspace, BlendMode, CSSGradientParser, Color}; #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum GradientBuilderError { InvalidHtmlColors(Vec), InvalidCssGradient, InvalidDomain, InvalidStops, } impl fmt::Display for GradientBuilderError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Self::InvalidHtmlColors(ref colors) => { write!( f, "invalid html colors: {}", colors .iter() .map(|x| format!("'{x}'")) .collect::>() .join(", ") ) } Self::InvalidCssGradient => f.write_str("invalid css gradient"), Self::InvalidDomain => f.write_str("invalid domain"), Self::InvalidStops => f.write_str("invalid stops"), } } } impl error::Error for GradientBuilderError {} /// Create custom gradient /// /// # Examples /// /// ``` /// # use std::error::Error; /// use colorgrad::{Color, Gradient}; /// /// # fn main() -> Result<(), Box> { /// let grad = colorgrad::GradientBuilder::new() /// .colors(&[ /// Color::from_rgba8(255, 0, 0, 255), /// Color::new(0.0, 0.0, 1.0, 1.0), /// ]) /// .build::()?; /// /// assert_eq!(grad.domain(), (0.0, 1.0)); // default domain /// assert_eq!(grad.at(0.0).to_rgba8(), [255, 0, 0, 255]); /// assert_eq!(grad.at(1.0).to_rgba8(), [0, 0, 255, 255]); /// # Ok(()) /// # } /// ``` /// #[cfg_attr( feature = "named-colors", doc = r##" ## Using web color format string ``` # use std::error::Error; use colorgrad::Gradient; # fn main() -> Result<(), Box> { let grad = colorgrad::GradientBuilder::new() .html_colors(&["deeppink", "gold", "seagreen"]) .domain(&[0.0, 100.0]) .mode(colorgrad::BlendMode::Rgb) .build::()?; assert_eq!(grad.at(0.0).to_rgba8(), [255, 20, 147, 255]); assert_eq!(grad.at(100.0).to_rgba8(), [46, 139, 87, 255]); # Ok(()) # } ```"## )] #[derive(Debug, Clone)] pub struct GradientBuilder { pub(crate) colors: Vec, pub(crate) positions: Vec, pub(crate) mode: BlendMode, invalid_html_colors: Vec, invalid_css_gradient: bool, clean: bool, } impl GradientBuilder { #[allow(clippy::new_without_default)] pub fn new() -> Self { Self { colors: Vec::new(), positions: Vec::new(), mode: BlendMode::Rgb, invalid_html_colors: Vec::new(), invalid_css_gradient: false, clean: false, } } /// Set gradient color pub fn colors<'a>(&'a mut self, colors: &[Color]) -> &'a mut Self { for c in colors { self.colors.push(c.clone()); } self.clean = false; self } /// Set gradient color using web / CSS color format. /// /// ## Supported Color Format /// /// * [Named colors](https://www.w3.org/TR/css-color-4/#named-colors) /// * RGB hexadecimal /// + Short format `#rgb` /// + Short format with alpha `#rgba` /// + Long format `#rrggbb` /// + Long format with alpha `#rrggbbaa` /// * `rgb()` and `rgba()` /// * `hsl()` and `hsla()` /// * `hwb()` /// * `hsv()` - not in CSS standard. pub fn html_colors<'a, S: AsRef + ToString>( &'a mut self, html_colors: &[S], ) -> &'a mut Self { for s in html_colors { if let Ok(c) = csscolorparser::parse(s.as_ref()) { self.colors.push(c); } else { self.invalid_html_colors.push(s.to_string()); } } self.clean = false; self } /// Set the gradient domain and/or color position. pub fn domain<'a>(&'a mut self, positions: &[f32]) -> &'a mut Self { self.positions = positions.to_vec(); self.clean = false; self } /// Set the color blending mode pub fn mode(&mut self, mode: BlendMode) -> &mut Self { self.mode = mode; self } /// Parse [CSS gradient](https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/linear-gradient) format /// /// ``` /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// let grad = colorgrad::GradientBuilder::new() /// .css("#fff, 75%, #00f") /// .build::()?; /// # Ok(()) /// # } /// ``` pub fn css<'a>(&'a mut self, s: &str) -> &'a mut Self { let [dmin, dmax] = if self.positions.len() == 2 && self.positions[0] < self.positions[1] { [self.positions[0], self.positions[1]] } else { [0.0, 1.0] }; let mut cgp = CSSGradientParser::new(); cgp.set_domain(dmin, dmax); cgp.set_mode(self.mode); if let Some((colors, positions)) = cgp.parse(s) { self.invalid_css_gradient = false; self.colors = colors; self.positions = positions; } else { self.invalid_css_gradient = true; } self.clean = false; self } pub fn reset(&mut self) -> &mut Self { self.colors.clear(); self.positions.clear(); self.mode = BlendMode::Rgb; self.invalid_html_colors.clear(); self.invalid_css_gradient = false; self.clean = false; self } #[doc(hidden)] pub fn get_colors(&self) -> &[Color] { &self.colors } #[doc(hidden)] pub fn get_positions(&self) -> &[f32] { &self.positions } pub fn build<'a, T>(&'a mut self) -> Result where T: TryFrom<&'a mut Self, Error = GradientBuilderError>, { T::try_from(self) } /// Build the gradient pub(crate) fn prepare_build(&mut self) -> Result<(), GradientBuilderError> { if self.clean { return Ok(()); } if !self.invalid_html_colors.is_empty() { return Err(GradientBuilderError::InvalidHtmlColors( self.invalid_html_colors.clone(), )); } if self.invalid_css_gradient { return Err(GradientBuilderError::InvalidCssGradient); } let colors = if self.colors.is_empty() { vec![ Color::new(0.0, 0.0, 0.0, 1.0), Color::new(1.0, 1.0, 1.0, 1.0), ] } else if self.colors.len() == 1 { vec![self.colors[0].clone(), self.colors[0].clone()] } else { self.colors.to_vec() }; let positions = if self.positions.is_empty() { linspace(0.0, 1.0, colors.len()).collect() } else if self.positions.len() == colors.len() { for p in self.positions.windows(2) { if p[0] > p[1] { return Err(GradientBuilderError::InvalidDomain); } } self.positions.to_vec() } else if self.positions.len() == 2 { if self.positions[0] >= self.positions[1] { return Err(GradientBuilderError::InvalidDomain); } linspace(self.positions[0], self.positions[1], colors.len()).collect() } else { return Err(GradientBuilderError::InvalidDomain); }; self.colors.clear(); self.positions.clear(); let mut prev = positions[0]; let last_idx = positions.len() - 1; for (i, (pos, col)) in positions.iter().zip(colors.iter()).enumerate() { let next = if i == last_idx { positions[last_idx] } else { positions[i + 1] }; if (pos - prev) + (next - pos) < f32::EPSILON { // skip } else { self.positions.push(*pos); self.colors.push(col.clone()); } prev = *pos; } if self.colors.len() < 2 { return Err(GradientBuilderError::InvalidStops); } self.clean = true; Ok(()) } } colorgrad-0.8.0/src/core.rs000064400000000000000000000163711046102023000137020ustar 00000000000000use crate::{modulo, norm}; use crate::{Color, InverseGradient, SharpGradient}; use alloc::boxed::Box; use alloc::vec; use alloc::vec::Vec; /// Color blending mode #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum BlendMode { Rgb, LinearRgb, Oklab, #[cfg(feature = "lab")] Lab, } /// All gradient types in `colorgrad` implement `Gradient` trait. /// /// You can also implement `Gradient` for your own types. /// /// ``` /// use colorgrad::{Color, Gradient}; /// /// #[derive(Clone)] /// struct MyRedGradient {} /// /// impl Gradient for MyRedGradient { /// fn at(&self, t: f32) -> Color { /// Color::new(1.0, 0.0, 0.0, 1.0) /// } /// } /// /// let g = MyRedGradient{}; /// assert_eq!(g.domain(), (0.0, 1.0)); /// assert_eq!(g.at(0.1).to_css_hex(), "#ff0000"); /// /// for color in g.colors_iter(25) { /// println!("{:?}", color.to_rgba8()); /// } /// ``` pub trait Gradient: CloneGradient { /// Get color at certain position fn at(&self, t: f32) -> Color; /// Get color at certain position (**repeat** mode) fn repeat_at(&self, t: f32) -> Color { let (dmin, dmax) = self.domain(); let t = norm(t, dmin, dmax); self.at(dmin + modulo(t, 1.0) * (dmax - dmin)) } /// Get color at certain position (**reflect** mode) fn reflect_at(&self, t: f32) -> Color { let (dmin, dmax) = self.domain(); let t = norm(t, dmin, dmax); self.at(dmin + (modulo(1.0 + t, 2.0) - 1.0).abs() * (dmax - dmin)) } /// Get the gradient's domain min and max fn domain(&self) -> (f32, f32) { (0.0, 1.0) } /// Get n colors evenly spaced across gradient fn colors(&self, n: usize) -> Vec { let (dmin, dmax) = self.domain(); if n == 1 { return vec![self.at(dmin)]; } (0..n) .map(|i| self.at(dmin + (i as f32 * (dmax - dmin)) / (n - 1) as f32)) .collect() } /// Returns iterator for n colors evenly spaced across gradient fn colors_iter(&self, n: usize) -> GradientColors<'_> where Self: Sized, { GradientColors::new(self, n) } #[cfg_attr( feature = "preset", doc = r##" Get new hard-edge gradient ``` let g = colorgrad::preset::rainbow(); ``` ![img](https://raw.githubusercontent.com/mazznoer/colorgrad-rs/master/docs/images/preset/rainbow.png) ``` use colorgrad::Gradient; let g = colorgrad::preset::rainbow().sharp(11, 0.0); ``` ![img](https://raw.githubusercontent.com/mazznoer/colorgrad-rs/master/docs/images/rainbow-sharp.png) "## )] fn sharp(&self, segment: u16, smoothness: f32) -> SharpGradient { let colors = if segment > 1 { self.colors(segment.into()) } else { vec![self.at(self.domain().0), self.at(self.domain().0)] }; SharpGradient::new(&colors, self.domain(), smoothness) } #[cfg_attr( feature = "preset", doc = r##" Convert gradient to boxed trait object This is a convenience function, which is useful when you want to store gradients with different types in a collection, or when you want to return a gradient from a function but the type is not known at compile time. ## Examples ``` # fn main() -> Result<(), Box> { # let is_rainbow = true; # use colorgrad::{BlendMode, LinearGradient, GradientBuilder}; use colorgrad::Gradient; let g = if is_rainbow { colorgrad::preset::rainbow().boxed() } else { colorgrad::preset::sinebow().boxed() }; // Vector of different gradient types let g2: LinearGradient = GradientBuilder::new() .css("#a52a2a, 35%, #ffd700") .mode(BlendMode::Oklab) .build()?; let gradients = vec![ g2.sharp(7, 0.0).boxed(), g2.boxed(), colorgrad::preset::magma().boxed(), colorgrad::preset::turbo().boxed(), ]; # Ok(()) # } ``` "## )] fn boxed<'a>(self) -> Box where Self: Sized + 'a, { Box::new(self) } /// Get a new gradient that inverts the gradient /// /// The minimum value of the inner gradient will be the maximum value of the inverse gradient and /// vice versa. /// /// # Example /// /// ``` /// use colorgrad::Gradient; /// /// let grad = colorgrad::GradientBuilder::new() /// .html_colors(&["#fff", "#000"]) /// .build::() /// .unwrap(); /// /// let inverse = grad.inverse(); /// ``` fn inverse<'a>(&self) -> InverseGradient<'_> where Self: 'a, { InverseGradient::new(self.clone_boxed()) } } pub trait CloneGradient { fn clone_boxed<'s>(&self) -> Box where Self: 's; } impl CloneGradient for T where T: Gradient + Clone, { fn clone_boxed<'s>(&self) -> Box where Self: 's, { Box::new(self.clone()) } } impl Clone for Box { fn clone(&self) -> Self { (**self).clone_boxed() } } impl Gradient for Box { fn at(&self, t: f32) -> Color { (**self).at(t) } fn repeat_at(&self, t: f32) -> Color { (**self).repeat_at(t) } fn reflect_at(&self, t: f32) -> Color { (**self).reflect_at(t) } fn domain(&self) -> (f32, f32) { (**self).domain() } fn colors(&self, n: usize) -> Vec { (**self).colors(n) } fn sharp(&self, segment: u16, smoothness: f32) -> SharpGradient { (**self).sharp(segment, smoothness) } fn boxed<'a>(self) -> Box where Self: 'a, { Box::new(self) } } #[cfg_attr( feature = "preset", doc = r##" Iterator for evenly spaced colors across gradient ## Examples ``` use colorgrad::Gradient; let gradient = colorgrad::preset::magma(); for color in gradient.colors_iter(15) { println!("{:?}", color.to_rgba8()); } // reverse order for color in gradient.colors_iter(15).rev() { println!("{:?}", color.to_rgba8()); } ``` "## )] pub struct GradientColors<'a> { gradient: &'a dyn Gradient, a_idx: usize, b_idx: usize, max: f32, } impl<'a> GradientColors<'a> { pub fn new(gradient: &'a dyn Gradient, total: usize) -> Self { Self { gradient, a_idx: 0, b_idx: total, max: if total == 0 { 0.0 } else { (total - 1) as f32 }, } } } impl Iterator for GradientColors<'_> { type Item = Color; fn next(&mut self) -> Option { if self.a_idx == self.b_idx { return None; } let (dmin, dmax) = self.gradient.domain(); let t = dmin + (self.a_idx as f32 * (dmax - dmin)) / self.max; self.a_idx += 1; Some(self.gradient.at(t)) } } impl DoubleEndedIterator for GradientColors<'_> { fn next_back(&mut self) -> Option { if self.a_idx == self.b_idx { return None; } let (dmin, dmax) = self.gradient.domain(); self.b_idx -= 1; let t = dmin + (self.b_idx as f32 * (dmax - dmin)) / self.max; Some(self.gradient.at(t)) } } impl ExactSizeIterator for GradientColors<'_> { fn len(&self) -> usize { self.b_idx - self.a_idx } } colorgrad-0.8.0/src/css_gradient.rs000064400000000000000000000304111046102023000154060ustar 00000000000000use crate::{BlendMode, Color}; use alloc::vec::Vec; #[derive(Debug, PartialEq)] struct Stop { col: Option, pos: Option, } impl Stop { fn new(col: Option, pos: Option) -> Self { Self { col, pos } } fn valid(&self) -> bool { self.col.is_some() && self.pos.is_some() } } pub struct CSSGradientParser { dmin: f32, dmax: f32, mode: BlendMode, stops: Vec, } impl CSSGradientParser { pub fn new() -> Self { Self { dmin: 0.0, dmax: 1.0, mode: BlendMode::Rgb, stops: Vec::new(), } } pub fn set_domain(&mut self, min: f32, max: f32) { assert!(min < max); self.dmin = min; self.dmax = max; } pub fn set_mode(&mut self, mode: BlendMode) { self.mode = mode; } #[allow(dead_code)] pub fn reset(&mut self) { self.dmin = 0.0; self.dmax = 1.0; self.mode = BlendMode::Rgb; self.stops.clear(); } #[allow(clippy::question_mark)] pub fn parse(&mut self, s: &str) -> Option<(Vec, Vec)> { if self.dmin >= self.dmax { return None; } for stop in split_by_comma(s) { if !self.parse_stop(stop) { return None; } } let stops = &mut self.stops; if stops.is_empty() { return None; } if stops[0].col.is_none() { return None; } if stops[0].pos.is_none() { stops[0].pos = Some(self.dmin); } for i in 0..stops.len() { if i == stops.len() - 1 { if stops[i].pos.is_none() { stops[i].pos = Some(self.dmax); } break; } if stops[i].col.is_none() { if stops[i + 1].col.is_none() { return None; } let col1 = stops[i - 1].col.as_ref().unwrap(); let col2 = stops[i + 1].col.as_ref().unwrap(); let col = match self.mode { BlendMode::Rgb => col1.interpolate_rgb(col2, 0.5), BlendMode::LinearRgb => col1.interpolate_linear_rgb(col2, 0.5), BlendMode::Oklab => col1.interpolate_oklab(col2, 0.5), #[cfg(feature = "lab")] BlendMode::Lab => col1.interpolate_lab(col2, 0.5), }; stops[i].col = Some(col); } } if stops[0].pos.unwrap() > self.dmin { stops.insert(0, Stop::new(stops[0].col.clone(), Some(self.dmin))); } if stops[stops.len() - 1].pos.unwrap() < self.dmax { stops.push(Stop::new( stops[stops.len() - 1].col.clone(), Some(self.dmax), )); } for i in 0..stops.len() { if stops[i].pos.is_none() { for j in (i + 1)..stops.len() { if let Some(next) = stops[j].pos { let prev = stops[i - 1].pos.unwrap(); stops[i].pos = Some(prev + (next - prev) / (j - i + 1) as f32); break; } } } if i > 0 { stops[i].pos = Some(stops[i].pos.unwrap().max(stops[i - 1].pos.unwrap())); } } for stop in &self.stops { if !stop.valid() { return None; } } let positions: Vec<_> = self.stops.iter().map(|s| s.pos.unwrap()).collect(); let colors: Vec<_> = self.stops.iter().map(|s| s.col.clone().unwrap()).collect(); Some((colors, positions)) } #[rustfmt::skip] pub fn parse_stop(&mut self, s: &str) -> bool { let mut it = split_by_space(s); match (it.next(), it.next(), it.next()) { (Some(s), None, None) => { if let Ok(color) = s.parse::() { self.stops.push(Stop::new(Some(color), None)); } else if let Some(position) = self.parse_pos(s) { self.stops.push(Stop::new(None, Some(position))); } else { return false; } } (Some(color), Some(position), None) => { let ( Ok(color), Some(position), ) = ( color.parse::(), self.parse_pos(position), ) else { return false; }; self.stops.push(Stop::new(Some(color), Some(position))); } (Some(color), Some(position1), Some(position2)) => { if it.next().is_some() { return false; } let ( Ok(color), Some(position1), Some(position2), ) = ( color.parse::(), self.parse_pos(position1), self.parse_pos(position2), ) else { return false; }; self.stops.push(Stop::new(Some(color.clone()), Some(position1))); self.stops.push(Stop::new(Some(color), Some(position2))); } _ => { return false; } } true } #[rustfmt::skip] pub fn parse_pos(&self, s: &str) -> Option { s.strip_suffix('%') .and_then(|s| { s.parse().ok().map(|t: f32| { t / 100.0 * (self.dmax - self.dmin) + self.dmin }) }) .or_else(|| s.parse().ok()) } } fn split_by_comma(s: &str) -> impl Iterator { core::iter::from_fn({ let mut pos = 0; let mut inside = false; move || { if pos > s.len() { return None; } let start = pos; for (i, c) in s[pos..].char_indices() { if c == ',' && !inside { pos = pos + i + 1; return Some(&s[start..pos - 1]); } else if c == '(' { inside = true; } else if c == ')' { inside = false; } } pos = s.len() + 1; Some(&s[start..]) } }) } fn split_by_space(s: &str) -> impl Iterator { let mut pos = 0; let mut inside = false; core::iter::from_fn(move || { // Skip leading whitespace while pos < s.len() && s.as_bytes()[pos] == b' ' && !inside { pos += 1; } if pos >= s.len() { return None; } let start = pos; // Scan until we hit a space outside parentheses while pos < s.len() { let byte = s.as_bytes()[pos]; match byte { b'(' => inside = true, b')' => inside = false, b' ' if !inside => break, _ => {} } pos += 1; } let end = pos; // Move pos past the space (if we stopped on one) if pos < s.len() && s.as_bytes()[pos] == b' ' { pos += 1; } Some(&s[start..end]) }) } #[cfg(test)] mod tests { use super::*; use alloc::string::String; #[test] fn utils() { let s = "red, #fff, lime"; let mut it = split_by_comma(s); assert_eq!(it.next(), Some("red")); assert_eq!(it.next(), Some(" #fff")); assert_eq!(it.next(), Some(" lime")); assert_eq!(it.next(), None); let s = "#ff0000, rgb(255, 0, 0), hsv(120, 50%, 50%) 75%, blue"; let mut it = split_by_comma(s); assert_eq!(it.next(), Some("#ff0000")); assert_eq!(it.next(), Some(" rgb(255, 0, 0)")); assert_eq!(it.next(), Some(" hsv(120, 50%, 50%) 75%")); assert_eq!(it.next(), Some(" blue")); assert_eq!(it.next(), None); let s = "rgb(0, 0, 150) 0.75 1"; let mut it = split_by_space(s); assert_eq!(it.next(), Some("rgb(0, 0, 150)")); assert_eq!(it.next(), Some("0.75")); assert_eq!(it.next(), Some("1")); assert_eq!(it.next(), None); let s = "hsv(360, 50%, 30%) 0% 35%"; let mut it = split_by_space(s); assert_eq!(it.next(), Some("hsv(360, 50%, 30%)")); assert_eq!(it.next(), Some("0%")); assert_eq!(it.next(), Some("35%")); assert_eq!(it.next(), None); let s = " #00f 75%"; let mut it = split_by_space(s); assert_eq!(it.next(), Some("#00f")); assert_eq!(it.next(), Some("75%")); assert_eq!(it.next(), None); } #[test] fn test_parse_pos() { let mut gp = CSSGradientParser::new(); assert_eq!(gp.parse_pos("0.5"), Some(0.5)); assert_eq!(gp.parse_pos("50%"), Some(0.5)); assert_eq!(gp.parse_pos("1.1"), Some(1.1)); assert_eq!(gp.parse_pos("100%"), Some(1.0)); assert_eq!(gp.parse_pos("75%"), Some(0.75)); assert_eq!(gp.parse_pos(""), None); assert_eq!(gp.parse_pos("50x%"), None); assert_eq!(gp.parse_pos("y"), None); gp.set_domain(10.0, 30.0); assert_eq!(gp.parse_pos("0%"), Some(10.0)); assert_eq!(gp.parse_pos("50%"), Some(20.0)); assert_eq!(gp.parse_pos("100%"), Some(30.0)); assert_eq!(gp.parse_pos("17"), Some(17.0)); } #[test] fn test_parse_stop() { fn c(s: &str) -> Color { s.parse::().unwrap() } assert_eq!(c("#ff0000"), Color::new(1.0, 0.0, 0.0, 1.0)); assert_ne!(c("#ff0001"), Color::new(1.0, 0.0, 0.0, 1.0)); let mut gp = CSSGradientParser::new(); // color only assert!(gp.parse_stop("#f00")); assert_eq!(gp.stops[0], Stop::new(Some(c("#f00")), None)); // position only assert!(gp.parse_stop("75%")); assert_eq!(gp.stops[1], Stop::new(None, Some(0.75))); // color & position assert!(gp.parse_stop("#f00 10%")); assert_eq!(gp.stops[2], Stop::new(Some(c("#f00")), Some(0.1))); // color & double positions assert!(gp.parse_stop("#ff0 0% 50%")); assert_eq!(gp.stops[3], Stop::new(Some(c("#ff0")), Some(0.0))); assert_eq!(gp.stops[4], Stop::new(Some(c("#ff0")), Some(0.5))); assert_eq!(gp.stops.len(), 5); // invalid assert!(!gp.parse_stop("")); assert!(!gp.parse_stop("#zbb")); assert!(!gp.parse_stop("0x%")); assert!(!gp.parse_stop("#000 x")); assert!(!gp.parse_stop("#xyz 10%")); assert!(!gp.parse_stop("#f00 50% x")); assert!(!gp.parse_stop("#f00 x 0%")); assert!(!gp.parse_stop("#ffm 20% 30%")); assert!(!gp.parse_stop("#f00 20% 30% 50%")); assert_eq!(gp.stops.len(), 5); } fn colors2hex(colors: Vec) -> Vec { colors.iter().map(|c| c.to_css_hex()).collect() } #[test] fn parse_css_gradient() { let mut gp = CSSGradientParser::new(); let s = "#f00, #0f0"; let (colors, positions) = gp.parse(s).unwrap(); assert_eq!(colors2hex(colors), ["#ff0000", "#00ff00"]); assert_eq!(positions, [0.0, 1.0]); gp.reset(); gp.set_domain(-10.0, 10.0); let (colors, positions) = gp.parse(s).unwrap(); assert_eq!(colors2hex(colors), ["#ff0000", "#00ff00"]); assert_eq!(positions, [-10.0, 10.0]); let s = "#f00, #00f 75%, #0f0"; gp.reset(); let (colors, positions) = gp.parse(s).unwrap(); assert_eq!(colors2hex(colors), ["#ff0000", "#0000ff", "#00ff00"]); assert_eq!(positions, [0.0, 0.75, 1.0]); gp.reset(); gp.set_domain(0.0, 100.0); let (colors, positions) = gp.parse(s).unwrap(); assert_eq!(colors2hex(colors), ["#ff0000", "#0000ff", "#00ff00"]); assert_eq!(positions, [0.0, 75.0, 100.0]); let s = "#f00, #0f0 15, #00f"; gp.reset(); gp.set_domain(0.0, 20.0); let (colors, positions) = gp.parse(s).unwrap(); assert_eq!(colors2hex(colors), ["#ff0000", "#00ff00", "#0000ff"]); assert_eq!(positions, [0.0, 15.0, 20.0]); } } colorgrad-0.8.0/src/gradient/basis.rs000064400000000000000000000071431046102023000156450ustar 00000000000000use core::convert::TryFrom; use alloc::vec::Vec; use crate::{convert_colors, BlendMode, Color, Gradient, GradientBuilder, GradientBuilderError}; // Basis spline algorithm adapted from: // https://github.com/d3/d3-interpolate/blob/master/src/basis.js #[inline] fn basis(t1: f32, v0: f32, v1: f32, v2: f32, v3: f32) -> f32 { let t2 = t1 * t1; let t3 = t2 * t1; ((1.0 - 3.0 * t1 + 3.0 * t2 - t3) * v0 + (4.0 - 6.0 * t2 + 3.0 * t3) * v1 + (1.0 + 3.0 * t1 + 3.0 * t2 - 3.0 * t3) * v2 + t3 * v3) / 6.0 } #[cfg_attr( feature = "named-colors", doc = r##" ``` # fn main() -> Result<(), Box> { # use colorgrad::{GradientBuilder, BasisGradient}; use colorgrad::Gradient; let grad = GradientBuilder::new() .html_colors(&["deeppink", "gold", "seagreen"]) .build::()?; # Ok(()) # } ``` "## )] #[derive(Debug, Clone)] pub struct BasisGradient { values: Vec<[f32; 4]>, positions: Vec, domain: (f32, f32), mode: BlendMode, first_color: Color, last_color: Color, } impl BasisGradient { pub(crate) fn new(colors: &[Color], positions: Vec, mode: BlendMode) -> Self { let dmin = positions[0]; let dmax = positions[positions.len() - 1]; let first_color = colors[0].clone(); let last_color = colors[colors.len() - 1].clone(); Self { values: convert_colors(colors, mode).collect(), positions, domain: (dmin, dmax), mode, first_color, last_color, } } } impl Gradient for BasisGradient { fn at(&self, t: f32) -> Color { if t <= self.domain.0 { return self.first_color.clone(); } if t >= self.domain.1 { return self.last_color.clone(); } if t.is_nan() { return Color::new(0.0, 0.0, 0.0, 1.0); } let mut low = 0; let mut high = self.positions.len(); let n = high - 1; while low < high { let mid = (low + high) / 2; if self.positions[mid] < t { low = mid + 1; } else { high = mid; } } if low == 0 { low = 1; } let pos0 = self.positions[low - 1]; let pos1 = self.positions[low]; let val0 = self.values[low - 1]; let val1 = self.values[low]; let i = low - 1; let t = (t - pos0) / (pos1 - pos0); let mut zz = [0.0; 4]; for (j, (v1, v2)) in val0.iter().zip(val1.iter()).enumerate() { let v0 = if i > 0 { self.values[i - 1][j] } else { 2.0 * v1 - v2 }; let v3 = if i < (n - 1) { self.values[i + 2][j] } else { 2.0 * v2 - v1 }; zz[j] = basis(t, v0, *v1, *v2, v3); } let [c0, c1, c2, c3] = zz; match self.mode { BlendMode::Rgb => Color::new(c0, c1, c2, c3), BlendMode::LinearRgb => Color::from_linear_rgba(c0, c1, c2, c3), BlendMode::Oklab => Color::from_oklaba(c0, c1, c2, c3), #[cfg(feature = "lab")] BlendMode::Lab => Color::from_laba(c0, c1, c2, c3), } } fn domain(&self) -> (f32, f32) { self.domain } } impl TryFrom<&mut GradientBuilder> for BasisGradient { type Error = GradientBuilderError; fn try_from(gb: &mut GradientBuilder) -> Result { gb.prepare_build()?; Ok(Self::new(&gb.colors, gb.positions.clone(), gb.mode)) } } colorgrad-0.8.0/src/gradient/catmull_rom.rs000064400000000000000000000122471046102023000170630ustar 00000000000000use alloc::vec::Vec; use core::convert::TryFrom; use libm::powf; use crate::{convert_colors, BlendMode, Color, Gradient, GradientBuilder, GradientBuilderError}; // Catmull-Rom spline algorithm adapted from: // https://qroph.github.io/2018/07/30/smooth-paths-using-catmull-rom-splines.html #[cfg_attr( feature = "named-colors", doc = r##" ``` # fn main() -> Result<(), Box> { # use colorgrad::{GradientBuilder, CatmullRomGradient}; use colorgrad::Gradient; let grad = GradientBuilder::new() .html_colors(&["deeppink", "gold", "seagreen"]) .build::()?; # Ok(()) # } ``` "## )] #[derive(Debug, Clone)] pub struct CatmullRomGradient { segments: Vec<[[f32; 4]; 4]>, positions: Vec, domain: (f32, f32), mode: BlendMode, first_color: Color, last_color: Color, } fn to_catmull_segments(values: &[f32]) -> Vec<[f32; 4]> { let alpha = 0.5; let tension = 0.0; let n = values.len(); let mut vals = Vec::with_capacity(n + 2); vals.push(2.0 * values[0] - values[1]); for v in values.iter() { vals.push(*v); } vals.push(2.0 * values[n - 1] - values[n - 2]); let mut segments = Vec::with_capacity(n - 1); for i in 1..(vals.len() - 2) { let v0 = vals[i - 1]; let v1 = vals[i]; let v2 = vals[i + 1]; let v3 = vals[i + 2]; let t0 = 0.0; let t1 = t0 + powf((v0 - v1).abs(), alpha); let t2 = t1 + powf((v1 - v2).abs(), alpha); let t3 = t2 + powf((v2 - v3).abs(), alpha); let m1 = (1.0 - tension) * (t2 - t1) * ((v0 - v1) / (t0 - t1) - (v0 - v2) / (t0 - t2) + (v1 - v2) / (t1 - t2)); let m2 = (1.0 - tension) * (t2 - t1) * ((v1 - v2) / (t1 - t2) - (v1 - v3) / (t1 - t3) + (v2 - v3) / (t2 - t3)); let m1 = if m1.is_nan() { 0.0 } else { m1 }; let m2 = if m2.is_nan() { 0.0 } else { m2 }; let a = 2.0 * v1 - 2.0 * v2 + m1 + m2; let b = -3.0 * v1 + 3.0 * v2 - 2.0 * m1 - m2; let c = m1; let d = v1; segments.push([a, b, c, d]); } segments } impl CatmullRomGradient { pub(crate) fn new(colors: &[Color], positions: Vec, mode: BlendMode) -> Self { let n = colors.len(); let mut a = Vec::with_capacity(n); let mut b = Vec::with_capacity(n); let mut c = Vec::with_capacity(n); let mut d = Vec::with_capacity(n); for col in convert_colors(colors, mode) { a.push(col[0]); b.push(col[1]); c.push(col[2]); d.push(col[3]); } let s1 = to_catmull_segments(&a); let s2 = to_catmull_segments(&b); let s3 = to_catmull_segments(&c); let s4 = to_catmull_segments(&d); let dmin = positions[0]; let dmax = positions[positions.len() - 1]; let first_color = colors[0].clone(); let last_color = colors[colors.len() - 1].clone(); Self { segments: s1 .iter() .zip(&s2) .zip(&s3) .zip(&s4) .map(|(((a, b), c), d)| [*a, *b, *c, *d]) .collect(), positions, domain: (dmin, dmax), mode, first_color, last_color, } } } impl Gradient for CatmullRomGradient { fn at(&self, t: f32) -> Color { if t <= self.domain.0 { return self.first_color.clone(); } if t >= self.domain.1 { return self.last_color.clone(); } if t.is_nan() { return Color::new(0.0, 0.0, 0.0, 1.0); } let mut low = 0; let mut high = self.positions.len(); while low < high { let mid = (low + high) / 2; if self.positions[mid] < t { low = mid + 1; } else { high = mid; } } if low == 0 { low = 1; } let pos0 = self.positions[low - 1]; let pos1 = self.positions[low]; let [seg_a, seg_b, seg_c, seg_d] = self.segments[low - 1]; let t1 = (t - pos0) / (pos1 - pos0); let t2 = t1 * t1; let t3 = t2 * t1; let c0 = seg_a[0] * t3 + seg_a[1] * t2 + seg_a[2] * t1 + seg_a[3]; let c1 = seg_b[0] * t3 + seg_b[1] * t2 + seg_b[2] * t1 + seg_b[3]; let c2 = seg_c[0] * t3 + seg_c[1] * t2 + seg_c[2] * t1 + seg_c[3]; let c3 = seg_d[0] * t3 + seg_d[1] * t2 + seg_d[2] * t1 + seg_d[3]; match self.mode { BlendMode::Rgb => Color::new(c0, c1, c2, c3), BlendMode::LinearRgb => Color::from_linear_rgba(c0, c1, c2, c3), BlendMode::Oklab => Color::from_oklaba(c0, c1, c2, c3), #[cfg(feature = "lab")] BlendMode::Lab => Color::from_laba(c0, c1, c2, c3), } } fn domain(&self) -> (f32, f32) { self.domain } } impl TryFrom<&mut GradientBuilder> for CatmullRomGradient { type Error = GradientBuilderError; fn try_from(gb: &mut GradientBuilder) -> Result { gb.prepare_build()?; Ok(Self::new(&gb.colors, gb.positions.clone(), gb.mode)) } } colorgrad-0.8.0/src/gradient/gimp.rs000064400000000000000000000274621046102023000155060ustar 00000000000000// References: // https://gitlab.gnome.org/GNOME/gimp/-/blob/master/devel-docs/ggr.txt // https://gitlab.gnome.org/GNOME/gimp/-/blob/master/app/core/gimpgradient.c // https://gitlab.gnome.org/GNOME/gimp/-/blob/master/app/core/gimpgradient-load.c use crate::interpolate_linear; use crate::{Color, Gradient}; use alloc::string::{String, ToString}; use alloc::vec::Vec; use std::{ error, f32::consts::{FRAC_PI_2, LN_2, PI}, fmt, io::BufRead, }; #[derive(Debug)] pub struct ParseGgrError { message: String, line: usize, } impl error::Error for ParseGgrError {} impl fmt::Display for ParseGgrError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} (line {})", &self.message, self.line) } } #[derive(Debug, Copy, Clone)] enum BlendingType { Linear, Curved, Sinusoidal, SphericalIncreasing, SphericalDecreasing, Step, } #[derive(Debug, Copy, Clone)] enum ColoringType { Rgb, HsvCw, HsvCcw, } #[derive(Debug, Clone)] struct GimpSegment { // Left endpoint color lcolor: [f32; 4], // Right endpoint color rcolor: [f32; 4], // Left endpoint coordinate lpos: f32, // Midpoint coordinate mpos: f32, // Right endpoint coordinate rpos: f32, // Blending function type blending_type: BlendingType, // Coloring type coloring_type: ColoringType, } /// Parse GIMP gradient (ggr) /// /// # Example /// /// ``` /// use colorgrad::{Color, Gradient}; /// use std::fs::File; /// use std::io::BufReader; /// /// # fn main() -> Result<(), Box> { /// let input = File::open("examples/ggr/Abstract_1.ggr")?; /// let buf = BufReader::new(input); /// let col = Color::default(); /// let grad = colorgrad::GimpGradient::new(buf, &col, &col)?; /// /// assert_eq!(grad.name(), "Abstract 1"); /// # Ok(()) /// # } /// ``` /// ![img](https://raw.githubusercontent.com/mazznoer/colorgrad-rs/master/docs/images/ggr_abstract_1.png) #[derive(Debug, Clone)] pub struct GimpGradient { name: String, segments: Vec, dmin: f32, dmax: f32, } impl GimpGradient { pub fn new(r: R, foreground: &Color, background: &Color) -> Result where R: BufRead, { parse_ggr(r, foreground, background) } pub fn name(&self) -> &str { &self.name } } impl Gradient for GimpGradient { fn at(&self, t: f32) -> Color { if t < self.dmin || t > self.dmax || t.is_nan() { return Color::new(0.0, 0.0, 0.0, 1.0); } let mut low = 0; let mut high = self.segments.len(); let mut mid = 0; while low < high { mid = (low + high) / 2; if t > self.segments[mid].rpos { low = mid + 1; } else if t < self.segments[mid].lpos { high = mid; } else { break; } } let seg = &self.segments[mid]; let seg_len = seg.rpos - seg.lpos; let (middle, pos) = if seg_len < f32::EPSILON { (0.5, 0.5) } else { ((seg.mpos - seg.lpos) / seg_len, (t - seg.lpos) / seg_len) }; let f = match seg.blending_type { BlendingType::Linear => calc_linear_factor(middle, pos), BlendingType::Curved => { if middle < f32::EPSILON { match seg.coloring_type { ColoringType::Rgb => { let [r, g, b, a] = seg.rcolor; return Color::new(r, g, b, a); } _ => { let [h, s, v, a] = seg.rcolor; return Color::from_hsva(h, s, v, a); } } } else if (1.0 - middle).abs() < f32::EPSILON { match seg.coloring_type { ColoringType::Rgb => { let [r, g, b, a] = seg.lcolor; return Color::new(r, g, b, a); } _ => { let [h, s, v, a] = seg.lcolor; return Color::from_hsva(h, s, v, a); } } } else { (-LN_2 * pos.log10() / middle.log10()).exp() } } BlendingType::Sinusoidal => { let f = calc_linear_factor(middle, pos); ((-FRAC_PI_2 + (PI * f)).sin() + 1.0) / 2.0 } BlendingType::SphericalIncreasing => { let f = calc_linear_factor(middle, pos) - 1.0; (1.0 - f * f).sqrt() } BlendingType::SphericalDecreasing => { let f = calc_linear_factor(middle, pos); 1.0 - (1.0 - f * f).sqrt() } BlendingType::Step => { if pos >= middle { match seg.coloring_type { ColoringType::Rgb => { let [r, g, b, a] = seg.rcolor; return Color::new(r, g, b, a); } _ => { let [h, s, v, a] = seg.rcolor; return Color::from_hsva(h, s, v, a); } } } else { match seg.coloring_type { ColoringType::Rgb => { let [r, g, b, a] = seg.lcolor; return Color::new(r, g, b, a); } _ => { let [h, s, v, a] = seg.lcolor; return Color::from_hsva(h, s, v, a); } } } } }; match seg.coloring_type { ColoringType::Rgb => Color::from(interpolate_linear(&seg.lcolor, &seg.rcolor, f)), ColoringType::HsvCcw => blend_hsv_ccw(&seg.lcolor, &seg.rcolor, f), ColoringType::HsvCw => blend_hsv_cw(&seg.lcolor, &seg.rcolor, f), } } } #[inline] fn calc_linear_factor(middle: f32, pos: f32) -> f32 { if pos <= middle { if middle < f32::EPSILON { 0.0 } else { 0.5 * pos / middle } } else { let pos = pos - middle; let middle = 1.0 - middle; if middle < f32::EPSILON { 1.0 } else { 0.5 + 0.5 * pos / middle } } } fn parse_ggr( r: R, foreground: &Color, background: &Color, ) -> Result { let mut segments = Vec::new(); let mut seg_n = 0; let mut seg_x = 0; let mut name = "".to_string(); for (line_no, line) in r.lines().enumerate() { if let Ok(s) = line { if line_no == 0 { let s = s.trim_start_matches('\u{feff}'); if s != "GIMP Gradient" { return Err(ParseGgrError { message: "invalid header".to_string(), line: 1, }); } continue; } else if line_no == 1 { if !s.starts_with("Name:") { return Err(ParseGgrError { message: "invalid header".to_string(), line: 2, }); } name = s[5..].trim().to_string(); continue; } else if line_no == 2 { if let Ok(n) = s.parse::() { seg_n = n; } else { return Err(ParseGgrError { message: "invalid header".to_string(), line: 3, }); } continue; } if line_no >= seg_n + 3 { break; } seg_x += 1; if let Some(seg) = parse_segment(&s, foreground, background) { segments.push(seg); } else { return Err(ParseGgrError { message: "invalid segment".to_string(), line: line_no + 1, }); } } } if seg_x < seg_n { return Err(ParseGgrError { message: "wrong segments count".to_string(), line: 3, }); } if segments.is_empty() { return Err(ParseGgrError { message: "no segment".to_string(), line: 4, }); } Ok(GimpGradient { name, segments, dmin: 0.0, dmax: 1.0, }) } fn parse_segment(s: &str, foreground: &Color, background: &Color) -> Option { let d: Result, _> = s.split_whitespace().map(|x| x.parse::()).collect(); let d = if let Ok(t) = d { t } else { return None; }; if d.len() != 13 && d.len() != 15 { return None; } let blending_type = match d[11] as isize { 0 => BlendingType::Linear, 1 => BlendingType::Curved, 2 => BlendingType::Sinusoidal, 3 => BlendingType::SphericalIncreasing, 4 => BlendingType::SphericalDecreasing, 5 => BlendingType::Step, _ => return None, }; let coloring_type = match d[12] as isize { 0 => ColoringType::Rgb, 1 => ColoringType::HsvCcw, 2 => ColoringType::HsvCw, _ => return None, }; let lcolor_code = if d.len() == 15 { d[13] as isize } else { 0 }; let rcolor_code = if d.len() == 15 { d[14] as isize } else { 0 }; let lcolor = match lcolor_code { 0 => Color::new(d[3], d[4], d[5], d[6]), 1 => foreground.clone(), 2 => { let [r, g, b, _] = foreground.to_array(); Color::new(r, g, b, 0.0) } 3 => background.clone(), 4 => { let [r, g, b, _] = background.to_array(); Color::new(r, g, b, 0.0) } _ => return None, }; let rcolor = match rcolor_code { 0 => Color::new(d[7], d[8], d[9], d[10]), 1 => foreground.clone(), 2 => { let [r, g, b, _] = foreground.to_array(); Color::new(r, g, b, 0.0) } 3 => background.clone(), 4 => { let [r, g, b, _] = background.to_array(); Color::new(r, g, b, 0.0) } _ => return None, }; let lcolor = match coloring_type { ColoringType::Rgb => lcolor.to_array(), _ => lcolor.to_hsva(), }; let rcolor = match coloring_type { ColoringType::Rgb => rcolor.to_array(), _ => rcolor.to_hsva(), }; Some(GimpSegment { lcolor, rcolor, lpos: d[0], mpos: d[1], rpos: d[2], blending_type, coloring_type, }) } fn blend_hsv_ccw(c1: &[f32; 4], c2: &[f32; 4], t: f32) -> Color { let [h1, s1, v1, a1] = c1; let [h2, s2, v2, a2] = c2; let hue = if h1 < h2 { h1 + ((h2 - h1) * t) } else { let h = h1 + ((360.0 - (h1 - h2)) * t); if h > 360.0 { h - 360.0 } else { h } }; Color::from_hsva( hue, s1 + t * (s2 - s1), v1 + t * (v2 - v1), a1 + t * (a2 - a1), ) } fn blend_hsv_cw(c1: &[f32; 4], c2: &[f32; 4], t: f32) -> Color { let [h1, s1, v1, a1] = c1; let [h2, s2, v2, a2] = c2; let hue = if h2 < h1 { h1 - ((h1 - h2) * t) } else { let h = h1 - ((360.0 - (h2 - h1)) * t); if h < 0.0 { h + 360.0 } else { h } }; Color::from_hsva( hue, s1 + t * (s2 - s1), v1 + t * (v2 - v1), a1 + t * (a2 - a1), ) } colorgrad-0.8.0/src/gradient/inverse.rs000064400000000000000000000017301046102023000162130ustar 00000000000000use crate::{Color, Gradient}; use alloc::boxed::Box; #[cfg_attr( feature = "preset", doc = r##" A special gradient that inverts the inner gradient. The minimum value of the inner gradient will be the maximum value of the inverse gradient and vice versa. ``` use colorgrad::Gradient; let gradient = colorgrad::preset::magma(); let inverted = gradient.inverse(); assert_eq!(gradient.at(0.9).to_rgba8(), inverted.at(0.1).to_rgba8()); for color in inverted.colors_iter(15) { println!("{}", color.to_css_hex()); } ``` "## )] #[derive(Clone)] pub struct InverseGradient<'a> { inner: Box, } impl<'a> InverseGradient<'a> { pub fn new(inner: Box) -> Self { Self { inner } } } impl Gradient for InverseGradient<'_> { fn at(&self, t: f32) -> Color { let (min, max) = self.inner.domain(); self.inner.at(max - t + min) } fn domain(&self) -> (f32, f32) { self.inner.domain() } } colorgrad-0.8.0/src/gradient/linear.rs000064400000000000000000000054221046102023000160140ustar 00000000000000use alloc::vec::Vec; use core::convert::TryFrom; use crate::{convert_colors, interpolate_linear}; use crate::{BlendMode, Color, Gradient, GradientBuilder, GradientBuilderError}; #[cfg_attr( feature = "named-colors", doc = r##" ``` # fn main() -> Result<(), Box> { # use colorgrad::{BlendMode, GradientBuilder, LinearGradient}; use colorgrad::Gradient; let grad = GradientBuilder::new() .html_colors(&["deeppink", "gold", "seagreen"]) .mode(BlendMode::Oklab) .build::()?; # Ok(()) # } ``` "## )] #[derive(Debug, Clone)] pub struct LinearGradient { stops: Vec<(f32, [f32; 4])>, domain: (f32, f32), mode: BlendMode, first_color: Color, last_color: Color, } impl LinearGradient { pub(crate) fn new(colors: &[Color], positions: &[f32], mode: BlendMode) -> Self { let dmin = positions[0]; let dmax = positions[positions.len() - 1]; let first_color = colors[0].clone(); let last_color = colors[colors.len() - 1].clone(); let colors = convert_colors(colors, mode); Self { stops: positions.iter().zip(colors).map(|(p, c)| (*p, c)).collect(), domain: (dmin, dmax), mode, first_color, last_color, } } } impl Gradient for LinearGradient { fn at(&self, t: f32) -> Color { if t <= self.domain.0 { return self.first_color.clone(); } if t >= self.domain.1 { return self.last_color.clone(); } if t.is_nan() { return Color::new(0.0, 0.0, 0.0, 1.0); } let mut low = 0; let mut high = self.stops.len(); while low < high { let mid = (low + high) / 2; if self.stops[mid].0 < t { low = mid + 1; } else { high = mid; } } if low == 0 { low = 1; } let (pos_0, col_0) = self.stops[low - 1]; let (pos_1, col_1) = self.stops[low]; let t = (t - pos_0) / (pos_1 - pos_0); let [a, b, c, d] = interpolate_linear(&col_0, &col_1, t); match self.mode { BlendMode::Rgb => Color::new(a, b, c, d), BlendMode::LinearRgb => Color::from_linear_rgba(a, b, c, d), BlendMode::Oklab => Color::from_oklaba(a, b, c, d), #[cfg(feature = "lab")] BlendMode::Lab => Color::from_laba(a, b, c, d), } } fn domain(&self) -> (f32, f32) { self.domain } } impl TryFrom<&mut GradientBuilder> for LinearGradient { type Error = GradientBuilderError; fn try_from(gb: &mut GradientBuilder) -> Result { gb.prepare_build()?; Ok(Self::new(&gb.colors, &gb.positions, gb.mode)) } } colorgrad-0.8.0/src/gradient/sharp.rs000064400000000000000000000056431046102023000156640ustar 00000000000000use crate::{convert_colors, linspace, BlendMode, Color, Gradient}; use alloc::vec::Vec; #[cfg_attr( feature = "preset", doc = r##" ``` use colorgrad::Gradient; let g = colorgrad::preset::rainbow().sharp(11, 0.0); ```"## )] #[derive(Debug, Clone)] pub struct SharpGradient { stops: Vec<(f32, [f32; 4])>, domain: (f32, f32), first_color: Color, last_color: Color, } impl SharpGradient { pub(crate) fn new(colors_in: &[Color], domain: (f32, f32), t: f32) -> Self { let n = colors_in.len(); let mut colors = Vec::with_capacity(n * 2); for c in colors_in { colors.push(c.clone()); colors.push(c.clone()); } let t = t.clamp(0.0, 1.0) * (domain.1 - domain.0) / n as f32 / 4.0; let p: Vec<_> = linspace(domain.0, domain.1, n + 1).collect(); let mut positions = Vec::with_capacity(n * 2); let mut j = 0; for i in 0..n { positions.push(p[i]); if j > 0 { positions[j] += t; } j += 1; positions.push(p[i + 1]); if j < colors.len() - 1 { positions[j] -= t; } j += 1; } let colors = convert_colors(&colors, BlendMode::Rgb); let first_color = colors_in[0].clone(); let last_color = colors_in[n - 1].clone(); Self { stops: positions.iter().zip(colors).map(|(p, c)| (*p, c)).collect(), domain, first_color, last_color, } } } impl Gradient for SharpGradient { fn at(&self, t: f32) -> Color { if t <= self.domain.0 { return self.first_color.clone(); } if t >= self.domain.1 { return self.last_color.clone(); } if t.is_nan() { return Color::new(0.0, 0.0, 0.0, 1.0); } let mut low = 0; let mut high = self.stops.len(); while low < high { let mid = (low + high) / 2; if self.stops[mid].0 < t { low = mid + 1; } else { high = mid; } } if low == 0 { low = 1; } let i = low - 1; let (pos_0, col_0) = &self.stops[i]; let (pos_1, col_1) = &self.stops[low]; if i & 1 == 0 { return Color::new(col_0[0], col_0[1], col_0[2], col_0[3]); } let t = (t - pos_0) / (pos_1 - pos_0); let [a, b, c, d] = smoothstep(col_0, col_1, t); Color::new(a, b, c, d) } fn domain(&self) -> (f32, f32) { self.domain } } #[inline] fn smoothstep(a: &[f32; 4], b: &[f32; 4], t: f32) -> [f32; 4] { [ (b[0] - a[0]) * (3.0 - t * 2.0) * t * t + a[0], (b[1] - a[1]) * (3.0 - t * 2.0) * t * t + a[1], (b[2] - a[2]) * (3.0 - t * 2.0) * t * t + a[2], (b[3] - a[3]) * (3.0 - t * 2.0) * t * t + a[3], ] } colorgrad-0.8.0/src/gradient.rs000064400000000000000000000005751046102023000145460ustar 00000000000000mod basis; mod catmull_rom; mod inverse; mod linear; mod sharp; pub use basis::BasisGradient; pub use catmull_rom::CatmullRomGradient; pub use inverse::InverseGradient; pub use linear::LinearGradient; pub use sharp::SharpGradient; #[cfg(all(feature = "ggr", feature = "std"))] mod gimp; #[cfg(all(feature = "ggr", feature = "std"))] pub use gimp::{GimpGradient, ParseGgrError}; colorgrad-0.8.0/src/lib.rs000064400000000000000000000064251046102023000135170ustar 00000000000000#![cfg_attr( all(feature = "preset", feature = "named-colors"), doc = r##" # Overview Rust color scales library for data visualization, charts, games, maps, generative art and others. ## Usage Using preset gradient: ``` use colorgrad::Gradient; let g = colorgrad::preset::rainbow(); assert_eq!(g.domain(), (0.0, 1.0)); // all preset gradients are in the domain [0..1] assert_eq!(g.at(0.5).to_rgba8(), [175, 240, 91, 255]); assert_eq!(g.at(0.5).to_css_hex(), "#aff05b"); for color in g.colors_iter(20) { println!("{:?}", color.to_rgba8()); } ``` Custom gradient: ``` # fn main() -> Result<(), Box> { # use colorgrad::{Color, GradientBuilder, LinearGradient}; use colorgrad::Gradient; let g = GradientBuilder::new() .colors(&[ Color::from_rgba8(255, 0, 0, 255), Color::from_rgba8(0, 255, 0, 255), ]) .build::()?; for color in g.colors_iter(20) { println!("{:?}", color.to_rgba8()); } # Ok(()) # } ``` Using HTML color format: ``` # fn main() -> Result<(), Box> { # use colorgrad::{GradientBuilder, LinearGradient}; let g = GradientBuilder::new() .html_colors(&["red", "#abc", "gold"]) .build::()?; # Ok(()) # } ``` Using CSS gradient format: ``` # fn main() -> Result<(), Box> { # use colorgrad::{GradientBuilder, LinearGradient}; let g = GradientBuilder::new() .css("gold, 35%, #f00") .build::()?; # Ok(()) # } ``` ## Examples ### Gradient Image ``` # fn main() -> Result<(), Box> { use colorgrad::Gradient; let grad = colorgrad::GradientBuilder::new() .html_colors(&["deeppink", "gold", "seagreen"]) .build::()?; let width = 1500; let height = 70; let imgbuf = image::RgbaImage::from_fn(width, height, |x, _| { image::Rgba(grad.at(x as f32 / width as f32).to_rgba8()) }); imgbuf.save("gradient.png")?; # Ok(()) # } ``` Example output: ![img](https://raw.githubusercontent.com/mazznoer/colorgrad-rs/master/docs/images/example-gradient.png) ### Colored Noise ```ignore use colorgrad::Gradient; use noise::NoiseFn; let scale = 0.015; let grad = colorgrad::preset::rainbow().sharp(5, 0.15); let ns = noise::OpenSimplex::new(); let imgbuf = image::RgbaImage::from_fn(600, 350, |x, y| { let t = ns.get([x as f32 * scale, y as f32 * scale]); let t = remap(t, -0.5, 0.5, 0.0, 1.0); image::Rgba(grad.at(t).to_rgba8()) }); imgbuf.save("noise.png")?; // Map t which is in range [a, b] to range [c, d] fn remap(t: f32, a: f32, b: f32, c: f32, d: f32) -> f32 { (t - a) * ((d - c) / (b - a)) + c } ``` Example output: ![img](https://raw.githubusercontent.com/mazznoer/colorgrad-rs/master/docs/images/example-noise.png) ## Preset Gradients [See here](https://github.com/mazznoer/colorgrad-rs/blob/master/PRESET.md) "## )] #![no_std] #![forbid(unsafe_code)] #[cfg(feature = "std")] extern crate std; extern crate alloc; pub use csscolorparser::{Color, ParseColorError}; mod core; pub use core::{BlendMode, Gradient, GradientColors}; mod builder; pub use builder::{GradientBuilder, GradientBuilderError}; mod gradient; pub use gradient::*; #[cfg(feature = "preset")] pub mod preset; mod utils; use utils::*; mod css_gradient; use css_gradient::CSSGradientParser; colorgrad-0.8.0/src/preset.rs000064400000000000000000000222241046102023000142460ustar 00000000000000//! # Preset Gradients //! //! ``` //! use colorgrad::Gradient; //! let grad = colorgrad::preset::rainbow(); //! //! assert_eq!(grad.domain(), (0.0, 1.0)); // all preset gradients are in the domain [0..1] //! assert_eq!(grad.at(0.25).to_rgba8(), [255, 94, 99, 255]); //! assert_eq!(grad.at(0.75).to_rgba8(), [26, 199, 194, 255]); //! assert_eq!(grad.at(0.37).to_css_hex(), "#f2a42f"); //! ``` use alloc::vec::Vec; use core::f32::consts::{FRAC_PI_3, PI}; use libm::{cosf, roundf, sinf}; use crate::{linspace, BasisGradient, BlendMode, Color, Gradient}; const PI2_3: f32 = PI * 2.0 / 3.0; // Sinebow #[derive(Debug, Clone)] pub struct SinebowGradient {} pub fn sinebow() -> SinebowGradient { SinebowGradient {} } impl Gradient for SinebowGradient { fn at(&self, t: f32) -> Color { let t = (0.5 - t) * PI; let r = sinf(t); let g = sinf(t + FRAC_PI_3); let b = sinf(t + PI2_3); Color::new( (r * r).clamp(0.0, 1.0), (g * g).clamp(0.0, 1.0), (b * b).clamp(0.0, 1.0), 1.0, ) } } // Turbo #[derive(Debug, Clone)] pub struct TurboGradient {} pub fn turbo() -> TurboGradient { TurboGradient {} } impl Gradient for TurboGradient { fn at(&self, t: f32) -> Color { let t = t.clamp(0.0, 1.0); let r = roundf( 34.61 + t * (1172.33 - t * (10793.56 - t * (33300.12 - t * (38394.49 - t * 14825.05)))), ); let g = roundf( 23.31 + t * (557.33 + t * (1225.33 - t * (3574.96 - t * (1073.77 + t * 707.56)))), ); let b = roundf( 27.2 + t * (3211.1 - t * (15327.97 - t * (27814.0 - t * (22569.18 - t * 6838.66)))), ); Color::new( (r / 255.0).clamp(0.0, 1.0), (g / 255.0).clamp(0.0, 1.0), (b / 255.0).clamp(0.0, 1.0), 1.0, ) } } // Cividis #[derive(Debug, Clone)] pub struct CividisGradient {} pub fn cividis() -> CividisGradient { CividisGradient {} } impl Gradient for CividisGradient { fn at(&self, t: f32) -> Color { let t = t.clamp(0.0, 1.0); let r = roundf( -4.54 - t * (35.34 - t * (2381.73 - t * (6402.7 - t * (7024.72 - t * 2710.57)))), ); let g = roundf(32.49 + t * (170.73 + t * (52.82 - t * (131.46 - t * (176.58 - t * 67.37))))); let b = roundf( 81.24 + t * (442.36 - t * (2482.43 - t * (6167.24 - t * (6614.94 - t * 2475.67)))), ); Color::new( (r / 255.0).clamp(0.0, 1.0), (g / 255.0).clamp(0.0, 1.0), (b / 255.0).clamp(0.0, 1.0), 1.0, ) } } // Cubehelix #[derive(Debug, Clone)] struct Cubehelix { h: f32, s: f32, l: f32, } impl Cubehelix { fn to_color(&self) -> Color { let h = (self.h + 120.0) * (PI / 180.0); let l = self.l; let a = self.s * l * (1.0 - l); let cosh = cosf(h); let sinh = sinf(h); let r = l - a * (0.14861 * cosh - 1.78277 * sinh); let g = l - a * (0.29227 * cosh + 0.90649 * sinh); let b = l + a * (1.97294 * cosh); Color::new(r.clamp(0.0, 1.0), g.clamp(0.0, 1.0), b.clamp(0.0, 1.0), 1.0) } fn interpolate(&self, other: &Cubehelix, t: f32) -> Cubehelix { Cubehelix { h: self.h + t * (other.h - self.h), s: self.s + t * (other.s - self.s), l: self.l + t * (other.l - self.l), } } } // Cubehelix gradient #[derive(Debug, Clone)] pub struct CubehelixGradient { start: Cubehelix, end: Cubehelix, } impl Gradient for CubehelixGradient { fn at(&self, t: f32) -> Color { self.start .interpolate(&self.end, t.clamp(0.0, 1.0)) .to_color() } } pub fn cubehelix_default() -> CubehelixGradient { CubehelixGradient { start: Cubehelix { h: 300.0, s: 0.5, l: 0.0, }, end: Cubehelix { h: -240.0, s: 0.5, l: 1.0, }, } } pub fn warm() -> CubehelixGradient { CubehelixGradient { start: Cubehelix { h: -100.0, s: 0.75, l: 0.35, }, end: Cubehelix { h: 80.0, s: 1.5, l: 0.8, }, } } pub fn cool() -> CubehelixGradient { CubehelixGradient { start: Cubehelix { h: 260.0, s: 0.75, l: 0.35, }, end: Cubehelix { h: 80.0, s: 1.5, l: 0.8, }, } } // Rainbow #[derive(Debug, Clone)] pub struct RainbowGradient {} pub fn rainbow() -> RainbowGradient { RainbowGradient {} } impl Gradient for RainbowGradient { fn at(&self, t: f32) -> Color { let t = t.clamp(0.0, 1.0); let ts = (t - 0.5).abs(); Cubehelix { h: 360.0 * t - 100.0, s: 1.5 - 1.5 * ts, l: 0.8 - 0.9 * ts, } .to_color() } } // --- fn build_preset(colors: &[u32]) -> BasisGradient { fn to_color(c: &u32) -> Color { Color::from_rgba8( ((c >> 16) & 0xff) as _, ((c >> 8) & 0xff) as _, (c & 0xff) as _, 255, ) } let colors = colors.iter().map(to_color).collect::>(); let pos = linspace(0.0, 1.0, colors.len()).collect(); BasisGradient::new(&colors, pos, BlendMode::Rgb) } macro_rules! preset { ($colors:expr; $name:ident) => { pub fn $name() -> BasisGradient { build_preset($colors) } }; } // Diverging preset!(&[0x543005, 0x8c510a, 0xbf812d, 0xdfc27d, 0xf6e8c3, 0xf5f5f5, 0xc7eae5, 0x80cdc1, 0x35978f, 0x01665e, 0x003c30]; br_bg); preset!(&[0x40004b, 0x762a83, 0x9970ab, 0xc2a5cf, 0xe7d4e8, 0xf7f7f7, 0xd9f0d3, 0xa6dba0, 0x5aae61, 0x1b7837, 0x00441b]; pr_gn); preset!(&[0x8e0152, 0xc51b7d, 0xde77ae, 0xf1b6da, 0xfde0ef, 0xf7f7f7, 0xe6f5d0, 0xb8e186, 0x7fbc41, 0x4d9221, 0x276419]; pi_yg); preset!(&[0x2d004b, 0x542788, 0x8073ac, 0xb2abd2, 0xd8daeb, 0xf7f7f7, 0xfee0b6, 0xfdb863, 0xe08214, 0xb35806, 0x7f3b08]; pu_or); preset!(&[0x67001f, 0xb2182b, 0xd6604d, 0xf4a582, 0xfddbc7, 0xf7f7f7, 0xd1e5f0, 0x92c5de, 0x4393c3, 0x2166ac, 0x053061]; rd_bu); preset!(&[0x67001f, 0xb2182b, 0xd6604d, 0xf4a582, 0xfddbc7, 0xffffff, 0xe0e0e0, 0xbababa, 0x878787, 0x4d4d4d, 0x1a1a1a]; rd_gy); preset!(&[0xa50026, 0xd73027, 0xf46d43, 0xfdae61, 0xfee090, 0xffffbf, 0xe0f3f8, 0xabd9e9, 0x74add1, 0x4575b4, 0x313695]; rd_yl_bu); preset!(&[0xa50026, 0xd73027, 0xf46d43, 0xfdae61, 0xfee08b, 0xffffbf, 0xd9ef8b, 0xa6d96a, 0x66bd63, 0x1a9850, 0x006837]; rd_yl_gn); preset!(&[0x9e0142, 0xd53e4f, 0xf46d43, 0xfdae61, 0xfee08b, 0xffffbf, 0xe6f598, 0xabdda4, 0x66c2a5, 0x3288bd, 0x5e4fa2]; spectral); // Sequential (Single Hue) preset!(&[0xf7fbff, 0xdeebf7, 0xc6dbef, 0x9ecae1, 0x6baed6, 0x4292c6, 0x2171b5, 0x08519c, 0x08306b]; blues); preset!(&[0xf7fcf5, 0xe5f5e0, 0xc7e9c0, 0xa1d99b, 0x74c476, 0x41ab5d, 0x238b45, 0x006d2c, 0x00441b]; greens); preset!(&[0xffffff, 0xf0f0f0, 0xd9d9d9, 0xbdbdbd, 0x969696, 0x737373, 0x525252, 0x252525, 0x000000]; greys); preset!(&[0xfff5eb, 0xfee6ce, 0xfdd0a2, 0xfdae6b, 0xfd8d3c, 0xf16913, 0xd94801, 0xa63603, 0x7f2704]; oranges); preset!(&[0xfcfbfd, 0xefedf5, 0xdadaeb, 0xbcbddc, 0x9e9ac8, 0x807dba, 0x6a51a3, 0x54278f, 0x3f007d]; purples); preset!(&[0xfff5f0, 0xfee0d2, 0xfcbba1, 0xfc9272, 0xfb6a4a, 0xef3b2c, 0xcb181d, 0xa50f15, 0x67000d]; reds); // Sequential (Multi-Hue) preset!(&[0x440154, 0x482777, 0x3f4a8a, 0x31678e, 0x26838f, 0x1f9d8a, 0x6cce5a, 0xb6de2b, 0xfee825]; viridis); preset!(&[0x000004, 0x170b3a, 0x420a68, 0x6b176e, 0x932667, 0xbb3654, 0xdd513a, 0xf3771a, 0xfca50a, 0xf6d644, 0xfcffa4]; inferno); preset!(&[0x000004, 0x140e37, 0x3b0f70, 0x641a80, 0x8c2981, 0xb63679, 0xde4968, 0xf66f5c, 0xfe9f6d, 0xfece91, 0xfcfdbf]; magma); preset!(&[0x0d0887, 0x42039d, 0x6a00a8, 0x900da3, 0xb12a90, 0xcb4678, 0xe16462, 0xf1834b, 0xfca636, 0xfccd25, 0xf0f921]; plasma); preset!(&[0xf7fcfd, 0xe5f5f9, 0xccece6, 0x99d8c9, 0x66c2a4, 0x41ae76, 0x238b45, 0x006d2c, 0x00441b]; bu_gn); preset!(&[0xf7fcfd, 0xe0ecf4, 0xbfd3e6, 0x9ebcda, 0x8c96c6, 0x8c6bb1, 0x88419d, 0x810f7c, 0x4d004b]; bu_pu); preset!(&[0xf7fcf0, 0xe0f3db, 0xccebc5, 0xa8ddb5, 0x7bccc4, 0x4eb3d3, 0x2b8cbe, 0x0868ac, 0x084081]; gn_bu); preset!(&[0xfff7ec, 0xfee8c8, 0xfdd49e, 0xfdbb84, 0xfc8d59, 0xef6548, 0xd7301f, 0xb30000, 0x7f0000]; or_rd); preset!(&[0xfff7fb, 0xece2f0, 0xd0d1e6, 0xa6bddb, 0x67a9cf, 0x3690c0, 0x02818a, 0x016c59, 0x014636]; pu_bu_gn); preset!(&[0xfff7fb, 0xece7f2, 0xd0d1e6, 0xa6bddb, 0x74a9cf, 0x3690c0, 0x0570b0, 0x045a8d, 0x023858]; pu_bu); preset!(&[0xf7f4f9, 0xe7e1ef, 0xd4b9da, 0xc994c7, 0xdf65b0, 0xe7298a, 0xce1256, 0x980043, 0x67001f]; pu_rd); preset!(&[0xfff7f3, 0xfde0dd, 0xfcc5c0, 0xfa9fb5, 0xf768a1, 0xdd3497, 0xae017e, 0x7a0177, 0x49006a]; rd_pu); preset!(&[0xffffd9, 0xedf8b1, 0xc7e9b4, 0x7fcdbb, 0x41b6c4, 0x1d91c0, 0x225ea8, 0x253494, 0x081d58]; yl_gn_bu); preset!(&[0xffffe5, 0xf7fcb9, 0xd9f0a3, 0xaddd8e, 0x78c679, 0x41ab5d, 0x238443, 0x006837, 0x004529]; yl_gn); preset!(&[0xffffe5, 0xfff7bc, 0xfee391, 0xfec44f, 0xfe9929, 0xec7014, 0xcc4c02, 0x993404, 0x662506]; yl_or_br); preset!(&[0xffffcc, 0xffeda0, 0xfed976, 0xfeb24c, 0xfd8d3c, 0xfc4e2a, 0xe31a1c, 0xbd0026, 0x800026]; yl_or_rd); colorgrad-0.8.0/src/utils.rs000064400000000000000000000037551046102023000141140ustar 00000000000000use crate::{BlendMode, Color}; pub(crate) fn convert_colors( colors: &[Color], mode: BlendMode, ) -> impl Iterator + use<'_> { colors.iter().map(move |c| match mode { BlendMode::Rgb => c.to_array(), BlendMode::LinearRgb => c.to_linear_rgba(), BlendMode::Oklab => c.to_oklaba(), #[cfg(feature = "lab")] BlendMode::Lab => c.to_laba(), }) } #[inline] pub(crate) fn interpolate_linear(a: &[f32; 4], b: &[f32; 4], t: f32) -> [f32; 4] { [ a[0] + t * (b[0] - a[0]), a[1] + t * (b[1] - a[1]), a[2] + t * (b[2] - a[2]), a[3] + t * (b[3] - a[3]), ] } pub(crate) fn linspace(min: f32, max: f32, n: usize) -> impl Iterator { let d = max - min; let l = n as f32 - 1.0; (0..n).map(move |i| { if n == 1 { min } else { min + (i as f32 * d) / l } }) } #[inline] pub(crate) fn modulo(x: f32, y: f32) -> f32 { (x % y + y) % y } // Map t from range [a, b] to range [0, 1] #[inline] pub(crate) fn norm(t: f32, a: f32, b: f32) -> f32 { (t - a) * (1.0 / (b - a)) } #[cfg(test)] mod tests { use super::*; use alloc::vec::Vec; #[test] fn utils() { assert_eq!(linspace(0.0, 1.0, 0).next(), None); assert_eq!(linspace(0.0, 1.0, 1).collect::>(), [0.0]); assert_eq!(linspace(0.0, 1.0, 2).collect::>(), [0.0, 1.0]); assert_eq!(linspace(0.0, 1.0, 3).collect::>(), [0.0, 0.5, 1.0]); assert_eq!( linspace(-1.0, 1.0, 5).collect::>(), [-1.0, -0.5, 0.0, 0.5, 1.0] ); assert_eq!( linspace(0.0, 100.0, 5).collect::>(), [0.0, 25.0, 50.0, 75.0, 100.0] ); assert_eq!(modulo(7.0, 10.0), 7.0); assert_eq!(modulo(17.0, 10.0), 7.0); assert_eq!(norm(0.79, 0.0, 1.0), 0.79); assert_eq!(norm(16.0, 0.0, 100.0), 0.16); assert_eq!(norm(20.0, 15.0, 25.0), 0.5); } } colorgrad-0.8.0/tests/builder.rs000064400000000000000000000177331046102023000147560ustar 00000000000000use colorgrad::{ BlendMode, Color, Gradient, GradientBuilder, GradientBuilderError, LinearGradient, }; mod utils; use utils::*; #[test] fn builder() { // Default colors let g = GradientBuilder::new().build::().unwrap(); assert_eq!(g.domain(), (0.0, 1.0)); cmp_hex!(g.at(0.0), "#000000"); cmp_hex!(g.at(1.0), "#ffffff"); // Single color let g = GradientBuilder::new() .colors(&[Color::new(1.0, 0.0, 0.0, 1.0)]) .build::() .unwrap(); assert_eq!(g.at(0.0).to_rgba8(), [255, 0, 0, 255]); assert_eq!(g.at(0.5).to_rgba8(), [255, 0, 0, 255]); assert_eq!(g.at(1.0).to_rgba8(), [255, 0, 0, 255]); // Default domain let g = GradientBuilder::new() .html_colors(&["#f00", "#0f0", "#00f"]) .build::() .unwrap(); cmp_hex!(g.at(0.0), "#ff0000"); cmp_hex!(g.at(0.5), "#00ff00"); cmp_hex!(g.at(1.0), "#0000ff"); // Custom domain let g = GradientBuilder::new() .html_colors(&["#f00", "#0f0", "#00f"]) .domain(&[-100.0, 100.0]) .build::() .unwrap(); cmp_hex!(g.at(-100.0), "#ff0000"); cmp_hex!(g.at(0.0), "#00ff00"); cmp_hex!(g.at(100.0), "#0000ff"); // Color position let g = GradientBuilder::new() .html_colors(&["#f00", "#0f0", "#00f", "#f0f"]) .domain(&[13.0, 27.3, 90.0, 97.5]) .build::() .unwrap(); cmp_hex!(g.at(13.0), "#ff0000"); cmp_hex!(g.at(27.3), "#00ff00"); cmp_hex!(g.at(90.0), "#0000ff"); cmp_hex!(g.at(97.5), "#ff00ff"); // Multiple colors, custom domain let cols1 = vec!["#00f", "#00ffff"]; let cols2 = vec!["lime".to_string()]; let mut gb = GradientBuilder::new(); gb.html_colors(&cols1); gb.colors(&[Color::new(1.0, 1.0, 0.0, 0.5)]); gb.html_colors(&cols2); gb.domain(&[10.0, 50.0]); let g = gb.build::().unwrap(); assert_eq!( &colors2hex(&gb.get_colors()), &["#0000ff", "#00ffff", "#ffff0080", "#00ff00"] ); assert_eq!( &colors2hex(&g.colors(4)), &["#0000ff", "#00ffff", "#ffff0080", "#00ff00"] ); // Filter stops let mut gb = GradientBuilder::new(); gb.html_colors(&["gold", "red", "blue", "yellow", "black", "white", "plum"]); gb.domain(&[0.0, 0.0, 0.5, 0.5, 0.5, 1.0, 1.0]); gb.build::().unwrap(); assert_eq!(&gb.get_positions(), &[0.0, 0.5, 0.5, 1.0]); assert_eq!( &colors2hex(&gb.get_colors()), &["#ff0000", "#0000ff", "#000000", "#ffffff"] ); // Reusing builder let mut gb = GradientBuilder::new(); gb.colors(&[ Color::from_rgba8(255, 0, 0, 255), Color::from_rgba8(0, 0, 255, 255), Color::from_rgba8(0, 255, 0, 255), ]); gb.domain(&[0.0, 0.5, 1.0]); gb.mode(BlendMode::Rgb); let mut gb2 = gb.clone(); let g = gb.build::().unwrap(); assert_eq!(g.at(0.0).to_rgba8(), [255, 0, 0, 255]); assert_eq!(g.at(0.5).to_rgba8(), [0, 0, 255, 255]); assert_eq!(g.at(1.0).to_rgba8(), [0, 255, 0, 255]); // change color position gb2.domain(&[0.0, 35.0, 100.0]); let g = gb2.build::().unwrap(); assert_eq!(g.at(0.0).to_rgba8(), [255, 0, 0, 255]); assert_eq!(g.at(35.0).to_rgba8(), [0, 0, 255, 255]); assert_eq!(g.at(100.0).to_rgba8(), [0, 255, 0, 255]); // Reset gb.reset(); gb.colors(&[ Color::from_rgba8(127, 0, 100, 255), Color::from_rgba8(50, 255, 0, 255), ]); gb.build::().unwrap(); assert_eq!(gb.get_positions(), &[0.0, 1.0]); assert_eq!(&colors2hex(gb.get_colors()), &["#7f0064", "#32ff00"]); } #[test] fn css_gradient() { #[rustfmt::skip] let test_data = [ ( "blue", vec![0.0, 1.0], vec!["#0000ff", "#0000ff"], ), ( "red, blue", vec![0.0, 1.0], vec!["#ff0000", "#0000ff"], ), ( "red, lime, blue", vec![0.0, 0.5, 1.0], vec!["#ff0000", "#00ff00", "#0000ff"], ), ( "red, lime 75%, blue", vec![0.0, 0.75, 1.0], vec!["#ff0000", "#00ff00", "#0000ff"], ), ( "red 70%, lime, blue", vec![0.0, 0.7, 0.85, 1.0], vec!["#ff0000", "#ff0000", "#00ff00", "#0000ff"], ), ( "red, lime, blue 100", vec![0.0, 50.0, 100.0], vec!["#ff0000", "#00ff00", "#0000ff"], ), ( "#00f, #ff0 10% 35%, #f00", vec![0.0, 0.1, 0.35, 1.0], vec!["#0000ff", "#ffff00", "#ffff00", "#ff0000"], ), ( "red, 75%, #ff0", vec![0.0, 0.75, 1.0], vec!["#ff0000", "#ff8000", "#ffff00"], ), ( "red -100, lime, blue 100", vec![-100.0, 0.0, 100.0], vec!["#ff0000", "#00ff00", "#0000ff"], ), ( "red, lime -10, blue 15, gold", vec![0.0, 15.0], vec!["#00ff00", "#0000ff"], ), ]; for (s, positions, colors) in test_data { let mut gb = GradientBuilder::new(); gb.css(s).build::().unwrap(); assert_eq!(gb.get_positions(), &positions); assert_eq!(&colors2hex(gb.get_colors()), &colors); } // Custom domain let mut gb = GradientBuilder::new(); gb.domain(&[0.0, 50.0]); gb.css("#f00, #0f0 50%, #00f"); let g: LinearGradient = gb.build().unwrap(); assert_eq!(gb.get_positions(), [0.0, 25.0, 50.0]); assert_eq!( colors2hex(gb.get_colors()), ["#ff0000", "#00ff00", "#0000ff"] ); assert_eq!(g.domain(), (0.0, 50.0)); // Invalid format let invalid_css = [ "", " ", "0, red, lime", "red, lime, 100%", "deeppink, 0.4, 0.9, pink", "50%", "0%, 100%", ]; for s in invalid_css { let g = GradientBuilder::new().css(s).build::(); assert_eq!(g.unwrap_err(), GradientBuilderError::InvalidCssGradient); } } #[test] fn builder_error() { // Invalid HTML colors let g = GradientBuilder::new() .html_colors(&["#777", "bloodred", "#bbb", "#zzz"]) .build::(); assert_eq!( g.as_ref().unwrap_err(), &GradientBuilderError::InvalidHtmlColors(vec!["bloodred".to_string(), "#zzz".to_string()]) ); assert_eq!( g.unwrap_err().to_string(), "invalid html colors: 'bloodred', '#zzz'" ); // Invalid domain let g = GradientBuilder::new() .html_colors(&["#777", "gold", "#bbb", "#f0f"]) .domain(&[0.0, 0.75, 1.0]) .build::(); assert_eq!(g.unwrap_err(), GradientBuilderError::InvalidDomain); // Invalid domain let g = GradientBuilder::new() .html_colors(&["#777", "gold", "#bbb", "#f0f"]) .domain(&[0.0, 0.71, 0.7, 1.0]) .build::(); assert_eq!(g.unwrap_err(), GradientBuilderError::InvalidDomain); // Invalid domain let g = GradientBuilder::new() .html_colors(&["#777", "gold", "#bbb", "#f0f"]) .domain(&[1.0, 0.0]) .build::(); assert_eq!(g.unwrap_err(), GradientBuilderError::InvalidDomain); // Invalid domain let g = GradientBuilder::new() .html_colors(&["#777", "#bbb"]) .domain(&[2.0, 1.0]) .build::(); assert_eq!(g.unwrap_err(), GradientBuilderError::InvalidDomain); // Invalid CSS gradient let g = GradientBuilder::new() .css("#f00, 30%, 55%, #00f") .build::(); assert_eq!(g.unwrap_err(), GradientBuilderError::InvalidCssGradient); // Invalid stops let g = GradientBuilder::new() .html_colors(&["#777", "#f0f", "#f00"]) .domain(&[0.0, 0.0, 0.0]) .build::(); assert_eq!(g.unwrap_err(), GradientBuilderError::InvalidStops); } colorgrad-0.8.0/tests/g_basis.rs000064400000000000000000000012731046102023000147270ustar 00000000000000use colorgrad::Gradient; mod utils; use utils::*; #[test] fn basic() { let g = colorgrad::GradientBuilder::new() .html_colors(&["#f00", "#0f0", "#00f"]) .mode(colorgrad::BlendMode::Rgb) .build::() .unwrap(); cmp_hex!(g.at(0.00), "#ff0000"); cmp_hex!(g.at(0.25), "#857505"); cmp_hex!(g.at(0.50), "#2baa2b"); cmp_hex!(g.at(0.75), "#057585"); cmp_hex!(g.at(1.00), "#0000ff"); assert_eq!( colors2hex(&g.colors(5)), &["#ff0000", "#857505", "#2baa2b", "#057585", "#0000ff"] ); cmp_hex!(g.at(-0.1), "#ff0000"); cmp_hex!(g.at(1.11), "#0000ff"); cmp_hex!(g.at(f32::NAN), "#000000"); } colorgrad-0.8.0/tests/g_catmullrom.rs000064400000000000000000000013001046102023000157740ustar 00000000000000use colorgrad::Gradient; mod utils; use utils::*; #[test] fn basic() { let g = colorgrad::GradientBuilder::new() .html_colors(&["#f00", "#0f0", "#00f"]) .mode(colorgrad::BlendMode::Rgb) .build::() .unwrap(); cmp_hex!(g.at(0.00), "#ff0000"); cmp_hex!(g.at(0.25), "#609f00"); cmp_hex!(g.at(0.50), "#00ff00"); cmp_hex!(g.at(0.75), "#009f60"); cmp_hex!(g.at(1.00), "#0000ff"); assert_eq!( colors2hex(&g.colors(5)), &["#ff0000", "#609f00", "#00ff00", "#009f60", "#0000ff"] ); cmp_hex!(g.at(-0.1), "#ff0000"); cmp_hex!(g.at(1.11), "#0000ff"); cmp_hex!(g.at(f32::NAN), "#000000"); } colorgrad-0.8.0/tests/g_gimp.rs000064400000000000000000000135421046102023000145640ustar 00000000000000#[cfg(feature = "ggr")] use std::io::BufReader; #[cfg(feature = "ggr")] use colorgrad::{Color, GimpGradient, Gradient}; #[cfg(feature = "ggr")] #[test] fn parse_gimp_gradients() { let col = Color::default(); let red = Color::new(1.0, 0.0, 0.0, 1.0); let blue = Color::new(0.0, 0.0, 1.0, 1.0); // Black to white let ggr = "GIMP Gradient\nName: My Gradient\n1\n0 0.5 1 0 0 0 1 1 1 1 1 0 0 0 0"; let grad = GimpGradient::new(BufReader::new(ggr.as_bytes()), &col, &col).unwrap(); assert_eq!(grad.name(), "My Gradient"); assert_eq!(grad.domain(), (0.0, 1.0)); assert_eq!(grad.at(0.0).to_rgba8(), [0, 0, 0, 255]); assert_eq!(grad.at(1.0).to_rgba8(), [255, 255, 255, 255]); assert_eq!(grad.at(-0.5).to_rgba8(), [0, 0, 0, 255]); assert_eq!(grad.at(1.5).to_rgba8(), [0, 0, 0, 255]); assert_eq!(grad.at(f32::NAN).to_rgba8(), [0, 0, 0, 255]); // Foreground to background let ggr = "GIMP Gradient\nName: My Gradient\n1\n0 0.5 1 0 0 0 1 1 1 1 1 0 0 1 3"; let grad = GimpGradient::new(BufReader::new(ggr.as_bytes()), &red, &blue).unwrap(); assert_eq!(grad.at(0.0).to_rgba8(), [255, 0, 0, 255]); assert_eq!(grad.at(1.0).to_rgba8(), [0, 0, 255, 255]); // Background to foreground let ggr = "GIMP Gradient\nName: My Gradient\n1\n0 0.5 1 0 0 0 1 1 1 1 1 0 0 3 1"; let grad = GimpGradient::new(BufReader::new(ggr.as_bytes()), &red, &blue).unwrap(); assert_eq!(grad.at(0.0).to_rgba8(), [0, 0, 255, 255]); assert_eq!(grad.at(1.0).to_rgba8(), [255, 0, 0, 255]); // Foreground transparent to background transparent let ggr = "GIMP Gradient\nName: My Gradient\n1\n0 0.5 1 0 0 0 1 1 1 1 1 0 0 2 4"; let grad = GimpGradient::new(BufReader::new(ggr.as_bytes()), &red, &blue).unwrap(); assert_eq!(grad.at(0.0).to_rgba8(), [255, 0, 0, 0]); assert_eq!(grad.at(1.0).to_rgba8(), [0, 0, 255, 0]); // Background transparent to foreground transparent let ggr = "GIMP Gradient\nName: My Gradient\n1\n0 0.5 1 0 0 0 1 1 1 1 1 0 0 4 2"; let grad = GimpGradient::new(BufReader::new(ggr.as_bytes()), &red, &blue).unwrap(); assert_eq!(grad.at(0.0).to_rgba8(), [0, 0, 255, 0]); assert_eq!(grad.at(1.0).to_rgba8(), [255, 0, 0, 0]); // Blending function: step let ggr = "GIMP Gradient\nName: My Gradient\n1\n0 0.5 1 1 0 0 1 0 0 1 1 5 0 0 0"; let grad = GimpGradient::new(BufReader::new(ggr.as_bytes()), &col, &col).unwrap(); assert_eq!(grad.at(0.00).to_rgba8(), [255, 0, 0, 255]); assert_eq!(grad.at(0.25).to_rgba8(), [255, 0, 0, 255]); assert_eq!(grad.at(0.49).to_rgba8(), [255, 0, 0, 255]); assert_eq!(grad.at(0.51).to_rgba8(), [0, 0, 255, 255]); assert_eq!(grad.at(0.75).to_rgba8(), [0, 0, 255, 255]); assert_eq!(grad.at(1.00).to_rgba8(), [0, 0, 255, 255]); let ggr = "GIMP Gradient\nName: My Gradient\n1\n0 0.75 1 1 0 0 1 0 0 1 1 5 0 0 0"; let grad = GimpGradient::new(BufReader::new(ggr.as_bytes()), &col, &col).unwrap(); assert_eq!(grad.at(0.00).to_rgba8(), [255, 0, 0, 255]); assert_eq!(grad.at(0.25).to_rgba8(), [255, 0, 0, 255]); assert_eq!(grad.at(0.50).to_rgba8(), [255, 0, 0, 255]); assert_eq!(grad.at(0.74).to_rgba8(), [255, 0, 0, 255]); assert_eq!(grad.at(0.76).to_rgba8(), [0, 0, 255, 255]); assert_eq!(grad.at(0.90).to_rgba8(), [0, 0, 255, 255]); assert_eq!(grad.at(1.00).to_rgba8(), [0, 0, 255, 255]); // Coloring type: HSV CCW (white to blue) let ggr = "GIMP Gradient\nName: My Gradient\n1\n0 0.5 1 1 1 1 1 0 0 1 1 0 1 0 0"; let grad = GimpGradient::new(BufReader::new(ggr.as_bytes()), &red, &blue).unwrap(); assert_eq!(grad.at(0.0).to_rgba8(), [255, 255, 255, 255]); assert_eq!(grad.at(0.5).to_rgba8(), [128, 255, 128, 255]); assert_eq!(grad.at(1.0).to_rgba8(), [0, 0, 255, 255]); // Coloring type: HSV CW (white to blue) let ggr = "GIMP Gradient\nName: My Gradient\n1\n0 0.5 1 1 1 1 1 0 0 1 1 0 2 0 0"; let grad = GimpGradient::new(BufReader::new(ggr.as_bytes()), &red, &blue).unwrap(); assert_eq!(grad.at(0.0).to_rgba8(), [255, 255, 255, 255]); assert_eq!(grad.at(0.5).to_rgba8(), [255, 128, 255, 255]); assert_eq!(grad.at(1.0).to_rgba8(), [0, 0, 255, 255]); // UTF-8 with BOM let ggr = include_str!("../examples/ggr/UTF_8_BOM.ggr"); let grad = GimpGradient::new(BufReader::new(ggr.as_bytes()), &red, &blue).unwrap(); assert_eq!(grad.name(), "Pelangi"); assert_eq!(grad.at(0.0).to_rgba8(), [36, 87, 158, 255]); assert_eq!(grad.at(1.0).to_rgba8(), [72, 120, 168, 255]); } #[cfg(feature = "ggr")] #[test] fn invalid_format() { let col = Color::default(); let test_data = vec![ ("GIMP Pallete\n9", "invalid header (line 1)"), ("GIMP Gradient\n6", "invalid header (line 2)"), ( "GIMP Gradient\nName: Gradient\nx", "invalid header (line 3)", ), ( "GIMP Gradient\nName: Gradient\n1\n0 0.5 1", "invalid segment (line 4)", ), ( "GIMP Gradient\nName: Gradient\n3\n0 0.5 1 0 0 0 1 1 1 1 1 0 0 0 0", "wrong segments count (line 3)", ), ("GIMP Gradient\nName: Gradient\n0", "no segment (line 4)"), ]; for (ggr, err_msg) in test_data { let res = GimpGradient::new(BufReader::new(ggr.as_bytes()), &col, &col); assert_eq!(res.unwrap_err().to_string(), err_msg); } let invalid_segments = vec![ "GIMP Gradient\nName: Gradient\n1\n0 0.5 1 0 0 0 1 1 1 1 1 6 0 0 0", "GIMP Gradient\nName: Gradient\n1\n0 0.5 1 0 0 0 1 1 1 1 1 0 3 0 0", "GIMP Gradient\nName: Gradient\n1\n0 0.5 1 0 0 0 1 1 1 1 1 0 0 5 0", "GIMP Gradient\nName: Gradient\n1\n0 0.5 1 0 0 0 1 1 1 1 1 0 0 0 5", "GIMP Gradient\nName: Gradient\n1\n0 0.5 1 0 0 0 A 1 1 1 A 0 0 0 0", ]; for ggr in invalid_segments { let res = GimpGradient::new(BufReader::new(ggr.as_bytes()), &col, &col); assert!(res.is_err()); } } colorgrad-0.8.0/tests/g_inverse.rs000064400000000000000000000036431046102023000153040ustar 00000000000000use colorgrad::Gradient; mod utils; use utils::*; #[test] fn inverse() { macro_rules! cmp_rgba8 { ($a:expr, $b:expr) => { assert_eq!($a.to_rgba8(), $b.to_rgba8()); }; } let grad = colorgrad::GradientBuilder::new() .html_colors(&["#000", "#fff"]) .build::() .unwrap(); let inv = grad.inverse(); assert_eq!(grad.domain(), (0.0, 1.0)); assert_eq!(inv.domain(), (0.0, 1.0)); cmp_rgba8!(inv.at(0.0), grad.at(1.0)); cmp_rgba8!(inv.at(0.3), grad.at(0.7)); cmp_rgba8!(inv.at(0.5), grad.at(0.5)); cmp_rgba8!(inv.at(0.7), grad.at(0.3)); cmp_rgba8!(inv.at(1.0), grad.at(0.0)); cmp_hex!(inv.repeat_at(-0.9), "#e6e6e6"); cmp_hex!(inv.repeat_at(1.1), "#e6e6e6"); cmp_hex!(inv.reflect_at(-0.9), "#191919"); cmp_hex!(inv.reflect_at(1.1), "#191919"); assert_eq!( colors2hex(&grad.colors(5)), &["#000000", "#404040", "#808080", "#bfbfbf", "#ffffff"] ); assert_eq!( colors2hex(&inv.colors(5)), &["#ffffff", "#bfbfbf", "#808080", "#404040", "#000000"] ); // Custom domain let grad = colorgrad::GradientBuilder::new() .html_colors(&["#000", "#fff"]) .domain(&[-100.0, 100.0]) .build::() .unwrap(); let inv = grad.inverse(); assert_eq!(grad.domain(), (-100.0, 100.0)); assert_eq!(inv.domain(), (-100.0, 100.0)); cmp_rgba8!(inv.at(-100.0), grad.at(100.0)); cmp_rgba8!(inv.at(-50.0), grad.at(50.0)); cmp_rgba8!(inv.at(0.0), grad.at(0.0)); cmp_rgba8!(inv.at(50.0), grad.at(-50.0)); cmp_rgba8!(inv.at(100.0), grad.at(-100.0)); assert_eq!( colors2hex(&grad.colors(5)), &["#000000", "#404040", "#808080", "#bfbfbf", "#ffffff"] ); assert_eq!( colors2hex(&inv.colors(5)), &["#ffffff", "#bfbfbf", "#808080", "#404040", "#000000"] ); } colorgrad-0.8.0/tests/g_linear.rs000064400000000000000000000012741046102023000151010ustar 00000000000000use colorgrad::Gradient; mod utils; use utils::*; #[test] fn basic() { let g = colorgrad::GradientBuilder::new() .html_colors(&["#f00", "#0f0", "#00f"]) .mode(colorgrad::BlendMode::Rgb) .build::() .unwrap(); cmp_hex!(g.at(0.00), "#ff0000"); cmp_hex!(g.at(0.25), "#808000"); cmp_hex!(g.at(0.50), "#00ff00"); cmp_hex!(g.at(0.75), "#008080"); cmp_hex!(g.at(1.00), "#0000ff"); assert_eq!( colors2hex(&g.colors(5)), &["#ff0000", "#808000", "#00ff00", "#008080", "#0000ff"] ); cmp_hex!(g.at(-0.1), "#ff0000"); cmp_hex!(g.at(1.11), "#0000ff"); cmp_hex!(g.at(f32::NAN), "#000000"); } colorgrad-0.8.0/tests/g_sharp.rs000064400000000000000000000060731046102023000147460ustar 00000000000000use colorgrad::{Gradient, GradientBuilder, LinearGradient}; #[test] fn sharp_gradient() { let grad = GradientBuilder::new() .html_colors(&["#f00", "#0f0", "#00f"]) .build::() .unwrap(); let g0 = grad.sharp(0, 0.0); assert_eq!(g0.at(0.0).to_rgba8(), [255, 0, 0, 255]); assert_eq!(g0.at(0.5).to_rgba8(), [255, 0, 0, 255]); assert_eq!(g0.at(0.1).to_rgba8(), [255, 0, 0, 255]); let g1 = grad.sharp(1, 0.0); assert_eq!(g1.at(0.0).to_rgba8(), [255, 0, 0, 255]); assert_eq!(g1.at(0.5).to_rgba8(), [255, 0, 0, 255]); assert_eq!(g1.at(0.1).to_rgba8(), [255, 0, 0, 255]); let g3 = grad.sharp(3, 0.0); assert_eq!(g3.at(0.0).to_rgba8(), [255, 0, 0, 255]); assert_eq!(g3.at(0.1).to_rgba8(), [255, 0, 0, 255]); assert_eq!(g3.at(0.4).to_rgba8(), [0, 255, 0, 255]); assert_eq!(g3.at(0.5).to_rgba8(), [0, 255, 0, 255]); assert_eq!(g3.at(0.6).to_rgba8(), [0, 255, 0, 255]); assert_eq!(g3.at(0.9).to_rgba8(), [0, 0, 255, 255]); assert_eq!(g3.at(1.0).to_rgba8(), [0, 0, 255, 255]); assert_eq!(g3.at(-0.1).to_rgba8(), [255, 0, 0, 255]); assert_eq!(g3.at(1.1).to_rgba8(), [0, 0, 255, 255]); assert_eq!(g3.at(f32::NAN).to_rgba8(), [0, 0, 0, 255]); let grad = GradientBuilder::new() .html_colors(&["#f00", "#0f0", "#00f"]) .domain(&[-1.0, 1.0]) .build::() .unwrap(); let g2 = grad.sharp(2, 0.0); assert_eq!(g2.at(-1.0).to_rgba8(), [255, 0, 0, 255]); assert_eq!(g2.at(-0.5).to_rgba8(), [255, 0, 0, 255]); assert_eq!(g2.at(-0.1).to_rgba8(), [255, 0, 0, 255]); assert_eq!(g2.at(0.1).to_rgba8(), [0, 0, 255, 255]); assert_eq!(g2.at(0.5).to_rgba8(), [0, 0, 255, 255]); assert_eq!(g2.at(1.0).to_rgba8(), [0, 0, 255, 255]); } #[test] fn sharp_gradient_with_smoothness() { let g = GradientBuilder::new() .html_colors(&["#f00", "#0f0", "#00f"]) .build::() .unwrap(); let g0 = g.sharp(0, 0.1); assert_eq!(g0.at(0.0).to_rgba8(), [255, 0, 0, 255]); assert_eq!(g0.at(0.5).to_rgba8(), [255, 0, 0, 255]); assert_eq!(g0.at(1.0).to_rgba8(), [255, 0, 0, 255]); let g1 = g.sharp(1, 0.1); assert_eq!(g1.at(0.0).to_rgba8(), [255, 0, 0, 255]); assert_eq!(g1.at(0.5).to_rgba8(), [255, 0, 0, 255]); assert_eq!(g1.at(1.0).to_rgba8(), [255, 0, 0, 255]); let g = g.sharp(3, 0.1); assert_eq!(g.at(0.0).to_rgba8(), [255, 0, 0, 255]); assert_eq!(g.at(0.1).to_rgba8(), [255, 0, 0, 255]); assert_eq!(g.at(1.0 / 3.0).to_rgba8(), [128, 128, 0, 255]); assert_eq!(g.at(0.45).to_rgba8(), [0, 255, 0, 255]); assert_eq!(g.at(0.50).to_rgba8(), [0, 255, 0, 255]); assert_eq!(g.at(0.55).to_rgba8(), [0, 255, 0, 255]); assert_eq!(g.at(1.0 / 3.0 * 2.0).to_rgba8(), [0, 128, 128, 255]); assert_eq!(g.at(0.9).to_rgba8(), [0, 0, 255, 255]); assert_eq!(g.at(1.0).to_rgba8(), [0, 0, 255, 255]); assert_eq!(g.at(-0.5).to_rgba8(), [255, 0, 0, 255]); assert_eq!(g.at(1.5).to_rgba8(), [0, 0, 255, 255]); assert_eq!(g.at(f32::NAN).to_rgba8(), [0, 0, 0, 255]); } colorgrad-0.8.0/tests/gradient.rs000064400000000000000000000226501046102023000151170ustar 00000000000000use colorgrad::{BlendMode, Color, Gradient, GradientBuilder, LinearGradient}; mod utils; use utils::*; #[test] fn spread_inside_domain() { macro_rules! cmp_rgba8 { ($a:expr, $b:expr) => { assert_eq!($a.to_rgba8(), $b.to_rgba8()); }; } let g = GradientBuilder::new() .html_colors(&["#00f", "#fff"]) .build::() .unwrap(); cmp_rgba8!(g.at(0.0), g.repeat_at(0.0)); cmp_rgba8!(g.at(0.0), g.reflect_at(0.0)); cmp_rgba8!(g.at(0.01), g.repeat_at(0.01)); cmp_rgba8!(g.at(0.01), g.reflect_at(0.01)); cmp_rgba8!(g.at(0.25), g.repeat_at(0.25)); cmp_rgba8!(g.at(0.25), g.reflect_at(0.25)); cmp_rgba8!(g.at(0.5), g.repeat_at(0.5)); cmp_rgba8!(g.at(0.5), g.reflect_at(0.5)); cmp_rgba8!(g.at(0.75), g.repeat_at(0.75)); cmp_rgba8!(g.at(0.75), g.reflect_at(0.75)); cmp_rgba8!(g.at(0.999), g.repeat_at(0.999)); cmp_rgba8!(g.at(0.999), g.reflect_at(0.999)); } #[test] fn spread_repeat() { let g = GradientBuilder::new() .html_colors(&["#000", "#fff"]) .build::() .unwrap(); cmp_hex!(g.repeat_at(-2.0), "#000000"); cmp_hex!(g.repeat_at(-1.9), "#1a1a1a"); cmp_hex!(g.repeat_at(-1.5), "#808080"); cmp_hex!(g.repeat_at(-1.1), "#e6e6e6"); cmp_hex!(g.repeat_at(-1.0), "#000000"); cmp_hex!(g.repeat_at(-0.9), "#1a1a1a"); cmp_hex!(g.repeat_at(-0.5), "#808080"); cmp_hex!(g.repeat_at(-0.1), "#e6e6e6"); cmp_hex!(g.repeat_at(0.0), "#000000"); cmp_hex!(g.repeat_at(0.1), "#1a1a1a"); cmp_hex!(g.repeat_at(0.5), "#808080"); cmp_hex!(g.repeat_at(0.9), "#e6e6e6"); cmp_hex!(g.repeat_at(1.0), "#000000"); cmp_hex!(g.repeat_at(1.1), "#1a1a1a"); cmp_hex!(g.repeat_at(1.5), "#808080"); cmp_hex!(g.repeat_at(1.9), "#e6e6e6"); cmp_hex!(g.repeat_at(2.0), "#000000"); cmp_hex!(g.repeat_at(2.1), "#191919"); cmp_hex!(g.repeat_at(2.5), "#808080"); cmp_hex!(g.repeat_at(2.9), "#e6e6e6"); } #[test] fn spread_reflect() { let g = GradientBuilder::new() .html_colors(&["#000", "#fff"]) .build::() .unwrap(); cmp_hex!(g.reflect_at(-2.0), "#000000"); cmp_hex!(g.reflect_at(-1.9), "#1a1a1a"); cmp_hex!(g.reflect_at(-1.5), "#808080"); cmp_hex!(g.reflect_at(-1.1), "#e6e6e6"); cmp_hex!(g.reflect_at(-1.0), "#ffffff"); cmp_hex!(g.reflect_at(-0.9), "#e6e6e6"); cmp_hex!(g.reflect_at(-0.5), "#808080"); cmp_hex!(g.reflect_at(-0.1), "#191919"); cmp_hex!(g.reflect_at(0.0), "#000000"); cmp_hex!(g.reflect_at(0.1), "#191919"); cmp_hex!(g.reflect_at(0.5), "#808080"); cmp_hex!(g.reflect_at(0.9), "#e6e6e6"); cmp_hex!(g.reflect_at(1.0), "#ffffff"); cmp_hex!(g.reflect_at(1.1), "#e6e6e6"); cmp_hex!(g.reflect_at(1.5), "#808080"); cmp_hex!(g.reflect_at(1.9), "#191919"); cmp_hex!(g.reflect_at(2.0), "#000000"); cmp_hex!(g.reflect_at(2.1), "#191919"); cmp_hex!(g.reflect_at(2.5), "#808080"); cmp_hex!(g.reflect_at(2.9), "#e6e6e6"); } #[test] fn colors() { let g = GradientBuilder::new() .html_colors(&["#f00", "#0f0", "#00f"]) .build::() .unwrap(); assert_eq!(g.domain(), (0.0, 1.0)); assert_eq!(g.colors(0).len(), 0); assert_eq!(colors2hex(&g.colors(1)), &["#ff0000",]); assert_eq!(colors2hex(&g.colors(2)), &["#ff0000", "#0000ff",]); assert_eq!( colors2hex(&g.colors(3)), &["#ff0000", "#00ff00", "#0000ff",] ); assert_eq!( colors2hex(&g.colors(5)), &["#ff0000", "#808000", "#00ff00", "#008080", "#0000ff",] ); let g = GradientBuilder::new() .html_colors(&["#f00", "#0f0", "#00f"]) .domain(&[-1.0, 1.0]) .build::() .unwrap(); assert_eq!(g.domain(), (-1.0, 1.0)); assert_eq!( colors2hex(&g.colors(3)), &["#ff0000", "#00ff00", "#0000ff",] ); assert_eq!( colors2hex(&g.colors(5)), &["#ff0000", "#808000", "#00ff00", "#008080", "#0000ff",] ); } #[test] fn colors_iter() { fn hex(c: Color) -> String { c.to_css_hex() } macro_rules! cmp { ($a:expr, $b:expr) => { assert_eq!($a.map(hex).as_deref(), Some($b)); }; } let g = GradientBuilder::new() .html_colors(&["#000", "#f00", "#ff0", "#fff"]) .mode(BlendMode::Rgb) .build::() .unwrap(); let mut it = g.colors_iter(0); assert_eq!(it.len(), 0); assert_eq!(it.next(), None); assert_eq!(it.next_back(), None); let mut it = g.colors_iter(1); cmp!(it.next(), "#000000"); assert_eq!(it.next(), None); assert_eq!(it.next_back(), None); let mut it = g.colors_iter(1); cmp!(it.next_back(), "#000000"); assert_eq!(it.next(), None); assert_eq!(it.next_back(), None); let mut it = g.colors_iter(2); cmp!(it.next(), "#000000"); cmp!(it.next(), "#ffffff"); assert_eq!(it.next(), None); assert_eq!(it.next_back(), None); let mut it = g.colors_iter(4); cmp!(it.next(), "#000000"); cmp!(it.next(), "#ff0000"); cmp!(it.next(), "#ffff00"); cmp!(it.next(), "#ffffff"); assert_eq!(it.len(), 0); assert_eq!(it.next(), None); assert_eq!(it.next_back(), None); let mut it = g.colors_iter(4); cmp!(it.next_back(), "#ffffff"); cmp!(it.next_back(), "#ffff00"); cmp!(it.next_back(), "#ff0000"); cmp!(it.next_back(), "#000000"); assert_eq!(it.next(), None); assert_eq!(it.next_back(), None); let mut it = g.colors_iter(4); assert_eq!(it.len(), 4); cmp!(it.next(), "#000000"); assert_eq!(it.len(), 3); cmp!(it.next_back(), "#ffffff"); assert_eq!(it.len(), 2); cmp!(it.next(), "#ff0000"); assert_eq!(it.len(), 1); cmp!(it.next_back(), "#ffff00"); assert_eq!(it.len(), 0); assert_eq!(it.next(), None); assert_eq!(it.next_back(), None); let mut it = g.colors_iter(999); cmp!(it.next(), "#000000"); cmp!(it.next_back(), "#ffffff"); assert_eq!(it.len(), 997); assert_eq!(it.count(), 997); let colors: Vec<_> = g.colors_iter(73).collect(); assert_eq!(colors.len(), 73); // reverse let mut it = g.colors_iter(4).rev(); cmp!(it.next(), "#ffffff"); cmp!(it.next(), "#ffff00"); cmp!(it.next(), "#ff0000"); cmp!(it.next(), "#000000"); assert_eq!(it.len(), 0); // compare with Gradient::colors() let colors: Vec<_> = g.colors_iter(27).collect(); assert_eq!(g.colors(27), colors); // --- Custom gradient domain let g = GradientBuilder::new() .html_colors(&["#f00", "#0f0", "#00f"]) .domain(&[-5.0, 17.0]) .mode(BlendMode::Rgb) .build::() .unwrap(); let mut it = g.colors_iter(3); cmp!(it.next(), "#ff0000"); cmp!(it.next(), "#00ff00"); cmp!(it.next(), "#0000ff"); assert_eq!(it.next(), None); // reverse let mut it = g.colors_iter(3).rev(); cmp!(it.next(), "#0000ff"); cmp!(it.next(), "#00ff00"); cmp!(it.next(), "#ff0000"); assert_eq!(it.next(), None); // compare with Gradient::colors() let colors: Vec<_> = g.colors_iter(10).collect(); assert_eq!(g.colors(10), colors); } #[test] fn boxed_gradients() { let gradient = GradientBuilder::new() .html_colors(&["#fff", "#000"]) .build::() .unwrap() .boxed(); assert_eq!(gradient.at(0.0).to_rgba8(), [255, 255, 255, 255]); assert_eq!(gradient.repeat_at(1.25).to_rgba8(), [191, 191, 191, 255]); assert_eq!(gradient.reflect_at(1.25).to_rgba8(), [64, 64, 64, 255]); assert_eq!(gradient.domain(), (0.0, 1.0)); assert_eq!(gradient.colors(3).len(), 3); assert_eq!(gradient.sharp(3, 0.0).colors(3).len(), 3); } #[test] fn others() { let gd: Box = Box::new(GradientBuilder::new().build::().unwrap()); let _: Box = gd.clone(); let _ = gd.clone().boxed(); let _ = gd.inverse(); let _ = gd.inverse().boxed(); let _ = gd.clone_boxed(); let gd: &dyn Gradient = &GradientBuilder::new().build::().unwrap(); let _ = gd.inverse(); let _ = gd.inverse().boxed(); let _ = gd.clone_boxed(); } #[test] fn impl_gradient() { // Default domain #[derive(Clone)] struct MyGradient1 {} impl Gradient for MyGradient1 { fn at(&self, t: f32) -> Color { if t < 0.5 { Color::new(0.0, 0.0, 1.0, 1.0) } else { Color::new(1.0, 0.0, 0.0, 1.0) } } } let g = MyGradient1 {}; assert_eq!(g.domain(), (0.0, 1.0)); cmp_hex!(g.at(0.00), "#0000ff"); cmp_hex!(g.at(0.49), "#0000ff"); cmp_hex!(g.at(0.51), "#ff0000"); cmp_hex!(g.at(1.00), "#ff0000"); // Custom domain #[derive(Clone)] struct MyGradient2 {} impl Gradient for MyGradient2 { fn at(&self, t: f32) -> Color { if (t as usize / 10) & 1 == 0 { Color::new(0.0, 0.0, 1.0, 1.0) } else { Color::new(1.0, 0.0, 0.0, 1.0) } } fn domain(&self) -> (f32, f32) { (1.0, 99.0) } } let g = MyGradient2 {}; assert_eq!(g.domain(), (1.0, 99.0)); cmp_hex!(g.at(25.0), "#0000ff"); assert_eq!( colors2hex(&g.colors(10)), &[ "#0000ff", "#ff0000", "#0000ff", "#ff0000", "#0000ff", "#ff0000", "#0000ff", "#ff0000", "#0000ff", "#ff0000", ] ); } colorgrad-0.8.0/tests/preset.rs000064400000000000000000000037551046102023000146310ustar 00000000000000use colorgrad::Gradient; mod utils; #[test] fn preset() { let g = colorgrad::preset::viridis(); cmp_hex!(g.at(0.0), "#440154"); cmp_hex!(g.at(0.5), "#27838e"); cmp_hex!(g.at(1.0), "#fee825"); cmp_hex!(g.at(f32::NAN), "#000000"); let g = colorgrad::preset::greys(); cmp_hex!(g.at(0.0), "#ffffff"); cmp_hex!(g.at(1.0), "#000000"); let g = colorgrad::preset::turbo(); cmp_hex!(g.at(0.0), "#23171b"); cmp_hex!(g.at(1.0), "#900c00"); let g = colorgrad::preset::cividis(); cmp_hex!(g.at(0.0), "#002051"); cmp_hex!(g.at(1.0), "#fdea45"); let g = colorgrad::preset::cubehelix_default(); cmp_hex!(g.at(0.0), "#000000"); cmp_hex!(g.at(1.0), "#ffffff"); let g = colorgrad::preset::warm(); cmp_hex!(g.at(0.0), "#6e40aa"); cmp_hex!(g.at(1.0), "#aff05b"); let g = colorgrad::preset::cool(); cmp_hex!(g.at(0.0), "#6e40aa"); cmp_hex!(g.at(1.0), "#aff05b"); macro_rules! presets { ($($name:ident),+ $(,)?) => { $({ let g = colorgrad::preset::$name(); assert_eq!(g.domain(), (0.0, 1.0)); })* } } presets!( blues, br_bg, bu_gn, bu_pu, cividis, cool, cubehelix_default, gn_bu, greens, greys, inferno, magma, or_rd, oranges, pi_yg, plasma, pr_gn, pu_bu, pu_bu_gn, pu_or, pu_rd, purples, rainbow, rd_bu, rd_gy, rd_pu, rd_yl_bu, rd_yl_gn, reds, sinebow, spectral, turbo, viridis, warm, yl_gn, yl_gn_bu, yl_or_br, yl_or_rd, ); } #[test] fn cyclic() { let g = colorgrad::preset::rainbow(); assert_eq!(g.at(0.0).to_rgba8(), g.at(1.0).to_rgba8()); let g = colorgrad::preset::sinebow(); assert_eq!(g.at(0.0).to_rgba8(), g.at(1.0).to_rgba8()); } colorgrad-0.8.0/tests/utils.rs000064400000000000000000000005361046102023000144610ustar 00000000000000use colorgrad::Color; #[allow(dead_code)] pub fn colors2hex(colors: &[Color]) -> Vec { let mut res = Vec::with_capacity(colors.len()); for c in colors { res.push(c.to_css_hex()); } res } #[macro_export] macro_rules! cmp_hex { ($color:expr, $hex:expr) => { assert_eq!($color.to_css_hex(), $hex); }; }