Pull to refresh

Пишем свой Core Image Filter

Reading time 7 min
Views 7.1K
Давно, в 2005 году, мне купили первый телефон с камерой — Siemens M65. После того как было сделано приличное количество снимков, возникла нужда их упорядочить — была написана небольшая программа на Delphi, которая упорядочивала и позволяла просматривать изображения по дате — путём выбора нужного года, месяца и числа. Шло время, увеличивались мощности телефонов, количество мегапикселей, появилась поддержка видео — и всё это по мере появления было встроено в программу. Позже появилась версия программы и под Mac OS X, которая была написана с использованием фреймворков, доступных в этой ОС для работы с изображениями (Core Image) и видео (AV Foundation). О первом фреймворке и создании для него своего фильтра поговорим более подробно.


Core Image


Core Image — технология обработки изображений, которая появилась в Mac OS X 10.4 — позволяет проводить попиксельные операции с исходным изображением при помощи так называемых плагинов. Встроенных плагинов достаточно много — при некотором количестве времени на основе Core Image можно сделать некоторый аналог фотошопа со слоями, режимами наложения, корректировками уровней, яркости и т.п. Причем всё просчёты будут произведены на GPU (при его наличии), что даст существенную скорость в обработке изображений.

Core Image Filter


Core Image Filter — плагин для обработки изображений, который имеет как минимум 2 функции: принимать входное изображение и выдавать результат. На странице описания встроенных плагинов есть примеры того, как будет выглядеть изображение до и после фильтра. Также плагины делятся на категории:
  • CICategoryBlur — размытие изображений (пример — размытие по Гауссу)
  • CICategoryColorAdjustment — управление цветом (пример — работа с цветовыми каналами)
  • CICategoryColorEffect — цветовые эффекты (пример — сепия)
  • CICategoryCompositeOperation — наложение изображений (пример — наложение 2-х изображений методом «Экран»)
  • CICategoryDistortionEffect — искажения изображений (пример — «закручивание» изображения)
  • CICategoryGenerator — генераторы изображений (пример — генератор изображения с заданными цветом)
  • CICategoryGeometryAdjustment — управление геометрией изображений (пример — масштабирование, перспектива)
  • CICategoryGradient — генераторы градиентов (пример — линейный, радиальный градиенты)
Доступны также другие категории, полный список по ссылке.

Свой фильтр


Подготовка
Наша же цель — написать собственный плагин-фильтр. А в качестве примера я решил выбрать создание анаглифного изображения для просмотра его красно-бирюзовыми очками. Всё это было реализовано в моей программе, речь о которой шла в начале статьи. Для начала запускаем xcode. Далее определяемся — будем ли мы писать плагин как отдельный проект, либо же добавим его в существующий. В моём случае я добавил новый проект в существующий — для этого чуть ниже окна TARGETS жмём Add Target:

и выбираем: Mac OS X -> System Plug-in -> Image Unit Plug-in:

Далее вводим имя фильтра (мой вариант — AnaglyphFilter) и сразу добавляем его в сборку проекта(Product — Edit Scheme — Build, список Targets). Я также добавил зависимость сборки главного проекта от фильтра (чтобы фильтр был собран наверняка при сборке проекта, Build Phases — Target Dependencies) и автоматическое копирование фильтра в папку Resources, всё того же главного проекта (Build Phases — Add Build Phase — Add Copy Files). Теперь можно приступить непосредственно к программированию фильтра. Но для начала немного о содержании плагина.

Взгляд изнутри
После создания нового плагина можно заметить следующие файлы в папке <имя плагина> в дереве проекта:
  • AnaglyphFilterPlugInLoader .h/.m — класс загрузчика нашего плагина
  • AnaglyphFilterFilter .h/.m — класс плагина, который содержит некоторые вспомогательные данные
  • index.html — файл с описанием / справкой нашего плагина
  • Description.plist — информация о нашем плагине
  • Description.strings — файл локализации
  • AnaglyphFilterFilterKernel.cikernel — самая интересная часть, программа на языке Core Image Kernel Language (CIKL), которая отвечает за работу с пикселями


Убираем лишнее
Шаблон, который сделал нам xcode, нужно модифицировать под наши нужды. В фильтре будут 2 входных параметра: основное изображение(левое) и правое. На выходе же будет совмещённое анаглифное изображение. Для начала оставляем только нужные нам параметры. Открываем файл AnaglyphFilterFilter.h и приводим класс AnaglyphFilterFilter к следующему виду:
@interface AnaglyphFilterFilter : CIFilter
{
    CIImage *inputImage;//входное изображение (левое)
    CIImage *rightImage;//правое изображение
}
@end

