Как стать автором
Обновить

Использование фильтров Pixel Bender на примере создания приложения для ВКонтакте

Время на прочтение7 мин
Количество просмотров1.7K
Фоторедактор Данная статья была написана в продолжение темы, затронутой здесь, по просьбе пользователя namata. В статье речь пойдет о использовании фильтров ActionScript и Pixel Bender в Вашем приложении, и о тех подводных камнях, которые могут Вас подстеригать в этом деле. Гуру флэша и иже с ними могут пойти, попить чайку. Им эта статья вряд ли будет интересна. Но тем кто только знакомится с этими вещами, данная статья поможет не наступить лишний раз на грабли.



Итак! С чего же начать?.. Ну, наверное, лучше начать с вопроса: «Почему именно для ВКонтакте?». Ответ прост: мне хотелось разобраться с их API:) На самом деле это не принципиально. Просто с приложениями, размещенными на сторонних серверах ситуация складывается несколько иная, нежели с приложениями, размещенными на Вашем сайте. Но обо всем по порядку.

В данной статье я не буду останавливаться на вопросах написания самих фильтров. Будем считать, что у Вас они уже есть. Ну хотя бы один… Если все таки нет, то вот здесь и еще вон там есть несколько примеров. Что же с ними делать, когда у Вас на руках уже есть файл фильтра с расширением .pbj?

Существует по крайней мере два способа подключения фильтров Pixel Bender в Ваше Flash/Flex приложение. Первый способ подразумевает встраивание фильтра при помощи метатеггов:

[Embed(source="SomeFilter.pbj", mimeType="application/octet-stream")]
private var _someFilter:Class;



В этом случае дальнейшее использование фильтра будет происходить примерно по таком сценарию:

var shader:Shader = new Shader(new _someFilter() as ByteArray);
var shaderFilter:ShaderFilter = new ShaderFilter(shader);

var bitmap:Bitmap = new Bitmap(someBitmapData);
bitmap.filters = [shaderFilter];



Свойство filters представляет собой массив фильтров. Но простое добавление нового фильтра в конец массива (например методом push([...])) не повлечет за собой пременения этого фильтра. Нужно сначала добавить фильтр в массив, а затем переприсвоить этот массив свойству filters.

Второй способ подразумевает загрузку фильтров во время выполнения:

var urlLoader:URLLoader = new URLLoader();
urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
urlLoader.addEventListener(Event.COMPLETE, onLoadComplete);
urlLoader.load(new URLRequest("SomeShader.pbj"));
var shader:Shader;

function onLoadComplete(event:Event):void {
shader = new Shader();
shader.byteCode = loader.data;

var shaderFilter:ShaderFilter = new ShaderFilter(shader);
var bitmap:Bitmap = new Bitmap(someBitmapData);
bitmap.filters = [shaderFilter];
}



Вообще, после загрузки (или встраивания) фильтра уже не имеет значения что это за фильтр: написанный на Pixel Bender'е или базовый фильтр языка. Дальнейшее их использование в общих чертах схоже.

Так что там насчет камней?



Теперь поговорим о особенностях применения этих фильтров для Вашего приложения.

Рекламная пауза:
Приложение, которое я разрабатывал представляет собой небольшой редактор фотографий, который позволяет изменять такие характеристики изображения как яркость, контрастность, насыщенность и осветление. Можно изменять как значения всех каналов изображения, так и каждого канала по отдельности. Также есть возможность выставлять пороговые значения срабатывания фильтра.

Выбор альбомов:

первая картинка

Редактирование фотографии:

вторая картинка

Первый подводный камень о котором я говорил был в том, что изображения (в данном случае фотографии пользователя) мы можем получить, используя API. Но вот получить доступ к данным изображения мы не можем:) О чем я говорю? Вот о чем:

Допустим, у нас есть некая переменная в которую мы запихнули строку, содержащую путь к интересующей нас фотографии на сервере (этот путь мы получили в результате запроса к серверу). Далее, используя класс Image из Flex framework мы пишем примерно следующее:

[Bindable]
private var _photoSrc:String = "path/to/image";
<mx:Image id="curPhoto" source="{this._photoSrc}" />



Далее, где-нибудь в коде (в моем случае это происходило в обработчике события перетаскивания ползунка слайдера), мы пишем следующее:

curPhoto.filters = [shaderFilter];



Фильтр вроде бы применился -> картинка на экране стала немного другой -> мы довольны -> можно заливать на сервер ВКонтакте.

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

Рассмотрим другой вариант развития событий.

Допустим, что для загрузки изображения Вы воспользовались классом Loader. При использовании этого метода есть два ключевых момента:

1. Метод load() объекта класса Loader в общем случае должен иметь следующую сигнатуру:

loader.load(new URLRequest(this._photoSrc), new LoaderContext(true));



Вторым аргументом метода должен быть объект класса LoaderContext первым аргументом которого будет значение true. В результате этого Flash Player попытается загрузить файл политики безопасности прежде, чем начать загружать сам файл изображения (вариант использования Security.allowDomain("*"); в этом случае не канает).

2. В обработчике события Event.COMPLETE у нас должно быть следущее:

var bitmapData:BitmapData = new BitmapData((event.currentTarget as LoaderInfo).content.width, (event.currentTarget as LoaderInfo).content.height);
bitmapData.draw(this.loader);

this.bitmap = new Bitmap(bitmapData);
this.curPhoto.source = this.bitmap;
this.bitmap.filters = [shaderFilter];



Важным является то, что в метод draw() мы должны передавать не this.loader.content, а сам объект this.loader. Почему так, и что это — баг или фича? Я, если честно, так и не понял. Но это работает, а все другие варианты приводят к нарушению политики безопасности изолированной среды.

