Фильтр для Photoshop


  1. Предисловие
  2. Пишем фильтр
    1. Ресурсы
    2. Структура FilterRecord и написание фильтра
  3. Заключение

 

Предисловие

Довольно часто слышу мнение, что ассемблер мертвый язык, что он никому не нужен, за исключением особых ситуаций и т.д. и т.п. Хочу надеяться, что эта статья будет неплохим гвоздем в гроб этих мнений еще одним гвоздем в крышку гроба этих мнений, т.к. в этом рассмотренном ниже случае его применение более чем оправдано. Как говорил Остап Бендер: «Слухи о моей смерти явно преувеличены». Тоже самое может сказать и за ассемблер, который периодически закапывают не уставая все кому не лень со времени его появления. И все-таки он жив. Жив и активно развивается. Убедитесь сами!

Пишем фильтр

Являясь большим поклонником как замечательного продукта Adobe Photoshop, так и не меньшим (если не большим) поклонником ассемблера, мне подумалось — почему бы не усилить эффект и соединить два в одном — так, собственно, и родилась идея написания плагина-дополнения для Photoshop, и непременно на ассемблере. Порывшись в интернете и скачав Photoshop 6.0 SDK, я обнаружил практически полное отсутствие информации об этом, а платить Adobe за участие в их форумах не хотелось. Но кое-какую информацию найти все же удалось — совершенно случайно на сайте WhizKid-а я обнаружил пример фильтра для Photoshop, но написан он был достаточно громоздко с использованием синтаксиса nasm. Пришлось заняться рассмотрением примеров и углубиться в чтение документации, содержащимися в SDK, благо — все оказалась достаточно информативным.

Что удалось выяснить. Photoshop изначально создавался как продукт для Apple Macintosh, соответственно первые плагины и интерфейс взаимодействия программы-хоста (Photoshop) и плагина создавался, учитывая специфику яблочных компьютеров. Отголоски этого при написании плагина в среде Windows состоят в специфическом формате ресурсов PiPL. Но об этом формате немного позже. Еще одним наследием Apple является то, что данные и указатели для Photoshop формируются в формате big endian (в отличие от little endian у процессоров Intel), но я пока не замечал этого влияния, так что об этом пока достаточно просто упомянуть (с этим столкнутся те, кто будет писать плагины импорта и экспорта).

2.1 Ресурсы

Формат ресурсов для фильтров Photoshop отличается от формата ресурсов программ для Windows. Остановимся на его основных элементах подробнее.

Kind { Filter } — тип плагина, возможные варианты — «Filter», «Parser», «ImageFormat», «Extension», «Acquire», «Export». Соответственно у нас тип плагина — фильтр.
Name { «masquer» } — имя в меню Plugins, здесь только фантазия разработчика.
Category { «masquer test» } — имя в субменю, здесь будет masquer->masquer test.
Version { (1 << 16) | 0 } — версия плагина, здесь 1.0.
CodeWin32X86 { «PluginMain» } — точка входа в наш плагин.
SupportedModes — режимы изображений, которые мы будем поддерживать, у нас будет так:
noBitmap, doesSupportGrayScale, noIndexedColor, noRGBColor, noCMYKColor, noHSLColor, noHSBColor, noMultichannel, noDuotone, noLABColor
EnableInfo — строка, при выполнении условий которой, наш плагин будет находиться в положении Enable.
Например,
"in (PSHOP_ImageMode, GrayScaleMode)"
Функция in возвращает истинное значение только тогда, когда первый параметр соответствует хотя бы одному из следующих. В данном случае мы имеем истинное значение, если изображение находится в режиме градаций серого.
FilterCaseInfo — массив из 7 элементов, каждый из которых состоит из четырех байт. Первые два значения определяют действия хоста для пре- и постпроцессинга. Т.е. определив в нашем случае inStraightData, outStraightData мы говорим хосту (Adobe Photoshop) что мы хотим получить наши данные немаскированными, и отдадим их с тем же условием, что хост не будет их демаскировать (dematte). Оставшиеся элементы составляют комбинацию флагов:

  • doNotWriteOutsideSelection — должен ли фильтр писать за границы выделения
  • filtersLayerMasks — можно ли обрабатывать маски слоев
  • worksWithBlankData — может ли фильтр работать с абсолютно прозрачным выделением/изображением
  • copySourceToDestination — будут ли копироваться исходные данные для фильтрования в другое место и уже там обрабатываться

Полный список функций и параметров можно найти в файле Plug-in Resource Guide.pdf.

Теперь, когда у нас готов файл с ресурсами (расширение *.r), для пишущих под Windows необходимо привести этот формат к стандартному. Для этого с Photoshop 6.0 SDK идет утилита Cnvtpipl.exe. В качестве параметра нужно просто указать наш файл с ресурсом и все — у нас уже есть файл с «нормальными» ресурсами (*.rc).

2.2 Структура FilterRecord и написание фильтра

