Создание пользовательских фильтров

На OS X, если фильтры, предоставленные Базовым Изображением, не обеспечивают функциональность, Вам нужно, можно записать собственный фильтр. (Пользовательские фильтры не доступны на iOS.) Можно включать фильтр как часть проекта приложения, или можно упаковать один или несколько фильтров как автономный модуль изображения. Модули изображения используют NSBundle класс и представляет сменную архитектуру для фильтров.

Следующие разделы предоставляют подробную информацию о том, как создать и использовать пользовательские фильтры и модули изображения:

Выражение операций обработки изображений в базовом изображении

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

Создание пользовательского фильтра

Этот раздел показывает, как создать Базовый фильтр Изображения, имеющий часть Objective C и часть ядра. Путем выполнения шагов в этом разделе Вы создадите фильтр, который является исполнимой программой CPU. Если Вы хотели бы как модуль изображения путем следования инструкциям в Упаковке и Загрузке Модулей Изображения, можно упаковать этот фильтр, вместе с другими фильтрами. Или, можно просто использовать фильтр из собственного приложения. Посмотрите Используя Свой Собственный Фильтр для подробных данных.

Фильтр в этом разделе предполагает, что совпадают область интереса (ROI) и домен определения. Если Вы хотите записать фильтр, для которого это предположение не является истиной, удостоверьтесь, что Вы также читаете Предоставление Функции ROI. Перед созданием собственного фильтра удостоверьтесь, что Вы понимаете Базовые координатные пространства Изображения. Посмотрите Создавание Словаря Фильтров.

Для создания пользовательского исполнимого фильтра CPU выполните следующие шаги:

  1. Запишите код ядра

  2. Используйте кварцевого композитора для тестирования подпрограммы ядра

  3. Объявите интерфейс для фильтра

  4. Запишите метод Init для объекта CIKernel

  5. Запишите метод пользовательских атрибутов

  6. Запишите выходной метод изображения

  7. Зарегистрируйте фильтр

  8. Запишите метод для создания экземпляров фильтра

Каждый шаг описан подробно в разделах, следующих за использованием фильтра удаления тумана как пример. Эффект фильтра удаления тумана состоит в том, чтобы скорректировать яркость и контраст изображения, и применять увеличение резкости к нему. Этот фильтр полезен для исправления изображений, взятых через легкую вуаль или туман, обычно имеющий место при взятии изображения от самолета. Рисунок 9-1 показывает изображение прежде и после обработки с фильтром удаления тумана. Приложение с помощью фильтра обеспечивает ползунки, позволяющие пользователю скорректировать входные параметры фильтра.

Рисунок 9-1  изображение прежде и после обработки с фильтром удаления тумана
An image before and after processing with the haze removal filter

Запишите код ядра

Код, выполняющий обработку на пиксель, находится в файле с .cikernel расширение. Можно включать больше чем одну подпрограмму ядра в этот файл. Если Вы хотите сделать свой код модульным, можно также включать другие подпрограммы. Вы указываете ядро с помощью подмножества Языка Штриховки OpenGL (glslang) и Базовых расширений Изображения его. Посмотрите Базовую Ссылку Языка Ядра Изображения для получения информации о допустимых элементах языка.

Подпись подпрограммы ядра должна возвратить вектор (vec4) это содержит результат отображения исходного пикселя к целевому пикселю. Базовое Изображение вызывает подпрограмму ядра один раз для каждого пикселя. Следует иметь в виду, что Ваш код не может накопить знание от пикселя до пикселя. Хорошая стратегия при записи кода состоит в том, чтобы переместить как можно больше инвариантного вычисления от фактического ядра и поместить его в часть Objective C фильтра.

Перечисление 9-1 показывает подпрограмму ядра для фильтра удаления тумана. Подробное объяснение каждой пронумерованной строки кода следует за перечислением. (Существуют примеры других обрабатывающих пиксель подпрограмм в Примерах Подпрограммы Ядра и в Учебном руководстве по Модулю Изображения.)

Перечисление 9-1  подпрограмма ядра для фильтра удаления тумана

kernel vec4 myHazeRemovalKernel(sampler src,             // 1
                     __color color,
                    float distance,
                    float slope)
{
    vec4   t;
    float  d;
 
    d = destCoord().y * slope  +  distance;              // 2
    t = unpremultiply(sample(src, samplerCoord(src)));   // 3
    t = (t - d*color) / (1.0-d);                         // 4
 
    return premultiply(t);                               // 5
}

