Click here to Skip to main content
15,885,366 members
Articles / Programming Languages / C#
Article

Steganography X - Tuning the Image Processing

Rate me:
Please Sign up or sign in to vote.
4.47/5 (9 votes)
29 Nov 2004CPOL3 min read 87.9K   2.4K   33   14
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:

C#
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.

C#
//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:

C#
//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.

C#
//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:

C#
//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.

C#
/// <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:

C#
//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.

Image 1

The Paste button does not do a lot, it only copies the bitmap from the clipboard to the carrier file:

C#
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:

C#
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:

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Germany Germany
Corinna lives in Hanover/Germany and works as a C# developer.

Comments and Discussions

 
Questionimage processing Pin
HebaMK21-Mar-08 9:11
HebaMK21-Mar-08 9:11 
GeneralSpeed improvement Pin
Be very quiet27-Dec-06 6:25
Be very quiet27-Dec-06 6:25 
GeneralRe: Speed improvement Pin
Corinna John27-Dec-06 6:38
Corinna John27-Dec-06 6:38 
GeneralVS2005 Compiler Warning Pin
Moomansun8-Sep-06 8:01
Moomansun8-Sep-06 8:01 
GeneralApplication won't quit Pin
firmwaredsp4-May-06 21:39
firmwaredsp4-May-06 21:39 
GeneralRe: Application won't quit Pin
Corinna John5-May-06 0:34
Corinna John5-May-06 0:34 
GeneralRe: Application won't quit Pin
firmwaredsp5-May-06 6:15
firmwaredsp5-May-06 6:15 
GeneralRe: Application won't quit Pin
Corinna John5-May-06 6:30
Corinna John5-May-06 6:30 
GeneralRe: Application won't quit Pin
firmwaredsp5-May-06 7:56
firmwaredsp5-May-06 7:56 
QuestionVC6++ HELP HOW??? Pin
cnncnn18-Aug-04 20:21
cnncnn18-Aug-04 20:21 
AnswerRe: VC6++ HELP HOW??? Pin
Marek.T10-Dec-04 13:06
Marek.T10-Dec-04 13:06 
Generalabt u Pin
Srinivas Varukala11-Jul-04 13:37
Srinivas Varukala11-Jul-04 13:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.