Skip to content

Instantly share code, notes, and snippets.

@adam-zethraeus
Created July 3, 2024 06:57
Show Gist options
  • Save adam-zethraeus/5d428d5bbebf11d8d723d3ed18a6810f to your computer and use it in GitHub Desktop.
Save adam-zethraeus/5d428d5bbebf11d8d723d3ed18a6810f to your computer and use it in GitHub Desktop.
SFEdgeShape
import SwiftUI
import Vision
@available(iOS 17, *)
public struct SFEdgeShape: InsettableShape {
public init(systemName: String) {
self.systemName = systemName
}
var systemName: String
var insetAmount: CGFloat = 0
nonisolated var trimmedImage: UIImage? {
let cfg = UIImage.SymbolConfiguration(pointSize: 256.0)
let img = UIImage(systemName: systemName, withConfiguration: cfg)?.withTintColor(.black, renderingMode: .alwaysOriginal)
guard let imgA = img else {
return nil
}
guard let cgRef = imgA.cgImage else {
return nil
}
let imgB = UIImage(cgImage: cgRef, scale: imgA.scale, orientation: imgA.imageOrientation)
.withTintColor(.black, renderingMode: .alwaysOriginal)
let resultImage = UIGraphicsImageRenderer(size: imgB.size).image { ctx in
UIColor.white.setFill()
ctx.fill(CGRect(origin: .zero, size: imgB.size))
imgB.draw(at: .zero)
}
return resultImage
}
public func path(in rect: CGRect) -> Path {
guard let inputImage = self.trimmedImage
else {
return Path()
}
guard let cgPath = detectVisionContours(from: inputImage) else { return Path() }
let scW: CGFloat = (rect.width - CGFloat(insetAmount)) / cgPath.boundingBox.width
let scH: CGFloat = (rect.height - CGFloat(insetAmount)) / cgPath.boundingBox.height
// we need to invert the Y-coordinate space
var transform = CGAffineTransform.identity
.scaledBy(x: scW, y: -scH)
.translatedBy(x: 0.0, y: -cgPath.boundingBox.height)
if let imagePath = cgPath.copy(using: &transform) {
return Path(imagePath)
} else {
return Path()
}
}
nonisolated public func inset(by amount: CGFloat) -> some InsettableShape {
var shape = self
shape.insetAmount += amount
return shape
}
nonisolated func detectVisionContours(from sourceImage: UIImage) -> CGPath? {
let inputImage = CIImage.init(cgImage: sourceImage.cgImage!)
let contourRequest = VNDetectContoursRequest()
contourRequest.revision = VNDetectContourRequestRevision1
contourRequest.contrastAdjustment = 1.0
contourRequest.maximumImageDimension = 512
let requestHandler = VNImageRequestHandler(ciImage: inputImage, options: [:])
try? requestHandler.perform([contourRequest])
return contourRequest.results?.first?.normalizedPath
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment