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

NSMallocException崩溃:大图片模糊处理代码优化咨询

修复大图片模糊时的NSMallocException崩溃

崩溃原因分析

我之前也碰到过一模一样的问题!你遇到的Failed to grow buffer崩溃,本质是inProvider?.data背后调用的CGDataProviderCopyData方法,会尝试把整张图片的像素数据一次性复制到内存的连续缓冲区。当图片尺寸过大(比如几K分辨率的ARGB8888格式图片),系统根本拿不出这么大的连续内存块,直接就触发了内存分配异常。

解决方案:分块处理图片

我们完全不需要一次性加载整张图片到内存,而是把图片分割成多个小「块(tile)」,逐个处理每个块的模糊效果,最后把所有模糊后的块拼接成完整图片。这样每个块占用的内存极小,再也不会触发内存分配失败。

需要注意两个关键点:

  • 分块时要保留一定的重叠区域,避免块与块之间出现生硬的拼接痕迹(毕竟box模糊需要用到周围的像素)
  • 处理完每个块后及时释放临时内存,防止内存累积

修改后的代码

import CoreGraphics
import Accelerate

extension CGImage {
    func blurEffect(_ boxSize: CGFloat) -> CGImage? {
        let boxSize = boxSize - (boxSize.truncatingRemainder(dividingBy: 2)) + 1
        let kernelRadius = Int((boxSize - 1) / 2)
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        let bitmapInfo = self.bitmapInfo
        
        // 输出图片的基础参数
        let outputWidth = self.width
        let outputHeight = self.height
        let bitsPerComponent = 8
        let bytesPerPixel = 4 // ARGB8888格式
        let bytesPerRow = outputWidth * bytesPerPixel
        
        // 创建最终输出的上下文
        guard let outputContext = CGContext(
            data: nil,
            width: outputWidth,
            height: outputHeight,
            bitsPerComponent: bitsPerComponent,
            bytesPerRow: bytesPerRow,
            space: colorSpace,
            bitmapInfo: bitmapInfo.rawValue
        ) else { return nil }
        
        // 分块大小(可根据设备内存调整,比如在低内存设备用512x512)
        let tileSize = 1024
        // 每个块需要额外的边缘区域,用于模糊时的边界计算
        let tilePadding = kernelRadius
        
        // 遍历所有分块
        for y in stride(from: 0, to: outputHeight, by: tileSize) {
            for x in stride(from: 0, to: outputWidth, by: tileSize) {
                // 计算当前块的实际范围(包含padding)
                let tileX = max(0, x - tilePadding)
                let tileY = max(0, y - tilePadding)
                let tileWidth = min(outputWidth - tileX, tileSize + tilePadding * 2)
                let tileHeight = min(outputHeight - tileY, tileSize + tilePadding * 2)
                
                // 创建临时上下文,绘制当前块的原始像素
                guard let tileContext = CGContext(
                    data: nil,
                    width: tileWidth,
                    height: tileHeight,
                    bitsPerComponent: bitsPerComponent,
                    bytesPerRow: tileWidth * bytesPerPixel,
                    space: colorSpace,
                    bitmapInfo: bitmapInfo.rawValue
                ) else { continue }
                
                // 把原始图片的对应区域绘制到临时上下文
                let drawRect = CGRect(x: -tileX, y: -tileY, width: outputWidth, height: outputHeight)
                tileContext.draw(self, in: drawRect)
                
                // 获取临时上下文的像素数据,准备模糊处理
                guard let tileData = tileContext.data else { continue }
                var inBuffer = vImage_Buffer(
                    data: tileData,
                    height: vImagePixelCount(tileHeight),
                    width: vImagePixelCount(tileWidth),
                    rowBytes: tileContext.bytesPerRow
                )
                
                // 分配模糊后的临时缓冲区
                guard let outData = malloc(tileHeight * tileContext.bytesPerRow) else { continue }
                defer { free(outData) } // 处理完立即释放内存
                
                var outBuffer = vImage_Buffer(
                    data: outData,
                    height: vImagePixelCount(tileHeight),
                    width: vImagePixelCount(tileWidth),
                    rowBytes: tileContext.bytesPerRow
                )
                
                // 执行box模糊
                vImageBoxConvolve_ARGB8888(
                    &inBuffer,
                    &outBuffer,
                    nil,
                    0,
                    0,
                    UInt32(boxSize),
                    UInt32(boxSize),
                    nil,
                    vImage_Flags(kvImageEdgeExtend)
                )
                
                // 创建模糊后的块图片
                guard let blurredTileCGImage = CGContext(
                    data: outBuffer.data,
                    width: tileWidth,
                    height: tileHeight,
                    bitsPerComponent: bitsPerComponent,
                    bytesPerRow: tileContext.bytesPerRow,
                    space: colorSpace,
                    bitmapInfo: bitmapInfo.rawValue
                )?.makeImage() else { continue }
                
                // 计算需要绘制到最终输出的区域(去掉padding部分)
                let destX = x
                let destY = y
                let destWidth = min(tileSize, outputWidth - x)
                let destHeight = min(tileSize, outputHeight - y)
                let sourceRect = CGRect(
                    x: tilePadding,
                    y: tilePadding,
                    width: destWidth,
                    height: destHeight
                )
                
                // 将模糊后的块绘制到最终输出上下文
                outputContext.draw(blurredTileCGImage, in: CGRect(x: destX, y: destY, width: destWidth, height: destHeight), source: sourceRect)
            }
        }
        
        // 返回最终的模糊图片
        return outputContext.makeImage()
    }
}

代码说明

  1. 分块策略:默认用1024x1024的块大小,你可以根据设备内存情况调整(比如低内存设备改成512x512),每个块额外保留kernelRadius大小的边缘,确保模糊处理时边缘像素计算正确。
  2. 内存管理:用defer { free(outData) }保证每个块处理完后立即释放临时缓冲区,不会造成内存泄漏。
  3. 边缘过渡:通过kvImageEdgeExtend标记处理边缘像素,同时分块的重叠区域保证了块与块之间的过渡自然,不会出现拼接痕迹。

额外优化建议

  • 如果不需要原尺寸的模糊效果,可以先把图片缩小到合适尺寸再模糊,能大幅减少内存占用和处理时间。
  • 可以根据设备的内存状态动态调整分块大小,比如通过ProcessInfo.processInfo.physicalMemory判断后设置不同的块尺寸。

内容的提问来源于stack exchange,提问作者Manuel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:51:38