Introduction
Look at the picture above. It looks like an ordinary picture of bad quality. But if you select the image (with the mouse or press Ctrl-A), the image will change. There is no script or something else which changes the picture. Cool, isn't it? I have written a Windows application which can be used to generate such magic images. You just have to select the front and the back (the hidden one) image and the program will generate the magic image. You also can adjust contrast and opacity to improve the appearance.
The small but powerful application consists of a web browser control that displays the result image, and the panel on the right where you can adjust the parameters. Open the 'New Image' dialog in the main menu to specify the output image and the two input images and hit the 'Generate' button. Additionally, you can adjust contrast and opacity. Hit the 'Refresh' button to re-generate the magic image.
How to generate 'Magic' images
For better understanding, I will give a short description of how to build such images with Adobe PhotoShop, before I will start to explain the code.
- Open the image that you want to hide.
- Duplicate the layer by right clicking and selecting 'Duplicate Layer'.
- Select the old layer and invert it (use Ctrl-I).
- Create a new image (Size: 2x2 pixels, Background: transparent) that looks like the following:
- Hit Ctrl-A to select the new image and go to Edit/Define Pattern, enter a name and hit OK. Now you can close the new image.
- Add a new layer to your hidden image and and use the paint bucket tool to fill the pattern you just defined.
- Now, hold Ctrl down and click on the layer you just filled. Now, click on the duplicate of the original layer and hit 'Delete' to delete the selected pixels.
- Remove the layer that you have filled with the pattern. You should now have two layers, the original with inverted colors, and the duplicate with the deleted pixels.
- Merge the two images (link the two layers and hit Ctrl-E).
- Go to Image/Adjustments/Brightness/Contrast and slide the contrast down to -30.
- Open the front image and hit Ctrl-A and Ctrl-C to copy it to the clipboard.
- Go back to the hidden image and hit Ctrl-V to paste the front image.
- Then change the opacity of the front image to 50%, save the image, and you are done.
You can test it by opening the picture in Microsoft Internet Explorer and hit Ctrl-A to select all.
About the code
All the work is done in the GenerateMagicImage
function.
This function iterates through all pixels on the hidden image and inverts every pixel that matches the pattern above. To invert a pixel, we simply have to subtract the color value from 255. For further information on Image Processing, see Christian Graus's articles on Image Processing.
The following code operation matches steps 2-9 of How to generate 'Magic' images.
...
BitmapData bmData =
bHidden.LockBits( new Rectangle(0, 0, bHidden.Width, bHidden.Height),
ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
int stride = bmData.Stride;
System.IntPtr Scan0 = bmData.Scan0;
unsafe
{
byte * p = (byte *)(void *)Scan0;
int nOffset = stride - bHidden.Width*3;
for ( int y=0; y<bHidden.Height; ++y)
{
for ( int x=0; x<bHidden.Width; ++x )
{
if ( (x+y) % 2 == 0 )
{
for ( int i=0; i<3; i++ )
p[i] = (byte)(255-p[i]);
}
...
p += 3;
}
p += nOffset;
}
}
bHidden.UnlockBits(bmData);
...
Now, we have to change the contrast of the picture. (Step 10 of How to generate 'Magic' images).
...
if (nContrast < -100) return null;
if (nContrast > 100) return null;
double pixel = 0, contrast = (100.0+nContrast)/100.0;
contrast *= contrast;
BitmapData bmData =
bHidden.LockBits( new Rectangle(0, 0, bHidden.Width, bHidden.Height),
ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
int stride = bmData.Stride;
System.IntPtr Scan0 = bmData.Scan0;
unsafe
{
byte * p = (byte *)(void *)Scan0;
int nOffset = stride - bHidden.Width*3;
for ( int y=0; y<bHidden.Height; ++y)
{
for ( int x=0; x<bHidden.Width; ++x )
{
...
for ( int i=0; i<3; i++ )
{
pixel = p[i]/255.0;
pixel -= 0.5;
pixel *= contrast;
pixel += 0.5;
pixel *= 255;
if (pixel < 0) pixel = 0;
if (pixel > 255) pixel = 255;
p[i] = (byte) pixel;
}
p += 3;
}
p += nOffset;
}
}
bHidden.UnlockBits(bmData);
...
Finally, we have to add the front image and change the opacity of the front image. (Step 11-13 of How to generate 'Magic' images.)
...
Graphics g = Graphics.FromImage( bHidden );
float[][] ptsArray ={ new float[] {1, 0, 0, 0, 0},
new float[] {0, 1, 0, 0, 0},
new float[] {0, 0, 1, 0, 0},
new float[] {0, 0, 0, opacity, 0},
new float[] {0, 0, 0, 0, 1}};
ColorMatrix clrMatrix = new ColorMatrix( ptsArray );
ImageAttributes imgAttributes = new ImageAttributes();
imgAttributes.SetColorMatrix( clrMatrix,
ColorMatrixFlag.Default, ColorAdjustType.Bitmap );
g.DrawImage( bFront, new Rectangle( 0, 0, bFront.Width, bFront.Height ), 0, 0,
bFront.Width, bFront.Height, GraphicsUnit.Pixel, imgAttributes );
Bitmap bResult = new Bitmap( bHidden.Width, bHidden.Height );
bResult = (Bitmap)bHidden.Clone();
return bResult;
...
Here is the whole GenerateMagicImages
function:
public Bitmap GenerateMagicImage(Bitmap bHidden,
Bitmap bFront, sbyte nContrast, float opacity )
{
if (nContrast < -100) return null;
if (nContrast > 100) return null;
double pixel = 0, contrast = (100.0+nContrast)/100.0;
contrast *= contrast;
BitmapData bmData =
bHidden.LockBits( new Rectangle(0, 0, bHidden.Width, bHidden.Height),
ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
int stride = bmData.Stride;
System.IntPtr Scan0 = bmData.Scan0;
unsafe
{
byte * p = (byte *)(void *)Scan0;
int nOffset = stride - bHidden.Width*3;
for(int y=0; y<bHidden.Height; ++y)
{
for(int x=0; x<bHidden.Width; ++x )
{
if ( (x+y) % 2 == 0 )
{
for ( int i=0; i<3; i++ )
p[i] = (byte)(255-p[i]);
}
for ( int i=0; i<3; i++ )
{
pixel = p[i]/255.0;
pixel -= 0.5;
pixel *= contrast;
pixel += 0.5;
pixel *= 255;
if (pixel < 0) pixel = 0;
if (pixel > 255) pixel = 255;
p[i] = (byte) pixel;
}
p += 3;
}
p += nOffset;
}
}
bHidden.UnlockBits(bmData);
Graphics g = Graphics.FromImage( bHidden );
float[][] ptsArray ={ new float[] {1, 0, 0, 0, 0},
new float[] {0, 1, 0, 0, 0},
new float[] {0, 0, 1, 0, 0},
new float[] {0, 0, 0, opacity, 0},
new float[] {0, 0, 0, 0, 1}};
ColorMatrix clrMatrix = new ColorMatrix( ptsArray );
ImageAttributes imgAttributes = new ImageAttributes();
imgAttributes.SetColorMatrix( clrMatrix,
ColorMatrixFlag.Default, ColorAdjustType.Bitmap );
g.DrawImage( bFront,
new Rectangle( 0, 0, bFront.Width, bFront.Height ), 0, 0,
bFront.Width, bFront.Height,
GraphicsUnit.Pixel, imgAttributes );
Bitmap bResult = new Bitmap( bHidden.Width, bHidden.Height );
bResult = (Bitmap)bHidden.Clone();
return bResult;
}
The selecting and unselecting of the image is done using the ExecWB
function of the AxWebBrowser
control.
Object o = new Object();
SHDocVw.OLECMDID SelectAllCommand = SHDocVw.OLECMDID.OLECMDID_SELECTALL;
SHDocVw.OLECMDEXECOPT exeOpt = SHDocVw.OLECMDEXECOPT.OLECMDEXECOPT_DODEFAULT;
preview.ExecWB(SelectAllCommand, exeOpt, ref o, ref o);