如何在 Swift 中获取 UIImage 中像素的颜色? [英] How to get the color of a pixel in a UIImage in Swift?

查看:69
本文介绍了如何在 Swift 中获取 UIImage 中像素的颜色?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

How do I get the color of a pixel in a UIImage in Swift?

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

The existing answers on SO are either outdated or don't work for me.

推荐答案

Swift 5

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

To come up with a reliable solution, we need to take into account the following:

  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.
  1. The pixel size of the image can differ from its point size that is returned by image.size.width/image.size.height.
  2. There can be various layouts used by pixel components in the image, such as BGRA, ABGR, ARGB etc. or pixels may not have the alpha component at all, such as BGR and RGB. UIView.drawHierarchy(in:afterScreenUpdates:) method can produce BGRA images.
  3. Color components can be premultiplied by the alpha for all pixels in the image and need to be divided by alpha in order to restore the original color. Again, UIView.drawHierarchy(in:afterScreenUpdates:) method can produce BGRA images with color components premultiplied by alpha.
  4. For memory optimization used by CGImage, the size of a pixel row in bytes can be greater than the mere multiplication of the pixel width by 4.

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

The code below is to provide a comprehensive solution to get the UIColor of a pixel for all such special cases in Swift 5. The code is optimized for usability and clarity, not for performance.

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
    }

}

这篇关于如何在 Swift 中获取 UIImage 中像素的颜色?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