Skip to content

Commit

Permalink
Refactor
Browse files Browse the repository at this point in the history
- Remove SharpGradient(), IntoColors()
- Add new methods for Gradient:
    + ColorfulColors()
    + Colors() -- now it returns []color.Color instead of []colorful.Color
    + Domain()
    + Sharp()
- Refactoring
  • Loading branch information
mazznoer committed Aug 9, 2020
1 parent abd9fc8 commit 8a9f890
Show file tree
Hide file tree
Showing 10 changed files with 423 additions and 348 deletions.
26 changes: 16 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,17 @@ if err != nil {
panic(err)
}

dmin, dmax := grad.Domain()

// Get single color at certain position.
grad.At(0) // colorful.Color
grad.At(0.5).Hex() // hex color string
grad.At(1)
// t in the range dmin..dmax (default to 0..1)
c1 := grad.At(t) // colorful.Color
c2 := grad.At(t).Hex() // hex color string
var c3 color.Color = grad.At(t) // color.Color

// Get n colors evenly spaced across gradient.
grad.Colors(27) // []colorful.Color
colorgrad.IntoColors(grad.Colors(10)) // []color.Color
colors1 := grad.ColorfulColors(9) // []colorful.Color
colors2 := grad.Colors(23) // []color.Color
```
![img](img/black-to-white.png)

Expand Down Expand Up @@ -163,18 +166,21 @@ grad1, err := colorgrad.NewGradient().
![img](img/normal-gradient.png)

```go
grad2 := colorgrad.SharpGradient(grad1, 7)
grad2 := grad1.Sharp(7)

dmin, dmax := grad2.Domain() // 0, 100 -- same as original gradient
```
![img](img/classes-gradient.png)

Note: Result gradient from `SharpGradient()` will always in the range `0..1`.

### Preset Gradients

```go
grad := colorgrad.Rainbow()
grad.At(t) // t in the range 0..1
grad.Colors(15)

