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

в 9:43, , рубрики: mac os x, objective-c, анаглиф, обработка изображений, Программирование, метки: , , ,

Давно, в 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:
image
и выбираем: Mac OS X -> System Plug-in -> Image Unit Plug-in:
image
Далее вводим имя фильтра(мой вариант — 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:(NValue*)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 оказалась не такой сложной как может показаться. А вот и результаты работы:

  • исходное левое изображение:image
  • исходное правое изображениеimage
  • результат image

Автор: m29A

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js