2

The user provides my app an image, from which the app needs to make a mask:

The mask contains a red pixel for each transparent pixel in the original image.

I tried the following:

Bitmap OrgImg = Image.FromFile(FilePath);
Bitmap NewImg = new Bitmap(OrgImg.Width, OrgImg.Height);
for (int y = 0; y <= OrgImg.Height - 1; y++) {
    for (int x = 0; x <= OrgImg.Width - 1; x++) {
        if (OrgImg.GetPixel(x, y).A != 255) {
            NewImg.SetPixel(x, y, Color.FromArgb(255 - OrgImg.GetPixel(x, y).A, 255, 0, 0));
        }
    }
}
OrgImg.Dispose();
PictureBox1.Image = NewImg;

I am worried about the performance on slow PCs. Is there a better approach to do this?

Elmo
  • 6,409
  • 16
  • 72
  • 140
  • I would use exactly the same approach. It is slow but bear in mind that you are dealing with pixels and its number can be pretty big. Analysing just one image should be a pretty quick process in any computer. – varocarbas Jul 21 '13 at 14:47
  • For very large images you could consider using `Task` or `worker thread` rather than main thread which freezes UI till it finish processing. – Sriram Sakthivel Jul 21 '13 at 15:32
  • I'm already using a `BackgroundWorker` for this. – Elmo Jul 21 '13 at 15:39

2 Answers2

4

It is perfectly acceptable to use GetPixel() if it is only used sporadicly, e.g. on loading one image. However, if you want to do a more serious image processing, it is better to work directly with BitmapData. A small example:

//Load the bitmap
Bitmap image = (Bitmap)Image.FromFile("image.png"); 

//Get the bitmap data
var bitmapData = image.LockBits (
    new Rectangle (0, 0, image.Width, image.Height),
    ImageLockMode.ReadWrite, 
    image.PixelFormat
);

//Initialize an array for all the image data
byte[] imageBytes = new byte[bitmapData.Stride * image.Height];

//Copy the bitmap data to the local array
Marshal.Copy(bitmapData.Scan0,imageBytes,0,imageBytes.Length);

//Unlock the bitmap
image.UnlockBits(bitmapData);

//Find pixelsize
int pixelSize = Image.GetPixelFormatSize(image.PixelFormat);

// An example on how to use the pixels, lets make a copy
int x = 0;
int y = 0;
var bitmap = new Bitmap (image.Width, image.Height);

//Loop pixels
for(int i=0;i<imageBytes.Length;i+=pixelSize/8)
{
    //Copy the bits into a local array
    var pixelData = new byte[3];
    Array.Copy(imageBytes,i,pixelData,0,3);

    //Get the color of a pixel
    var color = Color.FromArgb (pixelData [0], pixelData [1], pixelData [2]);

    //Set the color of a pixel
    bitmap.SetPixel (x,y,color);

    //Map the 1D array to (x,y)
    x++;
    if( x >= bitmap.Width)
    {
        x=0;
        y++;
    }

}

//Save the duplicate
bitmap.Save ("image_copy.png");
dsfgsho
  • 2,731
  • 2
  • 22
  • 39
  • I know this is old but someone might find this useful later. – Chuck Fecteau Jun 25 '17 at 20:57
  • I know this is old but someone might find this useful later. If you are going to talk Bitmap speed in C#, then you need to understand that in a for loop, you should NOT use bitmap.Width as C# is horrifically slow in accessing. Instead, set a local variable to Bitmap.Width and use that in your loop comparisons. MUCH faster. Also Vadim is correct in that locking the memory is much faster. – Chuck Fecteau Jun 25 '17 at 21:04
1

This approach is indeed slow. A better approach would be using Lockbits and access the underlying matrix directly. Take a look at https://web.archive.org/web/20141229164101/http://bobpowell.net/lockingbits.aspx or http://www.mfranc.com/programming/operacje-na-bitmapkach-net-1/ or https://learn.microsoft.com/en-us/dotnet/api/system.drawing.bitmap.lockbits or other articles about lockbits in StackOverflow.

It's a tiny bit more complex since you'll have to work with bytes directly (4 per pixel if you're working with RGBA), but the performance boost is significant and is well worth it.

Another note - OrgImg.GetPixel(x, y) is slow, if you're sticking with this (and not lockbits) make sure you only use it once (it may be already optimized, just check if there's a difference).

Andrew Morton
  • 24,203
  • 9
  • 60
  • 84
Vadim
  • 2,847
  • 15
  • 18