aboutsummaryrefslogtreecommitdiff
path: root/distill.go
blob: fca47593842d27e3a01d7d77010dbe2846e93c59 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package main

import (
	"fmt"
	"image"
	"image/color"
	"math"
	"os"

	"github.com/EdlinOrg/prominentcolor"
	"github.com/anthonynsimon/bild/clone"
	"github.com/anthonynsimon/bild/imgio"
	"github.com/jessevdk/go-flags"
	colorful "github.com/lucasb-eyer/go-colorful"
)

var (
	opts struct {
		BlockSize          int    `short:"b" long:"block-size" default:"50" description:"The size of the blocks the image should be distilled to. E.g. 10 will result in square blocks of 10x10 pixels. Note that this is the size of the blocks, not the number of blocks."`
		MaxDominantColours int    `short:"n" long:"max-dominant-colours" default:"8" description:"The number of dominant colours to be extracted from the image."`
		OutputPath         string `short:"o" long:"output-path" default:"out.png" description:"Path to the output file. The format will be png."`
	}
	imagePath string
)

func init() {
	args, err := flags.Parse(&opts)
	if err != nil {

		os.Exit(1)
	}
	if len(args) == 1 {
		imagePath = args[0]
	} else {
		fmt.Println("Specity an image to process")
		os.Exit(1)
	}

}

func main() {

	img, err := imgio.Open(imagePath)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	source := clone.AsRGBA(img)

	prominentColours, err := extractProminentColours(source)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	for x := 0; x < source.Bounds().Max.X; x += opts.BlockSize {
		for y := 0; y < source.Bounds().Max.Y; y += opts.BlockSize {
			var (
				block          = source.SubImage(image.Rect(x, y, x+opts.BlockSize, y+opts.BlockSize))
				avgBlockColour = calculateAverageBlockColour(block)
				nearestColour  = nearestColour(avgBlockColour, prominentColours)
			)
			fillBlock(x, y, opts.BlockSize, nearestColour, source)
		}
	}

	if err := imgio.Save(opts.OutputPath, source, imgio.PNGEncoder()); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

func fillBlock(blockX, blockY, blockSize int, fillColour color.Color, image *image.RGBA) {
	for x := blockX; x <= blockX+blockSize; x++ {
		for y := blockY; y <= blockY+blockSize; y++ {
			image.Set(x, y, fillColour)
		}
	}
}

func extractProminentColours(image image.Image) ([]colorful.Color, error) {
	var prominentcolors []colorful.Color
	sampleWidth := uint(image.Bounds().Max.X / 2)

	colours, err := prominentcolor.KmeansWithAll(opts.MaxDominantColours, image, prominentcolor.ArgumentNoCropping, sampleWidth, nil)
	if err != nil {
		return nil, err
	}

	// convert prominentcolor.ColourItem to colourful.Color
	for _, colourItem := range colours {
		golangColor := color.RGBA{
			uint8(colourItem.Color.R),
			uint8(colourItem.Color.G),
			uint8(colourItem.Color.B),
			255}

		colourfulColor, _ := colorful.MakeColor(golangColor)
		prominentcolors = append(prominentcolors, colourfulColor)
	}

	return prominentcolors, nil
}

func nearestColour(colour color.Color, possibleColours []colorful.Color) color.Color {
	var (
		nearestColour   color.Color
		closestDistance = math.MaxFloat64
	)

	sourceColour, _ := colorful.MakeColor(colour)

	for _, possibleColour := range possibleColours {
		var distance = sourceColour.DistanceCIEDE2000(possibleColour)
		if distance < closestDistance {
			closestDistance = distance
			nearestColour = possibleColour
		}
	}
	return nearestColour
}

func calculateAverageBlockColour(block image.Image) color.Color {
	var avgR, avgG, avgB uint32

	bounds := block.Bounds()

	for x := bounds.Min.X; x < bounds.Max.X; x++ {
		for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
			r, g, b, _ := block.At(x, y).RGBA()
			avgR += r
			avgG += g
			avgB += b
		}
	}

	totalPixels := uint32(bounds.Dy() * bounds.Dx())
	avgR /= totalPixels
	avgG /= totalPixels
	avgB /= totalPixels

	return color.RGBA{uint8(avgR / 0x101), uint8(avgG / 0x101), uint8(avgB / 0x101), 255}
}