LoginSignup
4
0

More than 1 year has passed since last update.

【Unity】Texture2Dとクリップボード

Last updated at Posted at 2022-09-30

TL;DR

  • Win32APIを使ってクリップボードとTexture2D間で画像データを読み書きする
    • クリップボード->Texture2D
    • Texture2D->クリップボード

動機

適当に遊んでる時に画面のキャプチャを画像ファイルではなくクリップボードに書き込みたかった(DiscordやVSCode等に貼る為)

実装

画像の読み込みについては引用先にて詳細に説明されている為割愛します

clipboard.cpp
#include <Windows.h>
#define DLL_API extern "C" __declspec(dllexport)

DLL_API bool setClipboardImage(unsigned char* data, int width, int height) {
	bool result = false;
	if (OpenClipboard(NULL)) {
		int size = width * height * 4;

		HGLOBAL hg = GlobalAlloc(GMEM_MOVEABLE, sizeof(BITMAPINFOHEADER) + size);
		BITMAPINFO* pInfo = (BITMAPINFO*)GlobalLock(hg);

		pInfo->bmiHeader.biSize = sizeof(BITMAPINFO);
		pInfo->bmiHeader.biWidth = width;
		pInfo->bmiHeader.biHeight = height;
		pInfo->bmiHeader.biPlanes = 1;
		pInfo->bmiHeader.biBitCount = 32;
		pInfo->bmiHeader.biCompression = BI_RGB;
		pInfo->bmiHeader.biSizeImage = width * height * 4;
		pInfo->bmiHeader.biXPelsPerMeter = 0;
		pInfo->bmiHeader.biYPelsPerMeter = 0;
		pInfo->bmiHeader.biClrUsed = 0;
		pInfo->bmiHeader.biClrImportant = 0;

		memcpy(pInfo->bmiColors, data, size);
		GlobalUnlock(hg);

		EmptyClipboard();
		SetClipboardData(CF_DIB, hg);
		result = true;
	}
	CloseClipboard();
	return result;
}
Clipboard.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
//using UniHumanoid;
using UnityEngine;
using UnityEngine.UIElements;

public static class Clipboard {
    #region DLL
    [DllImport("libymtrplus")] private static extern bool hasClipboardImage();
    [DllImport("libymtrplus")] private static extern void getClipboardImageSize(ref int width, ref int height, ref int bitsPerPixel);
    [DllImport("libymtrplus")] private static extern bool getClipboardImage(IntPtr buffer);
    [DllImport("libymtrplus")] private static extern bool setClipboardImage(IntPtr data, int width, int height);
    #endregion

    public static bool GetClipboardImage(out Texture2D tex) {
        //  Get Info
        int width = 0, height = 0, bitsPerPixel = 0;
        getClipboardImageSize(ref width, ref height, ref bitsPerPixel);

        //  Check size
        if (width * height < 1) {
            throw new Exception("[Clipboard] Invalid image size");
        }
        int ch = bitsPerPixel / 8;
        if (ch != 3 && ch != 4) {
            throw new Exception("[Clipboard] Invalid image ch size");
        }

        //  Get Image
        byte[] buffer = new byte[width * height * ch];
        GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        IntPtr pBuffer = handle.AddrOfPinnedObject();
        bool success = getClipboardImage(pBuffer);
        handle.Free();

        //  Create texture
        if (success) {
            tex = new Texture2D(width, height, TextureFormat.BGRA32, false);
            if (ch == 4) {
                tex.LoadRawTextureData(buffer);
            } else if (ch == 3) {
                Color32[] cols = new Color32[width * height];
                for (int i = 0; i < cols.Length; i++) {
                    cols[i].b = buffer[ch * i + 0];
                    cols[i].g = buffer[ch * i + 1];
                    cols[i].r = buffer[ch * i + 2];
                    cols[i].a = 0xff;
                }
                tex.SetPixels32(cols);
                cols = null;
            }
            tex.Apply();
        } else {
            tex = null;
        }
        return success;
    }
    public static bool SetClipboardImage(Texture2D tex) {
        //  Prepare Data
        byte[] btex = tex.GetRawTextureData();
        for (int i = 0; i < btex.Length; i += 4) {
            //  RGBA -> BGRA
            byte tmp = btex[i + 0];
            btex[i + 0] = btex[i + 2];
            btex[i + 2] = tmp;
        }
        ScreenCapture.CaptureScreenshotAsTexture();
        //  Set Data
        IntPtr pData = Marshal.AllocHGlobal(btex.Length);
        Marshal.Copy(btex, 0, pData, btex.Length);
        return setClipboardImage(pData, tex.width, tex.height);
    }
}
Capture.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.InputSystem;

public class TestCapture : MonoBehaviour {
    [DllImport("libymtrplus")] private static extern bool hasClipboardImage();
    [DllImport("libymtrplus")] private static extern void getClipboardImageSize(ref int width, ref int height, ref int bitsPerPixel);
    [DllImport("libymtrplus")] private static extern bool getClipboardImage(IntPtr buffer);
    [DllImport("libymtrplus")] private static extern bool setClipboardImage(IntPtr data, int width, int height);

    void Update() {
        if (Keyboard.current.pKey.wasPressedThisFrame) {
            StartCoroutine(UpdateInput());
        }
    }
    IEnumerator UpdateInput() {
        yield return new WaitForEndOfFrame();

        //  Capture
        var tex = new Texture2D(Screen.width, Screen.height);
        tex.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);
        tex.Apply();

        //  Set clipboard
        Clipboard.SetClipboardImage(tex);
    }
}

実行結果

  • Unity Editor
    image.png

  • 取得画像(クリップボード内容)
    image.png

つまづいた点

BITMAPINFOHEADER

BITMAPINFOHEADERの後ろに画像データの配列を置いてあげればよさそうだったので、clipboard.cppにて下記のように初期化

BITMAPINFOHEADER* pData = (BITMAPINFOHEADER*)GlobalLock(hg);

hgに確保しているサイズは同じなので問題ないかと思ったが左下に黒い画素が表示され、uvスクロールを行ったような画像となった

BITMAPINFOHEADER*BITMAPINFO*にて解決

ScreenCapture.CaptureScreenshotAsTexture()

Texture2D tex = ScreenCapture.CaptureScreenshotAsTexture();

Gameウィンドウの表示状態に依存しているようで、Free Aspectの際にはそれほど気にならないが余白部が画像データに含まれており、クロップされたような画像となる

詳細な原因は未調査

Reference

4
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0