macosswiftuicore-graphics

What is the way to extract the value of an Image pixel given that a GCPoint value is available?


I have an Image View which displays an image and the CGPoint when the cursor is over the image. I am able to get and display the cursor position in pixels but cannot figure out how to get the value of the pixel rgb.

import SwiftUI

struct ImageView: View {
    
    @State var location: CGPoint = .zero
    @State var didHover = false
    
    var body: some View {
        VStack {
            Text("Os três")
                .font(.system(size: 16))
            
            Image("Burros_1") // placeholder
                .resizable()
                .scaledToFit()
                .aspectRatio(contentMode: .fit)
                .background(Color.black.opacity(0.5))
                .frame(width: 640, height: 480)
                .border(.green)
                .onHover { hover in
                    if hover {
                        NSCursor.crosshair.push()
                    } else {
                        NSCursor.pop()
                    }
                }
                .onContinuousHover { phase in // (col, row)
                    switch phase {
                    case .active(let location):
                        NSCursor.crosshair.push()
                        self.location = location // WORKS
                        self.didHover = true
                    case .ended:
                        self.didHover = false
                        break
                    }
                }
            
            // GOAT add DN
            let rowString = String(format: "%03d", Int(round(location.x)))
            let colString = String(format: "%03d", Int(round(location.y)))
            
            if didHover {  // Missing pixel value GOAT
                Text("(r, c, DN) \(rowString), \(colString), ??")
                    .monospaced()
            }
            else {  // this conserves a line for the Text
                Text(" ")
            }
        } // VStack
    } //
}

I've searched on-line, including stack overflow, extensively but most results are for iOS, not macOS, and don't really address my problem or won't compile. I've also 'jumped' to definitions to try to spot a method but no luck.


Solution

  • You could try this approach using NSBitmapImageRep, to get the pixel rgb.

    Example code:

    struct ContentView: View {
        var body: some View {
            ImageView()
        }
    }
    
    struct ImageView: View {
        let nsimg = NSImage(named: "mycat")!
        @State private var location: CGPoint = .zero
        @State private var pixelColor: NSColor?
        
        var body: some View {
            VStack {
                Text("location: \(location.debugDescription)")
                Text("color: \(pixelColor?.debugDescription ?? "no color")")
                
                GeometryReader { geo in
                    Image(nsImage: nsimg)
                        .resizable()
                        .frame(width: 640, height: 480)
                        .onContinuousHover { phase in
                            switch phase {
                            case .active(let location):
                                NSCursor.crosshair.push()
                                self.location = location
                                
                                // todo convert coords using GeometryReader
                                
                                pixelColor = nsimg.colorAt(location)
                            case .ended:
                                break
                            }
                        }
                        .border(.green)
                }
            }
            .onAppear {
                pixelColor = nsimg.colorAt(CGPoint(x: 20, y: 60)) // for testing
                print("---> pixelColor: \(pixelColor)")
            }
        }
        
    }
    
    extension NSImage {
        func colorAt(_ pos: CGPoint) -> NSColor? {
            let imageRep = NSBitmapImageRep(data: self.tiffRepresentation!)
            return imageRep?.colorAt(x: Int(pos.x), y: Int(pos.y))
        }
    }