HSB Colorspace in Java ---------------------- rgb to hsb: ----------- Input: red, green, and blue as integers scaled from 0 to 255 Output: hue, saturation, and brightness as floats scaled from 0.0 to 1.0 int bri = max(red, green, blue); if (bri == 0) { // short-circuit now and avoid division by zero problems later brightness = 0.0; saturation = 0.0; hue = 0.0; return; } brightness = bri / 255.0; int desaturator = min(red, green, blue); if (bri == desaturator) { // we're grey (and still have division by zero issues to bypass) saturation = 0.0; hue = 0.0; return; } saturation = (brightness - (desaturator / 255.0)) / brightness; int midigator = mid(red, green, blue); // "domains" are 60 degrees of red, yellow, green, cyan, blue, or magenta // compute how far we are from a domain base float domainBase; float oneSixth = 1.0f / 6.0f; float domainOffset = (midigator - desaturator) / (float)(bri - desaturator) / 6.0; if (red == bri) { if (midigator == green) { // green is ascending domainBase = 0 / 6.0; // red domain } else { // blue is descending domainBase = 5 / 6.0; // magenta domain domainOffset = oneSixth - domainOffset; } } else if (grn == bri) { if (midigator == blue) { // blue is ascending domainBase = 2 / 6.0; // green domain } else { // red is descending domainBase = 1 / 6.0; // yellow domain domainOffset = oneSixth - domainOffset; } } else { if (midigator == red) { // red is ascending domainBase = 4 / 6.0; // blue domain } else { // green is descending domainBase = 3 / 6.0; // cyan domain domainOffset = oneSixth - domainOffset; } } hue = domainBase + domainOffset; return; hsb to rgb ---------- Input: hue, saturation, and brightness as floats scaled from 0.0 to 1.0 Output: red, green, and blue as floats scaled from 0.0 to 1.0 if (brightness == 0.0) { // safety short circuit again red = 0.0; green = 0.0; blue = 0.0; return; } if (saturation == 0.0) { // grey red = brightness; green = brightness; blue = brightness; return; } float domainOffset; // hue mod 1/6 if (hue < 1.0/6) { // red domain; green ascends domainOffset = hue; red = brightness; blue = brightness * (1.0 - saturation); green = blue + (brightness - blue) * domainOffset * 6; } else if (hue < 2.0/6) { // yellow domain; red descends domainOffset = hue - 1.0/6; green = brightness; blue = brightness * (1.0 - saturation); red = green - (brightness - blue) * domainOffset * 6; } else if (hue < 3.0/6) { // green domain; blue ascends domainOffset = hue - 2.0/6; green = brightness; red = brightness * (1.0 - saturation); blue = red + (brightness - red) * domainOffset * 6; } else if (hue < 4.0/6) { // cyan domain; green descends domainOffset = hue - 3.0/6; blue = brightness; red = brightness * (1.0 - saturation); green = blue - (brightness - red) * domainOffset * 6; } else if (hue < 5.0/6) { // blue domain; red ascends domainOffset = hue - 4.0/6; blue = brightness; green = brightness * (1.0 - saturation); red = green + (brightness - green) * domainOffset * 6; } else { // magenta domain; blue descends domainOffset = hue - 5.0/6; red = brightness; green = brightness * (1.0 - saturation); blue = red - (brightness - green) * domainOffset * 6; } return; commentary ---------- The one thing I wish that they would have done differently is scale hue from 0 to 2*PI. Scaling it to 1.0 max makes it look like hue is the same sort of thing as are brightness and saturation. Hue is fundamentally different -- a circle instead of a linearly ascending straight line. So the "parallelism" is false and will lead to unnecessary confusion. (In fairness, scaling to 2*PI is a true non-parallelism, and will lead to necessary confusion.) Since Java trigonometry is based on radians instead of degrees, then the 2*PI scale actually has advantages instead of simply forcing programmers to recognize it as something different.