如何管理WPF中的逐像素渲染(例如,对于光线跟踪器)?我最初的猜测是创建一个BitmapImage,修改缓冲区,并在Image控件中显示它,但我无法弄清楚如何创建一个(create方法需要一块非托管内存)
答案 0 :(得分:38)
我强烈建议反对之前的两条建议。假设您使用的是3.5 SP1,The WriteableBitmap class提供了所需的功能。在SP1之前,除非你采取一些严肃的诡计,否则在WPF中对这个问题没有真正好的答案。
答案 1 :(得分:11)
如果您正在考虑做一些类似光线跟踪器的事情,您需要绘制许多点,您可能需要直接在位图的内存空间中绘制,而不是通过抽象层。此代码将为您提供明显更好的渲染时间,但它需要“不安全”代码,这可能是也可能不是您的选择。
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
}
要清理hBitmap,您需要在类文件的顶部附近声明:
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
另请注意,您需要设置项目属性以允许不安全的代码。您可以通过右键单击解决方案资源管理器中的项目并选择属性来执行此操作。转到Build选项卡。选中“允许不安全代码”框。另外我相信你需要添加对System.Drawing的引用。
答案 2 :(得分:8)
您可以将1X1矩形对象放置到画布上
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);
}
这应该非常接近你想要的
答案 3 :(得分:7)
如果需要,您可以添加小的rects,但每个都是FrameworkElement,所以它可能有点重量级。另一种选择是创建一个DrawingVisual,在其上绘制,渲染然后将其粘贴在图像中:
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);
}
答案 4 :(得分:1)
这是使用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));
}
使用它:
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
}