web-dev-qa-db-fra.com

Comment obtenir la couleur d'un pixel dans une UIImage avec Swift?

J'essaie d'obtenir la couleur d'un pixel dans un UIImage avec Swift, mais il semble toujours renvoyer 0. Voici le code, traduit de la réponse de @Minas sur this thread:

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)
}

Merci d'avance!

38
Zoyt

Un peu de recherche me mène ici puisque je faisais face au même problème. Votre code fonctionne bien. Le problème peut être soulevé à partir de votre image. 

Code: 

  //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)
      }  
  }

Qu'est-ce qui se passe est que cette méthode va choisir la couleur de pixel de l'image CGImage de l'image. Assurez-vous donc de choisir la bonne image. par exemple. Si UIImage mesure 200 x 200, mais que le fichier image d'origine d'Imgaes.xcassets ou de son origine, mesure 400 x 400, et que vous choisissez un point (100 100), vous sélectionnez en fait le point dans la partie supérieure gauche de l'image. milieu.

Deux solutions: 
1, utilise l'image d'Imgaes.xcassets et ne met qu'une image @ 1x dans un champ 1x. Laissez le @ 2x, @ 3x vierge. Assurez-vous de connaître la taille de l'image et choisissez un point situé dans la plage. 

//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, vous redimensionnez CGPoint vers le haut/bas de la proportion pour correspondre à la UIImage. par exemple. let point = CGPoint(100,100) dans l'exemple ci-dessus, 

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)) 

J'ai seulement testé la première méthode et je l'utilise pour obtenir une couleur d'une palette de couleurs. Les deux devraient fonctionner. Joyeux codage :)

52
Jack Song

Swift 3, XCODE 8 testé et fonctionnel

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)
    }

}
23

Si vous appelez la question répondue plus d'une fois, vous ne devez pas utiliser la fonction sur chaque pixel, car vous recréez le même ensemble de données. Si vous voulez toutes les couleurs d'une image, faites quelque chose de plus semblable à ceci:

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
}

Voici un projet Example

En remarque, cette fonction est nettement plus rapide que la réponse acceptée, mais donne un résultat moins défini. Je viens de mettre UIImageView dans le paramètre 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
}
10
Sethmr

Swift3 (IOS 10.3)

Important: -Ceci ne fonctionne que pour @ 1x image.

Demande: -

si vous avez une solution pour @ 2x et @ 3x images, partagez-les. Je vous remercie :)

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)
    }

}

Utilisation

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

Vous pouvez utiliser tapGestureRecognizer pour la localisation.

6
ramchandra n

Les couleurs du rouge et du bleu étaient interverties… .. La fonction d'origine ne tenait pas compte non plus des octets réels par rangée et des octets par pixel …….

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)
    }
}
6
MJLyco

Votre code fonctionne bien pour moi, en tant qu'extension de UIImage. Comment testez-vous votre couleur? voici mon exemple:

    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)")

Cela imprime:

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

... ce qui est correct, étant donné que mon image est un carré vert plein.

(Que voulez-vous dire par "il semble toujours renvoyer 0"? Vous ne testez pas sur un pixel noir, n'est-ce pas?)

4
Matt Gibson

Je pense que vous devez diviser chaque composant par 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)
4
Ben Stahl

J'obtiens des couleurs inverses en termes d'échange de R et B, je ne sais pas pourquoi cela, je pensais que l'ordre était 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 ressemble à ceci:  enter image description here

image ressemble à ceci:  enter image description here

et testColor ressemble à:  enter image description here

(Je peux comprendre que la valeur bleue puisse être un peu éteinte et être 0.502 avec une imprécision en virgule flottante)

Avec le code commuté sur:

    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)

Je reçois testColor comme:  enter image description here

2
richy

J'essayais de trouver les couleurs des quatre coins d'une image et obtenais des résultats inattendus, notamment UIColor.clear.

Le problème est que les pixels commencent à 0, donc demander un pixel à la largeur de l'image reviendrait en réalité à me retourner et me donnerait le premier pixel de la deuxième ligne.

Par exemple, le pixel en haut à droite d'une image 640 x 480 serait en réalité x: 639, y: 0 et le pixel en bas à droite serait x: 639, y: 479.

Voici ma mise en œuvre de l'extension UIImage avec cet ajustement:

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)
}
0
OffensivelyBad