-----Original Message-----
From: r-help-bounces at r-project.org [mailto:r-help-bounces at r-
project.org] On Behalf Of Kevin Wright
Sent: Sunday, June 02, 2013 10:43 AM
To: John Fox
Cc: r-help; Michael Friendly; Martin Maechler
Subject: Re: [R] measuring distances between colours?
Sorry about the bug. How embarrassing. Especially because I've learned
over
the years to trust my gut feelings when something doesn't feel quite
right,
and when I was testing the function, I remember thinking "surely there
a
better matching named color than 'magenta'".
Thanks for the fix.
Kevin
On Sat, Jun 1, 2013 at 11:30 AM, John Fox <jfox at mcmaster.ca> wrote:
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
version, which uses local() rather than function() to produce the
The matrix which.col contains character data, as a consequence of
the minimum squared distances to colour names, and thus the
cols.near[2,] < near^2 doesn't work properly when, ironically, the
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)
+ }
+ })
rgb2col2(c("#010101", "#EEEEEE", "#AA0000", "#00AA00", "#0000AA",
+ "#AAAA00", "#AA00AA", "#00AAAA"))
[1] "black" "gray93" "darkred" "green4"
"darkgoldenrod"
[7] "darkmagenta" "cyan4"
The same bug is in the code that I just posted using Lab colours, so
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 =
+ 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))
+ }
+ })
rgb2col(c("#010101", "#EEEEEE", "#AA0000", "#00AA00", "#0000AA",
"#AAAA00", "#AA00AA", "#00AAAA"))
[1] "black" "gray93" "#AA0000" "#00AA00" "#0000AA" "#AAAA00"
[7] "#AA00AA" "#00AAAA"
rgb2col(c("#010101", "#EEEEEE", "#AA0000", "#00AA00", "#0000AA",
"#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
colours unmatched. I experimented a bit, and using 15 (as above)
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
scale, is squared to make the comparison, this shouldn't be
I hope that finally this will be a satisfactory solution.
Best,
John
-----Original Message-----
From: r-help-bounces at r-project.org [mailto:r-help-bounces at r-
project.org] On Behalf Of Michael Friendly
Sent: Saturday, June 01, 2013 11:33 AM
To: John Fox
Cc: 'r-help'; 'Martin Maechler'
Subject: Re: [R] measuring distances between colours?
Just a quick note: The following two versions of your function
give the same results. I'm not sure why, and also not sure why the
criterion for 'near' should be expressed in squared distance.
# version 1
rgb2col <- local({
hex2dec <- function(hexnums) {
# suggestion of Eik Vettorazzi
sapply(strtoi(hexnums, 16L), function(x) x %/% 256^(2:0)
256)
}
findMatch <- function(dec.col) {
sq.dist <- colSums((hsv - dec.col)^2)
rbind(which.min(sq.dist), min(sq.dist))
}
colors <- colors()
hsv <- rgb2hsv(col2rgb(colors))
function(cols, near=0.25) {
cols <- sub("^#", "", toupper(cols))
dec.cols <- rgb2hsv(hex2dec(cols))
which.col <- apply(dec.cols, 2, findMatch)
matches <- colors[which.col[1, ]]
unmatched <- which.col[2, ] > near^2
matches[unmatched] <- paste("#", cols[unmatched], sep="")
matches
}
})
# version 2
rgb2col2 <- local({
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(cols.near[2,] < near^2, cols.near[1,], cols.hex)
}
})
# tests
> rgb2col(c("#010101", "#EEEEEE", "#AA0000", "#00AA00", "#0000AA",
"#AAAA00", "#AA00AA", "#00AAAA"))
[1] "black" "gray93" "darkred" "green4"
[5] "blue4" "darkgoldenrod" "darkmagenta" "cyan4"
> rgb2col2(c("#010101", "#EEEEEE", "#AA0000", "#00AA00",
"#AAAA00", "#AA00AA", "#00AAAA"))
[1] "#010101" "#EEEEEE" "darkred" "green4"
[5] "blue4" "darkgoldenrod" "darkmagenta" "cyan4"
On 5/31/2013 7:42 PM, John Fox wrote:
Dear Kevin,
I generally prefer your solution. I didn't realize that col2rgb()
with hex-colour input (as opposed to named colours), so my code
hex numbers to decimal is unnecessary; and using ifelse() is
replacing the non-matches.
I'm not so sure about avoiding the closure, since for converting
numbers of colours, your function will spend most of its time
the local function find.near() and building all.hsv. Here's an
using your rgb2col() and a comparable function employing a
of your examples executed 100 times:
+ 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(cols.near[2,] < near^2, cols.near[1,], cols.hex)
+ }
+ }
mycols <- c("#010101", "#EEEEEE", "#AA0000", "#00AA00",
+ "#AAAA00", "#AA00AA", "#00AAAA")
system.time(for (i in 1:100) oldnew <- c(mycols, rgb2col(mycols,
near=.25)))
user system elapsed
1.97 0.00 1.97
system.time({rgb2col2 <- r2c()
+ for (i in 1:100) oldnew2 <- c(mycols, rgb2col2(mycols,
+ })
user system elapsed
0.08 0.00 0.08
[,1] [,2] [,3] [,4] [,5] [,6]
oldnew "#010101" "#EEEEEE" "#AA0000" "#00AA00" "#0000AA"
oldnew2 "#010101" "#EEEEEE" "#AA0000" "#00AA00" "#0000AA"
[,7] [,8] [,9] [,10] [,11] [,12]
oldnew "#AA00AA" "#00AAAA" "#010101" "#EEEEEE" "darkred"
oldnew2 "#AA00AA" "#00AAAA" "#010101" "#EEEEEE" "darkred"
[,13] [,14] [,15] [,16]
oldnew "blue4" "darkgoldenrod" "darkmagenta" "cyan4"
oldnew2 "blue4" "darkgoldenrod" "darkmagenta" "cyan4"
Does this really make a difference? Frankly, it wouldn't for my
(for colour selection in the Rcmdr) where a user is likely to
most one or two conversions of a small number of colours in a
time advantage of the second approach will depend upon the number
the function is invoked and the number of colours converted each
-----Original Message-----
From: r-help-bounces at r-project.org [mailto:r-help-bounces at r-
project.org] On Behalf Of Kevin Wright
Sent: Friday, May 31, 2013 3:39 PM
To: Martin Maechler
Cc: r-help; John Fox
Subject: Re: [R] measuring distances between colours?
Thanks for the discussion. I've also wanted to be able to find
colors. I took the code and comments in this thread and
function even further. (Personally, I think using closures
Rube-Goldberg code. YMMV.) The first example below is what I
'group' colors in lattice.
Kevin Wright
rgb2col <- function(cols.hex, near=.25){
# Given a vector of hex colors, find the nearest 'named' R
# If no color closer than 'near' is found, return the hex
# Authors: John Fox, Martin Maechler, Kevin Wright
# From r-help discussion 5.30.13
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))
}
all.names <- colors()
all.hsv <- rgb2hsv(col2rgb(all.names))
cols.hsv <- rgb2hsv(col2rgb(cols.hex))
cols.near <- apply(cols.hsv, 2, find.near)
ifelse(cols.near[2,] < near^2, cols.near[1,], cols.hex)
}
mycols <- c("royalblue", "red", "#009900", "dark orange",
"#a6761d", "#aa00da")
mycols <- c("#010101", "#EEEEEE", "#AA0000", "#00AA00",
"#AAAA00", "#AA00AA", "#00AAAA")
mycols <- c("#010101", "#090909", "#090000", "#000900",
"#090900", "#090009", "#000909")
oldnew <- c(mycols, rgb2col(mycols, near=.25)) # Also try
pie(rep(1,2*length(mycols)), labels=oldnew, col=oldnew)
[[alternative HTML version deleted]]