Вот то, что делает код:

  1. Берет четыре входных параметра и возвращает вектор. Когда Вы объявляете интерфейс для фильтра, необходимо удостовериться, что объявили то же число входных параметров, как Вы указываете в ядре. Ядро должно возвратить a vec4 тип данных.

  2. Вычисляет значение на основе y-значения координаты назначения и наклона и входных параметров расстояния. destCoord подпрограмма (предоставленный Базовым Изображением) возвращает позицию, в координатах рабочей области, пикселя, в настоящее время будучи вычисленным.

  3. Получает пиксельное значение, в пространстве сэмплера, сэмплера src это связано с пикселем текущей производительности после того, как любая матрица преобразования связалась с src применяется. Вспомните, что Базовое Изображение использует компоненты цвета с предварительно умноженными альфа-значениями. Перед обработкой Вам нужны к unpremultiply значения цвета, которые Вы получаете от сэмплера.

  4. Вычисляет выходной вектор путем применения формулы удаления тумана, включающей наклон и вычисления расстояния и приводящей в соответствие с цветом.

  5. Возвраты a vec4 вектор, как требуется. Ядро выполняет работу умножения в обратном порядке прежде, чем возвратить результат, потому что Базовое Изображение использует компоненты цвета с предварительно умноженными альфа-значениями.

Используйте кварцевого композитора для тестирования подпрограммы ядра

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

bullet
Загружать Кварцевого Композитора
  1. Откройте Xcode.

  2. Выберите Xcode> Open Developer Tool> More Developer Tools...

    Выбор этого элемента возьмет Вас к developer.apple.com.

  3. Регистрируйтесь к developer.apple.com.

    Необходимо тогда видеть Загрузки для веб-страницы Разработчиков Apple.

  4. Загрузите Графические Инструменты для пакета XCode, содержащего Кварцевого Композитора.

Кварцевый Композитор обеспечивает патч — Базовый Фильтр Изображения — в который можно поместить подпрограмму ядра. Вы просто открываете Inspector для Базового патча Фильтра Изображения, и или вставляете или вводите Ваш код в текстовое поле, как показано на рисунке 9-2.

Рисунок 9-2  подпрограмма ядра удаления тумана вставляется в область Settings
The haze removal kernel routine pasted into the Settings pane

После ввода кода входные порты патча автоматически создаются согласно прототипу функции ядра, как Вы видите на рисунке 9-3. Патч всегда имеет единственный выходной порт, представляющий получающееся изображение, произведенное ядром.

Простой состав, показанный на рисунке 9-3, импортирует файл образа с помощью патча Средства импорта Изображения, обрабатывает его через ядро, затем представляет результат на экране с помощью патча Billboard. Ваше ядро может использовать больше чем одно изображение или, если это генерирует вывод, это не могло бы потребовать никаких входных изображений.

Состав, который Вы создаете для тестирования ядра, может быть более сложным, чем показанный на рисунке 9-3. Например, Вы могли бы хотеть объединить свою подпрограмму ядра в цепочку с другими встроенными Базовыми фильтрами Изображения или с другими подпрограммами ядра. Кварцевый Композитор обеспечивает много других патчей, которые можно использовать в ходе тестирования подпрограммы ядра.

Рисунок 9-3  Кварцевый состав Композитора, тестирующий подпрограмму ядра
A Quartz Composer composition that tests a kernel routine

Объявите интерфейс для фильтра

.h файл для фильтра содержит интерфейс, указывающий вводы фильтра, как показано в Перечислении 9-2. Ядро удаления тумана имеет четыре входных параметра: источник, цвет, расстояние и наклон. Интерфейс для фильтра должен также содержать эти входные параметры. Входные параметры должны быть в том же порядке, как указано для фильтра, и типы данных должны быть совместимыми между двумя.

  Код перечисления 9-2, объявляющий интерфейс для фильтра удаления тумана

@interface MyHazeFilter: CIFilter
{
    CIImage   *inputImage;
    CIColor   *inputColor;
    NSNumber  *inputDistance;
    NSNumber  *inputSlope;
}
 
@end

Запишите метод Init для объекта CIKernel

