简体   繁体   English

C#更新picturebox中的位图

[英]C# Update bitmap in picturebox

I'm working on a screen sharing project ,and i recieve a small blocks of image from a Socket constantly and need to update them on a certain initial dekstop bitmap i have. 我正在进行一个屏幕共享项目,我不断收到来自Socket一小块图像,需要在我拥有的某个初始dekstop位图上更新它们。

Basically i constantly read data from socket(data which is stored as jpeg image) ,using Image.FromStream() to retrieve the image and copying the recieved block pixels to the full primary bitmap(at a specific position X and Y which i also get from the socket)- that's how the initial image gets updated. 基本上我经常从套接字读取数据(数据存储为jpeg图像),使用Image.FromStream()检索图像并将接收到的块像素复制到完整的主位图(在特定位置XY ,我也得到从套接字) - 这是初始图像更新的方式。 But then comes the part where i need to display it on a Picturebox I handle the Paint event and redrawing it all again-the entire inital image,which is pretty big(1920X1080 in my case). 但接下来是我需要在Picturebox上显示它的Picturebox我处理Paint事件并再次重新绘制它 - 整个初始图像,这是非常大的(在我的情况下是1920X1080)。

This is my code: 这是我的代码:

    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
        }
    }

Because i'm aiming at high speed performance(it's kind of a real-time project) , i would like to know if there isn't any method to draw current recieved the block itself on the picturebox instead of drawing the whole initial bitmap again-which seems very inefficient to me... This is my drawing method(works extremly fast, copying block with memcpy ): 因为我的目标是高速性能(它是一种实时项目),我想知道是否没有任何方法可以在图片框上绘制当前收到的块本身,而不是再次绘制整个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);
        }
    }

Here are some examples of a full primary bitmap,and other small blocks i'm getting and need to draw over the full one. 以下是完整主要位图的一些示例,以及我正在获取并需要绘制完整位图的其他小块。

Full bitmap: 完整位图: 完整的位图 small block: 小块:

在此输入图像描述 small block: 小块:

在此输入图像描述

small block: 小块:

在此输入图像描述

I'm getting large amount of small blocks per second(30~40) somtimes their bounds are really small(rectangle of 100X80 pixels for example) so redrawing the entire bitmap again is not necessary...Rapidly Refreshing a full screen image would kill the performance... 我每秒获得大量的小块(30~40),其边界非常小(例如100X80像素的矩形),因此不需要重新绘制整个位图...快速刷新全屏图像会杀死表现......

I hope my explaination was clear. 我希望我的解释清楚。

Looking forward for an answer. 期待一个答案。

Thanks. 谢谢。

It would be shame to leave that question without some answer. 如果没有一些答案就离开那个问题会很遗憾。 The following is about 10 times faster in my tests when updating small portions of the picture box. 在更新图片框的一小部分时,我的测试速度大约快10倍。 What it does basically is smart invalidating (invalidates just the updated portion of the bitmap, considering the scaling) and smart painting (draws only the invalidated portion of the picture box, taken from e.ClipRectangle and considering the scaling): 它的作用基本上是智能无效 (仅考虑缩放的位图的更新部分无效)和智能绘画 (仅绘制图片框的无效部分,取自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);
    }
}

The only kind of tricky part is determining the scaled rectangles, especially the one for invalidating, due to floating point to int conversions required, so we make sure it's eventually a little bigger than needed, but not less. 唯一棘手的部分是确定缩放的矩形,特别是用于无效的矩形,因为需要浮点到int转换,所以我们确保它最终比需要的大一点,但不能少。

If you just need to draw on top of the canvas, you can draw the initial image just once and then use CreateGraphics() and DrawImage to update the content: 如果您只需要在画布上绘制,只需绘制一次初始图像,然后使用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());
}

I'll update the answer with a performance comparison as I'm not convinced this will give any major benefit; 我将通过性能比较更新答案,因为我不相信这会带来任何重大好处; it will, at least, avoid a double DrawImage though. 它至少会避免双重DrawImage

Please carefully read the following document: 请仔细阅读以下文件:

http://www.cs.columbia.edu/~lennox/draft-lennox-avt-app-sharing-00.html http://www.cs.columbia.edu/~lennox/draft-lennox-avt-app-sharing-00.html

I read it and it is helping a lot with the understanding of desktop - sharing application. 我阅读它,它对桌面共享应用程序的理解有很大帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM