Playing with Color (part I)
(Originally published on June 20, 2009)
I’m fascinated by color. When I was younger I dreamt of discovering a color that nobody has ever seen before. And since most computer languages give you easy ways to manipulate graphics, I played with color on a computer.
One day I saw an interesting effect on some certificate I received. When I photocopied it in black and white, a word appeared in the photocopy ("COPY") that wasn’t visible in the original. This "security measure" took advantage of the photocopier’s inability to faithfully replicate the document (the word COPY was actually visible in the original but it was difficult to see because it consisted of a myriad of tiny dots). I wondered whether it would be possible to hide messages in documents even if the copier were to produce faithful facsimiles, but converted color to black & white images.
It turns out that it’s possible, and not that difficult. I’ll explain how, while trying to limit distracting you with color theory to the minimum.
On a computer screen, each color is a mixture of three primary colors, red, green and blue (printers use a slightly different scheme but for all intents and purposes we can limit ourselves to talking about the RGB space). It turns out that pure red does not appear as “bright” to the eye as pure green. In fact, the intensity of a color (how “bright” it appears to the eye regardless of its hue) has been found to be a weighed average of the three primary components:
intensity = 0.299*red + 0.587*green + 0.114*blue
This intensity is precisely what you see when you convert an image to grayscale (note that you may find different formulas depending on what devices you’re dealing with — different devices have different sensitivity to various primary colors). For example, consider the following image and its grayscale version:
A simple idea for hiding messages is therefore to produce an image of pixels whose intensity is all the same, but whose hue is different (so that we can see the message in color). For example, we can give every pixel of the message the color of (1, 0.3, 0.4) and every pixel of the background the color of (0.3, 0.7, 0.176). The intensity of the former is 0.52, as is the intensity of the latter. Since the intensities are close to each other, when we desaturate the image, the message will blend with the background.
This works well if we know the relationship for a given device exactly. Some printers (if we wish to print the image out) use a slightly different relationship (or perhaps they round the weights). In such a case the two intensities will not be exactly the same and the eye is surprisingly good at telling the difference between two close colors if they cover large areas! We can improve this algorithm by adding some randomness (this is just a suggestion that works pretty well, you can find much better algorithms!):
Consider an input image that contains the message (white text on black background). Each pixel has an intensity of 0 (if the pixel belongs to the background) or 1 (if the pixel belongs to the text), and probably somewhere in between if the text is antialiased.
First, transform the image by adding some random noise and reducing the variance between white and black:
targetIntensity = (rand() + sourceIntensity + 1)*0.33
Once we have the target intensity, we’ll randomly adjust the intensity formula and find an output color such that the intensity formula cancels targetIntensity out:
alpha = 0.587 + (rand(25)-12)*0.001
beta = 0.299 + (rand(25)-12)*0.001
gamma = 1.0 - alpha - beta
ratio = (alpha-gamma)/beta
r = 0.44 + targetIntensity/3.0
g = 0.62 - targetIntensity*ratio/3.0
b = 0.9 - targetIntensity/3.0
Convince yourself that the intensity is a constant, independent of targetIntensity. But since targetIntensity varies, the color version of the image will consist of distinctly different pixels.
As a nice touch we can alternate between two different formulas for added noise:
Print the above image out in black and white and see the message disappear!
Demo!
You can see a demo of this effect here
See also
Check out the second part of this post (hiding in the color rendering) here.
Check out References for some useful color-related references, including a note on color space conversions.
Check the code out on github.