Файл реализации для фильтра содержит метод, инициализирующий Базовый объект ядра Изображения (CIKernel) с подпрограммой ядра, указанной в .cikernel файл. A .cikernel файл может содержать больше чем одну подпрограмму ядра. Подробное объяснение каждой пронумерованной строки кода появляется после перечисления.

Перечисление 9-3  init метод, инициализирующий ядро

static CIKernel *hazeRemovalKernel = nil;
 
- (id)init
{
    if(hazeRemovalKernel == nil)                                         // 1
    {
        NSBundle    *bundle = [NSBundle bundleForClass: [self class]];   // 2
        NSString    *code = [NSString stringWithContentsOfFile: [bundle
                                pathForResource: @"MyHazeRemoval"
                                ofType: @"cikernel"]];                   // 3
        NSArray     *kernels = [CIKernel kernelsWithString: code];       // 4
        hazeRemovalKernel = kernels[0];                                  // 5
    }
    return [super init];
}

Вот то, что делает код:

  1. Проверки, ли CIKernel объект уже инициализируется.

  2. Возвращает пакет, динамично загружающийся CIFilter класс.

  3. Возвращает строку, создаваемую из имени файла в указанном пути, который в этом случае является MyHazeRemoval.cikernel файл.

  4. Создает a CIKernel объект от строки, указанной code параметр. Каждая подпрограмма в .cikernel файл, отмеченный как ядро, возвращается в kernels массив. Этот пример имеет только одно ядро в .cikernel файл, таким образом, массив содержит только один элемент.

  5. Наборы hazeRemovalKernel к первому ядру в kernels массив. Если .cikernel файл содержит больше чем одно ядро, Вы также инициализировали бы те ядра в этой подпрограмме.

Запишите метод пользовательских атрибутов

A customAttributes метод позволяет клиентам фильтра получать атрибуты фильтра, такие как входные параметры, значения по умолчанию и минимальные и максимальные значения. (См. Ссылку класса CIFilter для полного списка атрибутов.) Фильтр не требуется, чтобы предоставлять любую информацию об атрибуте кроме его класса, но фильтр должен вести себя разумным способом, если не присутствуют атрибуты.

Как правило, это атрибуты что Ваш customAttributes метод возвратился бы:

  • Параметры ввода и вывода

  • Класс атрибута для каждого параметра Вы предоставляете (обязательный)

  • Минимум, максимум и значения по умолчанию для каждого (дополнительного) параметра

  • Другая информация, столь же надлежащая, такая как ползунок минимальные и (дополнительные) максимальные значения

Перечисление 9-4 показывает customAttributes метод для фильтра Тумана. Входные параметры inputDistance и inputSlope у каждого есть минимум, максимум, минимум ползунка, максимум ползунка, значение по умолчанию и набор значений идентификационных данных. Ползунок минимальные и максимальные значения используется для установки ползунков, показанных на рисунке 9-1. inputColor параметру установили значение по умолчанию.

Перечисление 9-4  customAttributes метод для фильтра Тумана

- (NSDictionary *)customAttributes
{
    return @{
        @"inputDistance" :  @{
            kCIAttributeMin       : @0.0,
            kCIAttributeMax       : @1.0,
            kCIAttributeSliderMin : @0.0,
            kCIAttributeSliderMax : @0.7,
            kCIAttributeDefault   : @0.2,
            kCIAttributeIdentity  : @0.0,
            kCIAttributeType      : kCIAttributeTypeScalar
            },
        @"inputSlope" : @{
            kCIAttributeSliderMin : @-0.01,
            kCIAttributeSliderMax : @0.01,
            kCIAttributeDefault   : @0.00,
            kCIAttributeIdentity  : @0.00,
            kCIAttributeType      : kCIAttributeTypeScalar
            },
         kCIInputColorKey : @{
         kCIAttributeDefault : [CIColor colorWithRed:1.0
                                               green:1.0
                                                blue:1.0
                                               alpha:1.0]
           },
   };
}

Запишите выходной метод изображения

outputImage метод создает a CISampler объект для каждого входного изображения (или маска изображения), создает a CIFilterShape объект (если надлежащий), и применяет метод ядра. Перечисление 9-5 показывает outputImage метод для фильтра удаления тумана. Первая вещь, которую делает код, состоит в том, чтобы установить сэмплер для выборки пикселей от входного изображения. Поскольку этот фильтр использует только одно входное изображение, кодовые наборы только один сэмплер.

