【问题标题】:Draw a single pixel on Windows Forms在 Windows 窗体上绘制单个像素
【发布时间】:2010-10-20 03:32:03
【问题描述】:

我无法在 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#。

【问题讨论】:

  • 我制作了一个控件,它绘制了一些非常漂亮的科学图表(具有附加功能,我不需要在任何商业控件中提供)。我可以用和 X 或 + 或一个小框来绘制数据点。但是对于不重要的数据,我只想要一个像素。
  • 我看到的所有答案似乎对于单个像素来说真的是矫枉过正?为什么只做一个 buffer[y*width+x]=color; 似乎更容易

标签: c# .net winforms gdi+ pixel


【解决方案1】:

这里有很多答案,但都非常糟糕:(

绝对最好的方法是创建一个位图并将一个 intptr(指针)传递给一个现有数组。这允许数组和位图数据共享相同的内存......无需'Bitmap.lockbits'/'Bitmap.unlockbits'(慢)。

这是大致轮廓...

  1. 将您的函数标记为“不安全”并将您的项目构建设置设置为允许“不安全”代码! (c# 指针)
  2. 创建您的数组[,]。使用 Uint32、字节或允许 Uint32 或单个 Uint8 访问的结构 (通过使用显式字段偏移)
  3. 使用“System.Runtime.InteropServices.Marshal.UnsaveAddrOfPinnedArrayElement”获取数组开头的 Intptr。
  4. 使用采用 Intptr 和 Stride 的构造函数创建位图。这将使新位图与现有数组数据重叠。

您现在可以永久直接访问像素数据!

底层数组

底层数组可能是用户结构“像素”的二维数组。为什么?嗯...结构可以允许多个成员变量通过使用显式的固定偏移来共享相同的空间!这意味着该结构可以有 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.FillRect”绘制单个像素的过程非常缓慢。

其他好处...

超级流畅的滚动!您的像素缓冲区在高度和宽度上都可以大于您的画布/位图!这可以实现有效的滚动!!!!

怎么做?

好吧,当您从数组创建位图时,您可以通过获取该 Pixel[,] 的 IntPtr 将位图的左上角坐标指向任意 [y,x] 坐标。

然后,通过故意设置位图“步幅”以匹配数组的宽度(不是位图的宽度),您可以渲染较大数组的预定义子集矩形......同时绘制(提前)进入看不见的边缘!这就是平滑滚动条中“离屏绘制”的原理。

终于

您真的应该将 Bitmap 和 Array 包装到 FastBitmap 类中。这将帮助您控制数组/位图对的生命周期。显然,如果数组超出范围或被破坏 - 位图将指向非法内存地址。通过将它们包装在 FastBitmap 类中,您可以确保不会发生这种情况......

...它也是一个非常方便的地方,可以放置您不可避免地想要添加的各种实用程序...例如滚动、淡入淡出、使用颜色平面等。

记住:

  1. 从 MemoryStream 创建位图非常慢
  2. 使用 Graphics.FillRect 绘制像素效率极低
  3. 使用 lockpixels/unlockpixels 访问底层位图数据非常慢
  4. 而且,如果您使用的是“System.Runtime.InteropServices.Marshal.Copy”,请停止!

将位图映射到一些现有的数组内存是可行的方法。做对了,你就再也不需要/想再次使用框架位图了:)

不客气;)

【讨论】:

  • 我在 12 年前问过这个问题,现在仍然得到答案。这听起来像是一个非常有价值的方法,我同意它应该被做成一个类......我希望你已经这样做了。如果您可以发布此类课程的代码并使我们免于“重新发明轮子”,那将真的很有帮助。 (即使它不完整。)
【解决方案2】:

如果您在 SmoothingMode = AntiAlias 的图形上绘图,大多数绘图方法将绘制多个像素。如果只想绘制一个像素,则创建一个 1x1 位图,将位图的像素设置为所需的颜色,然后在图形上绘制位图。

using (var pixel = new Bitmap(1, 1, e.Graphics))
{
    pixel.SetPixel(0, 0, color);
    e.Graphics.DrawImage(pixel, x, y);
}

【讨论】:

    【解决方案3】:

    使用带有 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);
            }
        }
    

    【讨论】:

      【解决方案4】:

      MSDN Page on GetHdc

      我认为这就是您要寻找的。您将需要获取 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);
          }
          ...
      }
      

      【讨论】:

        【解决方案5】:

        只是为了显示 Henk Holterman 答案的完整代码:

        Brush aBrush = (Brush)Brushes.Black;
        Graphics g = this.CreateGraphics();
        
        g.FillRectangle(aBrush, x, y, 1, 1);
        

        【讨论】:

          【解决方案6】:

          这将设置一个像素:

          e.Graphics.FillRectangle(aBrush, x, y, 1, 1);
          

          【讨论】:

          • 谁会猜到 DrawRectangle 绘制一个 2x2 而 FillRectange 绘制一个 1x1 并具有相同的参数?谢谢。
          • @Mark:确实有点意思... DrawRectangle 说“从 x 和 y 开始画一个轮廓,这个宽度和高度”,所以它同时绘制起点和终点(x ,y; x+1,y;x,y+1; x+1,y+1) 以及它们之间的线。 FillRectangle 说“从这个宽度和高度的 x,y 开始绘制一个实体表面。”顺便说一句,FillRectangle 使用的是 Brush,而不是 Pen。
          • @R. Bemrose:实际上,这取决于你传递给DrawRectangle() 的 Pen 对象。如果它有Pen.Alignment = PenAlignment.Outset,它将在给定的矩形周围绘制Pen.Width像素厚的轮廓。但是,如果您指定Pen.Alignment = PenAlignment.Inset,它将在给定的矩形内绘制 Pen.Width 像素厚的“轮廓”。
          • 这是一个非常不令人满意的答案。尽管这设置了单个像素,但要求并不总是设置文字像素。我一直需要这个,通常,我需要用画线的同一支笔来绘制点,而不是画笔。换句话说,我们需要一条短于 2 个像素的线。我怀疑这是 OP 所需要的。
          • 我不喜欢这个答案。这种方式效率极低。
          【解决方案7】:

          在我绘制大量单个像素(用于各种自定义数据显示)的地方,我倾向于将它们绘制到位图上,然后将其 blit 到屏幕上。

          Bitmap GetPixel 和 SetPixel 操作并不是特别快,因为它们会进行大量的边界检查,但是制作一个可以快速访问位图的“快速位图”类非常容易。

          【讨论】:

            【解决方案8】:

            显然,DrawLine 绘制了一条比实际指定长度短一个像素的线。似乎没有 DrawPoint/DrawPixel/whatnot,但是您可以使用将宽度和高度设置为 1 的 DrawRectangle 来绘制单个像素。

            【讨论】:

            • 不错的尝试,但大小为 1 时会绘制 2x2 像素。 (把它画在一条线旁边,你可以看到它的宽度是原来的两倍。)当然,它的大小为零。
            • 对,我的错。 :) 好东西 FillRectangle 可以解决问题。
            【解决方案9】:

            Graphics 对象没有这个,因为它是一种抽象,可以用来覆盖矢量图形格式。在这种情况下,设置单个像素是没有意义的。 Bitmap 图像格式确实有 GetPixel()SetPixel(),但不是基于它们的图形对象。对于您的方案,您的选项似乎真的是唯一的一个,因为没有一种万能的方法来为一般图形对象设置单个像素(并且您不知道它到底是什么,作为您的控件/表单可以是双缓冲等)

            为什么需要设置单个像素?

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-10-20
              • 1970-01-01
              • 2020-04-23
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多