3
Graphics g;
using (var bmp = new Bitmap(_frame, _height, PixelFormat.Format24bppRgb))
{
    var data = bmp.LockBits(new Rectangle(0, 0, _frame, _height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
    var bmpWidth = data.Stride;
    var bytes = bmpWidth * _height;
    var rgb = new byte[bytes];
    var ptr = data.Scan0;
    Marshal.Copy(ptr, rgb, 0, bytes);

    for (var i = 0; i < _frame; i++)
    {
        var i3 = (i << 1) + i;
        for (var j = 0; j < _height; j++)
        {
            var ij = j * bmpWidth + i3;
            var val = (byte)(_values[i, j]);
            rgb[ij] = val;
            rgb[ij + 1] = val;
            rgb[ij + 2] = val;
        }
    }

    Marshal.Copy(rgb, 0, ptr, bytes);
    bmp.UnlockBits(data);

    g = _box.CreateGraphics();
    g.InterpolationMode = InterpolationMode.NearestNeighbor;
    g.DrawImage(bmp, 0, 0, _box.Width, _box.Height);
}
g.Dispose();

I use this code to convert an array of RGB values ​​(grayscale) in the PictureBox, but it's slow. Please tell me my mistakes. At the moment, an array of 441 000 items handled for 35 ms. I need to handle an array of 4 million for the same time.

Kir
  • 155
  • 2
  • 10
  • Is most of the time spent in the conversion, or in the rest of the code (allocation, locking, unlocking)? – user541686 Sep 23 '11 at 23:40
  • Most of the time in two nested loops (~ 32 ms). Perhaps this is due to cast int to byte. But I don `t know how to solve this optimal. – Kir Sep 23 '11 at 23:54
  • Are you doing these timings in the debugger? If so, then they're not at all reliable. Run the timings in release mode without attaching the debugger (Ctrl+F5 to run). – Jim Mischel Sep 23 '11 at 23:58
  • Running some performance test shows what? –  Sep 24 '11 at 00:09

5 Answers5

5

You can skip the first Array.Copy where you copy the data from the image to the array, as you will be overwriting all the data in the array anyway.

That will shave off something like 25% of time, but if you want it faster you will have to use an unsafe code block so that you can use pointers. That way you can get around the range checking when you access arrays, and you can write the data directly into the image data instead of copying it.

Guffa
  • 687,336
  • 108
  • 737
  • 1,005
3

I totally agree with Guffa's answer. Using an unsafe code block will speed up things. To further improve performance, you could execute your for loop in parallel by using the Parallel class in the .Net framework. For large bitmaps this improves performance. Here is a small code sample:

using (Bitmap bmp = (Bitmap)Image.FromFile(@"mybitmap.bmp"))
{
  int width = bmp.Width;
  int height = bmp.Height;

  BitmapData bd = bmp.LockBits(new Rectangle(0, 0, width, height),
    System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format24bppRgb);

  byte* s0 = (byte*)bd.Scan0.ToPointer();
  int stride = bd.Stride;

  Parallel.For(0, height, (y1) =>
  {
    int posY = y1*stride;
    byte* cpp = s0 + posY;

    for (int x = 0; x < width; x++)
    {              
      // Set your pixel values here.
      cpp[0] = 255;
      cpp[1] = 255;
      cpp[2] = 255;
      cpp += 3;
    }
  });

  bmp.UnlockBits(bd);
}

To keep the example simple I've set the pixel values to a constant value. Note, to compile the example above you have to allow unsafe code.

Hope, this helps.

Hans
  • 12,902
  • 2
  • 57
  • 60
1

In addition to Guffa's excellent advice, I would suggest that you profile your code to see where it's taking the time. Be sure that when you're timing this, you are running in release mode without the debugger attached.

I wouldn't be surprised if the call to DrawImage is taking up most of the time. You're scaling the image there, which can be pretty expensive. How large is the box that you're drawing the image to?

Finally, although this won't affect performance, you should change your code to read:

using (Graphics g = _box.CreateGraphics())
{
    g.InterpolationMode = InterpolationMode.NearestNeighbor;
    g.DrawImage(bmp, 0, 0, _box.Width, _box.Height);
}

And get rid of the first and last lines in your example.

Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
1

Try this using unsafe code:

byte* rp0;
int* vp0;
fixed (byte* rp1 = rgb)
{
    rp0 = rp1;
    fixed (int* vp1 = _values)
    {
        vp0 = vp1;
        Parallel.For(0, _width, (i) =>
        {
            var val = (byte)vp0[i];
            rp0[i] = val;
            rp0[i + 1] = val;
            rp0[i + 2] = val;
        });
    }
}

Runs very fast for me

Seph
  • 8,472
  • 10
  • 63
  • 94
0

My understanding is that multidimentional (square) arrays are pretty slow in .Net. You might try changing your _values array to be a single dimension array instead. Here is one reference, there are many more if you search: http://odetocode.com/articles/253.aspx

Array perf example.

using System;
using System.Diagnostics;

class Program
{
static void Main(string[] args)
{
    int w = 1000;
    int h = 1000;

    int c = 1000;

    TestL(w, h);
    TestM(w, h);


    var swl = Stopwatch.StartNew();
    for (int i = 0; i < c; i++)
    {
        TestL(w, h);
    }
    swl.Stop();

    var swm = Stopwatch.StartNew();
    for (int i = 0; i < c; i++)
    {
        TestM(w, h);
    }
    swm.Stop();

    Console.WriteLine(swl.Elapsed);
    Console.WriteLine(swm.Elapsed);
    Console.ReadLine();
}


static void TestL(int w, int h)
{
    byte[] b = new byte[w * h];
    int q = 0;
    for (int x = 0; x < w; x++)
        for (int y = 0; y < h; y++)
            b[q++] = 1;
}

static void TestM(int w, int h)
{
    byte[,] b = new byte[w, h];

    for (int y = 0; y < h; y++)
        for (int x = 0; x < w; x++)
            b[y, x] = 1;
}
}
MarkPflug
  • 28,292
  • 8
  • 46
  • 54
  • By "pretty slow" I just mean slower than normal array access. – MarkPflug Sep 23 '11 at 23:56
  • 1
    That understanding is based on .NET 1.x. Multi-dimensional arrays in .NET 2.0 and later are quite fast. – Jim Mischel Sep 23 '11 at 23:56
  • @Jim: If that is the case, please modify the code example that I just added and make it so that the timings are roughly equal. Because on my machine the single dimensional array is faster, significantly. Just set all the values to 1, as an example. Thanks! I will point out that this is only true when commpiling x86. With x64 the timings are equal. – MarkPflug Sep 24 '11 at 00:10
  • I stand corrected. I mis-read your statement and was thinking of the difference between multi-dimensional array `[,]` access and jagged array `[][]` access. You're correct that a one-dimensional array will be faster to access. – Jim Mischel Sep 24 '11 at 00:46
  • What's stranger still is that in 64 bit mode if you make the arrays `static`, the `TestM` method is *slower* than the version that allocates a new array every time. The `TestL` method is about 50% faster when you do that. Strange indeed. – Jim Mischel Sep 24 '11 at 00:48