0

I have a method that takes a System.Drawing.Bitmap, splits up the channels into RGBA and converts to double.

private BitmapToImage32(Bitmap bmap)
{
    var r = new double[bmap.Width, bmap.Height];
    var g = new double[bmap.Width, bmap.Height];
    var b = new double[bmap.Width, bmap.Height];
    var a = new double[bmap.Width, bmap.Height];

    var watch = System.Diagnostics.Stopwatch.StartNew();

    for (int x = 0; x < bmap.Width; x++)
    {
        for (int y = 0; y < bmap.Height; y++)
        {
            r[x, y] = (double)bmap.GetPixel(x, y).R / 255;
            g[x, y] = (double)bmap.GetPixel(x, y).G / 255;
            b[x, y] = (double)bmap.GetPixel(x, y).B / 255;
            a[x, y] = (double)bmap.GetPixel(x, y).A / 255;
        }
    }

    watch.Stop();

    Console.WriteLine("Milliseconds: {0}: ", watch.ElapsedMilliseconds);
}

For a JPEG with dimensions 1500x1000 this takes about 4.5 seconds.

For a PNG of the same size it takes about 3.5 seconds.

Question: How can I speed this up? Is there a way to vectorize the whole operation?

Also: Why is a PNG faster than a JPEG? The image has been converted to Bitmap, so shouldn't the speed be the same?

EDIT: I found a solution. Don't have enough reputation to post as an answer so I hope here is OK.

I found the examples in C# - Faster Alternatives to SetPixel and GetPixel for Bitmaps for Windows Forms App rather convoluted. What finally worked for me is the information here: http://csharpexamples.com/fast-image-processing-c/

Here is what I ended up with:

var r = new double[bmap.Width, bmap.Height];
var g = new double[bmap.Width, bmap.Height];
var b = new double[bmap.Width, bmap.Height];
var a = new double[bmap.Width, bmap.Height];

var watch = System.Diagnostics.Stopwatch.StartNew();

unsafe
{
    BitmapData bitmapData = bmap.LockBits(
    new Rectangle(0, 0, bmap.Width, bmap.Height), 
        ImageLockMode.ReadWrite, bmap.PixelFormat);

    int bytesPerPixel = Bitmap.GetPixelFormatSize(bmap.PixelFormat) / 8;
    int heightInPixels = bitmapData.Height;
    int widthInBytes = bitmapData.Width * bytesPerPixel;
    byte* PtrFirstPixel = (byte*)bitmapData.Scan0;

    if (bytesPerPixel == 3)
    {
        for (int y = 0; y < heightInPixels; y++)
        {
            byte* currentLine = PtrFirstPixel + (y * bitmapData.Stride);
            for (int x = 1; x <= widthInBytes; x = x + bytesPerPixel)
            {
                r[(x - 1) / bytesPerPixel, y] = (double)currentLine[x + 1] / 255;
                g[(x - 1) / bytesPerPixel, y] = (double)currentLine[x] / 255;
                b[(x - 1) / bytesPerPixel, y] = (double)currentLine[x - 1] / 255;
                a[(x - 1) / bytesPerPixel, y] = 0.0d;
            }
        }
    }
    else
    {
        for (int y = 0; y < heightInPixels; y++)
        {
            byte* currentLine = PtrFirstPixel + (y * bitmapData.Stride);
            for (int x = 1; x <= widthInBytes; x = x + bytesPerPixel)
            {
                r[(x - 1) / bytesPerPixel, y] = (double)currentLine[x + 1] / 255;
                g[(x - 1) / bytesPerPixel, y] = (double)currentLine[x] / 255;
                b[(x - 1) / bytesPerPixel, y] = (double)currentLine[x - 1] / 255;
                a[(x - 1) / bytesPerPixel, y] = (double)currentLine[x + 2] / 255;
            }
        }
    }
    bmap.UnlockBits(bitmapData);
}

watch.Stop();

Console.WriteLine("Milliseconds: {0}: ", watch.ElapsedMilliseconds);

For a JPEG at 1500x1000 this takes 57 milliseconds.

And for a PNG of the same size about 70 milliseconds.

The difference in speed here is due to the PNG having an alpha channel.

Clemens
  • 123,504
  • 12
  • 155
  • 268
tde
  • 150
  • 13

0 Answers0