Created
July 3, 2024 06:57
-
-
Save adam-zethraeus/5d428d5bbebf11d8d723d3ed18a6810f to your computer and use it in GitHub Desktop.
SFEdgeShape
This file contains 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
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