Color and Web Browsers
Message from 2022
This post is pretty old! Opinions and technical information in it are almost certainly oudated. Commands and configurations will probably not work. Consider the age of the content before putting any of it into practice.
This post is related to the Design Miami meetup on March 4, 2013. I did a quick talk about “Color and Computers”, and Dimitry Chamy did a wonderful and exhaustive talk about using colors in design. Links to meetup materials are available, and you’re welcome to future meetups.
Web browsers take descriptions of colors from CSS declarations. Some of them are:
- #F0F
- 12-bit RGB color, four bits per hexit1
- #FF00FF
- 24-bit RGB color, four bits per hexit
- rgb(255, 0, 255)
- RGB color, decimals
- rgb(100%, 0%, 100%)
- RGB color, decimal percents
- hsl(300, 100%, 50%)
- HSL color, decimal degree for hue, and percents for saturation and lightness2
The written-out RGB and HSL declarations with decimal-encoded numbers don’t specify precision per se. The W3C recommendation for the CSS Color Module doesn’t either. If you wanted, you could jam negative numbers or decimal places in there:
Values outside the device gamut should be clipped or mapped into the gamut when the gamut is known: the red, green, and blue values must be changed to fall within the range supported by the device. […]
em { color: rgb(255,0,0) } /* integer range 0 - 255 */ em { color: rgb(300,0,0) } /* clipped to rgb(255,0,0) */ em { color: rgb(255,-10,0) } /* clipped to rgb(255,0,0) */ em { color: rgb(110%, 0%, 0%) } /* clipped to rgb(100%,0%,0%) */
Since the W3C doesn’t provide much guidance, we have to see how browsers implement colors.
Google Chrome, Apple Safari, and other WebKit Browsers
CSS parsing is done by WebCore, mostly in WebCore/css/CSSParser.cpp
3.
It calls CSSParser::parseColorParameters
for RGB colors:
bool CSSParser::parseColorParameters(CSSParserValue* value, int* colorArray, bool parseAlpha)
{
// …
for (int i = 1; i < 3; i++) {
// …
colorArray[i] = colorIntFromValue(v);
RGB color components are converted to integers. HSL:
bool CSSParser::parseHSLParameters(CSSParserValue* value, double* colorArray, bool parseAlpha)
{
// …
colorArray[0] = (((static_cast(parsedDouble(v, ReleaseParsedCalcValue)) % 360) + 360) % 360) / 360.0;
for (int i = 1; i < 3; i++) {
// …
colorArray[i] = max(0.0, min(100.0, parsedDouble(v, ReleaseParsedCalcValue))) / 100.0;
HSL colors are parsed as floating-point (the double
type), but, in WebCore/platform/graphics/Color.cpp…
RGBA32 makeRGBAFromHSLA(double hue, double saturation, double lightness, double alpha)
{
// …
return makeRGBA(static_cast(calcHue(temp1, temp2, hue + 1.0 / 3.0) * scaleFactor),
static_cast(calcHue(temp1, temp2, hue) * scaleFactor),
static_cast(calcHue(temp1, temp2, hue - 1.0 / 3.0) * scaleFactor),
static_cast(alpha * scaleFactor));
}
We see that our carefully described, bespoke, artisanal, floating point colors are being converted down to 24-bit RGB (well, 32-bit RGBA).
Mozilla Firefox and other Mozilla Browsers
HSL conversion for CSS is done in layout/style/nsCSSParser.cpp
:
bool CSSParserImpl::ParseHSLColor(nscolor& aColor,
char aStop)
{
float h, s, l;
//…
if (ExpectSymbol(aStop, true)) {
aColor = NS_HSL2RGB(h, s, l);
return true;
HSL is converted into an nscolor
instance. As implemented in gfx/src/nsColor.h
, it’s a 32-bit RGBA thing, just like WebKit.
Conclusion
Storing more than 32 bits of color in your RGB/RGBA declarations is silly. If you use either of the 24-bit representations above (or the 12-bit representation, since it converts losslessly into 24-bit), you’ll get correct4 colors.
If you use HSL colors, you’ll lose some precision in conversion, but probably no worse than you’ll lose when your site gets displayed on a 18-bit or fewer display (such as in the iPhone 3GS and earlier).
Notes
- “hexit” is like a digit but hexadecimal instead of decimal. If you like contractions, “hexadecimal digit” becomes “hexit.”
- The degrees component doesn’t have a glyph because it’s unintuitive to type: ⌥⇧8 on Mac OS X, Alt-0176 on Windows, ⌃⇧-UB0 on Ubuntu.
- I’m not pasting whole functions because C++ is ugly and verbose.
- As much as you can have correct colors on the web.