I'm trying to achieve the same thing that modern web browsers like Firefox and Chrome allow you to do. When you right click a transparent image on the web and then select "Copy Image", the image is then copied to your clipboard. So you can later paste it for example to Discord chat. And the transparency is preserved.
I want to do the same thing in Python 3. I want to be able to copy image stored on my computer (for example in .png format) to windows clipboard using python script and then paste it to Discord chat with the transparency preserved.
I found this post featuring the code below. But as I see in the code, the image is converted to RGB only and the alpha channel is lost.
from io import BytesIO
import win32clipboard
from PIL import Image
def send_to_clipboard(clip_type, data):
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardData(clip_type, data)
win32clipboard.CloseClipboard()
image = Image.open("test.png")
output = BytesIO()
image.convert("RGB").save(output, "BMP")
data = output.getvalue()[14:]
output.close()
send_to_clipboard(win32clipboard.CF_DIB, data)
Result I get by using the code from the post mentioned above:
Desired result:
I also tried saving the image as png like this: image.save(output, "PNG")
.
But that didn't work, discord crashed when I tried pasting the image to chat.
Next I tried using CF_HDROP
, hoping that discord desktop app would recognize it.
import ctypes
import pythoncom
import win32clipboard
from ctypes import wintypes
class DROPFILES(ctypes.Structure):
_fields_ = [("pFiles", wintypes.DWORD),
("pt", wintypes.POINT),
("fNC", wintypes.BOOL),
("fWide", wintypes.BOOL)]
path = r"D:\Visual Studio Code Projects\clipboard-test\test.png"
offset = ctypes.sizeof(DROPFILES)
size = offset + (len(path) + 1) * ctypes.sizeof(ctypes.c_wchar) + 1
buffer = (ctypes.c_char * size)()
df = DROPFILES.from_buffer(buffer)
df.pFiles = offset
df.fWide = True
wchars = (ctypes.c_wchar * (len(path) + 1)).from_buffer(buffer, offset)
wchars.value = path
stg = pythoncom.STGMEDIUM()
stg.set(pythoncom.TYMED_HGLOBAL, buffer)
win32clipboard.OpenClipboard()
try:
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardData(win32clipboard.CF_HDROP, stg.data)
finally:
win32clipboard.CloseClipboard()
Windows file explorer was able to recognize the data that I stored inside the clipboard, but discord wasn't. So no visible result there.
At last I found about CF_HTML
or html clipboard format.
import win32clipboard
class HTMLClipboard:
def __init__(self):
win32clipboard.OpenClipboard()
self.format = win32clipboard.RegisterClipboardFormat("HTML Format")
self.headers = "Version:0.9\r\n" +\
"StartHTML:00000000\r\n" +\
"EndHTML:00000000\r\n" +\
"StartFragment:00000000\r\n" +\
"EndFragment:00000000\r\n"
def _insertHeaders(self, data):
data = self.headers + data
hStartHtml = data.find("StartHTML")
startHtml = str(data.find("<html>"))
data = data[:hStartHtml + 18 - len(startHtml)] + startHtml + data[hStartHtml + 19:]
hEndHtml = data.find("EndHTML")
endHtml = str(len(data) - 1)
data = data[:hEndHtml + 16 - len(endHtml)] + endHtml + data[hEndHtml + 17:]
hStartFragment = data.find("StartFragment")
startFragment = str(data.find("<!--StartFragment-->") + 20)
data = data[:hStartFragment + 22 - len(startFragment)] + startFragment + data[hStartFragment + 23:]
hEndFragment = data.find("EndFragment")
endFragment = str(data.find("<!--EndFragment-->") + 1)
data = data[:hEndFragment + 20 - len(endFragment)] + endFragment + data[hEndFragment + 21:]
return data
def write(self, html):
data = "<html>\r\n" +\
"<body>\r\n" +\
"<!--StartFragment-->" +\
html + "" +\
"<!--EndFragment-->\r\n" +\
"</body>\r\n" +\
"</html>"
data = self._insertHeaders(data)
win32clipboard.SetClipboardData(self.format, data.encode("ascii"))
def read(self):
pass
def close(self):
win32clipboard.CloseClipboard()
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
self.close()
with HTMLClipboard() as clip:
clip.write("<img class='lnXdpd' alt='Google' src='https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png' srcset='/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png 1x, /images/branding/googlelogo/2x/googlelogo_color_272x92dp.png 2x' data-atf='1' width='272' height='92'>")
Discord again showed no visible result. But strange thing occurred, I copied image from the web using Microsoft Edge (the new one, based on Chromium) to clipboard and tried rewriting only the html format part using my code and it did still work.
So I am guessing that either I am still forgetting something, some clipboard format that I am not setting but the browsers do, or discord doesn't use the html clipboard format at all to import images from clipboard.
I even tried using all the clipboard formats from the attempts above together but with no visible result except the one with the transparency lost (black background).
I really don't know how the web browsers do the thing. Any help would be appreciated.
After I found out my original answer doesn't work for most images, I did some research and constructed a working solution. Unfortunately, it has a few drawbacks:
The solution utilizes pywin32
and is as follows:
import os
import win32clipboard as clp
file_path = 'test.png'
clp.OpenClipboard()
clp.EmptyClipboard()
# This works for Discord, but not for Paint.NET:
wide_path = os.path.abspath(file_path).encode('utf-16-le') + b'\0'
clp.SetClipboardData(clp.RegisterClipboardFormat('FileNameW'), wide_path)
# This works for Paint.NET, but not for Discord:
file = open(file_path, 'rb')
clp.SetClipboardData(clp.RegisterClipboardFormat('image/png'), file.read())
clp.CloseClipboard()