Ben A.
Ben A.

Reputation: 1196

setColor on NSBitmapImageRep not working in Swift

I'm trying to figure out how setColor works. I have the following code:

    
    
    lazy var imageView:NSImageView = {
        let imageView = NSImageView(frame: view.frame)
        return imageView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        createColorProjection()
        view.wantsLayer = true
        view.addSubview(imageView)
        
        view.needsDisplay = true
    }
    
    func createColorProjection() {
        var bitmap = NSBitmapImageRep(cgImage: cgImage!)
        var x = 0
        while x < bitmap.pixelsWide {
            var y = 0
            while y < bitmap.pixelsHigh {
                //pixels[Point(x: x, y: y)] = (getColor(x: x, y: y, bitmap: bitmap))
                bitmap.setColor(NSColor(cgColor: .black)!, atX: x, y: y)
                y += 1
            }
            x += 1
        }
        
        let image = createImage(bitmap: bitmap)
        imageView.image = image
        imageView.needsDisplay = true
    }
    
    
    func createImage(bitmap:NSBitmapImageRep) -> NSImage {
        let image = bitmap.cgImage
        return NSImage(cgImage: image! , size: CGSize(width: image!.width, height: image!.height))
    }

The intention of the code is to change a photo (a rainbow) to be entirely black (I'm just testing with black right now to make sure I understand how it works). However, when I run the program, the unchanged picture of the rainbow is shown, not a black photo.

I am getting these errors: Unrecognized colorspace number -1 and Unknown number of components for colorspace model -1.

Thanks.

Upvotes: 4

Views: 309

Answers (1)

Loengard
Loengard

Reputation: 431

First, you're right: setColor has been broken at least since Catalina. Apple hasn't fixed it probably because it's so slow and inefficient, and nobody ever used it.

Second, docs say NSBitmapImageRep(cgImage: CGImage) produces a read-only bitmap so your code wouldn't have worked even if setColor worked.

As Alexander says, making your own CIFilter is the best way to change a photo's pixels to different colors. Writing and implementing the OpenGL isn't easy, but it's the best.

If you were to add an extension to NSBitmapImageRep like this:

extension NSBitmapImageRep {
    func setColorNew(_ color: NSColor, atX x: Int, y: Int) {
        guard let data = bitmapData else { return }
        
        let ptr = data + bytesPerRow * y + samplesPerPixel * x
        
        ptr[0] = UInt8(color.redComponent * 255.1)
        ptr[1] = UInt8(color.greenComponent * 255.1)
        ptr[2] = UInt8(color.blueComponent * 255.1)
        
        if samplesPerPixel > 3 {
            ptr[3] = UInt8(color.alphaComponent * 255.1)
        }
    }
}

Then simply changing an image's pixels could be done like this:

func changePixels(image: NSImage, newColor: NSColor) -> NSImage {
    guard let imgData = image.tiffRepresentation,
          let bitmap = NSBitmapImageRep(data: imgData),
          let color = newColor.usingColorSpace(.deviceRGB)
    else { return image }
    
    var y = 0
    while y < bitmap.pixelsHigh {
        var x = 0
        while x < bitmap.pixelsWide {
            bitmap.setColorNew(color, atX: x, y: y)
            x += 1
        }
        y += 1
    }
    
    let newImage = NSImage(size: image.size)
    newImage.addRepresentation(bitmap)
    
    return newImage
}

Upvotes: 4

Related Questions