├── .github ├── FUNDING.yml └── workflows │ └── go.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── chromium_test.go ├── colorparser.go ├── colorparser_test.go ├── example_test.go ├── firefox_test.go ├── go.mod ├── named_colors.go └── named_colors_test.go /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: mazznoer 2 | liberapay: mazznoer 3 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | 13 | strategy: 14 | matrix: 15 | os: [macos-latest, windows-latest, ubuntu-latest] 16 | runs-on: ${{ matrix.os }} 17 | 18 | steps: 19 | - name: Check out code into the Go module directory 20 | uses: actions/checkout@v4 21 | 22 | - name: Set up Go 23 | uses: actions/setup-go@v5 24 | with: 25 | go-version: '1.18' 26 | 27 | - name: Build 28 | run: go build -v . 29 | 30 | - name: Vet 31 | run: go vet . 32 | 33 | - name: Test 34 | run: go test -v -coverprofile coverage.out && go tool cover -html coverage.out -o coverage.html 35 | 36 | - name: Format 37 | if: matrix.os == 'ubuntu-latest' 38 | run: if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then exit 1; fi 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | *.html 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased](https://github.com/mazznoer/csscolorparser/compare/v0.1.4...HEAD) 4 | 5 | ### Added 6 | 7 | - `Clamp()` 8 | 9 | ## v0.1.4 10 | 11 | ### Added 12 | 13 | - Support parsing `oklab()` and `oklch()` color format. 14 | - `FromOklab()`, `FromOklch()`, `FromLinearRgb()`, `FromHsl()`, `FromHsv()`, `FromHwb()` 15 | 16 | ### Fixed 17 | 18 | - Update `oklab` formula. 19 | 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Nor Khasyatillah 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | .PHONY: all check test 4 | 5 | all: check test 6 | 7 | check: 8 | go build && go vet && gofmt -s -l . 9 | 10 | test: 11 | go test -v -coverprofile coverage.out && go tool cover -html coverage.out -o coverage.html 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go Language CSS Color Parser Library 2 | 3 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/mazznoer/csscolorparser)](https://pkg.go.dev/github.com/mazznoer/csscolorparser) 4 | ![Build Status](https://github.com/mazznoer/csscolorparser/actions/workflows/go.yml/badge.svg) 5 | [![go report](https://goreportcard.com/badge/github.com/mazznoer/csscolorparser)](https://goreportcard.com/report/github.com/mazznoer/csscolorparser) 6 | 7 | [Go](https://www.golang.org/) library for parsing CSS color string as defined in the W3C's [CSS Color Module Level 4](https://www.w3.org/TR/css-color-4/). 8 | 9 | ## Supported Color Format 10 | 11 | * [Named colors](https://www.w3.org/TR/css-color-4/#named-colors) 12 | * RGB hexadecimal (with and without `#` prefix) 13 | * Short format `#rgb` 14 | * Short format with alpha `#rgba` 15 | * Long format `#rrggbb` 16 | * Long format with alpha `#rrggbbaa` 17 | * `rgb()` and `rgba()` 18 | * `hsl()` and `hsla()` 19 | * `hwb()` 20 | * `lab()` 21 | * `lch()` 22 | * `oklab()` 23 | * `oklch()` 24 | * `hwba()`, `hsv()`, `hsva()` - not in CSS standard. 25 | 26 | ## Usage Examples 27 | 28 | ```go 29 | import "github.com/mazznoer/csscolorparser" 30 | 31 | c, err := csscolorparser.Parse("gold") 32 | 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | fmt.Printf("R:%.3f, G:%.3f, B:%.3f, A:%.3f", c.R, c.G, c.B, c.A) // R:1.000, G:0.843, B:0.000, A:1.000 38 | fmt.Println(c.RGBA255()) // 255 215 0 255 39 | fmt.Println(c.HexString()) // #ffd700 40 | fmt.Println(c.RGBString()) // rgb(255,215,0) 41 | ``` 42 | 43 | ## Try It Online 44 | 45 | * [Playground 1](https://play.golang.org/p/8KMIc1TLQB0) 46 | * [Playground 2](https://play.golang.org/p/7kb62KSARwa) 47 | 48 | ## Similar Projects 49 | 50 | * [csscolorparser](https://github.com/mazznoer/csscolorparser-rs) (Rust) 51 | * [csscolorparser](https://github.com/deanm/css-color-parser-js) (Javascript) 52 | -------------------------------------------------------------------------------- /chromium_test.go: -------------------------------------------------------------------------------- 1 | package csscolorparser 2 | 3 | import "testing" 4 | 5 | func Test_BrowserChromium(t *testing.T) { 6 | // Randomly generated color string, parsed using Chromium 87.0.4280.66 7 | data := []struct { 8 | s string 9 | r, g, b, a uint8 10 | }{ 11 | {"#13EF", 17, 51, 238, 255}, 12 | {"#b42f", 187, 68, 34, 255}, 13 | {"#49e97851", 73, 233, 120, 81}, 14 | {"#8C68B2E0", 140, 104, 178, 224}, 15 | {"#AE42B424", 174, 66, 180, 36}, 16 | {"#60b", 102, 0, 187, 255}, 17 | {"#9675E6EA", 150, 117, 230, 234}, 18 | {"#7858", 119, 136, 85, 136}, 19 | {"#f06", 255, 0, 102, 255}, 20 | {"#C82", 204, 136, 34, 255}, 21 | {"#70A8AA", 112, 168, 170, 255}, 22 | {"#198", 17, 153, 136, 255}, 23 | {"#bc94198b", 188, 148, 25, 139}, 24 | {"#FC3B98", 252, 59, 152, 255}, 25 | {"#541a3847", 84, 26, 56, 71}, 26 | {"#29A", 34, 153, 170, 255}, 27 | {"#4FA2DC", 79, 162, 220, 255}, 28 | {"#812", 136, 17, 34, 255}, 29 | {"#7A57AD0C", 122, 87, 173, 12}, 30 | {"#F74", 255, 119, 68, 255}, 31 | {"#ADB", 170, 221, 187, 255}, 32 | {"#774AD2B1", 119, 74, 210, 177}, 33 | {"#ECC", 238, 204, 204, 255}, 34 | {"#DCF724B3", 220, 247, 36, 179}, 35 | {"#716195", 113, 97, 149, 255}, 36 | {"#FC39", 255, 204, 51, 153}, 37 | {"#D3B", 221, 51, 187, 255}, 38 | {"#7795", 119, 119, 153, 85}, 39 | {"#387B", 51, 136, 119, 187}, 40 | {"#C3CE", 204, 51, 204, 238}, 41 | {"#BF88D793", 191, 136, 215, 147}, 42 | {"#D97C", 221, 153, 119, 204}, 43 | {"#401D", 68, 0, 17, 221}, 44 | {"#0daf68", 13, 175, 104, 255}, 45 | {"#C84E7484", 200, 78, 116, 132}, 46 | {"#284", 34, 136, 68, 255}, 47 | {"#248", 34, 68, 136, 255}, 48 | {"#3422AF", 52, 34, 175, 255}, 49 | {"#46E", 68, 102, 238, 255}, 50 | {"#2ec6d703", 46, 198, 215, 3}, 51 | {"#29F659", 41, 246, 89, 255}, 52 | {"#69355999", 105, 53, 89, 153}, 53 | {"#4099", 68, 0, 153, 153}, 54 | {"#44abea", 68, 171, 234, 255}, 55 | {"#0623B60A", 6, 35, 182, 10}, 56 | {"#4304F493", 67, 4, 244, 147}, 57 | {"#91176A", 145, 23, 106, 255}, 58 | {"#875994CF", 135, 89, 148, 207}, 59 | {"#806F", 136, 0, 102, 255}, 60 | {"#DD32", 221, 221, 51, 34}, 61 | {"rgb(105.927,150.994,15.104)", 106, 151, 15, 255}, 62 | {"rgb(10.837,152.535,154.317,0.568)", 11, 153, 154, 145}, 63 | {"rgb(237.623,156.627,211.140,-0.058)", 238, 157, 211, 0}, 64 | {"rgb(80.685,2.873,82.097,0.592)", 81, 3, 82, 151}, 65 | {"rgb(31.530,138.330,246.696)", 32, 138, 247, 255}, 66 | {"rgb(127.659,188.444,102.421)", 128, 188, 102, 255}, 67 | {"rgb(114.062,6.757,101.538)", 114, 7, 102, 255}, 68 | {"rgb(34.047,96.606,76.235,0.045)", 34, 97, 76, 11}, 69 | {"rgb(214.707,10.143,78.461)", 215, 10, 78, 255}, 70 | {"rgb(158.355,172.716,136.060)", 158, 173, 136, 255}, 71 | {"rgb(41.366,221.162,32.067,1.107)", 41, 221, 32, 255}, 72 | {"rgb(247.083,25.052,0.185)", 247, 25, 0, 255}, 73 | {"rgb(67.440,90.228,257.506)", 67, 90, 255, 255}, 74 | {"rgb(99.603,18.924,133.951)", 100, 19, 134, 255}, 75 | {"rgb(71.837,242.253,112.316)", 72, 242, 112, 255}, 76 | {"rgb(262.877,206.567,71.233)", 255, 207, 71, 255}, 77 | {"rgb(153.467,73.396,96.642,1.070)", 153, 73, 97, 255}, 78 | {"rgb(253.472,35.005,92.178,0.210)", 253, 35, 92, 54}, 79 | {"rgb(63.611,20.378,1.885)", 64, 20, 2, 255}, 80 | {"rgb(70.695,209.671,111.060)", 71, 210, 111, 255}, 81 | {"rgb(35.748,184.365,-3.144,-0.099)", 36, 184, 0, 0}, 82 | {"rgb(213.630,43.353,243.800,0.932)", 214, 43, 244, 238}, 83 | {"rgb(4.895,236.929,198.497,0.162)", 5, 237, 198, 41}, 84 | {"rgb(262.492,104.420,135.986)", 255, 104, 136, 255}, 85 | {"rgb(196.148,82.928,2.328,1.194)", 196, 83, 2, 255}, 86 | {"rgb(3.984,130.315,223.066)", 4, 130, 223, 255}, 87 | {"rgb(5.098,7.075,228.457)", 5, 7, 228, 255}, 88 | {"rgb(132.649,122.071,261.147)", 133, 122, 255, 255}, 89 | {"rgb(215.091,198.018,80.956,0.673)", 215, 198, 81, 172}, 90 | {"rgb(224.466,219.548,80.411)", 224, 220, 80, 255}, 91 | {"rgb(88.093,249.677,143.753)", 88, 250, 144, 255}, 92 | {"rgb(261.342,42.332,150.964,0.257)", 255, 42, 151, 66}, 93 | {"rgb(24.315,19.390,143.873)", 24, 19, 144, 255}, 94 | {"rgb(206.152,103.265,49.028)", 206, 103, 49, 255}, 95 | {"rgb(121.903,253.961,42.196,0.698)", 122, 254, 42, 178}, 96 | {"rgb(81.981,163.951,231.462)", 82, 164, 231, 255}, 97 | {"rgb(-9.058,143.858,233.204)", 0, 144, 233, 255}, 98 | {"rgb(192.509,117.856,-7.540)", 193, 118, 0, 255}, 99 | {"rgb(25.067,206.513,164.628)", 25, 207, 165, 255}, 100 | {"rgb(194.332,192.906,10.594,0.980)", 194, 193, 11, 250}, 101 | {"rgb(112.924,234.651,160.614)", 113, 235, 161, 255}, 102 | {"rgb(250.062,24.310,60.243)", 250, 24, 60, 255}, 103 | {"rgb(103.655,61.625,36.981)", 104, 62, 37, 255}, 104 | {"rgb(41.189,109.543,71.332)", 41, 110, 71, 255}, 105 | {"rgb(62.731,22.426,-1.441,0.018)", 63, 22, 0, 5}, 106 | {"rgb(260.117,262.267,234.967)", 255, 255, 235, 255}, 107 | {"rgb(4.026,-1.738,159.101)", 4, 0, 159, 255}, 108 | {"rgb(231.300,105.609,231.611,0.289)", 231, 106, 232, 74}, 109 | {"rgb(92.148,125.339,3.161)", 92, 125, 3, 255}, 110 | {"rgb(141.451,213.294,42.291)", 141, 213, 42, 255}, 111 | {"rgb(106.637%,63.245%,13.953%)", 255, 161, 36, 255}, 112 | {"rgb(36.891%,83.946%,-0.776%,-0.109)", 94, 214, 0, 0}, 113 | {"rgb(30.098%,57.177%,97.202%)", 77, 146, 248, 255}, 114 | {"rgb(20.348%,9.007%,50.198%)", 52, 23, 128, 255}, 115 | {"rgb(65.635%,38.597%,51.540%)", 167, 98, 131, 255}, 116 | {"rgb(82.140%,86.294%,74.226%)", 209, 220, 189, 255}, 117 | {"rgb(68.001%,43.582%,-8.031%,0.953)", 173, 111, 0, 243}, 118 | {"rgb(97.555%,88.344%,107.508%,1.067)", 249, 225, 255, 255}, 119 | {"rgb(103.833%,31.883%,106.519%,1.096)", 255, 81, 255, 255}, 120 | {"rgb(91.878%,77.833%,42.737%,1.036)", 234, 198, 109, 255}, 121 | {"rgb(-1.946%,104.666%,92.518%)", 0, 255, 236, 255}, 122 | {"rgb(-8.918%,43.768%,48.678%)", 0, 112, 124, 255}, 123 | {"rgb(22.344%,108.142%,25.588%,0.568)", 57, 255, 65, 145}, 124 | {"rgb(-4.792%,103.802%,25.728%,1.044)", 0, 255, 66, 255}, 125 | {"rgb(9.756%,-4.527%,55.095%)", 25, 0, 140, 255}, 126 | {"rgb(4.812%,102.908%,-9.392%)", 12, 255, 0, 255}, 127 | {"rgb(3.693%,104.465%,84.813%)", 9, 255, 216, 255}, 128 | {"rgb(29.187%,99.246%,53.246%)", 74, 253, 136, 255}, 129 | {"rgb(-7.354%,95.764%,105.057%)", 0, 244, 255, 255}, 130 | {"rgb(47.159%,48.044%,109.916%)", 120, 123, 255, 255}, 131 | {"rgb(45.759%,42.046%,91.989%)", 117, 107, 235, 255}, 132 | {"rgb(86.097%,91.214%,42.376%,0.086)", 220, 233, 108, 22}, 133 | {"rgb(13.127%,1.330%,40.329%)", 33, 3, 103, 255}, 134 | {"rgb(65.359%,88.567%,8.510%)", 167, 226, 22, 255}, 135 | {"rgb(63.728%,14.245%,39.191%)", 163, 36, 100, 255}, 136 | {"rgb(82.029%,70.333%,22.627%,0.130)", 209, 179, 58, 33}, 137 | {"rgb(-7.584%,24.596%,106.456%,0.540)", 0, 63, 255, 138}, 138 | {"rgb(34.295%,33.879%,25.945%)", 87, 86, 66, 255}, 139 | {"rgb(36.848%,55.483%,24.020%,0.215)", 94, 141, 61, 55}, 140 | {"rgb(34.615%,79.081%,96.135%,0.104)", 88, 202, 245, 27}, 141 | {"rgb(29.072%,76.973%,103.559%)", 74, 196, 255, 255}, 142 | {"rgb(-1.165%,18.030%,23.750%)", 0, 46, 61, 255}, 143 | {"rgb(23.834%,-4.078%,35.657%)", 61, 0, 91, 255}, 144 | {"rgb(10.751%,0.545%,73.270%)", 27, 1, 187, 255}, 145 | {"rgb(44.688%,9.295%,-1.679%)", 114, 24, 0, 255}, 146 | {"rgb(72.485%,65.899%,108.170%,0.917)", 185, 168, 255, 234}, 147 | {"rgb(34.422%,77.037%,93.107%)", 88, 196, 237, 255}, 148 | {"rgb(91.368%,55.279%,62.854%)", 233, 141, 160, 255}, 149 | {"rgb(102.434%,56.702%,33.212%)", 255, 145, 85, 255}, 150 | {"rgb(39.892%,87.191%,100.708%)", 102, 222, 255, 255}, 151 | {"rgb(65.042%,105.175%,5.622%)", 166, 255, 14, 255}, 152 | {"rgb(-5.767%,48.382%,-5.126%)", 0, 123, 0, 255}, 153 | {"rgb(17.665%,40.385%,80.169%)", 45, 103, 204, 255}, 154 | {"rgb(84.019%,-0.364%,9.859%,0.625)", 214, 0, 25, 159}, 155 | {"rgb(108.993%,100.397%,52.671%)", 255, 255, 134, 255}, 156 | {"rgb(19.223%,47.737%,51.318%)", 49, 122, 131, 255}, 157 | {"rgb(7.603%,70.494%,61.450%)", 19, 180, 157, 255}, 158 | {"rgb(10.202%,89.790%,78.516%)", 26, 229, 200, 255}, 159 | {"rgb(77.686%,97.221%,100.579%,0.246)", 198, 248, 255, 63}, 160 | {"rgb(-2.333%,11.540%,89.009%,0.802)", 0, 29, 227, 205}, 161 | {"hsl(303.505deg,40.607%,47.237%,-0.108)", 169, 72, 164, 0}, 162 | {"hsl(166.317grad,35.505%,61.623%,0.885)", 122, 192, 157, 226}, 163 | {"hsl(0.890turn,84.315%,-4.192%,0.733)", 0, 0, 0, 187}, 164 | {"hsl(4.367rad,26.559%,25.815%,1.025)", 54, 48, 83, 255}, 165 | {"hsl(129.011deg,24.599%,10.476%)", 20, 33, 22, 255}, 166 | {"hsl(0.132turn,18.948%,108.092%)", 255, 255, 255, 255}, 167 | {"hsl(6.366rad,86.281%,76.850%)", 247, 153, 145, 255}, 168 | {"hsl(0.025turn,9.070%,17.743%)", 49, 42, 41, 255}, 169 | {"hsl(346.054grad,22.344%,71.574%,-0.062)", 199, 166, 193, 0}, 170 | {"hsl(0.015turn,35.648%,92.027%)", 242, 229, 227, 255}, 171 | {"hsl(47.756grad,14.468%,86.803%)", 226, 223, 216, 255}, 172 | {"hsl(82.948deg,106.898%,51.390%,0.409)", 160, 255, 7, 104}, 173 | {"hsl(30.836deg,-6.307%,103.528%)", 255, 255, 255, 255}, 174 | {"hsl(43.265,70.201%,70.996%)", 233, 204, 129, 255}, 175 | {"hsl(128.089grad,69.628%,64.418%)", 111, 227, 101, 255}, 176 | {"hsl(32.213,84.179%,0.508%,0.967)", 2, 1, 0, 247}, 177 | {"hsl(243.278deg,107.466%,94.419%,-0.087)", 228, 227, 255, 0}, 178 | {"hsl(17.877,56.334%,102.965%,0.275)", 255, 255, 255, 70}, 179 | {"hsl(290.112,107.611%,108.356%)", 255, 255, 255, 255}, 180 | {"hsl(200.221grad,30.283%,11.069%)", 20, 37, 37, 255}, 181 | {"hsl(0.738turn,12.560%,3.624%,0.318)", 9, 8, 10, 81}, 182 | {"hsl(0.109turn,70.850%,74.131%,1.187)", 236, 203, 142, 255}, 183 | {"hsl(163.566,83.141%,36.520%)", 16, 171, 128, 255}, 184 | {"hsl(132.194,80.652%,105.081%,1.052)", 255, 255, 255, 255}, 185 | {"hsl(307.249deg,60.237%,26.132%)", 107, 26, 97, 255}, 186 | {"hsl(87.916grad,18.382%,-5.926%,0.725)", 0, 0, 0, 185}, 187 | {"hsl(0.923turn,22.762%,41.884%,1.001)", 131, 82, 105, 255}, 188 | {"hsl(316.684grad,55.930%,52.703%,0.661)", 168, 67, 202, 169}, 189 | {"hsl(0.939turn,57.243%,97.977%,1.009)", 253, 247, 249, 255}, 190 | {"hsl(-0.091turn,27.351%,92.166%)", 240, 230, 236, 255}, 191 | {"hsl(1.089turn,103.459%,55.996%,0.097)", 255, 150, 31, 25}, 192 | {"hsl(174.033deg,10.079%,29.391%)", 67, 83, 81, 255}, 193 | {"hsl(201.674,28.476%,8.261%)", 15, 23, 27, 255}, 194 | {"hsl(142.622deg,39.472%,90.933%,-0.126)", 223, 241, 230, 0}, 195 | {"hsl(4.658rad,107.070%,7.771%,0.600)", 18, 0, 40, 153}, 196 | {"hsl(4.880rad,80.889%,-9.699%,0.319)", 0, 0, 0, 81}, 197 | {"hsl(1.121rad,-5.569%,97.761%,1.084)", 249, 249, 249, 255}, 198 | {"hsl(262.036,5.324%,-4.960%,0.148)", 0, 0, 0, 38}, 199 | {"hsl(-0.455grad,41.379%,78.928%)", 224, 179, 179, 255}, 200 | {"hsl(0.390turn,105.835%,79.907%)", 153, 255, 187, 255}, 201 | {"hsl(0.347turn,-9.555%,33.512%)", 85, 85, 85, 255}, 202 | {"hsl(5.535rad,84.788%,36.228%)", 171, 14, 126, 255}, 203 | {"hsl(202.153,42.328%,57.768%)", 102, 159, 193, 255}, 204 | {"hsl(4.754rad,99.620%,30.456%,-0.115)", 84, 0, 155, 0}, 205 | {"hsl(0.703turn,-9.041%,64.426%,1.076)", 164, 164, 164, 255}, 206 | {"hsl(-0.020turn,22.850%,95.116%,0.123)", 245, 240, 240, 31}, 207 | {"hsl(403.987grad,42.191%,10.063%)", 36, 16, 15, 255}, 208 | {"hsl(0.104turn,49.789%,74.133%,0.198)", 222, 197, 156, 50}, 209 | {"hsl(0.088turn,32.001%,103.668%)", 255, 255, 255, 255}, 210 | {"hsl(53.662,57.956%,60.528%)", 213, 200, 96, 255}, 211 | } 212 | for _, d := range data { 213 | c, err := Parse(d.s) 214 | test(t, err, nil) 215 | test(t, arr8(c.RGBA255()), arr8(d.r, d.g, d.b, d.a)) 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /colorparser.go: -------------------------------------------------------------------------------- 1 | // Package csscolorparser provides function for parsing CSS color string as defined in the W3C's CSS color module level 4. 2 | package csscolorparser 3 | 4 | import ( 5 | "fmt" 6 | "math" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | // Inspired by https://github.com/deanm/css-color-parser-js 12 | 13 | // R, G, B, A values in the range 0..1 14 | type Color struct { 15 | R, G, B, A float64 16 | } 17 | 18 | // Implement the Go color.Color interface. 19 | func (c Color) RGBA() (r, g, b, a uint32) { 20 | r = uint32(c.R*c.A*65535 + 0.5) 21 | g = uint32(c.G*c.A*65535 + 0.5) 22 | b = uint32(c.B*c.A*65535 + 0.5) 23 | a = uint32(c.A*65535 + 0.5) 24 | return 25 | } 26 | 27 | // RGBA255 returns R, G, B, A values in the range 0..255 28 | func (c Color) RGBA255() (r, g, b, a uint8) { 29 | r = uint8(c.R*255 + 0.5) 30 | g = uint8(c.G*255 + 0.5) 31 | b = uint8(c.B*255 + 0.5) 32 | a = uint8(c.A*255 + 0.5) 33 | return 34 | } 35 | 36 | // Clamp restricts R, G, B, A values to the range 0..1. 37 | func (c Color) Clamp() Color { 38 | return Color{ 39 | R: math.Max(math.Min(c.R, 1), 0), 40 | G: math.Max(math.Min(c.G, 1), 0), 41 | B: math.Max(math.Min(c.B, 1), 0), 42 | A: math.Max(math.Min(c.A, 1), 0), 43 | } 44 | } 45 | 46 | // HexString returns CSS hexadecimal string. 47 | func (c Color) HexString() string { 48 | r, g, b, a := c.RGBA255() 49 | if a < 255 { 50 | return fmt.Sprintf("#%02x%02x%02x%02x", r, g, b, a) 51 | } 52 | return fmt.Sprintf("#%02x%02x%02x", r, g, b) 53 | } 54 | 55 | // RGBString returns CSS RGB string. 56 | func (c Color) RGBString() string { 57 | r, g, b, _ := c.RGBA255() 58 | if c.A < 1 { 59 | return fmt.Sprintf("rgba(%d,%d,%d,%v)", r, g, b, c.A) 60 | } 61 | return fmt.Sprintf("rgb(%d,%d,%d)", r, g, b) 62 | } 63 | 64 | // Name returns name of this color if its available. 65 | func (c Color) Name() (string, bool) { 66 | r, g, b, _ := c.RGBA255() 67 | rgb := [3]uint8{r, g, b} 68 | for k, v := range namedColors { 69 | if v == rgb { 70 | return k, true 71 | } 72 | } 73 | return "", false 74 | } 75 | 76 | // Implement the Go TextUnmarshaler interface 77 | func (c *Color) UnmarshalText(text []byte) error { 78 | col, err := Parse(string(text)) 79 | if err != nil { 80 | return err 81 | } 82 | c.R = col.R 83 | c.G = col.G 84 | c.B = col.B 85 | c.A = col.A 86 | return nil 87 | } 88 | 89 | // Implement the Go TextMarshaler interface 90 | func (c Color) MarshalText() ([]byte, error) { 91 | return []byte(c.HexString()), nil 92 | } 93 | 94 | // FromHsv creates a Color from HSV colors. 95 | // 96 | // Arguments: 97 | // 98 | // - h: Hue angle [0..360] 99 | // - s: Saturation [0..1] 100 | // - v: Value [0..1] 101 | // - a: Alpha [0..1] 102 | func FromHsv(h, s, v, a float64) Color { 103 | r, g, b := hsvToRgb(normalizeAngle(h), clamp0_1(s), clamp0_1(v)) 104 | return Color{r, g, b, clamp0_1(a)} 105 | } 106 | 107 | // FromHsl creates a Color from HSL colors. 108 | // 109 | // Arguments: 110 | // 111 | // - h: Hue angle [0..360] 112 | // - s: Saturation [0..1] 113 | // - l: Lightness [0..1] 114 | // - a: Alpha [0..1] 115 | func FromHsl(h, s, l, a float64) Color { 116 | r, g, b := hslToRgb(normalizeAngle(h), clamp0_1(s), clamp0_1(l)) 117 | return Color{r, g, b, clamp0_1(a)} 118 | } 119 | 120 | // FromHwb creates a Color from HWB colors. 121 | // 122 | // Arguments: 123 | // 124 | // - h: Hue angle [0..360] 125 | // - w: Whiteness [0..1] 126 | // - b: Blackness [0..1] 127 | // - a: Alpha [0..1] 128 | func FromHwb(h, w, b, a float64) Color { 129 | r, g, b := hwbToRgb(normalizeAngle(h), clamp0_1(w), clamp0_1(b)) 130 | return Color{r, g, b, clamp0_1(a)} 131 | } 132 | 133 | func fromLinear(x float64) float64 { 134 | if x >= 0.0031308 { 135 | return 1.055*math.Pow(x, 1.0/2.4) - 0.055 136 | } 137 | return 12.92 * x 138 | } 139 | 140 | // FromLinearRGB creates a Color from linear-light RGB colors. 141 | // 142 | // Arguments: 143 | // 144 | // - r: Red value [0..1] 145 | // - g: Green value [0..1] 146 | // - b: Blue value [0..1] 147 | // - a: Alpha value [0..1] 148 | func FromLinearRGB(r, g, b, a float64) Color { 149 | return Color{fromLinear(r), fromLinear(g), fromLinear(b), clamp0_1(a)} 150 | } 151 | 152 | // FromOklab creates a Color from Oklab colors. 153 | // 154 | // Arguments: 155 | // 156 | // - l: Perceived lightness 157 | // - a: How green/red the color is 158 | // - b: How blue/yellow the color is 159 | // - alpha: Alpha [0..1] 160 | func FromOklab(l, a, b, alpha float64) Color { 161 | l_ := math.Pow(l+0.3963377774*a+0.2158037573*b, 3) 162 | m_ := math.Pow(l-0.1055613458*a-0.0638541728*b, 3) 163 | s_ := math.Pow(l-0.0894841775*a-1.2914855480*b, 3) 164 | 165 | R := 4.0767416621*l_ - 3.3077115913*m_ + 0.2309699292*s_ 166 | G := -1.2684380046*l_ + 2.6097574011*m_ - 0.3413193965*s_ 167 | B := -0.0041960863*l_ - 0.7034186147*m_ + 1.7076147010*s_ 168 | 169 | return FromLinearRGB(R, G, B, alpha) 170 | } 171 | 172 | // FromOklch creates a Color from OKLCh colors. 173 | // 174 | // Arguments: 175 | // 176 | // - l: Perceived lightness 177 | // - c: Chroma 178 | // - h: Hue angle in radians 179 | // - alpha: Alpha [0..1] 180 | func FromOklch(l, c, h, alpha float64) Color { 181 | return FromOklab(l, c*math.Cos(h), c*math.Sin(h), alpha) 182 | } 183 | 184 | func labToXyz(l, a, b float64) (x, y, z float64) { 185 | const ( 186 | e = 216.0 / 24389.0 187 | k = 24389.0 / 27.0 188 | Xn = 0.95047 189 | Yn = 1.00000 190 | Zn = 1.08883 191 | ) 192 | 193 | fy := (l + 16.0) / 116.0 194 | fx := fy + (a / 500.0) 195 | fz := fy - (b / 200.0) 196 | 197 | if math.Pow(fy, 3) <= e { 198 | y = (Yn * (fy*7.787 + 16.0/116.0)) 199 | } else { 200 | y = (Yn * math.Pow(fy, 3)) 201 | } 202 | 203 | if math.Pow(fx, 3) <= e { 204 | x = (Xn * (fx*7.787 + 16.0/116.0)) 205 | } else { 206 | x = (Xn * math.Pow(fx, 3)) 207 | } 208 | 209 | if math.Pow(fz, 3) <= e { 210 | z = (Zn * (fz*7.787 + 16.0/116.0)) 211 | } else { 212 | z = (Zn * math.Pow(fz, 3)) 213 | } 214 | 215 | return 216 | } 217 | 218 | func xyzToLinearRgb(x, y, z float64) (r, g, b float64) { 219 | r = 3.2404537*x - 1.537177*y - 0.498531*z 220 | g = -0.969266*x + 1.876010*y + 0.041556*z 221 | b = 0.055643*x - 0.204025*y + 1.057225*z 222 | return 223 | } 224 | 225 | func FromLab(l, a, b, alpha float64) Color { 226 | x, y, z := labToXyz(l, a, b) 227 | R, G, B := xyzToLinearRgb(x, y, z) 228 | return FromLinearRGB(R, G, B, alpha) 229 | } 230 | 231 | func FromLch(l, c, h, alpha float64) Color { 232 | return FromLab(l, c*math.Cos(h), c*math.Sin(h), alpha) 233 | } 234 | 235 | var black = Color{0, 0, 0, 1} 236 | 237 | // Parse parses CSS color string and returns, if successful, a Color. 238 | func Parse(s string) (Color, error) { 239 | input := s 240 | s = strings.TrimSpace(strings.ToLower(s)) 241 | 242 | if s == "transparent" { 243 | return Color{0, 0, 0, 0}, nil 244 | } 245 | 246 | // Predefined name / keyword 247 | c, ok := namedColors[s] 248 | if ok { 249 | return Color{float64(c[0]) / 255, float64(c[1]) / 255, float64(c[2]) / 255, 1}, nil 250 | } 251 | 252 | // Hexadecimal 253 | if strings.HasPrefix(s, "#") { 254 | c, ok := parseHex(s[1:]) 255 | if ok { 256 | return c, nil 257 | } 258 | return black, fmt.Errorf("Invalid hex color, %s", input) 259 | } 260 | 261 | op := strings.Index(s, "(") 262 | 263 | if (op != -1) && strings.HasSuffix(s, ")") { 264 | fname := strings.TrimSpace(s[:op]) 265 | s = s[op+1 : len(s)-1] 266 | f := func(c rune) bool { 267 | return c == ',' || c == '/' || c == ' ' 268 | } 269 | params := strings.FieldsFunc(s, f) 270 | 271 | if len(params) != 3 && len(params) != 4 { 272 | return black, fmt.Errorf("Invalid format") 273 | } 274 | 275 | alpha := 1.0 276 | if len(params) == 4 { 277 | v, ok, _ := parsePercentOrFloat(params[3]) 278 | if !ok { 279 | return black, fmt.Errorf("Invalid format") 280 | } 281 | alpha = clamp0_1(v) 282 | } 283 | 284 | if fname == "rgb" || fname == "rgba" { 285 | r, okR, _ := parsePercentOr255(params[0]) 286 | g, okG, _ := parsePercentOr255(params[1]) 287 | b, okB, _ := parsePercentOr255(params[2]) 288 | 289 | if okR && okG && okB { 290 | return Color{ 291 | clamp0_1(r), 292 | clamp0_1(g), 293 | clamp0_1(b), 294 | alpha, 295 | }, nil 296 | } 297 | return black, fmt.Errorf("Wrong %s() components, %s", fname, input) 298 | 299 | } else if fname == "hsl" || fname == "hsla" { 300 | h, okH := parseAngle(params[0]) 301 | s, okS, _ := parsePercentOrFloat(params[1]) 302 | l, okL, _ := parsePercentOrFloat(params[2]) 303 | 304 | if okH && okS && okL { 305 | return FromHsl(h, s, l, alpha), nil 306 | } 307 | return black, fmt.Errorf("Wrong %s() components, %s", fname, input) 308 | 309 | } else if fname == "hwb" || fname == "hwba" { 310 | H, okH := parseAngle(params[0]) 311 | W, okW, _ := parsePercentOrFloat(params[1]) 312 | B, okB, _ := parsePercentOrFloat(params[2]) 313 | 314 | if okH && okW && okB { 315 | return FromHwb(H, W, B, alpha), nil 316 | } 317 | return black, fmt.Errorf("Wrong hwb() components, %s", input) 318 | 319 | } else if fname == "hsv" || fname == "hsva" { 320 | h, okH := parseAngle(params[0]) 321 | s, okS, _ := parsePercentOrFloat(params[1]) 322 | v, okV, _ := parsePercentOrFloat(params[2]) 323 | 324 | if okH && okS && okV { 325 | return FromHsv(h, s, v, alpha), nil 326 | } 327 | return black, fmt.Errorf("Wrong hsv() components, %s", input) 328 | 329 | } else if fname == "oklab" { 330 | l, okL, _ := parsePercentOrFloat(params[0]) 331 | a, okA, fmtA := parsePercentOrFloat(params[1]) 332 | b, okB, fmtB := parsePercentOrFloat(params[2]) 333 | 334 | if okL && okA && okB { 335 | if fmtA { 336 | a = remap(a, -1.0, 1.0, -0.4, 0.4) 337 | } 338 | if fmtB { 339 | b = remap(b, -1.0, 1.0, -0.4, 0.4) 340 | } 341 | return FromOklab(math.Max(l, 0), a, b, alpha), nil 342 | } 343 | return black, fmt.Errorf("Wrong oklab() components, %s", input) 344 | 345 | } else if fname == "oklch" { 346 | l, okL, _ := parsePercentOrFloat(params[0]) 347 | c, okC, fmtC := parsePercentOrFloat(params[1]) 348 | h, okH := parseAngle(params[2]) 349 | 350 | if okL && okC && okH { 351 | if fmtC { 352 | c = c * 0.4 353 | } 354 | return FromOklch(math.Max(l, 0), math.Max(c, 0), h*math.Pi/180, alpha), nil 355 | } 356 | return black, fmt.Errorf("Wrong oklch() components, %s", input) 357 | } else if fname == "lab" { 358 | l, okL, fmtL := parsePercentOrFloat(params[0]) 359 | a, okA, fmtA := parsePercentOrFloat(params[1]) 360 | b, okB, fmtB := parsePercentOrFloat(params[2]) 361 | 362 | if okL && okA && okB { 363 | if fmtL { 364 | l = l * 100 365 | } 366 | if fmtA { 367 | a = remap(a, -1, 1, -125, 125) 368 | } 369 | if fmtB { 370 | b = remap(b, -1, 1, -125, 125) 371 | } 372 | return FromLab(math.Max(l, 0), a, b, alpha), nil 373 | } 374 | return black, fmt.Errorf("Invalid lab()") 375 | } else if fname == "lch" { 376 | l, okL, fmtL := parsePercentOrFloat(params[0]) 377 | c, okC, fmtC := parsePercentOrFloat(params[1]) 378 | h, okH := parseAngle(params[2]) 379 | 380 | if okL && okC && okH { 381 | if fmtL { 382 | l = l * 100 383 | } 384 | if fmtC { 385 | c = c * 150 386 | } 387 | return FromLch(math.Max(l, 0), math.Max(c, 0), h*math.Pi/180, alpha), nil 388 | } 389 | return black, fmt.Errorf("Invalid lch()") 390 | } 391 | } 392 | 393 | // RGB hexadecimal format without '#' prefix 394 | c2, ok2 := parseHex(s) 395 | if ok2 { 396 | return c2, nil 397 | } 398 | 399 | return black, fmt.Errorf("Invalid color format, %s", input) 400 | } 401 | 402 | // https://stackoverflow.com/questions/54197913/parse-hex-string-to-image-color 403 | 404 | func parseHex(s string) (c Color, ok bool) { 405 | c.A = 1 406 | ok = true 407 | 408 | hexToByte := func(b byte) byte { 409 | switch { 410 | case b >= '0' && b <= '9': 411 | return b - '0' 412 | case b >= 'a' && b <= 'f': 413 | return b - 'a' + 10 414 | } 415 | ok = false 416 | return 0 417 | } 418 | 419 | n := len(s) 420 | if n == 6 || n == 8 { 421 | c.R = float64(hexToByte(s[0])<<4+hexToByte(s[1])) / 255 422 | c.G = float64(hexToByte(s[2])<<4+hexToByte(s[3])) / 255 423 | c.B = float64(hexToByte(s[4])<<4+hexToByte(s[5])) / 255 424 | if n == 8 { 425 | c.A = float64(hexToByte(s[6])<<4+hexToByte(s[7])) / 255 426 | } 427 | } else if n == 3 || n == 4 { 428 | c.R = float64(hexToByte(s[0])*17) / 255 429 | c.G = float64(hexToByte(s[1])*17) / 255 430 | c.B = float64(hexToByte(s[2])*17) / 255 431 | if n == 4 { 432 | c.A = float64(hexToByte(s[3])*17) / 255 433 | } 434 | } else { 435 | ok = false 436 | } 437 | return 438 | } 439 | 440 | func modulo(x, y float64) float64 { 441 | return math.Mod(math.Mod(x, y)+y, y) 442 | } 443 | 444 | func hueToRgb(n1, n2, h float64) float64 { 445 | h = modulo(h, 6) 446 | if h < 1 { 447 | return n1 + ((n2 - n1) * h) 448 | } 449 | if h < 3 { 450 | return n2 451 | } 452 | if h < 4 { 453 | return n1 + ((n2 - n1) * (4 - h)) 454 | } 455 | return n1 456 | } 457 | 458 | // h = 0..360 459 | // s, l = 0..1 460 | // r, g, b = 0..1 461 | func hslToRgb(h, s, l float64) (r, g, b float64) { 462 | if s == 0 { 463 | return l, l, l 464 | } 465 | var n2 float64 466 | if l < 0.5 { 467 | n2 = l * (1 + s) 468 | } else { 469 | n2 = l + s - (l * s) 470 | } 471 | n1 := 2*l - n2 472 | h /= 60 473 | r = clamp0_1(hueToRgb(n1, n2, h+2)) 474 | g = clamp0_1(hueToRgb(n1, n2, h)) 475 | b = clamp0_1(hueToRgb(n1, n2, h-2)) 476 | return 477 | } 478 | 479 | func hwbToRgb(hue, white, black float64) (r, g, b float64) { 480 | if white+black >= 1 { 481 | gray := white / (white + black) 482 | return gray, gray, gray 483 | } 484 | r, g, b = hslToRgb(hue, 1, 0.5) 485 | r = r*(1-white-black) + white 486 | g = g*(1-white-black) + white 487 | b = b*(1-white-black) + white 488 | return 489 | } 490 | 491 | func hsvToHsl(H, S, V float64) (h, s, l float64) { 492 | h = H 493 | s = S 494 | l = (2 - S) * V / 2 495 | if l != 0 { 496 | if l == 1 { 497 | s = 0 498 | } else if l < 0.5 { 499 | s = S * V / (l * 2) 500 | } else { 501 | s = S * V / (2 - l*2) 502 | } 503 | } 504 | return 505 | } 506 | 507 | func hsvToRgb(H, S, V float64) (r, g, b float64) { 508 | h, s, l := hsvToHsl(H, S, V) 509 | return hslToRgb(h, s, l) 510 | } 511 | 512 | func clamp0_1(t float64) float64 { 513 | if t < 0 { 514 | return 0 515 | } 516 | if t > 1 { 517 | return 1 518 | } 519 | return t 520 | } 521 | 522 | func parseFloat(s string) (float64, bool) { 523 | f, err := strconv.ParseFloat(strings.TrimSpace(s), 64) 524 | return f, err == nil 525 | } 526 | 527 | // Returns (result, ok?, percentage?) 528 | func parsePercentOrFloat(s string) (float64, bool, bool) { 529 | if strings.HasSuffix(s, "%") { 530 | f, ok := parseFloat(s[:len(s)-1]) 531 | if ok { 532 | return f / 100, true, true 533 | } 534 | return 0, false, true 535 | } 536 | f, ok := parseFloat(s) 537 | return f, ok, false 538 | } 539 | 540 | // Returns (result, ok?, percentage?) 541 | func parsePercentOr255(s string) (float64, bool, bool) { 542 | if strings.HasSuffix(s, "%") { 543 | f, ok := parseFloat(s[:len(s)-1]) 544 | if ok { 545 | return f / 100, true, true 546 | } 547 | return 0, false, true 548 | } 549 | f, ok := parseFloat(s) 550 | if ok { 551 | return f / 255, true, false 552 | } 553 | return 0, false, false 554 | } 555 | 556 | // Result angle in degrees (not normalized) 557 | func parseAngle(s string) (float64, bool) { 558 | if strings.HasSuffix(s, "deg") { 559 | return parseFloat(s[:len(s)-3]) 560 | } 561 | if strings.HasSuffix(s, "grad") { 562 | f, ok := parseFloat(s[:len(s)-4]) 563 | if ok { 564 | return f / 400 * 360, true 565 | } 566 | return 0, false 567 | } 568 | if strings.HasSuffix(s, "rad") { 569 | f, ok := parseFloat(s[:len(s)-3]) 570 | if ok { 571 | return f / math.Pi * 180, true 572 | } 573 | return 0, false 574 | } 575 | if strings.HasSuffix(s, "turn") { 576 | f, ok := parseFloat(s[:len(s)-4]) 577 | if ok { 578 | return f * 360, true 579 | } 580 | return 0, false 581 | } 582 | return parseFloat(s) 583 | } 584 | 585 | func normalizeAngle(t float64) float64 { 586 | t = math.Mod(t, 360) 587 | if t < 0 { 588 | t += 360 589 | } 590 | return t 591 | } 592 | 593 | // Map t which is in range [a, b] to range [c, d] 594 | func remap(t, a, b, c, d float64) float64 { 595 | return (t-a)*((d-c)/(b-a)) + c 596 | } 597 | -------------------------------------------------------------------------------- /colorparser_test.go: -------------------------------------------------------------------------------- 1 | package csscolorparser 2 | 3 | import ( 4 | "image/color" 5 | "testing" 6 | ) 7 | 8 | // --- Helper functions 9 | 10 | func test(t *testing.T, a, b interface{}) { 11 | if a != b { 12 | t.Helper() 13 | t.Errorf("left: %v, right: %v", a, b) 14 | } 15 | } 16 | 17 | func testTrue(t *testing.T, b bool) { 18 | if !b { 19 | t.Helper() 20 | t.Errorf("it false") 21 | } 22 | } 23 | 24 | func testColor(t *testing.T, a, b Color) { 25 | x := arr8(a.RGBA255()) 26 | y := arr8(b.RGBA255()) 27 | if x == y { 28 | return 29 | } 30 | t.Helper() 31 | t.Errorf("left: %v, right: %v", x, y) 32 | } 33 | 34 | func testGoColor(t *testing.T, a, b color.Color) { 35 | x := arr32(a.RGBA()) 36 | y := arr32(b.RGBA()) 37 | if x == y { 38 | return 39 | } 40 | t.Helper() 41 | t.Errorf("left: %v, right: %v", x, y) 42 | } 43 | 44 | func arr8(r, g, b, a uint8) [4]uint8 { 45 | return [4]uint8{r, g, b, a} 46 | } 47 | 48 | func arr32(r, g, b, a uint32) [4]uint32 { 49 | return [4]uint32{r, g, b, a} 50 | } 51 | 52 | // --- 53 | 54 | func Test_Color(t *testing.T) { 55 | var c Color 56 | 57 | c = Color{0, 0, 1, 1} 58 | test(t, c.HexString(), "#0000ff") 59 | test(t, c.RGBString(), "rgb(0,0,255)") 60 | testGoColor(t, c, color.NRGBA{0, 0, 255, 255}) 61 | 62 | c = Color{0, 0, 1, 0.5} 63 | test(t, c.HexString(), "#0000ff80") 64 | test(t, c.RGBString(), "rgba(0,0,255,0.5)") 65 | //testGoColor(t, c, color.NRGBA{0,0,255,127}) 66 | 67 | testGoColor(t, Color{A: 1}, color.Gray{0}) 68 | 69 | c = Color{1.2001, 0.999, -0.001, 0.001}.Clamp() 70 | testColor(t, c, Color{1, 0.999, 0, 0.001}) 71 | 72 | c = FromHwb(0, 0, 0, 1) 73 | test(t, c.HexString(), "#ff0000") 74 | 75 | c = FromHwb(360, 0, 0, 1) 76 | test(t, c.HexString(), "#ff0000") 77 | 78 | c = FromHsv(120, 1, 1, 1) 79 | test(t, c.HexString(), "#00ff00") 80 | 81 | c = FromHsl(180, 1, 0.5, 1) 82 | test(t, c.HexString(), "#00ffff") 83 | 84 | c = FromOklab(0.62796, 0.22486, 0.12585, 1) 85 | test(t, c.HexString(), "#ff0000") 86 | 87 | c = FromOklch(0.62796, 0.25768, 0.51, 1) 88 | test(t, c.HexString(), "#ff0000") 89 | 90 | c = FromOklch(0.86644, 0.29483, 2.487, 1) 91 | test(t, c.HexString(), "#00ff00") 92 | } 93 | 94 | func Test_ParseColor(t *testing.T) { 95 | data0 := []struct { 96 | s string 97 | rgba8 [4]uint8 98 | }{ 99 | {"transparent", [4]uint8{0, 0, 0, 0}}, 100 | {"rebeccapurple", [4]uint8{102, 51, 153, 255}}, 101 | {"#ff00ff64", [4]uint8{255, 0, 255, 100}}, 102 | {"ff00ff64", [4]uint8{255, 0, 255, 100}}, 103 | {"rgb(247,179,99)", [4]uint8{247, 179, 99, 255}}, 104 | {"rgb(50% 50% 50%)", [4]uint8{128, 128, 128, 255}}, 105 | {"rgb(247,179,99,0.37)", [4]uint8{247, 179, 99, 94}}, 106 | {"oklab(64.3% 52.6% 40% 2.5%)", [4]uint8{6, 26, 133, 6}}, 107 | {"oklch(0.46212, 80.9%, 29.23388, 17.33713)", [4]uint8{214, 206, 150, 255}}, 108 | {"hsl(270 0% 50%)", [4]uint8{128, 128, 128, 255}}, 109 | {"hwb(0 50% 50%)", [4]uint8{128, 128, 128, 255}}, 110 | {"hsv(0 0% 50%)", [4]uint8{128, 128, 128, 255}}, 111 | {"hsv(0 0% 100%)", [4]uint8{255, 255, 255, 255}}, 112 | {"hsv(0 0% 19%)", [4]uint8{48, 48, 48, 255}}, 113 | //{"lab(0%,0,0)", [4]uint8{0, 0, 0, 255}}, 114 | //{"lab(100%,0,0)", [4]uint8{255, 255, 255, 255}}, 115 | //{"lch(0%,0,0)", [4]uint8{0, 0, 0, 255}}, 116 | //{"lch(100%,0,0)", [4]uint8{255, 255, 255, 255}}, 117 | } 118 | for _, d := range data0 { 119 | c, err := Parse(d.s) 120 | test(t, err, nil) 121 | test(t, arr8(c.RGBA255()), d.rgba8) 122 | } 123 | 124 | data1 := []struct { 125 | s string 126 | c Color 127 | }{ 128 | {"hwb(0, 0%, 0%)", FromHwb(0, 0, 0, 1)}, 129 | {"hwb(320, 10%, 30%)", FromHwb(320, 0.1, 0.3, 1)}, 130 | {"hsv(120, 30%, 50%)", FromHsv(120, 0.3, 0.5, 1)}, 131 | {"hsl(120, 30%, 50%)", FromHsl(120, 0.3, 0.5, 1)}, 132 | } 133 | for _, dt := range data1 { 134 | c, err := Parse(dt.s) 135 | test(t, err, nil) 136 | test(t, dt.c.HexString(), c.HexString()) 137 | } 138 | 139 | data2 := []string{ 140 | "#666666", 141 | "#ff0000", 142 | "#00ff7f", 143 | } 144 | for _, s := range data2 { 145 | c1, err := Parse(s) 146 | test(t, err, nil) 147 | c2, err2 := Parse(c1.RGBString()) 148 | test(t, err2, nil) 149 | test(t, c2.HexString(), s) 150 | } 151 | 152 | /* 153 | a, err := Parse("#7654CD") 154 | test(t, err, nil) 155 | b, err := Parse("lab(44.36% 36.05 -58.99)") 156 | test(t, err, nil) 157 | testColor(t, a, b) 158 | */ 159 | } 160 | 161 | func Test_MarshalUnmarshal(t *testing.T) { 162 | var c Color 163 | err := c.UnmarshalText([]byte("gold")) 164 | test(t, err, nil) 165 | test(t, c.HexString(), "#ffd700") 166 | 167 | encoding, err := c.MarshalText() 168 | test(t, err, nil) 169 | test(t, string(encoding), "#ffd700") 170 | 171 | err = c.UnmarshalText([]byte("golden")) 172 | testTrue(t, err != nil) 173 | } 174 | 175 | func Test_EqualColorsBlack(t *testing.T) { 176 | data := []string{ 177 | "black", 178 | "#000", 179 | "#000f", 180 | "#000000", 181 | "#000000ff", 182 | "rgb(0,0,0)", 183 | "rgb(0% 0% 0%)", 184 | "rgb(0 0 0 100%)", 185 | "hsl(270,100%,0%)", 186 | "hwb(90 0% 100%)", 187 | "hwb(120deg 0% 100% 100%)", 188 | "hsv(120 100% 0%)", 189 | } 190 | for _, d := range data { 191 | c, err := Parse(d) 192 | test(t, err, nil) 193 | testColor(t, c, Color{0, 0, 0, 1}) 194 | } 195 | } 196 | 197 | func Test_EqualColorsRed(t *testing.T) { 198 | data := []string{ 199 | "red", 200 | "#f00", 201 | "#f00f", 202 | "#ff0000", 203 | "#ff0000ff", 204 | "rgb(255,0,0)", 205 | "rgb(255 0 0)", 206 | "rgb(700, -99, 0)", // clamp to 0..255 207 | "rgb(100% 0% 0%)", 208 | "rgb(200% -10% -100%)", // clamp to 0%..100% 209 | "rgb(255 0 0 100%)", 210 | "RGB( 255 , 0 , 0 )", 211 | "RGB( 255 0 0 )", 212 | "hsl(0,100%,50%)", 213 | "hsl(360 100% 50%)", 214 | "hwb(0 0% 0%)", 215 | "hwb(360deg 0% 0% 100%)", 216 | "hsv(0 100% 100%)", 217 | "oklab(0.62796, 0.22486, 0.12585)", 218 | "oklch(0.62796, 0.25768, 29.23388)", 219 | } 220 | for _, d := range data { 221 | c, err := Parse(d) 222 | test(t, err, nil) 223 | testColor(t, c, Color{1, 0, 0, 1}) 224 | } 225 | } 226 | 227 | func Test_EqualColorsLime(t *testing.T) { 228 | data := []string{ 229 | "lime", 230 | "#0f0", 231 | "#0f0f", 232 | "#00ff00", 233 | "#00ff00ff", 234 | "rgb(0,255,0)", 235 | "rgb(0% 100% 0%)", 236 | "rgb(0 255 0 / 100%)", 237 | "rgba(0,255,0,1)", 238 | "hsl(120,100%,50%)", 239 | "hsl(120deg 100% 50%)", 240 | "hsl(-240 100% 50%)", 241 | "hsl(-240deg 100% 50%)", 242 | "hsl(0.3333turn 100% 50%)", 243 | "hsl(133.333grad 100% 50%)", 244 | "hsl(2.0944rad 100% 50%)", 245 | "hsla(120,100%,50%,100%)", 246 | "hwb(120 0% 0%)", 247 | "hwb(480deg 0% 0% / 100%)", 248 | "hsv(120 100% 100%)", 249 | "oklab(0.86644, -0.23389, 0.1795)", 250 | "oklch(0.86644, 0.29483, 142.49535)", 251 | } 252 | for _, d := range data { 253 | c, err := Parse(d) 254 | test(t, err, nil) 255 | testColor(t, c, Color{0, 1, 0, 1}) 256 | } 257 | } 258 | 259 | func Test_EqualColorsLimeAlpha(t *testing.T) { 260 | data := []string{ 261 | "#00ff0080", 262 | "rgb(0,255,0,50%)", 263 | "rgb(0% 100% 0% / 0.5)", 264 | "rgba(0%,100%,0%,50%)", 265 | "hsl(120,100%,50%,0.5)", 266 | "hsl(120deg 100% 50% / 50%)", 267 | "hsla(120,100%,50%,0.5)", 268 | "hwb(120 0% 0% / 50%)", 269 | "hsv(120 100% 100% / 50%)", 270 | } 271 | for _, d := range data { 272 | c, err := Parse(d) 273 | test(t, err, nil) 274 | testColor(t, c, Color{0, 1, 0, 0.5}) 275 | } 276 | } 277 | 278 | func Test_InvalidData(t *testing.T) { 279 | data := []string{ 280 | "", 281 | "bloodred", 282 | "#78afzd", 283 | "#fffff", 284 | "rgb(0,255,8s)", 285 | "rgb(100%,z9%,75%)", 286 | "cmyk(1 0 0)", 287 | "rgba(0 0)", 288 | "hsl(90',100%,50%)", 289 | "hsl(deg 100% 50%)", 290 | "hsl(Xturn 100% 50%)", 291 | "hsl(Zgrad 100% 50%)", 292 | "hsl(180 1 x%)", 293 | "hsla(360)", 294 | "hwb(Xrad,50%,50%)", 295 | "hwb(270 0% 0% 0% 0%)", 296 | "hsv(120 100% 100% 1 50%)", 297 | "hsv(120 XXX 100%)", 298 | "oklab(0,0)", 299 | "oklab(0,0,x,0)", 300 | "oklch(0,0,0,0,0)", 301 | "oklch(0,0,0,x)", 302 | } 303 | for _, s := range data { 304 | c, err := Parse(s) 305 | testTrue(t, err != nil) 306 | testColor(t, c, Color{A: 1}) 307 | } 308 | } 309 | 310 | func Test_Utils(t *testing.T) { 311 | // parseAngle 312 | 313 | data := []struct { 314 | s string 315 | f float64 316 | }{ 317 | {"360", 360}, 318 | {"127.356", 127.356}, 319 | {"+120deg", 120}, 320 | {"90deg", 90}, 321 | {"-127deg", -127}, 322 | {"100grad", 90}, 323 | {"1.5707963267948966rad", 90}, 324 | {"0.25turn", 90}, 325 | {"-0.25turn", -90}, 326 | } 327 | for _, s := range data { 328 | d, ok := parseAngle(s.s) 329 | testTrue(t, ok) 330 | test(t, d, s.f) 331 | } 332 | 333 | // normalizeAngle 334 | 335 | data2 := [][2]float64{ 336 | {0, 0}, 337 | {360, 0}, 338 | {400, 40}, 339 | {1155, 75}, 340 | {-360, 0}, 341 | {-90, 270}, 342 | {-765, 315}, 343 | } 344 | for _, d := range data2 { 345 | test(t, normalizeAngle(d[0]), d[1]) 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package csscolorparser_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mazznoer/csscolorparser" 7 | ) 8 | 9 | func Example_namedColor() { 10 | c, err := csscolorparser.Parse("gold") 11 | 12 | if err != nil { 13 | panic(err) 14 | } 15 | 16 | fmt.Printf("R:%.3f, G:%.3f, B:%.3f, A:%.3f\n", c.R, c.G, c.B, c.A) 17 | fmt.Println(c.RGBA255()) 18 | fmt.Println(c.HexString()) 19 | fmt.Println(c.RGBString()) 20 | // Output: 21 | // R:1.000, G:0.843, B:0.000, A:1.000 22 | // 255 215 0 255 23 | // #ffd700 24 | // rgb(255,215,0) 25 | } 26 | 27 | func Example_rgbColor() { 28 | c, err := csscolorparser.Parse("rgba(100%, 0%, 0%, 0.5)") 29 | 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | fmt.Printf("R:%.3f, G:%.3f, B:%.3f, A:%.3f\n", c.R, c.G, c.B, c.A) 35 | fmt.Println(c.RGBA255()) 36 | fmt.Println(c.HexString()) 37 | fmt.Println(c.RGBString()) 38 | // Output: 39 | // R:1.000, G:0.000, B:0.000, A:0.500 40 | // 255 0 0 128 41 | // #ff000080 42 | // rgba(255,0,0,0.5) 43 | } 44 | -------------------------------------------------------------------------------- /firefox_test.go: -------------------------------------------------------------------------------- 1 | package csscolorparser 2 | 3 | import "testing" 4 | 5 | func Test_BrowserFirefox(t *testing.T) { 6 | // Randomly generated color string, parsed using Mozilla Firefox 84.0.2 7 | data := []struct { 8 | s string 9 | r, g, b, a uint8 10 | }{ 11 | {"#817801", 129, 120, 1, 255}, 12 | {"#62D5", 102, 34, 221, 85}, 13 | {"#857D", 136, 85, 119, 221}, 14 | {"#8D0C5BBE", 141, 12, 91, 190}, 15 | {"#BD5", 187, 221, 85, 255}, 16 | {"#A64", 170, 102, 68, 255}, 17 | {"#8092", 136, 0, 153, 34}, 18 | {"#1C0FF87B", 28, 15, 248, 123}, 19 | {"#818FFE", 129, 143, 254, 255}, 20 | {"#1ed0ebea", 30, 208, 235, 234}, 21 | {"#0D5FCA67", 13, 95, 202, 103}, 22 | {"#C285F3", 194, 133, 243, 255}, 23 | {"#7D0D", 119, 221, 0, 221}, 24 | {"#E30", 238, 51, 0, 255}, 25 | {"#6e5dc4", 110, 93, 196, 255}, 26 | {"#389A", 51, 136, 153, 170}, 27 | {"#F1D", 255, 17, 221, 255}, 28 | {"#8BB5", 136, 187, 187, 85}, 29 | {"#3DAA", 51, 221, 170, 170}, 30 | {"#B1D88F42", 177, 216, 143, 66}, 31 | {"#793", 119, 153, 51, 255}, 32 | {"#55da", 85, 85, 221, 170}, 33 | {"#BE70C687", 190, 112, 198, 135}, 34 | {"#4FD", 68, 255, 221, 255}, 35 | {"#30D38748", 48, 211, 135, 72}, 36 | {"#878", 136, 119, 136, 255}, 37 | {"#AC0D", 170, 204, 0, 221}, 38 | {"#12E", 17, 34, 238, 255}, 39 | {"#0DD3", 0, 221, 221, 51}, 40 | {"#7F54C08F", 127, 84, 192, 143}, 41 | {"#6cc7cba0", 108, 199, 203, 160}, 42 | {"#E97933", 233, 121, 51, 255}, 43 | {"#9237", 153, 34, 51, 119}, 44 | {"#F1FB94BD", 241, 251, 148, 189}, 45 | {"#9a8", 153, 170, 136, 255}, 46 | {"#0fcb7f03", 15, 203, 127, 3}, 47 | {"#534603AF", 83, 70, 3, 175}, 48 | {"#ef4cfc11", 239, 76, 252, 17}, 49 | {"#5D16052C", 93, 22, 5, 44}, 50 | {"#090", 0, 153, 0, 255}, 51 | {"#bc1f653a", 188, 31, 101, 58}, 52 | {"#ED02A8", 237, 2, 168, 255}, 53 | {"#6dc", 102, 221, 204, 255}, 54 | {"#E2E9", 238, 34, 238, 153}, 55 | {"#77B69060", 119, 182, 144, 96}, 56 | {"#0953FC", 9, 83, 252, 255}, 57 | {"#09C", 0, 153, 204, 255}, 58 | {"#172aca", 23, 42, 202, 255}, 59 | {"#74c18f04", 116, 193, 143, 4}, 60 | {"#153B", 17, 85, 51, 187}, 61 | {"rgb(264.654,8.955,5.446)", 255, 9, 5, 255}, 62 | {"rgb(63.688,192.702,251.174,0.322)", 64, 193, 251, 82}, 63 | {"rgb(131.708,39.654,79.812,1.128)", 132, 40, 80, 255}, 64 | {"rgb(116.842,105.189,171.013)", 117, 105, 171, 255}, 65 | {"rgb(120.525,88.804,130.411,0.946)", 121, 89, 130, 241}, 66 | {"rgb(240.988,49.789,222.272,0.325)", 241, 50, 222, 83}, 67 | {"rgb(187.245,215.507,159.236)", 187, 216, 159, 255}, 68 | {"rgb(98.714,205.689,261.942)", 99, 206, 255, 255}, 69 | {"rgb(209.553,174.927,193.981)", 210, 175, 194, 255}, 70 | {"rgb(7.372,4.253,86.174,0.041)", 7, 4, 86, 10}, 71 | {"rgb(150.202,78.032,213.103,0.337)", 150, 78, 213, 86}, 72 | {"rgb(246.369,62.382,31.651)", 246, 62, 32, 255}, 73 | {"rgb(236.566,160.563,89.892)", 237, 161, 90, 255}, 74 | {"rgb(112.356,163.535,102.720,-0.042)", 112, 164, 103, 0}, 75 | {"rgb(35.219,244.481,202.782,0.493)", 35, 244, 203, 126}, 76 | {"rgb(232.189,255.053,111.323)", 232, 255, 111, 255}, 77 | {"rgb(109.771,118.748,181.671)", 110, 119, 182, 255}, 78 | {"rgb(92.105,105.280,144.175)", 92, 105, 144, 255}, 79 | {"rgb(18.643,213.863,186.733)", 19, 214, 187, 255}, 80 | {"rgb(3.754,214.128,20.735,1.158)", 4, 214, 21, 255}, 81 | {"rgb(234.259,224.675,206.049)", 234, 225, 206, 255}, 82 | {"rgb(-7.668,131.621,160.351,0.411)", 0, 132, 160, 105}, 83 | {"rgb(45.824,242.113,209.576)", 46, 242, 210, 255}, 84 | {"rgb(30.113,222.102,93.192)", 30, 222, 93, 255}, 85 | {"rgb(245.402,199.722,-8.903)", 245, 200, 0, 255}, 86 | {"rgb(106.884,234.873,44.912,0.572)", 107, 235, 45, 146}, 87 | {"rgb(76.730,168.471,90.981)", 77, 168, 91, 255}, 88 | {"rgb(197.334,-0.665,134.907,0.421)", 197, 0, 135, 107}, 89 | {"rgb(120.677,48.776,120.922)", 121, 49, 121, 255}, 90 | {"rgb(165.450,89.865,211.876)", 165, 90, 212, 255}, 91 | {"rgb(88.649,20.622,250.829)", 89, 21, 251, 255}, 92 | {"rgb(-3.239,112.321,33.676)", 0, 112, 34, 255}, 93 | {"rgb(211.846,130.462,175.005)", 212, 130, 175, 255}, 94 | {"rgb(154.051,122.824,-9.377)", 154, 123, 0, 255}, 95 | {"rgb(144.666,79.334,20.699,0.412)", 145, 79, 21, 105}, 96 | {"rgb(234.323,96.841,102.930,0.187)", 234, 97, 103, 48}, 97 | {"rgb(196.469,255.522,21.318,0.300)", 196, 255, 21, 77}, 98 | {"rgb(23.779,145.864,206.664)", 24, 146, 207, 255}, 99 | {"rgb(133.613,9.608,39.029)", 134, 10, 39, 255}, 100 | {"rgb(218.954,163.743,79.818)", 219, 164, 80, 255}, 101 | {"rgb(33.108,151.645,11.619,1.132)", 33, 152, 12, 255}, 102 | {"rgb(204.629,39.956,-4.544)", 205, 40, 0, 255}, 103 | {"rgb(123.245,255.852,24.856)", 123, 255, 25, 255}, 104 | {"rgb(90.894,-2.651,116.502,0.596)", 91, 0, 117, 152}, 105 | {"rgb(79.128,115.857,155.230)", 79, 116, 155, 255}, 106 | {"rgb(87.319,70.167,172.069,-0.033)", 87, 70, 172, 0}, 107 | {"rgb(48.049,235.369,171.190,0.534)", 48, 235, 171, 136}, 108 | {"rgb(15.808,223.023,81.102,-0.116)", 16, 223, 81, 0}, 109 | {"rgb(255.282,239.066,222.922)", 255, 239, 223, 255}, 110 | {"rgb(173.178,-7.708,29.095,1.177)", 173, 0, 29, 255}, 111 | {"rgb(-5.791%,-7.797%,44.772%)", 0, 0, 114, 255}, 112 | {"rgb(103.362%,86.514%,79.870%,0.467)", 255, 221, 204, 119}, 113 | {"rgb(79.920%,30.359%,66.318%)", 204, 77, 169, 255}, 114 | {"rgb(54.309%,48.762%,42.059%)", 138, 124, 107, 255}, 115 | {"rgb(89.114%,50.975%,80.230%,0.843)", 227, 130, 205, 215}, 116 | {"rgb(-2.707%,3.715%,26.256%,0.185)", 0, 9, 67, 47}, 117 | {"rgb(51.827%,102.172%,-0.876%,0.302)", 132, 255, 0, 77}, 118 | {"rgb(14.092%,63.162%,97.286%)", 36, 161, 248, 255}, 119 | {"rgb(20.757%,33.676%,26.518%)", 53, 86, 68, 255}, 120 | {"rgb(85.387%,32.861%,29.168%)", 218, 84, 74, 255}, 121 | {"rgb(20.322%,107.488%,-9.083%)", 52, 255, 0, 255}, 122 | {"rgb(9.805%,12.717%,78.073%)", 25, 32, 199, 255}, 123 | {"rgb(80.225%,-7.567%,91.104%)", 205, 0, 232, 255}, 124 | {"rgb(57.826%,66.957%,35.887%)", 147, 171, 92, 255}, 125 | {"rgb(-5.675%,36.883%,86.567%)", 0, 94, 221, 255}, 126 | {"rgb(96.884%,-7.570%,86.334%,0.539)", 247, 0, 220, 137}, 127 | {"rgb(109.872%,32.523%,-3.411%,1.081)", 255, 83, 0, 255}, 128 | {"rgb(31.101%,2.122%,41.198%)", 79, 5, 105, 255}, 129 | {"rgb(15.290%,88.533%,91.984%)", 39, 226, 235, 255}, 130 | {"rgb(43.254%,59.929%,88.796%)", 110, 153, 226, 255}, 131 | {"rgb(20.849%,65.810%,108.952%)", 53, 168, 255, 255}, 132 | {"rgb(107.764%,106.729%,5.219%,0.722)", 255, 255, 13, 184}, 133 | {"rgb(87.428%,12.272%,55.687%)", 223, 31, 142, 255}, 134 | {"rgb(66.030%,91.694%,36.578%)", 168, 234, 93, 255}, 135 | {"rgb(50.947%,70.496%,90.525%)", 130, 180, 231, 255}, 136 | {"rgb(20.316%,98.256%,21.624%)", 52, 251, 55, 255}, 137 | {"rgb(101.253%,97.684%,18.923%,0.982)", 255, 249, 48, 250}, 138 | {"rgb(73.848%,64.384%,24.685%)", 188, 164, 63, 255}, 139 | {"rgb(106.833%,104.707%,97.097%)", 255, 255, 248, 255}, 140 | {"rgb(0.219%,58.327%,64.714%)", 1, 149, 165, 255}, 141 | {"rgb(105.545%,54.528%,72.587%)", 255, 139, 185, 255}, 142 | {"rgb(15.832%,89.520%,67.361%)", 40, 228, 172, 255}, 143 | {"rgb(99.044%,-8.225%,107.718%,0.707)", 253, 0, 255, 180}, 144 | {"rgb(53.693%,23.908%,60.057%,-0.168)", 137, 61, 153, 0}, 145 | {"rgb(79.097%,41.894%,91.880%)", 202, 107, 234, 255}, 146 | {"rgb(75.495%,78.770%,40.656%,0.394)", 193, 201, 104, 100}, 147 | {"rgb(98.775%,87.084%,9.632%)", 252, 222, 25, 255}, 148 | {"rgb(7.499%,58.822%,37.091%,0.211)", 19, 150, 95, 54}, 149 | {"rgb(53.511%,45.547%,31.029%)", 136, 116, 79, 255}, 150 | {"rgb(103.106%,79.057%,60.671%)", 255, 202, 155, 255}, 151 | {"rgb(44.343%,52.743%,100.265%,-0.047)", 113, 134, 255, 0}, 152 | {"rgb(109.647%,58.782%,0.907%,0.718)", 255, 150, 2, 183}, 153 | {"rgb(24.882%,96.959%,43.001%,0.400)", 63, 247, 110, 102}, 154 | {"rgb(74.362%,41.983%,9.375%)", 190, 107, 24, 255}, 155 | {"rgb(46.816%,66.074%,108.821%)", 119, 168, 255, 255}, 156 | {"rgb(77.497%,89.829%,62.736%,-0.026)", 198, 229, 160, 0}, 157 | {"rgb(26.042%,65.488%,69.547%,0.348)", 66, 167, 177, 89}, 158 | {"rgb(23.342%,-0.633%,77.730%)", 60, 0, 198, 255}, 159 | {"rgb(33.015%,81.191%,78.615%)", 84, 207, 200, 255}, 160 | {"rgb(108.596%,93.998%,0.798%)", 255, 240, 2, 255}, 161 | {"hsl(237.711grad,88.536%,36.402%)", 11, 82, 175, 255}, 162 | {"hsl(281.434,105.519%,-5.626%)", 0, 0, 0, 255}, 163 | {"hsl(257.634deg,91.970%,34.848%)", 55, 7, 171, 255}, 164 | {"hsl(0.944turn,70.481%,50.217%)", 218, 39, 99, 255}, 165 | {"hsl(327.085deg,22.465%,-2.142%,-0.156)", 0, 0, 0, 0}, 166 | {"hsl(366.588deg,62.880%,50.057%,0.213)", 208, 65, 48, 54}, 167 | {"hsl(34.042,89.193%,33.385%)", 161, 95, 9, 255}, 168 | {"hsl(359.760,66.421%,97.192%)", 253, 243, 243, 255}, 169 | {"hsl(3.111grad,106.058%,21.159%)", 108, 5, 0, 255}, 170 | {"hsl(0.918rad,-9.357%,33.825%)", 86, 86, 86, 255}, 171 | {"hsl(37.202deg,96.433%,9.973%)", 50, 31, 1, 255}, 172 | {"hsl(2.694rad,56.805%,80.396%)", 177, 233, 209, 255}, 173 | {"hsl(369.063,52.881%,26.098%)", 102, 42, 31, 255}, 174 | {"hsl(142.665,70.616%,96.183%)", 238, 252, 244, 255}, 175 | {"hsl(266.585grad,98.461%,8.780%)", 0, 0, 44, 255}, 176 | {"hsl(0.292turn,89.098%,25.780%)", 36, 124, 7, 255}, 177 | {"hsl(-0.043turn,-9.603%,106.006%)", 255, 255, 255, 255}, 178 | {"hsl(135.985deg,29.099%,103.419%)", 255, 255, 255, 255}, 179 | {"hsl(293.828,52.597%,7.326%)", 26, 9, 29, 255}, 180 | {"hsl(309.911grad,74.581%,0.263%)", 1, 0, 1, 255}, 181 | {"hsl(1.676rad,91.390%,15.755%)", 33, 77, 3, 255}, 182 | {"hsl(350.968deg,33.726%,88.331%,0.471)", 235, 215, 218, 120}, 183 | {"hsl(353.287grad,-9.139%,89.568%)", 228, 228, 228, 255}, 184 | {"hsl(2.953rad,52.179%,10.657%,0.989)", 13, 41, 36, 252}, 185 | {"hsl(298.491,41.628%,64.058%)", 200, 125, 202, 255}, 186 | {"hsl(0.840turn,89.447%,73.390%,0.161)", 248, 126, 243, 41}, 187 | {"hsl(0.415turn,3.520%,74.963%)", 189, 193, 191, 255}, 188 | {"hsl(309.723deg,-2.498%,81.780%)", 209, 209, 209, 255}, 189 | {"hsl(346.595,27.379%,5.620%)", 18, 10, 12, 255}, 190 | {"hsl(0.345turn,28.417%,71.498%)", 162, 203, 165, 255}, 191 | {"hsl(-0.074turn,91.885%,16.669%)", 82, 3, 38, 255}, 192 | {"hsl(154.320deg,-5.633%,-4.799%,-0.005)", 0, 0, 0, 0}, 193 | {"hsl(5.983,-5.051%,50.117%)", 128, 128, 128, 255}, 194 | {"hsl(78.942deg,93.449%,108.614%)", 255, 255, 255, 255}, 195 | {"hsl(20.316deg,90.583%,20.504%)", 100, 37, 5, 255}, 196 | {"hsl(231.054grad,63.039%,82.668%)", 183, 213, 239, 255}, 197 | {"hsl(0.880turn,6.700%,10.668%)", 29, 25, 28, 255}, 198 | {"hsl(37.954grad,79.594%,68.066%,-0.063)", 238, 183, 109, 0}, 199 | {"hsl(193.327deg,59.120%,93.790%)", 230, 244, 249, 255}, 200 | {"hsl(143.684deg,30.652%,82.141%)", 196, 223, 207, 255}, 201 | {"hsl(134.726,4.308%,49.756%)", 121, 132, 124, 255}, 202 | {"hsl(0.277turn,-7.056%,25.211%)", 64, 64, 64, 255}, 203 | {"hsl(1.849deg,33.541%,58.253%,0.425)", 184, 115, 113, 108}, 204 | {"hsl(151.147,86.285%,-6.549%)", 0, 0, 0, 255}, 205 | {"hsl(0.226turn,107.942%,19.766%,0.533)", 65, 101, 0, 136}, 206 | {"hsl(351.807deg,51.829%,70.431%,-0.069)", 219, 141, 151, 0}, 207 | {"hsl(1.075turn,71.099%,71.695%)", 234, 178, 132, 255}, 208 | {"hsl(235.353,50.281%,96.254%)", 241, 241, 250, 255}, 209 | {"hsl(250.375deg,95.684%,91.300%)", 219, 212, 254, 255}, 210 | {"hsl(0.579turn,76.129%,22.968%)", 14, 61, 103, 255}, 211 | {"hwb(350.310grad 16.842% 49.389% / 0.801)", 129, 43, 107, 204}, 212 | {"hwb(0.556turn 32.348% 21.369%)", 82, 161, 201, 255}, 213 | {"hwb(212.983 45.306% 46.159%)", 116, 125, 137, 255}, 214 | {"hwb(1.124rad 9.630% 47.144%)", 127, 135, 25, 255}, 215 | {"hwb(16.407grad 14.632% 19.279%)", 206, 79, 37, 255}, 216 | {"hwb(312.685deg 17.332% 47.949%)", 133, 44, 114, 255}, 217 | {"hwb(36.625deg 20.770% 21.283%)", 201, 143, 53, 255}, 218 | {"hwb(270.376deg 28.978% 17.436% / 33%)", 143, 74, 211, 84}, 219 | {"hwb(2.270rad 39.166% 42.779%)", 100, 146, 108, 255}, 220 | {"hwb(294.088grad 21.336% 9.237%)", 127, 54, 231, 255}, 221 | {"hwb(341.343deg 31.446% 26.421%)", 188, 80, 114, 255}, 222 | {"hwb(366.759deg 14.484% 37.122%)", 160, 51, 37, 255}, 223 | {"hwb(4.419rad 6.507% 42.055% / 0.049)", 45, 17, 148, 12}, 224 | {"hwb(3.300rad 27.872% 10.515% / 0.945)", 71, 204, 228, 241}, 225 | {"hwb(9.667grad 40.560% 24.503% / 77%)", 193, 116, 103, 196}, 226 | {"hwb(219.084deg 48.127% 29.327% / 37%)", 123, 143, 180, 94}, 227 | {"hwb(150.033deg 36.948% 3.314% / -47%)", 94, 247, 170, 0}, 228 | {"hwb(135.100grad 4.023% 22.865% / -48%)", 10, 197, 15, 0}, 229 | {"hwb(36.523grad 35.583% 26.613%)", 187, 144, 91, 255}, 230 | {"hwb(0.223turn 1.835% 11.334%)", 151, 226, 5, 255}, 231 | {"hwb(184.588grad 24.269% 39.447%)", 62, 154, 133, 255}, 232 | {"hwb(2.453rad 38.525% 30.671% / 0.902)", 98, 177, 125, 230}, 233 | {"hwb(52.832deg 44.508% 47.077%)", 135, 132, 113, 255}, 234 | {"hwb(339.930grad 1.730% 12.675%)", 223, 4, 201, 255}, 235 | {"hwb(1.089turn 7.776% 42.154%)", 148, 88, 20, 255}, 236 | {"hwb(357.944 19.934% 27.083%)", 186, 51, 55, 255}, 237 | {"hwb(0.984rad 45.517% 17.457% / 0.319)", 210, 205, 116, 81}, 238 | {"hwb(2.601rad 4.712% 4.427% / 1.100)", 12, 244, 124, 255}, 239 | {"hwb(2.024rad 34.707% 7.772%)", 98, 235, 89, 255}, 240 | {"hwb(231.368 41.048% 36.009%)", 105, 113, 163, 255}, 241 | {"hwb(193.933 38.226% 32.016%)", 97, 156, 173, 255}, 242 | {"hwb(-0.286deg 10.467% 17.942%)", 209, 27, 28, 255}, 243 | {"hwb(0.539turn 3.154% 2.802% / 107%)", 8, 192, 248, 255}, 244 | {"hwb(113.811 27.163% 41.581%)", 77, 149, 69, 255}, 245 | {"hwb(404.001grad 20.404% 2.430%)", 249, 64, 52, 255}, 246 | {"hwb(171.817grad 20.855% 3.529%)", 53, 246, 164, 255}, 247 | {"hwb(368.489 31.606% 28.356%)", 183, 95, 81, 255}, 248 | {"hwb(0.718turn 7.762% 9.043% / -71%)", 85, 20, 232, 0}, 249 | {"hwb(324.675deg 17.130% 47.823% / 1.163)", 133, 44, 96, 255}, 250 | {"hwb(3.824rad 23.616% 29.663%)", 60, 102, 179, 255}, 251 | {"hwb(0.131turn 6.849% 10.758% / 0.571)", 228, 183, 17, 146}, 252 | {"hwb(0.580turn 44.908% 4.704%)", 115, 181, 243, 255}, 253 | {"hwb(155.890grad 16.635% 31.523%)", 42, 175, 87, 255}, 254 | {"hwb(4.212rad 30.404% 4.135% / 0.733)", 81, 78, 244, 187}, 255 | {"hwb(64.938grad 49.540% 39.713%)", 154, 153, 126, 255}, 256 | {"hwb(278.059grad 11.511% 14.158% / 0.667)", 62, 29, 219, 170}, 257 | {"hwb(347.931 36.736% 28.045%)", 183, 94, 112, 255}, 258 | {"hwb(264.825grad 49.915% 36.097%)", 127, 128, 163, 255}, 259 | {"hwb(3.820rad 20.592% 40.485%)", 53, 87, 152, 255}, 260 | {"hwb(197.668deg 13.713% 9.565% / 0.582)", 35, 173, 231, 148}, 261 | } 262 | for _, d := range data { 263 | c, err := Parse(d.s) 264 | test(t, err, nil) 265 | test(t, arr8(c.RGBA255()), arr8(d.r, d.g, d.b, d.a)) 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mazznoer/csscolorparser 2 | 3 | go 1.12 4 | -------------------------------------------------------------------------------- /named_colors.go: -------------------------------------------------------------------------------- 1 | package csscolorparser 2 | 3 | var namedColors = map[string][3]uint8{ 4 | "aliceblue": {240, 248, 255}, 5 | "antiquewhite": {250, 235, 215}, 6 | "aqua": {0, 255, 255}, 7 | "aquamarine": {127, 255, 212}, 8 | "azure": {240, 255, 255}, 9 | "beige": {245, 245, 220}, 10 | "bisque": {255, 228, 196}, 11 | "black": {0, 0, 0}, 12 | "blanchedalmond": {255, 235, 205}, 13 | "blue": {0, 0, 255}, 14 | "blueviolet": {138, 43, 226}, 15 | "brown": {165, 42, 42}, 16 | "burlywood": {222, 184, 135}, 17 | "cadetblue": {95, 158, 160}, 18 | "chartreuse": {127, 255, 0}, 19 | "chocolate": {210, 105, 30}, 20 | "coral": {255, 127, 80}, 21 | "cornflowerblue": {100, 149, 237}, 22 | "cornsilk": {255, 248, 220}, 23 | "crimson": {220, 20, 60}, 24 | "cyan": {0, 255, 255}, 25 | "darkblue": {0, 0, 139}, 26 | "darkcyan": {0, 139, 139}, 27 | "darkgoldenrod": {184, 134, 11}, 28 | "darkgray": {169, 169, 169}, 29 | "darkgreen": {0, 100, 0}, 30 | "darkgrey": {169, 169, 169}, 31 | "darkkhaki": {189, 183, 107}, 32 | "darkmagenta": {139, 0, 139}, 33 | "darkolivegreen": {85, 107, 47}, 34 | "darkorange": {255, 140, 0}, 35 | "darkorchid": {153, 50, 204}, 36 | "darkred": {139, 0, 0}, 37 | "darksalmon": {233, 150, 122}, 38 | "darkseagreen": {143, 188, 143}, 39 | "darkslateblue": {72, 61, 139}, 40 | "darkslategray": {47, 79, 79}, 41 | "darkslategrey": {47, 79, 79}, 42 | "darkturquoise": {0, 206, 209}, 43 | "darkviolet": {148, 0, 211}, 44 | "deeppink": {255, 20, 147}, 45 | "deepskyblue": {0, 191, 255}, 46 | "dimgray": {105, 105, 105}, 47 | "dimgrey": {105, 105, 105}, 48 | "dodgerblue": {30, 144, 255}, 49 | "firebrick": {178, 34, 34}, 50 | "floralwhite": {255, 250, 240}, 51 | "forestgreen": {34, 139, 34}, 52 | "fuchsia": {255, 0, 255}, 53 | "gainsboro": {220, 220, 220}, 54 | "ghostwhite": {248, 248, 255}, 55 | "gold": {255, 215, 0}, 56 | "goldenrod": {218, 165, 32}, 57 | "gray": {128, 128, 128}, 58 | "green": {0, 128, 0}, 59 | "greenyellow": {173, 255, 47}, 60 | "grey": {128, 128, 128}, 61 | "honeydew": {240, 255, 240}, 62 | "hotpink": {255, 105, 180}, 63 | "indianred": {205, 92, 92}, 64 | "indigo": {75, 0, 130}, 65 | "ivory": {255, 255, 240}, 66 | "khaki": {240, 230, 140}, 67 | "lavender": {230, 230, 250}, 68 | "lavenderblush": {255, 240, 245}, 69 | "lawngreen": {124, 252, 0}, 70 | "lemonchiffon": {255, 250, 205}, 71 | "lightblue": {173, 216, 230}, 72 | "lightcoral": {240, 128, 128}, 73 | "lightcyan": {224, 255, 255}, 74 | "lightgoldenrodyellow": {250, 250, 210}, 75 | "lightgray": {211, 211, 211}, 76 | "lightgreen": {144, 238, 144}, 77 | "lightgrey": {211, 211, 211}, 78 | "lightpink": {255, 182, 193}, 79 | "lightsalmon": {255, 160, 122}, 80 | "lightseagreen": {32, 178, 170}, 81 | "lightskyblue": {135, 206, 250}, 82 | "lightslategray": {119, 136, 153}, 83 | "lightslategrey": {119, 136, 153}, 84 | "lightsteelblue": {176, 196, 222}, 85 | "lightyellow": {255, 255, 224}, 86 | "lime": {0, 255, 0}, 87 | "limegreen": {50, 205, 50}, 88 | "linen": {250, 240, 230}, 89 | "magenta": {255, 0, 255}, 90 | "maroon": {128, 0, 0}, 91 | "mediumaquamarine": {102, 205, 170}, 92 | "mediumblue": {0, 0, 205}, 93 | "mediumorchid": {186, 85, 211}, 94 | "mediumpurple": {147, 112, 219}, 95 | "mediumseagreen": {60, 179, 113}, 96 | "mediumslateblue": {123, 104, 238}, 97 | "mediumspringgreen": {0, 250, 154}, 98 | "mediumturquoise": {72, 209, 204}, 99 | "mediumvioletred": {199, 21, 133}, 100 | "midnightblue": {25, 25, 112}, 101 | "mintcream": {245, 255, 250}, 102 | "mistyrose": {255, 228, 225}, 103 | "moccasin": {255, 228, 181}, 104 | "navajowhite": {255, 222, 173}, 105 | "navy": {0, 0, 128}, 106 | "oldlace": {253, 245, 230}, 107 | "olive": {128, 128, 0}, 108 | "olivedrab": {107, 142, 35}, 109 | "orange": {255, 165, 0}, 110 | "orangered": {255, 69, 0}, 111 | "orchid": {218, 112, 214}, 112 | "palegoldenrod": {238, 232, 170}, 113 | "palegreen": {152, 251, 152}, 114 | "paleturquoise": {175, 238, 238}, 115 | "palevioletred": {219, 112, 147}, 116 | "papayawhip": {255, 239, 213}, 117 | "peachpuff": {255, 218, 185}, 118 | "peru": {205, 133, 63}, 119 | "pink": {255, 192, 203}, 120 | "plum": {221, 160, 221}, 121 | "powderblue": {176, 224, 230}, 122 | "purple": {128, 0, 128}, 123 | "rebeccapurple": {102, 51, 153}, 124 | "red": {255, 0, 0}, 125 | "rosybrown": {188, 143, 143}, 126 | "royalblue": {65, 105, 225}, 127 | "saddlebrown": {139, 69, 19}, 128 | "salmon": {250, 128, 114}, 129 | "sandybrown": {244, 164, 96}, 130 | "seagreen": {46, 139, 87}, 131 | "seashell": {255, 245, 238}, 132 | "sienna": {160, 82, 45}, 133 | "silver": {192, 192, 192}, 134 | "skyblue": {135, 206, 235}, 135 | "slateblue": {106, 90, 205}, 136 | "slategray": {112, 128, 144}, 137 | "slategrey": {112, 128, 144}, 138 | "snow": {255, 250, 250}, 139 | "springgreen": {0, 255, 127}, 140 | "steelblue": {70, 130, 180}, 141 | "tan": {210, 180, 140}, 142 | "teal": {0, 128, 128}, 143 | "thistle": {216, 191, 216}, 144 | "tomato": {255, 99, 71}, 145 | "turquoise": {64, 224, 208}, 146 | "violet": {238, 130, 238}, 147 | "wheat": {245, 222, 179}, 148 | "white": {255, 255, 255}, 149 | "whitesmoke": {245, 245, 245}, 150 | "yellow": {255, 255, 0}, 151 | "yellowgreen": {154, 205, 50}, 152 | } 153 | -------------------------------------------------------------------------------- /named_colors_test.go: -------------------------------------------------------------------------------- 1 | package csscolorparser 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func Test_NamedColors(t *testing.T) { 9 | for name, rgb := range namedColors { 10 | c, err := Parse(name) 11 | r, g, b, _ := c.RGBA255() 12 | test(t, err, nil) 13 | test(t, [3]uint8{r, g, b}, rgb) 14 | if name == "aqua" || name == "cyan" || name == "fuchsia" || name == "magenta" { 15 | continue 16 | } 17 | if strings.Contains(name, "gray") || strings.Contains(name, "grey") { 18 | continue 19 | } 20 | resName, ok := c.Name() 21 | testTrue(t, ok) 22 | test(t, resName, name) 23 | } 24 | 25 | // Hex code 26 | 27 | data := [][2]string{ 28 | {"aliceblue", "#f0f8ff"}, 29 | {"bisque", "#ffe4c4"}, 30 | {"chartreuse", "#7fff00"}, 31 | {"coral", "#ff7f50"}, 32 | {"crimson", "#dc143c"}, 33 | {"dodgerblue", "#1e90ff"}, 34 | {"firebrick", "#b22222"}, 35 | {"gold", "#ffd700"}, 36 | {"hotpink", "#ff69b4"}, 37 | {"indigo", "#4b0082"}, 38 | {"lavender", "#e6e6fa"}, 39 | {"plum", "#dda0dd"}, 40 | {"salmon", "#fa8072"}, 41 | {"skyblue", "#87ceeb"}, 42 | {"tomato", "#ff6347"}, 43 | {"violet", "#ee82ee"}, 44 | {"yellowgreen", "#9acd32"}, 45 | } 46 | for _, d := range data { 47 | // parse name 48 | c, err := Parse(d[0]) 49 | test(t, err, nil) 50 | test(t, c.HexString(), d[1]) 51 | 52 | // parse hex 53 | c, err = Parse(d[1]) 54 | test(t, err, nil) 55 | name, ok := c.Name() 56 | testTrue(t, ok) 57 | test(t, name, d[0]) 58 | } 59 | 60 | // Colors without name 61 | 62 | data2 := []string{ 63 | "#f87cba", 64 | "#0033ff", 65 | "#012345", 66 | "#abcdef", 67 | } 68 | for _, s := range data2 { 69 | c, err := Parse(s) 70 | test(t, err, nil) 71 | name, ok := c.Name() 72 | test(t, ok, false) 73 | test(t, name, "") 74 | } 75 | } 76 | --------------------------------------------------------------------------------