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

时间:2014-08-05 19:13:05

标签: ios uiimage swift

我正在尝试使用Swift获取UIImage中像素的颜色,但它似乎总是返回0.这是代码,从this线程上的@Minas'答案翻译而来:

func getPixelColor(pos: CGPoint) -> UIColor {
    var pixelData = CGDataProviderCopyData(CGImageGetDataProvider(self.CGImage))
    var data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)

    var pixelInfo: Int = ((Int(self.size.width) * Int(pos.y)) + Int(pos.x)) * 4

    var r = CGFloat(data[pixelInfo])
    var g = CGFloat(data[pixelInfo+1])
    var b = CGFloat(data[pixelInfo+2])
    var a = CGFloat(data[pixelInfo+3])

    return UIColor(red: r, green: g, blue: b, alpha: a)
}

提前致谢!

13 个答案:

答案 0 :(得分:54)

由于我遇到了类似的问题,因此我在这里进行了一些搜索。 您的代码工作正常。问题可能会从您的图像中提出。

代码:

  //On the top of your swift 
  extension UIImage {
      func getPixelColor(pos: CGPoint) -> UIColor {

          let pixelData = CGDataProviderCopyData(CGImageGetDataProvider(self.CGImage))
          let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)

          let pixelInfo: Int = ((Int(self.size.width) * Int(pos.y)) + Int(pos.x)) * 4

          let r = CGFloat(data[pixelInfo]) / CGFloat(255.0)
          let g = CGFloat(data[pixelInfo+1]) / CGFloat(255.0)
          let b = CGFloat(data[pixelInfo+2]) / CGFloat(255.0)
          let a = CGFloat(data[pixelInfo+3]) / CGFloat(255.0)

          return UIColor(red: r, green: g, blue: b, alpha: a)
      }  
  }

这种方法会从图像的CGImage中选择像素颜色。因此,请确保从正确的图像中选择。例如如果你的UIImage是200x200,但是来自Imgaes.xcassets的原始图像文件或它来自的地方是400x400,并且你正在拾取点(100,100),你实际上是在图像的左上部分选取点,而不是中间。

两种解决方案:
1,使用来自Imgaes.xcassets的图像,并且只在1x字段中放置一个@ 1x图像。保留@ 2x,@ 3x为空。确保您知道图像大小,并选择一个范围内的点。

//Make sure only 1x image is set
let image : UIImage = UIImage(named:"imageName") 
//Make sure point is within the image
let color : UIColor = image.getPixelColor(CGPointMake(xValue, yValue)) 

2,向上/向下扩展CGPoint的比例以匹配UIImage。例如上例中的let point = CGPoint(100,100)

let xCoordinate : Float = Float(point.x) * (400.0/200.0)
let yCoordinate : Float = Float(point.y) * (400.0/200.0) 
let newCoordinate : CGPoint = CGPointMake(CGFloat(xCoordinate), CGFloat(yCoordinate))
let image : UIImage = largeImage
let color : UIColor = image.getPixelColor(CGPointMake(xValue, yValue)) 

我只测试了第一种方法,我用它来从调色板中获取颜色。两者都应该有效。 快乐的编码:)

答案 1 :(得分:24)

SWIFT 3,XCODE 8经过测试并正常工作

extension UIImage {
    func getPixelColor(pos: CGPoint) -> UIColor {

        let pixelData = self.cgImage!.dataProvider!.data
        let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)

        let pixelInfo: Int = ((Int(self.size.width) * Int(pos.y)) + Int(pos.x)) * 4

        let r = CGFloat(data[pixelInfo]) / CGFloat(255.0)
        let g = CGFloat(data[pixelInfo+1]) / CGFloat(255.0)
        let b = CGFloat(data[pixelInfo+2]) / CGFloat(255.0)
        let a = CGFloat(data[pixelInfo+3]) / CGFloat(255.0)

        return UIColor(red: r, green: g, blue: b, alpha: a)
    }

}

答案 2 :(得分:12)

如果您多次调用已回答的问题,则不应在每个像素上使用该功能,因为您正在重新创建同一组数据。如果您想要图像中的所有颜色,请执行以下操作:

