Somebody Explained Color on Angelfire in 2002
It got the perceptual brightness weights right, on a free host, with a hit counter. Then it told you to go and do the demos yourself in MSPaint. So we did them.
There is a page on Angelfire called The color system and it’s many uses, apostrophe included, first archived in February 2002 and written by someone going by ulillillia. It is a single scrolling document with green tables, inline <font color> tags and a JavaScript colour mixer that still works if you can get a browser to run it. It explains hexadecimal, the three channels of light, averaging, dithering, perceived brightness, complementary colours, and then keeps going into a colour format the author appears to have invented.
We went through it with a calculator, because a tribute that repeats an error is not much of a tribute. Every worked example on the page is correct except one digit. The brightness table, which the page never explains the source of, matches the international broadcast standard for perceived luminance to the unit, sixteen times out of sixteen.
What follows is the same material, with the parts that said “now open a paint program and do this by hand” replaced by the thing itself. All of the arithmetic below runs the way the original specified it, down to how it rounded.
A number is a row of switches
The page starts where every hexadecimal explanation starts: base 10 has ten digits, base 16 has sixteen, so it borrows six letters. True, and not much use on its own.
What helps is the thing underneath. Any written number is a row of positions, and each position is worth a fixed multiple of the one to its right. In binary that multiple is two, so the positions run 1, 2, 4, 8, 16 and up. To read the number, add up the positions that are switched on.
Figure 1: a number is a row of switches
Carrying is the only other rule, and pressing +1 shows it better than a description does. A switch that is already on cannot go up, so it drops to off and hands the job to its neighbour. Hexadecimal does the same thing with sixteen values per position instead of two, which is why the same quantity stays so much shorter: 255 takes eight binary switches, or two hex digits.
Two digits, one byte, three bytes, one color
Two hex digits reach exactly 255, which is one byte, which is exactly how much of one colour a screen stores. The original handled the conversion with two lookup tables and a six-step procedure. The mechanism they encode is smaller than they are: the left digit counts sixteens, the right counts ones. F2 is fifteen sixteens plus two, which is 242. That is both tables.
Figure 2: two hex digits make one byte
6 × 16 + 0 = 96
7 × 16 + 4 = 116
15 × 16 + 2 = 242
Stack three of those and you have a colour. Red, then green, then blue, two digits each, always that order. The page’s running example is the colour of the sky, 6074F2: 96 red, 116 green, 242 blue. Heavy on blue, as you would expect, and with more green in it than you would guess until you pull the green out and watch it go purple.
Two ways to reach the same color
Here the page does something that most colour explainers skip. It treats averaging and dithering as the same operation rather than two topics.
Averaging is arithmetic. Take two colours, add each channel, divide, and you have a third colour that sits between them. Dithering is not arithmetic at all. You paint the two original colours in a fine checkerboard and paint no third colour anywhere. Your eye does the division. At normal size, on a normal screen, at a normal distance, the two results are indistinguishable.
The page walked you through proving this with a mouse: make a document, fill one side, fill the other, hand-place alternating pixels, copy and paste the block until the canvas is full, zoom back out. Twenty minutes of work to see one thing happen.
Figure 3: averaging, then the same answer the long way
Dithering: the same two colours, alternating, with a flat patch of their average butted up against it. No third colour is painted anywhere on the left.
The lower panel is drawn in real device pixels, so the effect is honest rather than simulated. Take the cell size to 1 and the checkerboard and the flat average become the same colour with a seam you cannot find. Take it to 16 and the illusion collapses into what it actually is, which is two colours in a grid. Somewhere in between is every halftone newspaper photograph ever printed, and every dithered GIF anybody made in 1998.
The illusion is not in the image. It is in the person looking at it, which is why you cannot check this one with a calculator.
Where the page gets surprising
Alongside its sixteen-colour palette, the page prints a brightness number for each colour. It does not say where the numbers come from, and it does not present them as a big deal. Black is 0. White is 255. Yellow is 226. Blue is 29.
Those numbers are not the average of the three channels. If you average the channels, pure blue and pure red and pure green all come out to 85, which is the answer nearly every beginner tutorial of that era gave, and it is wrong in a way you can see. Those numbers are Rec.601 luma: red weighted 0.299, green 0.587, blue 0.114. It is the weighting the broadcast industry settled on so that a colour television picture would still look right to somebody watching it on a black and white set.
Figure 4: brightness is not what the colour looks like
The page also plotted this curve by hand, as an image, 769 pixels wide, and pointed out that the drop between cyan and blue is the steepest run on it. That is correct, and the reason is visible in the weights. That stretch is green going from full to nothing, and green is worth more than half of all perceived brightness. Nothing else on the curve can fall that fast except the matching climb from red to yellow, where green is arriving instead of leaving.
Figure 5: checking the homework, twenty-four years late
| colour | hex | published 2002 | Rec.601 now | match |
|---|---|---|---|---|
| black | 000000 | 0 | 0 | match |
| grey | 808080 | 128 | 128 | match |
| dark red | 800000 | 38 | 38 | match |
| dark yellow | 808000 | 113 | 113 | match |
| dark green | 008000 | 75 | 75 | match |
| dark cyan | 008080 | 90 | 90 | match |
| dark blue | 000080 | 15 | 15 | match |
| dark magenta | 800080 | 53 | 53 | match |
| white | FFFFFF | 255 | 255 | match |
| light grey | C0C0C0 | 192 | 192 | match |
| red | FF0000 | 76 | 76 | match |
| yellow | FFFF00 | 226 | 226 | match |
| green | 00FF00 | 150 | 150 | match |
| cyan | 00FFFF | 179 | 179 | match |
| blue | 0000FF | 29 | 29 | match |
| magenta | FF00FF | 105 | 105 | match |
16 / 16 exact. Not close. Exact.
Sixteen for sixteen is the part that changed how we read the rest of the page. You do not land on 0.299, 0.587 and 0.114 by feel. Either the author found the standard somewhere and understood it well enough to apply it correctly across a whole palette, or they worked backwards to it from looking very hard at greyscale conversions, which the rest of the page makes entirely plausible. Either way it is the right answer, arrived at and then presented without comment, in a table with a dark green background.
The one mistake
An opposite, in this system, is what is left when you take each channel away from 255. The page gives three examples. Yellow-orange and dark grey are both exactly right. Sky blue is not: it gives 9F8B07 where the blue channel works out to 0D.
Figure 6: the opposite is just what is left over
Page says 9F8B07. Arithmetic says 9F8B0D. You could not pick those apart in a line-up, which is presumably how it survived eighteen years of being on the internet.
luma 124 luma 131Opposites do not trade brightness evenly. That is the same 0.587 doing the work.
Six units of blue, at the dark end, inside a colour that is mostly olive. Nobody was ever going to catch that by looking, and it stayed on the internet for eighteen years.
The part that was ahead of itself
The last section is the one we did not expect. Having established that a colour is six hex digits, the page proposes extending it to twelve. Two more digits for transparency, two for reflectivity, and two for how much light is falling on the object.
Transparency as a fourth channel was already common by 2002. The other two are not colour information at all. Reflectivity describes the surface, and the page is careful about what it means by it: not how bright the object is, but how clearly you can see an image in it. A mirror is FF. A cardboard box is 00. Most real objects, it reckons, land somewhere between 08 and 20.
That is a material, not a colour. Splitting a surface into what it does to light rather than what colour it is, and giving reflectivity its own numeric channel with a defined scale, is the idea that physically based rendering is built on, and physically based rendering did not become standard practice in games for another decade. The page also separates incident lighting from the object itself and gives a reason: an object receives different amounts of light at different times of day, so that value does not belong in the object’s description. It then drops those digits from its own notation for exactly that reason.
Its worked example is water, which it puts at E0E0E0, fifteen sixteenths transparent, with a reflection about three sixteenths as sharp as a mirror. Argue with the numbers if you like. The structure underneath them is a material system, written on a free web host, by someone who was mostly trying to explain why numbers beat words for naming colours.
What does not hold up
Plenty. The page is a single column of text with no navigation and no way in except the top. Its central argument, that numeric colour names beat word ones, spends a lot of energy on a fight nobody is having any more. Its claim that crossing the sixteen palette colours yields fifty distinct results does not reproduce: counting every unordered pair we get ninety, and no interpretation we tried lands on fifty, though it depends on what you are willing to call visibly different on a CRT. And the hue, saturation and brightness scale it describes at the end, where the author estimates colours in the physical world by eye, is left explicitly unfinished, with a note about still looking for the formula.
None of that is the interesting part. The interesting part is that a page with a <center> tag and a broken apostrophe in its title contains a correct derivation of perceptual luminance and a rough sketch of a material model, and presents both of them as though they were obvious.
Why this is worth keeping
The web used to be full of pages like this. Someone got interested in something, worked it out further than they needed to, wrote it down in whatever they had, and put it up. No editor, no audience target, no reason. Angelfire has since stopped handing out free pages, this one survives through the Internet Archive, and the version you can read today has advertising scripts and a toolbar wrapped around it that were never part of what its author made.
What we changed is the smallest part of it. The explanations were already correct. All we did was take the instructions that said to go and do something by hand, and do it. That is genuinely all that separates this page from a good interactive explainer: not the understanding, which was there in 2002, but about four hundred lines of the kind of code that did not exist to be written yet.
If you want to go further with the vocabulary, we keep a plain-language glossary, and we wrote something elsewhere about what happens when nobody writes a thing down in a form a computer can read. This page is the opposite case. Somebody wrote it all down, in 1999-era HTML, and it was still there when we went looking.
Original: The color system and it’s many uses by ulillillia, angelfire.com/journal2/ulillillia, first archived February 2002. Read it as it was. The explanations, the palette, the brightness table and the twelve-digit format are theirs. The widgets on this page are ours, and the arithmetic behind them follows the original’s method, including its rounding.
