简体   繁体   English

CALayer shadowPath 在渲染为图像时被忽略

[英]CALayer shadowPath ignored on rendering as image

I am making an app that uses CAlayer to show contents like image in a NSWindow.我正在制作一个应用程序,它使用 CAlayer 在 NSWindow 中显示图像等内容。 The layer has shadows styled with shadowPath to make a better appearance.该图层具有使用 shadowPath 样式设置的阴影,以使外观更好。 Finally to save/export the whole CAlayer and its contents the parent NSView is converted to an NSImage.最后,为了保存/导出整个 CAlayer 及其内容,父 NSView 被转换为 NSImage。 The shadow in NSImage is entirely different from that of the actual shadow in CALayer. NSImage 中的阴影与 CALayer 中的实际阴影完全不同。 I can't get the reason why this is happening.我不明白为什么会发生这种情况。 Is it a normal thing on AppKit or am I doing it wrong?这是 AppKit 上的正常现象还是我做错了?

These are the difference in shadows:这些是阴影的区别:

image(1) - CAlayer with shadowPath (Shadow only in bottom). image(1) - 带有 shadowPath 的 CAlayer(仅在底部有阴影)。

image(2) - NSImage created from the superview (Shadow in 4 sides). image(2) - 从超级视图创建的 NSImage(4 面的阴影)。

在此处输入图像描述

This is how shadow is added in image(1):这是在 image(1) 中添加阴影的方式:

    layer?.masksToBounds = false
    let size: CGFloat = 100
    let distance: CGFloat = 200
    let rect = CGRect(
        x: -size,
        y: layer.frame.height - (size * 0.4) + distance,
        width: layer.frame.width + size * 2,
        height: size
    )

    layer.shadowColor = .black
    layer.shadowRadius = 100
    layer.shadowOpacity = 1
    layer.shadowPath = NSBezierPath(ovalIn: rect).cgPath

This is how the superview is converted to NSImage image(2):这就是将 superview 转换为 NSImage image(2) 的方式:

    let imageRep = view.bitmapImageRepForCachingDisplay(in: view.bounds)
    
    view.cacheDisplay(in: view.bounds, to: imageRep!)
    let image = NSImage(size: view.bounds.size)
    image.addRepresentation(imageRep!)
    let imageData = image.tiffRepresentation

    return NSImage(data: imageData!)!

One option - may or may not be suitable:一种选择 - 可能适合也可能不适合:

  • generate an oval image生成椭圆形图像
  • blur that image模糊该图像
  • use the resulting image in an image view or maybe as the content of a layer在图像视图中使用生成的图像,或者作为图层的内容

Here's an attempt - note: I work with iOS, so lots of hard-coded values and possibly (likely) incorrect ways to do this:这是一个尝试 - 注意:我使用 iOS,所以有很多硬编码值和可能(可能)不正确的方法:

import Cocoa

class ViewController: NSViewController {
    
    let cyanView = NSView()
    let shadowView = NSImageView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.wantsLayer = true
        if let myLayer = view.layer {
            myLayer.backgroundColor = NSColor.gray.cgColor
        }
        
        cyanView.wantsLayer = true
        if let myLayer = cyanView.layer {
            myLayer.backgroundColor = NSColor.cyan.cgColor
        }
        
        // let's use constraints
        [shadowView, cyanView].forEach { v in
            v.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(v)
        }
        
        NSLayoutConstraint.activate([
            
            cyanView.widthAnchor.constraint(equalToConstant: 400.0),
            cyanView.heightAnchor.constraint(equalToConstant: 200.0),
            cyanView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            cyanView.topAnchor.constraint(equalTo: view.topAnchor, constant: 80.0),
            
            shadowView.widthAnchor.constraint(equalTo: cyanView.widthAnchor, multiplier: 1.5),
            shadowView.heightAnchor.constraint(equalToConstant: 80.0),
            shadowView.topAnchor.constraint(equalTo: cyanView.bottomAnchor, constant: 0.0),
            shadowView.centerXAnchor.constraint(equalTo: cyanView.centerXAnchor),
            
        ])
        
        let recognizer = NSClickGestureRecognizer(target: self, action: #selector(clickView(_:)))
        view.addGestureRecognizer(recognizer)
        
    }
    
    override func viewDidLayout() {
        super.viewDidLayout()
        
        // create a blurred oval image for the shadowView
        let img = NSImage.init(color: .black, size: shadowView.frame.size).oval()
        
        guard let tiffRep = img.tiffRepresentation,
              let blurFilter = CIFilter(name: "CIGaussianBlur")
        else { return }
        let inputImage = CIImage(data: tiffRep)
        blurFilter.setDefaults()
        blurFilter.setValue(inputImage, forKey: kCIInputImageKey)
        blurFilter.setValue(NSNumber(value: 40.0), forKey: "inputRadius")
        guard let outputImage = blurFilter.value(forKey: kCIOutputImageKey) as? CIImage else { return }
        let outputImageRect = NSRectFromCGRect(outputImage.extent)
        let blurredImage = NSImage(size: outputImageRect.size)
        blurredImage.lockFocus()
        outputImage.draw(at: .zero, from: outputImageRect, operation: .copy, fraction: 1.0)
        blurredImage.unlockFocus()
        
        shadowView.image = blurredImage.resize(to: shadowView.bounds.size)
    }
    
    @objc func clickView(_ sender: NSClickGestureRecognizer) {
        
        let img = view.imageRepresentation()
        
        // do something with the image
        
        print("clicked")
        
    }
    
    override var representedObject: Any? {
        didSet {
            // Update the view, if already loaded.
        }
    }
    
}
extension NSView {
    
    func imageRepresentation() -> NSImage? {
        if let bitRep = self.bitmapImageRepForCachingDisplay(in: self.bounds) {
            bitRep.size = self.bounds.size
            self.cacheDisplay(in: self.bounds, to: bitRep)
            let image = NSImage(size: self.bounds.size)
            image.addRepresentation(bitRep)
            return image
        }
        return nil
    }

}

extension NSImage {
    
    func resize(to size: NSSize) -> NSImage {
        return NSImage(size: size, flipped: false, drawingHandler: {
            self.draw(in: $0)
            return true
        })
    }

    convenience init(color: NSColor, size: NSSize) {
        self.init(size: size)
        lockFocus()
        color.drawSwatch(in: NSRect(origin: .zero, size: size))
        unlockFocus()
    }
    
    func oval(in rect: CGRect) -> NSImage {
        let image = NSImage(size: size)
        image.lockFocus()
        
        NSGraphicsContext.current?.imageInterpolation = .high
        NSBezierPath(ovalIn: rect).addClip()
        draw(at: rect.origin, from: rect, operation: .sourceOver, fraction: 1)
        
        image.unlockFocus()
        return image
    }
    
    func oval() -> NSImage {
        return oval(in: NSRect(origin: .zero, size: size))
    }
    
}

Output when running: Output 运行时:

在此处输入图像描述

Result of let img = view.imageRepresentation() : let img = view.imageRepresentation()的结果:

在此处输入图像描述

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM