メインメニューを開く

目次

概要編集

Xamarin.MacMonoMacMac OS Xのスクリーンキャプチャを撮る。

これができるとHyperDockみたいなアプリを実現できるかもしれない。買った方が安いけどね。

職業プログラマであればエビデンスを取得するのに活躍するかもしれないが、そんなフリーソフトはすでに山のようにある。 しかも、猫も杓子もウェブアプリかウェブAPIとスマホアプリばかりで、Windowsの案件ですら壊滅している現状でMacのアプリなど需要あるのかって話でもある。 そんな細かいことを気にしてはならない。 MonoMacが流行ればそれでよし。

実装:スクリーン全体のキャプチャを撮る編集

スクリーン全体のキャプチャを撮るにはCore GraphicsフレームワークのCGDisplayCreateImageメソッドを用いる。 Xamarin.Mac (Classic API)にはCGDisplay◯◯◯◯系のAPIがないようなのでP/Invokeで直叩きしている。

このメソッドに渡すスクリーンIDはNSScreen.DeviceDescription["NSScreenNumber"]から簡単に取得できる。 本来であればCGGetActiveDisplayListメソッドで取得するらしいが直叩きするのは面倒なので上記でいいと思われる。

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using MonoMac.Foundation;
using MonoMac.AppKit;
using MonoMac.CoreGraphics;

namespace ScreenCapture
{
    public  static class CGDisplay
    {
        public const string DllName = "/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics";

        [DllImport(DllName)]
        extern static IntPtr CGDisplayCreateImage(UInt32 displayId);

        [DllImport(DllName)]
        extern static void CFRelease(IntPtr handle);

        public static CGImage CreateImage(UInt32 displayId)
        {
            IntPtr handle = IntPtr.Zero;

            try
            {
                handle = CGDisplayCreateImage(displayId);
                return new CGImage(handle);
            }
            finally
            {
                if (handle != IntPtr.Zero)
                {
                    CFRelease(handle);
                }
            }
        }
    }

    class MainClass
    {
        static void Main(string[] args)
        {
            NSApplication.Init();

            var id1 = (NSNumber)NSScreen.MainScreen.DeviceDescription["NSScreenNumber"];

            using (var cgImage = CGDisplay.CreateImage(id1.UInt32Value))
            using (var nsImage = new NSImage(cgImage, new SizeF(cgImage.Width, cgImage.Height)))
            using (var nsdata = nsImage.AsTiff())
            {
                nsdata.Save("/tmp/ScreenCapture.tiff", atomically:true);
            }
        }
    }
}

実装:ウインドウのキャプチャを撮る編集

Xamarin.Macには標準でCGImageクラスにScreenImageという便利な静的メソッドが用意されている。 これを呼ぶだけでウインドウのキャプチャを得ることができる。

public static CGImage ScreenImage(int windownumber, RectangleF bounds);

引数windownumberは以下の方法で取得した連想配列の「kCGWindowNumber」を使用している。 こちらはXamarin.Mac (Classic API)にはCGWindow◯◯◯◯系のAPIがないようなのでP/Invokeで直叩きしている。 P/Invokeしなくても取得できる方法があるのかもしれないが知らない。

引数boundsは取得する範囲を指定する。ウインドウ全体をキャプチャするには「RectangleF.Empty」を指定する。 Xamarin.Mac (Unified API)ではRectangleFはCGRectに置き換えられているので適宜読み替えること。

var cgImage = CGImage.ScreenImage(windowNumber, RectangleF.Empty)

以下に開いているウインドウを列挙してキャプチャするサンプルをメモしておく。

using System;
using System.Runtime.InteropServices;
using System.Drawing;
using MonoMac.Foundation;
using MonoMac.AppKit;
using MonoMac.CoreGraphics;

public static class CGWindow
{
    public const string DllName = "/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics";

    [DllImport(DllName)]
    extern static IntPtr CGWindowListCopyWindowInfo(CGWindowListOption option, UInt32 relativeToWindow);

    [DllImport(DllName)]
    extern static void CFRelease(IntPtr cf);

    public static NSDictionary[] GetWindowList(CGWindowListOption option, UInt32 relativeToWindow = 0)
    {
        IntPtr handle = IntPtr.Zero;

        try
        {
            handle = CGWindow.CGWindowListCopyWindowInfo(option, relativeToWindow);
            return NSArray.ArrayFromHandle<NSDictionary>(handle);
        }
        finally
        {
            if (handle != IntPtr.Zero)
            {
                CFRelease(handle);
            }
        }
    }
}

class MainClass
{
    static void Main(string[] args)
    {
        NSApplication.Init();

        var windows = CGWindow.GetWindowList(CGWindowListOption.All);
        foreach (var window in windows)
        {
            Console.WriteLine(window);

            var isOnscreen = window["kCGWindowIsOnscreen"];
            if (isOnscreen != null)
            {
                var windowName   = ((NSString)window["kCGWindowName"]).ToString();
                var windowNumber = ((NSNumber)window["kCGWindowNumber"]).Int32Value;

                using (var cgImage = CGImage.ScreenImage(windowNumber, RectangleF.Empty))
                using (var nsImage = new NSImage(cgImage, new SizeF(cgImage.Width, cgImage.Height)))
                using (var nsData = nsImage.AsTiff())
                {
                    var error = null as NSError;
                    var filename = string.Format("/tmp/img_{0:D5}_{1}.tiff", windowNumber, windowName);
                    nsData.Save(file: filename, auxiliaryFile: false, error: out error);
                }
            }
        }
    }
}

関連項目編集

参考文献編集