Собственно теперь мы можем приступать к непосредственному написанию кода нашего плагина. Но перед этим рассмотрим одну очень важную для нас структуру. А именно речь пойдет о структуре FilterRecord. Всю структуру я рассматривать не буду, она слишком большая, да и нет в этом смысла, я начну, а те кому это нужно будет, уже сами смогут разобрать остальные поля. Итак:

serialNumber dd 0 В предыдущих версиях здесь был серийный номер Photoshop-а и плагин мог применять его для copy-protection. Не знаю как в 6, а в 7 версии здесь 0
abortProc dd 0 Адрес процедуры TestAbort. Не интересно
progressProc dd 0 Адрес процедуры UpdateProgress. Для создания ProgressBar. Обойдемся без него.
parameters dd 0 Параметры, задаваемые пользователем. Для нас неинтересно. В начале содержит 0
imageSize pPOINT<> Размеры изображения. Структура из двух word-ов. Отсюда вытекает, что изображение не может быть больше, чем 65535х65535 пикселей
planes dw 0 количество каналов, например для CMYK — 4, для RGB — 3, мы упростим себе задачу и выберем GrayScale — 1.
filterRect pRECT<> Общий фильтруемый прямоугольник. Даже если мы фильтруем неровное выделение, все равно нам будет передан граничащий прямоугольник, внутри которого располагается выделение. Структура из 4 word-ов: top, left, bottom и right соответственно.
background RGBColor<> Цвет подложки (background)
foreground RGBColor<> Цвет переднего плана (foreground)
dw 0 Для выравнивания внутри структуры
maxSpace dd 0 Максимально возможный объем данных…
bufferSpace dd 0 …и буфера
inRect pRECT<> Прямоугольник, который будет передан фильтру в текущей итерации. Для уменьшения расхода памяти рекомендуется условно разбить изображение на прямоугольники типа 128х128, или 256х256 и «кусками» обрабатывать изображение. Мы этим пренебрежем сейчас и закажем все изображение.
inLoPlane dw 0 Первый запрашиваемый канал…
inHiPlane dw 0 …и последний
outRect pRECT<> Прямоугольник для результирующего изображения. У нас inRect и outRect будут совпадать
outLoPlane dw 0 так же, как и для inRect
outHiPlane dw 0
inData dd 0 адрес, где находится исходное изображение для обработки
inRowBytes dd 0 смещение между строками изображения. Дело в том, что если ширина изображения не кратна 32, а адрес каждой новой строки должен быть кратен 32. Соответственно в таких случаях остаток до границы строки заполняется 0 и не учитывается при выводе результата.
outData dd 0 адрес, где мы можем разместить результат
outRowBytes dd 0 то же смещение

 

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

Основной экспортируемой нашим плагином функцией является PluginMain. Экспортируемой, потому что наш плагин не что иное, как библиотека dll, имеющая расширение 8bf. В качестве входящих параметров мы имеем selector, адрес filterRecord, data, result. Начнем с конца, как с наименее важных параметров:

  1. result — результат выполнения, некоторые ошибки могут нами обрабатываться, некоторые — хостом. Будем считать, что мы все делаем без ошибок и памяти у нас достаточно, поэтому принимать во внимание этот параметр не будем.
  2. data — здесь можно хранить хендл к нашим структурам данных. Обойдемся.
  3. filterRecord — адрес нашей главной структуры.
  4. selector — здесь остановимся поподробнее. Именно через этот селектор хост (Photoshop) сообщает нам, что нам нужно делать, передавая нам одно из пяти значений. Если селектор равен 0, то о нашем фильтре хотят получить больше информации, а именно: выбрали наш плагин в Help->About Plug-In. Adobe, правда, хочет, чтобы мы выводили не просто месадж бокс, а создавали диалоговое окно без кнопок, но с надписями, ну да переживут как-нибудь.
align 4
DoAbout proc
  invoke MessageBox, 0, ADDR szText, ADDR szText, MB_OK
  ret
DoAbout endp

Параметром номер 1 является filterSelectorParameters, где по идее мы должны обработать передаваемые нам параметры и отразить диалог, мы упростим все до одного ret.

align 4
DoParameters proc
  ret
DoParameters endp

2 — filterSelectorPrepare — здесь мы должны посчитать и выделить память, а можно и нули занести, тогда Photoshop сам это за нас сделает. Так и решим.

align 4
DoPrepare proc
  xor eax, eax
  mov fr.bufferSpace, eax
  mov fr.maxSpace, eax
  ret
DoPrepare endp

3 — filterSelectorStart — здесь мы уже обрабатываем изображение

