图片换色

给图片的主色更换另外一种色调

920 发布: 2025/11/2 19:16 本文总阅读量
给现有的图片主色渲染新的色调
// MARK: - 图片扩展
extension UIImage {
    // 获取主色 (直方图方法)
    func dominantColor() -> UIColor? {
        guard let cgImage = self.cgImage else { return nil }
        let width = 40, height = 40
        let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: 4 * width, space: colorSpace, bitmapInfo: bitmapInfo)
        else { return nil }
        context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))
        guard let data = context.data else { return nil }
        let ptr = data.bindMemory(to: UInt8.self, capacity: width * height * 4)

        var colorCounts = [UInt32: Int]()
        for x in 0..<width {
            for y in 0..<height {
                let offset = 4 * (y * width + x)
                let r = ptr[offset]
                let g = ptr[offset + 1]
                let b = ptr[offset + 2]
                let a = ptr[offset + 3]
                if a < 127 { continue }
                let rgb = (UInt32(r) << 16) | (UInt32(g) << 8) | UInt32(b)
                colorCounts[rgb, default: 0] += 1
            }
        }
        let mostCommon = colorCounts.max { $0.value < $1.value }?.key
        if let rgb = mostCommon {
            return UIColor(
                red: CGFloat((rgb >> 16) & 0xFF) / 255.0,
                green: CGFloat((rgb >> 8) & 0xFF) / 255.0,
                blue: CGFloat(rgb & 0xFF) / 255.0,
                alpha: 1.0
            )
        }
        return nil
    }

    // 替换主色为指定颜色(简单直接替换主色的像素)
    func replaceDominantColor(with color: UIColor, threshold: CGFloat = 60.0) -> UIImage? {
        guard let cgImage = self.cgImage else { return nil }
        guard let domColor = self.dominantColor() else { return nil }
        let width = cgImage.width
        let height = cgImage.height
        let bytesPerRow = 4 * width
        let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue
        let colorSpace = CGColorSpaceCreateDeviceRGB()

        guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo)
        else { return nil }

        context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))
        guard let data = context.data else { return nil }
        let ptr = data.bindMemory(to: UInt8.self, capacity: width * height * 4)

        // 获取主色和替换色的rgb
        func rgbArray(from color: UIColor) -> [CGFloat] {
            var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
            color.getRed(&r, green: &g, blue: &b, alpha: &a)
            return [r * 255, g * 255, b * 255]
        }
        let srcRGB = rgbArray(from: domColor)
        let dstRGB = rgbArray(from: color)

        for i in 0..<(width * height) {
            let offset = i * 4
            let r = CGFloat(ptr[offset])
            let g = CGFloat(ptr[offset + 1])
            let b = CGFloat(ptr[offset + 2])
            let a = ptr[offset + 3]
            if a < 127 { continue }
            // 欧氏距离
            let dist = sqrt(pow(r - srcRGB[0], 2) + pow(g - srcRGB[1], 2) + pow(b - srcRGB[2], 2))
            if dist < threshold { // 可调阈值
                ptr[offset] = UInt8(dstRGB[0])
                ptr[offset + 1] = UInt8(dstRGB[1])
                ptr[offset + 2] = UInt8(dstRGB[2])
            }
        }
        if let newCGImage = context.makeImage() {
            return UIImage(cgImage: newCGImage, scale: self.scale, orientation: self.imageOrientation)
        }
        return nil
    }
}