Search code examples
unity-game-enginescreenshottransparentuser32

How to create a screenshot of a transparant Unity window + OS


So for an app we are trying to create we would like to turn the Unity window of a standalone app transparant(everything besides a couple of buttons) and have the user take a screenshot of their view/OS + the unity layer together.

So example: The user opens our application, clicks on a button and the whole unity window except for a couple of buttons turn transparant. The user can then use it's OS like normal, while the buttons mentioned earlier stay on top. The user can then click on of the buttons to create a screenshot of their OS, which we will then be saved to their system. This way we can for example show anything from within Unity(3D model, images) on top of the users OS, via a screenshot.

Currently, we can turn the window transparant with a simular setup like this: https://alastaira.wordpress.com/2015/06/15/creating-windowless-unity-applications/

That works fine, and so does clicking in windows etc. However, we would now like to create a screenshot and save it somewhere. For this we tried multiple things, and a couple of years back before we put this project aside, we got it working through a custom dll that uses the "using System.Drawing" code that we called from inside unity. See an example of this dll and code below.

using System.Drawing;

namespace ScreenShotDll
{
public class ScreenShotClass
{
    public static void TakeScreenShotRect(int srcX, int srcY, int dstX, int dstY) //both fullscreen screenshot and cropped rectangle screenshot
    {

        int width = Math.Abs(srcX - dstX);
        int height = Math.Abs(srcY - dstY);

        Bitmap memoryImage;
        memoryImage = new Bitmap(width, height);
        Size s = new Size(memoryImage.Width, memoryImage.Height);

        Graphics memoryGraphics = Graphics.FromImage(memoryImage);

        memoryGraphics.CopyFromScreen(srcX, srcY, 0, 0, s);

        string str = "";

        try
        {
            str = string.Format(AppDomain.CurrentDomain.BaseDirectory + @"Screenshot.png");
        }
        catch (Exception er)
        {
            Console.WriteLine("Sorry, there was an error: " + er.Message);
            Console.WriteLine();
        }

        memoryImage.Save(str);
    }

However this does not seem to work anymore. We are on the IL2CPP backend in Unity and get the error: NotSupportedException: System.Drawing.Bitmap

We are also trying to use the user32.dll from within Unity and using the GetPixel, ReleaseDC, and GetActiveWindow functions of this, as posted on a couple of forums, but all we get there is a white image.

Any ways to adjust our custom dll or any other way to do this would be highly appreciated. Please let me know if you need more information.


Solution

  • After a couple of days, I managed to resolve this from within Unity.

    I used the following code, to make a screenshot of the window, and save it to a bitmap. Then save it on my disk to a .png.

    [DllImport("user32.dll", SetLastError = true)] static extern int GetSystemMetrics(int smIndex);
    [DllImport("user32.dll", SetLastError = false)] static extern IntPtr GetDesktopWindow();
    [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleDC", SetLastError=true)] static extern IntPtr CreateCompatibleDC([In] IntPtr hdc);
    [DllImport("gdi32.dll", EntryPoint = "DeleteDC")] public static extern bool DeleteDC([In] IntPtr hdc);
    [DllImport("gdi32.dll", EntryPoint = "DeleteObject")] public static extern bool DeleteObject([In] IntPtr hObject);
    [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleBitmap")] static extern IntPtr CreateCompatibleBitmap([In] IntPtr hdc, int nWidth, int nHeight);
    [DllImport("gdi32.dll", EntryPoint = "SelectObject")] public static extern IntPtr SelectObject([In] IntPtr hdc, [In] IntPtr hgdiobj);
    [DllImport("gdi32.dll", EntryPoint = "BitBlt", SetLastError = true)] static extern bool BitBlt([In] IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, [In] IntPtr hdcSrc, int nXSrc, int nYSrc, uint dwRop);
    
    private void screenShot()
    {
        Debug.Log("In screenShot!");
        int nScreenWidth = GetSystemMetrics(0);
        int nScreenHeight = GetSystemMetrics(1);
        IntPtr hDesktopWnd = GetDesktopWindow();
        IntPtr hDesktopDC = GetDC(hDesktopWnd);
        IntPtr hCaptureDC = CreateCompatibleDC(hDesktopDC);
        IntPtr hCaptureBitmap = CreateCompatibleBitmap(hDesktopDC, nScreenWidth, nScreenHeight);
        SelectObject(hCaptureDC, hCaptureBitmap);
        BitBlt(hCaptureDC, 0, 0, nScreenWidth, nScreenHeight, hDesktopDC, 0, 0, 0x00CC0020 | 0x40000000);
    
        Bitmap bmp = Image.FromHbitmap(hCaptureBitmap);
    
        ImageConverter converter = new ImageConverter();
        byte[] bytes = (byte[])converter.ConvertTo(bmp, typeof(byte[]));
    
        string path = Application.dataPath;
        if (Application.platform == RuntimePlatform.OSXPlayer) {
            path += "/../../";
        }
        else if (Application.platform == RuntimePlatform.WindowsPlayer) {
            path += "/../";
        }
    
        File.WriteAllBytes(path + "Screenshot" + ".png", bytes);
    
        ReleaseDC(hDesktopWnd, hDesktopDC);
        DeleteDC(hCaptureDC);
        DeleteObject(hCaptureBitmap);
    }