【问题标题】:How to get the color of a pixel in a UIImage in Swift?如何在 Swift 中获取 UIImage 中像素的颜色?
【发布时间】:2020-05-31 12:20:37
【问题描述】:

如何在 Swift 中获取UIImage 中像素的颜色?

关于 SO 的现有答案要么已过时,要么对我不起作用。

【问题讨论】:

    标签: ios swift image-processing graphics uiimage


    【解决方案1】:

    斯威夫特 5

    要提出可靠的解决方案,我们需要考虑以下几点:

    1. 图像的像素大小可能与image.size.width/image.size.height 返回的点大小不同。
    2. 图像中的像素分量可以有多种布局,例如 BGRA、ABGR、ARGB 等,或者像素可能根本没有 alpha 分量,例如 BGR 和 RGB。 UIView.drawHierarchy(in:afterScreenUpdates:) 方法可以生成 BGRA 图像。
    3. 颜色分量可以预乘图像中所有像素的 alpha,需要除以 alpha 才能恢复原始颜色。同样,UIView.drawHierarchy(in:afterScreenUpdates:) 方法可以生成颜色分量预乘 alpha 的 BGRA 图像。
    4. 对于 CGImage 使用的内存优化,像素行的大小(以字节为单位)可能大于像素宽度乘以 4 的大小。

    下面的代码提供了一个全面的解决方案,用于在 Swift 5 中为所有此类特殊情况获取像素的UIColor。代码针对可用性和清晰度进行了优化,不是针对性能进行了优化。

    public extension UIImage {
    
        var pixelWidth: Int {
            return cgImage?.width ?? 0
        }
    
        var pixelHeight: Int {
            return cgImage?.height ?? 0
        }
    
        func pixelColor(x: Int, y: Int) -> UIColor {
            assert(
                0..<pixelWidth ~= x && 0..<pixelHeight ~= y,
                "Pixel coordinates are out of bounds")
    
            guard
                let cgImage = cgImage,
                let data = cgImage.dataProvider?.data,
                let dataPtr = CFDataGetBytePtr(data),
                let colorSpaceModel = cgImage.colorSpace?.model,
                let componentLayout = cgImage.bitmapInfo.componentLayout
            else {
                assertionFailure("Could not get the color of a pixel in an image")
                return .clear
            }
    
            assert(
                colorSpaceModel == .rgb,
                "The only supported color space model is RGB")
            assert(
                cgImage.bitsPerPixel == 32 || cgImage.bitsPerPixel == 24,
                "A pixel is expected to be either 4 or 3 bytes in size")
    
            let bytesPerRow = cgImage.bytesPerRow
            let bytesPerPixel = cgImage.bitsPerPixel/8
            let pixelOffset = y*bytesPerRow + x*bytesPerPixel
    
            if componentLayout.count == 4 {
                let components = (
                    dataPtr[pixelOffset + 0],
                    dataPtr[pixelOffset + 1],
                    dataPtr[pixelOffset + 2],
                    dataPtr[pixelOffset + 3]
                )
    
                var alpha: UInt8 = 0
                var red: UInt8 = 0
                var green: UInt8 = 0
                var blue: UInt8 = 0
    
                switch componentLayout {
                case .bgra:
                    alpha = components.3
                    red = components.2
                    green = components.1
                    blue = components.0
                case .abgr:
                    alpha = components.0
                    red = components.3
                    green = components.2
                    blue = components.1
                case .argb:
                    alpha = components.0
                    red = components.1
                    green = components.2
                    blue = components.3
                case .rgba:
                    alpha = components.3
                    red = components.0
                    green = components.1
                    blue = components.2
                default:
                    return .clear
                }
    
                // If chroma components are premultiplied by alpha and the alpha is `0`,
                // keep the chroma components to their current values.
                if cgImage.bitmapInfo.chromaIsPremultipliedByAlpha && alpha != 0 {
                    let invUnitAlpha = 255/CGFloat(alpha)
                    red = UInt8((CGFloat(red)*invUnitAlpha).rounded())
                    green = UInt8((CGFloat(green)*invUnitAlpha).rounded())
                    blue = UInt8((CGFloat(blue)*invUnitAlpha).rounded())
                }
    
                return .init(red: red, green: green, blue: blue, alpha: alpha)
    
            } else if componentLayout.count == 3 {
                let components = (
                    dataPtr[pixelOffset + 0],
                    dataPtr[pixelOffset + 1],
                    dataPtr[pixelOffset + 2]
                )
    
                var red: UInt8 = 0
                var green: UInt8 = 0
                var blue: UInt8 = 0
    
                switch componentLayout {
                case .bgr:
                    red = components.2
                    green = components.1
                    blue = components.0
                case .rgb:
                    red = components.0
                    green = components.1
                    blue = components.2
                default:
                    return .clear
                }
    
                return .init(red: red, green: green, blue: blue, alpha: UInt8(255))
    
            } else {
                assertionFailure("Unsupported number of pixel components")
                return .clear
            }
        }
    
    }
    
    public extension UIColor {
    
        convenience init(red: UInt8, green: UInt8, blue: UInt8, alpha: UInt8) {
            self.init(
                red: CGFloat(red)/255,
                green: CGFloat(green)/255,
                blue: CGFloat(blue)/255,
                alpha: CGFloat(alpha)/255)
        }
    
    }
    
    public extension CGBitmapInfo {
    
        enum ComponentLayout {
    
            case bgra
            case abgr
            case argb
            case rgba
            case bgr
            case rgb
    
            var count: Int {
                switch self {
                case .bgr, .rgb: return 3
                default: return 4
                }
            }
    
        }
    
        var componentLayout: ComponentLayout? {
            guard let alphaInfo = CGImageAlphaInfo(rawValue: rawValue & Self.alphaInfoMask.rawValue) else { return nil }
            let isLittleEndian = contains(.byteOrder32Little)
    
            if alphaInfo == .none {
                return isLittleEndian ? .bgr : .rgb
            }
            let alphaIsFirst = alphaInfo == .premultipliedFirst || alphaInfo == .first || alphaInfo == .noneSkipFirst
    
            if isLittleEndian {
                return alphaIsFirst ? .bgra : .abgr
            } else {
                return alphaIsFirst ? .argb : .rgba
            }
        }
    
        var chromaIsPremultipliedByAlpha: Bool {
            let alphaInfo = CGImageAlphaInfo(rawValue: rawValue & Self.alphaInfoMask.rawValue)
            return alphaInfo == .premultipliedFirst || alphaInfo == .premultipliedLast
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-18
      相关资源
      最近更新 更多