Итак, объект bitmap у нас в кармане, его можно добавлять в список отображения, применять к нему фильтры и загружать на сервер. Истории конец? Не совсем.

Второй подводный камень состоит в том, что не все фильтры одинаково полезны применять фильтры не так просто. Дело в том, что когда мы применяем фильтр посредством свойства filters, мы применяем его не к самим данным этого изображения, а только к экранному объекту этого изображения. Напомню, что в классе BitmapData содержатся фактические данные изображения, а в классе Bitmap — экранный объект, который можно включить в список отображения. Как Вы помните, мы присваивали наш фильтр свойству filters объекта Bitmap, а это значит, что сам объект BitmapData (содержащийся в свойстве bitmapData объекта this.bitmap) остался без изменения (то же самое в общем-то происходило и тогда, когда мы пытались загрузить на сервер экземпляр объекта Image в случае, описанном выше).

Казалось бы, выход очевиден — нужно применить метод applyFilter([...]) к какому-нибудь промежуточному объекту BitmapData (в этом случае первым аргументом метода был бы объект BitmapData из свойства this.bitmap.bitmapData). Но в моем случае, когда реализована поддержка изменения нескольких свойств, применение каждого последующего фильтра перезаписывало все изменения предыдущего фильтра. Т.е. после любых манипуляций были видны лишь изменения от применения последнего фильтра. К сожалению, использовать в этом методе не один фильтр, а массив фильтров, нельзя.

Как же быть?



Выход был найден несколько необычный. Помните наш объект класса Image? Здесь он нам пригодился. Вот примерный код:

var bitmapdata:BitmapData = new BitmapData((curPhoto.content as Bitmap).width, (curPhoto.content as Bitmap).height);
bitmapdata.draw(curPhoto.content as Bitmap);

var jpgEncoder:JPGEncoder = new JPGEncoder(85);
this.jpgStream = jpgEncoder.encode(bitmapdata);

// и дальше заливаем на сервак...



Т.е. мы создали промежуточный объект класса BitmapData и отрисовали в нем содержимое нашего объекта Image. Как мы помним, ранее мы присвоили экземпляр this.bitmap класса Bitmap (к которому применяли фильтр (-ы)) свойству source нашего объекта класса Image. Это значит, что фактически мы не работали с данными самого изображения, применяя фильтры, а только лишь с его экранным объектом.

Рекламная пауза:
Чтобы показать результат применения фильтров в моем приложении, приведу пару скриншотов (до и после применения фильтров).

Фото до редактирования:

до

… и после:

после


Некоторые соображения по теме:



1. В принципе, для редактирования тех параметров, которые есть в моем приложении на данный момент, можно было бы использовать и сверточный фильтр языка ActionScript (ConvolutionFilter). Насколько я могу судить, его бы хватило. Но мне просто хотелось получше разобраться с Pixel Bender'ом:)). Pixel Bender больше подходит для создания сложных фильтров. В нем проще и понятней работать с изображением (точнее с его пикселами). При использовании же сверточного фильтра, приходится работать с матрицами. В общем — это личное дело каждого.

2. Фильтры Pixel Bender лучше все же встраивать в проект, чем загружать во время выполнения. Они достаточно легковесны (ни один из моих фильтров не весил больше одного килобайта). Лишний код ни к чему.

3. API ВКонтакте в целом хорош. Немного напрягает этап проверки администрацией. Читая вопросы-ответы других разработчиков в теме «Вопросы по поводу создания приложений» раздела «Обсуждения» группы FLASH API, я понял, что для того чтобы твое приложение утвердили, нужно чтобы оно не только соответствовало всем требованиям, но и еще чтоб была нужная фаза луны (да и парад планет походу бы не помешал...). Вероятно, мне просто повезло, что приложение одобрили с первого раза. Хех, к слову сказать, приложение было написано с нуля за неделю, а проверяли его еще почти неделю… Еще б чуть-чуть и было бы смешно.

4. Отнюдь не порадовала обратная связь с разработчиками. Да, есть группа FLASH API, там есть тема в которой можно задавать свои вопросы по поводу создания приложений. НО! На вопросы, насколько я понял, отвечают люди, которые к администрации сайта отношения не имеют в принципе (т.е. в большинстве случаев другие разработчики, уже набившие себе шишки). И еще. Когда я искал ответ на один вопрос я понял две вещи:

  1. Вопросы в данной теме группы FLASH API повторяются в лучшем случае раз по пять каждый.
  2. Чтобы не быть долбозвончиком, я честно пытался найти в этой теме ответ на свой вопрос. Логика была проста: кто-то же должен был сталкиваться с этим раньше. Но, блин, дойдя до «двадцать-какой-то» страницы я понял, что уже забыл что искал. Если кто-то из близких к администрации сайта людей сейчас читает этот пост, то вам должно быть стыдно. Там напрочь отсутствует поиск или хоть какая-нибудь структура.


P.S.: Ну и в завершение темы приведу ссылку на само приложение. Все пожелания / предложения по расширению проекта приветствуются. Если приложение понравится пользователям, то могу реализовать почти все, что есть в фоторедакторе на сайте picnik.com, и даже больше (конечно, в рамках API ВКонтакте;)).
Теги:
Хабы:
Всего голосов 9: ↑6 и ↓3+3
Комментарии5

Публикации

Истории

Ближайшие события

2 – 18 декабря
Yandex DataLens Festival 2024
МоскваОнлайн
11 – 13 декабря
Международная конференция по AI/ML «AI Journey»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань