Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Звук, графика и видео > Прорисовка массива пикселей на граф. компонент


Автор: RESIN 7.12.2010, 04:02
Здравствуйте уважаимые!
Очень нужна помощь.

Имеется двумерный массив значений цветов пикселей Picture[y, x] типа array of array of longint. Массив достаточно большой, ~3000х3000 значений (=пикселей). Требуется сравнительно быстро ( > 30 кадров в секунду) выводить этот массив на любой графический компонент (по вашему усмотрению), который позволит создать события OnDblKlick, OnMouseMove, OnMouseDown и OnMouseUp. К тому же нужно, чтобы алгоритм вывода был не чувствителен к изменению размеров и позиции выбранного компонента, а при изменении позиции (Left и Top) изображение не "мерцало".

Уже пробовал Scanline-ами, BitBlt-ом, Handle-ом, QCanvas-ом (на ассемблере). Тормозит до жути. smile 
С OpenGL не разобрался до конца, сам дошел только до вот такого извращения:
Код

  glPointSize(1);
  glBegin(GL_POINTS);
  For y:=0 to Texture.Height-1 do
      For x:=0 to Texture.Width-1 do
      Begin
        pix:=Texture.Picture[y, x];
        glColor3ub (((pix ShL 24) ShR 24), ((pix ShL 16) ShR 24), (pix ShR 16));
        glVertex(x, y);
      end;
  glEnd;
  swapBuffers(DC);

Но это дебилизм полнейший, хоть и работает (иногда появляются вертикальные черные полоски толщиной 1px).

Пожалуйста, помогите решить задачу с учетом того, что выводить нужно ИМЕННО двумерный массив, описанный мной в пользовательских типах., не битмап, не спрайт, не PNGObject, а именно массив. Буду крайне рад готовому примеру (ну эт я уже обнаглел smile ).
Готов рассмотреть любые варианты: GDI, OpenGL, DirectX (DirectDraw) и любой другой.

Автор: x128 7.12.2010, 10:43
Цитата

...Массив достаточно большой, ~3000х3000 значений (=пикселей). Требуется сравнительно быстро ( > 30 кадров в секунду)...
...К тому же нужно, чтобы алгоритм вывода был не чувствителен к изменению размеров и позиции выбранного компонента...

1) Массив полностью обновляется >30 раз в секунду?
2) Что подразумевается под "не чувствителен к изменению размеров и позиции выбранного компонента", нужно выводить определенную часть массива?

Автор: Bitter 7.12.2010, 14:55
Можешь использовать устаревший DIrectDraw, но хоть он и устарел, он именно для этих целей и очень простой. В общем он для быстрого копирования растра на экран.

Автор: RESIN 7.12.2010, 15:30
to x128:

Полностью обновляется >90 раз в секунду. Массив может менять свои размеры в зависимости от заданного масштаба. В програме так же доступен скроллинг. По-этому при выводе компонент должен менять свой размер, а при скроллинге - позицию.

to Bitter:

А как это сделать? Подскажите, пожалуйста. И где взять SDK DirectDraw для Delphi.

Автор: Bitter 7.12.2010, 16:35
скачай книгу М Краснова "DitectX Графика в проектах Delphi". Вот ссылка. http://www.delphilab.ru/files/book/krasnov_dx.7z К ней прилагаются и примеры и модули для подключения DirectX. Там первая половина книги как раз про DirectDraw, но тебе не нужна вся половина, тебе достаточно будет несколько первых глав.

Автор: Alexeis 7.12.2010, 17:16
Цитата(RESIN @  7.12.2010,  16:30 Найти цитируемый пост)
Полностью обновляется >90 раз в секунду. Массив может менять свои размеры в зависимости от заданного масштаба

  Нет смысла рисовать чаще чем 30 раз в секунду. Лишние затраты будут. По поводу скорости, то можно попробовать SetDIBitsToDevice

Автор: RESIN 7.12.2010, 17:42
Спасибо большое, Bitter! Скачал, разбираюсь, нравится.
Правда уже при попытке запустить пример Ex10 из Chapter02 возникла проблема. Впринципе, не важно, какой пример, CodGear Rad 2009 ругается на строку #173 в модуле DirectDraw.pas:
Код

      lpDDSZBufferDest            : PDirectDrawSurface; // Surface to use as Z buffer for dest

Пишет: [DCC Error] DirectDraw.pas(173): E2154 Type 'IDirectDrawSurface' needs finalization - not allowed in variant record
Не знаешь, как решить сию проблему?

