Несмотря на то что обработка видео не спеша переезжает на OpenCL / CUDA VirtualDub остается удобным средством для простых действий с видео. Обрезка кадра, добавление фильтров или наложение выполняется гораздо удобнее чем из консоли ffmpeg. Кроме того за годы существования была разработана масса фильтров позволяющие выполнять многие операции быстро и удобно. Несмотря на простоту SDK, при написании плагина возникают некоторые нюансы. Статья посвящена работе с ними.

SDK доступно по ссылке с сайта автора. Последняя на данный момент версия 1.1 (VDPluginSDK-1.1.zip). Скачиваем и распаковываем в удобную для вас папку. Внутри находится файл справки PluginSDK.chm, частичным переложением которого этот текст и является. Разработка будет вестись в Microsoft Visual Studio Community 2015, можно использовать как более старые так и более новые версии. Для проверки настройки окружения можно воспользоваться файлами проектов с примерами лежащие в папке src, Samples.sln для новых версий студии или SamplesVC6.dsw для старого доброго Visual Studio 6. После сборки примеров в папке out\Release или out\Debug появится файл SampleVideoFilter.vdf. Это и есть тестовый фильтр. Для проверки достаточно положить его в папку VirtualDub\plugins и добавить из меню фильтров. Если всё работает значит Visual Studio установлен корректно.

В качестве примера напишем фильтр с нуля. Туториал рассчитан на начинающих или вспоминающих Win32 API. Создаём в студии пустой проект динамической библиотеки DLL.

Плагины для VirtualDub имеют расширение vdf, поэтому чтобы не переименовывать его каждый раз меняем расширение в свойства проекта Properties→General→Target extension на .vdf. Меняем для всех конфигураций, поэтому не забываем переключить их на вкладке настроек Configuration: на All Configurations и Platform на All platforms.

Копируем в проект папку include из распакованного SDK и добавляем файлы из него в проект через Atl-Shift-A или меню Add→Existing Item. Для работы нам понадобятся файлы заголовков из папки include и набор файлов хелпера VDXFrame. Не забываем добавить папку include в список папок где система будет их искать. Делается это из Properties→VC++ Directories→Include Directories, добавляем ссылку на корень проекта в виде $(ProjectDir)\include.

Добавляем в проект библиотеку VDXFrame, в примерах она используется в виде отдельного модуля, но так как лицензия позволяет, добавим её в виде исходного кода. Создадим в каталоге проекта папку src и скопируем в неё из SDK файлы VideoFilter.cpp,VideoFilterEntry.cpp,VideoFilterDialog.cpp и stdafx.cpp. Далее скопируем файл заголовка из include\stdafx.h в ранее созданную папку include. Не забываем добавить скопированные файлы в проект через Atl-Shift-A или из меню Add→Existing Item. На этом интеграция библиотеки хелпера заканчивается.
Переходим к написанию кода. Добавляем в проект новый файл main.cpp через Add→Existing Item или комбинацию клавиш Ctrl-Shift-A. Добавляем в main следующие строки
Плагин может содержать в себе произвольное количество фильтров описываемых макросом VDX_DECLARE_VIDEOFILTER с параметром в виде класса VDXFilterDefinition служащим оболочкой над классом фильтра. Сам фильтр описывается тремя текстовыми полями: Автор, Название и Описание. Создадим класс фильтра с именем BlackWhiteFilter, у автора VirtualDub классы именуются с использованием CamelCase поэтому создаем новый класс унаследованный от VDXVideoFilter в файле BlackWhiteFilter.h. Переменная g_VFVAPIVersion будет содержать версию API. Функции определенные с virtual являются частью SDK, а метод ToBlackAndWhite будет реализовывать преобразование картинки.
Реализацию пишем в файле BlackWhiteFilter.cpp, метод Start() выполняется первым, он предназначен для любых предварительных действий, например для определения совместимости с набором инструкций AVX или поддержки CUDA. Оставляем его пока пустым. Хелпер VDXFrame обеспечивает в пределах видимости этого класса указатель на экземпляр класса VDXFilterActivation с именем fa, содержащий информацию о кадре и буферах.
Метод GetParams() используется VirtualDub для определения совместимости фильтра, он должен вернуть битовую маску из перечисления FILTERPARAM
Для фильтра который будет конвертировать изображение RGB32 в черно-белое нам подойдет FILTERPARAM_SWAP_BUFFERS и FILTERPARAM_PURE_TRANSFORM. Если мы хотим поддерживать кодировку цвета отличную от RGB32 и версию SDK меньше 12 пишем проверку на g_VFVAPIVersion и если она поддержана проверяем формат полученного изображения в поле fa->src.mpPixmapLayout->format. Ранние версии VirtualDub не поддерживали представление цвета отличное от RGB32. Для упрощения обработки писать будем придерживаясь формата RGB32, но вообще VirtualDub поддерживает большой список форматов, перечисленный в VDXPixmapFormat.
Обработка кадра выполняется методом Run(). Данные о кадре и входном и выходном буферах хранятся в переменной fa являющаяся экземпляром класса VDXFilterActivation. VirtualDub поддерживает обрезку кадра, поэтому алгоритм обработки можно оптимизировать получив информацию о выбранном пользователем окне с координатами x1,y1,x2,y2. Данные кадра хранятся в объектах src и dst, соответственно входной и выходной буфер.
Если мы продолжаем писать код с поддержкой SDK меньше 12 версии то реализация метода Run() примет такой вид:
От версии которую поддерживает плагин зависит место хранения сырых данных в структуре. Итак, в функцию ToBlackAndWhite будет передано 6 параметров:
Для упрощения кода мы проигнорируем параметры обрезки, поэтому кадр будет обрабатываться с одинаковой скоростью вне зависимости от параметра Crop в настройках. Точка в буфере хранится в формате kPixFormat_XRGB8888 и занимает 32 бита. Реализуем простейшее преобразование кадра в черно-белый. Задача оптимизации у нас не стоит, поэтому считать будем по формуле с расчетом в арифметике с плавающей запятой
GRAY = 0.299 * R + 0.587 * G + 0.114 * B
Организуем два цикла, один проходит по строкам а второй по точкам, граничный уровень для определения цвета точки примем равным 128.
Собираем плагин, копируем файл Windows-VirtualDub-Plugin-BlackWhite.vdf в папку plugins VirtualDub и делаем его активным. В списке он будет виден под названием, которое мы задали в классе VDXFilterDefinition — Black White filter. Плагин собранный для 64 битной версии не будет видно в 32 битной версии VirtualDub, поэтому не забываем проверить активную конфигурацию проекта.

