在UIImageView上添加圆形遮罩层

时间:2013-02-03 09:12:39

标签: ios objective-c core-graphics

我正在构建一个照片过滤器应用程序(如Instagram,Camera +以及更多......),可能主屏幕是UIImageView向用户呈现图像,底部栏带有一些过滤器和其他选项。
其中一个选项是模糊,用户可以用手指捏住或移动代表非模糊部分的圆圈(半径和位置) - 此圆圈外的所有像素都会模糊。

当用户触摸屏幕时,我想在我的图像上方添加一个代表模糊部分的半透明层,其中一个完全透明的圆圈代表非模糊部分。

所以我的问题是,如何添加此图层?我想我需要在我的图像视图上方使用一些视图,并使用一些蒙版来获得我的圆形?我真的很感激这里的好建议。

再一件事
我需要圆圈不会直切,但有一种渐变淡化。像Instagram这样的东西:
enter image description here

非常重要是为了获得良好性能的效果,我用drawRect:成功获得此效果,但旧设备(iphone 4,iPod)的性能非常差)

3 个答案:

答案 0 :(得分:96)

夏普面具

每当你想要绘制一个由一个形状(或一系列形状)组成的路径作为另一个形状的洞时,该键几乎总是使用“偶数奇怪的缠绕规则”。

来自Winding RulesCocoa Drawing Guide部分:

  
    

绕线规则只是一种算法,可跟踪构成路径整体填充区域的每个连续区域的信息。从给定区域内的点到路径边界外的任何点绘制光线。然后使用规则来解释交叉路径线的总数(包括隐含线)和每个路径线的方向,这些规则确定是否应该填充该区域。

  

我很欣赏如果没有规则作为上下文和图表来使描述更容易理解,那么描述就没有用,所以我建议你阅读我上面提供的链接。为了创建我们的圆形遮罩层,下面的图表描绘了一个奇怪的缠绕规则允许我们完成的事情:

非零绕组规则

Non Zero Winding Rule

甚至奇数绕组规则

Even Odd Winding Rule

现在只需使用CAShapeLayer创建半透明蒙版,可以通过用户交互重新定位和扩展和缩小。

代码

#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

(此图可能有助于理解代码中的命名约定)


渐变蒙版

Apple Gradients sectionQuartz 2D Programming Guide详细说明了如何绘制径向渐变,我们可以使用这些渐变创建具有羽毛边缘的蒙版。这涉及直接通过子类化或实现其绘图委托来绘制CALayer内容。在这里,我们将其子类化为封装与其相关的数据,即原点和直径。

代码

  

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(无论您在何处实施模糊文件管理器屏蔽功能)

#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

(此图可能有助于理解代码中的命名约定)


注意

确保托管您图片的multipleTouchEnabled的{​​{1}}属性设置为UIImageView / YES

multipleTouchEnabled


注意

为了清楚地回答OP问题,这个答案继续使用最初使用的命名约定。这可能会对其他人产生一些误导。 “掩码”这个上下文并不是指图像蒙版,而是更一般意义上的掩模。这个答案不使用任何图像掩蔽操作。

答案 1 :(得分:1)

听起来好像要使用GPUImage框架中包含的GPUImageGaussianSelectiveBlurFilter。它应该是一种更快捷,更有效的方式来实现您想要的目标。

您可以将excludeCircleRadius属性与UIPinchGestureRecognizer相关联,以便用户更改非模糊圆圈的大小。然后使用'excludeCirclePoint'属性与UIPanGestureRecognizer结合使用,以允许用户移动非模糊圆圈的中心。

在此处详细了解如何应用过滤器:

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

答案 2 :(得分:1)

在Swift中如果有人需要它(也添加了pan手势):

<强> 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()
    }

    required 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()
}