![](https://habrastorage.org/web/71f/1f7/f98/71f1f7f9869a45c8bb341fee77993931.png)
Как менялся фоторедактор для Android — от первой версии Snapster до фильтров в официальном приложении ВКонтакте.
Эта история началась в 2015 году, когда мы приступили к созданию Snapster — самостоятельного приложения для редактирования фотографий. В то время и в наших официальных приложениях, и в том же Instagram редактирование изображений было устроено примитивно. Берём снимок, применяем к нему простые модификации цвета с помощью OpenGL-шейдеров, накладываем поверх текстуры, чтобы придать гламурности. Готово.
Спустя несколько месяцев с начала разработки, когда уже была готова первая версия Snapster, в ней всё ещё использовались фильтры из основного приложения ВКонтакте. В этот момент мы стали понимать, что существующий подход себя изжил, и нужно придумать что-то новое.
Мы решили, что будет круто, если пользователи смогут создавать собственные фильтры прямо на телефоне. Процесс создания фильтра должен быть простым — чтобы даже ткнув пару кнопок наугад можно было получить приличный результат. Вдобавок редактор должен работать очень быстро — производительность крайне важна, а значит, нам нужно по максимуму использовать возможности встроенных GPU.
Поскольку из крутилок для базовых параметров (яркость, контраст и т.п.) действительно уникальные фильтры не получатся, мы решили добавить в редактор полноценную цветокоррекцию.
Цветокоррекция
Базовый алгоритм состоит из двух шагов:
- Найти на изображении все пиксели, которые подходят под заданные условия. Например, если мы хотим заменить все чисто красные пиксели на что-то другое, то нужно найти такие пиксели, в которых в цветовом пространстве RGB компонента R будет равна 255, а G и B — нулю.
- Заменить их на пиксели нужного цвета.
Звучит довольно просто. Но сразу возникает несколько вопросов:
- Как задавать критерии поиска? Очевидно, что условия вроде «компонента R равна 255, а G и B — нулю» не применимы на практике. Как объяснить алгоритму, что надо выбирать пиксели примерно такого цвета?
- Как быстро искать нужные пиксели в изображении? Если условия для поиска будут слишком сложными, то сносной работы можно ожидать разве что на очень мощных устройствах.
- Если под критерии поиска могут попадать пиксели разных цветов, то и менять их нужно не строго на цвет N, а с учётом их исходного цвета.
Итак, чтобы найти пиксели нужного цвета, надо сформировать критерии поиска. Допустим, мы хотим найти все пиксели, которые отличаются от искомого цвета не более чем на N, где N — динамически задаваемая погрешность. Как это сделать? Так как в разных цветовых моделях цвет раскладывается на отдельные компоненты, являющиеся, по сути, координатами, то для расчета разницы между двумя цветами мы можем использовать евклидово расстояние.
Сначала мы попробовали находить нужные пиксели изображения, считая расстояние между их значениями в цветовой модели RGB. Но результаты поиска были очень далеки от идеальных. Пиксели, цвета которых, на взгляд человека, очень похожи на нужные, могут находиться на значительном евклидовом расстоянии друг от друга. Всё дело в том, что изменение цвета в модели RGB очень нелинейно с точки зрения человеческого восприятия. Иными словами, небольшое изменение значения RGB может быть очень заметно человеческому глазу и наоборот. Сравните:
![](https://habrastorage.org/web/82b/79d/b71/82b79db71e9e449f9ed0433195a252fa.png)
RGB (0,0,0) → RGB (50,0,0)
![](https://habrastorage.org/web/ff0/625/b25/ff0625b251cd4377940c03305dbf8320.png)
RGB (205,0,0) → RGB (255,0,0)
Чтобы решить эту проблему, мы перешли к использованию цветовой модели CIELab.
CIELab — это цветовая модель, максимально приближенная к человеческой системе восприятия цвета. В CIELab любой цвет однозначно определяется светлотой L и двумя хроматическими компонентами: a — позиция между зелёным и пурпурным цветами, и b — позиция между синим и жёлтым цветами.
![](https://habrastorage.org/web/e4c/c3a/58d/e4cc3a58df574df391b3c6a7d9b0309c.png)
Схема CIELab
CIELab позволяет оценивать разницу цветов так, как её видит человеческий глаз, с помощью простого расчёта евклидова расстояния между двумя цветами. Другими словами, теперь с помощью условия «если евклидово расстояние между Lab-значением пикселя и Lab-значением цвета Х меньше N, то пиксель нам подходит» можно находить пиксели, которые имеют примерно такой же цвет как искомый, в пределах некоторой погрешности N.
Кроме того, используя эту модель, можно очень естественно для человеческого глаза заменять найденные пиксели на нужный цвет. На основе того же евклидова расстояния мы можем рассчитать, как именно изменить цвет, сохранив натуральность изображения.
![](https://habrastorage.org/web/6be/940/cc2/6be940cc25fd48a9a4f0c11455f6b474.png)
Изменение выбранного цвета
Первая реализация
Мы хотели дать пользователю возможность сразу видеть результат на экране. К тому же, решение должно быть кроссплатформенным. Поэтому единственным вариантом был OpenGL.
Помимо цветокоррекции, редактор должен был уметь автоматически улучшать изображение, а также производить базовые операции вроде изменения яркости, контрастности и тому подобных характеристик.
Автоулучшение в нашем редакторе работает на основе техники выравнивания гистограммы. Суть в том, чтобы приблизить гистограмму исходного изображения к линейной функции. Таких техник существует несколько, мы используем CLAHE.
CLAHE (contrast-limited adaptive histogram equalization) — контрастно-ограниченное адаптивное выравнивание гистограммы. Адаптивное — потому что гистограмма рассматривается не для всего изображения разом, а для его небольших фрагментов в отдельности. Контрастно-ограниченное — потому что контраст меняется только в заданных пределах, что позволяет избежать нежелательного усиления шумов на снимке.
CLAHE выполняется на процессоре, но для каждого запуска редактора это случается один раз, а полученная текстура сохраняется в in-memory cache. Поэтому здесь не было особых требований к производительности.
В первой реализации конвейер редактора выглядел так:
- Применение автоулучшения с заданной интенсивностью.
- Применение базовых операций.
- Конвертация получившегося изображения в Lab.
- Применение цветокоррекции.
Стоит отметить, что мы дали пользователям возможность выбирать любые цвета в любом количестве для цветокоррекции. Поэтому OpenGL-шейдер собирали динамически прямо в то время, как пользователь выбирал и редактировал цвета.
Конвертация из RGB в Lab была сделана в лоб, без каких-либо оптимизаций. Это значит, что сначала значение цвета из RGB конвертируется в XYZ, а уже потом из XYZ в Lab. Обе конвертации довольно тяжёлые для вычисления, и в итоге это оказалось бутылочным горлышком для всего редактора. Для решения проблемы мы обратились к технологии 3D Lookup Table.
Её смысл предельно прост. 3D LUT — это трёхмерная таблица, которая хранит соответствие входных и выходных значений цветов. Для любого входного значения в одной цветовой модели мы можем однозначно установить выходное значение в другой. И это работает очень быстро!
![](https://habrastorage.org/web/ceb/5bf/535/ceb5bf535518474aa554daf200cc1a2c.png)
3D LUT Lab
Такая схема организации конвейера показала себя относительно хорошо. Но, конечно, богатый мир Android-устройств добавил нам проблем. От банальной нехватки видеопамяти и чудовищно низкой производительности на некоторых девайсах до жёстких ограничений на число инструкций в одном шейдере на некоторых GPU, из-за которых в некоторых случаях шейдер цветокоррекции даже не мог скомпилироваться. И если проблему с недостатком видеопамяти и низкой производительностью можно было решить, снизив разрешение рендеринга на слабых девайсах, то проблема с числом инструкций была не так проста. Нам пришлось научиться дробить шейдер цветокоррекции на части — причём динамически, и только когда это необходимо, ибо каждый дополнительный шаг в конвейере снижал производительность.
Что дальше?
После релиза первой версии захотелось добавить больше возможностей для редактирования. Так в редакторе появился популярный у фотографов инструмент Tone Curve, всеми любимые «кривые». С помощью кривых в приложении можно воздействовать на яркость пикселей в конкретных диапазонах RGB. При этом значения пикселей меняются в соответствии с формой кривой, которую пользователь видит на экране.
![](https://habrastorage.org/web/863/399/27a/86339927a1be4f40bcd91fe4442050e8.png)
Редактирование с помощью кривых в Snapster
Внедрение кривых помогло оптимизировать некоторые базовые операции. Теперь сами кривые, яркость, контрастность, фейд, температура и тинт (оттенок) стали применяться за один проход с помощью трёх 1D Lookup Tables.
Использование OpenGL означает довольно жёсткие ограничение на размеры выходного изображения, поскольку все текстуры должны храниться в видеопамяти. Некоторые Android-девайсы в то время едва ли могли отрендерить квадрат размером больше 1500х1500 пикселей с нашим конвейером, а нам хотелось даже большего. И если с самим редактором мы ничего поделать не могли, то для рендеринга финального результата решение нашлось. Мы продублировали всю функциональность конвейера на чистом Си. Получившийся код хранил все промежуточные текстуры в обычной оперативной памяти и по максимуму использовал возможности многоядерных процессоров. Благодаря этому пользователи даже далеко не самых мощных устройств смогли сохранять итоговые изображения в очень высоких разрешениях.
Фильтры в основном приложении
![](https://habrastorage.org/web/ce1/a5b/d48/ce1a5bd48cad4034bbd6b8924126b6a2.png)
Выбор фильтра, ВКонтакте для Android
Когда пришло время обновлять фоторедактор в основном приложении ВКонтакте для Android, мы решили использовать в нём наработки из Snapster. Сейчас там используется тот же набор стандартных фильтров. Редактор позволяет переключать фильтры свайпом по изображению, причём на экране могут одновременно находиться сразу два фильтра. При быстрых свайпах не происходит никаких задержек или подгрузок — для пользователя всё происходит мгновенно.
Полноценный конвейер из редактора Snapster не позволил бы добиться таких результатов. Он слишком тяжёлый и приспоболен под гораздо более точную работу с фотографией. Здесь важную роль сыграло осознание того факта, что почти весь конвейер фильтра статичен. Если в Snapster у пользователя была возможность менять любой фильтр как угодно, то в основном приложении мы хотели дать возможность лишь применять уже готовые фильтры. Это позволило нам использовать уже знакомую технологию 3D Lookup Table для применения фильтра. Вместо конвертации из RGB в Lab мы конвертируем цвет из оригинального фото в цвета фильтра, и это происходит очень быстро.
Подведём итоги
Нам удалось прийти от топорных модификаций цвета к мощному и гибкому инструменту для продвинутой коррекции изображений. Мы сумели сделать так, чтобы это работало, и работало быстро. Snapster стал полем для эксперимента, и это успешный эксперимент — мы создали крутой редактор, которым можно гордиться. Этот опыт помог нам и в официальном приложении ВКонтакте. Не исключено, что мы перенесём туда ещё какую-то часть функциональности Snapster.
Если Вы знаете и любите Android, приходите к нам, чтобы работать над приложением, которым каждым день пользуются миллионы людей.