diff options
author | Leo Tenenbaum <pommicket@gmail.com> | 2019-06-16 13:19:08 -0400 |
---|---|---|
committer | Leo Tenenbaum <pommicket@gmail.com> | 2019-06-16 13:19:08 -0400 |
commit | e2962707f79ab00ad055e09127bb8ef0f2c45b02 (patch) | |
tree | 08c5f77155d7ac2ff93cc373ad1edeee66663a30 | |
parent | bac7ad4b50fca46b24cfc843ac18a1313e1064ec (diff) |
-rw-r--r-- | autoart/autoaudio.go | 64 | ||||
-rw-r--r-- | autoart/autoimages.go | 265 | ||||
-rw-r--r-- | autoart/autovideos.go | 174 | ||||
-rw-r--r-- | autoutils/audio.go | 99 | ||||
-rw-r--r-- | autoutils/batches.go | 49 | ||||
-rw-r--r-- | autoutils/colorconversion.go | 65 | ||||
-rw-r--r-- | autoutils/functiongenerator.go | 264 |
7 files changed, 504 insertions, 476 deletions
diff --git a/autoart/autoaudio.go b/autoart/autoaudio.go index 05a91d4..cc5965e 100644 --- a/autoart/autoaudio.go +++ b/autoart/autoaudio.go @@ -20,37 +20,39 @@ along with AutoArt. If not, see <https://www.gnu.org/licenses/>. package autoart import ( - "github.com/pommicket/autoart/autoutils" - "io" + "github.com/pommicket/autoart/autoutils" + "io" ) func GenerateAudio(output io.Writer, duration float64, sampleRate int32, - functionLength int, rectifier int) error { - samples := int64(duration * float64(sampleRate)) - err := autoutils.WriteAudioHeader(output, samples, 1, sampleRate) - if err != nil { return err } - - vars := make([]float64, 1) - const sampleBufferSize = 4096 - sampleBuffer := make([]uint8, sampleBufferSize) - sampleBufferIndex := 0 - - var function autoutils.Function - function.Generate(1, functionLength) - - for s := int64(0); s < samples; s++ { - t := float64(s) / float64(sampleRate) - vars[0] = t - value := rectify(function.Evaluate(vars), rectifier) - sampleBuffer[sampleBufferIndex] = uint8(255 * value) - sampleBufferIndex++ - if sampleBufferIndex == sampleBufferSize { - err = autoutils.WriteAudioSamples(output, sampleBuffer) - if err != nil { - return err - } - sampleBufferIndex = 0 - } - } - return autoutils.WriteAudioSamples(output, sampleBuffer[:sampleBufferIndex]) -}
\ No newline at end of file + functionLength int, rectifier int) error { + samples := int64(duration * float64(sampleRate)) + err := autoutils.WriteAudioHeader(output, samples, 1, sampleRate) + if err != nil { + return err + } + + vars := make([]float64, 1) + const sampleBufferSize = 4096 + sampleBuffer := make([]uint8, sampleBufferSize) + sampleBufferIndex := 0 + + var function autoutils.Function + function.Generate(1, functionLength) + + for s := int64(0); s < samples; s++ { + t := float64(s) / float64(sampleRate) + vars[0] = t + value := rectify(function.Evaluate(vars), rectifier) + sampleBuffer[sampleBufferIndex] = uint8(255 * value) + sampleBufferIndex++ + if sampleBufferIndex == sampleBufferSize { + err = autoutils.WriteAudioSamples(output, sampleBuffer) + if err != nil { + return err + } + sampleBufferIndex = 0 + } + } + return autoutils.WriteAudioSamples(output, sampleBuffer[:sampleBufferIndex]) +} diff --git a/autoart/autoimages.go b/autoart/autoimages.go index 53eea18..df0a3fd 100644 --- a/autoart/autoimages.go +++ b/autoart/autoimages.go @@ -20,185 +20,186 @@ along with AutoArt. If not, see <https://www.gnu.org/licenses/>. package autoart import ( - "image" - "image/color" - "math" - "math/rand" "fmt" - "github.com/pommicket/autoart/autoutils" + "github.com/pommicket/autoart/autoutils" + "image" + "image/color" + "math" + "math/rand" ) const ( - XY = iota - RTHETA + XY = iota + RTHETA ) const ( - RGB = iota - GRAYSCALE - CMYK - HSV - YCbCr + RGB = iota + GRAYSCALE + CMYK + HSV + YCbCr ) const ( - MOD = iota - CLAMP - SIGMOID + MOD = iota + CLAMP + SIGMOID ) type Config struct { - FunctionLength int - ColorSpace int - CoordinateSys int - Alpha bool - Rectifier int // What to do with out-of-bounds values + FunctionLength int + ColorSpace int + CoordinateSys int + Alpha bool + Rectifier int // What to do with out-of-bounds values } func sigmoid(x float64) float64 { - return 1 / (1 + math.Exp(-x)) + return 1 / (1 + math.Exp(-x)) } func rectify(x float64, rectifier int) float64 { - switch rectifier { - case MOD: - return math.Mod(x, 1) - case CLAMP: - if x > 1 { - return 1 - } else if x < 0 { - return 0 - } - case SIGMOID: - return sigmoid(x) - } - return 0 + switch rectifier { + case MOD: + return math.Mod(x, 1) + case CLAMP: + if x > 1 { + return 1 + } else if x < 0 { + return 0 + } + case SIGMOID: + return sigmoid(x) + } + return 0 } func (conf *Config) nFunctions() int { - a := 0 - if conf.Alpha { a = 1 } - switch conf.ColorSpace { - case GRAYSCALE: - return a + 1 - case RGB, HSV, YCbCr: - return a + 3 - case CMYK: - return a + 4 - } - panic("Invalid color space!") - return a + a := 0 + if conf.Alpha { + a = 1 + } + switch conf.ColorSpace { + case GRAYSCALE: + return a + 1 + case RGB, HSV, YCbCr: + return a + 3 + case CMYK: + return a + 4 + } + panic("Invalid color space!") + return a } const defaultFunctionLength = 40 func GenerateImageFromFunctions(width int, height int, config Config, - functions []autoutils.Function, - vars []float64) image.Image { - var rect = image.Rectangle{image.Point{0, 0}, image.Point{width, height}} - img := image.NewRGBA(rect) - colorSpace := config.ColorSpace - alpha := config.Alpha - rectifier := config.Rectifier - nfunctions := len(functions) - rets := make([]uint8, nfunctions) - fwidth, fheight := float64(width), float64(height) - for y := 0; y < height; y++ { - for x := 0; x < width; x++ { - switch config.CoordinateSys { - case XY: - vars[0], vars[1] = float64(x)/fwidth, float64(y)/fheight - case RTHETA: - dx, dy := float64(x - width/2), float64(y - height/2) - vars[0] = math.Sqrt(dx * dx + dy * dy) / ((fwidth+fheight)/2) // r - vars[1] = math.Atan2(dy, dx) // theta - } - for i := range rets { - ret := rectify(functions[i].Evaluate(vars), rectifier) - rets[i] = uint8(255 * ret) - } - var r, g, b, a uint8 - a = 255 - switch (colorSpace) { - case RGB: - r, g, b = rets[0], rets[1], rets[2] - case GRAYSCALE: - r, g, b = rets[0], rets[0], rets[0] - case CMYK: - r, g, b = color.CMYKToRGB(rets[0], rets[1], rets[2], rets[3]) - case HSV: - r, g, b = autoutils.HSVToRGB(rets[0], rets[1], rets[2]) - case YCbCr: - r, g, b = color.YCbCrToRGB(rets[0], rets[1], rets[2]) - } - if (alpha) { - a = rets[nfunctions-1] - } - img.Set(x, y, color.RGBA{r, g, b, a}) - } - } - return img + functions []autoutils.Function, + vars []float64) image.Image { + var rect = image.Rectangle{image.Point{0, 0}, image.Point{width, height}} + img := image.NewRGBA(rect) + colorSpace := config.ColorSpace + alpha := config.Alpha + rectifier := config.Rectifier + nfunctions := len(functions) + rets := make([]uint8, nfunctions) + fwidth, fheight := float64(width), float64(height) + for y := 0; y < height; y++ { + for x := 0; x < width; x++ { + switch config.CoordinateSys { + case XY: + vars[0], vars[1] = float64(x)/fwidth, float64(y)/fheight + case RTHETA: + dx, dy := float64(x-width/2), float64(y-height/2) + vars[0] = math.Sqrt(dx*dx+dy*dy) / ((fwidth + fheight) / 2) // r + vars[1] = math.Atan2(dy, dx) // theta + } + for i := range rets { + ret := rectify(functions[i].Evaluate(vars), rectifier) + rets[i] = uint8(255 * ret) + } + var r, g, b, a uint8 + a = 255 + switch colorSpace { + case RGB: + r, g, b = rets[0], rets[1], rets[2] + case GRAYSCALE: + r, g, b = rets[0], rets[0], rets[0] + case CMYK: + r, g, b = color.CMYKToRGB(rets[0], rets[1], rets[2], rets[3]) + case HSV: + r, g, b = autoutils.HSVToRGB(rets[0], rets[1], rets[2]) + case YCbCr: + r, g, b = color.YCbCrToRGB(rets[0], rets[1], rets[2]) + } + if alpha { + a = rets[nfunctions-1] + } + img.Set(x, y, color.RGBA{r, g, b, a}) + } + } + return img } func GenerateImage(width int, height int, config Config) image.Image { - if config.FunctionLength == 0 { - // 0 value of config shouldn't have empty functions - config.FunctionLength = defaultFunctionLength - } + if config.FunctionLength == 0 { + // 0 value of config shouldn't have empty functions + config.FunctionLength = defaultFunctionLength + } - functionLength := config.FunctionLength + functionLength := config.FunctionLength - nfunctions := config.nFunctions() - functions := make([]autoutils.Function, nfunctions) - for i := range functions { - functions[i].Generate(2, functionLength) - } - vars := []float64{0, 0} - return GenerateImageFromFunctions(width, height, config, functions, vars) + nfunctions := config.nFunctions() + functions := make([]autoutils.Function, nfunctions) + for i := range functions { + functions[i].Generate(2, functionLength) + } + vars := []float64{0, 0} + return GenerateImageFromFunctions(width, height, config, functions, vars) } - func GenerateImages(width int, height int, config Config, number int, verbose bool) []image.Image { - c := make(chan image.Image) - for i := 0; i < number; i++ { - go func () { - c <- GenerateImage(width, height, config) - }() - } - imgs := make([]image.Image, number) - for i := range imgs { - imgs[i] = <-c + c := make(chan image.Image) + for i := 0; i < number; i++ { + go func() { + c <- GenerateImage(width, height, config) + }() + } + imgs := make([]image.Image, number) + for i := range imgs { + imgs[i] = <-c if verbose { fmt.Println("Generating images...", i+1, "/", number) } - } - return imgs + } + return imgs } type PaletteConfig struct { - NColors int - Alpha bool + NColors int + Alpha bool FunctionLength int - CoordinateSys int + CoordinateSys int } func GenerateImagePaletteFrom(width int, height int, conf PaletteConfig, - funcs []autoutils.Function, vars []float64, - palette []color.RGBA) image.Image { - img := image.NewRGBA(image.Rectangle{image.Point{0,0}, image.Point{width, height}}) - fwidth, fheight := float64(width), float64(height) + funcs []autoutils.Function, vars []float64, + palette []color.RGBA) image.Image { + img := image.NewRGBA(image.Rectangle{image.Point{0, 0}, image.Point{width, height}}) + fwidth, fheight := float64(width), float64(height) for y := 0; y < height; y++ { for x := 0; x < width; x++ { - switch conf.CoordinateSys { - case XY: - vars[0], vars[1] = float64(x)/fwidth, float64(y)/fheight - case RTHETA: - dx, dy := float64(x - width/2), float64(y - height/2) - vars[0] = math.Sqrt(dx * dx + dy * dy) / ((fwidth+fheight)/2) // r - vars[1] = math.Atan2(dy, dx) // theta - } + switch conf.CoordinateSys { + case XY: + vars[0], vars[1] = float64(x)/fwidth, float64(y)/fheight + case RTHETA: + dx, dy := float64(x-width/2), float64(y-height/2) + vars[0] = math.Sqrt(dx*dx+dy*dy) / ((fwidth + fheight) / 2) // r + vars[1] = math.Atan2(dy, dx) // theta + } for i := range palette { - if i == conf.NColors - 1 { + if i == conf.NColors-1 { // Background color img.Set(x, y, palette[i]) } else if funcs[i].Evaluate(vars) < 0 { @@ -216,7 +217,7 @@ func GenerateImagePalette(width int, height int, conf PaletteConfig) image.Image alpha := conf.Alpha functionLength := conf.FunctionLength - funcs := make([]autoutils.Function, nColors - 1) + funcs := make([]autoutils.Function, nColors-1) palette := make([]color.RGBA, nColors) // Choose palette diff --git a/autoart/autovideos.go b/autoart/autovideos.go index 4a51d1f..82c2747 100644 --- a/autoart/autovideos.go +++ b/autoart/autovideos.go @@ -24,103 +24,103 @@ NOTE: AutoVideos requires Go 1.11 or newer (for temp file patterns). package autoart import ( + "fmt" "github.com/pommicket/autoart/autoutils" + "image" + "image/color" + "image/png" "io" "io/ioutil" - "image" - "image/png" - "image/color" - "math/rand" - "fmt" - "os/exec" + "math/rand" "os" + "os/exec" ) func generateFrame(width int, height int, paletted bool, config Config, - pconfig PaletteConfig, palette []color.RGBA, - functions []autoutils.Function, time float64, - frameNumber int64, file *os.File) error { // NOTE: file is closed by this function + pconfig PaletteConfig, palette []color.RGBA, + functions []autoutils.Function, time float64, + frameNumber int64, file *os.File) error { // NOTE: file is closed by this function vars := []float64{0, 0, time} - var img image.Image - if paletted { - img = GenerateImagePaletteFrom(width, height, pconfig, functions, vars, palette) - } else { - img = GenerateImageFromFunctions(width, height, config, functions, vars) - } + var img image.Image + if paletted { + img = GenerateImagePaletteFrom(width, height, pconfig, functions, vars, palette) + } else { + img = GenerateImageFromFunctions(width, height, config, functions, vars) + } if err := png.Encode(file, img); err != nil { - file.Close() + file.Close() return err } - return file.Close() + return file.Close() } func generateVideo(width int, height int, paletted bool, config Config, - pconfig PaletteConfig, time float64, - framerate int, filename string, verbose bool) error { - - var palette []color.RGBA - if paletted { - // Generate palette - palette = make([]color.RGBA, pconfig.NColors) - for i := range palette { - r := uint8(rand.Intn(256)) - g := uint8(rand.Intn(256)) - b := uint8(rand.Intn(256)) - a := uint8(255) - if pconfig.Alpha { - a = uint8(rand.Intn(256)) - } - palette[i] = color.RGBA{r, g, b, a} - } - } - - if config.FunctionLength == 0 { + pconfig PaletteConfig, time float64, + framerate int, filename string, verbose bool) error { + + var palette []color.RGBA + if paletted { + // Generate palette + palette = make([]color.RGBA, pconfig.NColors) + for i := range palette { + r := uint8(rand.Intn(256)) + g := uint8(rand.Intn(256)) + b := uint8(rand.Intn(256)) + a := uint8(255) + if pconfig.Alpha { + a = uint8(rand.Intn(256)) + } + palette[i] = color.RGBA{r, g, b, a} + } + } + + if config.FunctionLength == 0 { // 0 value of config shouldn't have empty functions config.FunctionLength = defaultFunctionLength - } - - var functionLength int - if paletted { - functionLength = pconfig.FunctionLength - } else { - functionLength = config.FunctionLength - } - - var nfunctions int - if paletted { - nfunctions = pconfig.NColors - } else { - nfunctions = config.nFunctions() - } + } + + var functionLength int + if paletted { + functionLength = pconfig.FunctionLength + } else { + functionLength = config.FunctionLength + } + + var nfunctions int + if paletted { + nfunctions = pconfig.NColors + } else { + nfunctions = config.nFunctions() + } functions := make([]autoutils.Function, nfunctions) - for i := range functions { - functions[i].Generate(3, functionLength) - } - - frames := int64(time * float64(framerate)) - - files := make([]*os.File, frames) - defer func() { - // Delete all temporary files - for _, file := range files { - if file != nil { - os.Remove(file.Name()) - } - } - }() - // Create temporary frame files - for i := range files { - var err error - files[i], err = ioutil.TempFile("", "frame*.png") - if err != nil { - return err - } - } - - autoutils.RunInBatches(frames, "Generating video...", func (n int64, errs chan<- error) { - t := float64(n)/float64(framerate) - errs <- generateFrame(width, height, paletted, config, pconfig, palette, functions, t, n, files[n]) - }) + for i := range functions { + functions[i].Generate(3, functionLength) + } + + frames := int64(time * float64(framerate)) + + files := make([]*os.File, frames) + defer func() { + // Delete all temporary files + for _, file := range files { + if file != nil { + os.Remove(file.Name()) + } + } + }() + // Create temporary frame files + for i := range files { + var err error + files[i], err = ioutil.TempFile("", "frame*.png") + if err != nil { + return err + } + } + + autoutils.RunInBatches(frames, "Generating video...", func(n int64, errs chan<- error) { + t := float64(n) / float64(framerate) + errs <- generateFrame(width, height, paletted, config, pconfig, palette, functions, t, n, files[n]) + }) ffmpegInputFile, err := ioutil.TempFile("", "input*.txt") if err != nil { @@ -129,7 +129,7 @@ func generateVideo(width int, height int, paletted bool, config Config, defer os.Remove(ffmpegInputFile.Name()) for _, file := range files { - name := file.Name() + name := file.Name() info := fmt.Sprintf("file '%v'\nduration %v\n", name, 1/float64(framerate)) if _, err = io.WriteString(ffmpegInputFile, info); err != nil { return err @@ -159,13 +159,13 @@ func generateVideo(width int, height int, paletted bool, config Config, } func GenerateVideo(width int, height int, config Config, time float64, - framerate int, filename string, verbose bool) error { - var pconfig PaletteConfig - return generateVideo(width, height, false, config, pconfig, time, framerate, filename, verbose) + framerate int, filename string, verbose bool) error { + var pconfig PaletteConfig + return generateVideo(width, height, false, config, pconfig, time, framerate, filename, verbose) } func GenerateVideoPalette(width int, height int, pconfig PaletteConfig, - time float64, framerate int, filename string, verbose bool) error { - var config Config - return generateVideo(width, height, true, config, pconfig, time, framerate, filename, verbose) + time float64, framerate int, filename string, verbose bool) error { + var config Config + return generateVideo(width, height, true, config, pconfig, time, framerate, filename, verbose) } diff --git a/autoutils/audio.go b/autoutils/audio.go index 19ae5f3..67fe923 100644 --- a/autoutils/audio.go +++ b/autoutils/audio.go @@ -20,46 +20,69 @@ along with AutoArt. If not, see <https://www.gnu.org/licenses/>. package autoutils import ( - "io" - "encoding/binary" + "encoding/binary" + "io" ) // Write a header to writer. You need to decide ahead of time how many samples // you want. func WriteAudioHeader(writer io.Writer, nSamples int64, channels, sampleRate int32) error { - w := func (data interface{}) error { - return binary.Write(writer, binary.LittleEndian, data) - } - var err error - if err = w([]byte("RIFF")); err != nil { return err } - var chunkSize1 uint32 = 36 + uint32(nSamples) - if err = w(chunkSize1); err != nil { return err } - if err = w([]byte("WAVEfmt ")); err != nil { return err } - var subchunk1size uint32 = 16 - if err = w(subchunk1size); err != nil { return err } - var audioFormat uint16 = 1 - if err = w(audioFormat); err != nil { return err } - var nChannels uint16 = uint16(channels) - if err = w(nChannels); err != nil { return err } - var srate uint32 = uint32(sampleRate) - if err = w(srate); err != nil { return err } - var byteRate uint32 = srate * uint32(nChannels) - if err = w(byteRate); err != nil { return err } - var blockAlign uint16 = nChannels - if err = w(blockAlign); err != nil { return err } - var bitsPerSample uint16 = 8 - if err = w(bitsPerSample); err != nil { return err } - if err = w([]byte("data")); err != nil { return err } - var chunkSize2 uint32 = uint32(nSamples) * uint32(nChannels) - if err = w(chunkSize2); err != nil { return err } - return nil + w := func(data interface{}) error { + return binary.Write(writer, binary.LittleEndian, data) + } + var err error + if err = w([]byte("RIFF")); err != nil { + return err + } + var chunkSize1 uint32 = 36 + uint32(nSamples) + if err = w(chunkSize1); err != nil { + return err + } + if err = w([]byte("WAVEfmt ")); err != nil { + return err + } + var subchunk1size uint32 = 16 + if err = w(subchunk1size); err != nil { + return err + } + var audioFormat uint16 = 1 + if err = w(audioFormat); err != nil { + return err + } + var nChannels uint16 = uint16(channels) + if err = w(nChannels); err != nil { + return err + } + var srate uint32 = uint32(sampleRate) + if err = w(srate); err != nil { + return err + } + var byteRate uint32 = srate * uint32(nChannels) + if err = w(byteRate); err != nil { + return err + } + var blockAlign uint16 = nChannels + if err = w(blockAlign); err != nil { + return err + } + var bitsPerSample uint16 = 8 + if err = w(bitsPerSample); err != nil { + return err + } + if err = w([]byte("data")); err != nil { + return err + } + var chunkSize2 uint32 = uint32(nSamples) * uint32(nChannels) + if err = w(chunkSize2); err != nil { + return err + } + return nil } - // Writes some samples to the writer. You will need to write a header before // any samples. func WriteAudioSamples(writer io.Writer, samples []uint8) error { - return binary.Write(writer, binary.LittleEndian, samples) + return binary.Write(writer, binary.LittleEndian, samples) } /* @@ -72,12 +95,12 @@ you're using that many?!) */ func WriteAudio(writer io.Writer, audio []uint8, channels, sampleRate int32) error { - err := WriteAudioHeader(writer, int64(len(audio)), channels, sampleRate) - if err != nil { - return err - } - if err = WriteAudioSamples(writer, audio); err != nil { - return err - } - return nil + err := WriteAudioHeader(writer, int64(len(audio)), channels, sampleRate) + if err != nil { + return err + } + if err = WriteAudioSamples(writer, audio); err != nil { + return err + } + return nil } diff --git a/autoutils/batches.go b/autoutils/batches.go index fb39fc1..7e27473 100644 --- a/autoutils/batches.go +++ b/autoutils/batches.go @@ -20,7 +20,7 @@ along with AutoArt. If not, see <https://www.gnu.org/licenses/>. package autoutils import ( - "fmt" + "fmt" ) /* @@ -31,26 +31,27 @@ a message will be printed, starting with progress, and showing how many batches have been completed so far out of the total number of batches. */ const batchSize = 32 -func RunInBatches(number int64, progress string, f func (n int64, errs chan<- error)) error { - nBatches := number / batchSize - errs := make(chan error) - for batch := int64(0); batch <= nBatches; batch++ { - fmt.Println(progress, batch+1, "/", nBatches+1) - thisBatchSize := batchSize - if batch == nBatches { - // Deal with case of last batch - thisBatchSize = int(number - nBatches * batchSize) - } - for task := 0; task < thisBatchSize; task++ { - go f(int64(task) + batchSize * batch, errs) - } - - for completed := 0; completed < thisBatchSize; completed++ { - err := <-errs - if err != nil { - return err - } - } - } - return nil -}
\ No newline at end of file + +func RunInBatches(number int64, progress string, f func(n int64, errs chan<- error)) error { + nBatches := number / batchSize + errs := make(chan error) + for batch := int64(0); batch <= nBatches; batch++ { + fmt.Println(progress, batch+1, "/", nBatches+1) + thisBatchSize := batchSize + if batch == nBatches { + // Deal with case of last batch + thisBatchSize = int(number - nBatches*batchSize) + } + for task := 0; task < thisBatchSize; task++ { + go f(int64(task)+batchSize*batch, errs) + } + + for completed := 0; completed < thisBatchSize; completed++ { + err := <-errs + if err != nil { + return err + } + } + } + return nil +} diff --git a/autoutils/colorconversion.go b/autoutils/colorconversion.go index af21668..6128e39 100644 --- a/autoutils/colorconversion.go +++ b/autoutils/colorconversion.go @@ -18,40 +18,41 @@ along with AutoArt. If not, see <https://www.gnu.org/licenses/>. */ package autoutils + import ( - "math" + "math" ) func HSVToRGB(h uint8, s uint8, v uint8) (uint8, uint8, uint8) { - // https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB - V := float64(v) / 256 - S := float64(s) / 256 - C := V * S - H := float64(h) / (256/6) - X := float64(C) * (1 - math.Abs(math.Mod(H, 2) - 1)) - var r, g, b float64 - switch true { - case s == 0: - r, g, b = 0, 0, 0 - case H <= 1: - r, g, b = C, X, 0 - case H <= 2: - r, g, b = X, C, 0 - case H <= 3: - r, g, b = 0, C, X - case H <= 4: - r, g, b = 0, X, C - case H <= 5: - r, g, b = X, 0, C - default: - r, g, b = C, 0, X - } - m := V - C - r += m - g += m - b += m - r *= 255 - g *= 255 - b *= 255 - return uint8(r), uint8(g), uint8(b) + // https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB + V := float64(v) / 256 + S := float64(s) / 256 + C := V * S + H := float64(h) / (256 / 6) + X := float64(C) * (1 - math.Abs(math.Mod(H, 2)-1)) + var r, g, b float64 + switch true { + case s == 0: + r, g, b = 0, 0, 0 + case H <= 1: + r, g, b = C, X, 0 + case H <= 2: + r, g, b = X, C, 0 + case H <= 3: + r, g, b = 0, C, X + case H <= 4: + r, g, b = 0, X, C + case H <= 5: + r, g, b = X, 0, C + default: + r, g, b = C, 0, X + } + m := V - C + r += m + g += m + b += m + r *= 255 + g *= 255 + b *= 255 + return uint8(r), uint8(g), uint8(b) } diff --git a/autoutils/functiongenerator.go b/autoutils/functiongenerator.go index 7acb680..20ab0e0 100644 --- a/autoutils/functiongenerator.go +++ b/autoutils/functiongenerator.go @@ -20,27 +20,27 @@ along with AutoArt. If not, see <https://www.gnu.org/licenses/>. package autoutils import ( - "math" - "math/rand" - "fmt" + "fmt" + "math" + "math/rand" ) // Operators const ( - CONST = iota - ADD - SUB - MUL - DIV + CONST = iota + ADD + SUB + MUL + DIV MIN MAX - SQRT - SIN - COS - TAN + SQRT + SIN + COS + TAN LOG EXP - OPERATOR_COUNT + OPERATOR_COUNT ) const FIRST_BINARY = ADD const FIRST_UNARY = SQRT @@ -49,147 +49,147 @@ const UNARY_COUNT = OPERATOR_COUNT - FIRST_UNARY const FIRST_VAR = OPERATOR_COUNT type Operator struct { - op int // Operator number. If operator is a variable, v, it is equal to FIRST_VAR + v - constant float64 // Constant (if op = CONST) + op int // Operator number. If operator is a variable, v, it is equal to FIRST_VAR + v + constant float64 // Constant (if op = CONST) } type Function struct { - nvars int - operators []Operator + nvars int + operators []Operator } // Generate a random function f with the given length (i.e. len(f.operators)) // and the given number of variables func (f *Function) Generate(nvars int, length int) { - f.nvars = nvars - f.operators = make([]Operator, length) - nsOnStack := 0 - i := 0 - for nsOnStack + i < length { - var operator Operator - var optype int - if nsOnStack == 0 { - // Pick a random variable - optype = 0 - } else if nsOnStack == 1 { - // Pick a constant/variable/unary operator - optype = rand.Intn(3) - } else { - // Pick a constant/variable/unary/binary operator - optype = rand.Intn(4) - } - switch optype { - case 0: - // variable - operator.op = FIRST_VAR + rand.Intn(nvars) - nsOnStack++ - case 1: - // Constant - operator.op = CONST - operator.constant = rand.Float64() - nsOnStack++ - case 2: - // unary - operator.op = rand.Intn(UNARY_COUNT) + FIRST_UNARY - case 3: - // binary - operator.op = rand.Intn(BINARY_COUNT) + FIRST_BINARY - nsOnStack-- - } - f.operators[i] = operator - i++ - } - - if nsOnStack + i == length { - // Add a unary operator - f.operators[i].op = rand.Intn(UNARY_COUNT) + FIRST_UNARY - i++ - } - - // Keep adding binary operators until nsOnStack == 1 - for nsOnStack > 1 { - f.operators[i].op = rand.Intn(BINARY_COUNT) + FIRST_BINARY - nsOnStack-- - i++ - } + f.nvars = nvars + f.operators = make([]Operator, length) + nsOnStack := 0 + i := 0 + for nsOnStack+i < length { + var operator Operator + var optype int + if nsOnStack == 0 { + // Pick a random variable + optype = 0 + } else if nsOnStack == 1 { + // Pick a constant/variable/unary operator + optype = rand.Intn(3) + } else { + // Pick a constant/variable/unary/binary operator + optype = rand.Intn(4) + } + switch optype { + case 0: + // variable + operator.op = FIRST_VAR + rand.Intn(nvars) + nsOnStack++ + case 1: + // Constant + operator.op = CONST + operator.constant = rand.Float64() + nsOnStack++ + case 2: + // unary + operator.op = rand.Intn(UNARY_COUNT) + FIRST_UNARY + case 3: + // binary + operator.op = rand.Intn(BINARY_COUNT) + FIRST_BINARY + nsOnStack-- + } + f.operators[i] = operator + i++ + } + + if nsOnStack+i == length { + // Add a unary operator + f.operators[i].op = rand.Intn(UNARY_COUNT) + FIRST_UNARY + i++ + } + + // Keep adding binary operators until nsOnStack == 1 + for nsOnStack > 1 { + f.operators[i].op = rand.Intn(BINARY_COUNT) + FIRST_BINARY + nsOnStack-- + i++ + } } func (f *Function) Evaluate(vars []float64) float64 { - var stack []float64 - for _, op := range f.operators { - l := len(stack) - switch (op.op) { - case CONST: - stack = append(stack, op.constant) - case ADD: - stack[l-2] += stack[l-1] - stack = stack[:l-1] - case SUB: - stack[l-2] -= stack[l-1] - stack = stack[:l-1] - case MUL: - stack[l-2] *= stack[l-1] - stack = stack[:l-1] - case DIV: - if stack[l-1] == 0 { // Check for division by 0 - stack[l-1] = 0.01 - } - stack[l-2] /= stack[l-1] - stack = stack[:l-1] + var stack []float64 + for _, op := range f.operators { + l := len(stack) + switch op.op { + case CONST: + stack = append(stack, op.constant) + case ADD: + stack[l-2] += stack[l-1] + stack = stack[:l-1] + case SUB: + stack[l-2] -= stack[l-1] + stack = stack[:l-1] + case MUL: + stack[l-2] *= stack[l-1] + stack = stack[:l-1] + case DIV: + if stack[l-1] == 0 { // Check for division by 0 + stack[l-1] = 0.01 + } + stack[l-2] /= stack[l-1] + stack = stack[:l-1] case MIN: stack[l-2] = math.Min(stack[l-2], stack[l-1]) stack = stack[:l-1] case MAX: stack[l-2] = math.Max(stack[l-2], stack[l-1]) stack = stack[:l-1] - case SQRT: - stack[l-1] = math.Sqrt(math.Abs(stack[l-1])) - case SIN: - stack[l-1] = math.Sin(stack[l-1]) - case COS: - stack[l-1] = math.Cos(stack[l-1]) - case TAN: - stack[l-1] = math.Tan(stack[l-1]) + case SQRT: + stack[l-1] = math.Sqrt(math.Abs(stack[l-1])) + case SIN: + stack[l-1] = math.Sin(stack[l-1]) + case COS: + stack[l-1] = math.Cos(stack[l-1]) + case TAN: + stack[l-1] = math.Tan(stack[l-1]) case LOG: stack[l-1] = math.Log(math.Abs(stack[l-1])) case EXP: stack[l-1] = math.Exp(stack[l-1]) - default: - stack = append(stack, vars[op.op - FIRST_VAR]) - } - } - return stack[0] + default: + stack = append(stack, vars[op.op-FIRST_VAR]) + } + } + return stack[0] } func (f *Function) String() string { - var str string - for _, op := range f.operators { - switch (op.op) { - case CONST: - str += fmt.Sprintf("%v",op.constant) - case ADD: - str += "+" - case SUB: - str += "-" - case MUL: - str += "*" - case DIV: - str += "/" - case SQRT: - str += "sqrt" - case SIN: - str += "sin" - case COS: - str += "cos" - case TAN: - str += "tan" - default: - str += fmt.Sprintf("v%v", op.op - FIRST_VAR) - } - str += " " - } - return str + var str string + for _, op := range f.operators { + switch op.op { + case CONST: + str += fmt.Sprintf("%v", op.constant) + case ADD: + str += "+" + case SUB: + str += "-" + case MUL: + str += "*" + case DIV: + str += "/" + case SQRT: + str += "sqrt" + case SIN: + str += "sin" + case COS: + str += "cos" + case TAN: + str += "tan" + default: + str += fmt.Sprintf("v%v", op.op-FIRST_VAR) + } + str += " " + } + return str } const mutationRate = 0.01 @@ -202,14 +202,14 @@ func (f *Function) Mutate() { } } -func (f *Function) Breed(f1* Function, f2 *Function) { +func (f *Function) Breed(f1 *Function, f2 *Function) { // f(x) = (f1(x) + f2(x)) / 2 - f.operators = make([]Operator, len(f1.operators) + len(f2.operators) + 3) + f.operators = make([]Operator, len(f1.operators)+len(f2.operators)+3) for i, o := range f1.operators { f.operators[i] = o } for i, o := range f2.operators { - f.operators[i + len(f1.operators)] = o + f.operators[i+len(f1.operators)] = o } i := len(f1.operators) + len(f2.operators) f.operators[i].op = ADD |