Skip to content

Instantly share code, notes, and snippets.

@mtimkovich
Last active April 9, 2025 04:11
Show Gist options
  • Save mtimkovich/d211607e4344e6eba70e5c418416bb16 to your computer and use it in GitHub Desktop.
Save mtimkovich/d211607e4344e6eba70e5c418416bb16 to your computer and use it in GitHub Desktop.
Get the closest color to target color in a PNG
package main
import (
"fmt"
"image"
"image/color"
"image/png"
_ "image/png"
"math"
"os"
"sort"
)
type PixelMatch struct {
Color color.RGBA
X, Y int
PercentX float32
PercentY float32
Dist float64
}
func hexToRGBA(hex string) (color.RGBA, error) {
if len(hex) != 6 {
return color.RGBA{}, fmt.Errorf("invalid hex format")
}
var r, g, b uint8
_, err := fmt.Sscanf(hex, "%02x%02x%02x", &r, &g, &b)
return color.RGBA{r, g, b, 255}, err
}
func colorDistance(c1, c2 color.RGBA) float64 {
dr := math.Pow(float64(c1.R)-float64(c2.R), 2)
dg := math.Pow(float64(c1.G)-float64(c2.G), 2)
db := math.Pow(float64(c1.B)-float64(c2.B), 2)
return math.Sqrt(dr + dg + db)
}
func hexString(c color.RGBA) string {
return fmt.Sprintf("%02x%02x%02x", c.R, c.G, c.B)
}
func getPixelMatches(img image.Image, targetColor color.RGBA) []PixelMatch {
bounds := img.Bounds()
var matches []PixelMatch
seenColors := make(map[string]bool)
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
r, g, b, _ := img.At(x, y).RGBA()
pxColor := color.RGBA{uint8(r >> 8), uint8(g >> 8), uint8(b >> 8), 255}
hex := hexString(pxColor)
if seenColors[hex] {
continue
}
seenColors[hex] = true
dist := colorDistance(pxColor, targetColor)
matches = append(matches, PixelMatch{
Color: pxColor, X: x, Y: y,
PercentX: float32(x) / float32(bounds.Max.X),
PercentY: float32(y) / float32(bounds.Max.Y),
Dist: dist})
}
}
return matches
}
func main() {
if len(os.Args) < 3 {
fmt.Println("Usage: go run main.go <hex color> <image.png>")
os.Exit(1)
}
hexInput := os.Args[1]
filePath := os.Args[2]
targetColor, err := hexToRGBA(hexInput)
if err != nil {
fmt.Println("Invalid hex color:", err)
os.Exit(1)
}
file, err := os.Open(filePath)
if err != nil {
fmt.Println("Error opening image:", err)
os.Exit(1)
}
defer file.Close()
img, err := png.Decode(file)
if err != nil {
fmt.Println("Error decoding PNG:", err)
os.Exit(1)
}
matches := getPixelMatches(img, targetColor)
sort.Slice(matches, func(i, j int) bool {
return matches[i].Dist < matches[j].Dist
})
fmt.Printf("5 closest unique color matches to #%s:\n", hexInput)
for i := 0; i < 5 && i < len(matches); i++ {
m := matches[i]
fmt.Printf("- #%02x%02x%02x at (%d, %d) (%.1f%%, %.1f%%) [diff %.2f]\n", m.Color.R, m.Color.G, m.Color.B, m.X, m.Y, m.PercentX*100, m.PercentY*100, m.Dist)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment