diff options
Diffstat (limited to 'autoart')
-rw-r--r-- | autoart/autoaudio.go | 56 | ||||
-rw-r--r-- | autoart/autoimages.go | 257 | ||||
-rw-r--r-- | autoart/autovideos.go | 171 |
3 files changed, 484 insertions, 0 deletions
diff --git a/autoart/autoaudio.go b/autoart/autoaudio.go new file mode 100644 index 0000000..05a91d4 --- /dev/null +++ b/autoart/autoaudio.go @@ -0,0 +1,56 @@ +/* +Copyright (C) 2019 Leo Tenenbaum + +This file is part of AutoArt. + +AutoArt is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +AutoArt is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with AutoArt. If not, see <https://www.gnu.org/licenses/>. +*/ + +package autoart + +import ( + "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 diff --git a/autoart/autoimages.go b/autoart/autoimages.go new file mode 100644 index 0000000..53eea18 --- /dev/null +++ b/autoart/autoimages.go @@ -0,0 +1,257 @@ +/* +Copyright (C) 2019 Leo Tenenbaum + +This file is part of AutoArt. + +AutoArt is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +AutoArt is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +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" +) + +const ( + XY = iota + RTHETA +) + +const ( + RGB = iota + GRAYSCALE + CMYK + HSV + YCbCr +) + +const ( + 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 +} + +func sigmoid(x float64) float64 { + 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 +} + +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 +} + +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 +} + +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 + } + + 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) +} + + +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 + if verbose { + fmt.Println("Generating images...", i+1, "/", number) + } + } + return imgs +} + +type PaletteConfig struct { + NColors int + Alpha bool + FunctionLength 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) + 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 + } + for i := range palette { + if i == conf.NColors - 1 { + // Background color + img.Set(x, y, palette[i]) + } else if funcs[i].Evaluate(vars) < 0 { + img.Set(x, y, palette[i]) + break + } + } + } + } + return img +} + +func GenerateImagePalette(width int, height int, conf PaletteConfig) image.Image { + nColors := conf.NColors + alpha := conf.Alpha + functionLength := conf.FunctionLength + + funcs := make([]autoutils.Function, nColors - 1) + palette := make([]color.RGBA, nColors) + + // Choose palette + for i := range palette { + r, g, b := rand.Intn(256), rand.Intn(256), rand.Intn(256) + var a int + if alpha { + a = rand.Intn(256) + } else { + a = 255 + } + palette[i] = color.RGBA{uint8(r), uint8(g), uint8(b), uint8(a)} + } + // Choose functions + for i := range funcs { + funcs[i].Generate(2, functionLength) + } + + vars := make([]float64, 2) + return GenerateImagePaletteFrom(width, height, conf, funcs, vars, palette) +} + +func GenerateImagesPalette(width int, height int, conf PaletteConfig, number int, verbose bool) []image.Image { + c := make(chan image.Image) + for i := 0; i < number; i++ { + go func() { + c <- GenerateImagePalette(width, height, conf) + }() + } + images := make([]image.Image, number) + for i := 0; i < number; i++ { + images[i] = <-c + if verbose { + fmt.Println("Generating images...", i+1, "/", number) + } + } + return images +} diff --git a/autoart/autovideos.go b/autoart/autovideos.go new file mode 100644 index 0000000..4a51d1f --- /dev/null +++ b/autoart/autovideos.go @@ -0,0 +1,171 @@ +/* +Copyright (C) 2019 Leo Tenenbaum + +This file is part of AutoArt. + +AutoArt is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +AutoArt is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with AutoArt. If not, see <https://www.gnu.org/licenses/>. +*/ + +/* +NOTE: AutoVideos requires Go 1.11 or newer (for temp file patterns). +*/ + +package autoart + +import ( + "github.com/pommicket/autoart/autoutils" + "io" + "io/ioutil" + "image" + "image/png" + "image/color" + "math/rand" + "fmt" + "os/exec" + "os" +) + +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 + 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) + } + if err := png.Encode(file, img); err != nil { + file.Close() + return err + } + 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 { + // 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() + } + 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]) + }) + + ffmpegInputFile, err := ioutil.TempFile("", "input*.txt") + if err != nil { + return err + } + defer os.Remove(ffmpegInputFile.Name()) + + for _, file := range files { + 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 + } + } + + fmt.Println(ffmpegInputFile.Name()) + + if err = ffmpegInputFile.Close(); err != nil { + return err + } + + if verbose { + fmt.Println("ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", ffmpegInputFile.Name(), filename) + } + + cmd := exec.Command("ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", ffmpegInputFile.Name(), filename) + if verbose { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } + if err = cmd.Run(); err != nil { + return fmt.Errorf("ffmpeg failed: %v", err) + } + + return nil +} + +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) +} + +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) +} |