web-dev-qa-db-de.com

Wie bekomme ich mit Swift die Farbe eines Pixels in einem UIImage?

Ich versuche, die Farbe eines Pixels in einem UIImage mit Swift zu erhalten, aber es scheint immer 0 zurückzugeben. Hier ist der Code, der von @Minas 'Antwort in this thread übersetzt wird:

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

Danke im Voraus!

38
Zoyt

Ein bisschen Suche führt mich hierher, da ich vor dem ähnlichen Problem stand. Ihr Code funktioniert gut. Das Problem wird möglicherweise von Ihrem Bild ausgelöst. 

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

Diese Methode wählt die Pixelfarbe aus dem CGImage des Bildes. Stellen Sie also sicher, dass Sie das richtige Bild auswählen. z.B. Wenn Sie UIImage 200x200 verwenden, aber die Original-Bilddatei von Imgaes.xcassets oder wo immer sie herkommt, 400x400 ist und Sie einen Punkt (100,100) auswählen, wählen Sie tatsächlich den Punkt im oberen linken Bildbereich aus Mitte.

Zwei Lösungen: 
1, Verwenden Sie ein Bild aus Imgaes.xcassets und geben Sie nur ein @ 1x-Bild in das Feld 1x ein. Lassen Sie das @ 2x, @ 3x leer. Vergewissern Sie sich, dass Sie die Bildgröße kennen, und wählen Sie einen Punkt innerhalb des Bereichs. 

//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, skalieren Sie Ihren CGPoint um das Verhältnis, das dem UIImage entspricht. z.B. let point = CGPoint(100,100) im obigen Beispiel 

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

Ich habe nur die erste Methode getestet und verwende sie, um eine Farbe aus einer Farbpalette zu bekommen. Beides sollte funktionieren. Glückliche Kodierung :)

52
Jack Song

Swift 3, XCODE 8 Getestet und funktioniert

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

Wenn Sie die beantwortete Frage mehr als einmal aufrufen, sollten Sie die Funktion nicht für jedes Pixel verwenden, da Sie dieselbe Datenmenge neu erstellen. Wenn Sie alle Farben in einem Bild haben möchten, führen Sie etwas mehr aus:

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
}

Hier ist ein Beispielprojekt

Als Randbemerkung ist diese Funktion zwar deutlich schneller als die akzeptierte Antwort, sie liefert jedoch ein weniger definiertes Ergebnis.

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)

Wichtig: -Dies funktioniert nur für @ 1x image.

Anfrage: -

wenn Sie eine Lösung für @ 2x und @ 3x Bilder haben, teilen Sie sie bitte Vielen Dank :)

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

}

Verwendungszweck

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

Sie können tapGestureRecognizer als Standort verwenden.

6
ramchandra n

Ich bekam Farben für Rot und Blau vertauscht ... Die ursprüngliche Funktion berücksichtigte auch nicht die tatsächlichen Bytes pro Zeile und Bytes pro Pixel .. Ich entferne auch Optionals möglichst nicht aus.

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

Ihr Code funktioniert gut für mich als Erweiterung zu UIImage. Wie testest du deine Farbe? Hier ist mein Beispiel:

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

Dies druckt:

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

... was korrekt ist, da mein Bild ein grünes Quadrat ist.

(Was meinen Sie mit "es scheint immer 0 zurückzugeben"? Sie testen nicht zufällig einen schwarzen Pixel, oder?)

4
Matt Gibson

Ich denke, Sie müssen jede Komponente durch 255 teilen:

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

Ich bekomme rückwärtige Farben in Bezug auf das Austauschen von R und B. Ich bin mir nicht sicher, warum dies meiner Meinung nach RGBA war.

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

Wo color so aussieht:  enter image description here

image sieht folgendermaßen aus:  enter image description here

und testColor sieht folgendermaßen aus:  enter image description here

(Ich kann verstehen, dass der blaue Wert ein wenig aus sein kann und 0,502 mit Ungenauigkeit des Fließkommas sein kann.)

Mit dem Code gewechselt zu:

    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)

Ich bekomme testColor als:  enter image description here

2
richy

Ich habe versucht, die Farben aller vier Ecken eines Bildes zu ermitteln, und erhielt unerwartete Ergebnisse, einschließlich UIColor.clear.

Das Problem ist, dass die Pixel bei 0 beginnen. Das Anfordern eines Pixels in der Breite des Bildes würde tatsächlich zurückkehren und mir das erste Pixel der zweiten Zeile geben.

Das rechte obere Pixel eines 640 x 480-Bildes wäre beispielsweise x: 639, y: 0 und das untere rechte Pixel x: 639, y: 479.

Hier ist meine Implementierung der UIImage-Erweiterung mit dieser Anpassung:

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