2021-10-03 08:40:10 +03:00
|
|
|
// ColorX library project
|
|
|
|
|
// Copyright (C) 2021 IllusionMan1212 and contributors
|
|
|
|
|
//
|
|
|
|
|
// This program is free software; you can redistribute it and/or modify
|
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
|
// the Free Software Foundation; either version 3 of the License, or
|
|
|
|
|
// (at your option) any later version.
|
|
|
|
|
//
|
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
|
//
|
|
|
|
|
// You should have received a copy of the GNU General Public License along
|
|
|
|
|
// with this program; if not, see https://www.gnu.org/licenses.
|
|
|
|
|
|
|
|
|
|
package colorx
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"fmt"
|
|
|
|
|
"image"
|
2021-10-05 16:47:49 +03:00
|
|
|
"math"
|
2021-10-04 09:14:38 +03:00
|
|
|
"strconv"
|
2021-10-03 08:40:10 +03:00
|
|
|
|
2021-10-04 09:14:38 +03:00
|
|
|
"image/color"
|
2021-10-03 08:40:10 +03:00
|
|
|
_ "image/jpeg"
|
|
|
|
|
_ "image/png"
|
2021-10-04 09:14:38 +03:00
|
|
|
|
2021-10-05 16:47:49 +03:00
|
|
|
"golang.org/x/image/draw"
|
2021-10-04 09:14:38 +03:00
|
|
|
_ "golang.org/x/image/webp"
|
2021-10-03 08:40:10 +03:00
|
|
|
)
|
|
|
|
|
|
2021-10-05 16:47:49 +03:00
|
|
|
const MAX_PIXELS = 20000
|
|
|
|
|
|
|
|
|
|
// getResizedDims resizes the image to have 20000px or under while keeping the aspect ratio
|
|
|
|
|
func getResizedDims(width, height int) (int, int) {
|
|
|
|
|
imagePixels := width * height
|
|
|
|
|
|
|
|
|
|
// reference for equation: https://www.wyzant.com/resources/answers/91621/find_two_positive_numbers_whose_ratio_is_2_3_and_whose_product_is_600
|
|
|
|
|
scale := math.Sqrt(float64(MAX_PIXELS) / float64(imagePixels))
|
|
|
|
|
|
|
|
|
|
newWidth := scale * float64(width)
|
|
|
|
|
newHeight := scale * float64(height)
|
|
|
|
|
|
|
|
|
|
return int(newWidth), int(newHeight)
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-04 09:14:38 +03:00
|
|
|
// downsampleImage downsamples images larger than 20000px for faster image processing.
|
2021-10-05 16:47:49 +03:00
|
|
|
func downsampleImage(img image.Image, width int, height int) image.Image {
|
|
|
|
|
dstWidth, dstHeight := getResizedDims(width, height)
|
|
|
|
|
|
|
|
|
|
dstRect := image.Rect(0, 0, dstWidth, dstHeight)
|
|
|
|
|
dst := image.NewRGBA(dstRect)
|
|
|
|
|
|
|
|
|
|
draw.NearestNeighbor.Scale(dst, dstRect, img, img.Bounds(), draw.Over, nil)
|
|
|
|
|
|
|
|
|
|
return dst
|
2021-10-04 09:14:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-03 08:40:10 +03:00
|
|
|
// GetProminentColor gets the most prominent color in the image bytes reader that gets passed in.
|
|
|
|
|
// returns a hex color as a string.
|
2021-10-04 09:14:38 +03:00
|
|
|
func GetProminentColor(data []byte) (string, color.Color, error) {
|
2021-10-03 08:40:10 +03:00
|
|
|
buf := bytes.NewBuffer(data)
|
|
|
|
|
img, _, err := image.Decode(buf)
|
|
|
|
|
if err != nil {
|
2021-10-04 09:14:38 +03:00
|
|
|
return "", nil, err
|
2021-10-03 08:40:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
width := img.Bounds().Max.X
|
|
|
|
|
height := img.Bounds().Max.Y
|
|
|
|
|
|
2021-10-04 09:14:38 +03:00
|
|
|
// amount of pixels in image
|
2021-10-03 08:40:10 +03:00
|
|
|
pixels := height * width
|
|
|
|
|
|
2021-10-04 09:14:38 +03:00
|
|
|
// image is too big, downsample to a reasonable size
|
2021-10-05 16:47:49 +03:00
|
|
|
if pixels > MAX_PIXELS {
|
|
|
|
|
img = downsampleImage(img, width, height)
|
2021-10-04 09:14:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
2021-10-03 08:40:10 +03:00
|
|
|
|
2021-10-04 09:14:38 +03:00
|
|
|
return hexColor, maxColor, nil
|
2021-10-03 08:40:10 +03:00
|
|
|
}
|