├── .gitignore ├── anim └── anim.go ├── base └── base.go ├── blmath ├── math.go └── math_test.go ├── color ├── color.go └── color_test.go ├── drawing.go ├── easing └── easing.go ├── floodfill ├── coord.go ├── floodfill.go └── rgb.go ├── geom ├── circle.go ├── geom.go ├── geom_test.go ├── point.go └── rectangle.go ├── go.mod ├── go.sum ├── logdisplay ├── logdisplay.go └── logdisplayfull.go ├── makefile ├── noise ├── perlin.go └── simplex.go ├── p3d ├── point3d.go └── point3dlist.go ├── part ├── particle.go └── particlesystem.go ├── random ├── random.go └── random_test.go ├── readme.md ├── surface.go └── util ├── util.go └── util_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | test 2 | *.png 3 | -------------------------------------------------------------------------------- /anim/anim.go: -------------------------------------------------------------------------------- 1 | package anim 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/bit101/blgo" 7 | ) 8 | 9 | // RenderCallback signature for the function that will be called each frame. 10 | type RenderCallback func(float64) 11 | 12 | // Animation is a animated gif maker. 13 | type Animation struct { 14 | Surface *blgo.Surface 15 | FrameCount int 16 | FramesDir string 17 | } 18 | 19 | // NewAnimation creates a new animated gif project. 20 | func NewAnimation(surface *blgo.Surface, frameCount int, framesDir string) *Animation { 21 | return &Animation{ 22 | Surface: surface, 23 | FrameCount: frameCount, 24 | FramesDir: framesDir, 25 | } 26 | } 27 | 28 | // Render renders the gif 29 | func (p *Animation) Render(renderCallback RenderCallback) { 30 | for i := 0; i < p.FrameCount; i++ { 31 | percent := float64(i) / float64(p.FrameCount) 32 | fmt.Printf("\r%f", percent) 33 | renderCallback(percent) 34 | filename := fmt.Sprintf("%s/frame_%0.4d.png", p.FramesDir, i) 35 | p.Surface.WriteToPNG(filename) 36 | } 37 | fmt.Println() 38 | } 39 | -------------------------------------------------------------------------------- /base/base.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "github.com/bit101/blgo" 5 | "github.com/bit101/blgo/anim" 6 | "github.com/bit101/blgo/util" 7 | ) 8 | 9 | type RenderFunc func(surface *blgo.Surface, width, height, percent float64) 10 | type SetupFunc func(surface *blgo.Surface, width, height float64) 11 | 12 | type Sketch struct { 13 | width float64 14 | height float64 15 | surface *blgo.Surface 16 | setup SetupFunc 17 | render RenderFunc 18 | framesDir string 19 | } 20 | 21 | func NewSketch(setup SetupFunc, render RenderFunc) *Sketch { 22 | return &Sketch{ 23 | setup: setup, 24 | render: render, 25 | framesDir: "frames", // Must Exist!!! 26 | } 27 | } 28 | 29 | func (s *Sketch) SetSize(width, height float64) { 30 | s.width = width 31 | s.height = height 32 | s.surface = blgo.NewSurface(width, height) 33 | } 34 | 35 | func (s *Sketch) RenderImage(width, height, t float64) { 36 | outFileName := "out.png" 37 | s.SetSize(width, height) 38 | s.setup(s.surface, s.width, s.height) 39 | s.render(s.surface, s.width, s.height, t) 40 | s.surface.WriteToPNG(outFileName) 41 | util.ViewImage(outFileName) 42 | } 43 | 44 | func (s *Sketch) RenderFrames(width, height float64, seconds, fps int) { 45 | s.SetSize(width, height) 46 | s.renderAnim(seconds, fps) 47 | } 48 | 49 | func (s *Sketch) RenderGif(width, height float64, seconds, fps int) { 50 | outFileName := "out.gif" 51 | s.SetSize(width, height) 52 | s.renderAnim(seconds, fps) 53 | util.ConvertToGIF(s.framesDir, outFileName, float64(fps)) 54 | util.ViewImage(outFileName) 55 | } 56 | 57 | func (s *Sketch) RenderVideo(width, height float64, seconds, fps int) { 58 | outFileName := "out.mp4" 59 | s.SetSize(width, height) 60 | s.renderAnim(seconds, fps) 61 | util.ConvertToYoutube(s.framesDir, outFileName, fps) 62 | util.VLC(outFileName) 63 | } 64 | 65 | func (s *Sketch) renderAnim(seconds, fps int) { 66 | s.setup(s.surface, s.width, s.height) 67 | animation := anim.NewAnimation(s.surface, fps*seconds, s.framesDir) 68 | animation.Render(s.internalRender) 69 | } 70 | 71 | func (s *Sketch) internalRender(percent float64) { 72 | s.render(s.surface, s.width, s.height, percent) 73 | } 74 | -------------------------------------------------------------------------------- /blmath/math.go: -------------------------------------------------------------------------------- 1 | package blmath 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | // TwoPi 2 pi 8 | const TwoPi = math.Pi * 2 9 | 10 | // HalfPi pi / 2 11 | const HalfPi = math.Pi / 2 12 | 13 | // Difference returns the absolute value of the difference between two numbers. 14 | func Difference(a, b float64) float64 { 15 | return math.Abs(a - b) 16 | } 17 | 18 | // Norm returns a normalized value in a min/max range. 19 | func Norm(value float64, min float64, max float64) float64 { 20 | return (value - min) / (max - min) 21 | } 22 | 23 | // Lerp is linear interpolation within a min/max range. 24 | func Lerp(t float64, min float64, max float64) float64 { 25 | return min + (max-min)*t 26 | } 27 | 28 | // Map maps a value within one min/max range to a value within another range. 29 | func Map(srcValue float64, srcMin float64, srcMax float64, dstMin float64, dstMax float64) float64 { 30 | norm := Norm(srcValue, srcMin, srcMax) 31 | return Lerp(norm, dstMin, dstMax) 32 | } 33 | 34 | // Wrap wraps a value around so it remains between min and max. 35 | func Wrap(value float64, min float64, max float64) float64 { 36 | r := max - min 37 | return min + math.Mod((math.Mod(value-min, r)+r), r) 38 | } 39 | 40 | // Clamp enforces a value does not go beyond a min/max range. 41 | func Clamp(value float64, min float64, max float64) float64 { 42 | // let min and max be reversed and still work. 43 | realMin := min 44 | realMax := max 45 | if min > max { 46 | realMin = max 47 | realMax = min 48 | } 49 | result := value 50 | if value < realMin { 51 | result = realMin 52 | } 53 | if value > realMax { 54 | result = realMax 55 | } 56 | return result 57 | } 58 | 59 | // RoundTo rounds a number to the nearest decimal value. 60 | func RoundTo(value float64, decimal int) float64 { 61 | mult := math.Pow(10.0, float64(decimal)) 62 | return math.Round(value*mult) / mult 63 | } 64 | 65 | // RoundToNearest rounds a number to the nearest multiple of a value. 66 | func RoundToNearest(value float64, mult float64) float64 { 67 | return math.Round(value/mult) * mult 68 | } 69 | 70 | // SinRange returns the sin of an angle mapped to a min/max range. 71 | func SinRange(angle float64, min float64, max float64) float64 { 72 | return Map(math.Sin(angle), -1, 1, min, max) 73 | } 74 | 75 | // Fract returns the fractional part of a floating point number. 76 | func Fract(n float64) float64 { 77 | return n - math.Floor(n) 78 | } 79 | 80 | // CosRange returns the cos of an angle mapped to a min/max range. 81 | func CosRange(angle float64, min float64, max float64) float64 { 82 | return Map(math.Cos(angle), -1, 1, min, max) 83 | } 84 | 85 | // LerpSin maps a normal value to min and max values with a sine wave. 86 | func LerpSin(value, min, max float64) float64 { 87 | return SinRange(value*math.Pi*2, min, max) 88 | } 89 | 90 | // Equalish returns whether the two values are approximately equal. 91 | func Equalish(a float64, b float64, delta float64) bool { 92 | return math.Abs(a-b) <= delta 93 | } 94 | 95 | // ComplexImagAbs returns a complex number with the real component and the abolute value of the imaginary component. 96 | // Useful for certain types of fractals, such as "duck fractals" 97 | func ComplexImagAbs(z complex128) complex128 { 98 | if imag(z) < 0 { 99 | return complex(real(z), -imag(z)) 100 | } 101 | return z 102 | } 103 | 104 | // ComplexMagnitude returns the magnitude of a complex number 105 | func ComplexMagnitude(z complex128) float64 { 106 | return math.Hypot(real(z), imag(z)) 107 | } 108 | -------------------------------------------------------------------------------- /blmath/math_test.go: -------------------------------------------------------------------------------- 1 | package blmath 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | ) 7 | 8 | func TestNorm(t *testing.T) { 9 | var tests = []struct { 10 | val float64 11 | min float64 12 | max float64 13 | want float64 14 | }{ 15 | {0, 0, 100, 0}, 16 | {100, 0, 100, 1}, 17 | {50, 0, 100, 0.5}, 18 | {-50, 0, 100, -0.5}, 19 | {150, 0, 100, 1.5}, 20 | {0, 100, 0, 1}, 21 | {100, 100, 0, 0}, 22 | } 23 | for _, test := range tests { 24 | result := Norm(test.val, test.min, test.max) 25 | if result != test.want { 26 | t.Errorf("Norm(%f, %f, %f) != %f", test.val, test.min, test.max, test.want) 27 | } 28 | } 29 | } 30 | 31 | func TestLerp(t *testing.T) { 32 | var tests = []struct { 33 | t float64 34 | min float64 35 | max float64 36 | want float64 37 | }{ 38 | {0, 0, 100, 0}, 39 | {1, 0, 100, 100}, 40 | {0.5, 0, 100, 50}, 41 | {-0.5, 0, 100, -50}, 42 | {1.5, 0, 100, 150}, 43 | {0, 100, 0, 100}, 44 | {1, 100, 0, 0}, 45 | } 46 | for _, test := range tests { 47 | result := Lerp(test.t, test.min, test.max) 48 | if result != test.want { 49 | t.Errorf("Lerp(%f, %f, %f) != %f", test.t, test.min, test.max, test.want) 50 | } 51 | } 52 | } 53 | 54 | func TestMap(t *testing.T) { 55 | var tests = []struct { 56 | srcVal float64 57 | srcMin float64 58 | srcMax float64 59 | dstMin float64 60 | dstMax float64 61 | want float64 62 | }{ 63 | {0, 0, 100, 200, 400, 200}, 64 | {100, 0, 100, 200, 400, 400}, 65 | {50, 0, 100, 200, 400, 300}, 66 | {-50, 0, 100, 200, 400, 100}, 67 | {150, 0, 100, 200, 400, 500}, 68 | {0, 100, 0, 200, 400, 400}, 69 | {100, 100, 00, 200, 400, 200}, 70 | {0, 0, 100, 400, 200, 400}, 71 | {100, 0, 100, 400, 200, 200}, 72 | } 73 | 74 | for _, test := range tests { 75 | result := Map(test.srcVal, test.srcMin, test.srcMax, test.dstMin, test.dstMax) 76 | if result != test.want { 77 | t.Errorf("MapTo(%f, %f, %f, %f, %f) != %f", test.srcVal, test.srcMin, test.srcMax, test.dstMin, test.dstMax, test.want) 78 | } 79 | } 80 | } 81 | 82 | func TestWrap(t *testing.T) { 83 | var tests = []struct { 84 | val float64 85 | min float64 86 | max float64 87 | want float64 88 | }{ 89 | {0, 0, 360, 0}, 90 | {360, 0, 360, 0}, 91 | {400, 0, 360, 40}, 92 | {-50, 0, 360, 310}, 93 | {720, 0, 360, 0}, 94 | {719, 0, 360, 359}, 95 | {-360, 0, 360, 0}, 96 | {50, 0, 360, 50}, 97 | } 98 | for _, test := range tests { 99 | result := Wrap(test.val, test.min, test.max) 100 | if result != test.want { 101 | t.Errorf("Wrap(%f, %f, %f) != %f", test.val, test.min, test.max, test.want) 102 | } 103 | } 104 | } 105 | 106 | func TestClamp(t *testing.T) { 107 | var tests = []struct { 108 | val float64 109 | min float64 110 | max float64 111 | want float64 112 | }{ 113 | {0, 0, 100, 0}, 114 | {100, 0, 100, 100}, 115 | {-100, 0, 100, 0}, 116 | {150, 0, 100, 100}, 117 | {0, 100, 0, 0}, 118 | {100, 100, 0, 100}, 119 | {-100, 100, 0, 0}, 120 | {150, 100, 0, 100}, 121 | } 122 | for _, test := range tests { 123 | result := Clamp(test.val, test.min, test.max) 124 | if result != test.want { 125 | t.Errorf("Clamp(%f, %f, %f) != %f", test.val, test.min, test.max, test.want) 126 | } 127 | } 128 | } 129 | 130 | func TestEqualish(t *testing.T) { 131 | var tests = []struct { 132 | a float64 133 | b float64 134 | delta float64 135 | want bool 136 | }{ 137 | {0, 0, 0.01, true}, // values equal 138 | {-10, -10, 0.01, true}, // values equal 139 | {0.0001, 0.0003, 0.0001, false}, // values 0.2 different 140 | {0.0003, 0.0001, 0.0001, false}, // values 0.2 different 141 | {0.1, -0.1, 0.2, true}, // values 0.2 different, cross 0 142 | {-0.1, 0.1, 0.2, true}, // values 0.2 different, cross 0 143 | {-0.1000001, 0.1, 0.2, false}, // values 0.2000001 different 144 | {0, 0, 0, true}, // values equal, delta 0 145 | {0, 0, -1, false}, // delta negative, never true 146 | } 147 | for _, test := range tests { 148 | result := Equalish(test.a, test.b, test.delta) 149 | if result != test.want { 150 | t.Errorf("Equalish(%f, %f, %f) != %t", test.a, test.b, test.delta, test.want) 151 | } 152 | } 153 | } 154 | 155 | func TestSinRange(t *testing.T) { 156 | var tests = []struct { 157 | val float64 158 | min float64 159 | max float64 160 | want float64 161 | }{ 162 | {math.Pi * 0.0, 0, 100, 50}, 163 | {math.Pi * 0.5, 0, 100, 100}, 164 | {math.Pi * 1.0, 0, 100, 50}, 165 | {math.Pi * 1.5, 0, 100, 0}, 166 | {math.Pi * 2.0, 0, 100, 50}, 167 | } 168 | for _, test := range tests { 169 | result := SinRange(test.val, test.min, test.max) 170 | if !Equalish(result, test.want, 0.0001) { 171 | t.Errorf("SinRange(%f, %f, %f) != %f", test.val, test.min, test.max, test.want) 172 | } 173 | } 174 | } 175 | 176 | func TestCosRange(t *testing.T) { 177 | var tests = []struct { 178 | val float64 179 | min float64 180 | max float64 181 | want float64 182 | }{ 183 | {math.Pi * 0.0, 0, 100, 100}, 184 | {math.Pi * 0.5, 0, 100, 50}, 185 | {math.Pi * 1.0, 0, 100, 0}, 186 | {math.Pi * 1.5, 0, 100, 50}, 187 | {math.Pi * 2.0, 0, 100, 100}, 188 | } 189 | for _, test := range tests { 190 | result := CosRange(test.val, test.min, test.max) 191 | if !Equalish(result, test.want, 0.0001) { 192 | t.Errorf("CosRange(%f, %f, %f) != %f", test.val, test.min, test.max, test.want) 193 | } 194 | } 195 | } 196 | 197 | func TestRoundTo(t *testing.T) { 198 | var tests = []struct { 199 | value float64 200 | decimal int 201 | want float64 202 | }{ 203 | {1234.1234, 4, 1234.1234}, 204 | {1234.1234, 3, 1234.123}, 205 | {1234.1234, 2, 1234.12}, 206 | {1234.1234, 1, 1234.1}, 207 | {1234.1234, 0, 1234}, 208 | {1234.1234, -1, 1230}, 209 | {1234.1234, -2, 1200}, 210 | {1234.1234, -3, 1000}, 211 | {1234.1234, -4, 0}, 212 | {1234.5555, 4, 1234.5555}, 213 | {1234.5555, 3, 1234.556}, 214 | {1234.5555, 2, 1234.56}, 215 | {1234.5555, 1, 1234.6}, 216 | {1234.5555, 0, 1235}, 217 | {1234.5555, -1, 1230}, 218 | } 219 | for _, test := range tests { 220 | result := RoundTo(test.value, test.decimal) 221 | if result != test.want { 222 | t.Errorf("RoundTo(%f, %d) != %f", test.value, test.decimal, test.want) 223 | } 224 | } 225 | } 226 | 227 | func TestComplexMag(t *testing.T) { 228 | var tests = []struct { 229 | value complex128 230 | want float64 231 | }{ 232 | {complex(3, 4), 5.0}, 233 | {complex(4, 3), 5.0}, 234 | {complex(5, 12), 13.0}, 235 | {complex(12, 5), 13.0}, 236 | {complex(21, 28), 35.0}, 237 | {complex(28, 21), 35.0}, 238 | } 239 | for _, test := range tests { 240 | result := ComplexMagnitude(test.value) 241 | if result != test.want { 242 | t.Errorf("ComplexMagnitude(%f) != %f", test.value, test.want) 243 | } 244 | } 245 | } 246 | 247 | func TestComplexImagAbs(t *testing.T) { 248 | var tests = []struct { 249 | value complex128 250 | want complex128 251 | }{ 252 | {complex(4, -5), complex(4, 5)}, 253 | {complex(4, 5), complex(4, 5)}, 254 | {complex(-4, -5), complex(-4, 5)}, 255 | {complex(-4, 5), complex(-4, 5)}, 256 | {complex(4.2, -5.3), complex(4.2, 5.3)}, 257 | {complex(4.2, 5.3), complex(4.2, 5.3)}, 258 | {complex(-4.2, -5.3), complex(-4.2, 5.3)}, 259 | {complex(-4.2, 5.3), complex(-4.2, 5.3)}, 260 | } 261 | for _, test := range tests { 262 | result := ComplexImagAbs(test.value) 263 | if result != test.want { 264 | t.Errorf("ComplexImagAbs(%f) != %f", test.value, test.want) 265 | } 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /color/color.go: -------------------------------------------------------------------------------- 1 | package color 2 | 3 | import ( 4 | "math" 5 | "math/rand" 6 | ) 7 | 8 | // Color holds rgba values for a color. 9 | type Color struct { 10 | R float64 11 | G float64 12 | B float64 13 | A float64 14 | } 15 | 16 | // RGB creates a new Color struct with rgb values from 0.0 to 1.0 each (a = 1.0). 17 | func RGB(r float64, g float64, b float64) Color { 18 | return Color{r, g, b, 1.0} 19 | } 20 | 21 | // RGBA creates a new Color struct with rgba values from 0.0 to 1.0 each. 22 | func RGBA(r float64, g float64, b float64, a float64) Color { 23 | return Color{r, g, b, a} 24 | } 25 | 26 | // Number creates a new Color struct with a 24-bit int 0xRRGGBB (a = 1.0). 27 | func Number(value int) Color { 28 | r := (value >> 16 & 0xff) 29 | g := (value >> 8 & 0xff) 30 | b := (value & 0xff) 31 | return RGBHex(r, g, b) 32 | } 33 | 34 | // NumberWithAlpha creates a new Color struct with a 32-bit int 0xAARRGGBB. 35 | func NumberWithAlpha(value int) Color { 36 | a := (value >> 24) 37 | r := (value >> 16 & 0xff) 38 | g := (value >> 8 & 0xff) 39 | b := (value & 0xff) 40 | return RGBAHex(r, g, b, a) 41 | } 42 | 43 | // Lerp creates a new color by interpolating between two other colors 44 | func Lerp(colorA, colorB Color, t float64) Color { 45 | r := colorA.R + (colorB.R-colorA.R)*t 46 | g := colorA.G + (colorB.G-colorA.G)*t 47 | b := colorA.B + (colorB.B-colorA.B)*t 48 | a := colorA.A + (colorB.A-colorA.A)*t 49 | return RGBA(r, g, b, a) 50 | } 51 | 52 | // RGBHex creates a Color struct with rgb values from 0 to 255 (a = 255). 53 | func RGBHex(r int, g int, b int) Color { 54 | return RGBAHex(r, g, b, 255) 55 | } 56 | 57 | // RGBAHex creates a Color struct with rgba values from 0 to 255 each. 58 | func RGBAHex(r int, g int, b int, a int) Color { 59 | return RGBA(float64(r)/255.0, float64(g)/255.0, float64(b)/255.0, float64(a)/255.0) 60 | } 61 | 62 | // RandomRGB creates a color struct with random rgb values (a = 1.0). 63 | func RandomRGB() Color { 64 | r := rand.Float64() 65 | g := rand.Float64() 66 | b := rand.Float64() 67 | return RGB(r, g, b) 68 | } 69 | 70 | // HSV creates a Color struct using hue (0.0 - 360.0), value (0.0 - 1.0) and value (0.0 - 1.0) (a = 1.0). 71 | func HSV(h float64, s float64, v float64) Color { 72 | h = h / 360.0 73 | i := math.Floor(h * 6.0) 74 | f := h*6.0 - i 75 | p := v * (1.0 - s) 76 | q := v * (1.0 - f*s) 77 | t := v * (1.0 - (1.0-f)*s) 78 | switch int(i) % 6 { 79 | case 0: 80 | return RGB(v, t, p) 81 | case 1: 82 | return RGB(q, v, p) 83 | case 2: 84 | return RGB(p, v, t) 85 | case 3: 86 | return RGB(p, q, v) 87 | case 4: 88 | return RGB(t, p, v) 89 | case 5: 90 | return RGB(v, p, q) 91 | default: 92 | return RGB(0.0, 0.0, 0.0) 93 | } 94 | } 95 | 96 | // HSVA creates a Color struct using hue (0.0 - 360.0), value (0.0 - 1.0), value (0.0 - 1.0) and alpha (0.0 - 1.0). 97 | func HSVA(h float64, s float64, v float64, a float64) Color { 98 | c := HSV(h, s, v) 99 | c.A = a 100 | return c 101 | } 102 | 103 | // Grey creates a new Color struct with rgb all equal to the same value from 0.0 to 1.0 (a = 1.0). 104 | func Grey(shade float64) Color { 105 | return RGB(shade, shade, shade) 106 | } 107 | 108 | // GreyHex creates a new Color struct with rgb all equal to the same value from 0 to 255 (a = 1.0). 109 | func GreyHex(shade int) Color { 110 | return Grey(float64(shade) / 255.0) 111 | } 112 | 113 | // RandomGrey creates a Color struct with a random grey shade from 0.0 to 1.0 (a = 1.0). 114 | func RandomGrey() Color { 115 | return RandomGreyRange(0.0, 1.0) 116 | } 117 | 118 | // RandomGreyRange creates a Color struct with a random grey shade from min to max (a = 1.0). 119 | func RandomGreyRange(min float64, max float64) Color { 120 | return Grey(min + rand.Float64()*(max-min)) 121 | } 122 | 123 | // Blueviolet creates a Blueviolet Color struct. 124 | func Blueviolet() Color { return RGBHex(138, 43, 226) } 125 | 126 | // Brown creates a Brown Color struct. 127 | func Brown() Color { return RGBHex(165, 42, 42) } 128 | 129 | // Aliceblue creates a Aliceblue Color struct. 130 | func Aliceblue() Color { return RGBHex(240, 248, 255) } 131 | 132 | // Antiquewhite creates a Antiquewhite Color struct. 133 | func Antiquewhite() Color { return RGBHex(250, 235, 215) } 134 | 135 | // Aqua creates a Aqua Color struct. 136 | func Aqua() Color { return RGBHex(0, 255, 255) } 137 | 138 | // Aquamarine creates a Aquamarine Color struct. 139 | func Aquamarine() Color { return RGBHex(127, 255, 212) } 140 | 141 | // Azure creates a Azure Color struct. 142 | func Azure() Color { return RGBHex(240, 255, 255) } 143 | 144 | // Beige creates a Beige Color struct. 145 | func Beige() Color { return RGBHex(245, 245, 220) } 146 | 147 | // Bisque creates a Bisque Color struct. 148 | func Bisque() Color { return RGBHex(255, 228, 196) } 149 | 150 | // Black creates a Black Color struct. 151 | func Black() Color { return RGBHex(0, 0, 0) } 152 | 153 | // Blanchedalmond creates a Blanchedalmond Color struct. 154 | func Blanchedalmond() Color { return RGBHex(255, 235, 205) } 155 | 156 | // Blue creates a Blue Color struct. 157 | func Blue() Color { return RGBHex(0, 0, 255) } 158 | 159 | // Burlywood creates a Burlywood Color struct. 160 | func Burlywood() Color { return RGBHex(222, 184, 135) } 161 | 162 | // Cadetblue creates a Cadetblue Color struct. 163 | func Cadetblue() Color { return RGBHex(95, 158, 160) } 164 | 165 | // Chartreuse creates a Chartreuse Color struct. 166 | func Chartreuse() Color { return RGBHex(127, 255, 0) } 167 | 168 | // Chocolate creates a Chocolate Color struct. 169 | func Chocolate() Color { return RGBHex(210, 105, 30) } 170 | 171 | // Coral creates a Coral Color struct. 172 | func Coral() Color { return RGBHex(255, 127, 80) } 173 | 174 | // Cornflowerblue creates a Cornflowerblue Color struct. 175 | func Cornflowerblue() Color { return RGBHex(100, 149, 237) } 176 | 177 | // Cornsilk creates a Cornsilk Color struct. 178 | func Cornsilk() Color { return RGBHex(255, 248, 220) } 179 | 180 | // Crimson creates a Crimson Color struct. 181 | func Crimson() Color { return RGBHex(220, 20, 60) } 182 | 183 | // Cyan creates a Cyan Color struct. 184 | func Cyan() Color { return RGBHex(0, 255, 255) } 185 | 186 | // Darkblue creates a Darkblue Color struct. 187 | func Darkblue() Color { return RGBHex(0, 0, 139) } 188 | 189 | // Darkcyan creates a Darkcyan Color struct. 190 | func Darkcyan() Color { return RGBHex(0, 139, 139) } 191 | 192 | // Darkgoldenrod creates a Darkgoldenrod Color struct. 193 | func Darkgoldenrod() Color { return RGBHex(184, 134, 11) } 194 | 195 | // Darkgray creates a Darkgray Color struct. 196 | func Darkgray() Color { return RGBHex(169, 169, 169) } 197 | 198 | // Darkgreen creates a Darkgreen Color struct. 199 | func Darkgreen() Color { return RGBHex(0, 100, 0) } 200 | 201 | // Darkgrey creates a Darkgrey Color struct. 202 | func Darkgrey() Color { return RGBHex(169, 169, 169) } 203 | 204 | // Darkkhaki creates a Darkkhaki Color struct. 205 | func Darkkhaki() Color { return RGBHex(189, 183, 107) } 206 | 207 | // Darkmagenta creates a Darkmagenta Color struct. 208 | func Darkmagenta() Color { return RGBHex(139, 0, 139) } 209 | 210 | // Darkolivegreen creates a Darkolivegreen Color struct. 211 | func Darkolivegreen() Color { return RGBHex(85, 107, 47) } 212 | 213 | // Darkorange creates a Darkorange Color struct. 214 | func Darkorange() Color { return RGBHex(255, 140, 0) } 215 | 216 | // Darkorchid creates a Darkorchid Color struct. 217 | func Darkorchid() Color { return RGBHex(153, 50, 204) } 218 | 219 | // Darkred creates a Darkred Color struct. 220 | func Darkred() Color { return RGBHex(139, 0, 0) } 221 | 222 | // Darksalmon creates a Darksalmon Color struct. 223 | func Darksalmon() Color { return RGBHex(233, 150, 122) } 224 | 225 | // Darkseagreen creates a Darkseagreen Color struct. 226 | func Darkseagreen() Color { return RGBHex(143, 188, 143) } 227 | 228 | // Darkslateblue creates a Darkslateblue Color struct. 229 | func Darkslateblue() Color { return RGBHex(72, 61, 139) } 230 | 231 | // Darkslategray creates a Darkslategray Color struct. 232 | func Darkslategray() Color { return RGBHex(47, 79, 79) } 233 | 234 | // Darkslategrey creates a Darkslategrey Color struct. 235 | func Darkslategrey() Color { return RGBHex(47, 79, 79) } 236 | 237 | // Darkturquoise creates a Darkturquoise Color struct. 238 | func Darkturquoise() Color { return RGBHex(0, 206, 209) } 239 | 240 | // Darkviolet creates a Darkviolet Color struct. 241 | func Darkviolet() Color { return RGBHex(148, 0, 211) } 242 | 243 | // Deeppink creates a Deeppink Color struct. 244 | func Deeppink() Color { return RGBHex(255, 20, 147) } 245 | 246 | // Deepskyblue creates a Deepskyblue Color struct. 247 | func Deepskyblue() Color { return RGBHex(0, 191, 255) } 248 | 249 | // Dimgray creates a Dimgray Color struct. 250 | func Dimgray() Color { return RGBHex(105, 105, 105) } 251 | 252 | // Dimgrey creates a Dimgrey Color struct. 253 | func Dimgrey() Color { return RGBHex(105, 105, 105) } 254 | 255 | // Dodgerblue creates a Dodgerblue Color struct. 256 | func Dodgerblue() Color { return RGBHex(30, 144, 255) } 257 | 258 | // Firebrick creates a Firebrick Color struct. 259 | func Firebrick() Color { return RGBHex(178, 34, 34) } 260 | 261 | // Floralwhite creates a Floralwhite Color struct. 262 | func Floralwhite() Color { return RGBHex(255, 250, 240) } 263 | 264 | // Forestgreen creates a Forestgreen Color struct. 265 | func Forestgreen() Color { return RGBHex(34, 139, 34) } 266 | 267 | // Fuchsia creates a Fuchsia Color struct. 268 | func Fuchsia() Color { return RGBHex(255, 0, 255) } 269 | 270 | // Gainsboro creates a Gainsboro Color struct. 271 | func Gainsboro() Color { return RGBHex(220, 220, 220) } 272 | 273 | // Ghostwhite creates a Ghostwhite Color struct. 274 | func Ghostwhite() Color { return RGBHex(248, 248, 255) } 275 | 276 | // Gold creates a Gold Color struct. 277 | func Gold() Color { return RGBHex(255, 215, 0) } 278 | 279 | // Goldenrod creates a Goldenrod Color struct. 280 | func Goldenrod() Color { return RGBHex(218, 165, 32) } 281 | 282 | // Gray creates a Gray Color struct. 283 | func Gray() Color { return RGBHex(128, 128, 128) } 284 | 285 | // Green creates a Green Color struct. 286 | func Green() Color { return RGBHex(0, 128, 0) } 287 | 288 | // Greenyellow creates a Greenyellow Color struct. 289 | func Greenyellow() Color { return RGBHex(173, 255, 47) } 290 | 291 | // Honeydew creates a Honeydew Color struct. 292 | func Honeydew() Color { return RGBHex(240, 255, 240) } 293 | 294 | // Hotpink creates a Hotpink Color struct. 295 | func Hotpink() Color { return RGBHex(255, 105, 180) } 296 | 297 | // Indianred creates a Indianred Color struct. 298 | func Indianred() Color { return RGBHex(205, 92, 92) } 299 | 300 | // Indigo creates a Indigo Color struct. 301 | func Indigo() Color { return RGBHex(75, 0, 130) } 302 | 303 | // Ivory creates a Ivory Color struct. 304 | func Ivory() Color { return RGBHex(255, 255, 240) } 305 | 306 | // Khaki creates a Khaki Color struct. 307 | func Khaki() Color { return RGBHex(240, 230, 140) } 308 | 309 | // Lavender creates a Lavender Color struct. 310 | func Lavender() Color { return RGBHex(230, 230, 250) } 311 | 312 | // Lavenderblush creates a Lavenderblush Color struct. 313 | func Lavenderblush() Color { return RGBHex(255, 240, 245) } 314 | 315 | // Lawngreen creates a Lawngreen Color struct. 316 | func Lawngreen() Color { return RGBHex(124, 252, 0) } 317 | 318 | // Lemonchiffon creates a Lemonchiffon Color struct. 319 | func Lemonchiffon() Color { return RGBHex(255, 250, 205) } 320 | 321 | // Lightblue creates a Lightblue Color struct. 322 | func Lightblue() Color { return RGBHex(173, 216, 230) } 323 | 324 | // Lightcoral creates a Lightcoral Color struct. 325 | func Lightcoral() Color { return RGBHex(240, 128, 128) } 326 | 327 | // Lightcyan creates a Lightcyan Color struct. 328 | func Lightcyan() Color { return RGBHex(224, 255, 255) } 329 | 330 | // Lightgoldenrodyellow creates a Lightgoldenrodyellow Color struct. 331 | func Lightgoldenrodyellow() Color { return RGBHex(250, 250, 210) } 332 | 333 | // Lightgray creates a Lightgray Color struct. 334 | func Lightgray() Color { return RGBHex(211, 211, 211) } 335 | 336 | // Lightgreen creates a Lightgreen Color struct. 337 | func Lightgreen() Color { return RGBHex(144, 238, 144) } 338 | 339 | // Lightgrey creates a Lightgrey Color struct. 340 | func Lightgrey() Color { return RGBHex(211, 211, 211) } 341 | 342 | // Lightpink creates a Lightpink Color struct. 343 | func Lightpink() Color { return RGBHex(255, 182, 193) } 344 | 345 | // Lightsalmon creates a Lightsalmon Color struct. 346 | func Lightsalmon() Color { return RGBHex(255, 160, 122) } 347 | 348 | // Lightseagreen creates a Lightseagreen Color struct. 349 | func Lightseagreen() Color { return RGBHex(32, 178, 170) } 350 | 351 | // Lightskyblue creates a Lightskyblue Color struct. 352 | func Lightskyblue() Color { return RGBHex(135, 206, 250) } 353 | 354 | // Lightslategray creates a Lightslategray Color struct. 355 | func Lightslategray() Color { return RGBHex(119, 136, 153) } 356 | 357 | // Lightslategrey creates a Lightslategrey Color struct. 358 | func Lightslategrey() Color { return RGBHex(119, 136, 153) } 359 | 360 | // Lightsteelblue creates a Lightsteelblue Color struct. 361 | func Lightsteelblue() Color { return RGBHex(176, 196, 222) } 362 | 363 | // Lightyellow creates a Lightyellow Color struct. 364 | func Lightyellow() Color { return RGBHex(255, 255, 224) } 365 | 366 | // Lime creates a Lime Color struct. 367 | func Lime() Color { return RGBHex(0, 255, 0) } 368 | 369 | // Limegreen creates a Limegreen Color struct. 370 | func Limegreen() Color { return RGBHex(50, 205, 50) } 371 | 372 | // Linen creates a Linen Color struct. 373 | func Linen() Color { return RGBHex(250, 240, 230) } 374 | 375 | // Magenta creates a Magenta Color struct. 376 | func Magenta() Color { return RGBHex(255, 0, 255) } 377 | 378 | // Maroon creates a Maroon Color struct. 379 | func Maroon() Color { return RGBHex(128, 0, 0) } 380 | 381 | // Mediumaquamarine creates a Mediumaquamarine Color struct. 382 | func Mediumaquamarine() Color { return RGBHex(102, 205, 170) } 383 | 384 | // Mediumblue creates a Mediumblue Color struct. 385 | func Mediumblue() Color { return RGBHex(0, 0, 205) } 386 | 387 | // Mediumorchid creates a Mediumorchid Color struct. 388 | func Mediumorchid() Color { return RGBHex(186, 85, 211) } 389 | 390 | // Mediumpurple creates a Mediumpurple Color struct. 391 | func Mediumpurple() Color { return RGBHex(147, 112, 219) } 392 | 393 | // Mediumseagreen creates a Mediumseagreen Color struct. 394 | func Mediumseagreen() Color { return RGBHex(60, 179, 113) } 395 | 396 | // Mediumslateblue creates a Mediumslateblue Color struct. 397 | func Mediumslateblue() Color { return RGBHex(123, 104, 238) } 398 | 399 | // Mediumspringgreen creates a Mediumspringgreen Color struct. 400 | func Mediumspringgreen() Color { return RGBHex(0, 250, 154) } 401 | 402 | // Mediumturquoise creates a Mediumturquoise Color struct. 403 | func Mediumturquoise() Color { return RGBHex(72, 209, 204) } 404 | 405 | // Mediumvioletred creates a Mediumvioletred Color struct. 406 | func Mediumvioletred() Color { return RGBHex(199, 21, 133) } 407 | 408 | // Midnightblue creates a Midnightblue Color struct. 409 | func Midnightblue() Color { return RGBHex(25, 25, 112) } 410 | 411 | // Mintcream creates a Mintcream Color struct. 412 | func Mintcream() Color { return RGBHex(245, 255, 250) } 413 | 414 | // Mistyrose creates a Mistyrose Color struct. 415 | func Mistyrose() Color { return RGBHex(255, 228, 225) } 416 | 417 | // Moccasin creates a Moccasin Color struct. 418 | func Moccasin() Color { return RGBHex(255, 228, 181) } 419 | 420 | // Navajowhite creates a Navajowhite Color struct. 421 | func Navajowhite() Color { return RGBHex(255, 222, 173) } 422 | 423 | // Navy creates a Navy Color struct. 424 | func Navy() Color { return RGBHex(0, 0, 128) } 425 | 426 | // Oldlace creates a Oldlace Color struct. 427 | func Oldlace() Color { return RGBHex(253, 245, 230) } 428 | 429 | // Olive creates a Olive Color struct. 430 | func Olive() Color { return RGBHex(128, 128, 0) } 431 | 432 | // Olivedrab creates a Olivedrab Color struct. 433 | func Olivedrab() Color { return RGBHex(107, 142, 35) } 434 | 435 | // Orange creates a Orange Color struct. 436 | func Orange() Color { return RGBHex(255, 165, 0) } 437 | 438 | // Orangered creates a Orangered Color struct. 439 | func Orangered() Color { return RGBHex(255, 69, 0) } 440 | 441 | // Orchid creates a Orchid Color struct. 442 | func Orchid() Color { return RGBHex(218, 112, 214) } 443 | 444 | // Palegoldenrod creates a Palegoldenrod Color struct. 445 | func Palegoldenrod() Color { return RGBHex(238, 232, 170) } 446 | 447 | // Palegreen creates a Palegreen Color struct. 448 | func Palegreen() Color { return RGBHex(152, 251, 152) } 449 | 450 | // Paleturquoise creates a Paleturquoise Color struct. 451 | func Paleturquoise() Color { return RGBHex(175, 238, 238) } 452 | 453 | // Palevioletred creates a Palevioletred Color struct. 454 | func Palevioletred() Color { return RGBHex(219, 112, 147) } 455 | 456 | // Papayawhip creates a Papayawhip Color struct. 457 | func Papayawhip() Color { return RGBHex(255, 239, 213) } 458 | 459 | // Peachpuff creates a Peachpuff Color struct. 460 | func Peachpuff() Color { return RGBHex(255, 218, 185) } 461 | 462 | // Peru creates a Peru Color struct. 463 | func Peru() Color { return RGBHex(205, 133, 63) } 464 | 465 | // Pink creates a Pink Color struct. 466 | func Pink() Color { return RGBHex(255, 192, 203) } 467 | 468 | // Plum creates a Plum Color struct. 469 | func Plum() Color { return RGBHex(221, 160, 221) } 470 | 471 | // Powderblue creates a Powderblue Color struct. 472 | func Powderblue() Color { return RGBHex(176, 224, 230) } 473 | 474 | // Purple creates a Purple Color struct. 475 | func Purple() Color { return RGBHex(128, 0, 128) } 476 | 477 | // Rebeccapurple creates a Rebeccapurple Color struct. 478 | func Rebeccapurple() Color { return RGBHex(102, 51, 153) } 479 | 480 | // Red creates a Red Color struct. 481 | func Red() Color { return RGBHex(255, 0, 0) } 482 | 483 | // Rosybrown creates a Rosybrown Color struct. 484 | func Rosybrown() Color { return RGBHex(188, 143, 143) } 485 | 486 | // Royalblue creates a Royalblue Color struct. 487 | func Royalblue() Color { return RGBHex(65, 105, 225) } 488 | 489 | // Saddlebrown creates a Saddlebrown Color struct. 490 | func Saddlebrown() Color { return RGBHex(139, 69, 19) } 491 | 492 | // Salmon creates a Salmon Color struct. 493 | func Salmon() Color { return RGBHex(250, 128, 114) } 494 | 495 | // Sandybrown creates a Sandybrown Color struct. 496 | func Sandybrown() Color { return RGBHex(244, 164, 96) } 497 | 498 | // Seagreen creates a Seagreen Color struct. 499 | func Seagreen() Color { return RGBHex(46, 139, 87) } 500 | 501 | // Seashell creates a Seashell Color struct. 502 | func Seashell() Color { return RGBHex(255, 245, 238) } 503 | 504 | // Sienna creates a Sienna Color struct. 505 | func Sienna() Color { return RGBHex(160, 82, 45) } 506 | 507 | // Silver creates a Silver Color struct. 508 | func Silver() Color { return RGBHex(192, 192, 192) } 509 | 510 | // Skyblue creates a Skyblue Color struct. 511 | func Skyblue() Color { return RGBHex(135, 206, 235) } 512 | 513 | // Slateblue creates a Slateblue Color struct. 514 | func Slateblue() Color { return RGBHex(106, 90, 205) } 515 | 516 | // Slategray creates a Slategray Color struct. 517 | func Slategray() Color { return RGBHex(112, 128, 144) } 518 | 519 | // Slategrey creates a Slategrey Color struct. 520 | func Slategrey() Color { return RGBHex(112, 128, 144) } 521 | 522 | // Snow creates a Snow Color struct. 523 | func Snow() Color { return RGBHex(255, 250, 250) } 524 | 525 | // Springgreen creates a Springgreen Color struct. 526 | func Springgreen() Color { return RGBHex(0, 255, 127) } 527 | 528 | // Steelblue creates a Steelblue Color struct. 529 | func Steelblue() Color { return RGBHex(70, 130, 180) } 530 | 531 | // Tan creates a Tan Color struct. 532 | func Tan() Color { return RGBHex(210, 180, 140) } 533 | 534 | // Teal creates a Teal Color struct. 535 | func Teal() Color { return RGBHex(0, 128, 128) } 536 | 537 | // Thistle creates a Thistle Color struct. 538 | func Thistle() Color { return RGBHex(216, 191, 216) } 539 | 540 | // Tomato creates a Tomato Color struct. 541 | func Tomato() Color { return RGBHex(255, 99, 71) } 542 | 543 | // Turquoise creates a Turquoise Color struct. 544 | func Turquoise() Color { return RGBHex(64, 224, 208) } 545 | 546 | // Violet creates a Violet Color struct. 547 | func Violet() Color { return RGBHex(238, 130, 238) } 548 | 549 | // Wheat creates a Wheat Color struct. 550 | func Wheat() Color { return RGBHex(245, 222, 179) } 551 | 552 | // White creates a White Color struct. 553 | func White() Color { return RGBHex(255, 255, 255) } 554 | 555 | // Whitesmoke creates a Whitesmoke Color struct. 556 | func Whitesmoke() Color { return RGBHex(245, 245, 245) } 557 | 558 | // Yellow creates a Yellow Color struct. 559 | func Yellow() Color { return RGBHex(255, 255, 0) } 560 | 561 | // Yellowgreen creates a Yellowgreen Color struct. 562 | func Yellowgreen() Color { return RGBHex(154, 205, 50) } 563 | -------------------------------------------------------------------------------- /color/color_test.go: -------------------------------------------------------------------------------- 1 | package color 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestRGB(t *testing.T) { 9 | var tests = []struct { 10 | r float64 11 | g float64 12 | b float64 13 | want Color 14 | }{ 15 | {0, 0, 0, Color{0, 0, 0, 1}}, 16 | {1, 0, 0, Color{1, 0, 0, 1}}, 17 | {0, 1, 0, Color{0, 1, 0, 1}}, 18 | {0, 0, 1, Color{0, 0, 1, 1}}, 19 | {0, 0.5, 1, Color{0, 0.5, 1, 1}}, 20 | } 21 | for _, test := range tests { 22 | result := RGB(test.r, test.g, test.b) 23 | if result != test.want { 24 | t.Errorf("RGB(%f, %f, %f) != %v", test.r, test.g, test.b, test.want) 25 | } 26 | } 27 | } 28 | 29 | func TestRGBA(t *testing.T) { 30 | var tests = []struct { 31 | r float64 32 | g float64 33 | b float64 34 | a float64 35 | want Color 36 | }{ 37 | {0, 0, 0, 0, Color{0, 0, 0, 0}}, 38 | {1, 0, 0, 0.5, Color{1, 0, 0, 0.5}}, 39 | {0, 1, 0, 0.75, Color{0, 1, 0, 0.75}}, 40 | {0, 0, 1, 1, Color{0, 0, 1, 1}}, 41 | {0, 0.5, 1, 0.2, Color{0, 0.5, 1, 0.2}}, 42 | } 43 | for _, test := range tests { 44 | result := RGBA(test.r, test.g, test.b, test.a) 45 | if result != test.want { 46 | t.Errorf("RGBA(%f, %f, %f, %f) != %v", test.r, test.g, test.b, test.a, test.want) 47 | } 48 | } 49 | } 50 | 51 | func TestNumber(t *testing.T) { 52 | var tests = []struct { 53 | number int 54 | want Color 55 | }{ 56 | {0x000000, Color{0, 0, 0, 1}}, 57 | {0xff0000, Color{1, 0, 0, 1}}, 58 | {0x00ff00, Color{0, 1, 0, 1}}, 59 | {0x0000ff, Color{0, 0, 1, 1}}, 60 | {0xff00ff, Color{1, 0, 1, 1}}, 61 | {0x1100ff, Color{0.06666666666666667, 0, 1, 1}}, 62 | } 63 | for _, test := range tests { 64 | result := Number(test.number) 65 | if result != test.want { 66 | t.Errorf("Number(%d) != %v", test.number, test.want) 67 | } 68 | } 69 | } 70 | 71 | func TestNumberWithAlpha(t *testing.T) { 72 | var tests = []struct { 73 | number int 74 | want Color 75 | }{ 76 | {0x00000000, Color{0, 0, 0, 0}}, 77 | {0xffffffff, Color{1, 1, 1, 1}}, 78 | {0xffff0000, Color{1, 0, 0, 1}}, 79 | {0x0000ff00, Color{0, 1, 0, 0}}, 80 | {0xff0000ff, Color{0, 0, 1, 1}}, 81 | {0x00ff00ff, Color{1, 0, 1, 0}}, 82 | {0x110000ff, Color{0, 0, 1, 0.06666666666666667}}, 83 | } 84 | for _, test := range tests { 85 | result := NumberWithAlpha(test.number) 86 | if result != test.want { 87 | t.Errorf("NumberWithAlpha(%d) != %v", test.number, test.want) 88 | } 89 | } 90 | } 91 | 92 | func TestRGBHex(t *testing.T) { 93 | var tests = []struct { 94 | r int 95 | g int 96 | b int 97 | want Color 98 | }{ 99 | {0, 0, 0, Color{0, 0, 0, 1}}, 100 | {255, 0, 0, Color{1, 0, 0, 1}}, 101 | {0, 255, 0, Color{0, 1, 0, 1}}, 102 | {0, 0, 255, Color{0, 0, 1, 1}}, 103 | {0, 17, 255, Color{0, 0.06666666666666667, 1, 1}}, 104 | } 105 | for _, test := range tests { 106 | result := RGBHex(test.r, test.g, test.b) 107 | if result != test.want { 108 | t.Errorf("RGBHex(%d, %d, %d) != %v", test.r, test.g, test.b, test.want) 109 | } 110 | } 111 | } 112 | 113 | func TestRGBAHex(t *testing.T) { 114 | var tests = []struct { 115 | r int 116 | g int 117 | b int 118 | a int 119 | want Color 120 | }{ 121 | {0, 0, 0, 255, Color{0, 0, 0, 1}}, 122 | {255, 0, 0, 0, Color{1, 0, 0, 0}}, 123 | {0, 255, 0, 255, Color{0, 1, 0, 1}}, 124 | {0, 0, 255, 0, Color{0, 0, 1, 0}}, 125 | {0, 17, 255, 17, Color{0, 0.06666666666666667, 1, 0.06666666666666667}}, 126 | } 127 | for _, test := range tests { 128 | result := RGBAHex(test.r, test.g, test.b, test.a) 129 | if result != test.want { 130 | t.Errorf("RGBAHex(%d, %d, %d, %d) != %v", test.r, test.g, test.b, test.a, test.want) 131 | } 132 | } 133 | } 134 | 135 | func TestRandomRGB(t *testing.T) { 136 | result := RandomRGB() 137 | if !(result.R >= 0.0 && result.R < 1.0 && 138 | result.G >= 0.0 && result.G < 1.0 && 139 | result.B >= 0.0 && result.B < 1.0) { 140 | t.Errorf("RandomRGB() rgb values not between 0.0 and 1.0") 141 | } 142 | } 143 | 144 | func TestHSV(t *testing.T) { 145 | var tests = []struct { 146 | h float64 147 | s float64 148 | v float64 149 | want Color 150 | }{ 151 | {0, 0, 0, Color{0, 0, 0, 1}}, 152 | {0, 0, 1, Color{1, 1, 1, 1}}, 153 | 154 | {0, 1, 1, Color{1, 0, 0, 1}}, 155 | {30, 1, 1, Color{1, 0.5, 0, 1}}, 156 | {60, 1, 1, Color{1, 1, 0, 1}}, 157 | {90, 1, 1, Color{0.5, 1, 0, 1}}, 158 | {120, 1, 1, Color{0, 1, 0, 1}}, 159 | {180, 1, 1, Color{0, 1, 1, 1}}, 160 | {240, 1, 1, Color{0, 0, 1, 1}}, 161 | {300, 1, 1, Color{1, 0, 1, 1}}, 162 | {360, 1, 1, Color{1, 0, 0, 1}}, 163 | } 164 | for _, test := range tests { 165 | result := HSV(test.h, test.s, test.v) 166 | if result != test.want { 167 | t.Errorf("HSV(%f, %f, %f) != %v", test.h, test.s, test.v, test.want) 168 | fmt.Println(result) 169 | } 170 | } 171 | } 172 | 173 | func TestLerp(t *testing.T) { 174 | result := Lerp(Color{0.0, 0.0, 0.0, 1.0}, Color{0.5, 1.0, 0.0, 1.0}, 0.5) 175 | want := Color{0.25, 0.5, 0.0, 1.0} 176 | if result != want { 177 | t.Errorf("%v != %v", result, want) 178 | } 179 | } 180 | 181 | func TestGrey(t *testing.T) { 182 | result := Grey(0) 183 | want := Color{0, 0, 0, 1} 184 | if result != want { 185 | t.Errorf("Grey(%f) != %v", 0.0, want) 186 | } 187 | 188 | result = Grey(1) 189 | want = Color{1, 1, 1, 1} 190 | if result != want { 191 | t.Errorf("Grey(%f) != %v", 1.0, want) 192 | } 193 | 194 | result = Grey(0.5) 195 | if !(result.R == result.G && result.G == result.B) { 196 | t.Errorf("Grey(0.5) rgb values are not equal.") 197 | } 198 | 199 | } 200 | 201 | func TestRandomGrey(t *testing.T) { 202 | result := RandomGrey() 203 | if !(result.R >= 0.0 && result.R < 1.0) { 204 | t.Errorf("RandomGrey() rgb values not between 0.0 and 1.0") 205 | } 206 | if !(result.R == result.G && result.G == result.B) { 207 | t.Errorf("RandomGrey() rgb values not equal") 208 | } 209 | } 210 | 211 | func TestRandomGreyRange(t *testing.T) { 212 | result := RandomGreyRange(0.0, 1.0) 213 | if !(result.R >= 0.0 && result.R < 1.0) { 214 | t.Errorf("RandomGreyRange(0.0, 1.0) rgb values not between 0.0 and 1.0") 215 | } 216 | if !(result.R == result.G && result.G == result.B) { 217 | t.Errorf("RandomGreyRange(0.0, 1.0) rgb values not equal") 218 | } 219 | 220 | result = RandomGreyRange(0.0, 0.5) 221 | if !(result.R >= 0.0 && result.R < 0.5) { 222 | t.Errorf("RandomGreyRange(0.0, 0.5) rgb values not between 0.0 and 0.5") 223 | } 224 | 225 | result = RandomGreyRange(0.5, 1.0) 226 | if !(result.R >= 0.5 && result.R < 1.0) { 227 | t.Errorf("RandomGreyRange(0.5, 1.0) rgb values not between 0.5 and 1.0") 228 | } 229 | } 230 | 231 | func TestBlueviolet(t *testing.T) { 232 | value := Blueviolet() 233 | want := RGBHex(138, 43, 226) 234 | if value != want { 235 | t.Errorf("%v != %v", value, want) 236 | } 237 | } 238 | 239 | func TestBrown(t *testing.T) { 240 | value := Brown() 241 | want := RGBHex(165, 42, 42) 242 | if value != want { 243 | t.Errorf("%v != %v", value, want) 244 | } 245 | } 246 | 247 | func TestAliceblue(t *testing.T) { 248 | value := Aliceblue() 249 | want := RGBHex(240, 248, 255) 250 | if value != want { 251 | t.Errorf("%v != %v", value, want) 252 | } 253 | } 254 | 255 | func TestAntiquewhite(t *testing.T) { 256 | value := Antiquewhite() 257 | want := RGBHex(250, 235, 215) 258 | if value != want { 259 | t.Errorf("%v != %v", value, want) 260 | } 261 | } 262 | 263 | func TestAqua(t *testing.T) { 264 | value := Aqua() 265 | want := RGBHex(0, 255, 255) 266 | if value != want { 267 | t.Errorf("%v != %v", value, want) 268 | } 269 | } 270 | 271 | func TestAquamarine(t *testing.T) { 272 | value := Aquamarine() 273 | want := RGBHex(127, 255, 212) 274 | if value != want { 275 | t.Errorf("%v != %v", value, want) 276 | } 277 | } 278 | 279 | func TestAzure(t *testing.T) { 280 | value := Azure() 281 | want := RGBHex(240, 255, 255) 282 | if value != want { 283 | t.Errorf("%v != %v", value, want) 284 | } 285 | } 286 | 287 | func TestBeige(t *testing.T) { 288 | value := Beige() 289 | want := RGBHex(245, 245, 220) 290 | if value != want { 291 | t.Errorf("%v != %v", value, want) 292 | } 293 | } 294 | 295 | func TestBisque(t *testing.T) { 296 | value := Bisque() 297 | want := RGBHex(255, 228, 196) 298 | if value != want { 299 | t.Errorf("%v != %v", value, want) 300 | } 301 | } 302 | 303 | func TestBlack(t *testing.T) { 304 | value := Black() 305 | want := RGBHex(0, 0, 0) 306 | if value != want { 307 | t.Errorf("%v != %v", value, want) 308 | } 309 | } 310 | 311 | func TestBlanchedalmond(t *testing.T) { 312 | value := Blanchedalmond() 313 | want := RGBHex(255, 235, 205) 314 | if value != want { 315 | t.Errorf("%v != %v", value, want) 316 | } 317 | } 318 | 319 | func TestBlue(t *testing.T) { 320 | value := Blue() 321 | want := RGBHex(0, 0, 255) 322 | if value != want { 323 | t.Errorf("%v != %v", value, want) 324 | } 325 | } 326 | 327 | func TestBurlywood(t *testing.T) { 328 | value := Burlywood() 329 | want := RGBHex(222, 184, 135) 330 | if value != want { 331 | t.Errorf("%v != %v", value, want) 332 | } 333 | } 334 | 335 | func TestCadetblue(t *testing.T) { 336 | value := Cadetblue() 337 | want := RGBHex(95, 158, 160) 338 | if value != want { 339 | t.Errorf("%v != %v", value, want) 340 | } 341 | } 342 | 343 | func TestChartreuse(t *testing.T) { 344 | value := Chartreuse() 345 | want := RGBHex(127, 255, 0) 346 | if value != want { 347 | t.Errorf("%v != %v", value, want) 348 | } 349 | } 350 | 351 | func TestChocolate(t *testing.T) { 352 | value := Chocolate() 353 | want := RGBHex(210, 105, 30) 354 | if value != want { 355 | t.Errorf("%v != %v", value, want) 356 | } 357 | } 358 | 359 | func TestCoral(t *testing.T) { 360 | value := Coral() 361 | want := RGBHex(255, 127, 80) 362 | if value != want { 363 | t.Errorf("%v != %v", value, want) 364 | } 365 | } 366 | 367 | func TestCornflowerblue(t *testing.T) { 368 | value := Cornflowerblue() 369 | want := RGBHex(100, 149, 237) 370 | if value != want { 371 | t.Errorf("%v != %v", value, want) 372 | } 373 | } 374 | 375 | func TestCornsilk(t *testing.T) { 376 | value := Cornsilk() 377 | want := RGBHex(255, 248, 220) 378 | if value != want { 379 | t.Errorf("%v != %v", value, want) 380 | } 381 | } 382 | 383 | func TestCrimson(t *testing.T) { 384 | value := Crimson() 385 | want := RGBHex(220, 20, 60) 386 | if value != want { 387 | t.Errorf("%v != %v", value, want) 388 | } 389 | } 390 | 391 | func TestCyan(t *testing.T) { 392 | value := Cyan() 393 | want := RGBHex(0, 255, 255) 394 | if value != want { 395 | t.Errorf("%v != %v", value, want) 396 | } 397 | } 398 | 399 | func TestDarkblue(t *testing.T) { 400 | value := Darkblue() 401 | want := RGBHex(0, 0, 139) 402 | if value != want { 403 | t.Errorf("%v != %v", value, want) 404 | } 405 | } 406 | 407 | func TestDarkcyan(t *testing.T) { 408 | value := Darkcyan() 409 | want := RGBHex(0, 139, 139) 410 | if value != want { 411 | t.Errorf("%v != %v", value, want) 412 | } 413 | } 414 | 415 | func TestDarkgoldenrod(t *testing.T) { 416 | value := Darkgoldenrod() 417 | want := RGBHex(184, 134, 11) 418 | if value != want { 419 | t.Errorf("%v != %v", value, want) 420 | } 421 | } 422 | 423 | func TestDarkgray(t *testing.T) { 424 | value := Darkgray() 425 | want := RGBHex(169, 169, 169) 426 | if value != want { 427 | t.Errorf("%v != %v", value, want) 428 | } 429 | } 430 | 431 | func TestDarkgreen(t *testing.T) { 432 | value := Darkgreen() 433 | want := RGBHex(0, 100, 0) 434 | if value != want { 435 | t.Errorf("%v != %v", value, want) 436 | } 437 | } 438 | 439 | func TestDarkgrey(t *testing.T) { 440 | value := Darkgrey() 441 | want := RGBHex(169, 169, 169) 442 | if value != want { 443 | t.Errorf("%v != %v", value, want) 444 | } 445 | } 446 | 447 | func TestDarkkhaki(t *testing.T) { 448 | value := Darkkhaki() 449 | want := RGBHex(189, 183, 107) 450 | if value != want { 451 | t.Errorf("%v != %v", value, want) 452 | } 453 | } 454 | 455 | func TestDarkmagenta(t *testing.T) { 456 | value := Darkmagenta() 457 | want := RGBHex(139, 0, 139) 458 | if value != want { 459 | t.Errorf("%v != %v", value, want) 460 | } 461 | } 462 | 463 | func TestDarkolivegreen(t *testing.T) { 464 | value := Darkolivegreen() 465 | want := RGBHex(85, 107, 47) 466 | if value != want { 467 | t.Errorf("%v != %v", value, want) 468 | } 469 | } 470 | 471 | func TestDarkorange(t *testing.T) { 472 | value := Darkorange() 473 | want := RGBHex(255, 140, 0) 474 | if value != want { 475 | t.Errorf("%v != %v", value, want) 476 | } 477 | } 478 | 479 | func TestDarkorchid(t *testing.T) { 480 | value := Darkorchid() 481 | want := RGBHex(153, 50, 204) 482 | if value != want { 483 | t.Errorf("%v != %v", value, want) 484 | } 485 | } 486 | 487 | func TestDarkred(t *testing.T) { 488 | value := Darkred() 489 | want := RGBHex(139, 0, 0) 490 | if value != want { 491 | t.Errorf("%v != %v", value, want) 492 | } 493 | } 494 | 495 | func TestDarksalmon(t *testing.T) { 496 | value := Darksalmon() 497 | want := RGBHex(233, 150, 122) 498 | if value != want { 499 | t.Errorf("%v != %v", value, want) 500 | } 501 | } 502 | 503 | func TestDarkseagreen(t *testing.T) { 504 | value := Darkseagreen() 505 | want := RGBHex(143, 188, 143) 506 | if value != want { 507 | t.Errorf("%v != %v", value, want) 508 | } 509 | } 510 | 511 | func TestDarkslateblue(t *testing.T) { 512 | value := Darkslateblue() 513 | want := RGBHex(72, 61, 139) 514 | if value != want { 515 | t.Errorf("%v != %v", value, want) 516 | } 517 | } 518 | 519 | func TestDarkslategray(t *testing.T) { 520 | value := Darkslategray() 521 | want := RGBHex(47, 79, 79) 522 | if value != want { 523 | t.Errorf("%v != %v", value, want) 524 | } 525 | } 526 | 527 | func TestDarkslategrey(t *testing.T) { 528 | value := Darkslategrey() 529 | want := RGBHex(47, 79, 79) 530 | if value != want { 531 | t.Errorf("%v != %v", value, want) 532 | } 533 | } 534 | 535 | func TestDarkturquoise(t *testing.T) { 536 | value := Darkturquoise() 537 | want := RGBHex(0, 206, 209) 538 | if value != want { 539 | t.Errorf("%v != %v", value, want) 540 | } 541 | } 542 | 543 | func TestDarkviolet(t *testing.T) { 544 | value := Darkviolet() 545 | want := RGBHex(148, 0, 211) 546 | if value != want { 547 | t.Errorf("%v != %v", value, want) 548 | } 549 | } 550 | 551 | func TestDeeppink(t *testing.T) { 552 | value := Deeppink() 553 | want := RGBHex(255, 20, 147) 554 | if value != want { 555 | t.Errorf("%v != %v", value, want) 556 | } 557 | } 558 | 559 | func TestDeepskyblue(t *testing.T) { 560 | value := Deepskyblue() 561 | want := RGBHex(0, 191, 255) 562 | if value != want { 563 | t.Errorf("%v != %v", value, want) 564 | } 565 | } 566 | 567 | func TestDimgray(t *testing.T) { 568 | value := Dimgray() 569 | want := RGBHex(105, 105, 105) 570 | if value != want { 571 | t.Errorf("%v != %v", value, want) 572 | } 573 | } 574 | 575 | func TestDimgrey(t *testing.T) { 576 | value := Dimgrey() 577 | want := RGBHex(105, 105, 105) 578 | if value != want { 579 | t.Errorf("%v != %v", value, want) 580 | } 581 | } 582 | 583 | func TestDodgerblue(t *testing.T) { 584 | value := Dodgerblue() 585 | want := RGBHex(30, 144, 255) 586 | if value != want { 587 | t.Errorf("%v != %v", value, want) 588 | } 589 | } 590 | 591 | func TestFirebrick(t *testing.T) { 592 | value := Firebrick() 593 | want := RGBHex(178, 34, 34) 594 | if value != want { 595 | t.Errorf("%v != %v", value, want) 596 | } 597 | } 598 | 599 | func TestFloralwhite(t *testing.T) { 600 | value := Floralwhite() 601 | want := RGBHex(255, 250, 240) 602 | if value != want { 603 | t.Errorf("%v != %v", value, want) 604 | } 605 | } 606 | 607 | func TestForestgreen(t *testing.T) { 608 | value := Forestgreen() 609 | want := RGBHex(34, 139, 34) 610 | if value != want { 611 | t.Errorf("%v != %v", value, want) 612 | } 613 | } 614 | 615 | func TestFuchsia(t *testing.T) { 616 | value := Fuchsia() 617 | want := RGBHex(255, 0, 255) 618 | if value != want { 619 | t.Errorf("%v != %v", value, want) 620 | } 621 | } 622 | 623 | func TestGainsboro(t *testing.T) { 624 | value := Gainsboro() 625 | want := RGBHex(220, 220, 220) 626 | if value != want { 627 | t.Errorf("%v != %v", value, want) 628 | } 629 | } 630 | 631 | func TestGhostwhite(t *testing.T) { 632 | value := Ghostwhite() 633 | want := RGBHex(248, 248, 255) 634 | if value != want { 635 | t.Errorf("%v != %v", value, want) 636 | } 637 | } 638 | 639 | func TestGold(t *testing.T) { 640 | value := Gold() 641 | want := RGBHex(255, 215, 0) 642 | if value != want { 643 | t.Errorf("%v != %v", value, want) 644 | } 645 | } 646 | 647 | func TestGoldenrod(t *testing.T) { 648 | value := Goldenrod() 649 | want := RGBHex(218, 165, 32) 650 | if value != want { 651 | t.Errorf("%v != %v", value, want) 652 | } 653 | } 654 | 655 | func TestGray(t *testing.T) { 656 | value := Gray() 657 | want := RGBHex(128, 128, 128) 658 | if value != want { 659 | t.Errorf("%v != %v", value, want) 660 | } 661 | } 662 | 663 | func TestGreen(t *testing.T) { 664 | value := Green() 665 | want := RGBHex(0, 128, 0) 666 | if value != want { 667 | t.Errorf("%v != %v", value, want) 668 | } 669 | } 670 | 671 | func TestGreenyellow(t *testing.T) { 672 | value := Greenyellow() 673 | want := RGBHex(173, 255, 47) 674 | if value != want { 675 | t.Errorf("%v != %v", value, want) 676 | } 677 | } 678 | 679 | func TestHoneydew(t *testing.T) { 680 | value := Honeydew() 681 | want := RGBHex(240, 255, 240) 682 | if value != want { 683 | t.Errorf("%v != %v", value, want) 684 | } 685 | } 686 | 687 | func TestHotpink(t *testing.T) { 688 | value := Hotpink() 689 | want := RGBHex(255, 105, 180) 690 | if value != want { 691 | t.Errorf("%v != %v", value, want) 692 | } 693 | } 694 | 695 | func TestIndianred(t *testing.T) { 696 | value := Indianred() 697 | want := RGBHex(205, 92, 92) 698 | if value != want { 699 | t.Errorf("%v != %v", value, want) 700 | } 701 | } 702 | 703 | func TestIndigo(t *testing.T) { 704 | value := Indigo() 705 | want := RGBHex(75, 0, 130) 706 | if value != want { 707 | t.Errorf("%v != %v", value, want) 708 | } 709 | } 710 | 711 | func TestIvory(t *testing.T) { 712 | value := Ivory() 713 | want := RGBHex(255, 255, 240) 714 | if value != want { 715 | t.Errorf("%v != %v", value, want) 716 | } 717 | } 718 | 719 | func TestKhaki(t *testing.T) { 720 | value := Khaki() 721 | want := RGBHex(240, 230, 140) 722 | if value != want { 723 | t.Errorf("%v != %v", value, want) 724 | } 725 | } 726 | 727 | func TestLavender(t *testing.T) { 728 | value := Lavender() 729 | want := RGBHex(230, 230, 250) 730 | if value != want { 731 | t.Errorf("%v != %v", value, want) 732 | } 733 | } 734 | 735 | func TestLavenderblush(t *testing.T) { 736 | value := Lavenderblush() 737 | want := RGBHex(255, 240, 245) 738 | if value != want { 739 | t.Errorf("%v != %v", value, want) 740 | } 741 | } 742 | 743 | func TestLawngreen(t *testing.T) { 744 | value := Lawngreen() 745 | want := RGBHex(124, 252, 0) 746 | if value != want { 747 | t.Errorf("%v != %v", value, want) 748 | } 749 | } 750 | 751 | func TestLemonchiffon(t *testing.T) { 752 | value := Lemonchiffon() 753 | want := RGBHex(255, 250, 205) 754 | if value != want { 755 | t.Errorf("%v != %v", value, want) 756 | } 757 | } 758 | 759 | func TestLightblue(t *testing.T) { 760 | value := Lightblue() 761 | want := RGBHex(173, 216, 230) 762 | if value != want { 763 | t.Errorf("%v != %v", value, want) 764 | } 765 | } 766 | 767 | func TestLightcoral(t *testing.T) { 768 | value := Lightcoral() 769 | want := RGBHex(240, 128, 128) 770 | if value != want { 771 | t.Errorf("%v != %v", value, want) 772 | } 773 | } 774 | 775 | func TestLightcyan(t *testing.T) { 776 | value := Lightcyan() 777 | want := RGBHex(224, 255, 255) 778 | if value != want { 779 | t.Errorf("%v != %v", value, want) 780 | } 781 | } 782 | 783 | func TestLightgoldenrodyellow(t *testing.T) { 784 | value := Lightgoldenrodyellow() 785 | want := RGBHex(250, 250, 210) 786 | if value != want { 787 | t.Errorf("%v != %v", value, want) 788 | } 789 | } 790 | 791 | func TestLightgray(t *testing.T) { 792 | value := Lightgray() 793 | want := RGBHex(211, 211, 211) 794 | if value != want { 795 | t.Errorf("%v != %v", value, want) 796 | } 797 | } 798 | 799 | func TestLightgreen(t *testing.T) { 800 | value := Lightgreen() 801 | want := RGBHex(144, 238, 144) 802 | if value != want { 803 | t.Errorf("%v != %v", value, want) 804 | } 805 | } 806 | 807 | func TestLightgrey(t *testing.T) { 808 | value := Lightgrey() 809 | want := RGBHex(211, 211, 211) 810 | if value != want { 811 | t.Errorf("%v != %v", value, want) 812 | } 813 | } 814 | 815 | func TestLightpink(t *testing.T) { 816 | value := Lightpink() 817 | want := RGBHex(255, 182, 193) 818 | if value != want { 819 | t.Errorf("%v != %v", value, want) 820 | } 821 | } 822 | 823 | func TestLightsalmon(t *testing.T) { 824 | value := Lightsalmon() 825 | want := RGBHex(255, 160, 122) 826 | if value != want { 827 | t.Errorf("%v != %v", value, want) 828 | } 829 | } 830 | 831 | func TestLightseagreen(t *testing.T) { 832 | value := Lightseagreen() 833 | want := RGBHex(32, 178, 170) 834 | if value != want { 835 | t.Errorf("%v != %v", value, want) 836 | } 837 | } 838 | 839 | func TestLightskyblue(t *testing.T) { 840 | value := Lightskyblue() 841 | want := RGBHex(135, 206, 250) 842 | if value != want { 843 | t.Errorf("%v != %v", value, want) 844 | } 845 | } 846 | 847 | func TestLightslategray(t *testing.T) { 848 | value := Lightslategray() 849 | want := RGBHex(119, 136, 153) 850 | if value != want { 851 | t.Errorf("%v != %v", value, want) 852 | } 853 | } 854 | 855 | func TestLightslategrey(t *testing.T) { 856 | value := Lightslategrey() 857 | want := RGBHex(119, 136, 153) 858 | if value != want { 859 | t.Errorf("%v != %v", value, want) 860 | } 861 | } 862 | 863 | func TestLightsteelblue(t *testing.T) { 864 | value := Lightsteelblue() 865 | want := RGBHex(176, 196, 222) 866 | if value != want { 867 | t.Errorf("%v != %v", value, want) 868 | } 869 | } 870 | 871 | func TestLightyellow(t *testing.T) { 872 | value := Lightyellow() 873 | want := RGBHex(255, 255, 224) 874 | if value != want { 875 | t.Errorf("%v != %v", value, want) 876 | } 877 | } 878 | 879 | func TestLime(t *testing.T) { 880 | value := Lime() 881 | want := RGBHex(0, 255, 0) 882 | if value != want { 883 | t.Errorf("%v != %v", value, want) 884 | } 885 | } 886 | 887 | func TestLimegreen(t *testing.T) { 888 | value := Limegreen() 889 | want := RGBHex(50, 205, 50) 890 | if value != want { 891 | t.Errorf("%v != %v", value, want) 892 | } 893 | } 894 | 895 | func TestLinen(t *testing.T) { 896 | value := Linen() 897 | want := RGBHex(250, 240, 230) 898 | if value != want { 899 | t.Errorf("%v != %v", value, want) 900 | } 901 | } 902 | 903 | func TestMagenta(t *testing.T) { 904 | value := Magenta() 905 | want := RGBHex(255, 0, 255) 906 | if value != want { 907 | t.Errorf("%v != %v", value, want) 908 | } 909 | } 910 | 911 | func TestMaroon(t *testing.T) { 912 | value := Maroon() 913 | want := RGBHex(128, 0, 0) 914 | if value != want { 915 | t.Errorf("%v != %v", value, want) 916 | } 917 | } 918 | 919 | func TestMediumaquamarine(t *testing.T) { 920 | value := Mediumaquamarine() 921 | want := RGBHex(102, 205, 170) 922 | if value != want { 923 | t.Errorf("%v != %v", value, want) 924 | } 925 | } 926 | 927 | func TestMediumblue(t *testing.T) { 928 | value := Mediumblue() 929 | want := RGBHex(0, 0, 205) 930 | if value != want { 931 | t.Errorf("%v != %v", value, want) 932 | } 933 | } 934 | 935 | func TestMediumorchid(t *testing.T) { 936 | value := Mediumorchid() 937 | want := RGBHex(186, 85, 211) 938 | if value != want { 939 | t.Errorf("%v != %v", value, want) 940 | } 941 | } 942 | 943 | func TestMediumpurple(t *testing.T) { 944 | value := Mediumpurple() 945 | want := RGBHex(147, 112, 219) 946 | if value != want { 947 | t.Errorf("%v != %v", value, want) 948 | } 949 | } 950 | 951 | func TestMediumseagreen(t *testing.T) { 952 | value := Mediumseagreen() 953 | want := RGBHex(60, 179, 113) 954 | if value != want { 955 | t.Errorf("%v != %v", value, want) 956 | } 957 | } 958 | 959 | func TestMediumslateblue(t *testing.T) { 960 | value := Mediumslateblue() 961 | want := RGBHex(123, 104, 238) 962 | if value != want { 963 | t.Errorf("%v != %v", value, want) 964 | } 965 | } 966 | 967 | func TestMediumspringgreen(t *testing.T) { 968 | value := Mediumspringgreen() 969 | want := RGBHex(0, 250, 154) 970 | if value != want { 971 | t.Errorf("%v != %v", value, want) 972 | } 973 | } 974 | 975 | func TestMediumturquoise(t *testing.T) { 976 | value := Mediumturquoise() 977 | want := RGBHex(72, 209, 204) 978 | if value != want { 979 | t.Errorf("%v != %v", value, want) 980 | } 981 | } 982 | 983 | func TestMediumvioletred(t *testing.T) { 984 | value := Mediumvioletred() 985 | want := RGBHex(199, 21, 133) 986 | if value != want { 987 | t.Errorf("%v != %v", value, want) 988 | } 989 | } 990 | 991 | func TestMidnightblue(t *testing.T) { 992 | value := Midnightblue() 993 | want := RGBHex(25, 25, 112) 994 | if value != want { 995 | t.Errorf("%v != %v", value, want) 996 | } 997 | } 998 | 999 | func TestMintcream(t *testing.T) { 1000 | value := Mintcream() 1001 | want := RGBHex(245, 255, 250) 1002 | if value != want { 1003 | t.Errorf("%v != %v", value, want) 1004 | } 1005 | } 1006 | 1007 | func TestMistyrose(t *testing.T) { 1008 | value := Mistyrose() 1009 | want := RGBHex(255, 228, 225) 1010 | if value != want { 1011 | t.Errorf("%v != %v", value, want) 1012 | } 1013 | } 1014 | 1015 | func TestMoccasin(t *testing.T) { 1016 | value := Moccasin() 1017 | want := RGBHex(255, 228, 181) 1018 | if value != want { 1019 | t.Errorf("%v != %v", value, want) 1020 | } 1021 | } 1022 | 1023 | func TestNavajowhite(t *testing.T) { 1024 | value := Navajowhite() 1025 | want := RGBHex(255, 222, 173) 1026 | if value != want { 1027 | t.Errorf("%v != %v", value, want) 1028 | } 1029 | } 1030 | 1031 | func TestNavy(t *testing.T) { 1032 | value := Navy() 1033 | want := RGBHex(0, 0, 128) 1034 | if value != want { 1035 | t.Errorf("%v != %v", value, want) 1036 | } 1037 | } 1038 | 1039 | func TestOldlace(t *testing.T) { 1040 | value := Oldlace() 1041 | want := RGBHex(253, 245, 230) 1042 | if value != want { 1043 | t.Errorf("%v != %v", value, want) 1044 | } 1045 | } 1046 | 1047 | func TestOlive(t *testing.T) { 1048 | value := Olive() 1049 | want := RGBHex(128, 128, 0) 1050 | if value != want { 1051 | t.Errorf("%v != %v", value, want) 1052 | } 1053 | } 1054 | 1055 | func TestOlivedrab(t *testing.T) { 1056 | value := Olivedrab() 1057 | want := RGBHex(107, 142, 35) 1058 | if value != want { 1059 | t.Errorf("%v != %v", value, want) 1060 | } 1061 | } 1062 | 1063 | func TestOrange(t *testing.T) { 1064 | value := Orange() 1065 | want := RGBHex(255, 165, 0) 1066 | if value != want { 1067 | t.Errorf("%v != %v", value, want) 1068 | } 1069 | } 1070 | 1071 | func TestOrangered(t *testing.T) { 1072 | value := Orangered() 1073 | want := RGBHex(255, 69, 0) 1074 | if value != want { 1075 | t.Errorf("%v != %v", value, want) 1076 | } 1077 | } 1078 | 1079 | func TestOrchid(t *testing.T) { 1080 | value := Orchid() 1081 | want := RGBHex(218, 112, 214) 1082 | if value != want { 1083 | t.Errorf("%v != %v", value, want) 1084 | } 1085 | } 1086 | 1087 | func TestPalegoldenrod(t *testing.T) { 1088 | value := Palegoldenrod() 1089 | want := RGBHex(238, 232, 170) 1090 | if value != want { 1091 | t.Errorf("%v != %v", value, want) 1092 | } 1093 | } 1094 | 1095 | func TestPalegreen(t *testing.T) { 1096 | value := Palegreen() 1097 | want := RGBHex(152, 251, 152) 1098 | if value != want { 1099 | t.Errorf("%v != %v", value, want) 1100 | } 1101 | } 1102 | 1103 | func TestPaleturquoise(t *testing.T) { 1104 | value := Paleturquoise() 1105 | want := RGBHex(175, 238, 238) 1106 | if value != want { 1107 | t.Errorf("%v != %v", value, want) 1108 | } 1109 | } 1110 | 1111 | func TestPalevioletred(t *testing.T) { 1112 | value := Palevioletred() 1113 | want := RGBHex(219, 112, 147) 1114 | if value != want { 1115 | t.Errorf("%v != %v", value, want) 1116 | } 1117 | } 1118 | 1119 | func TestPapayawhip(t *testing.T) { 1120 | value := Papayawhip() 1121 | want := RGBHex(255, 239, 213) 1122 | if value != want { 1123 | t.Errorf("%v != %v", value, want) 1124 | } 1125 | } 1126 | 1127 | func TestPeachpuff(t *testing.T) { 1128 | value := Peachpuff() 1129 | want := RGBHex(255, 218, 185) 1130 | if value != want { 1131 | t.Errorf("%v != %v", value, want) 1132 | } 1133 | } 1134 | 1135 | func TestPeru(t *testing.T) { 1136 | value := Peru() 1137 | want := RGBHex(205, 133, 63) 1138 | if value != want { 1139 | t.Errorf("%v != %v", value, want) 1140 | } 1141 | } 1142 | 1143 | func TestPink(t *testing.T) { 1144 | value := Pink() 1145 | want := RGBHex(255, 192, 203) 1146 | if value != want { 1147 | t.Errorf("%v != %v", value, want) 1148 | } 1149 | } 1150 | 1151 | func TestPlum(t *testing.T) { 1152 | value := Plum() 1153 | want := RGBHex(221, 160, 221) 1154 | if value != want { 1155 | t.Errorf("%v != %v", value, want) 1156 | } 1157 | } 1158 | 1159 | func TestPowderblue(t *testing.T) { 1160 | value := Powderblue() 1161 | want := RGBHex(176, 224, 230) 1162 | if value != want { 1163 | t.Errorf("%v != %v", value, want) 1164 | } 1165 | } 1166 | 1167 | func TestPurple(t *testing.T) { 1168 | value := Purple() 1169 | want := RGBHex(128, 0, 128) 1170 | if value != want { 1171 | t.Errorf("%v != %v", value, want) 1172 | } 1173 | } 1174 | 1175 | func TestRebeccapurple(t *testing.T) { 1176 | value := Rebeccapurple() 1177 | want := RGBHex(102, 51, 153) 1178 | if value != want { 1179 | t.Errorf("%v != %v", value, want) 1180 | } 1181 | } 1182 | 1183 | func TestRed(t *testing.T) { 1184 | value := Red() 1185 | want := RGBHex(255, 0, 0) 1186 | if value != want { 1187 | t.Errorf("%v != %v", value, want) 1188 | } 1189 | } 1190 | 1191 | func TestRosybrown(t *testing.T) { 1192 | value := Rosybrown() 1193 | want := RGBHex(188, 143, 143) 1194 | if value != want { 1195 | t.Errorf("%v != %v", value, want) 1196 | } 1197 | } 1198 | 1199 | func TestRoyalblue(t *testing.T) { 1200 | value := Royalblue() 1201 | want := RGBHex(65, 105, 225) 1202 | if value != want { 1203 | t.Errorf("%v != %v", value, want) 1204 | } 1205 | } 1206 | 1207 | func TestSaddlebrown(t *testing.T) { 1208 | value := Saddlebrown() 1209 | want := RGBHex(139, 69, 19) 1210 | if value != want { 1211 | t.Errorf("%v != %v", value, want) 1212 | } 1213 | } 1214 | 1215 | func TestSalmon(t *testing.T) { 1216 | value := Salmon() 1217 | want := RGBHex(250, 128, 114) 1218 | if value != want { 1219 | t.Errorf("%v != %v", value, want) 1220 | } 1221 | } 1222 | 1223 | func TestSandybrown(t *testing.T) { 1224 | value := Sandybrown() 1225 | want := RGBHex(244, 164, 96) 1226 | if value != want { 1227 | t.Errorf("%v != %v", value, want) 1228 | } 1229 | } 1230 | 1231 | func TestSeagreen(t *testing.T) { 1232 | value := Seagreen() 1233 | want := RGBHex(46, 139, 87) 1234 | if value != want { 1235 | t.Errorf("%v != %v", value, want) 1236 | } 1237 | } 1238 | 1239 | func TestSeashell(t *testing.T) { 1240 | value := Seashell() 1241 | want := RGBHex(255, 245, 238) 1242 | if value != want { 1243 | t.Errorf("%v != %v", value, want) 1244 | } 1245 | } 1246 | 1247 | func TestSienna(t *testing.T) { 1248 | value := Sienna() 1249 | want := RGBHex(160, 82, 45) 1250 | if value != want { 1251 | t.Errorf("%v != %v", value, want) 1252 | } 1253 | } 1254 | 1255 | func TestSilver(t *testing.T) { 1256 | value := Silver() 1257 | want := RGBHex(192, 192, 192) 1258 | if value != want { 1259 | t.Errorf("%v != %v", value, want) 1260 | } 1261 | } 1262 | 1263 | func TestSkyblue(t *testing.T) { 1264 | value := Skyblue() 1265 | want := RGBHex(135, 206, 235) 1266 | if value != want { 1267 | t.Errorf("%v != %v", value, want) 1268 | } 1269 | } 1270 | 1271 | func TestSlateblue(t *testing.T) { 1272 | value := Slateblue() 1273 | want := RGBHex(106, 90, 205) 1274 | if value != want { 1275 | t.Errorf("%v != %v", value, want) 1276 | } 1277 | } 1278 | 1279 | func TestSlategray(t *testing.T) { 1280 | value := Slategray() 1281 | want := RGBHex(112, 128, 144) 1282 | if value != want { 1283 | t.Errorf("%v != %v", value, want) 1284 | } 1285 | } 1286 | 1287 | func TestSlategrey(t *testing.T) { 1288 | value := Slategrey() 1289 | want := RGBHex(112, 128, 144) 1290 | if value != want { 1291 | t.Errorf("%v != %v", value, want) 1292 | } 1293 | } 1294 | 1295 | func TestSnow(t *testing.T) { 1296 | value := Snow() 1297 | want := RGBHex(255, 250, 250) 1298 | if value != want { 1299 | t.Errorf("%v != %v", value, want) 1300 | } 1301 | } 1302 | 1303 | func TestSpringgreen(t *testing.T) { 1304 | value := Springgreen() 1305 | want := RGBHex(0, 255, 127) 1306 | if value != want { 1307 | t.Errorf("%v != %v", value, want) 1308 | } 1309 | } 1310 | 1311 | func TestSteelblue(t *testing.T) { 1312 | value := Steelblue() 1313 | want := RGBHex(70, 130, 180) 1314 | if value != want { 1315 | t.Errorf("%v != %v", value, want) 1316 | } 1317 | } 1318 | 1319 | func TestTan(t *testing.T) { 1320 | value := Tan() 1321 | want := RGBHex(210, 180, 140) 1322 | if value != want { 1323 | t.Errorf("%v != %v", value, want) 1324 | } 1325 | } 1326 | 1327 | func TestTeal(t *testing.T) { 1328 | value := Teal() 1329 | want := RGBHex(0, 128, 128) 1330 | if value != want { 1331 | t.Errorf("%v != %v", value, want) 1332 | } 1333 | } 1334 | 1335 | func TestThistle(t *testing.T) { 1336 | value := Thistle() 1337 | want := RGBHex(216, 191, 216) 1338 | if value != want { 1339 | t.Errorf("%v != %v", value, want) 1340 | } 1341 | } 1342 | 1343 | func TestTomato(t *testing.T) { 1344 | value := Tomato() 1345 | want := RGBHex(255, 99, 71) 1346 | if value != want { 1347 | t.Errorf("%v != %v", value, want) 1348 | } 1349 | } 1350 | 1351 | func TestTurquoise(t *testing.T) { 1352 | value := Turquoise() 1353 | want := RGBHex(64, 224, 208) 1354 | if value != want { 1355 | t.Errorf("%v != %v", value, want) 1356 | } 1357 | } 1358 | 1359 | func TestViolet(t *testing.T) { 1360 | value := Violet() 1361 | want := RGBHex(238, 130, 238) 1362 | if value != want { 1363 | t.Errorf("%v != %v", value, want) 1364 | } 1365 | } 1366 | 1367 | func TestWheat(t *testing.T) { 1368 | value := Wheat() 1369 | want := RGBHex(245, 222, 179) 1370 | if value != want { 1371 | t.Errorf("%v != %v", value, want) 1372 | } 1373 | } 1374 | 1375 | func TestWhite(t *testing.T) { 1376 | value := White() 1377 | want := RGBHex(255, 255, 255) 1378 | if value != want { 1379 | t.Errorf("%v != %v", value, want) 1380 | } 1381 | } 1382 | 1383 | func TestWhitesmoke(t *testing.T) { 1384 | value := Whitesmoke() 1385 | want := RGBHex(245, 245, 245) 1386 | if value != want { 1387 | t.Errorf("%v != %v", value, want) 1388 | } 1389 | } 1390 | 1391 | func TestYellow(t *testing.T) { 1392 | value := Yellow() 1393 | want := RGBHex(255, 255, 0) 1394 | if value != want { 1395 | t.Errorf("%v != %v", value, want) 1396 | } 1397 | } 1398 | 1399 | func TestYellowgreen(t *testing.T) { 1400 | value := Yellowgreen() 1401 | want := RGBHex(154, 205, 50) 1402 | if value != want { 1403 | t.Errorf("%v != %v", value, want) 1404 | } 1405 | } 1406 | -------------------------------------------------------------------------------- /drawing.go: -------------------------------------------------------------------------------- 1 | package blgo 2 | 3 | import ( 4 | "math" 5 | "math/rand" 6 | 7 | "github.com/bit101/blgo/blmath" 8 | "github.com/bit101/blgo/floodfill" 9 | "github.com/bit101/blgo/geom" 10 | ) 11 | 12 | // Plot draws a single pixel. 13 | func (s *Surface) Plot(p *geom.Point) { 14 | s.Save() 15 | s.Translate(p.X, p.Y) 16 | s.FillRectangle(-0.5, -0.5, 1, 1) 17 | s.Restore() 18 | } 19 | 20 | //////////////////////////////////////// 21 | // Line 22 | //////////////////////////////////////// 23 | 24 | // Line draws a line between two x, y points. 25 | func (s *Surface) Line(x0, y0, x1, y1 float64) { 26 | s.MoveTo(x0, y0) 27 | s.LineTo(x1, y1) 28 | s.Stroke() 29 | } 30 | 31 | // LineThrough draws a line through two x, y points. 32 | func (s *Surface) LineThrough(x0, y0, x1, y1, overlap float64) { 33 | s.Save() 34 | s.Translate(x0, y0) 35 | s.Rotate(math.Atan2(y1-y0, x1-x0)) 36 | p2 := math.Hypot(x0-x1, y0-y1) 37 | 38 | s.MoveTo(-overlap, 0) 39 | s.LineTo(p2+overlap, 0) 40 | s.Stroke() 41 | s.Restore() 42 | } 43 | 44 | //////////////////////////////////////// 45 | // Ray 46 | //////////////////////////////////////// 47 | 48 | // Ray draws a line at an angle. 49 | func (s *Surface) Ray(x, y, angle, offset, length float64) { 50 | s.Save() 51 | s.Translate(x, y) 52 | s.Rotate(angle) 53 | s.MoveTo(offset, 0) 54 | s.LineTo(offset+length, 0) 55 | s.Stroke() 56 | s.Restore() 57 | } 58 | 59 | //////////////////////////////////////// 60 | // Rectangle 61 | //////////////////////////////////////// 62 | 63 | // FillRectangle draws a filled rectancle. 64 | func (s *Surface) FillRectangle(x, y, w, h float64) { 65 | s.Rectangle(x, y, w, h) 66 | s.Fill() 67 | } 68 | 69 | // StrokeRectangle draws a stroked rectangle. 70 | func (s *Surface) StrokeRectangle(x, y, w, h float64) { 71 | s.Rectangle(x, y, w, h) 72 | s.Stroke() 73 | } 74 | 75 | //////////////////////////////////////// 76 | // RoundRectangle 77 | //////////////////////////////////////// 78 | 79 | // RoundRectangle draws a rounded rectangle. 80 | func (s *Surface) RoundRectangle(x, y, w, h, r float64) { 81 | s.MoveTo(x+r, y) 82 | s.LineTo(x+w-r, y) 83 | s.Arc(x+w-r, y+r, r, -blmath.HalfPi, 0.0) 84 | s.LineTo(x+w, y+h-r) 85 | s.Arc(x+w-r, y+h-r, r, 0.0, blmath.HalfPi) 86 | s.LineTo(x+r, y+h) 87 | s.Arc(x+r, y+h-r, r, blmath.HalfPi, math.Pi) 88 | s.LineTo(x, y+r) 89 | s.Arc(x+r, y+r, r, math.Pi, -blmath.HalfPi) 90 | } 91 | 92 | // StrokeRoundRectangle draws a stroked, rounded rectangle. 93 | func (s *Surface) StrokeRoundRectangle(x, y, w, h, r float64) { 94 | s.RoundRectangle(x, y, w, h, r) 95 | s.Stroke() 96 | } 97 | 98 | // FillRoundRectangle draws a filled, rounded rectangle. 99 | func (s *Surface) FillRoundRectangle(x, y, w, h, r float64) { 100 | s.RoundRectangle(x, y, w, h, r) 101 | s.Fill() 102 | } 103 | 104 | //////////////////////////////////////// 105 | // Circle 106 | //////////////////////////////////////// 107 | 108 | // Circle draws a circle 109 | func (s *Surface) Circle(x, y, r float64) { 110 | s.Arc(x, y, r, 0.0, blmath.TwoPi) 111 | } 112 | 113 | // FillCircle draws a filled circle. 114 | func (s *Surface) FillCircle(x, y, r float64) { 115 | s.Circle(x, y, r) 116 | s.Fill() 117 | } 118 | 119 | // StrokeCircle draws a stroked circle. 120 | func (s *Surface) StrokeCircle(x, y, r float64) { 121 | s.Circle(x, y, r) 122 | s.Stroke() 123 | } 124 | 125 | //////////////////////////////////////// 126 | // Ellipse 127 | //////////////////////////////////////// 128 | 129 | // Ellipse draws an ellipse. 130 | func (s *Surface) Ellipse(x, y, xr, yr float64) { 131 | if xr == 0 || yr == 0 { 132 | return 133 | } 134 | s.Save() 135 | s.Translate(x, y) 136 | s.Scale(xr, yr) 137 | s.Circle(0.0, 0.0, 1.0) 138 | s.Restore() 139 | } 140 | 141 | // FillEllipse draws a filled ellipse. 142 | func (s *Surface) FillEllipse(x, y, xr, yr float64) { 143 | s.Ellipse(x, y, xr, yr) 144 | s.Fill() 145 | } 146 | 147 | // StrokeEllipse draws a stroked ellipse. 148 | func (s *Surface) StrokeEllipse(x, y, xr, yr float64) { 149 | s.Ellipse(x, y, xr, yr) 150 | s.Stroke() 151 | } 152 | 153 | //////////////////////////////////////// 154 | // Path 155 | //////////////////////////////////////// 156 | 157 | // Path draws a path of points. 158 | func (s *Surface) Path(points []*geom.Point) { 159 | for _, point := range points { 160 | s.LineTo(point.X, point.Y) 161 | } 162 | } 163 | 164 | // FillPath draws a filled path of points. 165 | func (s *Surface) FillPath(points []*geom.Point) { 166 | s.Path(points) 167 | s.Fill() 168 | } 169 | 170 | // StrokePath draws a stroked path of points. 171 | func (s *Surface) StrokePath(points []*geom.Point, close bool) { 172 | s.Path(points) 173 | if close { 174 | s.ClosePath() 175 | } 176 | s.Stroke() 177 | } 178 | 179 | //////////////////////////////////////// 180 | // Polygon 181 | //////////////////////////////////////// 182 | 183 | // Polygon draws a polygon. 184 | func (s *Surface) Polygon(x, y, r float64, sides int, rotation float64) { 185 | s.Save() 186 | s.Translate(x, y) 187 | s.Rotate(rotation) 188 | s.MoveTo(r, 0.0) 189 | for i := 0; i < sides; i++ { 190 | angle := blmath.TwoPi / float64(sides) * float64(i) 191 | s.LineTo(math.Cos(angle)*r, math.Sin(angle)*r) 192 | } 193 | s.LineTo(r, 0.0) 194 | s.Restore() 195 | } 196 | 197 | // StrokePolygon draws a stroked polygon. 198 | func (s *Surface) StrokePolygon(x, y, r float64, sides int, rotation float64) { 199 | s.Polygon(x, y, r, sides, rotation) 200 | s.Stroke() 201 | } 202 | 203 | // FillPolygon draws a filled polygon. 204 | func (s *Surface) FillPolygon(x, y, r float64, sides int, rotation float64) { 205 | s.Polygon(x, y, r, sides, rotation) 206 | s.Fill() 207 | } 208 | 209 | //////////////////////////////////////// 210 | // Star 211 | //////////////////////////////////////// 212 | 213 | // Star draws a star. 214 | func (s *Surface) Star(x, y, r0, r1 float64, points int, rotation float64) { 215 | s.Save() 216 | s.Translate(x, y) 217 | s.Rotate(rotation) 218 | for i := 0; i < points*2; i++ { 219 | r := r1 220 | if i%2 == 1 { 221 | r = r0 222 | } 223 | angle := math.Pi / float64(points) * float64(i) 224 | s.LineTo(math.Cos(angle)*r, math.Sin(angle)*r) 225 | } 226 | s.ClosePath() 227 | s.Restore() 228 | } 229 | 230 | // StrokeStar draws a stroked star. 231 | func (s *Surface) StrokeStar(x, y, r0, r1 float64, points int, rotation float64) { 232 | s.Star(x, y, r0, r1, points, rotation) 233 | s.Stroke() 234 | } 235 | 236 | // FillStar draws a filled star. 237 | func (s *Surface) FillStar(x, y, r0, r1 float64, points int, rotation float64) { 238 | s.Star(x, y, r0, r1, points, rotation) 239 | s.Fill() 240 | } 241 | 242 | //////////////////////////////////////// 243 | // Splat 244 | //////////////////////////////////////// 245 | 246 | // Splat draws a splat. 247 | func (s *Surface) Splat( 248 | x, y float64, 249 | numNodes int, 250 | radius, innerRadius, variation float64, 251 | ) { 252 | var points []*geom.Point 253 | slice := blmath.TwoPi / float64(numNodes*2) 254 | angle := 0.0 255 | curve := 0.3 256 | radiusRange := radius - innerRadius 257 | variation = blmath.Clamp(variation, 0.0, 1.0) 258 | for i := 0; i < numNodes; i++ { 259 | radius := radius + variation*(rand.Float64()*radiusRange*2.0-radiusRange) 260 | radiusRange := radius - innerRadius 261 | points = append(points, makePoint(angle-slice*(1.0+curve), innerRadius)) 262 | points = append(points, makePoint(angle+slice*curve, innerRadius)) 263 | points = append(points, makePoint(angle-slice*curve, innerRadius+radiusRange*0.8)) 264 | points = append(points, makePoint(angle+slice/2.0, radius)) 265 | points = append(points, makePoint(angle+slice*(1.0+curve), innerRadius+radiusRange*0.8)) 266 | angle += slice * 2.0 267 | } 268 | 269 | s.Save() 270 | s.Translate(x, y) 271 | s.MultiLoop(points) 272 | s.Restore() 273 | 274 | } 275 | 276 | func makePoint(angle, radius float64) *geom.Point { 277 | return geom.NewPoint( 278 | math.Cos(angle)*radius, 279 | math.Sin(angle)*radius, 280 | ) 281 | } 282 | 283 | // StrokeSplat draws a stroked splat 284 | func (s *Surface) StrokeSplat( 285 | x, y float64, 286 | numNodes int, 287 | radius, innerRadius, variation float64, 288 | ) { 289 | s.Splat(x, y, numNodes, radius, innerRadius, variation) 290 | s.Stroke() 291 | } 292 | 293 | // FillSplat draws a filled splat. 294 | func (s *Surface) FillSplat( 295 | x, y float64, 296 | numNodes int, 297 | radius, innerRadius, variation float64, 298 | ) { 299 | s.Splat(x, y, numNodes, radius, innerRadius, variation) 300 | s.Fill() 301 | } 302 | 303 | //////////////////////////////////////// 304 | // FractalLine 305 | //////////////////////////////////////// 306 | 307 | // FractalLine draws a fractal line. 308 | func (s *Surface) FractalLine(x1, y1, x2, y2, roughness float64, iterations int) { 309 | dx := x2 - x1 310 | dy := y2 - y1 311 | offset := math.Sqrt(dx*dx+dy*dy) * 0.15 312 | 313 | var path []*geom.Point 314 | path = append(path, geom.NewPoint(x1, y1)) 315 | path = append(path, geom.NewPoint(x2, y2)) 316 | 317 | for i := 0; i < iterations; i++ { 318 | var newPath []*geom.Point 319 | for j, point := range path { 320 | newPath = append(newPath, geom.NewPoint(point.X, point.Y)) 321 | if j < len(path)-1 { 322 | x := (point.X+path[j+1].X)/2.0 + rand.Float64()*offset*2.0 - offset 323 | y := (point.Y+path[j+1].Y)/2.0 + rand.Float64()*offset*2.0 - offset 324 | newPath = append(newPath, geom.NewPoint(x, y)) 325 | } 326 | } 327 | offset *= roughness 328 | path = newPath 329 | } 330 | s.Path(path) 331 | } 332 | 333 | func (s *Surface) strokeFractalLine(x1, y1, x2, y2, roughness float64, iterations int) { 334 | s.FractalLine(x1, y1, x2, y2, roughness, iterations) 335 | s.Stroke() 336 | } 337 | 338 | //////////////////////////////////////// 339 | // Heart 340 | //////////////////////////////////////// 341 | 342 | // Heart draws a heart shape. 343 | func (s *Surface) Heart(x, y, w, h, r float64) { 344 | s.Save() 345 | s.Translate(x, y) 346 | s.Rotate(r) 347 | var path []*geom.Point 348 | res := math.Sqrt(w * h) 349 | for i := 0; i < int(res); i++ { 350 | a := blmath.TwoPi * float64(i) / res 351 | x := w * math.Pow(math.Sin(a), 3.0) 352 | y := h*(0.8125*math.Cos(a) - 0.3125*math.Cos(2.0*a) - 0.125*math.Cos(3.0*a) - 0.0625*math.Cos(4.0*a)) 353 | path = append(path, geom.NewPoint(x, -y)) 354 | } 355 | s.Path(path) 356 | s.Restore() 357 | } 358 | 359 | // FillHeart draws a filled heart shape. 360 | func (s *Surface) FillHeart(x, y, w, h, r float64) { 361 | s.Heart(x, y, w, h, r) 362 | s.Fill() 363 | } 364 | 365 | // StrokeHeart draws a stroked heart shape. 366 | func (s *Surface) StrokeHeart(x, y, w, h, r float64) { 367 | s.Heart(x, y, w, h, r) 368 | s.Stroke() 369 | } 370 | 371 | //////////////////////////////////////// 372 | // Points 373 | //////////////////////////////////////// 374 | 375 | // Points draws a number of points. 376 | func (s *Surface) Points(points []*geom.Point, radius float64) { 377 | for _, point := range points { 378 | s.FillCircle(point.X, point.Y, radius) 379 | } 380 | } 381 | 382 | //////////////////////////////////////// 383 | // CurveTo 384 | //////////////////////////////////////// 385 | 386 | // StrokeCurveTo draws a stroked curve. 387 | func (s *Surface) StrokeCurveTo(x0, y0, x1, y1, x2, y2 float64) { 388 | s.CurveTo(x0, y0, x1, y1, x2, y2) 389 | s.Stroke() 390 | } 391 | 392 | //////////////////////////////////////// 393 | // QuadraticCurveTo 394 | //////////////////////////////////////// 395 | 396 | // QuadraticCurveTo draws a quadratic curve to two points. 397 | func (s *Surface) QuadraticCurveTo(x0, y0, x1, y1 float64) { 398 | px, py := s.GetCurrentPoint() 399 | s.CurveTo( 400 | 2.0/3.0*x0+1.0/3.0*px, 401 | 2.0/3.0*y0+1.0/3.0*py, 402 | 2.0/3.0*x0+1.0/3.0*x1, 403 | 2.0/3.0*y0+1.0/3.0*y1, 404 | x1, y1, 405 | ) 406 | } 407 | 408 | // StrokeQuadraticCurveTo draws a stroked quadratic curve. 409 | func (s *Surface) StrokeQuadraticCurveTo(x0, y0, x1, y1 float64) { 410 | s.QuadraticCurveTo(x0, y0, x1, y1) 411 | s.Stroke() 412 | } 413 | 414 | //////////////////////////////////////// 415 | // MultiCurve 416 | //////////////////////////////////////// 417 | 418 | // MultiCurve draws a smooth curve between a set of points. 419 | func (s *Surface) MultiCurve(points []*geom.Point) { 420 | s.MoveTo(points[0].X, points[0].Y) 421 | s.LineTo( 422 | (points[0].X+points[1].X)/2.0, 423 | (points[0].Y+points[1].Y)/2.0, 424 | ) 425 | i := 1 426 | for i < len(points)-1 { 427 | p0 := points[i] 428 | p1 := points[i+1] 429 | midx := (p0.X + p1.X) / 2.0 430 | midy := (p0.Y + p1.Y) / 2.0 431 | s.QuadraticCurveTo(p0.X, p0.Y, midx, midy) 432 | i = i + 1 433 | 434 | } 435 | p := points[len(points)-1] 436 | s.LineTo(p.X, p.Y) 437 | } 438 | 439 | // StrokeMultiCurve draws a stroked curve between a set of points. 440 | func (s *Surface) StrokeMultiCurve(points []*geom.Point) { 441 | s.MultiCurve(points) 442 | s.Stroke() 443 | } 444 | 445 | //////////////////////////////////////// 446 | // MultiLoop 447 | //////////////////////////////////////// 448 | 449 | // MultiLoop draws a smooth, closed curve between a set of points. 450 | func (s *Surface) MultiLoop(points []*geom.Point) { 451 | pA := points[0] 452 | pZ := points[len(points)-1] 453 | mid1x := (pZ.X + pA.X) / 2.0 454 | mid1y := (pZ.Y + pA.Y) / 2.0 455 | s.MoveTo(mid1x, mid1y) 456 | for i := 0; i < len(points)-1; i++ { 457 | p0 := points[i] 458 | p1 := points[i+1] 459 | midx := (p0.X + p1.X) / 2.0 460 | midy := (p0.Y + p1.Y) / 2.0 461 | s.QuadraticCurveTo(p0.X, p0.Y, midx, midy) 462 | } 463 | s.QuadraticCurveTo(pZ.X, pZ.Y, mid1x, mid1y) 464 | } 465 | 466 | // FillMultiLoop draws a filled, smooth, closed curve between a set of points. 467 | func (s *Surface) FillMultiLoop(points []*geom.Point) { 468 | s.MultiLoop(points) 469 | s.Fill() 470 | } 471 | 472 | // StrokeMultiLoop draws a stroked, smooth, closed curve between a set of points. 473 | func (s *Surface) StrokeMultiLoop(points []*geom.Point) { 474 | s.MultiLoop(points) 475 | s.Stroke() 476 | } 477 | 478 | //////////////////////////////////////// 479 | // FloodFill 480 | //////////////////////////////////////// 481 | 482 | // FloodFill fills adjacent pixels with a specified color 483 | func (s *Surface) FloodFill(x, y, r, g, b, threshold float64) { 484 | floodfill.FloodFill(s, x, y, r, g, b, threshold) 485 | } 486 | 487 | //////////////////////////////////////// 488 | // Grid 489 | //////////////////////////////////////// 490 | 491 | // Grid draws a grid. 492 | func (s *Surface) Grid(x, y, w, h, xres, yres float64) { 493 | xx := x 494 | yy := y 495 | for xx <= x+w { 496 | s.MoveTo(xx, y) 497 | s.LineTo(xx, y+h) 498 | xx += xres 499 | } 500 | for yy <= y+h { 501 | s.MoveTo(x, yy) 502 | s.LineTo(x+w, yy) 503 | yy += yres 504 | } 505 | s.Stroke() 506 | } 507 | 508 | //////////////////////////////////////// 509 | // Grid 510 | //////////////////////////////////////// 511 | 512 | // HexGrid draws a hexagonal grid 513 | func (s *Surface) HexGrid(x, y, w, h, res0, res1 float64) { 514 | sin60r := math.Sin(math.Pi/3.0) * res0 515 | xInc := 2.0 * sin60r 516 | yInc := res0 * 1.5 517 | offset := 0.0 518 | 519 | for yy := y; yy < y+h+yInc; yy += yInc { 520 | for xx := x; xx < x+w+xInc; xx += xInc { 521 | s.Polygon(xx+offset, yy, res1, 6, math.Pi/2) 522 | } 523 | if offset == 0 { 524 | offset = sin60r 525 | } else { 526 | offset = 0 527 | } 528 | } 529 | } 530 | 531 | func (s *Surface) FillHexGrid(x, y, w, h, res0, res1 float64) { 532 | s.Save() 533 | s.Rectangle(x, y, w, h) 534 | s.Clip() 535 | s.HexGrid(x, y, w, h, res0, res1) 536 | s.Fill() 537 | s.Restore() 538 | } 539 | 540 | func (s *Surface) StrokeHexGrid(x, y, w, h, res0, res1 float64) { 541 | s.Save() 542 | s.Rectangle(x, y, w, h) 543 | s.Clip() 544 | s.HexGrid(x, y, w, h, res0, res1) 545 | s.Stroke() 546 | s.Restore() 547 | } 548 | -------------------------------------------------------------------------------- /easing/easing.go: -------------------------------------------------------------------------------- 1 | package easing 2 | 3 | import "math" 4 | 5 | // LinearEase interpolates between a start and end value linearly. 6 | func LinearEase(t, start, end float64) float64 { 7 | return start + (end-start)*t 8 | } 9 | 10 | // QuadraticEaseIn interpolates between a start and end value with a quadratic easing. 11 | func QuadraticEaseIn(t, start, end float64) float64 { 12 | t = t * t 13 | return start + (end-start)*t 14 | } 15 | 16 | // interpolates between a start and end value with a quadratic easing. 17 | func QuadraticEaseOut(t, start, end float64) float64 { 18 | t = -t * (t - 2) 19 | return start + (end-start)*t 20 | } 21 | 22 | // interpolates between a start and end value with a quadratic easing. 23 | func QuadraticEaseInOut(t, start, end float64) float64 { 24 | if t < 0.5 { 25 | t = 2 * t * t 26 | } else { 27 | t = -2*t*t + 4*t - 1 28 | } 29 | return start + (end-start)*t 30 | } 31 | 32 | // CubicEaseIn interpolates between a start and end value with a cubic easing. 33 | func CubicEaseIn(t, start, end float64) float64 { 34 | t = t * t * t 35 | return start + (end-start)*t 36 | } 37 | 38 | // CubicEaseOut interpolates between a start and end value with a cubic easing. 39 | func CubicEaseOut(t, start, end float64) float64 { 40 | t = math.Pow(t-1, 3) + 1 41 | return start + (end-start)*t 42 | } 43 | 44 | // CubicEaseInOut interpolates between a start and end value with a cubic easing. 45 | func CubicEaseInOut(t, start, end float64) float64 { 46 | if t < 0.5 { 47 | t = 4 * t * t * t 48 | } else { 49 | t = math.Pow(2*t-2, 3)*0.5 + 1 50 | } 51 | return start + (end-start)*t 52 | } 53 | 54 | // QuarticEaseIn interpolates between a start and end value with a quartic easing. 55 | func QuarticEaseIn(t, start, end float64) float64 { 56 | t = t * t * t * t 57 | return start + (end-start)*t 58 | } 59 | 60 | // QuarticEaseOut interpolates between a start and end value with a quartic easing. 61 | func QuarticEaseOut(t, start, end float64) float64 { 62 | t = math.Pow(t-1, 3)*(1-t) + 1 63 | return start + (end-start)*t 64 | } 65 | 66 | // QuarticEaseInOut interpolates between a start and end value with a quartic easing. 67 | func QuarticEaseInOut(t, start, end float64) float64 { 68 | if t < 0.5 { 69 | t = 8 * t * t * t * t 70 | } else { 71 | t = -math.Pow(t-1, 4)*8 + 1 72 | } 73 | return start + (end-start)*t 74 | } 75 | 76 | // QuinticEaseIn interpolates between a start and end value with a quintic easing. 77 | func QuinticEaseIn(t, start, end float64) float64 { 78 | t = t * t * t * t * t 79 | return start + (end-start)*t 80 | } 81 | 82 | // QuinticEaseOut interpolates between a start and end value with a quintic easing. 83 | func QuinticEaseOut(t, start, end float64) float64 { 84 | t = math.Pow(t-1, 5) + 1 85 | return start + (end-start)*t 86 | } 87 | 88 | // QuinticEaseInOut interpolates between a start and end value with a quintic easing. 89 | func QuinticEaseInOut(t, start, end float64) float64 { 90 | if t < 0.5 { 91 | t = 16 * t * t * t * t * t 92 | } else { 93 | t = math.Pow(2*t-2, 5)*0.5 + 1 94 | } 95 | return start + (end-start)*t 96 | } 97 | 98 | // SineEaseIn interpolates between a start and end value with a sine easing. 99 | func SineEaseIn(t, start, end float64) float64 { 100 | t = math.Sin((t-1)*math.Pi/2) + 1 101 | return start + (end-start)*t 102 | } 103 | 104 | // SineEaseOut interpolates between a start and end value with a sine easing. 105 | func SineEaseOut(t, start, end float64) float64 { 106 | t = math.Sin(t * math.Pi / 2) 107 | return start + (end-start)*t 108 | } 109 | 110 | // SineEaseInOut interpolates between a start and end value with a sine easing. 111 | func SineEaseInOut(t, start, end float64) float64 { 112 | t = 0.5 * (1 - math.Cos(t*math.Pi)) 113 | return start + (end-start)*t 114 | } 115 | 116 | // CircularEaseIn interpolates between a start and end value with a circular easing. 117 | func CircularEaseIn(t, start, end float64) float64 { 118 | t = 1 - math.Sqrt(1-t*t) 119 | return start + (end-start)*t 120 | } 121 | 122 | // CircularEaseOut interpolates between a start and end value with a circular easing. 123 | func CircularEaseOut(t, start, end float64) float64 { 124 | t = math.Sqrt(t * (2 - t)) 125 | return start + (end-start)*t 126 | } 127 | 128 | // CircularEaseInOut interpolates between a start and end value with a circular easing. 129 | func CircularEaseInOut(t, start, end float64) float64 { 130 | if t < 0.5 { 131 | t = 0.5 * (1 - math.Sqrt(1-4*t*t)) 132 | } else { 133 | t = 0.5 * (math.Sqrt((-2*t+3)*(2*t-1)) + 1) 134 | } 135 | return start + (end-start)*t 136 | } 137 | 138 | // ExponentialEaseIn interpolates between a start and end value with a exponential easing. 139 | func ExponentialEaseIn(t, start, end float64) float64 { 140 | if t != 0.0 { 141 | t = math.Pow(2, 10*(t-1)) 142 | } 143 | return start + (end-start)*t 144 | } 145 | 146 | // ExponentialEaseOut interpolates between a start and end value with a exponential easing. 147 | func ExponentialEaseOut(t, start, end float64) float64 { 148 | if t != 1.0 { 149 | t = 1 - math.Pow(2, -10*t) 150 | } 151 | return start + (end-start)*t 152 | } 153 | 154 | // ExponentialEaseInOut interpolates between a start and end value with a exponential easing. 155 | func ExponentialEaseInOut(t, start, end float64) float64 { 156 | if t != 0.0 && t != 1.0 { 157 | if t < 0.5 { 158 | t = 0.5 * math.Pow(2, 20*t-10) 159 | } else { 160 | t = -0.5*math.Pow(2, -20*t+10) + 1 161 | } 162 | } 163 | return start + (end-start)*t 164 | } 165 | 166 | // ElasticEaseIn interpolates between a start and end value with a elastic easing. 167 | func ElasticEaseIn(t, start, end float64) float64 { 168 | t = math.Sin(13*math.Pi/2*t) * math.Pow(2, 10*(t-1)) 169 | return start + (end-start)*t 170 | } 171 | 172 | // ElasticEaseOut interpolates between a start and end value with a elastic easing. 173 | func ElasticEaseOut(t, start, end float64) float64 { 174 | t = math.Sin(-13*math.Pi/2*(t+1))*math.Pow(2, -10*t) + 1 175 | return start + (end-start)*t 176 | } 177 | 178 | // ElasticEaseInOut interpolates between a start and end value with a elastic easing. 179 | func ElasticEaseInOut(t, start, end float64) float64 { 180 | if t < 0.5 { 181 | t = 0.5 * math.Sin(13*math.Pi*t) * math.Pow(2, 20*t-10) 182 | } else { 183 | t = 0.5 * (math.Sin(-13*math.Pi*t)*math.Pow(2, -20*t+10) + 2) 184 | } 185 | return start + (end-start)*t 186 | } 187 | 188 | // BackEaseIn interpolates between a start and end value with a back easing. 189 | func BackEaseIn(t, start, end float64) float64 { 190 | t = t*t*t - t*math.Sin(t*math.Pi) 191 | return start + (end-start)*t 192 | } 193 | 194 | // BackEaseOut interpolates between a start and end value with a back easing. 195 | func BackEaseOut(t, start, end float64) float64 { 196 | t = 1 - t 197 | t = 1 - (t*t*t - t*math.Sin(t*math.Pi)) 198 | return start + (end-start)*t 199 | } 200 | 201 | // BackEaseInOut interpolates between a start and end value with a back easing. 202 | func BackEaseInOut(t, start, end float64) float64 { 203 | if t < 0.5 { 204 | t = 2 * t 205 | t = 0.5 * (t*t*t - t*math.Sin(t*math.Pi)) 206 | } else { 207 | t = 1 - 2*t + 1 208 | t = 0.5*(1-t*t*t+t*math.Sin(t*math.Pi)) + 0.5 209 | } 210 | return start + (end-start)*t 211 | } 212 | 213 | // BounceEaseIn interpolates between a start and end value with a bounce easing. 214 | func BounceEaseIn(t, start, end float64) float64 { 215 | t = 1 - BounceEaseOut(1-t, 0, 1) 216 | return start + (end-start)*t 217 | } 218 | 219 | // BounceEaseOut interpolates between a start and end value with a bounce easing. 220 | func BounceEaseOut(t, start, end float64) float64 { 221 | if t < 4/11.0 { 222 | t = 121 * t * t / 16.0 223 | } else if t < 8/11.0 { 224 | t = 363/40.0*t*t - 99/10.0*t + 17/5.0 225 | } else if t < 9/10.0 { 226 | t = 4356/361.0*t*t - 35442/1805.0*t + 16061/1805.0 227 | } else { 228 | t = 54/5.0*t*t - 513/25.0*t + 268/25.0 229 | } 230 | return start + (end-start)*t 231 | } 232 | 233 | // BounceEaseInOut interpolates between a start and end value with a bounce easing. 234 | func BounceEaseInOut(t, start, end float64) float64 { 235 | if t < 0.5 { 236 | t = 0.5 * BounceEaseIn(t*2, 0, 1) 237 | } else { 238 | t = 0.5*BounceEaseOut(t*2-1, 0, 1) + 0.5 239 | } 240 | return start + (end-start)*t 241 | } 242 | -------------------------------------------------------------------------------- /floodfill/coord.go: -------------------------------------------------------------------------------- 1 | package floodfill 2 | 3 | type coord struct { 4 | index int 5 | x int 6 | y int 7 | } 8 | 9 | func newCoord(x, y, w int) coord { 10 | // we get width passed so we can precompute the data index 11 | index := (y*w + x) * 4 12 | return coord{index: index, x: x, y: y} 13 | } 14 | -------------------------------------------------------------------------------- /floodfill/floodfill.go: -------------------------------------------------------------------------------- 1 | package floodfill 2 | 3 | // Surface is an interface for a surface used for performing flood fills 4 | // Used to avoid import cycles 5 | type Surface interface { 6 | GetHeight() int 7 | GetWidth() int 8 | SetData([]byte) 9 | GetData() []byte 10 | } 11 | 12 | // FloodFill fills a surface from the specified point with the specified color 13 | func FloodFill(surface Surface, x, y, r, g, b float64, threshold float64) { 14 | // surface uses float values, but pixel data is all int values. convert. 15 | xi, yi := int(x), int(y) 16 | w, h := surface.GetWidth(), surface.GetHeight() 17 | 18 | // if pixel outside of image, return 19 | if xi < 0 || xi >= w || yi < 0 || yi >= h { 20 | return 21 | } 22 | 23 | // more int conversions 24 | ri, gi, bi, th := int(r*255), int(g*255), int(b*255), int(threshold*255) 25 | 26 | data := surface.GetData() 27 | nextCoord := newCoord(xi, yi, w) 28 | 29 | // color of target color and replacement color for fill 30 | targetRGB := getRGB(data, nextCoord) 31 | replacementRGB := newRGB(ri, gi, bi) 32 | 33 | // target color is already replacement color, return 34 | if targetRGB.isEqual(replacementRGB, th) { 35 | return 36 | } 37 | 38 | // change color of first pixel to replacement color, add coord to queue 39 | setRGB(data, nextCoord, replacementRGB) 40 | var queue []coord 41 | queue = append(queue, nextCoord) 42 | 43 | // helper func. if coord is in bounds and has target color, change to replacement color and add to queue 44 | checkNeighbor := func(x, y int) { 45 | neighbor := newCoord(x, y, w) 46 | if neighbor.x < w && neighbor.x >= 0 && neighbor.y < h && neighbor.y >= 0 && 47 | getRGB(data, neighbor).isEqual(targetRGB, th) { 48 | 49 | setRGB(data, neighbor, replacementRGB) 50 | queue = append(queue, neighbor) 51 | } 52 | } 53 | 54 | // while there are items in the queue 55 | for len(queue) > 0 { 56 | // shift the first one off (it's already been colored 57 | nextCoord, queue = queue[0], queue[1:] 58 | 59 | // check four surrounding coords 60 | checkNeighbor(nextCoord.x+1, nextCoord.y) 61 | checkNeighbor(nextCoord.x-1, nextCoord.y) 62 | checkNeighbor(nextCoord.x, nextCoord.y+1) 63 | checkNeighbor(nextCoord.x, nextCoord.y-1) 64 | } 65 | 66 | // queue is empty. rewrite data 67 | surface.SetData(data) 68 | } 69 | -------------------------------------------------------------------------------- /floodfill/rgb.go: -------------------------------------------------------------------------------- 1 | package floodfill 2 | 3 | type rgb struct { 4 | r int 5 | g int 6 | b int 7 | } 8 | 9 | func newRGB(r, g, b int) rgb { 10 | return rgb{ 11 | r: r, 12 | g: g, 13 | b: b, 14 | } 15 | } 16 | 17 | func (p rgb) isEqual(q rgb, max int) bool { 18 | return abs(p.r, q.r) <= max && abs(p.g, q.g) <= max && abs(p.b, q.b) <= max 19 | } 20 | 21 | func abs(a, b int) int { 22 | diff := a - b 23 | if diff < 0 { 24 | return -diff 25 | } 26 | return diff 27 | } 28 | 29 | func getRGB(data []byte, c coord) rgb { 30 | // stored as b, g, r, a 31 | return newRGB(int(data[c.index+2]), int(data[c.index+1]), int(data[c.index])) 32 | } 33 | 34 | func setRGB(data []byte, c coord, replacementRGB rgb) { 35 | // stored as b, g, r, a 36 | data[c.index+2] = byte(replacementRGB.r) 37 | data[c.index+1] = byte(replacementRGB.g) 38 | data[c.index] = byte(replacementRGB.b) 39 | } 40 | -------------------------------------------------------------------------------- /geom/circle.go: -------------------------------------------------------------------------------- 1 | package geom 2 | 3 | // Circle is a struct for holding a circle. 4 | type Circle struct { 5 | Center *Point 6 | Radius float64 7 | } 8 | 9 | // NewCircle creates a new circle. 10 | func NewCircle(x float64, y float64, r float64) *Circle { 11 | return &Circle{ 12 | NewPoint(x, y), 13 | r, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /geom/geom.go: -------------------------------------------------------------------------------- 1 | package geom 2 | 3 | import ( 4 | "errors" 5 | "math" 6 | ) 7 | 8 | // Distance returns the distance between two x,y positions 9 | func Distance(x0, y0, x1, y1 float64) float64 { 10 | return math.Hypot(x1-x0, y1-y0) 11 | } 12 | 13 | // DotProduct returns the dot product between two lines. 14 | func DotProduct(p0 *Point, p1 *Point, p2 *Point, p3 *Point) float64 { 15 | dx0 := p1.X - p0.X 16 | dy0 := p1.Y - p0.Y 17 | dx1 := p3.X - p2.X 18 | dy1 := p3.Y - p2.Y 19 | return dx0*dx1 + dy0*dy1 20 | } 21 | 22 | // AngleBetween returns the angle between two lines. 23 | func AngleBetween(p0 *Point, p1 *Point, p2 *Point, p3 *Point) float64 { 24 | dp := DotProduct(p0, p1, p2, p3) 25 | mag0 := p0.Distance(p1) 26 | mag1 := p2.Distance(p3) 27 | return math.Acos(dp / mag0 / mag1) 28 | } 29 | 30 | // InRect returns whether or not an x, y point is within a rectangle. 31 | func InRect(xp, yp, x, y, w, h float64) bool { 32 | return xp >= x && xp <= x+w && yp >= y && yp <= y+h 33 | } 34 | 35 | // PointInRect returns whether or not an x, y point is within a rectangle. 36 | func PointInRect(p *Point, x, y, w, h float64) bool { 37 | return InRect(p.X, p.Y, x, y, w, h) 38 | } 39 | 40 | // InCircle returns whether or not an x, y point is within a circle. 41 | func InCircle(xp, yp, x, y, r float64) bool { 42 | dx := xp - x 43 | dy := yp - y 44 | return math.Sqrt(dx*dx+dy*dy) <= r 45 | } 46 | 47 | // PointInCircle returns whether or not an x, y point is within a circle. 48 | func PointInCircle(p *Point, x, y, r float64) bool { 49 | return InCircle(p.X, p.Y, x, y, r) 50 | } 51 | 52 | // XYToPolar returns an angle and distance to origin from a given x, y location. 53 | func XYToPolar(x, y float64) (float64, float64) { 54 | return math.Atan2(y, x), math.Hypot(x, y) 55 | } 56 | 57 | // BezierPoint calculates a point along a Bezier curve. 58 | func BezierPoint(p0 *Point, p1 *Point, p2 *Point, p3 *Point, t float64) *Point { 59 | oneMinusT := 1.0 - t 60 | m0 := oneMinusT * oneMinusT * oneMinusT 61 | m1 := 3.0 * oneMinusT * oneMinusT * t 62 | m2 := 3.0 * oneMinusT * t * t 63 | m3 := t * t * t 64 | return &Point{ 65 | m0*p0.X + m1*p1.X + m2*p2.X + m3*p3.X, 66 | m0*p0.Y + m1*p1.Y + m2*p2.Y + m3*p3.Y, 67 | } 68 | } 69 | 70 | // QuadraticPoint calculated a point along a quadratic Bezier curve. 71 | func QuadraticPoint(p0 *Point, p1 *Point, p2 *Point, t float64) *Point { 72 | oneMinusT := 1.0 - t 73 | m0 := oneMinusT * oneMinusT 74 | m1 := 2.0 * oneMinusT * t 75 | m2 := t * t 76 | return &Point{ 77 | m0*p0.X + m1*p1.X + m2*p2.X, 78 | m0*p0.Y + m1*p1.Y + m2*p2.Y, 79 | } 80 | } 81 | 82 | // SegmentIntersect returns whether or not two line segments cross. 83 | func SegmentIntersect(p0 *Point, p1 *Point, p2 *Point, p3 *Point) (*Point, error) { 84 | a1 := p1.Y - p0.Y 85 | b1 := p0.X - p1.X 86 | c1 := a1*p0.X + b1*p0.Y 87 | a2 := p3.Y - p2.Y 88 | b2 := p2.X - p3.X 89 | c2 := a2*p2.X + b2*p2.Y 90 | denominator := a1*b2 - a2*b1 91 | 92 | if denominator == 0.0 { 93 | return &Point{}, errors.New("nope") 94 | } 95 | intersectX := (b2*c1 - b1*c2) / denominator 96 | intersectY := (a1*c2 - a2*c1) / denominator 97 | rx0 := (intersectX - p0.X) / (p1.X - p0.X) 98 | ry0 := (intersectY - p0.Y) / (p1.Y - p0.Y) 99 | rx1 := (intersectX - p2.X) / (p3.X - p2.X) 100 | ry1 := (intersectY - p2.Y) / (p3.Y - p2.Y) 101 | 102 | if ((rx0 >= 0.0 && rx0 <= 1.0) || (ry0 >= 0.0 && ry0 <= 1.0)) && 103 | ((rx1 >= 0.0 && rx1 <= 1.0) || (ry1 >= 0.0 && ry1 <= 1.0)) { 104 | return &Point{ 105 | intersectX, 106 | intersectY, 107 | }, nil 108 | } 109 | return &Point{}, errors.New("nope") 110 | } 111 | 112 | // TangentPointToCircle calculates the point at which a line extending from a point will contact a circle. 113 | func TangentPointToCircle(point *Point, circle *Circle, anticlockwise bool) *Point { 114 | d := point.Distance(circle.Center) 115 | dir := -1.0 116 | if anticlockwise { 117 | dir = 1.0 118 | } 119 | angle := math.Cos(-circle.Radius/d) * dir 120 | baseAngle := math.Atan2(circle.Center.Y-point.Y, circle.Center.X-point.X) 121 | totalAngle := baseAngle + angle 122 | 123 | return &Point{ 124 | circle.Center.X + math.Cos(totalAngle)*circle.Radius, 125 | circle.Center.Y + math.Sin(totalAngle)*circle.Radius, 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /geom/geom_test.go: -------------------------------------------------------------------------------- 1 | package geom 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | ) 7 | 8 | func TestDotProduct(t *testing.T) { 9 | p0 := NewPoint(0, -1) 10 | p1 := NewPoint(0, 1) 11 | p2 := NewPoint(-1, 0) 12 | p3 := NewPoint(1, 0) 13 | 14 | result := DotProduct(p0, p1, p2, p3) 15 | if result != 0 { 16 | t.Errorf("DotProduct of 90 degrees != 0") 17 | } 18 | 19 | p0.X = -0.1 20 | result = DotProduct(p0, p1, p2, p3) 21 | if result <= 0 { 22 | t.Errorf("DotProduct of > 90 degrees not > 0") 23 | } 24 | 25 | p0.X = 0.1 26 | result = DotProduct(p0, p1, p2, p3) 27 | if result >= 0 { 28 | t.Errorf("DotProduct of < 90 degrees not < 0") 29 | } 30 | } 31 | 32 | func TestAngleBetween(t *testing.T) { 33 | p0 := NewPoint(0, -1) 34 | p1 := NewPoint(0, 0) 35 | p2 := NewPoint(1, 0) 36 | 37 | result := AngleBetween(p1, p0, p1, p2) 38 | if result != math.Pi/2 { 39 | t.Errorf("angle between points not 90 degrees") 40 | } 41 | 42 | p0.X = -0.1 43 | result = AngleBetween(p1, p0, p1, p2) 44 | if result <= math.Pi/2 { 45 | t.Errorf("angle between points not > 90 degrees") 46 | } 47 | 48 | p0.X = 0.1 49 | result = AngleBetween(p1, p0, p1, p2) 50 | if result >= math.Pi/2 { 51 | t.Errorf("angle between points not < 90 degrees") 52 | } 53 | } 54 | 55 | func TestLerpPoint(t *testing.T) { 56 | p0 := NewPoint(0, 0) 57 | p1 := NewPoint(100, -200) 58 | var tests = []struct { 59 | t float64 60 | wantX float64 61 | wantY float64 62 | }{ 63 | {0, 0, 0}, 64 | {1, 100, -200}, 65 | {0.5, 50, -100}, 66 | } 67 | 68 | for _, test := range tests { 69 | result := LerpPoint(test.t, p0, p1) 70 | if result.X != test.wantX || result.Y != test.wantY { 71 | t.Errorf("LerpPoint(%f, %v, %v) != %f, %f", test.t, p0, p1, test.wantX, test.wantY) 72 | } 73 | } 74 | 75 | } 76 | 77 | func TestMapRectangle(t *testing.T) { 78 | r0 := NewRectangle(0, 0, 100, 100) 79 | r1 := NewRectangle(200, 200, 400, 400) 80 | var tests = []struct { 81 | r0 *Rectangle 82 | r1 *Rectangle 83 | x float64 84 | y float64 85 | wantX float64 86 | wantY float64 87 | }{ 88 | {r0, r1, 0, 0, 200, 200}, 89 | {r0, r1, 100, 100, 600, 600}, 90 | {r0, r1, 0, 100, 200, 600}, 91 | {r0, r1, 100, 0, 600, 200}, 92 | {r0, r1, 50, 50, 400, 400}, 93 | {r0, r1, 25, 75, 300, 500}, 94 | } 95 | 96 | for _, test := range tests { 97 | x, y := MapRectangle(test.x, test.y, test.r0, test.r1) 98 | if x != test.wantX || y != test.wantY { 99 | t.Errorf("MapRectangle(%f, %f, %v, %v) != %f, %f", test.x, test.y, test.r0, test.r1, x, y) 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /geom/point.go: -------------------------------------------------------------------------------- 1 | package geom 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/bit101/blgo/blmath" 7 | "github.com/bit101/blgo/random" 8 | ) 9 | 10 | // Point 2d point 11 | type Point struct { 12 | X float64 13 | Y float64 14 | } 15 | 16 | // NewPoint creates a new 2d point 17 | func NewPoint(x float64, y float64) *Point { 18 | return &Point{ 19 | X: x, 20 | Y: y, 21 | } 22 | } 23 | 24 | // LerpPoint linearly interpolates between two points. 25 | func LerpPoint(t float64, p0 *Point, p1 *Point) *Point { 26 | return &Point{ 27 | blmath.Lerp(t, p0.X, p1.X), 28 | blmath.Lerp(t, p0.Y, p1.Y), 29 | } 30 | } 31 | 32 | // RandomPoint returns a point within the rectangle defined in the params x, y, w, h. 33 | func RandomPoint(x, y, w, h float64) *Point { 34 | return &Point{ 35 | X: random.FloatRange(x, x+w), 36 | Y: random.FloatRange(y, y+h), 37 | } 38 | } 39 | 40 | func RandomPolarPoint(x, y, r float64) *Point { 41 | angle := random.FloatRange(0, math.Pi*2) 42 | radius := random.FloatRange(0, r) 43 | return &Point{ 44 | X: x + math.Cos(angle)*radius, 45 | Y: y + math.Sin(angle)*radius, 46 | } 47 | } 48 | 49 | // RandomPointInTriangle returns a randomly generated point within the triangle described by the given points. 50 | func RandomPointInTriangle(A, B, C *Point) *Point { 51 | s := random.Float() 52 | t := random.Float() 53 | a := 1.0 - math.Sqrt(t) 54 | b := (1.0 - s) * math.Sqrt(t) 55 | c := s * math.Sqrt(t) 56 | return NewPoint(a*A.X+b*B.X+c*C.X, a*A.Y+b*B.Y+c*C.Y) 57 | } 58 | 59 | // FromPolar creates a new point from and angle and radius. 60 | func FromPolar(angle float64, radius float64) *Point { 61 | return &Point{math.Cos(angle) * radius, math.Sin(angle) * radius} 62 | } 63 | 64 | // Distance between this point and another point 65 | func (p *Point) Distance(p1 *Point) float64 { 66 | return math.Hypot(p.X-p1.X, p.Y-p1.Y) 67 | } 68 | 69 | // Magnitude is distance from origin to this point 70 | func (p *Point) Magnitude() float64 { 71 | return math.Hypot(p.X, p.Y) 72 | } 73 | 74 | // Angle returns the angle from the origin to this point. 75 | func (p *Point) Angle() float64 { 76 | return math.Atan2(p.Y, p.X) 77 | } 78 | 79 | // Translate moves this point on the x and y axes. 80 | func (p *Point) Translate(x float64, y float64) { 81 | p.X += x 82 | p.Y += y 83 | } 84 | 85 | // Scale scales this point on the x and y axes. 86 | func (p *Point) Scale(scaleX float64, scaleY float64) { 87 | p.X *= scaleX 88 | p.Y *= scaleY 89 | } 90 | 91 | // Rotate rotates this point around the origin. 92 | func (p *Point) Rotate(angle float64) { 93 | x := p.X*math.Cos(angle) + p.Y*math.Sin(angle) 94 | y := p.Y*math.Cos(angle) - p.X*math.Sin(angle) 95 | p.X = x 96 | p.Y = y 97 | } 98 | -------------------------------------------------------------------------------- /geom/rectangle.go: -------------------------------------------------------------------------------- 1 | package geom 2 | 3 | import "github.com/bit101/blgo/blmath" 4 | 5 | type Rectangle struct { 6 | X, Y, W, H float64 7 | } 8 | 9 | func NewRectangle(x, y, w, h float64) *Rectangle { 10 | return &Rectangle{x, y, w, h} 11 | } 12 | 13 | func MapRectangle(x, y float64, sRect, dRect *Rectangle) (float64, float64) { 14 | xx := blmath.Map(x, sRect.X, sRect.X+sRect.W, dRect.X, dRect.X+dRect.W) 15 | yy := blmath.Map(y, sRect.Y, sRect.Y+sRect.H, dRect.Y, dRect.Y+dRect.H) 16 | return xx, yy 17 | } 18 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bit101/blgo 2 | 3 | go 1.14 4 | 5 | require github.com/bit101/go-cairo v1.0.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bit101/go-cairo v1.0.0 h1:So7dRXutHrRNV/lhNafTTAKwYP0MXAQdcF0d4/8HDBE= 2 | github.com/bit101/go-cairo v1.0.0/go.mod h1:DcWQM9/iziHK80FGTZbA/1eTvXL9STRistJwCRcSsEo= 3 | -------------------------------------------------------------------------------- /logdisplay/logdisplay.go: -------------------------------------------------------------------------------- 1 | package logdisplay 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/bit101/blgo" 7 | "github.com/bit101/blgo/color" 8 | ) 9 | 10 | // LogDisplay represents a bit array. Pixel values are incremented or set directly. 11 | // Max value is kept track of 12 | // The value of each pixel is then log10(value) / log10(max). 13 | type LogDisplay struct { 14 | surface *blgo.Surface 15 | width, height int 16 | values []float64 17 | max float64 18 | } 19 | 20 | // NewLogDisplay creates a new LogDisplay struct. 21 | func NewLogDisplay(surface *blgo.Surface) *LogDisplay { 22 | width := int(surface.Width) 23 | height := int(surface.Height) 24 | return &LogDisplay{ 25 | surface: surface, 26 | width: width, 27 | height: height, 28 | max: 0.0, 29 | values: make([]float64, width*height), 30 | } 31 | } 32 | 33 | // Inc increments the raw value of a pixel by 1. 34 | func (d *LogDisplay) Inc(x, y float64) { 35 | xx, yy := int(x), int(y) 36 | if xx >= 0 && xx < d.width && yy >= 0 && yy < d.height { 37 | index := xx + yy*d.width 38 | value := d.values[index] + 1 39 | d.values[index] = value 40 | if value > d.max { 41 | d.max = value 42 | } 43 | } 44 | } 45 | 46 | // Get calculates the logarithmic value of the pixel. 47 | func (d *LogDisplay) Get(x, y int) float64 { 48 | xx, yy := x, y 49 | value := d.values[xx+yy*d.width] 50 | return math.Log(value) / math.Log(d.max) 51 | } 52 | 53 | func (d *LogDisplay) RenderGrey() { 54 | d.Render(color.Grey) 55 | } 56 | 57 | func (d *LogDisplay) RenderHSV(min, max float64) { 58 | d.Render(func(g float64) color.Color { 59 | h := g*(max-min) + min 60 | return color.HSV(h, 1, g) 61 | }) 62 | } 63 | 64 | func (d *LogDisplay) Render(colorFunc func(float64) color.Color) { 65 | for x := 0; x < d.width; x++ { 66 | for y := 0; y < d.height; y++ { 67 | g := d.Get(x, y) 68 | d.surface.SetSourceColor(colorFunc(g)) 69 | d.surface.FillRectangle(float64(x), float64(y), 1, 1) 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /logdisplay/logdisplayfull.go: -------------------------------------------------------------------------------- 1 | package logdisplay 2 | 3 | import "math" 4 | 5 | // LogDisplayFilled represents a bit array. All elements are filled with arbitrary values. 6 | // Max and min values are kept track of. 7 | // The value of each pixel is then log10(value - min) / log10(max - min). 8 | type LogDisplayFilled struct { 9 | width, height int 10 | values []float64 11 | max, min float64 12 | } 13 | 14 | // NewLogDisplayFilled creates a new LogDisplayFilled struct. 15 | func NewLogDisplayFilled(width, height int) *LogDisplayFilled { 16 | return &LogDisplayFilled{ 17 | width: width, 18 | height: height, 19 | max: 0.0, 20 | min: math.MaxFloat64, 21 | values: make([]float64, width*height), 22 | } 23 | } 24 | 25 | // Set sets the raw value of a pixel. 26 | func (d *LogDisplayFilled) Set(value, x, y float64) { 27 | xx, yy := int(x), int(y) 28 | if xx >= 0 && xx < d.width && yy >= 0 && yy < d.height { 29 | index := xx + yy*d.width 30 | d.values[index] = value 31 | if value > d.max { 32 | d.max = value 33 | } 34 | if value < d.min { 35 | d.min = value 36 | } 37 | } 38 | } 39 | 40 | // Get calculates the logarithmic value of the pixel. 41 | func (d *LogDisplayFilled) Get(x, y float64) float64 { 42 | xx, yy := int(x), int(y) 43 | value := d.values[xx+yy*d.width] 44 | return math.Log(value-d.min) / math.Log(d.max-d.min) 45 | } 46 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | default: 2 | @go test ./... 3 | -------------------------------------------------------------------------------- /noise/perlin.go: -------------------------------------------------------------------------------- 1 | package noise 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | // Perlin1 is 1d perlin noise 8 | func Perlin1(x float64) float64 { 9 | return Perlin(x, 0, 0) 10 | } 11 | 12 | // Perlin2 is 2d perlin noise 13 | func Perlin2(x, y float64) float64 { 14 | return Perlin(x, y, 0) 15 | } 16 | 17 | // Perlin is 3d perlin noise 18 | func Perlin(x, y, z float64) float64 { 19 | X := int(math.Floor(x)) & 255 20 | Y := int(math.Floor(y)) & 255 21 | Z := int(math.Floor(z)) & 255 22 | x -= math.Floor(x) 23 | y -= math.Floor(y) 24 | z -= math.Floor(z) 25 | u := fade(x) 26 | v := fade(y) 27 | w := fade(z) 28 | A := p[X] + Y 29 | AA := p[A] + Z 30 | AB := p[A+1] + Z 31 | B := p[X+1] + Y 32 | BA := p[B] + Z 33 | BB := p[B+1] + Z 34 | return lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z), 35 | grad(p[BA], x-1, y, z)), 36 | lerp(u, grad(p[AB], x, y-1, z), 37 | grad(p[BB], x-1, y-1, z))), 38 | lerp(v, lerp(u, grad(p[AA+1], x, y, z-1), 39 | grad(p[BA+1], x-1, y, z-1)), 40 | lerp(u, grad(p[AB+1], x, y-1, z-1), 41 | grad(p[BB+1], x-1, y-1, z-1)))) 42 | } 43 | func fade(t float64) float64 { return t * t * t * (t*(t*6-15) + 10) } 44 | func lerp(t, a, b float64) float64 { return a + t*(b-a) } 45 | func grad(hash int, x, y, z float64) float64 { 46 | // Go doesn't have a ternary. Ternaries can be translated directly 47 | // with if statements, but chains of if statements are often better 48 | // expressed with switch statements. 49 | switch hash & 15 { 50 | case 0, 12: 51 | return x + y 52 | case 1, 14: 53 | return y - x 54 | case 2: 55 | return x - y 56 | case 3: 57 | return -x - y 58 | case 4: 59 | return x + z 60 | case 5: 61 | return z - x 62 | case 6: 63 | return x - z 64 | case 7: 65 | return -x - z 66 | case 8: 67 | return y + z 68 | case 9, 13: 69 | return z - y 70 | case 10: 71 | return y - z 72 | } 73 | // case 11, 16: 74 | return -y - z 75 | } 76 | 77 | var permutation = []int{ 78 | 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 79 | 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 80 | 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 81 | 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 82 | 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 83 | 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 84 | 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 85 | 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 86 | 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 87 | 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 88 | 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 89 | 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 90 | 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 91 | 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 92 | 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 93 | 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, 94 | } 95 | var p = append(permutation, permutation...) 96 | 97 | // PerlinOct creates Perlin noise with given number of octaves. 98 | // persistence does well at 0.5 to start with. 99 | func PerlinOct(x, y, z float64, octaves int, persistence float64) float64 { 100 | total := 0.0 101 | frequency := 1.0 102 | amplitude := 1.0 103 | maxValue := 0.0 // Used for normalizing result to -1.0 - 1.0 104 | for i := 0; i < octaves; i++ { 105 | total += Perlin(x*frequency, y*frequency, z*frequency) * amplitude 106 | 107 | maxValue += amplitude 108 | 109 | amplitude *= persistence 110 | frequency *= 2 111 | } 112 | 113 | return total / maxValue 114 | } 115 | -------------------------------------------------------------------------------- /noise/simplex.go: -------------------------------------------------------------------------------- 1 | /* https://github.com/larspensjo/Go-simplex-noise */ 2 | 3 | /* SimplexNoise1234, Simplex noise with true analytic 4 | * derivative in 1D to 4D. 5 | * 6 | * Author: Stefan Gustavson, 2003-2005 7 | * Contact: stegu@itn.liu.se 8 | * 9 | * This code was GPL licensed until February 2011. 10 | * As the original author of this code, I hereby 11 | * release it into the public domain. 12 | * Please feel free to use it for whatever you want. 13 | * Credit is appreciated where appropriate, and I also 14 | * appreciate being told where this code finds any use, 15 | * but you may do as you like. 16 | */ 17 | 18 | /** \file 19 | \brief C implementation of Perlin Simplex Noise over 1,2,3, and 4 dimensions. 20 | \author Stefan Gustavson (stegu@itn.liu.se) 21 | 22 | Adapted to Go by Lars Pensjö (lars.pensjo@gmail.com) 23 | */ 24 | 25 | /* 26 | * This implementation is "Simplex Noise" as presented by 27 | * Ken Perlin at a relatively obscure and not often cited course 28 | * session "Real-Time Shading" at Siggraph 2001 (before real 29 | * time shading actually took on), under the title "hardware noise". 30 | * The 3D function is numerically equivalent to his Java reference 31 | * code available in the PDF course notes, although I re-implemented 32 | * it from scratch to get more readable code. The 1D, 2D and 4D cases 33 | * were implemented from scratch by me from Ken Perlin's text. 34 | */ 35 | 36 | package noise 37 | 38 | // We don't need to include this. It does no harm, but no use either. 39 | 40 | func FASTFLOOR(x float64) int { 41 | if x > 0 { 42 | return int(x) 43 | } 44 | return int(x) - 1 45 | } 46 | 47 | //--------------------------------------------------------------------- 48 | // Static data 49 | 50 | /* 51 | * Permutation table. This is just a random jumble of all numbers 0-255, 52 | * repeated twice to avoid wrapping the index at 255 for each lookup. 53 | * This needs to be exactly the same for all instances on all platforms, 54 | * so it's easiest to just keep it as static explicit data. 55 | * This also removes the need for any initialisation of this class. 56 | * 57 | * Note that making this an int[] instead of a char[] might make the 58 | * code run faster on platforms with a high penalty for unaligned single 59 | * byte addressing. Intel x86 is generally single-byte-friendly, but 60 | * some other CPUs are faster with 4-aligned reads. 61 | * However, a char[] is smaller, which avoids cache trashing, and that 62 | * is probably the most important aspect on most architectures. 63 | * This array is accessed a *lot* by the noise functions. 64 | * A vector-valued noise over 3D accesses it 96 times, and a 65 | * float-valued 4D noise 64 times. We want this to fit in the cache! 66 | */ 67 | var perm = [512]uint8{ 68 | 151, 160, 137, 91, 90, 15, 69 | 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 70 | 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 71 | 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 72 | 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 73 | 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 74 | 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 75 | 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 76 | 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 77 | 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 78 | 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 79 | 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 80 | 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, 81 | 151, 160, 137, 91, 90, 15, 82 | 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 83 | 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 84 | 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 85 | 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 86 | 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 87 | 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 88 | 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 89 | 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 90 | 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 91 | 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 92 | 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 93 | 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, 94 | } 95 | 96 | //--------------------------------------------------------------------- 97 | 98 | /* 99 | * Helper functions to compute gradients-dot-residualvectors (1D to 4D) 100 | * Note that these generate gradients of more than unit length. To make 101 | * a close match with the value range of classic Perlin noise, the final 102 | * noise values need to be rescaled to fit nicely within [-1,1]. 103 | * (The simplex noise functions as such also have different scaling.) 104 | * Note also that these noise functions are the most practical and useful 105 | * signed version of Perlin noise. To return values according to the 106 | * RenderMan specification from the SL noise() and pnoise() functions, 107 | * the noise values need to be scaled and offset to [0,1], like this: 108 | * float SLnoise = (noise(x,y,z) + 1.0) * 0.5; 109 | */ 110 | 111 | func Q(cond bool, v1 float64, v2 float64) float64 { 112 | if cond { 113 | return v1 114 | } 115 | return v2 116 | } 117 | 118 | func grad1(hash uint8, x float64) float64 { 119 | h := hash & 15 120 | grad := float64(1 + h&7) // Gradient value 1.0, 2.0, ..., 8.0 121 | if h&8 != 0 { 122 | grad = -grad // Set a random sign for the gradient 123 | } 124 | return grad * x // Multiply the gradient with the distance 125 | } 126 | 127 | func grad2(hash uint8, x float64, y float64) float64 { 128 | h := hash & 7 // Convert low 3 bits of hash code 129 | u := Q(h < 4, x, y) // into 8 simple gradient directions, 130 | v := Q(h < 4, y, x) // and compute the dot product with (x,y). 131 | return Q(h&1 != 0, -u, u) + Q(h&2 != 0, -2*v, 2*v) 132 | } 133 | 134 | func grad3(hash uint8, x, y, z float64) float64 { 135 | h := hash & 15 // Convert low 4 bits of hash code into 12 simple 136 | u := Q(h < 8, x, y) // gradient directions, and compute dot product. 137 | v := Q(h < 4, y, Q(h == 12 || h == 14, x, z)) // Fix repeats at h = 12 to 15 138 | return Q(h&1 != 0, -u, u) + Q(h&2 != 0, -v, v) 139 | } 140 | 141 | // 1D simplex noise 142 | func Simplex1(x float64) float64 { 143 | i0 := FASTFLOOR(x) 144 | i1 := i0 + 1 145 | x0 := x - float64(i0) 146 | x1 := x0 - 1 147 | 148 | t0 := 1 - x0*x0 149 | t0 *= t0 150 | n0 := t0 * t0 * grad1(perm[i0&0xff], x0) 151 | 152 | t1 := 1 - x1*x1 153 | t1 *= t1 154 | n1 := t1 * t1 * grad1(perm[i1&0xff], x1) 155 | // The maximum value of this noise is 8*(3/4)^4 = 2.53125 156 | // A factor of 0.395 would scale to fit exactly within [-1,1]. 157 | // fmt.Printf("Noise1 x %.4f, i0 %v, i1 %v, x0 %.4f, x1 %.4f, perm0 %d, perm1 %d: %.4f,%.4f\n", x, i0, i1, x0, x1, perm[i0&0xff], perm[i1&0xff], n0, n1) 158 | // The algorithm isn't perfect, as it is assymetric. The correction will normalize the result to the interval [-1,1], but the average will be off by 3%. 159 | return (n0 + n1 + 0.076368899) / 2.45488110001 160 | } 161 | 162 | // 2D simplex noise 163 | func Simplex2(x, y float64) float64 { 164 | 165 | const F2 = 0.366025403 // F2 = 0.5*(sqrt(3.0)-1.0) 166 | const G2 = 0.211324865 // G2 = (3.0-Math.sqrt(3.0))/6.0 167 | 168 | var n0, n1, n2 float64 // Noise contributions from the three corners 169 | 170 | // Skew the input space to determine which simplex cell we're in 171 | s := (x + y) * F2 // Hairy factor for 2D 172 | xs := x + s 173 | ys := y + s 174 | i := FASTFLOOR(xs) 175 | j := FASTFLOOR(ys) 176 | 177 | t := float64(i+j) * G2 178 | X0 := float64(i) - t // Unskew the cell origin back to (x,y) space 179 | Y0 := float64(j) - t 180 | x0 := x - X0 // The x,y distances from the cell origin 181 | y0 := y - Y0 182 | 183 | // For the 2D case, the simplex shape is an equilateral triangle. 184 | // Determine which simplex we are in. 185 | var i1, j1 int // Offsets for second (middle) corner of simplex in (i,j) coords 186 | if x0 > y0 { 187 | i1 = 1 188 | j1 = 0 // lower triangle, XY order: (0,0)->(1,0)->(1,1) 189 | } else { 190 | i1 = 0 191 | j1 = 1 192 | } // upper triangle, YX order: (0,0)->(0,1)->(1,1) 193 | 194 | // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and 195 | // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where 196 | // c = (3-sqrt(3))/6 197 | 198 | x1 := x0 - float64(i1) + G2 // Offsets for middle corner in (x,y) unskewed coords 199 | y1 := y0 - float64(j1) + G2 200 | x2 := x0 - 1 + 2*G2 // Offsets for last corner in (x,y) unskewed coords 201 | y2 := y0 - 1 + 2*G2 202 | 203 | // Wrap the integer indices at 256, to avoid indexing perm[] out of bounds 204 | ii := i & 0xff 205 | jj := j & 0xff 206 | 207 | // Calculate the contribution from the three corners 208 | t0 := 0.5 - x0*x0 - y0*y0 209 | if t0 < 0 { 210 | n0 = 0 211 | } else { 212 | t0 *= t0 213 | n0 = t0 * t0 * grad2(perm[ii+int(perm[jj])], x0, y0) 214 | } 215 | 216 | t1 := 0.5 - x1*x1 - y1*y1 217 | if t1 < 0 { 218 | n1 = 0 219 | } else { 220 | t1 *= t1 221 | n1 = t1 * t1 * grad2(perm[ii+i1+int(perm[jj+j1])], x1, y1) 222 | } 223 | 224 | t2 := 0.5 - x2*x2 - y2*y2 225 | if t2 < 0 { 226 | n2 = 0 227 | } else { 228 | t2 *= t2 229 | n2 = t2 * t2 * grad2(perm[ii+1+int(perm[jj+1])], x2, y2) 230 | } 231 | 232 | // Add contributions from each corner to get the final noise value. 233 | // The result is scaled to return values in the interval [-1,1]. 234 | return (n0 + n1 + n2) / 0.022108854818853867 235 | } 236 | 237 | // 3D simplex noise 238 | func Simplex3(x, y, z float64) float64 { 239 | 240 | // Simple skewing factors for the 3D case 241 | const F3 = 0.333333333 242 | const G3 = 0.166666667 243 | 244 | var n0, n1, n2, n3 float64 // Noise contributions from the four corners 245 | 246 | // Skew the input space to determine which simplex cell we're in 247 | s := (x + y + z) * F3 // Very nice and simple skew factor for 3D 248 | xs := x + s 249 | ys := y + s 250 | zs := z + s 251 | i := FASTFLOOR(xs) 252 | j := FASTFLOOR(ys) 253 | k := FASTFLOOR(zs) 254 | 255 | t := float64(i+j+k) * G3 256 | X0 := float64(i) - t // Unskew the cell origin back to (x,y,z) space 257 | Y0 := float64(j) - t 258 | Z0 := float64(k) - t 259 | x0 := float64(x) - X0 // The x,y,z distances from the cell origin 260 | y0 := float64(y) - Y0 261 | z0 := float64(z) - Z0 262 | 263 | // For the 3D case, the simplex shape is a slightly irregular tetrahedron. 264 | // Determine which simplex we are in. 265 | var i1, j1, k1 int // Offsets for second corner of simplex in (i,j,k) coords 266 | var i2, j2, k2 int // Offsets for third corner of simplex in (i,j,k) coords 267 | 268 | /* This code would benefit from a backport from the GLSL version! */ 269 | if x0 >= y0 { 270 | if y0 >= z0 { 271 | i1 = 1 272 | j1 = 0 273 | k1 = 0 274 | i2 = 1 275 | j2 = 1 276 | k2 = 0 // X Y Z order 277 | } else if x0 >= z0 { 278 | i1 = 1 279 | j1 = 0 280 | k1 = 0 281 | i2 = 1 282 | j2 = 0 283 | k2 = 1 // X Z Y order 284 | } else { 285 | i1 = 0 286 | j1 = 0 287 | k1 = 1 288 | i2 = 1 289 | j2 = 0 290 | k2 = 1 // Z X Y order 291 | } 292 | } else { // x0 90 { 128 | c += 6 129 | } 130 | s += string(c) 131 | } 132 | return s 133 | } 134 | -------------------------------------------------------------------------------- /random/random_test.go: -------------------------------------------------------------------------------- 1 | package random 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestSeed(t *testing.T) { 9 | Seed(0) 10 | valueA := Float() 11 | 12 | Seed(1) 13 | valueB := Float() 14 | 15 | Seed(0) 16 | valueC := Float() 17 | 18 | if valueA == valueB { 19 | t.Errorf("%f == %f", valueA, valueB) 20 | } 21 | if valueA != valueC { 22 | t.Errorf("%f != %f", valueA, valueC) 23 | } 24 | } 25 | 26 | func TestRandSeed(t *testing.T) { 27 | RandSeed() 28 | valueA := Float() 29 | 30 | RandSeed() 31 | valueB := Float() 32 | 33 | if valueA == valueB { 34 | t.Errorf("%f == %f", valueA, valueB) 35 | } 36 | } 37 | 38 | func TestFloat(t *testing.T) { 39 | value := Float() 40 | 41 | if value < 0.0 || value >= 1.0 { 42 | t.Errorf("%f not within 0.0 - 1.0", value) 43 | } 44 | } 45 | 46 | func TestInt(t *testing.T) { 47 | value := Int() 48 | 49 | if value < 0 || value >= 9223372036854775807 { 50 | t.Errorf("%d not within 0 - 9223372036854775807", value) 51 | } 52 | } 53 | 54 | func TestFloatRange(t *testing.T) { 55 | value := FloatRange(100, 200) 56 | 57 | if value < 100 || value >= 200 { 58 | t.Errorf("%f not within 100 - 200", value) 59 | } 60 | } 61 | 62 | func TestIntRange(t *testing.T) { 63 | value := IntRange(100, 200) 64 | 65 | if value < 100 || value >= 200 { 66 | t.Errorf("%d not within 100 - 200", value) 67 | } 68 | } 69 | 70 | func TestBool(t *testing.T) { 71 | value := Boolean() 72 | // lame test 73 | if value != true && value != false { 74 | t.Errorf("%t not boolean", value) 75 | } 76 | } 77 | 78 | func TestWeightedBool(t *testing.T) { 79 | value := WeightedBool(0.5) 80 | // can do better here. 81 | if value != true && value != false { 82 | t.Errorf("%t not boolean", value) 83 | } 84 | } 85 | 86 | func TestRandomString(t *testing.T) { 87 | value := String(10) 88 | if len(value) != 10 { 89 | t.Error("string should have 10 characters") 90 | } 91 | } 92 | 93 | func TestRandomStringLower(t *testing.T) { 94 | value := StringLower(10) 95 | if len(value) != 10 { 96 | t.Error("string should have 10 characters") 97 | } 98 | for _, c := range value { 99 | if c < 'a' || c > 'z' { 100 | t.Error("All chars of string should be lower case letters.") 101 | } 102 | } 103 | } 104 | 105 | func TestRandomStringUpper(t *testing.T) { 106 | value := StringUpper(10) 107 | if len(value) != 10 { 108 | t.Error("string should have 10 characters") 109 | } 110 | for _, c := range value { 111 | if c < 'A' || c > 'Z' { 112 | t.Error("All chars of string should be upper case letters.") 113 | } 114 | } 115 | } 116 | 117 | func TestRandomStringAlpha(t *testing.T) { 118 | value := StringAlpha(10) 119 | fmt.Println(value) 120 | if len(value) != 10 { 121 | t.Error("string should have 10 characters") 122 | } 123 | for _, c := range value { 124 | if (c < 'A' || c > 'Z') && (c < 'a' || c > 'z') { 125 | t.Error("All chars of string should be upper case letters.") 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | deprecated. using https://github.com/bit101/bitlib 2 | 3 | `blgo` is an adaption of my `bitlib` library for use with `go-cairo`. 4 | 5 | install cairo 6 | 7 | https://cairographics.org/download/ 8 | 9 | install blgo: 10 | 11 | `go get github.com/bit101/blgo` 12 | 13 | install go-cairo: 14 | 15 | https://github.com/bit101/go-cairo 16 | 17 | `go get github.com/bit101/go-cairo` 18 | 19 | -------------------------------------------------------------------------------- /surface.go: -------------------------------------------------------------------------------- 1 | package blgo 2 | 3 | import ( 4 | "github.com/bit101/blgo/color" 5 | "github.com/bit101/blgo/geom" 6 | cairo "github.com/bit101/go-cairo" 7 | ) 8 | 9 | // Surface represents a drawing surface with added methods. 10 | type Surface struct { 11 | Width float64 12 | Height float64 13 | cairo.Surface 14 | } 15 | 16 | // NewSurface creates a new Surface. 17 | func NewSurface(width float64, height float64) *Surface { 18 | return &Surface{ 19 | width, 20 | height, 21 | *cairo.NewSurface(cairo.FormatARGB32, int(width), int(height)), 22 | } 23 | } 24 | 25 | // NewSurfaceFromPNG creates a new Surface. 26 | func NewSurfaceFromPNG(filename string) (*Surface, cairo.Status) { 27 | surface, status := cairo.NewSurfaceFromPNG(filename) 28 | if status != cairo.StatusSuccess { 29 | return nil, status 30 | } 31 | return &Surface{ 32 | float64(surface.GetWidth()), 33 | float64(surface.GetHeight()), 34 | *surface, 35 | }, status 36 | } 37 | 38 | // NewSVGSurface creates a new surface for creating an SVG image. Finish with surface.Finish() 39 | func NewSVGSurface(filename string, width, height float64) *Surface { 40 | return &Surface{ 41 | width, 42 | height, 43 | *cairo.NewSVGSurface(filename, width, height, cairo.SVGVersion12), 44 | } 45 | } 46 | 47 | // ClearRGB clears the surface to the given rgb color. 48 | func (s *Surface) ClearRGB(r float64, g float64, b float64) { 49 | s.Save() 50 | // todo: set identity transform 51 | s.SetSourceRGB(r, g, b) 52 | s.Paint() 53 | s.Restore() 54 | } 55 | 56 | // ClearRGBA clears the surface to the given rgba color. 57 | func (s *Surface) ClearRGBA(r, g, b, a float64) { 58 | s.Save() 59 | // todo: set identity transform 60 | s.SetSourceRGBA(r, g, b, a) 61 | s.Paint() 62 | s.Restore() 63 | } 64 | 65 | // ClearColor clears surface to given color. 66 | func (s *Surface) ClearColor(color color.Color) { 67 | s.ClearRGB(color.R, color.G, color.B) 68 | } 69 | 70 | // ClearWhite clears surface to white. 71 | func (s *Surface) ClearWhite() { 72 | s.ClearRGB(1, 1, 1) 73 | } 74 | 75 | // ClearBlack clears surface to white. 76 | func (s *Surface) ClearBlack() { 77 | s.ClearRGB(0, 0, 0) 78 | } 79 | 80 | // ClearGrey clears surface to white. 81 | func (s *Surface) ClearGrey(g float64) { 82 | s.ClearRGB(g, g, g) 83 | } 84 | 85 | // SetSourceColor sets the source to the given color. 86 | func (s *Surface) SetSourceColor(color color.Color) { 87 | s.SetSourceRGBA(color.R, color.G, color.B, color.A) 88 | } 89 | 90 | // SetSourceHSV sets the source to a color created with the given hue, saturation and value. 91 | func (s *Surface) SetSourceHSV(hue, sat, val float64) { 92 | s.SetSourceColor(color.HSV(hue, sat, val)) 93 | } 94 | 95 | func (s *Surface) SetSourceBlack() { 96 | s.SetSourceRGB(0, 0, 0) 97 | } 98 | 99 | func (s *Surface) SetSourceWhite() { 100 | s.SetSourceRGB(1, 1, 1) 101 | } 102 | 103 | func (s *Surface) SetSourceGray(gray float64) { 104 | s.SetSourceRGB(gray, gray, gray) 105 | } 106 | 107 | // GetPixel returns the b, g, r, a value at a given x, y location. 108 | func (s *Surface) GetPixel(x int, y int) (byte, byte, byte, byte) { 109 | data := s.GetData() 110 | index := (y*s.GetWidth() + x) * 4 111 | return data[index+2], data[index+1], data[index], data[index+3] 112 | } 113 | 114 | func (s *Surface) Center() { 115 | s.Translate(s.Width/2, s.Height/2) 116 | } 117 | 118 | // GetBounds returns a rectangle defined by the size of the surface. 119 | func (s *Surface) GetBounds() *geom.Rectangle { 120 | return geom.NewRectangle(0, 0, s.Width, s.Height) 121 | } 122 | 123 | // GetAspectRatio returns the aspect ratio of the surface (width / height). 124 | func (s *Surface) GetAspectRatio() float64 { 125 | return s.Width / s.Height 126 | } 127 | 128 | // FillText draws text 129 | func (s *Surface) FillText(text string, x, y float64) { 130 | s.Save() 131 | s.Translate(x, y) 132 | s.ShowText(text) 133 | s.Fill() 134 | s.Restore() 135 | } 136 | 137 | // TraverseFunc defines a callback function for traversing a surface. 138 | type TraverseFunc func(x, y float64) 139 | 140 | // Traverse calls a callback function for every x, y "pixel" on a surface 141 | func (s *Surface) Traverse(callback TraverseFunc) { 142 | for x := 0.0; x < s.Width; x++ { 143 | for y := 0.0; y < s.Height; y++ { 144 | callback(x, y) 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "os/exec" 8 | "path/filepath" 9 | "runtime" 10 | ) 11 | 12 | func MakeGIF(tool, folder, outFileName string, fps float64) { 13 | if tool == "convert" { 14 | ConvertToGIF(folder, outFileName, fps) 15 | } else if tool == "ffmpeg" { 16 | FfmpegToGIF(folder, outFileName, fps) 17 | } 18 | 19 | } 20 | 21 | // ConvertToGIF converts a folder of pngs into an animated gif using imagemagick convert. 22 | func ConvertToGIF(folder, outFileName string, fps float64) { 23 | delay := fmt.Sprintf("%f", 1000.0/fps/10.0) 24 | path := folder + "/*.png" 25 | cmd := exec.Command("convert", "-delay", delay, "-layers", "Optimize", path, outFileName) 26 | err := cmd.Run() 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | } 31 | 32 | // FfmpegToGIF converts a folder of pngs into an animated gif using ffmpeg. 33 | func FfmpegToGIF(folder, outFileName string, fps float64) { 34 | path := folder + "/frame_%04d.png" 35 | fpsArg := fmt.Sprintf("%d", int(fps)) 36 | 37 | paletteCmd := exec.Command("ffmpeg", "-y", "-i", path, "-vf", "palettegen", "palette.png") 38 | err := paletteCmd.Run() 39 | if err != nil { 40 | log.Fatal(err) 41 | } 42 | 43 | outCmd := exec.Command("ffmpeg", "-y", "-framerate", fpsArg, "-i", path, "-i", "palette.png", "-filter_complex", "paletteuse", outFileName) 44 | err = outCmd.Run() 45 | if err != nil { 46 | log.Fatal(err) 47 | } 48 | } 49 | 50 | // ConvertToYoutube converts a folder of pngs into a Youtube compatible mp4 video file. Requires ffmpeg. 51 | func ConvertToYoutube(folder, outFileName string, fps int) { 52 | path := folder + "/frame_%04d.png" 53 | fpsArg := fmt.Sprintf("%d", fps) 54 | 55 | cmd := exec.Command("ffmpeg", "-framerate", fpsArg, "-i", path, "-s:v", "1280x720", 56 | "-c:v", "libx264", "-profile:v", "high", "-crf", "20", 57 | "-pix_fmt", "yuv420p", outFileName) 58 | err := cmd.Run() 59 | if err != nil { 60 | log.Fatal(err) 61 | } 62 | } 63 | 64 | // ViewImage displays an image using installed image viewer. 65 | func ViewImage(imagePath string) { 66 | cmd := exec.Command("eog", imagePath) 67 | if runtime.GOOS == "darwin" { 68 | cmd = exec.Command("qlmanage", "-p", imagePath) 69 | } 70 | err := cmd.Run() 71 | if err != nil { 72 | log.Fatal(err) 73 | } 74 | } 75 | 76 | // VLC launches vlc to play a video 77 | func VLC(fileName string) { 78 | cmd := exec.Command("vlc", fileName) 79 | err := cmd.Run() 80 | if err != nil { 81 | log.Fatal(err) 82 | } 83 | } 84 | 85 | // ParentDir returns the immediated directory name of the current working directory. 86 | func ParentDir() string { 87 | wd, err := os.Getwd() 88 | if err != nil { 89 | fmt.Fprintln(os.Stderr, "Cannot get directory.") 90 | os.Exit(1) 91 | } 92 | return filepath.Base(wd) 93 | } 94 | -------------------------------------------------------------------------------- /util/util_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestNorm(t *testing.T) { 8 | result := ParentDir() 9 | expected := "util" 10 | if result != expected { 11 | t.Errorf("Expected %s, got %s", expected, result) 12 | } 13 | } 14 | --------------------------------------------------------------------------------