C # parallel.for image filter takes longer

here is the code, is there any way to make it faster because its slower single

public Bitmap pSetInvert(Bitmap _currentBitmap)
    {
        Bitmap temp = (Bitmap)_currentBitmap;
        Bitmap bmap = (Bitmap)temp.Clone();
        Color c;
        Parallel.For(0, bmap.Width, i =>
        {
            lock (bmap)
            {
                for (int j = 0; j < bmap.Height; j++)
                {
                    c = bmap.GetPixel(i, j);
                    bmap.SetPixel(i, j, Color.FromArgb(255 - c.R, 255 - c.G, 255 - c.B));
                } 
            }
        });
        return (Bitmap)bmap.Clone();
    }

      

+3


source to share


3 answers


lock

c Parallel.For

will cause the code to run slower than a single threaded loop. Locking allows only one thread to do useful work with the added value of acquiring the lock.

Also, GetPixel and SetPixel are extremely slow. They are also not guaranteed by thread safety, so you are probably getting InvalidOperationException

All public static (Shared in Visual Basic) members of this type are thread safe. Any member of the instance is not guaranteed to be thread safe.

http://msdn.microsoft.com/en-us/library/System.Drawing.Bitmap(v=vs.110).aspx



Have a look at WriteableBitmap instead . Although the class was introduced with WPF, you can use it from different environments. I recently used it in a console application. The WriteableBitmap can be converted to a standard bitmap if needed, or written to a ".bmp" file.

Alternatively, you can use unsafe code to directly access the Bitmap buffer.

If you need, you can use multiple threads for WriteableBitmap or insecure access to the bitmap boom since you are directly reading / writing memory.

+1


source


Here are two versions of your filtering program that you can play with. They take the image of one PictureBox

and, after starting the filter, assign it to the second PictureBox

.

  • The first one uses LockBits

    and can do 10.000 (!) Cycles for an image of about 400x400 in 40 seconds on my machine.
  • The second uses Parallel.For

    in addition to LockBits

    and does the same job in 35 seconds here.

Of course, these timings are heavily dependent on overhead (and possibly the dumb mistakes I made.) But actually usage LockBits

is the key to speed here, since there is so little "computation" that concurrency control is itself kernel time ...



using System.Runtime.InteropServices;
// ..


public void filter1()
{
    if (pictureBox2.Image != null) pictureBox2.Image.Dispose();
    Bitmap bmp = new Bitmap(pictureBox1.Image);

    Size s1 = bmp.Size;
    PixelFormat fmt1 = bmp.PixelFormat;

    Rectangle rect = new Rectangle(0, 0, s1.Width, s1.Height);
    BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, fmt1);
    byte bpp = 4;  // <<-------------------set to 3 for 24bbp !!

    int size1 = bmpData.Stride * bmpData.Height;
    byte[] data = new byte[size1];
    Marshal.Copy(bmpData.Scan0, data, 0, size1);

    for (int y = 0; y < s1.Height; y++)
    {
        for (int x = 0; x < s1.Width; x++)
        {
            int index = y * bmpData.Stride + x * bpp;

            data[index + 0] = (byte) (255 - data[index + 0]);  // Blue
            data[index + 1] = (byte) (255 - data[index + 1]);  // Green
            data[index + 2] = (byte) (255 - data[index + 2]);  // Red
            data[index + 3] = 255;   // Alpha, comment out for 24 bpp!
        }
    }

    Marshal.Copy(data, 0, bmpData.Scan0, data.Length);
    bmp.UnlockBits(bmpData);

    pictureBox2.Image = bmp;

}


public void filter2()
{
    if (pictureBox2.Image != null) pictureBox2.Image.Dispose();

    Bitmap bmp1 = new Bitmap(pictureBox1.Image);
    Size s1 = bmp1.Size;
    Bitmap bmp2 = new Bitmap(s1.Width, s1.Height);

    PixelFormat fmt1 = bmp1.PixelFormat;

    Rectangle rect = new Rectangle(0, 0, s1.Width, s1.Height);
    BitmapData bmpData1 = bmp1.LockBits(rect, ImageLockMode.ReadOnly, fmt1);
    BitmapData bmpData2 = bmp2.LockBits(rect, ImageLockMode.WriteOnly, fmt1);
    byte bpp = 4;  // set to 3 for 24bbp !!

    int size1 = bmpData1.Stride * bmpData1.Height;
    byte[] data1 = new byte[size1];
    byte[] data2 = new byte[size1];
    Marshal.Copy(bmpData1.Scan0, data1, 0, size1);
    Marshal.Copy(bmpData2.Scan0, data2, 0, size1);

    int degreeOfParallelism = Environment.ProcessorCount - 1;
    var options = new ParallelOptions();
    options.MaxDegreeOfParallelism = degreeOfParallelism;

     Parallel.For(0, bmp1.Width, options, y =>
     {
        {
           for (int x = 0; x < s1.Width; x++)
           {
                 int index = y * bmpData1.Stride + x * bpp;

                 data2[index + 0] = (byte)(255 - data1[index + 0]);  // Blue
                 data2[index + 1] = (byte)(255 - data1[index + 1]);  // Green
                 data2[index + 2] = (byte)(255 - data1[index + 2]);  // Red
                 data2[index + 3] = 255;   // Alpha, comment out for 24 bpp!
           }
        }
    });
    Marshal.Copy(data2, 0, bmpData2.Scan0, data2.Length);
    bmp1.UnlockBits(bmpData1);
    bmp2.UnlockBits(bmpData2);

    pictureBox2.Image = bmp2;
}

      

