WindowsのクリップボードにPNGをコピペする ~Python x win32clipboardでPNGコピペする方法を調査してみた~
こんにちはRcatです。
今回はPythonのwin32clipboardライブラリを使って、Windowsのクリップボード(コピペ)についてみていきます。
発端はあるプログラムで出来上がった画像を保存ではなく、コピーしてすぐに使えるようにしたい。
自分で調べたり、チャッピーに聞いたりするとちゃんと動くコードが出力されるが背景が白かったり黒かったりする。つまり透過情報が失われる。
意外とPNGをそのままコピペする方法は調べても出てこなかったので、色々と工夫してみましたので、記事にしてみます。
はじめに
利用規約
情報や作品の活用時は事前に利用規約をご確認ください。
コメントについて
利用規約のガイドラインを確認の上コメントしてください
概要
概要
いつもパワーポイントを使って背景を透過した画像などをコピーして、いろんな資料で使い回すことが多かったが、同じことをPythonでやろうとしたらつまずいてしまった。具体的には背景の透明度情報が失われてしまい白または黒になってしまう。
例えばチャッピーに聞いたのと同じような情報しか出てこず、どうしようもなかったのでもう自分で考えることにした。

ちなみに、このコードの場合は画像をビットマップとして保存し、それをクリップボードにそのままコピーしている。ちなみにビットマップは直接コピーが可能。
ただし、ビットマップは背景透過をサポートしていないので、この時点でその情報が失われてしまう。そういうことを指摘しても治らないどころか、動かないコードが出力される始末。つまりそういった情報は出回っていない=チャッピーは知らないと推測される。
結局、PNGコピペはできるのできないの?
できます。
今回色々調査してやり方がわかりました。
ざっと調べても出てこないので、こういうことしてる人はいないのかな?それとも調べ方が悪いのか。
というわけで、この記事では私が色々やった結果をまとめています。
少なくともパワポに対してコピペすることと、ブラウザに対して貼り付けることは動作確認が取れています。
前提知識の確認
Pythonで画像を貼り付けするには?
そもそもPythonを使ってグリップボードから画像を取得する方法について見ていきます。これを知らないとこの先意味が分かりません。
貼り付けと表現しているのは、クリップボードから画像を取得すること。つまりPythonに貼り付けるという意味で使っています。
というわけで調べたらすぐに出てくる一般的なコードを提示します。
まず最初にライブラリをインストールしてください。
クリップボードを扱うライブラリと画像を使うライブラリです。
pip install pywin32 pillowimport io
import win32clipboard
from PIL import Image
def CopySave():
try:
win32clipboard.OpenClipboard()
data = win32clipboard.GetClipboardData(win32clipboard.CF_DIB)
except:
print("取得に失敗しました")
finally:
win32clipboard.CloseClipboard()
img = Image.open(io.BytesIO(data))
img.save("test.png")このコードを使うことで、コピーされた画像をRGB形式で取得することができます。ここで大事なのがRGBAではないことです。
今回の悩みポイントの1つです。
ちなみに、ここではパワーポイントに配置した図形オブジェクトを選択した状態で実行しています。

Pythonで画像をコピーするには?
次にPythonからクリップボードに画像を書き込むことをコピーとして説明します。
こちらも一般的なコードを提示します。
import io
import win32clipboard
from PIL import Image
def WriteClipBoard():
img = Image.open("tmp.png")
output = io.BytesIO()
img.save(output, 'BMP')
data = output.getvalue()[14:]
win32clipboard.OpenClipboard()
try:
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data)
except:
print("書き込みに失敗しました")
finally:
win32clipboard.CloseClipboard()このコードは画像を開いてビットマップとして保存後コピーしています。ヘッダーはスキップする必要があるようで、14バイトはスキップしています。


いやいや保存形式BMPじゃそりゃなくなるだろ。PNGにしろよと思って以下のように書き換えたくなると思いますが、結果はこちら
def WriteClipBoard():
img = Image.open("tmp.png")
output = io.BytesIO()
img.save(output, 'PNG')
win32clipboard.OpenClipboard()
try:
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardData(win32clipboard.CF_DIB, output.getvalue())
except:
print("書き込みに失敗しました")
finally:
win32clipboard.CloseClipboard()PowerPointに貼り付けようとするとこうなります。
それ以外ではそもそも貼り付けすら行えなくなります。
CF_DIBをCF_DIBV5にしても同じです。こちらにするとパワポでも貼り付けすることすらできなくなります。

つまり、無理やりPNGでコピペすることはそう簡単にはいかないというのが結論です。
どうしたらPNGをコピペできるか調査する
しかし、現実問題パワーポイントで選択した図形を貼り付けると、図形以外の部分は透過されて貼り付けることができます。
なので実際はPNGないしは他のフォーマットで背景の透過はサポートされていることになります。
というわけでこれを切り口に調べていきます。
調べ方
調べ方は単純です。
クリップボードの中に何がコピーされているのかを調べまくります。
Pythonのクリップボードライブラリは、WindowsのAPIそのものを持ってきているようで、Microsoftのドキュメントとほぼ同じ扱いが可能なようです。
そして、その中にはコピーされているオブジェクトが何なのか調べる関数があることがわかりました。
というわけで調べるソースコードがこちら
これを使うとフォーマットのIDと形式を取得することができます。

