C # Обновление растрового изображения в картинке

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

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

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

Ждем ответа.

Благодарю.

 Slashy17 июл. 2016 г., 00:49
@ Только рисовать сверху.
 Slashy01 авг. 2016 г., 22:10
@hoodaticusGDI что означает? bitblt? я работаю сmemcpy по одной-единственной причине: он работает намного быстрее, чем использование затем указателей непосредственно .. не спрашивайте меня почему .. memcpy работает очень быстро .... в соответствии с моими тестами - что-то между 7-7,5 раз быстрее ... :)
 Stefano d'Antonio17 июл. 2016 г., 00:40
@ Слаши, тебе нужно очистить холст или просто нарисовать сверху?
 Slashy30 июл. 2016 г., 22:34
@MathiasLykkegaardLorenzen Я раньше не работал с профилировщиком, но доказывает ли это что-то еще, чтоqueryperformancecounter илиStopWatch класс не предоставляет? (извините за вопрос, я просто никогда не работал с этим раньше)
 Artavazd Balayan29 июл. 2016 г., 21:20
@ Slashy, как насчет деления большой картинки (1920X1080) на маленькие регионы (много PictureBoxes) и обновления маленьких областей, а не большой картинки?
 Artavazd Balayan29 июл. 2016 г., 21:26
@Slashy, вы также пробовали Invalidate () с Rectangle (обновленный регион) вместо .Refresh ()?
 hoodaticus01 авг. 2016 г., 20:39
Мое предложение состоит в том, чтобы использовать GDI для реальной живописи. Кроме того, на мой взгляд, если вы уже работаете с указателями, вы можете также перейти в небезопасную, а не работать через абстракцию IntPtr. Pixel24 * гораздо понятнее, чем IntPtr * = 3 и все.
 Slashy17 июл. 2016 г., 00:40
@ Ханс Пассант об остальной части вашего ответа, я постараюсь ответить завтра. Пора немного поспать :)
 Slashy30 июл. 2016 г., 22:41
@ArtavazdBalayan не реалистично .. я не могу сделать что-то подобное, ха-ха. это должно быть динамическое изображение (в контексте изменения размера). о недействительности - я попробую .. не знал, что этот метод перегружен ..
 Stefano d'Antonio17 июл. 2016 г., 00:22
Я думаю, что вы можете использоватьControl.CreateGraphics чтобы получить графику из графического окна, а затем нарисовать поверх нееGraphics.DrawImage(image, x, y) так что вы избегаете перерисовки начального изображения. Но я не знаю, насколько он эффективен, он все равно может подорвать его под капотом. Возможно, стоит провести тестирование производительности.
 Mathias Lykkegaard Lorenzen29 июл. 2016 г., 18:19
Рассматривали ли вы запуск этого кода через профилировщик? Как правило, это очень легко настроить производительность приложения.
 Mathias Lykkegaard Lorenzen31 июл. 2016 г., 09:44
@ Слаши, я действительно думаю, что может. Есть одна встроенная в Visual Studio. Попробуйте использовать это. Профилировщик является очень часто используемым инструментом при создании приложений, требующих высокой производительности.
 Hans Passant17 июл. 2016 г., 00:29
Измените пиксельный формат изображения на 32bppPArgb, он рендерит в 10 раз быстрее, чем все остальные. Убедитесь, что изображение никогда не нужно масштабировать, чтобы оно соответствовало картинке, ClientRectangle не помогает вообще. Никогда не используйте Refresh, вместо этого используйте Invalidate (Rectangle), где прямоугольник - это часть изображения, которая должна быть перерисована.
 Slashy17 июл. 2016 г., 00:37
@ Ханс Пассант изображения, которые я получаю в формате JPEG. .. (24bpprgb) .. Мне нужно отобразить масштабированные изображения ... это для общего экрана ... так что если кто-то имеет разрешение 1920X1080, и он делит экран с кем-то, кто имеет разрешение 1600X880, клиент не сможет чтобы увидеть весь экран ... Если изображение больше, чем его компьютерное разрешение, не будет возможности увидеть все изображение;) поэтому я думаю, что растяжение должно быть сделано здесь. Для правильного отображения экрана.

Ответы на вопрос(3)

Решение Вопроса

