Changeset View
Changeset View
Standalone View
Standalone View
plugins/python/highpass/highpass.py
1 | ''' | 1 | # This script is licensed CC 0 1.0, so that you can learn from it. | ||
---|---|---|---|---|---|
2 | This script is licensed CC 0 1.0, so that you can learn from it. | | |||
3 | 2 | | |||
4 | ------ CC 0 1.0 --------------- | 3 | # ------ CC 0 1.0 --------------- | ||
5 | 4 | | |||
6 | The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. | 5 | # The person who associated a work with this deed has dedicated the | ||
6 | # work to the public domain by waiving all of his or her rights to the | ||||
7 | # work worldwide under copyright law, including all related and | ||||
8 | # neighboring rights, to the extent allowed by law. | ||||
7 | 9 | | |||
8 | You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. | 10 | # You can copy, modify, distribute and perform the work, even for | ||
11 | # commercial purposes, all without asking permission. | ||||
9 | 12 | | |||
10 | https://creativecommons.org/publicdomain/zero/1.0/legalcode | 13 | # https://creativecommons.org/publicdomain/zero/1.0/legalcode | ||
11 | ''' | 14 | | ||
12 | import sys | 15 | from PyQt5 import QtCore | ||
13 | from PyQt5.QtGui import * | 16 | from PyQt5.QtWidgets import ( | ||
14 | from PyQt5.QtWidgets import * | 17 | QCheckBox, | ||
15 | from krita import * | 18 | QComboBox, | ||
19 | QDialog, | ||||
20 | QDialogButtonBox, | ||||
21 | QFormLayout, | ||||
22 | QMessageBox, | ||||
23 | QSpinBox, | ||||
24 | QVBoxLayout, | ||||
25 | ) | ||||
26 | from krita import Extension | ||||
16 | 27 | | |||
17 | 28 | | |||
18 | class HighpassExtension(Extension): | 29 | class HighpassExtension(Extension): | ||
19 | 30 | | |||
20 | def __init__(self, parent): | 31 | def __init__(self, parent): | ||
21 | super().__init__(parent) | 32 | super().__init__(parent) | ||
22 | 33 | | |||
23 | def setup(self): | 34 | def setup(self): | ||
24 | pass | 35 | pass | ||
25 | 36 | | |||
26 | def createActions(self, window): | 37 | def createActions(self, window): | ||
27 | action = window.createAction("high_pass_filter", i18n("High Pass")) | 38 | action = window.createAction("high_pass_filter", i18n("High Pass")) | ||
28 | action.triggered.connect(self.showDialog) | 39 | action.triggered.connect(self.showDialog) | ||
29 | 40 | | |||
30 | def showDialog(self): | 41 | def showDialog(self): | ||
31 | doc = Application.activeDocument() | 42 | doc = Application.activeDocument() | ||
32 | if doc == None: | 43 | if doc is None: | ||
33 | QMessageBox.information(Application.activeWindow().qwindow(), i18n("High Pass Filter"), i18n("There is no active image.")) | 44 | QMessageBox.information( | ||
45 | Application.activeWindow().qwindow(), | ||||
46 | i18n("High Pass Filter"), | ||||
47 | i18n("There is no active image.")) | ||||
34 | return | 48 | return | ||
35 | 49 | | |||
36 | self.dialog = QDialog(Application.activeWindow().qwindow()) | 50 | self.dialog = QDialog(Application.activeWindow().qwindow()) | ||
37 | 51 | | |||
38 | self.intRadius = QSpinBox() | 52 | self.intRadius = QSpinBox() | ||
39 | self.intRadius.setValue(10) | 53 | self.intRadius.setValue(10) | ||
40 | self.intRadius.setRange(2, 200) | 54 | self.intRadius.setRange(2, 200) | ||
41 | 55 | | |||
42 | self.cmbMode = QComboBox() | 56 | self.cmbMode = QComboBox() | ||
43 | self.cmbMode.addItems(["Color", "Preserve DC", "Greyscale", "Greyscale, Apply Chroma", "Redrobes"]) | 57 | self.cmbMode.addItems( | ||
58 | ["Color", "Preserve DC", "Greyscale", | ||||
59 | "Greyscale, Apply Chroma", "Redrobes"] | ||||
60 | ) | ||||
44 | self.keepOriginal = QCheckBox(i18n("Keep original layer")) | 61 | self.keepOriginal = QCheckBox(i18n("Keep original layer")) | ||
45 | self.keepOriginal.setChecked(True) | 62 | self.keepOriginal.setChecked(True) | ||
46 | form = QFormLayout() | 63 | form = QFormLayout() | ||
47 | form.addRow(i18n("Filter radius:"), self.intRadius) | 64 | form.addRow(i18n("Filter radius:"), self.intRadius) | ||
48 | form.addRow(i18n("Mode:"), self.cmbMode) | 65 | form.addRow(i18n("Mode:"), self.cmbMode) | ||
49 | form.addRow("", self.keepOriginal) | 66 | form.addRow("", self.keepOriginal) | ||
50 | 67 | | |||
51 | self.buttonBox = QDialogButtonBox(self.dialog) | 68 | self.buttonBox = QDialogButtonBox(self.dialog) | ||
52 | self.buttonBox.setOrientation(QtCore.Qt.Horizontal) | 69 | self.buttonBox.setOrientation(QtCore.Qt.Horizontal) | ||
53 | self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) | 70 | self.buttonBox.setStandardButtons( | ||
71 | QDialogButtonBox.Ok | QDialogButtonBox.Cancel) | ||||
54 | self.buttonBox.accepted.connect(self.dialog.accept) | 72 | self.buttonBox.accepted.connect(self.dialog.accept) | ||
55 | self.buttonBox.accepted.connect(self.highpass) | 73 | self.buttonBox.accepted.connect(self.highpass) | ||
56 | self.buttonBox.rejected.connect(self.dialog.reject) | 74 | self.buttonBox.rejected.connect(self.dialog.reject) | ||
57 | 75 | | |||
58 | vbox = QVBoxLayout(self.dialog) | 76 | vbox = QVBoxLayout(self.dialog) | ||
59 | vbox.addLayout(form) | 77 | vbox.addLayout(form) | ||
60 | vbox.addWidget(self.buttonBox) | 78 | vbox.addWidget(self.buttonBox) | ||
61 | 79 | | |||
62 | self.dialog.show() | 80 | self.dialog.show() | ||
63 | self.dialog.activateWindow() | 81 | self.dialog.activateWindow() | ||
64 | self.dialog.exec_() | 82 | self.dialog.exec_() | ||
65 | 83 | | |||
66 | def highpass(self): | 84 | def highpass(self): | ||
67 | # XXX: Start undo macro | 85 | # XXX: Start undo macro | ||
68 | image = Application.activeDocument() | 86 | image = Application.activeDocument() | ||
69 | original = image.activeNode() | 87 | original = image.activeNode() | ||
70 | working_layer = original | 88 | working_layer = original | ||
71 | 89 | | |||
72 | # We can only highpass on paint layers | 90 | # We can only highpass on paint layers | ||
73 | if self.keepOriginal.isChecked() or original.type() != "paintlayer": | 91 | if self.keepOriginal.isChecked() or original.type() != "paintlayer": | ||
74 | working_layer = image.createNode("working", "paintlayer") | 92 | working_layer = image.createNode("working", "paintlayer") | ||
75 | working_layer.setColorSpace(original.colorModel(), original.colorSpace(), original.profile()) | 93 | working_layer.setColorSpace( | ||
76 | working_layer.writeBytes(original.readBytes(0, 0, image.width(), image.height()), | 94 | original.colorModel(), | ||
77 | 0, 0, image.width(), image.height()) | 95 | original.colorSpace(), | ||
78 | original.parentNode().addChildNode(working_layer, original) # XXX: Unimplemented | 96 | original.profile()) | ||
97 | working_layer.writeBytes( | ||||
98 | original.readBytes(0, 0, image.width(), image.height()), | ||||
99 | 0, 0, image.width(), image.height()) | ||||
100 | | ||||
101 | # XXX: Unimplemented: | ||||
102 | original.parentNode().addChildNode(working_layer, original) | ||||
79 | 103 | | |||
80 | image.setActiveNode(working_layer) | 104 | image.setActiveNode(working_layer) | ||
81 | colors_layer = None | 105 | colors_layer = None | ||
82 | 106 | | |||
83 | # if keeping colors | 107 | # if keeping colors | ||
84 | if self.cmbMode.currentIndex() == 1 or self.cmbMode.currentIndex() == 3: | 108 | if (self.cmbMode.currentIndex() == 1 | ||
85 | colors_layer = working_layer.duplicate() # XXX: Unimplemented | 109 | or self.cmbMode.currentIndex() == 3): | ||
110 | # XXX: Unimplemented: | ||||
111 | colors_layer = working_layer.duplicate() | ||||
86 | colors_layer.setName("colors") | 112 | colors_layer.setName("colors") | ||
87 | original.parentNode().addChildNode(working_layer, colors_layer) # XXX: Unimplemented | 113 | # XXX: Unimplemented: | ||
114 | original.parentNode().addChildNode(working_layer, colors_layer) | ||||
88 | 115 | | |||
89 | # if greyscale, desature | 116 | # if greyscale, desature | ||
90 | if (self.cmbMode.currentIndex() == 2 or self.cmbMode.currentIndex() == 3): | 117 | if (self.cmbMode.currentIndex() == 2 | ||
118 | or self.cmbMode.currentIndex() == 3): | ||||
91 | filter = Application.filter("desaturate") | 119 | filter = Application.filter("desaturate") | ||
92 | filter.apply(working_layer, 0, 0, image.width(), image.height()) | 120 | filter.apply(working_layer, 0, 0, image.width(), image.height()) | ||
93 | 121 | | |||
94 | # Duplicate on top and blur | 122 | # Duplicate on top and blur | ||
95 | blur_layer = working_layer.duplicate() | 123 | blur_layer = working_layer.duplicate() | ||
96 | blur_layer.setName("blur") | 124 | blur_layer.setName("blur") | ||
97 | original.parentNode().addChildNode(blur_layer, working_layer) # XXX: Unimplemented | 125 | # XXX: Unimplemented: | ||
126 | original.parentNode().addChildNode(blur_layer, working_layer) | ||||
98 | 127 | | |||
99 | # blur | 128 | # blur | ||
100 | filter = Application.filter("gaussian blur") | 129 | filter = Application.filter("gaussian blur") | ||
101 | filter_configuration = filter.configuration() | 130 | filter_configuration = filter.configuration() | ||
102 | filter_configuration.setProperty("horizRadius", self.intRadius.value()) | 131 | filter_configuration.setProperty("horizRadius", self.intRadius.value()) | ||
103 | filter_configuration.setProperty("vertRadius", self.intRadius.value()) | 132 | filter_configuration.setProperty("vertRadius", self.intRadius.value()) | ||
104 | filter_configuration.setProperty("lockAspect", true) | 133 | filter_configuration.setProperty("lockAspect", True) | ||
105 | filter.setConfiguration(filter_configuration) | 134 | filter.setConfiguration(filter_configuration) | ||
106 | filter.apply(blur_layer, 0, 0, image.width(), image.height()) | 135 | filter.apply(blur_layer, 0, 0, image.width(), image.height()) | ||
107 | 136 | | |||
108 | if self.cmbMode.currentIndex() <= 3: | 137 | if self.cmbMode.currentIndex() <= 3: | ||
109 | blur_layer.setBlendingMode("grain_extract") | 138 | blur_layer.setBlendingMode("grain_extract") | ||
110 | working_layer = image.mergeDown(blur_layer) | 139 | working_layer = image.mergeDown(blur_layer) | ||
111 | 140 | | |||
112 | # if preserve chroma, change set the mode to value and merge down with the layer we kept earlier. | 141 | # if preserve chroma, change set the mode to value and | ||
142 | # merge down with the layer we kept earlier. | ||||
113 | if self.cmbMode.currentIndex() == 3: | 143 | if self.cmbMode.currentIndex() == 3: | ||
114 | working_layer.setBlendingMode("value") | 144 | working_layer.setBlendingMode("value") | ||
115 | working_layer = image.mergeDown(working_layer) | 145 | working_layer = image.mergeDown(working_layer) | ||
116 | 146 | | |||
117 | # if preserve DC, change set the mode to overlay and merge down with the average colour of the layer we kept earlier. | 147 | # if preserve DC, change set the mode to overlay and merge | ||
148 | # down with the average colour of the layer we kept | ||||
149 | # earlier. | ||||
118 | if self.cmbMode.currentIndex() == 1: | 150 | if self.cmbMode.currentIndex() == 1: | ||
119 | # get the average color of the entire image | 151 | # get the average color of the entire image | ||
120 | # clear the colors layer to the given color | 152 | # clear the colors layer to the given color | ||
121 | working_layer = image.mergeDown(working_layer) | 153 | working_layer = image.mergeDown(working_layer) | ||
122 | 154 | | |||
123 | else: # Mode == 4, RedRobes | 155 | else: # Mode == 4, RedRobes | ||
124 | image.setActiveNode(blur_layer) | 156 | image.setActiveNode(blur_layer) | ||
125 | # Get the average color of the input layer | 157 | # Get the average color of the input layer | ||
126 | # copy the solid colour layer | 158 | # copy the solid colour layer | ||
127 | # copy the blurred layer | 159 | # copy the blurred layer | ||
128 | # XXX: End undo macro | 160 | # XXX: End undo macro | ||
129 | | ||||
130 | Scripter.addExtension(HighpassExtension(Krita.instance())) | |