Skip to content

Instantly share code, notes, and snippets.

@RustyKnight
Last active August 2, 2018 09:03
Show Gist options
  • Save RustyKnight/5348f815b231ce390659f85982df9862 to your computer and use it in GitHub Desktop.
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
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
}
}
// 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
}
}
@RustyKnight
Copy link
Author

The basic intent is to produce something like...

Example output

(which displays 25% complete)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment