- PVSM.RU - https://www.pvsm.ru -

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

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

Core Image

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

Core Image Filter

Core Image Filter — плагин для обработки изображений, который имеет как минимум 2 функции — принимать входное изображение, и выдавать результат. На странице описания [3] встроенных плагинов есть примеры того, как будет выглядеть изображение до и после фильтра. Так же плагины делятся на категории:

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

Доступны так же другие категории, полный список по ссылке [3].

Свой фильтр

Подготовка

Наша же цель — написать собственный плагин-фильтр. А в качестве примера я решил выбрать создание анаглифного [4] изображения для просмотра его красно-бирюзовыми очками. Всё это было реализовано в моей программе, речь о которой шла в начале статьи. Для начала запускаем 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 [5] (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 [6]. Подпрограммы, которые мы будем вызывать из основного кода, начинаются с ключего слова 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


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/programmirovanie/6123

Ссылки в тексте:

[1] Core Image: http://en.wikipedia.org/wiki/Core_Image

[2] GPU: http://ru.wikipedia.org/wiki/Графический_процессор

[3] странице описания: http://developer.apple.com/library/mac/#documentation/graphicsimaging/reference/CoreImageFilterReference/Reference/reference.html

[4] анаглифного: http://ru.wikipedia.org/wiki/Анаглиф

[5] Core Image Kernel Language: https://developer.apple.com/library/mac/#documentation/GraphicsImaging/Reference/CIKernelLangRef/ci_gslang_ext.html

[6] OpenGL Shading Language: http://ru.wikipedia.org/wiki/OpenGL_Shading_Language