Как менялся фоторедактор для Android — от первой версии Snapster до фильтров в официальном приложении ВКонтакте.

Эта история началась в 2015 году, когда мы приступили к созданию Snapster — самостоятельного приложения для редактирования фотографий. В то время и в наших официальных приложениях, и в том же Instagram редактирование изображений было устроено примитивно. Берём снимок, применяем к нему простые модификации цвета с помощью OpenGL-шейдеров, накладываем поверх текстуры, чтобы придать гламурности. Готово.

Спустя несколько месяцев с начала разработки, когда уже была готова первая версия Snapster, в ней всё ещё использовались фильтры из основного приложения ВКонтакте. В этот момент мы стали понимать, что существующий подход себя изжил, и нужно придумать что-то новое.

Мы решили, что будет круто, если пользователи смогут создавать собственные фильтры прямо на телефоне. Процесс создания фильтра должен быть простым — чтобы даже ткнув пару кнопок наугад можно было получить приличный результат. Вдобавок редактор должен работать очень быстро — производительность крайне важна, а значит, нам нужно по максимуму использовать возможности встроенных GPU.

Поскольку из крутилок для базовых параметров (яркость, контраст и т.п.) действительно уникальные фильтры не получатся, мы решили добавить в редактор полноценную цветокоррекцию.

Цветокоррекция


Базовый алгоритм состоит из двух шагов:

  1. Найти на изображении все пиксели, которые подходят под заданные условия. Например, если мы хотим заменить все чисто красные пиксели на что-то другое, то нужно найти такие пиксели, в которых в цветовом пространстве RGB компонента R будет равна 255, а G и B — нулю.
  2. Заменить их на пиксели нужного цвета.

Звучит довольно просто. Но сразу возникает несколько вопросов:

  1. Как задавать критерии поиска? Очевидно, что условия вроде «компонента R равна 255, а G и B — нулю» не применимы на практике. Как объяснить алгоритму, что надо выбирать пиксели примерно такого цвета?
  2. Как быстро искать нужные пиксели в изображении? Если условия для поиска будут слишком сложными, то сносной работы можно ожидать разве что на очень мощных устройствах.
  3. Если под критерии поиска могут попадать пиксели разных цветов, то и менять их нужно не строго на цвет N, а с учётом их исходного цвета.

Итак, чтобы найти пиксели нужного цвета, надо сформировать критерии поиска. Допустим, мы хотим найти все пиксели, которые отличаются от искомого цвета не более чем на N, где N — динамически задаваемая погрешность. Как это сделать? Так как в разных цветовых моделях цвет раскладывается на отдельные компоненты, являющиеся, по сути, координатами, то для расчета разницы между двумя цветами мы можем использовать евклидово расстояние.

Сначала мы попробовали находить нужные пиксели изображения, считая расстояние между их значениями в цветовой модели RGB. Но результаты поиска были очень далеки от идеальных. Пиксели, цвета которых, на взгляд человека, очень похожи на нужные, могут находиться на значительном евклидовом расстоянии друг от друга. Всё дело в том, что изменение цвета в модели RGB очень нелинейно с точки зрения человеческого восприятия. Иными словами, небольшое изменение значения RGB может быть очень заметно человеческому глазу и наоборот. Сравните:


RGB (0,0,0) > RGB (50,0,0)


RGB (205,0,0) > RGB (255,0,0)

Чтобы решить эту проблему, мы перешли к использованию цветовой модели CIELab.

CIELab — это цветовая модель, максимально приближенная к человеческой системе восприятия цвета. В CIELab любой цвет однозначно определяется светлотой L и двумя хроматическими компонентами: a — позиция между зелёным и пурпурным цветами, и b — позиция между синим и жёлтым цветами.


Схема CIELab

CIELab позволяет оценивать разницу цветов так, как её видит человеческий глаз, с помощью простого расчёта евклидова расстояния между двумя цветами. Другими словами, теперь с помощью условия «если евклидово расстояние между Lab-значением пикселя и Lab-значением цвета Х меньше N, то пиксель нам подходит» можно находить пиксели, которые имеют примерно такой же цвет как искомый, в пределах некоторой погрешности N.

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


