0

I'm using Xamarin/MONO to write a MacOS application, and I'd like to screenshot an image from the screen and use it alongside other image libraries in .NET

I've found I can use the native system libs like so

[DllImport(Constants.CoreGraphicsLibrary)]
static extern IntPtr CGWindowListCreateImage(CGRect screenBounds, CGWindowListOption windowOption, uint windowID, CGWindowImageOption imageOption);

however once I have the CGImage intptr, it isn't a lot of good to me as I want to do some System.Drawing stuff to it that I've already implemented for other platforms. Is there a way I can use the IntPtr and somehow get a System.Drawing image? or perhaps save the CGImage intptr to disk and load it using System.Drawing?

SushiHangover
  • 73,120
  • 10
  • 106
  • 165
Richthofen
  • 2,076
  • 19
  • 39

1 Answers1

0

You can use CGImageDestination to create a png/jpeg representation of your CGImage either in a file, NSMutableData, or CG consumer.

If your C# library can act upon a sequence/array of bytes, then instead of saving the file to disk, you can use the resulting NSMutableData (via GetBytes) to copy the PNG/JPEG-formatted bytes to a buffer for you to slice and dice in memory...

var cgImagePtr = CGWindowListCreateImage(new CGRect(0, 0, 100, 100), CGWindowListOption.All, 0, CGWindowImageOption.Default);
using (var url = new NSUrl("file", "localhost", "/Users/Sushi/Desktop/someFileName.png"))
using (var cgImage = new CGImage(cgImagePtr))
using (var destination = CGImageDestination.Create(url, "public.png", 1)) 
{
    destination.AddImage(cgImage);
    destination.Close();
}
cgImagePtr = null;

Note: Do not use a UTType as your typeIdentifier, your CGImageDestination will always by null, use "public.png", "public.jpeg", etc... (These are case sensitive)

SushiHangover
  • 73,120
  • 10
  • 106
  • 165
  • "If your C# library can act upon a sequence/array of bytes, then instead of saving the file to disk, you can use the resulting NSMutableData (via GetBytes) to copy the PNG/JPEG-formatted bytes to a buffer for you to slice and dice in memory..." This is exactly the approach I want to take, I'm just unsure how to proceed. If I could get from the IntPtr `CGWindowListCreateImage` returns to a C# ByteArray I would be able to take it from there. Problem is my project is dual platform and I can't use NSFoundation classes or any MonoMac stuff directly. – Richthofen Nov 07 '18 at 21:18
  • `Problem is my project is dual platform and I can't use NSFoundation classes or any MonoMac stuff directly` If you are not using `Xamarin.Mac`(?) and just pure Mono via Interop(?), you would have to marshal/pin the IntPtr in memory and decode/transform the memory structure of a `CGImage`, not something very fun (or productive). You could also Interop to the ObjectiveC runtime via messaging and manually call the ObjC methods (it is all done via messaging, not direct method/pointer invocation), ugly, but doable, you can look at the Xamarin.iOS/Xamarin.Mac code based to see how this is done. – SushiHangover Nov 07 '18 at 21:28
  • @Richthofen You could add at least a `Xamarin.Mac`-based library and use dependency injection to load it when you are on a macOS platform. Personally I would use Xamarin.Mac as the shell application/container and reference your cross-platform library(s) within that. – SushiHangover Nov 07 '18 at 21:30
  • @Richthofen As an alternative, you could shell out to a process and invoke a custom AppleScript to take your screenshot and then load that into your cross-platform app, but I do not know what your actual application requirements are, just that you are doing a screen snapshot. – SushiHangover Nov 07 '18 at 21:33
  • Oh man, if you only knew. We are already shelling out to `screencapture` but that actually fails due to some windows being captured transparently (and not all the time!). So in using the native coregraphics libs to try to capture the window, we're trying to get 'closer to the metal' and see if the transparent captures are a problem with MacOS in general or something more specific. I've read that screencapture uses CGWindowListCreateImage under the covers so i'm trying to consume it directly to find out. – Richthofen Nov 07 '18 at 21:45
  • 1
    @Richthofen As a test then, create a Xamarin.Mac application and include the code in my answer within a NSButton.Activated EventHandler as a quick test for your issue (Note: You might need to play with the flags; `CGWindowListOption` & `CGWindowImageOption` in order to meet your needs). And if you do that, you can directly use the Xamarin helper `CGImage.ScreenImage` to capture the screen to a CGImage (vs using the Interop call to CGWindowListCreateImage). – SushiHangover Nov 07 '18 at 21:49