30

私は写真フィルター アプリ (Instagram、Camera+ など) を作成しています。メイン画面はUIImageViewユーザーに画像を表示する画面で、下部のバーにはいくつかのフィルターやその他のオプションがあります。
オプションの 1 つはblurです。ユーザーは指を使って、ぼかしのない部分 (半径と位置) を表す円をつまんだり動かしたりできます。この円の外側のすべてのピクセルがぼやけます。

ユーザーが画面に触れると、ぼやけた部分を表す画像の上に半透明のレイヤーを追加し、ぼやけていない部分を表す完全に透明な円を追加します。

私の質問は、このレイヤーを追加するにはどうすればよいですか? 画像ビューの上にいくつかのビューを使用し、いくつかのマスクを使用して円の形状を取得する必要があると思いますか? ここで良いヒントをいただければ幸いです。

もう1つ
必要なのは、円がまっすぐにカットされるのではなく、一種のグラデーションフェードが必要なことです。Instagramのようなもの:
ここに画像の説明を入力

そして、非常に重要なことは、この効果を良いパフォーマンスで得ることです。私はこの効果を得ることに成功しましdrawRect:たが、古いデバイス (iphone 4、iPod) ではパフォーマンスが非常に悪かったです。

4

3 に答える 3

97

シャープマスク

1 つの形状 (または一連の形状) で構成されるパスを別の形状の穴として描きたいときはいつでも、キーはほとんどの場合、「偶数奇数ワインディング ルール」を使用することです。

Cocoa Drawing GuideWinding Rulesセクションから:

ワインディング ルールは、パスの塗りつぶし領域全体を構成する各隣接領域に関する情報を追跡する単純なアルゴリズムです。光線は、指定された領域内のポイントからパス境界外の任意のポイントに描画されます。次に、交差するパス ラインの総数 (暗黙のラインを含む) と各パス ラインの方向が、領域を塗りつぶすかどうかを決定するルールを使用して解釈されます。

コンテキストとしてのルールと理解しやすくするための図がなければ、説明はあまり役に立たないことを理解しています。そのため、上記のリンクを読むことをお勧めします. サークル マスク レイヤーを作成するために、次の図は偶数奇数ワインディング ルールで実現できることを示しています。

非ゼロワインディング ルール

非ゼロワインディング ルール

偶数奇数巻ルール

偶数奇数巻ルール

これで、 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);
}

...

コード規則図

(この図は、コード内の命名規則を理解するのに役立ちます)


グラデーションマスク

Apple のQuartz 2D Programming GuideGradients セクションでは、ぼやけたエッジを持つマスクを作成するために使用できる放射状グラデーションを描画する方法について詳しく説明しています。これには、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];
}

...

コード規則図

(この図は、コード内の命名規則を理解するのに役立ちます)


ノート

イメージをホストするmultipleTouchEnabledプロパティが/に設定されていることを確認します。UIImageViewYEStrue

multipleTouchEnabled


ノート

OPの質問への回答を明確にするために、この回答では、最初に使用された命名規則を引き続き使用しています。これは、他の人に誤解を与える可能性があります。「マスク」とは、このコンテキストがイメージ マスクを参照するのではなく、より一般的な意味でマスクを参照することです。この回答では、画像マスキング操作を使用していません。

于 2013-03-15T09:49:05.430 に答える
1

GPUImageフレームワークGPUImageGaussianSelectiveBlurFilter内に含まれているものを使用したいようです。それはあなたが望むものを達成するためのより速くより効率的な方法であるべきです.

ユーザーがぼやけていない円のサイズを変更できるようにするために、excludeCircleRadiusプロパティをUIPinchGestureRecognizerに接続できます。次に、「excludeCirclePoint」プロパティをUIPanGestureRecognizerと組み合わせて使用​​して、ユーザーがぼやけていない円の中心を移動できるようにします。

フィルタの適用方法について詳しくは、次をご覧ください。

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

于 2013-03-10T19:39:59.170 に答える
1

誰かがそれを必要とする場合はSwiftで(パンジェスチャーも追加):

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()
}
于 2015-12-14T20:56:18.083 に答える