align 4
DoStart proc uses edi esi
  mov edx, dword ptr fr
  assume edx:PTR FilterRecord ; edx указывает на FilterRecord
  movzx eax, [edx].imageSize.h ; получаем размеры изображения
  movzx ebx, [edx].imageSize.v

  mov word ptr [edx].inRect.top, 0 ; укажем хосту что именно мы хотим обрабатывать
  mov word ptr [edx].inRect.left, 0
  mov word ptr [edx].inRect.bottom, bx
  mov word ptr [edx].inRect.right, ax

  mov word ptr [edx].outRect.top, 0 ; здесь inRect = outRect
  mov word ptr [edx].outRect.left, 0
  mov word ptr [edx].outRect.bottom, bx
  mov word ptr [edx].outRect.right, ax

  mov eax, [edx].advanceState ; пускай хост обновит параметры,
  call eax  ; хотя нам в данном случае это и не нужно. Это необходимо при
      ; обработке изображения "кусками"

  mov edx, dword ptr fr
  assume edx:PTR FilterRecord
  mov esi, dword ptr [edx].outData ; так как у нас inRect = outRect,
           ; то esi указывает на данные для обработки
  mov edi, esi ; и edi тоже

  movzx eax, [edx].imageSize.h ; рассчитаем количество итераций цикла обработки
  mov ebx, eax
  shr eax, 4
  test ebx, 0Fh
  jz @@_1
  inc eax
@@_1:
  shl eax, 4
  movzx ebx, [edx].imageSize.v
  mul ebx
  mov ecx, eax
  shr ecx, 4 ; наконец-то рассчитали
; дальше идет основной цикл вычислений.
; Чтобы особо не выдумывать я сдвигаю каждое значение яркости на 1 бит влево используя MMX.
; Но об этом в другой раз.
@@next:
  movq mm0, [esi]
  movq mm1, [esi+8]

  movq mm2, mm0
  movq mm3, mm1

  psllw mm0, 9
  psrlw mm2, 8
  psllw mm2, 9
  psrlw mm2, 8

  psllw mm1, 9
  psrlw mm3, 8
  psllw mm3, 9
  psrlw mm3, 8

  por mm0, mm2
  por mm1, mm3

  movq [esi], mm0
  movq [esi+8], mm1
  add esi, 16
  dec ecx
  jz @@fin
  jmp @@next
;end of main
@@fin:
  emms ; вдруг кому-то еще с сопроцессором работать нужно
  mov edx, dword ptr fr
  assume edx:PTR FilterRecord
  mov dword ptr [edx].inRect.top, 0 ; скажем хосту - все готово
  mov dword ptr [edx].inRect.bottom, 0
  mov dword ptr [edx].outRect.top, 0
  mov dword ptr [edx].outRect.bottom, 0
  mov dword ptr [edx].maskRect.top, 0
  mov dword ptr [edx].maskRect.bottom, 0
  ret
DoStart endp

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

align 4
DoContinue proc
  ret
DoContinue endp

5 — filterSelectorFinish — финиш — он и есть финиш — очистка ресурсов, а так как никаких ресурсов мы не занимали, то и чистить нам нечего.

align 4
DoFinish proc
  ret
DoFinish endp

Теперь посмотрим, как все это вызвать, чтобы работало. Собственно, процедура PluginMain

 .data
  szText db "masquer's Photoshop Plugin",0
  procs dd DoAbout, DoParameters, DoPrepare, DoStart, DoContinue, DoFinish 
      ;LUT (LookUp Table)
  align 4
  fr FilterRecord <>
  outRect pRECT <>
  imageSize pPOINT <>
 .code
 ...
align 4
PluginMain proc uses ebp edi ecx, selector:DWORD,
     filterRecord:DWORD, data:DWORD, result:DWORD
  mov ecx, result ; нулевой результат нам не нужен
  jcxz @@exit
  mov eax, filterRecord
  lea eax, [eax]
  mov dword ptr [fr], eax ; настроим указатели
  mov eax, selector
  cmp eax, 5
  ja @@exit
  call [procs+eax*4] ; вызовет процедуру в соответствии с селектором
@@exit:
  mov esp, ebp ; подчистим стек
  pop ebp
  retn
PluginMain endp
 ...

Ну, и, как и у любой нормальной dll, пропишем DllEntry

align 4
DllEntry proc hInstDLL:DWORD, reason:DWORD, unused:DWORD
  mov eax, 1
  ret
DllEntry Endp

Для отладки я использовал незаменимый во всех случаях жизни SoftIce, а точнее я цеплялся к MessageBoxA, смотрел хелп, ну а дальше уже дело техники. Напоследок приведу только содержимое .def файла

LIBRARY test.8bf
EXPORTS
PluginMain

Да, чуть не забыл. Для того чтобы все это заработало, достаточно скопировать полученный test.8bf в папку к плагинам [Здесь путь к каталогу Photoshop]\Plug-Ins\test.8bf.

Заключение

Как говорится — вуаля! Заготовка полностью написана, теперь только знание математики и ее применение к обработке изображений сдерживает наш творческий порыв. Но к следующему разу я что-нибудь обязательно придумаю, про MMX, например, попробуем рассказать, что это такое и где его можно применить.

[C] masquer

 

Источник: WASM.RU /21.11.2002/

Поделиться в соц сетях

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Есть идеи, замечания, предложения? Воспользуйтесь формой Обратная связь или отправьте сообщение по адресу replay@sciencestory.ru
© 2017 Истории науки. Информация на сайте опубликована в ознакомительных целях может иметь ограничение 18+