使用CGPoint从图像中获取像素颜色

3

我在Swift中想要获取指定CGPoint的像素颜色,但是遇到了很多困难。以下是我的代码:

@IBOutlet weak var graphImage: UIImageView!

var image : UIImage?

override func viewDidLoad() {
    super.viewDidLoad()
}

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

    var pixelInfo : Int = ((Int(graphImage.image!.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)
}

@IBAction func playTapped(sender: AnyObject) {

    getPixelColor(CGPoint(x: 100.0, y: 100.0))

}

当我点击播放按钮后,我的应用程序崩溃了,在“let g = CGFloat…”这一行。尽管我对任何与图像有关的东西都很陌生,但我没有看到任何错误。我想知道是否需要使用不同类型的图像,或者我的pixelInfo变量中是否有问题。顺便说一下,我从How do I get the color of a pixel in a UIImage with Swift?中获取了大部分代码。有人能指出可能存在的问题吗?非常感谢您的帮助!

什么是错误,请发布崩溃代码。同时使用断点了解崩溃发生的行数。 - Saheb Roy
我也在这里 @SahebRoy - Vizllx
兄弟,@Vizllx,你在 Stack Overflow 上聊天了吗?如何添加? - Saheb Roy
类似的问题已经被提出。 - MrWaqasAhmed
崩溃代码只显示EXC_BAD_INSTRUCTION,没有其他信息。它在“let g = CGFloat(data[pixelInfo]+1) / CGFloat(255.0)”这一行崩溃。我知道有一个类似的问题被问到了,但是我的问题与那个问题不同,尽管我们使用相同的代码。 - Sunay
4个回答

6

jasonnoahchoi给出了一个很好的答案 - 如果你在转换为Swift 3.0时遇到问题,请尝试使用以下更改的相同代码:

class PixelExtractor: NSObject {
    
    let image: CGImage
    let context: CGContext?
    
    var width: Int {
        get {
            return image.width
        }
    }
    
    var height: Int {
        get {
            return image.height
        }
    }
    
    init(img: CGImage) {
        image = img
        context = PixelExtractor.createBitmapContext(cgImage: img)
    }
    
    class func createBitmapContext(cgImage: CGImage) -> CGContext {
        
        // Get image width, height
        let pixelsWide = cgImage.width
        let pixelsHigh = cgImage.height
        
        let bitmapBytesPerRow = pixelsWide * 4
        let bitmapByteCount = bitmapBytesPerRow * Int(pixelsHigh)
        
        // Use the generic RGB color space.
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        
        // Allocate memory for image data. This is the destination in memory
        // where any drawing to the bitmap context will be rendered.
        let bitmapData = malloc(bitmapByteCount)
        let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue)
        let size = CGSize(width: pixelsWide, height: pixelsHigh)
        UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
        // create bitmap
        let context = CGContext(data: bitmapData, width: pixelsWide, height: pixelsHigh, bitsPerComponent: 8,
                                bytesPerRow: bitmapBytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)
        
        // draw the image onto the context
        let rect = CGRect(x: 0, y: 0, width: pixelsWide, height: pixelsHigh)
        context?.draw(cgImage, in: rect)
        
        return context!
    }
    
    private func colorAt(x: Int, y: Int) -> UIColor {
        
        assert(0<=x && x<width)
        assert(0<=y && y<height)
        
        let data = context!.data!
        
        let offset = 4 * (y * width + x)
        
        let alpha = CGFloat(data.load(fromByteOffset: offset, as: UInt8.self)) / 255.0
        let red = CGFloat(data.load(fromByteOffset: offset + 1, as: UInt8.self)) / 255.0
        let green = CGFloat(data.load(fromByteOffset: offset + 2, as: UInt8.self)) / 255.0
        let blue = CGFloat(data.load(fromByteOffset: offset + 3, as: UInt8.self)) / 255.0
        
        let color = UIColor(red: red, green: green, blue: blue, alpha: alpha)
        
        return color
    }
}

5

我以前做过类似于这样的事情。我所做的是创建一个名为PixelExtractor的单独类。

class PixelExtractor: NSObject {

  let image: CGImage
  let context: CGContextRef?

  var width: Int {
      get {
          return CGImageGetWidth(image)
      }
  }

  var height: Int {
      get {
          return CGImageGetHeight(image)
      }
  }

  init(img: CGImage) {
      image = img
      context = PixelExtractor.createBitmapContext(img)
  }

  class func createBitmapContext(img: CGImage) -> CGContextRef {

      // Get image width, height
      let pixelsWide = CGImageGetWidth(img)
      let pixelsHigh = CGImageGetHeight(img)

      let bitmapBytesPerRow = pixelsWide * 4
      let bitmapByteCount = bitmapBytesPerRow * Int(pixelsHigh)

      // Use the generic RGB color space.
      let colorSpace = CGColorSpaceCreateDeviceRGB()

