Нарисуйте свечение вокруг внутреннего края нескольких CGPaths - PullRequest
16 голосов
/ 13 декабря 2011

image http://img403.imageshack.us/img403/9582/paths.jpg

Если я создаю CGMutablePathRef путем сложения двух круговых путей, как показано на левом изображении, возможно ли получить окончательный CGPathRef, который представляет только внешнюю границу, как показано справаimage?

Спасибо за любую помощь!

Ответы [ 2 ]

30 голосов
/ 13 декабря 2011

То, о чем вы просите, - это объединение путей Безье. Apple не поставляет никаких API для вычисления объединения путей. На самом деле это довольно сложный алгоритм. Вот несколько ссылок:

Если вы объясните, что вы хотите сделать с путем объединения, мы могли бы предложить несколько альтернатив, которые не требуют фактического вычисления объединения.

Вы можете нарисовать довольно приличное внутреннее свечение, фактически не вычисляя объединение путей. Вместо этого сделайте растровое изображение. Заполните каждый путь на растровом изображении. Вы будете использовать это в качестве маски. Затем создайте перевернутое изображение маски, в котором все за пределами заполненной области объединения заполнено. Вы нарисуете это, чтобы CoreGraphics нарисовал тень вокруг внутреннего края объединения. Наконец, установите маску в качестве маски CGContext, задайте параметры тени и нарисуйте перевернутое изображение.

Хорошо, это звучит сложно. Но вот как это выглядит (версия Retina справа):

glow glow retina

Он не идеален (слишком светлый в углах), но довольно хорош.

Так вот код. Я передаю UIBezierPaths вместо CGPaths, но переходить между ними тривиально. Я использую некоторые функции и объекты UIKit. Помните, что вы всегда можете заставить UIKit рисовать произвольный CGContext, используя UIGraphicsPushContext и UIGraphicsPopContext.

Во-первых, нам нужно изображение маски. Это должно быть изображение только для альфа-канала, которое равно 1 внутри любого из путей и 0 за пределами всех путей. Этот метод возвращает такое изображение:

- (UIImage *)maskWithPaths:(NSArray *)paths bounds:(CGRect)bounds
{
    // Get the scale for good results on Retina screens.
    CGFloat scale = [UIScreen mainScreen].scale;
    CGSize scaledSize = CGSizeMake(bounds.size.width * scale, bounds.size.height * scale);

    // Create the bitmap with just an alpha channel.
    // When created, it has value 0 at every pixel.
    CGContextRef gc = CGBitmapContextCreate(NULL, scaledSize.width, scaledSize.height, 8, scaledSize.width, NULL, kCGImageAlphaOnly);

    // Adjust the current transform matrix for the screen scale.
    CGContextScaleCTM(gc, scale, scale);
    // Adjust the CTM in case the bounds origin isn't zero.
    CGContextTranslateCTM(gc, -bounds.origin.x, -bounds.origin.y);

    // whiteColor has all components 1, including alpha.
    CGContextSetFillColorWithColor(gc, [UIColor whiteColor].CGColor);

    // Fill each path into the mask.
    for (UIBezierPath *path in paths) {
        CGContextBeginPath(gc);
        CGContextAddPath(gc, path.CGPath);
        CGContextFillPath(gc);
    }

    // Turn the bitmap context into a UIImage.
    CGImageRef cgImage = CGBitmapContextCreateImage(gc);
    CGContextRelease(gc);
    UIImage *image = [UIImage imageWithCGImage:cgImage scale:scale orientation:UIImageOrientationDownMirrored];
    CGImageRelease(cgImage);
    return image;
}

Это была действительно сложная часть. Теперь нам нужно изображение, которое является нашим цветом свечения везде вне области маски (объединения путей). Мы можем использовать функции UIKit, чтобы сделать это проще, чем чистый подход CoreGraphics:

