├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── actions │ ├── generate_release_notes │ │ ├── LICENSE-APACHE │ │ ├── LICENSE-MIT │ │ ├── README.md │ │ ├── action.yml │ │ ├── dist │ │ │ └── index.js │ │ ├── local │ │ │ └── index.js │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── src │ │ │ ├── generate.ts │ │ │ ├── index.ts │ │ │ └── run_local.ts │ │ └── tsconfig.json │ └── increment_version_number │ │ ├── LICENSE-APACHE │ │ ├── LICENSE-MIT │ │ ├── README.md │ │ ├── action.yml │ │ ├── dist │ │ └── index.js │ │ ├── local │ │ └── index.js │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── src │ │ ├── edit_toml.ts │ │ ├── increment.ts │ │ ├── index.ts │ │ └── run_local.ts │ │ └── tsconfig.json ├── pull_request_template.md └── workflows │ ├── benchmark.yml │ ├── ci.yml │ ├── coverage.yml │ ├── prepare_release.yml │ └── publish_docs.yml ├── .gitignore ├── .vscode └── settings.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benchmarks ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT └── benches │ ├── cie.rs │ ├── matrix.rs │ └── rgb.rs ├── bors.toml ├── codecov.yml ├── codegen ├── Cargo.toml ├── res │ └── svg_colors.txt └── src │ ├── codegen_file.rs │ ├── lut.rs │ ├── lut │ └── model.rs │ ├── main.rs │ └── named.rs ├── example-data ├── blend.svg ├── compose.svg └── input │ ├── README.md │ ├── blend_fg.png │ ├── cat-128.png │ ├── cat.png │ ├── compose_bg.png │ ├── compose_fg.png │ ├── fruits-128.png │ └── fruits.png ├── gfx ├── readme_color_operations_1.png ├── readme_color_operations_2.png ├── readme_converting.png ├── readme_gradients_1.png └── readme_pixels_and_buffers.png ├── integration_tests ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── regression_tests │ └── issue_283.rs └── tests │ ├── color_checker.rs │ ├── color_checker_data │ ├── babel.csv │ ├── babel.rs │ ├── color_checker.csv │ ├── color_checker.rs │ ├── load_data.rs │ └── mod.rs │ ├── color_convert.rs │ ├── convert │ ├── data_cie_15_2004.csv │ ├── data_cie_15_2004.rs │ ├── data_ciede_2000.csv │ ├── data_ciede_2000.rs │ ├── data_color_mine.csv │ ├── data_color_mine.rs │ ├── lab_lch.rs │ └── mod.rs │ ├── hsluv_convert.rs │ ├── hsluv_dataset │ ├── LICENSE.hsluv_dataset │ ├── hsluv_dataset.json │ ├── hsluv_dataset.rs │ └── mod.rs │ ├── pointer_convert.rs │ └── pointer_dataset │ ├── mod.rs │ ├── pointer_data.csv │ └── pointer_data.rs ├── no_std_test ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT └── src │ └── main.rs ├── palette ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples │ ├── blend.rs │ ├── compose.rs │ ├── gradient.rs │ ├── hue.rs │ ├── random.rs │ ├── readme_examples.rs │ ├── saturate.rs │ ├── shade.rs │ └── struct_of_arrays.rs └── src │ ├── alpha.rs │ ├── alpha │ └── alpha.rs │ ├── angle.rs │ ├── angle │ └── wide.rs │ ├── blend.rs │ ├── blend │ ├── blend.rs │ ├── blend_with.rs │ ├── compose.rs │ ├── equations.rs │ ├── pre_alpha.rs │ └── test.rs │ ├── bool_mask.rs │ ├── bool_mask │ └── wide.rs │ ├── cam16.rs │ ├── cam16 │ ├── full.rs │ ├── math.rs │ ├── math │ │ ├── chromaticity.rs │ │ └── luminance.rs │ ├── parameters.rs │ ├── partial.rs │ ├── ucs_jab.rs │ └── ucs_jmh.rs │ ├── cast.rs │ ├── cast │ ├── array.rs │ ├── as_arrays_traits.rs │ ├── as_components_traits.rs │ ├── as_uints_traits.rs │ ├── from_into_arrays_traits.rs │ ├── from_into_components_traits.rs │ ├── from_into_uints_traits.rs │ ├── packed.rs │ └── uint.rs │ ├── chromatic_adaptation.rs │ ├── color_difference.rs │ ├── color_theory.rs │ ├── convert.rs │ ├── convert │ ├── from_into_color.rs │ ├── from_into_color_mut.rs │ ├── from_into_color_unclamped.rs │ ├── from_into_color_unclamped_mut.rs │ ├── matrix3.rs │ └── try_from_into_color.rs │ ├── encoding.rs │ ├── encoding │ ├── adobe.rs │ ├── gamma.rs │ ├── linear.rs │ ├── lut.rs │ ├── lut │ │ └── codegen.rs │ ├── p3.rs │ ├── prophoto.rs │ ├── rec_standards.rs │ └── srgb.rs │ ├── hsl.rs │ ├── hsluv.rs │ ├── hsv.rs │ ├── hues.rs │ ├── hwb.rs │ ├── lab.rs │ ├── lch.rs │ ├── lchuv.rs │ ├── lib.rs │ ├── lms.rs │ ├── lms │ ├── lms.rs │ └── matrix.rs │ ├── luma.rs │ ├── luma │ ├── channels.rs │ └── luma.rs │ ├── luv.rs │ ├── luv_bounds.rs │ ├── macros.rs │ ├── macros │ ├── arithmetics.rs │ ├── blend.rs │ ├── casting.rs │ ├── clamp.rs │ ├── color_difference.rs │ ├── color_theory.rs │ ├── convert.rs │ ├── copy_clone.rs │ ├── equality.rs │ ├── hue.rs │ ├── lazy_select.rs │ ├── lighten_saturate.rs │ ├── mix.rs │ ├── random.rs │ ├── reference_component.rs │ ├── simd.rs │ └── struct_of_arrays.rs │ ├── matrix.rs │ ├── named.rs │ ├── named │ └── codegen.rs │ ├── num.rs │ ├── num │ ├── libm.rs │ └── wide.rs │ ├── ok_utils.rs │ ├── okhsl.rs │ ├── okhsl │ ├── alpha.rs │ ├── properties.rs │ ├── random.rs │ └── visual_eq.rs │ ├── okhsv.rs │ ├── okhsv │ ├── alpha.rs │ ├── properties.rs │ ├── random.rs │ └── visual_eq.rs │ ├── okhwb.rs │ ├── okhwb │ ├── alpha.rs │ ├── properties.rs │ ├── random.rs │ └── visual_eq.rs │ ├── oklab.rs │ ├── oklab │ ├── alpha.rs │ ├── properties.rs │ ├── random.rs │ └── visual_eq.rs │ ├── oklch.rs │ ├── oklch │ ├── alpha.rs │ ├── properties.rs │ └── random.rs │ ├── random_sampling.rs │ ├── random_sampling │ └── cone.rs │ ├── relative_contrast.rs │ ├── rgb.rs │ ├── rgb │ ├── channels.rs │ ├── hex.rs │ └── rgb.rs │ ├── serde.rs │ ├── serde │ ├── alpha_deserializer.rs │ └── alpha_serializer.rs │ ├── stimulus.rs │ ├── visual.rs │ ├── white_point.rs │ ├── xyz.rs │ ├── xyz │ └── meta.rs │ └── yxy.rs └── palette_derive ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md └── src ├── alpha ├── mod.rs └── with_alpha.rs ├── cast ├── array_cast.rs └── mod.rs ├── color_types.rs ├── convert ├── from_color_unclamped.rs ├── mod.rs └── util.rs ├── lib.rs ├── meta ├── field_attributes.rs ├── mod.rs └── type_item_attributes.rs └── util.rs /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug/Defect Report 3 | about: Use this template when reporting some problem with the library. 4 | labels: defect 5 | --- 6 | 7 | 8 | 9 | ## How To Reproduce 10 | 11 | 12 | 13 | ## Expected Outcome 14 | 15 | 16 | 17 | ## Actual Outcome 18 | 19 | 20 | 21 | ## Additional Details 22 | 23 | * Cargo.toml entry: 24 | * Rust version(s): 25 | * Target platform: 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Questions and discussions 4 | url: https://github.com/Ogeon/palette/discussions 5 | about: Ask questions or discuss Pallete in general here. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Use this template when requesting a feature addition or change in the library. 4 | --- 5 | 6 | ## Description 7 | 8 | 9 | 10 | ## Motivation 11 | 12 | 13 | -------------------------------------------------------------------------------- /.github/actions/generate_release_notes/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Erik Hedvall 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /.github/actions/generate_release_notes/README.md: -------------------------------------------------------------------------------- 1 | # Generate Release Notes 2 | 3 | This Github action generates release notes from the commit history. It uses git tags to know where each release point is and requests commits between those versions. Each merge commit with a `#123` pull request number is collected as a log entry if the linked pull request isn't labelled with a label named "internal" (case insensitive). 4 | 5 | ## Github Action Usage 6 | 7 | Example usage: 8 | 9 | ```yml 10 | name: Prepare release 11 | on: 12 | workflow_dispatch: 13 | inputs: 14 | version: 15 | description: The new version number 16 | required: true 17 | 18 | jobs: 19 | compile_and_test: 20 | name: Update release notes 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v2.3.4 24 | with: 25 | fetch-depth: 0 # We want the full history and all tags 26 | - name: Generate release notes 27 | uses: ./.github/actions/generate_release_notes 28 | with: 29 | version: ${{ github.event.inputs.version }} 30 | token: ${{ secrets.GITHUB_TOKEN }} # Optional, defaults to ${{ github.token }} 31 | file: CHANGELOG.md # Optional, this is the default value 32 | 33 | # ... 34 | ``` 35 | 36 | ## Local Usage 37 | 38 | The action can also run as a local version, mainly for inspection, from an alternate entry point. Make sure you have node.js and npm installed. Then run 39 | 40 | ```shell 41 | npm install 42 | ``` 43 | 44 | to install the dependencies. Then run the script by providing the repository owner and name: 45 | 46 | ```shell 47 | node local/index.js MyUserName MyRepoName 48 | ``` 49 | 50 | Or add a version argument to set the upcoming version instead of "Unreleased": 51 | 52 | ```shell 53 | node local/index.js MyUserName MyRepoName 1.2.3 54 | ``` 55 | 56 | The resulting output will be printed in markdown format to `stdout`. 57 | 58 | ## Developing 59 | 60 | The action is implemented in Typescript. To rebuild the files in `local`, run 61 | 62 | ```shell 63 | npm run build 64 | ``` 65 | 66 | ## License 67 | 68 | Licensed under either of 69 | 70 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) 71 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or ) 72 | 73 | at your option. 74 | 75 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 76 | -------------------------------------------------------------------------------- /.github/actions/generate_release_notes/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Generate Release Notes' 2 | description: 'Generate release notes from merged pull requests' 3 | inputs: 4 | version: 5 | description: 'The next version to be released' 6 | required: true 7 | token: 8 | description: 'An access token for Github API calls' 9 | default: ${{ github.token }} 10 | file: 11 | description: 'The change log file to update (default is CHANGELOG.md)' 12 | default: CHANGELOG.md 13 | runs: 14 | using: 'node12' 15 | main: 'dist/index.js' 16 | -------------------------------------------------------------------------------- /.github/actions/generate_release_notes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generate_release_notes", 3 | "version": "1.0.0", 4 | "description": "Generate release notes from merged pull requests", 5 | "main": "dist/index.js", 6 | "keywords": [], 7 | "author": "", 8 | "license": "MIT OR Apache-2.0", 9 | "scripts": { 10 | "build": "ncc build src/index.ts -m -o dist && ncc build src/run_local.ts -m -o local" 11 | }, 12 | "dependencies": { 13 | "@actions/core": "^1.9.1", 14 | "@actions/github": "^6.0.0", 15 | "@octokit/core": "^5.0.2", 16 | "@octokit/plugin-paginate-rest": "^9.2.2", 17 | "semver": "^7.5.2", 18 | "simple-git": "^3.16.0" 19 | }, 20 | "devDependencies": { 21 | "@types/node": "^20.11.0", 22 | "@types/semver": "^7.3.6", 23 | "@vercel/ncc": "^0.38.1", 24 | "typescript": "~5.3.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/actions/generate_release_notes/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core'; 2 | import * as github from '@actions/github'; 3 | import { promises as fs } from 'fs'; 4 | import { generate } from './generate'; 5 | 6 | const token = core.getInput('token', { required: true }); 7 | const version = core.getInput('version', { required: true }); 8 | const outFile = core.getInput('file', { required: true }); 9 | const { owner, repo } = github.context.repo; 10 | 11 | const octokit = github.getOctokit(token); 12 | 13 | (async () => { 14 | try { 15 | const output = await generate(octokit, owner, repo, version); 16 | await fs.writeFile(outFile, output); 17 | } catch (error) { 18 | core.setFailed(`could not generate release notes: ${error}`); 19 | } 20 | })() 21 | 22 | 23 | -------------------------------------------------------------------------------- /.github/actions/generate_release_notes/src/run_local.ts: -------------------------------------------------------------------------------- 1 | import { Octokit } from '@octokit/core' 2 | import { generate } from './generate'; 3 | import { paginateRest } from '@octokit/plugin-paginate-rest'; 4 | 5 | const octokit = new (Octokit.plugin(paginateRest)); 6 | const owner = process.argv[2]; 7 | const repo = process.argv[3]; 8 | const nextVersion = process.argv[4] 9 | 10 | if (!owner || !repo) { 11 | console.error(`usage: ${process.argv[0]} ${process.argv[1]} OwnerName RepoName [1.2.3]`); 12 | process.exit(1); 13 | } 14 | 15 | (async () => { 16 | try { 17 | const output = await generate(octokit, owner, repo, nextVersion); 18 | console.log(output); 19 | } catch (error) { 20 | console.error(`could not generate release notes: ${error}`); 21 | process.exit(1); 22 | } 23 | })() 24 | -------------------------------------------------------------------------------- /.github/actions/generate_release_notes/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "alwaysStrict": true, 5 | "moduleResolution": "node", 6 | "module": "CommonJS", 7 | "target": "ES2019", 8 | }, 9 | "compileOnSave": true 10 | } 11 | -------------------------------------------------------------------------------- /.github/actions/increment_version_number/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Erik Hedvall 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /.github/actions/increment_version_number/README.md: -------------------------------------------------------------------------------- 1 | # Generate Release Notes 2 | 3 | This Github action updates the version number of a crate and its dependencies to specific version numbers. This is useful for incrementing the version when preparing a release. Version numbers that are updated: 4 | 5 | * In Cargo.toml: 6 | * The crate version 7 | * Version numbers in the documentation link 8 | * Any dependency that is set through the `dependencies` input. 9 | * In compilation targets (i.e. `lib.rs`, etc.): 10 | * Version numbers in the `#![doc(html_root_url = "...")]` attribute. 11 | * In `README.md`: 12 | * Any mention of `my_crate = "1.2.3"`. 13 | * Any mention of `version = "1.2.3"`. 14 | 15 | ## Github Action Usage 16 | 17 | Example usage: 18 | 19 | ```yml 20 | name: Prepare release 21 | on: 22 | workflow_dispatch: 23 | inputs: 24 | version: 25 | description: The new version number 26 | required: true 27 | 28 | jobs: 29 | compile_and_test: 30 | name: Update crate versions 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v2.3.4 34 | - name: Increment version for my_crate 35 | uses: ./.github/actions/increment_version_number 36 | with: 37 | version: ${{ github.event.inputs.version }} 38 | crate: my_crate # Optional, for when the root directory isn't the crate directory. 39 | dependencies: '{"companion_crate": "${{ github.event.inputs.version }}"}' 40 | # ^~~ Optional, for when dependencies are updated at the same time. 41 | 42 | # ... 43 | ``` 44 | 45 | ## Local Usage 46 | 47 | The action can also run as a local version, mainly for inspection, from an alternate entry point. Make sure you have node.js and npm installed. 48 | 49 | ***Note!*** *The files will be changed. This is not a dry run!* 50 | 51 | ```shell 52 | npm install 53 | ``` 54 | 55 | will install the dependencies. Then run the script by providing the repository owner and name: 56 | 57 | ```shell 58 | node local/index.js path/to/crate 1.2.3 59 | ``` 60 | 61 | Or add a version argument to set the upcoming version instead of "Unreleased": 62 | 63 | ```shell 64 | node local/index.js path/to/crate 1.2.3 '{"companion_crate": "4.5.6"}' 65 | ``` 66 | 67 | ## Developing 68 | 69 | The action is implemented in Typescript. To rebuild the files in `local`, run 70 | 71 | ```shell 72 | npm run build 73 | ``` 74 | 75 | ## License 76 | 77 | Licensed under either of 78 | 79 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) 80 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or ) 81 | 82 | at your option. 83 | 84 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 85 | -------------------------------------------------------------------------------- /.github/actions/increment_version_number/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Increment Version Number' 2 | description: 'Finds mentions of the old version number in the project and increments them.' 3 | inputs: 4 | version: 5 | description: 'The new version number.' 6 | required: true 7 | crate: 8 | description: 'An optional path to the crate directory, where Cargo.toml can be found. Defaults to the current directory.' 9 | dependencies: 10 | description: 'An optional, JSON encoded mapping of dependency versions to change. Example: "example_crate": "1.2.3"}.' 11 | runs: 12 | using: 'node12' 13 | main: 'dist/index.js' 14 | -------------------------------------------------------------------------------- /.github/actions/increment_version_number/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "increment_version_number", 3 | "version": "1.0.0", 4 | "description": "Finds mentions of the old version number in the project and increments them", 5 | "main": "dist/index.js", 6 | "keywords": [], 7 | "author": "", 8 | "license": "MIT OR Apache-2.0", 9 | "scripts": { 10 | "build": "ncc build src/index.ts -m -o dist && ncc build src/run_local.ts -m -o local" 11 | }, 12 | "dependencies": { 13 | "@actions/core": "^1.9.1", 14 | "@actions/exec": "^1.1.0", 15 | "@actions/io": "^1.1.1", 16 | "@iarna/toml": "^2.2.5", 17 | "@toml-tools/lexer": "^1.0.0", 18 | "chevrotain": "^11.0.3", 19 | "semver": "^7.5.2" 20 | }, 21 | "devDependencies": { 22 | "@types/iarna__toml": "^2.0.2", 23 | "@types/node": "^20.11.0", 24 | "@types/semver": "^7.3.6", 25 | "@vercel/ncc": "^0.38.1", 26 | "typescript": "~5.3.3" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.github/actions/increment_version_number/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core'; 2 | import { increment, isDependencies } from './increment'; 3 | 4 | const version = core.getInput('version', { required: true }); 5 | const crate = core.getInput('crate', { required: false }); 6 | const dependenciesJSON = core.getInput('dependencies'); 7 | 8 | (async () => { 9 | let dependencies: unknown; 10 | 11 | try { 12 | dependencies = dependenciesJSON.length ? JSON.parse(dependenciesJSON) : {}; 13 | } catch (error) { 14 | core.setFailed(`could not parse dependencies as JSON: ${error}`); 15 | return; 16 | } 17 | 18 | if (!isDependencies(dependencies)) { 19 | core.setFailed(`expected dependencies as JSON data: {"example_crate": "1.2.3"}`); 20 | return; 21 | } 22 | try { 23 | await increment(crate.length ? crate : undefined, version, dependencies); 24 | } catch (error) { 25 | core.setFailed(`could not increment versions: ${error}`); 26 | } 27 | })(); 28 | -------------------------------------------------------------------------------- /.github/actions/increment_version_number/src/run_local.ts: -------------------------------------------------------------------------------- 1 | import * as process from 'process'; 2 | import { increment, isDependencies } from './increment'; 3 | 4 | const version = process.argv[2].trim(); 5 | const crate = process.argv[3].trim(); 6 | const dependenciesJSON = process.argv[4]; 7 | 8 | (async () => { 9 | let dependencies: unknown; 10 | 11 | try { 12 | dependencies = dependenciesJSON.length ? JSON.parse(dependenciesJSON) : {}; 13 | } catch (error) { 14 | console.error(`could not parse dependencies as JSON: ${error}`); 15 | return; 16 | } 17 | 18 | if (!isDependencies(dependencies)) { 19 | console.error(`expected dependencies as JSON data: {"example_crate": "1.2.3"}`); 20 | return; 21 | } 22 | try { 23 | await increment(crate.length ? crate : undefined, version, dependencies); 24 | } catch (error) { 25 | console.error(`could not increment versions: ${error}`); 26 | } 27 | })(); 28 | -------------------------------------------------------------------------------- /.github/actions/increment_version_number/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "alwaysStrict": true, 5 | "moduleResolution": "node", 6 | "module": "CommonJS", 7 | "target": "ES2019", 8 | }, 9 | "compileOnSave": true 10 | } 11 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 15 | -------------------------------------------------------------------------------- /.github/workflows/benchmark.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | paths: 6 | - '**.rs' 7 | pull_request: 8 | paths: 9 | - '**.rs' 10 | - '.github/workflows/benchmark.yml' 11 | # `workflow_dispatch` allows CodSpeed to trigger backtest 12 | # performance analysis in order to generate initial data. 13 | workflow_dispatch: 14 | 15 | name: Benchmark pull requests 16 | jobs: 17 | runBenchmark: 18 | name: Run benchmark 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 0 24 | - name: Setup rust toolchain, cache and cargo-codspeed binary 25 | uses: moonrepo/setup-rust@v0 26 | with: 27 | channel: stable 28 | cache-target: release 29 | bins: cargo-codspeed 30 | - name: Build the benchmark target(s) 31 | run: cargo codspeed build -p benchmarks 32 | - name: Run the benchmarks 33 | uses: CodSpeedHQ/action@v3 34 | with: 35 | run: cargo codspeed run 36 | token: ${{ secrets.CODSPEED_TOKEN }} 37 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | branches: 4 | - master 5 | paths: 6 | - '**.rs' 7 | - 'codecov.yml' 8 | push: 9 | branches: 10 | - master 11 | paths: 12 | - '**.rs' 13 | - 'codecov.yml' 14 | 15 | name: Test coverage 16 | 17 | jobs: 18 | coverage: 19 | runs-on: ubuntu-latest 20 | env: 21 | CARGO_TERM_COLOR: always 22 | steps: 23 | - uses: actions/checkout@v4 24 | with: 25 | fetch-depth: 0 26 | - uses: dtolnay/rust-toolchain@nightly 27 | - uses: taiki-e/install-action@cargo-llvm-cov 28 | - name: Collect code coverage 29 | run: cargo +nightly llvm-cov --all-features -p integration_tests -p palette --codecov --doctests --output-path codecov.json 30 | - name: Upload coverage to Codecov 31 | uses: codecov/codecov-action@v3 32 | with: 33 | token: ${{ secrets.CODECOV_TOKEN }} 34 | files: codecov.json 35 | -------------------------------------------------------------------------------- /.github/workflows/prepare_release.yml: -------------------------------------------------------------------------------- 1 | name: Prepare release 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | version: 6 | description: The new version number 7 | required: true 8 | 9 | jobs: 10 | compile_and_test: 11 | name: Update version and release notes 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 # We want the full history and all tags 17 | - name: Increment version for palette_derive 18 | uses: ./.github/actions/increment_version_number 19 | with: 20 | version: ${{ github.event.inputs.version }} 21 | crate: palette_derive 22 | - name: Increment version for palette 23 | uses: ./.github/actions/increment_version_number 24 | with: 25 | version: ${{ github.event.inputs.version }} 26 | crate: palette 27 | dependencies: '{"palette_derive": "${{ github.event.inputs.version }}"}' 28 | - name: Increment version in README.md 29 | run: 'sed -i ''s/\[Released\](https:\/\/docs.rs\/palette\/.*\/palette\/)/[Released](https:\/\/docs.rs\/palette\/${{ github.event.inputs.version }}\/palette\/)/'' README.md' 30 | - name: Generate release notes 31 | uses: ./.github/actions/generate_release_notes 32 | with: 33 | version: ${{ github.event.inputs.version }} 34 | - name: Create Pull Request 35 | uses: peter-evans/create-pull-request@v3 36 | with: 37 | commit-message: Prepare release ${{ github.event.inputs.version }} 38 | branch: prepare_${{ github.event.inputs.version }} 39 | delete-branch: true 40 | title: Version ${{ github.event.inputs.version }} 41 | body: Prepare release ${{ github.event.inputs.version }} 42 | labels: internal 43 | -------------------------------------------------------------------------------- /.github/workflows/publish_docs.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | 6 | name: Publish documentation 7 | 8 | jobs: 9 | documentation: 10 | name: Publish 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: dtolnay/rust-toolchain@stable 15 | - name: Generate 16 | run: cargo doc --package palette --no-deps --all-features 17 | - name: Upload 18 | uses: JamesIves/github-pages-deploy-action@4.1.4 19 | with: 20 | branch: gh-pages 21 | folder: target/doc 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | example-data/output/* 4 | node_modules 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.features": [ 3 | "random", 4 | "serializing", 5 | "bytemuck", 6 | "wide" 7 | ], 8 | "rust-analyzer.imports.granularity.enforce": true, 9 | "rust-analyzer.imports.granularity.group": "crate", 10 | "rust-analyzer.imports.group.enable": true, 11 | "rust-analyzer.check.command": "clippy", 12 | "rust-analyzer.imports.preferNoStd": true 13 | } 14 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "palette", 4 | "palette_derive", 5 | 6 | # Test crates 7 | "integration_tests", 8 | "no_std_test", 9 | "benchmarks", 10 | 11 | # Tool crates 12 | "codegen" 13 | ] 14 | resolver = "2" 15 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Erik Hedvall 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # palette 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/palette.svg)](https://crates.io/crates/palette/) 4 | [![Docs.rs](https://docs.rs/palette/badge.svg)](https://docs.rs/palette) 5 | 6 | A color management and conversion library that focuses on maintaining correctness, flexibility and ease of use. It makes use of the type system to prevent mistakes, support a wide range of color spaces including user defined variants and offer different ways of integrating with other libraries. 7 | 8 | [Usage and examples can be found in the `palette` directory](https://github.com/Ogeon/palette/tree/master/palette). 9 | 10 | ## Online Documentation 11 | 12 | [Released](https://docs.rs/palette/0.7.6/palette/) 13 | 14 | [Master branch](https://ogeon.github.io/palette/palette/index.html) 15 | 16 | ## Minimum Supported Rust Version (MSRV) 17 | 18 | This version of Palette has been automatically tested with Rust version `1.61.0`, `1.63.0`, and the `stable`, `beta`, and `nightly` channels. The minimum supported version may vary with the set of enabled features. 19 | 20 | Future versions of the library may advance the minimum supported version to make use of new language features, but this will normally be considered a breaking change. Exceptions may be made for security patches, dependencies advancing their MSRV in minor or patch releases, and similar changes. 21 | 22 | ## Contributing 23 | 24 | All sorts of contributions are welcome (including especially speling corrections), so take a look at [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines, if you are interested. 25 | 26 | ## License 27 | 28 | Licensed under either of 29 | 30 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) 31 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or ) 32 | 33 | at your option. 34 | 35 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 36 | -------------------------------------------------------------------------------- /benchmarks/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "benchmarks" 3 | version = "0.0.0" 4 | authors = ["Erik Hedvall "] 5 | exclude = [] 6 | description = "Benchmark crate for palette." 7 | repository = "https://github.com/Ogeon/palette" 8 | license = "MIT OR Apache-2.0" 9 | edition = "2021" 10 | publish = false 11 | 12 | [[bench]] 13 | path = "benches/cie.rs" 14 | name = "cie_conversion" 15 | harness = false 16 | 17 | [[bench]] 18 | path = "benches/rgb.rs" 19 | name = "rgb_conversion" 20 | harness = false 21 | 22 | [[bench]] 23 | path = "benches/matrix.rs" 24 | name = "matrix" 25 | harness = false 26 | 27 | [dev-dependencies] 28 | approx = { version = "0.5", default-features = false } 29 | codspeed-criterion-compat = "2.1.0" 30 | criterion = { version = "0.5.1", default-features = false } 31 | csv = "1" 32 | lazy_static = "1" 33 | palette = { path = "../palette", features = ["wide"] } 34 | serde = "1" 35 | serde_derive = "1" 36 | wide = "0.7.3" 37 | -------------------------------------------------------------------------------- /benchmarks/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /benchmarks/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /benchmarks/benches/matrix.rs: -------------------------------------------------------------------------------- 1 | use codspeed_criterion_compat::{black_box, criterion_group, criterion_main, Criterion}; 2 | 3 | use palette::encoding; 4 | use palette::matrix::{matrix_inverse, multiply_3x3, rgb_to_xyz_matrix}; 5 | 6 | fn matrix(c: &mut Criterion) { 7 | let mut group = c.benchmark_group("Matrix functions"); 8 | 9 | let inp1 = [1.0f32, 2.0, 3.0, 3.0, 2.0, 1.0, 2.0, 1.0, 3.0]; 10 | let inp2 = [4.0, 5.0, 6.0, 6.0, 5.0, 4.0, 4.0, 6.0, 5.0]; 11 | let inverse: [f32; 9] = [3.0, 0.0, 2.0, 2.0, 0.0, -2.0, 0.0, 1.0, 1.0]; 12 | 13 | group.bench_function("multiply_3x3", |b| { 14 | b.iter(|| multiply_3x3(black_box(inp1), black_box(inp2))) 15 | }); 16 | group.bench_with_input("matrix_inverse", &inverse, |b, inverse| { 17 | b.iter(|| matrix_inverse(*inverse)) 18 | }); 19 | group.bench_function("rgb_to_xyz_matrix", |b| { 20 | b.iter(rgb_to_xyz_matrix::) 21 | }); 22 | } 23 | 24 | criterion_group!(benches, matrix); 25 | criterion_main!(benches); 26 | -------------------------------------------------------------------------------- /bors.toml: -------------------------------------------------------------------------------- 1 | status = [ 2 | "ci", 3 | ] 4 | 5 | delete_merged_branches = true -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: 2 | layout: "header, diff, flags, components" 3 | 4 | component_management: 5 | default_rules: 6 | statuses: 7 | - type: project 8 | target: auto 9 | individual_components: 10 | - component_id: palette 11 | paths: 12 | - palette/** 13 | - component_id: palette_derive 14 | paths: 15 | - palette_derive/** 16 | -------------------------------------------------------------------------------- /codegen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "codegen" 3 | version = "0.0.0" 4 | authors = ["Erik Hedvall "] 5 | exclude = [] 6 | description = "Code generation crate for palette." 7 | repository = "https://github.com/Ogeon/palette" 8 | license = "MIT OR Apache-2.0" 9 | edition = "2021" 10 | publish = false 11 | 12 | [dependencies] 13 | anyhow = "1.0.86" 14 | proc-macro2 = "1.0.86" 15 | quote = "1.0.37" 16 | phf = "0.11.2" 17 | phf_codegen = "0.11.2" 18 | 19 | 20 | -------------------------------------------------------------------------------- /codegen/res/svg_colors.txt: -------------------------------------------------------------------------------- 1 | aliceblue 240, 248, 255 2 | antiquewhite 250, 235, 215 3 | aqua 0, 255, 255 4 | aquamarine 127, 255, 212 5 | azure 240, 255, 255 6 | beige 245, 245, 220 7 | bisque 255, 228, 196 8 | black 0, 0, 0 9 | blanchedalmond 255, 235, 205 10 | blue 0, 0, 255 11 | blueviolet 138, 43, 226 12 | brown 165, 42, 42 13 | burlywood 222, 184, 135 14 | cadetblue 95, 158, 160 15 | chartreuse 127, 255, 0 16 | chocolate 210, 105, 30 17 | coral 255, 127, 80 18 | cornflowerblue 100, 149, 237 19 | cornsilk 255, 248, 220 20 | crimson 220, 20, 60 21 | cyan 0, 255, 255 22 | darkblue 0, 0, 139 23 | darkcyan 0, 139, 139 24 | darkgoldenrod 184, 134, 11 25 | darkgray 169, 169, 169 26 | darkgreen 0, 100, 0 27 | darkgrey 169, 169, 169 28 | darkkhaki 189, 183, 107 29 | darkmagenta 139, 0, 139 30 | darkolivegreen 85, 107, 47 31 | darkorange 255, 140, 0 32 | darkorchid 153, 50, 204 33 | darkred 139, 0, 0 34 | darksalmon 233, 150, 122 35 | darkseagreen 143, 188, 143 36 | darkslateblue 72, 61, 139 37 | darkslategray 47, 79, 79 38 | darkslategrey 47, 79, 79 39 | darkturquoise 0, 206, 209 40 | darkviolet 148, 0, 211 41 | deeppink 255, 20, 147 42 | deepskyblue 0, 191, 255 43 | dimgray 105, 105, 105 44 | dimgrey 105, 105, 105 45 | dodgerblue 30, 144, 255 46 | firebrick 178, 34, 34 47 | floralwhite 255, 250, 240 48 | forestgreen 34, 139, 34 49 | fuchsia 255, 0, 255 50 | gainsboro 220, 220, 220 51 | ghostwhite 248, 248, 255 52 | gold 255, 215, 0 53 | goldenrod 218, 165, 32 54 | gray 128, 128, 128 55 | grey 128, 128, 128 56 | green 0, 128, 0 57 | greenyellow 173, 255, 47 58 | honeydew 240, 255, 240 59 | hotpink 255, 105, 180 60 | indianred 205, 92, 92 61 | indigo 75, 0, 130 62 | ivory 255, 255, 240 63 | khaki 240, 230, 140 64 | lavender 230, 230, 250 65 | lavenderblush 255, 240, 245 66 | lawngreen 124, 252, 0 67 | lemonchiffon 255, 250, 205 68 | lightblue 173, 216, 230 69 | lightcoral 240, 128, 128 70 | lightcyan 224, 255, 255 71 | lightgoldenrodyellow 250, 250, 210 72 | lightgray 211, 211, 211 73 | lightgreen 144, 238, 144 74 | lightgrey 211, 211, 211 75 | lightpink 255, 182, 193 76 | lightsalmon 255, 160, 122 77 | lightseagreen 32, 178, 170 78 | lightskyblue 135, 206, 250 79 | lightslategray 119, 136, 153 80 | lightslategrey 119, 136, 153 81 | lightsteelblue 176, 196, 222 82 | lightyellow 255, 255, 224 83 | lime 0, 255, 0 84 | limegreen 50, 205, 50 85 | linen 250, 240, 230 86 | magenta 255, 0, 255 87 | maroon 128, 0, 0 88 | mediumaquamarine 102, 205, 170 89 | mediumblue 0, 0, 205 90 | mediumorchid 186, 85, 211 91 | mediumpurple 147, 112, 219 92 | mediumseagreen 60, 179, 113 93 | mediumslateblue 123, 104, 238 94 | mediumspringgreen 0, 250, 154 95 | mediumturquoise 72, 209, 204 96 | mediumvioletred 199, 21, 133 97 | midnightblue 25, 25, 112 98 | mintcream 245, 255, 250 99 | mistyrose 255, 228, 225 100 | moccasin 255, 228, 181 101 | navajowhite 255, 222, 173 102 | navy 0, 0, 128 103 | oldlace 253, 245, 230 104 | olive 128, 128, 0 105 | olivedrab 107, 142, 35 106 | orange 255, 165, 0 107 | orangered 255, 69, 0 108 | orchid 218, 112, 214 109 | palegoldenrod 238, 232, 170 110 | palegreen 152, 251, 152 111 | paleturquoise 175, 238, 238 112 | palevioletred 219, 112, 147 113 | papayawhip 255, 239, 213 114 | peachpuff 255, 218, 185 115 | peru 205, 133, 63 116 | pink 255, 192, 203 117 | plum 221, 160, 221 118 | powderblue 176, 224, 230 119 | purple 128, 0, 128 120 | rebeccapurple 102, 51, 153 121 | red 255, 0, 0 122 | rosybrown 188, 143, 143 123 | royalblue 65, 105, 225 124 | saddlebrown 139, 69, 19 125 | salmon 250, 128, 114 126 | sandybrown 244, 164, 96 127 | seagreen 46, 139, 87 128 | seashell 255, 245, 238 129 | sienna 160, 82, 45 130 | silver 192, 192, 192 131 | skyblue 135, 206, 235 132 | slateblue 106, 90, 205 133 | slategray 112, 128, 144 134 | slategrey 112, 128, 144 135 | snow 255, 250, 250 136 | springgreen 0, 255, 127 137 | steelblue 70, 130, 180 138 | tan 210, 180, 140 139 | teal 0, 128, 128 140 | thistle 216, 191, 216 141 | tomato 255, 99, 71 142 | turquoise 64, 224, 208 143 | violet 238, 130, 238 144 | wheat 245, 222, 179 145 | white 255, 255, 255 146 | whitesmoke 245, 245, 245 147 | yellow 255, 255, 0 148 | yellowgreen 154, 205, 50 149 | -------------------------------------------------------------------------------- /codegen/src/codegen_file.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::File, 3 | io::Write, 4 | path::Path, 5 | process::{Command, Output, Stdio}, 6 | }; 7 | 8 | use anyhow::{Context, Result}; 9 | use proc_macro2::TokenStream; 10 | 11 | const HEADER_COMMENT: &str = r#"// This file is auto-generated and any manual changes to it will be overwritten. 12 | // 13 | // Run `cargo run -p codegen` from the project root to regenerate it. 14 | "#; 15 | 16 | pub struct CodegenFile { 17 | file: File, 18 | } 19 | 20 | impl CodegenFile { 21 | /// Create a new generated file. 22 | pub fn create>(path: P) -> Result { 23 | let path = path.as_ref(); 24 | let mut file = 25 | File::create(path).with_context(|| format!("could not open or create {path:?}"))?; 26 | 27 | writeln!(file, "{HEADER_COMMENT}")?; 28 | 29 | Ok(Self { file }) 30 | } 31 | 32 | /// Formats and appends the tokens to the output file. 33 | pub fn append(&mut self, tokens: TokenStream) -> Result<()> { 34 | // Taken from https://github.com/Michael-F-Bryan/scad-rs/blob/4dbff0c30ce991105f1e649e678d68c2767e894b/crates/codegen/src/pretty_print.rs 35 | 36 | let mut child = Command::new("rustfmt") 37 | .stdin(Stdio::piped()) 38 | .stdout(Stdio::piped()) 39 | .stderr(Stdio::piped()) 40 | .spawn() 41 | .context("unable to start `rustfmt`. Is it installed?")?; 42 | 43 | let mut stdin = child.stdin.take().unwrap(); 44 | write!(stdin, "{tokens}")?; 45 | stdin.flush()?; 46 | drop(stdin); 47 | 48 | let Output { 49 | status, 50 | stdout, 51 | stderr, 52 | } = child.wait_with_output()?; 53 | let stdout = String::from_utf8_lossy(&stdout); 54 | let stderr = String::from_utf8_lossy(&stderr); 55 | 56 | if !status.success() { 57 | eprintln!("---- Stdout ----"); 58 | eprintln!("{stdout}"); 59 | eprintln!("---- Stderr ----"); 60 | eprintln!("{stderr}"); 61 | let code = status.code(); 62 | match code { 63 | Some(code) => anyhow::bail!("the `rustfmt` command failed with return code {code}"), 64 | None => anyhow::bail!("the `rustfmt` command failed"), 65 | } 66 | } 67 | 68 | writeln!(self.file, "{stdout}")?; 69 | 70 | Ok(()) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /codegen/src/lut/model.rs: -------------------------------------------------------------------------------- 1 | use super::TransferFn; 2 | 3 | /// This struct contains the scale and bias for a linear 4 | /// regression model of a transfer function on a given interval. 5 | /// 6 | /// This model is calculated by using simple linear regression with 7 | /// integration instead of summation. 8 | pub(super) struct LinearModel { 9 | scale: f64, 10 | bias: f64, 11 | } 12 | 13 | impl LinearModel { 14 | pub(super) fn new( 15 | transfer_fn: &TransferFn, 16 | start: u32, 17 | end: u32, 18 | man_index_width: u32, 19 | t_width: u32, 20 | ) -> Self { 21 | let TransferFn { 22 | linear_scale, 23 | alpha, 24 | beta, 25 | gamma, 26 | .. 27 | } = *transfer_fn; 28 | 29 | let beta_bits = (beta as f32).to_bits(); 30 | // Corresponds to the scale between differentials. Specifically, 31 | // `dx = exp_scale * dt` 32 | let exp_scale = f32::from_bits(((start >> 23) - man_index_width - t_width) << 23) as f64; 33 | let start_x = f32::from_bits(start) as f64; 34 | let end_x = f32::from_bits(end) as f64; 35 | 36 | // If the transfer function is purely linear on a given interval, 37 | // integration is unnecessary. 38 | if let Some(linear_scale) = linear_scale { 39 | if end <= beta_bits { 40 | return Self { 41 | scale: linear_scale * exp_scale, 42 | bias: linear_scale * start_x, 43 | }; 44 | } 45 | } 46 | 47 | let max_t = 2.0f64.powi(t_width as i32); 48 | 49 | let (integral_y, integral_ty) = match linear_scale { 50 | Some(linear_scale) if start < beta_bits => { 51 | let beta_t = 52 | (beta_bits << (9 + man_index_width)) as f64 * 2.0f64.powi(t_width as i32 - 32); 53 | let int_linear = 54 | integrate_linear((start_x, beta), (0.0, beta_t), linear_scale, exp_scale); 55 | let int_exponential = 56 | integrate_exponential((beta, end_x), (beta_t, max_t), alpha, gamma, exp_scale); 57 | ( 58 | int_linear.0 + int_exponential.0, 59 | int_linear.1 + int_exponential.1, 60 | ) 61 | } 62 | _ => integrate_exponential((start_x, end_x), (0.0, max_t), alpha, gamma, exp_scale), 63 | }; 64 | let max_t2 = max_t * max_t; 65 | let integral_t = max_t2 * 0.5; 66 | let integral_t2 = max_t2 * max_t / 3.0; 67 | 68 | let scale = (max_t * integral_ty - integral_t * integral_y) 69 | / (max_t * integral_t2 - integral_t * integral_t); 70 | Self { 71 | scale, 72 | bias: (integral_y - scale * integral_t) / max_t, 73 | } 74 | } 75 | 76 | pub(super) fn into_u8_lookup(self) -> u32 { 77 | let scale_uint = (255.0 * self.scale * 65536.0 + 0.5) as u32; 78 | let bias_uint = (((255.0 * self.bias + 0.5) * 128.0 + 0.5) as u32) << 9; 79 | (bias_uint << 7) | scale_uint 80 | } 81 | 82 | pub(super) fn into_u16_lookup(self) -> u64 { 83 | let scale_uint = (65535.0 * self.scale * 4294967296.0 + 0.5) as u64; 84 | let bias_uint = (((65535.0 * self.bias + 0.5) * 32768.0 + 0.5) as u64) << 17; 85 | (bias_uint << 15) | scale_uint 86 | } 87 | } 88 | 89 | fn integrate_linear( 90 | (start_x, end_x): (f64, f64), 91 | (start_t, end_t): (f64, f64), 92 | linear_scale: f64, 93 | exp_scale: f64, 94 | ) -> (f64, f64) { 95 | let antiderive_y = |x: f64| 0.5 * linear_scale * x * x / exp_scale; 96 | let antiderive_ty = 97 | |x: f64, t: f64| 0.5 * linear_scale * x * x * (t - x / (3.0 * exp_scale)) / exp_scale; 98 | 99 | ( 100 | antiderive_y(end_x) - antiderive_y(start_x), 101 | antiderive_ty(end_x, end_t) - antiderive_ty(start_x, start_t), 102 | ) 103 | } 104 | 105 | fn integrate_exponential( 106 | (start_x, end_x): (f64, f64), 107 | (start_t, end_t): (f64, f64), 108 | alpha: f64, 109 | gamma: f64, 110 | exp_scale: f64, 111 | ) -> (f64, f64) { 112 | let one_plus_gamma_inv = 1.0 + gamma.recip(); 113 | let antiderive_y = |x: f64, t: f64| { 114 | alpha * gamma * x.powf(one_plus_gamma_inv) / (exp_scale * (1.0 + gamma)) + (1.0 - alpha) * t 115 | }; 116 | let antiderive_ty = |x: f64, t: f64| { 117 | alpha 118 | * gamma 119 | * x.powf(one_plus_gamma_inv) 120 | * (t - gamma * x / (exp_scale * (1.0 + 2.0 * gamma))) 121 | / (exp_scale * (1.0 + gamma)) 122 | + 0.5 * (1.0 - alpha) * t * t 123 | }; 124 | 125 | ( 126 | antiderive_y(end_x, end_t) - antiderive_y(start_x, start_t), 127 | antiderive_ty(end_x, end_t) - antiderive_ty(start_x, start_t), 128 | ) 129 | } 130 | -------------------------------------------------------------------------------- /codegen/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | 3 | mod codegen_file; 4 | mod lut; 5 | mod named; 6 | 7 | fn main() -> Result<()> { 8 | named::generate().context("could not generate named color constants")?; 9 | lut::generate().context("could not generate conversion lookup tables")?; 10 | 11 | Ok(()) 12 | } 13 | -------------------------------------------------------------------------------- /codegen/src/named.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::File, 3 | io::{BufRead, BufReader}, 4 | }; 5 | 6 | use anyhow::{Context, Result}; 7 | use proc_macro2::{Ident, Span, TokenStream}; 8 | use quote::quote; 9 | 10 | use crate::codegen_file::CodegenFile; 11 | 12 | pub fn generate() -> Result<()> { 13 | let mut file = CodegenFile::create("palette/src/named/codegen.rs")?; 14 | 15 | let colors = parse_colors()?; 16 | 17 | file.append(build_colors(&colors))?; 18 | file.append(build_from_str(&colors))?; 19 | 20 | Ok(()) 21 | } 22 | 23 | struct ColorEntry { 24 | name: String, 25 | constant: Ident, 26 | red: u8, 27 | green: u8, 28 | blue: u8, 29 | } 30 | 31 | fn parse_colors() -> Result> { 32 | let reader = BufReader::new( 33 | File::open("codegen/res/svg_colors.txt").expect("could not open svg_colors.txt"), 34 | ); 35 | 36 | // Expected format: "name\t123, 123, 123" 37 | reader 38 | .lines() 39 | .map(|line| { 40 | let line = line?; 41 | let mut parts = line.split('\t'); 42 | 43 | let name = parts 44 | .next() 45 | .context("couldn't get the color name")? 46 | .to_owned(); 47 | let mut rgb = parts 48 | .next() 49 | .with_context(|| format!("couldn't get RGB for {}", name))? 50 | .split(", "); 51 | 52 | let red: u8 = rgb 53 | .next() 54 | .with_context(|| format!("missing red for {name}"))? 55 | .trim() 56 | .parse() 57 | .with_context(|| format!("couldn't parse red for {}", name))?; 58 | 59 | let green: u8 = rgb 60 | .next() 61 | .with_context(|| format!("missing green for {name}"))? 62 | .trim() 63 | .parse() 64 | .with_context(|| format!("couldn't parse green for {}", name))?; 65 | 66 | let blue: u8 = rgb 67 | .next() 68 | .with_context(|| format!("missing blue for {name}"))? 69 | .trim() 70 | .parse() 71 | .with_context(|| format!("couldn't parse blue for {}", name))?; 72 | 73 | let constant = Ident::new(&name.to_ascii_uppercase(), Span::call_site()); 74 | 75 | Ok(ColorEntry { 76 | name, 77 | constant, 78 | red, 79 | green, 80 | blue, 81 | }) 82 | }) 83 | .collect() 84 | } 85 | 86 | fn build_colors(colors: &[ColorEntry]) -> TokenStream { 87 | let constants = colors.iter().map(|ColorEntry { name, constant, red, green, blue }| { 88 | let swatch_html = format!( 89 | r#"
"# 90 | ); 91 | 92 | quote! { 93 | #[doc = #swatch_html] 94 | pub const #constant: crate::rgb::Srgb = crate::rgb::Srgb::new(#red, #green, #blue); 95 | } 96 | }); 97 | 98 | quote! { 99 | #(#constants)* 100 | } 101 | } 102 | 103 | fn build_from_str(entries: &[ColorEntry]) -> TokenStream { 104 | let mut map = phf_codegen::Map::new(); 105 | 106 | for entry in entries { 107 | map.entry(&*entry.name, &entry.constant.to_string()); 108 | } 109 | 110 | let phf_entries: TokenStream = map 111 | .build() 112 | .to_string() 113 | .parse() 114 | .expect("phf should generate a valid token stream"); 115 | 116 | quote! { 117 | pub(crate) static COLORS: ::phf::Map<&'static str, crate::rgb::Srgb> = #phf_entries; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /example-data/blend.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 15 | 17 | 21 | 25 | 29 | 33 | 37 | 41 | 45 | 46 | 55 | 56 | 58 | 65 | 72 | 79 | 86 | 93 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /example-data/compose.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 15 | 19 | 25 | 32 | 39 | 40 | 41 | 43 | 50 | 53 | 60 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /example-data/input/README.md: -------------------------------------------------------------------------------- 1 | Test images were taken from http://homepages.cae.wisc.edu/~ece533/images/, 2 | where they are released as "Public-Domain Test Images for Homeworks and 3 | Projects". 4 | -------------------------------------------------------------------------------- /example-data/input/blend_fg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ogeon/palette/c2295a5bdd3200604da59ebbbdd1c6fd5c5de641/example-data/input/blend_fg.png -------------------------------------------------------------------------------- /example-data/input/cat-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ogeon/palette/c2295a5bdd3200604da59ebbbdd1c6fd5c5de641/example-data/input/cat-128.png -------------------------------------------------------------------------------- /example-data/input/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ogeon/palette/c2295a5bdd3200604da59ebbbdd1c6fd5c5de641/example-data/input/cat.png -------------------------------------------------------------------------------- /example-data/input/compose_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ogeon/palette/c2295a5bdd3200604da59ebbbdd1c6fd5c5de641/example-data/input/compose_bg.png -------------------------------------------------------------------------------- /example-data/input/compose_fg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ogeon/palette/c2295a5bdd3200604da59ebbbdd1c6fd5c5de641/example-data/input/compose_fg.png -------------------------------------------------------------------------------- /example-data/input/fruits-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ogeon/palette/c2295a5bdd3200604da59ebbbdd1c6fd5c5de641/example-data/input/fruits-128.png -------------------------------------------------------------------------------- /example-data/input/fruits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ogeon/palette/c2295a5bdd3200604da59ebbbdd1c6fd5c5de641/example-data/input/fruits.png -------------------------------------------------------------------------------- /gfx/readme_color_operations_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ogeon/palette/c2295a5bdd3200604da59ebbbdd1c6fd5c5de641/gfx/readme_color_operations_1.png -------------------------------------------------------------------------------- /gfx/readme_color_operations_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ogeon/palette/c2295a5bdd3200604da59ebbbdd1c6fd5c5de641/gfx/readme_color_operations_2.png -------------------------------------------------------------------------------- /gfx/readme_converting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ogeon/palette/c2295a5bdd3200604da59ebbbdd1c6fd5c5de641/gfx/readme_converting.png -------------------------------------------------------------------------------- /gfx/readme_gradients_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ogeon/palette/c2295a5bdd3200604da59ebbbdd1c6fd5c5de641/gfx/readme_gradients_1.png -------------------------------------------------------------------------------- /gfx/readme_pixels_and_buffers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ogeon/palette/c2295a5bdd3200604da59ebbbdd1c6fd5c5de641/gfx/readme_pixels_and_buffers.png -------------------------------------------------------------------------------- /integration_tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_tests" 3 | version = "0.0.0" 4 | authors = ["Erik Hedvall "] 5 | exclude = [] 6 | description = "Integration test crate for palette." 7 | repository = "https://github.com/Ogeon/palette" 8 | license = "MIT OR Apache-2.0" 9 | edition = "2021" 10 | publish = false 11 | 12 | [[example]] 13 | name = "issue_283" 14 | path = "regression_tests/issue_283.rs" 15 | 16 | [dev-dependencies] 17 | approx = { version = "0.5", default-features = false } 18 | csv = "1" 19 | lazy_static = "1" 20 | palette = { path = "../palette", features = ["wide"] } 21 | scad = "1.2.2" # For regression testing #283 22 | serde = "1" 23 | serde_derive = "1" 24 | serde_json = "1" 25 | 26 | [dev-dependencies.wide] 27 | version = "0.7.3" 28 | default-features = false 29 | -------------------------------------------------------------------------------- /integration_tests/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /integration_tests/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /integration_tests/regression_tests/issue_283.rs: -------------------------------------------------------------------------------- 1 | // Checks that issue #283 (fixed in 0.7.1) doesn't re-appear. The cause was the 2 | // existence of `impl Mul> for f32` and `impl Mul> for 3 | // f64`. 4 | 5 | // Both of these uses are necessary for triggering the issue 6 | #[allow(unused_imports)] 7 | use palette::Oklch; 8 | #[allow(unused_imports)] 9 | use scad::OffsetType; 10 | 11 | fn main() { 12 | println!("{}", 42.0 * 1.0); // bug also happens when specifying f32 or f64 13 | } 14 | -------------------------------------------------------------------------------- /integration_tests/tests/color_checker.rs: -------------------------------------------------------------------------------- 1 | mod color_checker_data; 2 | -------------------------------------------------------------------------------- /integration_tests/tests/color_checker_data/load_data.rs: -------------------------------------------------------------------------------- 1 | use serde_derive::Deserialize; 2 | 3 | use super::babel::BabelData; 4 | use super::color_checker::ColorCheckerData; 5 | 6 | #[derive(Deserialize, PartialEq)] 7 | pub struct ColorCheckerRaw { 8 | pub color_name: String, 9 | pub yxy_x: f64, 10 | pub yxy_y: f64, 11 | pub yxy_luma: f64, 12 | pub xyz_x: f64, 13 | pub xyz_y: f64, 14 | pub xyz_z: f64, 15 | pub lab_l: f64, 16 | pub lab_a: f64, 17 | pub lab_b: f64, 18 | pub adobe_r: f64, 19 | pub adobe_g: f64, 20 | pub adobe_b: f64, 21 | pub apple_rgb_r: f64, 22 | pub apple_rgb_g: f64, 23 | pub apple_rgb_b: f64, 24 | pub bestrgb_r: f64, 25 | pub bestrgb_g: f64, 26 | pub bestrgb_b: f64, 27 | pub beta_rgb_r: f64, 28 | pub beta_rgb_g: f64, 29 | pub beta_rgb_b: f64, 30 | pub bruce_rgb_r: f64, 31 | pub bruce_rgb_g: f64, 32 | pub bruce_rgb_b: f64, 33 | pub cie_rgb_r: f64, 34 | pub cie_rgb_g: f64, 35 | pub cie_rgb_b: f64, 36 | pub colormatch_r: f64, 37 | pub colormatch_g: f64, 38 | pub colormatch_b: f64, 39 | pub donrgb4_r: f64, 40 | pub donrgb4_g: f64, 41 | pub donrgb4_b: f64, 42 | pub ecirgb_v2_r: f64, 43 | pub ecirgb_v2_g: f64, 44 | pub ecirgb_v2_b: f64, 45 | pub ekta_space_ps5_r: f64, 46 | pub ekta_space_ps5_g: f64, 47 | pub ekta_space_ps5_b: f64, 48 | pub hdtv_hd_cif_r: f64, 49 | pub hdtv_hd_cif_g: f64, 50 | pub hdtv_hd_cif_b: f64, 51 | pub ntsc_r: f64, 52 | pub ntsc_g: f64, 53 | pub ntsc_b: f64, 54 | pub pal_secam_r: f64, 55 | pub pal_secam_g: f64, 56 | pub pal_secam_b: f64, 57 | pub prophoto_r: f64, 58 | pub prophoto_g: f64, 59 | pub prophoto_b: f64, 60 | pub sgi_r: f64, 61 | pub sgi_g: f64, 62 | pub sgi_b: f64, 63 | pub smpte_240m_r: f64, 64 | pub smpte_240m_g: f64, 65 | pub smpte_240m_b: f64, 66 | pub smpte_c_r: f64, 67 | pub smpte_c_g: f64, 68 | pub smpte_c_b: f64, 69 | pub srgb_r: f64, 70 | pub srgb_g: f64, 71 | pub srgb_b: f64, 72 | pub wide_gamut_r: f64, 73 | pub wide_gamut_g: f64, 74 | pub wide_gamut_b: f64, 75 | } 76 | 77 | pub fn load_babel() -> Vec { 78 | let file_name = "tests/color_checker_data/babel.csv"; 79 | let mut rdr = csv::Reader::from_path(file_name) 80 | .expect("csv file could not be loaded in tests for pointer data"); 81 | let mut color_data: Vec = Vec::new(); 82 | for record in rdr.deserialize() { 83 | let r: ColorCheckerRaw = 84 | record.expect("color data could not be decoded in tests for cie 2004 data"); 85 | color_data.push(r.into()) 86 | } 87 | color_data 88 | } 89 | 90 | pub fn load_color_checker() -> Vec { 91 | let file_name = "tests/color_checker_data/color_checker.csv"; 92 | let mut rdr = csv::Reader::from_path(file_name) 93 | .expect("csv file could not be loaded in tests for pointer data"); 94 | let mut color_data: Vec = Vec::new(); 95 | for record in rdr.deserialize() { 96 | let r: ColorCheckerRaw = 97 | record.expect("color data could not be decoded in tests for cie 2004 data"); 98 | color_data.push(r.into()) 99 | } 100 | color_data 101 | } 102 | -------------------------------------------------------------------------------- /integration_tests/tests/color_checker_data/mod.rs: -------------------------------------------------------------------------------- 1 | mod babel; 2 | mod color_checker; 3 | mod load_data; 4 | 5 | const MAX_ERROR: f64 = 0.000000000001; 6 | 7 | #[test] 8 | pub fn babel_from_yxy() { 9 | babel::run_from_yxy_tests(); 10 | } 11 | #[test] 12 | pub fn babel_from_xyz() { 13 | babel::run_from_xyz_tests(); 14 | } 15 | #[test] 16 | pub fn babel_from_lab() { 17 | babel::run_from_lab_tests(); 18 | } 19 | 20 | #[test] 21 | pub fn color_checker_from_yxy() { 22 | color_checker::run_from_yxy_tests(); 23 | } 24 | #[test] 25 | pub fn color_checker_from_xyz() { 26 | color_checker::run_from_xyz_tests(); 27 | } 28 | #[test] 29 | pub fn color_checker_from_lab() { 30 | color_checker::run_from_lab_tests(); 31 | } 32 | 33 | pub mod wide { 34 | #[test] 35 | pub fn babel_from_yxy() { 36 | super::babel::wide_f64x2::run_from_yxy_tests(); 37 | } 38 | #[test] 39 | pub fn babel_from_xyz() { 40 | super::babel::wide_f64x2::run_from_xyz_tests(); 41 | } 42 | #[test] 43 | pub fn babel_from_lab() { 44 | super::babel::wide_f64x2::run_from_lab_tests(); 45 | } 46 | 47 | #[test] 48 | pub fn color_checker_from_yxy() { 49 | super::color_checker::wide_f64x2::run_from_yxy_tests(); 50 | } 51 | #[test] 52 | pub fn color_checker_from_xyz() { 53 | super::color_checker::wide_f64x2::run_from_xyz_tests(); 54 | } 55 | #[test] 56 | pub fn color_checker_from_lab() { 57 | super::color_checker::wide_f64x2::run_from_lab_tests(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /integration_tests/tests/color_convert.rs: -------------------------------------------------------------------------------- 1 | mod convert; 2 | -------------------------------------------------------------------------------- /integration_tests/tests/convert/data_cie_15_2004.csv: -------------------------------------------------------------------------------- 1 | xyz_x,xyz_y,xyz_z,yxy_x,yxy_y,yxy_luma,luv_u,luv_v 2 | 1.0985,1,0.3558,0.44758,0.40745,1,0.25597,0.52429 3 | 0.9504,1,1.0888,0.31272,0.32903,1,0.19783,0.46834 4 | 0.9807,1,1.1822,0.31006,0.31616,1,0.20089,0.46089 5 | 0.9642,1,0.8251,0.34567,0.35851,1,0.20916,0.48808 6 | 0.9568,1,0.9214,0.33243,0.34744,1,0.20443,0.48075 7 | 0.9497,1,1.2261,0.29903,0.31488,1,0.19353,0.45853 8 | 1.1114,1,0.352,0.45117,0.40594,1,0.25896,0.52425 9 | 0.9481,1,1.0732,0.31381,0.33098,1,0.19786,0.46954 10 | 0.9729,1,1.1614,0.31039,0.31905,1,0.2,0.46255 11 | 0.9672,1,0.8143,0.34773,0.35952,1,0.21015,0.48886 12 | 0.958,1,0.9093,0.33412,0.34877,1,0.20507,0.48165 13 | 0.9442,1,1.2064,0.29968,0.3174,1,0.19305,0.46004 14 | -------------------------------------------------------------------------------- /integration_tests/tests/convert/data_ciede_2000.csv: -------------------------------------------------------------------------------- 1 | lab1_l,lab1_a,lab1_b,lab2_l,lab2_a,lab2_b,delta_e 2 | 50.0000,2.6772,-79.7751,50.0000,0.0000,-82.7485,2.0425 3 | 50.0000,3.1571,-77.2803,50.0000,0.0000,-82.7485,2.8615 4 | 50.0000,2.8361,-74.0200,50.0000,0.0000,-82.7485,3.4412 5 | 50.0000,-1.3802,-84.2814,50.0000,0.0000,-82.7485,1.0000 6 | 50.0000,-1.1848,-84.8006,50.0000,0.0000,-82.7485,1.0000 7 | 50.0000,-0.9009,-85.5211,50.0000,0.0000,-82.7485,1.0000 8 | 50.0000,0.0000,0.0000,50.0000,-1.0000,2.0000,2.3669 9 | 50.0000,-1.0000,2.0000,50.0000,0.0000,0.0000,2.3669 10 | 50.0000,2.4900,-0.0010,50.0000,-2.4900,0.0009,7.1792 11 | 50.0000,2.4900,-0.0010,50.0000,-2.4900,0.0010,7.1792 12 | 50.0000,2.4900,-0.0010,50.0000,-2.4900,0.0011,7.2195 13 | 50.0000,2.4900,-0.0010,50.0000,-2.4900,0.0012,7.2195 14 | 50.0000,-0.0010,2.4900,50.0000,0.0009,-2.4900,4.8045 15 | 50.0000,-0.0010,2.4900,50.0000,0.0010,-2.4900,4.8045 16 | 50.0000,-0.0010,2.4900,50.0000,0.0011,-2.4900,4.7461 17 | 50.0000,2.5000,0.0000,50.0000,0.0000,-2.5000,4.3065 18 | 50.0000,2.5000,0.0000,73.0000,25.0000,-18.0000,27.1492 19 | 50.0000,2.5000,0.0000,61.0000,-5.0000,29.0000,22.8977 20 | 50.0000,2.5000,0.0000,56.0000,-27.0000,-3.0000,31.9030 21 | 50.0000,2.5000,0.0000,58.0000,24.0000,15.0000,19.4535 22 | 50.0000,2.5000,0.0000,50.0000,3.1736,0.5854,1.0000 23 | 50.0000,2.5000,0.0000,50.0000,3.2972,0.0000,1.0000 24 | 50.0000,2.5000,0.0000,50.0000,1.8634,0.5757,1.0000 25 | 50.0000,2.5000,0.0000,50.0000,3.2592,0.3350,1.0000 26 | 60.2574,-34.0099,36.2677,60.4626,-34.1751,39.4387,1.2644 27 | 63.0109,-31.0961,-5.8663,62.8187,-29.7946,-4.0864,1.2630 28 | 61.2901,3.7196,-5.3901,61.4292,2.2480,-4.9620,1.8731 29 | 35.0831,-44.1164,3.7933,35.0232,-40.0716,1.5901,1.8645 30 | 22.7233,20.0904,-46.6940,23.0331,14.9730,-42.5619,2.0373 31 | 36.4612,47.8580,18.3852,36.2715,50.5065,21.2231,1.4146 32 | 90.8027,-2.0831,1.4410,91.1528,-1.6435,0.0447,1.4441 33 | 90.9257,-0.5406,-0.9208,88.6381,-0.8985,-0.7239,1.5381 34 | 6.7747,-0.2908,-2.4247,5.8714,-0.0985,-2.2286,0.6377 35 | 2.0776,0.0795,-1.1350,0.9033,-0.0636,-0.5514,0.9082 36 | -------------------------------------------------------------------------------- /integration_tests/tests/convert/data_ciede_2000.rs: -------------------------------------------------------------------------------- 1 | /* 2 | Data from http://www2.ece.rochester.edu/~gsharma/ciede2000/ 3 | 4 | Tests Lab color differences with expected delta E* 5 | 6 | Note: Test uses `f64` because `f32` failed Travis CI builds on Linux for Lch on 7 | case 13 or 14 which is noted in the paper as testing accuracy of hue 8 | angle and atan calculation (calculated: 4.7460666, expected: 4.8045). 9 | MacOS and Windows passed the tests so be wary when using f32 on Linux. 10 | */ 11 | 12 | use approx::assert_relative_eq; 13 | use serde_derive::Deserialize; 14 | 15 | use palette::{ 16 | color_difference::Ciede2000, convert::FromColorUnclamped, white_point::D65, Lab, Lch, 17 | }; 18 | 19 | #[derive(Deserialize, PartialEq)] 20 | struct Cie2000Raw { 21 | lab1_l: f64, 22 | lab1_a: f64, 23 | lab1_b: f64, 24 | lab2_l: f64, 25 | lab2_a: f64, 26 | lab2_b: f64, 27 | delta_e: f64, 28 | } 29 | 30 | #[derive(Copy, Clone, PartialEq, Debug)] 31 | struct Cie2000 { 32 | c1: Lab, 33 | c2: Lab, 34 | delta_e: f64, 35 | } 36 | 37 | impl From for Cie2000 { 38 | fn from(src: Cie2000Raw) -> Cie2000 { 39 | Cie2000 { 40 | c1: Lab::new(src.lab1_l, src.lab1_a, src.lab1_b), 41 | c2: Lab::new(src.lab2_l, src.lab2_a, src.lab2_b), 42 | delta_e: src.delta_e, 43 | } 44 | } 45 | } 46 | 47 | fn load_data() -> Vec { 48 | let file_name = "tests/convert/data_ciede_2000.csv"; 49 | let mut rdr = csv::Reader::from_path(file_name) 50 | .expect("csv file could not be loaded in tests for cie 2000 data"); 51 | let mut color_data: Vec = Vec::new(); 52 | for record in rdr.deserialize() { 53 | let r: Cie2000Raw = 54 | record.expect("color data could not be decoded in tests for cie 2000 data"); 55 | color_data.push(r.into()) 56 | } 57 | color_data 58 | } 59 | 60 | fn check_equal_lab(result: f64, expected: f64) { 61 | assert_relative_eq!(result, expected, epsilon = 0.0001); 62 | } 63 | 64 | fn check_equal_lch(result: f64, expected: f64) { 65 | assert_relative_eq!(result, expected, epsilon = 0.0001); 66 | } 67 | 68 | pub fn run_tests() { 69 | let data = load_data(); 70 | 71 | for expected in data.iter() { 72 | let result_lab = expected.c1.difference(expected.c2); 73 | check_equal_lab(result_lab, expected.delta_e); 74 | 75 | let lch1: Lch<_, f64> = Lch::from_color_unclamped(expected.c1); 76 | let lch2: Lch<_, f64> = Lch::from_color_unclamped(expected.c2); 77 | let result_lch = lch1.difference(lch2); 78 | check_equal_lch(result_lch, expected.delta_e); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /integration_tests/tests/convert/lab_lch.rs: -------------------------------------------------------------------------------- 1 | use approx::assert_relative_eq; 2 | 3 | use palette::convert::IntoColorUnclamped; 4 | use palette::white_point::D65; 5 | use palette::{Lab, Lch}; 6 | 7 | #[test] 8 | fn lab_lch_green() { 9 | let lab = Lab::::new(46.23, -66.176, 63.872); 10 | let lch = Lch::new(46.23, 91.972, 136.015); 11 | let expect_lab = lch.into_color_unclamped(); 12 | let expect_lch = lab.into_color_unclamped(); 13 | 14 | assert_relative_eq!(lab, expect_lab, epsilon = 0.001); 15 | assert_relative_eq!(lch, expect_lch, epsilon = 0.001); 16 | } 17 | 18 | #[test] 19 | fn lab_lch_magenta() { 20 | let lab = Lab::::new(60.320, 98.254, -60.843); 21 | let lch = Lch::new(60.320, 115.567, 328.233); 22 | 23 | let expect_lab = lch.into_color_unclamped(); 24 | let expect_lch = lab.into_color_unclamped(); 25 | 26 | assert_relative_eq!(lab, expect_lab, epsilon = 0.001); 27 | assert_relative_eq!(lch, expect_lch, epsilon = 0.001); 28 | } 29 | 30 | #[test] 31 | fn lab_lch_blue() { 32 | let lab = Lab::::new(32.303, 79.197, -107.864); 33 | let lch = Lch::new(32.303, 133.816, 306.287); 34 | 35 | let expect_lab = lch.into_color_unclamped(); 36 | let expect_lch = lab.into_color_unclamped(); 37 | 38 | assert_relative_eq!(lab, expect_lab, epsilon = 0.001); 39 | assert_relative_eq!(lch, expect_lch, epsilon = 0.001); 40 | } 41 | -------------------------------------------------------------------------------- /integration_tests/tests/convert/mod.rs: -------------------------------------------------------------------------------- 1 | mod data_cie_15_2004; 2 | mod data_ciede_2000; 3 | mod data_color_mine; 4 | mod lab_lch; 5 | 6 | #[test] 7 | pub fn xyz_yxy_conversion() { 8 | data_cie_15_2004::run_tests(); 9 | } 10 | 11 | #[test] 12 | pub fn color_difference_ciede() { 13 | data_ciede_2000::run_tests(); 14 | } 15 | 16 | #[test] 17 | pub fn color_mine_from_lab() { 18 | data_color_mine::run_from_lab_tests(); 19 | } 20 | #[test] 21 | pub fn color_mine_from_lch() { 22 | data_color_mine::run_from_lch_tests(); 23 | } 24 | #[test] 25 | pub fn color_mine_from_xyz() { 26 | data_color_mine::run_from_xyz_tests(); 27 | } 28 | #[test] 29 | pub fn color_mine_from_yxy() { 30 | data_color_mine::run_from_yxy_tests(); 31 | } 32 | #[test] 33 | pub fn color_mine_from_linear_rgb() { 34 | data_color_mine::run_from_linear_rgb_tests(); 35 | } 36 | #[test] 37 | pub fn color_mine_from_rgb() { 38 | data_color_mine::run_from_rgb_tests(); 39 | } 40 | #[test] 41 | pub fn color_mine_from_hsl() { 42 | data_color_mine::run_from_hsl_tests(); 43 | } 44 | #[test] 45 | pub fn color_mine_from_hsv() { 46 | data_color_mine::run_from_hsv_tests(); 47 | } 48 | #[test] 49 | pub fn color_mine_from_hwb() { 50 | data_color_mine::run_from_hwb_tests(); 51 | } 52 | 53 | pub mod wide { 54 | #[test] 55 | pub fn xyz_yxy_conversion() { 56 | super::data_cie_15_2004::wide_f32x4::run_tests(); 57 | } 58 | 59 | #[test] 60 | pub fn color_mine_from_xyz() { 61 | super::data_color_mine::wide_f64x2::run_from_xyz_tests(); 62 | } 63 | #[test] 64 | pub fn color_mine_from_yxy() { 65 | super::data_color_mine::wide_f64x2::run_from_yxy_tests(); 66 | } 67 | #[test] 68 | pub fn color_mine_from_linear_rgb() { 69 | super::data_color_mine::wide_f64x2::run_from_linear_rgb_tests(); 70 | } 71 | #[test] 72 | pub fn color_mine_from_rgb() { 73 | super::data_color_mine::wide_f64x2::run_from_rgb_tests(); 74 | } 75 | #[test] 76 | pub fn color_mine_from_hsl() { 77 | super::data_color_mine::wide_f64x2::run_from_hsl_tests(); 78 | } 79 | #[test] 80 | pub fn color_mine_from_hsv() { 81 | super::data_color_mine::wide_f64x2::run_from_hsv_tests(); 82 | } 83 | #[test] 84 | pub fn color_mine_from_hwb() { 85 | super::data_color_mine::wide_f64x2::run_from_hwb_tests(); 86 | } 87 | #[test] 88 | pub fn color_mine_from_lab() { 89 | super::data_color_mine::wide_f64x2::run_from_lab_tests(); 90 | } 91 | #[test] 92 | pub fn color_mine_from_lch() { 93 | super::data_color_mine::wide_f64x2::run_from_lch_tests(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /integration_tests/tests/hsluv_convert.rs: -------------------------------------------------------------------------------- 1 | mod hsluv_dataset; 2 | -------------------------------------------------------------------------------- /integration_tests/tests/hsluv_dataset/LICENSE.hsluv_dataset: -------------------------------------------------------------------------------- 1 | hsluv_dataset.json is taken from https://github.com/hsluv/hsluv/blob/master/snapshots/snapshot-rev4.json . 2 | 3 | The License associated with that repository is reproduced below: 4 | 5 | -------------------------------------------------------------------------------- 6 | 7 | Copyright (c) 2012-2020 Alexei Boronine 8 | Copyright (c) 2016 Florian Dormont 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. -------------------------------------------------------------------------------- /integration_tests/tests/hsluv_dataset/hsluv_dataset.rs: -------------------------------------------------------------------------------- 1 | //! rev4 of the verification dataset from HSLuv 2 | //! https://github.com/hsluv/hsluv/blob/master/snapshots/snapshot-rev4.json 3 | 4 | use approx::assert_relative_eq; 5 | use lazy_static::lazy_static; 6 | use serde_json; 7 | 8 | use palette::convert::IntoColorUnclamped; 9 | use palette::white_point::D65; 10 | use palette::{Hsluv, Lchuv, Luv, LuvHue, Xyz}; 11 | use std::collections::HashMap; 12 | 13 | #[derive(Clone, Debug)] 14 | struct HsluvExample { 15 | lchuv: Lchuv, 16 | hsluv: Hsluv, 17 | luv: Luv, 18 | xyz: Xyz, 19 | } 20 | 21 | type Examples = HashMap; 22 | 23 | fn load_data() -> Examples { 24 | let filename = "tests/hsluv_dataset/hsluv_dataset.json"; 25 | let data_str = std::fs::read_to_string(filename).unwrap(); 26 | let raw_data: serde_json::Value = serde_json::from_str(&data_str).unwrap(); 27 | 28 | let m = raw_data.as_object().expect("failed to parse dataset"); 29 | let to_vec = |c: &serde_json::Value| { 30 | c.as_array() 31 | .unwrap() 32 | .iter() 33 | .flat_map(|x| x.as_f64()) 34 | .collect() 35 | }; 36 | 37 | m.iter() 38 | .map(|(k, v)| { 39 | let colors = v.as_object().unwrap(); 40 | let luv_data: Vec = to_vec(&colors["luv"]); 41 | let lchuv_data: Vec = to_vec(&colors["lch"]); 42 | let hsluv_data: Vec = to_vec(&colors["hsluv"]); 43 | let xyz_data: Vec = to_vec(&colors["xyz"]); 44 | 45 | ( 46 | k.clone(), 47 | HsluvExample { 48 | luv: Luv::new(luv_data[0], luv_data[1], luv_data[2]), 49 | hsluv: Hsluv::new(hsluv_data[0], hsluv_data[1], hsluv_data[2]), 50 | lchuv: Lchuv::new(lchuv_data[0], lchuv_data[1], lchuv_data[2]), 51 | xyz: Xyz::new(xyz_data[0], xyz_data[1], xyz_data[2]), 52 | }, 53 | ) 54 | }) 55 | .collect() 56 | } 57 | 58 | lazy_static! { 59 | static ref TEST_DATA: Examples = load_data(); 60 | } 61 | 62 | #[test] 63 | pub fn run_xyz_to_luv_tests() { 64 | for (_, v) in TEST_DATA.iter() { 65 | let to_luv: Luv = v.xyz.into_color_unclamped(); 66 | assert_relative_eq!(to_luv, v.luv, epsilon = 0.1); 67 | } 68 | } 69 | 70 | #[test] 71 | pub fn run_luv_to_xyz_tests() { 72 | for (_, v) in TEST_DATA.iter() { 73 | let to_xyz: Xyz = v.luv.into_color_unclamped(); 74 | assert_relative_eq!(to_xyz, v.xyz, epsilon = 0.001); 75 | } 76 | } 77 | 78 | #[test] 79 | pub fn run_lchuv_to_luv_tests() { 80 | for (_, v) in TEST_DATA.iter() { 81 | let to_luv: Luv = v.lchuv.into_color_unclamped(); 82 | assert_relative_eq!(to_luv, v.luv, epsilon = 0.001); 83 | } 84 | } 85 | 86 | #[test] 87 | pub fn run_luv_to_lchuv_tests() { 88 | for (_, v) in TEST_DATA.iter() { 89 | let mut to_lchuv: Lchuv = v.luv.into_color_unclamped(); 90 | if to_lchuv.chroma < 1e-8 { 91 | to_lchuv.hue = LuvHue::from_degrees(0.0); 92 | } 93 | assert_relative_eq!(to_lchuv, v.lchuv, epsilon = 0.001); 94 | } 95 | } 96 | 97 | #[test] 98 | pub fn run_lchuv_to_hsluv_tests() { 99 | for (_, v) in TEST_DATA.iter() { 100 | let mut to_hsluv: Hsluv = v.lchuv.into_color_unclamped(); 101 | if to_hsluv.l > 100.0 - 1e-5 { 102 | to_hsluv.saturation = 0.0; 103 | } 104 | assert_relative_eq!(to_hsluv, v.hsluv, epsilon = 1e-5); 105 | } 106 | } 107 | 108 | #[test] 109 | pub fn run_hsluv_to_lchuv_tests() { 110 | for (_, v) in TEST_DATA.iter() { 111 | let to_lchuv: Lchuv = v.hsluv.into_color_unclamped(); 112 | assert_relative_eq!(to_lchuv, v.lchuv, epsilon = 1e-5); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /integration_tests/tests/hsluv_dataset/mod.rs: -------------------------------------------------------------------------------- 1 | #[allow(clippy::module_inception)] 2 | pub mod hsluv_dataset; 3 | -------------------------------------------------------------------------------- /integration_tests/tests/pointer_convert.rs: -------------------------------------------------------------------------------- 1 | mod pointer_dataset; 2 | -------------------------------------------------------------------------------- /integration_tests/tests/pointer_dataset/mod.rs: -------------------------------------------------------------------------------- 1 | mod pointer_data; 2 | 3 | #[test] 4 | pub fn from_lab() { 5 | pointer_data::run_from_lab_tests(); 6 | } 7 | #[test] 8 | pub fn from_lch() { 9 | pointer_data::run_from_lch_tests(); 10 | } 11 | 12 | mod wide { 13 | #[test] 14 | pub fn from_lab() { 15 | super::pointer_data::wide_f64x2::run_from_lab_tests(); 16 | } 17 | #[test] 18 | pub fn from_lch() { 19 | super::pointer_data::wide_f64x2::run_from_lch_tests(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /no_std_test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "no_std_test" 3 | version = "0.0.0" 4 | authors = ["Erik Hedvall "] 5 | exclude = [] 6 | description = "Test crate for #[no_std]." 7 | repository = "https://github.com/Ogeon/palette" 8 | license = "MIT OR Apache-2.0" 9 | edition = "2021" 10 | publish = false 11 | 12 | [[bin]] 13 | name = "no_std_test" 14 | bench = false 15 | 16 | [features] 17 | nightly = [] 18 | # Avoids getting these features included in other packages in the same workspace. 19 | all_features = ["palette/libm", "palette/named"] 20 | 21 | [dependencies.libc] 22 | version = "0.2" 23 | default-features = false 24 | 25 | [dependencies.palette] 26 | path = "../palette" 27 | default-features = false 28 | -------------------------------------------------------------------------------- /no_std_test/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /no_std_test/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /no_std_test/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "nightly", no_main)] 2 | #![no_std] 3 | 4 | #[cfg(feature = "nightly")] 5 | use core::panic::PanicInfo; 6 | 7 | extern crate libc; 8 | 9 | #[cfg(feature = "nightly")] 10 | #[no_mangle] 11 | pub extern "C" fn main(_argc: isize, _argv: *const *const u8) -> isize { 12 | let _magenta = palette::Srgb::new(255u8, 0, 255); 13 | 14 | 0 15 | } 16 | 17 | #[cfg(feature = "nightly")] 18 | #[panic_handler] 19 | fn panic(_info: &PanicInfo) -> ! { 20 | loop {} 21 | } 22 | 23 | #[cfg(feature = "nightly")] 24 | #[no_mangle] 25 | pub unsafe fn __aeabi_unwind_cpp_pr0() -> () { 26 | loop {} 27 | } 28 | 29 | #[cfg(not(feature = "nightly"))] 30 | fn main() {} 31 | -------------------------------------------------------------------------------- /palette/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "palette" 3 | version = "0.7.6" #automatically updated 4 | authors = ["Erik Hedvall "] 5 | exclude = [ 6 | "scripts/*", 7 | "examples/*", 8 | "tests/*", 9 | "regression_tests/*", 10 | "benches/*", 11 | "res/*", 12 | ".travis.yml", 13 | ".gitignore", 14 | "CHANGELOG.md", 15 | "CONTRIBUTING.md", 16 | "version.sh", 17 | ] 18 | description = "Convert and manage colors with a focus on correctness, flexibility and ease of use." 19 | documentation = "https://docs.rs/palette/0.7.6/palette/" 20 | repository = "https://github.com/Ogeon/palette" 21 | readme = "README.md" 22 | keywords = ["color", "conversion", "linear", "pixel", "rgb"] 23 | license = "MIT OR Apache-2.0" 24 | edition = "2021" 25 | categories = ["graphics", "multimedia::images", "no-std"] 26 | rust-version = "1.61.0" 27 | 28 | [features] 29 | default = ["named_from_str", "std", "approx"] 30 | named = ["phf"] 31 | random = ["rand"] 32 | serializing = ["serde", "std"] 33 | find-crate = ["palette_derive/find-crate"] 34 | std = ["alloc", "approx?/std"] 35 | alloc = [] 36 | gamma_lut_u16 = [] 37 | 38 | # Deprecated. Alias for `"named"`. 39 | named_from_str = ["named"] 40 | 41 | [lib] 42 | bench = false 43 | 44 | [dependencies] 45 | palette_derive = { version = "0.7.6", path = "../palette_derive" } 46 | approx = { version = "0.5.0", default-features = false, optional = true } 47 | libm = { version = "0.2.1", default-features = false, optional = true } 48 | 49 | [dependencies.phf] 50 | version = "0.11.0" 51 | optional = true 52 | default-features = false 53 | 54 | [dependencies.rand] 55 | version = "0.8.0" 56 | default-features = false 57 | optional = true 58 | 59 | [dependencies.serde] 60 | version = "1.0.103" 61 | features = ["serde_derive"] 62 | optional = true 63 | 64 | [dependencies.bytemuck] 65 | version = "1.0.0" 66 | optional = true 67 | 68 | [dependencies.wide] 69 | version = "0.7.3" 70 | optional = true 71 | default-features = false 72 | 73 | [dev-dependencies] 74 | serde_json = "1.0.0" 75 | ron = "=0.8.0" # Pinned due to MSRV mismatch 76 | enterpolation = "0.2.0" 77 | 78 | [dev-dependencies.image] 79 | version = "0.23.14" 80 | default-features = false 81 | features = ["png"] 82 | 83 | [dev-dependencies.rand_mt] 84 | version = "4.0.0" 85 | default-features = false 86 | features = ["rand-traits"] 87 | 88 | [package.metadata.docs.rs] 89 | all-features = true 90 | -------------------------------------------------------------------------------- /palette/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /palette/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /palette/examples/blend.rs: -------------------------------------------------------------------------------- 1 | //! Demonstrates the output of blending in different color spaces and some of 2 | //! their characteristics. 3 | //! 4 | //! The output image shows the color spaces as groups of three columns. The 5 | //! color spaces are sRGB and XYZ. The columns within a group show the 6 | //! foreground image with its transparency multiplied by 0%, 50%, and 100% each. 7 | //! 8 | //! Each row is a different composition function, ordered from top to bottom as 9 | //! `multiply`, `screen`, `overlay`, `darken`, `lighten`, `dodge`, `burn`, 10 | //! `hard_light`, `soft_light`, `difference`, and `exclusion`. 11 | 12 | use palette::{blend::Blend, Alpha, FromColor, IntoColor, LinSrgba, Srgba, Xyza}; 13 | 14 | const ALPHA_STEPS: u32 = 3; 15 | const COLOR_SPACES: u32 = 2; 16 | const COLUMNS: u32 = ALPHA_STEPS * COLOR_SPACES; 17 | const BLEND_FNS: u32 = 11; 18 | 19 | fn main() { 20 | let background = image::open("example-data/input/fruits-128.png") 21 | .expect("could not open 'example-data/input/fruits-128.png'") 22 | .to_rgba8(); 23 | let foreground = image::open("example-data/input/blend_fg.png") 24 | .expect("could not open 'example-data/input/blend_fg.png'") 25 | .to_rgba8(); 26 | 27 | let tile_width = background.width(); 28 | let tile_height = background.height(); 29 | 30 | let mut image = image::RgbaImage::new(tile_width * COLUMNS, tile_height * BLEND_FNS); 31 | 32 | for ((x, y, background), foreground) in background.enumerate_pixels().zip(foreground.pixels()) { 33 | let background = Srgba::from(background.0).into_format(); 34 | let foreground = Srgba::from(foreground.0).into_format(); 35 | 36 | let bg_rgb = background; 37 | let bg_xyz = Xyza::from_color(background); 38 | 39 | for alpha_step in 0..ALPHA_STEPS { 40 | // Copy the original so we don't stack alpha changes. 41 | let mut foreground = foreground; 42 | foreground.alpha *= alpha_step as f32 / (ALPHA_STEPS - 1) as f32; 43 | 44 | draw_composed_pixels( 45 | &mut image, 46 | x, 47 | y, 48 | alpha_step, 49 | tile_width, 50 | tile_height, 51 | foreground, 52 | bg_rgb, 53 | ); 54 | 55 | draw_composed_pixels( 56 | &mut image, 57 | x, 58 | y, 59 | ALPHA_STEPS + alpha_step, 60 | tile_width, 61 | tile_height, 62 | Xyza::from_color(foreground), 63 | bg_xyz, 64 | ); 65 | } 66 | } 67 | 68 | let _ = std::fs::create_dir("example-data/output"); 69 | match image.save("example-data/output/blend.png") { 70 | Ok(()) => println!("see 'example-data/output/blend.png' for the result"), 71 | Err(e) => println!("failed to write 'example-data/output/blend.png': {}", e), 72 | } 73 | } 74 | 75 | #[allow(clippy::too_many_arguments)] 76 | fn draw_composed_pixels( 77 | image: &mut image::RgbaImage, 78 | x: u32, 79 | y: u32, 80 | column: u32, 81 | tile_width: u32, 82 | tile_height: u32, 83 | fg: Alpha, 84 | bg: Alpha, 85 | ) where 86 | Alpha: Blend + IntoColor + Copy, 87 | { 88 | let x = x + tile_width * column; 89 | let functions = [ 90 | Blend::multiply, 91 | Blend::screen, 92 | Blend::overlay, 93 | Blend::darken, 94 | Blend::lighten, 95 | Blend::dodge, 96 | Blend::burn, 97 | Blend::hard_light, 98 | Blend::soft_light, 99 | Blend::difference, 100 | Blend::exclusion, 101 | ]; 102 | 103 | for (row, function) in IntoIterator::into_iter(functions).enumerate() { 104 | let row = row as u32; 105 | 106 | image.put_pixel(x, y + tile_height * row, compose(fg, bg, function)); 107 | } 108 | } 109 | 110 | type ComposeFn = fn(Alpha, Alpha) -> Alpha; 111 | 112 | fn compose(fg: Alpha, bg: Alpha, function: ComposeFn) -> image::Rgba 113 | where 114 | Alpha: Blend + IntoColor, 115 | { 116 | let composed = function(fg, bg).into_color(); 117 | image::Rgba(Srgba::from_linear(composed).into()) 118 | } 119 | -------------------------------------------------------------------------------- /palette/examples/gradient.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | use enterpolation::{ 3 | linear::{ConstEquidistantLinear, Linear}, 4 | Curve, Merge, 5 | }; 6 | use palette::{FromColor, IntoColor, Lch, LinSrgb, Mix, Srgb}; 7 | 8 | use image::{GenericImage, GenericImageView, RgbImage}; 9 | 10 | // A gradient of evenly spaced colors 11 | let grad1 = ConstEquidistantLinear::::equidistant_unchecked([ 12 | LinSrgb::new(1.0, 0.1, 0.1), 13 | LinSrgb::new(0.1, 0.1, 1.0), 14 | LinSrgb::new(0.1, 1.0, 0.1), 15 | ]); 16 | 17 | // The same colors as in grad1, but with the blue point shifted down 18 | let grad2 = Linear::builder() 19 | .elements([ 20 | LinSrgb::new(1.0, 0.1, 0.1), 21 | LinSrgb::new(0.1, 0.1, 1.0), 22 | LinSrgb::new(0.1, 1.0, 0.1), 23 | ]) 24 | .knots([0.0, 0.25, 1.0]) 25 | .build() 26 | .unwrap(); 27 | 28 | // Necessary since Lch doesn't implement `Merge` automatically, but also to 29 | // get the correct circular blending. 30 | #[derive(Clone, Copy, Debug)] 31 | struct Adapter(T); 32 | 33 | impl Merge for Adapter { 34 | fn merge(self, to: Self, factor: T::Scalar) -> Self { 35 | Adapter(self.0.mix(to.0, factor)) 36 | } 37 | } 38 | 39 | // The same colors and offsets as in grad1, but in a color space where the hue 40 | // is a component 41 | let grad3 = Linear::equidistant_unchecked([ 42 | Adapter(Lch::from_color(LinSrgb::new(1.0, 0.1, 0.1))), 43 | Adapter(Lch::from_color(LinSrgb::new(0.1, 0.1, 1.0))), 44 | Adapter(Lch::from_color(LinSrgb::new(0.1, 1.0, 0.1))), 45 | ]); 46 | 47 | // The same colors and color space as in grad3, but with the blue point 48 | // shifted down 49 | let grad4 = Linear::builder() 50 | .elements([ 51 | Adapter(Lch::from_color(LinSrgb::new(1.0, 0.1, 0.1))), 52 | Adapter(Lch::from_color(LinSrgb::new(0.1, 0.1, 1.0))), 53 | Adapter(Lch::from_color(LinSrgb::new(0.1, 1.0, 0.1))), 54 | ]) 55 | .knots([0.0, 0.25, 1.0]) 56 | .build() 57 | .unwrap(); 58 | 59 | let mut image = RgbImage::new(256, 128); 60 | 61 | for (i, ((c1, c2), (Adapter(c3), Adapter(c4)))) in grad1 62 | .take(256) 63 | .zip(grad2.take(256)) 64 | .zip(grad3.take(256).zip(grad4.take(256))) 65 | .enumerate() 66 | { 67 | let c1 = Srgb::from_linear(c1).into(); 68 | let c2 = Srgb::from_linear(c2).into(); 69 | let c3 = Srgb::from_linear(c3.into_color()).into(); 70 | let c4 = Srgb::from_linear(c4.into_color()).into(); 71 | 72 | { 73 | let mut sub_image = image.sub_image(i as u32, 0, 1, 31); 74 | let (width, height) = sub_image.dimensions(); 75 | for x in 0..width { 76 | for y in 0..height { 77 | sub_image.put_pixel(x, y, image::Rgb(c1)); 78 | } 79 | } 80 | } 81 | 82 | { 83 | let mut sub_image = image.sub_image(i as u32, 32, 1, 31); 84 | let (width, height) = sub_image.dimensions(); 85 | for x in 0..width { 86 | for y in 0..height { 87 | sub_image.put_pixel(x, y, image::Rgb(c2)); 88 | } 89 | } 90 | } 91 | 92 | { 93 | let mut sub_image = image.sub_image(i as u32, 65, 1, 31); 94 | let (width, height) = sub_image.dimensions(); 95 | for x in 0..width { 96 | for y in 0..height { 97 | sub_image.put_pixel(x, y, image::Rgb(c3)); 98 | } 99 | } 100 | } 101 | 102 | { 103 | let mut sub_image = image.sub_image(i as u32, 97, 1, 31); 104 | let (width, height) = sub_image.dimensions(); 105 | for x in 0..width { 106 | for y in 0..height { 107 | sub_image.put_pixel(x, y, image::Rgb(c4)); 108 | } 109 | } 110 | } 111 | } 112 | 113 | let _ = std::fs::create_dir("example-data/output"); 114 | match image.save("example-data/output/gradient.png") { 115 | Ok(()) => println!("see 'example-data/output/gradient.png' for the result"), 116 | Err(e) => println!("failed to write 'example-data/output/gradient.png': {}", e), 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /palette/examples/hue.rs: -------------------------------------------------------------------------------- 1 | use palette::{FromColor, Hsl, Lch, ShiftHue, Srgb}; 2 | 3 | fn main() { 4 | let mut image = image::open("example-data/input/fruits.png") 5 | .expect("could not open 'example-data/input/fruits.png'") 6 | .to_rgb8(); 7 | 8 | //Shift hue by 180 degrees as HSL in bottom left part, and as LCh in top 9 | //right part. Notice how LCh manages to preserve the apparent lightness of 10 | //of the colors, compared to the original. 11 | for (x, y, pixel) in image.enumerate_pixels_mut() { 12 | let color = Srgb::from(pixel.0).into_format(); 13 | 14 | pixel.0 = if x < y { 15 | let hue_shifted = Hsl::from_color(color).shift_hue(180.0); 16 | Srgb::from_color(hue_shifted).into_format().into() 17 | } else { 18 | let hue_shifted = Lch::from_color(color).shift_hue(180.0); 19 | Srgb::from_color(hue_shifted).into_format().into() 20 | }; 21 | } 22 | 23 | let _ = std::fs::create_dir("example-data/output"); 24 | match image.save("example-data/output/hue.png") { 25 | Ok(()) => println!("see 'example-data/output/hue.png' for the result"), 26 | Err(e) => println!("failed to write 'example-data/output/hue.png': {}", e), 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /palette/examples/random.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "random"))] 2 | fn main() { 3 | println!("You can't use the `rand` integration without the \"random\" feature"); 4 | } 5 | 6 | #[cfg(feature = "random")] 7 | fn main() { 8 | use palette::{Hsl, Hsv, Hwb, IntoColor, RgbHue, Srgb}; 9 | 10 | use image::{GenericImage, GenericImageView, RgbImage}; 11 | use rand::Rng; 12 | 13 | let mut image = RgbImage::new(512, 256); 14 | let mut rng = rand_mt::Mt::default(); 15 | 16 | // RGB 17 | { 18 | let mut sub_image = image.sub_image(0, 0, 128, 128); 19 | let (width, height) = sub_image.dimensions(); 20 | for x in 0..width { 21 | for y in 0..height { 22 | let random_color: Srgb = Srgb::new(rng.gen(), rng.gen(), rng.gen()); 23 | sub_image.put_pixel(x, y, image::Rgb(random_color.into_format().into())); 24 | } 25 | } 26 | } 27 | 28 | { 29 | let mut sub_image = image.sub_image(0, 128, 128, 128); 30 | let (width, height) = sub_image.dimensions(); 31 | for x in 0..width { 32 | for y in 0..height { 33 | let random_color: Srgb = rng.gen(); 34 | sub_image.put_pixel(x, y, image::Rgb(random_color.into_format().into())); 35 | } 36 | } 37 | } 38 | 39 | // HSV 40 | { 41 | let mut sub_image = image.sub_image(128, 0, 128, 128); 42 | let (width, height) = sub_image.dimensions(); 43 | for x in 0..width { 44 | for y in 0..height { 45 | let random_color: Srgb = 46 | Hsv::new(rng.gen::(), rng.gen(), rng.gen()).into_color(); 47 | sub_image.put_pixel(x, y, image::Rgb(random_color.into_format().into())); 48 | } 49 | } 50 | } 51 | 52 | { 53 | let mut sub_image = image.sub_image(128, 128, 128, 128); 54 | let (width, height) = sub_image.dimensions(); 55 | for x in 0..width { 56 | for y in 0..height { 57 | let random_color: Srgb = rng.gen::().into_color(); 58 | sub_image.put_pixel(x, y, image::Rgb(random_color.into_format().into())); 59 | } 60 | } 61 | } 62 | 63 | // HSL 64 | { 65 | let mut sub_image = image.sub_image(256, 0, 128, 128); 66 | let (width, height) = sub_image.dimensions(); 67 | for x in 0..width { 68 | for y in 0..height { 69 | let random_color: Srgb = 70 | Hsl::new(rng.gen::(), rng.gen(), rng.gen()).into_color(); 71 | sub_image.put_pixel(x, y, image::Rgb(random_color.into_format().into())); 72 | } 73 | } 74 | } 75 | 76 | { 77 | let mut sub_image = image.sub_image(256, 128, 128, 128); 78 | let (width, height) = sub_image.dimensions(); 79 | for x in 0..width { 80 | for y in 0..height { 81 | let random_color: Srgb = rng.gen::().into_color(); 82 | sub_image.put_pixel(x, y, image::Rgb(random_color.into_format().into())); 83 | } 84 | } 85 | } 86 | 87 | // HWB 88 | { 89 | let mut sub_image = image.sub_image(384, 0, 128, 128); 90 | let (width, height) = sub_image.dimensions(); 91 | for x in 0..width { 92 | for y in 0..height { 93 | let random_color: Srgb = 94 | Hwb::new(rng.gen::(), rng.gen(), rng.gen()).into_color(); 95 | sub_image.put_pixel(x, y, image::Rgb(random_color.into_format().into())); 96 | } 97 | } 98 | } 99 | 100 | { 101 | let mut sub_image = image.sub_image(384, 128, 128, 128); 102 | let (width, height) = sub_image.dimensions(); 103 | for x in 0..width { 104 | for y in 0..height { 105 | let random_color: Srgb = rng.gen::().into_color(); 106 | sub_image.put_pixel(x, y, image::Rgb(random_color.into_format().into())); 107 | } 108 | } 109 | } 110 | 111 | let _ = std::fs::create_dir("example-data/output"); 112 | match image.save("example-data/output/random.png") { 113 | Ok(()) => println!("see 'example-data/output/random.png' for the result"), 114 | Err(e) => println!("failed to write 'example-data/output/random.png': {}", e), 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /palette/examples/saturate.rs: -------------------------------------------------------------------------------- 1 | use palette::{FromColor, Hsl, IntoColor, Lch, Saturate, Srgb}; 2 | 3 | use image::{GenericImage, GenericImageView}; 4 | 5 | fn main() { 6 | let mut image = image::open("example-data/input/cat.png") 7 | .expect("could not open 'example-data/input/cat.png'") 8 | .to_rgb8(); 9 | 10 | let width = image.width(); 11 | let height = image.height(); 12 | 13 | //Increase the saturation by 30% towards full saturation as HSL in the left half, and as LCh 14 | //in the right half. Notice the strong yellow tone in the HSL part. 15 | { 16 | let mut sub_image = image.sub_image(0, 0, width / 2, height); 17 | let (width, height) = sub_image.dimensions(); 18 | for x in 0..width { 19 | for y in 0..height { 20 | let color: Hsl = Srgb::from(sub_image.get_pixel(x, y).0) 21 | .into_format() 22 | .into_color(); 23 | 24 | let saturated = color.saturate(0.3); 25 | sub_image.put_pixel( 26 | x, 27 | y, 28 | image::Rgb(Srgb::from_color(saturated).into_format().into()), 29 | ); 30 | } 31 | } 32 | } 33 | 34 | { 35 | let mut sub_image = image.sub_image(width / 2, 0, width / 2, height); 36 | let (width, height) = sub_image.dimensions(); 37 | for x in 0..width { 38 | for y in 0..height { 39 | let color: Lch = Srgb::from(sub_image.get_pixel(x, y).0) 40 | .into_format() 41 | .into_color(); 42 | 43 | let saturated = color.saturate(0.3); 44 | sub_image.put_pixel( 45 | x, 46 | y, 47 | image::Rgb(Srgb::from_color(saturated).into_format().into()), 48 | ); 49 | } 50 | } 51 | } 52 | 53 | let _ = std::fs::create_dir("example-data/output"); 54 | match image.save("example-data/output/saturate.png") { 55 | Ok(()) => println!("see 'example-data/output/saturate.png' for the result"), 56 | Err(e) => println!("failed to write 'example-data/output/saturate.png': {}", e), 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /palette/examples/shade.rs: -------------------------------------------------------------------------------- 1 | use palette::{Darken, FromColor, Hsv, IntoColor, Lab, Lighten, LinSrgb, Srgb}; 2 | 3 | use image::{GenericImage, GenericImageView, RgbImage}; 4 | 5 | fn main() { 6 | //The same color in linear RGB, CIE L*a*b*, and HSV 7 | let rgb = LinSrgb::new(0.5, 0.0, 0.0); 8 | let lab = Lab::from_color(rgb); 9 | let hsv = Hsv::from_color(rgb); 10 | 11 | let mut image = RgbImage::new(220, 193); 12 | 13 | for i in 0..11 { 14 | let rgb1 = Srgb::from_linear(rgb.darken(0.1 * i as f32)).into(); 15 | let rgb2 = Srgb::from_linear(rgb.lighten(0.1 * i as f32)).into(); 16 | 17 | { 18 | let mut sub_image = image.sub_image(i as u32 * 20, 0, 20, 31); 19 | let (width, height) = sub_image.dimensions(); 20 | for x in 0..width { 21 | for y in 0..height { 22 | sub_image.put_pixel(x, y, image::Rgb(rgb1)); 23 | } 24 | } 25 | } 26 | 27 | { 28 | let mut sub_image = image.sub_image(i as u32 * 20, 32, 20, 31); 29 | let (width, height) = sub_image.dimensions(); 30 | for x in 0..width { 31 | for y in 0..height { 32 | sub_image.put_pixel(x, y, image::Rgb(rgb2)); 33 | } 34 | } 35 | } 36 | 37 | let lab1 = Srgb::from_linear(lab.darken(0.1 * i as f32).into_color()).into(); 38 | let lab2 = Srgb::from_linear(lab.lighten(0.1 * i as f32).into_color()).into(); 39 | 40 | { 41 | let mut sub_image = image.sub_image(i as u32 * 20, 65, 20, 31); 42 | let (width, height) = sub_image.dimensions(); 43 | for x in 0..width { 44 | for y in 0..height { 45 | sub_image.put_pixel(x, y, image::Rgb(lab1)); 46 | } 47 | } 48 | } 49 | 50 | { 51 | let mut sub_image = image.sub_image(i as u32 * 20, 97, 20, 31); 52 | let (width, height) = sub_image.dimensions(); 53 | for x in 0..width { 54 | for y in 0..height { 55 | sub_image.put_pixel(x, y, image::Rgb(lab2)); 56 | } 57 | } 58 | } 59 | 60 | let hsv1 = Srgb::from_linear(hsv.darken(0.1 * i as f32).into_color()).into(); 61 | let hsv2 = Srgb::from_linear(hsv.lighten(0.1 * i as f32).into_color()).into(); 62 | 63 | { 64 | let mut sub_image = image.sub_image(i as u32 * 20, 130, 20, 31); 65 | let (width, height) = sub_image.dimensions(); 66 | for x in 0..width { 67 | for y in 0..height { 68 | sub_image.put_pixel(x, y, image::Rgb(hsv1)); 69 | } 70 | } 71 | } 72 | 73 | { 74 | let mut sub_image = image.sub_image(i as u32 * 20, 162, 20, 31); 75 | let (width, height) = sub_image.dimensions(); 76 | for x in 0..width { 77 | for y in 0..height { 78 | sub_image.put_pixel(x, y, image::Rgb(hsv2)); 79 | } 80 | } 81 | } 82 | } 83 | 84 | let _ = std::fs::create_dir("example-data/output"); 85 | match image.save("example-data/output/shade.png") { 86 | Ok(()) => println!("see 'example-data/output/shade.png' for the result"), 87 | Err(e) => println!("failed to write 'example-data/output/shade.png': {}", e), 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /palette/examples/struct_of_arrays.rs: -------------------------------------------------------------------------------- 1 | use palette::{cast::ComponentsAs, color_difference::EuclideanDistance, IntoColor, Oklab, Srgb}; 2 | 3 | fn main() { 4 | let image = image::open("example-data/input/fruits.png") 5 | .expect("could not open 'example-data/input/fruits.png'") 6 | .to_rgb8(); 7 | 8 | let image: &[Srgb] = image.components_as(); 9 | 10 | // Convert and collect the colors in a struct-of-arrays (SoA) format, where 11 | // each component is a Vec of all the pixels' component values. 12 | let oklab_image: Oklab> = image 13 | .iter() 14 | .map(|rgb| rgb.into_linear::().into_color()) 15 | .collect(); 16 | 17 | // Find the min, max and average of each component. We are doing it by 18 | // iterating over each component Vec separately, starting with l... 19 | let (min_l, max_l, average_l) = get_min_max_average(oklab_image.l.iter().copied()); 20 | let (min_a, max_a, average_a) = get_min_max_average(oklab_image.a.iter().copied()); 21 | let (min_b, max_b, average_b) = get_min_max_average(oklab_image.b.iter().copied()); 22 | 23 | // Find out how far the colors in the image are from the average color. In 24 | // this case, we can just iterate over all of the colors and consume the 25 | // collection(s). 26 | let average_color = Oklab::new(average_l, average_a, average_b); 27 | let (min_d, max_d, average_d) = get_min_max_average( 28 | oklab_image 29 | .into_iter() 30 | .map(|color| average_color.distance(color)), 31 | ); 32 | 33 | // Print the stats. 34 | println!("Oklab l: min {min_l}, average {average_l}, max {max_l}"); 35 | println!("Oklab a: min {min_a}, average {average_a}, max {max_a}"); 36 | println!("Oklab b: min {min_b}, average {average_b}, max {max_b}"); 37 | println!("Distance from average color: min {min_d}, average {average_d}, max {max_d}"); 38 | } 39 | 40 | /// Calculates the min, max and average of the iterator's values. 41 | fn get_min_max_average(iter: impl ExactSizeIterator) -> (f32, f32, f32) { 42 | let length = iter.len(); 43 | 44 | let (min, max, sum) = iter.fold( 45 | (f32::INFINITY, f32::NEG_INFINITY, 0.0), 46 | |(min, max, sum), value| (min.min(value), max.max(value), sum + value), 47 | ); 48 | 49 | (min, max, sum / length as f32) 50 | } 51 | -------------------------------------------------------------------------------- /palette/src/angle/wide.rs: -------------------------------------------------------------------------------- 1 | use ::wide::{f32x4, f32x8, f64x2, f64x4, CmpEq}; 2 | 3 | use super::*; 4 | 5 | macro_rules! impl_angle_wide_float { 6 | ($($ty: ident),+) => { 7 | $( 8 | impl HalfRotation for $ty { 9 | #[inline] 10 | fn half_rotation() -> Self { 11 | $ty::splat(180.0) 12 | } 13 | } 14 | 15 | impl FullRotation for $ty { 16 | #[inline] 17 | fn full_rotation() -> Self { 18 | $ty::splat(360.0) 19 | } 20 | } 21 | 22 | impl RealAngle for $ty { 23 | #[inline] 24 | fn degrees_to_radians(self) -> Self { 25 | self.to_radians() 26 | } 27 | 28 | #[inline] 29 | fn radians_to_degrees(self) -> Self { 30 | self.to_degrees() 31 | } 32 | } 33 | 34 | impl AngleEq for $ty { 35 | #[inline] 36 | fn angle_eq(&self, other: &Self) -> Self { 37 | self.normalize_unsigned_angle().cmp_eq(other.normalize_unsigned_angle()) 38 | } 39 | } 40 | 41 | impl SignedAngle for $ty { 42 | #[inline] 43 | fn normalize_signed_angle(self) -> Self { 44 | self - Round::ceil(((self + 180.0) / 360.0) - 1.0) * 360.0 45 | } 46 | } 47 | 48 | impl UnsignedAngle for $ty { 49 | #[inline] 50 | fn normalize_unsigned_angle(self) -> Self { 51 | self - (Round::floor(self / 360.0) * 360.0) 52 | } 53 | } 54 | )+ 55 | }; 56 | } 57 | 58 | impl_angle_wide_float!(f32x4, f32x8, f64x2, f64x4); 59 | -------------------------------------------------------------------------------- /palette/src/blend.rs: -------------------------------------------------------------------------------- 1 | //! Color blending and blending equations. 2 | //! 3 | //! Palette offers both OpenGL style blending equations, as well as most of the 4 | //! SVG composition operators (also common in photo manipulation software). The 5 | //! composition operators are all implemented in the [`Compose`] and [`Blend`] 6 | //! traits, and ready to use with any appropriate color type: 7 | //! 8 | //! ``` 9 | //! use palette::{blend::Blend, LinSrgba}; 10 | //! 11 | //! let a = LinSrgba::new(0.2, 0.5, 0.1, 0.8); 12 | //! let b = LinSrgba::new(0.6, 0.3, 0.5, 0.1); 13 | //! let c = a.overlay(b); 14 | //! ``` 15 | //! 16 | //! Blending equations can be defined using the [`Equations`] type, which is 17 | //! then passed to the `blend` function, from the `Blend` trait: 18 | //! 19 | //! ``` 20 | //! use palette::LinSrgba; 21 | //! use palette::blend::{BlendWith, Equations, Parameter}; 22 | //! 23 | //! let blend_mode = Equations::from_parameters( 24 | //! Parameter::SourceAlpha, 25 | //! Parameter::OneMinusSourceAlpha 26 | //! ); 27 | //! 28 | //! let a = LinSrgba::new(0.2, 0.5, 0.1, 0.8); 29 | //! let b = LinSrgba::new(0.6, 0.3, 0.5, 0.1); 30 | //! let c = a.blend_with(b, blend_mode); 31 | //! ``` 32 | //! 33 | //! Note that blending will use [premultiplied alpha](crate::blend::PreAlpha), 34 | //! which may result in loss of some color information in some cases. One such 35 | //! case is that a completely transparent resultant color will become black. 36 | 37 | use crate::{ 38 | cast::{self, ArrayCast}, 39 | clamp, 40 | num::{Arithmetics, Clamp, One, Real, Zero}, 41 | stimulus::Stimulus, 42 | }; 43 | 44 | pub use self::{ 45 | blend::Blend, 46 | blend_with::BlendWith, 47 | compose::Compose, 48 | equations::{Equation, Equations, Parameter, Parameters}, 49 | pre_alpha::PreAlpha, 50 | }; 51 | 52 | #[allow(clippy::module_inception)] 53 | mod blend; 54 | mod blend_with; 55 | mod compose; 56 | mod equations; 57 | mod pre_alpha; 58 | 59 | #[cfg(test)] 60 | mod test; 61 | 62 | /// A trait for custom blend functions. 63 | pub trait BlendFunction 64 | where 65 | C: Premultiply, 66 | { 67 | /// Apply this blend function to a pair of colors. 68 | #[must_use] 69 | fn apply_to(self, source: PreAlpha, destination: PreAlpha) -> PreAlpha; 70 | } 71 | 72 | impl BlendFunction for F 73 | where 74 | C: Premultiply, 75 | F: FnOnce(PreAlpha, PreAlpha) -> PreAlpha, 76 | { 77 | #[inline] 78 | fn apply_to(self, source: PreAlpha, destination: PreAlpha) -> PreAlpha { 79 | (self)(source, destination) 80 | } 81 | } 82 | 83 | /// Alpha masking and unmasking. 84 | pub trait Premultiply: Sized { 85 | /// The color's component type. 86 | type Scalar: Real + Stimulus; 87 | 88 | /// Alpha mask the color. 89 | /// 90 | /// This is done by multiplying the color's component by `alpha`. 91 | #[must_use] 92 | fn premultiply(self, alpha: Self::Scalar) -> PreAlpha; 93 | 94 | /// Alpha unmask the color, resulting in a color and transparency pair. 95 | /// 96 | /// This is done by dividing the masked color's component by `alpha`, or 97 | /// returning a black color if `alpha` is `0`. 98 | #[must_use] 99 | fn unpremultiply(premultiplied: PreAlpha) -> (Self, Self::Scalar); 100 | } 101 | 102 | fn blend_alpha(src: T, dst: T) -> T 103 | where 104 | T: Zero + One + Arithmetics + Clamp + Clone, 105 | { 106 | clamp(src.clone() + &dst - src * dst, T::zero(), T::one()) 107 | } 108 | 109 | fn zip_colors<'a, C, T, const N: usize>( 110 | src: C, 111 | dst: &'a mut C, 112 | ) -> impl Iterator 113 | where 114 | C: ArrayCast, 115 | T: 'a, 116 | { 117 | IntoIterator::into_iter(cast::into_array(src)).zip(cast::into_array_mut(dst)) 118 | } 119 | -------------------------------------------------------------------------------- /palette/src/blend/blend_with.rs: -------------------------------------------------------------------------------- 1 | use crate::{stimulus::Stimulus, Alpha}; 2 | 3 | use super::{BlendFunction, PreAlpha, Premultiply}; 4 | 5 | /// Blending with a custom blend function. 6 | /// 7 | /// This is a convenience trait that makes it possible to use [`BlendFunction`] 8 | /// via a method on the source color. This makes custom blending more similar to 9 | /// how the [`Compose`][super::Compose] and [`Blend`][super::Blend] are used, 10 | /// including automatic pre-multiplication. 11 | pub trait BlendWith { 12 | /// The base color type of `Self`. 13 | type Color: Premultiply; 14 | 15 | /// Blend self, as the source color, with `destination`, using 16 | /// `blend_function`. Anything that implements [`BlendFunction`] is 17 | /// acceptable, including functions and closures. 18 | /// 19 | /// ``` 20 | /// use palette::{LinSrgb, LinSrgba}; 21 | /// use palette::blend::{BlendWith, PreAlpha}; 22 | /// 23 | /// type PreRgba = PreAlpha>; 24 | /// 25 | /// fn blend_mode(a: PreRgba, b: PreRgba) -> PreRgba { 26 | /// PreAlpha { 27 | /// color: LinSrgb::new(a.red * b.green, a.green * b.blue, a.blue * b.red), 28 | /// alpha: a.alpha * b.alpha, 29 | /// } 30 | /// } 31 | /// 32 | /// let a = LinSrgba::new(0.2, 0.5, 0.1, 0.8); 33 | /// let b = LinSrgba::new(0.6, 0.3, 0.5, 0.1); 34 | /// let c = a.blend_with(b, blend_mode); 35 | /// ``` 36 | #[must_use] 37 | fn blend_with(self, destination: Self, blend_function: F) -> Self 38 | where 39 | F: BlendFunction; 40 | } 41 | 42 | impl BlendWith for PreAlpha 43 | where 44 | C: Premultiply, 45 | { 46 | type Color = C; 47 | 48 | #[inline] 49 | fn blend_with(self, other: Self, blend_function: F) -> Self 50 | where 51 | F: BlendFunction, 52 | { 53 | blend_function.apply_to(self, other) 54 | } 55 | } 56 | 57 | impl BlendWith for Alpha 58 | where 59 | C: Premultiply, 60 | { 61 | type Color = C; 62 | 63 | fn blend_with(self, destination: Self, blend_function: F) -> Self 64 | where 65 | F: crate::blend::BlendFunction, 66 | { 67 | self.premultiply() 68 | .blend_with(destination.premultiply(), blend_function) 69 | .unpremultiply() 70 | } 71 | } 72 | 73 | impl BlendWith for C 74 | where 75 | C: Premultiply, 76 | C::Scalar: Stimulus, 77 | { 78 | type Color = C; 79 | 80 | fn blend_with(self, other: Self, blend_function: F) -> Self 81 | where 82 | F: BlendFunction, 83 | { 84 | PreAlpha::new_opaque(self) 85 | .blend_with(PreAlpha::new_opaque(other), blend_function) 86 | .unpremultiply() 87 | .color 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /palette/src/bool_mask.rs: -------------------------------------------------------------------------------- 1 | //! Traits for abstracting over Boolean types. 2 | //! 3 | //! These traits are mainly useful for allowing SIMD values, where bit masks are 4 | //! typically used instead of `bool`. 5 | 6 | use core::ops::{BitAnd, BitOr, BitXor, Not}; 7 | 8 | #[cfg(feature = "wide")] 9 | mod wide; 10 | 11 | /// Associates a Boolean type to the implementing type. 12 | /// 13 | /// This is primarily used in traits and functions that can accept SIMD values 14 | /// and return a Boolean result. SIMD values use masks to select different values for 15 | /// each lane and `HasBoolMask::Mask` can be used to know which type that mask 16 | /// has. 17 | pub trait HasBoolMask { 18 | /// The mask type to use for selecting `Self` values. 19 | type Mask: BoolMask; 20 | } 21 | 22 | impl HasBoolMask for &'_ T 23 | where 24 | T: HasBoolMask, 25 | { 26 | type Mask = T::Mask; 27 | } 28 | 29 | impl HasBoolMask for &'_ mut T 30 | where 31 | T: HasBoolMask, 32 | { 33 | type Mask = T::Mask; 34 | } 35 | 36 | impl HasBoolMask for [T] 37 | where 38 | T: HasBoolMask, 39 | { 40 | type Mask = T::Mask; 41 | } 42 | 43 | impl HasBoolMask for [T; N] 44 | where 45 | T: HasBoolMask, 46 | { 47 | type Mask = T::Mask; 48 | } 49 | 50 | macro_rules! impl_has_bool_mask { 51 | ($($ty:ident),+) => { 52 | $( 53 | impl HasBoolMask for $ty { 54 | type Mask = bool; 55 | } 56 | )+ 57 | }; 58 | } 59 | 60 | impl_has_bool_mask!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64); 61 | 62 | /// Basic methods for boolean masks. 63 | pub trait BoolMask { 64 | /// Create a new mask where each lane is set to `value`. 65 | #[must_use] 66 | fn from_bool(value: bool) -> Self; 67 | 68 | /// Checks if all lanes in the mask are `true`. 69 | #[must_use] 70 | fn is_true(&self) -> bool; 71 | 72 | /// Checks if all lanes in the mask are `false`. 73 | #[must_use] 74 | fn is_false(&self) -> bool; 75 | } 76 | 77 | impl BoolMask for bool { 78 | #[inline] 79 | fn from_bool(value: bool) -> Self { 80 | value 81 | } 82 | 83 | #[inline] 84 | fn is_true(&self) -> bool { 85 | *self 86 | } 87 | 88 | #[inline] 89 | fn is_false(&self) -> bool { 90 | !*self 91 | } 92 | } 93 | 94 | /// Makes a mask bale to select between two values. 95 | pub trait Select 96 | where 97 | T: HasBoolMask, 98 | { 99 | /// Select lanes from `a` when corresponding lanes in `self` are `true`, and 100 | /// select from `b` when `false`. 101 | #[must_use] 102 | fn select(self, a: T, b: T) -> T; 103 | } 104 | 105 | impl Select for bool 106 | where 107 | T: HasBoolMask, 108 | { 109 | #[inline(always)] 110 | fn select(self, a: T, b: T) -> T { 111 | if self { 112 | a 113 | } else { 114 | b 115 | } 116 | } 117 | } 118 | 119 | /// Like [`Select`], but can avoid evaluating the input. 120 | pub trait LazySelect: Select 121 | where 122 | T: HasBoolMask, 123 | { 124 | /// Select lanes from the output of `a` when corresponding lanes in `self` 125 | /// are `true`, and select from the output of `b` when `false`. May avoid 126 | /// evaluating either option if it's not selected. 127 | #[must_use] 128 | fn lazy_select(self, a: A, b: B) -> T 129 | where 130 | A: FnOnce() -> T, 131 | B: FnOnce() -> T; 132 | } 133 | 134 | impl LazySelect for bool 135 | where 136 | T: HasBoolMask, 137 | { 138 | #[inline(always)] 139 | fn lazy_select(self, a: A, b: B) -> T 140 | where 141 | A: FnOnce() -> T, 142 | B: FnOnce() -> T, 143 | { 144 | if self { 145 | a() 146 | } else { 147 | b() 148 | } 149 | } 150 | } 151 | 152 | /// A helper trait that collects bit traits under one name. 153 | pub trait BitOps: 154 | Sized 155 | + BitAnd 156 | + BitOr 157 | + BitXor 158 | + Not 159 | + for<'a> BitAnd<&'a Self, Output = Self> 160 | + for<'a> BitOr<&'a Self, Output = Self> 161 | + for<'a> BitXor<&'a Self, Output = Self> 162 | { 163 | } 164 | 165 | impl BitOps for T where 166 | T: Sized 167 | + BitAnd 168 | + BitOr 169 | + BitXor 170 | + Not 171 | + for<'a> BitAnd<&'a Self, Output = Self> 172 | + for<'a> BitOr<&'a Self, Output = Self> 173 | + for<'a> BitXor<&'a Self, Output = Self> 174 | { 175 | } 176 | -------------------------------------------------------------------------------- /palette/src/bool_mask/wide.rs: -------------------------------------------------------------------------------- 1 | use wide::{f32x4, f32x8, f64x2, f64x4}; 2 | 3 | use super::{BoolMask, HasBoolMask, LazySelect, Select}; 4 | 5 | macro_rules! impl_wide_bool_mask { 6 | ($($ty: ident: ($scalar: ident, $uint: ident)),+) => { 7 | $( 8 | impl BoolMask for $ty { 9 | #[inline] 10 | fn from_bool(value: bool) -> Self { 11 | $ty::splat(if value { $scalar::from_bits($uint::MAX) } else { 0.0 }) 12 | } 13 | 14 | #[inline] 15 | fn is_true(&self) -> bool { 16 | self.all() 17 | } 18 | 19 | #[inline] 20 | fn is_false(&self) -> bool { 21 | self.none() 22 | } 23 | } 24 | 25 | impl HasBoolMask for $ty { 26 | type Mask = Self; 27 | } 28 | 29 | impl Select for $ty { 30 | #[inline] 31 | fn select(self, a: Self, b: Self) -> Self { 32 | self.blend(a, b) 33 | } 34 | } 35 | 36 | impl LazySelect for $ty { 37 | #[inline] 38 | fn lazy_select(self, a: A, b: B) -> Self 39 | where 40 | A: FnOnce() -> Self, 41 | B: FnOnce() -> Self, 42 | { 43 | let a = a(); 44 | let b = b(); 45 | 46 | self.select(a, b) 47 | } 48 | } 49 | )+ 50 | }; 51 | } 52 | 53 | impl_wide_bool_mask!( 54 | f32x4: (f32, u32), 55 | f32x8: (f32, u32), 56 | f64x2: (f64, u64), 57 | f64x4: (f64, u64) 58 | ); 59 | 60 | #[cfg(test)] 61 | mod test { 62 | use wide::{f32x4, f32x8, f64x2, f64x4}; 63 | 64 | use crate::bool_mask::BoolMask; 65 | 66 | #[test] 67 | fn from_true() { 68 | assert!(f32x4::from_bool(true).is_true()); 69 | assert!(!f32x4::from_bool(true).is_false()); 70 | 71 | assert!(f32x8::from_bool(true).is_true()); 72 | assert!(!f32x8::from_bool(true).is_false()); 73 | 74 | assert!(f64x2::from_bool(true).is_true()); 75 | assert!(!f64x2::from_bool(true).is_false()); 76 | 77 | assert!(f64x4::from_bool(true).is_true()); 78 | assert!(!f64x4::from_bool(true).is_false()); 79 | } 80 | 81 | #[test] 82 | fn from_false() { 83 | assert!(f32x4::from_bool(false).is_false()); 84 | assert!(!f32x4::from_bool(false).is_true()); 85 | 86 | assert!(f32x8::from_bool(false).is_false()); 87 | assert!(!f32x8::from_bool(false).is_true()); 88 | 89 | assert!(f64x2::from_bool(false).is_false()); 90 | assert!(!f64x2::from_bool(false).is_true()); 91 | 92 | assert!(f64x4::from_bool(false).is_false()); 93 | assert!(!f64x4::from_bool(false).is_true()); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /palette/src/cam16/math/chromaticity.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | bool_mask::{LazySelect, Select}, 3 | cam16::{ 4 | math::{self, DependentParameters}, 5 | BakedParameters, 6 | }, 7 | num::{Arithmetics, FromScalar, PartialCmp, Real, Sqrt, Zero}, 8 | }; 9 | 10 | /// One the apparent chromatic intensity metrics of CAM16. 11 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 12 | pub(crate) enum ChromaticityType { 13 | /// The [chroma](https://en.wikipedia.org/wiki/Colorfulness#Chroma) (C) of a 14 | /// color. 15 | Chroma(T), 16 | 17 | /// The [colorfulness](https://en.wikipedia.org/wiki/Colorfulness) (M) of a 18 | /// color. 19 | Colorfulness(T), 20 | 21 | /// The [saturation](https://en.wikipedia.org/wiki/Colorfulness#Saturation) 22 | /// (s) of a color. 23 | Saturation(T), 24 | } 25 | 26 | impl ChromaticityType { 27 | pub(crate) fn into_cam16( 28 | self, 29 | lightness: T, 30 | parameters: BakedParameters, 31 | ) -> (T, T, T) 32 | where 33 | T: Real + FromScalar + Zero + Arithmetics + Sqrt + PartialCmp + Clone, 34 | T::Mask: LazySelect + Clone, 35 | { 36 | let DependentParameters { c, a_w, f_l_4, .. } = parameters.inner; 37 | let is_black = lightness.eq(&T::zero()); 38 | 39 | match self { 40 | ChromaticityType::Chroma(chroma) => { 41 | let colorfulness = lazy_select! { 42 | if is_black.clone() => T::zero(), 43 | else => math::chroma_to_colorfulness(chroma.clone(), T::from_scalar(f_l_4)) 44 | }; 45 | let saturation = lazy_select! { 46 | if is_black.clone() => T::zero(), 47 | else => math::chroma_to_saturation( 48 | chroma.clone(), 49 | lightness, 50 | T::from_scalar(c), 51 | T::from_scalar(a_w), 52 | ) 53 | }; 54 | let chroma = is_black.select(T::zero(), chroma); 55 | 56 | (chroma, colorfulness, saturation) 57 | } 58 | ChromaticityType::Colorfulness(colorfulness) => { 59 | let chroma = lazy_select! { 60 | if is_black.clone() => T::zero(), 61 | else => math::colorfulness_to_chroma(colorfulness.clone(), T::from_scalar(f_l_4)) 62 | }; 63 | let saturation = lazy_select! { 64 | if is_black.clone() => T::zero(), 65 | else => math::chroma_to_saturation( 66 | chroma.clone(), 67 | lightness, 68 | T::from_scalar(c), 69 | T::from_scalar(a_w), 70 | ) 71 | }; 72 | let colorfulness = is_black.select(T::zero(), colorfulness); 73 | 74 | (chroma, colorfulness, saturation) 75 | } 76 | ChromaticityType::Saturation(saturation) => { 77 | let chroma = lazy_select! { 78 | if is_black.clone() => T::zero(), 79 | else => math::saturation_to_chroma( 80 | saturation.clone(), 81 | lightness, 82 | T::from_scalar(c), 83 | T::from_scalar(a_w), 84 | ) 85 | }; 86 | let colorfulness = lazy_select! { 87 | if is_black.clone() => T::zero(), 88 | else => math::chroma_to_colorfulness(chroma.clone(), T::from_scalar(f_l_4)) 89 | }; 90 | let saturation = is_black.select(T::zero(), saturation); 91 | 92 | (chroma, colorfulness, saturation) 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /palette/src/cam16/math/luminance.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | bool_mask::LazySelect, 3 | cam16::BakedParameters, 4 | num::{Arithmetics, FromScalar, PartialCmp, Real, Sqrt, Zero}, 5 | }; 6 | /// One the apparent luminance metrics of CAM16. 7 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 8 | #[non_exhaustive] 9 | pub(crate) enum LuminanceType { 10 | /// The [lightness](https://en.wikipedia.org/wiki/Lightness) (J) of a color. 11 | Lightness(T), 12 | 13 | /// The [brightness](https://en.wikipedia.org/wiki/Brightness) (Q) of a 14 | /// color. 15 | Brightness(T), 16 | } 17 | 18 | impl LuminanceType { 19 | pub(crate) fn into_cam16(self, parameters: BakedParameters) -> (T, T) 20 | where 21 | T: Real + FromScalar + Zero + Arithmetics + Sqrt + PartialCmp + Clone, 22 | T::Mask: LazySelect + Clone, 23 | { 24 | let parameters = parameters.inner; 25 | 26 | match self { 27 | LuminanceType::Lightness(lightness) => { 28 | let is_black = lightness.eq(&T::zero()); 29 | let brightness = lazy_select! { 30 | if is_black => T::zero(), 31 | else => crate::cam16::math::lightness_to_brightness( 32 | lightness.clone(), 33 | T::from_scalar(parameters.c), 34 | T::from_scalar(parameters.a_w), 35 | T::from_scalar(parameters.f_l_4), 36 | ) 37 | }; 38 | 39 | (lightness, brightness) 40 | } 41 | LuminanceType::Brightness(brightness) => { 42 | let is_black = brightness.eq(&T::zero()); 43 | let lightness = lazy_select! { 44 | if is_black => T::zero(), 45 | else => crate::cam16::math::brightness_to_lightness( 46 | brightness.clone(), 47 | T::from_scalar(parameters.c), 48 | T::from_scalar(parameters.a_w), 49 | T::from_scalar(parameters.f_l_4), 50 | ) 51 | }; 52 | 53 | (lightness, brightness) 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /palette/src/convert/from_into_color_unclamped.rs: -------------------------------------------------------------------------------- 1 | pub use palette_derive::FromColorUnclamped; 2 | 3 | #[cfg(feature = "alloc")] 4 | use crate::cast::{self, ArrayCast}; 5 | 6 | /// A trait for unchecked conversion of one color from another. 7 | /// 8 | /// See [`FromColor`](crate::convert::FromColor) for a lossy version of this trait. 9 | /// See [`TryFromColor`](crate::convert::TryFromColor) for a trait that gives an error when the result 10 | /// is out of bounds. 11 | /// 12 | /// See the [`convert`](crate::convert) module for how to implement `FromColorUnclamped` for 13 | /// custom colors. 14 | pub trait FromColorUnclamped: Sized { 15 | /// Convert from T. The resulting color might be invalid in its color space. 16 | /// 17 | /// ``` 18 | /// use palette::convert::FromColorUnclamped; 19 | /// use palette::{IsWithinBounds, Lch, Srgb}; 20 | /// 21 | /// let rgb = Srgb::from_color_unclamped(Lch::new(50.0f32, 100.0, -175.0)); 22 | /// assert!(!rgb.is_within_bounds()); 23 | /// ``` 24 | #[must_use] 25 | fn from_color_unclamped(val: T) -> Self; 26 | } 27 | 28 | #[cfg(feature = "alloc")] 29 | impl FromColorUnclamped> for alloc::vec::Vec 30 | where 31 | T: ArrayCast, 32 | U: ArrayCast + FromColorUnclamped, 33 | { 34 | /// Convert all colors in place, without reallocating. 35 | /// 36 | /// ``` 37 | /// use palette::{convert::FromColorUnclamped, SaturateAssign, Srgb, Lch}; 38 | /// 39 | /// let srgb = vec![Srgb::new(0.8f32, 1.0, 0.2), Srgb::new(0.9, 0.1, 0.3)]; 40 | /// let mut lch = Vec::::from_color_unclamped(srgb); 41 | /// 42 | /// lch.saturate_assign(0.1); 43 | /// 44 | /// let srgb = Vec::::from_color_unclamped(lch); 45 | /// ``` 46 | #[inline] 47 | fn from_color_unclamped(color: alloc::vec::Vec) -> Self { 48 | cast::map_vec_in_place(color, U::from_color_unclamped) 49 | } 50 | } 51 | 52 | #[cfg(feature = "alloc")] 53 | impl FromColorUnclamped> for alloc::boxed::Box<[U]> 54 | where 55 | T: ArrayCast, 56 | U: ArrayCast + FromColorUnclamped, 57 | { 58 | /// Convert all colors in place, without reallocating. 59 | /// 60 | /// ``` 61 | /// use palette::{convert::FromColorUnclamped, SaturateAssign, Srgb, Lch}; 62 | /// 63 | /// let srgb = vec![Srgb::new(0.8f32, 1.0, 0.2), Srgb::new(0.9, 0.1, 0.3)].into_boxed_slice(); 64 | /// let mut lch = Box::<[Lch]>::from_color_unclamped(srgb); 65 | /// 66 | /// lch.saturate_assign(0.1); 67 | /// 68 | /// let srgb = Box::<[Srgb]>::from_color_unclamped(lch); 69 | /// ``` 70 | #[inline] 71 | fn from_color_unclamped(color: alloc::boxed::Box<[T]>) -> Self { 72 | cast::map_slice_box_in_place(color, U::from_color_unclamped) 73 | } 74 | } 75 | 76 | /// A trait for unchecked conversion of a color into another. 77 | /// 78 | /// `U: IntoColorUnclamped` is implemented for every type `T: FromColorUnclamped`. 79 | /// 80 | /// See [`FromColorUnclamped`](crate::convert::FromColorUnclamped) for more details. 81 | pub trait IntoColorUnclamped: Sized { 82 | /// Convert into T. The resulting color might be invalid in its color space 83 | /// 84 | /// ``` 85 | /// use palette::convert::IntoColorUnclamped; 86 | /// use palette::{IsWithinBounds, Lch, Srgb}; 87 | /// 88 | ///let rgb: Srgb = Lch::new(50.0, 100.0, -175.0).into_color_unclamped(); 89 | ///assert!(!rgb.is_within_bounds()); 90 | ///``` 91 | #[must_use] 92 | fn into_color_unclamped(self) -> T; 93 | } 94 | 95 | impl IntoColorUnclamped for T 96 | where 97 | U: FromColorUnclamped, 98 | { 99 | #[inline] 100 | fn into_color_unclamped(self) -> U { 101 | U::from_color_unclamped(self) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /palette/src/convert/try_from_into_color.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | use crate::IsWithinBounds; 4 | 5 | use super::FromColorUnclamped; 6 | 7 | /// The error type for a color conversion that converted a color into a color 8 | /// with invalid values. 9 | #[derive(Debug)] 10 | pub struct OutOfBounds { 11 | color: T, 12 | } 13 | 14 | impl OutOfBounds { 15 | /// Create a new error wrapping a color 16 | #[inline] 17 | fn new(color: T) -> Self { 18 | OutOfBounds { color } 19 | } 20 | 21 | /// Consume this error and return the wrapped color 22 | #[inline] 23 | pub fn color(self) -> T { 24 | self.color 25 | } 26 | } 27 | 28 | #[cfg(feature = "std")] 29 | impl std::error::Error for OutOfBounds { 30 | fn description(&self) -> &str { 31 | "color conversion is out of bounds" 32 | } 33 | } 34 | 35 | impl fmt::Display for OutOfBounds { 36 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 37 | write!(fmt, "color conversion is out of bounds") 38 | } 39 | } 40 | 41 | /// A trait for fallible conversion of one color from another. 42 | /// 43 | /// `U: TryFromColor` is implemented for every type `U: FromColorUnclamped + Clamp`. 44 | /// 45 | /// See [`FromColor`](crate::convert::FromColor) for a lossy version of this trait. 46 | /// See [`FromColorUnclamped`](crate::convert::FromColorUnclamped) for a lossless version. 47 | /// 48 | /// See the [`convert`](crate::convert) module for how to implement `FromColorUnclamped` for 49 | /// custom colors. 50 | pub trait TryFromColor: Sized { 51 | /// Convert from T, returning ok if the color is inside of its defined 52 | /// range, otherwise an `OutOfBounds` error is returned which contains 53 | /// the unclamped color. 54 | /// 55 | ///``` 56 | /// use palette::convert::TryFromColor; 57 | /// use palette::{Hsl, Srgb}; 58 | /// 59 | /// let rgb = match Srgb::try_from_color(Hsl::new(150.0, 1.0, 1.1)) { 60 | /// Ok(color) => color, 61 | /// Err(err) => { 62 | /// println!("Color is out of bounds"); 63 | /// err.color() 64 | /// } 65 | /// }; 66 | /// ``` 67 | fn try_from_color(t: T) -> Result>; 68 | } 69 | 70 | impl TryFromColor for U 71 | where 72 | U: FromColorUnclamped + IsWithinBounds, 73 | { 74 | #[inline] 75 | fn try_from_color(t: T) -> Result> { 76 | let this = Self::from_color_unclamped(t); 77 | if this.is_within_bounds() { 78 | Ok(this) 79 | } else { 80 | Err(OutOfBounds::new(this)) 81 | } 82 | } 83 | } 84 | 85 | /// A trait for fallible conversion of a color into another. 86 | /// 87 | /// `U: TryIntoColor` is implemented for every type `T: TryFromColor`. 88 | /// 89 | /// See [`TryFromColor`](crate::convert::TryFromColor) for more details. 90 | pub trait TryIntoColor: Sized { 91 | /// Convert into T, returning ok if the color is inside of its defined 92 | /// range, otherwise an `OutOfBounds` error is returned which contains 93 | /// the unclamped color. 94 | /// 95 | ///``` 96 | /// use palette::convert::TryIntoColor; 97 | /// use palette::{Hsl, Srgb}; 98 | /// 99 | /// let rgb: Srgb = match Hsl::new(150.0, 1.0, 1.1).try_into_color() { 100 | /// Ok(color) => color, 101 | /// Err(err) => { 102 | /// println!("Color is out of bounds"); 103 | /// err.color() 104 | /// } 105 | /// }; 106 | /// ``` 107 | fn try_into_color(self) -> Result>; 108 | } 109 | 110 | impl TryIntoColor for T 111 | where 112 | U: TryFromColor, 113 | { 114 | #[inline] 115 | fn try_into_color(self) -> Result> { 116 | U::try_from_color(self) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /palette/src/encoding.rs: -------------------------------------------------------------------------------- 1 | //! Number and color encoding traits, types and standards. 2 | //! 3 | //! Some color spaces, particularly RGB, may be encoded in more than one way and 4 | //! may have more than one standard. These encodings and standards are 5 | //! represented as type parameters in Palette, as a form of type branding, to 6 | //! prevent accidental mixups. 7 | 8 | pub use self::adobe::AdobeRgb; 9 | #[allow(deprecated)] 10 | pub use self::gamma::{F2p2, Gamma}; 11 | pub use self::linear::Linear; 12 | pub use self::p3::{DciP3, DciP3Plus, DisplayP3, P3Gamma}; 13 | pub use self::prophoto::ProPhotoRgb; 14 | pub use self::rec_standards::{Rec2020, Rec709, RecOetf}; 15 | pub use self::srgb::Srgb; 16 | 17 | pub mod adobe; 18 | #[deprecated( 19 | since = "0.7.7", 20 | note = "`Gamma`, `GammaFn` and `F2p2` are error prone and incorrectly implemented. See `palette::encoding` for possible alternatives or implement `palette::encoding::FromLinear` and `palette::encoding::IntoLinear` for a custom type." 21 | )] 22 | pub mod gamma; 23 | pub mod linear; 24 | pub mod p3; 25 | pub mod prophoto; 26 | pub mod rec_standards; 27 | pub mod srgb; 28 | 29 | mod lut; 30 | 31 | /// A transfer function from linear space. 32 | pub trait FromLinear { 33 | /// Convert the color component `linear` from linear space. 34 | #[must_use] 35 | fn from_linear(linear: L) -> E; 36 | } 37 | 38 | /// A transfer function to linear space. 39 | pub trait IntoLinear { 40 | /// Convert the color component `encoded` into linear space. 41 | #[must_use] 42 | fn into_linear(encoded: E) -> L; 43 | } 44 | -------------------------------------------------------------------------------- /palette/src/encoding/gamma.rs: -------------------------------------------------------------------------------- 1 | //! Gamma encoding. 2 | 3 | use core::{marker::PhantomData, ops::Div}; 4 | 5 | use crate::{ 6 | luma::LumaStandard, 7 | num::{One, Powf, Real}, 8 | rgb::{RgbSpace, RgbStandard}, 9 | }; 10 | 11 | use super::{FromLinear, IntoLinear}; 12 | 13 | /// Gamma encoding. 14 | /// 15 | /// Gamma encoding or gamma correction is used to transform the intensity 16 | /// values to either match a non-linear display, like CRT, or to prevent 17 | /// banding among the darker colors. `GammaRgb` represents a gamma corrected 18 | /// RGB color, where the intensities are encoded using the following power-law 19 | /// expression: _V γ_ (where _V_ is the intensity value an _γ_ is the 20 | /// encoding gamma). 21 | /// 22 | /// The gamma value is stored as a simple type that represents an `f32` 23 | /// constant. 24 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 25 | pub struct Gamma(PhantomData<(S, N)>); 26 | 27 | impl RgbStandard for Gamma 28 | where 29 | Sp: RgbSpace, 30 | N: Number, 31 | { 32 | type Space = Sp; 33 | type TransferFn = GammaFn; 34 | } 35 | 36 | impl LumaStandard for Gamma 37 | where 38 | N: Number, 39 | { 40 | type WhitePoint = Wp; 41 | type TransferFn = GammaFn; 42 | } 43 | 44 | /// The transfer function for gamma encoded colors. 45 | /// 46 | /// Conversion is performed using a single `powf(x, gamma)` and `powf(x, 1.0 / 47 | /// gamma)` call, for from and into linear respectively. This makes 48 | /// `GammaFn` usable as a slightly less expensive approximation of the 49 | /// [`Srgb`][super::Srgb] transfer function. 50 | /// 51 | /// The gamma value is stored as a simple type that represents an `f32` 52 | /// constant. 53 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 54 | pub struct GammaFn(PhantomData); 55 | 56 | impl IntoLinear for GammaFn 57 | where 58 | T: Real + One + Powf + Div, 59 | N: Number, 60 | { 61 | #[inline] 62 | fn into_linear(x: T) -> T { 63 | x.powf(T::one() / T::from_f64(N::VALUE)) 64 | } 65 | } 66 | 67 | impl FromLinear for GammaFn 68 | where 69 | T: Real + Powf, 70 | N: Number, 71 | { 72 | #[inline] 73 | fn from_linear(x: T) -> T { 74 | x.powf(T::from_f64(N::VALUE)) 75 | } 76 | } 77 | 78 | /// A type level float constant. 79 | pub trait Number: 'static { 80 | /// The represented number. 81 | const VALUE: f64; 82 | } 83 | 84 | /// Represents `2.2f64`. 85 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 86 | pub struct F2p2; 87 | 88 | impl Number for F2p2 { 89 | const VALUE: f64 = 2.2; 90 | } 91 | -------------------------------------------------------------------------------- /palette/src/encoding/linear.rs: -------------------------------------------------------------------------------- 1 | //! Linear encoding 2 | 3 | use core::marker::PhantomData; 4 | 5 | use crate::{ 6 | luma::LumaStandard, 7 | rgb::{RgbSpace, RgbStandard}, 8 | }; 9 | 10 | use super::{FromLinear, IntoLinear}; 11 | 12 | /// A generic standard with linear components. 13 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 14 | pub struct Linear(PhantomData); 15 | 16 | impl RgbStandard for Linear 17 | where 18 | Sp: RgbSpace, 19 | { 20 | type Space = Sp; 21 | type TransferFn = LinearFn; 22 | } 23 | 24 | impl LumaStandard for Linear { 25 | type WhitePoint = Wp; 26 | type TransferFn = LinearFn; 27 | } 28 | 29 | /// Linear color component encoding. 30 | /// 31 | /// Converting anything from linear to linear space is a no-op and constant 32 | /// time. This is a useful property in generic code, where the transfer 33 | /// functions may be unknown. 34 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 35 | pub struct LinearFn; 36 | 37 | impl IntoLinear for LinearFn { 38 | #[inline(always)] 39 | fn into_linear(x: T) -> T { 40 | x 41 | } 42 | } 43 | 44 | impl FromLinear for LinearFn { 45 | #[inline(always)] 46 | fn from_linear(x: T) -> T { 47 | x 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /palette/src/encoding/lut.rs: -------------------------------------------------------------------------------- 1 | mod codegen; 2 | 3 | pub use crate::encoding::lut::codegen::*; 4 | 5 | const MAX_FLOAT_BITS: u32 = 0x3f7fffff; // 1.0 - f32::EPSILON 6 | 7 | // SAFETY: Only use this macro if `input` is clamped between `min_float` and `max_float`. 8 | macro_rules! unsafe_linear_float_to_encoded_uint { 9 | ($enc:ty, $lut:ty, $input:ident, $min_float_bits:ident, $table:ident, $bit_width:expr, $man_index_width:expr) => {{ 10 | let input_bits = $input.to_bits(); 11 | #[cfg(test)] 12 | { 13 | debug_assert!(($min_float_bits..=MAX_FLOAT_BITS).contains(&$input.to_bits())); 14 | } 15 | let entry = { 16 | let i = ((input_bits - $min_float_bits) >> (23 - $man_index_width)) as usize; 17 | #[cfg(test)] 18 | { 19 | debug_assert!($table.get(i).is_some()); 20 | } 21 | *$table.get_unchecked(i) 22 | }; 23 | 24 | let bias = (entry >> (2 * $bit_width)) << ($bit_width + 1); 25 | let scale = entry & ((1 << (2 * $bit_width)) - 1); 26 | let t = 27 | (input_bits as $lut >> (23 - $man_index_width - $bit_width)) & ((1 << $bit_width) - 1); 28 | let res = (bias + scale * t) >> (2 * $bit_width); 29 | #[cfg(test)] 30 | { 31 | debug_assert!(res < ((<$enc>::MAX as $lut) + 1), "{}", res); 32 | } 33 | res as $enc 34 | }}; 35 | } 36 | 37 | #[inline] 38 | pub fn linear_f32_to_encoded_u8(linear: f32, min_float_bits: u32, table: &[u32]) -> u8 { 39 | let min_float = f32::from_bits(min_float_bits); 40 | let max_float = f32::from_bits(MAX_FLOAT_BITS); 41 | 42 | let mut input = linear; 43 | if input.partial_cmp(&min_float) != Some(core::cmp::Ordering::Greater) { 44 | input = min_float; 45 | } else if input > max_float { 46 | input = max_float; 47 | } 48 | 49 | unsafe { unsafe_linear_float_to_encoded_uint!(u8, u32, input, min_float_bits, table, 8, 3) } 50 | } 51 | 52 | #[cfg(feature = "gamma_lut_u16")] 53 | #[inline] 54 | pub fn linear_f32_to_encoded_u16_with_linear_scale( 55 | linear: f32, 56 | linear_scale: f32, 57 | min_float_bits: u32, 58 | table: &[u64], 59 | ) -> u16 { 60 | let min_float = f32::from_bits(min_float_bits); 61 | let max_float = f32::from_bits(MAX_FLOAT_BITS); 62 | 63 | let mut input = linear; 64 | if input.partial_cmp(&0.0) != Some(core::cmp::Ordering::Greater) { 65 | input = 0.0; 66 | } else if input > max_float { 67 | input = max_float; 68 | } 69 | 70 | if input < min_float { 71 | return ((linear_scale * input + 8388608.0).to_bits() & 65535) as u16; 72 | } 73 | 74 | unsafe { unsafe_linear_float_to_encoded_uint!(u16, u64, input, min_float_bits, table, 16, 7) } 75 | } 76 | -------------------------------------------------------------------------------- /palette/src/lms.rs: -------------------------------------------------------------------------------- 1 | //! Types for the LMS color space. 2 | 3 | #[allow(clippy::module_inception)] 4 | mod lms; 5 | 6 | pub mod matrix; 7 | 8 | use crate::Alpha; 9 | 10 | pub use self::lms::*; 11 | 12 | /// LMS that uses the von Kries matrix. 13 | pub type VonKriesLms = Lms, T>; 14 | 15 | /// LMSA that uses the von Kries matrix. 16 | pub type VonKriesLmsa = Alpha, T>, T>; 17 | 18 | /// LMS that uses the Bradford matrix. 19 | pub type BradfordLms = Lms, T>; 20 | 21 | /// LMSA that uses the Bradford matrix. 22 | pub type BradfordLmsa = Alpha, T>, T>; 23 | -------------------------------------------------------------------------------- /palette/src/luma.rs: -------------------------------------------------------------------------------- 1 | //! Types for luma and luminance (grayscale) values. 2 | 3 | pub mod channels; 4 | #[allow(clippy::module_inception)] 5 | mod luma; 6 | #[allow(deprecated)] 7 | use crate::encoding::{Gamma, Linear, Srgb}; 8 | use crate::white_point::D65; 9 | 10 | pub use self::luma::{Iter, Luma, Lumaa}; 11 | 12 | /// sRGB encoded luminance. 13 | pub type SrgbLuma = Luma; 14 | /// sRGB encoded luminance with an alpha component. 15 | pub type SrgbLumaa = Lumaa; 16 | 17 | /// Linear luminance. 18 | #[doc(alias = "linear")] 19 | pub type LinLuma = Luma, T>; 20 | /// Linear luminance with an alpha component. 21 | #[doc(alias = "linear")] 22 | pub type LinLumaa = Lumaa, T>; 23 | 24 | /// Gamma 2.2 encoded luminance. 25 | #[deprecated( 26 | since = "0.7.7", 27 | note = "`Gamma`, `GammaFn` and `F2p2` are error prone and incorrectly implemented. See `palette::encoding` for possible alternatives or implement `palette::encoding::FromLinear` and `palette::encoding::IntoLinear` for a custom type." 28 | )] 29 | #[allow(deprecated)] 30 | pub type GammaLuma = Luma, T>; 31 | /// Gamma 2.2 encoded luminance with an alpha component. 32 | #[deprecated( 33 | since = "0.7.7", 34 | note = "`Gamma`, `GammaFn` and `F2p2` are error prone and incorrectly implemented. See `palette::encoding` for possible alternatives or implement `palette::encoding::FromLinear` and `palette::encoding::IntoLinear` for a custom type." 35 | )] 36 | #[allow(deprecated)] 37 | pub type GammaLumaa = Lumaa, T>; 38 | 39 | /// A white point and a transfer function. 40 | pub trait LumaStandard { 41 | /// The white point of the color space. 42 | type WhitePoint; 43 | 44 | /// The transfer function for the luminance component. 45 | type TransferFn; 46 | } 47 | 48 | impl LumaStandard for (Wp, Tf) { 49 | type WhitePoint = Wp; 50 | type TransferFn = Tf; 51 | } 52 | 53 | /// A packed representation of Luma+Alpha in LA order. 54 | pub type PackedLumaa

