简体   繁体   English

WriteableBitmap上的异步操作

[英]Asynchronous operations on WriteableBitmap

I'm writing an application in WPF (C#) which does long operations on a collection of Bitmaps. 我正在用WPF(C#)编写一个应用程序,该应用程序对一组位图进行长时间的操作。 To keep my application responsive, I decided to use another thread to perform the operations on bitmaps and report progress on a progressbar in main UI thread. 为了保持应用程序的响应速度,我决定使用另一个线程对位图执行操作,并在主UI线程的进度条上报告进度。 I thought BackgroundWorker would do anything for me, but looks like it won't be that easy. 我以为BackgroundWorker可以为我做任何事情,但看起来并不会那么容易。

I have the following code: 我有以下代码:

public class ImageProcessor
{
    public Collection<WriteableBitmap> Pictures { get; private set; }
    private BackgroundWorker _worker = new BackgroundWorker();

    public ImageProcessor()
    {
        _worker.DoWork += DoWork;
    }

    public void DoLotsOfOperations()
    {
        _worker.RunWorkerAsync();
    }

    private void DoWork(object sender, DoWorkEventArgs e)
    {
        // operations on Pictures collection
    }
}

At runtime I load images using a standard open file dialog into the Pictures collection and then I invoke DoLotsOfOperations() method. 在运行时,我使用标准的打开文件对话框将图像加载到Pictures集合中,然后调用DoLotsOfOperations()方法。 But as soon as I try to access any of the properties of a single bitmap I get InvalidOperationException: "The calling thread cannot access the object because different thread owns it". 但是,一旦我尝试访问单个位图的任何属性,我就会收到InvalidOperationException:“调用线程无法访问该对象,因为其他线程拥有它”。

It is obviosly true - I loaded bitmaps and populated the collection in the UI thread and I try to read collection elements in another thread. 确实如此-我加载了位图并在UI线程中填充了集合,然后尝试读取另一个线程中的集合元素。 So i tried different approaches: 所以我尝试了不同的方法:

  • I passed the whole collection as a parameter of RunWorkerAsync method and got it back in DoWork method from e.Argument but then when I tried to read properties of a single bitmap I still got the same exception. 我将整个集合作为RunWorkerAsync方法的参数传递,并从e.Argument的DoWork方法中获取,但是当我尝试读取单个位图的属性时,我仍然遇到相同的异常。
  • I tried the same thing, this time passing a single bitmap as backgroundworker's argument and still I couldn't get any of the bitmap's properties, let alone bitmap's pixels. 我尝试了同样的事情,这次传递了一个位图作为backgroundworker的参数,但我仍然无法获得位图的任何属性,更不用说位图的像素了。

So how can I access bitmap's data inside another thread (and preferably using BackgroundWorker)? 那么,如何在另一个线程中(最好使用BackgroundWorker)访问位图的数据呢?

I don't know, maybe my whole approach is wrong. 我不知道,也许我的整个方法是错误的。 The general idea I want to achieve is: 我要实现的总体思路是:

  1. User loads bitmaps which are then displayed in a window. 用户加载位图,然后将其显示在窗口中。
  2. User clicks a button and a long operation on the bitmaps is performed but the UI is responsive (which allows user for example to cancel the opration) and the progress is reported on a progess bar. 用户单击按钮,并执行了对位图的长时间操作,但UI响应(例如,允许用户取消操作),并且进度条上报告了进度。

Thanks in advance for any help. 在此先感谢您的帮助。

1) Proxy class (without thread restriction) 1)代理类(无线程限制)

    public class WriteableBitmapProxy
    {
        public IntPtr BackBuffer { get; set; }
        public int BackBufferStride { get; set; }
        public int PixelHeight { get; set; }
        public int PixelWidth { get; set; }
    }