Код вызывает apply:arguments:options: метод CIFilter произвести a CIImage объект. Первый параметр к применять методу CIKernel объект, содержащий функцию ядра удаления тумана. (См. Запись Код Ядра.) Вспоминают, что функция ядра удаления тумана берет четыре параметра: сэмплер, цвет, расстояние и наклон. Эти параметры передаются как следующие четыре параметра apply:arguments:options: метод в Перечислении 9-5. Остающиеся параметры применять методу указывают опции (пары ключ/значение), управляющие, как Базовое Изображение должно оценить функцию. Можно передать один из трех ключей: kCIApplyOptionExtent, kCIApplyOptionDefinition, или kCIApplyOptionUserInfo. Этот пример использует kCIApplyOptionDefinition ключ для указания домена определения (DOD) выходного изображения. См. Ссылку класса CIFilter для описания этих ключей и для получения дополнительной информации об использовании apply:arguments:options: метод.

Заключительный параметр nil, указывает конец списка опций.

Перечисление 9-5  метод, возвращающий вывод изображения из фильтра удаления тумана

- (CIImage *)outputImage
{
    CISampler *src = [CISampler samplerWithImage: inputImage];
 
    return [self apply: hazeRemovalKernel, src, inputColor, inputDistance,
        inputSlope, kCIApplyOptionDefinition, [src definition], nil];
}

Перечисление 9-5 является простым примером. Реализация для Вашего outputImage метод должен быть адаптирован в соответствии с Вашим фильтром. Если бы Ваш фильтр требует инвариантных циклом вычислений, Вы включали бы их в outputImage метод, а не в ядре.

Зарегистрируйте фильтр

Идеально, Вы упакуете фильтр как модуль изображения, независимо от того, планируете ли Вы распределить фильтр другим или использовать его только в Вашем собственном приложении. Если Вы запланируете упаковать этот фильтр как модуль изображения, то Вы зарегистрируете свой фильтр с помощью CIPlugInRegistration протокол описан в Упаковке и Загрузке Модулей Изображения. Можно пропустить остальную часть этого раздела.

Если по некоторым причинам Вы не захотите упаковывать фильтр как модуль изображения (который не рекомендуется), то необходимо будет зарегистрировать фильтр с помощью регистрационного метода CIFilter класс описал показанный в Перечислении 9-6. Инициализировать вызовы метода registerFilterName:constructor:classAttributes:. Необходимо зарегистрировать только имя дисплея (kCIAttributeFilterDisplayName) и категории фильтра (kCIAttributeFilterCategories). Все другие атрибуты фильтров должны быть указаны в customAttributes метод. (См. Запись Метод Пользовательских атрибутов).

Имя фильтра является строкой для создания фильтра удаления тумана, когда Вы хотите использовать его. Объект конструктора указал реализации filterWithName: метод (см. Запись Метод для Создания Экземпляров Фильтра). Атрибуты класса фильтра указаны как NSDictionary объект. Имя дисплея — что Вы показали бы в пользовательском интерфейсе — для этого фильтра, является Съемником Тумана.

Перечисление 9-6  , Регистрирующее фильтр, который не является частью модуля изображения

+ (void)initialize
{
    [CIFilter registerFilterName: @"MyHazeRemover"
                     constructor: self
                 classAttributes:
     @{kCIAttributeFilterDisplayName : @"Haze Remover",
       kCIAttributeFilterCategories : @[
               kCICategoryColorAdjustment, kCICategoryVideo,
               kCICategoryStillImage, kCICategoryInterlaced,
               kCICategoryNonSquarePixels]}
     ];
}

Запишите метод для создания экземпляров фильтра

Если Вы запланируете использовать этот фильтр только в Вашем собственном приложении, то необходимо будет реализовать a filterWithName: метод, как описано в этом разделе. Если Вы планируете упаковать этот фильтр как модуль изображения для использования сторонними разработчиками, то можно пропустить этот раздел, потому что упакованные фильтры могут использовать filterWithName: метод, предоставленный CIFilter класс.

filterWithName: метод, показанный в Перечислении 9-7, создает экземпляры фильтра, когда их требуют.

Перечисление 9-7  метод, создающий экземпляр фильтра

