如何从UIImage中提取非Alpha区域的Bezier路径?含示例场景
Hey there! Let's break down how to extract Bézier paths from the non-alpha parts of a UIImage—including that cat outline you mentioned. I'll cover two practical approaches, plus a bonus tip for using the resulting path as a mask.
Approach 1: Manual Alpha Channel Analysis (Core Graphics)
This method works well for simple, high-contrast images where you want full control over edge detection. We'll directly inspect the image's pixel data to find edges of non-transparent regions.
import UIKit func bezierPathFromNonAlphaRegion(of image: UIImage, alphaThreshold: CGFloat = 0.5) -> UIBezierPath? { guard let cgImage = image.cgImage, let dataProvider = cgImage.dataProvider, let pixelData = dataProvider.data, let bytes = CFDataGetBytePtr(pixelData) else { return nil } let width = cgImage.width let height = cgImage.height let bytesPerPixel = cgImage.bitsPerPixel / 8 let bytesPerRow = cgImage.bytesPerRow var edgePoints = [CGPoint]() // Iterate through every pixel to find edge points for y in 0..<height { for x in 0..<width { let pixelIndex = y * bytesPerRow + x * bytesPerPixel let alpha = CGFloat(bytes[pixelIndex + 3]) / 255.0 if alpha > alphaThreshold { // Check if this pixel is on the edge (surrounded by transparent pixels) let isEdge = checkIfPixelIsEdge(x: x, y: y, width: width, height: height, bytes: bytes, bytesPerRow: bytesPerRow, bytesPerPixel: bytesPerPixel, alphaThreshold: alphaThreshold) if isEdge { // Convert CGImage's bottom-left origin to UIKit's top-left let point = CGPoint(x: CGFloat(x), y: CGFloat(height - y)) edgePoints.append(point) } } } } guard !edgePoints.isEmpty else { return nil } let path = UIBezierPath() path.move(to: edgePoints[0]) edgePoints.dropFirst().forEach { path.addLine(to: $0) } path.close() return path } // Helper: Check if a pixel is an edge (has transparent neighbors) private func checkIfPixelIsEdge(x: Int, y: Int, width: Int, height: Int, bytes: UnsafePointer<UInt8>, bytesPerRow: Int, bytesPerPixel: Int, alphaThreshold: CGFloat) -> Bool { let neighborOffsets = [(-1, 0), (1, 0), (0, -1), (0, 1)] for (dx, dy) in neighborOffsets { let newX = x + dx let newY = y + dy if newX < 0 || newX >= width || newY < 0 || newY >= height { // Pixels on the image boundary count as edges return true } let pixelIndex = newY * bytesPerRow + newX * bytesPerPixel let neighborAlpha = CGFloat(bytes[pixelIndex + 3]) / 255.0 if neighborAlpha <= alphaThreshold { return true } } return false }
Pros: Full control over edge detection logic.
Cons: Can generate noisy paths for complex shapes (like your cat outline) and is less efficient for large images.
Approach 2: Vision Framework (Recommended for Complex Shapes)
Apple's Vision framework is built for image analysis and excels at detecting clean, accurate contours—perfect for your cat outline use case. It automatically filters noise and handles complex edges.
import UIKit import Vision func bezierPathFromNonAlphaRegionUsingVision(of image: UIImage) -> UIBezierPath? { guard let ciImage = CIImage(image: image) else { return nil } // Create a contour detection request let contourRequest = VNContourDetectionRequest { request, error in if let error = error { print("Contour detection failed: \(error.localizedDescription)") } } contourRequest.revision = VNContourDetectionRequestRevision1 contourRequest.preferBackgroundProcessing = false // Process the image let handler = VNImageRequestHandler(ciImage: ciImage, options: [:]) do { try handler.perform([contourRequest]) // Get the main contour (largest non-transparent region) guard let observations = contourRequest.results as? [VNContourObservation], let mainContour = observations.first?.normalizedContours.first else { return nil } // Convert normalized contour points to image-sized coordinates let imageSize = image.size let coordinateTransform = CGAffineTransform(scaleX: imageSize.width, y: imageSize.height) .concatenating(CGAffineTransform(translationX: 0, y: imageSize.height)) .scaledBy(x: 1, y: -1) // Flip y-axis to match UIKit's top-left origin // Build the Bézier path let path = UIBezierPath() let contourPoints = mainContour.points guard !contourPoints.isEmpty else { return nil } path.move(to: contourPoints[0].applying(coordinateTransform)) contourPoints.dropFirst().forEach { path.addLine(to: $0.applying(coordinateTransform)) } path.close() // Smooth the path for cleaner edges path.flatness = 0.5 return path } catch { print("Failed to run Vision request: \(error.localizedDescription)") return nil } }
Pros: Clean, accurate contours with minimal code; handles complex shapes like your cat outline effortlessly.
Cons: Requires iOS 11+ (which is standard for modern apps).
Bonus: Use the Bézier Path as a Mask
Since you already know how to mask images with another image, here's how to use your extracted Bézier path as a mask instead:
func maskImageWithBezierPath(image: UIImage, maskPath: UIBezierPath) -> UIImage? { UIGraphicsBeginImageContextWithOptions(image.size, false, image.scale) defer { UIGraphicsEndImageContext() } // Clip to the path and draw the image maskPath.addClip() image.draw(at: .zero) return UIGraphicsGetImageFromCurrentImageContext() }
内容的提问来源于stack exchange,提问作者Rob

