Skip to content

Commit

Permalink
Merge pull request #246 from janezd/ime-dpi
Browse files Browse the repository at this point in the history
Add dpi info to saved PNGs, and scale images in reports
  • Loading branch information
PrimozGodec authored May 5, 2023
2 parents b0f3206 + 34a16e1 commit 38972e4
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 24 deletions.
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

0 comments on commit 38972e4

Please sign in to comment.