見出し画像

WindowsのクリップボードにPNGをコピペする ~Python x win32clipboardでPNGコピペする方法を調査してみた~

こんにちはRcatです。
今回はPythonwin32clipboardライブラリを使って、Windowsのクリップボード(コピペ)についてみていきます。
発端はあるプログラムで出来上がった画像を保存ではなく、コピーしてすぐに使えるようにしたい
自分で調べたり、チャッピーに聞いたりするとちゃんと動くコードが出力されるが背景が白かったり黒かったりする。つまり透過情報が失われる
意外とPNGをそのままコピペする方法は調べても出てこなかったので、色々と工夫してみましたので、記事にしてみます。



はじめに

利用規約

情報や作品の活用時は事前に利用規約をご確認ください。

コメントについて

利用規約のガイドラインを確認の上コメントしてください


概要

概要

いつもパワーポイントを使って背景を透過した画像などをコピーして、いろんな資料で使い回すことが多かったが、同じことをPythonでやろうとしたらつまずいてしまった。具体的には背景の透明度情報が失われてしまい白または黒になってしまう。

例えばチャッピーに聞いたのと同じような情報しか出てこず、どうしようもなかったのでもう自分で考えることにした。

ちなみに、このコードの場合は画像をビットマップとして保存し、それをクリップボードにそのままコピーしている。ちなみにビットマップは直接コピーが可能。
ただし、ビットマップは背景透過をサポートしていないので、この時点でその情報が失われてしまう。そういうことを指摘しても治らないどころか、動かないコードが出力される始末。つまりそういった情報は出回っていない=チャッピーは知らないと推測される。

結局、PNGコピペはできるのできないの?

できます
今回色々調査してやり方がわかりました。
ざっと調べても出てこないので、こういうことしてる人はいないのかな?それとも調べ方が悪いのか。
というわけで、この記事では私が色々やった結果をまとめています。

少なくともパワポに対してコピペすることと、ブラウザに対して貼り付けることは動作確認が取れています。


前提知識の確認

Pythonで画像を貼り付けするには?

そもそもPythonを使ってグリップボードから画像を取得する方法について見ていきます。これを知らないとこの先意味が分かりません。

貼り付けと表現しているのは、クリップボードから画像を取得すること。つまりPythonに貼り付けるという意味で使っています。

というわけで調べたらすぐに出てくる一般的なコードを提示します。

まず最初にライブラリをインストールしてください。
クリップボードを扱うライブラリと画像を使うライブラリです。

pip install pywin32 pillow
import 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形式で読み書きする

続きはこちら

いいなと思ったら応援しよう!

Rcat999 情報が役に立ったと思えば、僅かでも投げ銭していただけるとありがたいです。