Skip to content

Commit d7eb261

Browse files
committed
Add Anderson and spiral generators
1 parent 375d1e7 commit d7eb261

File tree

8 files changed

+475
-124
lines changed

8 files changed

+475
-124
lines changed

cmd/anderson.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/signal"
7+
"syscall"
8+
9+
"github.com/spf13/cobra"
10+
"gitlab.com/ericworkman/generative/sketch"
11+
)
12+
13+
// andersonCmd represents the anderson command
14+
var andersonCmd = &cobra.Command{
15+
Use: "anderson",
16+
Short: "Create art based on Jason Anderson's work",
17+
Long: ``,
18+
Run: func(cmd *cobra.Command, args []string) {
19+
fmt.Println("anderson called", jitter)
20+
params := sketch.AndersonParams{
21+
DestWidth: width,
22+
DestHeight: height,
23+
Iterations: limitByIterations,
24+
}
25+
26+
csketch := sketch.NewAndersonSketch(params)
27+
28+
// catch the sigterm signal for ctrl-c quitting mostly
29+
// save the output at this point
30+
c := make(chan os.Signal)
31+
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
32+
go func() {
33+
<-c
34+
sketch.SaveOutput(csketch.Output(), outputImgName)
35+
os.Exit(1)
36+
}()
37+
38+
for i := 0; i <= limitByIterations; i++ {
39+
fmt.Println("Iteration", i)
40+
41+
csketch.Update(i)
42+
if save == true {
43+
sketch.SaveOutput(csketch.Output(), outputImgName)
44+
}
45+
}
46+
47+
sketch.SaveOutput(csketch.Output(), outputImgName)
48+
},
49+
}
50+
51+
func init() {
52+
rootCmd.AddCommand(andersonCmd)
53+
54+
andersonCmd.Flags().StringVarP(&outputImgName, "out", "o", "out.png", "Output image name")
55+
andersonCmd.Flags().IntVarP(&limitByIterations, "iterations", "i", 3, "Number of iterations")
56+
andersonCmd.Flags().IntVarP(&width, "width", "", 1920, "Width of output")
57+
andersonCmd.Flags().IntVarP(&height, "height", "", 1080, "Height of output")
58+
}