= crate::cast::Packed; 55 | 56 | /// A packed representation of Luma+Alpha in AL order. 57 | pub type PackedAluma

= crate::cast::Packed; 58 | -------------------------------------------------------------------------------- /palette/src/luma/channels.rs: -------------------------------------------------------------------------------- 1 | //! Channel orders for packed Luma types. 2 | 3 | use crate::{cast::ComponentOrder, luma}; 4 | 5 | /// Luma+Alpha color packed in LA order. 6 | /// 7 | /// See [Packed](crate::cast::Packed) for more details. 8 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 9 | pub struct La; 10 | 11 | impl ComponentOrder, [T; 2]> for La { 12 | #[inline] 13 | fn pack(color: luma::Lumaa) -> [T; 2] { 14 | color.into() 15 | } 16 | 17 | #[inline] 18 | fn unpack(packed: [T; 2]) -> luma::Lumaa { 19 | packed.into() 20 | } 21 | } 22 | 23 | /// Luma+Alpha color packed in AL order. 24 | /// 25 | /// See [Packed](crate::cast::Packed) for more details. 26 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 27 | pub struct Al; 28 | 29 | impl ComponentOrder, [T; 2]> for Al { 30 | #[inline] 31 | fn pack(color: luma::Lumaa) -> [T; 2] { 32 | let [luma, alpha]: [T; 2] = color.into(); 33 | [alpha, luma] 34 | } 35 | 36 | #[inline] 37 | fn unpack(packed: [T; 2]) -> luma::Lumaa { 38 | let [alpha, luma] = packed; 39 | luma::Lumaa::new(luma, alpha) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /palette/src/macros.rs: -------------------------------------------------------------------------------- 1 | // From https://stackoverflow.com/questions/60187436/rust-macro-repetition-with-plus 2 | macro_rules! strip_plus { 3 | (+ $($rest: tt)*) => { 4 | $($rest)* 5 | } 6 | } 7 | 8 | #[macro_use] 9 | mod arithmetics; 10 | #[macro_use] 11 | mod casting; 12 | #[macro_use] 13 | mod mix; 14 | #[macro_use] 15 | mod lighten_saturate; 16 | #[macro_use] 17 | mod equality; 18 | #[macro_use] 19 | mod blend; 20 | #[macro_use] 21 | mod lazy_select; 22 | #[macro_use] 23 | mod simd; 24 | #[macro_use] 25 | mod clamp; 26 | #[macro_use] 27 | mod convert; 28 | #[macro_use] 29 | mod color_difference; 30 | #[macro_use] 31 | mod struct_of_arrays; 32 | #[macro_use] 33 | mod reference_component; 34 | #[macro_use] 35 | mod copy_clone; 36 | #[macro_use] 37 | mod hue; 38 | #[macro_use] 39 | mod random; 40 | #[macro_use] 41 | mod color_theory; 42 | -------------------------------------------------------------------------------- /palette/src/macros/blend.rs: -------------------------------------------------------------------------------- 1 | macro_rules! impl_premultiply { 2 | ($ty: ident {$($component: ident),+} $(phantom: $phantom: ident)? $(where $($where: tt)+)?) => { 3 | impl_premultiply!($ty<> {$($component),+} $(phantom: $phantom)? $(where $($where)+)?); 4 | }; 5 | ($ty: ident <$($ty_param: ident),*> {$($component: ident),+} $(phantom: $phantom: ident)? $(where $($where: tt)+)?) => { 6 | impl<$($ty_param,)* T> crate::blend::Premultiply for $ty<$($ty_param,)* T> 7 | where 8 | T: crate::num::Real 9 | + crate::stimulus::Stimulus 10 | + crate::num::Zero 11 | + crate::num::IsValidDivisor 12 | + core::ops::Mul 13 | + core::ops::Div 14 | + Clone, 15 | T::Mask: crate::bool_mask::LazySelect + Clone, 16 | $($($where)+)? 17 | { 18 | type Scalar = T; 19 | 20 | #[inline] 21 | fn premultiply(self, alpha: T) -> crate::blend::PreAlpha { 22 | crate::blend::PreAlpha { 23 | color: self * alpha.clone(), 24 | alpha 25 | } 26 | } 27 | 28 | #[inline] 29 | fn unpremultiply(premultiplied: crate::blend::PreAlpha) -> (Self, T) { 30 | let crate::blend::PreAlpha { 31 | color: $ty { $($component,)+ .. }, 32 | alpha, 33 | } = premultiplied; 34 | 35 | let is_valid_divisor = alpha.is_valid_divisor(); 36 | 37 | let color = Self { 38 | $( 39 | $component: lazy_select! { 40 | if is_valid_divisor.clone() => $component / alpha.clone(), 41 | else => T::zero() 42 | }, 43 | )+ 44 | $($phantom: core::marker::PhantomData,)? 45 | }; 46 | 47 | (color, alpha) 48 | } 49 | } 50 | 51 | impl<$($ty_param,)* T> From> for $ty<$($ty_param,)* T> 52 | where 53 | Self: crate::blend::Premultiply, 54 | { 55 | fn from(premultiplied: crate::blend::PreAlpha) -> Self { 56 | use crate::blend::Premultiply; 57 | 58 | Self::unpremultiply(premultiplied).0 59 | } 60 | } 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /palette/src/macros/color_difference.rs: -------------------------------------------------------------------------------- 1 | macro_rules! impl_euclidean_distance { 2 | ( 3 | $ty: ident 4 | {$($component: ident),+} 5 | $(where $($where: tt)+)? 6 | ) => { 7 | // add empty generics brackets 8 | impl_euclidean_distance!($ty<> {$($component),+} $(where $($where)+)?); 9 | }; 10 | ( 11 | $ty: ident <$($ty_param: ident),*> 12 | {$($component: ident),+} 13 | $(where $($where: tt)+)? 14 | ) => { 15 | impl<$($ty_param,)* T> crate::color_difference::EuclideanDistance for $ty<$($ty_param,)* T> 16 | where 17 | T: crate::num::Real + core::ops::Sub + core::ops::Add + core::ops::Mul + Clone, 18 | $($($where)+)? 19 | { 20 | type Scalar = T; 21 | 22 | #[inline] 23 | fn distance_squared(self, other: Self) -> Self::Scalar { 24 | let difference = self - other; 25 | let difference_squared = difference.clone() * difference; 26 | 27 | strip_plus!($(+ difference_squared.$component)+) 28 | } 29 | } 30 | }; 31 | } 32 | 33 | macro_rules! impl_hyab { 34 | ( 35 | $ty: ident 36 | {$($components: tt)+} 37 | $(where $($where: tt)+)? 38 | ) => { 39 | // add empty generics brackets 40 | impl_hyab!($ty<> {$($components)+} $(where $($where)+)?); 41 | }; 42 | ( 43 | $ty: ident <$($ty_param: ident),*> 44 | {lightness: $lightness:ident, chroma1: $chroma1:ident, chroma2: $chroma2:ident $(,)? } 45 | $(where $($where: tt)+)? 46 | ) => { 47 | impl<$($ty_param,)* T> crate::color_difference::HyAb for $ty<$($ty_param,)* T> 48 | where 49 | T: crate::num::Real + crate::num::Abs + crate::num::Sqrt + core::ops::Sub + core::ops::Add + core::ops::Mul + Clone, 50 | $($($where)+)? 51 | { 52 | type Scalar = T; 53 | 54 | #[inline] 55 | fn hybrid_distance(self, other: Self) -> Self::Scalar { 56 | let lightness = self.$lightness - other.$lightness; 57 | let chroma1 = self.$chroma1 - other.$chroma1; 58 | let chroma2 = self.$chroma2 - other.$chroma2; 59 | 60 | lightness.abs() + (chroma1.clone() * chroma1 + chroma2.clone() * chroma2).sqrt() 61 | } 62 | } 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /palette/src/macros/convert.rs: -------------------------------------------------------------------------------- 1 | /// Check that traits for converting to and from XYZ have been implemented. 2 | #[cfg(test)] 3 | macro_rules! test_convert_into_from_xyz { 4 | ($ty:ty) => { 5 | #[test] 6 | fn convert_from_xyz() { 7 | use crate::FromColor; 8 | 9 | let _: $ty = <$ty>::from_color(crate::Xyz::::default()); 10 | } 11 | 12 | #[test] 13 | fn convert_into_xyz() { 14 | use crate::FromColor; 15 | 16 | let _: crate::Xyz = crate::Xyz::from_color(<$ty>::default()); 17 | } 18 | }; 19 | } 20 | 21 | macro_rules! impl_tuple_conversion { 22 | ($ty: ident as ($($component_ty: ident),+)) => { 23 | impl_tuple_conversion!($ty<> as ($($component_ty),+)); 24 | }; 25 | ($ty: ident <$($ty_param: ident),*> as ($($component_ty: ident),+)) => { 26 | impl<$($ty_param,)* T> From<($($component_ty,)+)> for $ty<$($ty_param,)* T> { 27 | fn from(components: ($($component_ty,)+)) -> Self { 28 | Self::from_components(components) 29 | } 30 | } 31 | 32 | impl<$($ty_param,)* T> From<$ty<$($ty_param,)* T>> for ($($component_ty,)+) { 33 | fn from(color: $ty<$($ty_param,)* T>) -> ($($component_ty,)+) { 34 | color.into_components() 35 | } 36 | } 37 | 38 | impl<$($ty_param,)* T, A> From<($($component_ty,)+ A)> for crate::Alpha<$ty<$($ty_param,)* T>, A> { 39 | fn from(components: ($($component_ty,)+ A)) -> Self { 40 | Self::from_components(components) 41 | } 42 | } 43 | 44 | impl<$($ty_param,)* T, A> From, A>> for ($($component_ty,)+ A) { 45 | fn from(color: crate::Alpha<$ty<$($ty_param,)* T>, A>) -> ($($component_ty,)+ A) { 46 | color.into_components() 47 | } 48 | } 49 | }; 50 | } 51 | 52 | macro_rules! __replace_generic_hue { 53 | (H, $hue_ty: ident) => {$hue_ty}; 54 | ($other: ident, $hue_ty: ident) => {$other}; 55 | } 56 | 57 | macro_rules! impl_tuple_conversion_hue { 58 | ($ty: ident as ($($component_ty: ident),+), $hue_ty: ident) => { 59 | impl_tuple_conversion_hue!($ty<> as ($($component_ty),+), $hue_ty); 60 | }; 61 | ($ty: ident <$($ty_param: ident),*> as ($($component_ty: ident),+), $hue_ty: ident) => { 62 | impl<$($ty_param,)* T, H: Into<$hue_ty>> From<($($component_ty,)+)> for $ty<$($ty_param,)* T> { 63 | fn from(components: ($($component_ty,)+)) -> Self { 64 | Self::from_components(components) 65 | } 66 | } 67 | 68 | impl<$($ty_param,)* T> From<$ty<$($ty_param,)* T>> for ($(__replace_generic_hue!($component_ty, $hue_ty),)+) { 69 | fn from(color: $ty<$($ty_param,)* T>) -> ($(__replace_generic_hue!($component_ty, $hue_ty),)+) { 70 | color.into_components() 71 | } 72 | } 73 | 74 | impl<$($ty_param,)* T, H: Into<$hue_ty>, A> From<($($component_ty,)+ A)> for crate::Alpha<$ty<$($ty_param,)* T>, A> { 75 | fn from(components: ($($component_ty,)+ A)) -> Self { 76 | Self::from_components(components) 77 | } 78 | } 79 | 80 | impl<$($ty_param,)* T, A> From, A>> for ($(__replace_generic_hue!($component_ty, $hue_ty),)+ A) { 81 | fn from(color: crate::Alpha<$ty<$($ty_param,)* T>, A>) -> ($(__replace_generic_hue!($component_ty, $hue_ty),)+ A) { 82 | color.into_components() 83 | } 84 | } 85 | }; 86 | } 87 | -------------------------------------------------------------------------------- /palette/src/macros/copy_clone.rs: -------------------------------------------------------------------------------- 1 | macro_rules! impl_copy_clone { 2 | ( $self_ty: ident , [$($element: ident),+] $(, $phantom: ident)?) => { 3 | impl_copy_clone!($self_ty<>, [$($element),+] $(, $phantom)?); 4 | }; 5 | ( $self_ty: ident < $($phantom_ty: ident)? > , [$($element: ident),+] $(, $phantom: ident)?) => { 6 | impl<$($phantom_ty,)? T> Copy for $self_ty<$($phantom_ty,)? T> where T: Copy {} 7 | 8 | impl<$($phantom_ty,)? T> Clone for $self_ty<$($phantom_ty,)? T> 9 | where 10 | T: Clone, 11 | { 12 | fn clone(&self) -> $self_ty<$($phantom_ty,)? T> { 13 | $self_ty { 14 | $($element: self.$element.clone(),)* 15 | $($phantom: core::marker::PhantomData,)? 16 | } 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /palette/src/macros/hue.rs: -------------------------------------------------------------------------------- 1 | macro_rules! impl_hue_ops { 2 | ( $self_ty: ident , $hue_ty: ident) => { 3 | impl_hue_ops!($self_ty<>, $hue_ty); 4 | }; 5 | ( $self_ty: ident < $($ty_param: ident),* > , $hue_ty: ident) => { 6 | impl<$($ty_param,)* T> crate::GetHue for $self_ty<$($ty_param,)* T> 7 | where 8 | T: Clone, 9 | { 10 | type Hue = $hue_ty; 11 | 12 | #[inline] 13 | fn get_hue(&self) -> $hue_ty { 14 | self.hue.clone() 15 | } 16 | } 17 | 18 | impl<$($ty_param,)* T, H> crate::WithHue for $self_ty<$($ty_param,)* T> 19 | where 20 | H: Into<$hue_ty>, 21 | { 22 | #[inline] 23 | fn with_hue(mut self, hue: H) -> Self { 24 | self.hue = hue.into(); 25 | self 26 | } 27 | } 28 | 29 | impl<$($ty_param,)* T, H> crate::SetHue for $self_ty<$($ty_param,)* T> 30 | where 31 | H: Into<$hue_ty>, 32 | { 33 | #[inline] 34 | fn set_hue(&mut self, hue: H) { 35 | self.hue = hue.into(); 36 | } 37 | } 38 | 39 | impl<$($ty_param,)* T> crate::ShiftHue for $self_ty<$($ty_param,)* T> 40 | where 41 | T: core::ops::Add, 42 | { 43 | type Scalar = T; 44 | 45 | #[inline] 46 | fn shift_hue(mut self, amount: Self::Scalar) -> Self { 47 | self.hue = self.hue + amount; 48 | self 49 | } 50 | } 51 | 52 | impl<$($ty_param,)* T> crate::ShiftHueAssign for $self_ty<$($ty_param,)* T> 53 | where 54 | T: core::ops::AddAssign, 55 | { 56 | type Scalar = T; 57 | 58 | #[inline] 59 | fn shift_hue_assign(&mut self, amount: Self::Scalar) { 60 | self.hue += amount; 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /palette/src/macros/lazy_select.rs: -------------------------------------------------------------------------------- 1 | /// Chains calls to `LazySelect::lazy_select` to mimic an if-else chain. 2 | /// 3 | /// ```ignore 4 | /// let result = lazy_select! { 5 | /// if predicate1 => result1, 6 | /// if predicate2 => result2, 7 | /// else => result3, 8 | /// }; 9 | /// ``` 10 | macro_rules! lazy_select { 11 | ( if $if_pred:expr => $if_body:expr, $(if $else_if_pred:expr => $else_if_body:expr,)* else => $else_body:expr $(,)?) => { 12 | crate::bool_mask::LazySelect::lazy_select( 13 | $if_pred, 14 | || $if_body, 15 | || lazy_select!($(if $else_if_pred => $else_if_body,)* else => $else_body) 16 | ) 17 | }; 18 | (else => $else_body:expr) => { 19 | $else_body 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /palette/src/macros/mix.rs: -------------------------------------------------------------------------------- 1 | macro_rules! impl_mix { 2 | ($ty: ident $(where $($where: tt)+)?) => { 3 | impl_mix!($ty<> $(where $($where)+)?); 4 | }; 5 | ($ty: ident <$($ty_param: ident),*> $(where $($where: tt)+)?) => { 6 | impl<$($ty_param,)* T> crate::Mix for $ty<$($ty_param,)* T> 7 | where 8 | T: crate::num::Real 9 | + crate::num::Zero 10 | + crate::num::One 11 | + crate::num::Arithmetics 12 | + crate::num::Clamp 13 | + Clone, 14 | $($($where)+)? 15 | { 16 | type Scalar = T; 17 | 18 | #[inline] 19 | fn mix(self, other: Self, factor: T) -> Self { 20 | let factor = crate::clamp(factor, T::zero(), T::one()); 21 | self.clone() + (other - self) * factor 22 | } 23 | } 24 | 25 | impl<$($ty_param,)* T> crate::MixAssign for $ty<$($ty_param,)* T> 26 | where 27 | T: crate::num::Real 28 | + crate::num::Zero 29 | + crate::num::One 30 | + core::ops::AddAssign 31 | + crate::num::Arithmetics 32 | + crate::num::Clamp 33 | + Clone, 34 | $($($where)+)? 35 | { 36 | type Scalar = T; 37 | 38 | #[inline] 39 | fn mix_assign(&mut self, other: Self, factor: T) { 40 | let factor = crate::clamp(factor, T::zero(), T::one()); 41 | *self += (other - self.clone()) * factor; 42 | } 43 | } 44 | }; 45 | } 46 | 47 | macro_rules! impl_mix_hue { 48 | ($ty: ident {$($other_field: ident),*} $(phantom: $phantom: ident)?) => { 49 | impl_mix_hue!($ty<> {$($other_field),*} $(phantom: $phantom)?); 50 | }; 51 | ($ty: ident <$($ty_param: ident),*> {$($other_field: ident),*} $(phantom: $phantom: ident)?) => { 52 | impl<$($ty_param,)* T> crate::Mix for $ty<$($ty_param,)* T> 53 | where 54 | T: crate::angle::RealAngle 55 | + crate::angle::SignedAngle 56 | + crate::num::Zero 57 | + crate::num::One 58 | + crate::num::Clamp 59 | + crate::num::Arithmetics 60 | + Clone, 61 | { 62 | type Scalar = T; 63 | 64 | #[inline] 65 | fn mix(self, other: Self, factor: T) -> Self { 66 | let factor = crate::clamp(factor, T::zero(), T::one()); 67 | let hue = (other.hue - self.hue.clone()).into_degrees(); 68 | $( 69 | let $other_field = other.$other_field - &self.$other_field; 70 | )* 71 | 72 | $ty { 73 | $( 74 | $other_field: self.$other_field + $other_field * &factor, 75 | )* 76 | hue: self.hue + hue * factor, 77 | $($phantom: PhantomData)? 78 | } 79 | } 80 | } 81 | 82 | impl<$($ty_param,)* T> crate::MixAssign for $ty<$($ty_param,)* T> 83 | where 84 | T: crate::angle::RealAngle 85 | + crate::angle::SignedAngle 86 | + crate::num::Zero 87 | + crate::num::One 88 | + crate::num::Clamp 89 | + core::ops::AddAssign 90 | + crate::num::Arithmetics 91 | + Clone, 92 | { 93 | type Scalar = T; 94 | 95 | #[inline] 96 | fn mix_assign(&mut self, other: Self, factor: T) { 97 | let factor = crate::clamp(factor, T::zero(), T::one()); 98 | let hue = (other.hue - self.hue.clone()).into_degrees(); 99 | $( 100 | let $other_field = other.$other_field - &self.$other_field; 101 | )* 102 | 103 | $( 104 | self.$other_field += $other_field * &factor; 105 | )* 106 | self.hue += hue * factor; 107 | } 108 | } 109 | }; 110 | } 111 | -------------------------------------------------------------------------------- /palette/src/named.rs: -------------------------------------------------------------------------------- 1 | //! A collection of named color constants. Can be toggled with the `"named"` 2 | //! Cargo features. 3 | //! 4 | //! They are taken from the [SVG keyword 5 | //! colors](https://www.w3.org/TR/SVG11/types.html#ColorKeywords). These are 6 | //! also part of the CSS3 standard. 7 | //! 8 | //! ``` 9 | //! use palette::Srgb; 10 | //! use palette::named; 11 | //! 12 | //! //From constant 13 | //! let from_const = named::OLIVE; 14 | //! 15 | //! //From name string 16 | //! let from_str = named::from_str("olive").expect("unknown color"); 17 | //! 18 | //! assert_eq!(from_const, from_str); 19 | //! ``` 20 | 21 | use core::{fmt, iter::FusedIterator}; 22 | 23 | pub use codegen::*; 24 | 25 | mod codegen; 26 | 27 | /// Get an SVG/CSS3 color by name. 28 | /// 29 | /// The names are the same as the constants, but lower case. 30 | pub fn from_str(name: &str) -> Option> { 31 | COLORS.get(name).copied() 32 | } 33 | 34 | /// Get an iterator over all SVG/CSS3 names and colors in arbitrary order. 35 | /// 36 | /// ``` 37 | /// use palette::Srgb; 38 | /// 39 | /// let red = Srgb::new(255u8, 0, 0); 40 | /// 41 | /// let red_entry = palette::named::entries().find(|(name, color)| *color == red); 42 | /// assert_eq!(red_entry, Some(("red", red))); 43 | /// ``` 44 | pub fn entries() -> Entries { 45 | Entries { 46 | iter: COLORS.entries(), 47 | } 48 | } 49 | 50 | /// Get an iterator over all SVG/CSS3 color names in arbitrary order. 51 | pub fn names() -> Names { 52 | Names { 53 | iter: COLORS.keys(), 54 | } 55 | } 56 | 57 | /// Get an iterator over all SVG/CSS3 color values in arbitrary order. 58 | pub fn colors() -> Colors { 59 | Colors { 60 | iter: COLORS.values(), 61 | } 62 | } 63 | 64 | /// An iterator over SVG/CSS3 color entries. 65 | #[derive(Clone)] 66 | pub struct Entries { 67 | iter: phf::map::Entries<'static, &'static str, crate::Srgb>, 68 | } 69 | 70 | impl fmt::Debug for Entries { 71 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 72 | self.iter.fmt(f) 73 | } 74 | } 75 | 76 | impl Iterator for Entries { 77 | type Item = (&'static str, crate::Srgb); 78 | 79 | fn next(&mut self) -> Option { 80 | self.iter.next().map(|(&name, &color)| (name, color)) 81 | } 82 | 83 | fn size_hint(&self) -> (usize, Option) { 84 | self.iter.size_hint() 85 | } 86 | } 87 | 88 | impl DoubleEndedIterator for Entries { 89 | fn next_back(&mut self) -> Option { 90 | self.iter.next_back().map(|(&name, &color)| (name, color)) 91 | } 92 | } 93 | 94 | impl ExactSizeIterator for Entries {} 95 | 96 | impl FusedIterator for Entries {} 97 | 98 | /// An iterator over SVG/CSS3 color names. 99 | #[derive(Clone)] 100 | pub struct Names { 101 | iter: phf::map::Keys<'static, &'static str, crate::Srgb>, 102 | } 103 | 104 | impl fmt::Debug for Names { 105 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 106 | self.iter.fmt(f) 107 | } 108 | } 109 | 110 | impl Iterator for Names { 111 | type Item = &'static str; 112 | 113 | fn next(&mut self) -> Option { 114 | self.iter.next().copied() 115 | } 116 | 117 | fn size_hint(&self) -> (usize, Option) { 118 | self.iter.size_hint() 119 | } 120 | } 121 | 122 | impl DoubleEndedIterator for Names { 123 | fn next_back(&mut self) -> Option { 124 | self.iter.next_back().copied() 125 | } 126 | } 127 | 128 | impl ExactSizeIterator for Names {} 129 | 130 | impl FusedIterator for Names {} 131 | 132 | /// An iterator over SVG/CSS3 color values. 133 | #[derive(Clone)] 134 | pub struct Colors { 135 | iter: phf::map::Values<'static, &'static str, crate::Srgb>, 136 | } 137 | 138 | impl fmt::Debug for Colors { 139 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 140 | self.iter.fmt(f) 141 | } 142 | } 143 | 144 | impl Iterator for Colors { 145 | type Item = crate::Srgb; 146 | 147 | fn next(&mut self) -> Option { 148 | self.iter.next().copied() 149 | } 150 | 151 | fn size_hint(&self) -> (usize, Option) { 152 | self.iter.size_hint() 153 | } 154 | } 155 | 156 | impl DoubleEndedIterator for Colors { 157 | fn next_back(&mut self) -> Option { 158 | self.iter.next_back().copied() 159 | } 160 | } 161 | 162 | impl ExactSizeIterator for Colors {} 163 | 164 | impl FusedIterator for Colors {} 165 | -------------------------------------------------------------------------------- /palette/src/okhsl/alpha.rs: -------------------------------------------------------------------------------- 1 | use crate::hues::OklabHue; 2 | use crate::{angle::FromAngle, stimulus::FromStimulus, Alpha}; 3 | 4 | use super::Okhsl; 5 | 6 | /// Okhsl with an alpha component. 7 | pub type Okhsla = Alpha, T>; 8 | 9 | ///[`Okhsla`](crate::Okhsla) implementations. 10 | impl Alpha, A> { 11 | /// Create an `Okhsl` color with transparency. 12 | pub fn new>>(hue: H, saturation: T, lightness: T, alpha: A) -> Self { 13 | Alpha { 14 | color: Okhsl::new(hue, saturation, lightness), 15 | alpha, 16 | } 17 | } 18 | 19 | /// Create an `Okhsla` color. This is the same as `Okhsla::new` without the 20 | /// generic hue type. It's temporary until `const fn` supports traits. 21 | pub const fn new_const(hue: OklabHue, saturation: T, lightness: T, alpha: A) -> Self { 22 | Alpha { 23 | color: Okhsl::new_const(hue, saturation, lightness), 24 | alpha, 25 | } 26 | } 27 | 28 | /// Convert into another component type. 29 | pub fn into_format(self) -> Alpha, B> 30 | where 31 | U: FromStimulus + FromAngle, 32 | B: FromStimulus, 33 | { 34 | Alpha { 35 | color: self.color.into_format(), 36 | alpha: B::from_stimulus(self.alpha), 37 | } 38 | } 39 | 40 | /// Convert from another component type. 41 | pub fn from_format(color: Alpha, B>) -> Self 42 | where 43 | T: FromStimulus + FromAngle, 44 | A: FromStimulus, 45 | { 46 | color.into_format() 47 | } 48 | 49 | /// Convert to a `(hue, saturation, lightness, alpha)` tuple. 50 | pub fn into_components(self) -> (OklabHue, T, T, A) { 51 | ( 52 | self.color.hue, 53 | self.color.saturation, 54 | self.color.lightness, 55 | self.alpha, 56 | ) 57 | } 58 | 59 | /// Convert from a `(hue, saturation, lightness, alpha)` tuple. 60 | pub fn from_components>>( 61 | (hue, saturation, lightness, alpha): (H, T, T, A), 62 | ) -> Self { 63 | Self::new(hue, saturation, lightness, alpha) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /palette/src/okhsl/properties.rs: -------------------------------------------------------------------------------- 1 | use crate::{hues::OklabHueIter, white_point::D65}; 2 | 3 | use crate::{ 4 | bool_mask::LazySelect, 5 | num::{Arithmetics, PartialCmp, Real}, 6 | stimulus::Stimulus, 7 | FromColor, OklabHue, Xyz, 8 | }; 9 | 10 | use super::Okhsl; 11 | 12 | impl_is_within_bounds! { 13 | Okhsl { 14 | saturation => [Self::min_saturation(), Self::max_saturation()], 15 | lightness => [Self::min_lightness(), Self::max_lightness()] 16 | } 17 | where T: Stimulus 18 | } 19 | impl_clamp! { 20 | Okhsl { 21 | saturation => [Self::min_saturation(), Self::max_saturation()], 22 | lightness => [Self::min_lightness(), Self::max_lightness()] 23 | } 24 | other {hue} 25 | where T: Stimulus 26 | } 27 | 28 | impl_mix_hue!(Okhsl { 29 | saturation, 30 | lightness 31 | }); 32 | impl_lighten!(Okhsl increase {lightness => [Self::min_lightness(), Self::max_lightness()]} other {hue, saturation} where T: Stimulus); 33 | impl_saturate!(Okhsl increase {saturation => [Self::min_saturation(), Self::max_saturation()]} other {hue, lightness} where T: Stimulus); 34 | impl_hue_ops!(Okhsl, OklabHue); 35 | 36 | impl_color_add!(Okhsl, [hue, saturation, lightness]); 37 | impl_color_sub!(Okhsl, [hue, saturation, lightness]); 38 | 39 | impl_array_casts!(Okhsl, [T; 3]); 40 | impl_simd_array_conversion_hue!(Okhsl, [saturation, lightness]); 41 | impl_struct_of_array_traits_hue!(Okhsl, OklabHueIter, [saturation, lightness]); 42 | 43 | impl_eq_hue!(Okhsl, OklabHue, [hue, saturation, lightness]); 44 | 45 | #[allow(deprecated)] 46 | impl crate::RelativeContrast for Okhsl 47 | where 48 | T: Real + Arithmetics + PartialCmp, 49 | T::Mask: LazySelect, 50 | Xyz: FromColor, 51 | { 52 | type Scalar = T; 53 | 54 | #[inline] 55 | fn get_contrast_ratio(self, other: Self) -> T { 56 | let xyz1 = Xyz::from_color(self); 57 | let xyz2 = Xyz::from_color(other); 58 | 59 | crate::contrast_ratio(xyz1.y, xyz2.y) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /palette/src/okhsl/random.rs: -------------------------------------------------------------------------------- 1 | use crate::{Okhsl, OklabHue}; 2 | 3 | impl_rand_traits_hsl_bicone!( 4 | UniformOkhsl, 5 | Okhsl { 6 | hue: UniformOklabHue => OklabHue, 7 | height: lightness, 8 | radius: saturation 9 | } 10 | ); 11 | -------------------------------------------------------------------------------- /palette/src/okhsl/visual_eq.rs: -------------------------------------------------------------------------------- 1 | use crate::angle::{AngleEq, HalfRotation, RealAngle, SignedAngle}; 2 | use crate::num::{One, Zero}; 3 | use crate::visual::{VisualColor, VisuallyEqual}; 4 | use crate::{HasBoolMask, Okhsl, OklabHue}; 5 | use approx::AbsDiffEq; 6 | use std::borrow::Borrow; 7 | use std::ops::{Mul, Neg, Sub}; 8 | 9 | impl VisualColor for Okhsl 10 | where 11 | T: PartialOrd 12 | + HasBoolMask 13 | + AbsDiffEq 14 | + One 15 | + Zero 16 | + Neg, 17 | T::Epsilon: Clone, 18 | OklabHue: AbsDiffEq, 19 | { 20 | /// Returns true, if `saturation == 0` 21 | fn is_grey(&self, epsilon: T::Epsilon) -> bool { 22 | debug_assert!(self.saturation >= -epsilon.clone()); 23 | self.saturation.abs_diff_eq(&T::zero(), epsilon) 24 | } 25 | 26 | /// Returns true, if `Self::is_grey` && `lightness >= 1`, 27 | /// i.e. the color's hue is irrelevant **and** it is at or beyond the 28 | /// `sRGB` maximum luminance. A color at or beyond maximum brightness isn't 29 | /// necessarily white. It may also be a bright shining hue. 30 | fn is_white(&self, epsilon: T::Epsilon) -> bool { 31 | self.is_grey(epsilon.clone()) && self.lightness > T::one() 32 | || self.lightness.abs_diff_eq(&T::one(), epsilon) 33 | } 34 | 35 | /// Returns true if `lightness == 0` 36 | fn is_black(&self, epsilon: T::Epsilon) -> bool { 37 | debug_assert!(self.lightness >= -epsilon.clone()); 38 | self.lightness <= epsilon 39 | } 40 | } 41 | 42 | impl VisuallyEqual for Okhsl 43 | where 44 | T: PartialOrd 45 | + HasBoolMask 46 | + RealAngle 47 | + SignedAngle 48 | + Zero 49 | + One 50 | + AngleEq 51 | + Sub 52 | + AbsDiffEq 53 | + Neg 54 | + Clone, 55 | T::Epsilon: Clone + HalfRotation + Mul, 56 | S: Borrow + Copy, 57 | O: Borrow + Copy, 58 | { 59 | fn visually_eq(s: S, o: O, epsilon: T::Epsilon) -> bool { 60 | VisuallyEqual::both_black_or_both_white(s, o, epsilon.clone()) 61 | || VisuallyEqual::both_greyscale(s, o, epsilon.clone()) 62 | && s.borrow() 63 | .lightness 64 | .abs_diff_eq(&o.borrow().lightness, epsilon.clone()) 65 | || s.borrow().hue.abs_diff_eq(&o.borrow().hue, epsilon.clone()) 66 | && s.borrow() 67 | .saturation 68 | .abs_diff_eq(&o.borrow().saturation, epsilon.clone()) 69 | && s.borrow() 70 | .lightness 71 | .abs_diff_eq(&o.borrow().lightness, epsilon) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /palette/src/okhsv/alpha.rs: -------------------------------------------------------------------------------- 1 | use super::Okhsv; 2 | use crate::angle::FromAngle; 3 | use crate::hues::OklabHue; 4 | use crate::num::{MinMax, Zero}; 5 | use crate::stimulus::FromStimulus; 6 | use crate::Alpha; 7 | 8 | /// Okhsv with an alpha component. See the [`Okhsva` implementation in 9 | /// `Alpha`](crate::Alpha#Okhsva). 10 | pub type Okhsva = Alpha, T>; 11 | 12 | ///[`Hsva`](crate::Hsva) implementations. 13 | impl Alpha, A> { 14 | /// Create an `Okhsv` color with transparency. 15 | pub fn new>>(hue: H, saturation: T, value: T, alpha: A) -> Self { 16 | Alpha { 17 | color: Okhsv::new(hue.into(), saturation, value), 18 | alpha, 19 | } 20 | } 21 | 22 | /// Create an `Okhsva` color. This is the same as `Okhsva::new` without the 23 | /// generic hue type. It's temporary until `const fn` supports traits. 24 | pub const fn new_const(hue: OklabHue, saturation: T, value: T, alpha: A) -> Self { 25 | Alpha { 26 | color: Okhsv::new_const(hue, saturation, value), 27 | alpha, 28 | } 29 | } 30 | 31 | /// Convert into another component type. 32 | pub fn into_format(self) -> Alpha, B> 33 | where 34 | U: FromStimulus + FromAngle, 35 | B: FromStimulus, 36 | { 37 | Alpha { 38 | color: self.color.into_format(), 39 | alpha: B::from_stimulus(self.alpha), 40 | } 41 | } 42 | 43 | /// Convert from another component type. 44 | pub fn from_format(color: Alpha, B>) -> Self 45 | where 46 | T: FromStimulus + FromAngle, 47 | A: FromStimulus, 48 | U: Zero + MinMax, 49 | { 50 | color.into_format() 51 | } 52 | 53 | /// Convert to a `(hue, saturation, value, alpha)` tuple. 54 | pub fn into_components(self) -> (OklabHue, T, T, A) { 55 | ( 56 | self.color.hue, 57 | self.color.saturation, 58 | self.color.value, 59 | self.alpha, 60 | ) 61 | } 62 | 63 | /// Convert from a `(hue, saturation, value, alpha)` tuple. 64 | pub fn from_components>>( 65 | (hue, saturation, value, alpha): (H, T, T, A), 66 | ) -> Self { 67 | Self::new(hue, saturation, value, alpha) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /palette/src/okhsv/properties.rs: -------------------------------------------------------------------------------- 1 | use crate::hues::OklabHueIter; 2 | use crate::num::{Arithmetics, Real}; 3 | 4 | use crate::stimulus::Stimulus; 5 | use crate::{ok_utils, OklabHue}; 6 | 7 | use super::Okhsv; 8 | 9 | impl_is_within_bounds! { 10 | Okhsv { 11 | saturation => [Self::min_saturation(), Self::max_saturation()+ T::from_f64(ok_utils::MAX_SRGB_SATURATION_INACCURACY)], 12 | value => [Self::min_value(), Self::max_value()+ T::from_f64(ok_utils::MAX_SRGB_SATURATION_INACCURACY)] 13 | } 14 | where T: Real+Arithmetics+Stimulus 15 | } 16 | 17 | impl_clamp! { 18 | Okhsv { 19 | saturation => [Self::min_saturation(), Self::max_saturation()+ T::from_f64(ok_utils::MAX_SRGB_SATURATION_INACCURACY)], 20 | value => [Self::min_value(), Self::max_value()+ T::from_f64(ok_utils::MAX_SRGB_SATURATION_INACCURACY)] 21 | } 22 | other {hue} 23 | where T: Real+Arithmetics+Stimulus 24 | } 25 | 26 | impl_mix_hue!(Okhsv { saturation, value }); 27 | impl_lighten!(Okhsv increase {value => [Self::min_value(), Self::max_value()]} other {hue, saturation} where T: Real+Stimulus); 28 | impl_saturate!(Okhsv increase {saturation => [Self::min_saturation(), Self::max_saturation()]} other {hue, value} where T:Real+ Stimulus); 29 | impl_hue_ops!(Okhsv, OklabHue); 30 | 31 | impl_color_add!(Okhsv, [hue, saturation, value]); 32 | impl_color_sub!(Okhsv, [hue, saturation, value]); 33 | 34 | impl_array_casts!(Okhsv, [T; 3]); 35 | impl_simd_array_conversion_hue!(Okhsv, [saturation, value]); 36 | impl_struct_of_array_traits_hue!(Okhsv, OklabHueIter, [saturation, value]); 37 | 38 | impl_eq_hue!(Okhsv, OklabHue, [hue, saturation, value]); 39 | -------------------------------------------------------------------------------- /palette/src/okhsv/random.rs: -------------------------------------------------------------------------------- 1 | use crate::{Okhsv, OklabHue}; 2 | 3 | impl_rand_traits_hsv_cone!( 4 | UniformOkhsv, 5 | Okhsv { 6 | hue: UniformOklabHue => OklabHue, 7 | height: value, 8 | radius: saturation 9 | } 10 | ); 11 | 12 | #[cfg(feature = "bytemuck")] 13 | unsafe impl bytemuck::Zeroable for Okhsv where T: bytemuck::Zeroable {} 14 | 15 | #[cfg(feature = "bytemuck")] 16 | unsafe impl bytemuck::Pod for Okhsv where T: bytemuck::Pod {} 17 | -------------------------------------------------------------------------------- /palette/src/okhsv/visual_eq.rs: -------------------------------------------------------------------------------- 1 | use crate::angle::{AngleEq, HalfRotation, RealAngle, SignedAngle}; 2 | use crate::num::{One, Zero}; 3 | use crate::visual::{VisualColor, VisuallyEqual}; 4 | use crate::{HasBoolMask, Okhsv, OklabHue}; 5 | use approx::AbsDiffEq; 6 | use std::borrow::Borrow; 7 | use std::ops::{Mul, Neg, Sub}; 8 | 9 | impl VisualColor for Okhsv 10 | where 11 | T: PartialOrd 12 | + HasBoolMask 13 | + AbsDiffEq 14 | + One 15 | + Zero 16 | + Neg, 17 | T::Epsilon: Clone, 18 | OklabHue: AbsDiffEq, 19 | { 20 | /// Returns true, if `saturation == 0` 21 | fn is_grey(&self, epsilon: T::Epsilon) -> bool { 22 | self.saturation.abs_diff_eq(&T::zero(), epsilon) 23 | } 24 | 25 | /// Returns true, if `Self::is_grey` && `value >= 1`, 26 | /// i.e. the color's hue is irrelevant **and** it is at or beyond the 27 | /// `sRGB` maximum brightness. A color at or beyond maximum brightness isn't 28 | /// necessarily white. It can also be a bright shining hue. 29 | fn is_white(&self, epsilon: T::Epsilon) -> bool { 30 | self.is_grey(epsilon.clone()) && self.value >= T::one() 31 | || self.value.abs_diff_eq(&T::one(), epsilon) 32 | } 33 | 34 | /// Returns true if `value == 0` 35 | fn is_black(&self, epsilon: T::Epsilon) -> bool { 36 | debug_assert!(self.value >= -epsilon.clone()); 37 | self.value.abs_diff_eq(&T::zero(), epsilon) 38 | } 39 | } 40 | 41 | impl VisuallyEqual for Okhsv 42 | where 43 | T: PartialOrd 44 | + HasBoolMask 45 | + RealAngle 46 | + SignedAngle 47 | + Zero 48 | + One 49 | + AngleEq 50 | + Sub 51 | + AbsDiffEq 52 | + Neg 53 | + Clone, 54 | T::Epsilon: Clone + HalfRotation + Mul, 55 | S: Borrow + Copy, 56 | O: Borrow + Copy, 57 | { 58 | fn visually_eq(s: S, o: O, epsilon: T::Epsilon) -> bool { 59 | VisuallyEqual::both_black_or_both_white(s, o, epsilon.clone()) 60 | || VisuallyEqual::both_greyscale(s, o, epsilon.clone()) 61 | && s.borrow() 62 | .value 63 | .abs_diff_eq(&o.borrow().value, epsilon.clone()) 64 | || s.borrow().hue.abs_diff_eq(&o.borrow().hue, epsilon.clone()) 65 | && s.borrow() 66 | .saturation 67 | .abs_diff_eq(&o.borrow().saturation, epsilon.clone()) 68 | && s.borrow().value.abs_diff_eq(&o.borrow().value, epsilon) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /palette/src/okhwb/alpha.rs: -------------------------------------------------------------------------------- 1 | use crate::angle::FromAngle; 2 | use crate::okhwb::Okhwb; 3 | use crate::stimulus::FromStimulus; 4 | use crate::{Alpha, OklabHue}; 5 | 6 | /// Okhwb with an alpha component. See the [`Okhwba` implementation in 7 | /// `Alpha`](crate::Alpha#Okhwba). 8 | pub type Okhwba = Alpha, T>; 9 | 10 | ///[`Okhwba`](crate::Okhwba) implementations. 11 | impl Alpha, A> { 12 | /// Create an `Okhwb` color with transparency. 13 | pub fn new>>(hue: H, whiteness: T, blackness: T, alpha: A) -> Self { 14 | Alpha { 15 | color: Okhwb::new(hue.into(), whiteness, blackness), 16 | alpha, 17 | } 18 | } 19 | 20 | /// Create an `Okhwba` color. This is the same as `Okhwba::new` without the 21 | /// generic hue type. It's temporary until `const fn` supports traits. 22 | pub const fn new_const(hue: OklabHue, whiteness: T, blackness: T, alpha: A) -> Self { 23 | Alpha { 24 | color: Okhwb::new_const(hue, whiteness, blackness), 25 | alpha, 26 | } 27 | } 28 | 29 | /// Convert into another component type. 30 | pub fn into_format(self) -> Alpha, B> 31 | where 32 | U: FromStimulus + FromAngle, 33 | B: FromStimulus, 34 | { 35 | Alpha { 36 | color: self.color.into_format(), 37 | alpha: B::from_stimulus(self.alpha), 38 | } 39 | } 40 | 41 | /// Convert from another component type. 42 | pub fn from_format(color: Alpha, B>) -> Self 43 | where 44 | T: FromStimulus + FromAngle, 45 | A: FromStimulus, 46 | { 47 | color.into_format() 48 | } 49 | 50 | /// Convert to a `(hue, whiteness, blackness, alpha)` tuple. 51 | pub fn into_components(self) -> (OklabHue, T, T, A) { 52 | ( 53 | self.color.hue, 54 | self.color.whiteness, 55 | self.color.blackness, 56 | self.alpha, 57 | ) 58 | } 59 | 60 | /// Convert from a `(hue, whiteness, blackness, alpha)` tuple. 61 | pub fn from_components>>( 62 | (hue, whiteness, blackness, alpha): (H, T, T, A), 63 | ) -> Self { 64 | Self::new(hue, whiteness, blackness, alpha) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /palette/src/okhwb/properties.rs: -------------------------------------------------------------------------------- 1 | use crate::hues::OklabHueIter; 2 | use crate::num::{Arithmetics, PartialCmp, Real}; 3 | use crate::stimulus::Stimulus; 4 | use crate::white_point::D65; 5 | use crate::{ 6 | bool_mask::{LazySelect, Select}, 7 | FromColor, OklabHue, Xyz, 8 | }; 9 | 10 | use super::Okhwb; 11 | 12 | impl_is_within_bounds_hwb!(Okhwb where T: Stimulus); 13 | impl_clamp_hwb!(Okhwb where T: Stimulus); 14 | 15 | impl_mix_hue!(Okhwb { 16 | whiteness, 17 | blackness 18 | }); 19 | impl_lighten_hwb!(Okhwb where T: Stimulus); 20 | impl_hue_ops!(Okhwb, OklabHue); 21 | 22 | impl_color_add!(Okhwb, [hue, whiteness, blackness]); 23 | impl_color_sub!(Okhwb, [hue, whiteness, blackness]); 24 | 25 | impl_array_casts!(Okhwb, [T; 3]); 26 | impl_simd_array_conversion_hue!(Okhwb, [whiteness, blackness]); 27 | impl_struct_of_array_traits_hue!(Okhwb, OklabHueIter, [whiteness, blackness]); 28 | 29 | #[allow(deprecated)] 30 | impl crate::RelativeContrast for Okhwb 31 | where 32 | T: Real + Arithmetics + PartialCmp, 33 | T::Mask: LazySelect, 34 | Xyz: FromColor, 35 | { 36 | type Scalar = T; 37 | 38 | #[inline] 39 | fn get_contrast_ratio(self, other: Self) -> T { 40 | let xyz1 = Xyz::from_color(self); 41 | let xyz2 = Xyz::from_color(other); 42 | 43 | crate::contrast_ratio(xyz1.y, xyz2.y) 44 | } 45 | } 46 | 47 | impl_eq_hue!(Okhwb, OklabHue, [hue, whiteness, blackness]); 48 | -------------------------------------------------------------------------------- /palette/src/okhwb/random.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | okhsv::{Okhsv, UniformOkhsv}, 3 | Okhwb, 4 | }; 5 | 6 | impl_rand_traits_hwb_cone!( 7 | UniformOkhwb, 8 | Okhwb, 9 | UniformOkhsv, 10 | Okhsv { 11 | height: value, 12 | radius: saturation 13 | } 14 | ); 15 | -------------------------------------------------------------------------------- /palette/src/okhwb/visual_eq.rs: -------------------------------------------------------------------------------- 1 | use crate::angle::{AngleEq, HalfRotation, RealAngle, SignedAngle}; 2 | use crate::num::{Arithmetics, One, Zero}; 3 | use crate::visual::{VisualColor, VisuallyEqual}; 4 | use crate::{HasBoolMask, Okhwb, OklabHue}; 5 | use approx::AbsDiffEq; 6 | use std::borrow::Borrow; 7 | 8 | impl VisualColor for Okhwb 9 | where 10 | T: PartialOrd + HasBoolMask + AbsDiffEq + One + Zero + Arithmetics, 11 | T::Epsilon: Clone, 12 | OklabHue: AbsDiffEq, 13 | { 14 | /// Returns `true`, if `self.blackness + self.whiteness >= 1`, 15 | /// assuming (but not asserting) that neither 16 | /// `blackness` nor `whiteness` can be negative. 17 | fn is_grey(&self, epsilon: T::Epsilon) -> bool { 18 | let wb_sum = self.blackness.clone() + self.whiteness.clone(); 19 | wb_sum > T::one() || wb_sum.abs_diff_eq(&T::one(), epsilon) 20 | } 21 | 22 | /// Returns `true`, if `Self::is_grey && blackness == 0`, 23 | /// i.e. the color's hue is irrelevant **and** the color contains 24 | /// no black component it must be white. 25 | fn is_white(&self, epsilon: T::Epsilon) -> bool { 26 | self.is_grey(epsilon.clone()) && self.blackness < epsilon 27 | } 28 | 29 | /// Returns `true` if `Self::is_grey && whiteness == 0` 30 | fn is_black(&self, epsilon: T::Epsilon) -> bool { 31 | self.is_grey(epsilon.clone()) && self.whiteness < epsilon 32 | } 33 | } 34 | 35 | impl VisuallyEqual for Okhwb 36 | where 37 | T: PartialOrd 38 | + HasBoolMask 39 | + RealAngle 40 | + SignedAngle 41 | + Zero 42 | + One 43 | + AngleEq 44 | + Arithmetics 45 | + AbsDiffEq 46 | + Clone, 47 | T::Epsilon: Clone + HalfRotation, 48 | S: Borrow + Copy, 49 | O: Borrow + Copy, 50 | { 51 | fn visually_eq(s: S, o: O, epsilon: T::Epsilon) -> bool { 52 | VisuallyEqual::both_black_or_both_white(s, o, epsilon.clone()) 53 | || VisuallyEqual::both_greyscale(s, o, epsilon.clone()) 54 | && s.borrow() 55 | .whiteness 56 | .abs_diff_eq(&o.borrow().whiteness, epsilon.clone()) 57 | && s.borrow() 58 | .blackness 59 | .abs_diff_eq(&o.borrow().blackness, epsilon.clone()) 60 | || s.borrow().hue.abs_diff_eq(&o.borrow().hue, epsilon.clone()) 61 | && s.borrow() 62 | .blackness 63 | .abs_diff_eq(&o.borrow().blackness, epsilon.clone()) 64 | && s.borrow() 65 | .whiteness 66 | .abs_diff_eq(&o.borrow().whiteness, epsilon) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /palette/src/oklab/alpha.rs: -------------------------------------------------------------------------------- 1 | use crate::alpha::Alpha; 2 | use crate::oklab::Oklab; 3 | 4 | /// Oklab with an alpha component. 5 | pub type Oklaba = Alpha, T>; 6 | 7 | ///[`Oklaba`](crate::Oklaba) implementations. 8 | impl Alpha, A> { 9 | /// Create an Oklab color with transparency. 10 | pub const fn new(l: T, a: T, b: T, alpha: A) -> Self { 11 | Alpha { 12 | color: Oklab::new(l, a, b), 13 | alpha, 14 | } 15 | } 16 | 17 | /// Convert to a `(L, a, b, alpha)` tuple. 18 | pub fn into_components(self) -> (T, T, T, A) { 19 | (self.color.l, self.color.a, self.color.b, self.alpha) 20 | } 21 | 22 | /// Convert from a `(L, a, b, alpha)` tuple. 23 | pub fn from_components((l, a, b, alpha): (T, T, T, A)) -> Self { 24 | Self::new(l, a, b, alpha) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /palette/src/oklab/properties.rs: -------------------------------------------------------------------------------- 1 | use core::ops::{Add, Neg}; 2 | 3 | use crate::{ 4 | angle::RealAngle, 5 | bool_mask::LazySelect, 6 | num::{Arithmetics, One, PartialCmp, Real, Trigonometry, Zero}, 7 | white_point::D65, 8 | FromColor, GetHue, OklabHue, Xyz, 9 | }; 10 | 11 | use super::Oklab; 12 | 13 | impl_is_within_bounds! { 14 | Oklab { 15 | l => [Self::min_l(), Self::max_l()] 16 | } 17 | where T: Zero + One 18 | } 19 | impl_clamp! { 20 | Oklab { 21 | l => [Self::min_l(), Self::max_l()] 22 | } 23 | other {a, b} 24 | where T: Zero + One 25 | } 26 | 27 | impl_mix!(Oklab); 28 | impl_lighten!(Oklab increase {l => [Self::min_l(), Self::max_l()]} other {a, b} where T: One); 29 | impl_premultiply!(Oklab { l, a, b }); 30 | impl_euclidean_distance!(Oklab { l, a, b }); 31 | impl_hyab!(Oklab { 32 | lightness: l, 33 | chroma1: a, 34 | chroma2: b 35 | }); 36 | impl_lab_color_schemes!(Oklab[l]); 37 | 38 | impl GetHue for Oklab 39 | where 40 | T: RealAngle + Trigonometry + Add + Neg + Clone, 41 | { 42 | type Hue = OklabHue; 43 | 44 | fn get_hue(&self) -> OklabHue { 45 | OklabHue::from_cartesian(self.a.clone(), self.b.clone()) 46 | } 47 | } 48 | 49 | impl_color_add!(Oklab, [l, a, b]); 50 | impl_color_sub!(Oklab, [l, a, b]); 51 | impl_color_mul!(Oklab, [l, a, b]); 52 | impl_color_div!(Oklab, [l, a, b]); 53 | 54 | impl_array_casts!(Oklab, [T; 3]); 55 | impl_simd_array_conversion!(Oklab, [l, a, b]); 56 | impl_struct_of_array_traits!(Oklab, [l, a, b]); 57 | 58 | impl_eq!(Oklab, [l, a, b]); 59 | 60 | #[allow(deprecated)] 61 | impl crate::RelativeContrast for Oklab 62 | where 63 | T: Real + Arithmetics + PartialCmp, 64 | T::Mask: LazySelect, 65 | Xyz: FromColor, 66 | { 67 | type Scalar = T; 68 | 69 | #[inline] 70 | fn get_contrast_ratio(self, other: Self) -> T { 71 | let xyz1 = Xyz::from_color(self); 72 | let xyz2 = Xyz::from_color(other); 73 | 74 | crate::contrast_ratio(xyz1.y, xyz2.y) 75 | } 76 | } 77 | 78 | #[cfg(test)] 79 | mod test { 80 | #[cfg(feature = "approx")] 81 | use crate::{Oklab, Oklch}; 82 | 83 | test_lab_color_schemes!(Oklab / Oklch[l]); 84 | } 85 | -------------------------------------------------------------------------------- /palette/src/oklab/random.rs: -------------------------------------------------------------------------------- 1 | use core::ops::{Add, Mul, Sub}; 2 | 3 | use crate::{num::One, Oklab}; 4 | 5 | impl_rand_traits_cartesian!( 6 | UniformOklab, 7 | Oklab { 8 | l, 9 | a => [|x| x * (T::one() + T::one()) - T::one()], 10 | b => [|x| x * (T::one() + T::one()) - T::one()] 11 | } 12 | where T: Mul + Add + Sub + One 13 | ); 14 | -------------------------------------------------------------------------------- /palette/src/oklab/visual_eq.rs: -------------------------------------------------------------------------------- 1 | use crate::angle::{AngleEq, HalfRotation, RealAngle, SignedAngle}; 2 | use crate::num::{One, Zero}; 3 | use crate::visual::{VisualColor, VisuallyEqual}; 4 | use crate::{HasBoolMask, Oklab, OklabHue}; 5 | use approx::AbsDiffEq; 6 | use std::borrow::Borrow; 7 | use std::ops::{Mul, Neg, Sub}; 8 | 9 | impl VisualColor for Oklab 10 | where 11 | T: PartialOrd 12 | + HasBoolMask 13 | + AbsDiffEq 14 | + One 15 | + Zero 16 | + Neg, 17 | T::Epsilon: Clone, 18 | OklabHue: AbsDiffEq, 19 | { 20 | /// Returns true, if `chroma == 0` 21 | #[allow(dead_code)] 22 | fn is_grey(&self, epsilon: T::Epsilon) -> bool { 23 | self.a.abs_diff_eq(&T::zero(), epsilon.clone()) && self.b.abs_diff_eq(&T::zero(), epsilon) 24 | } 25 | 26 | /// Returns true, if `lightness >= 1` 27 | /// 28 | /// **Note:** `sRGB` to `Oklab` conversion uses `f32` constants. 29 | /// A tolerance `epsilon >= 1e-8` is required to reliably detect white. 30 | /// Conversion of `sRGB` via XYZ requires `epsilon >= 1e-5` 31 | fn is_white(&self, epsilon: T::Epsilon) -> bool { 32 | self.l >= T::one() || self.l.abs_diff_eq(&T::one(), epsilon) 33 | } 34 | 35 | /// Returns true, if `lightness == 0` 36 | fn is_black(&self, epsilon: T::Epsilon) -> bool { 37 | self.l.abs_diff_eq(&T::zero(), epsilon) 38 | } 39 | } 40 | 41 | impl VisuallyEqual for Oklab 42 | where 43 | T: PartialOrd 44 | + HasBoolMask 45 | + RealAngle 46 | + SignedAngle 47 | + Zero 48 | + One 49 | + AngleEq 50 | + Sub 51 | + AbsDiffEq 52 | + Neg 53 | + Clone, 54 | T::Epsilon: Clone + HalfRotation + Mul, 55 | S: Borrow + Copy, 56 | O: Borrow + Copy, 57 | { 58 | fn visually_eq(s: S, o: O, epsilon: T::Epsilon) -> bool { 59 | VisuallyEqual::both_black_or_both_white(s, o, epsilon.clone()) 60 | || s.borrow().l.abs_diff_eq(&o.borrow().l, epsilon.clone()) 61 | && s.borrow().a.abs_diff_eq(&o.borrow().a, epsilon.clone()) 62 | && s.borrow().b.abs_diff_eq(&o.borrow().b, epsilon) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /palette/src/oklch/alpha.rs: -------------------------------------------------------------------------------- 1 | use crate::{Alpha, OklabHue}; 2 | 3 | use super::Oklch; 4 | 5 | /// Oklch with an alpha component. See the [`Oklcha` implementation in 6 | /// `Alpha`](crate::Alpha#Oklcha). 7 | pub type Oklcha = Alpha, T>; 8 | 9 | ///[`Oklcha`](crate::Oklcha) implementations. 10 | impl Alpha, A> { 11 | /// Create an Oklch color with transparency. 12 | pub fn new>>(l: T, chroma: T, hue: H, alpha: A) -> Self { 13 | Alpha { 14 | color: Oklch::new(l, chroma, hue), 15 | alpha, 16 | } 17 | } 18 | 19 | /// Create an `Oklcha` color. This is the same as `Oklcha::new` without the 20 | /// generic hue type. It's temporary until `const fn` supports traits. 21 | pub const fn new_const(l: T, chroma: T, hue: OklabHue, alpha: A) -> Self { 22 | Alpha { 23 | color: Oklch::new_const(l, chroma, hue), 24 | alpha, 25 | } 26 | } 27 | 28 | /// Convert to a `(L, C, h, alpha)` tuple. 29 | pub fn into_components(self) -> (T, T, OklabHue, A) { 30 | (self.color.l, self.color.chroma, self.color.hue, self.alpha) 31 | } 32 | 33 | /// Convert from a `(L, C, h, alpha)` tuple. 34 | pub fn from_components>>((l, chroma, hue, alpha): (T, T, H, A)) -> Self { 35 | Self::new(l, chroma, hue, alpha) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /palette/src/oklch/properties.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | bool_mask::LazySelect, 3 | hues::OklabHueIter, 4 | num::{Arithmetics, One, PartialCmp, Real, Zero}, 5 | white_point::D65, 6 | FromColor, OklabHue, Xyz, 7 | }; 8 | 9 | use super::Oklch; 10 | 11 | impl_is_within_bounds! { 12 | Oklch { 13 | l => [Self::min_l(), Self::max_l()], 14 | chroma => [Self::min_chroma(), None] 15 | } 16 | where T: Zero + One 17 | } 18 | impl_clamp! { 19 | Oklch { 20 | l => [Self::min_l(), Self::max_l()], 21 | chroma => [Self::min_chroma()] 22 | } 23 | other {hue} 24 | where T: Zero + One 25 | } 26 | 27 | impl_mix_hue!(Oklch { l, chroma }); 28 | impl_lighten!(Oklch increase {l => [Self::min_l(), Self::max_l()]} other {hue, chroma} where T: Zero + One); 29 | impl_hue_ops!(Oklch, OklabHue); 30 | 31 | impl_color_add!(Oklch, [l, chroma, hue]); 32 | impl_color_sub!(Oklch, [l, chroma, hue]); 33 | 34 | impl_array_casts!(Oklch, [T; 3]); 35 | impl_simd_array_conversion_hue!(Oklch, [l, chroma]); 36 | impl_struct_of_array_traits_hue!(Oklch, OklabHueIter, [l, chroma]); 37 | 38 | impl_eq_hue!(Oklch, OklabHue, [l, chroma, hue]); 39 | 40 | #[allow(deprecated)] 41 | impl crate::RelativeContrast for Oklch 42 | where 43 | T: Real + Arithmetics + PartialCmp, 44 | T::Mask: LazySelect, 45 | Xyz: FromColor, 46 | { 47 | type Scalar = T; 48 | 49 | #[inline] 50 | fn get_contrast_ratio(self, other: Self) -> T { 51 | let xyz1 = Xyz::from_color(self); 52 | let xyz2 = Xyz::from_color(other); 53 | 54 | crate::contrast_ratio(xyz1.y, xyz2.y) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /palette/src/oklch/random.rs: -------------------------------------------------------------------------------- 1 | use super::Oklch; 2 | 3 | use crate::OklabHue; 4 | 5 | impl_rand_traits_cylinder!( 6 | UniformOklch, 7 | Oklch { 8 | hue: UniformOklabHue => OklabHue, 9 | height: l, 10 | radius: chroma // FIXME: Same as with Oklab: The limit of chroma has no meaning 11 | } 12 | ); 13 | -------------------------------------------------------------------------------- /palette/src/random_sampling.rs: -------------------------------------------------------------------------------- 1 | mod cone; 2 | 3 | pub(crate) use self::cone::*; 4 | 5 | #[cfg(test)] 6 | pub(crate) mod test_utils { 7 | pub(crate) const BINS: usize = 10; 8 | pub(crate) const SAMPLES: usize = 20_000; 9 | 10 | /// Perform a Chi-squared goodness-of-fit test to check if the bins are 11 | /// uniformly distributed. Returns the p-value. 12 | pub(crate) fn uniform_distribution_test(bins: &[usize]) -> f64 { 13 | let sum = bins.iter().sum::() as f64; 14 | let expected = sum / bins.len() as f64; 15 | let critical_value = bins 16 | .iter() 17 | .map(|&bin| { 18 | let difference = bin as f64 - expected; 19 | difference * difference / expected 20 | }) 21 | .sum::(); 22 | 23 | chi_square(bins.len() - 1, critical_value) 24 | } 25 | 26 | // Shamelessly taken from https://www.codeproject.com/Articles/432194/How-to-Calculate-the-Chi-Squared-P-Value 27 | fn chi_square(dof: usize, critical_value: f64) -> f64 { 28 | if critical_value < 0.0 || dof < 1 { 29 | return 0.0; 30 | } 31 | let k = dof as f64 * 0.5; 32 | let x = critical_value * 0.5; 33 | if dof == 2 { 34 | return (-x).exp(); 35 | } 36 | 37 | let mut p_value = incomplete_gamma_function(k, x); 38 | if p_value.is_nan() || p_value.is_infinite() || p_value <= 1e-8 { 39 | return 1e-14; 40 | } 41 | 42 | p_value /= approximate_gamma(k); 43 | 44 | 1.0 - p_value 45 | } 46 | 47 | fn incomplete_gamma_function(mut s: f64, z: f64) -> f64 { 48 | if z < 0.0 { 49 | return 0.0; 50 | } 51 | let mut sc = 1.0 / s; 52 | sc *= z.powf(s); 53 | sc *= (-z).exp(); 54 | 55 | let mut sum = 1.0; 56 | let mut nom = 1.0; 57 | let mut denom = 1.0; 58 | 59 | for _ in 0..200 { 60 | nom *= z; 61 | s += 1.0; 62 | denom *= s; 63 | sum += nom / denom; 64 | } 65 | 66 | sum * sc 67 | } 68 | 69 | fn approximate_gamma(z: f64) -> f64 { 70 | #[allow(clippy::excessive_precision)] 71 | const RECIP_E: f64 = 0.36787944117144232159552377016147; // RECIP_E = (E^-1) = (1.0 / E) 72 | const TWOPI: f64 = core::f64::consts::TAU; 73 | 74 | let mut d = 1.0 / (10.0 * z); 75 | d = 1.0 / ((12.0 * z) - d); 76 | d = (d + z) * RECIP_E; 77 | d = d.powf(z); 78 | d *= (TWOPI / z).sqrt(); 79 | 80 | d 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /palette/src/rgb/hex.rs: -------------------------------------------------------------------------------- 1 | use core::num::ParseIntError; 2 | 3 | #[inline] 4 | pub(crate) fn rgb_from_hex_4bit(hex: &str) -> Result<(u8, u8, u8), ParseIntError> { 5 | let red = u8::from_str_radix(&hex[..1], 16)?; 6 | let green = u8::from_str_radix(&hex[1..2], 16)?; 7 | let blue = u8::from_str_radix(&hex[2..3], 16)?; 8 | 9 | Ok((red * 17, green * 17, blue * 17)) 10 | } 11 | 12 | #[inline] 13 | pub(crate) fn rgba_from_hex_4bit(hex: &str) -> Result<(u8, u8, u8, u8), ParseIntError> { 14 | let (red, green, blue) = rgb_from_hex_4bit(hex)?; 15 | let alpha = u8::from_str_radix(&hex[3..4], 16)?; 16 | 17 | Ok((red, green, blue, alpha * 17)) 18 | } 19 | 20 | #[inline] 21 | pub(crate) fn rgb_from_hex_8bit(hex: &str) -> Result<(u8, u8, u8), ParseIntError> { 22 | let red = u8::from_str_radix(&hex[..2], 16)?; 23 | let green = u8::from_str_radix(&hex[2..4], 16)?; 24 | let blue = u8::from_str_radix(&hex[4..6], 16)?; 25 | 26 | Ok((red, green, blue)) 27 | } 28 | 29 | #[inline] 30 | pub(crate) fn rgba_from_hex_8bit(hex: &str) -> Result<(u8, u8, u8, u8), ParseIntError> { 31 | let (red, green, blue) = rgb_from_hex_8bit(hex)?; 32 | let alpha = u8::from_str_radix(&hex[6..8], 16)?; 33 | 34 | Ok((red, green, blue, alpha)) 35 | } 36 | 37 | #[inline] 38 | pub(crate) fn rgb_from_hex_16bit(hex: &str) -> Result<(u16, u16, u16), ParseIntError> { 39 | let red = u16::from_str_radix(&hex[..4], 16)?; 40 | let green = u16::from_str_radix(&hex[4..8], 16)?; 41 | let blue = u16::from_str_radix(&hex[8..12], 16)?; 42 | 43 | Ok((red, green, blue)) 44 | } 45 | 46 | #[inline] 47 | pub(crate) fn rgba_from_hex_16bit(hex: &str) -> Result<(u16, u16, u16, u16), ParseIntError> { 48 | let (red, green, blue) = rgb_from_hex_16bit(hex)?; 49 | let alpha = u16::from_str_radix(&hex[12..16], 16)?; 50 | 51 | Ok((red, green, blue, alpha)) 52 | } 53 | 54 | #[inline] 55 | pub(crate) fn rgb_from_hex_32bit(hex: &str) -> Result<(u32, u32, u32), ParseIntError> { 56 | let red = u32::from_str_radix(&hex[..8], 16)?; 57 | let green = u32::from_str_radix(&hex[8..16], 16)?; 58 | let blue = u32::from_str_radix(&hex[16..24], 16)?; 59 | 60 | Ok((red, green, blue)) 61 | } 62 | 63 | #[inline] 64 | pub(crate) fn rgba_from_hex_32bit(hex: &str) -> Result<(u32, u32, u32, u32), ParseIntError> { 65 | let (red, green, blue) = rgb_from_hex_32bit(hex)?; 66 | let alpha = u32::from_str_radix(&hex[24..32], 16)?; 67 | 68 | Ok((red, green, blue, alpha)) 69 | } 70 | -------------------------------------------------------------------------------- /palette/src/visual.rs: -------------------------------------------------------------------------------- 1 | use approx::AbsDiffEq; 2 | use core::borrow::Borrow; 3 | 4 | /// Methods to tell the tell about the visual characteristics of the color 5 | pub trait VisualColor 6 | where 7 | T: AbsDiffEq, 8 | { 9 | /// Returns `true`, if the color is grey within the bounds of `epsilon`-tolerance, 10 | /// i.e. visually only the luminance value matters, not the color's hue. 11 | fn is_grey(&self, epsilon: T::Epsilon) -> bool; 12 | 13 | /// Returns `true`, if the color is white within the bounds of `epsilon`-tolerance, 14 | /// i.e. the color's hue is irrelevant **and** it is at or beyond the 15 | /// `sRGB` maximum brightness. 16 | /// A color that is *only* at or beyond maximum brightness isn't 17 | /// necessarily white. It may also be a bright shining hue. 18 | fn is_white(&self, epsilon: T::Epsilon) -> bool; 19 | 20 | /// Returns `true`, if the color is black within the bounds of `epsilon`-tolerance. 21 | fn is_black(&self, epsilon: T::Epsilon) -> bool; 22 | } 23 | 24 | /// Methods to compare visual characteristics of two colors 25 | pub trait VisuallyEqual: VisualColor 26 | where 27 | T: AbsDiffEq + Clone, 28 | S: Borrow, 29 | O: Borrow, 30 | { 31 | /// Returns true, if `self` and `other` are either both white or both black 32 | fn both_black_or_both_white(s: S, o: O, epsilon: T::Epsilon) -> bool { 33 | s.borrow().is_white(epsilon.clone()) && o.borrow().is_white(epsilon.clone()) 34 | || s.borrow().is_black(epsilon.clone()) && o.borrow().is_black(epsilon) 35 | } 36 | 37 | /// Returns true, if `self` and `other` are both fully desaturated 38 | fn both_greyscale(s: S, o: O, epsilon: T::Epsilon) -> bool { 39 | s.borrow().is_grey(epsilon.clone()) && o.borrow().is_grey(epsilon) 40 | } 41 | fn visually_eq(s: S, o: O, epsilon: T::Epsilon) -> bool; 42 | } 43 | -------------------------------------------------------------------------------- /palette/src/xyz/meta.rs: -------------------------------------------------------------------------------- 1 | //! Meta types and traits for [`Xyz`][super::Xyz]. 2 | 3 | /// Implemented by meta types that contain a meta type for [`Xyz`][super::Xyz]. 4 | pub trait HasXyzMeta { 5 | /// A meta type that can be used in [`Xyz`][super::Xyz]. 6 | type XyzMeta; 7 | } 8 | -------------------------------------------------------------------------------- /palette_derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "palette_derive" 3 | version = "0.7.6" #automatically updated 4 | authors = ["Erik Hedvall "] 5 | exclude = [] 6 | description = "Automatically implement traits from the palette crate." 7 | documentation = "https://docs.rs/palette/0.7.6/palette/" 8 | repository = "https://github.com/Ogeon/palette" 9 | readme = "README.md" 10 | keywords = ["palette", "derive", "macros"] 11 | license = "MIT OR Apache-2.0" 12 | edition = "2021" 13 | rust-version = "1.61.0" 14 | 15 | [lib] 16 | proc-macro = true 17 | bench = false 18 | 19 | [dependencies] 20 | syn = { version = "2.0.13", default-features = false, features = [ 21 | "derive", 22 | "parsing", 23 | "printing", 24 | "clone-impls", 25 | "extra-traits", 26 | "proc-macro", 27 | ] } 28 | quote = "1.0.0" 29 | proc-macro2 = "1.0.0" 30 | by_address = "1.2.1" 31 | find-crate = { version = "0.6.0", optional = true } 32 | -------------------------------------------------------------------------------- /palette_derive/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /palette_derive/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /palette_derive/README.md: -------------------------------------------------------------------------------- 1 | # palette_derive 2 | 3 | Contains derive macros for the [`palette`](https://crates.io/crates/palette/) crate. They are all reexported through `palette` and require it to be a dependency, so there is typically no reason to use this crate directly. 4 | 5 | ## Minimum Supported Rust Version (MSRV) 6 | 7 | This version of Palette has been automatically tested with Rust version `1.61.0`, `1.63.0`, and the `stable`, `beta`, and `nightly` channels. The minimum supported version may vary with the set of enabled features. 8 | 9 | Future versions of the library may advance the minimum supported version to make use of new language features, but this will normally be considered a breaking change. Exceptions may be made for security patches, dependencies advancing their MSRV in minor or patch releases, and similar changes. 10 | 11 | ## License 12 | 13 | Licensed under either of 14 | 15 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) 16 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or ) 17 | 18 | at your option. 19 | -------------------------------------------------------------------------------- /palette_derive/src/alpha/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::with_alpha::derive as derive_with_alpha; 2 | 3 | mod with_alpha; 4 | -------------------------------------------------------------------------------- /palette_derive/src/alpha/with_alpha.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use proc_macro2::{Span, TokenStream as TokenStream2}; 3 | 4 | use quote::quote; 5 | use syn::{parse_quote, DeriveInput, Generics, Ident, Type}; 6 | 7 | use crate::{ 8 | meta::{ 9 | parse_field_attributes, parse_namespaced_attributes, FieldAttributes, IdentOrIndex, 10 | TypeItemAttributes, 11 | }, 12 | util, 13 | }; 14 | 15 | pub fn derive(item: TokenStream) -> ::std::result::Result> { 16 | let DeriveInput { 17 | ident, 18 | generics: original_generics, 19 | data, 20 | attrs, 21 | .. 22 | } = syn::parse(item).map_err(|error| vec![error])?; 23 | let generics = original_generics; 24 | 25 | let (item_meta, item_errors) = parse_namespaced_attributes::(attrs); 26 | 27 | let (fields_meta, field_errors) = if let syn::Data::Struct(struct_data) = data { 28 | parse_field_attributes::(struct_data.fields) 29 | } else { 30 | return Err(vec![syn::Error::new( 31 | Span::call_site(), 32 | "only structs are supported", 33 | )]); 34 | }; 35 | 36 | let implementation = if let Some((alpha_property, alpha_type)) = fields_meta.alpha_property { 37 | implement_for_internal_alpha(&ident, &generics, &alpha_property, &alpha_type, &item_meta) 38 | } else { 39 | implement_for_external_alpha(&ident, &generics, &item_meta) 40 | }; 41 | 42 | let item_errors = item_errors 43 | .into_iter() 44 | .map(|error| error.into_compile_error()); 45 | let field_errors = field_errors 46 | .into_iter() 47 | .map(|error| error.into_compile_error()); 48 | 49 | Ok(quote! { 50 | #(#item_errors)* 51 | #(#field_errors)* 52 | 53 | #implementation 54 | } 55 | .into()) 56 | } 57 | 58 | fn implement_for_internal_alpha( 59 | ident: &Ident, 60 | generics: &Generics, 61 | alpha_property: &IdentOrIndex, 62 | alpha_type: &Type, 63 | item_meta: &TypeItemAttributes, 64 | ) -> TokenStream2 { 65 | let with_alpha_trait_path = util::path(["WithAlpha"], item_meta.internal); 66 | let stimulus_trait_path = util::path(["stimulus", "Stimulus"], item_meta.internal); 67 | 68 | let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); 69 | 70 | quote! { 71 | #[automatically_derived] 72 | impl #impl_generics #with_alpha_trait_path<#alpha_type> for #ident #type_generics #where_clause { 73 | type Color = Self; 74 | type WithAlpha = Self; 75 | 76 | fn with_alpha(mut self, alpha: #alpha_type) -> Self::WithAlpha { 77 | self.#alpha_property = alpha; 78 | self 79 | } 80 | 81 | fn without_alpha(mut self) -> Self::Color { 82 | self.#alpha_property = #stimulus_trait_path::max_intensity(); 83 | self 84 | } 85 | 86 | fn split(mut self) -> (Self::Color, #alpha_type) { 87 | let opaque_alpha = #stimulus_trait_path::max_intensity(); 88 | let alpha = core::mem::replace(&mut self.#alpha_property, opaque_alpha); 89 | (self, alpha) 90 | } 91 | } 92 | } 93 | } 94 | 95 | fn implement_for_external_alpha( 96 | ident: &Ident, 97 | generics: &Generics, 98 | item_meta: &TypeItemAttributes, 99 | ) -> TokenStream2 { 100 | let with_alpha_trait_path = util::path(["WithAlpha"], item_meta.internal); 101 | let stimulus_trait_path = util::path(["stimulus", "Stimulus"], item_meta.internal); 102 | let alpha_path = util::path(["Alpha"], item_meta.internal); 103 | 104 | let (_, type_generics, _) = generics.split_for_impl(); 105 | 106 | let alpha_type: Type = parse_quote!(_A); 107 | let mut impl_generics = generics.clone(); 108 | impl_generics.params.push(parse_quote!(_A)); 109 | impl_generics 110 | .make_where_clause() 111 | .predicates 112 | .push(parse_quote!(_A: #stimulus_trait_path)); 113 | let (impl_generics, _, where_clause) = impl_generics.split_for_impl(); 114 | 115 | quote! { 116 | #[automatically_derived] 117 | impl #impl_generics #with_alpha_trait_path<#alpha_type> for #ident #type_generics #where_clause { 118 | type Color = Self; 119 | type WithAlpha = #alpha_path; 120 | 121 | fn with_alpha(self, alpha: #alpha_type) -> Self::WithAlpha { 122 | #alpha_path { 123 | color: self, 124 | alpha 125 | } 126 | } 127 | 128 | fn without_alpha(self) -> Self::Color { 129 | self 130 | } 131 | 132 | fn split(self) -> (Self::Color, #alpha_type) { 133 | (self, #stimulus_trait_path::max_intensity()) 134 | } 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /palette_derive/src/cast/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::array_cast::derive as derive_array_cast; 2 | 3 | mod array_cast; 4 | -------------------------------------------------------------------------------- /palette_derive/src/convert/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::from_color_unclamped::derive as derive_from_color_unclamped; 2 | 3 | mod from_color_unclamped; 4 | pub mod util; 5 | -------------------------------------------------------------------------------- /palette_derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Derives traits from the [palette](https://crates.io/crates/palette) crate. 2 | 3 | use proc_macro::TokenStream; 4 | 5 | macro_rules! syn_try { 6 | ($e:expr) => { 7 | match $e { 8 | Ok(value) => value, 9 | Err(errors) => { 10 | trait IntoErrors { 11 | fn into_errors(self) -> Vec<::syn::parse::Error>; 12 | } 13 | impl IntoErrors for Vec<::syn::parse::Error> { 14 | fn into_errors(self) -> Vec<::syn::parse::Error> { 15 | self 16 | } 17 | } 18 | impl IntoErrors for ::syn::parse::Error { 19 | fn into_errors(self) -> Vec<::syn::parse::Error> { 20 | vec![self] 21 | } 22 | } 23 | 24 | let errors: ::proc_macro2::TokenStream = IntoErrors::into_errors(errors) 25 | .iter() 26 | .map(::syn::parse::Error::to_compile_error) 27 | .collect(); 28 | return ::proc_macro::TokenStream::from(errors); 29 | } 30 | } 31 | }; 32 | } 33 | 34 | mod alpha; 35 | mod cast; 36 | mod color_types; 37 | mod convert; 38 | mod meta; 39 | mod util; 40 | 41 | #[proc_macro_derive(WithAlpha, attributes(palette))] 42 | pub fn derive_with_alpha(tokens: TokenStream) -> TokenStream { 43 | syn_try!(alpha::derive_with_alpha(tokens)) 44 | } 45 | 46 | #[proc_macro_derive(FromColorUnclamped, attributes(palette))] 47 | pub fn derive_from_color_unclamped(tokens: TokenStream) -> TokenStream { 48 | syn_try!(convert::derive_from_color_unclamped(tokens)) 49 | } 50 | 51 | #[proc_macro_derive(ArrayCast, attributes(palette))] 52 | pub fn derive_array_cast(tokens: TokenStream) -> TokenStream { 53 | syn_try!(cast::derive_array_cast(tokens)) 54 | } 55 | -------------------------------------------------------------------------------- /palette_derive/src/meta/field_attributes.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | 3 | use syn::{spanned::Spanned, Expr, ExprLit}; 4 | use syn::{Lit, Meta, MetaNameValue, Type}; 5 | 6 | use super::{assert_path_meta, FieldAttributeArgumentParser, IdentOrIndex}; 7 | 8 | #[derive(Default)] 9 | pub struct FieldAttributes { 10 | pub alpha_property: Option<(IdentOrIndex, Type)>, 11 | pub zero_size_fields: HashSet, 12 | pub type_substitutes: HashMap, 13 | } 14 | 15 | impl FieldAttributeArgumentParser for FieldAttributes { 16 | fn argument( 17 | &mut self, 18 | field_name: &IdentOrIndex, 19 | ty: &Type, 20 | argument: Meta, 21 | ) -> Result<(), Vec> { 22 | let argument_name = argument.path().get_ident().map(ToString::to_string); 23 | 24 | match argument_name.as_deref() { 25 | Some("alpha") => { 26 | assert_path_meta(&argument).map_err(|error| vec![error])?; 27 | self.alpha_property = Some((field_name.clone(), ty.clone())); 28 | } 29 | Some("unsafe_same_layout_as") => { 30 | let substitute = if let Meta::NameValue(MetaNameValue { 31 | value: 32 | Expr::Lit(ExprLit { 33 | lit: Lit::Str(string), 34 | .. 35 | }), 36 | .. 37 | }) = argument 38 | { 39 | string.parse().map_err(|error| vec![error])? 40 | } else { 41 | return Err(vec![::syn::parse::Error::new( 42 | argument.span(), 43 | "expected `unsafe_same_layout_as = \"SomeType\"`", 44 | )]); 45 | }; 46 | 47 | self.type_substitutes.insert(field_name.clone(), substitute); 48 | } 49 | Some("unsafe_zero_sized") => { 50 | assert_path_meta(&argument).map_err(|error| vec![error])?; 51 | self.zero_size_fields.insert(field_name.clone()); 52 | } 53 | _ => { 54 | return Err(vec![::syn::parse::Error::new( 55 | argument.span(), 56 | "unknown field attribute", 57 | )]); 58 | } 59 | } 60 | 61 | Ok(()) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /palette_derive/src/util.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Span, TokenStream}; 2 | use quote::quote; 3 | use syn::{parse_quote, Ident, Type}; 4 | 5 | pub fn path<'a, P: AsRef<[&'a str]>>(path: P, internal: bool) -> TokenStream { 6 | let path = path 7 | .as_ref() 8 | .iter() 9 | .map(|&ident| Ident::new(ident, Span::call_site())); 10 | 11 | if internal { 12 | quote! {crate::#(#path)::*} 13 | } else { 14 | let crate_name = find_crate_name(); 15 | quote! {#crate_name::#(#path)::*} 16 | } 17 | } 18 | 19 | pub fn path_type(path: &[&str], internal: bool) -> Type { 20 | let path = path 21 | .iter() 22 | .map(|&ident| Ident::new(ident, Span::call_site())); 23 | 24 | if internal { 25 | parse_quote! {crate::#(#path)::*} 26 | } else { 27 | let crate_name = find_crate_name(); 28 | parse_quote! {#crate_name::#(#path)::*} 29 | } 30 | } 31 | 32 | #[cfg(feature = "find-crate")] 33 | fn find_crate_name() -> Ident { 34 | use find_crate::Error; 35 | 36 | match find_crate::find_crate(|name| name == "palette") { 37 | Ok(package) => Ident::new(&package.name, Span::call_site()), 38 | Err(Error::NotFound) => Ident::new("palette", Span::call_site()), 39 | Err(error) => panic!( 40 | "error when trying to find the name of the `palette` crate: {}", 41 | error 42 | ), 43 | } 44 | } 45 | 46 | #[cfg(not(feature = "find-crate"))] 47 | fn find_crate_name() -> Ident { 48 | Ident::new("palette", Span::call_site()) 49 | } 50 | --------------------------------------------------------------------------------