      // Allocate memory for image data. This is the destination in memory
      // where any drawing to the bitmap context will be rendered.
      let bitmapData = malloc(bitmapByteCount)
      let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.PremultipliedFirst.rawValue)
      let size = CGSizeMake(CGFloat(pixelsWide), CGFloat(pixelsHigh))
      UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
      // create bitmap
      let context = CGBitmapContextCreate(bitmapData, pixelsWide, pixelsHigh, 8,
        bitmapBytesPerRow, colorSpace, bitmapInfo.rawValue)

      // draw the image onto the context
      let rect = CGRect(x: 0, y: 0, width: pixelsWide, height: pixelsHigh)
      CGContextDrawImage(context, rect, img)

      return context!
  }

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

      assert(0<=x && x<width)
      assert(0<=y && y<height)

      let uncastedData = CGBitmapContextGetData(context)
      let data = UnsafePointer<UInt8>(uncastedData)

      let offset = 4 * (y * width + x)

      let alpha: UInt8 = data[offset]
      let red: UInt8 = data[offset+1]
      let green: UInt8 = data[offset+2]
      let blue: UInt8 = data[offset+3]

      let color = UIColor(red: CGFloat(red)/255.0, green: CGFloat(green)/255.0, blue: CGFloat(blue)/255.0, alpha: CGFloat(alpha)/255.0)

      return color
  }
}

嗨!谢谢你的回答!就像我说的,我对这一切都非常陌生:我尝试了你的代码,但它对我来说并没有起作用,但这可能只是一个小的技术问题。你介意给我发送更多的项目代码,这样我就可以看到你在什么上下文中使用了这段代码吗? - Sunay
你可以在你的视图控制器中这样调用它:PixelExtractor(image.CGImage).colorAt(x: 1, y: 2) // x 和 y 基本上是 CGPoint 的 Int 值,所以如果你有一个 CGPoint,你需要将 X 和 Y 强制转换为 Int,像这样 Int(point.x), Int(point.y) - jasonnoahchoi
1
嘿!非常感谢你!这个完美地解决了问题! :) - Sunay
1
@jasonnoahchoi 在返回color之前,确保调用free(uncastedData),因为unsafeMutablePointers不受ARC的保护。 - swillsea
1
如果您认为可能会多次调用colorAt()(并且在类的deinit方法中释放),我还建议将数据和uncastedData存储为init方法中的变量。但这完全取决于您在代码中实现PixelExtractor的方式。 - swillsea

4

Swift 5版本的jasonnoahchoi答案

class PixelExtractor: NSObject {

    let image: CGImage
    let context: CGContext?

    var width: Int {
        get {
            return image.width
        }
    }

    var height: Int {
        get {
            return image.height
        }
    }

    init(img: CGImage) {
        image = img
        context = PixelExtractor.createBitmapContext(img: img)
    }

    class func createBitmapContext(img: CGImage) -> CGContext {

        // Get image width, height
        let pixelsWide = img.width
        let pixelsHigh = img.height

        let bitmapBytesPerRow = pixelsWide * 4
        let bitmapByteCount = bitmapBytesPerRow * Int(pixelsHigh)

        // Use the generic RGB color space.
        let colorSpace = CGColorSpaceCreateDeviceRGB()

        // Allocate memory for image data. This is the destination in memory
        // where any drawing to the bitmap context will be rendered.
        let bitmapData = malloc(bitmapByteCount)
        let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue)
        let size = CGSize(width: CGFloat(pixelsWide), height: CGFloat(pixelsHigh))
        UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
        // create bitmap
        let context = CGContext(data: bitmapData,
                                width: pixelsWide,
                                height: pixelsHigh,
                                bitsPerComponent: 8,
                                bytesPerRow: bitmapBytesPerRow,
                                space: colorSpace,
                                bitmapInfo: bitmapInfo.rawValue)

        // draw the image onto the context
        let rect = CGRect(x: 0, y: 0, width: pixelsWide, height: pixelsHigh)

        context?.draw(img, in: rect)

        return context!
    }

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

        assert(0<=x && x<width)
        assert(0<=y && y<height)

        guard let pixelBuffer = context?.data else { return .white }
        let data = pixelBuffer.bindMemory(to: UInt8.self, capacity: width * height)

        let offset = 4 * (y * width + x)

        let alpha: UInt8 = data[offset]
        let red: UInt8 = data[offset+1]
        let green: UInt8 = data[offset+2]
        let blue: UInt8 = data[offset+3]

        let color = UIColor(red: CGFloat(red)/255.0, green: CGFloat(green)/255.0, blue: CGFloat(blue)/255.0, alpha: CGFloat(alpha)/255.0)

        return color
    }
}

很好,我很高兴不必更新我的答案。真棒的东西。 - jasonnoahchoi

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接