func findColors(_ image: UIImage) -> [UIColor] {
    let pixelsWide = Int(image.size.width)
    let pixelsHigh = Int(image.size.height)

    guard let pixelData = image.cgImage?.dataProvider?.data else { return [] }
    let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)

    var imageColors: [UIColor] = []
    for x in 0..<pixelsWide {
        for y in 0..<pixelsHigh {
            let point = CGPoint(x: x, y: y)
            let pixelInfo: Int = ((pixelsWide * Int(point.y)) + Int(point.x)) * 4
            let color = UIColor(red: CGFloat(data[pixelInfo]) / 255.0,
                                green: CGFloat(data[pixelInfo + 1]) / 255.0,
                                blue: CGFloat(data[pixelInfo + 2]) / 255.0,
                                alpha: CGFloat(data[pixelInfo + 3]) / 255.0)
            imageColors.append(color)
        }
    }
    return imageColors
}

这是Example Project

作为旁注,这个函数明显快于接受的答案,但它给出了一个定义较少的结果。我只是将UIImageView放在sourceView参数中。

func getPixelColorAtPoint(point: CGPoint, sourceView: UIView) -> UIColor {
    let pixel = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: 4)
    let colorSpace = CGColorSpaceCreateDeviceRGB()
    let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
    let context = CGContext(data: pixel, width: 1, height: 1, bitsPerComponent: 8, bytesPerRow: 4, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)

    context!.translateBy(x: -point.x, y: -point.y)

    sourceView.layer.render(in: context!)
    let color: UIColor = UIColor(red: CGFloat(pixel[0])/255.0,
                                 green: CGFloat(pixel[1])/255.0,
                                 blue: CGFloat(pixel[2])/255.0,
                                 alpha: CGFloat(pixel[3])/255.0)
    pixel.deallocate(capacity: 4)
    return color
}

答案 3 :(得分:7)

I was getting swapped colors for red and blue. The original function also did not account for the actual bytes per row and bytes per pixel. I also avoid unwrapping optionals whenever possible. Here's an updated function.

import UIKit

extension UIImage {
    /// Get the pixel color at a point in the image
    func pixelColor(atLocation point: CGPoint) -> UIColor? {
        guard let cgImage = cgImage, let pixelData = cgImage.dataProvider?.data else { return nil }

        let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)

        let bytesPerPixel = cgImage.bitsPerPixel / 8

        let pixelInfo: Int = ((cgImage.bytesPerRow * Int(point.y)) + (Int(point.x) * bytesPerPixel))

        let b = CGFloat(data[pixelInfo]) / CGFloat(255.0)
        let g = CGFloat(data[pixelInfo+1]) / CGFloat(255.0)
        let r = CGFloat(data[pixelInfo+2]) / CGFloat(255.0)
        let a = CGFloat(data[pixelInfo+3]) / CGFloat(255.0)

        return UIColor(red: r, green: g, blue: b, alpha: a)
    }
}

答案 4 :(得分:7)

Swift3(IOS 10.3)

  
    

重要提示: - 这仅适用于 @ 1x 图片。

  

请求: -

  

如果您有 @ 2x @ 3x 图片的解决方案,请分享。谢谢:))

extension UIImage {

    func getPixelColor(atLocation location: CGPoint, withFrameSize size: CGSize) -> UIColor {
        let x: CGFloat = (self.size.width) * location.x / size.width
        let y: CGFloat = (self.size.height) * location.y / size.height

        let pixelPoint: CGPoint = CGPoint(x: x, y: y)

        let pixelData = self.cgImage!.dataProvider!.data
        let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)

        let pixelIndex: Int = ((Int(self.size.width) * Int(pixelPoint.y)) + Int(pixelPoint.x)) * 4

        let r = CGFloat(data[pixelIndex]) / CGFloat(255.0)
        let g = CGFloat(data[pixelIndex+1]) / CGFloat(255.0)
        let b = CGFloat(data[pixelIndex+2]) / CGFloat(255.0)
        let a = CGFloat(data[pixelIndex+3]) / CGFloat(255.0)

