Skip to content

Commit 21c4a52

Browse files
committed
Copy only the matplotlib image and make background transparent
1 parent d890570 commit 21c4a52

3 files changed

Lines changed: 158 additions & 16 deletions

File tree

openmc_plotter/plotgui.py

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import io
12
from functools import partial
23

34
from PySide6 import QtCore, QtGui
@@ -182,22 +183,60 @@ def saveImage(self, filename):
182183

183184
def copyImageToClipboard(self):
184185
"""Copy the current canvas image to the clipboard."""
185-
self.draw()
186-
width, height = self.get_width_height()
187-
if width <= 0 or height <= 0:
186+
image = self._export_plot_image()
187+
if image is None:
188188
return False
189189

190-
image = QtGui.QImage(self.buffer_rgba(),
191-
width,
192-
height,
193-
QtGui.QImage.Format_RGBA8888).copy()
194-
pixmap = QtGui.QPixmap.fromImage(image)
195-
if pixmap.isNull():
190+
clipboard = QtGui.QGuiApplication.clipboard()
191+
if clipboard is None:
196192
return False
197193

198-
QtGui.QGuiApplication.clipboard().setPixmap(pixmap)
194+
clipboard.setImage(image)
199195
return True
200196

197+
def _export_plot_image(self):
198+
self.draw()
199+
width, height = self.get_width_height()
200+
if width <= 0 or height <= 0:
201+
return None
202+
203+
buffer = io.BytesIO()
204+
self.figure.savefig(buffer,
205+
format='png',
206+
transparent=True)
207+
image = QtGui.QImage.fromData(buffer.getvalue(), 'PNG')
208+
if image.isNull():
209+
return None
210+
211+
crop_rect = self._visible_canvas_rect(image.width(), image.height())
212+
if crop_rect.isEmpty():
213+
return None
214+
215+
return image.copy(crop_rect)
216+
217+
def _visible_canvas_rect(self, image_width, image_height):
218+
if self.width() <= 0 or self.height() <= 0:
219+
return QtCore.QRect()
220+
221+
if self.parent is None or not hasattr(self.parent, 'viewport'):
222+
return QtCore.QRect(0, 0, image_width, image_height)
223+
224+
viewport = self.parent.viewport()
225+
visible_width = min(viewport.width(), self.width())
226+
visible_height = min(viewport.height(), self.height())
227+
x_offset = self.parent.horizontalScrollBar().value()
228+
y_offset = self.parent.verticalScrollBar().value()
229+
230+
scale_x = image_width / self.width()
231+
scale_y = image_height / self.height()
232+
233+
return QtCore.QRect(round(x_offset * scale_x),
234+
round(y_offset * scale_y),
235+
round(visible_width * scale_x),
236+
round(visible_height * scale_y)).intersected(
237+
QtCore.QRect(0, 0, image_width, image_height)
238+
)
239+
201240
def getDataIndices(self, event):
202241
cv = self.model.currentView
203242

tests/setup_test/test.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,19 +58,21 @@ def test_batch_image(tmpdir, qtbot):
5858

5959
mw.close()
6060

61-
def test_copy_image_to_clipboard(tmpdir, monkeypatch):
61+
def test_copy_image_to_clipboard(tmpdir, monkeypatch, qtbot):
6262
orig = tmpdir.chdir()
6363
QtWidgets.QApplication.instance() or QtWidgets.QApplication([])
6464
mw = MainWindow(model_path=orig)
6565
_openmcReload(model_path=orig)
6666
mw.loadGui()
67+
qtbot.addWidget(mw)
68+
mw.show()
6769

6870
class FakeClipboard:
6971
def __init__(self):
70-
self.pixmap = None
72+
self.image = None
7173

72-
def setPixmap(self, pixmap):
73-
self.pixmap = pixmap
74+
def setImage(self, image):
75+
self.image = image
7476