cmd/spiral.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,8 @@ var (
1818
// spiralCmd represents the spiral command
1919
var spiralCmd = &cobra.Command{
2020
Use: "spiral",
21-
Short: "A brief description of your command",
22-
Long: `A longer description that spans multiple lines and likely contains examples
23-
and usage of using your command. For example:
24-
25-
Cobra is a CLI library for Go that empowers applications.
26-
This application is a tool to generate the needed files
27-
to quickly create a Cobra application.`,
21+
Short: "Create a logarithmic spiral",
22+
Long: ``,
2823
Run: func(cmd *cobra.Command, args []string) {
2924
fmt.Println("spiral called")
3025
params := sketch.SpiralParams{

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
github.com/mitchellh/go-homedir v1.1.0
99
github.com/spf13/cobra v1.1.1
1010
github.com/spf13/viper v1.7.0
11+
github.com/teacat/noire v1.1.0
1112
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136
1213
golang.org/x/image v0.0.0-20201208152932-35266b937fa6 // indirect
1314
)

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,11 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
175175
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
176176
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
177177
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
178+
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
178179
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
179180
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
181+
github.com/teacat/noire v1.1.0 h1:5IgJ1H8jodiSSYnrVadV2JjbAnEgCCjYUQxSUuaQ7Sg=
182+
github.com/teacat/noire v1.1.0/go.mod h1:cetGlnqr+9yKJcFgRgYXOWJY66XIrrjUsGBwNlNNtAk=
180183
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
181184
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
182185
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
@@ -306,6 +309,7 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
306309
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
307310
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
308311
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
312+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
309313
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
310314
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
311315
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

sketch/anderson.go

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
package sketch
2+
3+
import (
4+
"fmt"
5+
"image"
6+
"image/color"
7+
"math"
8+
"math/rand"
9+
10+
"github.com/fogleman/gg"
11+
"github.com/teacat/noire"
12+
)
13+
14+
var (
15+
andersonColors = [...][3]uint8{
16+
{224, 105, 99},
17+
{119, 194, 169},
18+
{45, 225, 100},
19+
{135, 174, 99},
20+
{232, 223, 104},
21+
{56, 125, 179},
22+
}
23+
sky = [3]int{46, 59, 75}
24+
water = [3]int{52, 56, 57}
25+
dark = [3]int{36, 47, 62}
26+
)
27+
28+
// AndersonParams contains externally-provided parameters
29+
type AndersonParams struct {
30+
// tweakable parameters for the cli
31+
DestWidth int
32+
DestHeight int
33+
Iterations int
34+
}
35+
36+
// AndersonSketch wraps all the components needed to draw the spiral sketch
37+
type AndersonSketch struct {
38+
AndersonParams
39+
DC *gg.Context
40+
currentR float64
41+
horizon int // also max height of each slot
42+
slot float64
43+
slotOffsets [6]int
44+
}
45+
46+
// NewAndersonSketch initializes the canvas and AndersonSketch
47+
// The sketch has a top sky zone and a bottom water zone made of a blue-ish color on top and a gray-blue color on bottom
48+
// Each color fits into a "slot" of equal possible width.
49+
// The edges on the left and right can be different sizes than the rest of the slots.
50+
// The water rectangle mirrors the sky rectangle but gets muted out.
51+
// The sky rectangle has progressively more opaque and vibrant colored squares with paint marks and fades.
52+
// The progression of colors can be offset to either side of the slot.
53+
// The water rectangle contains aberations that look vaguely like waves.
54+
func NewAndersonSketch(params AndersonParams) *AndersonSketch {
55+
fmt.Println("Starting Sketch")
56+
57+
s := &AndersonSketch{AndersonParams: params, currentR: 2.0}
58+
s.horizon = randIntRangeFrom(s.DestHeight/5, s.DestHeight*4/5)
59+
//s.horizon = 300
60+
s.slot = float64(s.DestWidth) / 8.0
61+
62+
// canvas is a gg image context and contains what gets drawn to the screen
63+
canvas := gg.NewContext(s.DestWidth, s.DestHeight)
64+
canvas.SetLineWidth(0.0)
65+
// set sky rectangle
66+
canvas.SetRGBA255(sky[0], sky[1], sky[2], 255)
67+
canvas.DrawRectangle(0, 0, float64(s.DestWidth), float64(s.horizon))
68+
canvas.FillPreserve()
69+
canvas.Stroke()
70+
// set water rectangle
71+
canvas.SetRGBA255(water[0], water[1], water[2], 255)
72+
canvas.DrawRectangle(0, float64(s.horizon), float64(s.DestWidth), float64(s.DestHeight))
73+
canvas.FillPreserve()
74+
canvas.Stroke()
75+
s.DC = canvas
76+
77+
rand.Shuffle(len(andersonColors), func(i, j int) {
78+
andersonColors[i], andersonColors[j] = andersonColors[j], andersonColors[i]
79+
})
80+
81+
// slot offsets, 1 for left and -1 for right
82+
slotOffsets := [6]int{}
83+
for j := 0; j < len(andersonColors); j++ {
84+
if rand.Intn(100) > 50 {
85+
slotOffsets[j] = 1
86+
} else {
87+
slotOffsets[j] = -1
88+
}
89+
}
90+
s.slotOffsets = slotOffsets
91+
92+
return s
93+
}
94+
95+
// Output produces an image output of the current state of the sketch
96+
func (s *AndersonSketch) Output() image.Image {
97+
return s.DC.Image()
98+
}
99+
100+
// Update makes a logical step into generation
101+
func (s *AndersonSketch) Update(i int) {
102+
for j := 0; j < len(andersonColors); j++ {
103+
acolor := andersonColors[j]
104+
105+
x := s.slot + float64(j)*s.slot
106+
y := float64(s.horizon)
107+
108+
maxWidth := float64(s.slot) / float64(i+1)
109+
nextStepWidth := float64(s.slot) / float64(i+2)
110+
w := maxWidth
111+
if i != 0 {
112+
w = randFloat64RangeFrom(nextStepWidth+(maxWidth-nextStepWidth)/2, maxWidth)
113+
}
114+
// push the starting place to the right if selected at initilization
115+
if s.slotOffsets[j] == -1 {
116+
x += s.slot - w
117+
}
118+
119+
maxHeight := y / float64(i+1)
120+
nextStepHeight := y / float64(i+2)
121+
h := randFloat64RangeFrom(nextStepHeight, maxHeight)
122+
123+
// gradient is two circles: first is the solid color and is the smaller of the two
124+
// second is the transparent color and is larger
125+
// solid color is 100% within the first circle and transitions between the smaller and larger circles
126+
// transparent color is 100% outside the second circle.
127+
// Ensure the first circle is entirely below the horizon, so that the base is a solid color.
128+
// Jitter left and right and radius of larger circle for some variation
129+
grad := gg.NewRadialGradient(x+w/2, y+5, 5, x+w/2+randFloat64Range(5), y+5, h+randFloat64Range(5))
130+
131+
alpha := minFloat64(0.2+0.2*float64(i), 1.0)
132+
solid := color.RGBA{}
133+
solid.R = uint8(alpha * float64(acolor[0]))
134+
solid.G = uint8(alpha * float64(acolor[1]))
135+
solid.B = uint8(alpha * float64(acolor[2]))
136+
solid.A = uint8(alpha * 255)
137+
138+
transparent := color.RGBA{}
139+
transparent.R = 0
140+
transparent.G = 0
141+
transparent.B = 0
142+
transparent.A = 0
143+
144+
grad.AddColorStop(0, solid)
145+
grad.AddColorStop(1, transparent)
146+
147+
s.DC.SetFillStyle(grad)
148+
s.DC.DrawRectangle(x, y, w, -h)
149+
s.DC.Fill()
150+
s.DC.Stroke()
151+
152+
// water mirror
153+
wgrad := gg.NewRadialGradient(x+w/2, y-5, 5, x+w/2, y-5, h)
154+
wcolor := noire.NewRGB(float64(acolor[0]), float64(acolor[1]), float64(acolor[2]))
155+
r, g, b := wcolor.Darken(0.33).RGB()
156+
157+
walpha := minFloat64(0.2+0.2*float64(i), 1.0)
158+
wsolid := color.RGBA{}
159+
wsolid.R = uint8(walpha * float64(r))
160+
wsolid.G = uint8(walpha * float64(g))
161+
wsolid.B = uint8(walpha * float64(b))
162+
wsolid.A = uint8(walpha * 255)
163+
164+
wtransparent := color.RGBA{}
165+
wtransparent.R = 0
166+
wtransparent.G = 0
167+
wtransparent.B = 0
168+
wtransparent.A = 0
169+
170+
wgrad.AddColorStop(0, wsolid)
171+
wgrad.AddColorStop(1, wtransparent)
172+
173+
waterScale := 1.4
174+
s.DC.SetFillStyle(wgrad)
175+
s.DC.DrawRectangle(x, y, w, h*waterScale)
176+
s.DC.Fill()
177+
s.DC.Stroke()
178+
179+
// if this is the last iteration, we are going to put some full strength color and near black blocks in the front
180+
// and draw the sides
181+
if i == s.Iterations {
182+
// shuffle, we'll care about the first 3 only
183+
// 0 = darken
184+
// 1 = brighten
185+
// 2 = near-black
186+
// 3 = same
187+
options := [...]int{0, 1, 2, 2, 2, 2, 3}
188+
rand.Shuffle(len(options), func(i, j int) {
189+
options[i], options[j] = options[j], options[i]
190+
})
191+
192+
lcolor := noire.NewRGB(float64(acolor[0]), float64(acolor[1]), float64(acolor[2]))
193+
// reset
194+
x = s.slot + float64(j)*s.slot
195+
h := nextStepHeight
196+
down := y
197+
wi := math.Round(s.slot / 3)
198+
he := -h / 2
199+
hj := 0.0
200+
201+
for t := 0; t < 3; t++ {
202+
left := math.Round(x + float64(t)*s.slot/3)
203+
if t == 1 {
204+
hj = randFloat64Range(he / 5)
205+
} else {
206+
hj = 0.0
207+
}
208+
switch options[t] {
209+
case 0:
210+
dr, dg, db := lcolor.Darken(0.15).RGB()
211+
s.DC.SetRGBA255(int(dr), int(dg), int(db), 255)
212+
s.DC.DrawRectangle(left, down, wi, he-hj)
213+
s.DC.Fill()
214+
s.DC.Stroke()
215+
case 1:
216+
br, bg, bb := lcolor.Brighten(0.15).RGB()
217+
s.DC.SetRGBA255(int(br), int(bg), int(bb), 255)
218+
s.DC.DrawRectangle(left, down, wi, he-hj)
219+
s.DC.Fill()
220+
s.DC.Stroke()
221+
case 2:
222+
s.DC.SetRGBA255(dark[0], dark[1], dark[2], 255)
223+
s.DC.DrawRectangle(left, down, wi, he-hj)
224+
s.DC.Fill()
225+
s.DC.Stroke()
226+
case 3:
227+
nr, ng, nb := lcolor.RGB()
228+
s.DC.SetRGBA255(int(nr), int(ng), int(nb), 255)
229+
s.DC.DrawRectangle(left, down, wi, he-hj)
230+
s.DC.Fill()
231+
s.DC.Stroke()
232+
}
233+
}
234+
235+
// draw the shapes on the far edges of the sketch
236+
// left
237+
s.DC.SetRGBA255(dark[0], dark[1], dark[2], 255)
238+
s.DC.MoveTo(0, float64(s.horizon))
239+
s.DC.LineTo(s.slot, float64(s.horizon))
240+
s.DC.LineTo(s.slot, float64(s.horizon)+he)
241+
s.DC.LineTo(0, float64(s.horizon)+2*he)
242+
s.DC.ClosePath()
243+
s.DC.Fill()
244+
s.DC.Stroke()
245+
// right
246+
s.DC.SetRGBA255(dark[0], dark[1], dark[2], 255)
247+
s.DC.MoveTo(float64(s.DestWidth)-s.slot, float64(s.horizon))
248+
s.DC.LineTo(float64(s.DestWidth), float64(s.horizon))
249+
s.DC.LineTo(float64(s.DestWidth), float64(s.horizon)+2*he)
250+
s.DC.LineTo(float64(s.DestWidth)-s.slot, float64(s.horizon)+he)
251+
s.DC.ClosePath()
252+
s.DC.Fill()
253+
s.DC.Stroke()
254+
255+
// add ripples
256+
ripples := (s.DestHeight - s.horizon) / 100
257+
for k := 0; k < ripples; k++ {
258+
s.DC.SetRGBA255(dark[0], dark[1], dark[2], 10)
259+
s.DC.DrawRectangle(0, float64(s.horizon+100*k)+randFloat64Range(5.0), float64(s.DestWidth), 10+randFloat64Range(3))
260+
s.DC.Fill()
261+
s.DC.Stroke()
262+
}
263+
}
264+
}
265+
}

0 commit comments

Comments
 (0)