Подтвердить что ты не робот

Добавление слоя маски круга в UIImageView

Я создаю приложение для фильтра фотографий (например, Instagram, Camera + и многие другие..), главный экран - это UIImageView, представляющий изображение для пользователя, и нижняя панель с некоторыми фильтрами и другими параметрами.
Один из вариантов - размытие, где пользователь может использовать свои пальцы, чтобы ущипнуть или переместить круг, который представляет часть размытия (радиус и положение) - все пиксели вне этого круга будут размыты,

Когда пользователь прикасается к экрану, я хочу добавить полупрозрачный слой над моим изображением, представляющий размытую часть, с полностью прозрачным кружком, представляющим часть без размытия.

Итак, мой вопрос: как добавить этот слой? Я полагаю, мне нужно использовать какой-то вид над моим представлением изображения и использовать какую-то маску, чтобы получить форму круга? Я бы очень признателен за хороший совет.

Еще одна вещь
Мне нужен круг, который не будет стричь прямо, но у вас будет какой-то градиент. что-то вроде Instagram:
enter image description here

И что очень важно, чтобы получить этот эффект с хорошей производительностью, мне удастся получить этот эффект с помощью drawRect:, но производительность была очень плохой для старых устройств (iphone 4, iPod)

4b9b3361

Ответ 1

Острая маска

Всякий раз, когда вы хотите нарисовать путь, который состоит из фигуры (или серии фигур) в виде отверстия в другой фигуре, ключ почти всегда использует "правило четной нечетной намотки".

Из раздела " Правила намотки " в Руководстве по рисованию cocoa:

Правило намотки - это просто алгоритм, который отслеживает информацию о каждой смежной области, которая составляет общую область заполнения пути. Луч рисуется из точки внутри данной области в любую точку за пределами границ пути. Общее количество пересеченных линий пути (включая неявные линии) и направление каждой линии пути затем интерпретируются с использованием правил, которые определяют, должна ли область быть заполнена.

Я ценю, что это описание не очень полезно без правил в виде контекста и диаграмм, чтобы его было легче понять, поэтому я призываю вас прочитать ссылки, которые я предоставил выше. Для создания нашего слоя с круговой маской на следующих диаграммах показано, что позволяет нам выполнить правило четного нечетного наматывания:

Нулевое правило намотки

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 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 Quartz 2D подробно описывает, как рисовать радиальные градиенты, которые мы можем использовать для создания маски с заостренным краем. Это включает в себя рисование содержимого 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 свойство UIImageView хостинг изображения устанавливаются в YES/true:

multipleTouchEnabled


Заметка

Для ясности в ответе на вопрос OPs этот ответ продолжает использовать первоначально использованные соглашения об именах. Это может немного вводить в заблуждение других. "Маска" означает, что этот контекст относится не к маске изображения, а к маске в более общем смысле. Этот ответ не использует никаких операций маскировки изображения.

Ответ 2

Похоже, вы хотите использовать GPUImageGaussianSelectiveBlurFilter, который содержится внутри рамки GPUImage. Это должен быть более эффективный способ достижения желаемого.

Вы можете подключить свойство excludeCircleRadius к UIPinchGestureRecognizer, чтобы пользователь мог изменить размер не- размытый круг. Затем используйте свойство excludeCirclePoint в сочетании с UIPanGestureRecognizer, чтобы пользователь мог перемещать центр не-размытого круга.

Подробнее о том, как применить фильтр здесь:

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

Ответ 3

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