ちなみにクリップボードには形式というものがあり、標準形式とオリジナル形式があります。
標準形式は以下に記載されているので、興味のある方は見てみてください。上で使った"CF_DIB"のようなものが標準形式です。
PowerPointのオブジェクトをコピーした時
まず調査するのがこちらパワーポイントです。
以下のようにPowerPointで図形を置いて、その2つを選択してから貼り付け直すと画像として貼り付けることもできますよね。
それどういう仕組みなんだろう?ということで中身見てみました。

解析の結果はこちらです。上記関数に加え、中身のデータも表示できるようにしてみました。長すぎるデータは20文字でカットされています。あと、なんかエラーになってるのもありますが、まあそこは無視しましょう。
このうち"CF_"で、始まらないデータはアプリケーション特有のデータフォーマットになります。ほとんどオリジナルじゃないですか…。
ID:49161 Name:DataObject Value:b'8\x08\x0f\x00'
ID:49401 Name:Preferred DropEffect Value:b'\x00\x00\x00\x00'
ID:49406 Name:InShellDragLoop Value:b'\x00\x00\x00\x00'
ID:49166 Name:Object Descriptor Value:b'd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
ID:49893 Name:PowerPoint 12.0 Internal Theme Value:b',\x1a\xec%'
ID:49894 Name:PowerPoint 12.0 Internal Color Scheme Value:b'0\x1a\xec%'
ID:49877 Name:PowerPoint 12.0 Internal Shapes Value:b'|\x18\xec%'
ID:49819 Name:Art::GVML ClipFormat Value:b'PK\x03\x04\x14\x00\x06\x00\x08\x00\x00\x00!\x00\xbb\xe5H\x94\x05\x01'
ID:49757 Name:image/svg+xml Value:b'<svg width="455" hei'
ID:49355 Name:PNG Value:b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x02\xc8'
ID:49354 Name:JFIF Value:b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00x\x00x\x00\x00'
ID:49353 Name:GIF Value:b'GIF89a\xc7\x01u\x01p\x00\x00!\xf9\x04\x05\x00\x00\n'
ID:2 Name:CF_BITMAP Value:1510281192
ID:14 Name:CF_ENHMETAFILE Value:b'\x01\x00\x00\x00l\x00\x00\x00\xe3\xff\xff\xff\xe3\xff\xff\xff<\x0b\x00\x00'
2025-03-30 14:54:06,471 ERROR クリップボード (6, 'GetClipboardData:GetMetafileBitsEx(NULL)', 'ハンドルが無効です。')
ID:3 Name:CF_METAFILEPICT Value:None
ID:49171 Name:Ole Private Data Value:b'\x00\x00\x00\x00\xf8\x01\x00\x00\x01\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00'
ID:8 Name:CF_DIB Value:b'(\x00\x00\x00\xc7\x01\x00\x00u\x01\x00\x00\x01\x00 \x00\x03\x00\x00\x00'
ID:17 Name:CF_DIBV5 Value:b'|\x00\x00\x00\xc7\x01\x00\x00u\x01\x00\x00\x01\x00 \x00\x03\x00\x00\x00'で、この中で目を引くのが真ん中らへんにある"PNG"です。データ見ると思いっきり0x89PNGから始まっています。これPNGデータのヘッダーです。つまり生のデータが入っているということで、希望が見えてきました。それ以外にもGIFやSVGなどもあるみたいですね。
なるほど、オフィスで"形式を選択して貼り付け"という機能がありますが、こういうたくさんの形式でコピーされているからこそできることなんですね。
切り取り&スケッチでコピーした場合
次に便利機能Windowsキー+シフトキー+Sのスクショ機能を使った場合です。これスクショした瞬間コピーされるんですが、その中身を見てみました。
ID:49161 Name:DataObject Value:b'\x00\x00\x00\x00\x00\x00\x00\x00'
ID:2 Name:CF_BITMAP Value:331567
ID:49355 Name:PNG Value:b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x02\xc0'
ID:49771 Name:CanUploadToCloudClipboard Value:b'\x01\x00\x00\x00'
ID:49772 Name:CanIncludeInClipboardHistory Value:b'\x01\x00\x00\x00'
ID:49171 Name:Ole Private Data Value:b'\x00\x00\x00\x008\x01\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00'
ID:8 Name:CF_DIB Value:b'(\x00\x00\x00\xc0\x02\x00\x00\t\x02\x00\x00\x01\x00 \x00\x03\x00\x00\x00'
ID:17 Name:CF_DIBV5 Value:b'|\x00\x00\x00\xc0\x02\x00\x00\t\x02\x00\x00\x01\x00 \x00\x03\x00\x00\x00'なんとこちらにもPNGの表記があります。双方で同じものが入っているので、多分これがデータで間違いないと思われます。
クリップボードからPythonにPNG形式で読み書きする
続きはこちら
いいなと思ったら応援しよう!
情報が役に立ったと思えば、僅かでも投げ銭していただけるとありがたいです。