Плагин без настроек довольно уныл, добавим возможность настройки и кнопку предварительного просмотра. Для этого нам бы следовало погрузиться в дебри Win32 API, но по этой теме написано достаточно книг, поэтому не будем вдаваться в детали.
Для визуального представления окна настройки нам понадобится диалоговое окно. Создаем новый файл ресурсов через меню Ctrl-Shift-A → Resource → Resource File с именем Resource.rc. Добавим в него диалоговое окно через меню Add Resource → Dialog и изменим ему имя на IDD_DIALOG_BLACKWHITE_SETTING. По умолчанию у нас уже есть две кнопки Ok и Cancel. Создавать ресурсы лучше в английской локали, иначе можно получить проблему с не читаемым русским шрифтом на кнопке Отмена. Добавим на экран кнопку Preview с именем IDC_SLIDER_THRESHOLD. Чтобы потом не возвращаться добавим остальные элементы управления для настроек, это будет слайдер для изменения порогового значения IDC_SLIDER_THRESHOLD и checkbox IDC_CHECK_INVERTED позволяющий инвертировать картинку. Сверстать это можно например так.

Создадим класс диалога BlackWhiteFilterDialog унаследованный от VDXVideoFilterDialog.
В конструктор передаётся ссылка на класс IVDXFilterPreview который управляет окном предварительного просмотра, локальную ссылку мы будем хранить в переменной mifp.
Метод Show(HWND parent) перегружен вызовом конструктора родителя и использует в качестве параметра идентификатор ресурса диалога настроек IDD_DIALOG_BLACKWHITE_SETTING
DlgProc используется для обработки сообщений от диалогового окна и реализует обработку жизненного цикла диалога в методах OnInit(), OnDestroy() и обработку событий от элементов управления в OnCommand.
Для начала обработаем закрытие диалога по кнопкам Ok и Cancel. Кроме того нам понадобится обработчик Preview, управляющий отображением окна предварительного просмотра через метод Toggle((VDXHWND)mhdlg).
Класс для работы с диалогом написан, теперь его необходимо вызвать, для этого перегружаем в классе BlackWhiteFilter метод Configure(VDXHWND hwnd) и реализуем его
Собираем проект, копируем файл плагина в папку VirtualDub, добавляем новый фильтр в список и видим наш диалог и доступную кнопку Preview.

