commit ac61662d6df413c6bf511c828754b49f0cc0bfd3 Author: Rudi Date: Fri Feb 25 09:50:07 2022 -0500 Initial Commit diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..ff01dd8 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,33 @@ +kind: pipeline +type: docker +name: default + +steps: +- name: Build + image: golang + commands: + - go get ./.. + - go vet -v + - env + - GOOS="linux" go build -ldflags="-w -s" -o BackgroundDaemon-Linux + - GOOS="windows" go build -ldflags="-w -s" -o BackgroundDaemon-Win.exe + - GOOS="darwin" go build -ldflags="-w -s" -o BackgroundDaemon-macOS +- name: gitea_release + image: plugins/gitea-release + settings: + base_url: https://git.nightmare.haus + api_key: + from_secret: gitea_token + files: + - ./BackgroundDaemon-Linux + - ./BackgroundDaemon-Win.exe + - ./BackgroundDaemon-macOS + checksum: + - md5 + - sha1 + - sha256 + - sha512 + - adler32 + - crc32 + when: + event: [tag] \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..91da14f --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.png +*.jpg +*.jpeg +BackgroundDaemon diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b5cdf64 --- /dev/null +++ b/go.mod @@ -0,0 +1,20 @@ +module git.nightmare.haus/rudi/BackgroundDaemon + +go 1.17 + +require ( + github.com/fogleman/gg v1.3.0 + github.com/reujab/wallpaper v0.0.0-20210630195606-5f9f655b3740 +) + +require ( + github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 // indirect + github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329 // indirect + github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect + golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect + golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13 // indirect + gopkg.in/ini.v1 v1.62.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3e0431b --- /dev/null +++ b/go.sum @@ -0,0 +1,40 @@ +github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 h1:Y5Q2mEwfzjMt5+3u70Gtw93ZOu2UuPeeeTBDntF7FoY= +github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 h1:dy+DS31tGEGCsZzB45HmJJNHjur8GDgtRNX9U7HnSX4= +github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240/go.mod h1:3P4UH/k22rXyHIJD2w4h2XMqPX4Of/eySEZq9L6wqc4= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329 h1:qq2nCpSrXrmvDGRxW0ruW9BVEV1CN2a9YDOExdt+U0o= +github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329/go.mod h1:2VPVQDR4wO7KXHwP+DAypEy67rXf+okUx2zjgpCxZw4= +github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc= +github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= +github.com/reujab/wallpaper v0.0.0-20210630195606-5f9f655b3740 h1:X6IDPPN+zrSClp0Q+JiERA//d8L0WcU5MqcGeulCW1A= +github.com/reujab/wallpaper v0.0.0-20210630195606-5f9f655b3740/go.mod h1:WYwPVmM/8szeItLeWkwZSLRvQgrvsvstRzgznR8+E4Q= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13 h1:5jaG59Zhd+8ZXe8C+lgiAGqkOaZBruqrWclLkgAww34= +golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/main.go b/main.go new file mode 100644 index 0000000..8b1e741 --- /dev/null +++ b/main.go @@ -0,0 +1,187 @@ +package main + +// imports +import ( + "encoding/json" + "flag" + "fmt" + "image" + _ "image/jpeg" + "image/png" + "io" + "log" + "math/rand" + "net/http" + "os" + "time" + + "github.com/kbinani/screenshot" + "github.com/reujab/wallpaper" +) + +var ( + sourceImgName string + outputImgName string + useNasa bool + useRandom bool + setWallpaper bool + useSketch bool + nasaAPI string + totalCycleCount = 5000 +) + +func init() { + flag.BoolVar(&useNasa, "nasa", false, "Use NASA Astronomy Pic Of the Day as a source image") + flag.BoolVar(&setWallpaper, "set", false, "Set user wallpaper to the output.") + flag.BoolVar(&useRandom, "unsplash", false, "Use random unsplash image for seed (Will grab dimensions from background or source if set)") + flag.BoolVar(&useSketch, "sketch", false, "Use Sketch to generate a new image.") + flag.StringVar(&sourceImgName, "source", "", "Source image, will override current background") + flag.StringVar(&outputImgName, "output", "out.png", "Output image filename") + flag.StringVar(&nasaAPI, "nasa-key", "DEMO_KEY", "Supply your own API Key for NASA APOD") +} + +func main() { + flag.Parse() + background, err := wallpaper.Get() + if err != nil { + log.Panicln(err) + } + bounds := screenshot.GetDisplayBounds(0) + + var img image.Image + if useRandom { + img, err = loadRandomUnsplashImage(bounds.Dx(), bounds.Dy()) + if err != nil { + log.Panicln(err) + } + } else if useNasa { + resp, err := http.Get(fmt.Sprintf("https://api.nasa.gov/planetary/apod?api_key=%+v", nasaAPI)) + if err != nil { + log.Fatalf("\nUnable to get NASA API Response\n") + return + } + var apod APODResponse + decoder := json.NewDecoder(resp.Body) + err = decoder.Decode(&apod) + if err != nil { + log.Fatalf("\nUnable to decode APOD Response: \n%+v\n", resp) + return + } + file, err := os.CreateTemp(".", apod.Hdurl) + if err != nil { + log.Panicln(err) + } + defer os.Remove(file.Name()) + downloadFile(file.Name(), apod.Hdurl) + img, err = loadImage(file.Name()) + if err != nil { + log.Panicln(err) + } + } else if len(sourceImgName) > 0 { + img, err = loadImage(sourceImgName) + if err != nil { + log.Panicln(err) + } + } else { + img, err = loadImage(background) + if err != nil { + log.Println(background) + log.Panicln(err) + } + } + + s := NewSketch(img, UserParams{ + StrokeRatio: 0.75, + DestWidth: bounds.Dx(), + DestHeight: bounds.Dy(), + InitialAlpha: 0.1, + StrokeReduction: 0.002, + AlphaIncrease: 0.06, + StrokeInversionThreshold: 0.05, + StrokeJitter: int(0.1 * float64(bounds.Dx())), + MinEdgeCount: 3, + MaxEdgeCount: 8, + }) + + rand.Seed(time.Now().Unix()) + + if useSketch { + for i := 0; i < totalCycleCount; i++ { + s.Update() + } + + saveOutput(s.Output(), outputImgName) + } else { + + saveOutput(img, outputImgName) + } + + if setWallpaper { + err = wallpaper.SetFromFile(outputImgName) + if err != nil { + log.Panicln(err) + } + } +} + +func loadRandomUnsplashImage(width, height int) (image.Image, error) { + url := fmt.Sprintf("https://source.unsplash.com/random/%dx%d", width, height) + res, err := http.Get(url) + if err != nil { + return nil, err + } + defer res.Body.Close() + + img, _, err := image.Decode(res.Body) + return img, err +} + +func loadImage(src string) (image.Image, error) { + file, _ := os.Open(sourceImgName) + defer file.Close() + img, _, err := image.Decode(file) + return img, err +} + +func saveOutput(img image.Image, filePath string) error { + f, err := os.Create(filePath) + if err != nil { + return err + } + defer f.Close() + + // Encode to `PNG` with `DefaultCompression` level + // then save to file + err = png.Encode(f, img) + if err != nil { + return err + } + + return nil +} + +// https://stackoverflow.com/questions/33845770/how-do-i-download-a-file-with-a-http-request-in-go-language/33845771 +func downloadFile(filepath string, url string) (err error) { + + // Create the file + out, err := os.Create(filepath) + if err != nil { + return err + } + defer out.Close() + + // Get the data + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + // Writer the body to file + _, err = io.Copy(out, resp.Body) + if err != nil { + return err + } + + return nil +} diff --git a/sketch.go b/sketch.go new file mode 100644 index 0000000..409ea07 --- /dev/null +++ b/sketch.go @@ -0,0 +1,92 @@ +package main + +// imports +import ( + "github.com/fogleman/gg" + "image" + "image/color" + "math/rand" +) + +// imports + +type UserParams struct { + StrokeRatio float64 + DestWidth int + DestHeight int + InitialAlpha float64 + StrokeReduction float64 + AlphaIncrease float64 + StrokeInversionThreshold float64 + StrokeJitter int + MinEdgeCount int + MaxEdgeCount int +} + +type Sketch struct { + UserParams // embed for easier access + source image.Image + dc *gg.Context + sourceWidth int + sourceHeight int + strokeSize float64 + initialStrokeSize float64 +} + +func NewSketch(source image.Image, userParams UserParams) *Sketch { + s := &Sketch{UserParams: userParams} + bounds := source.Bounds() + s.sourceWidth, s.sourceHeight = bounds.Max.X, bounds.Max.Y + s.initialStrokeSize = s.StrokeRatio * float64(s.DestWidth) + s.strokeSize = s.initialStrokeSize + + canvas := gg.NewContext(s.DestWidth, s.DestHeight) + canvas.SetColor(color.Black) + canvas.DrawRectangle(0, 0, float64(s.DestWidth), float64(s.DestHeight)) + canvas.FillPreserve() + + s.source = source + s.dc = canvas + return s +} + +func (s *Sketch) Update() { + rndX := rand.Float64() * float64(s.sourceWidth) + rndY := rand.Float64() * float64(s.sourceHeight) + r, g, b := rgb255(s.source.At(int(rndX), int(rndY))) + + destX := rndX * float64(s.DestWidth) / float64(s.sourceWidth) + destX += float64(randRange(s.StrokeJitter)) + destY := rndY * float64(s.DestHeight) / float64(s.sourceHeight) + destY += float64(randRange(s.StrokeJitter)) + edges := s.MinEdgeCount + rand.Intn(s.MaxEdgeCount-s.MinEdgeCount+1) + + s.dc.SetRGBA255(r, g, b, int(s.InitialAlpha)) + s.dc.DrawRegularPolygon(edges, destX, destY, s.strokeSize, rand.Float64()) + s.dc.FillPreserve() + + if s.strokeSize <= s.StrokeInversionThreshold*s.initialStrokeSize { + if (r+g+b)/3 < 128 { + s.dc.SetRGBA255(255, 255, 255, int(s.InitialAlpha*2)) + } else { + s.dc.SetRGBA255(0, 0, 0, int(s.InitialAlpha*2)) + } + } + s.dc.Stroke() + + s.strokeSize -= s.StrokeReduction * s.strokeSize + s.InitialAlpha += s.AlphaIncrease +} + +func (s *Sketch) Output() image.Image { + return s.dc.Image() +} + +func rgb255(c color.Color) (r, g, b int) { + r0, g0, b0, _ := c.RGBA() + return int(r0 / 257), int(g0 / 257), int(b0 / 257) +} + +func randRange(max int) int { + return -max + rand.Intn(2*max) +} diff --git a/types.go b/types.go new file mode 100644 index 0000000..4f1dbe3 --- /dev/null +++ b/types.go @@ -0,0 +1,33 @@ +package main + +type APODResponse struct { + Date string `json:"date"` + Explanation string `json:"explanation"` + Hdurl string `json:"hdurl"` + MediaType string `json:"media_type"` + ServiceVersion string `json:"service_version"` + Title string `json:"title"` + URL string `json:"url"` +} + +type MarsResponse struct { + Photos []struct { + ID int `json:"id"` + Sol int `json:"sol"` + Camera struct { + ID int `json:"id"` + Name string `json:"name"` + RoverID int `json:"rover_id"` + FullName string `json:"full_name"` + } `json:"camera"` + ImgSrc string `json:"img_src"` + EarthDate string `json:"earth_date"` + Rover struct { + ID int `json:"id"` + Name string `json:"name"` + LandingDate string `json:"landing_date"` + LaunchDate string `json:"launch_date"` + Status string `json:"status"` + } `json:"rover"` + } `json:"photos"` +}