Last active
August 2, 2018 09:03
-
-
Save RustyKnight/5348f815b231ce390659f85982df9862 to your computer and use it in GitHub Desktop.
An extension to provide a "circular progress" effect, which makes the image grey scale, generates a "pie slice" progression path and overlays the "exposed" color image
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
internal extension FloatingPoint { | |
var degreesToRadians: Self { return self * .pi / 180 } | |
var radiansToDegrees: Self { return self * 180 / .pi } | |
} | |
extension UIImage { | |
func circularProgress(_ progress: Double, | |
grayScaleAlpha: Double = 1.0, | |
startAngle: Double = -90.0, | |
clockwise: Bool = true) -> UIImage? { | |
guard let grayScaled = noir else { | |
return nil | |
} | |
guard let mask = makeCircularMask(progress: progress, | |
startAngle: startAngle, | |
clockwise: clockwise) else { | |
return nil | |
} | |
guard let masked = apply(mask: mask) else { | |
return nil | |
} | |
guard let combined = grayScaled.overlay(image: masked, backgroundAlpha: grayScaleAlpha) else { | |
return nil | |
} | |
return combined | |
} | |
internal func makeCircularMask(progress: Double, | |
startAngle: Double = -90.0, | |
clockwise: Bool = true) -> UIImage? { | |
let center = CGPoint(x: size.width / 2.0, y: size.height / 2.0) | |
// Want to "over fill" the image area, so the mask can be applied | |
// to the entire image | |
let radius = max(size.width, size.height) | |
let path = UIBezierPath(arcCenter: center, | |
radius: radius, | |
startAngle: CGFloat(startAngle.degreesToRadians), | |
endAngle: CGFloat((startAngle + (360.0 * progress)).degreesToRadians), | |
clockwise: clockwise) | |
path.lineWidth = 1.0 | |
path.lineCapStyle = .round | |
path.lineJoinStyle = .round | |
path.addLine(to: center) | |
path.close() | |
UIGraphicsBeginImageContext(CGSize(width: size.width, height: size.height)) | |
let context = UIGraphicsGetCurrentContext() | |
UIColor.clear.setFill() | |
context?.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height)) | |
// Color doesn't matter, just need the difference | |
// in the alpha levels | |
UIColor.red.setFill() | |
path.fill(with: .normal, alpha: 1.0) | |
let image = UIGraphicsGetImageFromCurrentImageContext() | |
UIGraphicsEndImageContext() | |
return image | |
} | |
internal func apply(mask: UIImage) -> UIImage? { | |
UIGraphicsBeginImageContext(CGSize(width: size.width, height: size.height)) | |
let context = UIGraphicsGetCurrentContext() | |
UIColor.clear.setFill() | |
context?.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height)) | |
mask.draw(at: CGPoint(x: 0, y: 0), blendMode: .normal, alpha: 1.0) | |
draw(at: CGPoint(x: 0, y: 0), blendMode: .sourceIn, alpha: 1.0) | |
let result = UIGraphicsGetImageFromCurrentImageContext() | |
UIGraphicsEndImageContext() | |
return result | |
} | |
func overlay(image: UIImage, backgroundAlpha: Double = 1.0, foregroundAlpha: Double = 1.0) -> UIImage? { | |
UIGraphicsBeginImageContext(CGSize(width: size.width, height: size.height)) | |
let context = UIGraphicsGetCurrentContext() | |
UIColor.clear.setFill() | |
context?.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height)) | |
draw(at: CGPoint(x: 0, y: 0), blendMode: .normal, alpha: CGFloat(max(0.0, min(1.0, backgroundAlpha)))) | |
image.draw(at: CGPoint(x: 0, y: 0), blendMode: .normal, alpha: CGFloat(max(0.0, min(1.0, foregroundAlpha)))) | |
let result = UIGraphicsGetImageFromCurrentImageContext() | |
UIGraphicsEndImageContext() | |
return result | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// A gray scale implementation | |
extension UIImage { | |
var noir: UIImage? { | |
let context = CIContext(options: nil) | |
guard let currentFilter = CIFilter(name: "CIPhotoEffectNoir") else { return nil } | |
currentFilter.setValue(CIImage(image: self), forKey: kCIInputImageKey) | |
if let output = currentFilter.outputImage, | |
let cgImage = context.createCGImage(output, from: output.extent) { | |
return UIImage(cgImage: cgImage, scale: scale, orientation: imageOrientation) | |
} | |
return nil | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The basic intent is to produce something like...
(which displays 25% complete)