【问题标题】:C# Update bitmap in pictureboxC# 更新图片框中的位图
【发布时间】:2016-07-16 22:13:01
【问题描述】:

我正在做一个屏幕共享项目,我经常从Socket 收到一小块图像,需要在我拥有的某个初始桌面位图上更新它们。

基本上我不断从套接字读取数据(数据存储为 jpeg 图像),使用 Image.FromStream() 检索图像并将接收到的块像素复制到完整的主位图(在特定位置 X 和 @987654332 @ 我也从套接字获得)- 这就是初始图像的更新方式。但接下来是我需要在Picturebox 上显示它的部分 我处理Paint 事件并重新绘制它——整个初始图像非常大(在我的例子中是1920X1080)。

这是我的代码:

    private void MainScreenThread()
    {
        ReadData();//reading data from socket.
        initial = bufferToJpeg();//first intial full screen image.
        pictureBox1.Paint += pictureBox1_Paint;//activating the paint event.
        while (true)
        {
            int pos = ReadData();
            x = BlockX();//where to draw :X
            y = BlockY();//where to draw :Y
            Bitmap block = bufferToJpeg();//constantly reciving blocks.
            Draw(block, new Point(x, y));//applying the changes-drawing the block on the big initial image.using native memcpy.

            this.Invoke(new Action(() =>
            {
                pictureBox1.Refresh();//updaing the picturebox for seeing results.
                // this.Text = ((pos / 1000).ToString() + "KB");
            }));
        }
    }

    private void pictureBox1_Paint(object sender, PaintEventArgs e)
    {
        lock (initial)
        {
            e.Graphics.DrawImage(initial, pictureBox1.ClientRectangle); //draws at picturebox's bounds
        }
    }

因为我的目标是高速性能(它是一种实时项目),我想知道是否没有任何方法可以在图片框上绘制当前收到的块本身而不是绘制整个initial 又是位图——这对我来说似乎效率很低...... 这是我的绘图方法(工作得非常快,用memcpy复制块):

     private unsafe void Draw(Bitmap bmp2, Point point)
    {
        lock (initial)
        {  
            BitmapData bmData = initial.LockBits(new Rectangle(0, 0, initial.Width, initial.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, initial.PixelFormat);
            BitmapData bmData2 = bmp2.LockBits(new Rectangle(0, 0, bmp2.Width, bmp2.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp2.PixelFormat);
            IntPtr scan0 = bmData.Scan0;
            IntPtr scan02 = bmData2.Scan0;
            int stride = bmData.Stride;
            int stride2 = bmData2.Stride;
            int Width = bmp2.Width;
            int Height = bmp2.Height;
            int X = point.X;
            int Y = point.Y;

            scan0 = IntPtr.Add(scan0, stride * Y + X * 3);//setting the pointer to the requested line
            for (int y = 0; y < Height; y++)
            {
                memcpy(scan0, scan02 ,(UIntPtr)(Width * 3));//copy one line

                scan02 = IntPtr.Add(scan02, stride2);//advance pointers
                scan0 = IntPtr.Add(scan0, stride);//advance pointers//
            }


            initial.UnlockBits(bmData);
            bmp2.UnlockBits(bmData2);
        }
    }

这里有一些完整的主要位图示例,以及我得到的其他小块,需要在完整的位图上绘制。

完整位图: 小块:

小块:

小方块:

我每秒会得到大量的小块(30~40),有时它们的边界非常小(例如 100X80 像素的矩形),因此不需要再次重绘整个位图...快速刷新全屏图像会破坏性能...

我希望我的解释很清楚。

期待答案。

谢谢。

【问题讨论】:

  • 我认为您可以使用Control.CreateGraphics 从图片框中获取图形,然后使用Graphics.DrawImage(image, x, y) 在其上绘制,这样您就可以避免重绘初始图像。但我不知道它的效率如何,无论如何它可能会在引擎盖下运行它。可能值得做一些性能测试。
  • 将图像的像素格式更改为 32bppPArgb,它的渲染速度比其他所有的快 10 倍。确保图像永远不必重新缩放以适合图片框,ClientRectangle 根本没有帮助。永远不要使用 Refresh,使用 Invalidate(Rectangle) 代替矩形是实际必须重绘的图像部分。
  • @Hans Passant 我得到的图像是 jpeg。 ..(24bpprgb)..我必须显示缩放的图像..这是为了屏幕共享目的..所以如果某人的分辨率为 1920X1080,并且他将屏幕共享给分辨率为 1600X880 的人,客户端将无法看到整个屏幕......如果图像更大,那么他的计算机分辨率将无法看到整个图像;)所以我认为必须在这里进行拉伸。为了正确显示屏幕。
  • @Hans Passant 关于您的其余答案,我明天会尝试回复。是时候睡觉了:)
  • @Slashy 你需要清除画布还是只需要在上面绘制?

