Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Steganography X - Tuning the Image Processing

0.00/5 (No votes)
29 Nov 2004 1  
Unsafe pixel modification and bitmaps from the clipboard.

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.

//Color pixelColor; //old version - color of current pixel

PixelData* pPixel; //unsafe version - pointer to current pixel

When we begin hiding the message, we initialized pixelPosition to 0/0. Now, we move the pointer to pixel 0/0:

//begin with the first pixel

//pixelPosition = new Point(0,0); //old version - go to the first pixel

pPixel = (PixelData*)bitmapData.Scan0.ToPointer(); //go to the first pixel

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.

//get the next key

key = GetKey(keyStream);

//old version - move to the next pixel and get its colour

//MovePixelPosition(key, image.Width, ref pixelPosition);

//pixelColor = image.GetPixel(pixelPosition.X, pixelPosition.Y);


//new version - move [key] pixel forward

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:

//old version - change colour, set pixel

//SetColorComponent(pixelColor, currentColorComponent, colorComponent);

//image.SetPixel(pixelPosition.X, pixelPosition.Y, pixelColor);


//new version - change pixel

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.

/// <summary>Changes one component of a color</summary>

/// <param name="pPixel">Pointer to the pixel</param>

/// <param name="colorComponent">The component 

///         to change (0-R, 1-G, 2-B)</param>

/// <param name="newValue">New value of the component</param>

private unsafe void SetColorComponent(PixelData* pPixel,
        int colorComponent, byte newValue){
        
    switch(colorComponent){
    case 0:
        //pixelColor = Color.FromArgb(newValue, pixelColor.G, pixelColor.B);

        pPixel->Red = newValue;
        break;
    case 1:
        //pixelColor = Color.FromArgb(pixelColor.R, newValue, pixelColor.B);

        pPixel->Green = newValue;
        break;
    case 2:
        //pixelColor = Color.FromArgb(pixelColor.R, pixelColor.G, newValue);

        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:

//open the carrier image

Bitmap image = (Bitmap)Image.FromFile(carrierFile.SourceFileName);

//...


//Lock the image

BitmapData bitmapData = image.LockBits(
    new Rectangle(0, 0, image.Width, image.Height),
    ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

//move to the first pixel

pPixel = (PixelData*)bitmapData.Scan0.ToPointer();

for(; (count>=0 || carrierFile.CountBytesToHide==0); count--){
    messageByte = 0;

    for(int messageBitIndex=0; messageBitIndex<8; ){
      //move to the next pixel

      key = GetKey(keyStream);
      pPixel += key;
    
      //rotate color components

      currentColorComponent = (currentColorComponent==2)
                              ? 0 : (currentColorComponent+1);
      //get value of Red, Green or Blue

      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) ){
    //copy the bitmap and display the dummy text

    carrierFile.SourceObject = 
        clipboardData.GetData("System.Drawing.Bitmap", true);
    txtFileName.Text = Constants.PastedBitmapDummyFileName;
}else{
    //forgot to copy the image?

    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; //only for carriers in memory

    public String SourceFileName; //ignored if SourceObject != null

    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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here