├── .github └── workflows │ └── test.yml ├── .gitignore ├── Changes ├── LICENSE ├── META6.json ├── README.md ├── dist.ini ├── examples └── color.pl6 ├── lib ├── Color.rakumod └── Color │ ├── Conversion.rakumod │ ├── Manipulation.rakumod │ ├── Operators.rakumod │ └── Utilities.rakumod ├── logotype └── logo_32x32.png ├── t ├── 01-new-key-value.rakutest ├── 02-new-short-form.rakutest ├── 03-new-overflows.rakutest ├── 04-new-invalid.rakutest ├── 05-methods.rakutest ├── 06-operators.rakutest ├── 07-bugs-and-regressions.rakutest └── 08-alpha.rakutest └── xt └── meta.t /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | tags-ignore: 8 | - '*' 9 | pull_request: 10 | 11 | jobs: 12 | raku: 13 | strategy: 14 | matrix: 15 | os: 16 | - ubuntu-latest 17 | - macos-latest 18 | - windows-latest 19 | raku-version: 20 | - 'latest' 21 | runs-on: ${{ matrix.os }} 22 | steps: 23 | - uses: actions/checkout@v2 24 | - uses: Raku/setup-raku@v1 25 | with: 26 | raku-version: ${{ matrix.raku-version }} 27 | - name: Install App::Prove6 28 | run: zef install --/test App::Prove6 29 | - name: Run Tests 30 | run: prove6 -l t 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .precomp/ 2 | /Color-* 3 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | Revision history for Color 2 | 3 | {{$NEXT}} 4 | 5 | 1.004001 2022-02-20T15:45:24+01:00 6 | - Remove now incorrect mention of Color::Operators 7 | 8 | 1.004 2022-02-20T15:35:37+01:00 9 | - Operators are now exported by default 10 | - Also tighten up the tests a bit with plans! 11 | 12 | 1.003 2022-02-20T15:09:41+01:00 13 | - First version in the zef ecosystem 14 | 15 | 1.002008 2020-03-30 (Vadim Belman - vrurg) 16 | - Make class Color inheritable: bless the invokant class and preserve all 17 | named parameters. 18 | 19 | 1.002007 2019-11-15 (David Warring - dwarring) 20 | - Added hsla and hsva conversion formats. Both fairly trivial; they 21 | just carry an alpha channel. 22 | 23 | 1.002005 2017-12-06 (by Marcel Timmerman - MARTIMM on github) 24 | - Added .alpha method to get and set alpha channel 25 | - Modified lighten, darken, (de)saturate, invert and rotate to return the 26 | color with the transparency level of the input color. The attribute alpha-math 27 | is copied as well. 28 | 29 | 1.002001 2015-12-06 30 | - Added .hex4 method (#1) 31 | 32 | 1.001001 2015-11-16 33 | - First version released on an unsuspecting world 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The Artistic License 2.0 2 | 3 | Copyright (c) 2000-2006, The Perl Foundation. 4 | 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | This license establishes the terms under which a given free software 11 | Package may be copied, modified, distributed, and/or redistributed. 12 | The intent is that the Copyright Holder maintains some artistic 13 | control over the development of that Package while still keeping the 14 | Package available as open source and free software. 15 | 16 | You are always permitted to make arrangements wholly outside of this 17 | license directly with the Copyright Holder of a given Package. If the 18 | terms of this license do not permit the full use that you propose to 19 | make of the Package, you should contact the Copyright Holder and seek 20 | a different licensing arrangement. 21 | 22 | Definitions 23 | 24 | "Copyright Holder" means the individual(s) or organization(s) 25 | named in the copyright notice for the entire Package. 26 | 27 | "Contributor" means any party that has contributed code or other 28 | material to the Package, in accordance with the Copyright Holder's 29 | procedures. 30 | 31 | "You" and "your" means any person who would like to copy, 32 | distribute, or modify the Package. 33 | 34 | "Package" means the collection of files distributed by the 35 | Copyright Holder, and derivatives of that collection and/or of 36 | those files. A given Package may consist of either the Standard 37 | Version, or a Modified Version. 38 | 39 | "Distribute" means providing a copy of the Package or making it 40 | accessible to anyone else, or in the case of a company or 41 | organization, to others outside of your company or organization. 42 | 43 | "Distributor Fee" means any fee that you charge for Distributing 44 | this Package or providing support for this Package to another 45 | party. It does not mean licensing fees. 46 | 47 | "Standard Version" refers to the Package if it has not been 48 | modified, or has been modified only in ways explicitly requested 49 | by the Copyright Holder. 50 | 51 | "Modified Version" means the Package, if it has been changed, and 52 | such changes were not explicitly requested by the Copyright 53 | Holder. 54 | 55 | "Original License" means this Artistic License as Distributed with 56 | the Standard Version of the Package, in its current version or as 57 | it may be modified by The Perl Foundation in the future. 58 | 59 | "Source" form means the source code, documentation source, and 60 | configuration files for the Package. 61 | 62 | "Compiled" form means the compiled bytecode, object code, binary, 63 | or any other form resulting from mechanical transformation or 64 | translation of the Source form. 65 | 66 | 67 | Permission for Use and Modification Without Distribution 68 | 69 | (1) You are permitted to use the Standard Version and create and use 70 | Modified Versions for any purpose without restriction, provided that 71 | you do not Distribute the Modified Version. 72 | 73 | 74 | Permissions for Redistribution of the Standard Version 75 | 76 | (2) You may Distribute verbatim copies of the Source form of the 77 | Standard Version of this Package in any medium without restriction, 78 | either gratis or for a Distributor Fee, provided that you duplicate 79 | all of the original copyright notices and associated disclaimers. At 80 | your discretion, such verbatim copies may or may not include a 81 | Compiled form of the Package. 82 | 83 | (3) You may apply any bug fixes, portability changes, and other 84 | modifications made available from the Copyright Holder. The resulting 85 | Package will still be considered the Standard Version, and as such 86 | will be subject to the Original License. 87 | 88 | 89 | Distribution of Modified Versions of the Package as Source 90 | 91 | (4) You may Distribute your Modified Version as Source (either gratis 92 | or for a Distributor Fee, and with or without a Compiled form of the 93 | Modified Version) provided that you clearly document how it differs 94 | from the Standard Version, including, but not limited to, documenting 95 | any non-standard features, executables, or modules, and provided that 96 | you do at least ONE of the following: 97 | 98 | (a) make the Modified Version available to the Copyright Holder 99 | of the Standard Version, under the Original License, so that the 100 | Copyright Holder may include your modifications in the Standard 101 | Version. 102 | 103 | (b) ensure that installation of your Modified Version does not 104 | prevent the user installing or running the Standard Version. In 105 | addition, the Modified Version must bear a name that is different 106 | from the name of the Standard Version. 107 | 108 | (c) allow anyone who receives a copy of the Modified Version to 109 | make the Source form of the Modified Version available to others 110 | under 111 | 112 | (i) the Original License or 113 | 114 | (ii) a license that permits the licensee to freely copy, 115 | modify and redistribute the Modified Version using the same 116 | licensing terms that apply to the copy that the licensee 117 | received, and requires that the Source form of the Modified 118 | Version, and of any works derived from it, be made freely 119 | available in that license fees are prohibited but Distributor 120 | Fees are allowed. 121 | 122 | 123 | Distribution of Compiled Forms of the Standard Version 124 | or Modified Versions without the Source 125 | 126 | (5) You may Distribute Compiled forms of the Standard Version without 127 | the Source, provided that you include complete instructions on how to 128 | get the Source of the Standard Version. Such instructions must be 129 | valid at the time of your distribution. If these instructions, at any 130 | time while you are carrying out such distribution, become invalid, you 131 | must provide new instructions on demand or cease further distribution. 132 | If you provide valid instructions or cease distribution within thirty 133 | days after you become aware that the instructions are invalid, then 134 | you do not forfeit any of your rights under this license. 135 | 136 | (6) You may Distribute a Modified Version in Compiled form without 137 | the Source, provided that you comply with Section 4 with respect to 138 | the Source of the Modified Version. 139 | 140 | 141 | Aggregating or Linking the Package 142 | 143 | (7) You may aggregate the Package (either the Standard Version or 144 | Modified Version) with other packages and Distribute the resulting 145 | aggregation provided that you do not charge a licensing fee for the 146 | Package. Distributor Fees are permitted, and licensing fees for other 147 | components in the aggregation are permitted. The terms of this license 148 | apply to the use and Distribution of the Standard or Modified Versions 149 | as included in the aggregation. 150 | 151 | (8) You are permitted to link Modified and Standard Versions with 152 | other works, to embed the Package in a larger work of your own, or to 153 | build stand-alone binary or bytecode versions of applications that 154 | include the Package, and Distribute the result without restriction, 155 | provided the result does not expose a direct interface to the Package. 156 | 157 | 158 | Items That are Not Considered Part of a Modified Version 159 | 160 | (9) Works (including, but not limited to, modules and scripts) that 161 | merely extend or make use of the Package, do not, by themselves, cause 162 | the Package to be a Modified Version. In addition, such works are not 163 | considered parts of the Package itself, and are not subject to the 164 | terms of this license. 165 | 166 | 167 | General Provisions 168 | 169 | (10) Any use, modification, and distribution of the Standard or 170 | Modified Versions is governed by this Artistic License. By using, 171 | modifying or distributing the Package, you accept this license. Do not 172 | use, modify, or distribute the Package, if you do not accept this 173 | license. 174 | 175 | (11) If your Modified Version has been derived from a Modified 176 | Version made by someone other than you, you are nevertheless required 177 | to ensure that your Modified Version complies with the requirements of 178 | this license. 179 | 180 | (12) This license does not grant you the right to use any trademark, 181 | service mark, tradename, or logo of the Copyright Holder. 182 | 183 | (13) This license includes the non-exclusive, worldwide, 184 | free-of-charge patent license to make, have made, use, offer to sell, 185 | sell, import and otherwise transfer the Package with respect to any 186 | patent claims licensable by the Copyright Holder that are necessarily 187 | infringed by the Package. If you institute patent litigation 188 | (including a cross-claim or counterclaim) against any party alleging 189 | that the Package constitutes direct or contributory patent 190 | infringement, then this Artistic License to you shall terminate on the 191 | date that such litigation is filed. 192 | 193 | (14) Disclaimer of Warranty: 194 | THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS 195 | IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED 196 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR 197 | NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL 198 | LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL 199 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 200 | DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF 201 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 202 | 203 | -------------------------------------------------------------------------------- /META6.json: -------------------------------------------------------------------------------- 1 | { 2 | "auth": "zef:raku-community-modules", 3 | "authors": [ 4 | "Zoffix Znet" 5 | ], 6 | "build-depends": [ 7 | ], 8 | "depends": [ 9 | ], 10 | "description": "Format conversion, manipulation, and math operations on colors", 11 | "license": "Artistic-2.0", 12 | "name": "Color", 13 | "perl": "6.c", 14 | "provides": { 15 | "Color": "lib/Color.rakumod", 16 | "Color::Conversion": "lib/Color/Conversion.rakumod", 17 | "Color::Manipulation": "lib/Color/Manipulation.rakumod", 18 | "Color::Operators": "lib/Color/Operators.rakumod", 19 | "Color::Utilities": "lib/Color/Utilities.rakumod" 20 | }, 21 | "resources": [ 22 | ], 23 | "source-url": "https://github.com/raku-community-modules/Color.git", 24 | "support": { 25 | "source": "git://github.com/raku-community-modules/Color.git" 26 | }, 27 | "tags": [ 28 | ], 29 | "test-depends": [ 30 | ], 31 | "version": "1.004001" 32 | } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Actions Status](https://github.com/raku-community-modules/Color/workflows/test/badge.svg)](https://github.com/raku-community-modules/Color/actions) 2 | 3 | NAME 4 | ==== 5 | 6 | Color - Format conversion, manipulation, and math operations on colors 7 | 8 | SYNOPSIS 9 | ======== 10 | 11 | ```raku 12 | use Color; 13 | 14 | my $white = Color.new(255, 255, 255); 15 | my $almost_black = Color.new('#111'); 16 | say Color.new(:hsv<152 80 50>).hex; # convert HSV to HEX 17 | say "$white is way lighter than $almost_black"; 18 | 19 | my $lighter_pink = Color.new('#ED60A2').lighten(20); 20 | my $lighter_pink = Color.new('#ED60A2') ◐ 20; # same as above 21 | 22 | my $saturated_pink = Color.new('#ED60A2').saturate(20); 23 | my $saturated_pink = Color.new('#ED60A2') 🞉 20; # same as above 24 | 25 | # Create an inverted colour scheme: 26 | $_ = .invert for @colours_in_my_colourscheme; 27 | 28 | # Some ops to use on Color objects 29 | my $gray = $white / 2; 30 | say $gray.hex; # prints "#808080" 31 | say $almost_black + 25; # prints "42, 42, 42" 32 | ``` 33 | 34 | DESCRIPTION 35 | =========== 36 | 37 | This module allows you to perform mathematical operations on RGB color tuples, as well as convert them into other color formats, like hex, and manipulate them by, for example, making them lighter, darker, or more or less saturated. 38 | 39 | CONSTRUCTOR 40 | =========== 41 | 42 | `new` 43 | ----- 44 | 45 | ```raku 46 | my $rgb = Color.new('abc'); 47 | Color.new('#abc'); 48 | Color.new('face'); 49 | Color.new('#face'); 50 | Color.new('abcdef'); 51 | Color.new('#abcdef'); 52 | Color.new('abcdefaa'); 53 | Color.new('#abcdefaa'); 54 | Color.new(:hex); # same applies to all other hex variants 55 | Color.new( 255, 100, 25 ); # RGB 56 | Color.new( .5, .1, .3, .4 ); # CMYK 57 | Color.new( rgb => [ 255, 100, 25 ] ); 58 | Color.new(:rgb<255 100 25>); # same works on other formats 59 | Color.new( rgbd => [.086, .165, .282] ); # decimal RGB 60 | Color.new( rgba => [ 22, 42, 72, 88 ] ); 61 | Color.new( rgbad => [ .086, .165, .282, .345 ] ); 62 | Color.new( cmyk => [.55, .25, .85, .12] ); 63 | Color.new( hsl => [ 72, 78, 65] ); 64 | Color.new( hsla => [ 72, 78, 65, 42] ); 65 | Color.new( hsv => [ 90, 60, 70] ); 66 | Color.new( hsva => [ 90, 60, 70, 88] ); 67 | ``` 68 | 69 | Creates new `Color` object. All of the above formats are supported. **Note:** internally, the color will be converted to RGBA, which might incur slight precision loss when converting from other formats. 70 | 71 | ATTRIBUTES 72 | ========== 73 | 74 | `alpha-math` 75 | ------------ 76 | 77 | ```raku 78 | my $c = Color.new('abc'); 79 | $c.alpha-math = True; 80 | 81 | my $c = Color.new('abca'); 82 | $c.alpha-math = False; 83 | ``` 84 | 85 | Boolean. Specifies whether operator math from `Color::Operators` should affect the alpha channel. Colors constructed from RGBA automatically get this attribute set to `True`, rest of formats have it set as `False`. 86 | 87 | MANIPULATION METHODS 88 | ==================== 89 | 90 | `alpha` 91 | ======= 92 | 93 | ```raku 94 | my Color $c .= new('#ff0088'); 95 | say $c.alpha; # OUTPUT: 255 96 | $c.alpha(128); 97 | say $c.alpha-math; # OUTPUT: True 98 | ``` 99 | 100 | Get or set the alpha channel value. 101 | 102 | `darken` 103 | -------- 104 | 105 | ```raku 106 | say $c.darken(10).cmyk; # darken by 10% 107 | ``` 108 | 109 | Creates a new `Color` object that is darkened by the percentage given as the argument. 110 | 111 | `desaturate` 112 | ------------ 113 | 114 | ```raku 115 | say $c.desaturate(20).cmyk; 116 | ``` 117 | 118 | Creates a new `Color` object that is desaturated by the percentage given as the argument. 119 | 120 | `invert` 121 | -------- 122 | 123 | ```raku 124 | say $c.invert.cmyk; 125 | ``` 126 | 127 | Creates a new `Color` object that is inverted (black becomes white, etc). 128 | 129 | `lighten` 130 | --------- 131 | 132 | ```raku 133 | say $c.lighten(10).cmyk; # lighten by 10% 134 | ``` 135 | 136 | Creates a new `Color` object that is lightened by the percentage given as the argument. 137 | 138 | `saturate` 139 | ---------- 140 | 141 | ```raku 142 | say $c.saturate(20).cmyk; 143 | ``` 144 | 145 | Creates a new `Color` object that is saturated by the percentage given as the argument. 146 | 147 | `rotate` 148 | -------- 149 | 150 | ```raku 151 | $inverse = $c.rotate( 180 ); 152 | ``` 153 | 154 | Creates a new `Color` object, rotated around the HSL color wheel by the angle $α (in degrees). 155 | 156 | For all methods `darken`, `desaturate`, `invert`, `lighten`, `saturate` and `rotate` the colors will have their alpha channel copied from the input color. The attribute alpha-math is copied as well. 157 | 158 | CONVERSION METHODS 159 | ================== 160 | 161 | `to-string` 162 | ----------- 163 | 164 | ```raku 165 | $c.to-string('cmyk'); # cmyk(0.954955, 0.153153, 0, 0.129412) 166 | $c.to-string('hsl'); # hsl(189.622642, 91.379310, 45.490196) 167 | $c.to-string('hsla'); # hsl(189.622642, 91.379310, 45.490196, 255) 168 | $c.to-string('hsv'); # hsv(189.622642, 95.495495, 87.058824) 169 | $c.to-string('hsva'); # hsva(189.622642, 95.495495, 87.058824, 255) 170 | $c.to-string('rgb'); # rgb(10, 188, 222) 171 | $c.to-string('rgba'); # rgba(10, 188, 222, 255) 172 | $c.to-string('rgbd'); # rgb(0.039216, 0.737255, 0.870588) 173 | $c.to-string('rgbad');# rgba(0.039216, 0.737255, 0.870588, 1) 174 | $c.to-string('hex'); # #0ABCDE 175 | $c.to-string('hex3'); # #1CE 176 | $c.to-string('hex8'); # #0ABCDEFF 177 | ``` 178 | 179 | Converts the color to the format given by the argument and returns a string representation of it. See above for the format of the string for each color format. 180 | 181 | **Note:** the `.gist` and `.Str` methods of the `Color` object are equivalent to `.to-string('hex')`. 182 | 183 | `cmyk` 184 | ------ 185 | 186 | ```raku 187 | say $c.cmyk; # (<106/111>, <17/111>, 0.0, <11/85>) 188 | ``` 189 | 190 | Converts the color to CMYK format and returns a list containing each color (ranging `0`..`1`). 191 | 192 | `hex` 193 | ----- 194 | 195 | ```raku 196 | say $c.hex; # (0A BC DE); 197 | ``` 198 | 199 | Returns a list of 3 2-digit hex numbers representing the color. 200 | 201 | `hex3` 202 | ------ 203 | 204 | ```raku 205 | say $c.hex3; # (1 C E); 206 | ``` 207 | 208 | Returns a list of 3 1-digit hex numbers representing the color. They will be rounded and they need to be doubled (i.e. the above would be `11CCEE`) to get the actual value. 209 | 210 | `hex4` 211 | ------ 212 | 213 | ```raku 214 | say $c.hex4; # (1 C E F); 215 | ``` 216 | 217 | Returns a list of 4 1-digit hex numbers representing the color. They will be rounded and they need to be doubled (i.e. the above would be `11CCEEFF`) to get the actual value. 218 | 219 | `hex8` 220 | ------ 221 | 222 | ```raku 223 | say $c.hex8; # (0A BC DE FF); 224 | ``` 225 | 226 | Returns a list of 4 2-digit hex numbers representing the color, including the Alpha space. 227 | 228 | `hsl` 229 | ----- 230 | 231 | ```raku 232 | say $c.hsl; # (<10050/53>, <10600/111>, <1480/17>), 233 | ``` 234 | 235 | Converts the colour to HSL format and returns the three values (hue, saturation, lightness). The S/L are returned as percentages, not decimals, so 50% saturation is returned as `50`, not `.5`. 236 | 237 | `hsla` 238 | ------ 239 | 240 | ```raku 241 | say $c.hsla; # (<10050/53>, <10600/111>, <1480/17>, 255), 242 | ``` 243 | 244 | Converts the color to HSL format and returns the three values, and alpha channel. 245 | 246 | `hsv` 247 | ----- 248 | 249 | ```raku 250 | say $c.hsv; # (<10050/53>, <10600/111>, <1480/17>), 251 | ``` 252 | 253 | Converts the colour to HSV format and returns the three values (hue, saturation, value). The S/V are returned as percentages, not decimals, so 50% saturation is returned as `50`, not `.5`. 254 | 255 | `hsva` 256 | ------ 257 | 258 | ```raku 259 | say $c.hsva; # (<10050/53>, <10600/111>, <1480/17>, 255), 260 | ``` 261 | 262 | Converts the color to HSV format and returns the three values, and alpha channel. 263 | 264 | `rgb` 265 | ----- 266 | 267 | ```raku 268 | say $c.rgb; # (10, 188, 222) 269 | ``` 270 | 271 | Converts the color to RGB format and returns a list of the three colors. 272 | 273 | `rgba` 274 | ------ 275 | 276 | ```raku 277 | say $c.rgba; # (10, 188, 222, 255); 278 | ``` 279 | 280 | Converts the color to RGBA format and returns a list of the three colors, and alpha channel. 281 | 282 | `rgbd` 283 | ------ 284 | 285 | ```raku 286 | say $c.rgbd; # (<2/51>, <188/255>, <74/85>) 287 | ``` 288 | 289 | Converts the color to RGB format ranging `0`..`1` and returns a list of the three colours. 290 | 291 | `rgbad` 292 | ------- 293 | 294 | ```raku 295 | say $c.rgbad; # (<2/51>, <188/255>, <74/85>, 1.0) 296 | ``` 297 | 298 | Converts the color to RGBA format ranging `0`..`1` and returns a list of the three colours, and alpha channel. 299 | 300 | OPERATORS 301 | ========= 302 | 303 | `+` 304 | --- 305 | 306 | ```raku 307 | Color.new('424') + 10; 308 | 10 + Color.new('424'); 309 | Color.new('424') + Color.new('424'); 310 | ``` 311 | 312 | Add individual RGB values of each color. Plain numbers are added to each value. If [/alpha-math](/alpha-math) is turned on, alpha channel is affected as well. The operation returns a new `Color` object. 313 | 314 | `-` 315 | --- 316 | 317 | ```raku 318 | Color.new('424') - 10; 319 | 10 - Color.new('424'); 320 | Color.new('424') - Color.new('666'); 321 | ``` 322 | 323 | Subtract individual RGB values of each color. Plain numbers are subtracted from each value. If [/alpha-math](/alpha-math) is turned on, alpha channel is affected as well. The operation returns a new `Color` object. 324 | 325 | `*` 326 | --- 327 | 328 | ```raku 329 | Color.new('424') * 10; 330 | 10 * Color.new('424'); 331 | Color.new('424') * Color.new('424'); 332 | ``` 333 | 334 | Multiply individual RGB values of each color. Plain numbers are multiplied with each value. If [/alpha-math](/alpha-math) is turned on, alpha channel is affected as well. The operation returns a new `Color` object. 335 | 336 | `/` 337 | --- 338 | 339 | ```raku 340 | Color.new('424') / 10; 341 | Color.new('424') / 0; # doesn't die; sets values to 0 342 | 10 / Color.new('424'); 343 | Color.new('424') / Color.new('424'); 344 | ``` 345 | 346 | Divide individual RGB values of each color. Plain numbers are divided with each value. If [/alpha-math](/alpha-math) is turned on, alpha channel is affected as well. The operation returns a new `Color` object. Illegal operation of division by zero, doesn't die and simply sets the value to `0`. 347 | 348 | `◐` 349 | --- 350 | 351 | ```raku 352 | say $c ◐ 20; # lighten by 20% 353 | ``` 354 | 355 | `U+25D0 (e2 97 90): CIRCLE WITH LEFT HALF BLACK [◐]`. Same as `/lighten` 356 | 357 | `◑` 358 | --- 359 | 360 | ```raku 361 | say $c ◑ 20; # darken by 20% 362 | ``` 363 | 364 | `U+25D1 (e2 97 91): CIRCLE WITH RIGHT HALF BLACK [◑]`. Same as `/darken` 365 | 366 | `🞉` 367 | --- 368 | 369 | ```raku 370 | say $c 🞉 20; # saturate by 20% 371 | ``` 372 | 373 | `U+1F789 (f0 9f 9e 89): EXTREMELY HEAVY WHITE CIRCLE [🞉]`. Same as `/desaturate` 374 | 375 | `¡` 376 | --- 377 | 378 | ```raku 379 | say $c¡; # invert colour 380 | ``` 381 | 382 | `U+00A1 (c2 a1): INVERTED EXCLAMATION MARK [¡]`. Same as `/invert` 383 | 384 | STRINGIFICATION 385 | =============== 386 | 387 | ```raku 388 | say $c; 389 | say "$c"; 390 | ``` 391 | 392 | The `Color` object overrides `.Str` and `.gist` methods to be equivalent to `.to-string('hex')`. 393 | 394 | Functional interface 395 | ==================== 396 | 397 | The color conversion, manipulation and utility functions are defined within the modules `Color::Conversion`, `Color::Manipulation` and `Color::Utilities` and can be used without the OO interface. The names of functions are the same as those of methods. 398 | 399 | AUTHOR 400 | ====== 401 | 402 | Zoffix Znet 403 | 404 | Source can be located at: https://github.com/raku-community-modules/Color . Comments and Pull Requests are welcome. 405 | 406 | CONTRIBUTORS 407 | ============ 408 | 409 | Thanks to timotimo++, jnthn++, psch++, RabidGravy++, ab5tract++, moritz++, holli++, and anyone else who I forgot who helped me with questions on IRC. 410 | 411 | COPYRIGHT AND LICENSE 412 | ===================== 413 | 414 | Copyright 2015 - 2018 Zoffix Znet 415 | 416 | Copyright 2019 - 2022 Raku Community 417 | 418 | This library is free software; you can redistribute it and/or modify it under the Artistic License 2.0. 419 | 420 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = Color 2 | 3 | [ReadmeFromPod] 4 | ; enable = false 5 | filename = lib/Color.rakumod 6 | 7 | [UploadToZef] 8 | 9 | [PruneFiles] 10 | ; match = ^ 'xt/' 11 | 12 | [Badges] 13 | provider = github-actions/test 14 | -------------------------------------------------------------------------------- /examples/color.pl6: -------------------------------------------------------------------------------- 1 | use v6; 2 | use Color; 3 | 4 | say Color.new(:hsv<152 80 50>).hex; # convert HSV to HEX 5 | my $white = Color.new(255, 255, 255); 6 | my $almost_black = Color.new('#111'); 7 | say "$white is way lighter than $almost_black"; 8 | -------------------------------------------------------------------------------- /lib/Color.rakumod: -------------------------------------------------------------------------------- 1 | use Color::Conversion; 2 | use Color::Utilities; 3 | use Color::Manipulation; 4 | 5 | class Color:ver<1.004001>:auth { 6 | subset ValidRGB of Real where 0 <= $_ <= 255; 7 | 8 | has ValidRGB $.r = 0; 9 | has ValidRGB $.g = 0; 10 | has ValidRGB $.b = 0; 11 | has ValidRGB $.a = 255; 12 | has Bool $.alpha-math is rw = False; 13 | 14 | ########################################################################## 15 | # Call forms 16 | ########################################################################## 17 | proto method new(|) { * } 18 | 19 | multi method new(Real:D :$r, Real:D :$g, Real:D :$b, Real:D :$a, *%c) { 20 | self.bless: :$r, :$g, :$b, :$a, |%c 21 | } 22 | 23 | multi method new(Real:D $c, Real:D $m, Real:D $y, Real:D $k, *%c) { 24 | self.new: cmyk => [ $c, $m, $y, $k ], |%c 25 | } 26 | 27 | multi method new(Real:D $r, Real:D $g, Real:D $b, *%c ) { 28 | self.bless: :$r, :$g, :$b, |%c 29 | } 30 | 31 | multi method new(Real:D :$r, Real:D :$g, Real:D :$b, *%c) { 32 | self.bless: :$r, :$g, :$b, |%c 33 | } 34 | 35 | multi method new(Str:D $hex is copy where 8 <= .chars <= 9, *%c) { 36 | self.bless: :alpha-math, |parse-hex $hex, |%c 37 | } 38 | 39 | multi method new(Str:D $hex is copy where 3 <= .chars <= 7, *%c) { 40 | self.bless: |parse-hex $hex, |%c 41 | } 42 | 43 | multi method new(Str:D :$hex, *%c) { self.new: $hex, |%c } 44 | 45 | multi method new( 46 | Array() :$rgb where .defined && $_ ~~ [Real, Real, Real], *%c 47 | ) { 48 | clip-to 0, $_, 255 for @$rgb; 49 | self.bless: r => $rgb[0], g => $rgb[1], b => $rgb[2], |%c 50 | } 51 | 52 | multi method new( 53 | Array() :$rgbd where .defined && $_ ~~ [Real, Real, Real], *%c 54 | ) { 55 | clip-to 0, $_, 1 for @$rgbd; 56 | self.bless: 57 | r => $rgbd[0] * 255, 58 | g => $rgbd[1] * 255, 59 | b => $rgbd[2] * 255, 60 | |%c 61 | } 62 | 63 | multi method new( 64 | Array() :$rgba where .defined && $_ ~~ [Real, Real, Real, Real], *%c 65 | ) { 66 | clip-to 0, $_, 255 for @$rgba; 67 | self.bless: 68 | r => $rgba[0], 69 | g => $rgba[1], 70 | b => $rgba[2], 71 | a => $rgba[3], 72 | :alpha-math, 73 | |%c 74 | } 75 | 76 | multi method new( 77 | Array() :$rgbad where .defined && $_ ~~ [Real, Real, Real, Real], *%c 78 | ) { 79 | clip-to 0, $_, 1 for @$rgbad; 80 | self.bless: 81 | r => $rgbad[0] * 255, 82 | g => $rgbad[1] * 255, 83 | b => $rgbad[2] * 255, 84 | a => $rgbad[3] * 255, 85 | :alpha-math, 86 | |%c 87 | } 88 | 89 | multi method new( 90 | Array() :$cmyk where .defined && $_ ~~ [Real, Real, Real, Real], *%c 91 | ) { 92 | self.bless: |cmyk2rgb $cmyk, |%c 93 | } 94 | 95 | multi method new( 96 | Array() :$hsl where .defined && $_ ~~ [Real, Real, Real], *%c 97 | ) { 98 | self.bless: |hsl2rgb $hsl, |%c 99 | } 100 | 101 | multi method new( 102 | Array() :$hsla where .defined && $_ ~~ [Real, Real, Real, Real], *%c 103 | ) { 104 | self.bless: |hsla2rgba $hsla, |%c 105 | } 106 | 107 | multi method new(Array() :$hsv where .defined && $_ ~~ [Real, Real, Real], *%c 108 | ) { 109 | self.bless: |hsv2rgb $hsv, |%c 110 | } 111 | 112 | multi method new( 113 | Array() :$hsva where .defined && $_ ~~ [Real, Real, Real, Real], *%c 114 | ) { 115 | self.bless: |hsva2rgba $hsva, |%c 116 | } 117 | 118 | ########################################################################## 119 | # Methods 120 | ########################################################################## 121 | method cmyk() { rgb2cmyk $.r, $.g, $.b } 122 | method hsl() { rgb2hsl $.r, $.g, $.b } 123 | method hsla() { rgba2hsla $.r, $.g, $.b, $.a } 124 | method hsv() { rgb2hsv $.r, $.g, $.b } 125 | method hsva() { rgba2hsva $.r, $.g, $.b, $.a } 126 | method rgb() { map { .round }, $.r, $.g, $.b } 127 | method rgba() { map { .round }, $.r, $.g, $.b, $.a } 128 | method rgbd() { $.r/255, $.g/255, $.b/255 } 129 | method rgbad() { $.r/255, $.g/255, $.b/255, $.a/255 } 130 | method hex() { ($.r, $.g, $.b).map: *.fmt('%02X') } 131 | 132 | method hex3() { 133 | # Round the hex; the 247 bit is so we don't get hex 100 for high RGBs 134 | ($.r, $.g, $.b).map: { 135 | ($_ min 247).round(16).base(16).substr(0,1) 136 | } 137 | } 138 | method hex4() { 139 | # Round the hex; the 247 bit is so we don't get hex 100 for high RGBs 140 | ($.r, $.g, $.b, $.a).map: { 141 | ($_ min 247).round(16).base(16).substr(0,1) 142 | } 143 | } 144 | method hex8() { ($.r, $.g, $.b, $.a).map: *.fmt('%02X') } 145 | 146 | method darken(Real $Δ) { self.lighten(-$Δ); } 147 | method lighten(Real $Δ) { 148 | my Color $c := self.new: hsl => [lighten( |self.hsl, $Δ )]; 149 | $c.alpha($!a) unless $!a == 255; 150 | $c.alpha-math = $!alpha-math; 151 | $c 152 | } 153 | method desaturate(Real $Δ) { self.saturate(-$Δ) } 154 | method saturate(Real $Δ) { 155 | my Color $c := self.new: hsl => [saturate( |self.hsl, $Δ )]; 156 | $c.alpha($!a) unless $!a == 255; 157 | $c.alpha-math = $!alpha-math; 158 | $c 159 | } 160 | method invert() { 161 | my Color $c := self.new: 255-$.r, 255-$.g, 255-$.b; 162 | $c.alpha($!a) unless $!a == 255; 163 | $c.alpha-math = $!alpha-math; 164 | $c 165 | } 166 | method rotate(Real $α) { 167 | my Color $c := self.new: |rotate $.r, $.g, $.b, $α; 168 | $c.alpha($!a) unless $!a == 255; 169 | $c.alpha-math = $!alpha-math; 170 | $c 171 | } 172 | 173 | method to-string(Str $type = 'hex') { 174 | do given $type { 175 | when m:i/^ hex \d? $/ { '#' ~ self."$type"().join('') } 176 | when m:i/^ [ rgba?d? | cmyk | hs<[vl]>a? ] $/ { 177 | ( my $out_type = $type ) ~~ s/d$//; 178 | "$out_type\(" ~ self."$type"().join(", ") ~ ")" 179 | } 180 | when * { fail "Invalid format ($type) to convert to specified"; } 181 | } 182 | } 183 | 184 | # MARTIMM: better methods to get and set alpha channel 185 | multi method alpha(ValidRGB $alpha) { $!a = $alpha; $!alpha-math = True } 186 | multi method alpha() { $!a } 187 | 188 | method gist { self.to-string('hex') } 189 | method Str { self.to-string('hex') } 190 | } 191 | 192 | ########################################################################## 193 | # Operators 194 | ########################################################################## 195 | multi infix:<+>(Color $c1, Real $c2) is export { Color.new(|op $c1, $c2, '+') } 196 | multi infix:<+>(Real $c1, Color $c2) is export { Color.new(|op $c1, $c2, '+') } 197 | multi infix:<+>(Color $c1, Color $c2) is export { Color.new(|op $c1, $c2, '+') } 198 | multi infix:<->(Color $c1, Real $c2) is export { Color.new(|op $c1, $c2, '-') } 199 | multi infix:<->(Real $c1, Color $c2) is export { Color.new(|op $c1, $c2, '-') } 200 | multi infix:<->(Color $c1, Color $c2) is export { Color.new(|op $c1, $c2, '-') } 201 | multi infix:<*>(Color $c1, Real $c2) is export { Color.new(|op $c1, $c2, '*') } 202 | multi infix:<*>(Real $c1, Color $c2) is export { Color.new(|op $c1, $c2, '*') } 203 | multi infix:<*>(Color $c1, Color $c2) is export { Color.new(|op $c1, $c2, '*') } 204 | multi infix:(Color $c1, Real $c2) is export { Color.new(|op $c1, $c2, '/') } 205 | multi infix:(Real $c1, Color $c2) is export { Color.new(|op $c1, $c2, '/') } 206 | multi infix:(Color $c1, Color $c2) is export { Color.new(|op $c1, $c2, '/') } 207 | multi infix:<◐>(Color $c1, Real:D $Δ) is export { $c1.lighten($Δ) } 208 | multi infix:<◑>(Color $c1, Real:D $Δ) is export { $c1.darken($Δ) } 209 | multi infix:<🞅>(Color $c1, Real:D $Δ) is export { $c1.desaturate($Δ) } 210 | multi infix:<🞉>(Color $c1, Real:D $Δ) is export { $c1.saturate($Δ) } 211 | multi postfix:<¡>(Color $c1) is export { $c1.invert } 212 | 213 | ############################################################################## 214 | # Operator helpers 215 | ############################################################################## 216 | 217 | my sub op($obj1, $obj2, Str:D $op) { 218 | my %r; 219 | for { 220 | my $v1 = $obj1 ~~ Color ?? $obj1."$_"() !! $obj1; 221 | my $v2 = $obj2 ~~ Color ?? $obj2."$_"() !! $obj2; 222 | %r{$_} = ::('&infix:<' ~ $op ~ '>')($v1, $v2); 223 | %r{$_} = 0 if $op eq '/' and $v2 == 0; 224 | } 225 | 226 | %r = $obj1 ~~ Color ?? $obj1.a !! $obj2.a; 227 | if $obj1.?alpha-math or $obj2.?alpha-math { 228 | %r = ::('&infix:<' ~$op~ '>')($obj1.?a // $obj1, $obj2.?a // $obj2); 229 | %r = 0 230 | if $op eq '/' and ( $obj2 ~~ Color ?? $obj2.a == 0 !! $obj2 == 0 ); 231 | } 232 | 233 | clip-to 0, $_, 255 for values %r; 234 | %r 235 | } 236 | 237 | # See conversion formulas for CMYK and others here: 238 | # http://www.rapidtables.com/convert/color/cmyk-to-rgb.htm 239 | 240 | # ◐ 9680 25D0 CIRCLE WITH LEFT HALF BLACK 241 | # ◑ 9681 25D1 CIRCLE WITH RIGHT HALF BLACK 242 | # U+1F789 🞉 EXTREMELY HEAVY WHITE CIRCLE 243 | # U+1F785 🞅 f0 9f 9e 85 MEDIUM BOLD WHITE CIRCLE 244 | # 0xA1 ¡ INVERTED EXCLAMATION MARK 245 | # my $lighter = RGB.new('ccc') ◐ 10; 246 | # my $lighter = RGB.new('ccc') ◑ 10; 247 | # my $lighter = RGB.new('ccc') + 22.5; 248 | 249 | =begin pod 250 | 251 | =head1 NAME 252 | 253 | Color - Format conversion, manipulation, and math operations on colors 254 | 255 | =head1 SYNOPSIS 256 | 257 | =begin code :lang 258 | 259 | use Color; 260 | 261 | my $white = Color.new(255, 255, 255); 262 | my $almost_black = Color.new('#111'); 263 | say Color.new(:hsv<152 80 50>).hex; # convert HSV to HEX 264 | say "$white is way lighter than $almost_black"; 265 | 266 | my $lighter_pink = Color.new('#ED60A2').lighten(20); 267 | my $lighter_pink = Color.new('#ED60A2') ◐ 20; # same as above 268 | 269 | my $saturated_pink = Color.new('#ED60A2').saturate(20); 270 | my $saturated_pink = Color.new('#ED60A2') 🞉 20; # same as above 271 | 272 | # Create an inverted colour scheme: 273 | $_ = .invert for @colours_in_my_colourscheme; 274 | 275 | # Some ops to use on Color objects 276 | my $gray = $white / 2; 277 | say $gray.hex; # prints "#808080" 278 | say $almost_black + 25; # prints "42, 42, 42" 279 | 280 | =end code 281 | 282 | =head1 DESCRIPTION 283 | 284 | This module allows you to perform mathematical operations on RGB color tuples, 285 | as well as convert them into other color formats, like hex, and manipulate 286 | them by, for example, making them lighter, darker, or more or less saturated. 287 | 288 | =head1 CONSTRUCTOR 289 | 290 | =head2 C 291 | 292 | =begin code :lang 293 | 294 | my $rgb = Color.new('abc'); 295 | Color.new('#abc'); 296 | Color.new('face'); 297 | Color.new('#face'); 298 | Color.new('abcdef'); 299 | Color.new('#abcdef'); 300 | Color.new('abcdefaa'); 301 | Color.new('#abcdefaa'); 302 | Color.new(:hex); # same applies to all other hex variants 303 | Color.new( 255, 100, 25 ); # RGB 304 | Color.new( .5, .1, .3, .4 ); # CMYK 305 | Color.new( rgb => [ 255, 100, 25 ] ); 306 | Color.new(:rgb<255 100 25>); # same works on other formats 307 | Color.new( rgbd => [.086, .165, .282] ); # decimal RGB 308 | Color.new( rgba => [ 22, 42, 72, 88 ] ); 309 | Color.new( rgbad => [ .086, .165, .282, .345 ] ); 310 | Color.new( cmyk => [.55, .25, .85, .12] ); 311 | Color.new( hsl => [ 72, 78, 65] ); 312 | Color.new( hsla => [ 72, 78, 65, 42] ); 313 | Color.new( hsv => [ 90, 60, 70] ); 314 | Color.new( hsva => [ 90, 60, 70, 88] ); 315 | 316 | =end code 317 | 318 | Creates new C object. All of the above formats are supported. 319 | B internally, the color will be converted to RGBA, which might 320 | incur slight precision loss when converting from other formats. 321 | 322 | =head1 ATTRIBUTES 323 | 324 | =head2 C 325 | 326 | =begin code :lang 327 | 328 | my $c = Color.new('abc'); 329 | $c.alpha-math = True; 330 | 331 | my $c = Color.new('abca'); 332 | $c.alpha-math = False; 333 | 334 | =end code 335 | 336 | Boolean. Specifies whether operator math from C should affect 337 | the alpha channel. Colors constructed from RGBA automatically get this 338 | attribute set to C, rest of formats have it set as C. 339 | 340 | =head1 MANIPULATION METHODS 341 | 342 | =head1 C 343 | 344 | =begin code :lang 345 | 346 | my Color $c .= new('#ff0088'); 347 | say $c.alpha; # OUTPUT: 255 348 | $c.alpha(128); 349 | say $c.alpha-math; # OUTPUT: True 350 | 351 | =end code 352 | 353 | Get or set the alpha channel value. 354 | 355 | =head2 C 356 | 357 | =begin code :lang 358 | 359 | say $c.darken(10).cmyk; # darken by 10% 360 | 361 | =end code 362 | 363 | Creates a new C object that is darkened by the percentage given as the 364 | argument. 365 | 366 | =head2 C 367 | 368 | =begin code :lang 369 | 370 | say $c.desaturate(20).cmyk; 371 | 372 | =end code 373 | 374 | Creates a new C object that is desaturated by the percentage given as 375 | the argument. 376 | 377 | =head2 C 378 | 379 | =begin code :lang 380 | 381 | say $c.invert.cmyk; 382 | 383 | =end code 384 | 385 | Creates a new C object that is inverted (black becomes white, etc). 386 | 387 | =head2 C 388 | 389 | =begin code :lang 390 | 391 | say $c.lighten(10).cmyk; # lighten by 10% 392 | 393 | =end code 394 | 395 | Creates a new C object that is lightened by the percentage given as the 396 | argument. 397 | 398 | =head2 C 399 | 400 | =begin code :lang 401 | 402 | say $c.saturate(20).cmyk; 403 | 404 | =end code 405 | 406 | Creates a new C object that is saturated by the percentage given as 407 | the argument. 408 | 409 | =head2 C 410 | 411 | =begin code :lang 412 | 413 | $inverse = $c.rotate( 180 ); 414 | 415 | =end code 416 | 417 | Creates a new C object, rotated around the HSL color wheel 418 | by the angle $α (in degrees). 419 | 420 | For all methods C, C, C, C, C 421 | and C the colors will have their alpha channel copied from the 422 | input color. The attribute alpha-math is copied as well. 423 | 424 | =head1 CONVERSION METHODS 425 | 426 | =head2 C 427 | 428 | =begin code :lang 429 | 430 | $c.to-string('cmyk'); # cmyk(0.954955, 0.153153, 0, 0.129412) 431 | $c.to-string('hsl'); # hsl(189.622642, 91.379310, 45.490196) 432 | $c.to-string('hsla'); # hsl(189.622642, 91.379310, 45.490196, 255) 433 | $c.to-string('hsv'); # hsv(189.622642, 95.495495, 87.058824) 434 | $c.to-string('hsva'); # hsva(189.622642, 95.495495, 87.058824, 255) 435 | $c.to-string('rgb'); # rgb(10, 188, 222) 436 | $c.to-string('rgba'); # rgba(10, 188, 222, 255) 437 | $c.to-string('rgbd'); # rgb(0.039216, 0.737255, 0.870588) 438 | $c.to-string('rgbad');# rgba(0.039216, 0.737255, 0.870588, 1) 439 | $c.to-string('hex'); # #0ABCDE 440 | $c.to-string('hex3'); # #1CE 441 | $c.to-string('hex8'); # #0ABCDEFF 442 | 443 | =end code 444 | 445 | Converts the color to the format given by the argument and returns a string 446 | representation of it. See above for the format of the string for each 447 | color format. 448 | 449 | B the C<.gist> and C<.Str> methods of the C object 450 | are equivalent to C<.to-string('hex')>. 451 | 452 | =head2 C 453 | 454 | =begin code :lang 455 | 456 | say $c.cmyk; # (<106/111>, <17/111>, 0.0, <11/85>) 457 | 458 | =end code 459 | 460 | Converts the color to CMYK format and returns a list containing each color 461 | (ranging `0`..`1`). 462 | 463 | =head2 C 464 | 465 | =begin code :lang 466 | 467 | say $c.hex; # (0A BC DE); 468 | 469 | =end code 470 | 471 | Returns a list of 3 2-digit hex numbers representing the color. 472 | 473 | =head2 C 474 | 475 | =begin code :lang 476 | 477 | say $c.hex3; # (1 C E); 478 | 479 | =end code 480 | 481 | Returns a list of 3 1-digit hex numbers representing the color. They will 482 | be rounded and they need to be doubled (i.e. the above would be C<11CCEE>) 483 | to get the actual value. 484 | 485 | =head2 C 486 | 487 | =begin code :lang 488 | 489 | say $c.hex4; # (1 C E F); 490 | 491 | =end code 492 | 493 | Returns a list of 4 1-digit hex numbers representing the color. They will 494 | be rounded and they need to be doubled (i.e. the above would be C<11CCEEFF>) 495 | to get the actual value. 496 | 497 | =head2 C 498 | 499 | =begin code :lang 500 | 501 | say $c.hex8; # (0A BC DE FF); 502 | 503 | =end code 504 | 505 | Returns a list of 4 2-digit hex numbers representing the color, including 506 | the Alpha space. 507 | 508 | =head2 C 509 | 510 | =begin code :lang 511 | 512 | say $c.hsl; # (<10050/53>, <10600/111>, <1480/17>), 513 | 514 | =end code 515 | 516 | Converts the colour to HSL format and returns the three values 517 | (hue, saturation, lightness). The S/L are returned as percentages, not decimals, 518 | so 50% saturation is returned as C<50>, not C<.5>. 519 | 520 | =head2 C 521 | 522 | =begin code :lang 523 | 524 | say $c.hsla; # (<10050/53>, <10600/111>, <1480/17>, 255), 525 | 526 | =end code 527 | 528 | Converts the color to HSL format and returns the three values, 529 | and alpha channel. 530 | 531 | =head2 C 532 | 533 | =begin code :lang 534 | 535 | say $c.hsv; # (<10050/53>, <10600/111>, <1480/17>), 536 | 537 | =end code 538 | 539 | Converts the colour to HSV format and returns the three values 540 | (hue, saturation, value). The S/V are returned as percentages, not decimals, 541 | so 50% saturation is returned as C<50>, not C<.5>. 542 | 543 | =head2 C 544 | 545 | =begin code :lang 546 | 547 | say $c.hsva; # (<10050/53>, <10600/111>, <1480/17>, 255), 548 | 549 | =end code 550 | 551 | Converts the color to HSV format and returns the three values, 552 | and alpha channel. 553 | 554 | =head2 C 555 | 556 | =begin code :lang 557 | 558 | say $c.rgb; # (10, 188, 222) 559 | 560 | =end code 561 | 562 | Converts the color to RGB format and returns a list of the three colors. 563 | 564 | =head2 C 565 | 566 | =begin code :lang 567 | 568 | say $c.rgba; # (10, 188, 222, 255); 569 | 570 | =end code 571 | 572 | Converts the color to RGBA format and returns a list of the three colors, 573 | and alpha channel. 574 | 575 | =head2 C 576 | 577 | =begin code :lang 578 | 579 | say $c.rgbd; # (<2/51>, <188/255>, <74/85>) 580 | 581 | =end code 582 | 583 | Converts the color to RGB format ranging C<0>..C<1> and returns a list of 584 | the three colours. 585 | 586 | =head2 C 587 | 588 | =begin code :lang 589 | 590 | say $c.rgbad; # (<2/51>, <188/255>, <74/85>, 1.0) 591 | 592 | =end code 593 | 594 | Converts the color to RGBA format ranging C<0>..C<1> and returns a list of 595 | the three colours, and alpha channel. 596 | 597 | =head1 OPERATORS 598 | 599 | =head2 C<+> 600 | 601 | =begin code :lang 602 | 603 | Color.new('424') + 10; 604 | 10 + Color.new('424'); 605 | Color.new('424') + Color.new('424'); 606 | 607 | =end code 608 | 609 | Add individual RGB values of each color. Plain numbers are added to each 610 | value. If L is turned on, alpha channel is affected as well. 611 | The operation returns a new C object. 612 | 613 | =head2 C<-> 614 | 615 | =begin code :lang 616 | 617 | Color.new('424') - 10; 618 | 10 - Color.new('424'); 619 | Color.new('424') - Color.new('666'); 620 | 621 | =end code 622 | 623 | Subtract individual RGB values of each color. Plain numbers are subtracted 624 | from each value. If L is turned on, alpha channel is affected 625 | as well. The operation returns a new C object. 626 | 627 | =head2 C<*> 628 | 629 | =begin code :lang 630 | 631 | Color.new('424') * 10; 632 | 10 * Color.new('424'); 633 | Color.new('424') * Color.new('424'); 634 | 635 | =end code 636 | 637 | Multiply individual RGB values of each color. Plain numbers are multiplied 638 | with each value. If L is turned on, alpha channel is affected 639 | as well. The operation returns a new C object. 640 | 641 | =head2 C 642 | 643 | =begin code :lang 644 | 645 | Color.new('424') / 10; 646 | Color.new('424') / 0; # doesn't die; sets values to 0 647 | 10 / Color.new('424'); 648 | Color.new('424') / Color.new('424'); 649 | 650 | =end code 651 | 652 | Divide individual RGB values of each color. Plain numbers are divided 653 | with each value. If L is turned on, alpha channel is affected 654 | as well. The operation returns a new C object. Illegal operation 655 | of division by zero, doesn't die and simply sets the value to C<0>. 656 | 657 | =head2 C<◐> 658 | 659 | =begin code :lang 660 | 661 | say $c ◐ 20; # lighten by 20% 662 | 663 | =end code 664 | 665 | C. Same as C 666 | 667 | =head2 C<◑> 668 | 669 | =begin code :lang 670 | 671 | say $c ◑ 20; # darken by 20% 672 | 673 | =end code 674 | 675 | C. Same as C 676 | 677 | =head2 C<🞉> 678 | 679 | =begin code :lang 680 | 681 | say $c 🞉 20; # saturate by 20% 682 | 683 | =end code 684 | 685 | C. 686 | Same as C 687 | 688 | =head2 C<¡> 689 | 690 | =begin code :lang 691 | 692 | say $c¡; # invert colour 693 | 694 | =end code 695 | 696 | C. Same as C 697 | 698 | =head1 STRINGIFICATION 699 | 700 | =begin code :lang 701 | 702 | say $c; 703 | say "$c"; 704 | 705 | =end code 706 | 707 | The C object overrides C<.Str> and C<.gist> methods to be 708 | equivalent to C<.to-string('hex')>. 709 | 710 | =head1 Functional interface 711 | 712 | The color conversion, manipulation and utility functions are defined within 713 | the modules C, C and C and can 714 | be used without the OO interface. The names of functions are the same as those of methods. 715 | 716 | =head1 AUTHOR 717 | 718 | Zoffix Znet 719 | 720 | Source can be located at: https://github.com/raku-community-modules/Color . 721 | Comments and Pull Requests are welcome. 722 | 723 | =head1 CONTRIBUTORS 724 | 725 | Thanks to timotimo++, jnthn++, psch++, RabidGravy++, ab5tract++, moritz++, holli++, 726 | and anyone else who I forgot who helped me with questions on IRC. 727 | 728 | =head1 COPYRIGHT AND LICENSE 729 | 730 | Copyright 2015 - 2018 Zoffix Znet 731 | 732 | Copyright 2019 - 2022 Raku Community 733 | 734 | This library is free software; you can redistribute it and/or modify it under the Artistic License 2.0. 735 | 736 | =end pod 737 | 738 | # vim: expandtab shiftwidth=4 739 | -------------------------------------------------------------------------------- /lib/Color/Conversion.rakumod: -------------------------------------------------------------------------------- 1 | use Color::Utilities; 2 | 3 | unit module Color::Conversion; 4 | 5 | ############################################################################## 6 | # Conversion formulas 7 | # The math was taken from http://www.rapidtables.com/ 8 | ############################################################################## 9 | 10 | sub rgb2hsv($r is copy, $g is copy, $b is copy) is export { 11 | my ($h, $Δ, $c_max) = calc-hue($r, $g, $b); 12 | 13 | my $s := $c_max == 0 ?? 0 !! $Δ / $c_max; 14 | my $v := $c_max; 15 | 16 | $h, $s*100, $v*100 17 | } 18 | 19 | sub rgba2hsva($r, $g, $b, $a) is export { 20 | my ($h, $s, $v) = rgb2hsv($r, $g, $b); 21 | 22 | $h, $s, $v, $a 23 | } 24 | 25 | sub rgb2hsl($r is copy, $g is copy, $b is copy) is export { 26 | my ($h, $Δ, $c_max, $c_min) = calc-hue($r, $g, $b); 27 | my $l := ($c_max + $c_min) / 2; 28 | my $s := $Δ == 0 ?? 0 !! $Δ / (1 - abs(2*$l - 1)); 29 | 30 | $h, $s*100, $l*100 31 | } 32 | 33 | sub rgba2hsla($r, $g, $b, $a) is export { 34 | my ($h, $s, $l) = rgb2hsl( $r, $g, $b); 35 | $h, $s, $l, $a 36 | } 37 | 38 | sub rgb2cmyk($r is copy, $g is copy, $b is copy) is export { 39 | $_ /= 255 for $r, $g, $b; 40 | 41 | # Formula courtesy http://www.rapidtables.com/convert/color/rgb-to-cmyk.htm 42 | my $k := 1 - max $r, $g, $b; 43 | my $c := (1 - $r - $k) / (1 - $k); 44 | my $m := (1 - $g - $k) / (1 - $k); 45 | my $y := (1 - $b - $k) / (1 - $k); 46 | $c, $m, $y, $k 47 | } 48 | 49 | sub cmyk2rgb( @ ($c is copy, $m is copy, $y is copy, $k is copy) ) is export { 50 | clip-to 0, $_, 1 for $c, $m, $y, $k; 51 | my $r := 255 * (1-$c) * (1-$k); 52 | my $g := 255 * (1-$m) * (1-$k); 53 | my $b := 255 * (1-$y) * (1-$k); 54 | %(:$r, :$g, :$b) 55 | } 56 | 57 | sub hsl2rgb( @ ($h is copy, $s is copy, $l is copy) ) is export { 58 | $s /= 100; 59 | $l /= 100; 60 | $h -= 360 while $h >= 360; 61 | $h += 360 while $h < 0; 62 | clip-to 0, $s, 1; 63 | clip-to 0, $l, 1; 64 | 65 | # Formula courtesy of http://www.rapidtables.com/convert/color/hsl-to-rgb.htm 66 | my $c := (1 - abs(2*$l - 1)) * $s; 67 | my $x := $c * (1 - abs( (($h/60) % 2) - 1 )); 68 | my $m := $l - $c/2; 69 | rgb-from-c-x-m $h, $c, $x, $m 70 | } 71 | 72 | sub hsla2rgba( @ ($h, $s, $l, $a) ) is export { 73 | %(hsl2rgb(($h, $s, $l)), :$a) 74 | } 75 | 76 | sub hsv2rgb( @ ($h is copy, $s is copy, $v is copy) ) is export { 77 | $s /= 100; 78 | $v /= 100; 79 | $h -= 360 while $h >= 360; 80 | $h += 360 while $h < 0; 81 | clip-to 0, $s, 1; 82 | clip-to 0, $v, 1; 83 | 84 | # Formula cortesy of http://www.rapidtables.com/convert/color/hsv-to-rgb.htm 85 | my $c := $v * $s; 86 | my $x := $c * (1 - abs( (($h/60) % 2) - 1 ) ); 87 | my $m := $v - $c; 88 | rgb-from-c-x-m $h, $c, $x, $m 89 | } 90 | 91 | sub hsva2rgba( @ ($h, $s, $v, $a) ) is export { 92 | %(hsv2rgb(($h, $s, $v)), :$a) 93 | } 94 | 95 | sub rgb-from-c-x-m($h, $c, $x, $m) is export { 96 | my ($r, $g, $b); 97 | given $h { 98 | when 0..^60 { ($r, $g, $b) = ($c, $x, 0) } 99 | when 60..^120 { ($r, $g, $b) = ($x, $c, 0) } 100 | when 120..^180 { ($r, $g, $b) = (0, $c, $x) } 101 | when 180..^240 { ($r, $g, $b) = (0, $x, $c) } 102 | when 240..^300 { ($r, $g, $b) = ($x, 0, $c) } 103 | when 300..^360 { ($r, $g, $b) = ($c, 0, $x) } 104 | } 105 | ( $r, $g, $b ) = ($r,$g,$b).map: { ($_+$m) * 255 } 106 | %(r => $r.Real, g => $g.Real, b => $b.Real) 107 | } 108 | 109 | # vim: expandtab shiftwidth=4 110 | -------------------------------------------------------------------------------- /lib/Color/Manipulation.rakumod: -------------------------------------------------------------------------------- 1 | use Color::Utilities; 2 | use Color::Conversion; 3 | 4 | unit module Color::Manipulation; 5 | 6 | proto sub darken(|) is export {*} 7 | multi sub darken(%hsl, Real:D $Δ) { 8 | lighten %hsl, %hsl, %hsl, -$Δ 9 | } 10 | multi sub darken($h, $s, $l, Real:D $Δ) { 11 | lighten $h, $s, $l, -$Δ 12 | } 13 | 14 | proto sub lighten(|) is export {*} 15 | multi sub lighten(%hsl, Real:D $Δ) { 16 | lighten %hsl, %hsl, %hsl, $Δ 17 | } 18 | multi sub lighten($h, $s, $l is copy, Real:D $Δ) { 19 | $l += $Δ; 20 | clip-to 0, $l, 100; 21 | $h, $s, $l 22 | } 23 | 24 | proto sub desaturate(|) is export {*} 25 | multi sub desaturate(%hsl, Real:D $Δ) { 26 | saturate %hsl, %hsl, %hsl, -$Δ 27 | } 28 | multi sub desaturate($h, $s, $l, Real:D $Δ) { 29 | saturate( $h, $s, $l, -$Δ); 30 | } 31 | 32 | proto sub saturate(|) is export {*} 33 | multi sub saturate(%hsl, Real:D $Δ) { 34 | saturate %hsl, %hsl, %hsl, $Δ 35 | } 36 | multi sub saturate($h, $s is copy, $l, Real:D $Δ) { 37 | $s += $Δ; 38 | clip-to 0, $s, 100; 39 | $h, $s, $l 40 | } 41 | 42 | proto sub invert(|) is export {*} 43 | multi sub invert(%rgb) { 44 | invert %rgb, %rgb, %rgb 45 | } 46 | multi sub invert($r, $g, $b) { 47 | 255-$r, 255-$g, 255-$b; 48 | } 49 | 50 | sub rotate($r, $g, $b, $α) is export { 51 | my ($h, $s, $l) = rgb2hsl($r, $g, $b); 52 | 53 | $h += $α; 54 | 55 | if $h > 360 { 56 | $h -= 360; 57 | } 58 | elsif $h < 0 { 59 | $h += 360; 60 | } 61 | 62 | hsl2rgb @($h, $s, $l) 63 | } 64 | 65 | # vim: expandtab shiftwidth=4 66 | -------------------------------------------------------------------------------- /lib/Color/Operators.rakumod: -------------------------------------------------------------------------------- 1 | # The color operators have moved to Color.rakumod and are now exported 2 | # by default. This makes "use Color::Operators" a noop, and can thus 3 | # be removed. Please keep this module in this state though, to allow 4 | # code referencing it, to still compile. 5 | 6 | # vim: expandtab shiftwidth=4 7 | -------------------------------------------------------------------------------- /lib/Color/Utilities.rakumod: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # Utilities 3 | ############################################################################## 4 | 5 | unit module Color::Utilities; 6 | 7 | sub calc-hue($r is copy, $g is copy, $b is copy) is export { 8 | $_ /= 255 for $r, $g, $b; 9 | 10 | my $c_max := max $r, $g, $b; 11 | my $c_min := min $r, $g, $b; 12 | my \Δ = $c_max - $c_min; 13 | 14 | my $h := do given $c_max { 15 | when Δ == 0 { 0 } 16 | when $r { 60 * ( ($g - $b)/Δ % 6 ) } 17 | when $g { 60 * ( ($b - $r)/Δ + 2 ) } 18 | when $b { 60 * ( ($r - $g)/Δ + 4 ) } 19 | }; 20 | 21 | $h, Δ, $c_max, $c_min 22 | } 23 | 24 | sub parse-hex(Str:D $hex is copy) is export { 25 | $hex ~~ s/^ '#'//; 26 | 3 <= $hex.chars <= 4 and $hex ~~ s:g/(.)/$0$0/; 27 | my ( $r, $g, $b, $a ) = map { :16($_).Int }, $hex.comb(/../); 28 | $a //= 255; 29 | %(:$r, :$g, :$b, :$a) 30 | } 31 | 32 | sub clip-to($min, $v is rw, $max) is export { $v = ($min max $v) min $max } 33 | 34 | # vim: expandtab shiftwidth=4 35 | -------------------------------------------------------------------------------- /logotype/logo_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raku-community-modules/Color/4130ce0c0e107a2fdc1a7ff279569dd55fe1f907/logotype/logo_32x32.png -------------------------------------------------------------------------------- /t/01-new-key-value.rakutest: -------------------------------------------------------------------------------- 1 | use Test; 2 | use Color; 3 | 4 | plan 28; 5 | 6 | diag 'Short 3-hex'; 7 | subtest { 8 | my $c = Color.new( hex => '#fac'); 9 | isa-ok $c, 'Color'; 10 | is $c.r, 255, 'red is correct'; 11 | is $c.g, 170, 'green is correct'; 12 | is $c.b, 204, 'blue is correct'; 13 | is $c.a, 255, 'alpha is correct'; 14 | }, ".new( hex => '#fac')"; 15 | 16 | subtest { 17 | my $c = Color.new( hex => 'fac'); 18 | isa-ok $c, 'Color'; 19 | is $c.r, 255, 'red is correct'; 20 | is $c.g, 170, 'green is correct'; 21 | is $c.b, 204, 'blue is correct'; 22 | is $c.a, 255, 'alpha is correct'; 23 | }, ".new( hex => 'fac')"; 24 | 25 | subtest { 26 | my $c = Color.new(:hex); 27 | isa-ok $c, 'Color'; 28 | is $c.r, 255, 'red is correct'; 29 | is $c.g, 170, 'green is correct'; 30 | is $c.b, 204, 'blue is correct'; 31 | is $c.a, 255, 'alpha is correct'; 32 | }, ".new(:hex)"; 33 | 34 | diag 'Short 4-hex'; 35 | subtest { 36 | my $c = Color.new( hex => '#face'); 37 | isa-ok $c, 'Color'; 38 | is $c.r, 255, 'red is correct'; 39 | is $c.g, 170, 'green is correct'; 40 | is $c.b, 204, 'blue is correct'; 41 | is $c.a, 238, 'alpha is correct'; 42 | }, ".new( hex => '#face')"; 43 | 44 | subtest { 45 | my $c = Color.new( hex => 'face'); 46 | isa-ok $c, 'Color'; 47 | is $c.r, 255, 'red is correct'; 48 | is $c.g, 170, 'green is correct'; 49 | is $c.b, 204, 'blue is correct'; 50 | is $c.a, 238, 'alpha is correct'; 51 | }, ".new( hex => 'face')"; 52 | 53 | subtest { 54 | my $c = Color.new( :hex ); 55 | isa-ok $c, 'Color'; 56 | is $c.r, 255, 'red is correct'; 57 | is $c.g, 170, 'green is correct'; 58 | is $c.b, 204, 'blue is correct'; 59 | is $c.a, 238, 'alpha is correct'; 60 | }, ".new( :hex )"; 61 | 62 | diag 'Full 6-hex'; 63 | subtest { 64 | my $c = Color.new( hex => '#0abcde'); 65 | isa-ok $c, 'Color'; 66 | is $c.r, 10, 'red is correct'; 67 | is $c.g, 188, 'green is correct'; 68 | is $c.b, 222, 'blue is correct'; 69 | is $c.a, 255, 'alpha is correct'; 70 | }, ".new( hex => '#0abcde')"; 71 | 72 | subtest { 73 | my $c = Color.new( hex => '0abcde'); 74 | isa-ok $c, 'Color'; 75 | is $c.r, 10, 'red is correct'; 76 | is $c.g, 188, 'green is correct'; 77 | is $c.b, 222, 'blue is correct'; 78 | is $c.a, 255, 'alpha is correct'; 79 | }, ".new( hex => '0abcde')"; 80 | 81 | subtest { 82 | my $c = Color.new( :hex<0abcde>); 83 | isa-ok $c, 'Color'; 84 | is $c.r, 10, 'red is correct'; 85 | is $c.g, 188, 'green is correct'; 86 | is $c.b, 222, 'blue is correct'; 87 | is $c.a, 255, 'alpha is correct'; 88 | }, ".new( :hex<0abcde> )"; 89 | 90 | diag 'Full 8-hex'; 91 | subtest { 92 | my $c = Color.new( hex => '#0abcdef4'); 93 | isa-ok $c, 'Color'; 94 | is $c.r, 10, 'red is correct'; 95 | is $c.g, 188, 'green is correct'; 96 | is $c.b, 222, 'blue is correct'; 97 | is $c.a, 244, 'alpha is correct'; 98 | }, ".new( hex => '#0abcdef4')"; 99 | 100 | subtest { 101 | my $c = Color.new( hex => '0abcdef4'); 102 | isa-ok $c, 'Color'; 103 | is $c.r, 10, 'red is correct'; 104 | is $c.g, 188, 'green is correct'; 105 | is $c.b, 222, 'blue is correct'; 106 | is $c.a, 244, 'alpha is correct'; 107 | }, ".new( hex => '0abcdef4')"; 108 | 109 | subtest { 110 | my $c = Color.new( :hex<0abcdef4> ); 111 | isa-ok $c, 'Color'; 112 | is $c.r, 10, 'red is correct'; 113 | is $c.g, 188, 'green is correct'; 114 | is $c.b, 222, 'blue is correct'; 115 | is $c.a, 244, 'alpha is correct'; 116 | }, ".new( :hex<0abcdef4> )"; 117 | 118 | diag 'RGB tuple'; 119 | subtest { 120 | my $c = Color.new( rgb => [22, 42, 72] ); 121 | isa-ok $c, 'Color'; 122 | is $c.r, 22, 'red is correct'; 123 | is $c.g, 42, 'green is correct'; 124 | is $c.b, 72, 'blue is correct'; 125 | is $c.a, 255, 'alpha is correct'; 126 | }, ".new( rgb => [22, 42, 72] )"; 127 | 128 | subtest { 129 | my $c = Color.new(:rgb<22 42 72>); 130 | isa-ok $c, 'Color'; 131 | is $c.r, 22, 'red is correct'; 132 | is $c.g, 42, 'green is correct'; 133 | is $c.b, 72, 'blue is correct'; 134 | is $c.a, 255, 'alpha is correct'; 135 | }, ".new(:rgb<22 42 72>)"; 136 | 137 | diag 'RGB tuple (decimal form)'; 138 | subtest { 139 | my $c = Color.new( rgbd => [.086, .165, .282] ); 140 | isa-ok $c, 'Color'; 141 | is $c.r, 21.93, 'red is correct'; 142 | is $c.g, 42.075, 'green is correct'; 143 | is $c.b, 71.91, 'blue is correct'; 144 | is $c.a, 255, 'alpha is correct'; 145 | }, ".new( rgbd => [.086, .165, .282] )"; 146 | 147 | subtest { 148 | my $c = Color.new( :rgbd<.086 .165 .282> ); 149 | isa-ok $c, 'Color'; 150 | is $c.r, 21.93, 'red is correct'; 151 | is $c.g, 42.075, 'green is correct'; 152 | is $c.b, 71.91, 'blue is correct'; 153 | is $c.a, 255, 'alpha is correct'; 154 | }, ".new( :rgbd<.086 .165 .282> )"; 155 | 156 | diag 'RGBA tuple'; 157 | subtest { 158 | my $c = Color.new( rgba => [ 22, 42, 72, 88 ] ); 159 | isa-ok $c, 'Color'; 160 | is $c.r, 22, 'red is correct'; 161 | is $c.g, 42, 'green is correct'; 162 | is $c.b, 72, 'blue is correct'; 163 | is $c.a, 88, 'alpha is correct'; 164 | }, ".new( rgba => [ 22, 42, 72, 88 ] )"; 165 | 166 | subtest { 167 | my $c = Color.new( :rgba<22 42 72 88> ); 168 | isa-ok $c, 'Color'; 169 | is $c.r, 22, 'red is correct'; 170 | is $c.g, 42, 'green is correct'; 171 | is $c.b, 72, 'blue is correct'; 172 | is $c.a, 88, 'alpha is correct'; 173 | }, ".new( :rgba<22 42 72 88> )"; 174 | 175 | diag 'RGBA tuple (decimal form)'; 176 | subtest { 177 | my $c = Color.new( rgbad => [ .086, .165, .282, .345 ] ); 178 | isa-ok $c, 'Color'; 179 | is $c.r, 21.93, 'red is correct'; 180 | is $c.g, 42.075, 'green is correct'; 181 | is $c.b, 71.91, 'blue is correct'; 182 | is $c.a, 87.975, 'alpha is correct'; 183 | }, ".new( rgbad => [ .086, .165, .282, .345 ] )"; 184 | 185 | subtest { 186 | my $c = Color.new( :rgbad< .086 .165 .282 .345> ); 187 | isa-ok $c, 'Color'; 188 | is $c.r, 21.93, 'red is correct'; 189 | is $c.g, 42.075, 'green is correct'; 190 | is $c.b, 71.91, 'blue is correct'; 191 | is $c.a, 87.975, 'alpha is correct'; 192 | }, ".new( :rgbad< .086 .165 .282 .345> )"; 193 | 194 | diag 'CMYK tuple'; 195 | subtest { 196 | my $c = Color.new( cmyk => [.55, .25, .85, .12] ); 197 | isa-ok $c, 'Color'; 198 | is $c.r, 100.98, 'red is correct'; 199 | is $c.g, 168.3, 'green is correct'; 200 | is $c.b, 33.66, 'blue is correct'; 201 | is $c.a, 255, 'alpha is correct'; 202 | }, ".new( cmyk => [.55, .25, .85, .12] )"; 203 | 204 | subtest { 205 | my $c = Color.new( :cmyk<.55 .25 .85 .12> ); 206 | isa-ok $c, 'Color'; 207 | is $c.r, 100.98, 'red is correct'; 208 | is $c.g, 168.3, 'green is correct'; 209 | is $c.b, 33.66, 'blue is correct'; 210 | is $c.a, 255, 'alpha is correct'; 211 | }, ".new( :cmyk<.55 .25 .85 .12> )"; 212 | 213 | diag 'HSL tuple'; 214 | subtest { 215 | my $c = Color.new( hsl => [ 72, 78, 65] ); 216 | isa-ok $c, 'Color'; 217 | is $c.r, 207.519, 'red is correct'; 218 | is $c.g, 235.365, 'green is correct'; 219 | is $c.b, 96.135, 'blue is correct'; 220 | is $c.a, 255, 'alpha is correct'; 221 | }, ".new( hsl => [ 72, 78, 65] )"; 222 | 223 | diag 'HSLA tuple'; 224 | subtest { 225 | my $c = Color.new( hsla => [ 72, 78, 65, 123] ); 226 | isa-ok $c, 'Color'; 227 | is $c.r, 207.519, 'red is correct'; 228 | is $c.g, 235.365, 'green is correct'; 229 | is $c.b, 96.135, 'blue is correct'; 230 | is $c.a, 123, 'alpha is correct'; 231 | }, ".new( hsla => [ 72, 78, 65, 123] )"; 232 | 233 | subtest { 234 | my $c = Color.new( :hsl< 72 78 65> ); 235 | isa-ok $c, 'Color'; 236 | is $c.r, 207.519, 'red is correct'; 237 | is $c.g, 235.365, 'green is correct'; 238 | is $c.b, 96.135, 'blue is correct'; 239 | is $c.a, 255, 'alpha is correct'; 240 | }, ".new( :hsl< 72 78 65> )"; 241 | 242 | diag 'HSV tuple'; 243 | subtest { 244 | my $c = Color.new( hsv => [ 90, 60, 70] ); 245 | isa-ok $c, 'Color'; 246 | is $c.r, 124.95, 'red is correct'; 247 | is $c.g, 178.5, 'green is correct'; 248 | is $c.b, 71.4, 'blue is correct'; 249 | is $c.a, 255, 'alpha is correct'; 250 | }, ".new( hsv => [ 90, 60, 70] )"; 251 | 252 | diag 'HSVA tuple'; 253 | subtest { 254 | my $c = Color.new( hsva => [ 90, 60, 70, 123] ); 255 | isa-ok $c, 'Color'; 256 | is $c.r, 124.95, 'red is correct'; 257 | is $c.g, 178.5, 'green is correct'; 258 | is $c.b, 71.4, 'blue is correct'; 259 | is $c.a, 123, 'alpha is correct'; 260 | }, ".new( hsva => [ 90, 60, 70, 123] )"; 261 | 262 | subtest { 263 | my $c = Color.new( :hsv<90 60 70> ); 264 | isa-ok $c, 'Color'; 265 | is $c.r, 124.95, 'red is correct'; 266 | is $c.g, 178.5, 'green is correct'; 267 | is $c.b, 71.4, 'blue is correct'; 268 | is $c.a, 255, 'alpha is correct'; 269 | }, ".new( :hsv<90 60 70> )"; 270 | 271 | # vim: expandtab shiftwidth=4 272 | -------------------------------------------------------------------------------- /t/02-new-short-form.rakutest: -------------------------------------------------------------------------------- 1 | use Test; 2 | use Color; 3 | 4 | plan 10; 5 | 6 | diag 'Short 3-hex'; 7 | subtest { 8 | my $c = Color.new('#fac'); 9 | isa-ok $c, 'Color'; 10 | is $c.r, 255, 'red is correct'; 11 | is $c.g, 170, 'green is correct'; 12 | is $c.b, 204, 'blue is correct'; 13 | is $c.a, 255, 'alpha is correct'; 14 | }, ".new('#fac')"; 15 | 16 | subtest { 17 | my $c = Color.new('fac'); 18 | isa-ok $c, 'Color'; 19 | is $c.r, 255, 'red is correct'; 20 | is $c.g, 170, 'green is correct'; 21 | is $c.b, 204, 'blue is correct'; 22 | is $c.a, 255, 'alpha is correct'; 23 | }, ".new('fac')"; 24 | 25 | diag 'Short 4-hex'; 26 | subtest { 27 | my $c = Color.new('#face'); 28 | isa-ok $c, 'Color'; 29 | is $c.r, 255, 'red is correct'; 30 | is $c.g, 170, 'green is correct'; 31 | is $c.b, 204, 'blue is correct'; 32 | is $c.a, 238, 'alpha is correct'; 33 | }, ".new('#face')"; 34 | 35 | subtest { 36 | my $c = Color.new('face'); 37 | isa-ok $c, 'Color'; 38 | is $c.r, 255, 'red is correct'; 39 | is $c.g, 170, 'green is correct'; 40 | is $c.b, 204, 'blue is correct'; 41 | is $c.a, 238, 'alpha is correct'; 42 | }, ".new('face')"; 43 | 44 | diag 'Full 6-hex'; 45 | subtest { 46 | my $c = Color.new('#0abcde'); 47 | isa-ok $c, 'Color'; 48 | is $c.r, 10, 'red is correct'; 49 | is $c.g, 188, 'green is correct'; 50 | is $c.b, 222, 'blue is correct'; 51 | is $c.a, 255, 'alpha is correct'; 52 | }, ".new('#0abcde')"; 53 | 54 | subtest { 55 | my $c = Color.new('0abcde'); 56 | isa-ok $c, 'Color'; 57 | is $c.r, 10, 'red is correct'; 58 | is $c.g, 188, 'green is correct'; 59 | is $c.b, 222, 'blue is correct'; 60 | is $c.a, 255, 'alpha is correct'; 61 | }, ".new('0abcde')"; 62 | 63 | diag 'Full 8-hex'; 64 | subtest { 65 | my $c = Color.new('#0abcdef4'); 66 | isa-ok $c, 'Color'; 67 | is $c.r, 10, 'red is correct'; 68 | is $c.g, 188, 'green is correct'; 69 | is $c.b, 222, 'blue is correct'; 70 | is $c.a, 244, 'alpha is correct'; 71 | }, ".new('#0abcdef4')"; 72 | 73 | subtest { 74 | my $c = Color.new('0abcdef4'); 75 | isa-ok $c, 'Color'; 76 | is $c.r, 10, 'red is correct'; 77 | is $c.g, 188, 'green is correct'; 78 | is $c.b, 222, 'blue is correct'; 79 | is $c.a, 244, 'alpha is correct'; 80 | }, ".new('0abcdef4')"; 81 | 82 | diag 'RGB tuple'; 83 | subtest { 84 | my $c = Color.new( 22, 42, 72 ); 85 | isa-ok $c, 'Color'; 86 | is $c.r, 22, 'red is correct'; 87 | is $c.g, 42, 'green is correct'; 88 | is $c.b, 72, 'blue is correct'; 89 | is $c.a, 255, 'alpha is correct'; 90 | }, ".new( 22, 42, 72 )"; 91 | 92 | diag 'CMYK tuple'; 93 | subtest { 94 | my $c = Color.new( .55, .25, .85, .12 ); 95 | isa-ok $c, 'Color'; 96 | is $c.r, 100.98, 'red is correct'; 97 | is $c.g, 168.3, 'green is correct'; 98 | is $c.b, 33.66, 'blue is correct'; 99 | is $c.a, 255, 'alpha is correct'; 100 | }, ".new( .55, .25, .85, .12 )"; 101 | 102 | # vim: expandtab shiftwidth=4 103 | -------------------------------------------------------------------------------- /t/03-new-overflows.rakutest: -------------------------------------------------------------------------------- 1 | use Test; 2 | use Color; 3 | 4 | plan 8; 5 | 6 | subtest { 7 | my $c = Color.new( rgba => [ 300, 300, -10, -10 ] ); 8 | isa-ok $c, 'Color'; 9 | is $c.r, 255, 'red is correct'; 10 | is $c.g, 255, 'green is correct'; 11 | is $c.b, 0, 'blue is correct'; 12 | is $c.a, 0, 'alpha is correct'; 13 | }, "RGBA overflow"; 14 | 15 | subtest { 16 | my $c = Color.new( hsl => [ 370, 200, 200 ] ); 17 | isa-ok $c, 'Color'; 18 | is $c.r, 255, 'red is correct'; 19 | is $c.g, 255, 'green is correct'; 20 | is $c.b, 255, 'blue is correct'; 21 | is $c.a, 255, 'alpha is correct'; 22 | }, "HSL overflow (top)"; 23 | 24 | subtest { 25 | my $c = Color.new( hsl => [ -10, -10, -10 ] ); 26 | isa-ok $c, 'Color'; 27 | is $c.r, 0, 'red is correct'; 28 | is $c.g, 0, 'green is correct'; 29 | is $c.b, 0, 'blue is correct'; 30 | is $c.a, 255, 'alpha is correct'; 31 | }, "hsv overflow (bottom)"; 32 | 33 | subtest { 34 | is Color.new( hsl => [ -50, 50, 50 ] ).to-string('hsl'), 35 | Color.new( hsl => [ 310, 50, 50 ] ).to-string('hsl'), '-50 == 310'; 36 | is Color.new( hsl => [ 700, 50, 50 ] ).to-string('hsl'), 37 | Color.new( hsl => [ 340, 50, 50 ] ).to-string('hsl'), '700 == 340'; 38 | is Color.new( hsl => [ -1050, 50, 50 ] ).to-string('hsl'), 39 | Color.new( hsl => [ 30, 50, 50 ] ).to-string('hsl'), '-1050 == 30'; 40 | is Color.new( hsl => [ 1700, 50, 50 ] ).to-string('hsl'), 41 | Color.new( hsl => [ 260, 50, 50 ] ).to-string('hsl'), '1700 == 260'; 42 | }, "HSL hue overflow (should just overflow into a proper angle)"; 43 | 44 | subtest { 45 | my $c = Color.new( hsv => [ 370, 200, 200 ] ); 46 | isa-ok $c, 'Color'; 47 | is $c.r, 255, 'red is correct'; 48 | is $c.g, 42.5, 'green is correct'; 49 | is $c.b, 0, 'blue is correct'; 50 | is $c.a, 255, 'alpha is correct'; 51 | }, "HSV overflow (top)"; 52 | 53 | subtest { 54 | my $c = Color.new( hsv => [ -10, -10, -10 ] ); 55 | isa-ok $c, 'Color'; 56 | is $c.r, 0, 'red is correct'; 57 | is $c.g, 0, 'green is correct'; 58 | is $c.b, 0, 'blue is correct'; 59 | is $c.a, 255, 'alpha is correct'; 60 | }, "HSV overflow (bottom)"; 61 | 62 | subtest { 63 | is Color.new( hsv => [ -50, 50, 50 ] ).to-string('hsv'), 64 | Color.new( hsv => [ 310, 50, 50 ] ).to-string('hsv'), '-50 == 310'; 65 | is Color.new( hsv => [ 700, 50, 50 ] ).to-string('hsv'), 66 | Color.new( hsv => [ 340, 50, 50 ] ).to-string('hsv'), '700 == 340'; 67 | is Color.new( hsv => [ -1050, 50, 50 ] ).to-string('hsv'), 68 | Color.new( hsv => [ 30, 50, 50 ] ).to-string('hsv'), '-1050 == 30'; 69 | is Color.new( hsv => [ 1700, 50, 50 ] ).to-string('hsv'), 70 | Color.new( hsv => [ 260, 50, 50 ] ).to-string('hsv'), '1700 == 260'; 71 | }, "HSV hue overflow (should just overflow into a proper angle)"; 72 | 73 | subtest { 74 | my $c = Color.new( cmyk => [ 2, 2, -2, -2 ] ); 75 | isa-ok $c, 'Color'; 76 | is $c.r, 0, 'red is correct'; 77 | is $c.g, 0, 'green is correct'; 78 | is $c.b, 255, 'blue is correct'; 79 | is $c.a, 255, 'alpha is correct'; 80 | }, "CMYK overflow"; 81 | 82 | # vim: expandtab shiftwidth=4 83 | -------------------------------------------------------------------------------- /t/04-new-invalid.rakutest: -------------------------------------------------------------------------------- 1 | use Test; 2 | use Color; 3 | 4 | plan 7; 5 | 6 | subtest { 7 | dies-ok { Color.new('foobar') }, 'die on string instead of hex chars'; 8 | dies-ok { Color.new('fa') }, 'die on too few hex chars'; 9 | dies-ok { Color.new('faaaaaaaaaaaaaaa') }, 'die on too many hex chars'; 10 | }, 'hex, positional form'; 11 | 12 | subtest { 13 | dies-ok { Color.new(2) }, 'die on too few positionals'; 14 | dies-ok { Color.new(2, 3, 4, 5, 6, 7) }, 'die on too many positionals'; 15 | }, 'numbers, positional form'; 16 | 17 | diag 'Key-value forms'; 18 | 19 | subtest { 20 | dies-ok { Color.new( hex => 'foobar') }, 21 | 'die on string instead of hex chars'; 22 | dies-ok { Color.new( hex => 'fa') }, 23 | 'die on too few hex chars'; 24 | dies-ok { Color.new( hex => 'faaaaaaaaaaaaaaa') }, 25 | 'die on too many hex chars'; 26 | }, 'hex'; 27 | 28 | subtest { 29 | dies-ok { Color.new(rgb => [22, 42]) }, 30 | 'die on too few colours'; 31 | dies-ok { Color.new(rgb => [22, 42, 45, 46]) }, 32 | 'die on too many colours'; 33 | }, 'RGB Tuple'; 34 | 35 | subtest { 36 | dies-ok { Color.new(rgbd => [.5, .5]) }, 37 | 'die on too few colours'; 38 | dies-ok { Color.new(rgbd => [.5, .5, .5, .5]) }, 39 | 'die on too many colours'; 40 | }, 'RGB Tuple (decimal form)'; 41 | 42 | subtest { 43 | dies-ok { Color.new(rgba => [22, 42]) }, 44 | 'die on too few colours'; 45 | dies-ok { Color.new(rgba => [22, 42, 45, 46, 48]) }, 46 | 'die on too many colours'; 47 | }, 'RGBA Tuple'; 48 | 49 | subtest { 50 | dies-ok { Color.new(rgbad => [.5, .5]) }, 51 | 'die on too few colours'; 52 | dies-ok { Color.new(rgbad => [.5, .5, .5, .5, .5]) }, 53 | 'die on too many colours'; 54 | }, 'RGBA Tuple (decimal form)'; 55 | 56 | # vim: expandtab shiftwidth=4 57 | -------------------------------------------------------------------------------- /t/05-methods.rakutest: -------------------------------------------------------------------------------- 1 | use Test; 2 | use Color; 3 | 4 | plan 6; 5 | 6 | subtest { 7 | my $c = Color.new( hex => '#0abcde'); 8 | isa-ok $c, 'Color'; 9 | can-ok $c, $_, "can do $_" for qw/ 10 | r g b a cmyk hsl hsla hsv hsva rgb rgba rgbd rgbad 11 | hex hex3 hex4 hex8 darken lighten 12 | /; 13 | }, 'method/ISA check'; 14 | 15 | subtest { 16 | my $c = Color.new( hex => '#0abcde'); 17 | is $c.r, 10, 'red'; 18 | is $c.g, 188, 'green'; 19 | is $c.b, 222, 'blue'; 20 | is $c.a, 255, 'alpha'; 21 | }, 'component colour accessors'; 22 | 23 | subtest { 24 | my $c = Color.new( hex => '#0abcde'); 25 | is-deeply $c.cmyk, (<106/111>, <17/111>, 0.0, <11/85>), 'cmyk'; 26 | is-deeply $c.hsl, (<10050/53>, <2650/29>, <2320/51>), 'hsl'; 27 | is-deeply $c.hsla, (<10050/53>, <2650/29>, <2320/51>, 255), 'hsla'; 28 | is-deeply $c.hsv, (<10050/53>, <10600/111>, <1480/17>), 'hsv'; 29 | is-deeply $c.hsva, (<10050/53>, <10600/111>, <1480/17>, 255), 'hsva'; 30 | is-deeply $c.rgb, (10, 188, 222), 'rgb'; 31 | is-deeply $c.rgba, (10, 188, 222, 255), 'rgba'; 32 | is-deeply $c.rgbd, (<2/51>, <188/255>, <74/85>), 'rgbd'; 33 | is-deeply $c.rgbad, (<2/51>, <188/255>, <74/85>, 1.0), 'rgbad'; 34 | is $c.hex, <0A BC DE>, 'hex'; 35 | is $c.hex3, <1 C E>, 'hex3'; 36 | is $c.hex4, <1 C E F>, 'hex4'; 37 | is Color.new(255, 240, 218).hex3, , 'hex3 (rounding)'; 38 | is Color.new( 0, 218, 214).hex3, <0 E D>, 'hex3 (rounding, more)'; 39 | is $c.hex8, <0A BC DE FF>, 'hex8'; 40 | }, 'format conversion'; 41 | 42 | subtest { 43 | my $c = Color.new( hex => '#0abcde'); 44 | is $c.to-string('cmyk'), 'cmyk(0.954955, 0.153153, 0, 0.129412)', 'cmyk'; 45 | 46 | { # allow for trailing zero to be missing 47 | ok so($c.to-string('hsl') eq 'hsl(189.622642, 91.379310, 45.490196)' 48 | | 'hsl(189.622642, 91.37931, 45.490196)' 49 | ), 'hsl'; 50 | 51 | ok so($c.to-string('hsla') eq 'hsla(189.622642, 91.379310, 45.490196, 255)' 52 | | 'hsla(189.622642, 91.37931, 45.490196, 255)' 53 | ), 'hsla'; 54 | } 55 | 56 | is $c.to-string('hsv'), 'hsv(189.622642, 95.495495, 87.058824)', 'hsv'; 57 | is $c.to-string('hsva'), 'hsva(189.622642, 95.495495, 87.058824, 255)', 'hsva'; 58 | is $c.to-string('rgb'), 'rgb(10, 188, 222)', 'rgb'; 59 | is $c.to-string('rgba'), 'rgba(10, 188, 222, 255)', 'rgba'; 60 | is $c.to-string('rgbd'), 'rgb(0.039216, 0.737255, 0.870588)', 'rgbd'; 61 | is $c.to-string('rgbad'), 'rgba(0.039216, 0.737255, 0.870588, 1)', 'rgbad'; 62 | is $c.to-string('hex'), '#0ABCDE', 'hex'; 63 | is "$c", $c.to-string('hex'), 'stringification'; 64 | is $c.gist, $c.to-string('hex'), '.gist'; 65 | is $c.to-string('hex3'), '#1CE', 'hex3'; 66 | is $c.to-string('hex8'), '#0ABCDEFF', 'hex8'; 67 | dies-ok { $c.to-string('foobar') }, 'died on invalid format'; 68 | }, '.to-string()'; 69 | 70 | subtest { 71 | my $c = Color.new( hsl => [25, 50, 50] ); 72 | isa-ok $c.darken(20), 'Color'; 73 | isa-ok $c.lighten(20), 'Color'; 74 | isa-ok $c.saturate(20), 'Color'; 75 | isa-ok $c.desaturate(20), 'Color'; 76 | isa-ok $c.invert, 'Color'; 77 | 78 | is-deeply $c.darken(20).hsl, (25.0, 50.0, 30.0), 'darken by 20%' ; 79 | is-deeply $c.lighten(20).hsl, (25.0, 50.0, 70.0), 'lighten by 20%' ; 80 | is-deeply $c.desaturate(20).hsl, (25.0, 30.0, 50.0), 'desaturate by 20%'; 81 | is-deeply $c.saturate(20).hsl, (25.0, 70.0, 50.0), 'saturate by 20%' ; 82 | is-deeply $c.invert.rgba, (64, 138, 191, 255), 'invert colour' ; 83 | }, 'color manipulation'; 84 | 85 | subtest { 86 | my $c = Color.new( 255.0, 0.0, 0.0 ); 87 | is-deeply $c, $c.rotate( 360 ), 'turning color wheel way around'; 88 | is-deeply $c.invert, $c.rotate( 180 ), 'turning color wheel halfway around'; 89 | } 90 | 91 | # vim: expandtab shiftwidth=4 92 | -------------------------------------------------------------------------------- /t/06-operators.rakutest: -------------------------------------------------------------------------------- 1 | use Test; 2 | use Color; 3 | 4 | plan 5; 5 | 6 | diag 'Operator tests on rgb nums'; 7 | subtest { 8 | my $c1 = Color.new( rgb => [110, 120, 130] ); 9 | my $c2 = Color.new( rgb => [ 10, 20, 30] ); 10 | my $c3 = Color.new( rgb => [ 2, 1, 2] ); 11 | 12 | is-deeply ($c1 + $c2).rgba, (120, 140, 160, 255), 'Color + Color'; 13 | is-deeply ($c1 + 10).rgba, (120, 130, 140, 255), 'Color + number'; 14 | is-deeply (10 + $c1).rgba, (120, 130, 140, 255), 'number + Color'; 15 | 16 | is-deeply ($c1 - $c2).rgba, (100, 100, 100, 255), 'Color - Color'; 17 | is-deeply ($c1 - 10).rgba, (100, 110, 120, 255), 'Color - number'; 18 | is-deeply (200 - $c1).rgba, ( 90, 80, 70, 255), 'number - Color'; 19 | 20 | is-deeply ($c1 / $c2).rgba, (11, 6, 4, 255), 'Color / Color'; 21 | is-deeply ($c1 / 10).rgba, (11, 12, 13, 255), 'Color / number'; 22 | is-deeply (200 / $c2).rgba, ( 20, 10, 7, 255), 'number / Color'; 23 | is-deeply ($c1 / 0).rgba, ( 0, 0, 0, 255), 'Color / 0 (no die)'; 24 | 25 | is-deeply ($c1 * $c3).rgba, (220, 120, 255, 255), 'Color * Color'; 26 | is-deeply ($c2 * 10).rgba, (100, 200, 255, 255), 'Color * number'; 27 | is-deeply (2 * $c2).rgba, ( 20, 40, 60, 255), 'number * Color'; 28 | }, 'operator tests with rgb nums and alpha-math turned off [default]'; 29 | 30 | diag 'Operator tests (alpha-math turned off on rgba nums)'; 31 | subtest { 32 | my $c1 = Color.new( rgba => [110, 120, 130, 140]); 33 | my $c2 = Color.new( rgba => [ 10, 20, 30, 40]); 34 | my $c3 = Color.new( rgba => [ 2, 1, 2, 1]); 35 | .alpha-math = False for $c1, $c2, $c3; 36 | 37 | is-deeply ($c1 + $c2).rgba, (120, 140, 160, 140), 'Color + Color'; 38 | is-deeply ($c1 + 10).rgba, (120, 130, 140, 140), 'Color + number'; 39 | is-deeply (10 + $c1).rgba, (120, 130, 140, 140), 'number + Color'; 40 | 41 | is-deeply ($c1 - $c2).rgba, (100, 100, 100, 140), 'Color - Color'; 42 | is-deeply ($c1 - 10).rgba, (100, 110, 120, 140), 'Color - number'; 43 | is-deeply (200 - $c1).rgba, ( 90, 80, 70, 140), 'number - Color'; 44 | 45 | is-deeply ($c1 / $c2).rgba, (11, 6, 4, 140), 'Color / Color'; 46 | is-deeply ($c1 / 10).rgba, (11, 12, 13, 140), 'Color / number'; 47 | is-deeply (200 / $c2).rgba, ( 20, 10, 7, 40), 'number / Color'; 48 | is-deeply ($c1 / 0).rgba, ( 0, 0, 0, 140), 'Color / 0 (no die)'; 49 | 50 | is-deeply ($c1 * $c3).rgba, (220, 120, 255, 140), 'Color * Color'; 51 | is-deeply ($c2 * 10).rgba, (100, 200, 255, 40), 'Color * number'; 52 | is-deeply (2 * $c2).rgba, ( 20, 40, 60, 40), 'number * Color'; 53 | }, 'operator tests with rgba nums and alpha-math turned off'; 54 | 55 | diag 'Try again with enabled alpha math'; 56 | subtest { 57 | my $c1 = Color.new( rgba => [110, 120, 130, 140] ); 58 | my $c2 = Color.new( rgba => [ 10, 20, 30, 40] ); 59 | my $c3 = Color.new( rgba => [ 2, 1, 2, 1] ); 60 | 61 | is-deeply ($c1 + $c2).rgba, (120, 140, 160, 180), 'Color + Color'; 62 | is-deeply ($c1 + 10).rgba, (120, 130, 140, 150), 'Color + number'; 63 | is-deeply (10 + $c1).rgba, (120, 130, 140, 150), 'number + Color'; 64 | 65 | is-deeply ($c1 - $c2).rgba, (100, 100, 100, 100), 'Color - Color'; 66 | is-deeply ($c1 - 10).rgba, (100, 110, 120, 130), 'Color - number'; 67 | is-deeply (200 - $c1).rgba, ( 90, 80, 70, 60), 'number - Color'; 68 | 69 | is-deeply ($c1 / $c2).rgba, (11, 6, 4, 4), 'Color / Color'; 70 | is-deeply ($c1 / 10).rgba, (11, 12, 13, 14), 'Color / number'; 71 | is-deeply (200 / $c2).rgba, ( 20, 10, 7, 5), 'number / Color'; 72 | is-deeply ($c1 / 0).rgba, ( 0, 0, 0, 0), 'Color / 0 (no die)'; 73 | 74 | is-deeply ($c1 * $c3).rgba, (220, 120, 255, 140), 'Color * Color'; 75 | is-deeply ($c2 * 10).rgba, (100, 200, 255, 255), 'Color * number'; 76 | is-deeply (2 * $c2).rgba, ( 20, 40, 60, 80), 'number * Color'; 77 | }, 'operator tests on rgba nums with alpha-math turned on [default]'; 78 | 79 | diag 'Try again with enabled alpha math'; 80 | subtest { 81 | my $c1 = Color.new( rgb => [110, 120, 130] ); 82 | my $c2 = Color.new( rgb => [ 10, 20, 30] ); 83 | my $c3 = Color.new( rgb => [ 2, 1, 2] ); 84 | .alpha-math = True for $c1, $c2, $c3; 85 | 86 | is-deeply ($c1 + $c2).rgba, (120, 140, 160, 255), 'Color + Color'; 87 | is-deeply ($c1 + 10).rgba, (120, 130, 140, 255), 'Color + number'; 88 | is-deeply (10 + $c1).rgba, (120, 130, 140, 255), 'number + Color'; 89 | 90 | is-deeply ($c1 - $c2).rgba, (100, 100, 100, 0), 'Color - Color'; 91 | is-deeply ($c1 - 10).rgba, (100, 110, 120, 245), 'Color - number'; 92 | is-deeply (200 - $c1).rgba, ( 90, 80, 70, 0), 'number - Color'; 93 | 94 | is-deeply ($c1 / $c2).rgba, (11, 6, 4, 1), 'Color / Color'; 95 | is-deeply ($c1 / 10).rgba, (11, 12, 13, 26), 'Color / number'; 96 | is-deeply (200 / $c2).rgba, ( 20, 10, 7, 1), 'number / Color'; 97 | is-deeply ($c1 / 0).rgba, ( 0, 0, 0, 0), 'Color / 0 (no die)'; 98 | 99 | is-deeply ($c1 * $c3).rgba, (220, 120, 255, 255), 'Color * Color'; 100 | is-deeply ($c2 * 10).rgba, (100, 200, 255, 255), 'Color * number'; 101 | is-deeply (2 * $c2).rgba, ( 20, 40, 60, 255), 'number * Color'; 102 | }, 'operator tests with alpha_math turned on'; 103 | 104 | diag 'Our fancy-pants unicode ops'; 105 | subtest { 106 | my $c = Color.new( hsl => [25, 50, 50] ); 107 | isa-ok $c ◐ 20, 'Color'; 108 | isa-ok $c ◑ 20, 'Color'; 109 | isa-ok $c 🞅 20, 'Color'; 110 | isa-ok $c 🞉 20, 'Color'; 111 | isa-ok $c¡, 'Color'; 112 | 113 | is-deeply ($c ◐ 20).hsl, $c.lighten(20).hsl, '◐ does .lighten'; 114 | is-deeply ($c ◑ 20).hsl, $c.darken(20).hsl, '◑ does .darken'; 115 | is-deeply ($c 🞅 20).hsl, $c.desaturate(20).hsl, '🞅 does .desaturate'; 116 | is-deeply ($c 🞉 20).hsl, $c.saturate(20).hsl, '🞉 does .saturate'; 117 | is-deeply ($c¡).rgba, $c.invert.rgba, '¡ does .invert'; 118 | }, 'unicode operators'; 119 | 120 | # vim: expandtab shiftwidth=4 121 | -------------------------------------------------------------------------------- /t/07-bugs-and-regressions.rakutest: -------------------------------------------------------------------------------- 1 | use Test; 2 | use Color; 3 | 4 | plan 1; 5 | 6 | is Color.new('000').hsl, (0, 0, 0), 'black->hsl does not die (Issue #3)'; 7 | 8 | # vim: expandtab shiftwidth=4 9 | -------------------------------------------------------------------------------- /t/08-alpha.rakutest: -------------------------------------------------------------------------------- 1 | use Test; 2 | use Color; 3 | 4 | plan 2; 5 | 6 | diag 'Tests on alpha channel behavior'; 7 | subtest { 8 | 9 | my Color $color .= new('#ff0000'); 10 | is $color.alpha, 255, 'alpha channel not transparent'; 11 | is $color.alpha-math, False, 'no alpha channel math'; 12 | $color.alpha(128); 13 | is $color.alpha, 128, 'alpha channel 50% transparent'; 14 | is $color.alpha-math, True, 'alpha channel math'; 15 | 16 | }, 'Test set alpha'; 17 | 18 | 19 | subtest { 20 | 21 | my Color $color1 .= new('#8f8f00'); 22 | $color1.alpha(128); 23 | 24 | my Color $color2 = $color1.lighten(10); 25 | is $color2.alpha, 128, 'alpha channel still at 50% transparent after lighten'; 26 | 27 | $color2 = $color1.darken(10); 28 | is $color2.alpha, 128, 'alpha channel still at 50% transparent after darken'; 29 | 30 | $color2 = $color1.desaturate(10); 31 | is $color2.alpha, 128, 'alpha channel still at 50% transparent after desaturate'; 32 | 33 | $color2 = $color1.saturate(10); 34 | is $color2.alpha, 128, 'alpha channel still at 50% transparent after saturate'; 35 | 36 | $color2 = $color1.invert; 37 | is $color2.alpha, 128, 'alpha channel still at 50% transparent after invert'; 38 | 39 | $color2 = $color1.rotate(10); 40 | is $color2.alpha, 128, 'alpha channel still at 50% transparent after rotate'; 41 | 42 | }, 'Test follow alpha on darken, saturate etc'; 43 | 44 | # vim: expandtab shiftwidth=4 45 | -------------------------------------------------------------------------------- /xt/meta.t: -------------------------------------------------------------------------------- 1 | #!perl6 2 | 3 | use lib 'lib'; 4 | use Test; 5 | use Test::META; 6 | meta-ok; 7 | done-testing; 8 | --------------------------------------------------------------------------------