Agregar una capa de máscara de círculo en un UIImageView

Estoy construyendo una aplicación de filter de fotos (como Instagram, Camera + y muchos más …), la pantalla principal es un UIImageView que presenta la image al usuario, y una barra inferior con algunos filters y otras opciones.
Una de las opciones es la falta de definición , donde el usuario puede usar sus dedos para pellizcar o mover un círculo que represente la parte no borrosa (radio y position): todos los píxeles fuera de este círculo se borrarán.

Cuando el usuario toque la pantalla, quiero agregar una capa semi transparente sobre mi image que represente la parte borrosa, con un círculo totalmente transparente que represente la parte que no sea borrosa.

Entonces mi pregunta es: ¿cómo agrego esta capa? Supongo que necesito usar alguna vista sobre la vista de mi image y usar alguna máscara para get la forma de mi círculo. Realmente agradecería un buen consejo aquí.

Una cosa más
Necesito que el círculo no se corte directamente, pero que tenga una especie de degradado gradual. algo como Instagram:
introduzca la descripción de la imagen aquí

Y lo que es muy importante es lograr este efecto con un buen performance. Lograría get este efecto con drawRect: pero el performance fue muy malo en dispositivos antiguos (iPhone 4, iPod).

Mascara afilada

Cada vez que desea dibujar un path que consiste en una forma (o una serie de forms) como un agujero en otra forma, la key casi siempre usa una 'regla de enrollamiento incluso impar'.

De la sección Reglas de enrollamiento de la Guía de dibujo de cocoa :

Una regla de bobinado es simplemente un algorithm que rastrea la información sobre cada región contigua que constituye el área de relleno general de la ruta. Se dibuja un rayo desde un punto dentro de una región dada hasta cualquier punto fuera de los límites de la ruta. El número total de líneas de ruta cruzadas (incluidas las líneas implícitas) y la dirección de cada línea de ruta se interpretan entonces usando reglas que determinan si la región debe llenarse.

Aprecio que la descripción no sea realmente útil sin las reglas como context y diagtwigs para que sea más fácil de entender, por lo que le pido que lea los enlaces que he proporcionado anteriormente. Con el fin de crear nuestra capa de máscara de círculo, los siguientes diagtwigs muestran lo que nos permite lograr una regla de bobinado aún impar:

Regla de bobina no cero

Regla de bobina no cero

Incluso regla de liquidación impar

Incluso regla de liquidación impar

Ahora se trata simplemente de crear la máscara translúcida utilizando un CAShapeLayer que se puede reposicionar y expandir y contraer a través de iteraction del usuario.

Código

 #import <QuartzCore/QuartzCore.h> @interface ViewController () @property (strong, nonatomic) IBOutlet UIImageView *imageView; @property (strong) CAShapeLayer *blurFilterMask; @property (assign) CGPoint blurFilterOrigin; @property (assign) CGFloat blurFilterDiameter; @end @implementation ViewController // begin the blur masking operation. - (void)beginBlurMasking { self.blurFilterOrigin = self.imageView.center; self.blurFilterDiameter = MIN(CGRectGetWidth(self.imageView.bounds), CGRectGetHeight(self.imageView.bounds)); CAShapeLayer *blurFilterMask = [CAShapeLayer layer]; // Disable implicit animations for the blur filter mask's path property. blurFilterMask.actions = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNull null], @"path", nil]; blurFilterMask.fillColor = [UIColor blackColor].CGColor; blurFilterMask.fillRule = kCAFillRuleEvenOdd; blurFilterMask.frame = self.imageView.bounds; blurFilterMask.opacity = 0.5f; self.blurFilterMask = blurFilterMask; [self refreshBlurMask]; [self.imageView.layer addSublayer:blurFilterMask]; UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)]; [self.imageView addGestureRecognizer:tapGesture]; UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)]; [self.imageView addGestureRecognizer:pinchGesture]; } // Move the origin of the blur mask to the location of the tap. - (void)handleTap:(UITapGestureRecognizer *)sender { self.blurFilterOrigin = [sender locationInView:self.imageView]; [self refreshBlurMask]; } // Expand and contract the clear region of the blur mask. - (void)handlePinch:(UIPinchGestureRecognizer *)sender { // Use some combination of sender.scale and sender.velocity to determine the rate at which you want the circle to expand/contract. self.blurFilterDiameter += sender.velocity; [self refreshBlurMask]; } // Update the blur mask within the UI. - (void)refreshBlurMask { CGFloat blurFilterRadius = self.blurFilterDiameter * 0.5f; CGMutablePathRef blurRegionPath = CGPathCreateMutable(); CGPathAddRect(blurRegionPath, NULL, self.imageView.bounds); CGPathAddEllipseInRect(blurRegionPath, NULL, CGRectMake(self.blurFilterOrigin.x - blurFilterRadius, self.blurFilterOrigin.y - blurFilterRadius, self.blurFilterDiameter, self.blurFilterDiameter)); self.blurFilterMask.path = blurRegionPath; CGPathRelease(blurRegionPath); } ... 

