You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优化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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 21:45:33