Search code examples
swiftmacosappkitnsimage

Transform two NSImages on top of each other into a single NSImage without using `lockFocus`


I have a large image and a smaller image, both in Data type which I transformed into NSImage. I want to put the smaller image on top of the large image, top-left aligned, in a new single NSImage.

Currently this code works, however both lockFocus and unlockFocus are deprecated. I don't think the documentation is clear enough for me to understand how I should rewrite it.

This is the working code:

func combineImages(image1: NSImage, image2: NSImage) -> NSImage {
    let size = NSMakeSize(max(image1.size.width, image2.size.width), max(image1.size.height, image2.size.height))
    let newImage = NSImage(size: size)
    
    newImage.lockFocus()
    
    image2.draw(at: NSPoint(x: 0, y: size.height - image2.size.height), from: NSZeroRect, operation: .sourceOver, fraction: 1.0)
    image1.draw(at: NSZeroPoint, from: NSZeroRect, operation: .sourceOver, fraction: 1.0)
    
    newImage.unlockFocus()

    return newImage
}

The deprecation method is:

This method is incompatible with resolution-independent drawing and should not be used.


Solution

  • You can use init(size:flipped:drawingHandler:) instead:

    func combineImages(image1: NSImage, image2: NSImage) -> NSImage {
        let size = NSSize(width: max(image1.size.width, image2.size.width), height: max(image1.size.height, image2.size.height))
        let newImage = NSImage(size: size, flipped: false) { _ in
            let drawPointImage2 = NSPoint(x: 0, y: size.height - image2.size.height)
            image2.draw(at: drawPointImage2, from: NSRect(origin: .zero, size: image2.size), operation: .sourceOver, fraction: 1.0)
            image1.draw(at: NSPoint.zero, from: NSRect(origin: .zero, size: image1.size), operation: .sourceOver, fraction: 1.0)
            return true
        }
        
        return newImage
    }
    

    Using rect closure:

    func combineImages(image1: NSImage, image2: NSImage) -> NSImage {
        let size = NSSize(width: max(image1.size.width, image2.size.width), height: max(image1.size.height, image2.size.height))
        let newImage = NSImage(size: size, flipped: false) { rect -> Bool in
            let drawPointImage2 = NSPoint(x: 0, y: size.height - image2.size.height)
            image2.draw(at: drawPointImage2, from: NSRect(origin: .zero, size: image2.size), operation: .sourceOver, fraction: 1.0)
            image1.draw(at: NSPoint.zero, from: NSRect(origin: .zero, size: image1.size), operation: .sourceOver, fraction: 1.0)
            return true
        }
        
        return newImage
    }
    

    init(size:flipped:drawingHandler:)