Цитата(Alexeis @ 7.12.2010,  17:16)
  Нет смысла рисовать чаще чем 30 раз в секунду. Лишние затраты будут. По поводу скорости, то можно попробовать SetDIBitsToDevice

Чаще и не нужно )) Просто сейчас я иногда не получаю и 15 кадров в сек., и это косяк именно вывода на экран (проверял).
По поводу SetDIBitsToDevice - а она правда сможет принять в качестве параметра мой массив?

Автор: Bitter 7.12.2010, 18:30
RESIN, попробуй написать вместо 

Код

lpDDSZBufferDest            : PDirectDrawSurface;


вот так:

Код

lpDDSZBufferDest            : pointer;


а если он тебе понадобится, то использовать приведение типов. но я думаю не понадобится

Добавлено через 1 минуту и 47 секунд
По поводу той ошибки, я так понял, что в делфи 2010 (как и в Делфи 5) нельзя включать COM объекты в вариантные записи, бла бла бла и так далее

Автор: Alexeis 7.12.2010, 19:17
Цитата(RESIN @  7.12.2010,  18:42 Найти цитируемый пост)
По поводу SetDIBitsToDevice - а она правда сможет принять в качестве параметра мой массив?

  Нет конечно, но операция копирования байтов в DIB Bitmap не такая уж трудоемкая по сравнению с операцией вывода на экран (по времени) средствами GDI. На самом деле это отнимет времени не превышающего 10% от времени отрисовки (зависит от производительности процессора), поэтому ею можно пренебречь. 

Автор: AntonN 7.12.2010, 23:59
А вы найдете такой монитор, чтобы вывести все 3000*3000 пикселей?
Вывод на экран осуществляется путем копирования куска этого массива ил ресайзом массива до размеров контрола?

Автор: AntonN 8.12.2010, 00:41
Если нужно выводить часть массива, то выводи в битмап только часть его, а сам битмап ограничь размерами контрола. Вывод на контрол через BitBlt() (это очень быстрая функция по сравнению с остальным GD).
Однако собрав тестовый проект я не смог без тормозов изменять данные хотя бы 30 раз в секунду по всему массиву (рандомный нойс по всем каналам), уже один проц полностью напрягался (коре 2 дуо 6420). Вывод же сам по себе по диспетчеру занимает 0-1%.
Прилепил тестовый проект (только ехе, проверить напряжность операции) - http://desksoft.ru/index.php?downloads=attachments&id=304 (380кб), исходный массив за кадр изменяется путем рисования пяти линий, ума не приложу как изменять его полностью, это гигабайт в секунду (при 30 кадрах) нужно проворачивать, писать и читать в память - нехило так.

ЗЫ курсорными клавишами - "навигация"

Автор: Bitter 8.12.2010, 01:39
Думаю всё же под "полностью обновляется" автор имел в виду "частично обновляется", потому, что действительно гиг в секунду переписать это анбиливбл какой-то.

Хотя вот я только что попробовал в фотошопе картинку 3000х3000 прогнать через фильтр "шум", ну замерить время выполнения я не знаю как, но на глаз миллисекунд 50 заняло это, так что теоретически это можно делать 20 раз в секунду как минимум.

да, и если уж нужно всё таки обновлять такой массив, то это делается частичным обновлением видимой области. 

Автор: AntonN 8.12.2010, 01:58
Это реально, но задача так нефигово ресурсоемкая, в плане пропускной способности (если тупо переписывать из какого то массива в этот).
Фотошоп может использовать универсальные методы, потому может позволить себе тормозить. В принципе, я могу понять когда массив обновляется кем-то (класс какой либо, процесс) в какой либо области, и нам не известна эта область (как у меня в архиве выше - рисуется линия с рандомными координатами), но нужно быть готовым увидеть любую часть такого массива. Тогда рекомендую делать как я выше сказал - в битмап загонять лишь часть массива, а сам битмап через BitBlt() (битмап нужен для буферизации, в onpaint контрола его же и рисовать на контроле). Тогда вывод получается вполне быстрым и ненапряжным.