Изменение выбранного цвета

Первая реализация


Мы хотели дать пользователю возможность сразу видеть результат на экране. К тому же, решение должно быть кроссплатформенным. Поэтому единственным вариантом был OpenGL.

Помимо цветокоррекции, редактор должен был уметь автоматически улучшать изображение, а также производить базовые операции вроде изменения яркости, контрастности и тому подобных характеристик.

Автоулучшение в нашем редакторе работает на основе техники выравнивания гистограммы. Суть в том, чтобы приблизить гистограмму исходного изображения к линейной функции. Таких техник существует несколько, мы используем CLAHE.

CLAHE (contrast-limited adaptive histogram equalization) — контрастно-ограниченное адаптивное выравнивание гистограммы. Адаптивное — потому что гистограмма рассматривается не для всего изображения разом, а для его небольших фрагментов в отдельности. Контрастно-ограниченное — потому что контраст меняется только в заданных пределах, что позволяет избежать нежелательного усиления шумов на снимке.

CLAHE выполняется на процессоре, но для каждого запуска редактора это случается один раз, а полученная текстура сохраняется в in-memory cache. Поэтому здесь не было особых требований к производительности.

В первой реализации конвейер редактора выглядел так:

  1. Применение автоулучшения с заданной интенсивностью.
  2. Применение базовых операций.
  3. Конвертация получившегося изображения в Lab.
  4. Применение цветокоррекции.

Стоит отметить, что мы дали пользователям возможность выбирать любые цвета в любом количестве для цветокоррекции. Поэтому OpenGL-шейдер собирали динамически прямо в то время, как пользователь выбирал и редактировал цвета.

Конвертация из RGB в Lab была сделана в лоб, без каких-либо оптимизаций. Это значит, что сначала значение цвета из RGB конвертируется в XYZ, а уже потом из XYZ в Lab. Обе конвертации довольно тяжёлые для вычисления, и в итоге это оказалось бутылочным горлышком для всего редактора. Для решения проблемы мы обратились к технологии 3D Lookup Table.

Её смысл предельно прост. 3D LUT — это трёхмерная таблица, которая хранит соответствие входных и выходных значений цветов. Для любого входного значения в одной цветовой модели мы можем однозначно установить выходное значение в другой. И это работает очень быстро!


3D LUT Lab

Такая схема организации конвейера показала себя относительно хорошо. Но, конечно, богатый мир Android-устройств добавил нам проблем. От банальной нехватки видеопамяти и чудовищно низкой производительности на некоторых девайсах до жёстких ограничений на число инструкций в одном шейдере на некоторых GPU, из-за которых в некоторых случаях шейдер цветокоррекции даже не мог скомпилироваться. И если проблему с недостатком видеопамяти и низкой производительностью можно было решить, снизив разрешение рендеринга на слабых девайсах, то проблема с числом инструкций была не так проста. Нам пришлось научиться дробить шейдер цветокоррекции на части — причём динамически, и только когда это необходимо, ибо каждый дополнительный шаг в конвейере снижал производительность.

Что дальше?


После релиза первой версии захотелось добавить больше возможностей для редактирования. Так в редакторе появился популярный у фотографов инструмент Tone Curve, всеми любимые «кривые». С помощью кривых в приложении можно воздействовать на яркость пикселей в конкретных диапазонах RGB. При этом значения пикселей меняются в соответствии с формой кривой, которую пользователь видит на экране.


Редактирование с помощью кривых в Snapster

Внедрение кривых помогло оптимизировать некоторые базовые операции. Теперь сами кривые, яркость, контрастность, фейд, температура и тинт (оттенок) стали применяться за один проход с помощью трёх 1D Lookup Tables.

