How to Convert RGB to Color Name in Python
Human-readable color names like "Navy Blue" are more intuitive than RGB tuples (0, 0, 128). Converting between them requires either exact matching against known colors or finding the mathematically closest named color.
This guide covers both approaches with practical implementations.
Exact Match with webcolors
For standard CSS3 colors, use direct lookup:
pip install webcolors
import webcolors
def rgb_to_name_exact(rgb_tuple):
"""Get exact color name or None if no match."""
try:
return webcolors.rgb_to_name(rgb_tuple)
except ValueError:
return None
print(rgb_to_name_exact((255, 0, 0))) # red
print(rgb_to_name_exact((0, 0, 128))) # navy
print(rgb_to_name_exact((250, 5, 5))) # None (no exact match)
Exact matching only works for the 147 standard CSS3 named colors. Real-world colors from photos or designs rarely match exactly.
Closest Match with Euclidean Distance
Find the nearest named color mathematically:
import webcolors
import math
def closest_color(rgb):
"""Find the closest CSS3 named color."""
min_distance = float('inf')
closest_name = None
# Get all CSS3 color names
for name in webcolors.names("css3"):
r, g, b = webcolors.name_to_rgb(name)
# Euclidean distance
distance = math.sqrt(
(r - rgb[0]) ** 2 +
(g - rgb[1]) ** 2 +
(b - rgb[2]) ** 2
)
if distance < min_distance:
min_distance = distance
closest_name = name
return closest_name
print(closest_color((250, 0, 0))) # red
print(closest_color((250, 10, 10))) # red
print(closest_color((100, 50, 75))) # dimgray (or similar)
Combined Approach
Try exact match first, then fall back to closest:
import webcolors
import math
def rgb_to_name(rgb):
"""Get color name: exact if available, otherwise closest."""
# Try exact match first
try:
return webcolors.rgb_to_name(rgb)
except ValueError:
pass
# Find closest match
min_distance = float('inf')
closest_name = None
for name in webcolors.names("css3"):
r, g, b = webcolors.name_to_rgb(name)
distance = sum((a - b) ** 2 for a, b in zip(rgb, (r, g, b)))
if distance < min_distance:
min_distance = distance
closest_name = name
return closest_name
print(rgb_to_name((255, 0, 0))) # red (exact)
print(rgb_to_name((255, 10, 10))) # red (closest)
Hex Code to Color Name
Common in web development:
import webcolors
import math
def rgb_to_name_with_confidence(rgb):
"""Get color name with distance score."""
# Try exact match
try:
return {
'name': webcolors.rgb_to_name(rgb),
'distance': 0,
'exact_match': True
}
except ValueError:
pass
# Find closest
min_distance = float('inf')
closest_name = None
for name in webcolors.names("css3"):
r, g, b = webcolors.name_to_rgb(name)
distance = math.sqrt(sum((a - b) ** 2 for a, b in zip(rgb, (r, g, b))))
if distance < min_distance:
min_distance = distance
closest_name = name
# Max distance in RGB space is ~441 (0,0,0 to 255,255,255)
confidence = 1 - (min_distance / 441.67)
return {
'name': closest_name,
'distance': round(min_distance, 2),
'confidence': round(confidence, 2),
'exact_match': False
}
print(rgb_to_name_with_confidence((255, 0, 0)))
# {'name': 'red', 'distance': 0, 'exact_match': True}
print(rgb_to_name_with_confidence((200, 50, 50)))
# {'name': 'firebrick', 'distance': 31.56, 'confidence': 0.93, 'exact_match': False}
With Color Distance and Confidence
Return both name and match quality:
import webcolors
import math
def rgb_to_name_with_confidence(rgb):
"""Get color name with distance score."""
# Try exact match
try:
return {
'name': webcolors.rgb_to_name(rgb),
'distance': 0,
'exact_match': True
}
except ValueError:
pass
# Find closest
min_distance = float('inf')
closest_name = None
for name in webcolors.names("css3"):
r, g, b = webcolors.name_to_rgb(name)
distance = math.sqrt(sum((a - b) ** 2 for a, b in zip(rgb, (r, g, b))))
if distance < min_distance:
min_distance = distance
closest_name = name
# Max distance in RGB space is ~441 (0,0,0 to 255,255,255)
confidence = 1 - (min_distance / 441.67)
return {
'name': closest_name,
'distance': round(min_distance, 2),
'confidence': round(confidence, 2),
'exact_match': False
}
print(rgb_to_name_with_confidence((255, 0, 0)))
# {'name': 'red', 'distance': 0, 'exact_match': True}
print(rgb_to_name_with_confidence((200, 50, 50)))
# {'name': 'firebrick', 'distance': 31.56, 'confidence': 0.93, 'exact_match': False}
Color Categories (Simple Grouping)
For basic color classification:
def get_basic_color(rgb):
"""Classify RGB into basic color category."""
r, g, b = rgb
# Check for grayscale
if abs(r - g) < 20 and abs(g - b) < 20 and abs(r - b) < 20:
if max(r, g, b) < 50:
return "black"
elif min(r, g, b) > 200:
return "white"
return "gray"
# Find dominant channel
max_val = max(r, g, b)
if r == max_val:
if g > b:
return "orange" if g > 100 else "red"
return "red" if b < 100 else "pink"
elif g == max_val:
if b > r:
return "cyan" if b > 100 else "green"
return "green" if r < 100 else "yellow"
else: # b == max_val
if r > g:
return "purple" if r > 100 else "blue"
return "blue"
print(get_basic_color((255, 0, 0))) # red
print(get_basic_color((50, 50, 50))) # gray
print(get_basic_color((255, 165, 0))) # orange
Using scipy for Efficient Distance Calculation
For processing many colors:
import webcolors
import numpy as np
from scipy.spatial import KDTree
# Build color lookup once
def build_color_tree():
colors = []
names = []
for name in webcolors.names("css3"):
colors.append(webcolors.name_to_rgb(name))
names.append(name)
return KDTree(colors), names
COLOR_TREE, COLOR_NAMES = build_color_tree()
def rgb_to_name_fast(rgb):
"""Fast color name lookup using KDTree."""
distance, index = COLOR_TREE.query(rgb)
return COLOR_NAMES[index]
# Much faster for bulk processing
print(rgb_to_name_fast((200, 50, 50))) # firebrick
Batch Processing
Convert multiple colors efficiently:
import webcolors
import math
def rgb_to_name(rgb):
"""Get color name: exact if available, otherwise closest."""
# Try exact match first
try:
return webcolors.rgb_to_name(rgb)
except ValueError:
pass
# Find closest match
min_distance = float('inf')
closest_name = None
for name in webcolors.names("css3"):
r, g, b = webcolors.name_to_rgb(name)
distance = sum((a - b) ** 2 for a, b in zip(rgb, (r, g, b)))
if distance < min_distance:
min_distance = distance
closest_name = name
return closest_name
def batch_rgb_to_names(rgb_list):
"""Convert list of RGB tuples to names."""
return [rgb_to_name(rgb) for rgb in rgb_list]
colors = [
(255, 0, 0),
(0, 255, 0),
(0, 0, 255),
(255, 165, 0),
(128, 0, 128)
]
names = batch_rgb_to_names(colors)
print(dict(zip(colors, names)))
# {(255, 0, 0): 'red', (0, 255, 0): 'lime', (0, 0, 255): 'blue', (255, 165, 0): 'orange', (128, 0, 128): 'purple'}
Practical Example: Image Color Analysis
from PIL import Image
from collections import Counter
def get_dominant_colors(image_path, n_colors=5):
"""Get most common color names in an image."""
img = Image.open(image_path)
img = img.convert('RGB')
img = img.resize((100, 100)) # Reduce for speed
pixels = list(img.getdata())
# Convert each pixel to color name
color_names = [rgb_to_name(pixel) for pixel in pixels]
# Count occurrences
return Counter(color_names).most_common(n_colors)
# Usage:
# dominant = get_dominant_colors("photo.jpg")
# print(dominant)
# [('white', 2500), ('silver', 1800), ('gray', 1200), ...]
Summary
| Input | Method | Use Case |
|---|---|---|
| Exact RGB | webcolors.rgb_to_name() | Standard CSS colors |
| Any RGB | Euclidean distance | Real-world colors |
| Hex code | hex_to_rgb() + distance | Web development |
| Bulk processing | KDTree + scipy | Image analysis |
For user-facing features like image tagging or color pickers, always use the Euclidean distance method. Exact matches are too rare in real-world data (photos, scans) to be useful alone. Consider using LAB color space instead of RGB for perceptually more accurate matches.