+ (CIFilter *)filterWithName: (NSString *)name
{
    CIFilter  *filter;
    filter = [[self alloc] init];
    return filter;
}

После того, как Вы выполните эти шаги для создания фильтра, можно использовать фильтр в собственном приложении. Посмотрите Используя Свой Собственный Фильтр для подробных данных. Если Вы хотите сделать фильтр или набор фильтров доступными как плагин для других приложений, посмотрите Упаковку и Загрузку Модулей Изображения.

Используя Ваш собственный фильтр

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

[MyHazeFilter class];

Перечисление 9-8 показывает, как использовать фильтр удаления тумана. Отметьте подобие между этим кодом и кодом, обсужденным в Обработке Изображений.

Перечисление 9-8  Используя Ваш собственный фильтр

- (void)drawRect: (NSRect)rect
{
    CGRect  cg = CGRectMake(NSMinX(rect), NSMinY(rect),
                            NSWidth(rect), NSHeight(rect));
    CIContext *context = [[NSGraphicsContext currentContext] CIContext];
 
    if(filter == nil) {
        NSURL       *url;
 
        [MyHazeFilter class];
 
        url = [NSURL fileURLWithPath: [[NSBundle mainBundle]
                    pathForResource: @"CraterLake"  ofType: @"jpg"]];
        filter = [CIFilter filterWithName: @"MyHazeRemover"
                            keysAndValues:
                  kCIInputImageKey, [CIImage imageWithContentsOfURL: url],
                  kCIInputColorKey, [CIColor colorWithRed:0.7 green:0.9 blue:1],
                  nil];
    }
 
    [filter setValue: @(distance) forKey: @"inputDistance"];
    [filter setValue: @(slope) forKey: @"inputSlope"];
 
    [context drawImage: [filter valueForKey: kCIOutputImageKey]
        atPoint: cg.origin  fromRect: cg];
}

Предоставление функции ROI

Область интереса или ROI, определяет область в источнике, из которого сэмплер берет информацию о пикселе для обеспечения для ядра для обработки. Вспомните из Область обсуждения Интереса в Запросах Системы для Фильтров, что координаты рабочей области ROI и DOD или совпадают точно, зависят от друг друга, или не связанные. Базовое Изображение всегда предполагает, что совпадают ROI и DOD. Если это так, для фильтра Вы пишете, тогда Вы не должны предоставлять функцию ROI. Но если это предположение не является истиной для фильтра, Вы пишете, тогда необходимо предоставить функцию ROI. Далее, можно предоставить функцию ROI только для исполнимых фильтров CPU.

Функция ROI, которую Вы предоставляете, вычисляет представляющую интерес область для каждого сэмплера, использующегося ядром. Базовое Изображение вызывает Вашу функцию ROI, передавая ему индекс сэмплера, степень области, представляемой, и любые данные, которые необходимы Вашей подпрограмме. Сигнатура метода должна следовать за этой формой:

- (CGRect) regionOf:(int)samplerIndex
           destRect:(CGRect)r
           userInfo:obj;

где:

Базовое Изображение вызывает Вашу подпрограмму для каждого, проходят через фильтр. Ваш метод вычисляет ROI на основе прямоугольника, и информация о пользователе передала ему и возвращает ROI, указанный как a CGRect тип данных.

Вы регистрируете функцию ROI путем вызова CIKernel метод setROISelector:, предоставление ROI функционирует как aMethod параметр. Например:

[kernel setROISelector:@selector(regionOf:destRect:userInfo:)]

Следующие разделы обеспечивают примеры функций ROI.

Простая функция ROI

Если Ваша функция ROI не требует, чтобы данные были переданы ему в userInfo параметр, тогда Вы не должны включать тот параметр, как показано в Перечислении 9-9. Код в началах Перечисления 9-9 сэмплер на один пиксель, который является вычислением, используемым находящим край фильтром или любым 3x3 свертка.

Перечисление 9-9  простая функция ROI

- (CGRect)regionOf:(int)samplerIndex destRect:(CGRect)r
{
    return CGRectInset(r, -1.0, -1.0);
}

Обратите внимание на то, что эта функция игнорирует samplerIndex значение. Если Ваше ядро использует только один сэмплер, то можно проигнорировать индекс. Если Ваше ядро использует больше чем один сэмплер, необходимо удостовериться, что Вы возвращаете ROI, это подходяще для указанного сэмплера. Вы будете видеть, как сделать это в следующих разделах.

