2009-01-14 22 views
26

Jak zarządzać renderingiem piksel po pikselu w WPF (jak np. Raytracer)? Moim początkowym założeniem było stworzenie obrazu bitmapowego, zmodyfikowanie bufora i wyświetlenie go w formancie obrazu, ale nie mogłem wymyślić, jak go utworzyć (metoda tworzenia wymaga bloku niezarządzanej pamięci)Piksele rysunkowe w WPF

Odpowiedz

37

Gorąco polecam przeciwko dwa poprzednie sugestie. The WriteableBitmap class zapewnia to, czego potrzebujesz, zakładając, że używasz 3.5 SP1. Przed SP1 nie ma naprawdę dobrej odpowiedzi na to pytanie w WPF, chyba że uciekniesz się do jakiegoś poważnego oszustwa.

+7

Klasa WriteableBitmap jest dostępna od .NET 3.0 i jest to pierwsza wersja WPF. Nie potrzebujesz .NET 3.5 do korzystania z tej klasy. –

7

można umieścić 1X1 obiektów Rectangle na płótnie

private void AddPixel(double x, double y) 
    { 
    Rectangle rec = new Rectangle(); 
    Canvas.SetTop(rec, y); 
    Canvas.SetLeft(rec, x); 
    rec.Width = 1; 
    rec.Height = 1; 
    rec.Fill = new SolidColorBrush(Colors.Red); 
    myCanvas.Children.Add(rec); 
    } 

to powinno być dość blisko tego, co chcesz

6

Możesz dodać małe prostokąty, jeśli chcesz, ale każdy z nich jest FrameworkElement, więc to chyba trochę wagi ciężkiej. Inną opcją jest stworzenie sobie DrawingVisual, wyciągnąć z niego, czyni go potem trzymać go w obrazie:

private void DrawRubbish() 
{ 
    DrawingVisual dv = new DrawingVisual(); 
    using (DrawingContext dc = dv.RenderOpen()) 
    { 
     Random rand = new Random(); 

     for (int i = 0; i < 200; i++) 
      dc.DrawRectangle(Brushes.Red, null, new Rect(rand.NextDouble() * 200, rand.NextDouble() * 200, 1, 1)); 

     dc.Close(); 
    } 
    RenderTargetBitmap rtb = new RenderTargetBitmap(200, 200, 96, 96, PixelFormats.Pbgra32); 
    rtb.Render(dv); 
    Image img = new Image(); 
    img.Source = rtb; 
    MainGrid.Children.Add(img); 
} 
+0

doskonałym rozwiązaniem. Pracował jak urok. Tylko jedna mała rzecz: dc.Close(); nie jest potrzebny. Używając wywołań Dispose(), który działa tak samo jak Close(). Sprawdziłem kod źródłowy WPF :-) –

10

Jeśli myślisz o zrobieniu czegoś takiego jak ray tracer, w którym będziesz musiał narysować wiele punktów, prawdopodobnie będziesz chciał narysować bezpośrednio w pamięci bitmapy zamiast przez warstwy abstrakcji. Ten kod zapewnia znacznie lepsze czasy renderowania, ale przychodzi to kosztem "niebezpiecznego" kodu, który może być opcją dla ciebie.


      unsafe 
      { 
       System.Drawing.Point point = new System.Drawing.Point(320, 240); 
       IntPtr hBmp; 
       Bitmap bmp = new Bitmap(640, 480); 
       Rectangle lockRegion = new Rectangle(0, 0, 640, 480); 
       BitmapData data = bmp.LockBits(lockRegion, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); 
       byte* p; 

       p = (byte*)data.Scan0 + (point.Y * data.Stride) + (point.X * 3); 
       p[0] = 0; //B pixel 
       p[1] = 255; //G pixel 
       p[2] = 255; //R pixel 

       bmp.UnlockBits(data); 

       //Convert the bitmap to BitmapSource for use with WPF controls 
       hBmp = bmp.GetHbitmap(); 
       Canvas.Source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(hbmpCanvas, IntPtr.Zero, Int32Rect.Empty, System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions()); 
       Canvas.Source.Freeze(); 
       DeleteObject(hBmp); //Clean up original bitmap 
      } 

Aby oczyścić HBITMAP trzeba będzie zadeklarować to w górnej części pliku klasy:


     [System.Runtime.InteropServices.DllImport("gdi32.dll")] 
     public static extern bool DeleteObject(IntPtr hObject); 

Należy również pamiętać, że trzeba będzie ustawić właściwości projektu w celu umożliwienia niebezpieczny kod. Możesz to zrobić, klikając prawym przyciskiem myszy projekt w eksploratorze rozwiązań i wybierając właściwości. Przejdź do karty Build. Zaznacz pole "Zezwalaj na niebezpieczny kod". Sądzę również, że będziesz musiał dodać odniesienie do System.Drawing.

+2

Dzięki za to rozwiązanie. –

1

Oto metoda wykorzystująca WritableBitmap.

public void DrawRectangle(WriteableBitmap writeableBitmap, int left, int top, int width, int height, Color color) 
{ 
    // Compute the pixel's color 
    int colorData = color.R << 16; // R 
    colorData |= color.G << 8; // G 
    colorData |= color.B << 0; // B 
    int bpp = writeableBitmap.Format.BitsPerPixel/8; 

    unsafe 
    { 
     for (int y = 0; y < height; y++) 
     { 
      // Get a pointer to the back buffer 
      int pBackBuffer = (int)writeableBitmap.BackBuffer; 

      // Find the address of the pixel to draw 
      pBackBuffer += (top + y) * writeableBitmap.BackBufferStride; 
      pBackBuffer += left * bpp; 

      for (int x = 0; x < width; x++) 
      { 
       // Assign the color data to the pixel 
       *((int*)pBackBuffer) = colorData; 

       // Increment the address of the pixel to draw 
       pBackBuffer += bpp; 
      } 
     } 
    } 

    writeableBitmap.AddDirtyRect(new Int32Rect(left, top, width, height)); 
} 

I z niego korzystać:

private WriteableBitmap bitmap = new WriteableBitmap(1100, 1100, 96d, 96d, PixelFormats.Bgr24, null); 

private void Button_Click(object sender, RoutedEventArgs e) 
{ 
    int size = 10; 

    Random rnd = new Random(DateTime.Now.Millisecond); 
    bitmap.Lock(); // Lock() and Unlock() could be moved to the DrawRectangle() method. Just do some performance tests. 

    for (int y = 0; y < 99; y++) 
    { 
     for (int x = 0; x < 99; x++) 
     { 
      byte colR = (byte)rnd.Next(256); 
      byte colG = (byte)rnd.Next(256); 
      byte colB = (byte)rnd.Next(256); 

      DrawRectangle(bitmap, (size + 1) * x, (size + 1) * y, size, size, Color.FromRgb(colR, colG, colB)); 
     } 
    } 

    bitmap.Unlock(); // Lock() and Unlock() could be moved to the DrawRectangle() method. Just do some performance tests. 

    image.Source = bitmap; // This should be done only once 
} 
Powiązane problemy