¿Cómo crear un filter personalizado simple para iOS usando Core Image Framework?

Quiero usar en mi aplicación un filter personalizado . Ahora sé que necesito usar Core Image framework, pero no estoy seguro de que sea correcto. Core Image framework se utiliza para Mac OS y en iOS 5.0 , no estoy seguro de que se pueda usar para efectos CIFilter personalizados. ¿Puedes ayudarme con estos problemas? ¡Gracias a todos!

Todavía no puedes crear tus propios kernels / filters personalizados en iOS. Consulte http://developer.apple.com/library/mac/#documentation/graphicsimaging/Conceptual/CoreImaging/ci_intro/ci_intro.html , específicamente:

Aunque este documento está incluido en la biblioteca de reference, no se ha actualizado en detalle para iOS 5.0. Una próxima revisión detallará las diferencias en Core Image en iOS. En particular, la diferencia key es que Core Image en iOS no incluye la capacidad de crear filters de image personalizados .

(En negrita mío)

Como afirma Adam, actualmente Core Image en iOS no admite kernels personalizados como lo hizo la implementación anterior de Mac. Esto limita lo que puede hacer con el marco a ser algún tipo de combinación de filters existentes.

(Actualización: 13/02/2012)

Por esta razón, he creado un marco de código abierto para iOS denominado GPUImage , que le permite crear filters personalizados para aplicarlos a las imágenes y al video mediante sombreadores de fragments OpenGL ES 2.0. Describo más sobre cómo funciona este marco en mi publicación sobre el tema . Básicamente, puede proporcionar sus propios sombreadores de fragment personalizados de lenguaje de sombreado de OpenGL (GLSL) para crear un filter personalizado y luego ejecutar ese filter contra imágenes estáticas o video en vivo. Este marco es compatible con todos los dispositivos iOS compatibles con OpenGL ES 2.0 y puede crear aplicaciones orientadas a iOS 4.0.

Por ejemplo, puede configurar el filtrado de video en vivo usando un código como el siguiente:

 GPUImageVideoCamera *videoCamera = [[GPUImageVideoCamera alloc] initWithSessionPreset:AVCaptureSessionPreset640x480 cameraPosition:AVCaptureDevicePositionBack]; GPUImageFilter *customFilter = [[GPUImageFilter alloc] initWithFragmentShaderFromFile:@"CustomShader"]; GPUImageView *filtenetworkingVideoView = [[GPUImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, viewWidth, viewHeight)]; // Add the view somewhere so it's visible [videoCamera addTarget:thresholdFilter]; [customFilter addTarget:filtenetworkingVideoView]; [videoCamera startCameraCapture]; 

Como ejemplo de un progtwig de sombreador de fragment personalizado que define un filter, lo siguiente aplica un efecto de tono sepia:

 varying highp vec2 textureCoordinate; uniform sampler2D inputImageTexture; void main() { lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate); lowp vec4 outputColor; outputColor.r = (textureColor.r * 0.393) + (textureColor.g * 0.769) + (textureColor.b * 0.189); outputColor.g = (textureColor.r * 0.349) + (textureColor.g * 0.686) + (textureColor.b * 0.168); outputColor.b = (textureColor.r * 0.272) + (textureColor.g * 0.534) + (textureColor.b * 0.131); gl_FragColor = outputColor; } 

El lenguaje utilizado para escribir núcleos personalizados de Core Image en Mac es muy similar a GLSL. De hecho, podrá hacer algunas cosas que no puede utilizar en Core Image de escritorio, ya que el lenguaje del núcleo de Core Image carece de algunas cosas que GLSL tiene (como ramificación).

La respuesta original aceptada se deprecia. Desde iOS 8 puede crear kernels personalizados para filters. Puede encontrar más información sobre esto en:

  • Sesión 514 de la WWDC 2014 Avances en Core Image
  • Transcripción de la session 514
  • WWDC 2014 Session 515 Desarrollo de filters de image principales para iOS
  • Transcripción de la session 515

Puede crear filters personalizados para iOS más fácilmente que un complemento Image Unit para MacOS X, tanto que sería preferible, incluso si los complementos Image Unit fueran compatibles con iOS. El problema es que en realidad no puede "empaquetarlos" ni agruparlos como un recurso como los complementos de Image Unit; Tienes que exponer tu código fuente a los desarrolladores que los usan. Además, solo son útiles para los desarrolladores; no puede distribuirlos a los usuarios finales de las aplicaciones gráficas de iOS de la misma manera que puede para las aplicaciones gráficas de MacOS X que importan filters Core Image de terceros. Para eso, debes incrustarlos en una extensión de edición de fotos.

Sin embargo, incluso procesar imágenes con un filter Core Image personalizado para iOS es más fácil que con un complemento Image Unit. No hay import, seguido de la confusa tarea de configurar .plist y los files de descripción y lo que no.

Un filter Core Image personalizado para iOS es simplemente una class Cocoa Touch que es una subclass de CIFilter; en él, se especifican los parameters de input (siempre al less la image), las configuraciones de los attributes personalizados y sus valores pnetworkingeterminados, y luego cualquier combinación de filters Core Image incorporados o personalizados. Si desea agregar un kernel OpenGL a la canalización de image processing, simplemente agregue un método CIKernel, que carga el .cikernel que escribe en un file separado.

La belleza de este método particular para desarrollar un Core Image Filter personalizado para iOS es que los filters personalizados se crean y se denominan de la misma manera que los filters incorporados:

 CIFilter* PrewittKernel = [CIFilter filterWithName:@"PrewittKernel"]; CIImage *result = [CIFilter filterWithName:@"PrewittKernel" keysAndValues:kCIInputImageKey, self.inputImage, nil].outputImage; 

