Skip to content
Prev 324564 / 398503 Next

measuring distances between colours?

Hi Michael,

This has become a bit of a comedy of errors. 

The bug is in Kevin Wright's code, which I adapted, and you too in your
version, which uses local() rather than function() to produce the closure.
The matrix which.col contains character data, as a consequence of binding
the minimum squared distances to colour names, and thus the comparison
cols.near[2,] < near^2 doesn't work properly when, ironically, the distance
is small enough so that it's rendered in scientific notation. 

Converting to numeric appears to work:
+     all.names <- colors()
+     all.hsv <- rgb2hsv(col2rgb(all.names))
+     find.near <- function(x.hsv) {
+         # return the nearest R color name and distance
+         sq.dist <- colSums((all.hsv - x.hsv)^2)
+         rbind(all.names[which.min(sq.dist)], min(sq.dist))
+     }
+     function(cols.hex, near=.25){
+         cols.hsv <- rgb2hsv(col2rgb(cols.hex))
+         cols.near <- apply(cols.hsv, 2, find.near)
+         ifelse(as.numeric(cols.near[2,]) <= near^2, cols.near[1,],
cols.hex)
+     }
+ })
+     "#AAAA00", "#AA00AA", "#00AAAA"))

[1] "black"         "gray93"        "darkred"       "green4"        "blue4"
"darkgoldenrod"
[7] "darkmagenta"   "cyan4"

The same bug is in the code that I just posted using Lab colours, so (for
posterity) here's a fixed version of that, using local():
+     all.names <- colors()
+     all.lab <- t(convertColor(t(col2rgb(all.names)), from = "sRGB", 
+         to = "Lab", scale.in = 255))
+     find.near <- function(x.lab) {
+         sq.dist <- colSums((all.lab - x.lab)^2)
+         rbind(all.names[which.min(sq.dist)], min(sq.dist))
+     }
+     function(cols.hex, near = 2.3) {
+         cols.lab <- t(convertColor(t(col2rgb(cols.hex)), from = "sRGB", 
+             to = "Lab", scale.in = 255))
+         cols.near <- apply(cols.lab, 2, find.near)
+         ifelse(as.numeric(cols.near[2, ]) < near^2, cols.near[1, ],
toupper(cols.hex))
+     }
+ })
"#AAAA00", "#AA00AA", "#00AAAA"))

[1] "black"   "gray93"  "#AA0000" "#00AA00" "#0000AA" "#AAAA00"
[7] "#AA00AA" "#00AAAA"
"#AAAA00", "#AA00AA", "#00AAAA"), near=15)

[1] "black"         "gray93"        "firebrick3"    "limegreen"    
[5] "blue4"         "#AAAA00"       "darkmagenta"   "lightseagreen"

So with Lab colours, setting near to the JND of 2.3 leaves many of these
colours unmatched. I experimented a bit, and using 15 (as above) produces
matches that appear reasonably "close" to me.

I used squared distances to avoid taking the square-roots of all the
distances. Since the criterion for "near" colours, which is on the distance
scale, is squared to make the comparison, this shouldn't be problematic.

I hope that finally this will be a satisfactory solution.

Best,
 John