我一直在尝试打开 Windows 窗体上的单个像素。
graphics.DrawLine(Pens.Black, 50, 50, 51, 50); // draws two pixels
graphics.DrawLine(Pens.Black, 50, 50, 50, 50); // draws no pixels
API确实应该有一种方法来设置一个像素的颜色,但我没有看到。
我正在使用 C#。
这将设置一个像素:
e.Graphics.FillRectangle(aBrush, x, y, 1, 1);
该Graphics
对象没有这个,因为它是一种抽象,可用于覆盖矢量图形格式。在这种情况下,设置单个像素是没有意义的。Bitmap
图像格式确实有GetPixel()
and SetPixel()
,但没有建立在一个之上的图形对象。对于您的方案,您的选项似乎真的是唯一的一个,因为没有一种万能的方法来为一般图形对象设置单个像素(并且您不知道它到底是什么,作为您的控件/表单可以是双缓冲等)
为什么需要设置单个像素?
只是为了显示 Henk Holterman 答案的完整代码:
Brush aBrush = (Brush)Brushes.Black;
Graphics g = this.CreateGraphics();
g.FillRectangle(aBrush, x, y, 1, 1);
在我绘制大量单个像素(用于各种自定义数据显示)的地方,我倾向于将它们绘制到位图上,然后将其粘贴到屏幕上。
Bitmap GetPixel 和 SetPixel 操作并不是特别快,因为它们进行了大量的边界检查,但是制作一个可以快速访问位图的“快速位图”类非常容易。
我想这就是你要找的。您将需要获取 HDC,然后使用 GDI 调用来使用 SetPixel。请注意,GDI 中的 COLORREF 是存储 BGR 颜色的 DWORD。没有alpha通道,也不是像GDI+的Color结构那样的RGB。
这是我为完成相同任务而编写的一小段代码:
public class GDI
{
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
internal static extern bool SetPixel(IntPtr hdc, int X, int Y, uint crColor);
}
{
...
private void OnPanel_Paint(object sender, PaintEventArgs e)
{
int renderWidth = GetRenderWidth();
int renderHeight = GetRenderHeight();
IntPtr hdc = e.Graphics.GetHdc();
for (int y = 0; y < renderHeight; y++)
{
for (int x = 0; x < renderWidth; x++)
{
Color pixelColor = GetPixelColor(x, y);
// NOTE: GDI colors are BGR, not ARGB.
uint colorRef = (uint)((pixelColor.B << 16) | (pixelColor.G << 8) | (pixelColor.R));
GDI.SetPixel(hdc, x, y, colorRef);
}
}
e.Graphics.ReleaseHdc(hdc);
}
...
}
使用带有 DashStyle.DashStyle.Dot 的 Pen 绘制一条 2px 的线条会绘制一个像素。
private void Form1_Paint(object sender, PaintEventArgs e)
{
using (Pen p = new Pen(Brushes.Black))
{
p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
e.Graphics.DrawLine(p, 10, 10, 11, 10);
}
}
显然 DrawLine 绘制了一条比实际指定长度短一个像素的线。似乎没有 DrawPoint/DrawPixel/whatnot,但是您可以使用将宽度和高度设置为 1 的 DrawRectangle 来绘制单个像素。
如果您在 SmoothingMode = AntiAlias 的图形上绘图,大多数绘图方法将绘制多个像素。如果您只想绘制一个像素,请创建一个 1x1 位图,将位图的像素设置为所需的颜色,然后在图形上绘制位图。
using (var pixel = new Bitmap(1, 1, e.Graphics))
{
pixel.SetPixel(0, 0, color);
e.Graphics.DrawImage(pixel, x, y);
}
这里有很多答案,但它们都很糟糕:(
绝对最好的方法是创建一个位图并将一个 intptr(指针)传递给一个现有的数组。这允许数组和位图数据共享相同的内存......不需要'Bitmap.lockbits'/'Bitmap.unlockbits'(慢)。
这是大致的轮廓...
您现在可以永久直接访问像素数据!
底层数组
底层数组可能是用户结构“像素”的二维数组。为什么?嗯...结构可以允许多个成员变量通过使用显式的固定偏移来共享同一个空间!这意味着该结构可以有 4 个单字节成员(.R、.G、.B 和 .A),以及 3 个重叠的 Uint16(.AR、.RG 和 ,GB)……和一个 Uint32(.ARGB ) ...这可以使色彩平面操作更快。
由于 R、G、B、AR、RG、GB 和 ARGB 都访问同一个 32 位像素的不同部分,您可以以高度灵活的方式操作像素!
因为 Pixel[,] 的数组与 Bitmap 本身共享相同的内存,所以图形操作会立即更新 Pixel 数组 - 而对数组的 Pixel[,] 操作会立即更新位图!您现在有多种操作位图的方法;)
灵活快速;)
请记住,通过使用这种技术,您不需要使用“lockbits”来将位图数据编组进出缓冲区……这很好,因为 lockbits 非常非常慢。
您也不需要使用画笔并调用能够绘制图案化、可缩放、可旋转、可平移、可别名、矩形的复杂框架代码......只需编写一个像素相信我 - Graphics 类中的所有灵活性使绘图使用“Graphics.FillRect”的单个像素是一个非常缓慢的过程。
其他福利...
滚动超级流畅!您的像素缓冲区在高度和宽度上都可以大于您的画布/位图!这可以实现有效的滚动!!!!
如何?
好吧,当您从数组创建位图时,您可以通过获取该 Pixel[,] 的 IntPtr 将位图的左上角坐标指向任意 [y,x] 坐标。
然后,通过故意设置位图“步幅”以匹配数组的宽度(而不是位图的宽度),您可以渲染较大数组的预定义子集矩形......同时(提前)绘制到看不见的边缘!这就是平滑滚动条中“离屏绘图”的原理。
最后
您真的应该将 Bitmap 和 Array 包装到 FastBitmap 类中。这将帮助您控制数组/位图对的生命周期。显然,如果数组超出范围或被破坏 - 位图将指向非法内存地址。通过将它们包装在 FastBitmap 类中,您可以确保不会发生这种情况......
......它也是一个非常方便的地方,可以放置您不可避免地想要添加的各种实用程序......例如滚动,褪色,使用彩色平面等。
记住:
将位图映射到一些现有的数组内存是要走的路。做对了,你将永远不需要/想再次使用框架位图:)
别客气 ;)