3

I'm working with a canvas in Tkinter, and I'm trying to copy the contents of that canvas to the clipboard.

I am able to solve this by creating a file, and then copy the content of that file into the clipboard with xclip -selection clipboard -t image/png -i temp_file.png, but I want to use a buffer instead, allowing the user to copy it directly to the clipboard without touching the filesystem of the user.

To use xclip's input function, I require to give a filename, and not a string. I can circumvent this with echo/cat as cat temp_file.png < echo | xclip -selection clipboard -t image/png -i or cat file_name.png | xclip -selection clipboard -t image/png -i in the bash-line.

I have successfully been able to use the buffer to store the canvas, as such:

memory = io.BytesIO()
img.save(memory, format="png")
return memory

and can save the picture to a file as this:

img = pil.Image.open(memory)
img.save("file_name.png", format="png")

As far as I have understood, from pil's documentation, pil.Image.open is the same as the built-in open() function. Nevertheless, when trying to use open() to read the file, it claims BytesIO is not a valid file. Not a major problem, I guess.

I can read the contents of the buffer with memory.getvalue(), and then strip the surrouding b'...' with [2:-1]. I then replace all the ' in the string with \', so I can surround it with single quotation marks, and end up echoing said string with the command I used on a file earlier.

img_data = str(img_memory.getvalue())[2:-1]
img_data = img_data.replace("'", "\'")#.replace("`", "\`")
img_data = "'" + img_data + "'"

The output of cat file_name.png is pretty much the same as the output of the string I'm giving echo in my subprocess, but the following doesn't seem to do the job for me, as my clipboard remains untouched:

bash_cmd = f"echo -n {img_data} | xclip -selection clipboard -t image/png -i"
p = sp.Popen(bash_cmd, shell=True, stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE)

but

bash_cmd = f"xclip -selection clipboard -t image/png -i file_name.png"
p = sp.Popen(bash_cmd, shell=True, stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE)

does work.

tl;dr

What am I doing wrong? Am I altering the contents of the file, and creating a corrupt image, which is then copied?

How can I properly output the contents of a io.BufferIO into my clipboard (mainly on Unix/Linux, but also on MacOS/Windows)?

mazunki
  • 682
  • 5
  • 17

1 Answers1

1

I managed to copy the image data with xclip from python by adapting the answer from Can I pipe a io.BytesIO() stream to subprocess.popen() in Python?. No need to use echo here, but just write the content of memory to the process stdin:

memory = io.BytesIO()
img.save(memory, format="png")

output = subprocess.Popen(("xclip", "-selection", "clipboard", "-t", "image/png", "-i"), 
                          stdin=subprocess.PIPE)
# write image to stdin
output.stdin.write(memory.getvalue())
output.stdin.close()

As for a more multi-platform solution, I found the package klembord which works on Linux and Windows. The following command copies the picture to the clipboard (tested in Linux only):

import klembord
klembord.set({"image/png": memory.getvalue()})
j_4321
  • 15,431
  • 3
  • 34
  • 61