在 Windows 窗体上绘制单个像素

发布于 2024-07-17 20:22:47 字数 259 浏览 5 评论 0原文

我在尝试打开 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#。

I'm stuck trying to turn on a single pixel on a Windows Form.

graphics.DrawLine(Pens.Black, 50, 50, 51, 50); // draws two pixels

graphics.DrawLine(Pens.Black, 50, 50, 50, 50); // draws no pixels

The API really should have a method to set the color of one pixel, but I don't see one.

I am using C#.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

離人涙 2024-07-24 20:22:47

这将设置单个像素:

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

This will set a single pixel:

e.Graphics.FillRectangle(aBrush, x, y, 1, 1);
羁客 2024-07-24 20:22:47

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

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

The Graphics object doesn't have this, since it's an abstraction and could be used to cover a vector graphics format. In that context, setting a single pixel wouldn't make sense. The Bitmap image format does have GetPixel() and SetPixel(), but not a graphics object built on one. For your scenario, your option really seems like the only one because there's no one-size-fits-all way to set a single pixel for a general graphics object (and you don't know EXACTLY what it is, as your control/form could be double-buffered, etc.)

Why do you need to set a single pixel?

国际总奸 2024-07-24 20:22:47

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

Brush aBrush = (Brush)Brushes.Black;
Graphics g = this.CreateGraphics();

g.FillRectangle(aBrush, x, y, 1, 1);

Just to show complete code for Henk Holterman answer:

Brush aBrush = (Brush)Brushes.Black;
Graphics g = this.CreateGraphics();

g.FillRectangle(aBrush, x, y, 1, 1);
謸气贵蔟 2024-07-24 20:22:47

当我绘制大量单个像素(用于各种自定义数据显示)时,我倾向于将它们绘制为位图,然后将其传输到屏幕上。

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

Where I'm drawing lots of single pixels (for various customised data displays), I tend to draw them to a bitmap and then blit that onto the screen.

The Bitmap GetPixel and SetPixel operations are not particularly fast because they do an awful lot of boundschecking, but it's quite easy to make a 'fast bitmap' class which has quick access to a bitmap.

野の 2024-07-24 20:22:47

GetHdc 上的 MSDN 页面

我认为这个就是您正在寻找的。 您需要获取 HDC,然后使用 GDI 调用来使用 SetPixel。 请注意,GDI 中的 COLORREF 是存储 BGR 颜色的 DWORD。 没有alpha通道,也不像GDI+的颜色结构那样是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);
    }
    ...
}

MSDN Page on GetHdc

I think this is what you are looking for. You will need to get the HDC and then use the GDI calls to use SetPixel. Note, that a COLORREF in GDI is a DWORD storing a BGR color. There is no alpha channel, and it is not RGB like the Color structure of GDI+.

This is a small section of code that I wrote to accomplish the same task:

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);
    }
    ...
}
ゝ杯具 2024-07-24 20:22:47

绝对最好的方法是创建一个位图并将其传递给现有数组的 intptr (指针)。 这允许数组和位图数据共享相同的内存...不需要使用 Bitmap.lockbits/Bitmap.unlockbits ,两者都很慢。

