package main import ( "image" "image/color" "log" "runtime" "sync" ) // job passed to goroutines. blend color from img1 and img2 at position (x, y) type blendColorJob struct { X int Y int Img1 image.Image Img2 image.Image } // new color after blend, to apply at position (x, y) type blendColorResult struct { X int Y int Color color.Color } type blendWorkerPool struct { CpuCores int Jobs chan blendColorJob Results chan blendColorResult WorkerWG *sync.WaitGroup ConsumerWG *sync.WaitGroup } func (p *blendWorkerPool) InitWorkerPool() { p.CpuCores = runtime.NumCPU() p.Jobs = make(chan blendColorJob, 1) p.Results = make(chan blendColorResult, 1) p.WorkerWG = new(sync.WaitGroup) p.ConsumerWG = new(sync.WaitGroup) } // runs the pool of goroutines func (p *blendWorkerPool) RunWorkers(outImg *image.RGBA) { for i := 1; i < p.CpuCores; i++ { p.WorkerWG.Add(1) p.ConsumerWG.Add(1) go p.BlendWorker() go p.SetImageWorker(outImg) } } // blending pixels and assigning them to the output image func (p *blendWorkerPool) SendBlendJobs(dimensions image.Rectangle, img1 image.Image, img2 image.Image) { for x := 0; x < dimensions.Max.X; x++ { for y := 0; y < dimensions.Max.Y; y++ { p.Jobs <- blendColorJob{x, y, img1, img2} } } close(p.Jobs) } // goroutine to blend colors at position (x, y) from two images func (p *blendWorkerPool) BlendWorker() { defer p.WorkerWG.Done() for j := range p.Jobs { c1 := j.Img1.At(j.X, j.Y) c2 := j.Img2.At(j.X, j.Y) c3 := blendColor(c1, c2) p.Results <- blendColorResult{j.X, j.Y, c3} } } // goroutine to set the new color values at newImage position (x, y) func (p *blendWorkerPool) SetImageWorker(newImage *image.RGBA) { defer p.ConsumerWG.Done() for r := range p.Results { newImage.Set(r.X, r.Y, r.Color) } } func (p *blendWorkerPool) BlendImages(img1 image.Image, img2 image.Image, out *image.RGBA) { dimensions := dimensionsToRectangle(ConfigRegister.OutputWidth, ConfigRegister.OutputHeight) // TODO: use a worker pool for those operations ? // resize image img1Resized := resize(img1) img2Resized := resize(img2) p.RunWorkers(out) p.SendBlendJobs(dimensions, img1Resized, img2Resized) // first waitgroup to wait for the results to be ready before closing the channel p.WorkerWG.Wait() close(p.Results) // second waitgroup to ensure the encoding doesn't start before the goroutines are done p.ConsumerWG.Wait() } func BlendImagesMain() { log.Println("Blending the images...") // output image, ready to receive pixel values dimensions := dimensionsToRectangle(ConfigRegister.OutputWidth, ConfigRegister.OutputHeight) outImg := image.NewRGBA(dimensions) for i := 1; i < ConfigRegister.Amount; i++ { // get two random images and load them var img1 image.Image if ConfigRegister.BaseImage != "" && i == 1 { img1 = loadImage(ConfigRegister.BaseImage) } else { if i != 1 { img1 = loadImage("output.jpg") } else { img1 = loadImage(getRandomImage()) } } img2 := loadImage(getRandomImage()) // pool of workers unionizing, ready to blend a new picture using the power of friendship var pool blendWorkerPool pool.InitWorkerPool() // main blending routine pool.BlendImages(img1, img2, outImg) encodeImage(outImg) } }