Функция ROI для стеклянного фильтра искажения

Перечисление 9-10 показывает функцию ROI для стеклянного фильтра искажения. Эта функция возвращает ROI для двух сэмплеров. Сэмплер 0 представляет изображение для искажения и сэмплер 1 представляет текстуру, используемую для стекла.

Функция использует userInfo параметр для предоставления ввода масштабируется, это необходимо сэмплеру 0. Заметьте, что искажение является началом половиной предоставленного масштаба на всех сторонах.

Вся стеклянная текстура (сэмплер 1) потребности, на которые сошлются, потому что фильтр использует текстуру в качестве прямоугольного образца. В результате функция возвращает бесконечный прямоугольник как ROI. Бесконечный прямоугольник является соглашением, указывающим для использования всего сэмплера. (Константа CGRectInfinite определяется в Кварце 2D API.)

Перечисление 9-10  ROI функционирует для стеклянного фильтра искажения

- (CGRect)regionOf:(int)samplerIndex destRect:(CGRect)r userInfo:obj
{
    if (samplerIndex == 0) {
        CGFloat s = [obj floatValue] * 0.5f;
        return CGRectInset(r, -s,-s);
    }
    return CGRectInfinite;
}

Функция ROI для карты среды

Перечисление 9-11 показывает функцию ROI, возвращающую ROI для ядра, использующего три сэмплера, один из которых является картой среды. ROI для сэмплера 0 и сэмплер 1 совпадите с DOD. По этой причине код возвращается destination прямоугольник передал ему для сэмплеров кроме сэмплера 2.

Сэмплер 2 значения использования передали в userInfo параметр, которые указывают высоту и ширину среды, отображается для создания прямоугольника, указывающего область интереса.

Перечисление 9-11  , Предоставляющее подпрограмму, вычисляющую область интереса

- (CGRect)regionOf:(int)samplerIndex forRect:(CGRect)destination userInfo:(NSArray *)myArray
{
    if (samplerIndex == 2) {
        return CGRectMake(0, 0,
                          [myArray[0] floatValue],
                          [myArray[1] floatValue]);
    }
    return destination;
}

Указание порядка сэмплера

Как Вы видели от предыдущих примеров, сэмплеру связали индекс с ним. При предоставлении функции ROI Базовое Изображение передает индекс сэмплера Вам. Индекс сэмплера присваивается на основе его порядка, когда передано apply метод для фильтра. Вы вызываете apply из фильтра outputImage подпрограмма, как показано в Перечислении 9-12.

В этом перечислении заметьте особенно пронумерованные строки кода, устанавливающие сэмплеры и показывающие, как обеспечить их для ядра. Подробное объяснение каждой из этих строк появляется после Перечисления 9-12.

Перечисление 9-12  выходная подпрограмма изображения для фильтра, использующего карту среды

- (CIImage *)outputImage
{
    int i;
    CISampler *src, *blur, *env;                                      // 1
    CIVector *envscale;
    CGSize size;
    CIKernel *kernel;
 
    src = [CISampler samplerWithImage:inputImage];                    // 2
    blur = [CISampler samplerWithImage:inputHeightImage];             // 3
    env = [CISampler samplerWithImage:inputEnvironmentMap];           // 4
    size = [env extent].size;
    envscale = [CIVector vectorWithX:[inputEMapWidth floatValue]
                     Y:[inputEMapHeight floatValue]];
    i = [inputKind intValue];
    if ([inputHeightInAlpha boolValue])
        i += 8;
    kernel = roundLayerKernels[i];
    [kernel setROISelector:@selector(regionOf:forRect:userInfo:)];    // 5
    return [self apply: kernel,src, blur, env,
                    @( pow(10.0, [inputSurfaceScale floatValue]) ),
                    envscale,
                    inputEMapOpacity,
                    kCIApplyOptionDefinition,
                    [src definition],
                    kCIApplyOptionUserInfo,
                    @[ inputEMapWidth, inputEMapHeight ],
                    nil];                                             // 6
 }

