В общем, отдыхал я от интернета целых три месяца! Просто 1-го декабря прошлого года не продлил свой аккаунт и он заблокировался. Отдохнул от социальных сетей как следует... И вот сегодня я снова в сети! Естественно, я не сидел сложа руки перед тупым говорящим ящиком со стеклянным глазом. А потихонечку вспоминал все свои навыки в программировании. И, как всегда, решил заняться графикой. Но подойти с другой стороны. Так, если раньше я упрожнялся в бесконечном оттачивании алгоритмов построения линий и окружностей, то в этот раз я решил взяться за что-нибудь посложнее... Раньше в областях распознавания графических объектов я был полным нулём. Теперь и здесь имею маленький опыт. Так за месяц получилась крохотная утилита отделения заднего плана от остальных объектов кадра. Иными словами пародия на виртуальную студию. Функций у неё меньше чем у ManyCam, но и размер у неё в сотню раз меньше. Скорость же достигает 30-60 кадров 352x288 на процессоре Pentium-IV 2.4GHz, что позволяет сносно делает разные эффекты в линейном монтаже. Единственная неприятность, без VirtualDub она не работает. Так-как на винте не нашёл примеров доступа к платам видео захвата. По-этому, моя утилита после запуска сразу ищет окно VirtualDub, который обязательно должен быть в Capture Mode. К тому же, окно утилиты всегда "дырявится", чтобы не перекрывать активную область VirtualDub. Говоря иначе, костыль! Показал племяннице как в её сказках делают чудеса: Раздвоение персонажей, перемещение в пространстве, и т.п. Самым сложным оказалось найти ткань приличного размера голубого цвета. Да ещё правильно подсветить её, чтобы не было теней... Вот, решил было выложить свою работу в сеть как Open Source, однако друг долго настаивал на закрытом коде с финансовой выгодой. Конечно, под коммерческий проект программа врятли потянет, а вот для обучающихся графике и распознаванию образов, быть может, алгоритмы могут быть достаточно полезными. Вступление: Код (Text): /***************************************************************************** * * Задавшишь вопросом, отчего оффициальное программное обеспечение организации виртуальных студий имеет в основном закрытый код и распространяется в основном на платной основе, было решено опробовать собственные силы в этой области. Так за месяц работы над собственным алгоритмом, написанного с чистого листа, вышел довольно простой код, который, однако, обеспечивал сносную производительность. Как показала практика, организация всего алгоритма разбивается на несколько базовых функций. Прежде всего необходимо создать несколько битовых карт одного разрешения с Альфа-каналом. Как правило, по 32 бита на пиксел. Как минимум, их должно быть три: Source - Для хранения текущего захваченного кадра и сравнения с примерным; Sample - Хранит пример заднего плана. Идеально, может захватываться однажды; Result - Служит как промежуточный буфер комплексных данных и хранит результат. Также весь комплекс генерируемых данных разделяется на несколько классов: Low-Level Noise - Низкоуровневый шум, вызваный погрешностями АЦП видео тракта; High-Level Noise - Высокоуровневый шум, образованый бликом, насекомым и пылью; Spot - Пятно, низкоуровневый объект кадра. Самый крупный всегда первый; Cast - Объект среднего уровня с отслеживаемой позицией. Ступень #1: Первым основным этапом является анализ приходящего изображения и сравнением с зафиксированным изображением заднего плана. Каждый пиксел проверяется и если разность хотябы одной из цветовых составляющих оказывается выше установленного порога, пиксел помечается альфа-маской как отличающийся. Но, полученное альфа- изображение, как правило, содержит много шума и не годится к непосредственному использованию в качестве маски. Ступень #2: Вторым основным этапом является анализ получившейся альфа-маски изображения и выявление всех полезных объектов изображения с подавлением паразитных. Самый простейщий и удобный способ заключается в алгоритма заливки. Так, классические способы заливки в этом случае не могут выполнить свою задачу и необходима своя функция. Заливка производится только в альфа-канале и все отличающияся пикселы проверяются на принадлежность к определённой группе, одновременно индексируясь порядковым номером пятна в проходе по всему изображеную. Если их количество не превышает установленный порог, пятно считается паразитным и удаляется в альфа- канале чисткой всех бит. Пятна, содержащие большее число пикселей, остаются со своим индексом, а количество пикселей заносится в буфер. Всего допускается до 254 пятен, которые затем сортируются в порядке размера от крупного к мелкому и весь альфа-канал подвергается переиндексации всех этих пикселей с усечением индекса до первых самых крупных пятен из 16. Ступень #3: Теперь всего этого достаточно, чтобы заменить задний план изображения своим и можно объекты с определёнными индексами переместить, развернуть, отразить, а также просто скрыть. Однако, манипуляция над объектами по порядку их размера в кадре достаточна лишь при статическом изображении. В динамике же всё будет так или иначе путаться. Поэтому все объекты необходимо пересортировать по позициям и отслеживать их перемещение. А это решается простыми геометрическими приёмами с минимальными затратами на вычисление. ****************************************************************************** В целом, всё приложение составляется из нескольких описанных функций, а те, в свою очередь, делятся на две группы: написанные на ассемблере для достижения максимальной производительности и на описанные средствами высокоуровнего языка для повышения понимания логики с целью представления в Open Source проекте. Но при этом автором не распространяется ассемблерная часть кода в листинге, тогда как она может беспрепятственно восстанавливаться из соответствующей библиотеки практически как есть. Весь проект состоит всего из нескольких в одном /inc/ подкаталоге: /inc/main.h - Содержит базовые процедуры построения интерфейса; /inc/defines.h - Содержит определение нужных значений и псевдо-функций; /inc/types.h - Содержит описание всех необходимых типов и структур; /inc/vars.h - Включает в себя все переменные, необходимые для работы; /inc/algs.h - Описывает все ключевые процедуры работы с изображением; /inc/algs.x86.h - Оптимизированные варианты ключевых процедур; /inc/procs.h - Все остальные функции, в основном, служащие интерактивности. ****************************************************************************** Интерфейс приложения представляется несколькими закладками со всеми нужными настройками параметров, а также интерактивной статусной строкой. Помимо данных о скорости обработки кадров и сложности, статусная строка содержит и несколько кнопок управления режимом, дублируемые клавишами F9, F10, F11 и Pause/Break, а также меню отображения/скрытия любого из объектов в кадре. Здесь полный список клавиш и сочетаний с их функциями: Ctrl+A- Переключение на закладку Samples; Ctrl+S- Переключение на закладку Source; Ctrl+D- Переключение на закладку Order; Ctrl+C- Переключение на закладку Compare; Ctrl+E- Переключение на закладку Preview; F9 - Включить/выключить режим "зеркало"; F10 - Включить/выключить режим захвата заднего плана; F11 - Включить/выключить режим двухкратного вида; F12 - Включить/выключить режим ускорения; Pause - Включить/выключить режим захвата видео. * * *****************************************************************************/ И на последок. Напоролся я на уйму простых геометрических проблем. Например, очерчивание объектов в кадре контуром. Сначала использовал известные приёмы с комбинированием регионов, но они очень тормозят. А собственный алгоритм, который далее представлю, даёт сбои на зазубринах. В общем, по интересующим меня вопросам буду обращаться ниже. А пока, вот ссылка на демо-видео, где племянница демонстрирует работу иннерцоида в корыте )) http://www.youtube.com/watch?v=pvkpDobVZ44
Итак, прошлой ночью гуглил по вопросу построения контура некоторой фигуры, однако попадались либо сильно примитивные алгоритмы, либо только математическая модель решения. Разработаный мною алгоритм способен построить контур фигуры, но он сбоит и практически не годится в моей "виртуальной студии". Суть алгоритма заключается в реализации маленького бота "черепашки", который "проползает" по всему контуру и заносит координаты точек границы в массив. Ползти бот может в одном из восьми направлений. А поворачивать может либо на 45°, либо на 90° в обоих направлениях. Основу "мозгов" бота составляет 5 "сенсоров" вокруг "головы" бота, собирающих информацию об окружающей обстановке. Вот описание расположение сенсоров в графическом фрейме и их биты в статусе: Код (Text): +45° +0° -45° | 0x04 0x10 0x02 +90° Head -90° | 0x08 0x01 Тем самым, если "мина" находится впереди, получим код 0x10. А если впереди и впереди-слева, то код 0x14. И т.д. Далее, строим таблицу ассоциативного массива с постулатами действий бота. Так, если слева всё чисто, а справа и спереди - мины, свернём влево на 90°. Тем самым, в таблицу введём постулат 0x217, где 2 - 2x45°, а 17 - мина впереди(1) и по всем бортам, кроме крайне-левого(8): Код (Text): +45° +0° -45° | 0x04 0x10 0x02 | Mine Mine Mine -> 0x04 0x10 0x02 +90° Head -90° | 0x08 0x01 | Good +90° Mine -> 0x00 0x01 Однако, будут попадаться ситуации тупика, когда необходимо ориентироваться по обстановке предыдущего шага. По-этому необходимо запоминать предыдущую ситуацию. А дополнить таблицу такими исключительными постулатами. Чтобы обеспечить реверс бота при одинаковых, но зеркальных условиях, при этом не дублировать всю таблицу, биты статуса нужно продублировать в слове, но в зеркальных позициях. Например, 0x0108 или 0x0402 или 0x030C и т.д. Код алгоритма по-очереди сравнивает статус с младшим словом таблицы, а затем со старшим. Если совпадение было в младшем, бот поворачивается на положительный угол. Если совпадение в старшем, соответственно на такой же, но отрицательный угол. Таким образом, код таблицы 0x020015 означает ситуацию: Код (Text): Mine Mine Good Good Mine Mine Good +90° Mine или зеркальное Mine -90° Good А код 0x011C1A означает исключение: Код (Text): Good Mine Mine Mine Mine Good Mine +45° Good или зеркальное Good -45° Mine если предыдущая ситуация выглядила как Mine Mine Good Good Mine Mine Mine Good или зеркальным Good Mine Всего этого достаточно, чтобы описать любое сложное шаговое поведение нашего бота. Ниже представлен полный код программы на Си. Сам алгоритм: Код (Text): #include <math.h> typedef DWORD RGBA, *PRGBA; // Needed for operations with bitmap #define BMP_WIDTH 640 // Bitmap width #define BMP_HEIGHT 480 // Bitmap height #define BMP_SQUARE BMP_WIDTH * BMP_HEIGHT // Bitmap square in pixels #define MAX_POINTS 8192 // Maximal dimension of contour #define PI 3.141592f #define XY(x, y) ((y) * BMP_WIDTH + (x)) // Compute address in bitmap array by X,Y-position #define PIXEL(b,x,y) b[XY(x, y)] // Access to pixel HBITMAP hBmp; // Our bitmap HDC hBmpDC; // Bitmap context for output PRGBA pBmp; // Pointer to bitmap array POINT point[MAX_POINTS]; // Pixels of contour PPOINT pPoint = &point[0]; // Pointer to current pixel of contour RECT rt; DWORD around = 0x89ABCDEF; char dir; long i; static const DWORD turning[] = // Table of principles { 0x010003 ,0x010004 ,0x010007 ,0x010016 ,0x01001E ,0x020001 ,0x020005 ,0x020011 ,0x020015 ,0x011C1A ,0x011C16 ,0x01170B ,0x020305 ,0x02030D ,0x02081F ,0x020A1F /* ,0x020C0F ,0x020C1F ,0x020E00 ,0x020E1F ,0x02151D ,0x021705 ,0x02170D ,0x02171D ,0x021C1F ,0x021E1F ,0x021E00 ,0x030300 ,0x030304 ,0x031300 ,0x031304 ,0x031312 ,0x030314 ,0x031704 ,0x031710 ,0x031714 ,0x001C1E ,0x02181F ,0x01110F ,0x021109 ,0x021B0D ,0x020B1F*/ }; static const // Table of position displace by direction char dirs[] = {+0, +1, +1, +1, -0, -1, -1, -1}; #define GetAt(a) ((PIXEL(dst, pPoint[0].x + dirs[((a) + *dir) & 7], pPoint[0].y + dirs[((a) + *dir + 6) & 7]) >> 24)) #define GoTo(a) pPoint[0].x += dirs[((a) + *dir) & 7], pPoint[0].y += dirs[((a) + *dir + 6) & 7] void Look_About(DWORD *dst, PPOINT &pPoint, PDWORD around, PCHAR dir) { DWORD code, turn; DWORD q = 0x0; char dirl; if(*around != 0x89ABCDEF) { // Are we into object? Yes! if((*around & 0x0000001F) != 0x00000000 && (*around & 0x0000001F) != 0x0000001F) *around <<= 8; // Save last environment *around &= 0x1F001F00; if(pPoint[0].y < 2 || pPoint[0].x < 2) return; if(GetAt(+2)) *around |= 0x00080001; // Looking for right-side or flipped left-side if(GetAt(+1)) *around |= 0x00040002; // Looking for right-corner or flipped left-corner if(GetAt(+0)) *around |= 0x00100010; // Looking forward if(GetAt(-1)) *around |= 0x00020004; // Looking for left-corner or flipped right-corner if(GetAt(-2)) *around |= 0x00010008; // Looking for left-side or flipped right-side PIXEL(dst, pPoint[0].x, pPoint[0].y) &= 0xFFFF0000; // Visual markup of pixel dirl = *dir; for(i = sizeof(turning) / sizeof(*turning); i --;) {// Scan for postulate turn = turning[i]; if(turn & 0x00FF00) // Prepare to turns code = *around & 0x00FFFFFF; else code = *around & 0x000000FF; if((turn & 0x00FFFF) == (code & 0x00FFFF)) { *dir += (turn & 0x00030000) >> 16; // Turn to ... break; } if(turn & 0x00FF00) // Prepare to reverse code = (*around >> 16) & 0x0000FFFF; else code = (*around >> 16) & 0x000000FF; if((turn & 0x00FFFF) == (code & 0x00FFFF)) { *dir -= (turn & 0x00030000) >> 16; // Reverse to ... break; } } *dir += 8; *dir &= 7; if(dirl != *dir) { // Did we turned? Yes, let's save point pPoint[1].x = pPoint[0].x; pPoint[1].y = pPoint[0].y; ++ pPoint; } GoTo(0); // Do one step } else { for(long y = 1; y < BMP_HEIGHT - 1; ++ y) // Are we into object? No ... for(long x = 1; x < BMP_WIDTH - 1; ++ x) { // Let's find to border of object if(PIXEL(dst, x, y) >> 24) { pPoint->x = x; pPoint->y = y; ++ pPoint; pPoint->x = x; pPoint->y = y; *around = 0; *dir = 2; return; } } } } BOOL Do_Step(HDC hDC) { TCHAR text[32]; UINT state; i = pPoint - point; if(i > 1 && (i >= MAX_POINTS | (abs(point[0].x - pPoint->x) <= 2 && abs(point[0].y - pPoint->y) <= 2))) { for(i = 0; i < pPoint - point; ++ i) point[i].y = BMP_HEIGHT - point[i].y - 1; SelectObject(hDC, GetStockObject(GRAY_BRUSH)); Polygon(hDC, point, i); return false; } else { Look_About(pBmp, pPoint, &around, &dir); } i = 40; BitBlt(hDC, 0, 0, BMP_WIDTH, BMP_HEIGHT, hBmpDC, 0, 0, SRCCOPY); StretchBlt(hDC, BMP_WIDTH, 0, 64, 64, hBmpDC, pPoint->x - 7, BMP_HEIGHT - pPoint->y - 8, 15, 15, SRCCOPY); rt.bottom = BMP_HEIGHT; rt.left = BMP_WIDTH; rt.top = rt.bottom - i + 1; rt.right = rt.left + i - 1; state = around & 0x08 ? DFCS_CHECKED : NULL; state |= around & 0x0800 ? NULL : DFCS_INACTIVE; DrawFrameControl(hDC, &rt, DFC_BUTTON, state); OffsetRect(&rt, 0, -i); state = around & 0x04 ? DFCS_CHECKED : NULL; state |= around & 0x0400 ? NULL : DFCS_INACTIVE; DrawFrameControl(hDC, &rt, DFC_BUTTON, state); OffsetRect(&rt, i, 0); state = around & 0x10 ? DFCS_CHECKED : NULL; state |= around & 0x1000 ? NULL : DFCS_INACTIVE; DrawFrameControl(hDC, &rt, DFC_BUTTON, state); OffsetRect(&rt, i, 0); state = around & 0x02 ? DFCS_CHECKED : NULL; state |= around & 0x0200 ? NULL : DFCS_INACTIVE; DrawFrameControl(hDC, &rt, DFC_BUTTON, state); OffsetRect(&rt, 0, i); state = around & 0x01 ? DFCS_CHECKED : NULL; state |= around & 0x0100 ? NULL : DFCS_INACTIVE; DrawFrameControl(hDC, &rt, DFC_BUTTON, state); OffsetRect(&rt, -i, 0); wsprintf(text, "%02X|%02X", LOBYTE(around), HIBYTE(around)); DrawStatusText(hDC, &rt, text, SBT_POPOUT); return true; } И необходимые вставки: Код (Text): LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { BITMAPINFOHEADER bh; int times; ... case WM_TIMER: hdc = GetDC(hWnd); Do_Step(hdc); ReleaseDC(hWnd, hdc); break; case WM_KEYDOWN: hdc = GetDC(hWnd); switch(LOWORD(wParam)) { case VK_ESCAPE: while(Do_Step(hdc)) continue; times = 0; break; case VK_RETURN: times = 100; break; default: times = 1; } while(times --) Do_Step(hdc); ReleaseDC(hWnd, hdc); break; ... case WM_DESTROY: DeleteObject(hBmp); ReleaseDC(NULL, hBmpDC); PostQuitMessage(0); break; case WM_CREATE: // Create out bitmap and building complex spot ZeroMemory(&bh, sizeof(bh)); bh.biSize = sizeof(bh); bh.biWidth = BMP_WIDTH; bh.biHeight = BMP_HEIGHT; bh.biPlanes = 1; bh.biBitCount = 32; hBmp = CreateDIBSection(NULL, PBITMAPINFO(&bh), DIB_RGB_COLORS, (PVOID*)&pBmp, NULL, NULL); hBmpDC = CreateCompatibleDC(NULL); SelectObject(hBmpDC, hBmp); SelectObject(hBmpDC, GetStockObject(WHITE_PEN)); SelectObject(hBmpDC, GetStockObject(WHITE_BRUSH)); for(i = 0; i < 360; ++ i) { if(i < 270) point[i].x = long(BMP_WIDTH / 2 + float(sin(float(PI * i / 170.0f)) * (rand() % 2 + BMP_WIDTH / 2 - 4))), point[i].y = long(BMP_HEIGHT / 2 + float(cos(float(PI * i / 170.0f)) * (rand() % 2 + BMP_HEIGHT / 2 - 4))); else point[i].x = long(BMP_WIDTH / 2 - float(sin(float(PI * i / 60.0f)) * (rand() % 2 + BMP_WIDTH / 3 - 4))), point[i].y = long(BMP_HEIGHT / 2 + float(cos(float(PI * i / 40.0f)) * (rand() % 2 + BMP_HEIGHT / 3 - 4))); } Polygon(hBmpDC, point, i); ZeroMemory(&point, sizeof(point)); point[MAX_POINTS - 1].x = -1; point[MAX_POINTS - 1].y = -1; // Move image from RGB to Alpha for(i = 0; i < BMP_SQUARE; ++ i) pBmp[i] |= pBmp[i] ? 0xFF000000 : 0x00000000; //SetTimer(hWnd, 1, 7, NULL); break; Думаю, это всё. Кто решит проверить алгоритм в действии, без проблем вставит это в пустой Си-проект.
Оказалось оффициально такие алгоритмы называются "жуками", ползущими точно по контуру объектов. Правда многие из алгоритмов работают с 8-ю и 24-я соседями и принцип там сложнее. Вот ниже я выкладываю готовый проект. После компиляции и запуска можно смело нажать ESC и дождаться построения всего контура. Если в цикле WM_CREATE изменить параметры в строчках с sin/cos, можно изменить форму фигуры. При этом нажатие на ESC может привести к бесконечному циклу и ошибке. Потому советую после изменения фигуры протрассировать её. Для этого следует нажимать ПРОБЕЛ или ENTER. Пробелом делается один шаг жука, в правом-верхнем углу окна отображается Zoom ближайщего пространства жука. А в правом-нижнем углу окна выводится "зрение" жука: Состояние пяти сенсоров. Галочками помечается текущий статус, серостью - предыдущий. В центре - Hex-коды текущей обстановки и предыдущей. Именно в соответствии с этими кодами и строилась таблица "рефлексов" жука. Enter делает серию шагов. Это всё. Вот листинг на языке Си:
Просьба не расценивать это сообщение как рекламу. В настоящее время в мире создано множество редакторов, эл таблиц, графических пакетов, бесплатных и пиратских, но активно продолжается создание их клонов, созданных одним программистом. Чисто накопление опыта, хобби, личный интерес. Пройдет еще немного времени и Вы от создания простейшего бота с запрограммированным интеллектом перейдете к созданию простой игры. А начнется это с выбора подходящего игрового движка. Пожалуй, создание игр - это единстенная перспективная отрасль программирования. Возьмем, например S.T.A.L.K.E.R. Зов Припяти. После прохождения игры можно гулять по просторам трехмерной реальности, воссозданной авторами игры с фотографической точностью. 3D-ввижок xRay правильно воспроизводит геометрию и перспективу объектов, что снижает нагрузку на центральную нервную систему. Стоит прочитать документацию к игре, в которой упоминается, что неправильное сочетание цветовых пятен в игре может приводить к быстрой утомляемости и вызвать приступы эпилепсии. Честно, на таких играх отдыхаешь. Пожалуй, только растительный покров и деревья сделаны несколько топорно. В Интернете можно найти бесплатный движок трехмерной реальности OGRE с открытым кодом. Мне понравилась демка со Стоунхендом (magicofstonehenge - 8.7мег). Его можно использовать для своих проектов, презентаций и тд. После дополнения его необходимыми фичами из него можно сделать игровой движок с ботами.
Написaл фильтр, в функцию которого входит копирования буфера кадра источника в буфер приёмника. Но, с небольшой продвинутой фишкой: Каждый пиксел - 32 бита RGB+Alpha. Если Alpha == 0, то копируется всё линейно и быстро (метка moveARGB_Y). В противном случае, 24 бита RGB - это X и Y по 12 бит, указывающие координаты читаемого пиксела для подстановки. А сама Alpha указывает на индекс потока (видео с выбранной веб-камеры или avi). Всего потоков - 9. Так как Alpha - 8 бит, младшая тетрада - индекс 1-9 указывающий на конкретный поток, а 0 - текущий. Причём, нет разницы в разрешении выбранного потока, так как всё перемножается. Если нужно обойтись без коррекции координат, используются индексы 10-15, что являются теми же потоками 6-1, но с абсолютными координатами. Как Вы поняли уже, код достаточно замороченный. В режиме реального времени фильтр может копировать видео с восьми разных источников в один общий. Почему бы не использовать, например, OpenGL? Он используется, но нетрадиционно. Он "натягивает" с перспективой несколько текстур с координатными сетками, которые и образуют X/Y-координаты в RGB-битах. То есть, запускается оболочка, которая средствами OpenGL прорисовывает план сцены. Затем, оболочку можно закрыть (чаще - она обрушается сама), если конечно не нужна динамика колладжа из разных видео. В любом случае, представленный ниже код всё делает сам. Код (ASM): mov ecx,bitmap mov esi,src mov edi,dst mov eax,h mov j,eax jmp moveARGB_Y moveARGB_N: pop edx jmp moveARGB_I moveARGB_M: push edx mov ebx,pFilterData[ebx].pBlanks[eax*4] mov eax,0x000FFFFF and eax,edx shr eax,10 mov edx,1024 neg eax lea eax,[eax+edx] mov edx,PEUR_BITMAP[ebx].bih.biYPelsPerMeter cmovnc edx,PEUR_BITMAP[ebx].bih.biYPelsPerMeter mul edx xor eax,0x000FFC00 and eax,0x000FFC00 xchg eax,[esp] and eax,0x000003FF mov edx,1024 cmp edx,PEUR_BITMAP[ebx].bih.biXPelsPerMeter cmovnc edx,PEUR_BITMAP[ebx].bih.biXPelsPerMeter mul edx pop edx shr eax,10 add eax,edx mov eax,PEUR_BITMAP[ebx].raster[eax*4] jmp moveARGB_I moveARGB_L: mov edx,[ecx+1024*1024*4] rol edx,8 bswap edx mov eax,edx test eax,0x00F00000 je moveARGB_J moveARGB_K: mov ebx,pfd shr eax,20 and eax,0x0F je moveARGB_J cmp al,10 jc moveARGB_M or eax,0xFFFFFFF0 neg eax and edx,0x000FFFFF mov ebx,pFilterData[ebx].pBlanks[eax*4] mov eax,PEUR_BITMAP[ebx].raster[edx*4] jmp moveARGB_I moveARGB_J: mov ebx,0x000003FF and ebx,edx mov eax,0x000FFC00 and eax,edx shr eax,10 mul h shr eax,10 mul srcpitch shr eax,2 xchg eax,ebx mul w mov edx,src shr eax,10 add eax,ebx mov eax,[edx+eax*4] moveARGB_I: mov [edi],eax mov eax,0xFF000000 or eax,[esi] mov [ecx],eax add esi,4 add edi,4 add ecx,4 mov eax,esi or eax,edi or eax,ecx and eax,0x0000000C jnz moveARGB_L jmp moveARGB_V moveARGB_Y: mov eax,w mov i,eax moveARGB_X: movdqa xmm1,[ecx+1024*1024*4] psrlq xmm1,24 packssdw xmm1,xmm1 packsswb xmm1,xmm1 movd eax,xmm1 and eax,eax jne moveARGB_L//*/ movdqa xmm1,[ecx] movdqa [edi],xmm1 pcmpeqb xmm1,xmm1 pslld xmm1,24 por xmm1,[esi] movdqa [ecx],xmm1 add esi,16 add edi,16 add ecx,16 moveARGB_V: sub i,4 jnle moveARGB_X mov eax,sm lea esi,[esi+eax*4] mov eax,dm lea edi,[edi+eax*4] mov eax,jump lea ecx,[ecx+eax*4] dec j jnz moveARGB_Y А вот как это выглядит: Заметьте, что скорость движения центрального "баннера" меняется, что не является глюком, а управляется в оболочке вручную в реал-тайме. То, что "баннер" оставляет след - тоже опция с отключением предварительной зачистки кадра… Как Вы понимаете, идея сама по-себе достаточно занятная. В первую очередь тем, что: Запускается до 9 экземпляров VirtualDub каждый со своим источником видео. Затем, запускается оболочка со Squirrel-сценарием, в котором строится сцена и, при желании, анимируется. Если при анимации оболочка обрушится, просто анимация застрянет, но в VirtualDub'е все потоки будут продолжать показываться как есть… P.S.: Есть один изъян: Вся свистопляска в главном экземпляре VirtualDub жрёт до 22% процессорного времени (и от 3% при нормальном копировании одного потока), так как доступ происходит почти к рандомным областям памяти… Есть ли способы устранения этого? Например: Если строку кадра сначала заполнять в стек из разных областей памяти, а затем из стека выбрасывать линейно в буфер. Или есть OpenGL-механизмы? Да, я качал примеры с динамическими текстурами и прямым доступом к ним. Но, у меня каждый из девяти VirtualDub открывает свой bmp-файл на диске с мэппингом, а главный - читает всё с тех мэппов. Если позакрывать все сторонние, просто потоки разных источников уйдут в "паузу". По-идее, надо будет в каждом экземпляре VirtualDub буфер источника выбрасывать в OpenGL-текстуру со своим индексом, а в главном - всё собирать вместе? Но, даже в пределах одной "оболочки" разные потоки никак не могли управиться с OpenGL-контекстом и пришлось всё тупо проделывать в одном потоке. Как же тогда всё согласовать в разных экземплярах VirtualDub???