        return UIColor(red: r, green: g, blue: b, alpha: a)
    }

}

<强> 用法

print(yourImageView.image!.getPixelColor(atLocation: location, withFrameSize: yourImageView.frame.size))
  

您可以使用tapGestureRecognizer进行定位。

答案 5 :(得分:4)

您的代码对我来说很好,作为UIImage的扩展。你的测试颜色如何?这是我的例子:

    let green = UIImage(named: "green.png")
    let topLeft = CGPoint(x: 0, y: 0)

    // Use your extension
    let greenColour = green.getPixelColor(topLeft)

    // Dump RGBA values
    var redval: CGFloat = 0
    var greenval: CGFloat = 0
    var blueval: CGFloat = 0
    var alphaval: CGFloat = 0
    greenColour.getRed(&redval, green: &greenval, blue: &blueval, alpha: &alphaval)
    println("Green is r: \(redval) g: \(greenval) b: \(blueval) a: \(alphaval)")

打印:

    Green is r: 0.0 g: 1.0 b: 1.0 a: 1.0

...这是正确的,因为我的图像是一个坚实的绿色方块。

(“你似乎总是返回0”是什么意思?你不会在黑色像素上进行测试,是吗?)

答案 6 :(得分:4)

我认为您需要将每个组件除以255:

var r = CGFloat(data[pixelInfo]) / CGFloat(255.0)
var g = CGFloat(data[pixelInfo + 1]) / CGFloat(255.0)
var b = CGFloat(data[pixelInfo + 2]) / CGFloat(255.0)
var a = CGFloat(data[pixelInfo + 3]) / CGFloat(255.0)

答案 7 :(得分:2)

我在R和B交换时会得到倒退的颜色,不知道为什么我认为订单是RGBA。

func testGeneratedColorImage() {

    let color = UIColor(red: 0.5, green: 0, blue: 1, alpha: 1)
    let size = CGSize(width: 10, height: 10)
    let image = UIImage.image(fromColor: color, size: size)

    XCTAssert(image.size == size)

    XCTAssertNotNil(image.cgImage)

    XCTAssertNotNil(image.cgImage!.dataProvider)

    let pixelData = image.cgImage!.dataProvider!.data
    let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)

    let position = CGPoint(x: 1, y: 1)
    let pixelInfo: Int = ((Int(size.width) * Int(position.y)) + Int(position.x)) * 4

    let r = CGFloat(data[pixelInfo]) / CGFloat(255.0)
    let g = CGFloat(data[pixelInfo+1]) / CGFloat(255.0)
    let b = CGFloat(data[pixelInfo+2]) / CGFloat(255.0)
    let a = CGFloat(data[pixelInfo+3]) / CGFloat(255.0)

    let testColor = UIColor(red: r, green: g, blue: b, alpha: a)

    XCTAssert(testColor == color, "Colour: \(testColor) does not match: \(color)")
}

color看起来像这样: enter image description here

image看起来像这样: enter image description here

testColor看起来像: enter image description here

(我可以理解,蓝色值可能会偏离一点,并且浮动点不准确为0.502)

将代码切换为:

    let b = CGFloat(data[pixelInfo]) / CGFloat(255.0)
    let g = CGFloat(data[pixelInfo+1]) / CGFloat(255.0)
    let r = CGFloat(data[pixelInfo+2]) / CGFloat(255.0)
    let a = CGFloat(data[pixelInfo+3]) / CGFloat(255.0)

我得到testColorenter image description here

答案 8 :(得分:1)

我试图查找图像四个角的颜色,并且得到了意外的结果,包括UIColor.clear

问题是像素从0开始,因此请求在图像宽度处的像素实际上会回绕并为我提供第二行的第一个像素。

例如,一个640 x 480图像的右上像素实际上是x: 639, y: 0,而右下像素则是x: 639, y: 479

这是我对UIImage扩展的实现,并进行了以下调整:

