TITLE: R function to find nearest named colour
DATE: 2022-12-23
AUTHOR: John L. Godlee
====================================================================


I wrote an R function to find the nearest named colour to a given 
colour hexcode.

    #' Find the closest named colour to a given six character hex 
colour
    #'
    #' @param x vector of six digit hex colours
    #' @param method colour space, either "rgb" or "hsv".
    #' @param metric distance metric, either "euclidean" or 
"manhattan".
    #'
    #' @return vector of nearest named colour
    #' 
    #' @details If metric is "euclidean", distances are root 
sum-of-squares 
    #'     differences. "manhattan" distances are the sum of 
absolute differences. 
    #'     The named colours come from the list of 657 named 
colours stored in R, 
    #'     accessible using the \code{colors()} function.
    #' 
    #' @examples
    #' hexName(c("#117733", "#b58900", "#855C75"))
    #' 
    #' @export
    #' 
    hexName <- function(x, method = "rgb", metric = "euclidean") {
      # Check input is valid
      if (any(nchar(x) != 7) | any(!grepl("^#", x))) {
        stop("Hex code(s) invalid")
      }

      if (!method %in% c("rgb", "hsv")) {
        stop("method must be 'rgb' or 'hsv'")
      }

      if (!metric %in% c("euclidean", "manhattan")) {
        stop("metric must be 'euclidean' or 'manhattan'")
      }

      # Convert hex string to RGB 
      x <- col2rgb(x)

      # Create matrix of named colours as RGB values
      coltab <- col2rgb(colors())

      # If HSV
      if (method == "hsv") {
        x <- rgb2hsv(x)
        coltab <- rgb2hsv(coltab)
      }

      # Find nearest named colour by metric
      if (metric == "euclidean") {
        out <- colors()[apply(x, 2, function(y) {
          which.min(apply(apply(coltab, 2, "-", y)^2, 2, sum)) 
        })]
      } else if (metric == "manhattan") {
        out <- colors()[apply(x, 2, function(y) {
          which.min(apply(abs(apply(coltab, 2, "-", y)), 2, sum))
        })]
      }

      # Return
      return(out)
    }

The function lets you choose between either the RGB (Red, Green, 
Blue) or HSV (Hue, Saturation, Value) colour space. HSV is an 
alternative representation of the RGB colour space which more 
closely aligns with the way the human eye perceives colour 
differences. You can also choose between using Manhattan or 
Euclidean distances to find the nearest neighbour named colour.

R stores a list of 657 named colours, accessible from the colors() 
function.

Bonus: while learning about colour spaces I also wrote a function 
which takes a six digit colour hexcode and then finds the nearest 
three digit hexcode:

    #' Find the closest hex triplet to a given six character hex 
colour
    #'
    #' @param x vector of six digit hex colours
    #'
    #' @return vector of nearest hex colour represented as a triplet
    #' 
    #' @examples
    #' hexTrip(c("#117733", "#b58900", "#855C75"))
    #' hexTrip("#fffffff")
    #' hexTrip("855C75")
    #' 
    #' @export
    #' 
    hexTrip <- function(x) {
      if (any(nchar(x) != 7) | any(!grepl("^#", x))) {
        stop ("Hex code(s) invalid")
      }

      unlist(lapply(x, function(y) {
        paste(c("#", sprintf("%x", (col2rgb(y) + 8) %/% 17)), 
collapse = "")
      }))
    }