c := grad.At(t) // t in the range 0..1
colors1 := grad.ColorfulColors(5)
colors2 := grad.Colors(17)
grad2 := grad.Sharp(13)
```

#### Diverging
Expand Down
301 changes: 149 additions & 152 deletions colornames.go

Large diffs are not rendered by default.

33 changes: 33 additions & 0 deletions colornames_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package colorgrad

import (
"testing"

"github.com/lucasb-eyer/go-colorful"
)

func TestColornames(t *testing.T) {
for name, want := range testCases {
got, ok := colornames[name]
if !ok {
t.Errorf("Did not find %s", name)
continue
}
if got != want {
t.Errorf("%s:\ngot %v\nwant %v", name, got, want)
}
}
}

var testCases = map[string]colorful.Color{
"aqua": {R: 0, G: 1, B: 1},
"black": {R: 0, G: 0, B: 0},
"blue": {R: 0, G: 0, B: 1},
"cyan": {R: 0, G: 1, B: 1},
"fuchsia": {R: 1, G: 0, B: 1},
"lime": {R: 0, G: 1, B: 0},
"magenta": {R: 1, G: 0, B: 1},
"red": {R: 1, G: 0, B: 0},
"white": {R: 1, G: 1, B: 1},
"yellow": {R: 1, G: 1, B: 0},
}
4 changes: 2 additions & 2 deletions examples/basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (

func main() {
grad, _ := colorgrad.NewGradient().Build()
w := 800
h := 80
w := 900
h := 95
fw := float64(w)

img := image.NewRGBA(image.Rect(0, 0, w, h))
Expand Down
143 changes: 121 additions & 22 deletions grad.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,80 @@ const (
RGB
)

type gradientBase interface {
// Get color at certain position
At(float64) colorful.Color
}

type Gradient interface {
// Get color at certain position
At(float64) colorful.Color

// Get n colors evenly spaced across gradient
Colors(uint) []colorful.Color
ColorfulColors(uint) []colorful.Color

// Get n colors evenly spaced across gradient
Colors(uint) []color.Color

// Get the gradient domain min and max
Domain() (float64, float64)

// Return a new hard-edge gradient
Sharp(uint) Gradient
}

type gradient struct {
grad gradientBase
min float64
max float64
}

func (g gradient) At(t float64) colorful.Color {
return g.grad.At(t)
}

func (g gradient) ColorfulColors(count uint) []colorful.Color {
d := g.max - g.min
l := float64(count - 1)
colors := make([]colorful.Color, count)
for i := range colors {
colors[i] = g.grad.At(g.min + (float64(i)*d)/l)
}
return colors
}

func (g gradient) Colors(count uint) []color.Color {
colors := make([]color.Color, count)
for i, col := range g.ColorfulColors(count) {
colors[i] = col
}
return colors
}

func (g gradient) Domain() (float64, float64) {
return g.min, g.max
}

func (g gradient) Sharp(n uint) Gradient {
gradbase := sharpGradient{
colors: g.ColorfulColors(n),
pos: linspace(g.min, g.max, n+1),
n: int(n),
min: g.min,
max: g.max,
}
return gradient{
grad: gradbase,
min: g.min,
max: g.max,
}
}

type GradientBuilder struct {
colors []colorful.Color
pos []float64
mode BlendMode
colors []colorful.Color
pos []float64
mode BlendMode
invalidHtmlColors []string
}

func NewGradient() *GradientBuilder {
Expand All @@ -42,7 +104,6 @@ func NewGradient() *GradientBuilder {

func (gb *GradientBuilder) Colors(colors ...color.Color) *GradientBuilder {
gb.colors = make([]colorful.Color, len(colors))

for i, v := range colors {
c, _ := colorful.MakeColor(v)
gb.colors[i] = c
Expand All @@ -52,22 +113,28 @@ func (gb *GradientBuilder) Colors(colors ...color.Color) *GradientBuilder {

func (gb *GradientBuilder) HtmlColors(htmlColors ...string) *GradientBuilder {
colors := []colorful.Color{}
invalidColors := []string{}

for _, v := range htmlColors {
var col colorful.Color
c1, ok := colornames[strings.ToLower(v)]
c, ok := colornames[strings.ToLower(v)]
if ok {
c, _ := colorful.MakeColor(c1)
col = c
} else {
c, err := colorful.Hex(v)
if err != nil {
invalidColors = append(invalidColors, v)
continue
}
col = c
}
colors = append(colors, col)
}

if len(invalidColors) > 0 {
gb.invalidHtmlColors = invalidColors
}

gb.colors = colors
return gb
}
Expand All @@ -83,6 +150,10 @@ func (gb *GradientBuilder) Mode(mode BlendMode) *GradientBuilder {
}

func (gb *GradientBuilder) Build() (Gradient, error) {
if gb.invalidHtmlColors != nil {
return nil, fmt.Errorf("Invalid HTML colors: %v", gb.invalidHtmlColors)
}

if len(gb.colors) == 0 {
// Default colors
gb.colors = []colorful.Color{
Expand All @@ -94,7 +165,7 @@ func (gb *GradientBuilder) Build() (Gradient, error) {
}

if len(gb.pos) > 0 && len(gb.pos) != len(gb.colors) {
return nil, fmt.Errorf("Domain count not equal colors count")
return nil, fmt.Errorf("Domain's count (if domain is specified) must equal colors' count")
}

if len(gb.pos) == 0 {
Expand All @@ -108,19 +179,23 @@ func (gb *GradientBuilder) Build() (Gradient, error) {

for i := 0; i < len(gb.pos)-1; i++ {
if gb.pos[i] > gb.pos[i+1] {
return nil, fmt.Errorf("Domain is wrong")
return nil, fmt.Errorf("Domain number %v (%v) is bigger than the next domain (%v)", i, gb.pos[i], gb.pos[i+1])
}
}

//fmt.Printf("Pos: %v Colors: %v Mode: %v\n", gb.pos, gb.colors, gb.mode)

return gradientX{
gradbase := gradientX{
colors: gb.colors,
pos: gb.pos,
min: gb.pos[0],
max: gb.pos[len(gb.pos)-1],
count: len(gb.colors),
mode: gb.mode,
}

return gradient{
grad: gradbase,
min: gb.pos[0],
max: gb.pos[len(gb.pos)-1],
}, nil
}

Expand All @@ -138,9 +213,9 @@ func (gx gradientX) At(t float64) colorful.Color {
return gx.colors[0]
}

if t > gx.max {
return gx.colors[gx.count-1]
}
//if t > gx.max {
// return gx.colors[gx.count-1]
//}

for i := 0; i < gx.count-1; i++ {
p1 := gx.pos[i]
Expand Down Expand Up @@ -170,15 +245,39 @@ func (gx gradientX) At(t float64) colorful.Color {
return gx.colors[gx.count-1]
}

func (gx gradientX) Colors(count uint) []colorful.Color {
d := gx.max - gx.min
l := float64(count - 1)
colors := make([]colorful.Color, count)
type sharpGradient struct {
colors []colorful.Color
pos []float64
n int
min float64
max float64
}

for i := range colors {
colors[i] = gx.At(gx.min + (float64(i)*d)/l)
func (sg sharpGradient) At(t float64) colorful.Color {
if math.IsNaN(t) || t < sg.min {
return sg.colors[0]
}
return colors

//if t > sg.max {
// return sg.colors[sg.n-1]
//}

for i := 0; i < sg.n; i++ {
if (sg.pos[i] <= t) && (t <= sg.pos[i+1]) {
return sg.colors[i]
}
}
return sg.colors[sg.n-1]
}

func linspace(min, max float64, n uint) []float64 {
d := max - min
l := float64(n - 1)
res := make([]float64, n)
for i := range res {
res[i] = (min + (float64(i)*d)/l)
}
return res
}

// Algorithm taken from: https://github.com/gka/chroma.js
Expand Down
Loading

0 comments on commit 8a9f890

Please sign in to comment.