30 votes

Ajout d'un calque de masque de cercle sur une UIImageView

Je suis en train de construire un filtre Photo app (comme Instagram, Camera+ et beaucoup plus..), peut-écran principal est un UIImageView que la présentation de l'image de l'utilisateur, et une barre en bas avec quelques filtres et d'autres options.
L'une des options est flou, où l'utilisateur peut utiliser ses doigts pour pincer ou de déplacer un cercle qui représente la non-flou partie (le rayon et la position) - tous les pixels en dehors de ce cercle sera floue.

Lorsque l'utilisateur touche l'écran, je veux ajouter une couche semi transparente au-dessus de mon image qui représentent la partie floue, avec une totale transparence cercle qui représentent les non-flou de la partie.

Donc ma question est, comment puis-je ajouter cette couche? Je suppose que j'ai besoin d'utiliser au-dessus de mon image, et d'utiliser un masque pour obtenir mon cercle de la forme? Je voudrais vraiment apprécier un bon conseil ici.

Une Chose De Plus
J'ai besoin de le cercle ne sera pas coupé droit, mais ont une sorte de gradient fade. quelque chose comme Instagram:
enter image description here

Et ce qui est très important est pour obtenir cet effet avec de bonnes performances, j'aimerais réussir à obtenir cet effet avec drawRect: , mais le rendement est très mauvais sur les anciens appareils (iphone 4, iPod)

97voto

Elliott Perry Points 2172

Forte De Masque

Chaque fois que vous voulez dessiner un chemin qui se compose d'une forme (ou une série de formes) comme un trou dans une autre forme, la clé est presque toujours à l'aide d'un 'même bizarre d'enroulement de la règle'.

À partir de l' Enroulement des Règles de la section de la de Cacao Dessin Guide:

Une règle d'enroulement est tout simplement un algorithme qui permet de surveiller les informations à propos de chaque région contiguë qui fait le chemin d'ensemble de la zone de remplissage. Un rayon est établi à partir d'un point à l'intérieur d'une région donnée à n'importe quel point à l'extérieur du chemin de limites. Le nombre total de traversé les lignes de chemin (y compris implicite lignes) et la direction de chaque ligne de chemin d'accès sont ensuite interprétés à l'aide des règles qui déterminent si la région doit être rempli.

J'apprécie le fait que la description n'est pas vraiment utile sans les règles de contexte et des diagrammes afin de le rendre plus facile à comprendre donc je vous invite à lire les liens que j'ai fournis ci-dessus. Pour l'amour de la création de notre cercle calque de masque les diagrammes suivants illustrent ce qu'un même bizarre règle d'enroulement nous permet de réaliser:

Non Zéro Règle D'Enroulement

Non Zero Winding Rule

Même Étrange Règle D'Enroulement

Even Odd Winding Rule

Maintenant c'est simplement une question de la création de l'translucide masque à l'aide d'un CAShapeLayer qui peut être repositionné et élargi et contracté par l'utilisateur iteraction.

Code

#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);
}

...

Code Conventions Diagram

(Ce schéma peut aider à comprendre les conventions de nommage dans le code)


Masque En Dégradé

Les Gradients de la section de la Pomme de Quartz 2D Guide de Programmation détails comment dessiner les dégradés radiaux que nous pouvons utiliser pour créer un masque avec un contour progressif. Cette invovlves le dessin d'un CALayers contenu directement par le sous-classement ou de la mise en œuvre de son dessin délégué. Ici, nous partirons à encapsuler les données liées à elle c'est à dire de l'origine et de diamètre.

Code

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 (Où vous êtes à la mise en œuvre du flou filer la fonctionnalité de masquage)

#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];
}

...

Code Conventions Diagram

(Ce schéma peut aider à comprendre les conventions de nommage dans le code)


Note

Assurer l' multipleTouchEnabled de la propriété de l' UIImageView l'hébergement de votre image est réglée sur YES/true:

multipleTouchEnabled


Note

Par souci de clarté dans la réponse à la Fpo question de cette réponse continue à utiliser les conventions de nommage utilisé initialement. Cela peut être un peu trompeur pour les autres. 'Masque' est dans ce contexte ne fait pas référence à un masque d'image mais le masque dans un sens plus général. Cette réponse ne pas utiliser n'importe quelle image les opérations de masquage.

1voto

brynbodayle Points 4120

On dirait que vous voulez utiliser GPUImageGaussianSelectiveBlurFilter qui est contenue à l'intérieur de la GPUImage cadre. Il devrait être plus rapide d'un moyen plus efficace pour atteindre ce que vous voulez.

Vous pouvez brancher l' excludeCircleRadius de la propriété d'un UIPinchGestureRecognizer afin de permettre à l'utilisateur de modifier la taille de la non-floue cercle. Ensuite, utilisez la fonction 'excludeCirclePoint propriété en conjonction avec un UIPanGestureRecognizer pour permettre à l'utilisateur de déplacer le centre de la non-floue cercle.

Lire plus sur la façon d'appliquer le filtre:

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X