Автор: RESIN 8.12.2010, 03:17
Заверяю всех, что макссив по крайней мере 2000х2000 пикселей у меня меняется с весьма высокой скоростью. Проверял так: создал таймер интервалом 5 сек., создал отдельный поток, на который в бесконечный repeat-until повесил преобразование массива (склейка из отдельных массивов того же типа с наложением некоторых поверх и некот. вычислениями). Там же сделал инкрементацию переменной, считающей колличество повторений цикла. В самом таймере обнулял значение кол-ва кадров, предварительно выведя его, деленное на 5, в капчу формы. Даже относительно старый Pentium 4 с тактовой 1,3 примерно пятьдесят раз в секунду мой массив обновлял. Потому что в преобразованиях я использовал указатели на строки массивов пикселей (индексы Y), а сами преобразования для каждого пикселя хорошо оптимизировал (битовые сдвиги вместо умножений и делений, вынесения общих множетелей и т.д.).
После формирования массив масштабируется исходя из вещественного коэффициента: создается еще один массив, в который переписывается выборка строк и столбцов из исходного массива. Выборка тоже быстрая, т.к. вычисление индексов производится в целых числах: использовал алгоритм Брезентхамма для инкрементирования индексов, если интересно - могу показать. Эту часть тоже включал в зацикленный поток - утяжелила на ~0,3 кадра в секунду.
А вот вывод отмасштабированного массива очень медленен. Если масштаб =0.2 (20%) то еще терпимо, а вот когда =1 (100%) - жестокие тормоза, даже на моем Core2 Duo E8600.
Насчет обрезки массива по видимой области контролла уже думал, но будет очень неудобно переписывать часть программы: у меня много действий происходит по нажатию клавиши мыми по контроллу, и сам скроллинг контролла происходит по движению мыши по нему с зажатой ЛКМ. Придется подумать над преобразованием координат. Сделать впринципе можно, но размер контролла почти равен размеру самой формы (за минусом бордеров, гор. и верт. скроллов и маленькой панельки сверху, кот. скрывается либо отображается по двойному ЛКМ), а форма, в свою очередь, может и на весь экран быть растянута. Боюсь, что даже обрезка массива до 1200х980 не даст требуемой производительности (хоть правда, будет быстрее, чем сейчас).

Добавлено через 2 минуты и 50 секунд
Да, и при скроллинге боюсь появления мерцаний и "артефактов", если вывод не будет скроллы догонять...

Автор: Alexeis 8.12.2010, 09:00
Цитата(RESIN @  8.12.2010,  04:17 Найти цитируемый пост)
А вот вывод отмасштабированного массива очень медленен. Если масштаб =0.2 (20%) то еще терпимо, а вот когда =1 (100%) - жестокие тормоза, даже на моем Core2 Duo E8600.

  Можно использовать метод ближайшего соседа + отказаться от floating poing для вычисления точки откуда брать цвет.

Автор: x128 8.12.2010, 10:05
Цитата

Полностью обновляется >90 раз в секунду. Массив может менять свои размеры в зависимости от заданного масштаба. В програме так же доступен скроллинг. По-этому при выводе компонент должен менять свой размер, а при скроллинге - позицию.

Если массив обновляется >90 раз в секунду (более 3гб в сек.), это уже за приделами производительности для большинства компьютеров даже при обычном копировании не говоря о прочих операциях. Самый разумный подход, это обновлять только видимую часть изображения(массива) и выводить этот участок на экран, при таком подходе нагрузка будет минимальная.

Автор: RESIN 8.12.2010, 14:09
Цитата(Alexeis @ 8.12.2010,  09:00)
+ отказаться от floating poing для вычисления точки откуда брать цвет.

Не понял? Вещественные числа для вычисления координат пикселей, рисуемых на экран под масштабом? Итак использую целочисленный алгоритм (спер идею у Брезентхамма).

Автор: AntonN 8.12.2010, 14:11
Цитата

Да, и при скроллинге боюсь появления мерцаний и "артефактов", если вывод не будет скроллы догонять... 

А у меня видел мерцание? =)

Затестил с ресайзом из fastlib, файсресайз и билинейный, оба позволяют отресайзить поле 1024*786 почти без тормозов (первый так вообще без них). Правда на линиях при таком ресайзе даже биленейного мало для качества. http://desksoft.ru/index.php?downloads=attachments&id=305

Автор: RESIN 8.12.2010, 14:16
Цитата(AntonN @ 8.12.2010,  14:11)
А у меня видел мерцание? =)

Так понимаю, делиться тем, как ты этого добился не собираешься? smile 
Кстати, если не менять размера, позиции Пеинтбокса, у меня тоже не мерцает. Но вот скорость не ахти...

