Skip to content

Commit 7b759e1

Browse files
authored
Merge pull request #4615 from nulano/clipboard-png
2 parents 390b34c + d2e23e3 commit 7b759e1

3 files changed

Lines changed: 69 additions & 16 deletions

File tree

Tests/test_imagegrab.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
import os
12
import subprocess
23
import sys
34

45
import pytest
56
from PIL import Image, ImageGrab
67

7-
from .helper import assert_image
8+
from .helper import assert_image, assert_image_equal_tofile
89

910

1011
class TestImageGrab:
@@ -71,3 +72,27 @@ def test_grabclipboard(self):
7172

7273
im = ImageGrab.grabclipboard()
7374
assert_image(im, im.mode, im.size)
75+
76+
@pytest.mark.skipif(sys.platform != "win32", reason="Windows only")
77+
def test_grabclipboard_file(self):
78+
p = subprocess.Popen(["powershell", "-command", "-"], stdin=subprocess.PIPE)
79+
p.stdin.write(rb'Set-Clipboard -Path "Tests\images\hopper.gif"')
80+
p.communicate()
81+
82+
im = ImageGrab.grabclipboard()
83+
assert len(im) == 1
84+
assert os.path.samefile(im[0], "Tests/images/hopper.gif")
85+
86+
@pytest.mark.skipif(sys.platform != "win32", reason="Windows only")
87+
def test_grabclipboard_png(self):
88+
p = subprocess.Popen(["powershell", "-command", "-"], stdin=subprocess.PIPE)
89+
p.stdin.write(
90+
rb"""$bytes = [System.IO.File]::ReadAllBytes("Tests\images\hopper.png")
91+
$ms = new-object System.IO.MemoryStream(, $bytes)
92+
[Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
93+
[Windows.Forms.Clipboard]::SetData("PNG", $ms)"""
94+
)
95+
p.communicate()
96+
97+
im = ImageGrab.grabclipboard()
98+
assert_image_equal_tofile(im, "Tests/images/hopper.png")

src/PIL/ImageGrab.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,28 @@ def grabclipboard():
9393
os.unlink(filepath)
9494
return im
9595
elif sys.platform == "win32":
96-
data = Image.core.grabclipboard_win32()
96+
fmt, data = Image.core.grabclipboard_win32()
97+
if fmt == "file": # CF_HDROP
98+
import struct
99+
100+
o = struct.unpack_from("I", data)[0]
101+
if data[16] != 0:
102+
files = data[o:].decode("utf-16le").split("\0")
103+
else:
104+
files = data[o:].decode("mbcs").split("\0")
105+
return files[: files.index("")]
97106
if isinstance(data, bytes):
98-
from . import BmpImagePlugin
99107
import io
100108

101-
return BmpImagePlugin.DibImageFile(io.BytesIO(data))
102-
return data
109+
data = io.BytesIO(data)
110+
if fmt == "png":
111+
from . import PngImagePlugin
112+
113+
return PngImagePlugin.PngImageFile(data)
114+
elif fmt == "DIB":
115+
from . import BmpImagePlugin
116+
117+
return BmpImagePlugin.DibImageFile(data)
118+
return None
103119
else:
104120
raise NotImplementedError("ImageGrab.grabclipboard() is macOS and Windows only")

src/display.c

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -502,33 +502,45 @@ PyObject*
502502
PyImaging_GrabClipboardWin32(PyObject* self, PyObject* args)
503503
{
504504
int clip;
505-
HANDLE handle;
505+
HANDLE handle = NULL;
506506
int size;
507507
void* data;
508508
PyObject* result;
509+
UINT format;
510+
UINT formats[] = { CF_DIB, CF_DIBV5, CF_HDROP, RegisterClipboardFormatA("PNG"), 0 };
511+
LPCSTR format_names[] = { "DIB", "DIB", "file", "png", NULL };
509512

510-
clip = OpenClipboard(NULL);
511-
/* FIXME: check error status */
513+
if (!OpenClipboard(NULL)) {
514+
PyErr_SetString(PyExc_OSError, "failed to open clipboard");
515+
return NULL;
516+
}
517+
518+
// find best format as set by clipboard owner
519+
format = 0;
520+
while (!handle && (format = EnumClipboardFormats(format))) {
521+
for (UINT i = 0; formats[i] != 0; i++) {
522+
if (format == formats[i]) {
523+
handle = GetClipboardData(format);
524+
format = i;
525+
break;
526+
}
527+
}
528+
}
512529

513-
handle = GetClipboardData(CF_DIB);
514530
if (!handle) {
515-
/* FIXME: add CF_HDROP support to allow cut-and-paste from
516-
the explorer */
517531
CloseClipboard();
518-
Py_INCREF(Py_None);
519-
return Py_None;
532+
return Py_BuildValue("zO", NULL, Py_None);
520533
}
521534

522-
size = GlobalSize(handle);
523535
data = GlobalLock(handle);
536+
size = GlobalSize(handle);
524537

525538
result = PyBytes_FromStringAndSize(data, size);
526539

527540
GlobalUnlock(handle);
528-
529541
CloseClipboard();
530542

531-
return result;
543+
return Py_BuildValue("zN", format_names[format], result);
532544
}
533545

534546
/* -------------------------------------------------------------------- */

0 commit comments

Comments
 (0)