Вот то, что делает код:

  1. Объявляет переменные для каждого из трех сэмплеров, которые необходимы для ядра.

  2. Устанавливает сэмплер для входного изображения. ROI для этого сэмплера совпадает с DOD.

  3. Устанавливает сэмплер для изображения, используемого для входной высоты. ROI для этого сэмплера совпадает с DOD.

  4. Устанавливает сэмплер для карты среды. ROI для этого сэмплера не совпадает с DOD, что означает, что необходимо предоставить функцию ROI.

  5. Регистрирует функцию ROI в ядре, которое должно использовать ее.

  6. Применяет параметры ядру для создания Базового изображения Изображения (CIImage объект). Переданные аргументы должны быть типом, совместимым с функциональной подписью функции ядра (который не показан здесь, но предположите, что они - совместимый тип). Список параметров завершается nil, как требуется.

    Порядок параметров сэмплера определяет свой индекс. Первый сэмплер, предоставленный ядру, является индексом 0. В этом случае это src сэмплер. Второй сэмплер, предоставленный ядру —blur— присваивается индекс 1. Третий сэмплер —env— присваивается индекс 2. Важно проверить Вашу функцию ROI, чтобы удостовериться, что Вы обеспечиваете надлежащий ROI для каждого сэмплера.

Запись неисполнимых фильтров

Фильтр, который является неисполнимой программой CPU, как гарантируют, будет безопасен. Поскольку этот тип фильтра работает только на GPU, это не может участвовать в вирусе или действии Троянского коня или другом злонамеренном поведении. Для гарантии безопасности неисполнимые фильтры CPU имеют эти ограничения:

Выборка CIDemoImageUnit содержит неисполнимую программу, просачиваются MyKernelFilter.cikernel файл. Когда модуль изображения загружается, фильтр MyKernelFilter загружается вместе с фильтром FunHouseMirror, это находится также в модуле изображения. FunHouseMirror, однако, является исполнимым фильтром. Это имеет часть Objective C, а также часть ядра.

Когда Вы пишете неисполнимый фильтр, необходимо обеспечить все атрибуты фильтра в Descriptions.plist файл для пакета модуля изображения. Перечисление 9-13 показывает атрибуты для MyKernelFilter в выборке CIDemoImageUnit.

Перечисление 9-13  список свойств для неисполнимого фильтра MyKernelFilter

<key>MyKernelFilter</key>
        <dict>
            <key>CIFilterAttributes</key>
            <dict>
                <key>CIAttributeFilterCategories</key>
                <array>
                    <string>CICategoryStylize</string>
                    <string>CICategoryVideo</string>
                    <string>CICategoryStillImage</string>
                </array>
                <key>CIAttributeFilterDisplayName</key>
                <string>MyKernelFilter</string>
                <key>CIInputs</key>
                <array>
                    <dict>
                        <key>CIAttributeClass</key>
                        <string>CIImage</string>
                        <key>CIAttributeDisplayName</key>
                        <string>inputImage</string>
                        <key>CIAttributeName</key>
                        <string>inputImage</string>
                    </dict>
                    <dict>
                        <key>CIAttributeClass</key>
                        <string>NSNumber</string>
                        <key>CIAttributeDefault</key>
                        <real>8</real>
                        <key>CIAttributeDisplayName</key>
                        <string>inputScale</string>
                        <key>CIAttributeIdentity</key>
                        <real>8</real>
                        <key>CIAttributeMin</key>
                        <real>1</real>
                        <key>CIAttributeName</key>
                        <string>inputScale</string>
                        <key>CIAttributeSliderMax</key>
                        <real>16</real>
                        <key>CIAttributeSliderMin</key>
                        <real>1</real>
                    </dict>
                    <dict>
                        <key>CIAttributeClass</key>
                        <string>NSNumber</string>
                        <key>CIAttributeDefault</key>
                        <real>1.2</real>
                        <key>CIAttributeDisplayName</key>
                        <string> inputGreenWeight </string>
                        <key>CIAttributeIdentity</key>
                        <real>1.2</real>
                        <key>CIAttributeMin</key>
                        <real>1</real>
                        <key>CIAttributeName</key>
                        <string>inputGreenWeight</string>
                        <key>CIAttributeSliderMax</key>
                        <real>3.0</real>
                        <key>CIAttributeSliderMin</key>
                        <real>1</real>
                    </dict>
                </array>
            </dict>
            <key>CIFilterClass</key>
            <string>MyKernelFilter</string>
            <key>CIHasCustomInterface</key>
            <false/>
            <key>CIKernelFile</key>
            <string>MyKernelFilter</string>

