[英]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:一种选择 - 可能适合也可能不适合:
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.