Использование OpenGL означает довольно жёсткие ограничение на размеры выходного изображения, поскольку все текстуры должны храниться в видеопамяти. Некоторые Android-девайсы в то время едва ли могли отрендерить квадрат размером больше 1500х1500 пикселей с нашим конвейером, а нам хотелось даже большего. И если с самим редактором мы ничего поделать не могли, то для рендеринга финального результата решение нашлось. Мы продублировали всю функциональность конвейера на чистом Си. Получившийся код хранил все промежуточные текстуры в обычной оперативной памяти и по максимуму использовал возможности многоядерных процессоров. Благодаря этому пользователи даже далеко не самых мощных устройств смогли сохранять итоговые изображения в очень высоких разрешениях.

Фильтры в основном приложении



Выбор фильтра, ВКонтакте для Android

Когда пришло время обновлять фоторедактор в основном приложении ВКонтакте для Android, мы решили использовать в нём наработки из Snapster. Сейчас там используется тот же набор стандартных фильтров. Редактор позволяет переключать фильтры свайпом по изображению, причём на экране могут одновременно находиться сразу два фильтра. При быстрых свайпах не происходит никаких задержек или подгрузок — для пользователя всё происходит мгновенно.

Полноценный конвейер из редактора Snapster не позволил бы добиться таких результатов. Он слишком тяжёлый и приспоболен под гораздо более точную работу с фотографией. Здесь важную роль сыграло осознание того факта, что почти весь конвейер фильтра статичен. Если в Snapster у пользователя была возможность менять любой фильтр как угодно, то в основном приложении мы хотели дать возможность лишь применять уже готовые фильтры. Это позволило нам использовать уже знакомую технологию 3D Lookup Table для применения фильтра. Вместо конвертации из RGB в Lab мы конвертируем цвет из оригинального фото в цвета фильтра, и это происходит очень быстро.

Подведём итоги


Нам удалось прийти от топорных модификаций цвета к мощному и гибкому инструменту для продвинутой коррекции изображений. Мы сумели сделать так, чтобы это работало, и работало быстро. Snapster стал полем для эксперимента, и это успешный эксперимент — мы создали крутой редактор, которым можно гордиться. Этот опыт помог нам и в официальном приложении ВКонтакте. Не исключено, что мы перенесём туда ещё какую-то часть функциональности Snapster.

Если Вы знаете и любите Android, приходите к нам, чтобы работать над приложением, которым каждым день пользуются миллионы людей.