0 раз быстрее в моих тестах при обновлении небольших частей графического блока. Что он делает в основномумный аннулирование (делает недействительной только обновленную часть растрового изображения, учитывая масштабирование) иумная живопись (рисует только недействительную часть графического поля, взятую изe.ClipR,ectangle и учитывая масштабирование)

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, поэтому мы должны убедиться, что в конечном итоге это будет немного больше, чем нужно, но не меньше.

 Slashy17 авг. 2016 г., 12:01
Хорошо, спасибо
 Slashy04 авг. 2016 г., 22:43
Хорошо! Большое вам спасибо, вы не представляете, как это помогло :)
 Slashy15 сент. 2016 г., 19:09
Привет! долгое время, но я все еще ищу небольшие незначительные улучшения для этого, поправьте меня, если я ошибаюсь, но после вызоваInvalidate() метод здесьpictureBox1.Invalidate(targetRect); , тоpictureBox1.Update(); линия не нужна, так какInvalidate()  Этот метод также вызывает отправку сообщения рисования в элемент управления, что означает, что оно автоматически вызовет событие Paint. я прав?msdn.microsoft.com/en-us/library/598t492a(v=vs.110).aspx @ Иван Стоев
 Ivan Stoev04 авг. 2016 г., 14:44
(2) Да, я сделал. Также обратные коэффициенты масштабирования внутри действия обновления. (3) Нет, я не делал, все, что я делал до публикации ответа, играл с кодом, который вы видите на скрипке.
 Slashy02 янв. 2017 г., 13:33
 Ivan Stoev04 авг. 2016 г., 22:41
Вы не можете увидеть фактическую реализацию, потому чтоDrawImage простая обёртка к соответствующему методу Gdi + И Gdi + написан на неуправляемом C ++, и я не думаю, что исходный код доступен.
 Ivan Stoev04 авг. 2016 г., 14:49
Именно так. Даже если вы попытаетесь нарисовать за пределами этого прямоугольника, он все равно будет обрезан, поэтому я называю этоумная живопись.
 Slashy01 янв. 2017 г., 19:36
и с новым годом :) :)
 Slashy04 авг. 2016 г., 15:03
Спасибо огромное :)
 Ivan Stoev04 авг. 2016 г., 22:46
Добро пожаловать :) Если я найду что-то быстрее, я дам вам знать. Удачного кодирования!
 Slashy02 янв. 2017 г., 13:38
да .. но когда вы используетеUpdate()... вы заставляете перекрашивать ... так что я думаю, что я должен использовать одинUpdate() после всегоInvalidate() звонки .. (после прочтения и обработки целых областей в буферах) ... И последнее, хотя кто-то предложил мне ... проверить платформу WPF ... поскольку winForms не предназначены для очень быстрого рисования ... хотя это работает нормально прямо сейчас :) @Ivan Stoev
 Ivan Stoev01 янв. 2017 г., 19:22
Lol :) С новым годом!
 Slashy04 авг. 2016 г., 22:47
Я хотел бы услышать :) Еще раз спасибо!
 Slashy04 авг. 2016 г., 11:58
я вижу вид многих coruptted пикселей .. жду .. я предпочел заменитьimage переменная в вашем ответеinitial илиblock?
 Slashy04 авг. 2016 г., 22:34
меня это интересуетDrawImage реализуется ...Graphics класс Microsoft ... мне было интересно, могу ли я сделать немного быстрее его реализацию
 Ivan Stoev04 авг. 2016 г., 12:37
Вотиграть на скрипке с моим тестом. Скопируйте / вставьте его в новый проект WinForms, затем поиграйте сtestRect размер илиpb_Paint (закомментируйте все и раскомментируйте последнюю строку, чтобы увидеть разницу между частичным и полным изображением).
 Slashy01 янв. 2017 г., 19:21
Привет! Похоже, я не перестану беспокоить вас в течение следующих 5 лет :) После нескольких этапов разработки, я использую другой подход к обработке данных в моем проекте. Как написано ранее, это проект совместного использования экрана. краткое объяснение: каждый пакет, отправляемый с сервера клиенту, содержит измененную область (в виде растрового изображения jpeg), которую необходимо применить к исходному изображению на стороне клиента. Ваше решение идеально подходит для предыдущего подхода, где я читаю каждую область, применяю изменения к исходному изображению и рисую его, но теперь фактически каждый пакет содержитмало районы
 Slashy04 авг. 2016 г., 14:47