В файле AnaglyphFilterFilter.m есть несколько процедур, привожу описание:
  • init — загрузка кода плагина из файла и преобразование его в объект CIKernel, тут менять ничего не нужно
  • regionOf — возвращает область изображения, с которой будет работать фильтр. В нашем случае нам нужно всё изображение, поэтому меняем код так:
    - (CGRect)regionOf:(int)sampler destRect:(CGRect)rect  userInfo:(NSValue*)value
    {
        return rect;
    }
  • customAttributes — настройка и описание дополнительных аттрибутов фильтра. Добавляем аттрибут правого изображения:
    - (NSDictionary *)customAttributes
    {
        return [NSDictionary dictionaryWithObjectsAndKeys:
                [NSDictionary dictionaryWithObjectsAndKeys:
                 [CIImage class],kCIAttributeClass,
                 [CIImage emptyImage], kCIAttributeDefault,
                 nil],@"rightImage",
                nil];
    }
  • outputImage — вызов подпрограммы для работы с изображением. В процедуре apply передаются параметры leftSamp и rightSamp, которые будут переданы в подпрограмму CIKL:
    - (CIImage *)outputImage
    {
        CISampler *leftSamp = [CISampler samplerWithImage:inputImage];
        CISampler *rightSamp = [CISampler samplerWithImage:rightImage];
        return [self apply:_AnaglyphFilterFilterKernel,leftSamp,rightSamp,
                kCIApplyOptionDefinition,[src definition],nil];
    }


Программа CIKL
Представляет из себя код на языке CIKL, который является разновидностью OpenGL Shading Language. Подпрограммы, которые мы будем вызывать из основного кода, начинаются с ключего слова kernel и могут быть найдены по свойству name класса CIKernel. Но в нашем случае нам нужна только одна процедура, поэтому инициализация проходит следующим образом (в процедуре init):
_AnaglyphFilterFilterKernel = [[kernels objectAtIndex:0] retain];
Аналогично можно было бы получить процедуру по имени:
_AnaglyphFilterFilterKernel = [[kernels findKernelByName:@"procedureName"] retain];
Сама же процедура в файле .cikernel будет выглядеть примерно так:
kernel vec4 procedureName(sampler leftSamp, sampler rightSamp)
{
//код для работы с пикселями
//возврат результата
}


Анаглиф
Существуют разные по сложности способы получения анаглифных изображений. Для нашего случая мы воспользуемся полноцветным режимом наложения картинок:
  • У левого изображение убираем зеленый и синий каналы, остаётся красный
  • У правого изображения убираем красный канал, остаются зелёный и синий
  • Совмещаем изображение по методу наложения «Экран»

Подпрограмма на языке CIKL будет выглядеть следующим образом:
kernel vec4 anaglyphRedCyan(sampler leftSamp, sampler rightSamp)
{
    //получаем пиксель левого изображения
    vec4 l = sample(leftSamp,samplerCoord(leftSamp));
    //убираем зеленый и синий каналы
    l.g = l.b = 0.0;
    //получаем пиксель правого изображения
    vec4 r = sample(rightSamp,samplerCoord(rightSamp));
    //убираем красный канал
    r.r = 0.0;
    //накладываем изображением методом "Экран"
    vec4 ret;
    //красный канал
    ret.r = 1.0 - (1.0 - l.r)*(1.0 - r.r);
    //зеленый канал
    ret.g = 1.0 - (1.0 - l.g)*(1.0 - r.g);
    //синий канал
    ret.b = 1.0 - (1.0 - l.b)*(1.0 - r.b);
    //прозрачность не используется
    ret.a = 1.0;
    //результат
    return ret;
}


Используем фильтр
При загрузке основной программы нужно вставить код, который будет загружать наш фильтр (с учётом того, что мы положили его в папку Resources):
- (void)loadCoreImageFilters
{
    NSString* path = [[NSBundle mainBundle] pathForResource:@"AnaglyphFilter" ofType:@"plugin"];
    [CIPlugIn loadPlugIn:[NSURL fileURLWithPath:path] allowExecutableCode:YES];
}
Теперь использовать фильтр можно точно так же, как и остальные фильтры:
CIFilter* anaglyphFilter = [CIFilter filterWithName:@"AnaglyphFilter"];
[anaglyphFilter setDefaults];
[anaglyphFilter setValue:leftImage forKey:@"inputImage"];
[anaglyphFilter setValue:rightImage forKey:@"rightImage"];
CIImage* anaglyphImage = [anaglyphFilter valueForKey:@"outputImage"];


Заключение

Работа с Core Image оказалась не такой сложной, как может показаться. А вот и результаты работы:
  • исходное левое изображение:
  • исходное правое изображение
  • результат
Tags:
Hubs:
+5
Comments 1
Comments Comments 1

Articles