2009-12-06 60 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)希望它證明了事情。