С# Обновить растровое изображение в окне изображения

Я работаю над проектом совместного использования экрана, и я получаю небольшие блоки изображения из Socket постоянно и должен обновлять их на определенном исходном растровом растровом, который у меня есть.

В основном я постоянно читаю данные из сокета (данные, которые хранятся как jpeg-изображение), используя Image.FromStream() для извлечения изображения и копирования получаемых пикселов блока в полную первичную растровую карту (в определенной позиции X и Y который я также получаю из сокета) - то, как обновляется исходное изображение. Но затем приходит часть, где мне нужно отобразить ее на 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 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);
        }
    }

Вот несколько примеров полного первичного растрового изображения и других небольших блоков, которые я получаю и которым нужно рисовать поверх всего.

Полный битмап: full bitmap маленький блок:

введите описание изображения здесь маленький блок:

введите описание изображения здесь

маленький блок:

введите описание изображения здесь

Я получаю большое количество маленьких блоков в секунду (30 ~ 40), поэтому их границы действительно маленькие (прямоугольник размером 100X80 пикселей, например), поэтому перерисовка всего растрового изображения снова не требуется... Быстро Обновление полного экрана изображение убьет производительность...

Надеюсь, мое объяснение было ясным.

Ожидаем ответа.

Спасибо.

Ответ 1

Было бы стыдно оставлять этот вопрос без ответа. В моих тестах примерно в 10 раз быстрее, когда вы обновляете небольшие части окна изображения. В основном это интеллектуальная недействительная (отменяет только обновленную часть растрового изображения, учитывая масштабирование) и умную картину (рисует только недействительную часть поля изображения, взятую из 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, поэтому мы должны убедиться, что она в конце концов немного больше, чем нужно, но не менее.

Ответ 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, хотя.

Ответ 3

Пожалуйста, внимательно прочитайте следующий документ:

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

Я прочитал его, и он очень помогает в понимании приложения для совместного использования рабочего стола.