2009-12-06 160 views
3

我在WPF(C#)中编写了一个应用程序,它在一组Bitmap上执行很长时间的操作。为了让应用程序响应,我决定使用另一个线程来执行位图上的操作,并在主UI线程的进度条上报告进度。我认为BackgroundWorker会为我做任何事情,但看起来并不那么容易。WriteableBitmap上的异步操作

我有以下代码:

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 
    } 
} 

在运行时,我用一个标准的打开文件对话框的图像加载到图片集合,然后我调用DoLotsOfOperations()方法。但只要我尝试访问单个位图的任何属性,就会得到InvalidOperationException:“调用线程无法访问对象,因为不同的线程拥有它”。

这是显而易见的 - 我加载位图并在UI线程中填充集合,并尝试读取另一个线程中的集合元素。所以我尝试不同的方法:

  • 我通过整个集合作为的RunWorkerAsync方法的参数和从e.Argument的DoWork方法得到它回来,但后来当我试图读取一个位图我还是得到了性能同样的例外。
  • 我试过同样的事情,这次传递一个位图作为backgroundworker的参数,但仍然无法获得任何位图的属性,更不用说位图的像素。

那么如何访问另一个线程(最好使用BackgroundWorker)中的位图数据呢?

我不知道,也许我的整个方法是错误的。我想实现的一般想法是:

  1. 用户加载位图,然后显示在窗口中。
  2. 用户单击按钮并执行位图上的长操作,但UI响应(允许用户例如取消操作),并在进度条上报告进度。

在此先感谢您的帮助。

回答

3

WriteableBitmap对线程有明确的支持。但是你必须遵循协议,使用线程中的Lock()方法来访问BackBuffer。

5

1)Proxy类(无螺纹限制)

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

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)过程来在不同的线程把火长时间运行的操作

 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)长时间运行实施

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)希望它证明了事情。