Нарисуйте свечение вокруг внутреннего края нескольких CGPaths
http://img403.imageshack.us/img403/9582/paths.jpg
Если я создаю CGMutablePathRef путем добавления двух круговых путей, как показано на левом изображении, возможно ли получить окончательный CGPathRef, который представляет только внешнюю границу, как показано на правом изображении?
Спасибо за любую помощь!
2 ответа
То, о чем вы просите, - это объединение путей Безье. Apple не поставляет никаких API для вычисления объединения путей. На самом деле это довольно сложный алгоритм. Вот несколько ссылок:
- http://www.cocoadev.com/index.pl?NSBezierPathcombinatorics
- http://losingfight.com/blog/2011/07/09/how-to-implement-boolean-operations-on-bezier-paths-part-3/
Если вы объясните, что вы хотите сделать с путем объединения, мы могли бы предложить некоторые альтернативы, которые не требуют фактического вычисления объединения.
Вы можете нарисовать довольно приличное внутреннее свечение, не вычисляя объединение путей. Вместо этого сделайте растровое изображение. Заполните каждый путь на растровом изображении. Вы будете использовать это в качестве маски. Затем создайте перевернутое изображение маски, в котором есть все, что находится за пределами области объединения. Вы нарисуете это, чтобы CoreGraphics нарисовала тень вокруг внутреннего края объединения. Наконец, установите маску в качестве маски CGContext, установите параметры тени и нарисуйте перевернутое изображение.
Хорошо, это звучит сложно. Но вот как это выглядит (версия 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();
}
Вот два способа в Swift 3:
код пути:
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.")
}
четное нечетное правило заполнения:
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)
клип
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()