Introduction
This article does not introduce any new method of steganography. First, this one is the answer to your complaints about slow image processing. Second, it adds an alternative to the Noise Generator to the application from Steganography IX.
Unsafe Pixels
GetPixel
and SetPixel
are very slow. For example, hiding 1 MB in a carrier bitmap of 1280x1024 pixels takes 7931 milliseconds (on my poor machine). Using Unsafe Image Processing, the same process takes only 581 milliseconds, that's only 7% of the time!
GetPixel
returns a System.Drawing.Color
value. We do not need to copy the color anymore, because we are going to edit the pixel in the locked memory, instead of copying the color, changing it and then writing it back into the bitmap with SetPixel
. So, let us replace System.Drawing.Color
with a small and simple struct
, just like it is described in MSDN:
public struct PixelData {
public byte Blue;
public byte Green;
public byte Red;
}
The variable pixelColor
is not replaced at all, because we are not going to waste time copying colors anymore. Instead, we define a pointer to a PixelData
structure - this pointer will point directly into the memory block of the bitmap.
PixelData* pPixel;
When we begin hiding the message, we initialized pixelPosition
to 0/0. Now, we move the pointer to pixel 0/0:
pPixel = (PixelData*)bitmapData.Scan0.ToPointer();
Moving from one pixel to another is much easier now, because we don't need to care about columns and rows anymore, the pixels are one long array.
key = GetKey(keyStream);
pPixel += key;
We used to edit the color and then write it back into the pixel. The second step is not needed anymore, because we edit the pixel in the bitmap, not a managed copy:
SetColorComponent(pPixel, currentColorComponent, colorComponent);
The changes to SetColorComponent
are not big at all. Instead of creating a new Color
, we put the new value where it belongs right away.
private unsafe void SetColorComponent(PixelData* pPixel,
int colorComponent, byte newValue){
switch(colorComponent){
case 0:
pPixel->Red = newValue;
break;
case 1:
pPixel->Green = newValue;
break;
case 2:
pPixel->Blue = newValue;
break;
}
}
The same changes has been made to GetColorComponent
and Extract
. After opening the carrier image, we lock it and move along the pixels just like they were samples in a wave audio stream. Here is the changed part of Extract
:
Bitmap image = (Bitmap)Image.FromFile(carrierFile.SourceFileName);
BitmapData bitmapData = image.LockBits(
new Rectangle(0, 0, image.Width, image.Height),
ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
pPixel = (PixelData*)bitmapData.Scan0.ToPointer();
for(; (count>=0 || carrierFile.CountBytesToHide==0); count--){
messageByte = 0;
for(int messageBitIndex=0; messageBitIndex<8; ){
key = GetKey(keyStream);
pPixel += key;
currentColorComponent = (currentColorComponent==2)
? 0 : (currentColorComponent+1);
colorComponent = GetColorComponent(pPixel, currentColorComponent);
}
}
image.UnlockBits(bitmapData);
image.Dispose();
Bitmaps from the Clipboard
If you have an image editor like Photoshop or Gimp installed, you do not need to generate additional noise. You just open the image you want to use as a carrier, change brightness and/or contrast, add effects and ... no, do not save the file! From now on, you can copy the image to the Clipboard and paste it into the demo application.

The Paste button does not do a lot, it only copies the bitmap from the clipboard to the carrier file:
IDataObject clipboardData = Clipboard.GetDataObject();
if( clipboardData.GetDataPresent("System.Drawing.Bitmap", true) ){
carrierFile.SourceObject =
clipboardData.GetData("System.Drawing.Bitmap", true);
txtFileName.Text = Constants.PastedBitmapDummyFileName;
}else{
MessageBox.Show(this, "There is no image in the clipboard.");
}
CarrierFile
already contained the field SourceStream
for recorded wave streams. Now that in-memory-carriers do not have to be waves anymore, I have renamed it to SourceObject
:
public class CarrierFile{
public Object SourceObject;
public String SourceFileName;
public String DestinationFileName;
public Int32 CountBytesToHide;
public int CountBitsToHidePerCarrierUnit;
public decimal NoisePercent;
public long CountCarrierUnits;
public long CountUseableCarrierUnits;
}
SourceObject
can contain a wave audio stream or a bitmap. If ImageUtility
receives a carrier file with a source object, it must not dispose the bitmap! The CountUnits
method opens the source file, counts the pixels, and then disposes the bitmap. img.Dispose()
would destroy the memory bitmap forever, so this is our new CountUnits()
method:
public override long CountUnits(){
long countUnits;
if(carrierFile.SourceObject == null){
Image img = Image.FromFile(carrierFile.SourceFileName);
countUnits = img.Width * img.Height;
img.Dispose();
}else{
Image img = (Image)carrierFile.SourceObject;
countUnits = img.Width * img.Height;
}
return countUnits;
}
Thanks for the Hints
Now we have reduced the processing time for hiding/extracting data to and from pictures down to less than 10%, and we can avoid saving original carrier files by pasting bitmaps from the clipboard, so that we do not need additional noise anymore.
Finally, I have to say Thanks to...
- ...Yves, for pointing me to the MSDN article about unsafe image processing. That one managed to escape from my eyes until today ;-)
- ...schoef, for various ideas.