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);
}
}
実行結果
つまづいた点
BITMAPINFOHEADER
BITMAPINFOHEADER
の後ろに画像データの配列を置いてあげればよさそうだったので、clipboard.cpp
にて下記のように初期化
BITMAPINFOHEADER* pData = (BITMAPINFOHEADER*)GlobalLock(hg);
hg
に確保しているサイズは同じなので問題ないかと思ったが左下に黒い画素が表示され、uvスクロールを行ったような画像となった
BITMAPINFOHEADER*
→BITMAPINFO*
にて解決
ScreenCapture.CaptureScreenshotAsTexture()
Texture2D tex = ScreenCapture.CaptureScreenshotAsTexture();
Gameウィンドウの表示状態に依存しているようで、Free Aspect
の際にはそれほど気にならないが余白部が画像データに含まれており、クロップされたような画像となる
詳細な原因は未調査