以下是大致轮廓:

  1. 将您的函数标记为“不安全”,并将项目构建设置设置为允许“不安全”代码! (C# 指针)

  2. 创建您的数组[,]。 使用 Uint32、字节或允许 Uint32 或单个 Uint8 访问的结构(通过使用显式字段偏移量)

  3. 使用 System.Runtime.InteropServices.Marshal.UnsaveAddrOfPinnedArrayElement< /code> 获取数组开头的 Intptr


  4. 使用采用 Intptr 和 Stride 的构造函数创建位图。 这将使新位图与现有数组数据重叠。

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

底层数组

底层数组可能是用户结构Pixel的二维数组。 为什么? 好吧……结构体可以通过使用显式的固定偏移量来允许多个成员变量共享相同的空间! 这意味着该结构可以有 4 个单字节成员(.R.G.B.A< /code>),以及 3 个重叠的 Uint16(.AR.RG,GB)...以及一个 Uint32(< code>.ARGB)...这可以使色彩平面操作更快。

由于 R、G、B、AR、RG、GB 和 ARGB 都访问同一 32 位像素的不同部分,因此您可以以高度灵活的方式操作像素!

由于 Pixel[,] 数组与位图本身共享相同的内存,因此图形操作会立即更新像素数组 - 并且对数组进行 Pixel[,] 操作会立即更新位图! 您现在可以通过多种方式来操作位图。

请记住,通过使用此技术,您不需要使用“lockbits”将位图数据编组进出缓冲区......这很好,因为lockbits 非常慢。

您也不需要使用画笔并调用能够绘制图案、可缩放、可旋转、可翻译、可别名、矩形的复杂框架代码...只需编写单个像素相信我 - Graphics 类中的所有灵活性都使绘图变得容易使用 Graphics.FillRect 的单个像素是一个非常缓慢的过程。

其他好处

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

如何?

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

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

最后

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

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

记住:

  1. MemoryStream 创建位图非常慢

  2. 使用Graphics.FillRect绘制像素效率极低

  3. 使用 lockpixels/unlockpixels 访问底层位图数据非常慢

  4. 而且,如果您使用的是System.Runtime.InteropServices.Marshal.Copy,停下来!

将位图映射到一些现有的数组内存是可行的方法。 做得正确,您将永远不需要/不想再次使用框架位图。

The absolute best method is to create a bitmap and pass it an intptr (pointer) to an existing array. This allows the array and the bitmap data to share the same memory... no need to use Bitmap.lockbits/Bitmap.unlockbits, both of which are slow.

Here's the broad outline:

  1. Mark your function 'unsafe' and set your projects build settings to allow 'unsafe' code! (C# pointers)

  2. Create your array[,]. Either using Uint32's, Bytes, or a Struct that permits access to by both Uint32 OR individual Uint8's (By using explicit field offsets)

  3. Use System.Runtime.InteropServices.Marshal.UnsaveAddrOfPinnedArrayElement to obtain the Intptr to the start of the array.

  4. Create the Bitmap using the constructor that takes an Intptr and Stride. This will overlap the new bitmap with the existing array data.

You now have permanent direct access to the pixel data!

The underlying array

The underlying array would likely be a 2D array of a user-struct Pixel. Why? Well... Structs can allow multiple member variables to share the same space by using explicit fixed offsets! This means that the struct can have 4 single-bytes members (.R, .G, .B, and .A), and 3 overlapping Uint16's (.AR, .RG, and ,GB)... and a single Uint32 (.ARGB)... this can make colour-plane manipulations MUCH faster.

As R, G, B, AR, RG, GB and ARGB all access different parts of the same 32-bit pixel you can manipulate pixels in a highly flexible way!

Because the array of Pixel[,] shares the same memory as the Bitmap itself, Graphics operations immediately update the Pixel array - and Pixel[,] operations on the array immediately update the bitmap! You now have multiple ways of manipulating the bitmap.

Remember, by using this technique you do NOT need to use 'lockbits' to marshal the bitmap data in and out of a buffer... Which is good, because lockbits is very VERY slow.

You also don't need to use a brush and call complex framework code capable of drawing patterned, scalable, rotatable, translatable, aliasable, rectangles... Just to write a single pixel Trust me - all that flexibility in the Graphics class makes drawing a single pixel using Graphics.FillRect a very slow process.

Other benefits

Super-smooth scrolling! Your Pixel buffer can be larger than your canvas/bitmap, in both height, and width! This enables efficient scrolling!

How?

Well, when you create a Bitmap from the array you can point the bitmaps upper-left coordinate at some arbitrary [y,x] coordinate by taking the IntPtr of that Pixel[,].

Then, by deliberately setting the Bitmaps 'stride' to match width of the array (not the width of the bitmap) you can render a predefined subset rectangle of the larger array... Whilst drawing (ahead of time) into the unseen margins! This is the principle of "offscreen drawing" in smooth scrollers.

Finally

You REALLY should wrap the Bitmap and Array into a FastBitmap class. This will help you control the lifetime of the array/bitmap pair. Obviously, if the array goes out of scope or is destroyed - the bitmap will be left pointing at an illegal memory address. By wrapping them up in a FastBitmap class you can ensure this can't happen...

... It's also a really handy place to put the various utilities you'll inevitably want to add... Such as scrolling, fading, working with colour planes, etc.

Remember:

  1. Creating Bitmaps from a MemoryStream is very slow

  2. Using Graphics.FillRect to draw pixels is painfully inefficient

  3. Accessing underlying bitmap data with lockpixels/unlockpixels is very slow

  4. And, if you're using System.Runtime.InteropServices.Marshal.Copy, just stop!

Mapping the Bitmap onto some existing array memory is the way to go. Do it right, and you'll never need/want to use a framework Bitmap again.

记忆之渊 2024-07-24 20:22:47

使用带有 DashStyle.DashStyle.Dot 的笔绘制 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);
        }
    }

Drawing a Line 2px using a Pen with DashStyle.DashStyle.Dot draws a single Pixel.

    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);
        }
    }
A君 2024-07-24 20:22:47

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

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

If you are drawing on a graphic with SmoothingMode = AntiAlias, most drawing methods will draw more than one pixel. If you only want one pixel drawn, create a 1x1 bitmap, set the bitmap's pixel to the desired color, then draw the bitmap on the graphic.

using (var pixel = new Bitmap(1, 1, e.Graphics))
{
    pixel.SetPixel(0, 0, color);
    e.Graphics.DrawImage(pixel, x, y);
}
薯片软お妹 2024-07-24 20:22:47

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

Apparently DrawLine draws a line that is one pixel short of the actual specified length. There doesn't seem to be a DrawPoint/DrawPixel/whatnot, but instead you can use DrawRectangle with width and height set to 1 to draw a single pixel.

有深☉意 2024-07-24 20:22:47

您应该将代码放在 Paint 事件中,否则,它将不会刷新并自行删除。

例子:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {

    }

    private void Form1_Paint(object sender, PaintEventArgs e)
    {
        Brush aBrush = (Brush)Brushes.Red;
        Graphics g = this.CreateGraphics();

        g.FillRectangle(aBrush, 10, 10, 1, 1);
    }
}

You should put your code inside the Paint event, otherwise, it will not be refreshed and delete itself.

Example:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {

    }

    private void Form1_Paint(object sender, PaintEventArgs e)
    {
        Brush aBrush = (Brush)Brushes.Red;
        Graphics g = this.CreateGraphics();

        g.FillRectangle(aBrush, 10, 10, 1, 1);
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文