diff --git a/README.md b/README.md index 55aaa36..6408f45 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,14 @@ -# ColorX (WIP) +# ColorX ColorX is a library to determine the most prominent color in an image. +ColorX doesn't use any sort of complex algorithms to calculate the prominent color, it simply loops over the image pixels and returns the color that occurs the most. + +The `getProminentColor` function returns the color in 2 formats, a hex string such as: `#abfecd` and a variable of type `color.Color`. # Example -Example code is written in [example/example.go](https://github.com/illusionman1212/colorx/blob/master/example/example.go) +An example on how to use this library is written in [example/example.go](https://github.com/illusionman1212/colorx/blob/master/example/example.go) # Supported Image Formats Currently ColorX supports these image formats: - png - jpeg +- webp diff --git a/color.go b/color.go index 3f5ed37..0f7aa63 100644 --- a/color.go +++ b/color.go @@ -20,28 +20,91 @@ import ( "bytes" "fmt" "image" + "strconv" + "image/color" _ "image/jpeg" _ "image/png" + + _ "golang.org/x/image/webp" ) +// downsampleImage downsamples images larger than 20000px for faster image processing. +func downsampleImage(img image.Image) image.Image { + // TODO: implement downsampling + return img +} + +// calculateColorMap loops through the image pixels and creates a map- +// of all unique pixel colors and how many times they occur. +func calculateColorMap(img image.Image, width int, height int) map[color.Color]int { + colorMap := map[color.Color]int{} + + for x := 0; x < width; x++ { + for y := 0; y < height; y++ { + color := img.At(x, y) + // if the alpha is bigger than 100 then add the color to the map + if _, _, _, a := color.RGBA(); a > 100 { + colorMap[color] = colorMap[color] + 1 + } + } + } + + return colorMap +} + // GetProminentColor gets the most prominent color in the image bytes reader that gets passed in. // returns a hex color as a string. -func GetProminentColor(data []byte) (string, error) { +func GetProminentColor(data []byte) (string, color.Color, error) { buf := bytes.NewBuffer(data) img, _, err := image.Decode(buf) if err != nil { - return "", err + return "", nil, err } width := img.Bounds().Max.X height := img.Bounds().Max.Y + // amount of pixels in image pixels := height * width - fmt.Printf("Pixels in image: %v\n", pixels) - fmt.Printf("Width: %v\n", width) - fmt.Printf("Height: %v\n", height) + // image is too big, downsample to a reasonable size + if pixels > 20000 { + img = downsampleImage(img) + } - return "", nil + colorMap := calculateColorMap(img, width, height) + + var maxColor color.Color + maxAmmount := 0 + + for color, amount := range colorMap { + if amount > maxAmmount { + maxColor = color + maxAmmount = amount + } + } + + RGBAColor := color.RGBAModel.Convert(maxColor).(color.RGBA) + + red := strconv.FormatUint(uint64(RGBAColor.R), 16) + green := strconv.FormatUint(uint64(RGBAColor.G), 16) + blue := strconv.FormatUint(uint64(RGBAColor.B), 16) + + // prepend 0 if the hex number has less than 2 digits + if len(red) < 2 { + red = "0" + red + } + + if len(green) < 2 { + green = "0" + green + } + + if len(blue) < 2 { + blue = "0" + blue + } + + hexColor := fmt.Sprintf("#%2s%2s%2s", red, green, blue) + + return hexColor, maxColor, nil } diff --git a/example/example.go b/example/example.go index 634ff2b..78a95d7 100644 --- a/example/example.go +++ b/example/example.go @@ -38,7 +38,7 @@ func main() { log.Fatal(err) } - color, err := colorx.GetProminentColor(data) + color, _, err := colorx.GetProminentColor(data) if err != nil { log.Fatal(err) } diff --git a/go.mod b/go.mod index ecffcd1..2be3739 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/illusionman1212/colorx go 1.16 + +require golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..61e0989 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs= +golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +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=