Allow an Image to be accessed by several threads

前端 未结 2 1919
说谎
说谎 2021-01-05 02:13

I\'m trying to do some image processing in C#. I want to use some threads to do parallel computations on several zones in my image. Threads are actually getting and setting

相关标签:
2条回答
  • 2021-01-05 02:42

    Using LockBits (which is also much faster than GetPixel & SetPixel) you can copy the image's pixels to a buffer, run parallel threads on it, and then copy the buffer back.

    Here is a working example.

    void Test()
    {
        string inputFile = @"e:\temp\a.jpg";
        string outputFile = @"e:\temp\b.jpg";
    
        Bitmap bmp = Bitmap.FromFile(inputFile) as Bitmap;
    
        var rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
        var data = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);
        var depth = Bitmap.GetPixelFormatSize(data.PixelFormat) / 8; //bytes per pixel
    
        var buffer = new byte[data.Width * data.Height * depth];
    
        //copy pixels to buffer
        Marshal.Copy(data.Scan0, buffer, 0, buffer.Length);
    
        Parallel.Invoke(
            () => {
                //upper-left
                Process(buffer, 0, 0, data.Width / 2, data.Height / 2, data.Width, depth);
            },
            () => {
                //lower-right
                Process(buffer, data.Width / 2, data.Height / 2, data.Width, data.Height, data.Width, depth);
            }
        );
    
        //Copy the buffer back to image
        Marshal.Copy(buffer, 0, data.Scan0, buffer.Length);
    
        bmp.UnlockBits(data);
    
        bmp.Save(outputFile, ImageFormat.Jpeg);
    }
    
    void Process(byte[] buffer, int x, int y, int endx, int endy, int width, int depth)
    {
        for (int i = x; i < endx; i++)
        {
            for (int j = y; j < endy; j++)
            {
                var offset = ((j * width) + i) * depth;
                // Dummy work    
                // To grayscale (0.2126 R + 0.7152 G + 0.0722 B)
                var b = 0.2126 * buffer[offset + 0] + 0.7152 * buffer[offset + 1] + 0.0722 * buffer[offset + 2];
                buffer[offset + 0] = buffer[offset + 1] = buffer[offset + 2] = (byte)b;
            }
        }
    }
    

    Input Image:

    enter image description here

    Output Image:

    enter image description here

    Some rough tests:

    Converting a (41 MegaPixel, [7152x5368]) image to a gray scale on a dual core 2.1GHz machine

    • GetPixel/SetPixel - Single Core - 131 sec.
    • LockBits - Single Core - 4.5 sec.
    • LockBits - Dual Core - 3 sec.
    0 讨论(0)
  • 2021-01-05 02:43

    Another solution would be to create a temporary container with the information about the Bitmap such as width, height, stride, buffer and pixel format. Once you need to access the Bitmap in parallel just create it based on the information in the temporary container.

    For this we need an extension method to get the buffer and stride of a Bitmap:

    public static Tuple<IntPtr, int> ToBufferAndStride(this Bitmap bitmap)
    {
        BitmapData bitmapData = null;
    
        try
        {
            bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), 
                ImageLockMode.ReadOnly, bitmap.PixelFormat);
    
            return new Tuple<IntPtr, int>(bitmapData.Scan0, bitmapData.Stride);
        }
        finally
        {
            if (bitmapData != null)
                bitmap.UnlockBits(bitmapData);
        }
    }
    

    This extension method will be used inside the temporary container:

    public class BitmapContainer
    {
        public PixelFormat Format { get; }
    
        public int Width { get; }
    
        public int Height { get; }
    
        public IntPtr Buffer { get; }
    
        public int Stride { get; set; }
    
        public BitmapContainer(Bitmap bitmap)
        {
            if (bitmap == null)
                throw new ArgumentNullException(nameof(bitmap));
    
            Format = bitmap.PixelFormat;
            Width = bitmap.Width;
            Height = bitmap.Height;
    
            var bufferAndStride = bitmap.ToBufferAndStride();
            Buffer = bufferAndStride.Item1;
            Stride = bufferAndStride.Item2;
        }
    
        public Bitmap ToBitmap()
        {
            return new Bitmap(Width, Height, Stride, Format, Buffer);
        }
    }
    

    Now you can use the BitmapContainer in a method executed in parallel:

    BitmapContainer container = new BitmapContainer(bitmap);
    
    Parallel.For(0, 10, i =>
    {
        Bitmap parallelBitmap = container.ToBitmap();
        // ...
    });
    
    0 讨论(0)
提交回复
热议问题