хорошо! и как вы получаете контроль обновления области в го события краски? это линия детали e.ClipRectangle?
 Slashy01 янв. 2017 г., 19:29
И я искренне считаю, что чтение каждой области, блокировка битов исходного растрового изображения, применение изменений и их перерисовка (конечно, не перед разблокировкой) может быть очень неэффективным. поэтому я в первую очередь прочитал все регионы, применил изменения, заблокировав / разблокировав растровое изображение только один раз (для всего полученного пакета), а затем с помощью инвалида, но на этот раз я должен сделать недействительным* весь экранТак как многие регионы изменились, и аннулирование выполняется только тогда, когда весь исходный буфер и все измененные области применяются к исходному изображению. Я надеюсь, что я объяснил проблему хорошо .... :)
 Slashy02 янв. 2017 г., 12:58
о ... я понимаю ... но запуск рисования для каждой области ... может быть также медленнее ... я думаю, что я просто сделаю недействительной каждую область после применения изменений ... и в конце я вызову событие Paint, используя " Update () '... (даже думал, что это будет объединение и будет содержать несколько лишних пикселей .. думаю, мне придется измерить эти 2 подхода.) Кстати ... что-то, чего я никогда не понимал, когда я использованиеInvalidate() это не вызывает событие рисования сразу же, верно? так, когда это красит это? (в случае нетUpdate() позвони позже). Огромное спасибо. Я очень ценю вашу помощь
 Ivan Stoev04 авг. 2016 г., 12:11
(1) Извините,image должен был бытьinitial переменная. (2) Нет, эта перегрузка рисует только часть изображения - imageRect.
 Slashy01 янв. 2017 г., 19:31
Есть ли еще способ сделать недействительными только измененные области? (Но только один раз! После применения всех областей. Например, для пакета, содержащего 4 измененных прямоугольника, я сделал следующее: 1.read area -> lockBits () - -> применить изменения -> unlockBits () -> invalidate (area) (что привело к немедленному перекрашиванию Picturebox). Теперь подход - это 1.for (int i = 0; i <4; i ++) область чтения, применить изменения (когда я говорю применить, я имею в виду только копировать его пиксели (используя небезопасные указатели). 2. Только тогда нужно перерисовать, но на этот раз весь экран. Я знаю, что это довольно долго, но я надеюсь, что у вас есть сомнения .. ,
 Ivan Stoev07 авг. 2016 г., 15:02
Я думал о чем-то вроде этого, но похоже, что оно не поддерживает масштабирование. Я не могу вспомнить, будет ли это работать, если установить исходное и / или целевое преобразование.
 Slashy04 авг. 2016 г., 12:03
и я не вижу смысла .. вашеще перерисовывая полныйinitial изображение ... здесьe.Graphics.DrawImage(initial, targetRect, imageRect, GraphicsUnit.Pixel);
 Slashy07 авг. 2016 г., 14:36
осматривался ... было бы возможно реализовать это с роднымbitblt метод? прокрутите вниз доOnPaint событие-они использовали этот метод для рисования вместоDrawImage() pinvoke.net/default.aspx/gdi32/BitBlt.html
 Ivan Stoev04 авг. 2016 г., 14:39
(1) Недействительный имеет хорошее объяснение вMSDN: Делает недействительной указанную область элемента управления (добавляет ее в область обновления элемента управления, которая является областью, которая будет перекрашиваться при следующей операции рисования), и вызывает отправку сообщения управления в элемент управления. а затем вПримечания: Вызов метода Invalidate не приводит к принудительному синхронному рисованию; чтобы вызвать синхронную отрисовку, вызовите метод Update после вызова метода Invalidate.
 Ivan Stoev15 сент. 2016 г., 19:13
Правильно. Я положил его, чтобы проверить скорость всей операции, включаяDrawBitmap, Нет технической необходимости форсировать немедленное применениеInvalidateд регион, так что вы можете удалитьUpdate позвоните и дайте ему перекрасить, когда Windows решит.
 Ivan Stoev02 янв. 2017 г., 13:26
Когда вы делаетеInvalidateсоответствующий прямоугольник добавляется в недействительную область окна, иWM_PAINT сообщениеотправил к окну. Позже он обрабатываетсяWndProc который запускаетPaint событие. Хитрость заключается в том, что сообщения рисования всегда хранятся в конце очереди сообщений, поэтому они обрабатываются после всех других сообщений, таким образом сохраняя перерисовки на минимуме. Таким образом, если есть сотни недействительных, обычно они вызывают одну краску.
 Slashy17 авг. 2016 г., 11:20
может я что-то не так делаю? извините за многие вопросы .. я действительно ценю вашу помощь :). Обратите внимание, я не использовал метод масштабирования, потому что мне просто не нужно, если для этого теста.
 Slashy04 авг. 2016 г., 14:30
классно! это работает в 2 ~ 3 раза быстрее! Несколько вещей, которые я хотел бы понять: 1) Не могли бы вы дать краткое объяснениеinvlidate метод? это перерисовывает регион? я не уверен в этом :) 2) вы только что рассчитали коэффициент масштабирования в событии рисования, верно? 3) не очень нужно, но если у вас есть хотя бы несколько микрооптимизаций для этого, я хотел бы увидеть. В конце концов, я хочу лучшего выступления :) Большое вам спасибо.
 Ivan Stoev01 янв. 2017 г., 20:27
