├── CODEOWNERS ├── test ├── gleam_community_colour_test.gleam ├── colour │ └── accessibility.gleam └── colour.gleam ├── .gitignore ├── CHANGELOG.md ├── gleam.toml ├── .github └── workflows │ ├── test.yml │ └── release.yml ├── manifest.toml ├── README.md ├── CODE_OF_CONDUCT.md ├── src └── gleam_community │ ├── colour │ └── accessibility.gleam │ └── colour.gleam └── LICENCE /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @brettkolodny 2 | -------------------------------------------------------------------------------- /test/gleam_community_colour_test.gleam: -------------------------------------------------------------------------------- 1 | import gleeunit 2 | 3 | pub fn main() { 4 | gleeunit.main() 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .env* 3 | .idea/ 4 | .nova/ 5 | .vscode/ 6 | build/ 7 | coverage/ 8 | dist/ 9 | erl_crash.dump 10 | node_modules/ 11 | *.beam 12 | *.ez 13 | *.log 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.1.0 - Unreleased 4 | 5 | - Updated dependencies and code to be in line with Gleam 0.27.0. 6 | 7 | ## 1.0.0 8 | 9 | - Initial Release 10 | -------------------------------------------------------------------------------- /gleam.toml: -------------------------------------------------------------------------------- 1 | name = "gleam_community_colour" 2 | version = "2.0.2" 3 | licences = ["Apache-2.0"] 4 | description = "Colour types, conversions, and other utilities" 5 | repository = { type = "github", user = "gleam-community", repo = "colour" } 6 | gleam = ">= 1.4.0" 7 | 8 | [dependencies] 9 | gleam_stdlib = ">= 0.50.0 and < 2.0.0" 10 | gleam_json = ">= 2.2.0 and < 4.0.0" 11 | 12 | [dev-dependencies] 13 | gleeunit = ">= 1.3.0 and < 2.0.0" 14 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["**"] 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3.1.0 19 | - uses: erlef/setup-beam@v1.18.2 20 | with: 21 | otp-version: "27.2.1" 22 | gleam-version: "1.11.1" 23 | rebar3-version: "3" 24 | 25 | - run: gleam format --check 26 | 27 | - run: gleam test --target erlang 28 | 29 | - run: gleam test --target javascript 30 | -------------------------------------------------------------------------------- /test/colour/accessibility.gleam: -------------------------------------------------------------------------------- 1 | import gleam_community/colour 2 | import gleam_community/colour/accessibility 3 | import gleeunit/should 4 | 5 | pub fn contrast_ratio_test() { 6 | accessibility.contrast_ratio(colour.white, colour.black) 7 | |> should.equal(21.0) 8 | 9 | accessibility.contrast_ratio(colour.pink, colour.pink) 10 | |> should.equal(1.0) 11 | 12 | accessibility.contrast_ratio(colour.pink, colour.black) 13 | |> should.equal(accessibility.contrast_ratio(colour.black, colour.pink)) 14 | } 15 | 16 | pub fn luminance_test() { 17 | accessibility.luminance(colour.black) 18 | |> should.equal(0.0) 19 | 20 | accessibility.luminance(colour.white) 21 | |> should.equal(1.0) 22 | } 23 | 24 | pub fn maximum_contrast_test() { 25 | accessibility.maximum_contrast(colour.yellow, [ 26 | colour.white, 27 | colour.dark_blue, 28 | colour.green, 29 | ]) 30 | |> should.equal(Ok(colour.dark_blue)) 31 | } 32 | -------------------------------------------------------------------------------- /manifest.toml: -------------------------------------------------------------------------------- 1 | # This file was generated by Gleam 2 | # You typically do not need to edit this file 3 | 4 | packages = [ 5 | { name = "gleam_json", version = "3.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "874FA3C3BB6E22DD2BB111966BD40B3759E9094E05257899A7C08F5DE77EC049" }, 6 | { name = "gleam_stdlib", version = "0.61.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "3DC407D6EDA98FCE089150C11F3AD892B6F4C3CA77C87A97BAE8D5AB5E41F331" }, 7 | { name = "gleeunit", version = "1.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "63022D81C12C17B7F1A60E029964E830A4CBD846BBC6740004FC1F1031AE0326" }, 8 | ] 9 | 10 | [requirements] 11 | gleam_json = { version = ">= 2.2.0 and < 4.0.0" } 12 | gleam_stdlib = { version = ">= 0.50.0 and < 2.0.0" } 13 | gleeunit = { version = ">= 1.3.0 and < 2.0.0" } 14 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: ["v*"] 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v3.1.0 13 | - uses: erlef/setup-beam@v1.18.2 14 | with: 15 | otp-version: "27.2.1" 16 | gleam-version: "1.11.1" 17 | rebar3-version: "3" 18 | 19 | - run: | 20 | version="v$(cat gleam.toml | grep -m 1 "version" | sed -r "s/version *= *\"([[:digit:].]+)\"/\1/")" 21 | if [ "$version" != "${{ github.ref_name }}" ]; then 22 | echo "tag '${{ github.ref_name }}' does not match the version in gleam.toml" 23 | echo "expected a tag name 'v$version'" 24 | exit 1 25 | fi 26 | name: check version 27 | 28 | - run: gleam format --check 29 | 30 | - run: gleam test --target erlang 31 | 32 | - run: gleam test --target javascript 33 | 34 | - run: gleam publish -y 35 | env: 36 | HEXPM_USER: ${{ secrets.HEX_USERNAME }} 37 | HEXPM_PASS: ${{ secrets.HEX_PASSWORD }} 38 | 39 | - uses: softprops/action-gh-release@v1 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gleam-community/colour 2 | 3 | A package for a standard Colour type, conversions, and other utilities. 4 | 5 | [![Package Version](https://img.shields.io/hexpm/v/gleam_community_colour)](https://hex.pm/packages/gleam_community_colour) 6 | [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/gleam_community_colour/) 7 | 8 | ✨ This project is written in pure Gleam so you can use it anywhere Gleam runs: Erlang, Elixir, Node, Deno, and the browser! 9 | 10 | --- 11 | 12 | ## Quickstart 13 | 14 | ```gleam 15 | import gleam_community/colour 16 | import gleam_community/colour/accessibility 17 | 18 | pub fn main() { 19 | let foreground = colour.from_hsl(h: 0.858, s: 1.0, l: 0.843) 20 | 21 | let background_options = [colour.light_grey, colour.dark_grey] 22 | 23 | let background = accessibility.maximum_contrast(foreground, background_options) 24 | } 25 | ``` 26 | 27 | ## Installation 28 | 29 | `gleam_community` packages are published to [hex.pm](https://hex.pm/packages/gleam_community_colour) 30 | with the prefix `gleam_community_`. You can add them to your Gleam projects directly: 31 | 32 | ```sh 33 | gleam add gleam_community_colour 34 | ``` 35 | 36 | The docs can be found over at [hexdocs.pm](https://hexdocs.pm/gleam_community_colour). 37 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting one of the organizers or moderators of the gleam-community 59 | organization. All complaints will be reviewed and investigated and will result in a 60 | response that is deemed necessary and appropriate to the circumstances. The project team 61 | is obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is a direct decendant of the [Gleam Code of Conduct][gleam coc], 71 | which is itself a decendant of the [Contributor Covenant (v1.4)][cc]. 72 | 73 | [gleam coc]: https://github.com/gleam-lang/gleam/blob/f793b5d28a3102276a8b861c7e16a19c5231426e/CODE_OF_CONDUCT.md 74 | [cc]: https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 75 | -------------------------------------------------------------------------------- /src/gleam_community/colour/accessibility.gleam: -------------------------------------------------------------------------------- 1 | //// 2 | //// - **Accessibility** 3 | //// - [`luminance`](#luminance) 4 | //// - [`contrast_ratio`](#contrast_ratio) 5 | //// - [`maximum_contrast`](#maximum_contrast) 6 | //// 7 | //// --- 8 | //// 9 | //// This package was heavily inspired by the `elm-color-extra` module. 10 | //// The original source code can be found 11 | //// here. 12 | //// 13 | ////
14 | //// The license of that package is produced below: 15 | //// 16 | //// 17 | //// > MIT License 18 | //// 19 | //// > Copyright (c) 2016 Andreas Köberle 20 | //// 21 | //// > Permission is hereby granted, free of charge, to any person obtaining a copy 22 | //// of this software and associated documentation files (the "Software"), to deal 23 | //// in the Software without restriction, including without limitation the rights 24 | //// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | //// copies of the Software, and to permit persons to whom the Software is 26 | //// furnished to do so, subject to the following conditions: 27 | //// 28 | //// > The above copyright notice and this permission notice shall be included in all 29 | //// copies or substantial portions of the Software. 30 | //// 31 | //// > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | //// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | //// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | //// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | //// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | //// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | //// SOFTWARE. 38 | //// 39 | ////
40 | //// 41 | 42 | // Just in case we decide in the future to no longer include the above reference 43 | // and license, this package was initially a port of the `elm-color-extra` module: 44 | // 45 | // https://github.com/noahzgordon/elm-color-extra 46 | // 47 | 48 | // IMPORTS -------------------------------------------------------------------- 49 | 50 | import gleam/float 51 | import gleam/list 52 | import gleam_community/colour.{type Colour} 53 | 54 | // UTILITIES ------------------------------------------------------------------ 55 | 56 | fn intensity(colour_value: Float) -> Float { 57 | // Calculation taken from https://www.w3.org/TR/WCAG20/#relativeluminancedef 58 | case True { 59 | _ if colour_value <=. 0.03928 -> colour_value /. 12.92 60 | _ -> { 61 | // Is this guaranteed to be `OK`? 62 | let assert Ok(i) = float.power({ colour_value +. 0.055 } /. 1.055, 2.4) 63 | i 64 | } 65 | } 66 | } 67 | 68 | // ACCESSIBILITY -------------------------------------------------------------- 69 | 70 | /// Returns the relative brightness of the given `Colour` as a `Float` between 71 | /// 0.0, and 1.0 with 0.0 being the darkest possible colour and 1.0 being the lightest. 72 | /// 73 | ///
74 | /// Example: 75 | /// 76 | /// ```gleam 77 | /// fn example() { 78 | /// luminance(colour.white) // 1.0 79 | /// } 80 | /// ``` 81 | ///
82 | /// 83 | ///
84 | /// 85 | /// Spot a typo? Open an issue! 86 | /// 87 | /// 88 | /// Back to top ↑ 89 | /// 90 | ///
91 | /// 92 | pub fn luminance(colour: Colour) -> Float { 93 | // Calculation taken from https://www.w3.org/TR/WCAG20/#relativeluminancedef 94 | let #(r, g, b, _) = colour.to_rgba(colour) 95 | 96 | let r_intensity = intensity(r) 97 | let g_intensity = intensity(g) 98 | let b_intensity = intensity(b) 99 | 100 | 0.2126 *. r_intensity +. 0.7152 *. g_intensity +. 0.0722 *. b_intensity 101 | } 102 | 103 | /// Returns the contrast between two `Colour` values as a `Float` between 1.0, 104 | /// and 21.0 with 1.0 being no contrast and, 21.0 being the highest possible contrast. 105 | /// 106 | ///
107 | /// Example: 108 | /// 109 | /// ```gleam 110 | /// fn example() { 111 | /// contrast_ratio(between: colour.white, and: colour.black) // 21.0 112 | /// } 113 | /// ``` 114 | ///
115 | /// 116 | ///
117 | /// 118 | /// Spot a typo? Open an issue! 119 | /// 120 | /// 121 | /// Back to top ↑ 122 | /// 123 | ///
124 | /// 125 | pub fn contrast_ratio(between colour_a: Colour, and colour_b: Colour) -> Float { 126 | // Calculation taken from https://www.w3.org/TR/WCAG20/#contrast-ratiodef 127 | let luminance_a = luminance(colour_a) +. 0.05 128 | let luminance_b = luminance(colour_b) +. 0.05 129 | 130 | case luminance_a >. luminance_b { 131 | True -> luminance_a /. luminance_b 132 | False -> luminance_b /. luminance_a 133 | } 134 | } 135 | 136 | /// Returns the `Colour` with the highest contrast between the base `Colour`, 137 | /// and and the other provided `Colour` values. 138 | /// 139 | ///
140 | /// Example: 141 | /// 142 | /// ```gleam 143 | /// fn example() { 144 | /// maximum_contrast( 145 | /// colour.yellow, 146 | /// [colour.white, colour.dark_blue, colour.green], 147 | /// ) 148 | /// } 149 | /// ``` 150 | ///
151 | /// 152 | ///
153 | /// 154 | /// Spot a typo? Open an issue! 155 | /// 156 | /// 157 | /// Back to top ↑ 158 | /// 159 | ///
160 | /// 161 | pub fn maximum_contrast( 162 | base: Colour, 163 | colours: List(Colour), 164 | ) -> Result(Colour, Nil) { 165 | colours 166 | |> list.sort(fn(colour_a, colour_b) { 167 | let contrast_a = contrast_ratio(base, colour_a) 168 | let contrast_b = contrast_ratio(base, colour_b) 169 | 170 | float.compare(contrast_b, contrast_a) 171 | }) 172 | |> list.first() 173 | } 174 | -------------------------------------------------------------------------------- /test/colour.gleam: -------------------------------------------------------------------------------- 1 | import gleam/json 2 | import gleam_community/colour 3 | import gleeunit/should 4 | 5 | pub fn from_rgb255_test() { 6 | colour.from_rgb255(204, 0, 0) 7 | |> should.equal(Ok(colour.red)) 8 | 9 | colour.from_rgb255(114, 159, 207) 10 | |> should.equal(Ok(colour.light_blue)) 11 | 12 | colour.from_rgb255(255, 175, 243) 13 | |> should.equal(Ok(colour.pink)) 14 | } 15 | 16 | pub fn negative_from_rgb255_test() { 17 | colour.from_rgb255(-1, 0, 0) 18 | |> should.equal(Error(Nil)) 19 | } 20 | 21 | pub fn too_large_from_from_rgb255_test() { 22 | colour.from_rgb255(256, 0, 0) 23 | |> should.equal(Error(Nil)) 24 | } 25 | 26 | pub fn from_rgb_test() { 27 | colour.from_rgb(1.0, 0.6862745098039216, 0.9529411764705882) 28 | |> should.equal(Ok(colour.pink)) 29 | } 30 | 31 | pub fn negative_from_rgb_test() { 32 | colour.from_rgb(-1.0, 0.0, 0.0) 33 | |> should.equal(Error(Nil)) 34 | } 35 | 36 | pub fn too_large_from_rgb_test() { 37 | colour.from_rgb(256.0, 0.0, 0.0) 38 | |> should.equal(Error(Nil)) 39 | } 40 | 41 | pub fn from_rgba_test() { 42 | colour.from_rgba(1.0, 0.6862745098039216, 0.9529411764705882, 1.0) 43 | |> should.equal(Ok(colour.pink)) 44 | 45 | let assert Ok(pink_half_opacity) = 46 | colour.from_rgba(1.0, 0.6862745098039216, 0.9529411764705882, 0.5) 47 | 48 | pink_half_opacity 49 | |> colour.to_rgba() 50 | |> should.equal(#(1.0, 0.6862745098039216, 0.9529411764705882, 0.5)) 51 | } 52 | 53 | pub fn negative_from_rgba_test() { 54 | colour.from_rgba(-1.0, 0.0, 0.0, 1.0) 55 | |> should.equal(Error(Nil)) 56 | 57 | colour.from_rgba(1.0, 0.0, 0.0, -1.0) 58 | |> should.equal(Error(Nil)) 59 | } 60 | 61 | pub fn too_large_rgba_test() { 62 | colour.from_rgba(1.1, 0.0, 0.0, 1.0) 63 | |> should.equal(Error(Nil)) 64 | 65 | colour.from_rgba(256.0, 0.0, 0.0, 2.0) 66 | |> should.equal(Error(Nil)) 67 | } 68 | 69 | pub fn from_rgb_hex_test() { 70 | colour.from_rgb_hex(0xffaff3) 71 | |> should.equal(Ok(colour.pink)) 72 | } 73 | 74 | pub fn negative_from_rgb_hex_test() { 75 | colour.from_rgb_hex(-1 * 0xffaff3) 76 | |> should.equal(Error(Nil)) 77 | } 78 | 79 | pub fn too_large_from_rgb_hex_test() { 80 | colour.from_rgb_hex(0xfffaff3) 81 | |> should.equal(Error(Nil)) 82 | } 83 | 84 | pub fn from_rgb_hex_string_test() { 85 | colour.from_rgb_hex_string("#ffaff3") 86 | |> should.equal(Ok(colour.pink)) 87 | 88 | colour.from_rgb_hex_string("0xffaff3") 89 | |> should.equal(Ok(colour.pink)) 90 | 91 | colour.from_rgb_hex_string("ffaff3") 92 | |> should.equal(Ok(colour.pink)) 93 | } 94 | 95 | pub fn too_large_from_rgb_hex_string_test() { 96 | colour.from_rgb_hex_string("#fffaff3") 97 | |> should.equal(Error(Nil)) 98 | } 99 | 100 | pub fn from_hsl_test() { 101 | let assert Ok(c) = colour.from_hsl(0.25, 0.25, 0.5) 102 | 103 | c 104 | |> colour.to_rgba() 105 | |> should.equal(#(0.5, 0.625, 0.375, 1.0)) 106 | } 107 | 108 | pub fn negative_from_hsl_test() { 109 | colour.from_hsl(-0.25, 0.25, 0.5) 110 | |> should.equal(Error(Nil)) 111 | } 112 | 113 | pub fn too_large_from_hsl_test() { 114 | colour.from_hsl(25.0, 0.25, 0.5) 115 | |> should.equal(Error(Nil)) 116 | } 117 | 118 | pub fn from_hsla_test() { 119 | let assert Ok(c) = colour.from_hsla(h: 0.25, s: 0.25, l: 0.5, a: 1.0) 120 | 121 | colour.to_rgba(c) 122 | |> should.equal(#(0.5, 0.625, 0.375, 1.0)) 123 | } 124 | 125 | pub fn negative_from_hsla_test() { 126 | colour.from_hsla(h: -0.25, s: 0.25, l: 0.25, a: 1.0) 127 | |> should.equal(Error(Nil)) 128 | } 129 | 130 | pub fn too_large_from_hsla_test() { 131 | colour.from_hsla(h: 25.0, s: 0.25, l: 0.25, a: 1.0) 132 | |> should.equal(Error(Nil)) 133 | } 134 | 135 | pub fn to_rgba_hex_string_test() { 136 | let assert Ok(c) = colour.from_rgba(r: 1.0, g: 1.0, b: 1.0, a: 1.0) 137 | 138 | c 139 | |> colour.to_rgba_hex_string() 140 | |> should.equal("FFFFFFFF") 141 | 142 | let assert Ok(c) = colour.from_rgba(r: 1.0, g: 0.0, b: 0.0, a: 1.0) 143 | 144 | c 145 | |> colour.to_rgba_hex_string() 146 | |> should.equal("FF0000FF") 147 | 148 | colour.pink 149 | |> colour.to_rgba_hex_string() 150 | |> should.equal("FFAFF3FF") 151 | } 152 | 153 | pub fn pad_rgba_hex_string_test() { 154 | let assert Ok(c) = colour.from_rgba(r: 0.0, g: 1.0, b: 0.0, a: 1.0) 155 | 156 | c 157 | |> colour.to_rgba_hex_string() 158 | |> should.equal("00FF00FF") 159 | 160 | let assert Ok(c) = colour.from_rgba(r: 0.0, g: 0.0, b: 0.0, a: 1.0) 161 | 162 | c 163 | |> colour.to_rgba_hex_string() 164 | |> should.equal("000000FF") 165 | } 166 | 167 | pub fn to_rgb_hex_string_test() { 168 | let assert Ok(c) = colour.from_rgba(r: 1.0, g: 1.0, b: 1.0, a: 1.0) 169 | 170 | c 171 | |> colour.to_rgb_hex_string() 172 | |> should.equal("FFFFFF") 173 | 174 | let assert Ok(c) = colour.from_rgba(r: 1.0, g: 0.0, b: 0.0, a: 1.0) 175 | 176 | c 177 | |> colour.to_rgb_hex_string() 178 | |> should.equal("FF0000") 179 | 180 | colour.pink 181 | |> colour.to_rgb_hex_string() 182 | |> should.equal("FFAFF3") 183 | } 184 | 185 | pub fn pad_rgb_hex_string_test() { 186 | let assert Ok(c) = colour.from_rgb(r: 0.0, g: 1.0, b: 0.0) 187 | 188 | c 189 | |> colour.to_rgb_hex_string() 190 | |> should.equal("00FF00") 191 | 192 | let assert Ok(c) = colour.from_rgb(r: 0.0, g: 0.0, b: 0.0) 193 | 194 | c 195 | |> colour.to_rgb_hex_string() 196 | |> should.equal("000000") 197 | } 198 | 199 | pub fn to_rgba_hex_test() { 200 | let assert Ok(c) = colour.from_rgba(r: 1.0, g: 1.0, b: 1.0, a: 1.0) 201 | 202 | c 203 | |> colour.to_rgba_hex() 204 | |> should.equal(0xFFFFFFFF) 205 | 206 | let assert Ok(c) = colour.from_rgba(r: 1.0, g: 0.0, b: 0.0, a: 1.0) 207 | 208 | c 209 | |> colour.to_rgba_hex() 210 | |> should.equal(0xFF0000FF) 211 | 212 | colour.pink 213 | |> colour.to_rgba_hex() 214 | |> should.equal(0xFFAFF3FF) 215 | } 216 | 217 | pub fn to_rgb_hex_test() { 218 | let assert Ok(c) = colour.from_rgba(r: 1.0, g: 1.0, b: 1.0, a: 1.0) 219 | 220 | c 221 | |> colour.to_rgb_hex() 222 | |> should.equal(0xFFFFFF) 223 | 224 | let assert Ok(c) = colour.from_rgba(r: 1.0, g: 0.0, b: 0.0, a: 1.0) 225 | 226 | c 227 | |> colour.to_rgb_hex() 228 | |> should.equal(0xFF0000) 229 | 230 | colour.pink 231 | |> colour.to_rgb_hex() 232 | |> should.equal(0xFFAFF3) 233 | } 234 | 235 | pub fn json_identiy_test() { 236 | let assert Ok(c) = colour.from_rgba(r: 1.0, g: 1.0, b: 1.0, a: 1.0) 237 | 238 | c 239 | |> colour.encode 240 | |> json.to_string 241 | |> json.parse(colour.decoder()) 242 | |> should.equal(Ok(c)) 243 | } 244 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2023 Gleam Community Contributors 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. -------------------------------------------------------------------------------- /src/gleam_community/colour.gleam: -------------------------------------------------------------------------------- 1 | //// 2 | //// - **Types** 3 | //// - [`Colour`](#Colour) 4 | //// - [`Color`](#Color) 5 | //// - **Constructors** 6 | //// - [`from_rgb255`](#from_rgb255) 7 | //// - [`from_rgb`](#from_rgb) 8 | //// - [`from_rgba`](#from_rgba) 9 | //// - [`from_hsl`](#from_hsl) 10 | //// - [`from_hsla`](#from_hsla) 11 | //// - [`from_rgb_hex`](#from_rgb_hex) 12 | //// - [`from_rgba_hex`](#from_rgba_hex) 13 | //// - [`from_rgb_hex_string`](#from_rgb_hex_string) 14 | //// - [`from_rgba_hex_string`](#from_rgba_hex_string) 15 | //// - **Conversions** 16 | //// - [`to_rgba`](#to_rgba) 17 | //// - [`to_hsla`](#hsla) 18 | //// - [`to_css_rgba_string`](#to_css_rgba_string) 19 | //// - [`to_rgba_hex_string`](#to_rgba_hex_string) 20 | //// - [`to_rgb_hex_string`](#to_rgb_hex_string) 21 | //// - [`to_rgba_hex`](#to_rgba_hex) 22 | //// - [`to_rgb_hex`](#to_rgb_hex) 23 | //// - **JSON** 24 | //// - [`encode`](#encode) 25 | //// - [`decoder`](#decoder) 26 | //// - **Colours** 27 | //// - [`light_red`](#light_red) 28 | //// - [`red`](#red) 29 | //// - [`dark_red`](#dark_red) 30 | //// - [`light_orange`](#light_orange) 31 | //// - [`orange`](#orange) 32 | //// - [`dark_orange`](#dark_orange) 33 | //// - [`light_yellow`](#light_yellow) 34 | //// - [`yellow`](#yellow) 35 | //// - [`dark_yellow`](#dark_yellow) 36 | //// - [`light_green`](#light_green) 37 | //// - [`green`](#green) 38 | //// - [`dark_green`](#dark_green) 39 | //// - [`light_blue`](#light_blue) 40 | //// - [`blue`](#blue) 41 | //// - [`dark_blue`](#dark_blue) 42 | //// - [`light_purple`](#light_purple) 43 | //// - [`purple`](#purple) 44 | //// - [`dark_purple`](#dark_purple) 45 | //// - [`light_brown`](#light_brown) 46 | //// - [`brown`](#brown) 47 | //// - [`dark_brown`](#dark_brown) 48 | //// - [`black`](#black) 49 | //// - [`white`](#white) 50 | //// - [`light_grey`](#light_grey) 51 | //// - [`grey`](#grey) 52 | //// - [`dark_grey`](#dark_grey) 53 | //// - [`light_gray`](#light_gray) 54 | //// - [`gray`](#gray) 55 | //// - [`dark_gray`](#dark_gray) 56 | //// - [`light_charcoal`](#light_charcoal) 57 | //// - [`charcoal`](#charcoal) 58 | //// - [`dark_charcoal`](#dark_charcoal) 59 | //// - [`pink`](#pink) 60 | //// 61 | //// --- 62 | //// 63 | //// This package was heavily inspired by the `elm-color` module. 64 | //// The original source code can be found 65 | //// here. 66 | //// 67 | ////
68 | //// The license of that package is produced below: 69 | //// 70 | //// 71 | //// > MIT License 72 | //// 73 | //// > Copyright 2018 Aaron VonderHaar 74 | //// 75 | //// > Redistribution and use in source and binary forms, with or without modification, 76 | //// are permitted provided that the following conditions are met: 77 | //// 78 | //// 1. Redistributions of source code must retain the above copyright notice, 79 | //// this list of conditions and the following disclaimer. 80 | //// 81 | //// 2. Redistributions in binary form must reproduce the above copyright notice, 82 | //// this list of conditions and the following disclaimer in the documentation 83 | //// and/or other materials provided with the distribution. 84 | //// 85 | //// 3. Neither the name of the copyright holder nor the names of its contributors 86 | //// may be used to endorse or promote products derived from this software without 87 | //// specific prior written permission. 88 | //// 89 | //// > THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 90 | //// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 91 | //// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 92 | //// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 93 | //// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 94 | //// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 95 | //// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 96 | //// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 97 | //// 98 | //// > The above copyright notice and this permission notice shall be included in all 99 | //// copies or substantial portions of the Software. 100 | ////
101 | //// 102 | 103 | // Just in case we decide in the future to no longer include the above reference 104 | // and license, this package was initially a port of the `elm-color` module: 105 | // 106 | // https://github.com/avh4/elm-color/ 107 | // 108 | 109 | // IMPORTS -------------------------------------------------------------------- 110 | 111 | import gleam/dynamic/decode 112 | import gleam/float 113 | import gleam/int 114 | import gleam/json.{type Json} 115 | import gleam/list 116 | import gleam/result 117 | import gleam/string 118 | 119 | // TYPES ---------------------------------------------------------------------- 120 | 121 | /// A representation of a colour that can be converted to RGBA or HSLA format. 122 | /// 123 | ///
124 | /// 125 | /// Spot a typo? Open an issue! 126 | /// 127 | /// 128 | /// Back to top ↑ 129 | /// 130 | ///
131 | ///
132 | /// 133 | pub opaque type Colour { 134 | Rgba(r: Float, g: Float, b: Float, a: Float) 135 | Hsla(h: Float, s: Float, l: Float, a: Float) 136 | } 137 | 138 | /// Type alias for `Colour` 139 | /// 140 | ///
141 | /// 142 | /// Spot a typo? Open an issue! 143 | /// 144 | /// 145 | /// Back to top ↑ 146 | /// 147 | ///
148 | ///
149 | /// 150 | pub type Color = 151 | Colour 152 | 153 | // UTILITY -------------------------------------------------------------------- 154 | 155 | fn valid_colour_value(c: Float) -> Result(Float, Nil) { 156 | case c >. 1.0 || c <. 0.0 { 157 | True -> Error(Nil) 158 | False -> Ok(c) 159 | } 160 | } 161 | 162 | fn hue_to_rgb(hue: Float, m1: Float, m2: Float) -> Float { 163 | let h = case hue { 164 | _ if hue <. 0.0 -> hue +. 1.0 165 | _ if hue >. 1.0 -> hue -. 1.0 166 | _ -> hue 167 | } 168 | 169 | let h_t_6 = h *. 6.0 170 | let h_t_2 = h *. 2.0 171 | let h_t_3 = h *. 3.0 172 | 173 | case h { 174 | _ if h_t_6 <. 1.0 -> m1 +. { m2 -. m1 } *. h *. 6.0 175 | _ if h_t_2 <. 1.0 -> m2 176 | _ if h_t_3 <. 2.0 -> m1 +. { m2 -. m1 } *. { 2.0 /. 3.0 -. h } *. 6.0 177 | _ -> m1 178 | } 179 | } 180 | 181 | fn hex_string_to_int(hex_string: String) -> Result(Int, Nil) { 182 | let hex = case hex_string { 183 | "#" <> hex_number -> hex_number 184 | "0x" <> hex_number -> hex_number 185 | _ -> hex_string 186 | } 187 | 188 | hex 189 | |> string.lowercase() 190 | |> string.to_graphemes() 191 | |> list.reverse() 192 | |> list.index_fold(Ok(0), fn(total, char, index) { 193 | case total { 194 | Error(Nil) -> Error(Nil) 195 | Ok(v) -> { 196 | use num <- result.try(case char { 197 | "a" -> Ok(10) 198 | "b" -> Ok(11) 199 | "c" -> Ok(12) 200 | "d" -> Ok(13) 201 | "e" -> Ok(14) 202 | "f" -> Ok(15) 203 | _ -> int.parse(char) 204 | }) 205 | use base <- result.try(int.power(16, int.to_float(index))) 206 | Ok(v + float.round(int.to_float(num) *. base)) 207 | } 208 | } 209 | }) 210 | } 211 | 212 | fn hsla_to_rgba( 213 | h: Float, 214 | s: Float, 215 | l: Float, 216 | a: Float, 217 | ) -> #(Float, Float, Float, Float) { 218 | let m2 = case l <=. 0.5 { 219 | True -> l *. { s +. 1.0 } 220 | False -> l +. s -. l *. s 221 | } 222 | 223 | let m1 = l *. 2.0 -. m2 224 | 225 | let r = hue_to_rgb(h +. 1.0 /. 3.0, m1, m2) 226 | let g = hue_to_rgb(h, m1, m2) 227 | let b = hue_to_rgb(h -. 1.0 /. 3.0, m1, m2) 228 | 229 | #(r, g, b, a) 230 | } 231 | 232 | fn rgba_to_hsla( 233 | r: Float, 234 | g: Float, 235 | b: Float, 236 | a: Float, 237 | ) -> #(Float, Float, Float, Float) { 238 | let min_colour = float.min(r, float.min(g, b)) 239 | 240 | let max_colour = float.max(r, float.max(g, b)) 241 | 242 | let h1 = case True { 243 | _ if max_colour == r -> float.divide(g -. b, max_colour -. min_colour) 244 | _ if max_colour == g -> 245 | float.divide(b -. r, max_colour -. min_colour) 246 | |> result.try(fn(d) { Ok(2.0 +. d) }) 247 | _ -> 248 | float.divide(r -. g, max_colour -. min_colour) 249 | |> result.try(fn(d) { Ok(4.0 +. d) }) 250 | } 251 | 252 | let h2 = case h1 { 253 | Ok(v) -> Ok(v *. { 1.0 /. 6.0 }) 254 | _ -> h1 255 | } 256 | 257 | let h3 = case h2 { 258 | Ok(v) if v <. 0.0 -> v +. 1.0 259 | Ok(v) -> v 260 | _ -> 0.0 261 | } 262 | 263 | let l = { min_colour +. max_colour } /. 2.0 264 | 265 | let s = case True { 266 | _ if min_colour == max_colour -> 0.0 267 | _ if l <. 0.5 -> 268 | { max_colour -. min_colour } /. { max_colour +. min_colour } 269 | _ -> { max_colour -. min_colour } /. { 2.0 -. max_colour -. min_colour } 270 | } 271 | 272 | #(h3, s, l, a) 273 | } 274 | 275 | // CONSTRUCTORS --------------------------------------------------------------- 276 | 277 | /// Returns a `Result(Colour)` created from the given 8 bit RGB values. 278 | /// 279 | /// Returns `Error(Nil)` if the supplied RGB values are greater than 255 or less than 0. 280 | /// 281 | ///
282 | /// Example: 283 | /// 284 | /// ```gleam 285 | /// fn example() { 286 | /// assert Ok(red) = from_rgb255(255, 0, 0) 287 | /// } 288 | /// ``` 289 | ///
290 | /// 291 | ///
292 | /// 293 | /// Spot a typo? Open an issue! 294 | /// 295 | /// 296 | /// Back to top ↑ 297 | /// 298 | ///
299 | /// 300 | pub fn from_rgb255(r red: Int, g green: Int, b blue: Int) -> Result(Colour, Nil) { 301 | use r <- result.try( 302 | red 303 | |> int.to_float() 304 | |> float.divide(255.0) 305 | |> result.try(valid_colour_value), 306 | ) 307 | 308 | use g <- result.try( 309 | green 310 | |> int.to_float() 311 | |> float.divide(255.0) 312 | |> result.try(valid_colour_value), 313 | ) 314 | 315 | use b <- result.try( 316 | blue 317 | |> int.to_float() 318 | |> float.divide(255.0) 319 | |> result.try(valid_colour_value), 320 | ) 321 | 322 | Ok(Rgba(r: r, g: g, b: b, a: 1.0)) 323 | } 324 | 325 | /// Returns `Result(Colour)` created from the given RGB values. 326 | /// 327 | /// If the supplied RGB values are greater than 1.0 or less than 0.0 returns `Error(Nil)` 328 | /// 329 | ///
330 | /// Example: 331 | /// 332 | /// ```gleam 333 | /// fn example() { 334 | /// assert Ok(red) = from_rgb(1.0, 0.0, 0.0) 335 | /// } 336 | /// ``` 337 | ///
338 | /// 339 | ///
340 | /// 341 | /// Spot a typo? Open an issue! 342 | /// 343 | /// 344 | /// Back to top ↑ 345 | /// 346 | ///
347 | /// 348 | pub fn from_rgb( 349 | r red: Float, 350 | g green: Float, 351 | b blue: Float, 352 | ) -> Result(Colour, Nil) { 353 | use r <- result.try(valid_colour_value(red)) 354 | use g <- result.try(valid_colour_value(green)) 355 | use b <- result.try(valid_colour_value(blue)) 356 | 357 | Ok(Rgba(r: r, g: g, b: b, a: 1.0)) 358 | } 359 | 360 | /// Returns `Result(Colour)` created from the given RGBA values. 361 | /// 362 | /// Returns `Error(Nil)` if the supplied RGBA values are greater than 1.0 or less than 0.0. 363 | /// 364 | ///
365 | /// Example: 366 | /// 367 | /// ```gleam 368 | /// fn example() { 369 | /// assert Ok(red_half_opacity) = from_rbga(1.0, 0.0, 0.0, 0.5) 370 | /// } 371 | /// ``` 372 | ///
373 | /// 374 | ///
375 | /// 376 | /// Spot a typo? Open an issue! 377 | /// 378 | /// 379 | /// Back to top ↑ 380 | /// 381 | ///
382 | /// 383 | pub fn from_rgba( 384 | r red: Float, 385 | g green: Float, 386 | b blue: Float, 387 | a alpha: Float, 388 | ) -> Result(Colour, Nil) { 389 | use r <- result.try(valid_colour_value(red)) 390 | use g <- result.try(valid_colour_value(green)) 391 | use b <- result.try(valid_colour_value(blue)) 392 | use a <- result.try(valid_colour_value(alpha)) 393 | 394 | Ok(Rgba(r: r, g: g, b: b, a: a)) 395 | } 396 | 397 | /// Returns `Result(Colour)` created from the given HSLA values. 398 | /// 399 | /// Returns `Error(Nil)`f the supplied HSLA values are greater than 1.0 or less than 0.0. 400 | /// 401 | ///
402 | /// Example: 403 | /// 404 | /// ```gleam 405 | /// fn example() { 406 | /// assert Ok(red_half_opacity) = from_hsla(0.0, 1.0, 0.5, 0.5) 407 | /// } 408 | /// ``` 409 | ///
410 | /// 411 | ///
412 | /// 413 | /// Spot a typo? Open an issue! 414 | /// 415 | /// 416 | /// Back to top ↑ 417 | /// 418 | ///
419 | /// 420 | pub fn from_hsla( 421 | h hue: Float, 422 | s saturation: Float, 423 | l lightness: Float, 424 | a alpha: Float, 425 | ) -> Result(Colour, Nil) { 426 | use h <- result.try(valid_colour_value(hue)) 427 | use s <- result.try(valid_colour_value(saturation)) 428 | use l <- result.try(valid_colour_value(lightness)) 429 | use a <- result.try(valid_colour_value(alpha)) 430 | 431 | Ok(Hsla(h: h, s: s, l: l, a: a)) 432 | } 433 | 434 | /// Returns `Result(Colour)` created from the given HSL values. 435 | /// 436 | /// Returns `Error(Nil)` if the supplied HSL values are greater than 1.0 or less than 0.0. 437 | /// 438 | ///
439 | /// Example: 440 | /// 441 | /// ```gleam 442 | /// fn example() { 443 | /// assert Ok(red) = from_hsla(0.0, 1.0, 0.5) 444 | /// } 445 | /// ``` 446 | ///
447 | /// 448 | ///
449 | /// 450 | /// Spot a typo? Open an issue! 451 | /// 452 | /// 453 | /// Back to top ↑ 454 | /// 455 | ///
456 | /// 457 | pub fn from_hsl( 458 | h hue: Float, 459 | s saturation: Float, 460 | l lightness: Float, 461 | ) -> Result(Colour, Nil) { 462 | from_hsla(hue, saturation, lightness, 1.0) 463 | } 464 | 465 | /// Returns a `Result(Colour)` created from the given hex `Int`. 466 | /// 467 | /// Returns `Error(Nil)` if the supplied hex `Int is greater than 0xffffff or less than 0x0. 468 | /// 469 | ///
470 | /// Example: 471 | /// 472 | /// ```gleam 473 | /// fn example() { 474 | /// assert Ok(red) = from_rgb_hex(0xff0000) 475 | /// } 476 | /// ``` 477 | ///
478 | /// 479 | ///
480 | /// 481 | /// Spot a typo? Open an issue! 482 | /// 483 | /// 484 | /// Back to top ↑ 485 | /// 486 | ///
487 | /// 488 | pub fn from_rgb_hex(hex: Int) -> Result(Colour, Nil) { 489 | case hex > 0xffffff || hex < 0 { 490 | True -> Error(Nil) 491 | False -> { 492 | let r = 493 | int.bitwise_shift_right(hex, 16) 494 | |> int.bitwise_and(0xff) 495 | let g = 496 | int.bitwise_shift_right(hex, 8) 497 | |> int.bitwise_and(0xff) 498 | let b = int.bitwise_and(hex, 0xff) 499 | from_rgb255(r, g, b) 500 | } 501 | } 502 | } 503 | 504 | /// Returns a `Result(Colour)` created from the given RGB hex `String`. 505 | /// 506 | /// Returns `Error(Nil)` if the supplied hex `String` is invalid, or greater than `"#ffffff" or less than `"#0"` 507 | /// 508 | ///
509 | /// Example: 510 | /// 511 | /// ```gleam 512 | /// fn example() { 513 | /// assert Ok(red) = from_rgb_hex_string("#ff0000") 514 | /// } 515 | /// ``` 516 | ///
517 | /// 518 | ///
519 | /// 520 | /// Spot a typo? Open an issue! 521 | /// 522 | /// 523 | /// Back to top ↑ 524 | /// 525 | ///
526 | /// 527 | pub fn from_rgb_hex_string(hex_string: String) -> Result(Colour, Nil) { 528 | use hex_int <- result.try(hex_string_to_int(hex_string)) 529 | 530 | from_rgb_hex(hex_int) 531 | } 532 | 533 | /// Returns a `Result(Colour)` created from the given RGBA hex `String`. 534 | /// 535 | /// Returns `Error(Nil)` if the supplied hex `String` is invalid, or greater than `"#ffffffff" or less than `"#0"` 536 | /// 537 | ///
538 | /// Example: 539 | /// 540 | /// ```gleam 541 | /// fn example() { 542 | /// assert Ok(red_half_opacity) = from_rgba_hex_string("#ff00007f") 543 | /// } 544 | /// ``` 545 | ///
546 | /// 547 | ///
548 | /// 549 | /// Spot a typo? Open an issue! 550 | /// 551 | /// 552 | /// Back to top ↑ 553 | /// 554 | ///
555 | /// 556 | pub fn from_rgba_hex_string(hex_string: String) -> Result(Colour, Nil) { 557 | use hex_int <- result.try(hex_string_to_int(hex_string)) 558 | 559 | from_rgba_hex(hex_int) 560 | } 561 | 562 | /// Returns a `Result(Colour)` created from the given hex `Int`. 563 | /// 564 | /// Returns `Error(Nil)` if the supplied hex `Int is greater than 0xffffffff or less than 0x0. 565 | /// 566 | ///
567 | /// Example: 568 | /// 569 | /// ```gleam 570 | /// fn example() { 571 | /// assert Ok(red_half_opacity) = from_rgba_hex(0xff00007f) 572 | /// } 573 | /// ``` 574 | ///
575 | /// 576 | ///
577 | /// 578 | /// Spot a typo? Open an issue! 579 | /// 580 | /// 581 | /// Back to top ↑ 582 | /// 583 | ///
584 | /// 585 | pub fn from_rgba_hex(hex: Int) -> Result(Colour, Nil) { 586 | case hex > 0xffffffff || hex < 0 { 587 | True -> Error(Nil) 588 | False -> { 589 | // This won't fail because we are always dividing by 255.0 590 | let assert Ok(r) = 591 | int.bitwise_shift_right(hex, 24) 592 | |> int.bitwise_and(0xff) 593 | |> int.to_float() 594 | |> float.divide(255.0) 595 | // This won't fail because we are always dividing by 255.0 596 | let assert Ok(g) = 597 | int.bitwise_shift_right(hex, 16) 598 | |> int.bitwise_and(0xff) 599 | |> int.to_float() 600 | |> float.divide(255.0) 601 | // This won't fail because we are always dividing by 255.0 602 | let assert Ok(b) = 603 | int.bitwise_shift_right(hex, 8) 604 | |> int.bitwise_and(0xff) 605 | |> int.to_float() 606 | |> float.divide(255.0) 607 | // This won't fail because we are always dividing by 255.0 608 | let assert Ok(a) = 609 | int.bitwise_and(hex, 0xff) 610 | |> int.to_float() 611 | |> float.divide(255.0) 612 | from_rgba(r, g, b, a) 613 | } 614 | } 615 | } 616 | 617 | // CONVERSIONS ---------------------------------------------------------------- 618 | 619 | /// Returns `#(Float, Float, Float, Float)` representing the given `Colour`'s 620 | /// R, G, B, and A values respectively. 621 | /// 622 | ///
623 | /// Example: 624 | /// 625 | /// ```gleam 626 | /// fn example() { 627 | /// assert Ok(red) = from_rgb255(255, 0, 0) 628 | /// let #(r, g, b, a) = to_rgba(red) 629 | /// } 630 | /// ``` 631 | ///
632 | /// 633 | ///
634 | /// 635 | /// Spot a typo? Open an issue! 636 | /// 637 | /// 638 | /// Back to top ↑ 639 | /// 640 | ///
641 | /// 642 | pub fn to_rgba(colour: Colour) -> #(Float, Float, Float, Float) { 643 | case colour { 644 | Rgba(r, g, b, a) -> #(r, g, b, a) 645 | Hsla(h, s, l, a) -> hsla_to_rgba(h, s, l, a) 646 | } 647 | } 648 | 649 | /// Returns `#(Float, Float, Float, Float)` representing the given `Colour`'s 650 | /// H, S, L, and A values respectively. 651 | /// 652 | ///
653 | /// Example: 654 | /// 655 | /// ```gleam 656 | /// fn example() { 657 | /// assert Ok(red) = from_rgb255(255, 0, 0) 658 | /// let #(h, s, l, a) = to_hsla(red) 659 | /// } 660 | /// ``` 661 | ///
662 | /// 663 | ///
664 | /// 665 | /// Spot a typo? Open an issue! 666 | /// 667 | /// 668 | /// Back to top ↑ 669 | /// 670 | ///
671 | /// 672 | pub fn to_hsla(colour: Colour) -> #(Float, Float, Float, Float) { 673 | case colour { 674 | Hsla(h, s, l, a) -> #(h, s, l, a) 675 | Rgba(r, g, b, a) -> rgba_to_hsla(r, g, b, a) 676 | } 677 | } 678 | 679 | /// Returns an rgba formatted CSS `String` created from the given `Colour`. 680 | /// 681 | ///
682 | /// Example: 683 | /// 684 | /// ```gleam 685 | /// fn example() { 686 | /// assert Ok(red) = from_rgb255(255, 0, 0) 687 | /// let css_red = to_css_rgba_string(red) 688 | /// } 689 | /// ``` 690 | ///
691 | /// 692 | ///
693 | /// 694 | /// Spot a typo? Open an issue! 695 | /// 696 | /// 697 | /// Back to top ↑ 698 | /// 699 | ///
700 | /// 701 | pub fn to_css_rgba_string(colour: Colour) -> String { 702 | let #(r, g, b, a) = to_rgba(colour) 703 | 704 | let percent = fn(x: Float) -> Float { 705 | // This won't fail because we are always dividing by 100.0 706 | let assert Ok(p) = 707 | x 708 | |> float.multiply(10_000.0) 709 | |> float.round() 710 | |> int.to_float() 711 | |> float.divide(100.0) 712 | 713 | p 714 | } 715 | 716 | let round_to = fn(x: Float) -> Float { 717 | // This won't fail because we are always dividing by 1000.0 718 | let assert Ok(r) = 719 | x 720 | |> float.multiply(1000.0) 721 | |> float.round() 722 | |> int.to_float() 723 | |> float.divide(1000.0) 724 | 725 | r 726 | } 727 | 728 | string.join( 729 | [ 730 | "rgba(", 731 | float.to_string(percent(r)) <> "%,", 732 | float.to_string(percent(g)) <> "%,", 733 | float.to_string(percent(b)) <> "%,", 734 | float.to_string(round_to(a)), 735 | ")", 736 | ], 737 | "", 738 | ) 739 | } 740 | 741 | /// Returns an rgba hex formatted `String` created from the given `Colour`. 742 | /// 743 | ///
744 | /// Example: 745 | /// 746 | /// ```gleam 747 | /// fn example() { 748 | /// assert Ok(red) = from_rgba(1.0, 0.0, 0.0, 1.0) 749 | /// let red_hex = to_rgba_hex_string(red) 750 | /// } 751 | /// ``` 752 | ///
753 | /// 754 | ///
755 | /// 756 | /// Spot a typo? Open an issue! 757 | /// 758 | /// 759 | /// Back to top ↑ 760 | /// 761 | ///
762 | /// 763 | pub fn to_rgba_hex_string(colour: Colour) -> String { 764 | let hex_string = 765 | to_rgba_hex(colour) 766 | |> int.to_base16() 767 | 768 | case string.length(hex_string) { 769 | 8 -> hex_string 770 | l -> string.repeat("0", 8 - l) <> hex_string 771 | } 772 | } 773 | 774 | /// Returns an rgb hex formatted `String` created from the given `Colour`. 775 | /// 776 | ///
777 | /// Example: 778 | /// 779 | /// ```gleam 780 | /// fn example() { 781 | /// assert Ok(red) = from_rgba(255, 0, 0) 782 | /// let red_hex = to_rgb_hex_string(red) 783 | /// } 784 | /// ``` 785 | ///
786 | /// 787 | ///
788 | /// 789 | /// Spot a typo? Open an issue! 790 | /// 791 | /// 792 | /// Back to top ↑ 793 | /// 794 | ///
795 | /// 796 | pub fn to_rgb_hex_string(colour: Colour) -> String { 797 | let hex_string = 798 | to_rgb_hex(colour) 799 | |> int.to_base16() 800 | 801 | case string.length(hex_string) { 802 | 6 -> hex_string 803 | l -> string.repeat("0", 6 - l) <> hex_string 804 | } 805 | } 806 | 807 | /// Returns an hex `Int` created from the given `Colour`. 808 | /// 809 | ///
810 | /// Example: 811 | /// 812 | /// ```gleam 813 | /// fn example() { 814 | /// assert Ok(red) = from_rgba(1.0, 0.0, 0.0, 1.0) 815 | /// let red_hex_int = to_rgba_hex(red) 816 | /// } 817 | /// ``` 818 | ///
819 | /// 820 | ///
821 | /// 822 | /// Spot a typo? Open an issue! 823 | /// 824 | /// 825 | /// Back to top ↑ 826 | /// 827 | ///
828 | /// 829 | pub fn to_rgba_hex(colour: Colour) -> Int { 830 | let #(r, g, b, a) = to_rgba(colour) 831 | 832 | let red = 833 | r *. 255.0 834 | |> float.round() 835 | |> int.bitwise_shift_left(24) 836 | 837 | let green = 838 | g *. 255.0 839 | |> float.round() 840 | |> int.bitwise_shift_left(16) 841 | 842 | let blue = 843 | b *. 255.0 844 | |> float.round() 845 | |> int.bitwise_shift_left(8) 846 | 847 | let alpha = 848 | a *. 255.0 849 | |> float.round() 850 | 851 | red + green + blue + alpha 852 | } 853 | 854 | /// Returns a rgb hex `Int` created from the given `Colour`. 855 | /// 856 | ///
857 | /// Example: 858 | /// 859 | /// ```gleam 860 | /// fn example() { 861 | /// assert Ok(red) = from_rgba(255, 0, 0) 862 | /// let red_hex_int = to_rgb_hex(red) 863 | /// } 864 | /// ``` 865 | ///
866 | /// 867 | ///
868 | /// 869 | /// Spot a typo? Open an issue! 870 | /// 871 | /// 872 | /// Back to top ↑ 873 | /// 874 | ///
875 | /// 876 | pub fn to_rgb_hex(colour: Colour) -> Int { 877 | let #(r, g, b, _) = to_rgba(colour) 878 | 879 | let red = 880 | r *. 255.0 881 | |> float.round() 882 | |> int.bitwise_shift_left(16) 883 | 884 | let green = 885 | g *. 255.0 886 | |> float.round() 887 | |> int.bitwise_shift_left(8) 888 | 889 | let blue = 890 | b *. 255.0 891 | |> float.round() 892 | 893 | red + green + blue 894 | } 895 | 896 | // JSON ------------------------------------------------------------------------ 897 | 898 | /// Encodes a `Colour` value as a Gleam [`Json`](https://hexdocs.pm/gleam_json/gleam/json.html#Json) 899 | /// value. You'll need this if you want to send a `Colour` value over the network 900 | /// in a HTTP request or response, for example. 901 | /// 902 | ///
903 | /// 904 | /// Spot a typo? Open an issue! 905 | /// 906 | /// 907 | /// Back to top ↑ 908 | /// 909 | ///
910 | /// 911 | pub fn encode(colour: Colour) -> Json { 912 | case colour { 913 | Rgba(r, g, b, a) -> encode_rgba(r, g, b, a) 914 | Hsla(h, s, l, a) -> encode_hsla(h, s, l, a) 915 | } 916 | } 917 | 918 | fn encode_rgba(r: Float, g: Float, b: Float, a: Float) -> Json { 919 | json.object([ 920 | #("r", json.float(r)), 921 | #("g", json.float(g)), 922 | #("b", json.float(b)), 923 | #("a", json.float(a)), 924 | ]) 925 | } 926 | 927 | fn encode_hsla(h: Float, s: Float, l: Float, a: Float) -> Json { 928 | json.object([ 929 | #("h", json.float(h)), 930 | #("s", json.float(s)), 931 | #("l", json.float(l)), 932 | #("a", json.float(a)), 933 | ]) 934 | } 935 | 936 | /// Attempt to decode some [`Dynamic`](https://hexdocs.pm/gleam_stdlib/gleam/dynamic.html#Dynamic) 937 | /// value into a `Colour`. Most often you'll use this to decode some JSON. 938 | /// 939 | ///
940 | /// 941 | /// Spot a typo? Open an issue! 942 | /// 943 | /// 944 | /// Back to top ↑ 945 | /// 946 | ///
947 | /// 948 | pub fn decoder() -> decode.Decoder(Colour) { 949 | decode.one_of(rgba_decoder(), or: [hsla_decoder()]) 950 | } 951 | 952 | fn rgba_decoder() -> decode.Decoder(Colour) { 953 | use r <- decode.field("r", decode.float) 954 | use g <- decode.field("g", decode.float) 955 | use b <- decode.field("b", decode.float) 956 | use a <- decode.field("a", decode.float) 957 | 958 | decode.success(Rgba(r, g, b, a)) 959 | } 960 | 961 | fn hsla_decoder() -> decode.Decoder(Colour) { 962 | use h <- decode.field("h", decode.float) 963 | use s <- decode.field("s", decode.float) 964 | use l <- decode.field("l", decode.float) 965 | use a <- decode.field("a", decode.float) 966 | 967 | decode.success(Hsla(h, s, l, a)) 968 | } 969 | 970 | // COLOURS --------------------------------------------------------------------- 971 | 972 | /// A `Colour` reprsenting the colour RGBA(239, 41, 41, 1.0) 973 | pub const light_red = Rgba( 974 | r: 0.9372549019607843, 975 | g: 0.1607843137254902, 976 | b: 0.1607843137254902, 977 | a: 1.0, 978 | ) 979 | 980 | /// A `Colour` reprsenting the colour RGBA(204, 0, 0, 1.0) 981 | pub const red = Rgba(r: 0.8, g: 0.0, b: 0.0, a: 1.0) 982 | 983 | /// A `Colour` reprsenting the colour RGBA(164, 0, 0, 1.0) 984 | pub const dark_red = Rgba(r: 0.6431372549019608, g: 0.0, b: 0.0, a: 1.0) 985 | 986 | /// A `Colour` reprsenting the colour RGBA(252, 175, 62, 1.0) 987 | pub const light_orange = Rgba( 988 | r: 0.9882352941176471, 989 | g: 0.6862745098039216, 990 | b: 0.24313725490196078, 991 | a: 1.0, 992 | ) 993 | 994 | /// A `Colour` reprsenting the colour RGBA(245, 121, 0, 1.0) 995 | pub const orange = Rgba( 996 | r: 0.9607843137254902, 997 | g: 0.4745098039215686, 998 | b: 0.0, 999 | a: 1.0, 1000 | ) 1001 | 1002 | /// A `Colour` reprsenting the colour RGBA(206, 92, 0, 1.0) 1003 | pub const dark_orange = Rgba( 1004 | r: 0.807843137254902, 1005 | g: 0.3607843137254902, 1006 | b: 0.0, 1007 | a: 1.0, 1008 | ) 1009 | 1010 | /// A `Colour` reprsenting the colour RGBA(255, 233, 79, 1.0) 1011 | pub const light_yellow = Rgba( 1012 | r: 1.0, 1013 | g: 0.9137254901960784, 1014 | b: 0.30980392156862746, 1015 | a: 1.0, 1016 | ) 1017 | 1018 | /// A `Colour` reprsenting the colour RGBA(237, 212, 0, 1.0) 1019 | pub const yellow = Rgba( 1020 | r: 0.9294117647058824, 1021 | g: 0.8313725490196079, 1022 | b: 0.0, 1023 | a: 1.0, 1024 | ) 1025 | 1026 | /// A `Colour` reprsenting the colour RGBA(196, 160, 0, 1.0) 1027 | pub const dark_yellow = Rgba( 1028 | r: 0.7686274509803922, 1029 | g: 0.6274509803921569, 1030 | b: 0.0, 1031 | a: 1.0, 1032 | ) 1033 | 1034 | /// A `Colour` reprsenting the colour RGBA(138, 226, 52, 1.0) 1035 | pub const light_green = Rgba( 1036 | r: 0.5411764705882353, 1037 | g: 0.8862745098039215, 1038 | b: 0.20392156862745098, 1039 | a: 1.0, 1040 | ) 1041 | 1042 | /// A `Colour` reprsenting the colour RGBA(115, 210, 22, 1.0) 1043 | pub const green = Rgba( 1044 | r: 0.45098039215686275, 1045 | g: 0.8235294117647058, 1046 | b: 0.08627450980392157, 1047 | a: 1.0, 1048 | ) 1049 | 1050 | /// A `Colour` reprsenting the colour RGBA(78, 154, 6, 1.0) 1051 | pub const dark_green = Rgba( 1052 | r: 0.3058823529411765, 1053 | g: 0.6039215686274509, 1054 | b: 0.023529411764705882, 1055 | a: 1.0, 1056 | ) 1057 | 1058 | /// A `Colour` reprsenting the colour RGBA(114, 159, 207, 1.0) 1059 | pub const light_blue = Rgba( 1060 | r: 0.4470588235294118, 1061 | g: 0.6235294117647059, 1062 | b: 0.8117647058823529, 1063 | a: 1.0, 1064 | ) 1065 | 1066 | /// A `Colour` reprsenting the colour RGBA(52, 101, 164, 1.0) 1067 | pub const blue = Rgba( 1068 | r: 0.20392156862745098, 1069 | g: 0.396078431372549, 1070 | b: 0.6431372549019608, 1071 | a: 1.0, 1072 | ) 1073 | 1074 | /// A `Colour` reprsenting the colour RGBA(32, 74, 135, 1.0) 1075 | pub const dark_blue = Rgba( 1076 | r: 0.12549019607843137, 1077 | g: 0.2901960784313726, 1078 | b: 0.5294117647058824, 1079 | a: 1.0, 1080 | ) 1081 | 1082 | /// A `Colour` reprsenting the colour RGBA(173, 127, 168, 1.0) 1083 | pub const light_purple = Rgba( 1084 | r: 0.6784313725490196, 1085 | g: 0.4980392156862745, 1086 | b: 0.6588235294117647, 1087 | a: 1.0, 1088 | ) 1089 | 1090 | /// A `Colour` reprsenting the colour RGBA(117, 80, 123, 1.0) 1091 | pub const purple = Rgba( 1092 | r: 0.4588235294117647, 1093 | g: 0.3137254901960784, 1094 | b: 0.4823529411764706, 1095 | a: 1.0, 1096 | ) 1097 | 1098 | /// A `Colour` reprsenting the colour RGBA(92, 53, 102, 1.0) 1099 | pub const dark_purple = Rgba( 1100 | r: 0.3607843137254902, 1101 | g: 0.20784313725490197, 1102 | b: 0.4, 1103 | a: 1.0, 1104 | ) 1105 | 1106 | /// A `Colour` reprsenting the colour RGBA(233, 185, 110, 1.0) 1107 | pub const light_brown = Rgba( 1108 | r: 0.9137254901960784, 1109 | g: 0.7254901960784313, 1110 | b: 0.43137254901960786, 1111 | a: 1.0, 1112 | ) 1113 | 1114 | /// A `Colour` reprsenting the colour RGBA(193, 125, 17, 1.0) 1115 | pub const brown = Rgba( 1116 | r: 0.7568627450980392, 1117 | g: 0.49019607843137253, 1118 | b: 0.06666666666666667, 1119 | a: 1.0, 1120 | ) 1121 | 1122 | /// A `Colour` reprsenting the colour RGBA(143, 89, 2, 1.0) 1123 | pub const dark_brown = Rgba( 1124 | r: 0.5607843137254902, 1125 | g: 0.34901960784313724, 1126 | b: 0.00784313725490196, 1127 | a: 1.0, 1128 | ) 1129 | 1130 | /// A `Colour` reprsenting the colour RGBA(0, 0, 0, 1.0) 1131 | pub const black = Rgba(r: 0.0, g: 0.0, b: 0.0, a: 1.0) 1132 | 1133 | /// A `Colour` reprsenting the colour RGBA(255, 255, 255, 1.0) 1134 | pub const white = Rgba(r: 1.0, g: 1.0, b: 1.0, a: 1.0) 1135 | 1136 | /// A `Colour` reprsenting the colour RGBA(238, 238, 236, 1.0) 1137 | pub const light_grey = Rgba( 1138 | r: 0.9333333333333333, 1139 | g: 0.9333333333333333, 1140 | b: 0.9254901960784314, 1141 | a: 1.0, 1142 | ) 1143 | 1144 | /// A `Colour` reprsenting the colour RGBA(211, 215, 207, 1.0) 1145 | pub const grey = Rgba( 1146 | r: 0.8274509803921568, 1147 | g: 0.8431372549019608, 1148 | b: 0.8117647058823529, 1149 | a: 1.0, 1150 | ) 1151 | 1152 | /// A `Colour` reprsenting the colour RGBA(186, 189, 182, 1.0) 1153 | pub const dark_grey = Rgba( 1154 | r: 0.7294117647058823, 1155 | g: 0.7411764705882353, 1156 | b: 0.7137254901960784, 1157 | a: 1.0, 1158 | ) 1159 | 1160 | /// A `Colour` reprsenting the colour RGBA(238, 238, 236, 1.0) 1161 | pub const light_gray = Rgba( 1162 | r: 0.9333333333333333, 1163 | g: 0.9333333333333333, 1164 | b: 0.9254901960784314, 1165 | a: 1.0, 1166 | ) 1167 | 1168 | /// A `Colour` reprsenting the colour RGBA(211, 215, 207, 1.0) 1169 | pub const gray = Rgba( 1170 | r: 0.8274509803921568, 1171 | g: 0.8431372549019608, 1172 | b: 0.8117647058823529, 1173 | a: 1.0, 1174 | ) 1175 | 1176 | /// A `Colour` reprsenting the colour RGBA(186, 189, 182, 1.0) 1177 | pub const dark_gray = Rgba( 1178 | r: 0.7294117647058823, 1179 | g: 0.7411764705882353, 1180 | b: 0.7137254901960784, 1181 | a: 1.0, 1182 | ) 1183 | 1184 | /// A `Colour` reprsenting the colour RGBA(136, 138, 133, 1.0) 1185 | pub const light_charcoal = Rgba( 1186 | r: 0.5333333333333333, 1187 | g: 0.5411764705882353, 1188 | b: 0.5215686274509804, 1189 | a: 1.0, 1190 | ) 1191 | 1192 | /// A `Colour` reprsenting the colour RGBA(85, 87, 83, 1.0) 1193 | pub const charcoal = Rgba( 1194 | r: 0.3333333333333333, 1195 | g: 0.3411764705882353, 1196 | b: 0.3254901960784314, 1197 | a: 1.0, 1198 | ) 1199 | 1200 | /// A `Colour` reprsenting the colour RGBA(46, 52, 54, 1.0) 1201 | pub const dark_charcoal = Rgba( 1202 | r: 0.1803921568627451, 1203 | g: 0.20392156862745098, 1204 | b: 0.21176470588235294, 1205 | a: 1.0, 1206 | ) 1207 | 1208 | /// A `Colour` reprsenting the colour RGBA(255, 175, 243, 1.0) 1209 | pub const pink = Rgba( 1210 | r: 1.0, 1211 | g: 0.6862745098039216, 1212 | b: 0.9529411764705882, 1213 | a: 1.0, 1214 | ) 1215 | --------------------------------------------------------------------------------