mirror of
https://github.com/rbreu/beeref.git
synced 2026-03-11 08:54:28 +00:00
Export all images from scene
This commit is contained in:
parent
38aaaf2553
commit
479665b9f1
16 changed files with 1122 additions and 494 deletions
|
|
@ -9,6 +9,7 @@ Added
|
|||
setting can be overridden by Qt's default environment variable
|
||||
QT_IMAGEIO_MAXALLOC
|
||||
* Display error messages when images can't be loaded from bee files
|
||||
* Added option to export all images from scene (File -> Export Images)
|
||||
|
||||
|
||||
0.3.3 - 2024-05-05
|
||||
|
|
|
|||
|
|
@ -141,6 +141,12 @@ actions = ActionList([
|
|||
callback='on_action_export_scene',
|
||||
group='active_when_items_in_scene',
|
||||
),
|
||||
Action(
|
||||
id='export_images',
|
||||
text='Export &Images...',
|
||||
callback='on_action_export_images',
|
||||
group='active_when_items_in_scene',
|
||||
),
|
||||
Action(
|
||||
id='quit',
|
||||
text='&Quit',
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ menu_structure = [
|
|||
'save',
|
||||
'save_as',
|
||||
'export_scene',
|
||||
'export_images',
|
||||
MENU_SEPARATOR,
|
||||
'quit',
|
||||
],
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ class ThreadedIO(QtCore.QThread):
|
|||
progress = QtCore.pyqtSignal(int)
|
||||
finished = QtCore.pyqtSignal(str, list)
|
||||
begin_processing = QtCore.pyqtSignal(int)
|
||||
user_input_required = QtCore.pyqtSignal(str)
|
||||
|
||||
def __init__(self, func, *args, **kwargs):
|
||||
super().__init__()
|
||||
|
|
|
|||
|
|
@ -15,12 +15,14 @@
|
|||
|
||||
import base64
|
||||
import logging
|
||||
import pathlib
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
from PyQt6 import QtCore, QtGui
|
||||
|
||||
from .errors import BeeFileIOError
|
||||
from beeref import constants, widgets
|
||||
from beeref.items import BeePixmapItem
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -46,6 +48,36 @@ def register_exporter(cls):
|
|||
|
||||
|
||||
class ExporterBase:
|
||||
|
||||
def emit_begin_processing(self, worker, start):
|
||||
if worker:
|
||||
worker.begin_processing.emit(start)
|
||||
|
||||
def emit_progress(self, worker, progress):
|
||||
if worker:
|
||||
worker.progress.emit(progress)
|
||||
|
||||
def emit_finished(self, worker, filename, errors):
|
||||
filename = str(filename)
|
||||
if worker:
|
||||
worker.finished.emit(filename, errors)
|
||||
|
||||
def emit_user_input_required(self, worker, msg):
|
||||
if worker:
|
||||
worker.user_input_required.emit(msg)
|
||||
|
||||
def handle_export_error(self, filename, error, worker):
|
||||
filename = str(filename)
|
||||
logger.debug(f'Export failed: {error}')
|
||||
if worker:
|
||||
worker.finished.emit(filename, [str(error)])
|
||||
return
|
||||
else:
|
||||
e = error if isinstance(error, Exception) else None
|
||||
raise BeeFileIOError(msg=str(error), filename=filename) from e
|
||||
|
||||
|
||||
class SceneExporterBase(ExporterBase):
|
||||
"""For exporting the scene to a single image."""
|
||||
|
||||
def __init__(self, scene):
|
||||
|
|
@ -67,7 +99,7 @@ class ExporterBase:
|
|||
|
||||
|
||||
@register_exporter
|
||||
class SceneToPixmapExporter(ExporterBase):
|
||||
class SceneToPixmapExporter(SceneExporterBase):
|
||||
|
||||
TYPE = ExporterRegistry.DEFAULT_TYPE
|
||||
|
||||
|
|
@ -108,33 +140,25 @@ class SceneToPixmapExporter(ExporterBase):
|
|||
|
||||
def export(self, filename, worker=None):
|
||||
logger.debug(f'Exporting scene to {filename}')
|
||||
if worker:
|
||||
worker.begin_processing.emit(1)
|
||||
|
||||
self.emit_begin_processing(worker, 1)
|
||||
image = self.render_to_image()
|
||||
|
||||
if worker and worker.canceled:
|
||||
logger.debug('Export canceled')
|
||||
worker.finished.emit(filename, [])
|
||||
self.emit_finished(worker, filename, [])
|
||||
return
|
||||
|
||||
if not image.save(filename, quality=90):
|
||||
msg = 'Error writing file'
|
||||
logger.debug(f'Export failed: {msg}')
|
||||
if worker:
|
||||
worker.finished.emit(filename, [msg])
|
||||
return
|
||||
else:
|
||||
raise BeeFileIOError(msg, filename=filename)
|
||||
self.handle_export_error(filename, 'Error writing file', worker)
|
||||
return
|
||||
|
||||
logger.debug('Export finished')
|
||||
if worker:
|
||||
worker.progress.emit(1)
|
||||
worker.finished.emit(filename, [])
|
||||
self.emit_progress(worker, 1)
|
||||
self.emit_finished(worker, filename, [])
|
||||
|
||||
|
||||
@register_exporter
|
||||
class SceneToSVGExporter(ExporterBase):
|
||||
class SceneToSVGExporter(SceneExporterBase):
|
||||
|
||||
TYPE = 'svg'
|
||||
|
||||
|
|
@ -222,18 +246,15 @@ class SceneToSVGExporter(ExporterBase):
|
|||
element.set('opacity', str(item.opacity()))
|
||||
|
||||
svg.append(element)
|
||||
if worker:
|
||||
worker.progress.emit(i)
|
||||
if worker.canceled:
|
||||
return
|
||||
self.emit_progress(worker, i)
|
||||
if worker and worker.canceled:
|
||||
return
|
||||
|
||||
return svg
|
||||
|
||||
def export(self, filename, worker=None):
|
||||
logger.debug(f'Exporting scene to {filename}')
|
||||
if worker:
|
||||
worker.begin_processing.emit(len(self.scene.items()))
|
||||
|
||||
self.emit_begin_processing(worker, len(self.scene.items()))
|
||||
svg = self.render_to_svg(worker)
|
||||
|
||||
if worker and worker.canceled:
|
||||
|
|
@ -248,13 +269,89 @@ class SceneToSVGExporter(ExporterBase):
|
|||
with open(filename, 'w') as f:
|
||||
tree.write(f, encoding='unicode', xml_declaration=True)
|
||||
except OSError as e:
|
||||
logger.debug(f'Export failed: {e}')
|
||||
if worker:
|
||||
worker.finished.emit(filename, [str(e)])
|
||||
return
|
||||
else:
|
||||
raise BeeFileIOError(msg=str(e), filename=filename) from e
|
||||
self.handle_export_error(filename, e, worker)
|
||||
return
|
||||
|
||||
logger.debug('Export finished')
|
||||
if worker:
|
||||
worker.finished.emit(filename, [])
|
||||
self.emit_finished(worker, filename, [])
|
||||
|
||||
|
||||
class ImagesToDirectoryExporter(ExporterBase):
|
||||
"""Export all images to a folder.
|
||||
|
||||
Not registered in the registry as it is accessed via its own menu entry,
|
||||
not auto-detected by file extension.
|
||||
"""
|
||||
|
||||
def __init__(self, scene, dirname):
|
||||
self.scene = scene
|
||||
self.dirname = dirname
|
||||
self.items = list(self.scene.items_by_type(BeePixmapItem.TYPE))
|
||||
self.max_save_id = 0
|
||||
for item in self.items:
|
||||
if item.save_id:
|
||||
self.max_save_id = max(self.max_save_id, item.save_id)
|
||||
self.num_total = len(self.items)
|
||||
self.start_from = 0
|
||||
self.handle_existing = None
|
||||
|
||||
def export(self, worker=None):
|
||||
logger.debug(f'Exporting images to {self.dirname}')
|
||||
logger.debug(f'Starting at {self.start_from}')
|
||||
|
||||
self.emit_begin_processing(worker, self.num_total)
|
||||
self.emit_progress(worker, self.start_from)
|
||||
|
||||
for i, item in enumerate(
|
||||
self.items[self.start_from:], start=self.start_from):
|
||||
if worker and worker.canceled:
|
||||
logger.debug('Export canceled')
|
||||
worker.finished.emit(self.dirname, [])
|
||||
return
|
||||
|
||||
pixmap, imgformat = item.pixmap_to_bytes()
|
||||
|
||||
if item.save_id:
|
||||
filename = item.get_filename_for_export(imgformat)
|
||||
else:
|
||||
self.max_save_id += 1
|
||||
save_id = self.max_save_id
|
||||
filename = item.get_filename_for_export(imgformat, save_id)
|
||||
|
||||
try:
|
||||
path = pathlib.Path(self.dirname) / filename
|
||||
path_exists = path.exists()
|
||||
except OSError as e:
|
||||
self.handle_export_error(self.dirname, e, worker)
|
||||
return
|
||||
|
||||
if path_exists:
|
||||
logger.debug(f'File already exists: {path}')
|
||||
if self.handle_existing is None:
|
||||
self.start_from = i
|
||||
self.emit_user_input_required(worker, str(path))
|
||||
return
|
||||
else:
|
||||
if self.handle_existing == 'skip':
|
||||
self.handle_existing = None
|
||||
logger.debug('Skipping file')
|
||||
continue
|
||||
elif self.handle_existing == 'skip_all':
|
||||
logger.debug('Skipping file')
|
||||
continue
|
||||
elif self.handle_existing == 'overwrite':
|
||||
self.handle_existing = None
|
||||
logger.debug('Overwrite file')
|
||||
elif self.handle_existing == 'overwrite_all':
|
||||
logger.debug('Overwrite file')
|
||||
|
||||
logger.debug(f'Writing file: {path}')
|
||||
try:
|
||||
path.write_bytes(pixmap)
|
||||
except OSError as e:
|
||||
self.handle_export_error(path, e, worker)
|
||||
return
|
||||
|
||||
self.emit_progress(worker, i)
|
||||
|
||||
self.emit_finished(worker, self.dirname, [])
|
||||
|
|
|
|||
|
|
@ -296,13 +296,7 @@ class SQLiteIO:
|
|||
|
||||
if hasattr(item, 'pixmap_to_bytes'):
|
||||
pixmap, imgformat = item.pixmap_to_bytes()
|
||||
|
||||
if item.filename:
|
||||
basename = os.path.splitext(os.path.basename(item.filename))[0]
|
||||
name = f'{item.save_id:04}-{basename}.{imgformat}'
|
||||
else:
|
||||
name = f'{item.save_id:04}.{imgformat}'
|
||||
|
||||
name = item.get_filename_for_export(imgformat)
|
||||
self.ex(
|
||||
'INSERT INTO sqlar (item_id, name, mode, sz, data) '
|
||||
'VALUES (?, ?, ?, ?, ?)',
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ text).
|
|||
from collections import defaultdict
|
||||
from functools import cached_property
|
||||
import logging
|
||||
import os.path
|
||||
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||
from PyQt6.QtCore import Qt
|
||||
|
|
@ -197,6 +198,16 @@ class BeePixmapItem(BeeItemMixin, QtWidgets.QGraphicsPixmapItem):
|
|||
self.crop.width(),
|
||||
self.crop.height()]}
|
||||
|
||||
def get_filename_for_export(self, imgformat, save_id_default=None):
|
||||
save_id = self.save_id or save_id_default
|
||||
assert save_id is not None
|
||||
|
||||
if self.filename:
|
||||
basename = os.path.splitext(os.path.basename(self.filename))[0]
|
||||
return f'{save_id:04}-{basename}.{imgformat}'
|
||||
else:
|
||||
return f'{save_id:04}.{imgformat}'
|
||||
|
||||
def get_imgformat(self, img):
|
||||
"""Determines the format for storing this image."""
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ from beeref.config import CommandlineArgs, BeeSettings, KeyboardSettings
|
|||
from beeref import constants
|
||||
from beeref import fileio
|
||||
from beeref.fileio.errors import IMG_LOADING_ERROR_MSG
|
||||
from beeref.fileio.export import exporter_registry
|
||||
from beeref.fileio.export import exporter_registry, ImagesToDirectoryExporter
|
||||
from beeref import widgets
|
||||
from beeref.items import BeePixmapItem, BeeTextItem
|
||||
from beeref.main_controls import MainControlsMixin
|
||||
|
|
@ -396,7 +396,7 @@ class BeeGraphicsView(MainControlsMixin,
|
|||
self.worker.progress.connect(self.on_items_loaded)
|
||||
self.worker.finished.connect(self.on_loading_finished)
|
||||
self.progress = widgets.BeeProgressDialog(
|
||||
'Loading %s' % filename,
|
||||
f'Loading {filename}',
|
||||
worker=self.worker,
|
||||
parent=self)
|
||||
self.worker.start()
|
||||
|
|
@ -430,7 +430,7 @@ class BeeGraphicsView(MainControlsMixin,
|
|||
fileio.save_bee, filename, self.scene, create_new=create_new)
|
||||
self.worker.finished.connect(self.on_saving_finished)
|
||||
self.progress = widgets.BeeProgressDialog(
|
||||
'Saving %s' % filename,
|
||||
f'Saving {filename}',
|
||||
worker=self.worker,
|
||||
parent=self)
|
||||
self.worker.start()
|
||||
|
|
@ -481,7 +481,7 @@ class BeeGraphicsView(MainControlsMixin,
|
|||
self.worker = fileio.ThreadedIO(exporter.export, filename)
|
||||
self.worker.finished.connect(self.on_export_finished)
|
||||
self.progress = widgets.BeeProgressDialog(
|
||||
'Exporting %s' % filename,
|
||||
f'Exporting {filename}',
|
||||
worker=self.worker,
|
||||
parent=self)
|
||||
self.worker.start()
|
||||
|
|
@ -494,6 +494,39 @@ class BeeGraphicsView(MainControlsMixin,
|
|||
'Problem writing file',
|
||||
f'<p>Problem writing file {filename}</p><p>{err_msg}</p>')
|
||||
|
||||
def on_action_export_images(self):
|
||||
directory = os.path.dirname(self.filename) if self.filename else None
|
||||
directory = QtWidgets.QFileDialog.getExistingDirectory(
|
||||
parent=self,
|
||||
caption='Export Images',
|
||||
directory=directory)
|
||||
|
||||
if not directory:
|
||||
return
|
||||
|
||||
logger.debug(f'Got export directory {directory}')
|
||||
self.exporter = ImagesToDirectoryExporter(self.scene, directory)
|
||||
self.worker = fileio.ThreadedIO(self.exporter.export)
|
||||
self.worker.user_input_required.connect(
|
||||
self.on_export_images_file_exists)
|
||||
self.worker.finished.connect(self.on_export_finished)
|
||||
self.progress = widgets.BeeProgressDialog(
|
||||
f'Exporting to {directory}',
|
||||
worker=self.worker,
|
||||
parent=self)
|
||||
self.worker.start()
|
||||
|
||||
def on_export_images_file_exists(self, filename):
|
||||
dlg = widgets.ExportImagesFileExistsDialog(self, filename)
|
||||
if dlg.exec() == QtWidgets.QDialog.DialogCode.Accepted:
|
||||
self.exporter.handle_existing = dlg.get_answer()
|
||||
directory = self.exporter.dirname
|
||||
self.progress = widgets.BeeProgressDialog(
|
||||
f'Exporting to {directory}',
|
||||
worker=self.worker,
|
||||
parent=self)
|
||||
self.worker.start()
|
||||
|
||||
def on_action_quit(self):
|
||||
logger.info('User quit. Exiting...')
|
||||
self.app.quit()
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ class BeeProgressDialog(QtWidgets.QProgressDialog):
|
|||
worker.begin_processing.connect(self.on_begin_processing)
|
||||
worker.progress.connect(self.on_progress)
|
||||
worker.finished.connect(self.on_finished)
|
||||
worker.user_input_required.connect(self.on_finished)
|
||||
self.canceled.connect(worker.on_canceled)
|
||||
|
||||
def on_progress(self, value):
|
||||
|
|
@ -54,7 +55,7 @@ class BeeProgressDialog(QtWidgets.QProgressDialog):
|
|||
logger.debug(f'Beginn progress dialog: {value}')
|
||||
self.setMaximum(value)
|
||||
|
||||
def on_finished(self, filename, errors):
|
||||
def on_finished(self, *args, **kwargs):
|
||||
logger.debug('Finished progress dialog')
|
||||
self.setValue(self.maximum())
|
||||
self.reset()
|
||||
|
|
@ -296,3 +297,42 @@ class SampleColorWidget(QtWidgets.QWidget):
|
|||
self.set_pos(pos)
|
||||
self.color = color
|
||||
self.repaint()
|
||||
|
||||
|
||||
class ExportImagesFileExistsDialog(QtWidgets.QDialog):
|
||||
|
||||
def __init__(self, parent, filename):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle('File exists')
|
||||
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
label = QtWidgets.QLabel(
|
||||
f'File already exists:\n{filename}')
|
||||
layout.addWidget(label)
|
||||
|
||||
choices = (('skip', 'Skip this file'),
|
||||
('skip_all', 'Skip all existing files'),
|
||||
('overwrite', 'Overwrite this file'),
|
||||
('overwrite_all', 'Overwrite all existing files'))
|
||||
|
||||
self.radio_buttons = {}
|
||||
for (value, label) in choices:
|
||||
btn = QtWidgets.QRadioButton(label)
|
||||
self.radio_buttons[value] = btn
|
||||
layout.addWidget(btn)
|
||||
self.radio_buttons['skip'].setChecked(True)
|
||||
|
||||
# Bottom row of buttons
|
||||
buttons = QtWidgets.QDialogButtonBox(
|
||||
QtWidgets.QDialogButtonBox.StandardButton.Ok
|
||||
| QtWidgets.QDialogButtonBox.StandardButton.Cancel)
|
||||
buttons.rejected.connect(self.reject)
|
||||
buttons.accepted.connect(self.accept)
|
||||
layout.addWidget(buttons)
|
||||
|
||||
def get_answer(self):
|
||||
for value, btn in self.radio_buttons.items():
|
||||
if btn.isChecked():
|
||||
return value
|
||||
|
|
|
|||
|
|
@ -1,13 +1,5 @@
|
|||
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,
|
||||
|
|
@ -21,446 +13,3 @@ from beeref.fileio.export import (
|
|||
('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
|
||||
|
|
|
|||
287
tests/fileio/test_export_images_to_directory.py
Normal file
287
tests/fileio/test_export_images_to_directory.py
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
import os
|
||||
import stat
|
||||
from unittest.mock import MagicMock
|
||||
import pytest
|
||||
|
||||
from PyQt6 import QtGui
|
||||
|
||||
from beeref.items import BeePixmapItem
|
||||
from beeref.fileio.errors import BeeFileIOError
|
||||
from beeref.fileio.export import ImagesToDirectoryExporter
|
||||
|
||||
|
||||
def test_images_to_directory_exporter_export_writes_images(
|
||||
view, tmpdir, imgdata3x3, imgfilename3x3,):
|
||||
item1 = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item1)
|
||||
item2 = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
item2.save_id = 3
|
||||
view.scene.addItem(item2)
|
||||
exporter = ImagesToDirectoryExporter(view.scene, tmpdir)
|
||||
exporter.export()
|
||||
|
||||
with open(os.path.join(tmpdir, '0003.png'), 'rb') as f:
|
||||
assert f.read().startswith(b'\x89PNG')
|
||||
with open(os.path.join(tmpdir, '0004.png'), 'rb') as f:
|
||||
assert f.read().startswith(b'\x89PNG')
|
||||
|
||||
|
||||
def test_images_to_directory_exporter_export_file_exists_no_user_input(
|
||||
view, tmpdir, imgdata3x3, imgfilename3x3,):
|
||||
item1 = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item1)
|
||||
item2 = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item2)
|
||||
|
||||
with open(os.path.join(tmpdir, '0002.png'), 'w') as f:
|
||||
assert f.write('foo')
|
||||
|
||||
exporter = ImagesToDirectoryExporter(view.scene, tmpdir)
|
||||
exporter.export()
|
||||
|
||||
with open(os.path.join(tmpdir, '0001.png'), 'rb') as f:
|
||||
assert f.read().startswith(b'\x89PNG')
|
||||
with open(os.path.join(tmpdir, '0002.png'), 'r') as f:
|
||||
assert f.read() == 'foo'
|
||||
|
||||
assert exporter.start_from == 1
|
||||
|
||||
|
||||
def test_images_to_directory_exporter_export_file_exists_skip(
|
||||
view, tmpdir, imgdata3x3, imgfilename3x3,):
|
||||
item1 = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item1)
|
||||
item2 = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item2)
|
||||
item3 = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item3)
|
||||
|
||||
with open(os.path.join(tmpdir, '0002.png'), 'w') as f:
|
||||
assert f.write('foo')
|
||||
with open(os.path.join(tmpdir, '0003.png'), 'w') as f:
|
||||
assert f.write('bar')
|
||||
|
||||
exporter = ImagesToDirectoryExporter(view.scene, tmpdir)
|
||||
exporter.handle_existing = 'skip'
|
||||
exporter.export()
|
||||
|
||||
with open(os.path.join(tmpdir, '0001.png'), 'rb') as f:
|
||||
assert f.read().startswith(b'\x89PNG')
|
||||
with open(os.path.join(tmpdir, '0002.png'), 'r') as f:
|
||||
assert f.read() == 'foo'
|
||||
with open(os.path.join(tmpdir, '0003.png'), 'r') as f:
|
||||
assert f.read() == 'bar'
|
||||
|
||||
assert exporter.start_from == 2
|
||||
assert exporter.handle_existing is None
|
||||
|
||||
|
||||
def test_images_to_directory_exporter_export_file_exists_skip_all(
|
||||
view, tmpdir, imgdata3x3, imgfilename3x3,):
|
||||
item1 = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item1)
|
||||
item2 = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item2)
|
||||
item3 = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item3)
|
||||
|
||||
with open(os.path.join(tmpdir, '0002.png'), 'w') as f:
|
||||
assert f.write('foo')
|
||||
with open(os.path.join(tmpdir, '0003.png'), 'w') as f:
|
||||
assert f.write('bar')
|
||||
|
||||
exporter = ImagesToDirectoryExporter(view.scene, tmpdir)
|
||||
exporter.handle_existing = 'skip_all'
|
||||
exporter.export()
|
||||
|
||||
with open(os.path.join(tmpdir, '0001.png'), 'rb') as f:
|
||||
assert f.read().startswith(b'\x89PNG')
|
||||
with open(os.path.join(tmpdir, '0002.png'), 'r') as f:
|
||||
assert f.read() == 'foo'
|
||||
with open(os.path.join(tmpdir, '0003.png'), 'r') as f:
|
||||
assert f.read() == 'bar'
|
||||
|
||||
assert exporter.handle_existing == 'skip_all'
|
||||
|
||||
|
||||
def test_images_to_directory_exporter_export_file_exists_overwrite(
|
||||
view, tmpdir, imgdata3x3, imgfilename3x3,):
|
||||
item1 = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item1)
|
||||
item2 = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item2)
|
||||
item3 = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item3)
|
||||
|
||||
with open(os.path.join(tmpdir, '0002.png'), 'w') as f:
|
||||
assert f.write('foo')
|
||||
with open(os.path.join(tmpdir, '0003.png'), 'w') as f:
|
||||
assert f.write('bar')
|
||||
|
||||
exporter = ImagesToDirectoryExporter(view.scene, tmpdir)
|
||||
exporter.handle_existing = 'overwrite'
|
||||
exporter.export()
|
||||
|
||||
with open(os.path.join(tmpdir, '0001.png'), 'rb') as f:
|
||||
assert f.read().startswith(b'\x89PNG')
|
||||
with open(os.path.join(tmpdir, '0002.png'), 'rb') as f:
|
||||
assert f.read().startswith(b'\x89PNG')
|
||||
with open(os.path.join(tmpdir, '0003.png'), 'r') as f:
|
||||
assert f.read() == 'bar'
|
||||
|
||||
assert exporter.start_from == 2
|
||||
assert exporter.handle_existing is None
|
||||
|
||||
|
||||
def test_images_to_directory_exporter_export_file_exists_overwrite_all(
|
||||
view, tmpdir, imgdata3x3, imgfilename3x3,):
|
||||
item1 = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item1)
|
||||
item2 = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item2)
|
||||
item3 = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item3)
|
||||
|
||||
with open(os.path.join(tmpdir, '0002.png'), 'w') as f:
|
||||
assert f.write('foo')
|
||||
with open(os.path.join(tmpdir, '0003.png'), 'w') as f:
|
||||
assert f.write('bar')
|
||||
|
||||
exporter = ImagesToDirectoryExporter(view.scene, tmpdir)
|
||||
exporter.handle_existing = 'overwrite_all'
|
||||
exporter.export()
|
||||
|
||||
with open(os.path.join(tmpdir, '0001.png'), 'rb') as f:
|
||||
assert f.read().startswith(b'\x89PNG')
|
||||
with open(os.path.join(tmpdir, '0002.png'), 'rb') as f:
|
||||
assert f.read().startswith(b'\x89PNG')
|
||||
with open(os.path.join(tmpdir, '0003.png'), 'rb') as f:
|
||||
assert f.read().startswith(b'\x89PNG')
|
||||
|
||||
assert exporter.handle_existing == 'overwrite_all'
|
||||
|
||||
|
||||
def test_images_to_directory_exporter_export_with_worker(
|
||||
view, tmpdir, imgdata3x3, imgfilename3x3,):
|
||||
item = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item)
|
||||
worker = MagicMock(canceled=False)
|
||||
exporter = ImagesToDirectoryExporter(view.scene, tmpdir)
|
||||
exporter.export(worker)
|
||||
|
||||
with open(os.path.join(tmpdir, '0001.png'), 'rb') as f:
|
||||
assert f.read().startswith(b'\x89PNG')
|
||||
|
||||
worker.begin_processing.emit.assert_called_once_with(1)
|
||||
worker.progress.emit.assert_called_with(0)
|
||||
worker.finished.emit.assert_called_once_with(tmpdir, [])
|
||||
|
||||
|
||||
def test_images_to_directory_exporter_export_with_worker_when_canceled(
|
||||
view, tmpdir, imgdata3x3, imgfilename3x3,):
|
||||
item = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item)
|
||||
worker = MagicMock(canceled=True)
|
||||
exporter = ImagesToDirectoryExporter(view.scene, tmpdir)
|
||||
exporter.export(worker)
|
||||
|
||||
assert os.path.exists(os.path.join(tmpdir, '0001.png')) is False
|
||||
|
||||
worker.begin_processing.emit.assert_called_once_with(1)
|
||||
worker.progress.emit.assert_called_once_with(0)
|
||||
worker.finished.emit.assert_called_once_with(tmpdir, [])
|
||||
|
||||
|
||||
def test_images_to_directory_exporter_export_with_worker_when_file_exists(
|
||||
view, tmpdir, imgdata3x3, imgfilename3x3,):
|
||||
item = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item)
|
||||
|
||||
with open(os.path.join(tmpdir, '0001.png'), 'w') as f:
|
||||
assert f.write('foo')
|
||||
|
||||
worker = MagicMock(canceled=False)
|
||||
exporter = ImagesToDirectoryExporter(view.scene, tmpdir)
|
||||
exporter.export(worker)
|
||||
|
||||
imgfilename = os.path.join(tmpdir, '0001.png')
|
||||
with open(imgfilename, 'r') as f:
|
||||
assert f.read() == 'foo'
|
||||
|
||||
worker.begin_processing.emit.assert_called_once_with(1)
|
||||
worker.progress.emit.assert_called_with(0)
|
||||
worker.user_input_required.emit.assert_called_once_with(imgfilename)
|
||||
|
||||
|
||||
def test_images_to_directory_exporter_export_when_dir_not_writeable(
|
||||
view, tmpdir, imgdata3x3, imgfilename3x3,):
|
||||
|
||||
item = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item)
|
||||
|
||||
os.chmod(tmpdir, stat.S_IREAD)
|
||||
exporter = ImagesToDirectoryExporter(view.scene, tmpdir)
|
||||
|
||||
with pytest.raises(BeeFileIOError) as e:
|
||||
exporter.export()
|
||||
assert e.filename == tmpdir
|
||||
|
||||
|
||||
def test_images_to_directory_exporter_export_when_dir_not_writeable_w_worker(
|
||||
view, tmpdir, imgdata3x3, imgfilename3x3,):
|
||||
|
||||
item = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item)
|
||||
|
||||
os.chmod(tmpdir, stat.S_IREAD)
|
||||
exporter = ImagesToDirectoryExporter(view.scene, tmpdir)
|
||||
worker = MagicMock(canceled=False)
|
||||
|
||||
exporter.export(worker)
|
||||
worker.begin_processing.emit.assert_called_once_with(1)
|
||||
worker.finished.emit.assert_called_once()
|
||||
args = worker.finished.emit.call_args.args
|
||||
assert args[0] == tmpdir
|
||||
assert len(args[1]) == 1
|
||||
|
||||
|
||||
def test_images_to_directory_exporter_export_when_img_not_writeable(
|
||||
view, tmpdir, imgdata3x3, imgfilename3x3,):
|
||||
|
||||
item = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item)
|
||||
|
||||
imgfilename = os.path.join(tmpdir, '0001.png')
|
||||
with open(imgfilename, 'w') as f:
|
||||
assert f.write('foo')
|
||||
os.chmod(imgfilename, stat.S_IREAD)
|
||||
|
||||
exporter = ImagesToDirectoryExporter(view.scene, tmpdir)
|
||||
exporter.handle_existing = 'overwrite_all'
|
||||
|
||||
with pytest.raises(BeeFileIOError) as e:
|
||||
exporter.export()
|
||||
assert e.filename == tmpdir
|
||||
|
||||
|
||||
def test_images_to_directory_exporter_export_when_img_not_writeable_w_worker(
|
||||
view, tmpdir, imgdata3x3, imgfilename3x3,):
|
||||
|
||||
item = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item)
|
||||
|
||||
imgfilename = os.path.join(tmpdir, '0001.png')
|
||||
with open(imgfilename, 'w') as f:
|
||||
assert f.write('foo')
|
||||
os.chmod(imgfilename, stat.S_IREAD)
|
||||
|
||||
exporter = ImagesToDirectoryExporter(view.scene, tmpdir)
|
||||
exporter.handle_existing = 'overwrite_all'
|
||||
worker = MagicMock(canceled=False)
|
||||
|
||||
exporter.export(worker)
|
||||
worker.begin_processing.emit.assert_called_once_with(1)
|
||||
worker.finished.emit.assert_called_once()
|
||||
args = worker.finished.emit.call_args.args
|
||||
assert args[0] == imgfilename
|
||||
assert len(args[1]) == 1
|
||||
181
tests/fileio/test_export_scene_to_pixmap.py
Normal file
181
tests/fileio/test_export_scene_to_pixmap.py
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
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
|
||||
from beeref.fileio.errors import BeeFileIOError
|
||||
from beeref.fileio.export import SceneToPixmapExporter
|
||||
|
||||
|
||||
@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'])
|
||||
283
tests/fileio/test_export_scene_to_svg.py
Normal file
283
tests/fileio/test_export_scene_to_svg.py
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
import os
|
||||
import stat
|
||||
from unittest.mock import MagicMock
|
||||
import pytest
|
||||
|
||||
from PyQt6 import QtGui, QtCore
|
||||
|
||||
from beeref.items import BeePixmapItem, BeeTextItem
|
||||
from beeref.fileio.errors import BeeFileIOError
|
||||
from beeref.fileio.export import SceneToSVGExporter
|
||||
|
||||
|
||||
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
|
||||
|
|
@ -98,6 +98,43 @@ def test_get_extra_save_data(item):
|
|||
}
|
||||
|
||||
|
||||
def test_get_filename_for_export_when_save_id_and_filename(item):
|
||||
item.filename = 'foo.png'
|
||||
item.save_id = 5
|
||||
assert item.get_filename_for_export('jpg', 8) == '0005-foo.jpg'
|
||||
|
||||
|
||||
def test_get_filename_for_export_when_save_id_and_no_filename(item):
|
||||
item.filename = None
|
||||
item.save_id = 5
|
||||
assert item.get_filename_for_export('jpg', 8) == '0005.jpg'
|
||||
|
||||
|
||||
def test_get_filename_for_export_when_no_save_id_and_filename(item):
|
||||
item.filename = 'foo.png'
|
||||
item.save_id = None
|
||||
assert item.get_filename_for_export('jpg', 8) == '0008-foo.jpg'
|
||||
|
||||
|
||||
def test_get_filename_for_export_when_no_save_id_and_no_filename(item):
|
||||
item.filename = None
|
||||
item.save_id = None
|
||||
assert item.get_filename_for_export('jpg', 8) == '0008.jpg'
|
||||
|
||||
|
||||
def test_get_filename_for_export_when_save_id_and_no_default(item):
|
||||
item.filename = 'foo.png'
|
||||
item.save_id = 5
|
||||
assert item.get_filename_for_export('jpg') == '0005-foo.jpg'
|
||||
|
||||
|
||||
def test_get_filename_for_export_when_no_save_id_and_no_default(item):
|
||||
item.filename = 'foo.png'
|
||||
item.save_id = None
|
||||
with pytest.raises(AssertionError):
|
||||
assert item.get_filename_for_export('jpg')
|
||||
|
||||
|
||||
def test_get_imgformat_test_with_real_image(
|
||||
qapp, imgfilename3x3, item, settings):
|
||||
settings.setValue('Items/image_storage_format', 'best')
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import os.path
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import sqlite3
|
||||
from unittest.mock import MagicMock, patch, mock_open
|
||||
|
|
@ -370,6 +371,99 @@ def test_on_action_export_scene_settings_input_canceled(
|
|||
assert os.path.exists(filename) is False
|
||||
|
||||
|
||||
@patch('PyQt6.QtWidgets.QFileDialog.getExistingDirectory')
|
||||
def test_on_action_export_images(
|
||||
dir_mock, view, tmpdir, qtbot, imgfilename3x3):
|
||||
item = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item)
|
||||
dir_mock.return_value = tmpdir
|
||||
view.on_export_finished = MagicMock()
|
||||
|
||||
view.on_action_export_images()
|
||||
qtbot.waitUntil(lambda: view.on_export_finished.called is True)
|
||||
view.on_export_finished.assert_called_once_with(tmpdir, [])
|
||||
assert os.path.exists(os.path.join(tmpdir, '0001.png'))
|
||||
|
||||
|
||||
@patch('PyQt6.QtWidgets.QFileDialog.getExistingDirectory')
|
||||
def test_on_action_export_images_no_dirname(
|
||||
dir_mock, view, tmpdir, qtbot, imgfilename3x3):
|
||||
item = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item)
|
||||
dir_mock.return_value = None
|
||||
view.on_export_finished = MagicMock()
|
||||
|
||||
view.on_action_export_images()
|
||||
view.on_export_finished.assert_not_called()
|
||||
assert os.path.exists(os.path.join(tmpdir, '0001.png')) is False
|
||||
|
||||
|
||||
@patch('beeref.widgets.ExportImagesFileExistsDialog.exec',
|
||||
return_value=QtWidgets.QDialog.DialogCode.Accepted)
|
||||
@patch('beeref.widgets.ExportImagesFileExistsDialog.get_answer',
|
||||
return_value='overwrite')
|
||||
@patch('PyQt6.QtWidgets.QFileDialog.getExistingDirectory')
|
||||
def test_on_action_export_images_file_exists_overwrite(
|
||||
dir_mock, answer_mock, exec_mock, view, tmpdir, qtbot, imgfilename3x3):
|
||||
item = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item)
|
||||
dir_mock.return_value = tmpdir
|
||||
view.on_export_finished = MagicMock()
|
||||
|
||||
imgfilename = Path(tmpdir) / '0001.png'
|
||||
imgfilename.write_text('foo')
|
||||
|
||||
view.on_action_export_images()
|
||||
qtbot.waitUntil(lambda: view.on_export_finished.called is True)
|
||||
view.on_export_finished.assert_called_once_with(tmpdir, [])
|
||||
answer_mock.assert_called_once_with()
|
||||
exec_mock.assert_called_once_with()
|
||||
imgfilename.read_bytes().startswith(b'\x89PNG')
|
||||
|
||||
|
||||
@patch('beeref.widgets.ExportImagesFileExistsDialog.exec',
|
||||
return_value=QtWidgets.QDialog.DialogCode.Accepted)
|
||||
@patch('beeref.widgets.ExportImagesFileExistsDialog.get_answer',
|
||||
return_value='skip')
|
||||
@patch('PyQt6.QtWidgets.QFileDialog.getExistingDirectory')
|
||||
def test_on_action_export_images_file_exists_skip(
|
||||
dir_mock, answer_mock, exec_mock, view, tmpdir, qtbot, imgfilename3x3):
|
||||
item = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item)
|
||||
dir_mock.return_value = tmpdir
|
||||
view.on_export_finished = MagicMock()
|
||||
imgfilename = Path(tmpdir) / '0001.png'
|
||||
imgfilename.write_text('foo')
|
||||
|
||||
view.on_action_export_images()
|
||||
qtbot.waitUntil(lambda: view.on_export_finished.called is True)
|
||||
view.on_export_finished.assert_called_once_with(tmpdir, [])
|
||||
answer_mock.assert_called_once_with()
|
||||
exec_mock.assert_called_once_with()
|
||||
imgfilename.read_text() == 'foo'
|
||||
|
||||
|
||||
@patch('beeref.widgets.ExportImagesFileExistsDialog.exec',
|
||||
return_value=QtWidgets.QDialog.DialogCode.Rejected)
|
||||
@patch('beeref.widgets.ExportImagesFileExistsDialog.get_answer',
|
||||
return_value='skip')
|
||||
@patch('PyQt6.QtWidgets.QFileDialog.getExistingDirectory')
|
||||
def test_on_action_export_images_file_exists_canceled(
|
||||
dir_mock, answer_mock, exec_mock, view, tmpdir, qtbot, imgfilename3x3):
|
||||
item = BeePixmapItem(QtGui.QImage(imgfilename3x3))
|
||||
view.scene.addItem(item)
|
||||
dir_mock.return_value = tmpdir
|
||||
view.on_export_finished = MagicMock()
|
||||
imgfilename = Path(tmpdir) / '0001.png'
|
||||
imgfilename.write_text('foo')
|
||||
|
||||
view.on_action_export_images()
|
||||
qtbot.waitUntil(lambda: exec_mock.called is True)
|
||||
view.on_export_finished.assert_not_called()
|
||||
answer_mock.assert_not_called()
|
||||
imgfilename.read_text() == 'foo'
|
||||
|
||||
|
||||
@patch('beeref.widgets.settings.SettingsDialog.show')
|
||||
def test_on_action_settings(show_mock, view):
|
||||
view.on_action_settings()
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from beeref.widgets import (
|
|||
BeeNotification,
|
||||
ChangeOpacityDialog,
|
||||
DebugLogDialog,
|
||||
ExportImagesFileExistsDialog,
|
||||
SampleColorWidget,
|
||||
SceneToPixmapExporterDialog,
|
||||
)
|
||||
|
|
@ -139,3 +140,15 @@ def test_sample_color_widget_paint_event_when_no_color(view):
|
|||
brush = QtGui.QBrush(QtGui.QColor(0, 0, 0, 0))
|
||||
painter_mock.setBrush.assert_called_once_with(brush)
|
||||
painter_mock.drawRect.assert_called_once_with(0, 0, 50, 50)
|
||||
|
||||
|
||||
def test_export_images_file_exists_dialog(view):
|
||||
dlg = ExportImagesFileExistsDialog(view, '/tmp/foo.png')
|
||||
assert len(dlg.radio_buttons) == 4
|
||||
assert dlg.get_answer() == 'skip'
|
||||
|
||||
|
||||
def test_export_images_file_exists_dialog_get_answer(view):
|
||||
dlg = ExportImagesFileExistsDialog(view, '/tmp/foo.png')
|
||||
dlg.radio_buttons['overwrite'].setChecked(True)
|
||||
assert dlg.get_answer() == 'overwrite'
|
||||
|
|
|
|||
Loading…
Reference in a new issue