shinestacker 0.4.0__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of shinestacker might be problematic. Click here for more details.
- shinestacker/_version.py +1 -1
- shinestacker/algorithms/align.py +4 -12
- shinestacker/algorithms/balance.py +11 -9
- shinestacker/algorithms/depth_map.py +0 -30
- shinestacker/algorithms/utils.py +10 -0
- shinestacker/algorithms/vignetting.py +116 -70
- shinestacker/app/about_dialog.py +101 -12
- shinestacker/app/gui_utils.py +1 -1
- shinestacker/app/help_menu.py +1 -1
- shinestacker/app/main.py +2 -2
- shinestacker/app/project.py +2 -2
- shinestacker/config/constants.py +4 -1
- shinestacker/config/gui_constants.py +10 -9
- shinestacker/gui/action_config.py +5 -561
- shinestacker/gui/action_config_dialog.py +567 -0
- shinestacker/gui/base_form_dialog.py +18 -0
- shinestacker/gui/colors.py +5 -6
- shinestacker/gui/gui_logging.py +0 -1
- shinestacker/gui/gui_run.py +54 -106
- shinestacker/gui/ico/shinestacker.icns +0 -0
- shinestacker/gui/ico/shinestacker.ico +0 -0
- shinestacker/gui/ico/shinestacker.png +0 -0
- shinestacker/gui/ico/shinestacker.svg +60 -0
- shinestacker/gui/main_window.py +276 -367
- shinestacker/gui/menu_manager.py +236 -0
- shinestacker/gui/new_project.py +75 -20
- shinestacker/gui/project_converter.py +6 -6
- shinestacker/gui/project_editor.py +248 -165
- shinestacker/gui/project_model.py +2 -7
- shinestacker/gui/tab_widget.py +81 -0
- shinestacker/gui/time_progress_bar.py +95 -0
- shinestacker/retouch/base_filter.py +173 -40
- shinestacker/retouch/brush_preview.py +0 -10
- shinestacker/retouch/brush_tool.py +25 -11
- shinestacker/retouch/denoise_filter.py +5 -44
- shinestacker/retouch/display_manager.py +57 -20
- shinestacker/retouch/exif_data.py +10 -13
- shinestacker/retouch/file_loader.py +1 -1
- shinestacker/retouch/filter_manager.py +1 -4
- shinestacker/retouch/image_editor_ui.py +365 -49
- shinestacker/retouch/image_viewer.py +34 -11
- shinestacker/retouch/io_gui_handler.py +96 -43
- shinestacker/retouch/io_manager.py +23 -7
- shinestacker/retouch/layer_collection.py +2 -0
- shinestacker/retouch/shortcuts_help.py +12 -0
- shinestacker/retouch/unsharp_mask_filter.py +10 -10
- shinestacker/retouch/vignetting_filter.py +69 -0
- shinestacker/retouch/white_balance_filter.py +46 -14
- {shinestacker-0.4.0.dist-info → shinestacker-1.0.0.dist-info}/METADATA +14 -2
- shinestacker-1.0.0.dist-info/RECORD +90 -0
- shinestacker/app/app_config.py +0 -22
- shinestacker/gui/actions_window.py +0 -258
- shinestacker/retouch/image_editor.py +0 -201
- shinestacker/retouch/image_filters.py +0 -69
- shinestacker-0.4.0.dist-info/RECORD +0 -87
- {shinestacker-0.4.0.dist-info → shinestacker-1.0.0.dist-info}/WHEEL +0 -0
- {shinestacker-0.4.0.dist-info → shinestacker-1.0.0.dist-info}/entry_points.txt +0 -0
- {shinestacker-0.4.0.dist-info → shinestacker-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-0.4.0.dist-info → shinestacker-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
shinestacker/__init__.py,sha256=uq2fjAw2z_6TpH3mOcWFZ98GoEPRsNhTAK8N0MMm_e8,448
|
|
2
|
+
shinestacker/_version.py,sha256=dHuoY6voK7np1A7oRzw1xyBy7CK9_KBAc4FCKW29uRQ,21
|
|
3
|
+
shinestacker/algorithms/__init__.py,sha256=c4kRrdTLlVI70Q16XkI1RSmz5MD7npDqIpO_02jTG6g,747
|
|
4
|
+
shinestacker/algorithms/align.py,sha256=XT4DJoD5ZvpkC1-J3W3GWmWRsXJg3qJ-3zr9erT8oW0,17514
|
|
5
|
+
shinestacker/algorithms/balance.py,sha256=iSjO-pl0vQv58iEQ077EUcDTAExMKDBdtXmJXbMhazk,16721
|
|
6
|
+
shinestacker/algorithms/base_stack_algo.py,sha256=AFV2QkcFNaTcnISpsWHuAVy2De9hhaPcBNjE1O0h50I,1430
|
|
7
|
+
shinestacker/algorithms/denoise.py,sha256=GL3Z4_6MHxSa7Wo4ZzQECZS87tHBFqO0sIVF_jPuYQU,426
|
|
8
|
+
shinestacker/algorithms/depth_map.py,sha256=FOR5M0brO5-9NnXDY7TWpc3OtKKSuzrOSoBMe0cP6Ho,6076
|
|
9
|
+
shinestacker/algorithms/exif.py,sha256=gY9s6Cd4g4swo5qEjSbzuVIvl1GImCYu6ytOO9WrV0I,9435
|
|
10
|
+
shinestacker/algorithms/multilayer.py,sha256=5JA6TW8oO_R3mu6cOvPno9by4md8q5sXUb8ZfsRRpmY,9259
|
|
11
|
+
shinestacker/algorithms/noise_detection.py,sha256=CDnN8pglxufY5Y-dT3mVooD4zPySdSq9CMgtDGMXBnA,8970
|
|
12
|
+
shinestacker/algorithms/pyramid.py,sha256=_Pk19lRQ21b3W3aHQ6DgAe9VVOfbsi2a9jrynF0qFVw,8610
|
|
13
|
+
shinestacker/algorithms/sharpen.py,sha256=h7PMJBYxucg194Usp_6pvItPUMFYbT-ebAc_-7XBFUw,949
|
|
14
|
+
shinestacker/algorithms/stack.py,sha256=FCU89Of-s6C_DuMleG06c8V6fnIm9MFInvkkKtTsGBo,4906
|
|
15
|
+
shinestacker/algorithms/stack_framework.py,sha256=frw7sbc9qOfVBYP3ZOFZEaIn9O27Wms8j_mxSW79uI0,12460
|
|
16
|
+
shinestacker/algorithms/utils.py,sha256=2XFa16Q8JVq4C2iXZFOvv98GVKqxceFG73yFZNHJSqI,2475
|
|
17
|
+
shinestacker/algorithms/vignetting.py,sha256=yW-1TF4tesLWfKQOS0XxRkOEN82U-YDmMaj09C9cH4M,9552
|
|
18
|
+
shinestacker/algorithms/white_balance.py,sha256=PMKsBtxOSn5aRr_Gkx1StHS4eN6kBN2EhNnhg4UG24g,501
|
|
19
|
+
shinestacker/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
+
shinestacker/app/about_dialog.py,sha256=pkH7nnxUP8yc0D3vRGd1jRb5cwi1nDVbQRk_OC9yLk8,4144
|
|
21
|
+
shinestacker/app/gui_utils.py,sha256=08TrCj2gFGsNsF6hG7ySO2y7wcQakM5PzERkeplqNFs,2344
|
|
22
|
+
shinestacker/app/help_menu.py,sha256=g8lKG_xZmXtNQaC3SIRzyROKVWva_PLEgZsQWh6zUcQ,499
|
|
23
|
+
shinestacker/app/main.py,sha256=geZwxShTDg8dDOnsGu9TvWMw2Wru6MaX9CvlGHMnRvQ,6474
|
|
24
|
+
shinestacker/app/open_frames.py,sha256=bsu32iJSYJQLe_tQQbvAU5DuMDVX6MRuNdE7B5lojZc,1488
|
|
25
|
+
shinestacker/app/project.py,sha256=W0u715LZne_PNJvg9msSy27ybIjgDXiEAQdJ7_6BjYI,2774
|
|
26
|
+
shinestacker/app/retouch.py,sha256=ZQ-nRKnHo6xurcP34RNqaAWkmuGBjJ5jE05hTQ_ycis,2482
|
|
27
|
+
shinestacker/config/__init__.py,sha256=aXxi-LmAvXd0daIFrVnTHE5OCaYeK1uf1BKMr7oaXQs,197
|
|
28
|
+
shinestacker/config/config.py,sha256=eBko2D3ADhLTIm9X6hB_a_WsIjwgfE-qmBVkhP1XSvc,1636
|
|
29
|
+
shinestacker/config/constants.py,sha256=l7RcRxXlIThkBXcy2GVRBWFZQRXNgqYShNEVfDpgjEU,6096
|
|
30
|
+
shinestacker/config/gui_constants.py,sha256=5DR-ET1oeMMD7lIsjvAwSuln89A7I9wy9VuAeRo2G64,2575
|
|
31
|
+
shinestacker/core/__init__.py,sha256=IUEIx6SQ3DygDEHN3_E6uKpHjHtUa4a_U_1dLd_8yEU,484
|
|
32
|
+
shinestacker/core/colors.py,sha256=kr_tJA1iRsdck2JaYDb2lS-codZ4Ty9gdu3kHfiWvuM,1340
|
|
33
|
+
shinestacker/core/core_utils.py,sha256=ulJhzen5McAb5n6wWNA_KB4U_PdTEr-H2TCQkVKUaOw,1421
|
|
34
|
+
shinestacker/core/exceptions.py,sha256=2-noG-ORAGdvDhL8jBQFs0xxZS4fI6UIkMqrWekgk2c,1618
|
|
35
|
+
shinestacker/core/framework.py,sha256=zCnJuQrHNpwEgJW23_BgS7iQrLolRWTAMB1oRp_a7Kk,7447
|
|
36
|
+
shinestacker/core/logging.py,sha256=9SuSSy9Usbh7zqmLYMqkmy-VBkOJW000lwqAR0XQs30,3067
|
|
37
|
+
shinestacker/gui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
+
shinestacker/gui/action_config.py,sha256=yXNDv0MyONbHk4iUrkvMkLKKaDvpJyzA5Yr0Eikgo0c,16986
|
|
39
|
+
shinestacker/gui/action_config_dialog.py,sha256=6Xjbtj7oHGXBNiogcnPoFHqcuTOnZFlMzWCZXv8eBAI,32623
|
|
40
|
+
shinestacker/gui/base_form_dialog.py,sha256=yYqMee1mzw9VBx8siBS0jDk1qqsTIKJUgdjh92aprQk,687
|
|
41
|
+
shinestacker/gui/colors.py,sha256=m0pQQ-uvtIN1xmb_-N06BvC7pZYZZnq59ZSEJwutHuk,1432
|
|
42
|
+
shinestacker/gui/gui_images.py,sha256=e0KAXSPruZoRHrajfdlmOKBYoRJJQBDan1jgs7YFltY,5678
|
|
43
|
+
shinestacker/gui/gui_logging.py,sha256=kiZcrC2AFYCWgPZo0O5SKw-E5cFrezwf4anS3HjPuNw,8168
|
|
44
|
+
shinestacker/gui/gui_run.py,sha256=jGrtXNT3Gn53qMPSU74aEY6PP7UQfc85SD3XIesWLuY,15147
|
|
45
|
+
shinestacker/gui/main_window.py,sha256=6zGFZkgBh3_UNWjCfNzciPeiCvBsZnjJiPy62U4ldAw,23988
|
|
46
|
+
shinestacker/gui/menu_manager.py,sha256=_L6LOikB3impEYqilqwXc0WJuunishjz57ozZlrBn7Q,9616
|
|
47
|
+
shinestacker/gui/new_project.py,sha256=zHmGrT27L7I6YHM1L8wjt7DzukLFPddFsbVyGVHfJoc,11004
|
|
48
|
+
shinestacker/gui/project_converter.py,sha256=_AFfU2HYKPX78l6iX6bXJrlKpdjSl63pmKzrc6kQpn8,7348
|
|
49
|
+
shinestacker/gui/project_editor.py,sha256=uouzmUkrqouQlq-dqPOgSO16r1WOnGNV2v8jTcZlRXU,23749
|
|
50
|
+
shinestacker/gui/project_model.py,sha256=eRUmH3QmRzDtPtZoxgT6amKzN8_5XzwjHgEJeL-_JOE,4263
|
|
51
|
+
shinestacker/gui/select_path_widget.py,sha256=OfQImOmkzbvl5BBshmb7ePWrSGDJQ8VvyaAOypHAGd4,1023
|
|
52
|
+
shinestacker/gui/tab_widget.py,sha256=6iUifK-wu0EzjVFccKHirhA2fENglVi6xREKiD96aaY,2950
|
|
53
|
+
shinestacker/gui/time_progress_bar.py,sha256=Ty7pNTfbKU44Y_0YQNYtgEcxpOD-Bbi4lC8g-u9bno0,3012
|
|
54
|
+
shinestacker/gui/ico/focus_stack_bkg.png,sha256=Q86TgqvKEi_IzKI8m6aZB2a3T40UkDtexf2PdeBM9XE,163151
|
|
55
|
+
shinestacker/gui/ico/shinestacker.icns,sha256=3IshIOv0uFexYsAEPkE9xiyuw8mB5X5gffekOUhFlt0,45278
|
|
56
|
+
shinestacker/gui/ico/shinestacker.ico,sha256=8IMRk-toObWUz8iDXA-zHBWQ8Ps3vXN5u5ZEyw7sP3c,109613
|
|
57
|
+
shinestacker/gui/ico/shinestacker.png,sha256=VybGY-nig_-wMW8g_uImGxRYvcnltWcAVEPSX6AZUHM,22448
|
|
58
|
+
shinestacker/gui/ico/shinestacker.svg,sha256=r8jx5aiIT9K70MRP0ANWniFE0ctRCqH7LMQ7vcGJ8ss,6571
|
|
59
|
+
shinestacker/gui/img/close-round-line-icon.png,sha256=9HZwCjgni1s_JGUPUb_MoOfoe4tRZgM5OWzk92XFZlE,8019
|
|
60
|
+
shinestacker/gui/img/forward-button-icon.png,sha256=lNw86T4TOEd_uokHYF8myGSGUXzdsHvmDAjlbE18Pgo,4788
|
|
61
|
+
shinestacker/gui/img/play-button-round-icon.png,sha256=9j6Ks9mOGa-2cXyRFpimepAAvSaHzqJKBfxShRb4_dE,4595
|
|
62
|
+
shinestacker/gui/img/plus-round-line-icon.png,sha256=LS068Hlu-CeBvJuB3dwwdJg1lZq6D5MUIv53lu1yKJA,7534
|
|
63
|
+
shinestacker/retouch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
64
|
+
shinestacker/retouch/base_filter.py,sha256=7UfuhZV3wEZefQbExVIHJ6IqKnXlMF2MelxswTMaqOo,10356
|
|
65
|
+
shinestacker/retouch/brush.py,sha256=dzD2FzSpBIPdJRmTZobcrQ1FrVd3tF__ZPnUplNE72s,357
|
|
66
|
+
shinestacker/retouch/brush_gradient.py,sha256=F5SFhyzl8YTMqjJU3jK8BrIlLCYLUvITd5wz3cQE4xk,1453
|
|
67
|
+
shinestacker/retouch/brush_preview.py,sha256=QKD3pL7n7YJbIibinUFYKv7lkyq_AWLpt6oyqnltrwQ,4893
|
|
68
|
+
shinestacker/retouch/brush_tool.py,sha256=nxnEuvTioPNw1WeWsT20X1zl-LNZ8i-1ExOcihikEjk,8618
|
|
69
|
+
shinestacker/retouch/denoise_filter.py,sha256=TDUHzhRKlKvCa3D5SCYCZKTpjcl81kGwmONsgSDtO1k,440
|
|
70
|
+
shinestacker/retouch/display_manager.py,sha256=XPbOBmoYc_jNA791WkWkOSaFHb0ztCZechl2p2KSlwQ,9597
|
|
71
|
+
shinestacker/retouch/exif_data.py,sha256=giqoIaMlhN6H3x8BAd73ghVHODmWIcD_QbSoDymQycU,1864
|
|
72
|
+
shinestacker/retouch/file_loader.py,sha256=x8lzKShlgElcJYLFiDoeszeVEToUUiUbUrozAeF5SMU,4812
|
|
73
|
+
shinestacker/retouch/filter_manager.py,sha256=SdYIZkZBUvuB6wDG0moGWav5sfEvIcB9ioUJR5wJFts,388
|
|
74
|
+
shinestacker/retouch/icon_container.py,sha256=6gw1HO1bC2FrdB4dc_iH81DQuLjzuvRGksZ2hKLT9yA,585
|
|
75
|
+
shinestacker/retouch/image_editor_ui.py,sha256=GNozK_P7orgl8EioemJeyR4e_LLMbKVZi4xhYW9m38U,29893
|
|
76
|
+
shinestacker/retouch/image_viewer.py,sha256=3ebrLHTDtGd_EbIT2nNFRUjH836rblmmK7jZ62YcJ2U,19564
|
|
77
|
+
shinestacker/retouch/io_gui_handler.py,sha256=M9rlfqiIaP1wWCm6p-Ew15w0_pH8-DoBXJdEkTUeVCU,11525
|
|
78
|
+
shinestacker/retouch/io_manager.py,sha256=JUAA--AK0mVa1PTErJTnBFjaXIle5Qs7Ow0Wkd8at0o,2437
|
|
79
|
+
shinestacker/retouch/layer_collection.py,sha256=Q7zoCYRn__jYkfrEC2lY1uKHWfOUbsJ27xaYHIoKVxo,5730
|
|
80
|
+
shinestacker/retouch/shortcuts_help.py,sha256=SN4vNa_6yRAFaWxt5HpWn8FHgwmHrIs_wYwjl4iyDmg,3769
|
|
81
|
+
shinestacker/retouch/undo_manager.py,sha256=_ekbcOLcPbQLY7t-o8wf-b1uA6OPY9rRyLM-KqMlQRo,3257
|
|
82
|
+
shinestacker/retouch/unsharp_mask_filter.py,sha256=uFnth8fpZFGhdIgJCnS8x5v6lBQgJ3hX0CBke9pFXeM,3510
|
|
83
|
+
shinestacker/retouch/vignetting_filter.py,sha256=3WuoF38lQOIaU1MWmqviItuQn8NnbMN0nwV7pM9IJqU,3453
|
|
84
|
+
shinestacker/retouch/white_balance_filter.py,sha256=glMBYlmrF-i_OrB3sGUpjZE6X4FQdyLC4GBy2bWtaFc,6056
|
|
85
|
+
shinestacker-1.0.0.dist-info/licenses/LICENSE,sha256=pWgb-bBdsU2Gd2kwAXxketnm5W_2u8_fIeWEgojfrxs,7651
|
|
86
|
+
shinestacker-1.0.0.dist-info/METADATA,sha256=600w-Vjn2cDAvudN_Qt4nN3qkxJLmzgNo0a5j6-Hz3E,5957
|
|
87
|
+
shinestacker-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
88
|
+
shinestacker-1.0.0.dist-info/entry_points.txt,sha256=SY6g1LqtMmp23q1DGwLUDT_dhLX9iss8DvWkiWLyo_4,166
|
|
89
|
+
shinestacker-1.0.0.dist-info/top_level.txt,sha256=MhijwnBVX5psfsyX8JZjqp3SYiWPsKe69f3Gnyze4Fw,13
|
|
90
|
+
shinestacker-1.0.0.dist-info/RECORD,,
|
shinestacker/app/app_config.py
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# pylint: disable=C0114, C0115, C0116, C0103, W0201
|
|
2
|
-
from .. config.config import _ConfigBase
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class _AppConfig(_ConfigBase):
|
|
6
|
-
def __new__(cls):
|
|
7
|
-
return _ConfigBase.__new__(cls)
|
|
8
|
-
|
|
9
|
-
def _init_defaults(self):
|
|
10
|
-
self._DONT_USE_NATIVE_MENU = True
|
|
11
|
-
self._COMBINED_APP = False
|
|
12
|
-
|
|
13
|
-
@property
|
|
14
|
-
def DONT_USE_NATIVE_MENU(self):
|
|
15
|
-
return self._DONT_USE_NATIVE_MENU
|
|
16
|
-
|
|
17
|
-
@property
|
|
18
|
-
def COMBINED_APP(self):
|
|
19
|
-
return self._COMBINED_APP
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
app_config = _AppConfig()
|
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
# pylint: disable=C0114, C0115, C0116, E0611, R0914, R0912, R0915, W0718
|
|
2
|
-
import os.path
|
|
3
|
-
import os
|
|
4
|
-
# import traceback
|
|
5
|
-
import json
|
|
6
|
-
import jsonpickle
|
|
7
|
-
from PySide6.QtWidgets import QMessageBox, QFileDialog, QDialog
|
|
8
|
-
from .. core.core_utils import get_app_base_path
|
|
9
|
-
from .. config.constants import constants
|
|
10
|
-
from .project_model import ActionConfig
|
|
11
|
-
from .project_editor import ProjectEditor
|
|
12
|
-
from .new_project import NewProjectDialog
|
|
13
|
-
from .project_model import Project
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class ActionsWindow(ProjectEditor):
|
|
17
|
-
def __init__(self):
|
|
18
|
-
super().__init__()
|
|
19
|
-
self.update_title()
|
|
20
|
-
|
|
21
|
-
def update_title(self):
|
|
22
|
-
title = constants.APP_TITLE
|
|
23
|
-
file_name = self.current_file_name()
|
|
24
|
-
if file_name:
|
|
25
|
-
title += f" - {file_name}"
|
|
26
|
-
if self._modified_project:
|
|
27
|
-
title += " *"
|
|
28
|
-
self.window().setWindowTitle(title)
|
|
29
|
-
|
|
30
|
-
def mark_as_modified(self):
|
|
31
|
-
self._modified_project = True
|
|
32
|
-
self.project_buffer.append(self.project.clone())
|
|
33
|
-
self.update_title()
|
|
34
|
-
|
|
35
|
-
def close_project(self):
|
|
36
|
-
if self._check_unsaved_changes():
|
|
37
|
-
self.set_project(Project())
|
|
38
|
-
self.set_current_file_path('')
|
|
39
|
-
self.update_title()
|
|
40
|
-
self.job_list.clear()
|
|
41
|
-
self.action_list.clear()
|
|
42
|
-
self._modified_project = False
|
|
43
|
-
|
|
44
|
-
def new_project(self):
|
|
45
|
-
if not self._check_unsaved_changes():
|
|
46
|
-
return
|
|
47
|
-
os.chdir(get_app_base_path())
|
|
48
|
-
self.set_current_file_path('')
|
|
49
|
-
self._modified_project = False
|
|
50
|
-
self.update_title()
|
|
51
|
-
self.job_list.clear()
|
|
52
|
-
self.action_list.clear()
|
|
53
|
-
self.set_project(Project())
|
|
54
|
-
dialog = NewProjectDialog(self)
|
|
55
|
-
if dialog.exec() == QDialog.Accepted:
|
|
56
|
-
input_folder = dialog.get_input_folder().split('/')
|
|
57
|
-
working_path = '/'.join(input_folder[:-1])
|
|
58
|
-
input_path = input_folder[-1]
|
|
59
|
-
if dialog.get_noise_detection():
|
|
60
|
-
job_noise = ActionConfig(constants.ACTION_JOB,
|
|
61
|
-
{'name': 'detect-noise', 'working_path': working_path,
|
|
62
|
-
'input_path': input_path})
|
|
63
|
-
noise_detection = ActionConfig(constants.ACTION_NOISEDETECTION,
|
|
64
|
-
{'name': 'detect-noise'})
|
|
65
|
-
job_noise.add_sub_action(noise_detection)
|
|
66
|
-
self.project.jobs.append(job_noise)
|
|
67
|
-
job = ActionConfig(constants.ACTION_JOB,
|
|
68
|
-
{'name': 'focus-stack', 'working_path': working_path,
|
|
69
|
-
'input_path': input_path})
|
|
70
|
-
if dialog.get_noise_detection() or dialog.get_vignetting_correction() or \
|
|
71
|
-
dialog.get_align_frames() or dialog.get_balance_frames():
|
|
72
|
-
combo_action = ActionConfig(constants.ACTION_COMBO, {'name': 'align'})
|
|
73
|
-
if dialog.get_noise_detection():
|
|
74
|
-
mask_noise = ActionConfig(constants.ACTION_MASKNOISE, {'name': 'mask-noise'})
|
|
75
|
-
combo_action.add_sub_action(mask_noise)
|
|
76
|
-
if dialog.get_vignetting_correction():
|
|
77
|
-
vignetting = ActionConfig(constants.ACTION_VIGNETTING, {'name': 'vignetting'})
|
|
78
|
-
combo_action.add_sub_action(vignetting)
|
|
79
|
-
if dialog.get_align_frames():
|
|
80
|
-
align = ActionConfig(constants.ACTION_ALIGNFRAMES, {'name': 'align'})
|
|
81
|
-
combo_action.add_sub_action(align)
|
|
82
|
-
if dialog.get_balance_frames():
|
|
83
|
-
balance = ActionConfig(constants.ACTION_BALANCEFRAMES, {'name': 'balance'})
|
|
84
|
-
combo_action.add_sub_action(balance)
|
|
85
|
-
job.add_sub_action(combo_action)
|
|
86
|
-
if dialog.get_bunch_stack():
|
|
87
|
-
bunch_stack = ActionConfig(constants.ACTION_FOCUSSTACKBUNCH,
|
|
88
|
-
{'name': 'bunches', 'frames': dialog.get_bunch_frames(),
|
|
89
|
-
'overlap': dialog.get_bunch_overlap()})
|
|
90
|
-
job.add_sub_action(bunch_stack)
|
|
91
|
-
if dialog.get_focus_stack_pyramid():
|
|
92
|
-
focus_pyramid = ActionConfig(constants.ACTION_FOCUSSTACK,
|
|
93
|
-
{'name': 'focus-stack-pyramid',
|
|
94
|
-
'stacker': constants.STACK_ALGO_PYRAMID})
|
|
95
|
-
job.add_sub_action(focus_pyramid)
|
|
96
|
-
if dialog.get_focus_stack_depth_map():
|
|
97
|
-
focus_depth_map = ActionConfig(constants.ACTION_FOCUSSTACK,
|
|
98
|
-
{'name': 'focus-stack-depth-map',
|
|
99
|
-
'stacker': constants.STACK_ALGO_DEPTH_MAP})
|
|
100
|
-
job.add_sub_action(focus_depth_map)
|
|
101
|
-
if dialog.get_multi_layer():
|
|
102
|
-
input_path = []
|
|
103
|
-
if dialog.get_focus_stack_pyramid():
|
|
104
|
-
input_path.append("focus-stack-pyramid")
|
|
105
|
-
if dialog.get_focus_stack_depth_map():
|
|
106
|
-
input_path.append("focus-stack-depth-map")
|
|
107
|
-
if dialog.get_bunch_stack():
|
|
108
|
-
input_path.append("bunches")
|
|
109
|
-
else:
|
|
110
|
-
input_path.append(input_path)
|
|
111
|
-
multi_layer = ActionConfig(constants.ACTION_MULTILAYER,
|
|
112
|
-
{'name': 'multi-layer',
|
|
113
|
-
'input_path': ','.join(input_path)})
|
|
114
|
-
job.add_sub_action(multi_layer)
|
|
115
|
-
self.project.jobs.append(job)
|
|
116
|
-
self._modified_project = True
|
|
117
|
-
self.refresh_ui(0, -1)
|
|
118
|
-
|
|
119
|
-
def open_project(self, file_path=False):
|
|
120
|
-
if not self._check_unsaved_changes():
|
|
121
|
-
return
|
|
122
|
-
if file_path is False:
|
|
123
|
-
file_path, _ = QFileDialog.getOpenFileName(
|
|
124
|
-
self, "Open Project", "", "Project Files (*.fsp);;All Files (*)")
|
|
125
|
-
if file_path:
|
|
126
|
-
try:
|
|
127
|
-
self.set_current_file_path(file_path)
|
|
128
|
-
with open(self.current_file_path(), 'r', encoding="utf-8") as file:
|
|
129
|
-
json_obj = json.load(file)
|
|
130
|
-
project = Project.from_dict(json_obj['project'])
|
|
131
|
-
if project is None:
|
|
132
|
-
raise RuntimeError(f"Project from file {file_path} produced a null project.")
|
|
133
|
-
self.set_project(project)
|
|
134
|
-
self._modified_project = False
|
|
135
|
-
self.update_title()
|
|
136
|
-
self.refresh_ui(0, -1)
|
|
137
|
-
if self.job_list.count() > 0:
|
|
138
|
-
self.job_list.setCurrentRow(0)
|
|
139
|
-
except Exception as e:
|
|
140
|
-
# traceback.print_tb(e.__traceback__)
|
|
141
|
-
QMessageBox.critical(self, "Error", f"Cannot open file {file_path}:\n{str(e)}")
|
|
142
|
-
if len(self.project.jobs) > 0:
|
|
143
|
-
self.job_list.setCurrentRow(0)
|
|
144
|
-
self.activateWindow()
|
|
145
|
-
for job in self.project.jobs:
|
|
146
|
-
if 'working_path' in job.params.keys():
|
|
147
|
-
working_path = job.params['working_path']
|
|
148
|
-
if not os.path.isdir(working_path):
|
|
149
|
-
QMessageBox.warning(
|
|
150
|
-
self, "Working path not found",
|
|
151
|
-
f'''The working path specified in the project file for the job:
|
|
152
|
-
"{job.params['name']}"
|
|
153
|
-
was not found.\n
|
|
154
|
-
Please, select a valid working path.''')
|
|
155
|
-
self.edit_action(job)
|
|
156
|
-
for action in job.sub_actions:
|
|
157
|
-
if 'working_path' in job.params.keys():
|
|
158
|
-
working_path = job.params['working_path']
|
|
159
|
-
if working_path != '' and not os.path.isdir(working_path):
|
|
160
|
-
QMessageBox.warning(
|
|
161
|
-
self, "Working path not found",
|
|
162
|
-
f'''The working path specified in the project file for the job:
|
|
163
|
-
"{job.params['name']}"
|
|
164
|
-
was not found.\n
|
|
165
|
-
Please, select a valid working path.''')
|
|
166
|
-
self.edit_action(action)
|
|
167
|
-
|
|
168
|
-
def save_project(self):
|
|
169
|
-
path = self.current_file_path()
|
|
170
|
-
if path:
|
|
171
|
-
self.do_save(path)
|
|
172
|
-
else:
|
|
173
|
-
self.save_project_as()
|
|
174
|
-
|
|
175
|
-
def save_project_as(self):
|
|
176
|
-
file_path, _ = QFileDialog.getSaveFileName(
|
|
177
|
-
self, "Save Project As", "", "Project Files (*.fsp);;All Files (*)")
|
|
178
|
-
if file_path:
|
|
179
|
-
if not file_path.endswith('.fsp'):
|
|
180
|
-
file_path += '.fsp'
|
|
181
|
-
self.do_save(file_path)
|
|
182
|
-
self.set_current_file_path(file_path)
|
|
183
|
-
self._modified_project = False
|
|
184
|
-
self.update_title()
|
|
185
|
-
os.chdir(os.path.dirname(file_path))
|
|
186
|
-
|
|
187
|
-
def do_save(self, file_path):
|
|
188
|
-
try:
|
|
189
|
-
json_obj = jsonpickle.encode({
|
|
190
|
-
'project': self.project.to_dict(),
|
|
191
|
-
'version': 1
|
|
192
|
-
})
|
|
193
|
-
with open(file_path, 'w', encoding="utf-8") as f:
|
|
194
|
-
f.write(json_obj)
|
|
195
|
-
self._modified_project = False
|
|
196
|
-
except Exception as e:
|
|
197
|
-
QMessageBox.critical(self, "Error", f"Cannot save file:\n{str(e)}")
|
|
198
|
-
|
|
199
|
-
def _check_unsaved_changes(self) -> bool:
|
|
200
|
-
if self._modified_project:
|
|
201
|
-
reply = QMessageBox.question(
|
|
202
|
-
self, "Unsaved Changes",
|
|
203
|
-
"The project has unsaved changes. Do you want to continue?",
|
|
204
|
-
QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel
|
|
205
|
-
)
|
|
206
|
-
if reply == QMessageBox.Save:
|
|
207
|
-
self.save_project()
|
|
208
|
-
return True
|
|
209
|
-
return reply == QMessageBox.Discard
|
|
210
|
-
return True
|
|
211
|
-
|
|
212
|
-
def on_job_edit(self, item):
|
|
213
|
-
index = self.job_list.row(item)
|
|
214
|
-
if 0 <= index < len(self.project.jobs):
|
|
215
|
-
job = self.project.jobs[index]
|
|
216
|
-
dialog = self.action_config_dialog(job)
|
|
217
|
-
if dialog.exec() == QDialog.Accepted:
|
|
218
|
-
current_row = self.job_list.currentRow()
|
|
219
|
-
if current_row >= 0:
|
|
220
|
-
self.job_list.item(current_row).setText(job.params['name'])
|
|
221
|
-
self.refresh_ui()
|
|
222
|
-
|
|
223
|
-
def on_action_edit(self, item):
|
|
224
|
-
job_index = self.job_list.currentRow()
|
|
225
|
-
if 0 <= job_index < len(self.project.jobs):
|
|
226
|
-
job = self.project.jobs[job_index]
|
|
227
|
-
action_index = self.action_list.row(item)
|
|
228
|
-
current_action, is_sub_action = self.get_current_action_at(job, action_index)
|
|
229
|
-
if current_action:
|
|
230
|
-
if not is_sub_action:
|
|
231
|
-
self.set_enabled_sub_actions_gui(
|
|
232
|
-
current_action.type_name == constants.ACTION_COMBO)
|
|
233
|
-
dialog = self.action_config_dialog(current_action)
|
|
234
|
-
if dialog.exec() == QDialog.Accepted:
|
|
235
|
-
self.on_job_selected(job_index)
|
|
236
|
-
self.refresh_ui()
|
|
237
|
-
self.job_list.setCurrentRow(job_index)
|
|
238
|
-
self.action_list.setCurrentRow(action_index)
|
|
239
|
-
|
|
240
|
-
def edit_current_action(self):
|
|
241
|
-
current_action = None
|
|
242
|
-
job_row = self.job_list.currentRow()
|
|
243
|
-
if 0 <= job_row < len(self.project.jobs):
|
|
244
|
-
job = self.project.jobs[job_row]
|
|
245
|
-
if self.job_list.hasFocus():
|
|
246
|
-
current_action = job
|
|
247
|
-
elif self.action_list.hasFocus():
|
|
248
|
-
job_row, _action_row, pos = self.get_current_action()
|
|
249
|
-
if pos.actions is not None:
|
|
250
|
-
current_action = pos.action if not pos.is_sub_action else pos.sub_action
|
|
251
|
-
if current_action is not None:
|
|
252
|
-
self.edit_action(current_action)
|
|
253
|
-
|
|
254
|
-
def edit_action(self, action):
|
|
255
|
-
dialog = self.action_config_dialog(action)
|
|
256
|
-
if dialog.exec() == QDialog.Accepted:
|
|
257
|
-
self.on_job_selected(self.job_list.currentRow())
|
|
258
|
-
self.mark_as_modified()
|
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
# pylint: disable=C0114, C0115, C0116, E0611, R0902
|
|
2
|
-
from PySide6.QtWidgets import QMainWindow, QMessageBox, QAbstractItemView
|
|
3
|
-
from .. config.constants import constants
|
|
4
|
-
from .undo_manager import UndoManager
|
|
5
|
-
from .layer_collection import LayerCollection
|
|
6
|
-
from .io_gui_handler import IOGuiHandler
|
|
7
|
-
from .display_manager import DisplayManager
|
|
8
|
-
from .brush_tool import BrushTool
|
|
9
|
-
from .layer_collection import LayerCollectionHandler
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class ImageEditor(QMainWindow, LayerCollectionHandler):
|
|
13
|
-
def __init__(self):
|
|
14
|
-
QMainWindow.__init__(self)
|
|
15
|
-
LayerCollectionHandler.__init__(self, LayerCollection())
|
|
16
|
-
self.undo_manager = UndoManager()
|
|
17
|
-
self.undo_action = None
|
|
18
|
-
self.redo_action = None
|
|
19
|
-
self.undo_manager.stack_changed.connect(self.update_undo_redo_actions)
|
|
20
|
-
self.io_gui_handler = None
|
|
21
|
-
self.display_manager = None
|
|
22
|
-
self.brush_tool = BrushTool()
|
|
23
|
-
self.modified = False
|
|
24
|
-
self.installEventFilter(self)
|
|
25
|
-
self.mask_layer = None
|
|
26
|
-
|
|
27
|
-
def setup_ui(self):
|
|
28
|
-
self.display_manager = DisplayManager(
|
|
29
|
-
self.layer_collection, self.image_viewer,
|
|
30
|
-
self.master_thumbnail_label, self.thumbnail_list, parent=self)
|
|
31
|
-
self.io_gui_handler = IOGuiHandler(self.layer_collection, self.undo_manager, parent=self)
|
|
32
|
-
self.display_manager.status_message_requested.connect(self.show_status_message)
|
|
33
|
-
self.display_manager.cursor_preview_state_changed.connect(
|
|
34
|
-
lambda state: setattr(self.image_viewer, 'allow_cursor_preview', state))
|
|
35
|
-
self.io_gui_handler.status_message_requested.connect(self.show_status_message)
|
|
36
|
-
self.io_gui_handler.update_title_requested.connect(self.update_title)
|
|
37
|
-
self.brush_tool.setup_ui(self.brush, self.brush_preview, self.image_viewer,
|
|
38
|
-
self.brush_size_slider, self.hardness_slider, self.opacity_slider,
|
|
39
|
-
self.flow_slider)
|
|
40
|
-
self.image_viewer.brush = self.brush_tool.brush
|
|
41
|
-
self.brush_tool.update_brush_thumb()
|
|
42
|
-
self.io_gui_handler.setup_ui(self.display_manager, self.image_viewer)
|
|
43
|
-
self.image_viewer.display_manager = self.display_manager
|
|
44
|
-
|
|
45
|
-
def show_status_message(self, message):
|
|
46
|
-
self.statusBar().showMessage(message)
|
|
47
|
-
|
|
48
|
-
# pylint: disable=C0103
|
|
49
|
-
def keyPressEvent(self, event):
|
|
50
|
-
if self.image_viewer.empty:
|
|
51
|
-
return
|
|
52
|
-
if event.text() == '[':
|
|
53
|
-
self.brush_tool.decrease_brush_size()
|
|
54
|
-
return
|
|
55
|
-
if event.text() == ']':
|
|
56
|
-
self.brush_tool.increase_brush_size()
|
|
57
|
-
return
|
|
58
|
-
if event.text() == '{':
|
|
59
|
-
self.brush_tool.decrease_brush_hardness()
|
|
60
|
-
return
|
|
61
|
-
if event.text() == '}':
|
|
62
|
-
self.brush_tool.increase_brush_hardness()
|
|
63
|
-
return
|
|
64
|
-
super().keyPressEvent(event)
|
|
65
|
-
# pylint: enable=C0103
|
|
66
|
-
|
|
67
|
-
def check_unsaved_changes(self) -> bool:
|
|
68
|
-
if self.modified:
|
|
69
|
-
reply = QMessageBox.question(
|
|
70
|
-
self, "Unsaved Changes",
|
|
71
|
-
"The image stack has unsaved changes. Do you want to continue?",
|
|
72
|
-
QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel
|
|
73
|
-
)
|
|
74
|
-
if reply == QMessageBox.Save:
|
|
75
|
-
self.save_file()
|
|
76
|
-
return True
|
|
77
|
-
if reply == QMessageBox.Discard:
|
|
78
|
-
return True
|
|
79
|
-
return False
|
|
80
|
-
return True
|
|
81
|
-
|
|
82
|
-
def sort_layers(self, order):
|
|
83
|
-
self.sort_layers(order)
|
|
84
|
-
self.display_manager.update_thumbnails()
|
|
85
|
-
self.change_layer(self.current_layer())
|
|
86
|
-
|
|
87
|
-
def update_title(self):
|
|
88
|
-
title = constants.APP_TITLE
|
|
89
|
-
if self.io_gui_handler is not None:
|
|
90
|
-
path = self.io_gui_handler.current_file_path
|
|
91
|
-
if path != '':
|
|
92
|
-
title += f" - {path.split('/')[-1]}"
|
|
93
|
-
if self.modified:
|
|
94
|
-
title += " *"
|
|
95
|
-
self.window().setWindowTitle(title)
|
|
96
|
-
|
|
97
|
-
def mark_as_modified(self):
|
|
98
|
-
self.modified = True
|
|
99
|
-
self.update_title()
|
|
100
|
-
|
|
101
|
-
def change_layer(self, layer_idx):
|
|
102
|
-
if 0 <= layer_idx < self.number_of_layers():
|
|
103
|
-
view_state = self.image_viewer.get_view_state()
|
|
104
|
-
self.set_current_layer_idx(layer_idx)
|
|
105
|
-
self.display_manager.display_current_view()
|
|
106
|
-
self.image_viewer.set_view_state(view_state)
|
|
107
|
-
self.thumbnail_list.setCurrentRow(layer_idx)
|
|
108
|
-
self.thumbnail_list.setFocus()
|
|
109
|
-
self.image_viewer.update_brush_cursor()
|
|
110
|
-
self.image_viewer.setFocus()
|
|
111
|
-
|
|
112
|
-
def prev_layer(self):
|
|
113
|
-
if self.layer_stack() is not None:
|
|
114
|
-
new_idx = max(0, self.current_layer_idx() - 1)
|
|
115
|
-
if new_idx != self.current_layer_idx():
|
|
116
|
-
self.change_layer(new_idx)
|
|
117
|
-
self.highlight_thumbnail(new_idx)
|
|
118
|
-
|
|
119
|
-
def next_layer(self):
|
|
120
|
-
if self.layer_stack() is not None:
|
|
121
|
-
new_idx = min(self.number_of_layers() - 1, self.current_layer_idx() + 1)
|
|
122
|
-
if new_idx != self.current_layer_idx():
|
|
123
|
-
self.change_layer(new_idx)
|
|
124
|
-
self.highlight_thumbnail(new_idx)
|
|
125
|
-
|
|
126
|
-
def highlight_thumbnail(self, index):
|
|
127
|
-
self.thumbnail_list.setCurrentRow(index)
|
|
128
|
-
self.thumbnail_list.scrollToItem(
|
|
129
|
-
self.thumbnail_list.item(index), QAbstractItemView.PositionAtCenter)
|
|
130
|
-
|
|
131
|
-
def copy_layer_to_master(self):
|
|
132
|
-
if self.layer_stack() is None or self.master_layer() is None:
|
|
133
|
-
return
|
|
134
|
-
reply = QMessageBox.question(
|
|
135
|
-
self,
|
|
136
|
-
"Confirm Copy",
|
|
137
|
-
"Warning: the current master layer will be erased\n\nDo you want to continue?",
|
|
138
|
-
QMessageBox.Yes | QMessageBox.No,
|
|
139
|
-
QMessageBox.No
|
|
140
|
-
)
|
|
141
|
-
if reply == QMessageBox.Yes:
|
|
142
|
-
self.set_master_layer(self.current_layer().copy())
|
|
143
|
-
self.master_layer().setflags(write=True)
|
|
144
|
-
self.display_manager.display_current_view()
|
|
145
|
-
self.display_manager.update_thumbnails()
|
|
146
|
-
self.mark_as_modified()
|
|
147
|
-
self.statusBar().showMessage(f"Copied layer {self.current_layer_idx() + 1} to master")
|
|
148
|
-
|
|
149
|
-
def copy_brush_area_to_master(self, view_pos):
|
|
150
|
-
if self.layer_stack() is None or self.number_of_layers() == 0 \
|
|
151
|
-
or not self.display_manager.allow_cursor_preview():
|
|
152
|
-
return
|
|
153
|
-
area = self.brush_tool.apply_brush_operation(
|
|
154
|
-
self.master_layer_copy(),
|
|
155
|
-
self.current_layer(),
|
|
156
|
-
self.master_layer(), self.mask_layer,
|
|
157
|
-
view_pos, self.image_viewer)
|
|
158
|
-
self.undo_manager.extend_undo_area(*area)
|
|
159
|
-
|
|
160
|
-
def begin_copy_brush_area(self, pos):
|
|
161
|
-
if self.display_manager.allow_cursor_preview():
|
|
162
|
-
self.mask_layer = self.io_gui_handler.blank_layer.copy()
|
|
163
|
-
self.copy_master_layer()
|
|
164
|
-
self.undo_manager.reset_undo_area()
|
|
165
|
-
self.copy_brush_area_to_master(pos)
|
|
166
|
-
self.display_manager.needs_update = True
|
|
167
|
-
if not self.display_manager.update_timer.isActive():
|
|
168
|
-
self.display_manager.update_timer.start()
|
|
169
|
-
self.mark_as_modified()
|
|
170
|
-
|
|
171
|
-
def continue_copy_brush_area(self, pos):
|
|
172
|
-
if self.display_manager.allow_cursor_preview():
|
|
173
|
-
self.copy_brush_area_to_master(pos)
|
|
174
|
-
self.display_manager.needs_update = True
|
|
175
|
-
if not self.display_manager.update_timer.isActive():
|
|
176
|
-
self.display_manager.update_timer.start()
|
|
177
|
-
self.mark_as_modified()
|
|
178
|
-
|
|
179
|
-
def end_copy_brush_area(self):
|
|
180
|
-
if self.display_manager.update_timer.isActive():
|
|
181
|
-
self.display_manager.display_master_layer()
|
|
182
|
-
self.display_manager.update_master_thumbnail()
|
|
183
|
-
self.undo_manager.save_undo_state(self.master_layer_copy(), 'Brush Stroke')
|
|
184
|
-
self.display_manager.update_timer.stop()
|
|
185
|
-
self.mark_as_modified()
|
|
186
|
-
|
|
187
|
-
def update_undo_redo_actions(self, has_undo, undo_desc, has_redo, redo_desc):
|
|
188
|
-
if self.undo_action:
|
|
189
|
-
if has_undo:
|
|
190
|
-
self.undo_action.setText(f"Undo {undo_desc}")
|
|
191
|
-
self.undo_action.setEnabled(True)
|
|
192
|
-
else:
|
|
193
|
-
self.undo_action.setText("Undo")
|
|
194
|
-
self.undo_action.setEnabled(False)
|
|
195
|
-
if self.redo_action:
|
|
196
|
-
if has_redo:
|
|
197
|
-
self.redo_action.setText(f"Redo {redo_desc}")
|
|
198
|
-
self.redo_action.setEnabled(True)
|
|
199
|
-
else:
|
|
200
|
-
self.redo_action.setText("Redo")
|
|
201
|
-
self.redo_action.setEnabled(False)
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
# pylint: disable=C0114, C0115, C0116, R0914
|
|
2
|
-
import numpy as np
|
|
3
|
-
from .image_editor import ImageEditor
|
|
4
|
-
from .filter_manager import FilterManager
|
|
5
|
-
from .denoise_filter import DenoiseFilter
|
|
6
|
-
from .unsharp_mask_filter import UnsharpMaskFilter
|
|
7
|
-
from .white_balance_filter import WhiteBalanceFilter
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class ImageFilters(ImageEditor):
|
|
11
|
-
def __init__(self):
|
|
12
|
-
super().__init__()
|
|
13
|
-
self.filter_manager = FilterManager(self)
|
|
14
|
-
self.filter_manager.register_filter("denoise", DenoiseFilter)
|
|
15
|
-
self.filter_manager.register_filter("unsharp_mask", UnsharpMaskFilter)
|
|
16
|
-
self.filter_manager.register_filter("white_balance", WhiteBalanceFilter)
|
|
17
|
-
|
|
18
|
-
def denoise_filter(self):
|
|
19
|
-
self.filter_manager.apply("denoise")
|
|
20
|
-
|
|
21
|
-
def unsharp_mask(self):
|
|
22
|
-
self.filter_manager.apply("unsharp_mask")
|
|
23
|
-
|
|
24
|
-
def white_balance(self, init_val=None):
|
|
25
|
-
self.filter_manager.apply("white_balance", init_val=init_val or (128, 128, 128))
|
|
26
|
-
|
|
27
|
-
def connect_preview_toggle(self, preview_check, do_preview, restore_original):
|
|
28
|
-
def on_toggled(checked):
|
|
29
|
-
if checked:
|
|
30
|
-
do_preview()
|
|
31
|
-
else:
|
|
32
|
-
restore_original()
|
|
33
|
-
preview_check.toggled.connect(on_toggled)
|
|
34
|
-
|
|
35
|
-
def get_pixel_color_at(self, pos, radius=None):
|
|
36
|
-
item_pos = self.image_viewer.position_on_image(pos)
|
|
37
|
-
x = int(item_pos.x())
|
|
38
|
-
y = int(item_pos.y())
|
|
39
|
-
master_layer = self.master_layer()
|
|
40
|
-
if (0 <= x < self.master_layer().shape[1]) and \
|
|
41
|
-
(0 <= y < self.master_layer().shape[0]):
|
|
42
|
-
if radius is None:
|
|
43
|
-
radius = int(self.brush.size)
|
|
44
|
-
if radius > 0:
|
|
45
|
-
y_indices, x_indices = np.ogrid[-radius:radius + 1, -radius:radius + 1]
|
|
46
|
-
mask = x_indices**2 + y_indices**2 <= radius**2
|
|
47
|
-
x0 = max(0, x - radius)
|
|
48
|
-
x1 = min(master_layer.shape[1], x + radius + 1)
|
|
49
|
-
y0 = max(0, y - radius)
|
|
50
|
-
y1 = min(master_layer.shape[0], y + radius + 1)
|
|
51
|
-
mask = mask[radius - (y - y0): radius + (y1 - y),
|
|
52
|
-
radius - (x - x0): radius + (x1 - x)]
|
|
53
|
-
region = master_layer[y0:y1, x0:x1]
|
|
54
|
-
if region.size == 0:
|
|
55
|
-
pixel = master_layer[y, x]
|
|
56
|
-
else:
|
|
57
|
-
if region.ndim == 3:
|
|
58
|
-
pixel = [region[:, :, c][mask].mean() for c in range(region.shape[2])]
|
|
59
|
-
else:
|
|
60
|
-
pixel = region[mask].mean()
|
|
61
|
-
else:
|
|
62
|
-
pixel = self.master_layer()[y, x]
|
|
63
|
-
if np.isscalar(pixel):
|
|
64
|
-
pixel = [pixel, pixel, pixel]
|
|
65
|
-
pixel = [np.float32(x) for x in pixel]
|
|
66
|
-
if master_layer.dtype == np.uint16:
|
|
67
|
-
pixel = [x / 256.0 for x in pixel]
|
|
68
|
-
return tuple(int(v) for v in pixel)
|
|
69
|
-
return (0, 0, 0)
|