diff --git a/libs/libkis/Node.h b/libs/libkis/Node.h --- a/libs/libkis/Node.h +++ b/libs/libkis/Node.h @@ -481,10 +481,13 @@ * @param xRes the horizontal resolution in pixels per pt (there are 72 pts in an inch) * @param yRes the horizontal resolution in pixels per pt (there are 72 pts in an inch) * @param exportConfiguration a configuration object appropriate to the file format. + * @param exportRect the export bounds for saving a node as a QRect + * If \p exportRect is empty, then save exactBounds() of the node. If you'd like to save the image- + * aligned area of the node, just pass image->bounds() there. * See Document->exportImage for InfoObject details. * @return true if saving succeeded, false if it failed. */ - bool save(const QString &filename, double xRes, double yRes, const InfoObject &exportConfiguration); + bool save(const QString &filename, double xRes, double yRes, const InfoObject &exportConfiguration, const QRect &exportRect = QRect()); /** * @brief mergeDown merges the given node with the first visible node underneath this node in the layerstack. diff --git a/libs/libkis/Node.cpp b/libs/libkis/Node.cpp --- a/libs/libkis/Node.cpp +++ b/libs/libkis/Node.cpp @@ -539,13 +539,13 @@ return new Node(d->image, d->node->clone()); } -bool Node::save(const QString &filename, double xRes, double yRes, const InfoObject &exportConfiguration) +bool Node::save(const QString &filename, double xRes, double yRes, const InfoObject &exportConfiguration, const QRect &exportRect) { if (!d->node) return false; if (filename.isEmpty()) return false; KisPaintDeviceSP projection = d->node->projection(); - QRect bounds = d->node->exactBounds(); + QRect bounds = (exportRect.isEmpty())? d->node->exactBounds() : exportRect; QString mimeType = KisMimeDatabase::mimeTypeForFile(filename, false); QScopedPointer doc(KisPart::instance()->createDocument()); diff --git a/plugins/extensions/pykrita/sip/krita/Node.sip b/plugins/extensions/pykrita/sip/krita/Node.sip --- a/plugins/extensions/pykrita/sip/krita/Node.sip +++ b/plugins/extensions/pykrita/sip/krita/Node.sip @@ -56,7 +56,7 @@ QPoint position() const; bool remove(); Node *duplicate() /Factory/; - void save(const QString &filename, double xRes, double yRes, const InfoObject & exportConfiguration); + void save(const QString &filename, double xRes, double yRes, const InfoObject & exportConfiguration, const QRect &exportRect = QRect()); Node *mergeDown() /Factory/; void scaleNode(QPointF origin, int width, int height, QString strategy); void rotateNode(double radians); diff --git a/plugins/python/exportlayers/uiexportlayers.py b/plugins/python/exportlayers/uiexportlayers.py --- a/plugins/python/exportlayers/uiexportlayers.py +++ b/plugins/python/exportlayers/uiexportlayers.py @@ -13,7 +13,7 @@ # https://creativecommons.org/publicdomain/zero/1.0/legalcode from . import exportlayersdialog -from PyQt5.QtCore import Qt +from PyQt5.QtCore import (Qt, QRect) from PyQt5.QtWidgets import (QFormLayout, QListWidget, QHBoxLayout, QDialogButtonBox, QVBoxLayout, QFrame, QPushButton, QAbstractScrollArea, QLineEdit, @@ -29,10 +29,11 @@ self.mainDialog = exportlayersdialog.ExportLayersDialog() self.mainLayout = QVBoxLayout(self.mainDialog) self.formLayout = QFormLayout() + self.resSpinBoxLayout = QFormLayout() self.documentLayout = QVBoxLayout() self.directorySelectorLayout = QHBoxLayout() self.optionsLayout = QVBoxLayout() - self.resolutionLayout = QHBoxLayout() + self.rectSizeLayout = QHBoxLayout() self.refreshButton = QPushButton(i18n("Refresh")) self.widgetDocuments = QListWidget() @@ -43,9 +44,13 @@ self.batchmodeCheckBox = QCheckBox(i18n("Export in batchmode")) self.ignoreInvisibleLayersCheckBox = QCheckBox( i18n("Ignore invisible layers")) - self.xResSpinBox = QSpinBox() - self.yResSpinBox = QSpinBox() + self.cropToImageBounds = QCheckBox( + i18n("Adjust export size to layer content")) + + self.rectWidthSpinBox = QSpinBox() + self.rectHeightSpinBox = QSpinBox() self.formatsComboBox = QComboBox() + self.resSpinBox = QSpinBox() self.buttonBox = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel) @@ -60,16 +65,18 @@ self.refreshButton.clicked.connect(self.refreshButtonClicked) self.buttonBox.accepted.connect(self.confirmButton) self.buttonBox.rejected.connect(self.mainDialog.close) + self.cropToImageBounds.stateChanged.connect(self._toggleCropSize) self.mainDialog.setWindowModality(Qt.NonModal) self.widgetDocuments.setSizeAdjustPolicy( QAbstractScrollArea.AdjustToContents) def initialize(self): self.loadDocuments() - self.xResSpinBox.setRange(1, 10000) - self.yResSpinBox.setRange(1, 10000) + self.rectWidthSpinBox.setRange(1, 10000) + self.rectHeightSpinBox.setRange(1, 10000) + self.resSpinBox.setRange(20, 1200) self.formatsComboBox.addItem(i18n("JPEG")) self.formatsComboBox.addItem(i18n("PNG")) @@ -83,15 +90,19 @@ self.optionsLayout.addWidget(self.exportFilterLayersCheckBox) self.optionsLayout.addWidget(self.batchmodeCheckBox) self.optionsLayout.addWidget(self.ignoreInvisibleLayersCheckBox) + self.optionsLayout.addWidget(self.cropToImageBounds) + + self.resSpinBoxLayout.addRow(i18n("dpi:"), self.resSpinBox) - self.resolutionLayout.addWidget(self.xResSpinBox) - self.resolutionLayout.addWidget(self.yResSpinBox) + self.rectSizeLayout.addWidget(self.rectWidthSpinBox) + self.rectSizeLayout.addWidget(self.rectHeightSpinBox) + self.rectSizeLayout.addLayout(self.resSpinBoxLayout) self.formLayout.addRow(i18n("Documents:"), self.documentLayout) self.formLayout.addRow( i18n("Initial directory:"), self.directorySelectorLayout) self.formLayout.addRow(i18n("Export options:"), self.optionsLayout) - self.formLayout.addRow(i18n("Resolution:"), self.resolutionLayout) + self.formLayout.addRow(i18n("Export size:"), self.rectSizeLayout) self.formLayout.addRow( i18n("Images extensions:"), self.formatsComboBox) @@ -188,11 +199,16 @@ elif '[png]' in nodeName: _fileFormat = 'png' + if self.cropToImageBounds.isChecked(): + bounds = QRect() + else: + bounds = QRect(0, 0, self.rectWidthSpinBox.value(), self.rectHeightSpinBox.value()) + layerFileName = '{0}{1}/{2}.{3}'.format( self.directoryTextField.text(), parentDir, node.name(), _fileFormat) - node.save(layerFileName, self.xResSpinBox.value(), - self.yResSpinBox.value(), krita.InfoObject()) + node.save(layerFileName, self.resSpinBox.value() / 72., + self.resSpinBox.value() / 72., krita.InfoObject(), bounds) if node.childNodes(): self._exportLayers(node, fileFormat, newDir) @@ -207,5 +223,11 @@ def _setResolution(self, index): document = self.documentsList[index] - self.xResSpinBox.setValue(document.width()) - self.yResSpinBox.setValue(document.height()) + self.rectWidthSpinBox.setValue(document.width()) + self.rectHeightSpinBox.setValue(document.height()) + self.resSpinBox.setValue(document.resolution()) + + def _toggleCropSize(self): + cropToLayer = self.cropToImageBounds.isChecked() + self.rectWidthSpinBox.setDisabled(cropToLayer) + self.rectHeightSpinBox.setDisabled(cropToLayer)