diff --git a/plugins/python/channels2layers/channels2layers.py b/plugins/python/channels2layers/channels2layers.py
index f71f0c0073..c03dcdb893 100644
--- a/plugins/python/channels2layers/channels2layers.py
+++ b/plugins/python/channels2layers/channels2layers.py
@@ -1,1461 +1,1461 @@
#-----------------------------------------------------------------------------
# Channels to Layers
# Copyright (C) 2019 - Grum999
# -----------------------------------------------------------------------------
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.
# If not, see https://www.gnu.org/licenses/
# -----------------------------------------------------------------------------
# A Krita plugin designed to split channels from a layer to sub-layers
# . RGB
# . CMY
# . CMYK
# . RGB as greayscale values
# . CMY as greayscale values
# . CMYK as greayscale values
# -----------------------------------------------------------------------------
import re
from krita import (
Extension,
InfoObject,
Node,
Selection
)
from PyQt5.Qt import *
from PyQt5 import QtCore
from PyQt5.QtCore import (
pyqtSlot,
QBuffer,
QByteArray,
QIODevice
)
from PyQt5.QtGui import (
QColor,
QImage,
QPixmap,
)
from PyQt5.QtWidgets import (
QApplication,
QCheckBox,
QComboBox,
QDialog,
QDialogButtonBox,
QFormLayout,
QGroupBox,
QHBoxLayout,
QLabel,
QLineEdit,
QMessageBox,
QProgressBar,
QProgressDialog,
QVBoxLayout,
QWidget
)
PLUGIN_VERSION = '1.1.0'
EXTENSION_ID = 'pykrita_channels2layers'
PLUGIN_MENU_ENTRY = i18n('Channels to layers')
PLUGIN_DIALOG_TITLE = "{0} - {1}".format(i18n('Channels to layers'), PLUGIN_VERSION)
# Define DialogBox types
DBOX_INFO = 'i'
DBOX_WARNING ='w'
# Define Output modes
OUTPUT_MODE_RGB = i18n('RGB Colors')
OUTPUT_MODE_CMY = i18n('CMY Colors')
OUTPUT_MODE_CMYK = i18n('CMYK Colors')
OUTPUT_MODE_LRGB = i18n('RGB Grayscale levels')
OUTPUT_MODE_LCMY = i18n('CMY Grayscale levels')
OUTPUT_MODE_LCMYK = i18n('CMYK Grayscale levels')
OUTPUT_PREVIEW_MAXSIZE = 320
# Define original layer action
ORIGINAL_LAYER_KEEPUNCHANGED = i18n('Unchanged')
ORIGINAL_LAYER_KEEPVISIBLE = i18n('Visible')
ORIGINAL_LAYER_KEEPHIDDEN = i18n('Hidden')
ORIGINAL_LAYER_REMOVE = i18n('Remove')
# define dialog option minimum dimension
DOPT_MIN_WIDTH = OUTPUT_PREVIEW_MAXSIZE * 5 + 200
DOPT_MIN_HEIGHT = 480
OUTPUT_MODE_NFO = {
OUTPUT_MODE_RGB : {
'description' : 'Extract channels (Red, Green, Blue) and create a colored layer per channel',
'groupLayerName' : 'RGB',
'layers' : [
{
'color' : 'B',
'process': [
{
'action' : 'duplicate',
'value' : '@original'
},
{
'action' : 'new',
'value' : {
'type' : 'filllayer',
'color' : QColor(Qt.blue)
}
},
{
'action' : 'blending mode',
'value' : 'inverse_subtract'
},
{
'action' : 'merge down',
'value' : None
},
{
'action' : 'blending mode',
'value' : 'add'
}
]
},
{
'color' : 'G',
'process': [
{
'action' : 'duplicate',
'value' : '@original'
},
{
'action' : 'new',
'value' : {
'type' : 'filllayer',
'color' : QColor(Qt.green)
}
},
{
'action' : 'blending mode',
'value' : 'inverse_subtract'
},
{
'action' : 'merge down',
'value' : None
},
{
'action' : 'blending mode',
'value' : 'add'
}
]
},
{
'color' : 'R',
'process': [
{
'action' : 'duplicate',
'value' : '@original'
},
{
'action' : 'new',
'value' : {
'type' : 'filllayer',
'color' : QColor(Qt.red)
}
},
{
'action' : 'blending mode',
'value' : 'inverse_subtract'
},
{
'action' : 'merge down',
'value' : None
},
{
'action' : 'blending mode',
'value' : 'add'
}
]
}
]
},
OUTPUT_MODE_CMY : {
'description' : 'Extract channels (Cyan, Mangenta, Yellow) and create a colored layer per channel',
'groupLayerName' : 'CMY',
'layers' : [
{
'color' : 'Y',
'process': [
{
'action' : 'duplicate',
'value' : '@original'
},
{
'action' : 'new',
'value' : {
'type' : 'filllayer',
'color' : QColor(Qt.yellow)
}
},
{
'action' : 'blending mode',
'value' : 'add'
},
{
'action' : 'merge down',
'value' : None
},
{
'action' : 'blending mode',
'value' : 'multiply'
}
]
},
{
'color' : 'M',
'process': [
{
'action' : 'duplicate',
'value' : '@original'
},
{
'action' : 'new',
'value' : {
'type' : 'filllayer',
'color' : QColor(Qt.magenta)
}
},
{
'action' : 'blending mode',
'value' : 'add'
},
{
'action' : 'merge down',
'value' : None
},
{
'action' : 'blending mode',
'value' : 'multiply'
}
]
},
{
'color' : 'C',
'process': [
{
'action' : 'duplicate',
'value' : '@original'
},
{
'action' : 'new',
'value' : {
'type' : 'filllayer',
'color' : QColor(Qt.cyan)
}
},
{
'action' : 'blending mode',
'value' : 'add'
},
{
'action' : 'merge down',
'value' : None
},
{
'action' : 'blending mode',
'value' : 'multiply'
}
]
}
]
},
OUTPUT_MODE_CMYK : {
'description' : 'Extract channels (Cyan, Mangenta, Yellow, Black) and create a colored layer per channel',
'groupLayerName' : 'CMYK',
'layers' : [
{
'color' : 'K',
'process': [
{
'action' : 'duplicate',
'value' : '@original'
},
{
'action' : 'filter',
'value' : 'name=desaturate;type=5' # desaturate method = max
}
]
},
{
'color' : 'Y',
'process': [
{
'action' : 'duplicate',
'value' : '@original'
},
{
'action' : 'new',
'value' : {
'type' : 'filllayer',
'color' : QColor(Qt.yellow)
}
},
{
'action' : 'blending mode',
'value' : 'add'
},
{
'action' : 'merge down',
'value' : None
},
{
'action' : 'duplicate',
'value' : '@K'
},
{
'action' : 'blending mode',
'value' : 'divide'
},
{
'action' : 'merge down',
'value' : None
},
{
'action' : 'blending mode',
'value' : 'multiply'
}
]
},
{
'color' : 'M',
'process': [
{
'action' : 'duplicate',
'value' : '@original'
},
{
'action' : 'new',
'value' : {
'type' : 'filllayer',
'color' : QColor(Qt.magenta)
}
},
{
'action' : 'blending mode',
'value' : 'add'
},
{
'action' : 'merge down',
'value' : None
},
{
'action' : 'duplicate',
'value' : '@K'
},
{
'action' : 'blending mode',
'value' : 'divide'
},
{
'action' : 'merge down',
'value' : None
},
{
'action' : 'blending mode',
'value' : 'multiply'
}
]
},
{
'color' : 'C',
'process': [
{
'action' : 'duplicate',
'value' : '@original'
},
{
'action' : 'new',
'value' : {
'type' : 'filllayer',
'color' : QColor(Qt.cyan)
}
},
{
'action' : 'blending mode',
'value' : 'add'
},
{
'action' : 'merge down',
'value' : None
},
{
'action' : 'duplicate',
'value' : '@K'
},
{
'action' : 'blending mode',
'value' : 'divide'
},
{
'action' : 'merge down',
'value' : None
},
{
'action' : 'blending mode',
'value' : 'multiply'
}
]
}
]
},
OUTPUT_MODE_LRGB : {
'description' : 'Extract channels (Red, Green, Blue) and create a grayscale layer per channel',
'groupLayerName' : 'RGB[GS]',
'layers' : [
{
'color' : 'B',
'process': [
{
'action' : 'duplicate',
'value' : '@original'
},
{
'action' : 'new',
'value' : {
'type' : 'filllayer',
'color' : QColor(Qt.blue)
}
},
{
'action' : 'blending mode',
'value' : 'inverse_subtract'
},
{
'action' : 'merge down',
'value' : None
},
{
'action' : 'blending mode',
'value' : 'add'
},
{
'action' : 'filter',
'value' : 'name=desaturate;type=5' # desaturate method = max
}
]
},
{
'color' : 'G',
'process': [
{
'action' : 'duplicate',
'value' : '@original'
},
{
'action' : 'new',
'value' : {
'type' : 'filllayer',
'color' : QColor(Qt.green)
}
},
{
'action' : 'blending mode',
'value' : 'inverse_subtract'
},
{
'action' : 'merge down',
'value' : None
},
{
'action' : 'blending mode',
'value' : 'add'
},
{
'action' : 'filter',
'value' : 'name=desaturate;type=5' # desaturate method = max
}
]
},
{
'color' : 'R',
'process': [
{
'action' : 'duplicate',
'value' : '@original'
},
{
'action' : 'new',
'value' : {
'type' : 'filllayer',
'color' : QColor(Qt.red)
}
},
{
'action' : 'blending mode',
'value' : 'inverse_subtract'
},
{
'action' : 'merge down',
'value' : None
},
{
'action' : 'blending mode',
'value' : 'add'
},
{
'action' : 'filter',
'value' : 'name=desaturate;type=5' # desaturate method = max
}
]
}
]
},
OUTPUT_MODE_LCMY : {
'description' : 'Extract channels (Cyan, Mangenta, Yellow) and create a grayscale layer per channel',
'groupLayerName' : 'CMY[GS]',
'layers' : [
{
'color' : 'Y',
'process': [
{
'action' : 'duplicate',
'value' : '@original'
},
{
'action' : 'new',
'value' : {
'type' : 'filllayer',
'color' : QColor(Qt.yellow)
}
},
{
'action' : 'blending mode',
'value' : 'add'
},
{
'action' : 'merge down',
'value' : None
},
{
'action' : 'blending mode',
'value' : 'add'
},
{
'action' : 'filter',
'value' : 'name=desaturate;type=4' # desaturate method = min
}
]
},
{
'color' : 'M',
'process': [
{
'action' : 'duplicate',
'value' : '@original'
},
{
'action' : 'new',
'value' : {
'type' : 'filllayer',
'color' : QColor(Qt.magenta)
}
},
{
'action' : 'blending mode',
'value' : 'add'
},
{
'action' : 'merge down',
'value' : None
},
{
'action' : 'blending mode',
'value' : 'add'
},
{
'action' : 'filter',
'value' : 'name=desaturate;type=4' # desaturate method = min
}
]
},
{
'color' : 'C',
'process': [
{
'action' : 'duplicate',
'value' : '@original'
},
{
'action' : 'new',
'value' : {
'type' : 'filllayer',
'color' : QColor(Qt.cyan)
}
},
{
'action' : 'blending mode',
'value' : 'add'
},
{
'action' : 'merge down',
'value' : None
},
{
'action' : 'blending mode',
'value' : 'add'
},
{
'action' : 'filter',
'value' : 'name=desaturate;type=4' # desaturate method = min
}
]
}
]
},
OUTPUT_MODE_LCMYK : {
'description' : 'Extract channels (Cyan, Mangenta, Yellow, Black) and create a grayscale layer per channel',
'groupLayerName' : 'CMYK[GS]',
'layers' : [
{
'color' : 'K',
'process': [
{
'action' : 'duplicate',
'value' : '@original'
},
{
'action' : 'filter',
'value' : 'name=desaturate;type=5' # desaturate method = max
}
]
},
{
'color' : 'Y',
'process': [
{
'action' : 'duplicate',
'value' : '@original'
},
{
'action' : 'new',
'value' : {
'type' : 'filllayer',
'color' : QColor(Qt.yellow)
}
},
{
'action' : 'blending mode',
'value' : 'add'
},
{
'action' : 'merge down',
'value' : None
},
{
'action' : 'duplicate',
'value' : '@K'
},
{
'action' : 'blending mode',
'value' : 'divide'
},
{
'action' : 'merge down',
'value' : None
},
{
'action' : 'blending mode',
'value' : 'multiply'
},
{
'action' : 'filter',
'value' : 'name=desaturate;type=4' # desaturate method = min
}
]
},
{
'color' : 'M',
'process': [
{
'action' : 'duplicate',
'value' : '@original'
},
{
'action' : 'new',
'value' : {
'type' : 'filllayer',
'color' : QColor(Qt.magenta)
}
},
{
'action' : 'blending mode',
'value' : 'add'
},
{
'action' : 'merge down',
'value' : None
},
{
'action' : 'duplicate',
'value' : '@K'
},
{
'action' : 'blending mode',
'value' : 'divide'
},
{
'action' : 'merge down',
'value' : None
},
{
'action' : 'blending mode',
'value' : 'multiply'
},
{
'action' : 'filter',
'value' : 'name=desaturate;type=4' # desaturate method = min
}
]
},
{
'color' : 'C',
'process': [
{
'action' : 'duplicate',
'value' : '@original'
},
{
'action' : 'new',
'value' : {
'type' : 'filllayer',
'color' : QColor(Qt.cyan)
}
},
{
'action' : 'blending mode',
'value' : 'add'
},
{
'action' : 'merge down',
'value' : None
},
{
'action' : 'duplicate',
'value' : '@K'
},
{
'action' : 'blending mode',
'value' : 'divide'
},
{
'action' : 'merge down',
'value' : None
},
{
'action' : 'blending mode',
'value' : 'multiply'
},
{
'action' : 'filter',
'value' : 'name=desaturate;type=4' # desaturate method = min
}
]
}
]
}
}
TRANSLATIONS_DICT = {
'colorDepth' : {
'U8' : '8bits',
'U16' : '16bits',
'F16' : '16bits floating point',
'F32' : '32bits floating point'
},
'colorModel' : {
'A' : 'Alpha mask',
'RGBA' : 'RGB with alpha channel',
'XYZA' : 'XYZ with alpha channel',
'LABA' : 'LAB with alpha channel',
'CMYKA' : 'CMYK with alpha channel',
'GRAYA' : 'Gray with alpha channel',
'YCbCrA' : 'YCbCr with alpha channel'
},
'layerType' : {
'paintlayer' : 'Paint layer',
'grouplayer' : 'Group layer',
'filelayer' : 'File layer',
'filterlayer' : 'Filter layer',
'filllayer' : 'Fill layer',
'clonelayer' : 'Clone layer',
'vectorlayer' : 'Vector layer',
'transparencymask' : 'Transparency mask',
'filtermask' : 'Filter mask',
'transformmask': 'Transform mask',
'selectionmask': 'Selection mask',
'colorizemask' : 'Colorize mask'
}
}
class ChannelsToLayers(Extension):
def __init__(self, parent):
# Default options
self.__outputOptions = {
'outputMode': OUTPUT_MODE_RGB,
'originalLayerAction': ORIGINAL_LAYER_KEEPHIDDEN,
'layerGroupName': '{mode}-{source:name}',
'layerColorName': '{mode}[{color:short}]-{source:name}'
}
self.__sourceDocument = None
self.__sourceLayer = None
# Always initialise the superclass.
# This is necessary to create the underlying C++ object
super().__init__(parent)
self.parent = parent
def setup(self):
pass
def createActions(self, window):
action = window.createAction(EXTENSION_ID, PLUGIN_MENU_ENTRY, "tools/scripts")
action.triggered.connect(self.action_triggered)
def dBoxMessage(self, msgType, msg):
"""Simplified function for DialogBox 'OK' message"""
if msgType == DBOX_WARNING:
QMessageBox.warning(
QWidget(),
PLUGIN_DIALOG_TITLE,
i18n(msg)
)
else:
QMessageBox.information(
QWidget(),
PLUGIN_DIALOG_TITLE,
i18n(msg)
)
def action_triggered(self):
"""Action called when script is executed from Kitra menu"""
if self.checkCurrentLayer():
if self.openDialogOptions():
self.run()
def translateDictKey(self, key, value):
"""Translate key from dictionnary (mostly internal Krita internal values) to human readable values"""
returned = i18n('Unknown')
if key in TRANSLATIONS_DICT.keys():
if value in TRANSLATIONS_DICT[key].keys():
returned = i18n(TRANSLATIONS_DICT[key][value])
return returned
def checkCurrentLayer(self):
"""Check if current layer is valid
- A document must be opened
- Active layer properties must be:
. Layer type: a paint layer
. Color model: RGBA
. Color depth: 8bits
"""
self.__sourceDocument = Application.activeDocument()
# Check if there's an active document
if self.__sourceDocument is None:
self.dBoxMessage(DBOX_WARNING, "There's no active document!")
return False
self.__sourceLayer = self.__sourceDocument.activeNode()
# Check if current layer can be processed
if self.__sourceLayer.type() != "paintlayer" or self.__sourceLayer.colorModel() != "RGBA" or self.__sourceLayer.colorDepth() != "U8":
self.dBoxMessage(DBOX_WARNING, "Selected layer must be a 8bits RGBA Paint Layer!"
"\n\nCurrent layer '{0}' properties:"
"\n- Layer type: {1}"
"\n- Color model: {2} ({3})"
"\n- Color depth: {4}"
"\n\n> Action is cancelled".format(self.__sourceLayer.name(),
self.translateDictKey('layerType', self.__sourceLayer.type()),
self.__sourceLayer.colorModel(), self.translateDictKey('colorModel', self.__sourceLayer.colorModel()),
self.translateDictKey('colorDepth', self.__sourceLayer.colorDepth())
))
return False
return True
def toQImage(self, layerNode, rect=None):
"""Return `layerNode` content as a QImage (as ARGB32)
The `rect` value can be:
- None, in this case will return all `layerNode` content
- A QRect() object, in this case return `layerNode` content reduced to given rectangle bounds
"""
srcRect = layerNode.bounds()
if len(layerNode.childNodes()) == 0:
projectionMode = False
else:
projectionMode = True
if projectionMode == True:
img = QImage(layerNode.projectionPixelData(srcRect.left(), srcRect.top(), srcRect.width(), srcRect.height()), srcRect.width(), srcRect.height(), QImage.Format_ARGB32)
else:
img = QImage(layerNode.pixelData(srcRect.left(), srcRect.top(), srcRect.width(), srcRect.height()), srcRect.width(), srcRect.height(), QImage.Format_ARGB32)
return img.scaled(rect.width(), rect.height(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
def openDialogOptions(self):
"""Open dialog box to let user define channel extraction options"""
tmpDocument = None
previewBaSrc = QByteArray()
lblPreview = [QLabel(), QLabel(), QLabel(), QLabel()]
lblPreviewLbl = [QLabel(), QLabel(), QLabel(), QLabel()]
# ----------------------------------------------------------------------
# Define signal and slots for UI widgets
@pyqtSlot('QString')
def ledLayerGroupName_Changed(value):
self.__outputOptions['layerGroupName'] = value
@pyqtSlot('QString')
def ledLayerColorName_Changed(value):
self.__outputOptions['layerColorName'] = value
@pyqtSlot('QString')
def cmbOutputMode_Changed(value):
self.__outputOptions['outputMode'] = value
buildPreview()
@pyqtSlot('QString')
def cmbOriginalLayerAction_Changed(value):
self.__outputOptions['originalLayerAction'] = value
def buildPreview():
pbProgress.setVisible(True)
backupValue = self.__outputOptions['layerColorName']
self.__outputOptions['layerColorName'] = '{color:long}'
# create a temporary document to work
tmpDocument = Application.createDocument(imgThumbSrc.width(), imgThumbSrc.height(), "tmp", "RGBA", "U8", "", 120.0)
# create a layer used as original layer
originalLayer = tmpDocument.createNode("Original", "paintlayer")
tmpDocument.rootNode().addChildNode(originalLayer, None)
# and set original image content
originalLayer.setPixelData(previewBaSrc, 0, 0, tmpDocument.width(), tmpDocument.height())
# execute process
groupLayer = self.process(tmpDocument, originalLayer, pbProgress)
self.__outputOptions['layerColorName'] = backupValue
originalLayer.setVisible(False)
groupLayer.setVisible(True)
for layer in groupLayer.childNodes():
layer.setBlendingMode('normal')
layer.setVisible(False)
tmpDocument.refreshProjection()
index = 0
for layer in groupLayer.childNodes():
layer.setVisible(True)
tmpDocument.refreshProjection()
lblPreview[index].setPixmap(QPixmap.fromImage(tmpDocument.projection(0, 0, tmpDocument.width(), tmpDocument.height())))
lblPreviewLbl[index].setText("{0}".format(layer.name()))
layer.setVisible(False)
index+=1
if index > 3:
lblPreview[3].setVisible(True)
lblPreviewLbl[3].setVisible(True)
else:
lblPreview[3].setVisible(False)
lblPreviewLbl[3].setVisible(False)
tmpDocument.close()
pbProgress.setVisible(False)
# ----------------------------------------------------------------------
# Create dialog box
dlgMain = QDialog(Application.activeWindow().qwindow())
dlgMain.setWindowTitle(PLUGIN_DIALOG_TITLE)
# resizeable with minimum size
dlgMain.setSizeGripEnabled(True)
dlgMain.setMinimumSize(DOPT_MIN_WIDTH, DOPT_MIN_HEIGHT)
dlgMain.setModal(True)
# ......................................................................
# main dialog box, container
vbxMainContainer = QVBoxLayout(dlgMain)
# main dialog box, current layer name
lblLayerName = QLabel("{0} {1}".format(i18n("Processing layer"), self.__sourceLayer.name()))
lblLayerName.setFixedHeight(30)
vbxMainContainer.addWidget(lblLayerName)
# main dialog box, groupbox for layers options
gbxLayersMgt = QGroupBox("Layers management")
vbxMainContainer.addWidget(gbxLayersMgt)
# main dialog box, groupbox for output options
gbxOutputResults = QGroupBox("Output results")
vbxMainContainer.addWidget(gbxOutputResults)
vbxMainContainer.addStretch()
# main dialog box, OK/Cancel buttons
dbbxOkCancel = QDialogButtonBox(dlgMain)
dbbxOkCancel.setOrientation(QtCore.Qt.Horizontal)
dbbxOkCancel.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
dbbxOkCancel.accepted.connect(dlgMain.accept)
dbbxOkCancel.rejected.connect(dlgMain.reject)
vbxMainContainer.addWidget(dbbxOkCancel)
# ......................................................................
# create layout for groupbox "Layers management"
flLayersMgt = QFormLayout()
gbxLayersMgt.setLayout(flLayersMgt)
ledLayerGroupName = QLineEdit()
ledLayerGroupName.setText(self.__outputOptions['layerGroupName'])
ledLayerGroupName.textChanged.connect(ledLayerGroupName_Changed)
- flLayersMgt.addRow(i18n('New layer group name'), ledLayerGroupName)
+ flLayersMgt.addRow(i18nc('The name for a new group layer; the generated layers will be placed in this group.', 'New layer group name'), ledLayerGroupName)
ledLayerColorName = QLineEdit()
ledLayerColorName.setText(self.__outputOptions['layerColorName'])
ledLayerColorName.textChanged.connect(ledLayerColorName_Changed)
- flLayersMgt.addRow(i18n('New layers color name'), ledLayerColorName)
+ flLayersMgt.addRow(i18nc('Defines how the name for each layer created from the channel is generated.', 'New layers color name'), ledLayerColorName)
cmbOriginalLayerAction = QComboBox()
cmbOriginalLayerAction.addItems([
ORIGINAL_LAYER_KEEPUNCHANGED,
ORIGINAL_LAYER_KEEPVISIBLE,
ORIGINAL_LAYER_KEEPHIDDEN,
ORIGINAL_LAYER_REMOVE
])
cmbOriginalLayerAction.setCurrentText(self.__outputOptions['originalLayerAction'])
cmbOriginalLayerAction.currentTextChanged.connect(cmbOriginalLayerAction_Changed)
flLayersMgt.addRow(i18n("Original layer"), cmbOriginalLayerAction)
# ......................................................................
# create layout for groupbox "Output results"
flOutputResults = QFormLayout()
gbxOutputResults.setLayout(flOutputResults)
cmbOutputMode = QComboBox()
cmbOutputMode.addItems([
OUTPUT_MODE_RGB,
OUTPUT_MODE_CMY,
OUTPUT_MODE_CMYK,
OUTPUT_MODE_LRGB,
OUTPUT_MODE_LCMY,
OUTPUT_MODE_LCMYK
])
cmbOutputMode.setCurrentText(self.__outputOptions['outputMode'])
cmbOutputMode.currentTextChanged.connect(cmbOutputMode_Changed)
flOutputResults.addRow(i18n("Mode"), cmbOutputMode)
vbxPreviewLblContainer = QHBoxLayout()
flOutputResults.addRow('', vbxPreviewLblContainer)
vbxPreviewContainer = QHBoxLayout()
flOutputResults.addRow('', vbxPreviewContainer)
# add preview progressbar
pbProgress = QProgressBar()
pbProgress.setFixedHeight(8)
pbProgress.setTextVisible(False)
pbProgress.setVisible(False)
pbProgress.setRange(0, 107)
flOutputResults.addRow('', pbProgress)
imageRatio = self.__sourceDocument.width() / self.__sourceDocument.height()
rect = QRect(0, 0, OUTPUT_PREVIEW_MAXSIZE, OUTPUT_PREVIEW_MAXSIZE)
# always ensure that final preview width and/or height is lower or equal than OUTPUT_PREVIEW_MAXSIZE
if imageRatio < 1:
# width < height
rect.setWidth(int(imageRatio * OUTPUT_PREVIEW_MAXSIZE))
else:
# width >= height
rect.setHeight(int(OUTPUT_PREVIEW_MAXSIZE / imageRatio))
imgThumbSrc = self.toQImage(self.__sourceLayer, rect)
previewBaSrc.resize(imgThumbSrc.byteCount())
ptr = imgThumbSrc.bits()
ptr.setsize(imgThumbSrc.byteCount())
previewBaSrc = QByteArray(ptr.asstring())
lblPreviewSrc = QLabel()
lblPreviewSrc.setPixmap(QPixmap.fromImage(imgThumbSrc))
lblPreviewSrc.setFixedHeight(imgThumbSrc.height() + 4)
lblPreviewSrc.setFixedWidth(imgThumbSrc.width() + 4)
vbxPreviewContainer.addWidget(lblPreviewSrc)
lblPreviewLblSrc = QLabel(i18n("Original"))
lblPreviewLblSrc.setFixedWidth(imgThumbSrc.width() + 4)
vbxPreviewLblContainer.addWidget(lblPreviewLblSrc)
vbxPreviewLblContainer.addWidget(QLabel(" "))
vbxPreviewContainer.addWidget(QLabel(">"))
lblPreview[3].setPixmap(QPixmap.fromImage(imgThumbSrc))
lblPreview[3].setFixedHeight(imgThumbSrc.height() + 4)
lblPreview[3].setFixedWidth(imgThumbSrc.width() + 4)
vbxPreviewContainer.addWidget(lblPreview[3])
lblPreviewLbl[3] = QLabel(i18n("Cyan"))
lblPreviewLbl[3].setIndent(10)
lblPreviewLbl[3].setFixedWidth(imgThumbSrc.width() + 4)
vbxPreviewLblContainer.addWidget(lblPreviewLbl[3])
lblPreview[2] = QLabel()
lblPreview[2].setPixmap(QPixmap.fromImage(imgThumbSrc))
lblPreview[2].setFixedHeight(imgThumbSrc.height() + 4)
lblPreview[2].setFixedWidth(imgThumbSrc.width() + 4)
vbxPreviewContainer.addWidget(lblPreview[2])
lblPreviewLbl[2] = QLabel(i18n("Magenta"))
lblPreviewLbl[2].setIndent(10)
lblPreviewLbl[2].setFixedWidth(imgThumbSrc.width() + 4)
vbxPreviewLblContainer.addWidget(lblPreviewLbl[2])
lblPreview[1] = QLabel()
lblPreview[1].setPixmap(QPixmap.fromImage(imgThumbSrc))
lblPreview[1].setFixedHeight(imgThumbSrc.height() + 4)
lblPreview[1].setFixedWidth(imgThumbSrc.width() + 4)
vbxPreviewContainer.addWidget(lblPreview[1])
lblPreviewLbl[1] = QLabel(i18n("Yellow"))
lblPreviewLbl[1].setIndent(10)
lblPreviewLbl[1].setFixedWidth(imgThumbSrc.width() + 4)
vbxPreviewLblContainer.addWidget(lblPreviewLbl[1])
lblPreview[0] = QLabel()
lblPreview[0].setPixmap(QPixmap.fromImage(imgThumbSrc))
lblPreview[0].setFixedHeight(imgThumbSrc.height() + 4)
lblPreview[0].setFixedWidth(imgThumbSrc.width() + 4)
vbxPreviewContainer.addWidget(lblPreview[0])
lblPreviewLbl[0] = QLabel(i18n("Black"))
lblPreviewLbl[0].setIndent(10)
lblPreviewLbl[0].setFixedWidth(imgThumbSrc.width() + 4)
vbxPreviewLblContainer.addWidget(lblPreviewLbl[0])
vbxPreviewLblContainer.addStretch()
vbxPreviewContainer.addStretch()
buildPreview()
returned = dlgMain.exec_()
return returned
def progressNext(self, pProgress):
"""Update progress bar"""
if pProgress is not None:
stepCurrent=pProgress.value()+1
pProgress.setValue(stepCurrent)
QApplication.instance().processEvents()
def run(self):
"""Run process for current layer"""
pdlgProgress = QProgressDialog(self.__outputOptions['outputMode'], None, 0, 100, Application.activeWindow().qwindow())
pdlgProgress.setWindowTitle(PLUGIN_DIALOG_TITLE)
pdlgProgress.setMinimumSize(640, 200)
pdlgProgress.setModal(True)
pdlgProgress.show()
self.process(self.__sourceDocument, self.__sourceLayer, pdlgProgress)
pdlgProgress.close()
def process(self, pDocument, pOriginalLayer, pProgress):
"""Process given layer with current options"""
self.layerNum = 0
document = pDocument
originalLayer = pOriginalLayer
parentGroupLayer = None
currentProcessedLayer = None
originalLayerIsVisible = originalLayer.visible()
def getLayerByName(parent, value):
"""search and return a layer by name, within given parent group"""
if parent == None:
return document.nodeByName(value)
for layer in parent.childNodes():
if layer.name() == value:
return layer
return None
def duplicateLayer(currentProcessedLayer, value):
"""Duplicate layer from given name
New layer become active layer
"""
newLayer = None
srcLayer = None
srcName = re.match("^@(.*)", value)
if not srcName is None:
# reference to a specific layer
if srcName[1] == 'original':
# original layer currently processed
srcLayer = originalLayer
else:
# a color layer previously built (and finished)
srcLayer = getLayerByName(parentGroupLayer, parseLayerName(self.__outputOptions['layerColorName'], srcName[1]))
else:
# a layer with a fixed name
srcLayer = document.nodeByName(parseLayerName(value, ''))
if not srcLayer is None:
newLayer = srcLayer.duplicate()
self.layerNum+=1
newLayer.setName("c2l-w{0}".format(self.layerNum))
parentGroupLayer.addChildNode(newLayer, currentProcessedLayer)
return newLayer
else:
return None
def newLayer(currentProcessedLayer, value):
"""Create a new layer of given type
New layer become active layer
"""
newLayer = None
if value is None or not value['type'] in ['filllayer']:
# given type for new layer is not valid
# currently only one layer type is implemented
return None
if value['type'] == 'filllayer':
infoObject = InfoObject();
infoObject.setProperty("color", value['color'])
selection = Selection();
selection.select(0, 0, document.width(), document.height(), 255)
newLayer = document.createFillLayer(value['color'].name(), "color", infoObject, selection)
if newLayer:
self.layerNum+=1
newLayer.setName("c2l-w{0}".format(self.layerNum))
parentGroupLayer.addChildNode(newLayer, currentProcessedLayer)
# Need to force generator otherwise, information provided when creating layer seems to not be taken in
# account
newLayer.setGenerator("color", infoObject)
return newLayer
else:
return None
def mergeDown(currentProcessedLayer, value):
"""Merge current layer with layer below"""
if currentProcessedLayer is None:
return None
newLayer = currentProcessedLayer.mergeDown()
# note:
# when layer is merged down:
# - a new layer seems to be created (reference to 'down' layer does not match anymore layer in group)
# - retrieved 'newLayer' reference does not match to new layer resulting from merge
# - activeNode() in document doesn't match to new layer resulting from merge
# maybe it's norpmal, maybe not...
# but the only solution to be able to work on merged layer (with current script) is to consider that from
# parent node, last child match to last added layer and then, to our merged layer
currentProcessedLayer = parentGroupLayer.childNodes()[-1]
# for an unknown reason, merged layer bounds are not corrects... :'-(
currentProcessedLayer.cropNode(0, 0, document.width(), document.height())
return currentProcessedLayer
def applyBlendingMode(currentProcessedLayer, value):
"""Set blending mode for current layer"""
if currentProcessedLayer is None or value is None or value == '':
return False
currentProcessedLayer.setBlendingMode(value)
return True
def applyFilter(currentProcessedLayer, value):
"""Apply filter to layer"""
if currentProcessedLayer is None or value is None or value == '':
return None
filterName = re.match("name=([^;]+)", value)
if filterName is None:
return None
filter = Application.filter(filterName.group(1))
filterConfiguration = filter.configuration()
for parameter in value.split(';'):
parameterName = re.match("^([^=]+)=(.*)", parameter)
if not parameterName is None and parameterName != 'name':
filterConfiguration.setProperty(parameterName.group(1), parameterName.group(2))
filter.setConfiguration(filterConfiguration)
filter.apply(currentProcessedLayer, 0, 0, document.width(), document.height())
return currentProcessedLayer
def parseLayerName(value, color):
"""Parse layer name"""
returned = value
returned = returned.replace("{source:name}", originalLayer.name())
returned = returned.replace("{mode}", OUTPUT_MODE_NFO[self.__outputOptions['outputMode']]['groupLayerName'])
returned = returned.replace("{color:short}", color)
if color == "C":
returned = returned.replace("{color:long}", i18n("Cyan"))
elif color == "M":
returned = returned.replace("{color:long}", i18n("Magenta"))
elif color == "Y":
returned = returned.replace("{color:long}", i18n("Yellow"))
elif color == "K":
returned = returned.replace("{color:long}", i18n("Black"))
elif color == "R":
returned = returned.replace("{color:long}", i18n("Red"))
elif color == "G":
returned = returned.replace("{color:long}", i18n("Green"))
elif color == "B":
returned = returned.replace("{color:long}", i18n("Blue"))
else:
returned = returned.replace("{color:long}", "")
return returned
if document is None or originalLayer is None:
# should not occurs, but...
return None
if not pProgress is None:
stepTotal = 4
for layer in OUTPUT_MODE_NFO[self.__outputOptions['outputMode']]['layers']:
stepTotal+=len(layer['process'])
pProgress.setRange(0, stepTotal)
if originalLayerIsVisible == False:
originalLayer.setVisible(True)
# ----------------------------------------------------------------------
# Create new group layer
parentGroupLayer = document.createGroupLayer(parseLayerName(self.__outputOptions['layerGroupName'], ''))
self.progressNext(pProgress)
currentProcessedLayer = None
for layer in OUTPUT_MODE_NFO[self.__outputOptions['outputMode']]['layers']:
for process in layer['process']:
if process['action'] == 'duplicate':
currentProcessedLayer = duplicateLayer(currentProcessedLayer, process['value'])
elif process['action'] == 'new':
currentProcessedLayer = newLayer(currentProcessedLayer, process['value'])
elif process['action'] == 'merge down':
currentProcessedLayer = mergeDown(currentProcessedLayer, process['value'])
pass
elif process['action'] == 'blending mode':
applyBlendingMode(currentProcessedLayer, process['value'])
elif process['action'] == 'filter':
applyFilter(currentProcessedLayer, process['value'])
self.progressNext(pProgress)
if not currentProcessedLayer is None:
# rename currentProcessedLayer
currentProcessedLayer.setName(parseLayerName(self.__outputOptions['layerColorName'], layer['color']))
document.rootNode().addChildNode(parentGroupLayer, originalLayer)
self.progressNext(pProgress)
if self.__outputOptions['originalLayerAction'] == ORIGINAL_LAYER_KEEPVISIBLE:
originalLayer.setVisible(True)
elif self.__outputOptions['originalLayerAction'] == ORIGINAL_LAYER_KEEPHIDDEN:
originalLayer.setVisible(False)
elif self.__outputOptions['originalLayerAction'] == ORIGINAL_LAYER_REMOVE:
originalLayer.remove()
else:
# ORIGINAL_LAYER_KEEPUNCHANGED
originalLayer.setVisible(originalLayerIsVisible)
self.progressNext(pProgress)
document.refreshProjection()
self.progressNext(pProgress)
document.setActiveNode(parentGroupLayer)
return parentGroupLayer
#ChannelsToLayers(Krita.instance()).process(Application.activeDocument(), Application.activeDocument().activeNode(), None)
#ChannelsToLayers(Krita.instance()).action_triggered()