This is the solution I ended up with. I realized that colors came in two types: css strings and webgl typed arrays (usually 4 floats or ints, depending).
Hell with it, let the browser figure it: create a 1x1 canvas, fill it with any string color, grab the pixel, and destructure into an rgba array. There are two utilities below that create the 1x1 2d canvas ctx, attached.
# Return an RGB array given any legal CSS color, null otherwise.
# http://www.w3schools.com/cssref/css_colors_legal.asp
# The string can be CadetBlue, #0f0, rgb(255,0,0), hsl(120,100%,50%)
# The rgba/hsla forms ok too, but we don't return the a.
# Note: The browser speaks for itself: we simply set a 1x1 canvas fillStyle
# to the string and create a pixel, returning the r,g,b values.
# Warning: r=g=b=0 can indicate an illegal string. We test
# for a few obvious cases but beware of unexpected [0,0,0] results.
ctx1x1: u.createCtx 1, 1 # share across calls. closure wrapper better?
stringToRGB: (string) ->
@ctx1x1.fillStyle = string
@ctx1x1.fillRect 0, 0, 1, 1
[r, g, b, a] = @ctx1x1.getImageData(0, 0, 1, 1).data
return [r, g, b] if (r+g+b isnt 0) or
(string.match(/^black$/i)) or
(string in ["#000","#000000"]) or
(string.match(/rgba{0,1}\(0,0,0/i)) or
(string.match(/hsla{0,1}\(0,0%,0%/i))
null
What I love about it is that The Browser Speaks For Itself. Any legal string works just fine. Only downside is that if the string is illegal you get black, so need to do a few checks. The error checking is not great, but I don't need it in my usage.
The utility functions:
# Create a new canvas of given width/height
createCanvas: (width, height) ->
can = document.createElement 'canvas'
can.width = width; can.height = height
can
# As above, but returing the context object.
# Note ctx.canvas is the canvas for the ctx, and can be use as an image.
createCtx: (width, height) ->
can = @createCanvas width, height
can.getContext "2d"