- (UIImage *)invertedImageWithMask:(UIImage *)mask color:(UIColor *)color
{
    CGRect rect = { CGPointZero, mask.size };
    UIGraphicsBeginImageContextWithOptions(rect.size, NO, mask.scale); {
        // Fill the entire image with color.
        [color setFill];
        UIRectFill(rect);
        // Now erase the masked part.
        CGContextClipToMask(UIGraphicsGetCurrentContext(), rect, mask.CGImage);
        CGContextClearRect(UIGraphicsGetCurrentContext(), rect);
    }
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

С этими двумя изображениями мы можем нарисовать внутреннее свечение в текущем графическом контексте UIKit для массива путей:

- (void)drawInnerGlowWithPaths:(NSArray *)paths bounds:(CGRect)bounds color:(UIColor *)color offset:(CGSize)offset blur:(CGFloat)blur
{
    UIImage *mask = [self maskWithPaths:paths bounds:bounds];
    UIImage *invertedImage = [self invertedImageWithMask:mask color:color];
    CGContextRef gc = UIGraphicsGetCurrentContext();

    // Save the graphics state so I can restore the clip and
    // shadow attributes after drawing.
    CGContextSaveGState(gc); {
        CGContextClipToMask(gc, bounds, mask.CGImage);
        CGContextSetShadowWithColor(gc, offset, blur, color.CGColor);
        [invertedImage drawInRect:bounds];
    } CGContextRestoreGState(gc);
}

Чтобы проверить это, я создал изображение, используя пару кругов, и поместил его в UIImageView:

- (void)viewDidLoad
{
    [super viewDidLoad];

    UIBezierPath *path1 = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(20, 20, 60, 60)];
    UIBezierPath *path2 = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(50, 50, 60, 60)];
    NSArray *paths = [NSArray arrayWithObjects:path1, path2, nil];

    UIGraphicsBeginImageContextWithOptions(self.imageView.bounds.size, NO, 0.0); {
        [self drawInnerGlowWithPaths:paths bounds:self.imageView.bounds color:[UIColor colorWithHue:0 saturation:1 brightness:.8 alpha:.8] offset:CGSizeZero blur:10.0];
    }
    imageView.image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
}
0 голосов
/ 11 мая 2017

В Swift 3 есть два пути:

sample

код пути:

    let radius          = rect.height * 0.25
    let centerX         = rect.width  * 0.5
    let centerY         = rect.height * 0.5
    let arcCenterOffset = radius - radius * 0.5 * sqrt(3)
    let degree:(_: CGFloat) -> CGFloat = {
        return CGFloat.pi * $0 / 180
    }

    let gourd   = UIBezierPath()
    let circle1 = UIBezierPath(arcCenter: CGPoint(x: centerX - radius + arcCenterOffset, y: centerY), radius: radius, startAngle: degree(-30), endAngle: degree(30), clockwise: false)
    let circle2 = UIBezierPath(arcCenter: CGPoint(x: centerX + radius - arcCenterOffset, y: centerY ), radius: radius, startAngle: degree(150), endAngle: degree(-150), clockwise: false)
    gourd.append(circle1)
    gourd.append(circle2)

    let gourdInverse = UIBezierPath(cgPath: gourd.cgPath)
    let infiniteRect = UIBezierPath(rect: .infinite)
    gourdInverse.append(infiniteRect)

    guard let c = UIGraphicsGetCurrentContext() else {
        fatalError("current context not found.")
    }
  1. правило четного нечетного заполнения:

    c.beginPath()
    c.addPath(gourdInverse.cgPath)
    c.setShadow(offset: CGSize.zero, blur: 10, color: UIColor.red.cgColor)
    c.setFillColor(UIColor(white: 1, alpha: 1).cgColor)
    c.fillPath(using: .evenOdd)
    
  2. зажим

    c.beginPath()
    c.addPath(gourd.cgPath)
    c.clip()
    
    c.beginPath()
    c.addPath(gourdInverse.cgPath)
    c.setShadow(offset: CGSize.zero, blur: 10, color: UIColor.red.cgColor)
    c.fillPath()
    
...