Aquí hay un ejemplo simple que usa OpenGL para aplicar el Operador de Prewitt a una image; primero, la class Cocoa Touch (subsorting CIFilter), luego, el file CIKernel (que contiene el código OpenGL ES 3.0):

El file de encabezado:

 // // PrewittKernel.h // Photo Filter // // Created by James Alan Bush on 5/23/15. // // #import <CoreImage/CoreImage.h> @interface PrewittKernel : CIFilter { CIImage *inputImage; } @property (retain, nonatomic) CIImage *inputImage; @end 

El file de implementación:

 // // PrewittKernel.m // Photo Filter // // Created by James Alan Bush on 5/23/15. // // #import <CoreImage/CoreImage.h> @interface PrewittKernel : CIFilter { CIImage *inputImage; } @property (retain, nonatomic) CIImage *inputImage; @end @implementation PrewittKernel @synthesize inputImage; - (CIKernel *)prewittKernel { static CIKernel *kernelPrewitt = nil; NSBundle *bundle = [NSBundle bundleForClass:NSClassFromString(@"PrewittKernel")]; NSStringEncoding encoding = NSUTF8StringEncoding; NSError *error = nil; NSString *code = [NSString stringWithContentsOfFile:[bundle pathForResource:@"PrewittKernel" ofType:@"cikernel"] encoding:encoding error:&error]; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ kernelPrewitt = [CIKernel kernelWithString:code]; }); return kernelPrewitt; } - (CIImage *)outputImage { CIImage *result = self.inputImage; return [[self prewittKernel] applyWithExtent:result.extent roiCallback:^CGRect(int index, CGRect rect) { return CGRectMake(0, 0, CGRectGetWidth(result.extent), CGRectGetHeight(result.extent)); } arguments:@[result]]; } @end 

El CIKernel (OpenGL ES 3.0):

 /* PrewittKernel.cikernel */ kernel vec4 prewittKernel(sampler image) { vec2 xy = destCoord(); vec4 bottomLeftIntensity = sample(image, samplerTransform(image, xy + vec2(-1, -1))); vec4 topRightIntensity = sample(image, samplerTransform(image, xy + vec2(+1, +1))); vec4 topLeftIntensity = sample(image, samplerTransform(image, xy + vec2(+1, -1))); vec4 bottomRightIntensity = sample(image, samplerTransform(image, xy + vec2(-1, +1))); vec4 leftIntensity = sample(image, samplerTransform(image, xy + vec2(-1, 0))); vec4 rightIntensity = sample(image, samplerTransform(image, xy + vec2(+1, 0))); vec4 bottomIntensity = sample(image, samplerTransform(image, xy + vec2(0, -1))); vec4 topIntensity = sample(image, samplerTransform(image, xy + vec2(0, +1))); vec4 h = vec4(-topLeftIntensity - topIntensity - topRightIntensity + bottomLeftIntensity + bottomIntensity + bottomRightIntensity); vec4 v = vec4(-bottomLeftIntensity - leftIntensity - topLeftIntensity + bottomRightIntensity + rightIntensity + topRightIntensity); float h_max = max(hr, max(hg, hb)); float v_max = max(vr, max(vg, vb)); float mag = length(vec2(h_max, v_max)) * 1.0; return vec4(vec3(mag), 1.0); } 

Aquí hay otro filter que genera una máscara de desenfoque restando (o, más bien, diferenciando) una image borrosa gaussiana del original usando filters de image de núcleo incorporados, sin código de núcleo de kernel de image (OpenGL); muestra cómo especificar y usar un atributo personalizado, a saber, el radio del desenfoque gaussiano:

El file de encabezado:

 // // GaussianKernel.h // Chroma // // Created by James Alan Bush on 7/12/15. // Copyright © 2015 James Alan Bush. All rights reserved. // #import <CoreImage/CoreImage.h> @interface GaussianKernel : CIFilter { CIImage *inputImage; NSNumber *inputRadius; } @property (retain, nonatomic) CIImage *inputImage; @property (retain, nonatomic) NSNumber *inputRadius; @end 

El file de implementación:

 // // GaussianKernel.m // Chroma // // Created by James Alan Bush on 7/12/15. // Copyright © 2015 James Alan Bush. All rights reserved. // #import "GaussianKernel.h" @implementation GaussianKernel @synthesize inputImage; @synthesize inputRadius; + (NSDictionary *)customAttributes { return @{ @"inputRadius" : @{ kCIAttributeMin : @3.0, kCIAttributeMax : @15.0, kCIAttributeDefault : @7.5, kCIAttributeType : kCIAttributeTypeScalar } }; } - (void)setDefaults { self.inputRadius = @7.5; } - (CIImage *)outputImage { CIImage *result = self.inputImage; CGRect rect = [[GlobalCIImage shanetworkingSingleton].ciImage extent]; rect.origin = CGPointZero; CGRect cropRectLeft = CGRectMake(0, 0, rect.size.width, rect.size.height); CIVector *cropRect = [CIVector vectorWithX:rect.origin.x Y:rect.origin.y Z:rect.size.width W:rect.size.height]; result = [[CIFilter filterWithName:@"CIGaussianBlur" keysAndValues:kCIInputImageKey, result, @"inputRadius", [NSNumber numberWithFloat:inputRadius.floatValue], nil].outputImage imageByCroppingToRect:cropRectLeft]; result = [CIFilter filterWithName:@"CICrop" keysAndValues:@"inputImage", result, @"inputRectangle", cropRect, nil].outputImage; result = [CIFilter filterWithName:@"CIDifferenceBlendMode" keysAndValues:kCIInputImageKey, result, kCIInputBackgroundImageKey, result, nil].outputImage; return result; } @end