Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dpi info to saved PNGs, and scale images in reports #246

Merged
merged 1 commit into from
May 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 51 additions & 18 deletions orangewidget/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,25 @@ def _setup_painter(cls, painter, object, source_rect, buffer):
def _save_buffer(buffer, filename):
raise NotImplementedError

@staticmethod
def _meta_data(buffer):
meta_data = {}

try:
size = buffer.size()
except AttributeError:
pass
else:
meta_data["width"] = size.width()
meta_data["height"] = size.height()

try:
meta_data["pixel_ratio"] = buffer.devicePixelRatio()
except AttributeError:
pass

return meta_data

@staticmethod
def _get_exporter():
raise NotImplementedError
Expand Down Expand Up @@ -130,8 +149,7 @@ def save_pyqtgraph():
exporter = cls._get_exporter()
scene = object.scene()
if scene is None:
cls._export(exporter(scene), filename)
return
return cls._export(exporter(scene), filename)
views = scene.views()
if views:
# preserve scene rect and background brush
Expand All @@ -141,22 +159,21 @@ def save_pyqtgraph():
view = scene.views()[0]
scene.setSceneRect(view.sceneRect())
scene.setBackgroundBrush(effective_background(scene, view))
cls._export(exporter(scene), filename)
return cls._export(exporter(scene), filename)
finally:
# reset scene rect and background brush
scene.setBackgroundBrush(backgroundbrush)
scene.setSceneRect(scenerect)
else:
cls._export(exporter(scene), filename)
return cls._export(exporter(scene), filename)

def save_scene():
assert isinstance(object, QGraphicsScene)
ratio = get_scene_pixel_ratio(object)
views = object.views()
if not views:
rect = object.itemsBoundingRect()
_render(rect, ratio, rect.size(), object)
return
return _render(rect, ratio, rect.size(), object)

# Pick the first view. If there's a widget with multiple views that
# cares which one is used, it must set graph_name to view, not scene
Expand All @@ -166,11 +183,11 @@ def save_scene():
source_rect = QRect(
int(target_rect.x()), int(target_rect.y()),
int(target_rect.width()), int(target_rect.height()))
_render(source_rect, ratio, target_rect.size(), view)
return _render(source_rect, ratio, target_rect.size(), view)

def save_widget():
assert isinstance(object, QWidget)
_render(object.rect(), object.devicePixelRatio(), object.size(),
return _render(object.rect(), object.devicePixelRatio(), object.size(),
object)

def _render(
Expand Down Expand Up @@ -203,13 +220,14 @@ def _render(
# not a core dump
painter.end()
cls._save_buffer(buffer, filename)
return cls._meta_data(buffer)

if isinstance(object, GraphicsItem):
save_pyqtgraph()
return save_pyqtgraph()
elif isinstance(object, QGraphicsScene):
save_scene()
return save_scene()
elif isinstance(object, QWidget): # this includes QGraphicsView
save_widget()
return save_widget()
else:
raise TypeError(f"{cls.__name__} "
f"cannot imagine {type(object).__name__}")
Expand All @@ -218,7 +236,7 @@ def _render(
def write(cls, filename, scene):
if type(scene) == dict:
scene = scene['scene']
cls.write_image(filename, scene)
return cls.write_image(filename, scene)

@classproperty
def img_writers(cls): # type: () -> Mapping[str, Type[ImgFormat]]
Expand Down Expand Up @@ -260,7 +278,11 @@ def _setup_painter(cls, painter, object, source_rect, buffer):

@staticmethod
def _save_buffer(buffer, filename):
buffer.save(filename, "png")
image = buffer.toImage()
dpm = int(2835 * image.devicePixelRatio())
image.setDotsPerMeterX(dpm)
image.setDotsPerMeterY(dpm)
image.save(filename, "png")

@staticmethod
def _get_exporter():
Expand Down Expand Up @@ -300,6 +322,9 @@ def export(self, fileName=None, toBytes=False, copy=False):
QtGui.QImage.Format.Format_ARGB32)
self.png.fill(self.params['background'])
self.png.setDevicePixelRatio(self.ratio)
dpm = int(2835 * self.ratio)
self.png.setDotsPerMeterX(dpm)
self.png.setDotsPerMeterY(dpm)

## set resolution of image:
origTargetRect = self.getTargetRect()
Expand Down Expand Up @@ -344,20 +369,28 @@ def export(self, fileName=None, toBytes=False, copy=False):

return PngExporter

@staticmethod
def _export(exporter, filename):
@classmethod
def _export(cls, exporter, filename):
buffer = exporter.export(toBytes=True)
buffer.save(filename, "png")
return cls._meta_data(buffer)


class ClipboardFormat(PngFormat):
EXTENSIONS = ()
DESCRIPTION = 'System Clipboard'
PRIORITY = 50

@staticmethod
def _save_buffer(buffer, _):
QApplication.clipboard().setPixmap(buffer)
@classmethod
def _save_buffer(cls, buffer, _):
meta_data = cls._meta_data(buffer)
image = buffer.toImage()
if meta_data is not None:
ratio = meta_data.get("pixel_ratio", 1)
dpm = int(2835 * ratio)
image.setDotsPerMeterX(dpm)
image.setDotsPerMeterY(dpm)
QApplication.clipboard().setImage(image)

@staticmethod
def _export(exporter, _):
Expand Down
18 changes: 12 additions & 6 deletions orangewidget/report/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -627,13 +627,19 @@ def get_html_img(
byte_array = QByteArray()
filename = QBuffer(byte_array)
filename.open(QIODevice.WriteOnly)
PngFormat.write(filename, scene)
img_data = PngFormat.write(filename, scene)

img_encoded = byte_array.toBase64().data().decode("utf-8")
return '<img {} src="data:image/png;base64,{}"/>'.format(
("" if max_height is None
else 'style="max-height: {}px"'.format(max_height)),
img_encoded
)

style_opts = []
if max_height is not None:
style_opts.append(f"max-height: {max_height}px")
if img_data is not None:
ratio = img_data.get("pixel_ratio", 1)
if ratio != 1:
style_opts.append(f"zoom: {1 / ratio:.1f}")
style = f' style="{"; ".join(style_opts)}"' if style_opts else ''
return f'<img{style} src="data:image/png;base64,{img_encoded}"/>'


def get_icon_html(icon: QIcon, size: QSize) -> str:
Expand Down