commit f9d1395da6e2c1519486c86ffb523cd765c3a09c Author: Gator Date: Sun Apr 24 14:24:54 2022 +0200 initial commit diff --git a/assets/lavande.jpg b/assets/lavande.jpg new file mode 100644 index 0000000..5795267 Binary files /dev/null and b/assets/lavande.jpg differ diff --git a/assets/moutons.jpg b/assets/moutons.jpg new file mode 100644 index 0000000..a35ade3 Binary files /dev/null and b/assets/moutons.jpg differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..8bcb055 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module ariona.fr/git/gator/pixelweaver + +go 1.16 diff --git a/main.go b/main.go new file mode 100644 index 0000000..3c8b2e0 --- /dev/null +++ b/main.go @@ -0,0 +1,208 @@ +package main + +import ( + "fmt" + "image" + "image/color" + "image/jpeg" + _ "image/jpeg" + _ "image/png" + "log" + "math" + "math/rand" + "os" + "path/filepath" + "runtime" + "strings" + "sync" + "time" +) + +// goroutine to blend colors at position (x, y) from two images +func blendWorker(jobs <-chan blendColorJob, results chan<- blendColorResult, wg *sync.WaitGroup) { + defer wg.Done() + for j := range jobs { + c1 := j.Img1.At(j.X, j.Y) + c2 := j.Img2.At(j.X, j.Y) + c3 := blendColor(c1, c2) + results <- blendColorResult{j.X, j.Y, c3} + } +} + +// goroutine to set the new color values at newImage position (x, y) +func setImageWorker(results <-chan blendColorResult, newImage *image.RGBA, wg *sync.WaitGroup) { + defer wg.Done() + for r := range results { + newImage.Set(r.X, r.Y, r.Color) + } +} + +// convert RGBA pixel to grayscale +func grayscale(pixel color.Color) color.Color { + c := color.RGBAModel.Convert(pixel).(color.RGBA) + gray := uint8((c.R + c.G + c.B) / 3) + return color.RGBA{ + R: gray, + G: gray, + B: gray, + A: c.A, + } +} + +// alpha blending RGBA pixels +func blend(fc uint8, bc uint8, fa uint8, ba uint8) float64 { + // fc : foreground color + // bc : background color + // fa : foreground alpha + // ba : background alpha + // all values are alpha-premultiplied + + // darken only + return math.Min(float64(fc), float64(bc)) + + // fucky fucky fun + // return float64((fc * fa) + (bc * (fa * 2))) +} + +// blend two RGBA colors/pixels and returns a new one +func blendColor(color1 color.Color, color2 color.Color) color.Color { + oc1 := color.RGBAModel.Convert(color1).(color.RGBA) + oc2 := color.RGBAModel.Convert(color2).(color.RGBA) + r := uint8(blend(oc1.R, oc2.R, oc1.A, oc2.A)) + g := uint8(blend(oc1.G, oc2.G, oc1.A, oc2.A)) + b := uint8(blend(oc1.B, oc2.B, oc1.A, oc2.A)) + a := oc1.A + (1-oc1.A)*oc2.A + return color.RGBA{ + R: r, G: g, B: b, A: a, + } +} + +// load an image from a file into an Image value +func loadImage(filename string) image.Image { + img, err := os.Open(filename) + if err != nil { + log.Fatal(err) + } + defer img.Close() + + imgData, _, err := image.Decode(img) + if err != nil { + log.Fatal(err) + } + + return imgData +} + +// Walk through a folder recursively and returns a list of image paths +func getImagesList(path string) []string { + var imgs []string + + err := filepath.Walk(path, + func(path string, info os.FileInfo, err error) error { + ext := strings.ToLower(filepath.Ext(path)) + if err != nil { + return err + } + if ext == ".jpg" || ext == ".png" { + imgs = append(imgs, path) + } + return nil + }) + + if err != nil { + log.Println(err) + } + + return imgs +} + +// Randomly choose x number of image from a given folder +func getRandomImages(number int) []string { + rand.Seed(time.Now().UnixNano()) + var images []string + dir := getImagesList("/home/gator/Photos/") + for i := 0; i < number; i++ { + index := rand.Intn(len(dir)) + images = append(images, dir[index]) + } + return images +} + +// creates a new rectangle with the min height and width from both images +func getDimensions(img1 image.Image, img2 image.Image) image.Rectangle { + // get dimensions for both images + size1 := img1.Bounds().Size() + size2 := img2.Bounds().Size() + + // final image sized from lowest width and height + width := int(math.Min(float64(size1.X), float64(size2.X))) + height := int(math.Min(float64(size1.Y), float64(size2.Y))) + + // the dimensions, as Points, of the output image + upLeft := image.Point{0, 0} + lowRight := image.Point{width, height} + + return image.Rectangle{upLeft, lowRight} +} + +type blendColorJob struct { + X int + Y int + Img1 image.Image + Img2 image.Image +} + +type blendColorResult struct { + X int + Y int + Color color.Color +} + +func main() { + cpu := runtime.NumCPU() + // channels + jobs := make(chan blendColorJob, 1) + results := make(chan blendColorResult, 1) + // waitgroups + wgWorker := new(sync.WaitGroup) + wgConsumer := new(sync.WaitGroup) + + // get two random images and load them + imgs := getRandomImages(2) + // imData1 := loadImage("assets/moutons.jpg") + // imData2 := loadImage("assets/lavande.jpg") + imData1 := loadImage(imgs[0]) + imData2 := loadImage(imgs[1]) + + dimensions := getDimensions(imData1, imData2) + + // output image, ready to receive pixel values + outImg := image.NewRGBA(dimensions) + out, _ := os.Create("output.jpg") + defer out.Close() + + for i := 1; i < cpu; i++ { + wgConsumer.Add(1) + wgWorker.Add(1) + go blendWorker(jobs, results, wgWorker) + go setImageWorker(results, outImg, wgConsumer) + } + + // blending pixels and assigning them to the output image + for x := 0; x < dimensions.Max.X; x++ { + for y := 0; y < dimensions.Max.Y; y++ { + jobs <- blendColorJob{x, y, imData1, imData2} + } + } + close(jobs) + + // first waitgroup to wait for the results to be ready before closing the channel + wgWorker.Wait() + close(results) + + // second waitgroup to ensure the encoding doesn't start before the goroutines are done + wgConsumer.Wait() + fmt.Print("Encoding the image...") + jpeg.Encode(out, outImg, nil) + fmt.Println(" Done.") +}