Krita Source Code Documentation
Loading...
Searching...
No Matches
channels2layers.py
Go to the documentation of this file.
1#-----------------------------------------------------------------------------
2# Channels to Layers
3# SPDX-FileCopyrightText: 2019 - Grum 999
4# -----------------------------------------------------------------------------
5# SPDX-License-Identifier: GPL-3.0-or-later
6# -----------------------------------------------------------------------------
7# A Krita plugin designed to split channels from a layer to sub-layers
8# . RGB
9# . CMY
10# . CMYK
11# . RGB as grayscale values
12# . CMY as grayscale values
13# . CMYK as grayscale values
14# -----------------------------------------------------------------------------
15
16import re
17from krita import (
18 Extension,
19 InfoObject,
20 Selection
21 )
22from builtins import i18n, i18nc, Application
23try:
24 from PyQt6.QtCore import (
25 pyqtSlot,
26 QByteArray,
27 Qt,
28 QRect
29 )
30 from PyQt6.QtGui import (
31 QColor,
32 QImage,
33 QPixmap,
34 )
35 from PyQt6.QtWidgets import (
36 QApplication,
37 QComboBox,
38 QDialog,
39 QDialogButtonBox,
40 QFormLayout,
41 QGroupBox,
42 QHBoxLayout,
43 QLabel,
44 QLineEdit,
45 QMessageBox,
46 QProgressBar,
47 QProgressDialog,
48 QVBoxLayout,
49 QWidget
50 )
51except:
52 from PyQt5.QtCore import (
53 pyqtSlot,
54 QByteArray,
55 Qt,
56 QRect
57 )
58 from PyQt5.QtGui import (
59 QColor,
60 QImage,
61 QPixmap,
62 )
63 from PyQt5.QtWidgets import (
64 QApplication,
65 QComboBox,
66 QDialog,
67 QDialogButtonBox,
68 QFormLayout,
69 QGroupBox,
70 QHBoxLayout,
71 QLabel,
72 QLineEdit,
73 QMessageBox,
74 QProgressBar,
75 QProgressDialog,
76 QVBoxLayout,
77 QWidget
78 )
79
80
81PLUGIN_VERSION = '1.1.0'
82
83EXTENSION_ID = 'pykrita_channels2layers'
84PLUGIN_MENU_ENTRY = i18n('Channels to layers')
85PLUGIN_DIALOG_TITLE = "{0} - {1}".format(i18n('Channels to layers'), PLUGIN_VERSION)
86
87# Define DialogBox types
88DBOX_INFO = 'i'
89DBOX_WARNING ='w'
90
91
92# Define Output modes
93OUTPUT_MODE_RGB = i18n('RGB Colors')
94OUTPUT_MODE_CMY = i18n('CMY Colors')
95OUTPUT_MODE_CMYK = i18n('CMYK Colors')
96OUTPUT_MODE_LRGB = i18n('RGB Grayscale levels')
97OUTPUT_MODE_LCMY = i18n('CMY Grayscale levels')
98OUTPUT_MODE_LCMYK = i18n('CMYK Grayscale levels')
99
100OUTPUT_PREVIEW_MAXSIZE = 320
101
102# Define original layer action
103ORIGINAL_LAYER_KEEPUNCHANGED = i18n('Unchanged')
104ORIGINAL_LAYER_KEEPVISIBLE = i18n('Visible')
105ORIGINAL_LAYER_KEEPHIDDEN = i18n('Hidden')
106ORIGINAL_LAYER_REMOVE = i18n('Remove')
107
108# define dialog option minimum dimension
109DOPT_MIN_WIDTH = OUTPUT_PREVIEW_MAXSIZE * 5 + 200
110DOPT_MIN_HEIGHT = 480
111
112
113
114OUTPUT_MODE_NFO = {
115 OUTPUT_MODE_RGB : {
116 'description' : 'Extract channels (Red, Green, Blue) and create a colored layer per channel',
117 'groupLayerName' : 'RGB',
118 'layers' : [
119 {
120 'color' : 'B',
121 'process': [
122 {
123 'action' : 'duplicate',
124 'value' : '@original'
125 },
126 {
127 'action' : 'new',
128 'value' : {
129 'type' : 'filllayer',
130 'color' : QColor(Qt.GlobalColor.blue)
131 }
132 },
133 {
134 'action' : 'blending mode',
135 'value' : 'inverse_subtract'
136 },
137 {
138 'action' : 'merge down',
139 'value' : None
140 },
141 {
142 'action' : 'blending mode',
143 'value' : 'add'
144 }
145 ]
146 },
147 {
148 'color' : 'G',
149 'process': [
150 {
151 'action' : 'duplicate',
152 'value' : '@original'
153 },
154 {
155 'action' : 'new',
156 'value' : {
157 'type' : 'filllayer',
158 'color' : QColor(Qt.GlobalColor.green)
159 }
160 },
161 {
162 'action' : 'blending mode',
163 'value' : 'inverse_subtract'
164 },
165 {
166 'action' : 'merge down',
167 'value' : None
168 },
169 {
170 'action' : 'blending mode',
171 'value' : 'add'
172 }
173 ]
174 },
175 {
176 'color' : 'R',
177 'process': [
178 {
179 'action' : 'duplicate',
180 'value' : '@original'
181 },
182 {
183 'action' : 'new',
184 'value' : {
185 'type' : 'filllayer',
186 'color' : QColor(Qt.GlobalColor.red)
187 }
188 },
189 {
190 'action' : 'blending mode',
191 'value' : 'inverse_subtract'
192 },
193 {
194 'action' : 'merge down',
195 'value' : None
196 },
197 {
198 'action' : 'blending mode',
199 'value' : 'add'
200 }
201 ]
202 }
203 ]
204 },
205 OUTPUT_MODE_CMY : {
206 'description' : 'Extract channels (Cyan, Mangenta, Yellow) and create a colored layer per channel',
207 'groupLayerName' : 'CMY',
208 'layers' : [
209 {
210 'color' : 'Y',
211 'process': [
212 {
213 'action' : 'duplicate',
214 'value' : '@original'
215 },
216 {
217 'action' : 'new',
218 'value' : {
219 'type' : 'filllayer',
220 'color' : QColor(Qt.GlobalColor.yellow)
221 }
222 },
223 {
224 'action' : 'blending mode',
225 'value' : 'add'
226 },
227 {
228 'action' : 'merge down',
229 'value' : None
230 },
231 {
232 'action' : 'blending mode',
233 'value' : 'multiply'
234 }
235 ]
236 },
237 {
238 'color' : 'M',
239 'process': [
240 {
241 'action' : 'duplicate',
242 'value' : '@original'
243 },
244 {
245 'action' : 'new',
246 'value' : {
247 'type' : 'filllayer',
248 'color' : QColor(Qt.GlobalColor.magenta)
249 }
250 },
251 {
252 'action' : 'blending mode',
253 'value' : 'add'
254 },
255 {
256 'action' : 'merge down',
257 'value' : None
258 },
259 {
260 'action' : 'blending mode',
261 'value' : 'multiply'
262 }
263 ]
264 },
265 {
266 'color' : 'C',
267 'process': [
268 {
269 'action' : 'duplicate',
270 'value' : '@original'
271 },
272 {
273 'action' : 'new',
274 'value' : {
275 'type' : 'filllayer',
276 'color' : QColor(Qt.GlobalColor.cyan)
277 }
278 },
279 {
280 'action' : 'blending mode',
281 'value' : 'add'
282 },
283 {
284 'action' : 'merge down',
285 'value' : None
286 },
287 {
288 'action' : 'blending mode',
289 'value' : 'multiply'
290 }
291 ]
292 }
293 ]
294 },
295 OUTPUT_MODE_CMYK : {
296 'description' : 'Extract channels (Cyan, Mangenta, Yellow, Black) and create a colored layer per channel',
297 'groupLayerName' : 'CMYK',
298 'layers' : [
299 {
300 'color' : 'K',
301 'process': [
302 {
303 'action' : 'duplicate',
304 'value' : '@original'
305 },
306 {
307 'action' : 'filter',
308 'value' : 'name=desaturate;type=5' # desaturate method = max
309 }
310 ]
311 },
312 {
313 'color' : 'Y',
314 'process': [
315 {
316 'action' : 'duplicate',
317 'value' : '@original'
318 },
319 {
320 'action' : 'new',
321 'value' : {
322 'type' : 'filllayer',
323 'color' : QColor(Qt.GlobalColor.yellow)
324 }
325 },
326 {
327 'action' : 'blending mode',
328 'value' : 'add'
329 },
330 {
331 'action' : 'merge down',
332 'value' : None
333 },
334 {
335 'action' : 'duplicate',
336 'value' : '@K'
337 },
338 {
339 'action' : 'blending mode',
340 'value' : 'divide'
341 },
342 {
343 'action' : 'merge down',
344 'value' : None
345 },
346 {
347 'action' : 'blending mode',
348 'value' : 'multiply'
349 }
350 ]
351 },
352 {
353 'color' : 'M',
354 'process': [
355 {
356 'action' : 'duplicate',
357 'value' : '@original'
358 },
359 {
360 'action' : 'new',
361 'value' : {
362 'type' : 'filllayer',
363 'color' : QColor(Qt.GlobalColor.magenta)
364 }
365 },
366 {
367 'action' : 'blending mode',
368 'value' : 'add'
369 },
370 {
371 'action' : 'merge down',
372 'value' : None
373 },
374 {
375 'action' : 'duplicate',
376 'value' : '@K'
377 },
378 {
379 'action' : 'blending mode',
380 'value' : 'divide'
381 },
382 {
383 'action' : 'merge down',
384 'value' : None
385 },
386 {
387 'action' : 'blending mode',
388 'value' : 'multiply'
389 }
390 ]
391 },
392 {
393 'color' : 'C',
394 'process': [
395 {
396 'action' : 'duplicate',
397 'value' : '@original'
398 },
399 {
400 'action' : 'new',
401 'value' : {
402 'type' : 'filllayer',
403 'color' : QColor(Qt.GlobalColor.cyan)
404 }
405 },
406 {
407 'action' : 'blending mode',
408 'value' : 'add'
409 },
410 {
411 'action' : 'merge down',
412 'value' : None
413 },
414 {
415 'action' : 'duplicate',
416 'value' : '@K'
417 },
418 {
419 'action' : 'blending mode',
420 'value' : 'divide'
421 },
422 {
423 'action' : 'merge down',
424 'value' : None
425 },
426 {
427 'action' : 'blending mode',
428 'value' : 'multiply'
429 }
430 ]
431 }
432 ]
433 },
434 OUTPUT_MODE_LRGB : {
435 'description' : 'Extract channels (Red, Green, Blue) and create a grayscale layer per channel',
436 'groupLayerName' : 'RGB[GS]',
437 'layers' : [
438 {
439 'color' : 'B',
440 'process': [
441 {
442 'action' : 'duplicate',
443 'value' : '@original'
444 },
445 {
446 'action' : 'new',
447 'value' : {
448 'type' : 'filllayer',
449 'color' : QColor(Qt.GlobalColor.blue)
450 }
451 },
452 {
453 'action' : 'blending mode',
454 'value' : 'inverse_subtract'
455 },
456 {
457 'action' : 'merge down',
458 'value' : None
459 },
460 {
461 'action' : 'blending mode',
462 'value' : 'add'
463 },
464 {
465 'action' : 'filter',
466 'value' : 'name=desaturate;type=5' # desaturate method = max
467 }
468 ]
469 },
470 {
471 'color' : 'G',
472 'process': [
473 {
474 'action' : 'duplicate',
475 'value' : '@original'
476 },
477 {
478 'action' : 'new',
479 'value' : {
480 'type' : 'filllayer',
481 'color' : QColor(Qt.GlobalColor.green)
482 }
483 },
484 {
485 'action' : 'blending mode',
486 'value' : 'inverse_subtract'
487 },
488 {
489 'action' : 'merge down',
490 'value' : None
491 },
492 {
493 'action' : 'blending mode',
494 'value' : 'add'
495 },
496 {
497 'action' : 'filter',
498 'value' : 'name=desaturate;type=5' # desaturate method = max
499 }
500 ]
501 },
502 {
503 'color' : 'R',
504 'process': [
505 {
506 'action' : 'duplicate',
507 'value' : '@original'
508 },
509 {
510 'action' : 'new',
511 'value' : {
512 'type' : 'filllayer',
513 'color' : QColor(Qt.GlobalColor.red)
514 }
515 },
516 {
517 'action' : 'blending mode',
518 'value' : 'inverse_subtract'
519 },
520 {
521 'action' : 'merge down',
522 'value' : None
523 },
524 {
525 'action' : 'blending mode',
526 'value' : 'add'
527 },
528 {
529 'action' : 'filter',
530 'value' : 'name=desaturate;type=5' # desaturate method = max
531 }
532 ]
533 }
534 ]
535 },
536 OUTPUT_MODE_LCMY : {
537 'description' : 'Extract channels (Cyan, Mangenta, Yellow) and create a grayscale layer per channel',
538 'groupLayerName' : 'CMY[GS]',
539 'layers' : [
540 {
541 'color' : 'Y',
542 'process': [
543 {
544 'action' : 'duplicate',
545 'value' : '@original'
546 },
547 {
548 'action' : 'new',
549 'value' : {
550 'type' : 'filllayer',
551 'color' : QColor(Qt.GlobalColor.yellow)
552 }
553 },
554 {
555 'action' : 'blending mode',
556 'value' : 'add'
557 },
558 {
559 'action' : 'merge down',
560 'value' : None
561 },
562 {
563 'action' : 'blending mode',
564 'value' : 'add'
565 },
566 {
567 'action' : 'filter',
568 'value' : 'name=desaturate;type=4' # desaturate method = min
569 }
570 ]
571 },
572 {
573 'color' : 'M',
574 'process': [
575 {
576 'action' : 'duplicate',
577 'value' : '@original'
578 },
579 {
580 'action' : 'new',
581 'value' : {
582 'type' : 'filllayer',
583 'color' : QColor(Qt.GlobalColor.magenta)
584 }
585 },
586 {
587 'action' : 'blending mode',
588 'value' : 'add'
589 },
590 {
591 'action' : 'merge down',
592 'value' : None
593 },
594 {
595 'action' : 'blending mode',
596 'value' : 'add'
597 },
598 {
599 'action' : 'filter',
600 'value' : 'name=desaturate;type=4' # desaturate method = min
601 }
602 ]
603 },
604 {
605 'color' : 'C',
606 'process': [
607 {
608 'action' : 'duplicate',
609 'value' : '@original'
610 },
611 {
612 'action' : 'new',
613 'value' : {
614 'type' : 'filllayer',
615 'color' : QColor(Qt.GlobalColor.cyan)
616 }
617 },
618 {
619 'action' : 'blending mode',
620 'value' : 'add'
621 },
622 {
623 'action' : 'merge down',
624 'value' : None
625 },
626 {
627 'action' : 'blending mode',
628 'value' : 'add'
629 },
630 {
631 'action' : 'filter',
632 'value' : 'name=desaturate;type=4' # desaturate method = min
633 }
634 ]
635 }
636 ]
637 },
638 OUTPUT_MODE_LCMYK : {
639 'description' : 'Extract channels (Cyan, Mangenta, Yellow, Black) and create a grayscale layer per channel',
640 'groupLayerName' : 'CMYK[GS]',
641 'layers' : [
642 {
643 'color' : 'K',
644 'process': [
645 {
646 'action' : 'duplicate',
647 'value' : '@original'
648 },
649 {
650 'action' : 'filter',
651 'value' : 'name=desaturate;type=5' # desaturate method = max
652 }
653 ]
654 },
655 {
656 'color' : 'Y',
657 'process': [
658 {
659 'action' : 'duplicate',
660 'value' : '@original'
661 },
662 {
663 'action' : 'new',
664 'value' : {
665 'type' : 'filllayer',
666 'color' : QColor(Qt.GlobalColor.yellow)
667 }
668 },
669 {
670 'action' : 'blending mode',
671 'value' : 'add'
672 },
673 {
674 'action' : 'merge down',
675 'value' : None
676 },
677 {
678 'action' : 'duplicate',
679 'value' : '@K'
680 },
681 {
682 'action' : 'blending mode',
683 'value' : 'divide'
684 },
685 {
686 'action' : 'merge down',
687 'value' : None
688 },
689 {
690 'action' : 'blending mode',
691 'value' : 'multiply'
692 },
693 {
694 'action' : 'filter',
695 'value' : 'name=desaturate;type=4' # desaturate method = min
696 }
697 ]
698 },
699 {
700 'color' : 'M',
701 'process': [
702 {
703 'action' : 'duplicate',
704 'value' : '@original'
705 },
706 {
707 'action' : 'new',
708 'value' : {
709 'type' : 'filllayer',
710 'color' : QColor(Qt.GlobalColor.magenta)
711 }
712 },
713 {
714 'action' : 'blending mode',
715 'value' : 'add'
716 },
717 {
718 'action' : 'merge down',
719 'value' : None
720 },
721 {
722 'action' : 'duplicate',
723 'value' : '@K'
724 },
725 {
726 'action' : 'blending mode',
727 'value' : 'divide'
728 },
729 {
730 'action' : 'merge down',
731 'value' : None
732 },
733 {
734 'action' : 'blending mode',
735 'value' : 'multiply'
736 },
737 {
738 'action' : 'filter',
739 'value' : 'name=desaturate;type=4' # desaturate method = min
740 }
741 ]
742 },
743 {
744 'color' : 'C',
745 'process': [
746 {
747 'action' : 'duplicate',
748 'value' : '@original'
749 },
750 {
751 'action' : 'new',
752 'value' : {
753 'type' : 'filllayer',
754 'color' : QColor(Qt.GlobalColor.cyan)
755 }
756 },
757 {
758 'action' : 'blending mode',
759 'value' : 'add'
760 },
761 {
762 'action' : 'merge down',
763 'value' : None
764 },
765 {
766 'action' : 'duplicate',
767 'value' : '@K'
768 },
769 {
770 'action' : 'blending mode',
771 'value' : 'divide'
772 },
773 {
774 'action' : 'merge down',
775 'value' : None
776 },
777 {
778 'action' : 'blending mode',
779 'value' : 'multiply'
780 },
781 {
782 'action' : 'filter',
783 'value' : 'name=desaturate;type=4' # desaturate method = min
784 }
785 ]
786 }
787 ]
788 }
789}
790
791TRANSLATIONS_DICT = {
792 'colorDepth' : {
793 'U8' : '8bits',
794 'U16' : '16bits',
795 'F16' : '16bits floating point',
796 'F32' : '32bits floating point'
797 },
798 'colorModel' : {
799 'A' : 'Alpha mask',
800 'RGBA' : 'RGB with alpha channel',
801 'XYZA' : 'XYZ with alpha channel',
802 'LABA' : 'LAB with alpha channel',
803 'CMYKA' : 'CMYK with alpha channel',
804 'GRAYA' : 'Gray with alpha channel',
805 'YCbCrA' : 'YCbCr with alpha channel'
806 },
807 'layerType' : {
808 'paintlayer' : 'Paint layer',
809 'grouplayer' : 'Group layer',
810 'filelayer' : 'File layer',
811 'filterlayer' : 'Filter layer',
812 'filllayer' : 'Fill layer',
813 'clonelayer' : 'Clone layer',
814 'vectorlayer' : 'Vector layer',
815 'transparencymask' : 'Transparency mask',
816 'filtermask' : 'Filter mask',
817 'transformmask': 'Transform mask',
818 'selectionmask': 'Selection mask',
819 'colorizemask' : 'Colorize mask'
820 }
821}
822
823
824
825
826class ChannelsToLayers(Extension):
827
828 def __init__(self, parent):
829 # Default options
831 'outputMode': OUTPUT_MODE_RGB,
832 'originalLayerAction': ORIGINAL_LAYER_KEEPHIDDEN,
833 'layerGroupName': '{mode}-{source:name}',
834 'layerColorName': '{mode}[{color:short}]-{source:name}'
835 }
836
838 self.__sourceLayer = None
839
840 # Always initialise the superclass.
841 # This is necessary to create the underlying C++ object
842 super().__init__(parent)
843 self.parent = parent
844
845
846 def setup(self):
847 pass
848
849
850 def createActions(self, window):
851 action = window.createAction(EXTENSION_ID, PLUGIN_MENU_ENTRY, "tools/scripts")
852 action.triggered.connect(self.action_triggeredaction_triggered)
853
854 def dBoxMessage(self, msgType, msg):
855 """Simplified function for DialogBox 'OK' message"""
856 if msgType == DBOX_WARNING:
857 QMessageBox.warning(
858 QWidget(),
859 PLUGIN_DIALOG_TITLE,
860 i18n(msg)
861 )
862 else:
863 QMessageBox.information(
864 QWidget(),
865 PLUGIN_DIALOG_TITLE,
866 i18n(msg)
867 )
868
869
871 """Action called when script is executed from Kitra menu"""
872 if self.checkCurrentLayer():
873 if self.openDialogOptions():
874 self.run()
875
876
877 def translateDictKey(self, key, value):
878 """Translate key from dictionary (mostly internal Krita internal values) to human readable values"""
879 returned = i18n('Unknown')
880
881 if key in TRANSLATIONS_DICT.keys():
882 if value in TRANSLATIONS_DICT[key].keys():
883 returned = i18n(TRANSLATIONS_DICT[key][value])
884
885 return returned
886
887
888 def checkCurrentLayer(self):
889 """Check if current layer is valid
890 - A document must be opened
891 - Active layer properties must be:
892 . Layer type: a paint layer
893 . Color model: RGBA
894 . Color depth: 8bits
895 """
896 self.__sourceDocument = Application.activeDocument()
897 # Check if there's an active document
898 if self.__sourceDocument is None:
899 self.dBoxMessage(DBOX_WARNING, "There's no active document!")
900 return False
901
902 self.__sourceLayer = self.__sourceDocument.activeNode()
903
904 # Check if current layer can be processed
905 if self.__sourceLayer.type() != "paintlayer" or self.__sourceLayer.colorModel() != "RGBA" or self.__sourceLayer.colorDepth() != "U8":
906 self.dBoxMessage(DBOX_WARNING, "Selected layer must be a 8bits RGBA Paint Layer!"
907 "\n\nCurrent layer '{0}' properties:"
908 "\n- Layer type: {1}"
909 "\n- Color model: {2} ({3})"
910 "\n- Color depth: {4}"
911 "\n\n> Action is cancelled".format(self.__sourceLayer.name(),
912 self.translateDictKey('layerType', self.__sourceLayer.type()),
913 self.__sourceLayer.colorModel(), self.translateDictKey('colorModel', self.__sourceLayer.colorModel()),
914 self.translateDictKey('colorDepth', self.__sourceLayer.colorDepth())
915 ))
916 return False
917
918 return True
919
920
921
922 def toQImage(self, layerNode, rect=None):
923 """Return `layerNode` content as a QImage (as ARGB32)
924
925 The `rect` value can be:
926 - None, in this case will return all `layerNode` content
927 - A QRect() object, in this case return `layerNode` content reduced to given rectangle bounds
928 """
929 srcRect = layerNode.bounds()
930
931 if len(layerNode.childNodes()) == 0:
932 projectionMode = False
933 else:
934 projectionMode = True
935
936 if projectionMode == True:
937 img = QImage(layerNode.projectionPixelData(srcRect.left(), srcRect.top(), srcRect.width(), srcRect.height()), srcRect.width(), srcRect.height(), QImage.Format.Format_ARGB32)
938 else:
939 img = QImage(layerNode.pixelData(srcRect.left(), srcRect.top(), srcRect.width(), srcRect.height()), srcRect.width(), srcRect.height(), QImage.Format.Format_ARGB32)
940
941 return img.scaled(rect.width(), rect.height(), Qt.AspectRatioMode.IgnoreAspectRatio, Qt.TransformationMode.SmoothTransformation)
942
943
944 def openDialogOptions(self):
945 """Open dialog box to let user define channel extraction options"""
946
947 tmpDocument = None
948 previewBaSrc = QByteArray()
949 lblPreview = [QLabel(), QLabel(), QLabel(), QLabel()]
950 lblPreviewLbl = [QLabel(), QLabel(), QLabel(), QLabel()]
951
952
953 # ----------------------------------------------------------------------
954 # Define signal and slots for UI widgets
955 @pyqtSlot('QString')
956 def ledLayerGroupName_Changed(value):
957 self.__outputOptions['layerGroupName'] = value
958
959 @pyqtSlot('QString')
960 def ledLayerColorName_Changed(value):
961 self.__outputOptions['layerColorName'] = value
962
963 @pyqtSlot('QString')
964 def cmbOutputMode_Changed(value):
965 self.__outputOptions['outputMode'] = value
966 buildPreview()
967
968 @pyqtSlot('QString')
969 def cmbOriginalLayerAction_Changed(value):
970 self.__outputOptions['originalLayerAction'] = value
971
972 def buildPreview():
973 pbProgress.setVisible(True)
974
975 backupValue = self.__outputOptions['layerColorName']
976 self.__outputOptions['layerColorName'] = '{color:long}'
977
978 # create a temporary document to work
979 tmpDocument = Application.createDocument(imgThumbSrc.width(), imgThumbSrc.height(), "tmp", "RGBA", "U8", "", 120.0)
980
981 # create a layer used as original layer
982 originalLayer = tmpDocument.createNode("Original", "paintlayer")
983 tmpDocument.rootNode().addChildNode(originalLayer, None)
984 # and set original image content
985 originalLayer.setPixelData(previewBaSrc, 0, 0, tmpDocument.width(), tmpDocument.height())
986
987 # execute process
988 groupLayer = self.process(tmpDocument, originalLayer, pbProgress)
989
990 self.__outputOptions['layerColorName'] = backupValue
991
992 originalLayer.setVisible(False)
993 groupLayer.setVisible(True)
994
995 for layer in groupLayer.childNodes():
996 layer.setBlendingMode('normal')
997 layer.setVisible(False)
998 tmpDocument.refreshProjection()
999
1000 index = 0
1001 for layer in groupLayer.childNodes():
1002 layer.setVisible(True)
1003 tmpDocument.refreshProjection()
1004
1005 lblPreview[index].setPixmap(QPixmap.fromImage(tmpDocument.projection(0, 0, tmpDocument.width(), tmpDocument.height())))
1006 lblPreviewLbl[index].setText("<i>{0}</i>".format(layer.name()))
1007 layer.setVisible(False)
1008
1009 index+=1
1010
1011 if index > 3:
1012 lblPreview[3].setVisible(True)
1013 lblPreviewLbl[3].setVisible(True)
1014 else:
1015 lblPreview[3].setVisible(False)
1016 lblPreviewLbl[3].setVisible(False)
1017
1018
1019 tmpDocument.close()
1020
1021 pbProgress.setVisible(False)
1022
1023
1024 # ----------------------------------------------------------------------
1025 # Create dialog box
1026 dlgMain = QDialog(Application.activeWindow().qwindow())
1027 dlgMain.setWindowTitle(PLUGIN_DIALOG_TITLE)
1028 # resizeable with minimum size
1029 dlgMain.setSizeGripEnabled(True)
1030 dlgMain.setMinimumSize(DOPT_MIN_WIDTH, DOPT_MIN_HEIGHT)
1031 dlgMain.setModal(True)
1032
1033 # ......................................................................
1034 # main dialog box, container
1035 vbxMainContainer = QVBoxLayout(dlgMain)
1036
1037 # main dialog box, current layer name
1038 lblLayerName = QLabel("{0} <b><i>{1}</i></b>".format(i18n("Processing layer"), self.__sourceLayer.name()))
1039 lblLayerName.setFixedHeight(30)
1040 vbxMainContainer.addWidget(lblLayerName)
1041
1042 # main dialog box, groupbox for layers options
1043 gbxLayersMgt = QGroupBox("Layers management")
1044 vbxMainContainer.addWidget(gbxLayersMgt)
1045
1046 # main dialog box, groupbox for output options
1047 gbxOutputResults = QGroupBox("Output results")
1048 vbxMainContainer.addWidget(gbxOutputResults)
1049
1050 vbxMainContainer.addStretch()
1051
1052 # main dialog box, OK/Cancel buttons
1053 dbbxOkCancel = QDialogButtonBox(dlgMain)
1054 dbbxOkCancel.setOrientation(Qt.Orientation.Horizontal)
1055 dbbxOkCancel.setStandardButtons(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
1056 dbbxOkCancel.accepted.connect(dlgMain.accept)
1057 dbbxOkCancel.rejected.connect(dlgMain.reject)
1058 vbxMainContainer.addWidget(dbbxOkCancel)
1059
1060 # ......................................................................
1061 # create layout for groupbox "Layers management"
1062 flLayersMgt = QFormLayout()
1063 gbxLayersMgt.setLayout(flLayersMgt)
1064
1065 ledLayerGroupName = QLineEdit()
1066 ledLayerGroupName.setText(self.__outputOptions['layerGroupName'])
1067 ledLayerGroupName.textChanged.connect(ledLayerGroupName_Changed)
1068 flLayersMgt.addRow(i18nc('The name for a new group layer; the generated layers will be placed in this group.', 'New layer group name'), ledLayerGroupName)
1069
1070 ledLayerColorName = QLineEdit()
1071 ledLayerColorName.setText(self.__outputOptions['layerColorName'])
1072 ledLayerColorName.textChanged.connect(ledLayerColorName_Changed)
1073 flLayersMgt.addRow(i18nc('Defines how the name for each layer created from the channel is generated.', 'New layers color name'), ledLayerColorName)
1074
1075 cmbOriginalLayerAction = QComboBox()
1076 cmbOriginalLayerAction.addItems([
1077 ORIGINAL_LAYER_KEEPUNCHANGED,
1078 ORIGINAL_LAYER_KEEPVISIBLE,
1079 ORIGINAL_LAYER_KEEPHIDDEN,
1080 ORIGINAL_LAYER_REMOVE
1081 ])
1082 cmbOriginalLayerAction.setCurrentText(self.__outputOptions['originalLayerAction'])
1083 cmbOriginalLayerAction.currentTextChanged.connect(cmbOriginalLayerAction_Changed)
1084 flLayersMgt.addRow(i18n("Original layer"), cmbOriginalLayerAction)
1085
1086 # ......................................................................
1087 # create layout for groupbox "Output results"
1088 flOutputResults = QFormLayout()
1089 gbxOutputResults.setLayout(flOutputResults)
1090
1091 cmbOutputMode = QComboBox()
1092 cmbOutputMode.addItems([
1093 OUTPUT_MODE_RGB,
1094 OUTPUT_MODE_CMY,
1095 OUTPUT_MODE_CMYK,
1096 OUTPUT_MODE_LRGB,
1097 OUTPUT_MODE_LCMY,
1098 OUTPUT_MODE_LCMYK
1099 ])
1100 cmbOutputMode.setCurrentText(self.__outputOptions['outputMode'])
1101 cmbOutputMode.currentTextChanged.connect(cmbOutputMode_Changed)
1102 flOutputResults.addRow(i18n("Mode"), cmbOutputMode)
1103
1104 vbxPreviewLblContainer = QHBoxLayout()
1105 flOutputResults.addRow('', vbxPreviewLblContainer)
1106 vbxPreviewContainer = QHBoxLayout()
1107 flOutputResults.addRow('', vbxPreviewContainer)
1108
1109 # add preview progressbar
1110 pbProgress = QProgressBar()
1111 pbProgress.setFixedHeight(8)
1112 pbProgress.setTextVisible(False)
1113 pbProgress.setVisible(False)
1114 pbProgress.setRange(0, 107)
1115 flOutputResults.addRow('', pbProgress)
1116
1117
1118 imageRatio = self.__sourceDocument.width() / self.__sourceDocument.height()
1119 rect = QRect(0, 0, OUTPUT_PREVIEW_MAXSIZE, OUTPUT_PREVIEW_MAXSIZE)
1120
1121 # always ensure that final preview width and/or height is lower or equal than OUTPUT_PREVIEW_MAXSIZE
1122 if imageRatio < 1:
1123 # width < height
1124 rect.setWidth(int(imageRatio * OUTPUT_PREVIEW_MAXSIZE))
1125 else:
1126 # width >= height
1127 rect.setHeight(int(OUTPUT_PREVIEW_MAXSIZE / imageRatio))
1128
1129 imgThumbSrc = self.toQImage(self.__sourceLayer, rect)
1130
1131 previewBaSrc.resize(imgThumbSrc.sizeInBytes())
1132 ptr = imgThumbSrc.bits()
1133 ptr.setsize(imgThumbSrc.sizeInBytes())
1134 previewBaSrc = QByteArray(ptr.asstring())
1135
1136
1137 lblPreviewSrc = QLabel()
1138 lblPreviewSrc.setPixmap(QPixmap.fromImage(imgThumbSrc))
1139 lblPreviewSrc.setFixedHeight(imgThumbSrc.height() + 4)
1140 lblPreviewSrc.setFixedWidth(imgThumbSrc.width() + 4)
1141 vbxPreviewContainer.addWidget(lblPreviewSrc)
1142
1143 lblPreviewLblSrc = QLabel(i18nc("the original layer", "Original"))
1144 lblPreviewLblSrc.setFixedWidth(imgThumbSrc.width() + 4)
1145 vbxPreviewLblContainer.addWidget(lblPreviewLblSrc)
1146
1147
1148 vbxPreviewLblContainer.addWidget(QLabel(" "))
1149 vbxPreviewContainer.addWidget(QLabel(">"))
1150
1151 lblPreview[3].setPixmap(QPixmap.fromImage(imgThumbSrc))
1152 lblPreview[3].setFixedHeight(imgThumbSrc.height() + 4)
1153 lblPreview[3].setFixedWidth(imgThumbSrc.width() + 4)
1154 vbxPreviewContainer.addWidget(lblPreview[3])
1155
1156 lblPreviewLbl[3] = QLabel(i18n("<i>Cyan</i>"))
1157 lblPreviewLbl[3].setIndent(10)
1158 lblPreviewLbl[3].setFixedWidth(imgThumbSrc.width() + 4)
1159 vbxPreviewLblContainer.addWidget(lblPreviewLbl[3])
1160
1161
1162 lblPreview[2] = QLabel()
1163 lblPreview[2].setPixmap(QPixmap.fromImage(imgThumbSrc))
1164 lblPreview[2].setFixedHeight(imgThumbSrc.height() + 4)
1165 lblPreview[2].setFixedWidth(imgThumbSrc.width() + 4)
1166 vbxPreviewContainer.addWidget(lblPreview[2])
1167
1168 lblPreviewLbl[2] = QLabel(i18n("<i>Magenta</i>"))
1169 lblPreviewLbl[2].setIndent(10)
1170 lblPreviewLbl[2].setFixedWidth(imgThumbSrc.width() + 4)
1171 vbxPreviewLblContainer.addWidget(lblPreviewLbl[2])
1172
1173
1174 lblPreview[1] = QLabel()
1175 lblPreview[1].setPixmap(QPixmap.fromImage(imgThumbSrc))
1176 lblPreview[1].setFixedHeight(imgThumbSrc.height() + 4)
1177 lblPreview[1].setFixedWidth(imgThumbSrc.width() + 4)
1178 vbxPreviewContainer.addWidget(lblPreview[1])
1179
1180 lblPreviewLbl[1] = QLabel(i18n("<i>Yellow</i>"))
1181 lblPreviewLbl[1].setIndent(10)
1182 lblPreviewLbl[1].setFixedWidth(imgThumbSrc.width() + 4)
1183 vbxPreviewLblContainer.addWidget(lblPreviewLbl[1])
1184
1185
1186 lblPreview[0] = QLabel()
1187 lblPreview[0].setPixmap(QPixmap.fromImage(imgThumbSrc))
1188 lblPreview[0].setFixedHeight(imgThumbSrc.height() + 4)
1189 lblPreview[0].setFixedWidth(imgThumbSrc.width() + 4)
1190 vbxPreviewContainer.addWidget(lblPreview[0])
1191
1192 lblPreviewLbl[0] = QLabel(i18n("<i>Black</i>"))
1193 lblPreviewLbl[0].setIndent(10)
1194 lblPreviewLbl[0].setFixedWidth(imgThumbSrc.width() + 4)
1195 vbxPreviewLblContainer.addWidget(lblPreviewLbl[0])
1196
1197
1198 vbxPreviewLblContainer.addStretch()
1199 vbxPreviewContainer.addStretch()
1200
1201 buildPreview()
1202
1203 returned = dlgMain.exec()
1204
1205 return returned
1206
1207
1208 def progressNext(self, pProgress):
1209 """Update progress bar"""
1210 if pProgress is not None:
1211 stepCurrent=pProgress.value()+1
1212 pProgress.setValue(stepCurrent)
1213 QApplication.instance().processEvents()
1214
1215
1216 def run(self):
1217 """Run process for current layer"""
1218
1219 pdlgProgress = QProgressDialog(self.__outputOptions['outputMode'], None, 0, 100, Application.activeWindow().qwindow())
1220 pdlgProgress.setWindowTitle(PLUGIN_DIALOG_TITLE)
1221 pdlgProgress.setMinimumSize(640, 200)
1222 pdlgProgress.setModal(True)
1223 pdlgProgress.show()
1224
1225 self.process(self.__sourceDocument, self.__sourceLayer, pdlgProgress)
1226
1227 pdlgProgress.close()
1228
1229
1230
1231
1232 def process(self, pDocument, pOriginalLayer, pProgress):
1233 """Process given layer with current options"""
1234
1235 self.layerNum = 0
1236 document = pDocument
1237 originalLayer = pOriginalLayer
1238 parentGroupLayer = None
1239 currentProcessedLayer = None
1240 originalLayerIsVisible = originalLayer.visible()
1241
1242
1243 def getLayerByName(parent, value):
1244 """search and return a layer by name, within given parent group"""
1245 if parent == None:
1246 return document.nodeByName(value)
1247
1248 for layer in parent.childNodes():
1249 if layer.name() == value:
1250 return layer
1251
1252 return None
1253
1254
1255 def duplicateLayer(currentProcessedLayer, value):
1256 """Duplicate layer from given name
1257 New layer become active layer
1258 """
1259
1260 newLayer = None
1261 srcLayer = None
1262 srcName = re.match("^@(.*)", value)
1263
1264 if not srcName is None:
1265 # reference to a specific layer
1266 if srcName[1] == 'original':
1267 # original layer currently processed
1268 srcLayer = originalLayer
1269 else:
1270 # a color layer previously built (and finished)
1271 srcLayer = getLayerByName(parentGroupLayer, parseLayerName(self.__outputOptions['layerColorName'], srcName[1]))
1272 else:
1273 # a layer with a fixed name
1274 srcLayer = document.nodeByName(parseLayerName(value, ''))
1275
1276
1277 if not srcLayer is None:
1278 newLayer = srcLayer.duplicate()
1279
1280 self.layerNum+=1
1281 newLayer.setName("c2l-w{0}".format(self.layerNum))
1282
1283 parentGroupLayer.addChildNode(newLayer, currentProcessedLayer)
1284 return newLayer
1285 else:
1286 return None
1287
1288
1289 def newLayer(currentProcessedLayer, value):
1290 """Create a new layer of given type
1291 New layer become active layer
1292 """
1293
1294 newLayer = None
1295
1296 if value is None or not value['type'] in ['filllayer']:
1297 # given type for new layer is not valid
1298 # currently only one layer type is implemented
1299 return None
1300
1301 if value['type'] == 'filllayer':
1302 infoObject = InfoObject();
1303 infoObject.setProperty("color", value['color'])
1304 selection = Selection();
1305 selection.select(0, 0, document.width(), document.height(), 255)
1306
1307 newLayer = document.createFillLayer(value['color'].name(), "color", infoObject, selection)
1308
1309
1310 if newLayer:
1311 self.layerNum+=1
1312 newLayer.setName("c2l-w{0}".format(self.layerNum))
1313
1314 parentGroupLayer.addChildNode(newLayer, currentProcessedLayer)
1315
1316 # Need to force generator otherwise, information provided when creating layer seems to not be taken in
1317 # account
1318 newLayer.setGenerator("color", infoObject)
1319
1320 return newLayer
1321 else:
1322 return None
1323
1324
1325 def mergeDown(currentProcessedLayer, value):
1326 """Merge current layer with layer below"""
1327 if currentProcessedLayer is None:
1328 return None
1329
1330 newLayer = currentProcessedLayer.mergeDown()
1331 # note:
1332 # when layer is merged down:
1333 # - a new layer seems to be created (reference to 'down' layer does not match anymore layer in group)
1334 # - retrieved 'newLayer' reference does not match to new layer resulting from merge
1335 # - activeNode() in document doesn't match to new layer resulting from merge
1336 # maybe it's norpmal, maybe not...
1337 # but the only solution to be able to work on merged layer (with current script) is to consider that from
1338 # parent node, last child match to last added layer and then, to our merged layer
1339 currentProcessedLayer = parentGroupLayer.childNodes()[-1]
1340 # for an unknown reason, merged layer bounds are not corrects... :'-(
1341 currentProcessedLayer.cropNode(0, 0, document.width(), document.height())
1342 return currentProcessedLayer
1343
1344
1345 def applyBlendingMode(currentProcessedLayer, value):
1346 """Set blending mode for current layer"""
1347 if currentProcessedLayer is None or value is None or value == '':
1348 return False
1349
1350 currentProcessedLayer.setBlendingMode(value)
1351 return True
1352
1353
1354 def applyFilter(currentProcessedLayer, value):
1355 """Apply filter to layer"""
1356 if currentProcessedLayer is None or value is None or value == '':
1357 return None
1358
1359 filterName = re.match("name=([^;]+)", value)
1360
1361 if filterName is None:
1362 return None
1363
1364 filter = Application.filter(filterName.group(1))
1365 filterConfiguration = filter.configuration()
1366
1367 for parameter in value.split(';'):
1368 parameterName = re.match("^([^=]+)=(.*)", parameter)
1369
1370 if not parameterName is None and parameterName != 'name':
1371 filterConfiguration.setProperty(parameterName.group(1), parameterName.group(2))
1372
1373 filter.setConfiguration(filterConfiguration)
1374 filter.apply(currentProcessedLayer, 0, 0, document.width(), document.height())
1375
1376 return currentProcessedLayer
1377
1378
1379 def parseLayerName(value, color):
1380 """Parse layer name"""
1381
1382 returned = value
1383
1384 returned = returned.replace("{source:name}", originalLayer.name())
1385 returned = returned.replace("{mode}", OUTPUT_MODE_NFO[self.__outputOptions['outputMode']]['groupLayerName'])
1386 returned = returned.replace("{color:short}", color)
1387 if color == "C":
1388 returned = returned.replace("{color:long}", i18n("Cyan"))
1389 elif color == "M":
1390 returned = returned.replace("{color:long}", i18n("Magenta"))
1391 elif color == "Y":
1392 returned = returned.replace("{color:long}", i18n("Yellow"))
1393 elif color == "K":
1394 returned = returned.replace("{color:long}", i18n("Black"))
1395 elif color == "R":
1396 returned = returned.replace("{color:long}", i18n("Red"))
1397 elif color == "G":
1398 returned = returned.replace("{color:long}", i18n("Green"))
1399 elif color == "B":
1400 returned = returned.replace("{color:long}", i18n("Blue"))
1401 else:
1402 returned = returned.replace("{color:long}", "")
1403
1404 return returned
1405
1406
1407 if document is None or originalLayer is None:
1408 # should not occurs, but...
1409 return None
1410
1411 if not pProgress is None:
1412 stepTotal = 4
1413 for layer in OUTPUT_MODE_NFO[self.__outputOptions['outputMode']]['layers']:
1414 stepTotal+=len(layer['process'])
1415
1416 pProgress.setRange(0, stepTotal)
1417
1418
1419 if originalLayerIsVisible == False:
1420 originalLayer.setVisible(True)
1421
1422 # ----------------------------------------------------------------------
1423 # Create new group layer
1424 parentGroupLayer = document.createGroupLayer(parseLayerName(self.__outputOptions['layerGroupName'], ''))
1425
1426 self.progressNext(pProgress)
1427
1428 currentProcessedLayer = None
1429
1430 for layer in OUTPUT_MODE_NFO[self.__outputOptions['outputMode']]['layers']:
1431 for process in layer['process']:
1432 if process['action'] == 'duplicate':
1433 currentProcessedLayer = duplicateLayer(currentProcessedLayer, process['value'])
1434 elif process['action'] == 'new':
1435 currentProcessedLayer = newLayer(currentProcessedLayer, process['value'])
1436 elif process['action'] == 'merge down':
1437 currentProcessedLayer = mergeDown(currentProcessedLayer, process['value'])
1438 pass
1439 elif process['action'] == 'blending mode':
1440 applyBlendingMode(currentProcessedLayer, process['value'])
1441 elif process['action'] == 'filter':
1442 applyFilter(currentProcessedLayer, process['value'])
1443
1444 self.progressNext(pProgress)
1445
1446 if not currentProcessedLayer is None:
1447 # rename currentProcessedLayer
1448 currentProcessedLayer.setName(parseLayerName(self.__outputOptions['layerColorName'], layer['color']))
1449
1450 document.rootNode().addChildNode(parentGroupLayer, originalLayer)
1451 self.progressNext(pProgress)
1452
1453 if self.__outputOptions['originalLayerAction'] == ORIGINAL_LAYER_KEEPVISIBLE:
1454 originalLayer.setVisible(True)
1455 elif self.__outputOptions['originalLayerAction'] == ORIGINAL_LAYER_KEEPHIDDEN:
1456 originalLayer.setVisible(False)
1457 elif self.__outputOptions['originalLayerAction'] == ORIGINAL_LAYER_REMOVE:
1458 originalLayer.remove()
1459 else:
1460 # ORIGINAL_LAYER_KEEPUNCHANGED
1461 originalLayer.setVisible(originalLayerIsVisible)
1462
1463
1464 self.progressNext(pProgress)
1465
1466 document.refreshProjection()
1467 self.progressNext(pProgress)
1468
1469 document.setActiveNode(parentGroupLayer)
1470
1471 return parentGroupLayer
1472
1473
1474#ChannelsToLayers(Krita.instance()).process(Application.activeDocument(), Application.activeDocument().activeNode(), None)
1475#ChannelsToLayers(Krita.instance()).action_triggered()