Swift开发中如何替换CGImage指定坐标点的像素颜色
单像素颜色修改方法实现方向
你现有的像素读取代码已经搭好了核心的位图上下文逻辑,写像素修改方法可以完全复用这套上下文配置,只需要把读内存的逻辑替换为写入即可,注意CGImage 是不可变对象,无法直接修改原实例的像素数据,所有修改最终都要生成新的 CGImage 返回。
具体可以按以下步骤实现:
- 复用现有代码的上下文初始化参数:保持和读像素方法完全一致的RGB颜色空间、4字节/像素、8位/通道、
premultipliedLast+ 32位大端的位图配置,将原CGImage绘制到新创建的上下文中,拿到指向像素缓冲区的UInt8类型指针。 - 增加坐标合法性校验:判断传入的
CGPoint坐标x、y值是否落在0 ~ width、0 ~ height的范围内,越界时直接返回原图片,避免内存越界访问导致崩溃。 - 计算目标像素的内存偏移:和读像素的计算逻辑完全一致,偏移量公式为
i = bytesPerRow * Int(point.y) + bytesPerPixel * Int(point.x),索引i对应像素的R通道,i+1为G通道,i+2为B通道,i+3为A通道。 - 按预乘规则写入颜色值:因为你用的是预乘Alpha的位图格式,写入前需要把目标
UIColor拆分为r、g、b、a四个0~1区间的分量,将RGB分量分别乘以Alpha值做预乘,再乘以255转为UInt8类型写入对应内存位置,和你读像素时的反预乘逻辑对应,避免出现颜色偏暗、透明度异常的问题。 - 从修改完成的上下文中调用
makeImage()方法,生成新的CGImage实例返回即可。
参考实现代码
extension CGImage { func setColor(at point: CGPoint, to color: UIColor) -> CGImage? { let colorSpace = CGColorSpaceCreateDeviceRGB() let bytesPerPixel = 4 let bytesPerRow = bytesPerPixel * width let bitsPerComponent = 8 let bitmapInfo: UInt32 = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo), let ptr = context.data?.assumingMemoryBound(to: UInt8.self) else { return self } // 将原图像绘制到上下文缓冲区 context.draw(self, in: CGRect(x: 0, y: 0, width: width, height: height)) // 坐标越界直接返回原图 guard Int(point.x) >= 0, Int(point.x) < width, Int(point.y) >= 0, Int(point.y) < height else { return self } // 计算目标像素的内存起始位置 let pixelOffset = bytesPerRow * Int(point.y) + bytesPerPixel * Int(point.x) // 拆分目标颜色的RGBA分量 var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0 color.getRed(&r, green: &g, blue: &b, alpha: &a) // 预乘后写入对应字节,clamping避免数值越界 ptr[pixelOffset] = UInt8(clamping: Int(r * a * 255)) ptr[pixelOffset + 1] = UInt8(clamping: Int(g * a * 255)) ptr[pixelOffset + 2] = UInt8(clamping: Int(b * a * 255)) ptr[pixelOffset + 3] = UInt8(clamping: Int(a * 255)) // 从缓冲区生成新的CGImage return context.makeImage() } }
额外注意事项
- 如果需要批量修改多个像素,不要每修改一个像素就重复创建上下文、重绘原图,把上下文初始化、原图绘制、新图生成的逻辑提到循环外,批量修改完所有目标像素的内存值后再一次性生成新图,性能会有数量级的提升。
- 注意坐标系匹配:CoreGraphics上下文默认使用y轴向下的坐标系,和你当前读像素的逻辑一致;如果传入的是UIKit视图体系的坐标(y轴向上),需要先做坐标翻转:
point.y = CGFloat(height) - point.y - 1,否则修改的像素位置会上下颠倒。 - 写入
UInt8时使用clamping初始化方式,可以自动把浮点计算产生的超出0~255范围的值截断到合法区间,避免异常崩溃。
内容的提问来源于stack exchange,提问作者Chepelev Evgeniy
相关产品推荐
相关产品推荐