Note that the second filter routine must work in the second Bitmap for general purpose filter algorithms. This simple inversion filter is really unnecessary, but once you write objects that refer to neighboring pixels, like a blur filter for example.

Also pay attention to the channel byte order!

+1


source


By using lock

, you make the parallel portion of your code run sequentially, just like a single threaded application with synchronization overhead, so it will actually be slower.

here is a helper class that handles bitmap data directly and you can manipulate it at the same time.

FastImage helper class:

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;

namespace ImageManipulation
{
    class FastImage : IDisposable
    {
        private Bitmap _buffer;
        private byte[] _rawData;
        private GCHandle _rawHandle;
        private int _formatSize;
        private int _width;
        private int _height;

        public int Width
        {
            get { return _width; }
        }

        public int Height
        {
            get { return _height; }
        }

        public byte[] GetRawData()
        {
            return _rawData;
        }

        public byte this[int index]
        {
            get { return _rawData[index]; }
            set { _rawData[index] = value; }
        }

        public Color this[int x, int y]
        {
            get
            {
                return GetPixel(x, y);
            }
            set
            {
                SetPixel(x, y, value);
            }
        }

        public Color GetPixel(int x, int y)
        {
            var offset = y*_width*_formatSize;
            offset += x*_formatSize;
            return Color.FromArgb(_rawData[offset + 3], _rawData[offset + 2], _rawData[offset + 1], _rawData[offset]);
        }

        public void SetPixel(int x, int y, Color value)
        {
            var offset = y*_width*_formatSize;
            offset += x*_formatSize;

            _rawData[offset] = value.B;
            _rawData[offset + 1] = value.G;
            _rawData[offset + 2] = value.R;
            _rawData[offset + 3] = value.A;

        }

        private FastImage() { }

        public static FastImage Create(Image source)
        {
            var image = new FastImage();

            var bmpSource = new Bitmap(source);
            var bmpData = bmpSource.LockBits(new Rectangle(0, 0, source.Width, source.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, bmpSource.PixelFormat);

            image._width = source.Width;
            image._height = source.Height;

            image._formatSize = 4;
            var stride = bmpSource.Width * image._formatSize;
            image._rawData = new byte[stride * bmpSource.Height];
            image._rawHandle = GCHandle.Alloc(image._rawData, GCHandleType.Pinned);
            var pointer = Marshal.UnsafeAddrOfPinnedArrayElement(image._rawData, 0);
            image._buffer = new Bitmap(bmpSource.Width, bmpSource.Height, stride, PixelFormat.Format32bppArgb /*bmpSource.PixelFormat*/, pointer);
            bmpSource.UnlockBits(bmpData);

            var graphics = Graphics.FromImage(image._buffer);

            graphics.DrawImageUnscaledAndClipped(bmpSource, new Rectangle(0, 0, source.Width, source.Height));
            graphics.Dispose();

            return image;
        }

        public void Dispose()
        {
            _rawHandle.Free();
            _buffer.Dispose();
        }

        public void Save(Stream stream)
        {
            _buffer.Save(stream, ImageFormat.Bmp);
        }

        public Bitmap ToBitmap()
        {
            return (Bitmap)_buffer.Clone();
        }
    }
}

      

and here is your code using the class FastImage

:

 public Bitmap pSetInvert(Bitmap _currentBitmap)
        {
            using (var bmap = FastImage.Create(_currentBitmap))
            {
                Parallel.For(0, bmap.Width, i =>
                {
                    for (int j = 0; j < bmap.Height; j++)
                    {
                        var c = bmap.GetPixel(i, j);
                        bmap.SetPixel(i, j, Color.FromArgb(255 - c.R, 255 - c.G, 255 - c.B));

                    }
                });
                return bmap.ToBitmap();
            }
        }

      

+1


source







All Articles