2) Extension methods (unsafe) 2)扩展方法(不安全)

    public class RGBColor
    {
        public byte R { get; set; }
        public byte G { get; set; }
        public byte B { get; set; }
        public uint Value
        {
            get
            {
                return (uint)(((uint)R << 16) + ((uint)G << 8) + (B) + ((uint)255 << 24));
            }
        }
    }

   public static RGBColor GetPixel(this WriteableBitmap bmp, uint x, uint y)
    {
        unsafe
        {
            if (y >= bmp.PixelHeight) return default(RGBColor);
            if (x >= bmp.PixelWidth) return default(RGBColor);


            // Get a pointer to the back buffer.
            uint pBackBuffer = (uint)bmp.BackBuffer;

            // Find the address of the pixel to draw.
            pBackBuffer += y * (uint)bmp.BackBufferStride;
            pBackBuffer += x * 4;

            byte* pCol = (byte*)pBackBuffer;
            return new RGBColor() { B = pCol[0], G = pCol[1], R = pCol[2] };
        }
    }

    public static void SetPixel(this WriteableBitmapProxy bmp, uint x, uint y, RGBColor col)
    {
        SetPixel(bmp, x, y, col.Value);
    }

    public static void SetPixel(this WriteableBitmapProxy bmp, uint x, uint y, uint value)
    {
        unsafe
        {
            if (y >= bmp.PixelHeight) return;
            if (x >= bmp.PixelWidth) return;

            // Get a pointer to the back buffer.
            uint pBackBuffer = (uint)bmp.BackBuffer;

            // Find the address of the pixel to draw.
            pBackBuffer += y * (uint)bmp.BackBufferStride;
            pBackBuffer += x * 4;

            // Assign the color data to the pixel.
            *((uint*)pBackBuffer) = value;
        }
    }

3) Procedure to fire long running operation in different thread 3)在不同线程中启动长时间运行的过程

      var image = sender as Image;
        var bitmap = image.Source as WriteableBitmap;

        var prx = new WpfImage.MyToolkit.WriteableBitmapProxy()
        {
            BackBuffer = bitmap.BackBuffer,
            BackBufferStride = bitmap.BackBufferStride,
            PixelHeight = bitmap.PixelHeight,
            PixelWidth = bitmap.PixelWidth
        };

        bitmap.Lock();

        Thread loader = new Thread(new ThreadStart(() => 
        {


            Global_Histogramm(prx);

            Dispatcher.BeginInvoke(DispatcherPriority.Background,
                  (SendOrPostCallback)delegate { bitmap.AddDirtyRect(new Int32Rect(0, 0, prx.PixelWidth - 1, prx.PixelHeight - 1)); bitmap.Unlock(); }, null);

        }

        ));
        loader.Priority = ThreadPriority.Lowest;
        loader.Start();

4) Long Running operation implementation 4)长期运行实施

    void Global_Histogramm(WpfImage.MyToolkit.WriteableBitmapProxy src)
    {
        int SrcX = src.PixelWidth;
        int SrcY = src.PixelHeight;

        double[] HR = new double[256];
        double[] HG = new double[256];
        double[] HB = new double[256];
        double[] DR = new double[256];
        double[] DG = new double[256];
        double[] DB = new double[256];
        uint i, x, y;

        //  wyzeruj tablice
        for (i = 0; i < 256; i++) HB[i] = HG[i] = HR[i] = 0;

        //  wypelnij histogramy R G B
        for (y = 0; y < SrcY; y++)
            for (x = 0; x < SrcX; x++)
            {
                var color = src.GetPixel(x, y);
                HB[color.B]++;
                HG[color.G]++;
                HR[color.R]++;
            };

        // oblicz histogramy znormalizowane i przygotuj dystrybuanty
        int ilosc_punktow = SrcX * SrcY;
        double sumaR = 0, sumaG = 0, sumaB = 0;

        for (i = 0; i < 256; i++)
        {
            DB[i] = sumaB + HB[i] / ilosc_punktow;
            DG[i] = sumaG + HG[i] / ilosc_punktow;
            DR[i] = sumaR + HR[i] / ilosc_punktow;
            sumaB = DB[i];
            sumaG = DG[i];
            sumaR = DR[i];
        };

        Dispatcher.BeginInvoke(DispatcherPriority.Background,
              (SendOrPostCallback)delegate { progressBar1.Maximum = SrcY - 1; }, null);



        // aktualizuj bitmape
        for (y = 0; y < SrcY; y++)
        {
            for (x = 0; x < SrcX; x++)
            {

                var stmp = src.GetPixel(x, y);
                var val = new WpfImage.MyToolkit.RGBColor()
                {
                    B = (byte)(DB[stmp.B] * 255),
                    G = (byte)(DG[stmp.G] * 255),
                    R = (byte)(DR[stmp.R] * 255)
                };
                src.SetPixel(x, y, val);                    
            };

            Dispatcher.BeginInvoke(DispatcherPriority.Background,
                  (SendOrPostCallback)delegate { progressBar1.Value = y; }, null);


        }
    }

5) Hope it proves the thing. 5)希望它能证明事实。

WriteableBitmap has explicit support for threading. WriteableBitmap对线程有明确的支持。 But you have to follow the protocol, use the Lock() method in the thread to gain access to the BackBuffer. 但是您必须遵循该协议,在线程中使用Lock()方法来访问BackBuffer。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM