Search code examples
macoscocoacalayer

image in CALayer is not scaled down properly and has anti-aliasing issues


I am learning about CALayer and have the following problem. The following two images

enter image description here

are in fact two views in my app: the one on the left is an NSImageView and the one on the right is a layer-hosting NSView with the same image as contents. Here is the code for the ViewController that I used to set this up:

import Cocoa

class ViewController: NSViewController {
  @IBOutlet weak var aView: NSView!
  @IBOutlet weak var anImage: NSImageView!

  override func viewDidLoad() {
      super.viewDidLoad()

      let img = NSImage(named: "emptyStar")!

      let l = CALayer()
      l.contents = img

      aView.layer = l
      aView.wantsLayer = true

      anImage.image = img
  }
}

Why does the CALayer image look so bad and how can I fix this (sticking with CALayer)? A magnification of the right image shows bad anti-aliasing.

Things I tried are: 1) generating a CGImage using

   img.CGImageForProposedRect(&aView.frame, context: nil, hints: [NSImageHintInterpolation: 3])

and setting this as contents of l, 2) setting l.contentsScale=2, 3) setting l.edgeAntialiasingMask=[.LayerLeftEdge, .LayerRightEdge, .LayerBottomEdge, .LayerTopEdge], 4) making aView layer-backed, by moving aView.wantsLayer=true before aView.layer = l. I could not make any of those work...

Both views are 50 x 50 while the image itself is a 253 x 254 PNG image that I grabbed from this tutorial

https://developer.apple.com/library/content/referencelibrary/GettingStarted/DevelopiOSAppsSwift/

and it is in the Assets for this app.

I am pretty sure I am missing something simple here...

Thanks for any help!

UPDATE: Delegating the drawing seems to work:

class ViewController: NSViewController {
    @IBOutlet weak var aView: NSView!

    override func drawLayer(layer: CALayer, inContext ctx: CGContext) {
        NSGraphicsContext.setCurrentContext(NSGraphicsContext(CGContext: ctx, flipped: false))
        NSImage(named: "emptyStar")!.drawInRect(layer.bounds)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        let l = CALayer()
        l.delegate = self

        aView.layer = l
        aView.wantsLayer = true

        aView.layer!.setNeedsDisplay()
   }

}

Hopefully there is a better solution to something that should work off the box...


Solution

  • You'll get aliasing artifacts if you don't use the correct min/mag filters.

    Since you're putting a larger image into a smaller CALayer, you'd want to use linear, bilinear or trilinear filtering (in increasing order of cost).

    For a layer, you can set the min filter using:

    layer.minificationFilter = kCAFilterTrilinear;