func getPixelColor(pos: CGPoint) -> UIColor {

    guard let cgImage = cgImage, let pixelData = cgImage.dataProvider?.data else { return UIColor.clear }
    let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)

    let bytesPerPixel = cgImage.bitsPerPixel / 8
    // adjust the pixels to constrain to be within the width/height of the image
    let y = pos.y > 0 ? pos.y - 1 : 0
    let x = pos.x > 0 ? pos.x - 1 : 0
    let pixelInfo = ((Int(self.size.width) * Int(y)) + Int(x)) * bytesPerPixel

    let r = CGFloat(data[pixelInfo]) / CGFloat(255.0)
    let g = CGFloat(data[pixelInfo+1]) / CGFloat(255.0)
    let b = CGFloat(data[pixelInfo+2]) / CGFloat(255.0)
    let a = CGFloat(data[pixelInfo+3]) / CGFloat(255.0)

    return UIColor(red: r, green: g, blue: b, alpha: a)
}

答案 9 :(得分:0)

  

Swift 5 ,包括用于 @ 2x @ 3x 图片的解决方案

extension UIImage {
    subscript(_ point: CGPoint) -> UIColor? {
        guard let pixelData = self.cgImage?.dataProvider?.data else { return nil }
        let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)
        let pixelInfo: Int = Int((size.width * point.y + point.x) * 4.0 * scale * scale)
        let i = Array(0 ... 3).map { CGFloat(data[pixelInfo + $0]) / CGFloat(255) }
        return UIColor(red: i[0], green: i[1], blue: i[2], alpha: i[3])
    }
}

答案 10 :(得分:0)

我使用此扩展名:

public extension UIImage {

    var pixelWidth: Int {
        return cgImage?.width ?? 0
    }

    var pixelHeight: Int {
        return cgImage?.height ?? 0
    }

    func pixelColor(x: Int, y: Int) -> UIColor {

        if 0..<pixelWidth ~= x && 0..<pixelHeight ~= y {
            log.info("Pixel coordinates are in bounds")
        }else {
            log.info("Pixel coordinates are out of bounds")
                        return .black
        }
        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 a pixel of 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
        }
    }

}

但是对于正确的像素颜色,您只需要在x1中的xcasset中使用图像,否则您的引用是错误的,并且您需要使用此方法:让correctedImage = UIImage(data:image.pngData()!)检索正确的原点对于你的观点。

答案 11 :(得分:0)

https://stackoverflow.com/a/40237504/3286489的解决方案仅适用于sRGB色彩空间类型的图像。但是,对于其他颜色空间(扩展的sRGB ??),则不起作用。

因此,要使其正常工作,需要先将其转换为普通的sRGB图像类型,然后再从cgImage获取颜色。请注意,我们需要在计算中添加padding,以确保宽度始终是8的倍数。

public extension UIImage {
    func getPixelColor(pos: CGPoint) -> UIColor {
        
        // convert to standard sRGB image
        guard let cgImage = cgImage,
            let colorSpace = CGColorSpace(name: CGColorSpace.sRGB),
            let context = CGContext(data: nil, 
                width: Int(size.width), height: Int(size.height),
                bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace,
                bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)
        else { return .white }

        context.draw(cgImage, in: CGRect(origin: .zero, size: size))
        
        // Get the newly converted cgImage
        guard let newCGImage = context.makeImage(),
            let newDataProvider = newCGImage.dataProvider,
            let data = newDataProvider.data
        else { return .white }
        let pixelData: UnsafePointer<UInt8> = CFDataGetBytePtr(data)

        // Calculate the pixel position based on point given
        let remaining = 8 - ((Int(size.width)) % 8)
        let padding = (remaining < 8) ? remaining : 0
        let pixelInfo: Int = (((Int(size.width) + padding) * Int(pos.y)) + Int(pos.x)) * 4
        
        let r = CGFloat(pixelData[pixelInfo]) / CGFloat(255.0)
        let g = CGFloat(pixelData[pixelInfo+1]) / CGFloat(255.0)
        let b = CGFloat(pixelData[pixelInfo+2]) / CGFloat(255.0)
        let a = CGFloat(pixelData[pixelInfo+3]) / CGFloat(255.0)
        return UIColor(red: r, green: g, blue: b, alpha: a)
   }
}

