I have an AppleScriptObjC applet that imports an image into an NSImageView, overlays an NSVisualEffectView, and then adds both views to an NSView. This all works fine. My problem arises when attempting to save the NSView to disk as a PNG; using NSBitmapImageRep a PNG is created but it only contains the NSImage with no NSVisualEffectView overlay. I’m assuming that using NSBitmapImageRep is the root of the problem, but despite much Googling I’m stumped as to how to successfully export an NSView that includes an NSVisualEffectView.
This is the handler I’m currently using to save the NSView (named “theContainerView”):
on saveImage()
theContainerView's lockFocus()
set theRep to (NSBitmapImageRep's alloc()'s initWithFocusedViewRect:{{0, 0}, {600, 600}})
theContainerView's unlockFocus()
set theData to (theRep's representationUsingType:NSPNGFileType |properties|:(missing value))
set classicPath to (choose file name default location (path to desktop) default name "Saved.png")
set posixPath to POSIX path of classicPath
theData's writeToFile:posixPath atomically:true
end saveImage
Thanks for your reply, Shane. I’m using NSVisualEffectView to overlay a semi-transparent blurred band across an image, with the band containing text that has NSAppearance properties applied to it. Ultimately I want to export the NSView as a bitmap image that I can then use in a design project. Here’s my work-in-progress script:
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"
property NSFont : a reference to current application's NSFont
property NSView : a reference to current application's NSView
property NSColor : a reference to current application's NSColor
property NSImage : a reference to current application's NSImage
property NSButton : a reference to current application's NSButton
property NSWindow : a reference to current application's NSWindow
property NSTextView : a reference to current application's NSTextView
property NSImageView : a reference to current application's NSImageView
property NSAppearance : a reference to current application's NSAppearance
property NSPNGFileType : a reference to current application's NSPNGFileType
property NSVisualEffectView : a reference to current application's NSVisualEffectView
property NSBitmapImageRep : a reference to current application's NSBitmapImageRep
property NSFontWeightHeavy : a reference to current application's NSFontWeightHeavy
property NSTextAlignmentCenter : a reference to current application's NSTextAlignmentCenter
property NSBackingStoreBuffered : a reference to current application's NSBackingStoreBuffered
property theContainerView : missing value
# define image
set theImagePath to POSIX path of (path to desktop folder) & "Sunflower.png"
set theImage to (NSImage's alloc()'s initWithContentsOfFile:theImagePath)
# define image view
set theImageView to (NSImageView's alloc()'s initWithFrame:{{0, 0}, {600, 600}})
theImageView's setImage:theImage
# define text view
set theTextView to (NSTextView's alloc's initWithFrame:{{10, 0}, {580, 105}})
theTextView's setFont:(NSFont's systemFontOfSize:80 weight:NSFontWeightHeavy)
theTextView's setAlignment:NSTextAlignmentCenter
theTextView's setTextColor:(NSColor's tertiaryLabelColor)
theTextView's setAppearance:(NSAppearance's vibrantLightAppearance)
theTextView's setAllowsVibrancy:true
theTextView's setBackgroundColor:(NSColor's clearColor)
theTextView's setString:"Sunflower"
theTextView's setEditable:false
# define overlay view & add text view
set theOverlayView to (NSVisualEffectView's alloc()'s initWithFrame:{{0, 250}, {600, 100}})
theOverlayView's setBlendingMode:1
theOverlayView's setMaterial:1 # light material
theOverlayView's setState:1
theOverlayView's addSubview:theTextView
# define container view & add image view & overlay view
set theContainerView to (NSView's alloc()'s initWithFrame:{{0, 38}, {600, 600}})
theContainerView's addSubview:theImageView
theContainerView's addSubview:theOverlayView
# define 'Save Image' button
set theButton to (NSButton's alloc()'s initWithFrame:{{246, 5}, {110, 25}})
theButton's setTitle:"Save Image"
theButton's setBezelStyle:1
theButton's setTarget:me
theButton's setAction:"saveImage"
# define window & add container view & button view
set theWindow to (NSWindow's alloc()'s initWithContentRect:{{0, 0}, {600, 638}} styleMask:3 backing:NSBackingStoreBuffered defer:true)
theWindow's |center|()
theWindow's setTitle:"Sunflower"
theWindow's contentView()'s addSubview:theContainerView
theWindow's contentView()'s addSubview:theButton
theWindow's orderFrontRegardless()
theWindow's makeKeyAndOrderFront:me
on saveImage()
theContainerView's lockFocus()
set theRep to (NSBitmapImageRep's alloc()'s initWithFocusedViewRect:{{0, 0}, {600, 600}})
theContainerView's unlockFocus()
set theData to (theRep's representationUsingType:NSPNGFileType |properties|:(missing value))
set classicPath to (choose file name default location (path to desktop) default name "Saved.png")
set posixPath to POSIX path of classicPath
theData's writeToFile:posixPath atomically:true
end saveImage
Up to now I’ve been cheating by simply taking a screenshot, but I was wondering/hoping that there was a more legitimate/efficient way of saving the NSView and its contents as an image.
I did some change in your code and use pdf as output… maybe not what your want but I was able to output a image from NSView… from the NSVisualEffectView method you used.
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"
property NSFont : a reference to current application's NSFont
property NSView : a reference to current application's NSView
property NSColor : a reference to current application's NSColor
property NSImage : a reference to current application's NSImage
property NSButton : a reference to current application's NSButton
property NSWindow : a reference to current application's NSWindow
property NSTextView : a reference to current application's NSTextView
property NSImageView : a reference to current application's NSImageView
property NSAppearance : a reference to current application's NSAppearance
property NSPNGFileType : a reference to current application's NSPNGFileType
property NSVisualEffectView : a reference to current application's NSVisualEffectView
property NSBitmapImageRep : a reference to current application's NSBitmapImageRep
property NSFontWeightHeavy : a reference to current application's NSFontWeightHeavy
property NSTextAlignmentCenter : a reference to current application's NSTextAlignmentCenter
property NSBackingStoreBuffered : a reference to current application's NSBackingStoreBuffered
property theContainerView : missing value
# define image
set theImagePath to POSIX path of (path to desktop folder) & "Sunflower.png"
set theImage to (NSImage's alloc()'s initWithContentsOfFile:theImagePath)
# define image view
set theImageView to (NSImageView's alloc()'s initWithFrame:{{0, 0}, {600, 600}})
theImageView's setImage:theImage
# define text view
set theTextView to (NSTextView's alloc's initWithFrame:{{10, 0}, {580, 105}})
theTextView's setFont:(NSFont's systemFontOfSize:80 weight:NSFontWeightHeavy)
theTextView's setAlignment:NSTextAlignmentCenter
theTextView's setTextColor:(NSColor's tertiaryLabelColor)
theTextView's setAppearance:(NSAppearance's vibrantLightAppearance)
theTextView's setAllowsVibrancy:true
theTextView's setBackgroundColor:(NSColor's clearColor)
theTextView's setString:"Sunflower"
theTextView's setEditable:false
# define overlay view & add text view
set theOverlayView to (NSVisualEffectView's alloc()'s initWithFrame:{{0, 250}, {600, 100}})
theOverlayView's setBlendingMode:(current application's NSVisualEffectBlendingModeBehindWindow)
theOverlayView's setMaterial:1 # light material
theOverlayView's addSubview:theTextView
# define container view & add image view & overlay view
set theContainerView to (NSView's alloc()'s initWithFrame:{{0, 38}, {600, 600}})
theContainerView's addSubview:theImageView
theContainerView's addSubview:theOverlayView
# define 'Save Image' button
set theButton to (NSButton's alloc()'s initWithFrame:{{246, 5}, {110, 25}})
theButton's setTitle:"Save Image"
theButton's setBezelStyle:1
theButton's setTarget:me
theButton's setAction:"saveImage:"
# define window & add container view & button view
set theWindow to (NSWindow's alloc()'s initWithContentRect:{{0, 0}, {600, 638}} styleMask:3 backing:NSBackingStoreBuffered defer:true)
theWindow's |center|()
theWindow's setTitle:"Sunflower"
theWindow's contentView()'s addSubview:theContainerView
theWindow's contentView()'s addSubview:theButton
theWindow's orderFrontRegardless()
theWindow's makeKeyAndOrderFront:me
on saveImage:sender
-- theContainerView's lockFocus()
-- set theRep to (NSBitmapImageRep's alloc()'s initWithFocusedViewRect:{{0, 0}, {600, 600}})
-- theContainerView's unlockFocus()
set theData to theContainerView's dataWithPDFInsideRect:(theContainerView's |bounds|())
theData's writeToFile:(POSIX path of (path to desktop) & "saved.pdf") atomically:true
end saveImage:
I notice that you changed the NSVisualEffectBlendingMode to BehindWindow; however this needs to be set to WithinWindow for the NSVisualEffectView to be applied to the image rather than the window’s background, and doing so produces a PDF that, like my own attempt, is missing the text and NSVisualEffectView overlay.
If I run your script as is, I get a PDF that does have the text (although without the NSAppearance effect), but is still missing the NSVisualEffectView overlay.
I’m beginning to think that perhaps Shane is correct in his theory that the NSVisualEffectView is never actually applied to the image and exists only as interface “decoration” as it were, and thus cannot be written to a file. In my research I came across a StackOverflow post that said the following:
The problem with NSVisualEffectView is that it does not draw anything. Instead, it defines a region that tells the WindowServer process to do its vibrancy stuff in that region. This happens after your app draws its views. Therefore a compositing operation in your app cannot take into account the pixels that the window server draws later. In short, this cannot be done.
Just a digression from the problem you’ve already outlined: I was getting the error “NSWindow drag regions should only be invalidated on the Main Thread!” and was able to fix the issue by tweaking the code with a try statement and enclosing the view hierarchy code within a handler
***
property params : missing value
if current application's NSThread's isMainThread() as boolean then
my runCode:params
else
my performSelectorOnMainThread:"runCode:" withObject:params waitUntilDone:true
end if
on runCode:params
# define image, imageView, textView, overlay, button, container, window, etc as above
end runCode:
on saveImage:sender
***
end saveImage: