File indexing completed on 2024-05-12 05:46:37
0001 #----------------------------------------------------------------------------- 0002 # Channels to Layers 0003 # Copyright (C) 2019 - Grum999 0004 # ----------------------------------------------------------------------------- 0005 # This program is free software: you can redistribute it and/or modify 0006 # it under the terms of the GNU General Public License as published by 0007 # the Free Software Foundation, either version 3 of the License, or 0008 # (at your option) any later version. 0009 # 0010 # This program is distributed in the hope that it will be useful, 0011 # but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 0013 # See the GNU General Public License for more details. 0014 # 0015 # You should have received a copy of the GNU General Public License 0016 # along with this program. 0017 # If not, see https://www.gnu.org/licenses/ 0018 # ----------------------------------------------------------------------------- 0019 # A Krita plugin designed to split channels from a layer to sub-layers 0020 # . RGB 0021 # . CMY 0022 # . CMYK 0023 # . RGB as greayscale values 0024 # . CMY as greayscale values 0025 # . CMYK as greayscale values 0026 # ----------------------------------------------------------------------------- 0027 0028 import re 0029 from krita import ( 0030 Extension, 0031 InfoObject, 0032 Node, 0033 Selection 0034 ) 0035 from PyQt5.Qt import * 0036 from PyQt5 import QtCore 0037 from PyQt5.QtCore import ( 0038 pyqtSlot, 0039 QBuffer, 0040 QByteArray, 0041 QIODevice 0042 ) 0043 from PyQt5.QtGui import ( 0044 QColor, 0045 QImage, 0046 QPixmap, 0047 ) 0048 from PyQt5.QtWidgets import ( 0049 QApplication, 0050 QCheckBox, 0051 QComboBox, 0052 QDialog, 0053 QDialogButtonBox, 0054 QFormLayout, 0055 QGroupBox, 0056 QHBoxLayout, 0057 QLabel, 0058 QLineEdit, 0059 QMessageBox, 0060 QProgressBar, 0061 QProgressDialog, 0062 QVBoxLayout, 0063 QWidget 0064 ) 0065 0066 0067 PLUGIN_VERSION = '1.1.0' 0068 0069 EXTENSION_ID = 'pykrita_channels2layers' 0070 PLUGIN_MENU_ENTRY = i18n('Channels to layers') 0071 PLUGIN_DIALOG_TITLE = "{0} - {1}".format(i18n('Channels to layers'), PLUGIN_VERSION) 0072 0073 # Define DialogBox types 0074 DBOX_INFO = 'i' 0075 DBOX_WARNING ='w' 0076 0077 0078 # Define Output modes 0079 OUTPUT_MODE_RGB = i18n('RGB Colors') 0080 OUTPUT_MODE_CMY = i18n('CMY Colors') 0081 OUTPUT_MODE_CMYK = i18n('CMYK Colors') 0082 OUTPUT_MODE_LRGB = i18n('RGB Grayscale levels') 0083 OUTPUT_MODE_LCMY = i18n('CMY Grayscale levels') 0084 OUTPUT_MODE_LCMYK = i18n('CMYK Grayscale levels') 0085 0086 OUTPUT_PREVIEW_MAXSIZE = 320 0087 0088 # Define original layer action 0089 ORIGINAL_LAYER_KEEPUNCHANGED = i18n('Unchanged') 0090 ORIGINAL_LAYER_KEEPVISIBLE = i18n('Visible') 0091 ORIGINAL_LAYER_KEEPHIDDEN = i18n('Hidden') 0092 ORIGINAL_LAYER_REMOVE = i18n('Remove') 0093 0094 # define dialog option minimum dimension 0095 DOPT_MIN_WIDTH = OUTPUT_PREVIEW_MAXSIZE * 5 + 200 0096 DOPT_MIN_HEIGHT = 480 0097 0098 0099 0100 OUTPUT_MODE_NFO = { 0101 OUTPUT_MODE_RGB : { 0102 'description' : 'Extract channels (Red, Green, Blue) and create a colored layer per channel', 0103 'groupLayerName' : 'RGB', 0104 'layers' : [ 0105 { 0106 'color' : 'B', 0107 'process': [ 0108 { 0109 'action' : 'duplicate', 0110 'value' : '@original' 0111 }, 0112 { 0113 'action' : 'new', 0114 'value' : { 0115 'type' : 'filllayer', 0116 'color' : QColor(Qt.blue) 0117 } 0118 }, 0119 { 0120 'action' : 'blending mode', 0121 'value' : 'inverse_subtract' 0122 }, 0123 { 0124 'action' : 'merge down', 0125 'value' : None 0126 }, 0127 { 0128 'action' : 'blending mode', 0129 'value' : 'add' 0130 } 0131 ] 0132 }, 0133 { 0134 'color' : 'G', 0135 'process': [ 0136 { 0137 'action' : 'duplicate', 0138 'value' : '@original' 0139 }, 0140 { 0141 'action' : 'new', 0142 'value' : { 0143 'type' : 'filllayer', 0144 'color' : QColor(Qt.green) 0145 } 0146 }, 0147 { 0148 'action' : 'blending mode', 0149 'value' : 'inverse_subtract' 0150 }, 0151 { 0152 'action' : 'merge down', 0153 'value' : None 0154 }, 0155 { 0156 'action' : 'blending mode', 0157 'value' : 'add' 0158 } 0159 ] 0160 }, 0161 { 0162 'color' : 'R', 0163 'process': [ 0164 { 0165 'action' : 'duplicate', 0166 'value' : '@original' 0167 }, 0168 { 0169 'action' : 'new', 0170 'value' : { 0171 'type' : 'filllayer', 0172 'color' : QColor(Qt.red) 0173 } 0174 }, 0175 { 0176 'action' : 'blending mode', 0177 'value' : 'inverse_subtract' 0178 }, 0179 { 0180 'action' : 'merge down', 0181 'value' : None 0182 }, 0183 { 0184 'action' : 'blending mode', 0185 'value' : 'add' 0186 } 0187 ] 0188 } 0189 ] 0190 }, 0191 OUTPUT_MODE_CMY : { 0192 'description' : 'Extract channels (Cyan, Mangenta, Yellow) and create a colored layer per channel', 0193 'groupLayerName' : 'CMY', 0194 'layers' : [ 0195 { 0196 'color' : 'Y', 0197 'process': [ 0198 { 0199 'action' : 'duplicate', 0200 'value' : '@original' 0201 }, 0202 { 0203 'action' : 'new', 0204 'value' : { 0205 'type' : 'filllayer', 0206 'color' : QColor(Qt.yellow) 0207 } 0208 }, 0209 { 0210 'action' : 'blending mode', 0211 'value' : 'add' 0212 }, 0213 { 0214 'action' : 'merge down', 0215 'value' : None 0216 }, 0217 { 0218 'action' : 'blending mode', 0219 'value' : 'multiply' 0220 } 0221 ] 0222 }, 0223 { 0224 'color' : 'M', 0225 'process': [ 0226 { 0227 'action' : 'duplicate', 0228 'value' : '@original' 0229 }, 0230 { 0231 'action' : 'new', 0232 'value' : { 0233 'type' : 'filllayer', 0234 'color' : QColor(Qt.magenta) 0235 } 0236 }, 0237 { 0238 'action' : 'blending mode', 0239 'value' : 'add' 0240 }, 0241 { 0242 'action' : 'merge down', 0243 'value' : None 0244 }, 0245 { 0246 'action' : 'blending mode', 0247 'value' : 'multiply' 0248 } 0249 ] 0250 }, 0251 { 0252 'color' : 'C', 0253 'process': [ 0254 { 0255 'action' : 'duplicate', 0256 'value' : '@original' 0257 }, 0258 { 0259 'action' : 'new', 0260 'value' : { 0261 'type' : 'filllayer', 0262 'color' : QColor(Qt.cyan) 0263 } 0264 }, 0265 { 0266 'action' : 'blending mode', 0267 'value' : 'add' 0268 }, 0269 { 0270 'action' : 'merge down', 0271 'value' : None 0272 }, 0273 { 0274 'action' : 'blending mode', 0275 'value' : 'multiply' 0276 } 0277 ] 0278 } 0279 ] 0280 }, 0281 OUTPUT_MODE_CMYK : { 0282 'description' : 'Extract channels (Cyan, Mangenta, Yellow, Black) and create a colored layer per channel', 0283 'groupLayerName' : 'CMYK', 0284 'layers' : [ 0285 { 0286 'color' : 'K', 0287 'process': [ 0288 { 0289 'action' : 'duplicate', 0290 'value' : '@original' 0291 }, 0292 { 0293 'action' : 'filter', 0294 'value' : 'name=desaturate;type=5' # desaturate method = max 0295 } 0296 ] 0297 }, 0298 { 0299 'color' : 'Y', 0300 'process': [ 0301 { 0302 'action' : 'duplicate', 0303 'value' : '@original' 0304 }, 0305 { 0306 'action' : 'new', 0307 'value' : { 0308 'type' : 'filllayer', 0309 'color' : QColor(Qt.yellow) 0310 } 0311 }, 0312 { 0313 'action' : 'blending mode', 0314 'value' : 'add' 0315 }, 0316 { 0317 'action' : 'merge down', 0318 'value' : None 0319 }, 0320 { 0321 'action' : 'duplicate', 0322 'value' : '@K' 0323 }, 0324 { 0325 'action' : 'blending mode', 0326 'value' : 'divide' 0327 }, 0328 { 0329 'action' : 'merge down', 0330 'value' : None 0331 }, 0332 { 0333 'action' : 'blending mode', 0334 'value' : 'multiply' 0335 } 0336 ] 0337 }, 0338 { 0339 'color' : 'M', 0340 'process': [ 0341 { 0342 'action' : 'duplicate', 0343 'value' : '@original' 0344 }, 0345 { 0346 'action' : 'new', 0347 'value' : { 0348 'type' : 'filllayer', 0349 'color' : QColor(Qt.magenta) 0350 } 0351 }, 0352 { 0353 'action' : 'blending mode', 0354 'value' : 'add' 0355 }, 0356 { 0357 'action' : 'merge down', 0358 'value' : None 0359 }, 0360 { 0361 'action' : 'duplicate', 0362 'value' : '@K' 0363 }, 0364 { 0365 'action' : 'blending mode', 0366 'value' : 'divide' 0367 }, 0368 { 0369 'action' : 'merge down', 0370 'value' : None 0371 }, 0372 { 0373 'action' : 'blending mode', 0374 'value' : 'multiply' 0375 } 0376 ] 0377 }, 0378 { 0379 'color' : 'C', 0380 'process': [ 0381 { 0382 'action' : 'duplicate', 0383 'value' : '@original' 0384 }, 0385 { 0386 'action' : 'new', 0387 'value' : { 0388 'type' : 'filllayer', 0389 'color' : QColor(Qt.cyan) 0390 } 0391 }, 0392 { 0393 'action' : 'blending mode', 0394 'value' : 'add' 0395 }, 0396 { 0397 'action' : 'merge down', 0398 'value' : None 0399 }, 0400 { 0401 'action' : 'duplicate', 0402 'value' : '@K' 0403 }, 0404 { 0405 'action' : 'blending mode', 0406 'value' : 'divide' 0407 }, 0408 { 0409 'action' : 'merge down', 0410 'value' : None 0411 }, 0412 { 0413 'action' : 'blending mode', 0414 'value' : 'multiply' 0415 } 0416 ] 0417 } 0418 ] 0419 }, 0420 OUTPUT_MODE_LRGB : { 0421 'description' : 'Extract channels (Red, Green, Blue) and create a grayscale layer per channel', 0422 'groupLayerName' : 'RGB[GS]', 0423 'layers' : [ 0424 { 0425 'color' : 'B', 0426 'process': [ 0427 { 0428 'action' : 'duplicate', 0429 'value' : '@original' 0430 }, 0431 { 0432 'action' : 'new', 0433 'value' : { 0434 'type' : 'filllayer', 0435 'color' : QColor(Qt.blue) 0436 } 0437 }, 0438 { 0439 'action' : 'blending mode', 0440 'value' : 'inverse_subtract' 0441 }, 0442 { 0443 'action' : 'merge down', 0444 'value' : None 0445 }, 0446 { 0447 'action' : 'blending mode', 0448 'value' : 'add' 0449 }, 0450 { 0451 'action' : 'filter', 0452 'value' : 'name=desaturate;type=5' # desaturate method = max 0453 } 0454 ] 0455 }, 0456 { 0457 'color' : 'G', 0458 'process': [ 0459 { 0460 'action' : 'duplicate', 0461 'value' : '@original' 0462 }, 0463 { 0464 'action' : 'new', 0465 'value' : { 0466 'type' : 'filllayer', 0467 'color' : QColor(Qt.green) 0468 } 0469 }, 0470 { 0471 'action' : 'blending mode', 0472 'value' : 'inverse_subtract' 0473 }, 0474 { 0475 'action' : 'merge down', 0476 'value' : None 0477 }, 0478 { 0479 'action' : 'blending mode', 0480 'value' : 'add' 0481 }, 0482 { 0483 'action' : 'filter', 0484 'value' : 'name=desaturate;type=5' # desaturate method = max 0485 } 0486 ] 0487 }, 0488 { 0489 'color' : 'R', 0490 'process': [ 0491 { 0492 'action' : 'duplicate', 0493 'value' : '@original' 0494 }, 0495 { 0496 'action' : 'new', 0497 'value' : { 0498 'type' : 'filllayer', 0499 'color' : QColor(Qt.red) 0500 } 0501 }, 0502 { 0503 'action' : 'blending mode', 0504 'value' : 'inverse_subtract' 0505 }, 0506 { 0507 'action' : 'merge down', 0508 'value' : None 0509 }, 0510 { 0511 'action' : 'blending mode', 0512 'value' : 'add' 0513 }, 0514 { 0515 'action' : 'filter', 0516 'value' : 'name=desaturate;type=5' # desaturate method = max 0517 } 0518 ] 0519 } 0520 ] 0521 }, 0522 OUTPUT_MODE_LCMY : { 0523 'description' : 'Extract channels (Cyan, Mangenta, Yellow) and create a grayscale layer per channel', 0524 'groupLayerName' : 'CMY[GS]', 0525 'layers' : [ 0526 { 0527 'color' : 'Y', 0528 'process': [ 0529 { 0530 'action' : 'duplicate', 0531 'value' : '@original' 0532 }, 0533 { 0534 'action' : 'new', 0535 'value' : { 0536 'type' : 'filllayer', 0537 'color' : QColor(Qt.yellow) 0538 } 0539 }, 0540 { 0541 'action' : 'blending mode', 0542 'value' : 'add' 0543 }, 0544 { 0545 'action' : 'merge down', 0546 'value' : None 0547 }, 0548 { 0549 'action' : 'blending mode', 0550 'value' : 'add' 0551 }, 0552 { 0553 'action' : 'filter', 0554 'value' : 'name=desaturate;type=4' # desaturate method = min 0555 } 0556 ] 0557 }, 0558 { 0559 'color' : 'M', 0560 'process': [ 0561 { 0562 'action' : 'duplicate', 0563 'value' : '@original' 0564 }, 0565 { 0566 'action' : 'new', 0567 'value' : { 0568 'type' : 'filllayer', 0569 'color' : QColor(Qt.magenta) 0570 } 0571 }, 0572 { 0573 'action' : 'blending mode', 0574 'value' : 'add' 0575 }, 0576 { 0577 'action' : 'merge down', 0578 'value' : None 0579 }, 0580 { 0581 'action' : 'blending mode', 0582 'value' : 'add' 0583 }, 0584 { 0585 'action' : 'filter', 0586 'value' : 'name=desaturate;type=4' # desaturate method = min 0587 } 0588 ] 0589 }, 0590 { 0591 'color' : 'C', 0592 'process': [ 0593 { 0594 'action' : 'duplicate', 0595 'value' : '@original' 0596 }, 0597 { 0598 'action' : 'new', 0599 'value' : { 0600 'type' : 'filllayer', 0601 'color' : QColor(Qt.cyan) 0602 } 0603 }, 0604 { 0605 'action' : 'blending mode', 0606 'value' : 'add' 0607 }, 0608 { 0609 'action' : 'merge down', 0610 'value' : None 0611 }, 0612 { 0613 'action' : 'blending mode', 0614 'value' : 'add' 0615 }, 0616 { 0617 'action' : 'filter', 0618 'value' : 'name=desaturate;type=4' # desaturate method = min 0619 } 0620 ] 0621 } 0622 ] 0623 }, 0624 OUTPUT_MODE_LCMYK : { 0625 'description' : 'Extract channels (Cyan, Mangenta, Yellow, Black) and create a grayscale layer per channel', 0626 'groupLayerName' : 'CMYK[GS]', 0627 'layers' : [ 0628 { 0629 'color' : 'K', 0630 'process': [ 0631 { 0632 'action' : 'duplicate', 0633 'value' : '@original' 0634 }, 0635 { 0636 'action' : 'filter', 0637 'value' : 'name=desaturate;type=5' # desaturate method = max 0638 } 0639 ] 0640 }, 0641 { 0642 'color' : 'Y', 0643 'process': [ 0644 { 0645 'action' : 'duplicate', 0646 'value' : '@original' 0647 }, 0648 { 0649 'action' : 'new', 0650 'value' : { 0651 'type' : 'filllayer', 0652 'color' : QColor(Qt.yellow) 0653 } 0654 }, 0655 { 0656 'action' : 'blending mode', 0657 'value' : 'add' 0658 }, 0659 { 0660 'action' : 'merge down', 0661 'value' : None 0662 }, 0663 { 0664 'action' : 'duplicate', 0665 'value' : '@K' 0666 }, 0667 { 0668 'action' : 'blending mode', 0669 'value' : 'divide' 0670 }, 0671 { 0672 'action' : 'merge down', 0673 'value' : None 0674 }, 0675 { 0676 'action' : 'blending mode', 0677 'value' : 'multiply' 0678 }, 0679 { 0680 'action' : 'filter', 0681 'value' : 'name=desaturate;type=4' # desaturate method = min 0682 } 0683 ] 0684 }, 0685 { 0686 'color' : 'M', 0687 'process': [ 0688 { 0689 'action' : 'duplicate', 0690 'value' : '@original' 0691 }, 0692 { 0693 'action' : 'new', 0694 'value' : { 0695 'type' : 'filllayer', 0696 'color' : QColor(Qt.magenta) 0697 } 0698 }, 0699 { 0700 'action' : 'blending mode', 0701 'value' : 'add' 0702 }, 0703 { 0704 'action' : 'merge down', 0705 'value' : None 0706 }, 0707 { 0708 'action' : 'duplicate', 0709 'value' : '@K' 0710 }, 0711 { 0712 'action' : 'blending mode', 0713 'value' : 'divide' 0714 }, 0715 { 0716 'action' : 'merge down', 0717 'value' : None 0718 }, 0719 { 0720 'action' : 'blending mode', 0721 'value' : 'multiply' 0722 }, 0723 { 0724 'action' : 'filter', 0725 'value' : 'name=desaturate;type=4' # desaturate method = min 0726 } 0727 ] 0728 }, 0729 { 0730 'color' : 'C', 0731 'process': [ 0732 { 0733 'action' : 'duplicate', 0734 'value' : '@original' 0735 }, 0736 { 0737 'action' : 'new', 0738 'value' : { 0739 'type' : 'filllayer', 0740 'color' : QColor(Qt.cyan) 0741 } 0742 }, 0743 { 0744 'action' : 'blending mode', 0745 'value' : 'add' 0746 }, 0747 { 0748 'action' : 'merge down', 0749 'value' : None 0750 }, 0751 { 0752 'action' : 'duplicate', 0753 'value' : '@K' 0754 }, 0755 { 0756 'action' : 'blending mode', 0757 'value' : 'divide' 0758 }, 0759 { 0760 'action' : 'merge down', 0761 'value' : None 0762 }, 0763 { 0764 'action' : 'blending mode', 0765 'value' : 'multiply' 0766 }, 0767 { 0768 'action' : 'filter', 0769 'value' : 'name=desaturate;type=4' # desaturate method = min 0770 } 0771 ] 0772 } 0773 ] 0774 } 0775 } 0776 0777 TRANSLATIONS_DICT = { 0778 'colorDepth' : { 0779 'U8' : '8bits', 0780 'U16' : '16bits', 0781 'F16' : '16bits floating point', 0782 'F32' : '32bits floating point' 0783 }, 0784 'colorModel' : { 0785 'A' : 'Alpha mask', 0786 'RGBA' : 'RGB with alpha channel', 0787 'XYZA' : 'XYZ with alpha channel', 0788 'LABA' : 'LAB with alpha channel', 0789 'CMYKA' : 'CMYK with alpha channel', 0790 'GRAYA' : 'Gray with alpha channel', 0791 'YCbCrA' : 'YCbCr with alpha channel' 0792 }, 0793 'layerType' : { 0794 'paintlayer' : 'Paint layer', 0795 'grouplayer' : 'Group layer', 0796 'filelayer' : 'File layer', 0797 'filterlayer' : 'Filter layer', 0798 'filllayer' : 'Fill layer', 0799 'clonelayer' : 'Clone layer', 0800 'vectorlayer' : 'Vector layer', 0801 'transparencymask' : 'Transparency mask', 0802 'filtermask' : 'Filter mask', 0803 'transformmask': 'Transform mask', 0804 'selectionmask': 'Selection mask', 0805 'colorizemask' : 'Colorize mask' 0806 } 0807 } 0808 0809 0810 0811 0812 class ChannelsToLayers(Extension): 0813 0814 def __init__(self, parent): 0815 # Default options 0816 self.__outputOptions = { 0817 'outputMode': OUTPUT_MODE_RGB, 0818 'originalLayerAction': ORIGINAL_LAYER_KEEPHIDDEN, 0819 'layerGroupName': '{mode}-{source:name}', 0820 'layerColorName': '{mode}[{color:short}]-{source:name}' 0821 } 0822 0823 self.__sourceDocument = None 0824 self.__sourceLayer = None 0825 0826 # Always initialise the superclass. 0827 # This is necessary to create the underlying C++ object 0828 super().__init__(parent) 0829 self.parent = parent 0830 0831 0832 def setup(self): 0833 pass 0834 0835 0836 def createActions(self, window): 0837 action = window.createAction(EXTENSION_ID, PLUGIN_MENU_ENTRY, "tools/scripts") 0838 action.triggered.connect(self.action_triggered) 0839 0840 def dBoxMessage(self, msgType, msg): 0841 """Simplified function for DialogBox 'OK' message""" 0842 if msgType == DBOX_WARNING: 0843 QMessageBox.warning( 0844 QWidget(), 0845 PLUGIN_DIALOG_TITLE, 0846 i18n(msg) 0847 ) 0848 else: 0849 QMessageBox.information( 0850 QWidget(), 0851 PLUGIN_DIALOG_TITLE, 0852 i18n(msg) 0853 ) 0854 0855 0856 def action_triggered(self): 0857 """Action called when script is executed from Kitra menu""" 0858 if self.checkCurrentLayer(): 0859 if self.openDialogOptions(): 0860 self.run() 0861 0862 0863 def translateDictKey(self, key, value): 0864 """Translate key from dictionnary (mostly internal Krita internal values) to human readable values""" 0865 returned = i18n('Unknown') 0866 0867 if key in TRANSLATIONS_DICT.keys(): 0868 if value in TRANSLATIONS_DICT[key].keys(): 0869 returned = i18n(TRANSLATIONS_DICT[key][value]) 0870 0871 return returned 0872 0873 0874 def checkCurrentLayer(self): 0875 """Check if current layer is valid 0876 - A document must be opened 0877 - Active layer properties must be: 0878 . Layer type: a paint layer 0879 . Color model: RGBA 0880 . Color depth: 8bits 0881 """ 0882 self.__sourceDocument = Application.activeDocument() 0883 # Check if there's an active document 0884 if self.__sourceDocument is None: 0885 self.dBoxMessage(DBOX_WARNING, "There's no active document!") 0886 return False 0887 0888 self.__sourceLayer = self.__sourceDocument.activeNode() 0889 0890 # Check if current layer can be processed 0891 if self.__sourceLayer.type() != "paintlayer" or self.__sourceLayer.colorModel() != "RGBA" or self.__sourceLayer.colorDepth() != "U8": 0892 self.dBoxMessage(DBOX_WARNING, "Selected layer must be a 8bits RGBA Paint Layer!" 0893 "\n\nCurrent layer '{0}' properties:" 0894 "\n- Layer type: {1}" 0895 "\n- Color model: {2} ({3})" 0896 "\n- Color depth: {4}" 0897 "\n\n> Action is cancelled".format(self.__sourceLayer.name(), 0898 self.translateDictKey('layerType', self.__sourceLayer.type()), 0899 self.__sourceLayer.colorModel(), self.translateDictKey('colorModel', self.__sourceLayer.colorModel()), 0900 self.translateDictKey('colorDepth', self.__sourceLayer.colorDepth()) 0901 )) 0902 return False 0903 0904 return True 0905 0906 0907 0908 def toQImage(self, layerNode, rect=None): 0909 """Return `layerNode` content as a QImage (as ARGB32) 0910 0911 The `rect` value can be: 0912 - None, in this case will return all `layerNode` content 0913 - A QRect() object, in this case return `layerNode` content reduced to given rectangle bounds 0914 """ 0915 srcRect = layerNode.bounds() 0916 0917 if len(layerNode.childNodes()) == 0: 0918 projectionMode = False 0919 else: 0920 projectionMode = True 0921 0922 if projectionMode == True: 0923 img = QImage(layerNode.projectionPixelData(srcRect.left(), srcRect.top(), srcRect.width(), srcRect.height()), srcRect.width(), srcRect.height(), QImage.Format_ARGB32) 0924 else: 0925 img = QImage(layerNode.pixelData(srcRect.left(), srcRect.top(), srcRect.width(), srcRect.height()), srcRect.width(), srcRect.height(), QImage.Format_ARGB32) 0926 0927 return img.scaled(rect.width(), rect.height(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation) 0928 0929 0930 def openDialogOptions(self): 0931 """Open dialog box to let user define channel extraction options""" 0932 0933 tmpDocument = None 0934 previewBaSrc = QByteArray() 0935 lblPreview = [QLabel(), QLabel(), QLabel(), QLabel()] 0936 lblPreviewLbl = [QLabel(), QLabel(), QLabel(), QLabel()] 0937 0938 0939 # ---------------------------------------------------------------------- 0940 # Define signal and slots for UI widgets 0941 @pyqtSlot('QString') 0942 def ledLayerGroupName_Changed(value): 0943 self.__outputOptions['layerGroupName'] = value 0944 0945 @pyqtSlot('QString') 0946 def ledLayerColorName_Changed(value): 0947 self.__outputOptions['layerColorName'] = value 0948 0949 @pyqtSlot('QString') 0950 def cmbOutputMode_Changed(value): 0951 self.__outputOptions['outputMode'] = value 0952 buildPreview() 0953 0954 @pyqtSlot('QString') 0955 def cmbOriginalLayerAction_Changed(value): 0956 self.__outputOptions['originalLayerAction'] = value 0957 0958 def buildPreview(): 0959 pbProgress.setVisible(True) 0960 0961 backupValue = self.__outputOptions['layerColorName'] 0962 self.__outputOptions['layerColorName'] = '{color:long}' 0963 0964 # create a temporary document to work 0965 tmpDocument = Application.createDocument(imgThumbSrc.width(), imgThumbSrc.height(), "tmp", "RGBA", "U8", "", 120.0) 0966 0967 # create a layer used as original layer 0968 originalLayer = tmpDocument.createNode("Original", "paintlayer") 0969 tmpDocument.rootNode().addChildNode(originalLayer, None) 0970 # and set original image content 0971 originalLayer.setPixelData(previewBaSrc, 0, 0, tmpDocument.width(), tmpDocument.height()) 0972 0973 # execute process 0974 groupLayer = self.process(tmpDocument, originalLayer, pbProgress) 0975 0976 self.__outputOptions['layerColorName'] = backupValue 0977 0978 originalLayer.setVisible(False) 0979 groupLayer.setVisible(True) 0980 0981 for layer in groupLayer.childNodes(): 0982 layer.setBlendingMode('normal') 0983 layer.setVisible(False) 0984 tmpDocument.refreshProjection() 0985 0986 index = 0 0987 for layer in groupLayer.childNodes(): 0988 layer.setVisible(True) 0989 tmpDocument.refreshProjection() 0990 0991 lblPreview[index].setPixmap(QPixmap.fromImage(tmpDocument.projection(0, 0, tmpDocument.width(), tmpDocument.height()))) 0992 lblPreviewLbl[index].setText("<i>{0}</i>".format(layer.name())) 0993 layer.setVisible(False) 0994 0995 index+=1 0996 0997 if index > 3: 0998 lblPreview[3].setVisible(True) 0999 lblPreviewLbl[3].setVisible(True) 1000 else: 1001 lblPreview[3].setVisible(False) 1002 lblPreviewLbl[3].setVisible(False) 1003 1004 1005 tmpDocument.close() 1006 1007 pbProgress.setVisible(False) 1008 1009 1010 # ---------------------------------------------------------------------- 1011 # Create dialog box 1012 dlgMain = QDialog(Application.activeWindow().qwindow()) 1013 dlgMain.setWindowTitle(PLUGIN_DIALOG_TITLE) 1014 # resizeable with minimum size 1015 dlgMain.setSizeGripEnabled(True) 1016 dlgMain.setMinimumSize(DOPT_MIN_WIDTH, DOPT_MIN_HEIGHT) 1017 dlgMain.setModal(True) 1018 1019 # ...................................................................... 1020 # main dialog box, container 1021 vbxMainContainer = QVBoxLayout(dlgMain) 1022 1023 # main dialog box, current layer name 1024 lblLayerName = QLabel("{0} <b><i>{1}</i></b>".format(i18n("Processing layer"), self.__sourceLayer.name())) 1025 lblLayerName.setFixedHeight(30) 1026 vbxMainContainer.addWidget(lblLayerName) 1027 1028 # main dialog box, groupbox for layers options 1029 gbxLayersMgt = QGroupBox("Layers management") 1030 vbxMainContainer.addWidget(gbxLayersMgt) 1031 1032 # main dialog box, groupbox for output options 1033 gbxOutputResults = QGroupBox("Output results") 1034 vbxMainContainer.addWidget(gbxOutputResults) 1035 1036 vbxMainContainer.addStretch() 1037 1038 # main dialog box, OK/Cancel buttons 1039 dbbxOkCancel = QDialogButtonBox(dlgMain) 1040 dbbxOkCancel.setOrientation(QtCore.Qt.Horizontal) 1041 dbbxOkCancel.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) 1042 dbbxOkCancel.accepted.connect(dlgMain.accept) 1043 dbbxOkCancel.rejected.connect(dlgMain.reject) 1044 vbxMainContainer.addWidget(dbbxOkCancel) 1045 1046 # ...................................................................... 1047 # create layout for groupbox "Layers management" 1048 flLayersMgt = QFormLayout() 1049 gbxLayersMgt.setLayout(flLayersMgt) 1050 1051 ledLayerGroupName = QLineEdit() 1052 ledLayerGroupName.setText(self.__outputOptions['layerGroupName']) 1053 ledLayerGroupName.textChanged.connect(ledLayerGroupName_Changed) 1054 flLayersMgt.addRow(i18nc('The name for a new group layer; the generated layers will be placed in this group.', 'New layer group name'), ledLayerGroupName) 1055 1056 ledLayerColorName = QLineEdit() 1057 ledLayerColorName.setText(self.__outputOptions['layerColorName']) 1058 ledLayerColorName.textChanged.connect(ledLayerColorName_Changed) 1059 flLayersMgt.addRow(i18nc('Defines how the name for each layer created from the channel is generated.', 'New layers color name'), ledLayerColorName) 1060 1061 cmbOriginalLayerAction = QComboBox() 1062 cmbOriginalLayerAction.addItems([ 1063 ORIGINAL_LAYER_KEEPUNCHANGED, 1064 ORIGINAL_LAYER_KEEPVISIBLE, 1065 ORIGINAL_LAYER_KEEPHIDDEN, 1066 ORIGINAL_LAYER_REMOVE 1067 ]) 1068 cmbOriginalLayerAction.setCurrentText(self.__outputOptions['originalLayerAction']) 1069 cmbOriginalLayerAction.currentTextChanged.connect(cmbOriginalLayerAction_Changed) 1070 flLayersMgt.addRow(i18n("Original layer"), cmbOriginalLayerAction) 1071 1072 # ...................................................................... 1073 # create layout for groupbox "Output results" 1074 flOutputResults = QFormLayout() 1075 gbxOutputResults.setLayout(flOutputResults) 1076 1077 cmbOutputMode = QComboBox() 1078 cmbOutputMode.addItems([ 1079 OUTPUT_MODE_RGB, 1080 OUTPUT_MODE_CMY, 1081 OUTPUT_MODE_CMYK, 1082 OUTPUT_MODE_LRGB, 1083 OUTPUT_MODE_LCMY, 1084 OUTPUT_MODE_LCMYK 1085 ]) 1086 cmbOutputMode.setCurrentText(self.__outputOptions['outputMode']) 1087 cmbOutputMode.currentTextChanged.connect(cmbOutputMode_Changed) 1088 flOutputResults.addRow(i18n("Mode"), cmbOutputMode) 1089 1090 vbxPreviewLblContainer = QHBoxLayout() 1091 flOutputResults.addRow('', vbxPreviewLblContainer) 1092 vbxPreviewContainer = QHBoxLayout() 1093 flOutputResults.addRow('', vbxPreviewContainer) 1094 1095 # add preview progressbar 1096 pbProgress = QProgressBar() 1097 pbProgress.setFixedHeight(8) 1098 pbProgress.setTextVisible(False) 1099 pbProgress.setVisible(False) 1100 pbProgress.setRange(0, 107) 1101 flOutputResults.addRow('', pbProgress) 1102 1103 1104 imageRatio = self.__sourceDocument.width() / self.__sourceDocument.height() 1105 rect = QRect(0, 0, OUTPUT_PREVIEW_MAXSIZE, OUTPUT_PREVIEW_MAXSIZE) 1106 1107 # always ensure that final preview width and/or height is lower or equal than OUTPUT_PREVIEW_MAXSIZE 1108 if imageRatio < 1: 1109 # width < height 1110 rect.setWidth(int(imageRatio * OUTPUT_PREVIEW_MAXSIZE)) 1111 else: 1112 # width >= height 1113 rect.setHeight(int(OUTPUT_PREVIEW_MAXSIZE / imageRatio)) 1114 1115 imgThumbSrc = self.toQImage(self.__sourceLayer, rect) 1116 1117 previewBaSrc.resize(imgThumbSrc.byteCount()) 1118 ptr = imgThumbSrc.bits() 1119 ptr.setsize(imgThumbSrc.byteCount()) 1120 previewBaSrc = QByteArray(ptr.asstring()) 1121 1122 1123 lblPreviewSrc = QLabel() 1124 lblPreviewSrc.setPixmap(QPixmap.fromImage(imgThumbSrc)) 1125 lblPreviewSrc.setFixedHeight(imgThumbSrc.height() + 4) 1126 lblPreviewSrc.setFixedWidth(imgThumbSrc.width() + 4) 1127 vbxPreviewContainer.addWidget(lblPreviewSrc) 1128 1129 lblPreviewLblSrc = QLabel(i18nc("the original layer", "Original")) 1130 lblPreviewLblSrc.setFixedWidth(imgThumbSrc.width() + 4) 1131 vbxPreviewLblContainer.addWidget(lblPreviewLblSrc) 1132 1133 1134 vbxPreviewLblContainer.addWidget(QLabel(" ")) 1135 vbxPreviewContainer.addWidget(QLabel(">")) 1136 1137 lblPreview[3].setPixmap(QPixmap.fromImage(imgThumbSrc)) 1138 lblPreview[3].setFixedHeight(imgThumbSrc.height() + 4) 1139 lblPreview[3].setFixedWidth(imgThumbSrc.width() + 4) 1140 vbxPreviewContainer.addWidget(lblPreview[3]) 1141 1142 lblPreviewLbl[3] = QLabel(i18n("<i>Cyan</i>")) 1143 lblPreviewLbl[3].setIndent(10) 1144 lblPreviewLbl[3].setFixedWidth(imgThumbSrc.width() + 4) 1145 vbxPreviewLblContainer.addWidget(lblPreviewLbl[3]) 1146 1147 1148 lblPreview[2] = QLabel() 1149 lblPreview[2].setPixmap(QPixmap.fromImage(imgThumbSrc)) 1150 lblPreview[2].setFixedHeight(imgThumbSrc.height() + 4) 1151 lblPreview[2].setFixedWidth(imgThumbSrc.width() + 4) 1152 vbxPreviewContainer.addWidget(lblPreview[2]) 1153 1154 lblPreviewLbl[2] = QLabel(i18n("<i>Magenta</i>")) 1155 lblPreviewLbl[2].setIndent(10) 1156 lblPreviewLbl[2].setFixedWidth(imgThumbSrc.width() + 4) 1157 vbxPreviewLblContainer.addWidget(lblPreviewLbl[2]) 1158 1159 1160 lblPreview[1] = QLabel() 1161 lblPreview[1].setPixmap(QPixmap.fromImage(imgThumbSrc)) 1162 lblPreview[1].setFixedHeight(imgThumbSrc.height() + 4) 1163 lblPreview[1].setFixedWidth(imgThumbSrc.width() + 4) 1164 vbxPreviewContainer.addWidget(lblPreview[1]) 1165 1166 lblPreviewLbl[1] = QLabel(i18n("<i>Yellow</i>")) 1167 lblPreviewLbl[1].setIndent(10) 1168 lblPreviewLbl[1].setFixedWidth(imgThumbSrc.width() + 4) 1169 vbxPreviewLblContainer.addWidget(lblPreviewLbl[1]) 1170 1171 1172 lblPreview[0] = QLabel() 1173 lblPreview[0].setPixmap(QPixmap.fromImage(imgThumbSrc)) 1174 lblPreview[0].setFixedHeight(imgThumbSrc.height() + 4) 1175 lblPreview[0].setFixedWidth(imgThumbSrc.width() + 4) 1176 vbxPreviewContainer.addWidget(lblPreview[0]) 1177 1178 lblPreviewLbl[0] = QLabel(i18n("<i>Black</i>")) 1179 lblPreviewLbl[0].setIndent(10) 1180 lblPreviewLbl[0].setFixedWidth(imgThumbSrc.width() + 4) 1181 vbxPreviewLblContainer.addWidget(lblPreviewLbl[0]) 1182 1183 1184 vbxPreviewLblContainer.addStretch() 1185 vbxPreviewContainer.addStretch() 1186 1187 buildPreview() 1188 1189 returned = dlgMain.exec_() 1190 1191 return returned 1192 1193 1194 def progressNext(self, pProgress): 1195 """Update progress bar""" 1196 if pProgress is not None: 1197 stepCurrent=pProgress.value()+1 1198 pProgress.setValue(stepCurrent) 1199 QApplication.instance().processEvents() 1200 1201 1202 def run(self): 1203 """Run process for current layer""" 1204 1205 pdlgProgress = QProgressDialog(self.__outputOptions['outputMode'], None, 0, 100, Application.activeWindow().qwindow()) 1206 pdlgProgress.setWindowTitle(PLUGIN_DIALOG_TITLE) 1207 pdlgProgress.setMinimumSize(640, 200) 1208 pdlgProgress.setModal(True) 1209 pdlgProgress.show() 1210 1211 self.process(self.__sourceDocument, self.__sourceLayer, pdlgProgress) 1212 1213 pdlgProgress.close() 1214 1215 1216 1217 1218 def process(self, pDocument, pOriginalLayer, pProgress): 1219 """Process given layer with current options""" 1220 1221 self.layerNum = 0 1222 document = pDocument 1223 originalLayer = pOriginalLayer 1224 parentGroupLayer = None 1225 currentProcessedLayer = None 1226 originalLayerIsVisible = originalLayer.visible() 1227 1228 1229 def getLayerByName(parent, value): 1230 """search and return a layer by name, within given parent group""" 1231 if parent == None: 1232 return document.nodeByName(value) 1233 1234 for layer in parent.childNodes(): 1235 if layer.name() == value: 1236 return layer 1237 1238 return None 1239 1240 1241 def duplicateLayer(currentProcessedLayer, value): 1242 """Duplicate layer from given name 1243 New layer become active layer 1244 """ 1245 1246 newLayer = None 1247 srcLayer = None 1248 srcName = re.match("^@(.*)", value) 1249 1250 if not srcName is None: 1251 # reference to a specific layer 1252 if srcName[1] == 'original': 1253 # original layer currently processed 1254 srcLayer = originalLayer 1255 else: 1256 # a color layer previously built (and finished) 1257 srcLayer = getLayerByName(parentGroupLayer, parseLayerName(self.__outputOptions['layerColorName'], srcName[1])) 1258 else: 1259 # a layer with a fixed name 1260 srcLayer = document.nodeByName(parseLayerName(value, '')) 1261 1262 1263 if not srcLayer is None: 1264 newLayer = srcLayer.duplicate() 1265 1266 self.layerNum+=1 1267 newLayer.setName("c2l-w{0}".format(self.layerNum)) 1268 1269 parentGroupLayer.addChildNode(newLayer, currentProcessedLayer) 1270 return newLayer 1271 else: 1272 return None 1273 1274 1275 def newLayer(currentProcessedLayer, value): 1276 """Create a new layer of given type 1277 New layer become active layer 1278 """ 1279 1280 newLayer = None 1281 1282 if value is None or not value['type'] in ['filllayer']: 1283 # given type for new layer is not valid 1284 # currently only one layer type is implemented 1285 return None 1286 1287 if value['type'] == 'filllayer': 1288 infoObject = InfoObject(); 1289 infoObject.setProperty("color", value['color']) 1290 selection = Selection(); 1291 selection.select(0, 0, document.width(), document.height(), 255) 1292 1293 newLayer = document.createFillLayer(value['color'].name(), "color", infoObject, selection) 1294 1295 1296 if newLayer: 1297 self.layerNum+=1 1298 newLayer.setName("c2l-w{0}".format(self.layerNum)) 1299 1300 parentGroupLayer.addChildNode(newLayer, currentProcessedLayer) 1301 1302 # Need to force generator otherwise, information provided when creating layer seems to not be taken in 1303 # account 1304 newLayer.setGenerator("color", infoObject) 1305 1306 return newLayer 1307 else: 1308 return None 1309 1310 1311 def mergeDown(currentProcessedLayer, value): 1312 """Merge current layer with layer below""" 1313 if currentProcessedLayer is None: 1314 return None 1315 1316 newLayer = currentProcessedLayer.mergeDown() 1317 # note: 1318 # when layer is merged down: 1319 # - a new layer seems to be created (reference to 'down' layer does not match anymore layer in group) 1320 # - retrieved 'newLayer' reference does not match to new layer resulting from merge 1321 # - activeNode() in document doesn't match to new layer resulting from merge 1322 # maybe it's norpmal, maybe not... 1323 # but the only solution to be able to work on merged layer (with current script) is to consider that from 1324 # parent node, last child match to last added layer and then, to our merged layer 1325 currentProcessedLayer = parentGroupLayer.childNodes()[-1] 1326 # for an unknown reason, merged layer bounds are not corrects... :'-( 1327 currentProcessedLayer.cropNode(0, 0, document.width(), document.height()) 1328 return currentProcessedLayer 1329 1330 1331 def applyBlendingMode(currentProcessedLayer, value): 1332 """Set blending mode for current layer""" 1333 if currentProcessedLayer is None or value is None or value == '': 1334 return False 1335 1336 currentProcessedLayer.setBlendingMode(value) 1337 return True 1338 1339 1340 def applyFilter(currentProcessedLayer, value): 1341 """Apply filter to layer""" 1342 if currentProcessedLayer is None or value is None or value == '': 1343 return None 1344 1345 filterName = re.match("name=([^;]+)", value) 1346 1347 if filterName is None: 1348 return None 1349 1350 filter = Application.filter(filterName.group(1)) 1351 filterConfiguration = filter.configuration() 1352 1353 for parameter in value.split(';'): 1354 parameterName = re.match("^([^=]+)=(.*)", parameter) 1355 1356 if not parameterName is None and parameterName != 'name': 1357 filterConfiguration.setProperty(parameterName.group(1), parameterName.group(2)) 1358 1359 filter.setConfiguration(filterConfiguration) 1360 filter.apply(currentProcessedLayer, 0, 0, document.width(), document.height()) 1361 1362 return currentProcessedLayer 1363 1364 1365 def parseLayerName(value, color): 1366 """Parse layer name""" 1367 1368 returned = value 1369 1370 returned = returned.replace("{source:name}", originalLayer.name()) 1371 returned = returned.replace("{mode}", OUTPUT_MODE_NFO[self.__outputOptions['outputMode']]['groupLayerName']) 1372 returned = returned.replace("{color:short}", color) 1373 if color == "C": 1374 returned = returned.replace("{color:long}", i18n("Cyan")) 1375 elif color == "M": 1376 returned = returned.replace("{color:long}", i18n("Magenta")) 1377 elif color == "Y": 1378 returned = returned.replace("{color:long}", i18n("Yellow")) 1379 elif color == "K": 1380 returned = returned.replace("{color:long}", i18n("Black")) 1381 elif color == "R": 1382 returned = returned.replace("{color:long}", i18n("Red")) 1383 elif color == "G": 1384 returned = returned.replace("{color:long}", i18n("Green")) 1385 elif color == "B": 1386 returned = returned.replace("{color:long}", i18n("Blue")) 1387 else: 1388 returned = returned.replace("{color:long}", "") 1389 1390 return returned 1391 1392 1393 if document is None or originalLayer is None: 1394 # should not occurs, but... 1395 return None 1396 1397 if not pProgress is None: 1398 stepTotal = 4 1399 for layer in OUTPUT_MODE_NFO[self.__outputOptions['outputMode']]['layers']: 1400 stepTotal+=len(layer['process']) 1401 1402 pProgress.setRange(0, stepTotal) 1403 1404 1405 if originalLayerIsVisible == False: 1406 originalLayer.setVisible(True) 1407 1408 # ---------------------------------------------------------------------- 1409 # Create new group layer 1410 parentGroupLayer = document.createGroupLayer(parseLayerName(self.__outputOptions['layerGroupName'], '')) 1411 1412 self.progressNext(pProgress) 1413 1414 currentProcessedLayer = None 1415 1416 for layer in OUTPUT_MODE_NFO[self.__outputOptions['outputMode']]['layers']: 1417 for process in layer['process']: 1418 if process['action'] == 'duplicate': 1419 currentProcessedLayer = duplicateLayer(currentProcessedLayer, process['value']) 1420 elif process['action'] == 'new': 1421 currentProcessedLayer = newLayer(currentProcessedLayer, process['value']) 1422 elif process['action'] == 'merge down': 1423 currentProcessedLayer = mergeDown(currentProcessedLayer, process['value']) 1424 pass 1425 elif process['action'] == 'blending mode': 1426 applyBlendingMode(currentProcessedLayer, process['value']) 1427 elif process['action'] == 'filter': 1428 applyFilter(currentProcessedLayer, process['value']) 1429 1430 self.progressNext(pProgress) 1431 1432 if not currentProcessedLayer is None: 1433 # rename currentProcessedLayer 1434 currentProcessedLayer.setName(parseLayerName(self.__outputOptions['layerColorName'], layer['color'])) 1435 1436 document.rootNode().addChildNode(parentGroupLayer, originalLayer) 1437 self.progressNext(pProgress) 1438 1439 if self.__outputOptions['originalLayerAction'] == ORIGINAL_LAYER_KEEPVISIBLE: 1440 originalLayer.setVisible(True) 1441 elif self.__outputOptions['originalLayerAction'] == ORIGINAL_LAYER_KEEPHIDDEN: 1442 originalLayer.setVisible(False) 1443 elif self.__outputOptions['originalLayerAction'] == ORIGINAL_LAYER_REMOVE: 1444 originalLayer.remove() 1445 else: 1446 # ORIGINAL_LAYER_KEEPUNCHANGED 1447 originalLayer.setVisible(originalLayerIsVisible) 1448 1449 1450 self.progressNext(pProgress) 1451 1452 document.refreshProjection() 1453 self.progressNext(pProgress) 1454 1455 document.setActiveNode(parentGroupLayer) 1456 1457 return parentGroupLayer 1458 1459 1460 #ChannelsToLayers(Krita.instance()).process(Application.activeDocument(), Application.activeDocument().activeNode(), None) 1461 #ChannelsToLayers(Krita.instance()).action_triggered()