Окно конфигурации у нас есть, но настроек у фильтра пока нет, приступаем к реализации. Настройки будем хранить в классе BlackWhiteFilterConfig содержащем всего две переменные, mTreshold как величину порогового значения и флаг инверсии mInvert.
Отредактируем класс BlackWhiteFilterDialog, добавив в него два экземпляра класса BlackWhiteFilterConfig для хранения конфигурации mConfigNew и mConfigOld. Эти переменные будут хранить старое и измененное состояние настроек и понадобятся нам для работы кнопки
Ok и Cancel. Отредактируем конструктор, добавив в него параметр хранящий настройки и инициализацию конфигурации.
Настройки должны где-то х��аниться, добавляем в класс BlackWhiteFilter переменную BlackWhiteFilterConfig mConfig и меняем инициализацию класса BlackWhiteFilterDialog в методе Configure на новую.
Теперь необходимо снова поработать с элементами управления Win32. В классе BlackWhiteFilterDialog напишем два метода связывающих нашу конфигурацию и ее реализацию в диалоге.
Осталось использовать эти два метода в жизненном цикле диалога. В OnCommand для кнопки Ok вызываем SaveToConfig(), а для кнопки Cancel восстанавливаем старый набор настроек присваиванием mConfigNew = mConfigOld. Начальные параметры диалога настраиваются в методе OnInit(), диапазон слайдера устанавливается в 0-255 и на него устанавливается фокус.
Изменение настроек необходимо отобразить в окне предварительного просмотра с помощью метода RedoFrame(), для этого отредактируем метод DlgProc добавив вызов сохранения параметров в методе в обработчике WM_HSCROLL для слайдера с проверкой что окно Preview включено if(mifp && SaveToConfig())mifp->RedoFrame(). Для обработки CheckBox допишем в метод OnCommand условие для case на идентификатор IDC_CHECK_INVERTED и выполним такое же обновление.
Перепишем метод ToBlackAndWhite для использования конфигурации, учитывая два параметра, инверсию и пороговое значения. Константа BST_UNCHECKED унаследована от Win32 API и используется как значение флага true/false.
Собираем проект и опять тестируем фильтр в VirtualDub, включение инверсии превратила милого котика в нечто готические страшное.

Нам осталось совсем чуть-чуть до финала. Фильтры VirtualDub поддерживают сохранение параметров в файл настроек, для этого нужно сериализировать наш класс настроек. Для этого существует макрос VDXVF_DECLARE_SCRIPT_METHODS() который добавляется в заголовок класса BlackWhiteFilter и набор методов для реализации записи и отображения настроек GetSettingString, GetScriptString и метод ScriptConfig для синтаксического разбора параметров из файла настроек. Количество и там аргументов задаются в макросе VDXVF_DEFINE_SCRIPT_METHOD в виде последнего параметра. Новая версия класса BlackWhiteFilter будет выглядеть так
Реализуем методы которых не хватает. Декларируем количество параметров и их тип в макросе VDXVF_DEFINE_SCRIPT_METHOD, у нас их два, оба целочисленные, поэтому строка инициализации будет «ii». Список поддерживаемых форматов можно посмотреть в классе IVDXScriptInterpreter, доступны целые, дробные и строковые параметры. Метод GetSettingString отображает параметры в строке настроек, он нужен для человека который сможет быстро посмотреть параметры в окне Filters, в колонке описания Filter. Метод GetScriptString форматирует параметры для сохранения их в файл VirtualDub configuration (*.vcf) и последующего их чтения методом ScriptConfig.
Добавив данный код и собрав плагин мы получим возможность видеть настройки фильтра в окне Filters и сохранять их в файл через меню файл Save processing setting.

По умолчанию проект собирается с зависимостями от установленной в системе VC Runtime, если планируется его использование на других компьютерах, при сборке необходимо указать параметр Multi-threaded (/MT) из меню настроек Configuration->C/C++->Code Generation->Runtime Library. Плагин увеличит свой размер в десять раз но пользователям не придется подбирать Runtime под версию Visual Studio которую использовал разработчик.

Код проекта доступен на github. Материал нацелен на людей которым нужно сделать что-то быстро а вспоминать тонкости работы с Win32 API неохота. Мне этот плагин понадобился для переноса видео на платформу с однобитным представлением цвета, а прогонять каждый раз набор кадров через XnView надоело.

SDK доступно по ссылке с сайта автора. Последняя на данный момент версия 1.1 (VDPluginSDK-1.1.zip). Скачиваем и распаковываем в удобную для вас папку. Внутри находится файл справки PluginSDK.chm, частичным переложением которого этот текст и является. Разработка будет вестись в Microsoft Visual Studio Community 2015, можно использовать как более старые так и более новые версии. Для проверки настройки окружения можно воспользоваться файлами проектов с примерами лежащие в папке src, Samples.sln для новых версий студии или SamplesVC6.dsw для старого доброго Visual Studio 6. После сборки примеров в папке out\Release или out\Debug появится файл SampleVideoFilter.vdf. Это и есть тестовый фильтр. Для проверки достаточно положить его в папку VirtualDub\plugins и добавить из меню фильтров. Если всё работает значит Visual Studio установлен корректно.

