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)
|
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.
|
// Remap re-maps a number from one range to another.
|
||||||
func Remap(n, start1, stop1, start2, stop2 float64) float64 {
|
func Remap(n, start1, stop1, start2, stop2 float64) float64 {
|
||||||
newval := (n-start1)/(stop1-start1)*(stop2-start2) + start2
|
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