Diagrama de convenciones del código

(Este diagtwig puede ayudar a comprender las convenciones de nomenclatura en el código)


Máscara de gradiente

La sección Degradados de la Guía de progtwigción de Quartz 2D de Apple detalla cómo dibujar degradados radiales que podemos usar para crear una máscara con borde en forma de pluma. Esto implica dibujar el contenido de un CALayer directamente subclassando o implementando su delegado de dibujo. Aquí la subclasificamos para encapsular los datos relacionados con él, es decir, origen y diámetro.

Código

BlurFilterMask.h

 #import <QuartzCore/QuartzCore.h> @interface BlurFilterMask : CALayer @property (assign) CGPoint origin; // The centre of the blur filter mask. @property (assign) CGFloat diameter; // the diameter of the clear region of the blur filter mask. @end 

BlurFilterMask.m

 #import "BlurFilterMask.h" // The width in points the gradated region of the blur filter mask will span over. CGFloat const GRADIENT_WIDTH = 50.0f; @implementation BlurFilterMask - (void)drawInContext:(CGContextRef)context { CGFloat clearRegionRadius = self.diameter * 0.5f; CGFloat blurRegionRadius = clearRegionRadius + GRADIENT_WIDTH; CGColorSpaceRef baseColorSpace = CGColorSpaceCreateDeviceRGB(); CGFloat colours[8] = { 0.0f, 0.0f, 0.0f, 0.0f, // Clear region colour. 0.0f, 0.0f, 0.0f, 0.5f }; // Blur region colour. CGFloat colourLocations[2] = { 0.0f, 0.4f }; CGGradientRef gradient = CGGradientCreateWithColorComponents (baseColorSpace, colours, colourLocations, 2); CGContextDrawRadialGradient(context, gradient, self.origin, clearRegionRadius, self.origin, blurRegionRadius, kCGGradientDrawsAfterEndLocation); CGColorSpaceRelease(baseColorSpace); CGGradientRelease(gradient); } @end 

ViewController.m (dondequiera que esté implementando la funcionalidad de enmascaramiento del filer de desenfoque)

 #import "ViewController.h" #import "BlurFilterMask.h" #import <QuartzCore/QuartzCore.h> @interface ViewController () @property (strong, nonatomic) IBOutlet UIImageView *imageView; @property (strong) BlurFilterMask *blurFilterMask; @end @implementation ViewController // Begin the blur filter masking operation. - (void)beginBlurMasking { BlurFilterMask *blurFilterMask = [BlurFilterMask layer]; blurFilterMask.diameter = MIN(CGRectGetWidth(self.imageView.bounds), CGRectGetHeight(self.imageView.bounds)); blurFilterMask.frame = self.imageView.bounds; blurFilterMask.origin = self.imageView.center; blurFilterMask.shouldRasterize = YES; [self.imageView.layer addSublayer:blurFilterMask]; [blurFilterMask setNeedsDisplay]; self.blurFilterMask = blurFilterMask; UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)]; [self.imageView addGestureRecognizer:tapGesture]; UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)]; [self.imageView addGestureRecognizer:pinchGesture]; } // Move the origin of the blur mask to the location of the tap. - (void)handleTap:(UITapGestureRecognizer *)sender { self.blurFilterMask.origin = [sender locationInView:self.imageView]; [self.blurFilterMask setNeedsDisplay]; } // Expand and contract the clear region of the blur mask. - (void)handlePinch:(UIPinchGestureRecognizer *)sender { // Use some combination of sender.scale and sender.velocity to determine the rate at which you want the mask to expand/contract. self.blurFilterMask.diameter += sender.velocity; [self.blurFilterMask setNeedsDisplay]; } ... 