Комментарии (9)


  1. mSnus
    26.08.2017 05:43
    +4

    Спасибо за статью. Встроенный редактор просто ужасен, и теперь из статьи понятно, почему. Кстати, предыдущий редактор был намного лучше и удобнее, хотя тоже не блистал.


    1) Фильтры стали убогие.


    • Надо листать свайпами, выбрать из "превьюшек" нельзя.
    • Сравнить фильтр и оригинал нельзя — зачем-то можно только с предыдущим фильтром.
    • Задать степень применения фильтра (смешения с оригиналом) нельзя.
    • Всего фильтров — десяток… фантазия кончилась?

    Всё это — мощнейшие минусы к юзабилити. Откройте хоть разок Pixlr, или хотя бы Instagram для общего понимания, как надо. Фактически, идею фильтров вы похоронили.


    2) Рисование убого до невозможности. Прозрачность цвета не задать, цвет выбирается из 20 +10 серых. Вы слышали про такой цвет — тёмно-синий? а он есть… вы в 2017-м не научились выводить диалог выбора цвета?


    3) Рисовать пальцем, даже просто обвести кружком нужное место — нереально.


    Потому что:


    • простой зум вы реализовать не догадались, поэтому 20Мп фото будет сжиматься до размеров экрана, хотим мы или нет.


    • рисовать не под пальцем, а сбоку-сверху от него, видимо, тоже слишком сложная концепция. Будем загораживать пальцем то, что рисуем.


    • естественно, размер кисти при этом показывать не получится — все равно под пальцем не видно. Ластиком действуем вообще наугад.

    4) Чёрную или белую тень к тексту — увы, никак. Печаль, будем дважды один текст поверх другого печатать, как в Paint.


    5) В стикерах есть всё, кроме того, что может понадобиться нормальным людям — например, стрелок, восклицательного или вопросительного знака, круга, квадрата, треугольника… зато есть шапочка с оленьими рогами! Какие вы молодцы.


    6) То, что вы узнали о существовании пространства Lab — здорово. Ещё шаг, и дойдёте до HLS, преобразование в которое из RGB и обратно делается очень просто, и работа с оттенками там очень неплоха. Возможно, сумеете обойтись без использования 3D-видеоускорителя и OpenGL для простой обработки 2D-фото


    А так — 3D LUT таблицы для этого это костыль на костыле на костыле. Они хороши, когда надо быстро применить цветовой фильтр — например, к видеопотоку. Но для одной плоской картинки… господи, кто вам такое вообще посоветовал?


    7) То, что делает "Автоулучшение", лучше никому не показывать. Улучшать имеет смысл тремя параметрами:


    • вытягивать тени и пересветы / "сжимать" их (точки черного и белого)
    • увеличивать/уменьшать контрастность
    • увеличивать/уменьшать насыщенность

    Всего три параметра, Карл! В какую сторону их дергать автоматически — да, тут нужен алгоритм. Нет алгоритма — сделайте пресеты:
    +Compress+Hue+Contrast, -Compress+Hue+Contrast и т.д.


    Advanced level — подмешивать в цвета серый по типу Degradr, но это слишком уж круто.


    В общем, сейчас весь этот редактор не годится ни для чего. Кроме шапочки с оленьими рогами.


    1. dom1n1k
      26.08.2017 10:54

      HLS это в смысле HSL? Если имелось в виду оно, то нет, Lab оно заменить не в состоянии.


      1. mSnus
        26.08.2017 12:40

        Как назвать — дело вкуса) в дизайне принято HLS (hue, light, sat), но это неважно, можно и HSL.


        А что значит — "заменить не в состоянии"? Иногда Lab удобнее, вопрос в том, насколько выгодно использовать конверсию в Lab и работу в нем для описанных в статье операций — или же можно было для фильтров обойтись RGB / HLS.


        1. dom1n1k
          26.08.2017 13:36

          В «дизайне принято» — это где? Не встречал в реальной практике ни такого написания, ни собственно использования этой модели. Абсолютное большинство дизайнерского софта использует HSB/HSV. Модель HSL имеет широкую поддержку в вебе и некотором маргинальном софте, но на практике почти не используется из-за упомянутой нестыковки с миром графдизайна.

          Главный смысл Lab'а не удобстве, а в его перцептивной равномерности — он разрабатывался специально для этой цели. Никакие HSL/HSV/etc таким свойством не обладают.


          1. mSnus
            27.08.2017 07:56

            OK, про HSB/HSL убедили. Я все равно не об этом. Это всего лишь другие координаты того же пространства, что и RGB. А L-a-b — другое пространство, поэтому так затратно одно в другое преобразовывать. Вот я и задаюсь вопросом о целесообразности всего этого.


            Про Lab — мы же не работаем в этом редакторе с цветами напрямую, речь о готовых фильтрах. А


            1. dom1n1k
              27.08.2017 13:36

              Целесообразность в двух вещах:
              1. Они отбирают пиксели, подпадающие под действие фильтра с помощью метрики Delta E, которая работает только в Lab. Он был изначально придуман именно для этого.
              2. Многие цветокорректирующие штуки в Lab работают естественнее. Опять же по причине его равномерности восприятия.

              Хотя, конечно, вопросов статья вызывает очень много. Всё описано галопом по европам, деталей почти никаких, так что трудно сказать, насколько их реализация хороша.


  1. Legion21
    26.08.2017 08:09

    Сразу чувствуется, что парни отпетые андроидщики))) Ни дня без костыля


  1. stoodiakv1
    26.08.2017 11:57

    А почему с iOS приложением всё так плохо? Курсивный текст бесит…

    И зачем в десктопной версии два режима редактирования фото?


  1. mefi100fell
    28.08.2017 09:34

    Интересно, что ребята делают крутые(для меня) штуки, но targetSdk все еще меньше 23 и если отнять разрешение, то приложение больше его не просит, а пишет, что нет фоток или еще чего-то.