2009-05-01 11 views
12

Mam aplikację WPF na maszynie USG, która wyświetla obrazy ultradźwiękowe generowane w C++ z prędkością do 30 klatek na sekundę.Jak wyświetlić szybko aktualizujące się obrazy bez dużej alokacji pamięci?

Z tego co rozumiem, normalnym procesem wyświetlania obrazów w WPF jest utworzenie BitmapSource dla obrazu i ustawienie źródła dla obrazu, co powoduje jego unieważnienie i wyświetlenie.

Ponieważ BitmapSources nie implementują IDisposable, użycie tej metody zmusiło mnie do utworzenia 30 BitmapSources na sekundę. Dla obrazu 640x480 w formacie 32bppArgb, około 30 MB/s pamięci jest przydzielane na sekundę, a następnie śmieci są ułożone co 10 sekund, powodując widoczne opóźnienie. Oczywiście nie do przyjęcia rozwiązanie.

My obecnie rozwiązaniem jest:

W C++: tworzę System.Drawing.Bitmap (WinForms bitmapę) w Managed C++, zrobić memcpy ze wskaźnikiem do wypełnienia obrazu, użyj Graphics sprzeciw wobec zrobić dodatkowy rysunek, którego potrzebuję, i przekazać go do C#/WPF podczas zdarzenia ImageReceived.

W języku C# Image.Source jest ustawiony do źródła generowanej przez BitmapBuffer, który jest hack sposobem uzyskiwania dostępu do surowych danych o źródle bitmapy: Zobacz this link. Mam P/Invoke z CopyMemory skopiować dane z Bitmap.Scan0 do BitmapBuffer. Następnie unieważniam obraz, aby zaktualizować ekran, i usuwam() obiekt Drawing.Bitmap, aby zwolnić pamięć.

Podczas gdy metoda ta działała przez jakiś czas, wydaje się bardzo hacky i trudno mi uwierzyć, że nie ma innego "odpowiedniego" sposobu, aby to zrobić, niż poprzez refleksję.

Pytanie: Czy istnieje lepszy sposób?

Odpowiedz

9

Oto niektóre kodu napisałem * dla aliasing (Udostępnianie pamięci) pomiędzy WPF BitmapSource i GDI Bitmap (dla własnego projektu)

Oczywiście, trzeba dostosować go do własnych potrzeb, to Ostatecznie skończy się to na mniej "hackowatym" wyczuciu.

class AliasedBitmapSource : BitmapSource { 
    private Bitmap source; 
    public AliasedBitmapSource(Bitmap source) { 
     this.source = source; 
     this.pixelHeight = source.Height; 
     this.pixelWidth = source.Width; 
     this.dpiX = source.HorizontalResolution; 
     this.dpiY = source.VerticalResolution; 
    } 

    public override event EventHandler DownloadCompleted; 
    public override event EventHandler<ExceptionEventArgs> DownloadFailed; 
    public override event EventHandler<ExceptionEventArgs> DecodeFailed; 

    protected override Freezable CreateInstanceCore() { 
     throw new NotImplementedException(); 
    } 

    private readonly double dpiX; 
    public override double DpiX { 
     get { 
      return dpiX; 
     } 
    } 

    private readonly double dpiY; 
    public override double DpiY { 
     get { 
      return dpiY; 
     } 
    } 

    private readonly int pixelHeight; 
    public override int PixelHeight { 
     get { 
      return pixelHeight; 
     } 
    } 

    private readonly int pixelWidth; 
    public override int PixelWidth { 
     get { 
      return pixelWidth; 
     } 
    } 

    public override System.Windows.Media.PixelFormat Format { 
     get { 
      return PixelFormats.Bgra32; 
     } 
    } 

    public override BitmapPalette Palette { 
     get { 
      return null; 
     } 
    } 

    public unsafe override void CopyPixels(Int32Rect sourceRect, Array pixels, int stride, int offset) { 
     BitmapData sourceData = source.LockBits(
     sourceRect.ToRectangle(), 
     ImageLockMode.ReadWrite, 
     System.Drawing.Imaging.PixelFormat.Format32bppArgb); 

     fixed (byte* _ptr = &((byte[])pixels)[0]) { 
      byte* dstptr = _ptr; 
      byte* srcptr = (byte*)sourceData.Scan0; 

      for (int i = 0; i < pixels.Length; ++i) { 
       *dstptr = *srcptr; 
       ++dstptr; 
       ++srcptr; 
      } 
     } 

     source.UnlockBits(sourceData); 
    } 
} 

public static class Extensions { 
    public static Rectangle ToRectangle(this Int32Rect me) { 
     return new Rectangle(
     me.X, 
     me.Y, 
     me.Width, 
     me.Height); 
    } 

    public static Int32Rect ToInt32Rect(this Rectangle me) { 
     return new Int32Rect(
     me.X, 
     me.Y, 
     me.Width, 
     me.Height); 
    } 
} 

* przez „napisał” mam na myśli „rzucił razem w 10 minut”

+0

Korzystanie z Marshal.Copy zamiast niebezpiecznego kodu jest jeszcze szybsze i nie wymaga niebezpiecznych bloków. – Andreas

12

Jeśli używasz najnowszej bity WPF sprawdzeniu WriteableBitmap, trzeba zrobić więcej pracy nóg, ale Naprawdę szybkie aktualizacje.

Wykonaj szybkie google, a dostaniesz kilka próbek.

+0

Koniec końców zużywasz więcej pamięci i cykli procesora. Ponieważ pożądanym źródłem jest mapa bitowa GDI, będzie ona musiała zostać skopiowana do WriteableBitmap za każdym razem, gdy zostanie zaktualizowana. Podejście AliasedBitmap powyżej automagicznie odzwierciedli zmiany wprowadzone w bitmapie w drugiej. – bsneeze

+0

Jedyna dodatkowa pamięć, którą zużyjesz na bufor zwrotny; ale zaletą jest to, że unikniesz łzawienia. Jeśli chodzi o cykle procesora, kopia pamięci wydaje mi się mniej więcej taka sama, musisz pobrać dane bitmapowe z twojej aplikacji na ekran - a logika CopyPixels jest prawie taka sama. – Scott

+2

Wracając do tego, wierzę, że wiele lat później, w końcu posunąłem się w stronę tego rozwiązania. Trzymałem pojedynczą WriteableBitmap, wykonując Buffer.BlockCopy na BackBuffer z bufora obrazu maszyny ultradźwiękowej.W rezultacie nie było zużycia pamięci w czasie (nic nie przydzielono), a nawet z wolniejszym dwurdzeniowym procesorem, niezawodna wydajność około 60FPS. –

Powiązane problemy