如何优化UIImage中连续同色点移除函数的执行速度?
优化UIImage连续同色点移除函数的性能方案
原代码的核心性能瓶颈集中在已访问点判断效率低、BFS队列操作开销大、逐像素修改图像重复创建对象这几个方面,以下是针对性的优化措施:
1. 用二维布尔数组替代数组contains判断
原代码用targetPoints.contains判断点是否已处理,每次操作是O(n)复杂度,随着目标点数量增加耗时会指数级上升。改用和图像尺寸一致的二维布尔数组标记已访问点,判断操作直接变成O(1):
let width = cgImage.width let height = cgImage.height // 初始化已访问标记数组,默认所有点未处理 var visited = Array(repeating: Array(repeating: false, count: height), count: width) // 标记初始选中点 let startX = Int(point.x) let startY = Int(point.y) visited[startX][startY] = true
2. 使用高效队列实现BFS
原代码用Array的removeFirst()实现队列头部弹出,每次操作是O(n)复杂度。建议导入Collections框架使用Deque,它的头部弹出操作是O(1),大幅提升BFS效率:
import Collections // 初始化BFS队列 var queue: Deque<CGPoint> = [point]
如果不想额外导入框架,也可以用Array模拟队列,但要注意避免频繁调用removeFirst(),或者改用栈实现DFS(效果和BFS一致,只是遍历顺序不同)。
3. 批量修改像素,避免重复创建UIImage
原代码逐像素调用processByPixel生成新UIImage,每次都会创建图像对象,内存和CPU开销极大。改为直接操作像素缓冲区,一次性修改所有目标点后再生成最终图像:
// 创建可修改的图形上下文,复制原图像数据 guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: cgImage.bytesPerRow, space: cgImage.colorSpace!, bitmapInfo: cgImage.bitmapInfo.rawValue) else { completion((nil, targetPoints)) return } context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height)) guard let pixelData = context.data else { completion((nil, targetPoints)) return } // 批量修改所有目标点的像素(示例为设为透明,可按需修改) let bytesPerPixel = cgImage.bitsPerComponent / 8 * 4 // 按RGBA格式计算 for point in targetPoints { let x = Int(point.x) let y = Int(point.y) let offset = y * cgImage.bytesPerRow + x * bytesPerPixel pixelData.storeBytes(of: 0, toByteOffset: offset, as: UInt8.self) // R通道 pixelData.storeBytes(of: 0, toByteOffset: offset+1, as: UInt8.self) // G通道 pixelData.storeBytes(of: 0, toByteOffset: offset+2, as: UInt8.self) // B通道 pixelData.storeBytes(of: 0, toByteOffset: offset+3, as: UInt8.self) // A通道 } // 生成最终处理后的图像 guard let processedCGImage = context.makeImage() else { completion((nil, targetPoints)) return } let processedImage = UIImage(cgImage: processedCGImage, scale: image.scale, orientation: image.imageOrientation)
4. 优化颜色获取与边界判断
- 提前获取目标颜色的RGBA值,避免重复调用颜色获取方法
- 简化边界判断,去掉冗余条件(比如
pointX-1 >=0后无需再判断<= width) - 修复原代码中的逻辑错误(比如最后一个方向的边界判断误用
width、颜色取错点的问题)
新增CGImage的RGBA扩展方法,直接从像素数据读取颜色,避免创建多余的颜色对象:
extension CGImage { func rgba(at point: CGPoint) -> (UInt8, UInt8, UInt8, UInt8) { let width = self.width let height = self.height let x = Int(point.x) let y = Int(point.y) guard x >= 0, x < width, y >= 0, y < height else { return (0,0,0,0) } let bytesPerPixel = self.bitsPerComponent / 8 * 4 let bytesPerRow = self.bytesPerRow let offset = y * bytesPerRow + x * bytesPerPixel guard let data = self.dataProvider?.data, let bytes = CFDataGetBytePtr(data) else { return (0,0,0,0) } return (bytes[offset], bytes[offset+1], bytes[offset+2], bytes[offset+3]) } }
优化后的BFS方向判断逻辑:
// 左邻点判断 if x - 1 >= 0, !visited[x-1][y] { let leftPoint = CGPoint(x: x-1, y: y) if cgImage.rgba(at: leftPoint) == targetRGBA { visited[x-1][y] = true queue.append(leftPoint) targetPoints.append(leftPoint) } } // 右邻点判断 if x + 1 < width, !visited[x+1][y] { let rightPoint = CGPoint(x: x+1, y: y) if cgImage.rgba(at: rightPoint) == targetRGBA { visited[x+1][y] = true queue.append(rightPoint) targetPoints.append(rightPoint) } } // 上邻点判断 if y - 1 >= 0, !visited[x][y-1] { let topPoint = CGPoint(x: x, y: y-1) if cgImage.rgba(at: topPoint) == targetRGBA { visited[x][y-1] = true queue.append(topPoint) targetPoints.append(topPoint) } } // 下邻点判断 if y + 1 < height, !visited[x][y+1] { let bottomPoint = CGPoint(x: x, y: y+1) if cgImage.rgba(at: bottomPoint) == targetRGBA { visited[x][y+1] = true queue.append(bottomPoint) targetPoints.append(bottomPoint) } }
整合后的完整优化代码
import Collections extension CGImage { func rgba(at point: CGPoint) -> (UInt8, UInt8, UInt8, UInt8) { let width = self.width let height = self.height let x = Int(point.x) let y = Int(point.y) guard x >= 0, x < width, y >= 0, y < height else { return (0,0,0,0) } let bytesPerPixel = self.bitsPerComponent / 8 * 4 let bytesPerRow = self.bytesPerRow let offset = y * bytesPerRow + x * bytesPerPixel guard let data = self.dataProvider?.data, let bytes = CFDataGetBytePtr(data) else { return (0,0,0,0) } return (bytes[offset], bytes[offset+1], bytes[offset+2], bytes[offset+3]) } } func processImage(point: CGPoint, completion: @escaping ((UIImage?, [CGPoint])) -> Void) { guard let image = self.imageView?.image, let cgImage = image.cgImage else { completion((nil, [])) return } DispatchQueue.global(qos: .background).async { [weak self] in guard let self = self else { completion((nil, [])) return } let width = cgImage.width let height = cgImage.height let startX = Int(point.x) let startY = Int(point.y) // 边界校验 guard startX >= 0, startX < width, startY >= 0, startY < height else { completion((nil, [])) return } // 初始化已访问标记 var visited = Array(repeating: Array(repeating: false, count: height), count: width) visited[startX][startY] = true // 目标颜色RGBA值 let targetRGBA = cgImage.rgba(at: point) // BFS队列与目标点集合 var queue: Deque<CGPoint> = [point] var targetPoints: [CGPoint] = [point] // 执行BFS遍历连续同色点 while !queue.isEmpty { let currentPoint = queue.removeFirst() let x = Int(currentPoint.x) let y = Int(currentPoint.y) // 左邻点 if x - 1 >= 0, !visited[x-1][y] { let leftPoint = CGPoint(x: x-1, y: y) if cgImage.rgba(at: leftPoint) == targetRGBA { visited[x-1][y] = true queue.append(leftPoint) targetPoints.append(leftPoint) } } // 右邻点 if x + 1 < width, !visited[x+1][y] { let rightPoint = CGPoint(x: x+1, y: y) if cgImage.rgba(at: rightPoint) == targetRGBA { visited[x+1][y] = true queue.append(rightPoint) targetPoints.append(rightPoint) } } // 上邻点 if y - 1 >= 0, !visited[x][y-1] { let topPoint = CGPoint(x: x, y: y-1) if cgImage.rgba(at: topPoint) == targetRGBA { visited[x][y-1] = true queue.append(topPoint) targetPoints.append(topPoint) } } // 下邻点 if y + 1 < height, !visited[x][y+1] { let bottomPoint = CGPoint(x: x, y: y+1) if cgImage.rgba(at: bottomPoint) == targetRGBA { visited[x][y+1] = true queue.append(bottomPoint) targetPoints.append(bottomPoint) } } } // 批量修改像素生成最终图像 guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: cgImage.bytesPerRow, space: cgImage.colorSpace!, bitmapInfo: cgImage.bitmapInfo.rawValue) else { completion((nil, targetPoints)) return } context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height)) guard let pixelData = context.data else { completion((nil, targetPoints)) return } let bytesPerPixel = cgImage.bitsPerComponent / 8 * 4 for point in targetPoints { let x = Int(point.x) let y = Int(point.y) let offset = y * cgImage.bytesPerRow + x * bytesPerPixel // 设为透明,可根据需求修改为其他颜色 pixelData.storeBytes(of: 0, toByteOffset: offset, as: UInt8.self) pixelData.storeBytes(of: 0, toByteOffset: offset+1, as: UInt8.self) pixelData.storeBytes(of: 0, toByteOffset: offset+2, as: UInt8.self) pixelData.storeBytes(of: 0, toByteOffset: offset+3, as: UInt8.self) } guard let processedCGImage = context.makeImage() else { completion((nil, targetPoints)) return } let processedImage = UIImage(cgImage: processedCGImage, scale: image.scale, orientation: image.imageOrientation) DispatchQueue.main.async { completion((processedImage, targetPoints)) } } }
这些优化将原函数的时间复杂度从O(n²)降到O(n)(n为目标点数量),同时大幅减少内存开销,性能提升非常明显。
内容的提问来源于stack exchange,提问作者Roman Gorbatko
相关产品推荐
相关产品推荐

