/** This class contains routines for working with named colors. It uses the color names from xkcd: See: https://blog.xkcd.com/2010/05/03/color-survey-results/ And requires the file available at: https://xkcd.com/color/rgb.txt which should be renamed to xkcdrgb.txt to match the value in the readFile method. This class contains static methods that can be used to find named colors, to find colors with similar names, or to find colors that are similar to a specified color. In this file, colors are specified as [r,g,b] arrays where each value is expected to be an integer between 0 and 255 inclusive. */ class NamedColors { /** Name to [r,g,b] dictionary */ class var nameDict = NamedColors.readFile[] /** Takes a name and returns the [name, [r,g,b]] value for that color. If the exact color name does not exist, this uses the closest name by Levenshtein-Damerau edit distance. */ class byName[name] := { name = lc[name] color = nameDict@name if color != undef return [name, color] closestDist = million closestName = undef closestColor = undef for [n, color] = nameDict { dist = editDistanceDamerau[n, name] if dist < closestDist { closestDist = dist closestName = n closestColor = color } } return [closestName, closestColor] } /** Retains all the [name, [r,g,b]] values containing the specified text. */ class containingName[name] := { name = lc[name] reg = regex[name] result = new array for [n, color] = nameDict { if n =~ reg result.push[[n, color]] } return sort[result, {|a,b| length[a@0] <=> length[b@0]}] } /** Finds the nearest named color from an array representing colors as [r,g,b]. Returns [name, [r,g,b]] */ class nearestColor[array] := { return nearestColor[array@0, array@1, array@2] } /** Finds the nearest named color. Returns [name, [r,g,b]] */ class nearestColor[r, g, b] := { c1 = [r, g, b] nearestDist = million nearestName = undef nearestColor = undef for [name, c2] = nameDict { dist = hypotenuse[abs[subtract[c2,c1]]] if dist < nearestDist { nearestDist = dist nearestName = name nearestColor = c2 } } return [nearestName, nearestColor] } /** Finds the nearest named colors to a color specified as an array [r,g,b]. Returns an array of [name, [r,g,b]] of length at most num. */ class nearestColors[array, num] := { return nearestColors[array@0, array@1, array@2, num] } /** Finds the nearest named colors. Returns an array of [name, [r,g,b]] of length at most num. */ class nearestColors[r, g, b, num] := { c1 = [r, g, b] result = new array for [name, c2] = nameDict { dist = hypotenuse[abs[subtract[c2,c1]]] result.push[[name, c2, dist]] } return first[sort[result, byColumn[2]], num] } /** Turns a [r,g,b] color array from 0 to 255 into a color object. */ class toColor[array] := { return new color[array@0 / 255, array@1 / 255, array@2 / 255] } /** Turns a [r,g,b] color array from 0 to 255 into a hex string. */ class toHex[array] := { return padLeft[hex[array@0], 2, "0"] + padLeft[hex[array@1], 2, "0"] + padLeft[hex[array@2], 2, "0"] } /** Parses a color from hexadecimal notation with an optional preceding # sign. */ class fromHex[str] := { if [r, g, b] = str =~ %r/#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})/ { r = parseInt[r, 16] g = parseInt[g, 16] b = parseInt[b, 16] return [r, g, b] } return undef } /** Reads and initializes from the data file. You do not need to call this; it is called automatically on class initialization. */ class readFile[] := { nd = new dict LINE: for line = lines["file:xkcdrgb.txt"] { if line =~ %r/^\s*#/ next LINE if [name, r, g, b] = line =~ %r/(.*?)\s*#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i { r = parseInt[r, 16] g = parseInt[g, 16] b = parseInt[b, 16] nd@name = [r,g,b] } } return nd } }