Теперь по теме. К сожалению, эта оптимизация не будет работать, потому что прямоугольник клипа будет объединением недействительных прямоугольников. И нет никакой другой оптимизации, о которой я могу думать. Графика имеет так называемыйClip регион, но я не знаю, как разложить его на прямоугольники. Единственный способ, который я вижу, это после внесения изменений сделатьInvalidate сразу же послеUpdate для каждого изменил прямоугольник.
 Slashy17 авг. 2016 г., 11:19
я продолжал играть с инвалидом и обновлять код .. похоже, что все еще есть обновление для всего растрового события, хотя я использовалInvalidate(Rectangle) только для определенной области ... я просто нарисовал над растровым изображением для его полных границ, я ожидал увидеть толькоtestRect область изменилась на зеленую, а остальное все еще остается синим (несмотря на то, что я нарисовал все растровое изображение), потому что вOnPaint событие только что конкретный регион обновляется! так что визуально я должен видеть, что только красный прямоугольник меняется на зеленый, а остальная синяя область должна остаться.dotnetfiddle.net/pssVe9
 Ivan Stoev17 авг. 2016 г., 11:26
Что вам нужно знать, это то, чтоe.ClipRectangle внутри события Paint не всегда тот, который вы использовали дляInvalidate вызов. Например, при первом рисовании графического блока он будет содержать весь прямоугольник клиента. Также, если вы измените размер поля формы / рисунка, то снова это может быть иначе. Это в основном, как работает Windows. Для вас важно минимизировать области перерисовки, вызванные обновлениями вашего растрового изображения.

вы можете нарисоватьначальная изображение только один раз, а затем использовать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 хоть.

 Stefano d'Antonio17 июл. 2016 г., 10:41
Я понимаю, что вы имеете в виду: вы рисуете изображения, а затем меняетеSizeMode? Если так, я не ожидал бы, что это будет работать какGraphics Blit изображения только один, он не сохраняет ваши изменения, поэтому, если вы измените размер PB, вам придется перерисовать все с нуля. Возможно, вы захотите взглянуть на некоторые сторонние элементы управления, WinForms не идеальны для пользовательской графики.
 Stefano d'Antonio17 июл. 2016 г., 10:06
@Slashy: я принималDraw Метод рисовал изображение в графическом окне, поэтому я подумал, что оно вызывалось дважды. Я не уверен, что вы имеете в виду под «поврежденными пикселями», которые в моем тесте работают нормально. Заметка в вашем коде заключается в том, что блокировка, вероятно, самая трудоемкая часть.
 Stefano d'Antonio17 июл. 2016 г., 10:57
Тогда я бы не стал полагаться наSizeMode для рисования я бы просто растягивал изображение, а затем рисовал и оставлял его нормальным. Но я все еще думаю, что WinForms PictureBox не подходит для этого.

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

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

Ваш ответ на вопрос