13
votes

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.

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. 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).

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):

     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: full bitmap small block:

enter image description here small block:

enter image description here

small block:

enter image description here

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

I hope my explaination was clear.

Looking forward for an answer.

Thanks.

2
I think you can use Control.CreateGraphics to get the graphics from the picture box and then draw on top of it with Graphics.DrawImage(image, x, y) so you avoid redrawing the intial image. But I don't know how efficient it is, it might be blitting it anyway under the hood. Might be worth doing some performance testing. - Stefano d'Antonio
Change the pixel format of the image to 32bppPArgb, it renders 10 times faster than all the other ones. Ensure the image never has to be rescaled to fit the picturebox, ClientRectangle doesn't help at all. Never use Refresh, use Invalidate(Rectangle) instead where the rectangle is the part of the image that actually has to be redrawn. - Hans Passant
@Hans Passant the images I'm getting are jpeg. .. (24bpprgb ).. I have to display the images scaled..it's for a screen sharing purpose..so if someone has a 1920X1080 resolution, and he's sharing the screen to someone who has 1600X880 resolution the client won't be able to see the entire screen...If the image is bigger then he's computer resolution it won't be possible to see the whole image ;) so I think stretching is must do be done here. For properly displaying of the screen. - Slashy
@Hans Passant about the rest of your answer, I'll try and reply tomorrow. Time to sleep a little bit :) - Slashy
@Slashy do you need to clear the canvas or you just need to draw on top? - Stefano d'Antonio

2 Answers

3
votes

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. 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):

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.

0
votes

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:

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.