В качестве примера напишем фильтр с нуля. Туториал рассчитан на начинающих или вспоминающих Win32 API. Создаём в студии пустой проект динамической библиотеки DLL.

Плагины для VirtualDub имеют расширение vdf, поэтому чтобы не переименовывать его каждый раз меняем расширение в свойства проекта Properties→General→Target extension на .vdf. Меняем для всех конфигураций, поэтому не забываем переключить их на вкладке настроек Configuration: на All Configurations и Platform на All platforms.

Копируем в проект папку include из распакованного SDK и добавляем файлы из него в проект через Atl-Shift-A или меню Add→Existing Item. Для работы нам понадобятся файлы заголовков из папки include и набор файлов хелпера VDXFrame. Не забываем добавить папку include в список папок где система будет их искать. Делается это из Properties→VC++ Directories→Include Directories, добавляем ссылку на корень проекта в виде $(ProjectDir)\include.

Добавляем в проект библиотеку VDXFrame, в примерах она используется в виде отдельного модуля, но так как лицензия позволяет, добавим её в виде исходного кода. Создадим в каталоге проекта папку src и скопируем в неё из SDK файлы VideoFilter.cpp,VideoFilterEntry.cpp,VideoFilterDialog.cpp и stdafx.cpp. Далее скопируем файл заголовка из include\stdafx.h в ранее созданную папку include. Не забываем добавить скопированные файлы в проект через Atl-Shift-A или из меню Add→Existing Item. На этом интеграция библиотеки хелпера заканчивается.
Переходим к написанию кода. Добавляем в проект новый файл main.cpp через Add→Existing Item или комбинацию клавиш Ctrl-Shift-A. Добавляем в main следующие строки
#include <vd2/VDXFrame/VideoFilter.h> #include <BlackWhiteFilter.h> VDXFilterDefinition filterDef_blackWhite = VDXVideoFilterDefinition<BlackWhiteFilter>("Shadwork", "Black White filter", "Example for VirtualDub Plugin SDK: Applies a Black White filter to video."); VDX_DECLARE_VIDEOFILTERS_BEGIN() VDX_DECLARE_VIDEOFILTER(filterDef_blackWhite) VDX_DECLARE_VIDEOFILTERS_END() VDX_DECLARE_VFMODULE()
Плагин может содержать в себе произвольное количество фильтров описываемых макросом VDX_DECLARE_VIDEOFILTER с параметром в виде класса VDXFilterDefinition служащим оболочкой над классом фильтра. Сам фильтр описывается тремя текстовыми полями: Автор, Название и Описание. Создадим класс фильтра с именем BlackWhiteFilter, у автора VirtualDub классы именуются с использованием CamelCase поэтому создаем новый класс унаследованный от VDXVideoFilter в файле BlackWhiteFilter.h. Переменная g_VFVAPIVersion будет содержать версию API. Функции определенные с virtual являются частью SDK, а метод ToBlackAndWhite будет реализовывать преобразование картинки.
#include <vd2/VDXFrame/VideoFilter.h> #include <vd2/VDXFrame/VideoFilterEntry.h> #ifndef FILTER_VD_BLACK_WHITE #define FILTER_VD_BLACK_WHITE extern int g_VFVAPIVersion; class BlackWhiteFilter : public VDXVideoFilter { public: virtual uint32 GetParams(); virtual void Start(); virtual void Run(); protected: void ToBlackAndWhite(void *dst, ptrdiff_t dstpitch, const void *src, ptrdiff_t srcpitch, uint32 w, uint32 h); }; #endif
Реализацию пишем в файле BlackWhiteFilter.cpp, метод Start() выполняется первым, он предназначен для любых предварительных действий, например для определения совместимости с набором инструкций AVX или поддержки CUDA. Оставляем его пока пустым. Хелпер VDXFrame обеспечивает в пределах видимости этого класса указатель на экземпляр класса VDXFilterActivation с именем fa, содержащий информацию о кадре и буферах.
Метод GetParams() используется VirtualDub для определения совместимости фильтра, он должен вернуть битовую маску из перечисления FILTERPARAM
- FILTERPARAM_SWAP_BUFFERS создаётся два независимых буфера для входного и выходного кадров, рекомендуется использовать всегда чтобы не создавать такие буфера руками
- FILTERPARAM_NEEDS_LAST передаёт в фильтр не только текущий кадр но и идущий перед ним, используется для фильтров состояние которых зависит от предыдущего кадра
- FILTERPARAM_SUPPORTS_ALTFORMATS информирует VirtualDub что плагин поддерживает кодирование кадра отличное от RGB32, например YUV, что позволяет оптимизировать вычисления
- FILTERPARAM_ALIGN_SCANLINES фильтр требует выравнивания данных на 16 байт, а значит не поддерживает например длину строки 13 байт
- FILTERPARAM_PURE_TRANSFORM поведение фильтра зависит только от данных в буфере кадра, позволяет ускорить обработку и отображение фильтра
- FILTERPARAM_NOT_SUPPORTED фильтр не поддерживает входные данные в данном формате и работать не будет
Для фильтра который будет конвертировать изображение RGB32 в черно-белое нам подойдет FILTERPARAM_SWAP_BUFFERS и FILTERPARAM_PURE_TRANSFORM. Если мы хотим поддерживать кодировку цвета отличную от RGB32 и версию SDK меньше 12 пишем проверку на g_VFVAPIVersion и если она поддержана проверяем формат полученного изображения в поле fa->src.mpPixmapLayout->format. Ранние версии VirtualDub не поддерживали представление цвета отличное от RGB32. Для упрощения обработки писать будем придерживаясь формата RGB32, но вообще VirtualDub поддерживает большой список форматов, перечисленный в VDXPixmapFormat.
uint32 BlackWhiteFilter::GetParams() { if (g_VFVAPIVersion >= 12) { switch (fa->src.mpPixmapLayout->format) { case nsVDXPixmap::kPixFormat_XRGB8888: break; default: return FILTERPARAM_NOT_SUPPORTED; } } fa->dst.offset = 0; return FILTERPARAM_SWAP_BUFFERS; }
Обработка кадра выполняется методом Run(). Данные о кадре и входном и выходном буферах хранятся в переменной fa являющаяся экземпляром класса VDXFilterActivation. VirtualDub поддерживает обрезку кадра, поэтому алгоритм обработки можно оптимизировать получив информацию о выбранном пользователем окне с координатами x1,y1,x2,y2. Данные кадра хранятся в объектах src и dst, соответственно входной и выходной буфер.
class VDXFilterActivation { public: const VDXFilterDefinition *filter; // void *filter_data; VDXFBitmap& dst; VDXFBitmap& src; VDXFBitmap *_reserved0; VDXFBitmap *const last; uint32 x1; uint32 y1; uint32 x2; uint32 y2; VDXFilterStateInfo *pfsi; IVDXFilterPreview *ifp; IVDXFilterPreview2 *ifp2; // (V11+) uint32 mSourceFrameCount; // (V14+) VDXFBitmap *const *mpSourceFrames; // (V14+) VDXFBitmap *const *mpOutputFrames; // (V14+) };
Если мы продолжаем писать код с поддержкой SDK меньше 12 версии то реализация метода Run() примет такой вид:
void BlackWhiteFilter::Run() { if (g_VFVAPIVersion >= 12) { const VDXPixmap& pxdst = *fa->dst.mpPixmap; const VDXPixmap& pxsrc = *fa->src.mpPixmap; switch (pxdst.format) { case nsVDXPixmap::kPixFormat_XRGB8888: ToBlackAndWhite(pxdst.data, pxdst.pitch, pxsrc.data, pxsrc.pitch, pxsrc.w, pxsrc.h); break; } } else { ToBlackAndWhite(fa->dst.data, fa->dst.pitch, fa->src.data, fa->src.pitch, fa->dst.w, fa->dst.h); } }
От версии которую поддерживает плагин зависит место хранения сырых данных в структуре. Итак, в функцию ToBlackAndWhite будет передано 6 параметров:
- void *dst0 – выходной буфер кадра
- ptrdiff_t dstpitch — полная длина строки в байтах выходного буфера
- const void *src0 — входной буфер кадра
- ptrdiff_t srcpitch — полная длина строки входного буфера
- uint32 w — ширина кадра в пикселях
- uint32 h — высота кадра в пикселях
Для упрощения кода мы проигнорируем параметры обрезки, поэтому кадр будет обрабатываться с одинаковой скоростью вне зависимости от параметра Crop в настройках. Точка в буфере хранится в формате kPixFormat_XRGB8888 и занимает 32 бита. Реализуем простейшее преобразование кадра в черно-белый. Задача оптимизации у нас не стоит, поэтому считать будем по формуле с расчетом в арифметике с плавающей запятой
GRAY = 0.299 * R + 0.587 * G + 0.114 * B
Организуем два цикла, один проходит по строкам а второй по точкам, граничный уровень для определения цвета точки примем равным 128.
void BlackWhiteFilter::ToBlackAndWhite(void *dst0, ptrdiff_t dstpitch, const void *src0, ptrdiff_t srcpitch, uint32 w, uint32 h) { char *dst = (char *)dst0; const char *src = (const char *)src0; for (uint32 y = 0; y<h; ++y) { // Get scanline uint32 *srcline = (uint32 *)src; uint32 *dstline = (uint32 *)dst; for (uint32 x = 0; x<w; ++x) { // Process pixels uint32 data = srcline[x]; float gray = 0.299f * (data & 0x000000ff) + 0.587f * ((data & 0x0000ff00) >> 8) + 0.114f *((data & 0x00ff0000) >> 16); dstline[x] = gray < 128 ? 0x00000000 : 0x00ffffff; } src += srcpitch; dst += dstpitch; } }
Собираем плагин, копируем файл Windows-VirtualDub-Plugin-BlackWhite.vdf в папку plugins VirtualDub и делаем его активным. В списке он будет виден под названием, которое мы задали в классе VDXFilterDefinition — Black White filter. Плагин собранный для 64 битной версии не будет видно в 32 битной версии VirtualDub, поэтому не забываем проверить активную конфигурацию проекта.

Плагин без настроек довольно уныл, добавим возможность настройки и кнопку предварительного просмотра. Для этого нам бы следовало погрузиться в дебри Win32 API, но по этой теме написано достаточно книг, поэтому не будем вдаваться в детали.
Для визуального представления окна настройки нам понадобится диалоговое окно. Создаем новый файл ресурсов через меню Ctrl-Shift-A → Resource → Resource File с именем Resource.rc. Добавим в него диалоговое окно через меню Add Resource → Dialog и изменим ему имя на IDD_DIALOG_BLACKWHITE_SETTING. По умолчанию у нас уже есть две кнопки Ok и Cancel. Создавать ресурсы лучше в английской локали, иначе можно получить проблему с не читаемым русским шрифтом на кнопке Отмена. Добавим на экран кнопку Preview с именем IDC_SLIDER_THRESHOLD. Чтобы потом не возвращаться добавим остальные элементы управления для настроек, это будет слайдер для изменения порогового значения IDC_SLIDER_THRESHOLD и checkbox IDC_CHECK_INVERTED позволяющий инвертировать картинку. Сверстать это можно например так.

Создадим класс диалога BlackWhiteFilterDialog унаследованный от VDXVideoFilterDialog.
#include <windows.h> #include <commctrl.h> #include <resource.h> #include <vd2/VDXFrame/VideoFilterDialog.h> #include <vd2/VDXFrame/VideoFilter.h> #ifndef FILTER_VD_BLACK_WHITE_DIALOG #define FILTER_VD_BLACK_WHITE_DIALOG class BlackWhiteFilterDialog : public VDXVideoFilterDialog { public: BlackWhiteFilterDialog(IVDXFilterPreview *ifp); bool Show(HWND parent); virtual INT_PTR DlgProc(UINT msg, WPARAM wParam, LPARAM lParam); protected: IVDXFilterPreview *const mifp; bool OnInit(); bool OnCommand(int cmd); void OnDestroy(); }; #endif
В конструктор передаётся ссылка на класс IVDXFilterPreview который управляет окном предварительного просмотра, локальную ссылку мы будем хранить в переменной mifp.
BlackWhiteFilterDialog::BlackWhiteFilterDialog(IVDXFilterPreview *ifp):mifp(ifp){ }
Метод Show(HWND parent) перегружен вызовом конструктора родителя и использует в качестве параметра идентификатор ресурса диалога настроек IDD_DIALOG_BLACKWHITE_SETTING
bool BlackWhiteFilterDialog::Show(HWND parent) { return 0 != VDXVideoFilterDialog::Show(NULL, MAKEINTRESOURCE(IDD_DIALOG_BLACKWHITE_SETTING), parent); };
DlgProc используется для обработки сообщений от диалогового окна и реализует обработку жизненного цикла диалога в методах OnInit(), OnDestroy() и обработку событий от элементов управления в OnCommand.
INT_PTR BlackWhiteFilterDialog::DlgProc(UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_INITDIALOG: return !OnInit(); case WM_DESTROY: OnDestroy(); break; case WM_COMMAND: if (OnCommand(LOWORD(wParam))) return TRUE; break; case WM_HSCROLL: if (mifp) mifp->RedoFrame(); return TRUE; } return FALSE; }
Для начала обработаем закрытие диалога по кнопкам Ok и Cancel. Кроме того нам понадобится обработчик Preview, управляющий отображением окна предварительного просмотра через метод Toggle((VDXHWND)mhdlg).
bool BlackWhiteFilterDialog::OnCommand(int cmd) { switch (cmd) { case IDOK: EndDialog(mhdlg, true); return true; case IDCANCEL: EndDialog(mhdlg, false); return true; case IDC_PREVIEW: if (mifp) mifp->Toggle((VDXHWND)mhdlg); return true; } return false; }
Класс для работы с диалогом написан, теперь его необходимо вызвать, для этого перегружаем в классе BlackWhiteFilter метод Configure(VDXHWND hwnd) и реализуем его
bool BlackWhiteFilter::Configure(VDXHWND hwnd) { BlackWhiteFilterDialog dlg(fa->ifp); return dlg.Show((HWND)hwnd); }
Собираем проект, копируем файл плагина в папку VirtualDub, добавляем новый фильтр в список и видим наш диалог и доступную кнопку Preview.

Окно конфигурации у нас есть, но настроек у фильтра пока нет, приступаем к реализации. Настройки будем хранить в классе BlackWhiteFilterConfig содержащем всего две переменные, mTreshold как величину порогового значения и флаг инверсии mInvert.
#ifndef FILTER_VD_BLACK_WHITE_CONFIG #define FILTER_VD_BLACK_WHITE_CONFIG class BlackWhiteFilterConfig { public: BlackWhiteFilterConfig() { mTreshold = 128; mInvert = 0; } public: int mTreshold; int mInvert; }; #endif
Отредактируем класс BlackWhiteFilterDialog, добавив в него два экземпляра класса BlackWhiteFilterConfig для хранения конфигурации mConfigNew и mConfigOld. Эти переменные будут хранить старое и измененное состояние настроек и понадобятся нам для работы кнопки
Ok и Cancel. Отредактируем конструктор, добавив в него параметр хранящий настройки и инициализацию конфигурации.
BlackWhiteFilterDialog::BlackWhiteFilterDialog(BlackWhiteFilterConfig& config, IVDXFilterPreview *ifp):mifp(ifp){ mConfigNew = config; }
Настройки должны где-то х��аниться, добавляем в класс BlackWhiteFilter переменную BlackWhiteFilterConfig mConfig и меняем инициализацию класса BlackWhiteFilterDialog в методе Configure на новую.
bool BlackWhiteFilter::Configure(VDXHWND hwnd) { BlackWhiteFilterDialog dlg(mConfig, fa->ifp); return dlg.Show((HWND)hwnd); }
Теперь необходимо снова поработать с элементами управления Win32. В классе BlackWhiteFilterDialog напишем два метода связывающих нашу конфигурацию и ее реализацию в диалоге.
void BlackWhiteFilterDialog::LoadFromConfig() { SendDlgItemMessage(mhdlg, IDC_SLIDER_THRESHOLD, TBM_SETPOS, TRUE, mConfigNew.mTreshold); SendMessage(mhdlg, IDC_CHECK_INVERTED, mConfigNew.mInvert, 0); } bool BlackWhiteFilterDialog::SaveToConfig() { int threshold = SendDlgItemMessage(mhdlg, IDC_SLIDER_THRESHOLD, TBM_GETPOS, 0, 0); int inverted = SendDlgItemMessage(mhdlg, IDC_CHECK_INVERTED, BM_GETCHECK, 0, 0); if (threshold != mConfigNew.mTreshold || inverted!= mConfigNew.mInvert) { mConfigNew.mTreshold = threshold; mConfigNew.mInvert = inverted; return true; } return false; }
Осталось использовать эти два метода в жизненном цикле диалога. В OnCommand для кнопки Ok вызываем SaveToConfig(), а для кнопки Cancel восстанавливаем старый набор настроек присваиванием mConfigNew = mConfigOld. Начальные параметры диалога настраиваются в методе OnInit(), диапазон слайдера устанавливается в 0-255 и на него устанавливается фокус.
bool BlackWhiteFilterDialog::OnInit() { mConfigOld = mConfigNew; // Set up slider to range 0-255 SendDlgItemMessage(mhdlg, IDC_SLIDER_THRESHOLD, TBM_SETRANGE, TRUE, MAKELONG(0, 255)); LoadFromConfig(); // gain focus to slide control HWND hwndFirst = GetDlgItem(mhdlg, IDC_SLIDER_THRESHOLD); if (hwndFirst) SendMessage(mhdlg, WM_NEXTDLGCTL, (WPARAM)hwndFirst, TRUE); // init preview button HWND hwndPreview = GetDlgItem(mhdlg, IDC_PREVIEW); if (hwndPreview && mifp) { EnableWindow(hwndPreview, TRUE); mifp->InitButton((VDXHWND)hwndPreview); } return false; }
Изменение настроек необходимо отобразить в окне предварительного просмотра с помощью метода RedoFrame(), для этого отредактируем метод DlgProc добавив вызов сохранения параметров в методе в обработчике WM_HSCROLL для слайдера с проверкой что окно Preview включено if(mifp && SaveToConfig())mifp->RedoFrame(). Для обработки CheckBox допишем в метод OnCommand условие для case на идентификатор IDC_CHECK_INVERTED и выполним такое же обновление.
case IDC_CHECK_INVERTED: if (mifp && SaveToConfig())mifp->RedoFrame(); return true;
Перепишем метод ToBlackAndWhite для использования конфигурации, учитывая два параметра, инверсию и пороговое значения. Константа BST_UNCHECKED унаследована от Win32 API и используется как значение флага true/false.
if (mConfig.mInvert == BST_UNCHECKED) { dstline[x] = gray < mConfig.mTreshold ? 0x00000000 : 0x00ffffff; } else { dstline[x] = gray > =mConfig.mTreshold ? 0x00000000 : 0x00ffffff; }
Собираем проект и опять тестируем фильтр в VirtualDub, включение инверсии превратила милого котика в нечто готические страшное.

Нам осталось совсем чуть-чуть до финала. Фильтры VirtualDub поддерживают сохранение параметров в файл настроек, для этого нужно сериализировать наш класс настроек. Для этого существует макрос VDXVF_DECLARE_SCRIPT_METHODS() который добавляется в заголовок класса BlackWhiteFilter и набор методов для реализации записи и отображения настроек GetSettingString, GetScriptString и метод ScriptConfig для синтаксического разбора параметров из файла настроек. Количество и там аргументов задаются в макросе VDXVF_DEFINE_SCRIPT_METHOD в виде последнего параметра. Новая версия класса BlackWhiteFilter будет выглядеть так
#include <vd2/VDXFrame/VideoFilter.h> #include <vd2/VDXFrame/VideoFilterEntry.h> #include <BlackWhiteFilterDialog.h> #ifndef FILTER_VD_BLACK_WHITE #define FILTER_VD_BLACK_WHITE extern int g_VFVAPIVersion; class BlackWhiteFilter : public VDXVideoFilter { public: virtual uint32 GetParams(); virtual void Start(); virtual void Run(); virtual bool Configure(VDXHWND hwnd); virtual void GetSettingString(char *buf, int maxlen); virtual void GetScriptString(char *buf, int maxlen); VDXVF_DECLARE_SCRIPT_METHODS(); protected: void ToBlackAndWhite(void *dst, ptrdiff_t dstpitch, const void *src, ptrdiff_t srcpitch, uint32 w, uint32 h); BlackWhiteFilterConfig mConfig; void ScriptConfig(IVDXScriptInterpreter *isi, const VDXScriptValue *argv, int argc); }; #endif
Реализуем методы которых не хватает. Декларируем количество параметров и их тип в макросе VDXVF_DEFINE_SCRIPT_METHOD, у нас их два, оба целочисленные, поэтому строка инициализации будет «ii». Список поддерживаемых форматов можно посмотреть в классе IVDXScriptInterpreter, доступны целые, дробные и строковые параметры. Метод GetSettingString отображает параметры в строке настроек, он нужен для человека который сможет быстро посмотреть параметры в окне Filters, в колонке описания Filter. Метод GetScriptString форматирует параметры для сохранения их в файл VirtualDub configuration (*.vcf) и последующего их чтения методом ScriptConfig.
VDXVF_BEGIN_SCRIPT_METHODS(BlackWhiteFilter) VDXVF_DEFINE_SCRIPT_METHOD(BlackWhiteFilter, ScriptConfig, "ii") VDXVF_END_SCRIPT_METHODS() void BlackWhiteFilter::GetSettingString(char *buf, int maxlen) { SafePrintf(buf, maxlen, " (Treshold:%d, Invert:%d)", mConfig.mTreshold, mConfig.mInvert); } void BlackWhiteFilter::GetScriptString(char *buf, int maxlen) { SafePrintf(buf, maxlen, "Config(%d, %d)", mConfig.mTreshold, mConfig.mInvert); } void BlackWhiteFilter::ScriptConfig(IVDXScriptInterpreter *isi, const VDXScriptValue *argv, int argc) { mConfig.mTreshold = argv[0].asInt(); mConfig.mInvert = argv[1].asInt(); }
Добавив данный код и собрав плагин мы получим возможность видеть настройки фильтра в окне Filters и сохранять их в файл через меню файл Save processing setting.

По умолчанию проект собирается с зависимостями от установленной в системе VC Runtime, если планируется его использование на других компьютерах, при сборке необходимо указать параметр Multi-threaded (/MT) из меню настроек Configuration->C/C++->Code Generation->Runtime Library. Плагин увеличит свой размер в десять раз но пользователям не придется подбирать Runtime под версию Visual Studio которую использовал разработчик.

Код проекта доступен на github. Материал нацелен на людей которым нужно сделать что-то быстро а вспоминать тонкости работы с Win32 API неохота. Мне этот плагин понадобился для переноса видео на платформу с однобитным представлением цвета, а прогонять каждый раз набор кадров через XnView надоело.