Diagrama de convenciones del código

(Este diagtwig puede ayudar a comprender las convenciones de nomenclatura en el código)


Nota

Asegúrese de que la propiedad multipleTouchEnabled de UIImageView aloja su image está establecida en YES / true :

multipleTouchEnabled


Nota

En aras de la claridad en la respuesta a la pregunta de OP, esta respuesta continúa utilizando las convenciones de nomenclatura utilizadas originalmente. Esto puede ser un poco engañoso para otros. 'Máscara' es este context que no se refiere a una máscara de image sino a una máscara en un sentido más general. Esta respuesta no utiliza ninguna operación de enmascaramiento de imágenes.

Parece que quieres usar GPUImageGaussianSelectiveBlurFilter que está contenido dentro del marco GPUImage . Debería ser una forma más rápida y eficiente de lograr lo que desea.

Puede conectar la propiedad excludeCircleRadius a un UIPinchGestureRecognizer para permitir al usuario cambiar el tamaño del círculo no borroso. Luego, use la propiedad 'excludeCirclePoint' en set con un UIPanGestureRecognizer para permitir al usuario mover el centro del círculo no borroso.

Lea más sobre cómo aplicar el filter aquí:

https://github.com/BradLarson/GPUImage#processing-a-still-image

En Swift si alguien lo necesita (agregó el gesto de paneo también):

BlurFilterMask.swift

 import Foundation import QuartzCore class BlurFilterMask : CALayer { private let GRADIENT_WIDTH : CGFloat = 50.0 var origin : CGPoint? var diameter : CGFloat? override init() { super.init() } requinetworking init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func drawInContext(ctx: CGContext) { let clearRegionRadius : CGFloat = self.diameter! * 0.5 let blurRegionRadius : CGFloat = clearRegionRadius + GRADIENT_WIDTH let baseColorSpace = CGColorSpaceCreateDeviceRGB(); let colours : [CGFloat] = [0.0, 0.0, 0.0, 0.0, // Clear region 0.0, 0.0, 0.0, 0.5] // blur region color let colourLocations : [CGFloat] = [0.0, 0.4] let gradient = CGGradientCreateWithColorComponents (baseColorSpace, colours, colourLocations, 2) CGContextDrawRadialGradient(ctx, gradient, self.origin!, clearRegionRadius, self.origin!, blurRegionRadius, .DrawsAfterEndLocation); } } 

ViewController.swift

 func addMaskOverlay(){ imageView!.userInteractionEnabled = true imageView!.multipleTouchEnabled = true let blurFilterMask = BlurFilterMask() blurFilterMask.diameter = min(CGRectGetWidth(self.imageView!.bounds), CGRectGetHeight(self.imageView!.bounds)) blurFilterMask.frame = self.imageView!.bounds blurFilterMask.origin = self.imageView!.center blurFilterMask.shouldRasterize = true self.imageView!.layer.addSublayer(blurFilterMask) self.blurFilterMask = blurFilterMask self.blurFilterMask!.setNeedsDisplay() self.imageView!.addGestureRecognizer(UIPinchGestureRecognizer(target: self, action: "handlePinch:")) self.imageView!.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "handleTap:")) self.imageView!.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: "handlePan:")) } func donePressed(){ //save photo and add to textview let parent : LoggedInContainerViewController? = self.parentViewController as? LoggedInContainerViewController let vc : OrderFlowCareInstructionsTextViewController = parent?.viewControllers[(parent?.viewControllers.count)!-2] as! OrderFlowCareInstructionsTextViewController vc.addImageToTextView(imageView?.image) parent?.popViewController() } //MARK: Mask Overlay func handleTap(sender : UITapGestureRecognizer){ self.blurFilterMask!.origin = sender.locationInView(self.imageView!) self.blurFilterMask!.setNeedsDisplay() } func handlePinch(sender : UIPinchGestureRecognizer){ self.blurFilterMask!.diameter = self.blurFilterMask!.diameter! + sender.velocity*3 self.blurFilterMask!.setNeedsDisplay() } func handlePan(sender : UIPanGestureRecognizer){ let translation = sender.translationInView(self.imageView!) let center = CGPoint(x:self.imageView!.center.x + translation.x, y:self.imageView!.center.y + translation.y) self.blurFilterMask!.origin = center self.blurFilterMask!.setNeedsDisplay() }