写真フィルターアプリ(Instagram、Camera +など)を構築しています。メイン画面は、ユーザーに画像を表示するUIImageView
と、いくつかのフィルターやその他のオプションが表示された下部バーです。
オプションの1つはblurです。ユーザーは指を使用して、非ぼかし部分(半径と位置)を表す円をつまんだり移動したりできます。この円はぼやけます。
ユーザーが画面に触れたときに、ぼかし部分を表す半透明のレイヤーを画像の上に追加し、非ぼかし部分を表す完全に透明な円を追加します。
だから私の質問は、このレイヤーをどのように追加するのですか?画像ビューの上にあるビューを使用し、マスクを使用して円の形状を取得する必要があると思いますか?ここで良いヒントをいただければ幸いです。
もう1つ
円がまっすぐにカットされないようにする必要がありますが、一種のグラデーションフェードがあります。 Instagramのようなもの:
そして非常に重要は、この効果を良好なパフォーマンスで得ることです。drawRect:
でこの効果を得ることに成功しますが、古いデバイス(iphone 4、iPod)ではパフォーマンスが非常に悪かったです。
形状(または一連の形状)で構成されるパスを別の形状の穴として描画する場合、キーはほとんどの場合、「偶数の曲がり規則」を使用します。
Cocoa Drawing Guide の Winding 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の Gradientsセクション の Quartz 2Dプログラミングガイド には、エッジをぼかしたマスクを作成するために使用できる放射状グラデーションの描画方法が詳しく説明されています。これには、サブクラス化するか、その描画デリゲートを実装することで CALayer sコンテンツを直接描画することが含まれます。ここでは、それに関連するデータ(つまり、原点と直径)をカプセル化するためにサブクラス化します。
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
のUIImageView
プロパティがYES
/true
に設定されていることを確認します。
OPの質問への回答を明確にするために、この回答では、最初に使用された命名規則を引き続き使用します。これは、他の人を少し誤解させるかもしれません。 「マスク」とは、このコンテキストはイメージマスクではなく、より一般的な意味でのマスクを指します。この回答では、画像マスキング操作を使用していません。
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()
}
GPUImage フレームワーク内に含まれているGPUImageGaussianSelectiveBlurFilter
を使用したいようです。それはあなたが望むものを達成するためのより速くより効率的な方法であるべきです。
excludeCircleRadius
プロパティを IPinchGestureRecognizer にフックして、ユーザーがぼやけていない円のサイズを変更できるようにすることができます。次に、「excludeCirclePoint」プロパティを IPanGestureRecognizer と組み合わせて使用して、ユーザーがぼやけていない円の中心を移動できるようにします。
フィルターの適用方法については、こちらをご覧ください。
https://github.com/BradLarson/GPUImage#processing-a-still-image