An HSV to RGB color converter in Scala

Nov 3, 2010 16:11 · 1229 words · 6 minutes read Scala

I recently was working on my L-Systems evaluator again and wanted some nice colors for the output. Specifically, I wanted to be able to interpolate between two colors conveniently. I am using SVG as output format and in SVG all colors are always defined as RGB triplets. RGB is however a lousy colorspace for interpolating colors, the resulting interpolation just does not look “right”. So I decided to interpolate in the HSV colorspace, which organizes colors in a way that closer resembles the way humans perceive color.

So I had to write a simple conversion from RGB to HSV to do the interpolation on hsv triplets and then back to RGB to add the resulting colors to SVG. I picked the necessary formulae from the wikipedia article on the HSV color space and translated them to Scala. I was surprised and impressed how comfortable this can be done. This is the code I came up with:

object RgbHsv {
/** Converts an rgb triplet where each value is in the range [0,1]
*  into an hsv triplet where h (hue) is in the range [0,360], s (saturation) 
*  and value in the range [0,1]
*/  
def rgbToHsv(r:Double, g:Double, b:Double):(Double, Double, Double) = {
    //get the value of the strongest color component
    val M = (r max g) max b
    //get the value of the weakest color component
    val m = (r min g) min b
    //this is already a first measure of how "colorful" the color is
    //this is not yet the saturation but it is a measure of how 
    //dominant the max color component is
    val c = M-m
    //determine the position in the hsv color circle 
    //we check which third of the color circle we 
    //lie in by comparing to the three rgb components
    val h1 = if      (c==0.0) 0.0    //we have some grey tone
            else if (M==r)  ((g-b)/c) mod 6
            else if (M==g)  ((b-r)/c) + 2.0
            else   /*M==b*/ ((r-g)/c) + 4.0
    //convert to degrees. remove this step if you want 
    //the h value in the range [0,1] as well
    //(remember to adjust the hsvToRgb function if you do that!)
    val h = h1*60.0
    val v = M
    //if we have a greyish tone, saturation is zero
    //same is true if the value is zero, then we have black
    val s = if (c==0.0 || v==0.0) 0.0
            else c / v    
    
    (h,s,v)
}

/** Converts an hsv triplet in the range ([0,360],[0,1],[0,1])
*  to an rgb triplet in the range ([0,1],[0,1],[0,1])
*/
def hsvToRgb(h:Double, s:Double, v:Double)
    :(Double, Double, Double) = {
    //restore the "distance" between the max and min RGB component
    //this is simply the reverse of the calculation for s
    val c  = s*v
    //normalize the degree value for the hue to [0,1]
    //omit this step if you input h in the range [0,1] already
    val h1 = h / 60.0
    //restore one rgb value (we don't know which it is yet, hence "x")
    val x  = c*(1.0 - ((h1 mod 2) - 1.0).abs)
    //depending on where we lie in the color circle, restore a partial rgb triplet 
    val (r,g,b) = if      (h1 < 1.0) (c, x, 0.0)
                else if (h1 < 2.0) (x, c, 0.0)
                else if (h1 < 3.0) (0.0, c, x)
                else if (h1 < 4.0) (0.0, x, c)
                else if (h1 < 5.0) (x, 0.0, c)
                else  /*h1 < 6.0*/ (c, 0.0, x)
    //this is the smallest component. 
    val m = v-c
    //we offset all components by m.
    //the zero component is now set to the correct minimum
    //the other two are shifted to the final correct value
    (r+m, g+m, b+m)
}
}

Scala allows to implement the calculation pleasantly brief. The automatic type derivation of the compiler allows us to omit all the types for the variables. Explicitly adding them this case would not clarify anything, as it is all just doubles anyway.

More importantly here one can see how useful it is that the Scala if statement has a value and is not only executed for its side effects, like in JAVA. If we write the rgbToHsv if statement in JAVA we see how much boilerplate code the Scala version saves:

final double h1;
if      (c==0.0) h1 = 0.0
else if (M==r)   h1 = ((g-b)/c) mod 6
else if (M==g)   h1 = ((b-r)/c) + 2.0
else   /*M==b*/  h1 = ((r-g)/c) + 4.0

This is just a typo waiting to happen and annoying to type to boot. Of course the differentiation between val and var is something I so missed in JAVA. If only all JAVA variables where final by default (well, then they wouldn’t be variables) and there was a keyword for “not final” instead…

Another thing i really love about Scala is the builtin Tuple support. In Java you would either have to write a new class for this color triplet or (what happens often in these situations) people start throwing arrays around which robs the compiler of the chance to check the correct number of values is returned.

The careful reader will have noticed by now that the above code doesn’t compile just yet. The reason for this is the mod operator used. But this is easily remedied. Scala has a great and simple way to graft on all kinds of operators/methods to any type: implicit conversions. We simply wrap the double to the left of the mod operator into an object that defines a method named mod that takes a long argument. Then we import a method into the current scope that takes a Double and returns the corresponding wrapper object. By marking that method with the keyword implicit the compiler silently includes the conversion for us. Last but not least Scala allows any method that takes only one parameter to be written in operator notation, i.e. we can write:

foo mod bar

instead of

foo.mod(bar)

I.e. there is actually no difference between a method and an operator. Even the standard numerical operators are considered to be members of Int, Double, Long etc., so the following is valid Scala:

val a = 5.+(b)

So to round off our example, here is the missing code to allow using our custom mod operator:

class PoorLong (val l:Long) {
    /** a "mathematical" modulo that always maps to 0...n-1 (for positive n)
    */
    def mod(n:Long) = {
        val m = l % n
        if (m<0) m+n else m
    }
}

class PoorDouble (val d:Double) {
    /**the "decimals" of this double
    */
    def fraction:Double = d - d.floor

    /** a "pseudo-mod", 7.5 mod 5 would map to 2.5, -0.5 mod 5 would map to 4.5
    */
    def mod(n:Long) = new PoorLong(d.floor.longValue).mod(n).toDouble + fraction
}

object Conversions {
    implicit def longToPoorLong(l:Long) = new PoorLong(l)
    implicit def doubleToPoorDouble(d:Double) = new PoorDouble(d)
} 

import Conversions._ //put this import before your hsv converter code

P.S.: you may use the above code in any way (commercial/non-commercial/open/closed source…) you want. However I don’t take any liability for its correctness. For all you know it might format your harddisk and send your addressbook to spammers. So treat it with the usual care you should apply to any copy-pasted code from the internets.

P.P.S.: Here used to be the output of the interpolating L-System, but that didn’t seem to not go down well with some feed readers. Until I have some mechanism in place to filter certain content out of the feed creation, I will have to remove it :(