Last active
April 9, 2025 04:11
-
-
Save mtimkovich/d211607e4344e6eba70e5c418416bb16 to your computer and use it in GitHub Desktop.
Get the closest color to target color in a PNG
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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