Автор: AntonN 8.12.2010, 16:55
В onpaint рисуй буферный битмап на контрол, и не будет мерцания.
мне пока некогда выпилить из класса вещи, которые я не могу показывать smile к тому же массив линейный, а не двумерный (хз как он в памяти лежит).

Автор: RESIN 9.12.2010, 01:36
Цитата(AntonN @ 8.12.2010,  16:55)
к тому же массив линейный, а не двумерный (хз как он в памяти лежит).

Так и лежит smile В памяти все массивы линейны )
А проблема то в том и состоит, что буферный битмап долго рисуется даже со сканлайном. Ищу сейчас методы быстрого формирования битмапа...

Автор: AntonN 9.12.2010, 01:50
рисуется или формируется?

Автор: RESIN 9.12.2010, 05:13
Не так выразился, справедливое замечание ). Формируется медленно. BitBlt куда быстрее на канву Пейнтбокса происходит, в сравнении с самим формированием битмапа.

Автор: Alexeis 9.12.2010, 09:50
Цитата(RESIN @  9.12.2010,  06:13 Найти цитируемый пост)
 Формируется медленно. BitBlt куда быстрее на канву Пейнтбокса происходит, в сравнении с самим формированием битмапа. 

  Не может быть! Совсем наоборот ScanLine дает адрес обычной памяти. При помощи него я за секунду могу создать несколько тысяч кадров, но отрисовать лишь несколько десятков.

Автор: AntonN 9.12.2010, 19:42
Цитата

Не может быть! Совсем наоборот ScanLine дает адрес обычной памяти. 

может, сканлайн дает адрес, а мы кроме того что берем адрес еще им и манипулируем, вот тут и затык - как быстро заполнить данными битмап

Автор: Alexeis 9.12.2010, 20:14
Цитата(AntonN @  9.12.2010,  20:42 Найти цитируемый пост)
а мы кроме того что берем адрес еще им и манипулируем

  Адресация по указателю никак не медленнее чем элемента массива, так что не вижу ни каких оснований для тормозов. Кстати построчная обработка быстрее чем послолбцовая.

Автор: RESIN 10.12.2010, 03:02
Цитата(Alexeis @ 9.12.2010,  20:14)
Кстати построчная обработка быстрее чем послолбцовая.

100% вы правы! Только вот сама функция Scanline, видимо не очень быстро адрес возвращает.

Автор: RESIN 10.12.2010, 04:32
Вот так я сейчас отрисовываю, используя библиотеку QuickPixels с мастеров делфи: http://www.delphimaster.ru/articles/pixels/

Код

  BMP:=TBitmap.Create;
  BMP.PixelFormat:=pf24bit; // !!!
  BMP.SetSize(Texture.Width, Texture.Height);
  QP:=TQuickPixels.Create;
  QP.Attach(BMP);
  for y:=0 to Texture.Height-1 do
  begin
    Scanline:[email protected][y];
    for x:=0 to Texture.Width-1 do
    begin
      pix:=Scanline^[x];
      QP.SetPixel(x, y, ((pix shl 8) shr 24)+(((pix shl 16) shr 24) shl 8)+((pix shl 24) shr 8));
    end;
  end;
  BitBlt(PAINT_BOX.Canvas.Handle, 0, 0, Screen.Texture.Width, Screen.Texture.Height, BMP.Canvas.Handle, 0, 0, srccopy);
  BMP.Free;


Или вот так, со сканлайном, без QuickPixels:

Код

  BMP:=TBitmap.Create;
  BMP.PixelFormat:=pf32bit; // !!!
  BMP.SetSize(Texture.Width, Texture.Height);
  for y:=0 to Texture.Height-1 do
  begin
    Scanline:[email protected][y];
    BMP_Scanline:=BMP.ScanLine[y];
    for x:=0 to Texture.Width-1 do
      BMP_Scanline[x]:=Scanline^[x];
  end;
  BitBlt(PAINT_BOX.Canvas.Handle, 0, 0, Screen.Texture.Width, Screen.Texture.Height, BMP.Canvas.Handle, 0, 0, srccopy);
  BMP.Free;


Разници на своем E8600 не заметил.
Если есть идеи, как это можно улучшить, делитесь, пожалуйста smile Так же интересно ваше мнение о том, какой из способов лучше.

Автор: AntonN 10.12.2010, 09:12
Цитата

Адресация по указателю никак не медленнее чем элемента массива, так что не вижу ни каких оснований для тормозов. Кстати построчная обработка быстрее чем послолбцовая.

