Данная статья была написана в продолжение темы, затронутой здесь, по просьбе пользователя namata. В статье речь пойдет о использовании фильтров ActionScript и Pixel Bender в Вашем приложении, и о тех подводных камнях, которые могут Вас подстеригать в этом деле. Гуру флэша и иже с ними могут пойти, попить чайку. Им эта статья вряд ли будет интересна. Но тем кто только знакомится с этими вещами, данная статья поможет не наступить лишний раз на грабли.
Итак! С чего же начать?.. Ну, наверное, лучше начать с вопроса: «Почему именно для ВКонтакте?». Ответ прост: мне хотелось разобраться с их API:) На самом деле это не принципиально. Просто с приложениями, размещенными на сторонних серверах ситуация складывается несколько иная, нежели с приложениями, размещенными на Вашем сайте. Но обо всем по порядку.
В данной статье я не буду останавливаться на вопросах написания самих фильтров. Будем считать, что у Вас они уже есть. Ну хотя бы один… Если все таки нет, то вот здесь и еще вон там есть несколько примеров. Что же с ними делать, когда у Вас на руках уже есть файл фильтра с расширением .pbj?
Существует по крайней мере два способа подключения фильтров Pixel Bender в Ваше Flash/Flex приложение. Первый способ подразумевает встраивание фильтра при помощи метатеггов:
В этом случае дальнейшее использование фильтра будет происходить примерно по таком сценарию:
Свойство filters представляет собой массив фильтров. Но простое добавление нового фильтра в конец массива (например методом push([...])) не повлечет за собой пременения этого фильтра. Нужно сначала добавить фильтр в массив, а затем переприсвоить этот массив свойству filters.
Второй способ подразумевает загрузку фильтров во время выполнения:
Вообще, после загрузки (или встраивания) фильтра уже не имеет значения что это за фильтр: написанный на Pixel Bender'е или базовый фильтр языка. Дальнейшее их использование в общих чертах схоже.
Теперь поговорим о особенностях применения этих фильтров для Вашего приложения.
Рекламная пауза:
Приложение, которое я разрабатывал представляет собой небольшой редактор фотографий, который позволяет изменять такие характеристики изображения как яркость, контрастность, насыщенность и осветление. Можно изменять как значения всех каналов изображения, так и каждого канала по отдельности. Также есть возможность выставлять пороговые значения срабатывания фильтра.
Выбор альбомов:
Редактирование фотографии:
Первый подводный камень о котором я говорил был в том, что изображения (в данном случае фотографии пользователя) мы можем получить, используя API. Но вот получить доступ к данным изображения мы не можем:) О чем я говорю? Вот о чем:
Допустим, у нас есть некая переменная в которую мы запихнули строку, содержащую путь к интересующей нас фотографии на сервере (этот путь мы получили в результате запроса к серверу). Далее, используя класс Image из Flex framework мы пишем примерно следующее:
Далее, где-нибудь в коде (в моем случае это происходило в обработчике события перетаскивания ползунка слайдера), мы пишем следующее:
Фильтр вроде бы применился -> картинка на экране стала немного другой -> мы довольны -> можно заливать на сервер ВКонтакте.
Но погодите радоваться. В этом случае на сервер будет отправлено точно такое же изображение, как и на оригинале. Фильтр облажался? Ничуть.
Рассмотрим другой вариант развития событий.
Допустим, что для загрузки изображения Вы воспользовались классом Loader. При использовании этого метода есть два ключевых момента:
1. Метод load() объекта класса Loader в общем случае должен иметь следующую сигнатуру:
Вторым аргументом метода должен быть объект класса LoaderContext первым аргументом которого будет значение true. В результате этого Flash Player попытается загрузить файл политики безопасности прежде, чем начать загружать сам файл изображения (вариант использования Security.allowDomain("*"); в этом случае не канает).
2. В обработчике события Event.COMPLETE у нас должно быть следущее:
Важным является то, что в метод draw() мы должны передавать не this.loader.content, а сам объект this.loader. Почему так, и что это — баг или фича? Я, если честно, так и не понял. Но это работает, а все другие варианты приводят к нарушению политики безопасности изолированной среды.
Итак, объект bitmap у нас в кармане, его можно добавлять в список отображения, применять к нему фильтры и загружать на сервер. Истории конец? Не совсем.
Второй подводный камень состоит в том, чтоне все фильтры одинаково полезны применять фильтры не так просто. Дело в том, что когда мы применяем фильтр посредством свойства filters, мы применяем его не к самим данным этого изображения, а только к экранному объекту этого изображения. Напомню, что в классе BitmapData содержатся фактические данные изображения, а в классе Bitmap — экранный объект, который можно включить в список отображения. Как Вы помните, мы присваивали наш фильтр свойству filters объекта Bitmap, а это значит, что сам объект BitmapData (содержащийся в свойстве bitmapData объекта this.bitmap) остался без изменения (то же самое в общем-то происходило и тогда, когда мы пытались загрузить на сервер экземпляр объекта Image в случае, описанном выше).
Казалось бы, выход очевиден — нужно применить метод applyFilter([...]) к какому-нибудь промежуточному объекту BitmapData (в этом случае первым аргументом метода был бы объект BitmapData из свойства this.bitmap.bitmapData). Но в моем случае, когда реализована поддержка изменения нескольких свойств, применение каждого последующего фильтра перезаписывало все изменения предыдущего фильтра. Т.е. после любых манипуляций были видны лишь изменения от применения последнего фильтра. К сожалению, использовать в этом методе не один фильтр, а массив фильтров, нельзя.
Выход был найден несколько необычный. Помните наш объект класса Image? Здесь он нам пригодился. Вот примерный код:
Т.е. мы создали промежуточный объект класса BitmapData и отрисовали в нем содержимое нашего объекта Image. Как мы помним, ранее мы присвоили экземпляр this.bitmap класса Bitmap (к которому применяли фильтр (-ы)) свойству source нашего объекта класса Image. Это значит, что фактически мы не работали с данными самого изображения, применяя фильтры, а только лишь с его экранным объектом.
Рекламная пауза:
Чтобы показать результат применения фильтров в моем приложении, приведу пару скриншотов (до и после применения фильтров).
Фото до редактирования:
… и после:
1. В принципе, для редактирования тех параметров, которые есть в моем приложении на данный момент, можно было бы использовать и сверточный фильтр языка ActionScript (ConvolutionFilter). Насколько я могу судить, его бы хватило. Но мне просто хотелось получше разобраться с Pixel Bender'ом:)). Pixel Bender больше подходит для создания сложных фильтров. В нем проще и понятней работать с изображением (точнее с его пикселами). При использовании же сверточного фильтра, приходится работать с матрицами. В общем — это личное дело каждого.
2. Фильтры Pixel Bender лучше все же встраивать в проект, чем загружать во время выполнения. Они достаточно легковесны (ни один из моих фильтров не весил больше одного килобайта). Лишний код ни к чему.
3. API ВКонтакте в целом хорош. Немного напрягает этап проверки администрацией. Читая вопросы-ответы других разработчиков в теме «Вопросы по поводу создания приложений» раздела «Обсуждения» группы FLASH API, я понял, что для того чтобы твое приложение утвердили, нужно чтобы оно не только соответствовало всем требованиям, но и еще чтоб была нужная фаза луны (да и парад планет походу бы не помешал...). Вероятно, мне просто повезло, что приложение одобрили с первого раза. Хех, к слову сказать, приложение было написано с нуля за неделю, а проверяли его еще почти неделю… Еще б чуть-чуть и было бы смешно.
4. Отнюдь не порадовала обратная связь с разработчиками. Да, есть группа FLASH API, там есть тема в которой можно задавать свои вопросы по поводу создания приложений. НО! На вопросы, насколько я понял, отвечают люди, которые к администрации сайта отношения не имеют в принципе (т.е. в большинстве случаев другие разработчики, уже набившие себе шишки). И еще. Когда я искал ответ на один вопрос я понял две вещи:
P.S.: Ну и в завершение темы приведу ссылку на само приложение. Все пожелания / предложения по расширению проекта приветствуются. Если приложение понравится пользователям, то могу реализовать почти все, что есть в фоторедакторе на сайте picnik.com, и даже больше (конечно, в рамках API ВКонтакте;)).
Итак! С чего же начать?.. Ну, наверное, лучше начать с вопроса: «Почему именно для ВКонтакте?». Ответ прост: мне хотелось разобраться с их 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 у нас в кармане, его можно добавлять в список отображения, применять к нему фильтры и загружать на сервер. Истории конец? Не совсем.
Второй подводный камень состоит в том, что
Казалось бы, выход очевиден — нужно применить метод 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, там есть тема в которой можно задавать свои вопросы по поводу создания приложений. НО! На вопросы, насколько я понял, отвечают люди, которые к администрации сайта отношения не имеют в принципе (т.е. в большинстве случаев другие разработчики, уже набившие себе шишки). И еще. Когда я искал ответ на один вопрос я понял две вещи:
- Вопросы в данной теме группы FLASH API повторяются в лучшем случае раз по пять каждый.
- Чтобы не быть долбозвончиком, я честно пытался найти в этой теме ответ на свой вопрос. Логика была проста: кто-то же должен был сталкиваться с этим раньше. Но, блин, дойдя до «двадцать-какой-то» страницы я понял, что уже забыл что искал. Если кто-то из близких к администрации сайта людей сейчас читает этот пост, то вам должно быть стыдно. Там напрочь отсутствует поиск или хоть какая-нибудь структура.
P.S.: Ну и в завершение темы приведу ссылку на само приложение. Все пожелания / предложения по расширению проекта приветствуются. Если приложение понравится пользователям, то могу реализовать почти все, что есть в фоторедакторе на сайте picnik.com, и даже больше (конечно, в рамках API ВКонтакте;)).