7577
fake_clipboard = FakeClipboard()
7678
monkeypatch.setattr(QtGui.QGuiApplication,
@@ -79,11 +81,32 @@ def setPixmap(self, pixmap):
7981

8082
try:
8183
assert mw.waitForPlotIdle(60000)
84+
mw.model.currentView.domainVisible = False
85+
mw.plotIm.updatePixmap()
8286
assert mw.copyImageToClipboard()
8387
finally:
8488
orig.chdir()
8589

86-
assert fake_clipboard.pixmap is not None
87-
assert not fake_clipboard.pixmap.isNull()
90+
assert fake_clipboard.image is not None
91+
assert not fake_clipboard.image.isNull()
92+
assert fake_clipboard.image.hasAlphaChannel()
93+
94+
canvas_width, canvas_height = mw.plotIm.get_width_height()
95+
expected_width = round(
96+
min(mw.frame.viewport().width(), mw.plotIm.width())
97+
* canvas_width
98+
/ mw.plotIm.width()
99+
)
100+
expected_height = round(
101+
min(mw.frame.viewport().height(), mw.plotIm.height())
102+
* canvas_height
103+
/ mw.plotIm.height()
104+
)
105+
assert fake_clipboard.image.width() == pytest.approx(expected_width, abs=1)
106+
assert fake_clipboard.image.height() == pytest.approx(expected_height, abs=1)
107+
108+
center = fake_clipboard.image.pixelColor(fake_clipboard.image.width() // 2,
109+
fake_clipboard.image.height() // 2)
110+
assert center.alpha() == 0
88111

89112
mw.close()

tests/test_plotgui_clipboard.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from types import SimpleNamespace
2+
3+
import numpy as np
4+
import pytest
5+
from PySide6 import QtGui, QtWidgets
6+
7+
from openmc_plotter.plotgui import PlotImage
8+
9+
10+
class FakeClipboard:
11+
12+
def __init__(self):
13+
self.image = None
14+
15+
def setImage(self, image):
16+
self.image = image
17+
18+
19+
@pytest.fixture
20+
def qapp():
21+
return QtWidgets.QApplication.instance() or QtWidgets.QApplication([])
22+
23+
24+
def test_copy_image_to_clipboard_crops_to_plot_and_preserves_alpha(
25+
qapp, monkeypatch
26+
):
27+
scroll = QtWidgets.QScrollArea()
28+
scroll.resize(220, 160)
29+
main_window = SimpleNamespace(
30+
logicalDpiX=lambda: 100,
31+
zoom=100,
32+
coord_label=SimpleNamespace(show=lambda: None, hide=lambda: None),
33+
statusBar=lambda: SimpleNamespace(showMessage=lambda *args, **kwargs: None),
34+
)
35+
plot = PlotImage(model=None, parent=scroll, main_window=main_window)
36+
scroll.setWidget(plot)
37+
plot.resize(400, 300)
38+
plot.figure.clear()
39+
plot.ax = plot.figure.subplots()
40+
plot.ax.imshow(np.zeros((10, 10, 4)))
41+
scroll.show()
42+
qapp.processEvents()
43+
scroll.horizontalScrollBar().setValue(40)
44+
scroll.verticalScrollBar().setValue(30)
45+
qapp.processEvents()
46+
47+
fake_clipboard = FakeClipboard()
48+
monkeypatch.setattr(
49+
QtGui.QGuiApplication,
50+
"clipboard",
51+
staticmethod(lambda: fake_clipboard),
52+
)
53+
54+
try:
55+
assert plot.copyImageToClipboard()
56+
finally:
57+
plot.close()
58+
scroll.close()
59+
60+
assert fake_clipboard.image is not None
61+
assert not fake_clipboard.image.isNull()
62+
assert fake_clipboard.image.hasAlphaChannel()
63+
64+
canvas_width, canvas_height = plot.get_width_height()
65+
expected_width = round(
66+
min(scroll.viewport().width(), plot.width()) * canvas_width / plot.width()
67+
)
68+
expected_height = round(
69+
min(scroll.viewport().height(), plot.height()) * canvas_height / plot.height()
70+
)
71+
assert fake_clipboard.image.width() == pytest.approx(expected_width, abs=1)
72+
assert fake_clipboard.image.height() == pytest.approx(expected_height, abs=1)
73+
assert fake_clipboard.image.width() < canvas_width
74+
assert fake_clipboard.image.height() < canvas_height
75+
76+
center = fake_clipboard.image.pixelColor(
77+
fake_clipboard.image.width() // 2,
78+
fake_clipboard.image.height() // 2,
79+
)
80+
assert center.alpha() == 0

0 commit comments

Comments
 (0)