Преобразование типов при изменении байта по указателю, операции над longint при изменении байта по указателю smile
В то время как у меня "пиксели" напрямую копируются через регистр MMX в две операции. Вот о чем я - мало адрес получить, важно какими операциями в него писать.


RESIN, у меня там было два массива, один 3000*3000, второй по размеру битмапа вывода (он потом 1:1 выводился в битмап, который и рисовался). Быстрый ресайз (fast который, без качества) на меньший массив (так проще было сделать переключалку на полный показ массива и только на его часть), потом вывод. Постараюсь сегодня выпилить пример.

Добавлено через 1 минуту и 16 секунд
Ах да, самое главное - битмап создавать только один раз, а не при каждом выводе

Автор: Alexeis 10.12.2010, 09:42
Цитата(RESIN @  10.12.2010,  04:02 Найти цитируемый пост)
 Только вот сама функция Scanline, видимо не очень быстро адрес возвращает. 

  Так зачем же вызывать ее каждый раз? На самом деле ее достаточно вызывать всего 1 раз, после создания битмапа для получения адреса последней строки, что и будет адресом начала всего битмапа. Дальше строки идут одна за другой.

Цитата(AntonN @  10.12.2010,  10:12 Найти цитируемый пост)

Преобразование типов при изменении байта по указателю, операции над longint при изменении байта по указателю

  А я и не говорил что нужно адресовать байты.
Цитата(AntonN @  10.12.2010,  10:12 Найти цитируемый пост)
В то время как у меня "пиксели" напрямую копируются через регистр MMX в две операции. 

  А смысл? Запись в регистры mmx и вывод из них в память займет дополнительное время, в результате выигрыш от такого копирования будет исчезающе мал (по сравнению с той же CopyMemory). 

 smile 
Цитата(AntonN @  10.12.2010,  10:12 Найти цитируемый пост)
Ах да, самое главное - битмап создавать только один раз, а не при каждом выводе 

  Интересно, что такой же косяк я заметил в коде в механизме двойной буферизации VCL  smile . Подозреваю, что отрисовка юзер интерфейса не столь частая, поэтому тормоза не так заметны. 

Автор: AntonN 10.12.2010, 23:42
Цитата

А смысл? Запись в регистры mmx и вывод из них в память займет дополнительное время, в результате выигрыш от такого копирования будет исчезающе мал (по сравнению с той же CopyMemory). 

потому что я не копирую весь массив в битмап, а лишь часть его. CopyMemory тоже не волшебством работает, пока в тестах победил вариант с копированием через регистры MMX (по 4 байта в регистре, но с movq по 8, я обычно парой запускаю, это 16).
Еще одна фича - у битмапа в памяти строки наоборот.

Цитата

 А я и не говорил что нужно адресовать байты.

что быстрее:
Код

movq  mm0, [esi]
movq  mm1, [esi+8]
movq  [edi], mm0
movq  [edi+8], mm1

или 
Код

QP.SetPixel(x, y, ((pix shl 8) shr 24)+(((pix shl 16) shr 24) shl 8)+((pix shl 24) shr 8));

? вот о чем я, то что сканлайн дал адрес - это одно, то что нам в этот адрес надо записать (и главное как записать) - другое.

Автор: AntonN 11.12.2010, 00:10
Проект, выпиленый.
Кроме самого массива 3000*3000 создается дополнительный под размер вывода, на который ресайзится большой, потом этот меньший выводится в битмап, битмап на канву

Автор: RESIN 11.12.2010, 10:24
AntonN: Спасибо огромное за пример!  smile  Допилил его до минимума, разбираюсь )) На выводе, при развороте на весь экран, дает >50 кадров за 1000 тиков.

Добавлено @ 10:30
Вопрос теперь, ко всем. Какой код будет работать быстрее?
Этот:

Код

  A:=pix shr 24;
  B:=(pix shl  8) shr 24;
  G:=(pix shl 16) shr 24;  
  R:=(pix shl 24) shr 24;


или этот:

Код

  A:=pix shr 24;
  B:=(pix shr 16) and $000000FF;
  G:=(pix shr 8) and $000000FF;  
  R:=pix and $000000FF;


A, B, G, R : byte;
pix : longint;

Автор: x128 14.12.2010, 13:43
Быстрей будет такой код:
Код

type
  TRGB = packed record
    r,g,b,a: byte;
  end;

var
  p: TRGB;
  pix: integer;
...
  DWORD(p):=pix;


Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)