Примеры подпрограммы ядра

Сущность любого фильтра обработки изображений является ядром, выполняющим пиксельные вычисления. Листинги кода в этом разделе показывают некоторые типичные подпрограммы ядра для этих фильтров: прояснитесь, умножьтесь, и искажение дыры. Путем рассмотрения их можно понять то, как записать собственную подпрограмму ядра. Отметьте, однако, что эти подпрограммы являются примерами. Не предполагайте, что код, показанный здесь, - то, что Базовое Изображение использует для фильтров, которые это предоставляет.

Перед записью собственной подпрограммы ядра можно хотеть считать Выражение Операций Обработки изображений в Базовом Изображении для наблюдения, какие операции ставят проблему в Базовом Изображении. Вы также захотите смотреть на Базовую Ссылку Языка Ядра Изображения.

Можно найти всестороннюю информацию о записи ядер, а также большего количества примеров в Учебном руководстве по Модулю Изображения.

Вычисления проясняющегося эффекта

Перечисление 9-14 вычисляет проясняющийся эффект. Подробное объяснение каждой пронумерованной строки кода появляется после перечисления.

Перечисление 9-14  подпрограмма ядра, вычисляющая проясняющийся эффект

kernel vec4 brightenEffect (sampler src, float k)
{
  vec4 currentSource = sample (src, samplerCoord (src));         // 1
  currentSource.rgb = currentSource.rgb + k * currentSource.a;   // 2
  return currentSource;                                          // 3
}

Вот то, что делает код:

  1. Ищет исходный пиксель в сэмплере, связанном с позицией текущей производительности.

  2. Добавляет смещение к пиксельному значению. Смещение k масштабируемый альфа-значением пикселя для проверки пиксельное значение предварительно умножается.

  3. Возвращает измененный пиксель.

Вычисления умножить эффект

Перечисление 9-15 показывает подпрограмму ядра, вычисляющую умножить эффект. Код ищет исходный пиксель в сэмплере и затем умножается, это значением передало подпрограмме.

Перечисление 9-15  подпрограмма ядра, вычисляющая умножить эффект

kernel vec4 multiplyEffect (sampler src, __color mul)
{
  return sample (src, samplerCoord (src)) * mul;
}
 

Вычисления искажения дыры

Перечисление 9-16 показывает подпрограмму ядра, вычисляющую искажение дыры. Обратите внимание на то, что существует много способов вычислить искажение дыры. Подробное объяснение каждой пронумерованной строки кода появляется после перечисления.

Перечисление 9-16  подпрограмма ядра, вычисляющая искажение дыры

kernel vec4 holeDistortion (sampler src, vec2 center, vec2 params)   // 1
{
  vec2 t1;
  float distance0, distance1;
 
  t1 = destCoord () - center;                                        // 2
  distance0 = dot (t1, t1);                                          // 3
  t1 = t1 * inversesqrt (distance0);                                 // 4
  distance0 = distance0 * inversesqrt (distance0) * params.x;        // 5
  distance1 = distance0 - (1.0 / distance0);                         // 6
  distance0 = (distance0 < 1.0 ? 0.0 : distance1) * params.y;        // 7
  t1 = t1 * distance0 + center;                                      // 8
 
  return sample (src, samplerTransform (src, t1));                   // 9
}

Вот то, что делает код:

  1. Берет три параметра — сэмплер, вектор, указывающий центр искажения дыры, и params вектор, содержащий (1/radius, radius).

  2. Создает вектор t1 от центра до текущих рабочих координат.

  3. Придает расстоянию квадратную форму от центра и присваивает значение distance0 переменная.

  4. Нормализует t1. (Делает t1 единичный вектор.)

  5. Вычисляет параметрическое расстояние от центра (distance squared * 1/distance) * 1/radius. Это значение 0 в центре и 1, где расстояние равно радиусу.

  6. Создает дыру с надлежащим искажением вокруг этого. (x – 1/sqrt (x))

  7. Удостоверяется, что все пиксели в карте дыры от пикселя в центре, затем увеличивает масштаб искаженной функции расстояния радиусом.

  8. Масштабирует вектор для создания искажения и затем включает центр назад.

  9. Возвращает искаженную выборку из исходной текстуры.