[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\nko_fi: mazznoer\nliberapay: mazznoer\ncustom: \"https://paypal.me/mazznoer\"\n"
  },
  {
    "path": ".github/workflows/go.yml",
    "content": "name: CI\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n  build:\n    strategy:\n      matrix:\n        os: [macos-latest, windows-latest, ubuntu-latest]\n    runs-on: ${{ matrix.os }}\n\n    steps:\n    - name: Check out code into the Go module directory\n      uses: actions/checkout@v4\n\n    - name: Set up Go\n      uses: actions/setup-go@v5\n      with:\n        go-version-file: './go.mod'\n\n    - name: Build\n      run: go build -v .\n\n    - name: Vet\n      run: go vet .\n\n    - name: Test\n      run: go test -v -coverprofile coverage.out && go tool cover -html coverage.out -o coverage.html\n\n    - name: Run examples\n      run: |\n        go run examples/basic.go\n        go run examples/gradients.go\n\n    - name: Format\n      if: matrix.os == 'ubuntu-latest'\n      run: if [ \"$(gofmt -s -l . | wc -l)\" -gt 0 ]; then exit 1; fi\n"
  },
  {
    "path": ".gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, built with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n*.html\n\n# Dependency directories (remove the comment below to include it)\n# vendor/\n\nREADME.html\noutput/\ngradient.png\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## v0.11.1\n\n- Fix bug in Lab conversion\n\n## v0.11.0\n\n- New `GradientBuilder.Reset()`\n- New `BlendLab`\n- `Gradient`'s fields is now public\n- `GradientCore` is now public\n- New `InterpolationSmoothstep`\n\n## v0.10.2\n\n- Fix bug in `GradientBuilder.Css()`\n\n## v0.10.1\n\n- New `GradientBuilder.Css()`\n- Using `uint32` to store preset colors\n\n## v0.10.0\n\n- Added support for transparency\n- New `GoColor()`\n- Refactor lots of code\n- Remove color schemes\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Nor Khasyatillah\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\nApache-Style Software License for ColorBrewer software and ColorBrewer Color\nSchemes\n\nCopyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State\nUniversity.\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use\nthis file except in compliance with the License.  You may obtain a copy of the\nLicense at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed\nunder the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR\nCONDITIONS OF ANY KIND, either express or implied. See the License for the\nspecific language governing permissions and limitations under the License.\n"
  },
  {
    "path": "Makefile",
    "content": "SHELL := /bin/bash\n\n.PHONY: all check test\n\nall: check test\n\ncheck:\n\tgo build && go vet && gofmt -s -l .\n\ntest:\n\tgo test -v -coverprofile coverage.out && go tool cover -html coverage.out -o coverage.html\n\nbench:\n\tgo test -bench .\n"
  },
  {
    "path": "PRESET.md",
    "content": "# Preset Gradients\n\nAll preset gradients are in the domain [0..1].\n\n## Diverging\n\n`colorgrad.BrBG()`\n![img](doc/images/preset/BrBG.png)\n\n`colorgrad.PRGn()`\n![img](doc/images/preset/PRGn.png)\n\n`colorgrad.PiYG()`\n![img](doc/images/preset/PiYG.png)\n\n`colorgrad.PuOr()`\n![img](doc/images/preset/PuOr.png)\n\n`colorgrad.RdBu()`\n![img](doc/images/preset/RdBu.png)\n\n`colorgrad.RdGy()`\n![img](doc/images/preset/RdGy.png)\n\n`colorgrad.RdYlBu()`\n![img](doc/images/preset/RdYlBu.png)\n\n`colorgrad.RdYlGn()`\n![img](doc/images/preset/RdYlGn.png)\n\n`colorgrad.Spectral()`\n![img](doc/images/preset/Spectral.png)\n\n## Sequential (Single Hue)\n\n`colorgrad.Blues()`\n![img](doc/images/preset/Blues.png)\n\n`colorgrad.Greens()`\n![img](doc/images/preset/Greens.png)\n\n`colorgrad.Greys()`\n![img](doc/images/preset/Greys.png)\n\n`colorgrad.Oranges()`\n![img](doc/images/preset/Oranges.png)\n\n`colorgrad.Purples()`\n![img](doc/images/preset/Purples.png)\n\n`colorgrad.Reds()`\n![img](doc/images/preset/Reds.png)\n\n## Sequential (Multi-Hue)\n\n`colorgrad.Turbo()`\n![img](doc/images/preset/Turbo.png)\n\n`colorgrad.Viridis()`\n![img](doc/images/preset/Viridis.png)\n\n`colorgrad.Inferno()`\n![img](doc/images/preset/Inferno.png)\n\n`colorgrad.Magma()`\n![img](doc/images/preset/Magma.png)\n\n`colorgrad.Plasma()`\n![img](doc/images/preset/Plasma.png)\n\n`colorgrad.Cividis()`\n![img](doc/images/preset/Cividis.png)\n\n`colorgrad.Warm()`\n![img](doc/images/preset/Warm.png)\n\n`colorgrad.Cool()`\n![img](doc/images/preset/Cool.png)\n\n`colorgrad.CubehelixDefault()`\n![img](doc/images/preset/CubehelixDefault.png)\n\n`colorgrad.BuGn()`\n![img](doc/images/preset/BuGn.png)\n\n`colorgrad.BuPu()`\n![img](doc/images/preset/BuPu.png)\n\n`colorgrad.GnBu()`\n![img](doc/images/preset/GnBu.png)\n\n`colorgrad.OrRd()`\n![img](doc/images/preset/OrRd.png)\n\n`colorgrad.PuBuGn()`\n![img](doc/images/preset/PuBuGn.png)\n\n`colorgrad.PuBu()`\n![img](doc/images/preset/PuBu.png)\n\n`colorgrad.PuRd()`\n![img](doc/images/preset/PuRd.png)\n\n`colorgrad.RdPu()`\n![img](doc/images/preset/RdPu.png)\n\n`colorgrad.YlGnBu()`\n![img](doc/images/preset/YlGnBu.png)\n\n`colorgrad.YlGn()`\n![img](doc/images/preset/YlGn.png)\n\n`colorgrad.YlOrBr()`\n![img](doc/images/preset/YlOrBr.png)\n\n`colorgrad.YlOrRd()`\n![img](doc/images/preset/YlOrRd.png)\n\n## Cyclical\n\n`colorgrad.Rainbow()`\n![img](doc/images/preset/Rainbow.png)\n\n`colorgrad.Sinebow()`\n![img](doc/images/preset/Sinebow.png)\n"
  },
  {
    "path": "README.md",
    "content": "# colorgrad\n\n[![Release](https://img.shields.io/github/release/mazznoer/colorgrad.svg)](https://github.com/mazznoer/colorgrad/releases/latest)\n[![PkgGoDev](https://pkg.go.dev/badge/github.com/mazznoer/colorgrad)](https://pkg.go.dev/github.com/mazznoer/colorgrad)\n![Build Status](https://github.com/mazznoer/colorgrad/actions/workflows/go.yml/badge.svg)\n[![go report](https://goreportcard.com/badge/github.com/mazznoer/colorgrad)](https://goreportcard.com/report/github.com/mazznoer/colorgrad)\n\nGo (Golang) _color scales_ library for data visualization, charts, games, maps, generative art and others.\n\n## Support This Project\n\n[![Donate](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/mazznoer/donate)\n\n## Index\n\n* [Custom Gradient](#custom-gradient)\n* [Preset Gradients](#preset-gradients)\n* [Using the Gradient](#using-the-gradient)\n* [Examples](#examples)\n* [Playground](#playground)\n\n```go\nimport \"github.com/mazznoer/colorgrad\"\n```\n\n## Custom Gradient\n\n### Basic\n\n```go\ngrad, err := colorgrad.NewGradient().Build()\n```\n![img](doc/images/custom-default.png)\n\n### Custom Colors\n\n```go\ngrad, err := colorgrad.NewGradient().\n    Colors(\n        colorgrad.Rgb8(0, 206, 209, 255),\n        colorgrad.Rgb8(255, 105, 180, 255),\n        colorgrad.Rgb(0.274, 0.5, 0.7, 1),\n        colorgrad.Hsv(50, 1, 1, 1),\n        colorgrad.Hsv(348, 0.9, 0.8, 1),\n    ).\n    Build()\n```\n![img](doc/images/custom-colors.png)\n\n### Using Web Color Format\n\n`HtmlColors()` method accepts [named colors](https://www.w3.org/TR/css-color-4/#named-colors), hexadecimal (`#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`), `rgb()`, `rgba()`, `hsl()`, `hsla()`, `hwb()`, and `hsv()`.\n\n```go\ngrad, err := colorgrad.NewGradient().\n    HtmlColors(\"#C41189\", \"#00BFFF\", \"#FFD700\").\n    Build()\n```\n![img](doc/images/custom-hex-colors.png)\n\n```go\ngrad, err := colorgrad.NewGradient().\n    HtmlColors(\"gold\", \"hotpink\", \"darkturquoise\").\n    Build()\n```\n![img](doc/images/custom-named-colors.png)\n\n```go\ngrad, err := colorgrad.NewGradient().\n    HtmlColors(\n        \"rgb(125,110,221)\",\n        \"rgb(90%,45%,97%)\",\n        \"hsl(229,79%,85%)\",\n    ).\n    Build()\n```\n![img](doc/images/custom-css-colors.png)\n\n### Domain & Color Position\n\nDefault domain is [0..1].\n\n```go\ngrad, err := colorgrad.NewGradient().\n    HtmlColors(\"deeppink\", \"gold\", \"seagreen\").\n    Build()\n```\n![img](https://raw.githubusercontent.com/mazznoer/colorgrad-rs/master/docs/images/domain-default.png)\n\nSet the domain to [0..100].\n\n```go\ngrad, err := colorgrad.NewGradient().\n    HtmlColors(\"deeppink\", \"gold\", \"seagreen\").\n    Domain(0, 100).\n    Build()\n```\n![img](https://raw.githubusercontent.com/mazznoer/colorgrad-rs/master/docs/images/domain-100.png)\n\nSet the domain to [-1..1].\n\n```go\ngrad, err := colorgrad.NewGradient().\n    HtmlColors(\"deeppink\", \"gold\", \"seagreen\").\n    Domain(-1, 1).\n    Build()\n```\n![img](https://raw.githubusercontent.com/mazznoer/colorgrad-rs/master/docs/images/domain-neg1-1.png)\n\nSet exact position for each color. The domain is [0..1].\n\n```go\ngrad, err := colorgrad.NewGradient().\n    HtmlColors(\"deeppink\", \"gold\", \"seagreen\").\n    Domain(0, 0.7, 1).\n    Build()\n```\n![img](https://raw.githubusercontent.com/mazznoer/colorgrad-rs/master/docs/images/color-position-1.png)\n\nSet exact position for each color. The domain is [15..80].\n\n```go\ngrad, err := colorgrad.NewGradient().\n    HtmlColors(\"deeppink\", \"gold\", \"seagreen\").\n    Domain(15, 30, 80).\n    Build()\n```\n![img](https://raw.githubusercontent.com/mazznoer/colorgrad-rs/master/docs/images/color-position-2.png)\n\n### CSS Gradient Format\n\n```go\ngrad, err := colorgrad.NewGradient().\n    Css(\"deeppink, gold, seagreen\").\n    Build()\n```\n![img](doc/images/css-gradient-1.png)\n\n```go\ngrad, err := colorgrad.NewGradient().\n    Css(\"purple, gold 35%, green 35%, 55%, gold\").\n    Interpolation(colorgrad.InterpolationCatmullRom).\n    Build()\n```\n![img](doc/images/css-gradient-2.png)\n\n### Blending Mode\n\n```go\ngrad, err := colorgrad.NewGradient().\n    HtmlColors(\"#FFF\", \"#00F\").\n    Mode(colorgrad.BlendRgb).\n    Build()\n```\n![blend-modes](doc/images/blend-modes-3.png)\n\n### Interpolation Mode\n\n```go\ngrad, err := colorgrad.NewGradient().\n    HtmlColors(\"#C41189\", \"#00BFFF\", \"#FFD700\").\n    Interpolation(colorgrad.InterpolationLinear).\n    Build()\n```\n\n`InterpolationLinear`\n![interpolation-linear](doc/images/interpolation-linear2.png)\n\n`InterpolationSmoothstep`\n![interpolation-smoothstep](doc/images/interpolation-smoothstep.png)\n\n`InterpolationCatmullRom`\n![interpolation-catmull-rom](doc/images/interpolation-catmull-rom2.png)\n\n`InterpolationBasis`\n![interpolation-basis](doc/images/interpolation-basis2.png)\n\n## Preset Gradients\n\nSee [PRESET.md](PRESET.md)\n\n## Parsing GIMP Gradient\n\n```go\nimport \"os\"\n\nforeground := colorgrad.Rgb(0, 0, 0, 1)\nbackground := colorgrad.Rgb(1, 1, 1, 1)\nfile, err := os.Open(\"Abstract_1.ggr\")\n\nif err != nil {\n\tpanic(err)\n}\n\ndefer file.Close()\ngrad, name, err2 := colorgrad.ParseGgr(file, foreground, background)\nfmt.Println(name) // Abstract 1\n```\n\n![gimp-gradient](doc/images/ggr-abstract-1.png)\n\n## Using the Gradient\n\n### Get the domain\n\n```go\ngrad := colorgrad.Rainbow()\n\nfmt.Println(grad.Domain()) // 0 1\n```\n\n### Get single color at certain position\n\n```go\ngrad := colorgrad.Rainbow()\n\nfmt.Println(grad.At(0.0).HexString()) // #6e40aa\nfmt.Println(grad.At(0.5).HexString()) // #aff05b\nfmt.Println(grad.At(1.0).HexString()) // #6e40aa\n```\n\n### Get n colors evenly spaced across gradient\n\n```go\ngrad := colorgrad.Rainbow()\n\nfor _, c := range grad.Colors(10) {\n    fmt.Println(c.HexString())\n}\n```\n\nOutput:\n\n```console\n#6e40aa\n#c83dac\n#ff5375\n#ff8c38\n#c9d33a\n#7cf659\n#5dea8d\n#48b8d0\n#4775de\n#6e40aa\n```\n\n### Hard-Edged Gradient\n\nConvert gradient to hard-edged gradient with 11 segments and 0 smoothness.\n\n```go\ngrad := colorgrad.Rainbow().Sharp(11, 0)\n```\n![img](doc/images/rainbow-sharp.png)\n\nThis is the effect of different smoothness.\n\n![img](doc/images/sharp-gradients.png)\n\n## Examples\n\n### Gradient Image\n\n```go\npackage main\n\nimport (\n    \"image\"\n    \"image/png\"\n    \"os\"\n\n    \"github.com/mazznoer/colorgrad\"\n)\n\nfunc main() {\n    grad, _ := colorgrad.NewGradient().\n        HtmlColors(\"#C41189\", \"#00BFFF\", \"#FFD700\").\n        Build()\n\n    w := 1500\n    h := 70\n    fw := float64(w)\n\n    img := image.NewRGBA(image.Rect(0, 0, w, h))\n\n    for x := 0; x < w; x++ {\n        col := grad.At(float64(x) / fw)\n        for y := 0; y < h; y++ {\n            img.Set(x, y, col)\n        }\n    }\n\n    file, err := os.Create(\"gradient.png\")\n    if err != nil {\n        panic(err.Error())\n    }\n    defer file.Close()\n    png.Encode(file, img)\n}\n```\n\nExample output:\n\n![img](doc/images/custom-hex-colors.png)\n\n### Colored Noise\n\n```go\npackage main\n\nimport (\n    \"image\"\n    \"image/png\"\n    \"os\"\n\n    \"github.com/mazznoer/colorgrad\"\n    \"github.com/ojrac/opensimplex-go\"\n)\n\nfunc main() {\n    w := 600\n    h := 350\n    scale := 0.02\n\n    grad := colorgrad.Rainbow().Sharp(7, 0.2)\n    noise := opensimplex.NewNormalized(996)\n    img := image.NewRGBA(image.Rect(0, 0, w, h))\n\n    for y := 0; y < h; y++ {\n        for x := 0; x < w; x++ {\n            t := noise.Eval2(float64(x)*scale, float64(y)*scale)\n            img.Set(x, y, grad.At(t))\n        }\n    }\n\n    file, err := os.Create(\"noise.png\")\n    if err != nil {\n        panic(err.Error())\n    }\n    defer file.Close()\n    png.Encode(file, img)\n}\n```\n\nExample output:\n\n![noise](doc/images/noise.png)\n\n## Playground\n\n* [Basic](https://play.golang.org/p/PlMov8BKfRc)\n* [Random colors](https://play.golang.org/p/d67x9di4sAF)\n\n## Dependencies\n\n* [csscolorparser](https://github.com/mazznoer/csscolorparser)\n\n## Inspirations\n\n* [chroma.js](https://gka.github.io/chroma.js/#color-scales)\n* [d3-scale-chromatic](https://github.com/d3/d3-scale-chromatic/)\n* colorful's [gradientgen.go](https://github.com/lucasb-eyer/go-colorful/blob/master/doc/gradientgen/gradientgen.go)\n\n## Links\n\n* [colorgrad-rs](https://github.com/mazznoer/colorgrad-rs) - Rust port of this library\n"
  },
  {
    "path": "basis.go",
    "content": "package colorgrad\n\nimport (\n\t\"math\"\n)\n\n// https://github.com/d3/d3-interpolate/blob/master/src/basis.js\n\ntype basisGradient struct {\n\tcolors    [][4]float64\n\tpositions []float64\n\tmin       float64\n\tmax       float64\n\tmode      BlendMode\n\tfirst     Color\n\tlast      Color\n}\n\nfunc (lg basisGradient) At(t float64) Color {\n\tif t <= lg.min {\n\t\treturn lg.first\n\t}\n\n\tif t >= lg.max {\n\t\treturn lg.last\n\t}\n\n\tif math.IsNaN(t) {\n\t\treturn Color{A: 1}\n\t}\n\n\tlow := 0\n\thigh := len(lg.positions)\n\tn := high - 1\n\n\tfor low < high {\n\t\tmid := (low + high) / 2\n\t\tif lg.positions[mid] < t {\n\t\t\tlow = mid + 1\n\t\t} else {\n\t\t\thigh = mid\n\t\t}\n\t}\n\n\tif low == 0 {\n\t\tlow = 1\n\t}\n\n\tp1 := lg.positions[low-1]\n\tp2 := lg.positions[low]\n\tval0 := lg.colors[low-1]\n\tval1 := lg.colors[low]\n\ti := low - 1\n\tt = (t - p1) / (p2 - p1)\n\n\txx := func(v1, v2 float64, j int) float64 {\n\t\tv0 := 2*v1 - v2\n\t\tif i > 0 {\n\t\t\tv0 = lg.colors[i-1][j]\n\t\t}\n\n\t\tv3 := 2*v2 - v1\n\t\tif i < n-1 {\n\t\t\tv3 = lg.colors[i+2][j]\n\t\t}\n\n\t\treturn basis(t, v0, v1, v2, v3)\n\t}\n\n\ta := xx(val0[0], val1[0], 0)\n\tb := xx(val0[1], val1[1], 1)\n\tc := xx(val0[2], val1[2], 2)\n\td := xx(val0[3], val1[3], 3)\n\n\tswitch lg.mode {\n\tcase BlendRgb:\n\t\treturn Color{R: a, G: b, B: c, A: d}\n\tcase BlendLinearRgb:\n\t\treturn LinearRgb(a, b, c, d)\n\tcase BlendLab:\n\t\treturn Lab(a, b, c, d).Clamp()\n\tcase BlendOklab:\n\t\treturn Oklab(a, b, c, d).Clamp()\n\t}\n\n\treturn Color{}\n}\n\nfunc newBasisGradient(colors []Color, positions []float64, mode BlendMode) Gradient {\n\tgradbase := basisGradient{\n\t\tcolors:    convertColors(colors, mode),\n\t\tpositions: positions,\n\t\tmin:       positions[0],\n\t\tmax:       positions[len(positions)-1],\n\t\tmode:      mode,\n\t\tfirst:     colors[0],\n\t\tlast:      colors[len(colors)-1],\n\t}\n\n\treturn Gradient{\n\t\tCore: gradbase,\n\t\tMin:  positions[0],\n\t\tMax:  positions[len(positions)-1],\n\t}\n}\n\nfunc basis(t1, v0, v1, v2, v3 float64) float64 {\n\tt2 := t1 * t1\n\tt3 := t2 * t1\n\treturn ((1-3*t1+3*t2-t3)*v0 + (4-6*t2+3*t3)*v1 + (1+3*t1+3*t2-3*t3)*v2 + t3*v3) / 6\n}\n"
  },
  {
    "path": "basis_test.go",
    "content": "package colorgrad\n\nimport (\n\t\"math\"\n\t\"testing\"\n)\n\nfunc Test_BasisGradient(t *testing.T) {\n\tgrad, err := NewGradient().\n\t\tHtmlColors(\"#f00\", \"#0f0\", \"#00f\").\n\t\tMode(BlendRgb).\n\t\tInterpolation(InterpolationBasis).\n\t\tBuild()\n\n\ttest(t, err, nil)\n\ttest(t, grad.At(0.00).HexString(), \"#ff0000\")\n\ttest(t, grad.At(0.25).HexString(), \"#857505\")\n\ttest(t, grad.At(0.50).HexString(), \"#2baa2b\")\n\ttest(t, grad.At(0.75).HexString(), \"#057585\")\n\ttest(t, grad.At(1.00).HexString(), \"#0000ff\")\n\n\ttestSlice(t, colors2hex(grad.Colors(5)), []string{\n\t\t\"#ff0000\",\n\t\t\"#857505\",\n\t\t\"#2baa2b\",\n\t\t\"#057585\",\n\t\t\"#0000ff\",\n\t})\n\n\ttest(t, grad.At(-0.1).HexString(), \"#ff0000\")\n\ttest(t, grad.At(1.11).HexString(), \"#0000ff\")\n\ttest(t, grad.At(math.NaN()).HexString(), \"#000000\")\n}\n"
  },
  {
    "path": "bench_test.go",
    "content": "package colorgrad\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\nvar colors = []string{\n\t\"#87e575\", \"#e88ef2\", \"#7398ef\", \"#65c3f2\", \"#3e52a0\", \"#b659db\", \"#75b7ff\", \"#7555ba\",\n\t\"#fceac4\", \"#e8009e\", \"#cc7c26\", \"#e175f4\", \"#f959e7\", \"#31828e\", \"#e4bef7\", \"#a9fcc6\",\n\t\"#c122d6\", \"#81f9e1\", \"#caea81\", \"#47d192\", \"#db579d\", \"#ead36b\", \"#3c2bbc\", \"#9de544\",\n\t\"#e8e476\", \"#055d66\", \"#77c90c\", \"#bff49a\", \"#6b76db\", \"#3cf720\", \"#61bace\", \"#aa3405\",\n\t\"#a588d8\", \"#e2aef9\", \"#c0eff9\", \"#9b043b\", \"#b2ffe0\", \"#64e092\", \"#ff4cab\", \"#56d356\",\n\t\"#e185e2\", \"#ff72f3\", \"#ff4fbe\", \"#0a9366\", \"#dbc2f9\", \"#6cbacc\", \"#893009\", \"#13afaa\",\n\t\"#5208ad\", \"#9b1426\", \"#71e06d\", \"#c2ff0c\", \"#ce4244\", \"#ffebb5\", \"#169bf9\", \"#e58eb5\",\n\t\"#3c3ab2\", \"#2afca5\", \"#5946c4\", \"#ea7352\", \"#f46bbb\", \"#264daf\", \"#edaada\", \"#c6baf4\",\n\t\"#d984e8\", \"#61dd5f\", \"#1f26b7\", \"#f99345\", \"#b2d624\", \"#f911e2\", \"#bf882a\", \"#81f48b\",\n\t\"#a3ffba\", \"#13c139\", \"#dd7752\", \"#db755c\", \"#fcbdf2\", \"#f455b2\", \"#7414e2\", \"#074575\",\n\t\"#7cffef\", \"#dd778a\", \"#db55cb\", \"#7aa7cc\", \"#fcbfd2\", \"#b7f799\", \"#a65bc6\", \"#f242ff\",\n\t\"#f9c0b3\", \"#9890db\", \"#d01be8\", \"#20870e\", \"#f4426b\", \"#def260\", \"#521efc\", \"#ffbcc6\",\n\t\"#e285b9\", \"#0ed6f9\", \"#7825ed\", \"#f2c6ff\", \"#cdb2f4\", \"#5fd374\", \"#fc838d\", \"#27bec6\",\n}\nvar blendModes = []BlendMode{BlendRgb, BlendLinearRgb, BlendOklab}\nvar positions = []float64{0.73}\n\nfunc BenchmarkLinearGradient(b *testing.B) {\n\tfor _, mode := range blendModes {\n\t\tgrad, err := NewGradient().\n\t\t\tHtmlColors(colors...).\n\t\t\tMode(mode).\n\t\t\tInterpolation(InterpolationLinear).\n\t\t\tBuild()\n\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tfor _, pos := range positions {\n\t\t\tb.Run(\n\t\t\t\tfmt.Sprintf(\"%s_at_%.2f\", mode, pos), func(b *testing.B) {\n\t\t\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\t\t\tgrad.At(pos)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t}\n\t}\n}\n\nfunc BenchmarkCatmullRomGradient(b *testing.B) {\n\tfor _, mode := range blendModes {\n\t\tgrad, err := NewGradient().\n\t\t\tHtmlColors(colors...).\n\t\t\tMode(mode).\n\t\t\tInterpolation(InterpolationCatmullRom).\n\t\t\tBuild()\n\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tfor _, pos := range positions {\n\t\t\tb.Run(\n\t\t\t\tfmt.Sprintf(\"%s_at_%.2f\", mode, pos), func(b *testing.B) {\n\t\t\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\t\t\tgrad.At(pos)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t}\n\t}\n}\n\nfunc BenchmarkBasisGradient(b *testing.B) {\n\tfor _, mode := range blendModes {\n\t\tgrad, err := NewGradient().\n\t\t\tHtmlColors(colors...).\n\t\t\tMode(mode).\n\t\t\tInterpolation(InterpolationBasis).\n\t\t\tBuild()\n\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tfor _, pos := range positions {\n\t\t\tb.Run(\n\t\t\t\tfmt.Sprintf(\"%s_at_%.2f\", mode, pos), func(b *testing.B) {\n\t\t\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\t\t\tgrad.At(pos)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "builder.go",
    "content": "package colorgrad\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/mazznoer/csscolorparser\"\n)\n\ntype GradientBuilder struct {\n\tcolors             []Color\n\tpositions          []float64\n\tmode               BlendMode\n\tinterpolation      Interpolation\n\tinvalidHtmlColors  []string\n\tinvalidCssGradient bool\n\tclean              bool\n}\n\nfunc NewGradient() *GradientBuilder {\n\treturn &GradientBuilder{\n\t\tmode:               BlendRgb,\n\t\tinterpolation:      InterpolationLinear,\n\t\tinvalidCssGradient: false,\n\t\tclean:              false,\n\t}\n}\n\nfunc (gb *GradientBuilder) Colors(colors ...Color) *GradientBuilder {\n\tfor _, col := range colors {\n\t\tgb.colors = append(gb.colors, col)\n\t}\n\tgb.clean = false\n\treturn gb\n}\n\nfunc (gb *GradientBuilder) HtmlColors(htmlColors ...string) *GradientBuilder {\n\tfor _, s := range htmlColors {\n\t\tc, err := csscolorparser.Parse(s)\n\t\tif err != nil {\n\t\t\tgb.invalidHtmlColors = append(gb.invalidHtmlColors, s)\n\t\t\tcontinue\n\t\t}\n\t\tgb.colors = append(gb.colors, c)\n\t}\n\tgb.clean = false\n\treturn gb\n}\n\nfunc (gb *GradientBuilder) Css(s string) *GradientBuilder {\n\tgb.clean = false\n\tstops, ok := parseCss(s)\n\tif !ok {\n\t\tgb.invalidCssGradient = true\n\t\treturn gb\n\t}\n\tgb.colors = gb.colors[:0]\n\tgb.positions = gb.positions[:0]\n\tfor _, st := range stops {\n\t\tgb.colors = append(gb.colors, *st.color)\n\t\tgb.positions = append(gb.positions, *st.pos)\n\t}\n\treturn gb\n}\n\nfunc (gb *GradientBuilder) Domain(positions ...float64) *GradientBuilder {\n\tgb.positions = positions\n\tgb.clean = false\n\treturn gb\n}\n\nfunc (gb *GradientBuilder) Mode(mode BlendMode) *GradientBuilder {\n\tgb.mode = mode\n\treturn gb\n}\n\nfunc (gb *GradientBuilder) Interpolation(mode Interpolation) *GradientBuilder {\n\tgb.interpolation = mode\n\treturn gb\n}\n\nfunc (gb *GradientBuilder) Reset() *GradientBuilder {\n\tgb.colors = gb.colors[:0]\n\tgb.positions = gb.positions[:0]\n\tgb.mode = BlendRgb\n\tgb.interpolation = InterpolationLinear\n\tgb.invalidHtmlColors = gb.invalidHtmlColors[:0]\n\tgb.invalidCssGradient = false\n\tgb.clean = false\n\treturn gb\n}\n\nfunc (gb *GradientBuilder) prepareBuild() error {\n\tif gb.clean {\n\t\treturn nil\n\t}\n\n\tif gb.invalidHtmlColors != nil {\n\t\treturn fmt.Errorf(\"invalid HTML colors: %q\", gb.invalidHtmlColors)\n\t}\n\n\tif gb.invalidCssGradient {\n\t\treturn fmt.Errorf(\"invalid CSS gradient\")\n\t}\n\n\tvar colors []Color\n\tvar positions []float64\n\n\tif len(gb.colors) == 0 {\n\t\t// Default colors\n\t\tcolors = []Color{\n\t\t\t{R: 0, G: 0, B: 0, A: 1}, // black\n\t\t\t{R: 1, G: 1, B: 1, A: 1}, // white\n\t\t}\n\t} else if len(gb.colors) == 1 {\n\t\tcolors = []Color{gb.colors[0], gb.colors[0]}\n\t} else {\n\t\tcolors = make([]Color, len(gb.colors))\n\t\tcopy(colors, gb.colors)\n\t}\n\n\tif len(gb.positions) == 0 {\n\t\tpositions = linspace(0, 1, uint(len(colors)))\n\t} else if len(gb.positions) == len(colors) {\n\t\tfor i := 0; i < len(gb.positions)-1; i++ {\n\t\t\tif gb.positions[i] > gb.positions[i+1] {\n\t\t\t\treturn fmt.Errorf(\"invalid domain\")\n\t\t\t}\n\t\t}\n\t\tpositions = make([]float64, len(gb.positions))\n\t\tcopy(positions, gb.positions)\n\t} else if len(gb.positions) == 2 {\n\t\tif gb.positions[0] >= gb.positions[1] {\n\t\t\treturn fmt.Errorf(\"invalid domain\")\n\t\t}\n\t\tpositions = linspace(gb.positions[0], gb.positions[1], uint(len(colors)))\n\t} else {\n\t\treturn fmt.Errorf(\"invalid domain\")\n\t}\n\n\tgb.colors = gb.colors[:0]\n\tgb.positions = gb.positions[:0]\n\n\tprev := positions[0]\n\tlastIdx := len(positions) - 1\n\n\tfor i, col := range colors {\n\t\tpos := positions[i]\n\t\tvar next float64\n\t\tif i == lastIdx {\n\t\t\tnext = positions[lastIdx]\n\t\t} else {\n\t\t\tnext = positions[i+1]\n\t\t}\n\t\tif (pos-prev)+(next-pos) < epsilon {\n\t\t\t// skip\n\t\t} else {\n\t\t\tgb.colors = append(gb.colors, col)\n\t\t\tgb.positions = append(gb.positions, pos)\n\t\t}\n\t\tprev = pos\n\t}\n\n\tif len(gb.colors) != len(gb.positions) || len(gb.colors) < 2 {\n\t\treturn fmt.Errorf(\"invalid stops\")\n\t}\n\n\tgb.clean = true\n\treturn nil\n}\n\nfunc (gb *GradientBuilder) Build() (Gradient, error) {\n\tif err := gb.prepareBuild(); err != nil {\n\t\treturn Gradient{\n\t\t\tCore: zeroGradient{},\n\t\t\tMin:  0,\n\t\t\tMax:  1,\n\t\t}, err\n\t}\n\n\tif gb.interpolation == InterpolationLinear {\n\t\treturn newLinearGradient(gb.colors, gb.positions, gb.mode), nil\n\t}\n\n\tif gb.interpolation == InterpolationSmoothstep {\n\t\treturn newSmoothstepGradient(gb.colors, gb.positions, gb.mode), nil\n\t}\n\n\tif gb.interpolation == InterpolationBasis {\n\t\treturn newBasisGradient(gb.colors, gb.positions, gb.mode), nil\n\t}\n\n\treturn newCatmullRomGradient(gb.colors, gb.positions, gb.mode), nil\n}\n\n// For testing purposes\nfunc (gb *GradientBuilder) GetColors() *[]Color {\n\treturn &gb.colors\n}\n\n// For testing purposes\nfunc (gb *GradientBuilder) GetPositions() *[]float64 {\n\treturn &gb.positions\n}\n"
  },
  {
    "path": "builder_test.go",
    "content": "package colorgrad\n\nimport (\n\t\"image/color\"\n\t\"testing\"\n)\n\nfunc domain(min, max float64) [2]float64 {\n\treturn [2]float64{min, max}\n}\n\nfunc Test_Builder(t *testing.T) {\n\tvar grad Gradient\n\tvar err error\n\n\t// Default colors\n\tgrad, err = NewGradient().Build()\n\ttest(t, err, nil)\n\ttest(t, domain(grad.Domain()), [2]float64{0, 1})\n\ttest(t, grad.At(0).HexString(), \"#000000\")\n\ttest(t, grad.At(1).HexString(), \"#ffffff\")\n\n\t// Single color\n\tgrad, err = NewGradient().\n\t\tColors(Rgb8(0, 255, 0, 255)).\n\t\tBuild()\n\ttest(t, err, nil)\n\ttest(t, domain(grad.Domain()), [2]float64{0, 1})\n\ttest(t, grad.At(0).HexString(), \"#00ff00\")\n\ttest(t, grad.At(1).HexString(), \"#00ff00\")\n\n\t// Default domain\n\tgrad, err = NewGradient().\n\t\tHtmlColors(\"red\", \"lime\", \"blue\").\n\t\tBuild()\n\ttest(t, err, nil)\n\ttest(t, domain(grad.Domain()), [2]float64{0, 1})\n\ttest(t, grad.At(0.0).HexString(), \"#ff0000\")\n\ttest(t, grad.At(0.5).HexString(), \"#00ff00\")\n\ttest(t, grad.At(1.0).HexString(), \"#0000ff\")\n\n\t// Custom domain\n\tgrad, err = NewGradient().\n\t\tHtmlColors(\"red\", \"lime\", \"blue\").\n\t\tDomain(-100, 100).\n\t\tBuild()\n\ttest(t, err, nil)\n\ttest(t, domain(grad.Domain()), [2]float64{-100, 100})\n\ttest(t, grad.At(-100).HexString(), \"#ff0000\")\n\ttest(t, grad.At(0).HexString(), \"#00ff00\")\n\ttest(t, grad.At(100).HexString(), \"#0000ff\")\n\n\t// Color position\n\tgrad, err = NewGradient().\n\t\tHtmlColors(\"red\", \"lime\", \"blue\").\n\t\tDomain(13, 27.3, 90).\n\t\tBuild()\n\ttest(t, err, nil)\n\ttest(t, domain(grad.Domain()), [2]float64{13, 90})\n\ttest(t, grad.At(13).HexString(), \"#ff0000\")\n\ttest(t, grad.At(27.3).HexString(), \"#00ff00\")\n\ttest(t, grad.At(90).HexString(), \"#0000ff\")\n\n\t// Multiple colors, custom domain\n\tgb := NewGradient()\n\tgrad, err = gb.HtmlColors(\"#00f\", \"#00ffff\").\n\t\tColors(\n\t\t\tRgb8(255, 255, 0, 255),\n\t\t\tHwb(320, 0.1, 0.3, 1),\n\t\t\tGoColor(color.RGBA{R: 127, G: 0, B: 0, A: 127}),\n\t\t\tGoColor(color.Gray{185}),\n\t\t).\n\t\tHtmlColors(\"gold\", \"hwb(320, 10%, 30%)\").\n\t\tDomain(10, 50).\n\t\tMode(BlendRgb).\n\t\tInterpolation(InterpolationLinear).\n\t\tBuild()\n\ttest(t, err, nil)\n\ttest(t, domain(grad.Domain()), [2]float64{10, 50})\n\ttestSlice(t, colors2hex(grad.Colors(8)), []string{\n\t\t\"#0000ff\",\n\t\t\"#00ffff\",\n\t\t\"#ffff00\",\n\t\t\"#b31980\", // xxx\n\t\t\"#ff00007f\",\n\t\t\"#b9b9b9\",\n\t\t\"#ffd700\",\n\t\t\"#b31a80\",\n\t})\n\ttestSlice(t, colors2hex(*gb.GetColors()), []string{\n\t\t\"#0000ff\",\n\t\t\"#00ffff\",\n\t\t\"#ffff00\",\n\t\t\"#b31a80\",\n\t\t\"#ff00007f\",\n\t\t\"#b9b9b9\",\n\t\t\"#ffd700\",\n\t\t\"#b31a80\",\n\t})\n\n\t// Reset\n\tgrad, err = gb.Reset().Build()\n\ttest(t, err, nil)\n\ttest(t, domain(grad.Domain()), [2]float64{0, 1})\n\ttest(t, grad.At(0).HexString(), \"#000000\")\n\ttest(t, grad.At(1).HexString(), \"#ffffff\")\n\n\t// Filter stops\n\tgb = NewGradient()\n\tgb.HtmlColors(\"gold\", \"red\", \"blue\", \"yellow\", \"black\", \"white\", \"plum\")\n\tgb.Domain(0, 0, 0.5, 0.5, 0.5, 1, 1)\n\t_, err = gb.Build()\n\ttest(t, err, nil)\n\ttestSlice(t, *gb.GetPositions(), []float64{0, 0.5, 0.5, 1})\n\ttestSlice(t, colors2hex(*gb.GetColors()), []string{\n\t\t\"#ff0000\",\n\t\t\"#0000ff\",\n\t\t\"#000000\",\n\t\t\"#ffffff\",\n\t})\n\n\t// --- Builder Error\n\n\t// Invalid HTML colors\n\tgrad, err = NewGradient().\n\t\tHtmlColors(\"#777\", \"bloodred\", \"#bbb\", \"#zzz\").\n\t\tBuild()\n\ttestTrue(t, err != nil)\n\ttestTrue(t, isZeroGradient(grad))\n\n\t// Invalid domain\n\tgrad, err = NewGradient().\n\t\tHtmlColors(\"#777\", \"#fff\", \"#ccc\", \"#222\").\n\t\tDomain(0, 0.5, 1).\n\t\tBuild()\n\ttestTrue(t, err != nil)\n\ttestTrue(t, isZeroGradient(grad))\n\n\t// Invalid domain\n\tgrad, err = NewGradient().\n\t\tHtmlColors(\"#777\", \"#fff\", \"#ccc\", \"#222\").\n\t\tDomain(0, 0.71, 0.70, 1).\n\t\tBuild()\n\ttestTrue(t, err != nil)\n\ttestTrue(t, isZeroGradient(grad))\n\n\t// Invalid domain\n\tgrad, err = NewGradient().\n\t\tHtmlColors(\"#f00\", \"#0f0\").\n\t\tDomain(1, 1).\n\t\tBuild()\n\ttestTrue(t, err != nil)\n\ttestTrue(t, isZeroGradient(grad))\n\n\t// Invalid domain\n\tgrad, err = NewGradient().\n\t\tHtmlColors(\"#777\", \"#fff\", \"#ccc\", \"#222\").\n\t\tDomain(1, 0).\n\t\tBuild()\n\ttestTrue(t, err != nil)\n\ttestTrue(t, isZeroGradient(grad))\n}\n\nfunc Test_CssGradient(t *testing.T) {\n\ttestData := []struct {\n\t\ts        string\n\t\tposition []float64\n\t\tcolors   []string\n\t}{\n\t\t{\n\t\t\t\"blue\",\n\t\t\t[]float64{0, 1},\n\t\t\t[]string{\"#0000ff\", \"#0000ff\"},\n\t\t},\n\t\t{\n\t\t\t\"red, blue\",\n\t\t\t[]float64{0, 1},\n\t\t\t[]string{\"#ff0000\", \"#0000ff\"},\n\t\t},\n\t\t{\n\t\t\t\"red, lime, blue\",\n\t\t\t[]float64{0, 0.5, 1},\n\t\t\t[]string{\"#ff0000\", \"#00ff00\", \"#0000ff\"},\n\t\t},\n\t\t{\n\t\t\t\"red, lime 75%, blue\",\n\t\t\t[]float64{0, 0.75, 1},\n\t\t\t[]string{\"#ff0000\", \"#00ff00\", \"#0000ff\"},\n\t\t},\n\t\t{\n\t\t\t\"red 70%, lime, blue\",\n\t\t\t[]float64{0, 0.7, 0.85, 1},\n\t\t\t[]string{\"#ff0000\", \"#ff0000\", \"#00ff00\", \"#0000ff\"},\n\t\t},\n\t\t{\n\t\t\t\"red 5%, lime, blue 85%\",\n\t\t\t[]float64{0, 0.05, 0.45, 0.85, 1},\n\t\t\t[]string{\"#ff0000\", \"#ff0000\", \"#00ff00\", \"#0000ff\", \"#0000ff\"},\n\t\t},\n\t\t{\n\t\t\t\"#00f, #ff0 10% 35%, #f00\",\n\t\t\t[]float64{0, 0.1, 0.35, 1},\n\t\t\t[]string{\"#0000ff\", \"#ffff00\", \"#ffff00\", \"#ff0000\"},\n\t\t},\n\t\t{\n\t\t\t\"red, 75%, #ff0\",\n\t\t\t[]float64{0, 0.75, 1},\n\t\t\t[]string{\"#ff0000\", \"#ff8000\", \"#ffff00\"},\n\t\t},\n\t\t{\n\t\t\t\"red -5, lime, blue\",\n\t\t\t[]float64{-5, -2, 1},\n\t\t\t[]string{\"#ff0000\", \"#00ff00\", \"#0000ff\"},\n\t\t},\n\t}\n\n\tfor _, d := range testData {\n\t\tgb := NewGradient()\n\t\tgb.Css(d.s)\n\t\t_, err := gb.Build()\n\n\t\ttest(t, err, nil)\n\t\ttestSliceF(t, d.position, *gb.GetPositions())\n\t\ttestSlice(t, d.colors, colors2hex(*gb.GetColors()))\n\n\t\t/*dmin, dmax := grad.Domain()\n\t\ttest(t, 0.0, dmin)\n\t\ttest(t, 1.0, dmax)*/\n\t}\n\n\t// Invalid format\n\tinvalid := []string{\n\t\t\"\",\n\t\t\" \",\n\t\t\"reds, blue\",\n\t\t\"0, red, lime\",\n\t\t\"red, lime, 100%\",\n\t\t\"deeppink, 0.4, 0.9, pink\",\n\t\t\"50%\",\n\t\t\"0%, 100%\",\n\t\t\"æ\",\n\t\t\"red â 15%, blue\",\n\t\t\"red, ä, blue\",\n\t}\n\tfor _, s := range invalid {\n\t\t_, err := NewGradient().Css(s).Build()\n\t\ttestTrue(t, err != nil)\n\t\ttest(t, err.Error(), \"invalid CSS gradient\")\n\t}\n}\n"
  },
  {
    "path": "catmull_rom.go",
    "content": "package colorgrad\n\nimport (\n\t\"math\"\n)\n\n// Adapted from https://qroph.github.io/2018/07/30/smooth-paths-using-catmull-rom-splines.html\n\nfunc toCatmullRomSegments(values []float64) [][4]float64 {\n\talpha := 0.5\n\ttension := 0.0\n\tn := len(values)\n\n\tvals := make([]float64, n+2)\n\tvals[0] = 2*values[0] - values[1]\n\tfor i, v := range values {\n\t\tvals[i+1] = v\n\t}\n\tvals[n+1] = 2*values[n-1] - values[n-2]\n\n\tsegments := [][4]float64{}\n\n\tfor i := 1; i < len(vals)-2; i++ {\n\t\tv0 := vals[i-1]\n\t\tv1 := vals[i]\n\t\tv2 := vals[i+1]\n\t\tv3 := vals[i+2]\n\t\tt0 := 0.0\n\t\tt1 := t0 + math.Pow(math.Abs(v0-v1), alpha)\n\t\tt2 := t1 + math.Pow(math.Abs(v1-v2), alpha)\n\t\tt3 := t2 + math.Pow(math.Abs(v2-v3), alpha)\n\t\tm1 := (1. - tension) * (t2 - t1) * ((v0-v1)/(t0-t1) - (v0-v2)/(t0-t2) + (v1-v2)/(t1-t2))\n\t\tm2 := (1. - tension) * (t2 - t1) * ((v1-v2)/(t1-t2) - (v1-v3)/(t1-t3) + (v2-v3)/(t2-t3))\n\t\tif math.IsNaN(m1) {\n\t\t\tm1 = 0\n\t\t}\n\t\tif math.IsNaN(m2) {\n\t\t\tm2 = 0\n\t\t}\n\t\ta := 2*v1 - 2*v2 + m1 + m2\n\t\tb := -3*v1 + 3*v2 - 2*m1 - m2\n\t\tc := m1\n\t\td := v1\n\t\tsegments = append(segments, [4]float64{a, b, c, d})\n\t}\n\treturn segments\n}\n\ntype catmullRomGradient struct {\n\tsegments  [][4][4]float64\n\tpositions []float64\n\tmin       float64\n\tmax       float64\n\tmode      BlendMode\n\tfirst     Color\n\tlast      Color\n}\n\nfunc newCatmullRomGradient(colors []Color, positions []float64, space BlendMode) Gradient {\n\tn := len(colors)\n\ta := make([]float64, n)\n\tb := make([]float64, n)\n\tc := make([]float64, n)\n\td := make([]float64, n)\n\tfor i, col := range colors {\n\t\tvar arr [4]float64\n\t\tswitch space {\n\t\tcase BlendRgb:\n\t\t\tarr = [4]float64{col.R, col.G, col.B, col.A}\n\t\tcase BlendLinearRgb:\n\t\t\tarr = col2linearRgb(col)\n\t\tcase BlendLab:\n\t\t\tarr = col2lab(col)\n\t\tcase BlendOklab:\n\t\t\tarr = col2oklab(col)\n\t\t}\n\t\ta[i] = arr[0]\n\t\tb[i] = arr[1]\n\t\tc[i] = arr[2]\n\t\td[i] = arr[3]\n\t}\n\ts1 := toCatmullRomSegments(a)\n\ts2 := toCatmullRomSegments(b)\n\ts3 := toCatmullRomSegments(c)\n\ts4 := toCatmullRomSegments(d)\n\tsegments := make([][4][4]float64, len(s1))\n\tfor i, v1 := range s1 {\n\t\tsegments[i] = [4][4]float64{\n\t\t\tv1,\n\t\t\ts2[i],\n\t\t\ts3[i],\n\t\t\ts4[i],\n\t\t}\n\t}\n\tmin := positions[0]\n\tmax := positions[n-1]\n\tgradbase := catmullRomGradient{\n\t\tsegments:  segments,\n\t\tpositions: positions,\n\t\tmin:       min,\n\t\tmax:       max,\n\t\tmode:      space,\n\t\tfirst:     colors[0],\n\t\tlast:      colors[len(colors)-1],\n\t}\n\treturn Gradient{\n\t\tCore: gradbase,\n\t\tMin:  min,\n\t\tMax:  max,\n\t}\n}\n\nfunc (g catmullRomGradient) At(t float64) Color {\n\tif math.IsNaN(t) {\n\t\treturn Color{A: 1}\n\t}\n\n\tif t <= g.min {\n\t\treturn g.first\n\t}\n\n\tif t >= g.max {\n\t\treturn g.last\n\t}\n\n\tlow := 0\n\thigh := len(g.positions)\n\n\tfor low < high {\n\t\tmid := (low + high) / 2\n\t\tif g.positions[mid] < t {\n\t\t\tlow = mid + 1\n\t\t} else {\n\t\t\thigh = mid\n\t\t}\n\t}\n\n\tif low == 0 {\n\t\tlow = 1\n\t}\n\n\tpos0 := g.positions[low-1]\n\tpos1 := g.positions[low]\n\tseg_a := g.segments[low-1][0]\n\tseg_b := g.segments[low-1][1]\n\tseg_c := g.segments[low-1][2]\n\tseg_d := g.segments[low-1][3]\n\n\tt1 := (t - pos0) / (pos1 - pos0)\n\tt2 := t1 * t1\n\tt3 := t2 * t1\n\n\ta := seg_a[0]*t3 + seg_a[1]*t2 + seg_a[2]*t1 + seg_a[3]\n\tb := seg_b[0]*t3 + seg_b[1]*t2 + seg_b[2]*t1 + seg_b[3]\n\tc := seg_c[0]*t3 + seg_c[1]*t2 + seg_c[2]*t1 + seg_c[3]\n\td := seg_d[0]*t3 + seg_d[1]*t2 + seg_d[2]*t1 + seg_d[3]\n\n\tswitch g.mode {\n\tcase BlendRgb:\n\t\treturn Color{R: a, G: b, B: c, A: d}\n\tcase BlendLinearRgb:\n\t\treturn LinearRgb(a, b, c, d)\n\tcase BlendLab:\n\t\treturn Lab(a, b, c, d).Clamp()\n\tcase BlendOklab:\n\t\treturn Oklab(a, b, c, d).Clamp()\n\t}\n\n\treturn Color{}\n}\n"
  },
  {
    "path": "catmull_rom_test.go",
    "content": "package colorgrad\n\nimport (\n\t\"math\"\n\t\"testing\"\n)\n\nfunc Test_CatmullRomGradient(t *testing.T) {\n\tgrad, err := NewGradient().\n\t\tHtmlColors(\"#f00\", \"#0f0\", \"#00f\").\n\t\tMode(BlendRgb).\n\t\tInterpolation(InterpolationCatmullRom).\n\t\tBuild()\n\n\ttest(t, err, nil)\n\ttest(t, grad.At(0.00).HexString(), \"#ff0000\")\n\ttest(t, grad.At(0.25).HexString(), \"#609f00\")\n\ttest(t, grad.At(0.50).HexString(), \"#00ff00\")\n\ttest(t, grad.At(0.75).HexString(), \"#009f60\")\n\ttest(t, grad.At(1.00).HexString(), \"#0000ff\")\n\n\ttestSlice(t, colors2hex(grad.Colors(5)), []string{\n\t\t\"#ff0000\",\n\t\t\"#609f00\",\n\t\t\"#00ff00\",\n\t\t\"#009f60\",\n\t\t\"#0000ff\",\n\t})\n\n\ttest(t, grad.At(-0.1).HexString(), \"#ff0000\")\n\ttest(t, grad.At(1.11).HexString(), \"#0000ff\")\n\ttest(t, grad.At(math.NaN()).HexString(), \"#000000\")\n}\n"
  },
  {
    "path": "css_gradient.go",
    "content": "package colorgrad\n\nimport (\n\t\"math\"\n\t\"strings\"\n\n\t\"github.com/mazznoer/csscolorparser\"\n)\n\nfunc parseCss(s string) ([]cssGradientStop, bool) {\n\tstops := []cssGradientStop{}\n\n\tfor _, stop := range splitByComma(s) {\n\t\tif !prosesStop(&stops, splitBySpace(stop)) {\n\t\t\treturn stops, false\n\t\t}\n\t}\n\n\tif len(stops) == 0 {\n\t\treturn stops, false\n\t}\n\n\tif stops[0].color == nil {\n\t\treturn stops, false\n\t}\n\n\tif stops[0].pos == nil {\n\t\tstops[0].pos = ptr(0.0)\n\t}\n\n\tfor i, stop := range stops {\n\t\tif i == len(stops)-1 {\n\t\t\tif stop.pos == nil {\n\t\t\t\tstops[i].pos = ptr(1.0)\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\n\t\tif stop.color == nil {\n\t\t\tif stops[i+1].color == nil {\n\t\t\t\treturn stops, false\n\t\t\t}\n\t\t\tstops[i].color = ptrColor(blendRgb(*stops[i-1].color, *stops[i+1].color, 0.5))\n\t\t}\n\t}\n\n\tif *stops[0].pos > 0.0 {\n\t\tstops = append([]cssGradientStop{{ptr(0.0), stops[0].color}}, stops...)\n\t}\n\n\tif *stops[len(stops)-1].pos < 1.0 {\n\t\tstops = append(stops, cssGradientStop{ptr(1.0), stops[len(stops)-1].color})\n\t}\n\n\tfor i, stop := range stops {\n\t\tif stop.pos == nil {\n\t\t\tfor j := i + 1; j < len(stops); j++ {\n\t\t\t\tif stops[j].pos != nil {\n\t\t\t\t\tprev := *stops[i-1].pos\n\t\t\t\t\tnext := *stops[j].pos\n\t\t\t\t\tstops[i].pos = ptr(prev + (next-prev)/float64(j-i+1))\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif i > 0 {\n\t\t\tstops[i].pos = ptr(math.Max(*stops[i].pos, *stops[i-1].pos))\n\t\t}\n\t}\n\n\tfor _, stop := range stops {\n\t\tif stop.color == nil || stop.pos == nil {\n\t\t\treturn stops, false\n\t\t}\n\t}\n\n\treturn stops, true\n}\n\nfunc ptr(f float64) *float64 {\n\treturn &f\n}\n\nfunc ptrColor(c Color) *Color {\n\treturn &c\n}\n\ntype cssGradientStop struct {\n\tpos   *float64\n\tcolor *Color\n}\n\nfunc prosesStop(stops *[]cssGradientStop, arr []string) bool {\n\tswitch len(arr) {\n\tcase 1:\n\t\tcol, err := csscolorparser.Parse(arr[0])\n\t\tif err == nil {\n\t\t\t*stops = append(*stops, cssGradientStop{nil, &col})\n\t\t\treturn true\n\t\t}\n\n\t\tpos, ok := parsePos(arr[0])\n\t\tif ok {\n\t\t\t*stops = append(*stops, cssGradientStop{&pos, nil})\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\tcase 2:\n\t\tcol, err := csscolorparser.Parse(arr[0])\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\n\t\tpos, ok := parsePos(arr[1])\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\n\t\t*stops = append(*stops, cssGradientStop{&pos, &col})\n\tcase 3:\n\t\tcol, err := csscolorparser.Parse(arr[0])\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\n\t\tpos1, ok1 := parsePos(arr[1])\n\t\tif !ok1 {\n\t\t\treturn false\n\t\t}\n\n\t\tpos2, ok2 := parsePos(arr[2])\n\t\tif !ok2 {\n\t\t\treturn false\n\t\t}\n\n\t\t*stops = append(*stops, cssGradientStop{&pos1, &col})\n\t\t*stops = append(*stops, cssGradientStop{&pos2, &col})\n\tdefault:\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc splitByComma(s string) []string {\n\tres := []string{}\n\tbeg := 0\n\tinside := false\n\n\tfor i := 0; i < len(s); i++ {\n\t\tif s[i] == ',' && !inside {\n\t\t\tres = append(res, s[beg:i])\n\t\t\tbeg = i + 1\n\t\t} else if s[i] == '(' {\n\t\t\tinside = true\n\t\t} else if s[i] == ')' {\n\t\t\tinside = false\n\t\t}\n\t}\n\treturn append(res, s[beg:])\n}\n\nfunc splitBySpace(s string) []string {\n\tres := []string{}\n\tbeg := 0\n\tinside := false\n\n\tfor i := 0; i < len(s); i++ {\n\t\tif s[i] == ' ' && !inside {\n\t\t\tif len(s[beg:i]) > 0 {\n\t\t\t\tres = append(res, s[beg:i])\n\t\t\t}\n\t\t\tbeg = i + 1\n\t\t} else if s[i] == '(' {\n\t\t\tinside = true\n\t\t} else if s[i] == ')' {\n\t\t\tinside = false\n\t\t}\n\t}\n\tif len(s[beg:]) > 0 {\n\t\tres = append(res, s[beg:])\n\t}\n\treturn res\n}\n\nfunc parsePos(s string) (float64, bool) {\n\tif strings.HasSuffix(s, \"%\") {\n\t\tf, ok := parseFloat(s[:len(s)-1])\n\t\tif ok {\n\t\t\treturn f / 100, true\n\t\t}\n\t\treturn 0, false\n\t}\n\n\tf, ok := parseFloat(s)\n\treturn f, ok\n}\n"
  },
  {
    "path": "example_test.go",
    "content": "package colorgrad_test\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/mazznoer/colorgrad\"\n)\n\nfunc Example_presetGradient() {\n\tgrad := colorgrad.Rainbow()\n\tdmin, dmax := grad.Domain()\n\n\tfmt.Println(dmin, dmax)\n\tfmt.Println(grad.At(0).HexString())\n\t// Output:\n\t// 0 1\n\t// #6e40aa\n}\n\nfunc Example_customGradient() {\n\tgrad, err := colorgrad.NewGradient().\n\t\tHtmlColors(\"red\", \"#FFD700\", \"lime\").\n\t\tDomain(0, 0.35, 1).\n\t\tMode(colorgrad.BlendOklab).\n\t\tBuild()\n\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tfmt.Println(grad.At(0).HexString())\n\tfmt.Println(grad.At(1).HexString())\n\t// Output:\n\t// #ff0000\n\t// #00ff00\n}\n"
  },
  {
    "path": "examples/.gitignore",
    "content": "basic\ngradients\n*.png\noutput/*.png\n"
  },
  {
    "path": "examples/basic.go",
    "content": "//go:build ignore\n// +build ignore\n\npackage main\n\nimport (\n\t\"image\"\n\t\"image/png\"\n\t\"os\"\n\n\t\"github.com/mazznoer/colorgrad\"\n)\n\nfunc main() {\n\tgrad, _ := colorgrad.NewGradient().\n\t\tHtmlColors(\"#c41189\", \"#00BFFF\", \"#FFD700\").\n\t\tBuild()\n\n\tw := 1500\n\th := 70\n\tfw := float64(w)\n\n\timg := image.NewRGBA(image.Rect(0, 0, w, h))\n\n\tfor x := 0; x < w; x++ {\n\t\tcol := grad.At(float64(x) / fw)\n\t\tfor y := 0; y < h; y++ {\n\t\t\timg.Set(x, y, col)\n\t\t}\n\t}\n\n\tfile, err := os.Create(\"gradient.png\")\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tdefer file.Close()\n\tpng.Encode(file, img)\n}\n"
  },
  {
    "path": "examples/ggr/Abstract_1.ggr",
    "content": "GIMP Gradient\nName: Abstract 1\n6\n0.000000 0.286311 0.572621 0.269543 0.259267 1.000000 1.000000 0.215635 0.407414 0.984953 1.000000 0 0 0 0\n0.572621 0.657763 0.716194 0.215635 0.407414 0.984953 1.000000 0.040368 0.833333 0.619375 1.000000 0 0 0 0\n0.716194 0.734558 0.749583 0.040368 0.833333 0.619375 1.000000 0.680490 0.355264 0.977430 1.000000 0 0 0 0\n0.749583 0.784641 0.824708 0.680490 0.355264 0.977430 1.000000 0.553909 0.351853 0.977430 1.000000 0 0 0 0\n0.824708 0.853088 0.876461 0.553909 0.351853 0.977430 1.000000 1.000000 0.000000 1.000000 1.000000 0 0 0 0\n0.876461 0.943172 1.000000 1.000000 0.000000 1.000000 1.000000 1.000000 1.000000 0.000000 1.000000 0 0 0 0\n"
  },
  {
    "path": "examples/ggr/Full_saturation_spectrum_CW.ggr",
    "content": "GIMP Gradient\nName: Full saturation spectrum CW\n1\n0.000000 0.500000 1.000000 1.000000 0.000000 0.000000 1.000000 1.000000 0.000000 0.000000 1.000000 0 2\n"
  },
  {
    "path": "examples/gradients.go",
    "content": "//go:build ignore\n// +build ignore\n\npackage main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"image\"\n\t\"image/color\"\n\t\"image/draw\"\n\t\"image/png\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/mazznoer/colorgrad\"\n)\n\ntype data struct {\n\tgradient colorgrad.Gradient\n\tname     string\n}\n\ntype Opt struct {\n\ttestData  bool\n\tsaveImage bool\n}\n\nfunc main() {\n\tvar opt Opt\n\tflag.BoolVar(&opt.testData, \"test\", false, \"generate test data\")\n\tflag.BoolVar(&opt.saveImage, \"save-img\", false, \"save image file\")\n\tflag.Parse()\n\n\tpresetGradients := []data{\n\t\t{colorgrad.CubehelixDefault(), \"CubehelixDefault\"},\n\t\t{colorgrad.Warm(), \"Warm\"},\n\t\t{colorgrad.Cool(), \"Cool\"},\n\t\t{colorgrad.Rainbow(), \"Rainbow\"},\n\t\t{colorgrad.Cividis(), \"Cividis\"},\n\t\t{colorgrad.Sinebow(), \"Sinebow\"},\n\t\t{colorgrad.Turbo(), \"Turbo\"},\n\t\t{colorgrad.Viridis(), \"Viridis\"},\n\t\t{colorgrad.Plasma(), \"Plasma\"},\n\t\t{colorgrad.Magma(), \"Magma\"},\n\t\t{colorgrad.Inferno(), \"Inferno\"},\n\t\t{colorgrad.BrBG(), \"BrBG\"},\n\t\t{colorgrad.PRGn(), \"PRGn\"},\n\t\t{colorgrad.PiYG(), \"PiYG\"},\n\t\t{colorgrad.PuOr(), \"PuOr\"},\n\t\t{colorgrad.RdBu(), \"RdBu\"},\n\t\t{colorgrad.RdGy(), \"RdGy\"},\n\t\t{colorgrad.RdYlBu(), \"RdYlBu\"},\n\t\t{colorgrad.RdYlGn(), \"RdYlGn\"},\n\t\t{colorgrad.Spectral(), \"Spectral\"},\n\t\t{colorgrad.Blues(), \"Blues\"},\n\t\t{colorgrad.Greens(), \"Greens\"},\n\t\t{colorgrad.Greys(), \"Greys\"},\n\t\t{colorgrad.Oranges(), \"Oranges\"},\n\t\t{colorgrad.Purples(), \"Purples\"},\n\t\t{colorgrad.Reds(), \"Reds\"},\n\t\t{colorgrad.BuGn(), \"BuGn\"},\n\t\t{colorgrad.BuPu(), \"BuPu\"},\n\t\t{colorgrad.GnBu(), \"GnBu\"},\n\t\t{colorgrad.OrRd(), \"OrRd\"},\n\t\t{colorgrad.PuBuGn(), \"PuBuGn\"},\n\t\t{colorgrad.PuBu(), \"PuBu\"},\n\t\t{colorgrad.PuRd(), \"PuRd\"},\n\t\t{colorgrad.RdPu(), \"RdPu\"},\n\t\t{colorgrad.YlGnBu(), \"YlGnBu\"},\n\t\t{colorgrad.YlGn(), \"YlGn\"},\n\t\t{colorgrad.YlOrBr(), \"YlOrBr\"},\n\t\t{colorgrad.YlOrRd(), \"YlOrRd\"},\n\t}\n\n\t// Custom gradients\n\n\tgrad1, _ := colorgrad.NewGradient().Build()\n\n\tgrad2, _ := colorgrad.NewGradient().\n\t\tColors(\n\t\t\tcolorgrad.Rgb8(0, 206, 209, 255),\n\t\t\tcolorgrad.Rgb8(255, 105, 180, 255),\n\t\t\tcolorgrad.Rgb(0.274, 0.5, 0.7, 1),\n\t\t\tcolorgrad.Hsv(50, 1, 1, 1),\n\t\t\tcolorgrad.Hsv(348, 0.9, 0.8, 1),\n\t\t).\n\t\tBuild()\n\n\tgrad3, _ := colorgrad.NewGradient().\n\t\tHtmlColors(\"#C41189\", \"#00BFFF\", \"#FFD700\").\n\t\tBuild()\n\n\tgrad4, _ := colorgrad.NewGradient().\n\t\tHtmlColors(\"gold\", \"hotpink\", \"darkturquoise\").\n\t\tBuild()\n\n\tgrad5, _ := colorgrad.NewGradient().\n\t\tHtmlColors(\n\t\t\t\"rgb(125,110,221)\",\n\t\t\t\"rgb(90%,45%,97%)\",\n\t\t\t\"hsl(229,79%,85%)\",\n\t\t).\n\t\tBuild()\n\n\t// Domain & color position\n\n\tdomain1, _ := colorgrad.NewGradient().\n\t\tHtmlColors(\"deeppink\", \"gold\", \"seagreen\").\n\t\tBuild()\n\n\tdomain2, _ := colorgrad.NewGradient().\n\t\tHtmlColors(\"deeppink\", \"gold\", \"seagreen\").\n\t\tDomain(0, 100).\n\t\tBuild()\n\n\tdomain3, _ := colorgrad.NewGradient().\n\t\tHtmlColors(\"deeppink\", \"gold\", \"seagreen\").\n\t\tDomain(-1, 1).\n\t\tBuild()\n\n\tcolorPos1, _ := colorgrad.NewGradient().\n\t\tHtmlColors(\"deeppink\", \"gold\", \"seagreen\").\n\t\tDomain(0, 0.7, 1).\n\t\tBuild()\n\n\tcolorPos2, _ := colorgrad.NewGradient().\n\t\tHtmlColors(\"deeppink\", \"gold\", \"seagreen\").\n\t\tDomain(15, 30, 80).\n\t\tBuild()\n\n\tcolorPos3, _ := colorgrad.NewGradient().\n\t\tHtmlColors(\"deeppink\", \"#6d27a1\", \"#ff0\", \"#1185e4\").\n\t\tDomain(0, 0.7, 0.7, 1).\n\t\tBuild()\n\n\t// Blending modes\n\n\tcolors := []string{\"#fff\", \"#00f\"}\n\n\tblendRgb, _ := colorgrad.NewGradient().\n\t\tHtmlColors(colors...).\n\t\tMode(colorgrad.BlendRgb).\n\t\tBuild()\n\n\tblendLinearRgb, _ := colorgrad.NewGradient().\n\t\tHtmlColors(colors...).\n\t\tMode(colorgrad.BlendLinearRgb).\n\t\tBuild()\n\n\tblendOklab, _ := colorgrad.NewGradient().\n\t\tHtmlColors(colors...).\n\t\tMode(colorgrad.BlendOklab).\n\t\tBuild()\n\n\t// Interpolation modes\n\n\tcolors = []string{\"#C41189\", \"#00BFFF\", \"#FFD700\"}\n\n\tinterpLinear, _ := colorgrad.NewGradient().\n\t\tHtmlColors(colors...).\n\t\tInterpolation(colorgrad.InterpolationLinear).\n\t\tBuild()\n\n\tinterpCatmullRom, _ := colorgrad.NewGradient().\n\t\tHtmlColors(colors...).\n\t\tInterpolation(colorgrad.InterpolationCatmullRom).\n\t\tBuild()\n\n\tinterpBasis, _ := colorgrad.NewGradient().\n\t\tHtmlColors(colors...).\n\t\tInterpolation(colorgrad.InterpolationBasis).\n\t\tBuild()\n\n\tcustomGradients := []data{\n\t\t{grad1, \"custom-default\"},\n\t\t{grad2, \"custom-colors\"},\n\t\t{grad3, \"custom-hex-colors\"},\n\t\t{grad4, \"custom-named-colors\"},\n\t\t{grad5, \"custom-css-colors\"},\n\t\t{domain1, \"domain-default\"},\n\t\t{domain2, \"domain-0-100\"},\n\t\t{domain3, \"domain-neg1-1\"},\n\t\t{colorPos1, \"color-position-1\"},\n\t\t{colorPos2, \"color-position-2\"},\n\t\t{colorPos3, \"color-position-3\"},\n\t\t{blendRgb, \"blend-rgb\"},\n\t\t{blendLinearRgb, \"blend-linear-rgb\"},\n\t\t{blendOklab, \"blend-oklab\"},\n\t\t{interpLinear, \"interpolation-linear\"},\n\t\t{interpCatmullRom, \"interpolation-catmull-rom\"},\n\t\t{interpBasis, \"interpolation-basis\"},\n\t}\n\n\t// Sharp gradients\n\n\tgrad := colorgrad.Rainbow()\n\tvar segments uint = 11\n\n\tsharpGradients := []data{\n\t\t{grad.Sharp(segments, 0.0), \"0.0\"},\n\t\t{grad.Sharp(segments, 0.25), \"0.25\"},\n\t\t{grad.Sharp(segments, 0.5), \"0.5\"},\n\t\t{grad.Sharp(segments, 0.75), \"0.75\"},\n\t\t{grad.Sharp(segments, 1.0), \"1.0\"},\n\t}\n\n\tif opt.testData {\n\t\tsample := 12\n\n\t\tfor _, d := range presetGradients {\n\t\t\tcolors := d.gradient.Colors(uint(sample))\n\t\t\thexColors := make([]string, len(colors))\n\t\t\tfor i, c := range colors {\n\t\t\t\thexColors[i] = fmt.Sprintf(\"%q\", c.HexString())\n\t\t\t}\n\t\t\tfmt.Printf(\"grad = %s()\\n\", d.name)\n\t\t\tfmt.Printf(\"testSlice(t, colors2hex(grad.Colors(%v)), []string{\\n\", sample)\n\t\t\tfmt.Printf(\"  %v,\\n\", strings.Join(hexColors, \", \"))\n\t\t\tfmt.Printf(\"})\\n\\n\")\n\t\t}\n\t\treturn\n\t}\n\n\twidth := 1000\n\theight := 150\n\tpadding := 10\n\n\terr := os.Mkdir(\"output\", 0750)\n\tif err != nil && !os.IsExist(err) {\n\t\tpanic(err)\n\t}\n\n\tfor _, d := range presetGradients {\n\t\tfilepath := fmt.Sprintf(\"output/preset-%s.png\", d.name)\n\t\tfmt.Println(filepath)\n\t\tif opt.saveImage {\n\t\t\timg := gradRgbPlot(d.gradient, width, height, padding)\n\t\t\tsavePNG(img, filepath)\n\t\t}\n\t}\n\n\tfor _, d := range customGradients {\n\t\tfilepath := fmt.Sprintf(\"output/%s.png\", d.name)\n\t\tfmt.Println(filepath)\n\t\tif opt.saveImage {\n\t\t\timg := gradRgbPlot(d.gradient, width, height, padding)\n\t\t\tsavePNG(img, filepath)\n\t\t}\n\t}\n\n\tfor _, d := range sharpGradients {\n\t\tfilepath := fmt.Sprintf(\"output/sharp-smoothness-%s.png\", d.name)\n\t\tfmt.Println(filepath)\n\t\tif opt.saveImage {\n\t\t\timg := gradRgbPlot(d.gradient, width, height, padding)\n\t\t\tsavePNG(img, filepath)\n\t\t}\n\t}\n\n\t// GIMP gradients\n\n\tggrPath := \"./ggr/*.ggr\"\n\t//ggrPath = \"/usr/share/gimp/2.0/gradients/*.ggr\"\n\tggrs, ggrsErr := filepath.Glob(ggrPath)\n\n\tif ggrsErr == nil {\n\t\tfor _, s := range ggrs {\n\t\t\tgrad := parseGgr(s)\n\t\t\tfilepath := fmt.Sprintf(\"output/ggr_%s.png\", filepath.Base(s))\n\t\t\tfmt.Println(filepath)\n\t\t\tif opt.saveImage {\n\t\t\t\timg := gradRgbPlot(grad, width, height, padding)\n\t\t\t\tsavePNG(img, filepath)\n\t\t\t}\n\t\t}\n\t} else {\n\t\tfmt.Println(ggrsErr)\n\t}\n}\n\nfunc parseGgr(filepath string) colorgrad.Gradient {\n\tblack := colorgrad.Rgb(0, 0, 0, 1)\n\twhite := colorgrad.Rgb(1, 1, 1, 1)\n\tfile, err := os.Open(filepath)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer file.Close()\n\tgrad, _, err2 := colorgrad.ParseGgr(file, black, white)\n\tif err2 != nil {\n\t\tpanic(err2)\n\t}\n\treturn grad\n}\n\nfunc gradientImage(gradient colorgrad.Gradient, width, height int) image.Image {\n\tfw := float64(width)\n\tdmin, dmax := gradient.Domain()\n\timg := image.NewRGBA(image.Rect(0, 0, width, height))\n\tfor x := 0; x < width; x++ {\n\t\tcol := gradient.At(remap(float64(x), 0, fw, dmin, dmax))\n\t\tfor y := 0; y < height; y++ {\n\t\t\timg.Set(x, y, col)\n\t\t}\n\t}\n\treturn img\n}\n\nfunc rgbPlot(gradient colorgrad.Gradient, width, height int) image.Image {\n\timg := image.NewRGBA(image.Rect(0, 0, width, height))\n\tdraw.Draw(img, img.Bounds(), &image.Uniform{color.Gray{235}}, image.Point{}, draw.Src)\n\n\tdmin, dmax := gradient.Domain()\n\tfw := float64(width)\n\ty1 := 0.0\n\ty2 := float64(height)\n\n\tfor x := 0; x < width; x++ {\n\t\tcol := gradient.At(remap(float64(x), 0, fw, dmin, dmax))\n\n\t\tr := remap(col.R, 0, 1, y2, y1)\n\t\tg := remap(col.G, 0, 1, y2, y1)\n\t\tb := remap(col.B, 0, 1, y2, y1)\n\n\t\timg.Set(x, int(r), color.NRGBA{255, 0, 0, 255})\n\t\timg.Set(x, int(g), color.NRGBA{0, 128, 0, 255})\n\t\timg.Set(x, int(b), color.NRGBA{0, 0, 255, 255})\n\t}\n\treturn img\n}\n\nfunc gradRgbPlot(gradient colorgrad.Gradient, width, height, padding int) image.Image {\n\tw := width + padding*2\n\th := height*2 + padding*3\n\n\timg := image.NewRGBA(image.Rect(0, 0, w, h))\n\tdraw.Draw(img, img.Bounds(), &image.Uniform{color.Gray{255}}, image.Point{}, draw.Src)\n\n\tgradImg := gradientImage(gradient, width, height)\n\tplotImg := rgbPlot(gradient, width, height)\n\n\tx1 := padding\n\ty1 := padding\n\tx2 := x1 + width\n\ty2 := y1 + height\n\tdraw.Draw(img, image.Rect(x1, y1, x2, y2), gradImg, image.Point{}, draw.Src)\n\n\ty1 = y2 + padding\n\ty2 = y1 + height\n\tdraw.Draw(img, image.Rect(x1, y1, x2, y2), plotImg, image.Point{}, draw.Src)\n\treturn img\n}\n\n// Map t which is in range [a, b] to range [c, d]\nfunc remap(t, a, b, c, d float64) float64 {\n\treturn (t-a)*((d-c)/(b-a)) + c\n}\n\nfunc savePNG(img image.Image, filepath string) {\n\tfile, err := os.Create(filepath)\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n\tdefer file.Close()\n\tpng.Encode(file, img)\n}\n"
  },
  {
    "path": "gimp.go",
    "content": "package colorgrad\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\t\"strings\"\n)\n\n// References:\n// https://gitlab.gnome.org/GNOME/gimp/-/blob/master/devel-docs/ggr.txt\n// https://gitlab.gnome.org/GNOME/gimp/-/blob/master/app/core/gimpgradient.c\n// https://gitlab.gnome.org/GNOME/gimp/-/blob/master/app/core/gimpgradient-load.c\n\nconst epsilon = 1e-10\nconst fracPi2 = math.Pi / 2\n\ntype blendingType int\n\nconst (\n\tlinear blendingType = iota\n\tcurved\n\tsinusoidal\n\tsphericalIncreasing\n\tsphericalDecreasing\n\tstep\n)\n\ntype coloringType int\n\nconst (\n\trgb coloringType = iota\n\thsvCcw\n\thsvCw\n)\n\ntype gimpSegment struct {\n\t// Left endpoint color\n\tlcolor Color\n\t// Right endpoint color\n\trcolor Color\n\t// Left endpoint coordinate\n\tlpos float64\n\t// Midpoint coordinate\n\tmpos float64\n\t// Right endpoint coordinate\n\trpos float64\n\t// Blending function type\n\tblending blendingType\n\t// Coloring type\n\tcoloring coloringType\n}\n\ntype gimpGradient struct {\n\tsegments []gimpSegment\n\tmin      float64\n\tmax      float64\n}\n\nfunc (ggr gimpGradient) At(t float64) Color {\n\tif t <= ggr.min {\n\t\treturn ggr.segments[0].lcolor\n\t}\n\n\tif t >= ggr.max {\n\t\treturn ggr.segments[len(ggr.segments)-1].rcolor\n\t}\n\n\tif math.IsNaN(t) {\n\t\treturn Color{A: 1}\n\t}\n\n\tlow := 0\n\thigh := len(ggr.segments)\n\tmid := 0\n\n\tfor low < high {\n\t\tmid = (low + high) / 2\n\t\tif t > ggr.segments[mid].rpos {\n\t\t\tlow = mid + 1\n\t\t} else if t < ggr.segments[mid].lpos {\n\t\t\thigh = mid\n\t\t} else {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tseg := ggr.segments[mid]\n\tseg_len := seg.rpos - seg.lpos\n\n\tvar middle float64\n\tvar pos float64\n\n\tif seg_len < epsilon {\n\t\tmiddle = 0.5\n\t\tpos = 0.5\n\t} else {\n\t\tmiddle = (seg.mpos - seg.lpos) / seg_len\n\t\tpos = (t - seg.lpos) / seg_len\n\t}\n\n\tvar f float64\n\n\tswitch seg.blending {\n\tcase linear:\n\t\tf = calc_linear_factor(middle, pos)\n\tcase curved:\n\t\tif middle < epsilon {\n\t\t\treturn seg.rcolor\n\t\t} else if math.Abs(1-middle) < epsilon {\n\t\t\treturn seg.lcolor\n\t\t} else {\n\t\t\tf = math.Exp(-math.Ln2 * math.Log10(pos) / math.Log10(middle))\n\t\t}\n\tcase sinusoidal:\n\t\tx := calc_linear_factor(middle, pos)\n\t\tf = (math.Sin(-fracPi2+math.Pi*x) + 1) / 2\n\tcase sphericalIncreasing:\n\t\tx := calc_linear_factor(middle, pos) - 1\n\t\tf = math.Sqrt(1 - x*x)\n\tcase sphericalDecreasing:\n\t\tx := calc_linear_factor(middle, pos)\n\t\tf = 1 - math.Sqrt(1-x*x)\n\tcase step:\n\t\tif pos >= middle {\n\t\t\treturn seg.rcolor\n\t\t} else {\n\t\t\treturn seg.lcolor\n\t\t}\n\t}\n\n\tswitch seg.coloring {\n\tcase rgb:\n\t\treturn blendRgb(seg.lcolor, seg.rcolor, f)\n\tcase hsvCcw:\n\t\treturn blendHsvCcw(seg.lcolor, seg.rcolor, f)\n\tcase hsvCw:\n\t\treturn blendHsvCw(seg.lcolor, seg.rcolor, f)\n\t}\n\n\treturn ggr.segments[0].lcolor\n}\n\nfunc calc_linear_factor(middle, pos float64) float64 {\n\tif pos <= middle {\n\t\tif middle < epsilon {\n\t\t\treturn 0\n\t\t} else {\n\t\t\treturn 0.5 * pos / middle\n\t\t}\n\t} else {\n\t\tpos = pos - middle\n\t\tmiddle = 1 - middle\n\n\t\tif middle < epsilon {\n\t\t\treturn 1\n\t\t} else {\n\t\t\treturn 0.5 + 0.5*pos/middle\n\t\t}\n\t}\n}\n\nfunc blendHsvCcw(c1, c2 Color, t float64) Color {\n\thsvA := col2hsv(c1)\n\thsvB := col2hsv(c2)\n\n\tvar hue float64\n\n\tif hsvA[0] < hsvB[0] {\n\t\thue = hsvA[0] + ((hsvB[0] - hsvA[0]) * t)\n\t} else {\n\t\th := hsvA[0] + ((360 - (hsvA[0] - hsvB[0])) * t)\n\n\t\tif h > 360 {\n\t\t\thue = h - 360\n\t\t} else {\n\t\t\thue = h\n\t\t}\n\t}\n\n\treturn Hsv(\n\t\thue,\n\t\thsvA[1]+t*(hsvB[1]-hsvA[1]),\n\t\thsvA[2]+t*(hsvB[2]-hsvA[2]),\n\t\thsvA[3]+t*(hsvB[3]-hsvA[3]),\n\t)\n}\n\nfunc blendHsvCw(c1, c2 Color, t float64) Color {\n\thsvA := col2hsv(c1)\n\thsvB := col2hsv(c2)\n\n\tvar hue float64\n\n\tif hsvB[0] < hsvA[0] {\n\t\thue = hsvA[0] - ((hsvA[0] - hsvB[0]) * t)\n\t} else {\n\t\th := hsvA[0] - ((360 - (hsvB[0] - hsvA[0])) * t)\n\n\t\tif h < 0 {\n\t\t\thue = h + 360\n\t\t} else {\n\t\t\thue = h\n\t\t}\n\t}\n\n\treturn Hsv(\n\t\thue,\n\t\thsvA[1]+t*(hsvB[1]-hsvA[1]),\n\t\thsvA[2]+t*(hsvB[2]-hsvA[2]),\n\t\thsvA[3]+t*(hsvB[3]-hsvA[3]),\n\t)\n}\n\nfunc ParseGgr(r io.Reader, fg, bg Color) (Gradient, string, error) {\n\tzgrad := Gradient{\n\t\tCore: zeroGradient{},\n\t\tMin:  0,\n\t\tMax:  1,\n\t}\n\n\tsegments := []gimpSegment{}\n\tvar nseg int\n\tvar name string\n\txseg := 0\n\ti := 0\n\tscanner := bufio.NewScanner(r)\n\n\tfor scanner.Scan() {\n\t\tif i == 0 {\n\t\t\tif scanner.Text() != \"GIMP Gradient\" {\n\t\t\t\treturn zgrad, name, fmt.Errorf(\"invalid header\")\n\t\t\t}\n\t\t} else if i == 1 {\n\t\t\tif !strings.HasPrefix(scanner.Text(), \"Name:\") {\n\t\t\t\treturn zgrad, name, fmt.Errorf(\"invalid header\")\n\t\t\t}\n\n\t\t\tname = strings.TrimSpace(scanner.Text()[5:])\n\t\t} else if i == 2 {\n\t\t\tt, ok := parseFloat(scanner.Text())\n\n\t\t\tif ok {\n\t\t\t\tnseg = int(t)\n\t\t\t} else {\n\t\t\t\treturn zgrad, name, fmt.Errorf(\"invalid header\")\n\t\t\t}\n\t\t} else {\n\t\t\tif i >= nseg+3 {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tseg, ok := parseSegment(scanner.Text(), fg, bg)\n\n\t\t\tif ok {\n\t\t\t\tsegments = append(segments, seg)\n\t\t\t\txseg++\n\t\t\t} else {\n\t\t\t\treturn zgrad, name, fmt.Errorf(\"invalid segment\")\n\t\t\t}\n\t\t}\n\t\ti++\n\t}\n\n\tif err := scanner.Err(); err != nil {\n\t\treturn zgrad, name, err\n\t}\n\n\tif len(segments) == 0 {\n\t\treturn zgrad, name, fmt.Errorf(\"segments %v\", i)\n\t}\n\n\tif xseg < nseg {\n\t\treturn zgrad, name, fmt.Errorf(\"wrong segments count, %v, %v\", nseg, xseg)\n\t}\n\n\tgradbase := gimpGradient{\n\t\tsegments: segments,\n\t\tmin:      0,\n\t\tmax:      1,\n\t}\n\n\treturn Gradient{\n\t\tCore: gradbase,\n\t\tMin:  0,\n\t\tMax:  1,\n\t}, name, nil\n}\n\nfunc parseSegment(s string, fg, bg Color) (gimpSegment, bool) {\n\tparams := strings.Fields(s)\n\tplen := len(params)\n\n\tif plen != 13 && plen != 15 {\n\t\treturn gimpSegment{}, false\n\t}\n\n\td := make([]float64, 15)\n\n\tfor i, x := range params {\n\t\tt, ok := parseFloat(x)\n\n\t\tif ok {\n\t\t\td[i] = t\n\t\t\tcontinue\n\t\t}\n\n\t\treturn gimpSegment{}, false\n\t}\n\n\tif plen == 13 {\n\t\td[13] = 0\n\t\td[14] = 0\n\t}\n\n\tvar blending blendingType\n\n\tswitch int(d[11]) {\n\tcase 0:\n\t\tblending = linear\n\tcase 1:\n\t\tblending = curved\n\tcase 2:\n\t\tblending = sinusoidal\n\tcase 3:\n\t\tblending = sphericalIncreasing\n\tcase 4:\n\t\tblending = sphericalDecreasing\n\tcase 5:\n\t\tblending = step\n\tdefault:\n\t\treturn gimpSegment{}, false\n\t}\n\n\tvar coloring coloringType\n\n\tswitch int(d[12]) {\n\tcase 0:\n\t\tcoloring = rgb\n\tcase 1:\n\t\tcoloring = hsvCcw\n\tcase 2:\n\t\tcoloring = hsvCw\n\tdefault:\n\t\treturn gimpSegment{}, false\n\t}\n\n\tvar lcolor Color\n\n\tswitch int(d[13]) {\n\tcase 0:\n\t\tlcolor = Color{R: d[3], G: d[4], B: d[5], A: d[6]}\n\tcase 1:\n\t\tlcolor = fg\n\tcase 2:\n\t\tlcolor = Rgb(fg.R, fg.G, fg.B, 0)\n\tcase 3:\n\t\tlcolor = bg\n\tcase 4:\n\t\tlcolor = Rgb(bg.R, bg.G, bg.B, 0)\n\tdefault:\n\t\treturn gimpSegment{}, false\n\t}\n\n\tvar rcolor Color\n\n\tswitch int(d[14]) {\n\tcase 0:\n\t\trcolor = Color{R: d[7], G: d[8], B: d[9], A: d[10]}\n\tcase 1:\n\t\trcolor = fg\n\tcase 2:\n\t\trcolor = Rgb(fg.R, fg.G, fg.B, 0)\n\tcase 3:\n\t\trcolor = bg\n\tcase 4:\n\t\trcolor = Rgb(bg.R, bg.G, bg.B, 0)\n\tdefault:\n\t\treturn gimpSegment{}, false\n\t}\n\n\treturn gimpSegment{\n\t\tlcolor:   lcolor,\n\t\trcolor:   rcolor,\n\t\tlpos:     d[0],\n\t\tmpos:     d[1],\n\t\trpos:     d[2],\n\t\tblending: blending,\n\t\tcoloring: coloring,\n\t}, true\n}\n"
  },
  {
    "path": "gimp_test.go",
    "content": "package colorgrad\n\nimport (\n\t\"math\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc Test_GIMPGradient(t *testing.T) {\n\tblack := Rgb(0, 0, 0, 1)\n\tred := Rgb(1, 0, 0, 1)\n\tblue := Rgb(0, 0, 1, 1)\n\n\t// Black to white\n\tggr := \"GIMP Gradient\\nName: My Gradient\\n1\\n0 0.5 1 0 0 0 1 1 1 1 1 0 0 0 0\"\n\tgrad, name, err := ParseGgr(strings.NewReader(ggr), black, black)\n\ttest(t, err, nil)\n\ttest(t, name, \"My Gradient\")\n\ttest(t, grad.At(0).HexString(), \"#000000\")\n\ttest(t, grad.At(1).HexString(), \"#ffffff\")\n\ttest(t, grad.At(-0.5).HexString(), \"#000000\")\n\ttest(t, grad.At(1.5).HexString(), \"#ffffff\")\n\ttest(t, grad.At(math.NaN()).HexString(), \"#000000\")\n\n\t// Foreground to background\n\tggr = \"GIMP Gradient\\nName: My Gradient\\n1\\n0 0.5 1 0 0 0 1 1 1 1 1 0 0 1 3\"\n\tgrad, name, err = ParseGgr(strings.NewReader(ggr), red, blue)\n\ttest(t, err, nil)\n\ttest(t, name, \"My Gradient\")\n\ttest(t, grad.At(0).HexString(), \"#ff0000\")\n\ttest(t, grad.At(1).HexString(), \"#0000ff\")\n\n\t// Blending function: step\n\tggr = \"GIMP Gradient\\nName: My Gradient\\n1\\n0 0.5 1 1 0 0 1 0 0 1 1 5 0 0 0\"\n\tgrad, name, err = ParseGgr(strings.NewReader(ggr), black, black)\n\ttest(t, err, nil)\n\ttest(t, name, \"My Gradient\")\n\ttest(t, grad.At(0.00).HexString(), \"#ff0000\")\n\ttest(t, grad.At(0.25).HexString(), \"#ff0000\")\n\ttest(t, grad.At(0.49).HexString(), \"#ff0000\")\n\ttest(t, grad.At(0.51).HexString(), \"#0000ff\")\n\ttest(t, grad.At(0.75).HexString(), \"#0000ff\")\n\ttest(t, grad.At(1.00).HexString(), \"#0000ff\")\n\n\t// Coloring type: HSV CCW (white to blue)\n\tggr = \"GIMP Gradient\\nName: My Gradient\\n1\\n0 0.5 1 1 1 1 1 0 0 1 1 0 1 0 0\"\n\tgrad, name, err = ParseGgr(strings.NewReader(ggr), black, black)\n\ttest(t, err, nil)\n\ttest(t, name, \"My Gradient\")\n\ttest(t, grad.At(0.0).HexString(), \"#ffffff\")\n\ttest(t, grad.At(0.5).HexString(), \"#80ff80\")\n\ttest(t, grad.At(1.0).HexString(), \"#0000ff\")\n\n\t// Coloring type: HSV CW (white to blue)\n\tggr = \"GIMP Gradient\\nName: My Gradient\\n1\\n0 0.5 1 1 1 1 1 0 0 1 1 0 2 0 0\"\n\tgrad, name, err = ParseGgr(strings.NewReader(ggr), black, black)\n\ttest(t, err, nil)\n\ttest(t, name, \"My Gradient\")\n\ttest(t, grad.At(0.0).HexString(), \"#ffffff\")\n\ttest(t, grad.At(0.5).HexString(), \"#ff80ff\")\n\ttest(t, grad.At(1.0).HexString(), \"#0000ff\")\n\n\t// Invalid formats\n\n\tdata := []string{\n\t\t\"\",\n\t\t\" \",\n\t\t\"GIMP Palette\\nName: Gold\\n#\\n252 252 128\",\n\t\t\"GIMP Gradient\\nxx\",\n\t\t\"GIMP Gradient\\nName: Gradient\\nx\",\n\t\t\"GIMP Gradient\\nName: Gradient\\n1\\n0 0 0\",\n\t}\n\tfor _, s := range data {\n\t\t_, _, err := ParseGgr(strings.NewReader(s), black, black)\n\t\ttestTrue(t, err != nil)\n\t}\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/mazznoer/colorgrad\n\ngo 1.18\n\nrequire github.com/mazznoer/csscolorparser v0.1.8\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/mazznoer/csscolorparser v0.1.8 h1:i7w3wHW99d0q0KZv1ONkU/efXFAKcw1mgEgW6gj8KUA=\ngithub.com/mazznoer/csscolorparser v0.1.8/go.mod h1:OQRVvgCyHDCAquR1YWfSwwaDcM0LhnSffGnlbOew/3I=\n"
  },
  {
    "path": "gradient.go",
    "content": "package colorgrad\n\nimport (\n\t\"image/color\"\n\t\"math\"\n\n\t\"github.com/mazznoer/csscolorparser\"\n)\n\ntype BlendMode int\n\nconst (\n\tBlendRgb BlendMode = iota\n\tBlendLinearRgb\n\tBlendLab\n\tBlendOklab\n)\n\nfunc (b BlendMode) String() string {\n\tswitch b {\n\tcase BlendRgb:\n\t\treturn \"BlendRgb\"\n\tcase BlendLinearRgb:\n\t\treturn \"BlendLinearRgb\"\n\tcase BlendLab:\n\t\treturn \"BlendLab\"\n\tcase BlendOklab:\n\t\treturn \"BlendOklab\"\n\t}\n\treturn \"\"\n}\n\ntype Interpolation int\n\nconst (\n\tInterpolationLinear Interpolation = iota\n\tInterpolationSmoothstep\n\tInterpolationCatmullRom\n\tInterpolationBasis\n)\n\nfunc (i Interpolation) String() string {\n\tswitch i {\n\tcase InterpolationLinear:\n\t\treturn \"InterpolationLinear\"\n\tcase InterpolationSmoothstep:\n\t\treturn \"InterpolationSmoothstep\"\n\tcase InterpolationCatmullRom:\n\t\treturn \"InterpolationCatmullRom\"\n\tcase InterpolationBasis:\n\t\treturn \"InterpolationBasis\"\n\t}\n\treturn \"\"\n}\n\ntype Color = csscolorparser.Color\n\nvar Hwb = csscolorparser.FromHwb\nvar Hsv = csscolorparser.FromHsv\nvar Hsl = csscolorparser.FromHsl\nvar LinearRgb = csscolorparser.FromLinearRGB\nvar Lab = csscolorparser.FromLab\nvar Lch = csscolorparser.FromLch\nvar Oklab = csscolorparser.FromOklab\nvar Oklch = csscolorparser.FromOklch\n\nfunc Rgb(r, g, b, a float64) Color {\n\treturn Color{R: r, G: g, B: b, A: a}\n}\n\nfunc Rgb8(r, g, b, a uint8) Color {\n\treturn Color{R: float64(r) / 255, G: float64(g) / 255, B: float64(b) / 255, A: float64(a) / 255}\n}\n\nfunc GoColor(col color.Color) Color {\n\tr, g, b, a := col.RGBA()\n\tif a == 0 {\n\t\treturn csscolorparser.Color{}\n\t}\n\tr *= 0xffff\n\tr /= a\n\tg *= 0xffff\n\tg /= a\n\tb *= 0xffff\n\tb /= a\n\treturn csscolorparser.Color{R: float64(r) / 65535.0, G: float64(g) / 65535.0, B: float64(b) / 65535.0, A: float64(a) / 65535.0}\n}\n\ntype GradientCore interface {\n\t// Get color at certain position\n\tAt(float64) Color\n}\n\ntype Gradient struct {\n\tCore GradientCore\n\tMin  float64\n\tMax  float64\n}\n\n// Get color at certain position\nfunc (g Gradient) At(t float64) Color {\n\treturn g.Core.At(t)\n}\n\n// Get color at certain position\nfunc (g Gradient) RepeatAt(t float64) Color {\n\tt = norm(t, g.Min, g.Max)\n\treturn g.Core.At(g.Min + modulo(t, 1)*(g.Max-g.Min))\n}\n\n// Get color at certain position\nfunc (g Gradient) ReflectAt(t float64) Color {\n\tt = norm(t, g.Min, g.Max)\n\treturn g.Core.At(g.Min + math.Abs(modulo(1+t, 2)-1)*(g.Max-g.Min))\n}\n\n// Get n colors evenly spaced across gradient\nfunc (g Gradient) Colors(count uint) []Color {\n\td := g.Max - g.Min\n\tl := float64(count) - 1\n\tcolors := make([]Color, count)\n\tfor i := range colors {\n\t\tcolors[i] = g.Core.At(g.Min + (float64(i)*d)/l).Clamp()\n\t}\n\treturn colors\n}\n\n// Get the gradient domain min and max\nfunc (g Gradient) Domain() (float64, float64) {\n\treturn g.Min, g.Max\n}\n\n// Return a new hard-edge gradient\nfunc (g Gradient) Sharp(segment uint, smoothness float64) Gradient {\n\tcolors := []Color{}\n\tif segment >= 2 {\n\t\tcolors = g.Colors(segment)\n\t} else {\n\t\tcolors = append(colors, g.At(g.Min))\n\t\tcolors = append(colors, g.At(g.Min))\n\t}\n\treturn newSharpGradient(colors, g.Min, g.Max, smoothness)\n}\n\ntype zeroGradient struct {\n}\n\nfunc (zg zeroGradient) At(t float64) Color {\n\treturn Color{R: 0, G: 0, B: 0, A: 0}\n}\n"
  },
  {
    "path": "gradient_test.go",
    "content": "package colorgrad\n\nimport (\n\t\"fmt\"\n\t\"image/color\"\n\t\"testing\"\n)\n\nfunc Test_Basic(t *testing.T) {\n\ttest(t, Rgb(1, 0.8431, 0, 1).HexString(), \"#ffd700\")\n\ttest(t, Rgb8(46, 139, 87, 255).HexString(), \"#2e8b57\")\n\ttest(t, Hwb(330, 0.4118, 0, 1).HexString(), \"#ff69b4\")\n\n\t// Go color\n\ttest(t, GoColor(color.RGBA{R: 255, G: 0, B: 0, A: 255}).HexString(), \"#ff0000\")\n\ttest(t, GoColor(color.RGBA{R: 127, G: 0, B: 0, A: 127}).HexString(), \"#ff00007f\")\n\ttest(t, GoColor(color.RGBA{R: 0, G: 0, B: 0, A: 0}).HexString(), \"#00000000\")\n\n\ttest(t, GoColor(color.NRGBA{R: 0, G: 255, B: 0, A: 255}).HexString(), \"#00ff00\")\n\ttest(t, GoColor(color.NRGBA{R: 0, G: 255, B: 0, A: 127}).HexString(), \"#00ff007f\")\n\n\ttest(t, GoColor(color.Gray{0}).HexString(), \"#000000\")\n\ttest(t, GoColor(color.Gray{127}).HexString(), \"#7f7f7f\")\n\n\t// Enums\n\ttest(t, BlendRgb.String(), \"BlendRgb\")\n\ttest(t, fmt.Sprintf(\"%s\", BlendLinearRgb), \"BlendLinearRgb\")\n\ttest(t, fmt.Sprintf(\"%v\", BlendOklab), \"BlendOklab\")\n\n\ttest(t, InterpolationLinear.String(), \"InterpolationLinear\")\n\ttest(t, fmt.Sprintf(\"%s\", InterpolationCatmullRom), \"InterpolationCatmullRom\")\n\ttest(t, fmt.Sprintf(\"%v\", InterpolationBasis), \"InterpolationBasis\")\n}\n\nfunc Test_GetColors(t *testing.T) {\n\tgrad, _ := NewGradient().Build()\n\ttest(t, len(grad.Colors(0)), 0)\n\ttest(t, grad.Colors(1)[0].HexString(), \"#000000\")\n\ttestSlice(t, colors2hex(grad.Colors(2)), []string{\n\t\t\"#000000\",\n\t\t\"#ffffff\",\n\t})\n\ttestSlice(t, colors2hex(grad.Colors(3)), []string{\n\t\t\"#000000\",\n\t\t\"#808080\",\n\t\t\"#ffffff\",\n\t})\n\n\tgrad, _ = NewGradient().\n\t\tHtmlColors(\"#f00\", \"#0f0\", \"#00f\").\n\t\tDomain(-1, 1).\n\t\tBuild()\n\n\ttestSlice(t, colors2hex(grad.Colors(5)), []string{\n\t\t\"#ff0000\",\n\t\t\"#808000\",\n\t\t\"#00ff00\",\n\t\t\"#008080\",\n\t\t\"#0000ff\",\n\t})\n}\n\nfunc Test_SpreadRepeat(t *testing.T) {\n\tgrad, _ := NewGradient().\n\t\tHtmlColors(\"#000\", \"#fff\").\n\t\tBuild()\n\n\ttest(t, grad.RepeatAt(-2.0).HexString(), \"#000000\")\n\ttest(t, grad.RepeatAt(-1.9).HexString(), \"#1a1a1a\")\n\ttest(t, grad.RepeatAt(-1.5).HexString(), \"#808080\")\n\ttest(t, grad.RepeatAt(-1.1).HexString(), \"#e5e5e5\")\n\n\ttest(t, grad.RepeatAt(-1.0).HexString(), \"#000000\")\n\ttest(t, grad.RepeatAt(-0.9).HexString(), \"#191919\")\n\ttest(t, grad.RepeatAt(-0.5).HexString(), \"#808080\")\n\ttest(t, grad.RepeatAt(-0.1).HexString(), \"#e6e6e6\")\n\n\ttest(t, grad.RepeatAt(0.0).HexString(), \"#000000\")\n\ttest(t, grad.RepeatAt(0.1).HexString(), \"#1a1a1a\")\n\ttest(t, grad.RepeatAt(0.5).HexString(), \"#808080\")\n\ttest(t, grad.RepeatAt(0.9).HexString(), \"#e5e5e5\")\n\n\ttest(t, grad.RepeatAt(1.0).HexString(), \"#000000\")\n\ttest(t, grad.RepeatAt(1.1).HexString(), \"#1a1a1a\")\n\ttest(t, grad.RepeatAt(1.5).HexString(), \"#808080\")\n\ttest(t, grad.RepeatAt(1.9).HexString(), \"#e5e5e5\")\n\n\ttest(t, grad.RepeatAt(2.0).HexString(), \"#000000\")\n\ttest(t, grad.RepeatAt(2.1).HexString(), \"#1a1a1a\")\n\ttest(t, grad.RepeatAt(2.5).HexString(), \"#808080\")\n\ttest(t, grad.RepeatAt(2.9).HexString(), \"#e5e5e5\")\n}\n\nfunc Test_SpreadReflect(t *testing.T) {\n\tgrad, _ := NewGradient().\n\t\tHtmlColors(\"#000\", \"#fff\").\n\t\tBuild()\n\n\ttest(t, grad.ReflectAt(-2.0).HexString(), \"#000000\")\n\ttest(t, grad.ReflectAt(-1.9).HexString(), \"#1a1a1a\")\n\ttest(t, grad.ReflectAt(-1.5).HexString(), \"#808080\")\n\ttest(t, grad.ReflectAt(-1.1).HexString(), \"#e5e5e5\")\n\n\ttest(t, grad.ReflectAt(-1.0).HexString(), \"#ffffff\")\n\ttest(t, grad.ReflectAt(-0.9).HexString(), \"#e5e5e5\")\n\ttest(t, grad.ReflectAt(-0.5).HexString(), \"#808080\")\n\ttest(t, grad.ReflectAt(-0.1).HexString(), \"#1a1a1a\")\n\n\ttest(t, grad.ReflectAt(0.0).HexString(), \"#000000\")\n\ttest(t, grad.ReflectAt(0.1).HexString(), \"#1a1a1a\")\n\ttest(t, grad.ReflectAt(0.5).HexString(), \"#808080\")\n\ttest(t, grad.ReflectAt(0.9).HexString(), \"#e5e5e5\")\n\n\ttest(t, grad.ReflectAt(1.0).HexString(), \"#ffffff\")\n\ttest(t, grad.ReflectAt(1.1).HexString(), \"#e5e5e5\")\n\ttest(t, grad.ReflectAt(1.5).HexString(), \"#808080\")\n\ttest(t, grad.ReflectAt(1.9).HexString(), \"#1a1a1a\")\n\n\ttest(t, grad.ReflectAt(2.0).HexString(), \"#000000\")\n\ttest(t, grad.ReflectAt(2.1).HexString(), \"#1a1a1a\")\n\ttest(t, grad.ReflectAt(2.5).HexString(), \"#808080\")\n\ttest(t, grad.ReflectAt(2.9).HexString(), \"#e5e5e5\")\n}\n"
  },
  {
    "path": "linear.go",
    "content": "package colorgrad\n\nimport (\n\t\"math\"\n)\n\ntype linearGradient struct {\n\tcolors    [][4]float64\n\tpositions []float64\n\tmin       float64\n\tmax       float64\n\tmode      BlendMode\n\tfirst     Color\n\tlast      Color\n}\n\nfunc (lg linearGradient) At(t float64) Color {\n\tif t <= lg.min {\n\t\treturn lg.first\n\t}\n\n\tif t >= lg.max {\n\t\treturn lg.last\n\t}\n\n\tif math.IsNaN(t) {\n\t\treturn Color{A: 1}\n\t}\n\n\tlow := 0\n\thigh := len(lg.positions)\n\n\tfor low < high {\n\t\tmid := (low + high) / 2\n\t\tif lg.positions[mid] < t {\n\t\t\tlow = mid + 1\n\t\t} else {\n\t\t\thigh = mid\n\t\t}\n\t}\n\n\tif low == 0 {\n\t\tlow = 1\n\t}\n\n\tp1 := lg.positions[low-1]\n\tp2 := lg.positions[low]\n\tt = (t - p1) / (p2 - p1)\n\ta, b, c, d := linearInterpolate(lg.colors[low-1], lg.colors[low], t)\n\n\tswitch lg.mode {\n\tcase BlendRgb:\n\t\treturn Color{R: a, G: b, B: c, A: d}\n\tcase BlendLinearRgb:\n\t\treturn LinearRgb(a, b, c, d)\n\tcase BlendLab:\n\t\treturn Lab(a, b, c, d).Clamp()\n\tcase BlendOklab:\n\t\treturn Oklab(a, b, c, d).Clamp()\n\t}\n\n\treturn Color{}\n}\n\nfunc newLinearGradient(colors []Color, positions []float64, mode BlendMode) Gradient {\n\tgradbase := linearGradient{\n\t\tcolors:    convertColors(colors, mode),\n\t\tpositions: positions,\n\t\tmin:       positions[0],\n\t\tmax:       positions[len(positions)-1],\n\t\tmode:      mode,\n\t\tfirst:     colors[0],\n\t\tlast:      colors[len(colors)-1],\n\t}\n\n\treturn Gradient{\n\t\tCore: gradbase,\n\t\tMin:  positions[0],\n\t\tMax:  positions[len(positions)-1],\n\t}\n}\n"
  },
  {
    "path": "linear_test.go",
    "content": "package colorgrad\n\nimport (\n\t\"math\"\n\t\"testing\"\n)\n\nfunc Test_LinearGradient(t *testing.T) {\n\tgrad, err := NewGradient().\n\t\tHtmlColors(\"#f00\", \"#0f0\", \"#00f\").\n\t\tMode(BlendRgb).\n\t\tInterpolation(InterpolationLinear).\n\t\tBuild()\n\n\ttest(t, err, nil)\n\ttest(t, grad.At(0.00).HexString(), \"#ff0000\")\n\ttest(t, grad.At(0.25).HexString(), \"#808000\")\n\ttest(t, grad.At(0.50).HexString(), \"#00ff00\")\n\ttest(t, grad.At(0.75).HexString(), \"#008080\")\n\ttest(t, grad.At(1.00).HexString(), \"#0000ff\")\n\n\ttestSlice(t, colors2hex(grad.Colors(5)), []string{\n\t\t\"#ff0000\",\n\t\t\"#808000\",\n\t\t\"#00ff00\",\n\t\t\"#008080\",\n\t\t\"#0000ff\",\n\t})\n\n\ttest(t, grad.At(-0.1).HexString(), \"#ff0000\")\n\ttest(t, grad.At(1.11).HexString(), \"#0000ff\")\n\ttest(t, grad.At(math.NaN()).HexString(), \"#000000\")\n}\n"
  },
  {
    "path": "preset.go",
    "content": "package colorgrad\n\nimport (\n\t\"math\"\n)\n\n// Reference: https://github.com/d3/d3-scale-chromatic\n\nconst deg2rad = math.Pi / 180\nconst pi1_3 = math.Pi / 3\nconst pi2_3 = math.Pi * 2 / 3\n\n// Sinebow\n\ntype sinebowGradient struct{}\n\nfunc Sinebow() Gradient {\n\treturn Gradient{\n\t\tCore: sinebowGradient{},\n\t\tMin:  0,\n\t\tMax:  1,\n\t}\n}\n\nfunc (sg sinebowGradient) At(t float64) Color {\n\tt = (0.5 - t) * math.Pi\n\treturn Color{\n\t\tR: math.Pow(math.Sin(t), 2),\n\t\tG: math.Pow(math.Sin(t+pi1_3), 2),\n\t\tB: math.Pow(math.Sin(t+pi2_3), 2),\n\t\tA: 1,\n\t}\n}\n\n// Turbo\n\ntype turboGradient struct{}\n\nfunc Turbo() Gradient {\n\treturn Gradient{\n\t\tCore: turboGradient{},\n\t\tMin:  0,\n\t\tMax:  1,\n\t}\n}\n\nfunc (tg turboGradient) At(t float64) Color {\n\tt = math.Max(0, math.Min(1, t))\n\tr := math.Round(34.61 + t*(1172.33-t*(10793.56-t*(33300.12-t*(38394.49-t*14825.05)))))\n\tg := math.Round(23.31 + t*(557.33+t*(1225.33-t*(3574.96-t*(1073.77+t*707.56)))))\n\tb := math.Round(27.2 + t*(3211.1-t*(15327.97-t*(27814-t*(22569.18-t*6838.66)))))\n\treturn Color{\n\t\tR: clamp01(r / 255),\n\t\tG: clamp01(g / 255),\n\t\tB: clamp01(b / 255),\n\t\tA: 1,\n\t}\n}\n\n// Cividis\n\ntype cividisGradient struct{}\n\nfunc Cividis() Gradient {\n\treturn Gradient{\n\t\tCore: cividisGradient{},\n\t\tMin:  0,\n\t\tMax:  1,\n\t}\n}\n\nfunc (cg cividisGradient) At(t float64) Color {\n\tt = math.Max(0, math.Min(1, t))\n\tr := math.Round(-4.54 - t*(35.34-t*(2381.73-t*(6402.7-t*(7024.72-t*2710.57)))))\n\tg := math.Round(32.49 + t*(170.73+t*(52.82-t*(131.46-t*(176.58-t*67.37)))))\n\tb := math.Round(81.24 + t*(442.36-t*(2482.43-t*(6167.24-t*(6614.94-t*2475.67)))))\n\treturn Color{\n\t\tR: clamp01(r / 255),\n\t\tG: clamp01(g / 255),\n\t\tB: clamp01(b / 255),\n\t\tA: 1,\n\t}\n}\n\n// Cubehelix\n\ntype cubehelix struct {\n\th, s, l float64\n}\n\nfunc (c cubehelix) toColor() Color {\n\th := (c.h + 120) * deg2rad\n\tl := c.l\n\ta := c.s * l * (1 - l)\n\tcosh := math.Cos(h)\n\tsinh := math.Sin(h)\n\tr := (l - a*math.Min(0.14861*cosh-1.78277*sinh, 1.0))\n\tg := (l - a*math.Min(0.29227*cosh+0.90649*sinh, 1.0))\n\tb := l + a*(1.97294*cosh)\n\treturn Color{\n\t\tR: clamp01(r),\n\t\tG: clamp01(g),\n\t\tB: clamp01(b),\n\t\tA: 1,\n\t}\n}\n\nfunc (c cubehelix) interpolate(c2 cubehelix, t float64) cubehelix {\n\treturn cubehelix{\n\t\th: c.h + t*(c2.h-c.h),\n\t\ts: c.s + t*(c2.s-c.s),\n\t\tl: c.l + t*(c2.l-c.l),\n\t}\n}\n\n// Cubehelix gradient\n\ntype cubehelixGradient struct {\n\tstart, end cubehelix\n}\n\nfunc CubehelixDefault() Gradient {\n\tgradbase := cubehelixGradient{\n\t\tstart: cubehelix{300, 0.5, 0.0},\n\t\tend:   cubehelix{-240, 0.5, 1.0},\n\t}\n\treturn Gradient{\n\t\tCore: gradbase,\n\t\tMin:  0,\n\t\tMax:  1,\n\t}\n}\n\nfunc Warm() Gradient {\n\tgradbase := cubehelixGradient{\n\t\tstart: cubehelix{-100, 0.75, 0.35},\n\t\tend:   cubehelix{80, 1.50, 0.8},\n\t}\n\treturn Gradient{\n\t\tCore: gradbase,\n\t\tMin:  0,\n\t\tMax:  1,\n\t}\n}\n\nfunc Cool() Gradient {\n\tgradbase := cubehelixGradient{\n\t\tstart: cubehelix{260, 0.75, 0.35},\n\t\tend:   cubehelix{80, 1.50, 0.8},\n\t}\n\treturn Gradient{\n\t\tCore: gradbase,\n\t\tMin:  0,\n\t\tMax:  1,\n\t}\n}\n\nfunc (cg cubehelixGradient) At(t float64) Color {\n\treturn cg.start.interpolate(cg.end, clamp01(t)).toColor()\n}\n\n// Rainbow\n\ntype rainbowGradient struct{}\n\nfunc Rainbow() Gradient {\n\treturn Gradient{\n\t\tCore: rainbowGradient{},\n\t\tMin:  0,\n\t\tMax:  1,\n\t}\n}\n\nfunc (rg rainbowGradient) At(t float64) Color {\n\tt = math.Max(0, math.Min(1, t))\n\tts := math.Abs(t - 0.5)\n\treturn cubehelix{\n\t\th: 360*t - 100,\n\t\ts: 1.5 - 1.5*ts,\n\t\tl: 0.8 - 0.9*ts,\n\t}.toColor()\n}\n\n// --- Presets from color ramps\n\nfunc u32ToColor(v uint32) Color {\n\tr := uint8(v >> 16)\n\tg := uint8(v >> 8)\n\tb := uint8(v)\n\treturn Rgb8(r, g, b, 255)\n}\n\nfunc preset(data []uint32) Gradient {\n\tcolors := make([]Color, len(data))\n\tfor i, v := range data {\n\t\tcolors[i] = u32ToColor(v)\n\t}\n\tpos := linspace(0, 1, uint(len(colors)))\n\treturn newBasisGradient(colors, pos, BlendRgb)\n}\n\n// Diverging\n\nfunc BrBG() Gradient {\n\tcolors := []uint32{0x543005, 0x8c510a, 0xbf812d, 0xdfc27d, 0xf6e8c3, 0xf5f5f5, 0xc7eae5, 0x80cdc1, 0x35978f, 0x01665e, 0x003c30}\n\treturn preset(colors)\n}\n\nfunc PRGn() Gradient {\n\tcolors := []uint32{0x40004b, 0x762a83, 0x9970ab, 0xc2a5cf, 0xe7d4e8, 0xf7f7f7, 0xd9f0d3, 0xa6dba0, 0x5aae61, 0x1b7837, 0x00441b}\n\treturn preset(colors)\n}\n\nfunc PiYG() Gradient {\n\tcolors := []uint32{0x8e0152, 0xc51b7d, 0xde77ae, 0xf1b6da, 0xfde0ef, 0xf7f7f7, 0xe6f5d0, 0xb8e186, 0x7fbc41, 0x4d9221, 0x276419}\n\treturn preset(colors)\n}\n\nfunc PuOr() Gradient {\n\tcolors := []uint32{0x2d004b, 0x542788, 0x8073ac, 0xb2abd2, 0xd8daeb, 0xf7f7f7, 0xfee0b6, 0xfdb863, 0xe08214, 0xb35806, 0x7f3b08}\n\treturn preset(colors)\n}\n\nfunc RdBu() Gradient {\n\tcolors := []uint32{0x67001f, 0xb2182b, 0xd6604d, 0xf4a582, 0xfddbc7, 0xf7f7f7, 0xd1e5f0, 0x92c5de, 0x4393c3, 0x2166ac, 0x053061}\n\treturn preset(colors)\n}\n\nfunc RdGy() Gradient {\n\tcolors := []uint32{0x67001f, 0xb2182b, 0xd6604d, 0xf4a582, 0xfddbc7, 0xffffff, 0xe0e0e0, 0xbababa, 0x878787, 0x4d4d4d, 0x1a1a1a}\n\treturn preset(colors)\n}\n\nfunc RdYlBu() Gradient {\n\tcolors := []uint32{0xa50026, 0xd73027, 0xf46d43, 0xfdae61, 0xfee090, 0xffffbf, 0xe0f3f8, 0xabd9e9, 0x74add1, 0x4575b4, 0x313695}\n\treturn preset(colors)\n}\n\nfunc RdYlGn() Gradient {\n\tcolors := []uint32{0xa50026, 0xd73027, 0xf46d43, 0xfdae61, 0xfee08b, 0xffffbf, 0xd9ef8b, 0xa6d96a, 0x66bd63, 0x1a9850, 0x006837}\n\treturn preset(colors)\n}\n\nfunc Spectral() Gradient {\n\tcolors := []uint32{0x9e0142, 0xd53e4f, 0xf46d43, 0xfdae61, 0xfee08b, 0xffffbf, 0xe6f598, 0xabdda4, 0x66c2a5, 0x3288bd, 0x5e4fa2}\n\treturn preset(colors)\n}\n\n// Sequential (Single Hue)\n\nfunc Blues() Gradient {\n\tcolors := []uint32{0xf7fbff, 0xdeebf7, 0xc6dbef, 0x9ecae1, 0x6baed6, 0x4292c6, 0x2171b5, 0x08519c, 0x08306b}\n\treturn preset(colors)\n}\n\nfunc Greens() Gradient {\n\tcolors := []uint32{0xf7fcf5, 0xe5f5e0, 0xc7e9c0, 0xa1d99b, 0x74c476, 0x41ab5d, 0x238b45, 0x006d2c, 0x00441b}\n\treturn preset(colors)\n}\n\nfunc Greys() Gradient {\n\tcolors := []uint32{0xffffff, 0xf0f0f0, 0xd9d9d9, 0xbdbdbd, 0x969696, 0x737373, 0x525252, 0x252525, 0x000000}\n\treturn preset(colors)\n}\n\nfunc Oranges() Gradient {\n\tcolors := []uint32{0xfff5eb, 0xfee6ce, 0xfdd0a2, 0xfdae6b, 0xfd8d3c, 0xf16913, 0xd94801, 0xa63603, 0x7f2704}\n\treturn preset(colors)\n}\n\nfunc Purples() Gradient {\n\tcolors := []uint32{0xfcfbfd, 0xefedf5, 0xdadaeb, 0xbcbddc, 0x9e9ac8, 0x807dba, 0x6a51a3, 0x54278f, 0x3f007d}\n\treturn preset(colors)\n}\n\nfunc Reds() Gradient {\n\tcolors := []uint32{0xfff5f0, 0xfee0d2, 0xfcbba1, 0xfc9272, 0xfb6a4a, 0xef3b2c, 0xcb181d, 0xa50f15, 0x67000d}\n\treturn preset(colors)\n}\n\n// Sequential (Multi-Hue)\n\nfunc Viridis() Gradient {\n\tcolors := []uint32{0x440154, 0x482777, 0x3f4a8a, 0x31678e, 0x26838f, 0x1f9d8a, 0x6cce5a, 0xb6de2b, 0xfee825}\n\treturn preset(colors)\n}\n\nfunc Inferno() Gradient {\n\tcolors := []uint32{0x000004, 0x170b3a, 0x420a68, 0x6b176e, 0x932667, 0xbb3654, 0xdd513a, 0xf3771a, 0xfca50a, 0xf6d644, 0xfcffa4}\n\treturn preset(colors)\n}\n\nfunc Magma() Gradient {\n\tcolors := []uint32{0x000004, 0x140e37, 0x3b0f70, 0x641a80, 0x8c2981, 0xb63679, 0xde4968, 0xf66f5c, 0xfe9f6d, 0xfece91, 0xfcfdbf}\n\treturn preset(colors)\n}\n\nfunc Plasma() Gradient {\n\tcolors := []uint32{0x0d0887, 0x42039d, 0x6a00a8, 0x900da3, 0xb12a90, 0xcb4678, 0xe16462, 0xf1834b, 0xfca636, 0xfccd25, 0xf0f921}\n\treturn preset(colors)\n}\n\nfunc BuGn() Gradient {\n\tcolors := []uint32{0xf7fcfd, 0xe5f5f9, 0xccece6, 0x99d8c9, 0x66c2a4, 0x41ae76, 0x238b45, 0x006d2c, 0x00441b}\n\treturn preset(colors)\n}\n\nfunc BuPu() Gradient {\n\tcolors := []uint32{0xf7fcfd, 0xe0ecf4, 0xbfd3e6, 0x9ebcda, 0x8c96c6, 0x8c6bb1, 0x88419d, 0x810f7c, 0x4d004b}\n\treturn preset(colors)\n}\n\nfunc GnBu() Gradient {\n\tcolors := []uint32{0xf7fcf0, 0xe0f3db, 0xccebc5, 0xa8ddb5, 0x7bccc4, 0x4eb3d3, 0x2b8cbe, 0x0868ac, 0x084081}\n\treturn preset(colors)\n}\n\nfunc OrRd() Gradient {\n\tcolors := []uint32{0xfff7ec, 0xfee8c8, 0xfdd49e, 0xfdbb84, 0xfc8d59, 0xef6548, 0xd7301f, 0xb30000, 0x7f0000}\n\treturn preset(colors)\n}\n\nfunc PuBuGn() Gradient {\n\tcolors := []uint32{0xfff7fb, 0xece2f0, 0xd0d1e6, 0xa6bddb, 0x67a9cf, 0x3690c0, 0x02818a, 0x016c59, 0x014636}\n\treturn preset(colors)\n}\n\nfunc PuBu() Gradient {\n\tcolors := []uint32{0xfff7fb, 0xece7f2, 0xd0d1e6, 0xa6bddb, 0x74a9cf, 0x3690c0, 0x0570b0, 0x045a8d, 0x023858}\n\treturn preset(colors)\n}\n\nfunc PuRd() Gradient {\n\tcolors := []uint32{0xf7f4f9, 0xe7e1ef, 0xd4b9da, 0xc994c7, 0xdf65b0, 0xe7298a, 0xce1256, 0x980043, 0x67001f}\n\treturn preset(colors)\n}\n\nfunc RdPu() Gradient {\n\tcolors := []uint32{0xfff7f3, 0xfde0dd, 0xfcc5c0, 0xfa9fb5, 0xf768a1, 0xdd3497, 0xae017e, 0x7a0177, 0x49006a}\n\treturn preset(colors)\n}\n\nfunc YlGnBu() Gradient {\n\tcolors := []uint32{0xffffd9, 0xedf8b1, 0xc7e9b4, 0x7fcdbb, 0x41b6c4, 0x1d91c0, 0x225ea8, 0x253494, 0x081d58}\n\treturn preset(colors)\n}\n\nfunc YlGn() Gradient {\n\tcolors := []uint32{0xffffe5, 0xf7fcb9, 0xd9f0a3, 0xaddd8e, 0x78c679, 0x41ab5d, 0x238443, 0x006837, 0x004529}\n\treturn preset(colors)\n}\n\nfunc YlOrBr() Gradient {\n\tcolors := []uint32{0xffffe5, 0xfff7bc, 0xfee391, 0xfec44f, 0xfe9929, 0xec7014, 0xcc4c02, 0x993404, 0x662506}\n\treturn preset(colors)\n}\n\nfunc YlOrRd() Gradient {\n\tcolors := []uint32{0xffffcc, 0xffeda0, 0xfed976, 0xfeb24c, 0xfd8d3c, 0xfc4e2a, 0xe31a1c, 0xbd0026, 0x800026}\n\treturn preset(colors)\n}\n"
  },
  {
    "path": "preset_test.go",
    "content": "package colorgrad\n\nimport (\n\t\"testing\"\n)\n\nfunc Test_PresetGradients(t *testing.T) {\n\tvar grad Gradient\n\n\tgrad = CubehelixDefault()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#000000\", \"#19122c\", \"#1b354c\", \"#2c5c48\", \"#3f7533\", \"#7e7a36\", \"#bc7967\", \"#d486b0\", \"#cba8e6\", \"#c1d2f3\", \"#ddf0ef\", \"#ffffff\",\n\t})\n\n\tgrad = Warm()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#6e40aa\", \"#923db3\", \"#b83cb0\", \"#da3fa3\", \"#f6478d\", \"#ff5572\", \"#ff6956\", \"#ff823e\", \"#f59f30\", \"#ddbd30\", \"#c4d93e\", \"#aff05b\",\n\t})\n\n\tgrad = Cool()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#6e40aa\", \"#6252c5\", \"#5069d9\", \"#3c84e1\", \"#42a0dd\", \"#49bbcd\", \"#51d3b5\", \"#5ae597\", \"#65f17a\", \"#71f663\", \"#83f557\", \"#aff05b\",\n\t})\n\n\tgrad = Rainbow()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#6e40aa\", \"#b83cb0\", \"#f6478d\", \"#ff6956\", \"#f59f30\", \"#c4d93e\", \"#83f557\", \"#65f17a\", \"#51d3b5\", \"#42a0dd\", \"#5069d9\", \"#6e40aa\",\n\t})\n\n\tgrad = Cividis()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#002051\", \"#083069\", \"#24416e\", \"#44516d\", \"#5f626e\", \"#757372\", \"#898477\", \"#9d9778\", \"#b4aa73\", \"#d0be67\", \"#ecd354\", \"#fdea45\",\n\t})\n\n\tgrad = Sinebow()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#ff4040\", \"#eb860e\", \"#b4c901\", \"#6df61b\", \"#2cfd56\", \"#05dc9e\", \"#059edc\", \"#2c56fd\", \"#6d1bf6\", \"#b401c9\", \"#eb0e86\", \"#ff4040\",\n\t})\n\n\tgrad = Turbo()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#23171b\", \"#4a51d4\", \"#3491f8\", \"#25c9d5\", \"#3aef9a\", \"#71fe65\", \"#b8f140\", \"#f2cb2c\", \"#ff9220\", \"#ed5215\", \"#b41d07\", \"#900c00\",\n\t})\n\n\tgrad = Viridis()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#440154\", \"#461c6c\", \"#43377f\", \"#3c4f89\", \"#33648d\", \"#2a798e\", \"#248d8d\", \"#31a480\", \"#5ec263\", \"#94d641\", \"#cae02c\", \"#fee825\",\n\t})\n\n\tgrad = Plasma()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#0d0887\", \"#3c049a\", \"#6302a5\", \"#850ba3\", \"#a52097\", \"#bf3983\", \"#d5546e\", \"#e76f5a\", \"#f48d45\", \"#fbad33\", \"#f9d226\", \"#f0f921\",\n\t})\n\n\tgrad = Magma()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#000004\", \"#150b33\", \"#341062\", \"#59177b\", \"#7e2380\", \"#a3307c\", \"#c83f71\", \"#e65864\", \"#f77d63\", \"#fda775\", \"#fed296\", \"#fcfdbf\",\n\t})\n\n\tgrad = Inferno()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#000004\", \"#170834\", \"#3a0b5c\", \"#60146b\", \"#842169\", \"#a92f5c\", \"#ca4348\", \"#e45f2e\", \"#f58417\", \"#faae1b\", \"#f8d951\", \"#fcffa4\",\n\t})\n\n\tgrad = BrBG()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#543005\", \"#86500d\", \"#b47a2b\", \"#d6af67\", \"#edd9a9\", \"#f4eedc\", \"#deefec\", \"#acded7\", \"#6bbdb2\", \"#2e8f86\", \"#07635a\", \"#003c30\",\n\t})\n\n\tgrad = PRGn()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#40004b\", \"#6f2a7c\", \"#9362a3\", \"#b796c4\", \"#d9c2de\", \"#eee6ef\", \"#e8f2e5\", \"#c5e8c0\", \"#90cd8e\", \"#50a35b\", \"#1d7436\", \"#00441b\",\n\t})\n\n\tgrad = PiYG()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#8e0152\", \"#bc217a\", \"#d964a5\", \"#eba3cd\", \"#f8d0e7\", \"#f9ecf2\", \"#eff5e3\", \"#d4edb4\", \"#a8d674\", \"#77b43f\", \"#4b8d23\", \"#276419\",\n\t})\n\n\tgrad = PuOr()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#2d004b\", \"#51287f\", \"#7963a6\", \"#a49bc7\", \"#cac8e1\", \"#e8e8ef\", \"#f9ebd7\", \"#fdd197\", \"#f3a84e\", \"#d67b17\", \"#ad5708\", \"#7f3b08\",\n\t})\n\n\tgrad = RdBu()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#67001f\", \"#a61c2d\", \"#cf5349\", \"#ea9175\", \"#f9c6ad\", \"#f9e9df\", \"#e4edf2\", \"#b9d9e9\", \"#7cb6d6\", \"#418bbf\", \"#1f609f\", \"#053061\",\n\t})\n\n\tgrad = RdGy()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#67001f\", \"#a61c2d\", \"#cf5349\", \"#ea9175\", \"#f9c6ad\", \"#fdede3\", \"#f0efee\", \"#d2d2d2\", \"#ababab\", \"#7c7c7c\", \"#494949\", \"#1a1a1a\",\n\t})\n\n\tgrad = RdYlBu()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#a50026\", \"#d02d2a\", \"#ed623e\", \"#fa9b5a\", \"#fecd7f\", \"#feefaa\", \"#f0f8d8\", \"#cce9ef\", \"#9ccce2\", \"#6ca2cb\", \"#476eb1\", \"#313695\",\n\t})\n\n\tgrad = RdYlGn()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#a50026\", \"#d02d2a\", \"#ed623e\", \"#fa9b5a\", \"#fecd7c\", \"#fdefa5\", \"#ecf6a5\", \"#c6e780\", \"#94d16a\", \"#57b55e\", \"#1e924d\", \"#006837\",\n\t})\n\n\tgrad = Spectral()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#9e0142\", \"#cd374b\", \"#ec6649\", \"#fa9b5a\", \"#fecd7c\", \"#fef0a5\", \"#f2f9ac\", \"#cfec9e\", \"#98d5a4\", \"#5eb5ab\", \"#4283b4\", \"#5e4fa2\",\n\t})\n\n\tgrad = Blues()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#f7fbff\", \"#e5eff9\", \"#d3e4f3\", \"#bdd8ec\", \"#a0cae3\", \"#7eb8da\", \"#5da4d0\", \"#408ec4\", \"#2877b7\", \"#1460a7\", \"#0a488d\", \"#08306b\",\n\t})\n\n\tgrad = Greens()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#f7fcf5\", \"#e9f7e5\", \"#d7efd1\", \"#bfe6b9\", \"#a4da9e\", \"#84cb84\", \"#61bb6d\", \"#41a75b\", \"#289149\", \"#117a38\", \"#026128\", \"#00441b\",\n\t})\n\n\tgrad = Greys()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#ffffff\", \"#f4f4f4\", \"#e5e5e5\", \"#d3d3d3\", \"#bebebe\", \"#a4a4a4\", \"#898989\", \"#707070\", \"#575757\", \"#393939\", \"#1b1b1b\", \"#000000\",\n\t})\n\n\tgrad = Oranges()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#fff5eb\", \"#feead5\", \"#fedcb9\", \"#fdc997\", \"#fdb171\", \"#fc994d\", \"#f8802e\", \"#ed6614\", \"#db4f06\", \"#bd3e02\", \"#9c3203\", \"#7f2704\",\n\t})\n\n\tgrad = Purples()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#fcfbfd\", \"#f2f0f7\", \"#e5e4f0\", \"#d4d4e8\", \"#bfbfdd\", \"#a9a7cf\", \"#9390c3\", \"#7f77b7\", \"#6e59a7\", \"#5e3a98\", \"#4e1d8a\", \"#3f007d\",\n\t})\n\n\tgrad = Reds()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#fff5f0\", \"#fee5d9\", \"#fdcfbb\", \"#fcb399\", \"#fc9677\", \"#fb7859\", \"#f65940\", \"#e9392d\", \"#d12120\", \"#b61319\", \"#930b13\", \"#67000d\",\n\t})\n\n\tgrad = BuGn()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#f7fcfd\", \"#e9f7f9\", \"#d9f1f0\", \"#c0e7e0\", \"#9edacb\", \"#79cab1\", \"#59bb93\", \"#3fa971\", \"#289250\", \"#117a38\", \"#026128\", \"#00441b\",\n\t})\n\n\tgrad = BuPu()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#f7fcfd\", \"#e6f0f6\", \"#d1e0ee\", \"#b9cfe4\", \"#a3bcda\", \"#93a3cd\", \"#8d86be\", \"#8b67af\", \"#88489f\", \"#83278a\", \"#700d6e\", \"#4d004b\",\n\t})\n\n\tgrad = GnBu()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#f7fcf0\", \"#e6f6e1\", \"#d7efd1\", \"#c4e8c3\", \"#aadeba\", \"#8bd2bf\", \"#6bc2c9\", \"#4caecd\", \"#3193c2\", \"#1978b4\", \"#0a5d9f\", \"#084081\",\n\t})\n\n\tgrad = OrRd()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#fff7ec\", \"#feecd1\", \"#fedfb5\", \"#fdcf9b\", \"#fdbb84\", \"#fc9e6a\", \"#f77f54\", \"#eb5f41\", \"#da3a27\", \"#c3170f\", \"#a40302\", \"#7f0000\",\n\t})\n\n\tgrad = PuBuGn()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#fff7fb\", \"#f1e8f3\", \"#dfdaeb\", \"#c7cde4\", \"#a7bfdc\", \"#7eb0d3\", \"#56a0c9\", \"#3190b6\", \"#108394\", \"#027570\", \"#016150\", \"#014636\",\n\t})\n\n\tgrad = PuBu()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#fff7fb\", \"#f1ebf4\", \"#dfddec\", \"#c7cee4\", \"#a9bfdc\", \"#86b0d3\", \"#5da0c9\", \"#338cbe\", \"#1277b1\", \"#05649c\", \"#03507d\", \"#023858\",\n\t})\n\n\tgrad = PuRd()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#f7f4f9\", \"#ebe5f1\", \"#decee5\", \"#d3b3d7\", \"#ce96c8\", \"#d775b8\", \"#e14fa1\", \"#e12c84\", \"#d01762\", \"#b0094c\", \"#8b0138\", \"#67001f\",\n\t})\n\n\tgrad = RdPu()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#fff7f3\", \"#fee6e3\", \"#fdd3d0\", \"#fcbdc0\", \"#faa0b5\", \"#f77ca9\", \"#ec559e\", \"#d62f93\", \"#b60f84\", \"#92027a\", \"#6d0173\", \"#49006a\",\n\t})\n\n\tgrad = YlGnBu()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#ffffd9\", \"#f1f9bf\", \"#dbf1b4\", \"#b7e3b6\", \"#87d0bb\", \"#59bec0\", \"#35a8c2\", \"#238bbb\", \"#2168ad\", \"#23489c\", \"#1b2f81\", \"#081d58\",\n\t})\n\n\tgrad = YlGn()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#ffffe5\", \"#f8fcc6\", \"#e9f6b0\", \"#d0ec9f\", \"#b0de90\", \"#8bce80\", \"#64bc6f\", \"#41a65b\", \"#288c49\", \"#11753d\", \"#025e33\", \"#004529\",\n\t})\n\n\tgrad = YlOrBr()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#ffffe5\", \"#fff8c7\", \"#ffeda8\", \"#fedc83\", \"#fec559\", \"#fda938\", \"#f78a22\", \"#e76d13\", \"#d05407\", \"#b03f03\", \"#8b3005\", \"#662506\",\n\t})\n\n\tgrad = YlOrRd()\n\ttestSlice(t, colors2hex(grad.Colors(12)), []string{\n\t\t\"#ffffcc\", \"#fff2ac\", \"#ffe48d\", \"#fed06e\", \"#feb653\", \"#fd9942\", \"#fc7535\", \"#f74b29\", \"#e62621\", \"#cd0d22\", \"#ab0225\", \"#800026\",\n\t})\n\n\t// Cyclical gradients\n\n\tgrad = Rainbow()\n\ttest(t, grad.At(0).HexString(), grad.At(1).HexString())\n\n\tgrad = Sinebow()\n\ttest(t, grad.At(0).HexString(), grad.At(1).HexString())\n}\n"
  },
  {
    "path": "sharp.go",
    "content": "package colorgrad\n\nimport (\n\t\"math\"\n)\n\ntype sharpGradient struct {\n\tcolors    []Color\n\tpositions []float64\n\tlast      int\n\tmin       float64\n\tmax       float64\n}\n\nfunc (sg sharpGradient) At(t float64) Color {\n\tif t <= sg.min {\n\t\treturn sg.colors[0]\n\t}\n\n\tif t >= sg.max {\n\t\treturn sg.colors[sg.last]\n\t}\n\n\tif math.IsNaN(t) {\n\t\treturn Color{A: 1}\n\t}\n\n\tlow := 0\n\thigh := len(sg.positions)\n\n\tfor low < high {\n\t\tmid := (low + high) / 2\n\t\tif sg.positions[mid] < t {\n\t\t\tlow = mid + 1\n\t\t} else {\n\t\t\thigh = mid\n\t\t}\n\t}\n\n\tif low == 0 {\n\t\tlow = 1\n\t}\n\n\ti := low - 1\n\tp1 := sg.positions[i]\n\tp2 := sg.positions[low]\n\n\tif i%2 == 0 {\n\t\treturn sg.colors[i]\n\t}\n\n\tt = (t - p1) / (p2 - p1)\n\ta := sg.colors[i]\n\tb := sg.colors[low]\n\treturn blendRgb(a, b, t)\n}\n\nfunc newSharpGradient(colorsIn []Color, dmin, dmax float64, smoothness float64) Gradient {\n\tn := len(colorsIn)\n\tcolors := make([]Color, n*2)\n\ti := 0\n\tfor _, c := range colorsIn {\n\t\tcolors[i] = c\n\t\ti++\n\t\tcolors[i] = c\n\t\ti++\n\t}\n\tt := clamp01(smoothness) * (dmax - dmin) / float64(n) / 4\n\tp := linspace(dmin, dmax, uint(n+1))\n\tpositions := make([]float64, n*2)\n\ti = 0\n\tj := 0\n\tfor x := 0; x < int(n); x++ {\n\t\tpositions[i] = p[j]\n\t\tif i > 0 {\n\t\t\tpositions[i] += t\n\t\t}\n\t\ti++\n\t\tj++\n\t\tpositions[i] = p[j]\n\t\tif i < len(colors)-1 {\n\t\t\tpositions[i] -= t\n\t\t}\n\t\ti++\n\t}\n\tgradbase := sharpGradient{\n\t\tcolors:    colors,\n\t\tpositions: positions,\n\t\tlast:      int(n*2 - 1),\n\t\tmin:       dmin,\n\t\tmax:       dmax,\n\t}\n\treturn Gradient{\n\t\tCore: gradbase,\n\t\tMin:  dmin,\n\t\tMax:  dmax,\n\t}\n}\n"
  },
  {
    "path": "sharp_test.go",
    "content": "package colorgrad\n\nimport (\n\t\"math\"\n\t\"testing\"\n)\n\nfunc Test_SharpGradient(t *testing.T) {\n\tvar grad, gradBase Gradient\n\n\tgradBase, _ = NewGradient().\n\t\tHtmlColors(\"#f00\", \"#0f0\", \"#00f\").\n\t\tBuild()\n\n\t// Sharp(0)\n\tgrad = gradBase.Sharp(0, 0)\n\ttest(t, grad.At(0.0).HexString(), \"#ff0000\")\n\ttest(t, grad.At(0.5).HexString(), \"#ff0000\")\n\ttest(t, grad.At(1.0).HexString(), \"#ff0000\")\n\n\t// Sharp(1)\n\tgrad = gradBase.Sharp(1, 0)\n\ttest(t, grad.At(0.0).HexString(), \"#ff0000\")\n\ttest(t, grad.At(0.5).HexString(), \"#ff0000\")\n\ttest(t, grad.At(1.0).HexString(), \"#ff0000\")\n\n\t// Sharp(3)\n\tgrad = gradBase.Sharp(3, 0)\n\ttest(t, grad.At(0.0).HexString(), \"#ff0000\")\n\ttest(t, grad.At(0.2).HexString(), \"#ff0000\")\n\n\ttest(t, grad.At(0.4).HexString(), \"#00ff00\")\n\ttest(t, grad.At(0.5).HexString(), \"#00ff00\")\n\ttest(t, grad.At(0.6).HexString(), \"#00ff00\")\n\n\ttest(t, grad.At(0.9).HexString(), \"#0000ff\")\n\ttest(t, grad.At(1.0).HexString(), \"#0000ff\")\n\n\ttest(t, grad.At(-0.1).HexString(), \"#ff0000\")\n\ttest(t, grad.At(1.1).HexString(), \"#0000ff\")\n\ttest(t, grad.At(math.NaN()).HexString(), \"#000000\")\n\n\t// Sharp(2)\n\tgradBase, _ = NewGradient().\n\t\tHtmlColors(\"#f00\", \"#0f0\", \"#00f\").\n\t\tDomain(-1, 1).\n\t\tBuild()\n\n\tgrad = gradBase.Sharp(2, 0)\n\ttest(t, grad.At(-1.0).HexString(), \"#ff0000\")\n\ttest(t, grad.At(-0.5).HexString(), \"#ff0000\")\n\ttest(t, grad.At(-0.1).HexString(), \"#ff0000\")\n\n\ttest(t, grad.At(0.1).HexString(), \"#0000ff\")\n\ttest(t, grad.At(0.5).HexString(), \"#0000ff\")\n\ttest(t, grad.At(1.0).HexString(), \"#0000ff\")\n\n\t// Smoothness\n\tgradBase, _ = NewGradient().\n\t\tHtmlColors(\"#f00\", \"#0f0\", \"#00f\").\n\t\tBuild()\n\n\tgrad = gradBase.Sharp(0, 0.5)\n\ttest(t, grad.At(0.0).HexString(), \"#ff0000\")\n\ttest(t, grad.At(0.5).HexString(), \"#ff0000\")\n\ttest(t, grad.At(1.0).HexString(), \"#ff0000\")\n\n\tgrad = gradBase.Sharp(1, 0.5)\n\ttest(t, grad.At(0.0).HexString(), \"#ff0000\")\n\ttest(t, grad.At(0.5).HexString(), \"#ff0000\")\n\ttest(t, grad.At(1.0).HexString(), \"#ff0000\")\n\n\tgrad = gradBase.Sharp(3, 0.1)\n\n\ttest(t, grad.At(0).HexString(), \"#ff0000\")\n\ttest(t, grad.At(0.1).HexString(), \"#ff0000\")\n\n\ttest(t, grad.At(1.0/3).HexString(), \"#808000\")\n\n\ttest(t, grad.At(0.45).HexString(), \"#00ff00\")\n\ttest(t, grad.At(0.55).HexString(), \"#00ff00\")\n\n\ttest(t, grad.At(1.0/3*2).HexString(), \"#008080\")\n\n\ttest(t, grad.At(0.9).HexString(), \"#0000ff\")\n\ttest(t, grad.At(1).HexString(), \"#0000ff\")\n\n\ttest(t, grad.At(-0.01).HexString(), \"#ff0000\")\n\ttest(t, grad.At(1.01).HexString(), \"#0000ff\")\n\ttest(t, grad.At(math.NaN()).HexString(), \"#000000\")\n}\n"
  },
  {
    "path": "smoothstep.go",
    "content": "package colorgrad\n\nimport (\n\t\"math\"\n)\n\ntype smoothstepGradient struct {\n\tcolors    [][4]float64\n\tpositions []float64\n\tmin       float64\n\tmax       float64\n\tmode      BlendMode\n\tfirst     Color\n\tlast      Color\n}\n\nfunc (sg smoothstepGradient) At(t float64) Color {\n\tif t <= sg.min {\n\t\treturn sg.first\n\t}\n\n\tif t >= sg.max {\n\t\treturn sg.last\n\t}\n\n\tif math.IsNaN(t) {\n\t\treturn Color{A: 1}\n\t}\n\n\tlow := 0\n\thigh := len(sg.positions)\n\n\tfor low < high {\n\t\tmid := (low + high) / 2\n\t\tif sg.positions[mid] < t {\n\t\t\tlow = mid + 1\n\t\t} else {\n\t\t\thigh = mid\n\t\t}\n\t}\n\n\tif low == 0 {\n\t\tlow = 1\n\t}\n\n\tp1 := sg.positions[low-1]\n\tp2 := sg.positions[low]\n\tt = (t - p1) / (p2 - p1)\n\ta, b, c, d := smoothstepInterpolate(sg.colors[low-1], sg.colors[low], t)\n\n\tswitch sg.mode {\n\tcase BlendRgb:\n\t\treturn Color{R: a, G: b, B: c, A: d}\n\tcase BlendLinearRgb:\n\t\treturn LinearRgb(a, b, c, d)\n\tcase BlendLab:\n\t\treturn Lab(a, b, c, d).Clamp()\n\tcase BlendOklab:\n\t\treturn Oklab(a, b, c, d).Clamp()\n\t}\n\n\treturn Color{}\n}\n\nfunc newSmoothstepGradient(colors []Color, positions []float64, mode BlendMode) Gradient {\n\tgradbase := smoothstepGradient{\n\t\tcolors:    convertColors(colors, mode),\n\t\tpositions: positions,\n\t\tmin:       positions[0],\n\t\tmax:       positions[len(positions)-1],\n\t\tmode:      mode,\n\t\tfirst:     colors[0],\n\t\tlast:      colors[len(colors)-1],\n\t}\n\n\treturn Gradient{\n\t\tCore: gradbase,\n\t\tMin:  positions[0],\n\t\tMax:  positions[len(positions)-1],\n\t}\n}\n\nfunc smoothstepInterpolate(a, b [4]float64, t float64) (i, j, k, l float64) {\n\ti = (b[0]-a[0])*(3.0-t*2.0)*t*t + a[0]\n\tj = (b[1]-a[1])*(3.0-t*2.0)*t*t + a[1]\n\tk = (b[2]-a[2])*(3.0-t*2.0)*t*t + a[2]\n\tl = (b[3]-a[3])*(3.0-t*2.0)*t*t + a[3]\n\treturn\n}\n"
  },
  {
    "path": "util.go",
    "content": "package colorgrad\n\nimport (\n\t\"math\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nfunc linspace(min, max float64, n uint) []float64 {\n\tif n == 1 {\n\t\treturn []float64{min}\n\t}\n\td := max - min\n\tl := float64(n) - 1\n\tres := make([]float64, n)\n\tfor i := range res {\n\t\tres[i] = (min + (float64(i)*d)/l)\n\t}\n\treturn res\n}\n\n// Map t from range [a, b] to range [0, 1]\nfunc norm(t, a, b float64) float64 {\n\treturn (t - a) * (1 / (b - a))\n}\n\nfunc modulo(x, y float64) float64 {\n\treturn math.Mod(math.Mod(x, y)+y, y)\n}\n\nfunc clamp01(t float64) float64 {\n\treturn math.Max(0, math.Min(1, t))\n}\n\nfunc parseFloat(s string) (float64, bool) {\n\tf, err := strconv.ParseFloat(strings.TrimSpace(s), 64)\n\treturn f, err == nil\n}\n\nfunc toLinear(x float64) float64 {\n\tif x >= 0.04045 {\n\t\treturn math.Pow((x+0.055)/1.055, 2.4)\n\t}\n\treturn x / 12.92\n}\n\nfunc col2linearRgb(col Color) [4]float64 {\n\treturn [4]float64{\n\t\ttoLinear(col.R),\n\t\ttoLinear(col.G),\n\t\ttoLinear(col.B),\n\t\tcol.A,\n\t}\n}\n\nfunc col2oklab(col Color) [4]float64 {\n\tarr := col2linearRgb(col)\n\tl := math.Cbrt(0.4121656120*arr[0] + 0.5362752080*arr[1] + 0.0514575653*arr[2])\n\tm := math.Cbrt(0.2118591070*arr[0] + 0.6807189584*arr[1] + 0.1074065790*arr[2])\n\ts := math.Cbrt(0.0883097947*arr[0] + 0.2818474174*arr[1] + 0.6302613616*arr[2])\n\treturn [4]float64{\n\t\t0.2104542553*l + 0.7936177850*m - 0.0040720468*s,\n\t\t1.9779984951*l - 2.4285922050*m + 0.4505937099*s,\n\t\t0.0259040371*l + 0.7827717662*m - 0.8086757660*s,\n\t\tcol.A,\n\t}\n}\n\nfunc col2hsv(col Color) [4]float64 {\n\tv := math.Max(col.R, math.Max(col.G, col.B))\n\td := v - math.Min(col.R, math.Min(col.G, col.B))\n\n\tif math.Abs(d) < epsilon {\n\t\treturn [4]float64{0, 0, v, col.A}\n\t}\n\n\ts := d / v\n\tdr := (v - col.R) / d\n\tdg := (v - col.G) / d\n\tdb := (v - col.B) / d\n\n\tvar h float64\n\n\tif math.Abs(col.R-v) < epsilon {\n\t\th = db - dg\n\t} else if math.Abs(col.G-v) < epsilon {\n\t\th = 2.0 + dr - db\n\t} else {\n\t\th = 4.0 + dg - dr\n\t}\n\n\th = math.Mod(h*60.0, 360.0)\n\treturn [4]float64{normalizeAngle(h), s, v, col.A}\n}\n\nfunc normalizeAngle(t float64) float64 {\n\tt = math.Mod(t, 360.0)\n\tif t < 0.0 {\n\t\tt += 360.0\n\t}\n\treturn t\n}\n\nfunc convertColors(colorsIn []Color, mode BlendMode) [][4]float64 {\n\tcolors := make([][4]float64, len(colorsIn))\n\tfor i, col := range colorsIn {\n\t\tswitch mode {\n\t\tcase BlendRgb:\n\t\t\tcolors[i] = [4]float64{col.R, col.G, col.B, col.A}\n\t\tcase BlendLinearRgb:\n\t\t\tcolors[i] = col2linearRgb(col)\n\t\tcase BlendLab:\n\t\t\tcolors[i] = col2lab(col)\n\t\tcase BlendOklab:\n\t\t\tcolors[i] = col2oklab(col)\n\t\t}\n\t}\n\treturn colors\n}\n\nfunc linearInterpolate(a, b [4]float64, t float64) (i, j, k, l float64) {\n\ti = a[0] + t*(b[0]-a[0])\n\tj = a[1] + t*(b[1]-a[1])\n\tk = a[2] + t*(b[2]-a[2])\n\tl = a[3] + t*(b[3]-a[3])\n\treturn\n}\n\nfunc blendRgb(a, b Color, t float64) Color {\n\treturn Color{\n\t\tR: a.R + t*(b.R-a.R),\n\t\tG: a.G + t*(b.G-a.G),\n\t\tB: a.B + t*(b.B-a.B),\n\t\tA: a.A + t*(b.A-a.A),\n\t}\n}\n\n// --- Lab\n\nconst (\n\td65X = 0.95047\n\td65Y = 1.0\n\td65Z = 1.08883\n\n\tdelta  = 6.0 / 29.0\n\tdelta2 = delta * delta\n\tdelta3 = delta2 * delta\n)\n\nfunc linearRGBToXYZ(r, g, b float64) [3]float64 {\n\t// Inverse sRGB matrix (D65)\n\tx := 0.4124564*r + 0.3575761*g + 0.1804375*b\n\ty := 0.2126729*r + 0.7151522*g + 0.0721750*b\n\tz := 0.0193339*r + 0.1191920*g + 0.9503041*b\n\treturn [3]float64{x, y, z}\n}\n\nfunc xyzToLab(x, y, z float64) [3]float64 {\n\tlabF := func(t float64) float64 {\n\t\tif t > delta3 {\n\t\t\treturn math.Cbrt(t)\n\t\t}\n\t\treturn (t / (3.0 * delta2)) + (4.0 / 29.0)\n\t}\n\n\tfx := labF(x / d65X)\n\tfy := labF(y / d65Y)\n\tfz := labF(z / d65Z)\n\n\tl := 116.0*fy - 16.0\n\ta := 500.0 * (fx - fy)\n\tb := 200.0 * (fy - fz)\n\n\treturn [3]float64{l, a, b}\n}\n\nfunc col2lab(col Color) [4]float64 {\n\tc := col2linearRgb(col)\n\tx := linearRGBToXYZ(c[0], c[1], c[2])\n\tl := xyzToLab(x[0], x[1], x[2])\n\treturn [4]float64{l[0], l[1], l[2], col.A}\n}\n"
  },
  {
    "path": "util_test.go",
    "content": "package colorgrad\n\nimport (\n\t\"math\"\n\t\"testing\"\n\n\t\"github.com/mazznoer/csscolorparser\"\n)\n\nfunc Test_Utils(t *testing.T) {\n\t// linspace\n\n\ttest(t, len(linspace(0, 1, 0)), 0)\n\ttestTrue(t, linspace(0, 1, 1)[0] == 0.0)\n\ttestSlice(t, linspace(0, 1, 2), []float64{0, 1})\n\ttestSlice(t, linspace(0, 1, 3), []float64{0, 0.5, 1})\n\ttestSlice(t, linspace(0, 100, 3), []float64{0, 50, 100})\n\n\t// norm\n\n\ttest(t, norm(0.99, 0, 1), 0.99)\n\ttest(t, norm(12, 0, 100), 0.12)\n\ttest(t, norm(753, 0, 1000), 0.753)\n\n\t// modulo\n\n\ttest(t, modulo(2.73, 1), 0.73)\n\ttest(t, modulo(32, 25), 7.0)\n\n\t// clamp01\n\n\ttest(t, clamp01(0), 0.0)\n\ttest(t, clamp01(1), 1.0)\n\ttest(t, clamp01(0.997), 0.997)\n\ttest(t, clamp01(-0.51), 0.0)\n\ttest(t, clamp01(1.0001), 1.0)\n\n\t// parseFloat\n\n\tvalidData := []struct {\n\t\tstr string\n\t\tnum float64\n\t}{\n\t\t{\"0\", 0},\n\t\t{\"0.0\", 0},\n\t\t{\"1234\", 1234},\n\t\t{\"0.00027\", 0.00027},\n\t\t{\"-56.03\", -56.03},\n\t}\n\tfor _, dt := range validData {\n\t\tf, ok := parseFloat(dt.str)\n\t\ttestTrue(t, ok)\n\t\ttest(t, f, dt.num)\n\t}\n\n\tinvalidData := []string{\n\t\t\"\",\n\t\t\" \",\n\t\t\"25.0x\",\n\t\t\"1.0d7\",\n\t\t\"x10\",\n\t\t\"o\",\n\t}\n\tfor _, s := range invalidData {\n\t\t_, ok := parseFloat(s)\n\t\ttestTrue(t, !ok)\n\t}\n\n\t// convertColors\n\n\tcolors := []Color{\n\t\tRgb(1, 0.7, 0.1, 0.5),\n\t\t//Rgb8(10, 255, 125, 0), //\n\t\tLinearRgb(0.1, 0.9, 1, 1),\n\t\tHwb(0, 0, 0, 1),\n\t\tHwb(320, 0.1, 0.3, 1),\n\t\tHsv(120, 0.3, 0.2, 0.1),\n\t\tHsl(120, 0.3, 0.2, 1),\n\t}\n\n\tfor i, arr := range convertColors(colors, BlendRgb) {\n\t\tcol := Rgb(spreadF64(arr))\n\t\ttest(t, colors[i].HexString(), col.HexString())\n\t}\n\n\tfor i, arr := range convertColors(colors, BlendLinearRgb) {\n\t\tcol := LinearRgb(spreadF64(arr))\n\t\ttest(t, colors[i].HexString(), col.HexString())\n\t}\n\n\t/*for i, arr := range convertColors(colors, BlendOklab) {\n\t\tcol := Oklab(spreadF64(arr))\n\t\ttest(t, colors[i].HexString(), col.HexString())\n\t}*/\n\n\tfor _, c := range colors {\n\t\tcol, err := csscolorparser.Parse(c.HexString())\n\t\ttest(t, err, nil)\n\n\t\tx := Oklab(spreadF64(col2oklab(col)))\n\t\ttest(t, x.HexString(), c.HexString())\n\n\t\ty := LinearRgb(spreadF64(col2linearRgb(col)))\n\t\ttest(t, y.HexString(), c.HexString())\n\t}\n\n\thexColors := []string{\n\t\t\"#000000\",\n\t\t\"#ffffff\",\n\t\t\"#999999\",\n\t\t\"#135cdf\",\n\t\t\"#ff0000\",\n\t\t\"#00ff7f\",\n\t\t//\"#0aff7d\", //\n\t\t//\"#09ff7d\", //\n\t\t\"#abc5679b\",\n\t}\n\tfor _, s := range hexColors {\n\t\tcol, err := csscolorparser.Parse(s)\n\t\ttest(t, err, nil)\n\t\ttest(t, col.HexString(), s)\n\n\t\tx := Oklab(spreadF64(col2oklab(col)))\n\t\ttest(t, x.HexString(), s)\n\n\t\ty := LinearRgb(spreadF64(col2linearRgb(col)))\n\t\ttest(t, y.HexString(), s)\n\t}\n}\n\nfunc spreadF64(arr [4]float64) (a, b, c, d float64) {\n\ta = arr[0]\n\tb = arr[1]\n\tc = arr[2]\n\td = arr[3]\n\treturn\n}\n\nfunc Test_Lab(t *testing.T) {\n\ttestData := []string{\n\t\t\"#000000\",\n\t\t\"#ffffff\",\n\t\t\"#ff0000\",\n\t\t\"#123abc\",\n\t\t\"#bad455\",\n\t}\n\tfor _, s := range testData {\n\t\tc, err := csscolorparser.Parse(s)\n\t\ttest(t, err, nil)\n\t\tlab := col2lab(c)\n\t\tcc := Lab(lab[0], lab[1], lab[2], lab[3])\n\t\ttest(t, s, cc.HexString())\n\t}\n}\n\n// --- Helper functions\n\nfunc test(t *testing.T, a, b any) {\n\tif a != b {\n\t\tt.Helper()\n\t\tt.Errorf(\"left: %v, right: %v\", a, b)\n\t}\n}\n\nfunc testTrue(t *testing.T, b bool) {\n\tif !b {\n\t\tt.Helper()\n\t\tt.Errorf(\"it false\")\n\t}\n}\n\nfunc testSlice[S ~[]E, E comparable](t *testing.T, a, b S) {\n\tif len(a) != len(b) {\n\t\tt.Helper()\n\t\tt.Errorf(\"different length -> left: %v, right: %v\", len(a), len(b))\n\t\treturn\n\t}\n\tfor i, val := range a {\n\t\tif val != b[i] {\n\t\t\tt.Helper()\n\t\t\tt.Errorf(\"diff at index: %v, left: %v, right: %v\", i, val, b[i])\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc testSliceF(t *testing.T, a, b []float64) {\n\tif len(a) != len(b) {\n\t\tt.Helper()\n\t\tt.Errorf(\"different length -> left: %v, right: %v\", len(a), len(b))\n\t\treturn\n\t}\n\tepsilon := math.Nextafter(1.0, 2.0) - 1.0\n\tfor i, val := range a {\n\t\tif math.Abs(val-b[i]) > epsilon {\n\t\t\tt.Helper()\n\t\t\tt.Errorf(\"diff at index: %v, left: %v, right: %v\", i, val, b[i])\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc colors2hex(colors []Color) []string {\n\thexColors := make([]string, len(colors))\n\tfor i, c := range colors {\n\t\thexColors[i] = c.HexString()\n\t}\n\treturn hexColors\n}\n\nfunc isZeroGradient(grad Gradient) bool {\n\tfor _, col := range grad.Colors(13) {\n\t\tif col.HexString() != \"#00000000\" {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n"
  }
]