WIP: add solar flare
This commit is contained in:
parent
dfd32aa0a6
commit
4a07d63005
5 changed files with 216 additions and 0 deletions
62
common/blend.go
Normal file
62
common/blend.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
type BlendMode int
|
||||
|
||||
const (
|
||||
Add BlendMode = iota
|
||||
)
|
||||
|
||||
func Blend(src, dest *image.RGBA, mode BlendMode) *image.RGBA {
|
||||
img := image.NewRGBA(src.Bounds())
|
||||
|
||||
for i := 0; i <src.Bounds().Max.X; i++ {
|
||||
for j :=0; j<src.Bounds().Max.Y; j++ {
|
||||
switch mode {
|
||||
case Add:
|
||||
if compareColor(src.At(i, j), Black) {
|
||||
img.Set(i, j, dest.At(i, j))
|
||||
} else {
|
||||
img.SetRGBA(i, j, add(src.At(i, j), dest.At(i, j)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
func compareColor(src, dst color.Color) bool {
|
||||
sr, sg, sb ,sa := src.RGBA()
|
||||
dr, dg, db, da := dst.RGBA()
|
||||
|
||||
if sr == dr && sg == dg && sb == db && sa == da {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func add(srcC, dstC color.Color) color.RGBA {
|
||||
c := color.RGBA{}
|
||||
sr, sg, sb ,sa := srcC.RGBA()
|
||||
dr, dg, db, da := dstC.RGBA()
|
||||
|
||||
//aSrc := float64(sa)/255.0
|
||||
//aDst := 1.0 - aSrc
|
||||
|
||||
//c.R = uint8(float64(sr) * aSrc + float64(dr)*aDst)
|
||||
//c.G = uint8(float64(sg) * aSrc + float64(dg)*aDst)
|
||||
//c.B = uint8(float64(sb) * aSrc + float64(db)*aDst)
|
||||
//c.A = uint8(float64(sa) * aSrc + float64(da)*aDst)
|
||||
|
||||
c.R = uint8(ConstrainInt(int(uint32(float64(sr)*0.05)+dr), 0, 255))
|
||||
c.G = uint8(ConstrainInt(int(uint32(float64(sg)*0.05)+dg), 0, 255))
|
||||
c.B = uint8(ConstrainInt(int(uint32(float64(sb)*0.05)+db), 0, 255))
|
||||
c.A = uint8(ConstrainInt(int(uint32(float64(sa)*0.9)+da), 0, 255))
|
||||
//c.A = uint8(float64(sa) * aSrc + float64(da)*aDst)
|
||||
|
||||
return c
|
||||
}
|
|
@ -10,6 +10,25 @@ func Constrain(val, low, high float64) float64 {
|
|||
return math.Max(math.Min(val, high), low)
|
||||
}
|
||||
|
||||
// ConstrainInt returns a int value between a minimum and maximum value.
|
||||
func ConstrainInt(val, low, high int) int {
|
||||
return MaxInt(MinInt(val, high), low)
|
||||
}
|
||||
|
||||
func MinInt(a, b int)int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func MaxInt(a, b int)int {
|
||||
if a > b{
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Remap re-maps a number from one range to another.
|
||||
func Remap(n, start1, stop1, start2, stop2 float64) float64 {
|
||||
newval := (n-start1)/(stop1-start1)*(stop2-start2) + start2
|
||||
|
|
51
common/calculation_test.go
Normal file
51
common/calculation_test.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
package common
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestConstrain(t *testing.T) {
|
||||
type args struct {
|
||||
val float64
|
||||
low float64
|
||||
high float64
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want float64
|
||||
}{
|
||||
{name: "testcase1", args: args{val: 1.0, low:0.5, high: 1.5}, want: 1.0},
|
||||
{name: "testcase2", args: args{val: 0.4, low:0.5, high: 1.5}, want: 0.5},
|
||||
{name: "testcase3", args: args{val: -1.0, low:-3.5, high: 1.5}, want: -1.0},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := Constrain(tt.args.val, tt.args.low, tt.args.high); got != tt.want {
|
||||
t.Errorf("Constrain() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConstrainInt(t *testing.T) {
|
||||
type args struct {
|
||||
val int
|
||||
low int
|
||||
high int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want int
|
||||
}{
|
||||
{name: "testcase1", args: args{val: 256, low: 0, high: 255}, want: 255},
|
||||
{name: "testcase2", args: args{val: -1, low: 0, high: 255}, want: 0},
|
||||
{name: "testcase3", args: args{val: 100, low: 0, high: 255}, want: 100},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := ConstrainInt(tt.args.val, tt.args.low, tt.args.high); got != tt.want {
|
||||
t.Errorf("ConstrainInt() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
19
example/example_solarflare.go
Normal file
19
example/example_solarflare.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/jdxyw/generativeart"
|
||||
"github.com/jdxyw/generativeart/common"
|
||||
"image/color"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
rand.Seed(time.Now().Unix())
|
||||
c := generativeart.NewCanva(500, 500)
|
||||
c.SetBackground(common.Black)
|
||||
c.FillBackground()
|
||||
c.SetLineColor(color.RGBA{255, 64, 8, 128})
|
||||
c.Draw(generativeart.NewSolarFlare())
|
||||
c.ToPNG("solarflare.png")
|
||||
}
|
65
solarflare.go
Normal file
65
solarflare.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package generativeart
|
||||
|
||||
import (
|
||||
"github.com/fogleman/gg"
|
||||
"github.com/jdxyw/generativeart/common"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"math"
|
||||
)
|
||||
|
||||
type solarFlare struct {
|
||||
}
|
||||
|
||||
func NewSolarFlare() *solarFlare {
|
||||
return &solarFlare{}
|
||||
}
|
||||
|
||||
|
||||
// Generative draws a solar flare images.
|
||||
func (o *solarFlare) Generative(c *canva) {
|
||||
var xOffset, yOffset float64
|
||||
var offsetInc = 0.006
|
||||
var inc = 1.0
|
||||
var r = 1.0
|
||||
var m = 1.005
|
||||
noise := common.NewPerlinNoise()
|
||||
|
||||
|
||||
for r < 200 {
|
||||
for i :=0; i <10; i++ {
|
||||
nPoints := int(2*math.Pi*r)
|
||||
nPoints = common.MinInt(nPoints, 500)
|
||||
|
||||
img := image.NewRGBA(image.Rect(0, 0, c.width, c.height))
|
||||
draw.Draw(img, img.Bounds(), &image.Uniform{color.RGBA{0,0,0,255}}, image.ZP, draw.Src)
|
||||
ctex := gg.NewContextForRGBA(img)
|
||||
|
||||
ctex.Push()
|
||||
ctex.Translate(float64(c.width/2), float64(c.height/2))
|
||||
ctex.SetLineWidth(1.0)
|
||||
ctex.SetColor(c.opts.lineColor)
|
||||
|
||||
for j :=0.0; j<float64(nPoints+1); j+=1.0 {
|
||||
a := j/float64(nPoints) * math.Pi * 2
|
||||
px := math.Cos(a)
|
||||
py := math.Sin(a)
|
||||
n := noise.Noise2D(xOffset + px * inc, yOffset + py * inc)*r
|
||||
px *= n
|
||||
py *= n
|
||||
ctex.LineTo(px, py)
|
||||
}
|
||||
ctex.Stroke()
|
||||
ctex.Pop()
|
||||
|
||||
c.img = common.Blend(img, c.img, common.Add)
|
||||
//cc := NewCanva(0,0)
|
||||
//cc.img = c.img
|
||||
//cc.ToPNG(fmt.Sprintf("xxxx%v.png", r))
|
||||
xOffset += offsetInc
|
||||
yOffset += offsetInc
|
||||
r*=m
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue