beeref/tests/fileio/test_export.py
2023-12-31 12:26:48 +01:00

466 lines
16 KiB
Python

import os
import stat
from unittest.mock import patch, ANY, MagicMock
import pytest
from PyQt6 import QtGui, QtCore
from beeref import constants
from beeref.items import BeePixmapItem, BeeTextItem
from beeref.fileio.errors import BeeFileIOError
from beeref.fileio.export import (
exporter_registry,
SceneToPixmapExporter,
SceneToSVGExporter,
)
@pytest.mark.parametrize('key,expected',
[('png', SceneToPixmapExporter),
('jpg', SceneToPixmapExporter),
('svg', SceneToSVGExporter)])
def test_registry(key, expected):
exporter_registry[key] == expected
@patch('beeref.widgets.SceneToPixmapExporterDialog.exec', return_value=True)
@patch('beeref.widgets.SceneToPixmapExporterDialog.value',
return_value=QtCore.QSize(100, 200))
def test_scene_to_pixmap_exporter_get_user_input(value_mock, exec_mock, view):
exporter = SceneToPixmapExporter(view.scene)
value = exporter.get_user_input(None)
assert value is True
assert exporter.size == QtCore.QSize(100, 200)
@patch('beeref.widgets.SceneToPixmapExporterDialog.exec', return_value=False)
@patch('beeref.widgets.SceneToPixmapExporterDialog.value')
def test_scene_to_pixmap_exporter_get_user_input_when_canceled(
value_mock, exec_mock, view):
exporter = SceneToPixmapExporter(view.scene)
value = exporter.get_user_input(None)
assert value is False
def test_scene_to_pixmap_exporter_default_size_and_margin(view):
item1 = BeePixmapItem(
QtGui.QImage(100, 100, QtGui.QImage.Format.Format_RGB32))
item1.setPos(QtCore.QPointF(0, 0))
view.scene.addItem(item1)
item2 = BeePixmapItem(
QtGui.QImage(100, 100, QtGui.QImage.Format.Format_RGB32))
item1.setPos(QtCore.QPointF(200, 0))
view.scene.addItem(item2)
exporter = SceneToPixmapExporter(view.scene)
assert view.scene.sceneRect().size().toSize() == QtCore.QSize(300, 100)
assert (exporter.margin - 9) < 0.000001
assert exporter.default_size == QtCore.QSize(318, 118)
def test_scene_to_pixmap_exporter_default_size_and_margin_when_selection(view):
item1 = BeePixmapItem(
QtGui.QImage(100, 100, QtGui.QImage.Format.Format_RGB32))
item1.setPos(QtCore.QPointF(0, 0))
view.scene.addItem(item1)
item2 = BeePixmapItem(
QtGui.QImage(100, 100, QtGui.QImage.Format.Format_RGB32))
item1.setPos(QtCore.QPointF(200, 0))
view.scene.addItem(item2)
item2.setSelected(True)
exporter = SceneToPixmapExporter(view.scene)
assert view.scene.sceneRect().size().toSize() == QtCore.QSize(300, 100)
assert (exporter.margin - 9) < 0.000001
assert exporter.default_size == QtCore.QSize(318, 118)
@patch('beeref.scene.BeeGraphicsScene.render')
def test_scene_to_pixmap_exporter_render_sets_margins(render_mock, view):
item = BeePixmapItem(
QtGui.QImage(1000, 1200, QtGui.QImage.Format.Format_RGB32))
view.scene.addItem(item)
exporter = SceneToPixmapExporter(view.scene)
assert exporter.margin == 36
assert exporter.default_size == QtCore.QSize(1072, 1272)
exporter.size = QtCore.QSize(536, 636)
exporter.render_to_image()
render_mock.assert_called_once_with(
ANY,
source=QtCore.QRectF(0, 0, 1000, 1200),
target=QtCore.QRectF(18, 18, 500, 600))
def test_scene_to_pixmap_exporter_render_renders_scene(view):
item_img = QtGui.QImage(1000, 1200, QtGui.QImage.Format.Format_RGB32)
item_img.fill(QtGui.QColor(11, 22, 33))
item = BeePixmapItem(item_img)
view.scene.addItem(item)
exporter = SceneToPixmapExporter(view.scene)
assert exporter.margin == 36
assert exporter.default_size == QtCore.QSize(1072, 1272)
exporter.size = QtCore.QSize(536, 636)
image = exporter.render_to_image()
assert image.pixel(1, 1) == QtGui.QColor(*constants.COLORS['Scene:Canvas'])
assert image.pixel(100, 100) == QtGui.QColor(11, 22, 33)
def test_scene_to_pixmap_exporter_export_writes_image(view, tmpdir):
filename = os.path.join(tmpdir, 'foo.png')
item_img = QtGui.QImage(1000, 1200, QtGui.QImage.Format.Format_RGB32)
item = BeePixmapItem(item_img)
view.scene.addItem(item)
exporter = SceneToPixmapExporter(view.scene)
exporter.size = QtCore.QSize(100, 120)
exporter.export(filename)
with open(filename, 'rb') as f:
assert f.read().startswith(b'\x89PNG')
def test_scene_to_pixmap_exporter_export_with_worker(view, tmpdir):
filename = os.path.join(tmpdir, 'foo.png')
item_img = QtGui.QImage(1000, 1200, QtGui.QImage.Format.Format_RGB32)
item = BeePixmapItem(item_img)
view.scene.addItem(item)
exporter = SceneToPixmapExporter(view.scene)
exporter.size = QtCore.QSize(100, 120)
worker = MagicMock(canceled=False)
exporter.export(filename, worker)
worker.begin_processing.emit.assert_called_once_with(1)
worker.progress.emit.assert_called_once_with(1)
worker.finished.emit.assert_called_once_with(filename, [])
with open(filename, 'rb') as f:
assert f.read().startswith(b'\x89PNG')
def test_scene_to_pixmap_exporter_export_with_worker_when_canceled(
view, tmpdir):
filename = os.path.join(tmpdir, 'foo.png')
item_img = QtGui.QImage(1000, 1200, QtGui.QImage.Format.Format_RGB32)
item = BeePixmapItem(item_img)
view.scene.addItem(item)
exporter = SceneToPixmapExporter(view.scene)
exporter.size = QtCore.QSize(100, 120)
worker = MagicMock(canceled=True)
exporter.export(filename, worker)
worker.begin_processing.emit.assert_called_once_with(1)
worker.progress.emit.assert_not_called()
worker.finished.emit.assert_called_once_with(filename, [])
os.path.exists(filename) is False
def test_scene_to_pixmap_exporter_export_when_file_not_writeable(view, tmpdir):
filename = os.path.join(tmpdir, 'foo.png')
with open(filename, 'w') as f:
f.write('foo')
os.chmod(filename, stat.S_IREAD)
item_img = QtGui.QImage(1000, 1200, QtGui.QImage.Format.Format_RGB32)
item = BeePixmapItem(item_img)
view.scene.addItem(item)
exporter = SceneToPixmapExporter(view.scene)
exporter.size = QtCore.QSize(100, 120)
with pytest.raises(BeeFileIOError) as e:
exporter.export(filename)
assert e.filename == filename
def test_scene_to_pixmap_exporter_export_when_file_not_writeable_with_worker(
view, tmpdir):
filename = os.path.join(tmpdir, 'foo.png')
with open(filename, 'w') as f:
f.write('foo')
os.chmod(filename, stat.S_IREAD)
item_img = QtGui.QImage(1000, 1200, QtGui.QImage.Format.Format_RGB32)
item = BeePixmapItem(item_img)
view.scene.addItem(item)
exporter = SceneToPixmapExporter(view.scene)
exporter.size = QtCore.QSize(100, 120)
worker = MagicMock(canceled=False)
exporter.export(filename, worker=worker)
worker.begin_processing.emit.assert_called_once_with(1)
worker.progress.emit.assert_not_called()
worker.finished.emit.assert_called_once_with(
filename, ['Error writing file'])
def test_scene_to_svg_exporter_get_user_input(view):
item1 = BeePixmapItem(
QtGui.QImage(100, 100, QtGui.QImage.Format.Format_RGB32))
item1.setPos(QtCore.QPointF(0, 0))
view.scene.addItem(item1)
item2 = BeePixmapItem(
QtGui.QImage(100, 100, QtGui.QImage.Format.Format_RGB32))
item1.setPos(QtCore.QPointF(200, 0))
view.scene.addItem(item2)
assert view.scene.sceneRect().size().toSize() == QtCore.QSize(300, 100)
exporter = SceneToSVGExporter(view.scene)
value = exporter.get_user_input(None)
assert value is True
assert exporter.size == QtCore.QSize(318, 118)
def test_scene_to_svg_exporter_render_pixmap_items(view):
item1 = BeePixmapItem(
QtGui.QImage(100, 110, QtGui.QImage.Format.Format_RGB32))
item1.setPos(QtCore.QPointF(20, 30))
view.scene.addItem(item1)
item2 = BeePixmapItem(
QtGui.QImage(70, 77, QtGui.QImage.Format.Format_RGB32))
item2.setPos(QtCore.QPointF(50, 50))
item2.setZValue(-1)
view.scene.addItem(item2)
exporter = SceneToSVGExporter(view.scene)
exporter.size = QtCore.QSize(200, 400)
exporter.margin = 5
svg = exporter.render_to_svg()
assert svg.tag == 'svg'
assert svg.get('width') == '200'
assert svg.get('height') == '400'
assert svg.get('xmlns') == 'http://www.w3.org/2000/svg'
assert svg.get('xmlns:xlink') == 'http://www.w3.org/1999/xlink'
assert len(svg) == 2
element = svg[0] # item2
assert element.tag == 'image'
assert element.get('xlink:href').startswith('data:image/png;base64,iVBOR')
assert element.get('width') == '70.0'
assert element.get('height') == '77.0'
assert element.get('image-rendering') == 'optimizeQuality'
assert element.get('transform') == 'rotate(0.0 35.0 25.0)'
assert element.get('x') == '35.0'
assert element.get('y') == '25.0'
assert element.get('opacity') == '1.0'
element = svg[1] # item1
assert element.tag == 'image'
assert element.get('width') == '100.0'
assert element.get('height') == '110.0'
assert element.get('image-rendering') == 'optimizeQuality'
assert element.get('transform') == 'rotate(0.0 5.0 5.0)'
assert element.get('x') == '5.0'
assert element.get('y') == '5.0'
assert element.get('opacity') == '1.0'
def test_scene_to_svg_exporter_render_pixmap_with_crop(view):
item = BeePixmapItem(
QtGui.QImage(100, 110, QtGui.QImage.Format.Format_RGB32))
item.setPos(QtCore.QPointF(20, 30))
item.crop = QtCore.QRectF(20, 25, 30, 33)
view.scene.addItem(item)
exporter = SceneToSVGExporter(view.scene)
exporter.size = QtCore.QSize(200, 400)
exporter.margin = 5
svg = exporter.render_to_svg()
assert len(svg) == 1
element = svg[0]
assert element.tag == 'image'
assert element.get('width') == '30.0'
assert element.get('height') == '33.0'
assert element.get('transform') == 'rotate(0.0 -15.0 -20.0)'
assert element.get('x') == '5.0'
assert element.get('y') == '5.0'
def test_scene_to_svg_exporter_render_pixmap_with_rotation(view):
item = BeePixmapItem(
QtGui.QImage(100, 110, QtGui.QImage.Format.Format_RGB32))
item.setPos(QtCore.QPointF(20, 30))
item.setRotation(90)
view.scene.addItem(item)
exporter = SceneToSVGExporter(view.scene)
exporter.size = QtCore.QSize(200, 400)
exporter.margin = 5
svg = exporter.render_to_svg()
assert len(svg) == 1
element = svg[0]
assert element.tag == 'image'
assert element.get('transform') == 'rotate(90.0 115.0 5.0)'
assert element.get('x') == '115.0'
assert element.get('y') == '5.0'
def test_scene_to_svg_exporter_render_pixmap_with_opacity(view):
item = BeePixmapItem(
QtGui.QImage(100, 110, QtGui.QImage.Format.Format_RGB32))
item.setPos(QtCore.QPointF(20, 30))
item.setOpacity(0.75)
view.scene.addItem(item)
exporter = SceneToSVGExporter(view.scene)
exporter.size = QtCore.QSize(200, 400)
exporter.margin = 5
svg = exporter.render_to_svg()
assert len(svg) == 1
element = svg[0]
assert element.tag == 'image'
assert element.get('opacity') == '0.75'
def test_scene_to_svg_exporter_render_pixmap_with_flip(view):
item = BeePixmapItem(
QtGui.QImage(100, 110, QtGui.QImage.Format.Format_RGB32))
item.setPos(QtCore.QPointF(20, 30))
item.do_flip()
view.scene.addItem(item)
exporter = SceneToSVGExporter(view.scene)
exporter.size = QtCore.QSize(200, 400)
exporter.margin = 5
svg = exporter.render_to_svg()
assert len(svg) == 1
element = svg[0]
assert element.tag == 'image'
assert element.get('transform') == (
'translate(105.0 5.0) scale(-1.0 1)'
' translate(-105.0 -5.0) rotate(0.0 105.0 5.0)')
assert element.get('x') == '105.0'
assert element.get('y') == '5.0'
def test_scene_to_svg_exporter_render_text(view):
item = BeeTextItem('foo')
item.setPos(QtCore.QPointF(20, 30))
view.scene.addItem(item)
exporter = SceneToSVGExporter(view.scene)
exporter.size = QtCore.QSize(200, 400)
exporter.margin = 5
svg = exporter.render_to_svg()
assert len(svg) == 1
element = svg[0]
assert element.tag == 'text'
assert element.text == 'foo'
assert element.get('dominant-baseline') == 'hanging'
assert 'font-family' in element.get('style')
assert element.get('transform') == 'rotate(0.0 5.0 5.0)'
assert element.get('x') == '5.0'
assert element.get('y') == '5.0'
def test_scene_to_svg_exporter_export_when_file_not_writeable(view, tmpdir):
filename = os.path.join(tmpdir, 'foo.svg')
with open(filename, 'w') as f:
f.write('foo')
os.chmod(filename, stat.S_IREAD)
item = BeeTextItem('foo')
view.scene.addItem(item)
exporter = SceneToSVGExporter(view.scene)
exporter.size = QtCore.QSize(100, 120)
with pytest.raises(BeeFileIOError) as e:
exporter.export(filename)
assert e.filename == filename
def test_scene_to_svg_exporter_render_with_worker(view):
item = BeeTextItem('foo')
item.setPos(QtCore.QPointF(20, 30))
view.scene.addItem(item)
exporter = SceneToSVGExporter(view.scene)
exporter.size = QtCore.QSize(200, 400)
exporter.margin = 5
worker = MagicMock(canceled=False)
svg = exporter.render_to_svg(worker=worker)
assert len(svg) == 1
worker.progress.emit.assert_called_once_with(0)
def test_scene_to_svg_exporter_render_with_worker_canceled(view):
item = BeeTextItem('foo')
item.setPos(QtCore.QPointF(20, 30))
view.scene.addItem(item)
exporter = SceneToSVGExporter(view.scene)
exporter.size = QtCore.QSize(200, 400)
exporter.margin = 5
worker = MagicMock(canceled=True)
svg = exporter.render_to_svg(worker=worker)
assert svg is None
def test_scene_to_svg_exporter_export_writes_svg(view, tmpdir):
filename = os.path.join(tmpdir, 'foo.svg')
item = BeeTextItem('foo')
view.scene.addItem(item)
exporter = SceneToSVGExporter(view.scene)
exporter.size = QtCore.QSize(100, 120)
exporter.export(filename)
with open(filename, 'rb') as f:
assert f.read().startswith(b'<?xml')
def test_scene_to_svg_exporter_export_with_worker(view, tmpdir):
filename = os.path.join(tmpdir, 'foo.svg')
item = BeeTextItem('foo')
view.scene.addItem(item)
exporter = SceneToSVGExporter(view.scene)
exporter.size = QtCore.QSize(100, 120)
worker = MagicMock(canceled=False)
exporter.export(filename, worker)
worker.begin_processing.emit.assert_called_once_with(1)
worker.progress.emit.assert_called_once_with(0)
worker.finished.emit.assert_called_once_with(filename, [])
with open(filename, 'rb') as f:
assert f.read().startswith(b'<?xml')
def test_scene_to_svg_exporter_export_with_worker_canceled(view, tmpdir):
filename = os.path.join(tmpdir, 'foo.svg')
item = BeeTextItem('foo')
view.scene.addItem(item)
exporter = SceneToSVGExporter(view.scene)
exporter.size = QtCore.QSize(100, 120)
worker = MagicMock(canceled=True)
exporter.export(filename, worker)
worker.begin_processing.emit.assert_called_once_with(1)
worker.progress.emit.assert_called_once_with(0)
worker.finished.emit.assert_called_once_with(filename, [])
os.path.exists(filename) is False
def test_scene_to_svg_exporter_export_when_file_not_writeable_with_worker(
view, tmpdir):
filename = os.path.join(tmpdir, 'foo.svg')
with open(filename, 'w') as f:
f.write('foo')
os.chmod(filename, stat.S_IREAD)
item = BeeTextItem('foo')
view.scene.addItem(item)
exporter = SceneToSVGExporter(view.scene)
exporter.size = QtCore.QSize(100, 120)
worker = MagicMock(canceled=False)
exporter.export(filename, worker=worker)
worker.begin_processing.emit.assert_called_once_with(1)
worker.progress.emit.assert_called_once_with(0)
worker.finished.emit.assert_called_once()
args = worker.finished.emit.call_args.args
assert args[0] == filename
assert len(args[1]) == 1