(可选)如果不想转换为cgImage,只需替换

        // Get the newly converted cgImage
        guard let newCGImage = context.makeImage(),
            let newDataProvider = newCGImage.dataProvider,
            let newData = newDataProvider.data
            else { return .white }
        let pixelData: UnsafePointer<UInt8> = CFDataGetBytePtr(newData)

使用

        // Get the data and bind it from UnsafeMutableRawPointer to UInt8
        guard let data = context.data else { return .white }
        let pixelData = data.bindMemory(
            to: UInt8.self, capacity: Int(size.width * size.height * 4))

已更新

要获得更简洁的代码,我们可以直接使用UIGraphicsImageRenderer改进到sRGB的转换。由于确实进行了重绘,因此计算结果确实有所变化,将像素进一步细化为2倍。

    func getPixelColor(pos: CGPoint) -> UIColor {
        
        let newImage = UIGraphicsImageRenderer(size: size).image { _ in
            draw(in: CGRect(origin: .zero, size: size))
        }
        
        guard let cgImage = newImage.cgImage,
            let dataProvider = cgImage.dataProvider,
            let data = dataProvider.data else { return .white }
        let pixelData: UnsafePointer<UInt8> = CFDataGetBytePtr(data)
        let remaining = 8 - ((Int(size.width) * 2) % 8)
        let padding = (remaining < 8) ? remaining : 0
        let pixelInfo: Int = (((Int(size.width * 2) + padding) * Int(pos.y * 2)) + Int(pos.x * 2)) * 4
                
        let r = CGFloat(pixelData[pixelInfo]) / CGFloat(255.0)
        let g = CGFloat(pixelData[pixelInfo+1]) / CGFloat(255.0)
        let b = CGFloat(pixelData[pixelInfo+2]) / CGFloat(255.0)
        let a = CGFloat(pixelData[pixelInfo+3]) / CGFloat(255.0)
        return UIColor(red: r, green: g, blue: b, alpha: a)
    }

这是根据在https://stackoverflow.com/a/64538344/3286489中转换为sRGB的解决方案

答案 12 :(得分:0)

我在提供的互联网上的任何地方都没有找到答案

  • 简单代码
  • HDR 支持
  • 支持 bgr 等的颜色配置文件
  • 对@2x @3x 的缩放支持

原来是这样。据我所知,最终解决方案:

斯威夫特 5


import UIKit

public extension CGBitmapInfo {
  // https://stackoverflow.com/a/60247693/2585092
  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
  }
}

extension UIImage {
  // https://stackoverflow.com/a/68103748/2585092
  subscript(_ point: CGPoint) -> UIColor? {
    guard
      let cgImage = cgImage,
      let space = cgImage.colorSpace,
      let pixelData = cgImage.dataProvider?.data,
      let layout = cgImage.bitmapInfo.componentLayout
    else {
      return nil
    }
    let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)

    let comp = CGFloat(layout.count)
    let hdr = CGFloat(space.isHDR() ? 2 : 1)
    let pixelInfo = Int((size.width * point.y * scale + point.x * scale) * comp * hdr)
    let i = Array(0 ... Int(comp - 1)).map {
      CGFloat(data[pixelInfo + $0 * Int(hdr)]) / CGFloat(255)
    }

    switch layout {
    case .bgra:
      return UIColor(red: i[2], green: i[1], blue: i[0], alpha: i[3])
    case .abgr:
      return UIColor(red: i[3], green: i[2], blue: i[1], alpha: i[0])
    case .argb:
      return UIColor(red: i[1], green: i[2], blue: i[3], alpha: i[0])
    case .rgba:
      return UIColor(red: i[0], green: i[1], blue: i[2], alpha: i[3])
    case .bgr:
      return UIColor(red: i[2], green: i[1], blue: i[0], alpha: 1)
    case .rgb:
      return UIColor(red: i[0], green: i[1], blue: i[2], alpha: 1)
    }
  }
}