标签: c# performance bitmap


【解决方案1】:

如果没有答案就留下这个问题是很遗憾的。在我的测试中更新图片框的一小部分时,以下速度大约快 10 倍。它所做的基本上是 smart invalidating(仅使位图的更新部分无效,考虑到缩放)和 智能绘画(仅绘制图片框的无效部分,拍摄来自e.ClipRectangle 并考虑缩放):

private Rectangle GetViewRect() { return pictureBox1.ClientRectangle; }

private void MainScreenThread()
{
    ReadData();//reading data from socket.
    initial = bufferToJpeg();//first intial full screen image.
    pictureBox1.Paint += pictureBox1_Paint;//activating the paint event.
    // The update action
    Action<Rectangle> updateAction = imageRect =>
    {
        var viewRect = GetViewRect();
        var scaleX = (float)viewRect.Width / initial.Width;
        var scaleY = (float)viewRect.Height / initial.Height;
        // Make sure the target rectangle includes the new block
        var targetRect = Rectangle.FromLTRB(
            (int)Math.Truncate(imageRect.X * scaleX),
            (int)Math.Truncate(imageRect.Y * scaleY),
            (int)Math.Ceiling(imageRect.Right * scaleX),
            (int)Math.Ceiling(imageRect.Bottom * scaleY));
        pictureBox1.Invalidate(targetRect);
        pictureBox1.Update();
    };

    while (true)
    {
        int pos = ReadData();
        x = BlockX();//where to draw :X
        y = BlockY();//where to draw :Y
        Bitmap block = bufferToJpeg();//constantly reciving blocks.
        Draw(block, new Point(x, y));//applying the changes-drawing the block on the big initial image.using native memcpy.

        // Invoke the update action, passing the updated block rectangle
        this.Invoke(updateAction, new Rectangle(x, y, block.Width, block.Height));
    }
}

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
    lock (initial)
    {
        var viewRect = GetViewRect();
        var scaleX = (float)initial.Width / viewRect.Width;
        var scaleY = (float)initial.Height / viewRect.Height;
        var targetRect = e.ClipRectangle;
        var imageRect = new RectangleF(targetRect.X * scaleX, targetRect.Y * scaleY, targetRect.Width * scaleX, targetRect.Height * scaleY);
        e.Graphics.DrawImage(initial, targetRect, imageRect, GraphicsUnit.Pixel);
    }
}

唯一棘手的部分是确定缩放的矩形,尤其是用于无效的矩形,因为需要进行浮点到 int 的转换,所以我们确保它最终比需要的大一点,但不会小。

【讨论】:

  • 我看到很多损坏的像素..等等..我应该在您对initialblock 的回答中替换image 变量?
  • 我不明白这一点......你的仍然重绘完整的initial图像......这里e.Graphics.DrawImage(initial, targetRect, imageRect, GraphicsUnit.Pixel);
  • (1) 抱歉,image 应该是 initial 变量。 (2) 不,这个重载只绘制图像的一部分——imageRect。
  • 这是我测试的fiddle。将其复制/粘贴到新的 WinForms 项目,然后使用 testRect 大小或 pb_Paint(注释所有内容并取消注释最后一行以查看部分图像与完整图像绘制的区别)。
  • 没错。即使您尝试在该矩形之外绘制,它也会被剪裁,所以这就是我所说的智能绘画
【解决方案2】:

如果您只需要在画布上绘制,您可以只绘制一次初始图像,然后使用CreateGraphics()DrawImage更新内容:

ReadData();
initial = bufferToJpeg();
pictureBox1.Image = initial;
var graphics = pictureBox1.CreateGraphics();
while (true)
{
    int pos = ReadData();
    Bitmap block = bufferToJpeg();
    graphics.DrawImage(block, BlockX(), BlockY());
}

我将通过性能比较来更新答案,因为我不相信这会带来任何重大好处;不过,它至少会避免双重 DrawImage

【讨论】:

  • @Slashy:我假设Draw 方法正在将图像绘制到图片框,这就是为什么我认为它被调用了两次。我不确定您所说的“损坏的像素”是什么意思,它在我的测试中运行良好。代码上的一个注释是锁可能是最耗时的部分。
  • 我明白你的意思:你正在绘制图像,然后更改 SizeMode?如果是这样,我不希望它像Graphics blit 图像一样工作,它不会保留您的更改,因此如果您调整 PB 的大小,则必须从头开始重新绘制所有内容。您可能想看看一些第三方控件,WinForms 不适合自定义图形。
  • 然后我不会依赖SizeMode 进行绘图,我只是拉伸图像然后将其绘制并使其正常。但我仍然认为 WinForms PictureBox 不是这个合适的工具。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-12
  • 2020-04-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多