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.

Files changed (59) hide show
  1. shinestacker/_version.py +1 -1
  2. shinestacker/algorithms/align.py +4 -12
  3. shinestacker/algorithms/balance.py +11 -9
  4. shinestacker/algorithms/depth_map.py +0 -30
  5. shinestacker/algorithms/utils.py +10 -0
  6. shinestacker/algorithms/vignetting.py +116 -70
  7. shinestacker/app/about_dialog.py +101 -12
  8. shinestacker/app/gui_utils.py +1 -1
  9. shinestacker/app/help_menu.py +1 -1
  10. shinestacker/app/main.py +2 -2
  11. shinestacker/app/project.py +2 -2
  12. shinestacker/config/constants.py +4 -1
  13. shinestacker/config/gui_constants.py +10 -9
  14. shinestacker/gui/action_config.py +5 -561
  15. shinestacker/gui/action_config_dialog.py +567 -0
  16. shinestacker/gui/base_form_dialog.py +18 -0
  17. shinestacker/gui/colors.py +5 -6
  18. shinestacker/gui/gui_logging.py +0 -1
  19. shinestacker/gui/gui_run.py +54 -106
  20. shinestacker/gui/ico/shinestacker.icns +0 -0
  21. shinestacker/gui/ico/shinestacker.ico +0 -0
  22. shinestacker/gui/ico/shinestacker.png +0 -0
  23. shinestacker/gui/ico/shinestacker.svg +60 -0
  24. shinestacker/gui/main_window.py +276 -367
  25. shinestacker/gui/menu_manager.py +236 -0
  26. shinestacker/gui/new_project.py +75 -20
  27. shinestacker/gui/project_converter.py +6 -6
  28. shinestacker/gui/project_editor.py +248 -165
  29. shinestacker/gui/project_model.py +2 -7
  30. shinestacker/gui/tab_widget.py +81 -0
  31. shinestacker/gui/time_progress_bar.py +95 -0
  32. shinestacker/retouch/base_filter.py +173 -40
  33. shinestacker/retouch/brush_preview.py +0 -10
  34. shinestacker/retouch/brush_tool.py +25 -11
  35. shinestacker/retouch/denoise_filter.py +5 -44
  36. shinestacker/retouch/display_manager.py +57 -20
  37. shinestacker/retouch/exif_data.py +10 -13
  38. shinestacker/retouch/file_loader.py +1 -1
  39. shinestacker/retouch/filter_manager.py +1 -4
  40. shinestacker/retouch/image_editor_ui.py +365 -49
  41. shinestacker/retouch/image_viewer.py +34 -11
  42. shinestacker/retouch/io_gui_handler.py +96 -43
  43. shinestacker/retouch/io_manager.py +23 -7
  44. shinestacker/retouch/layer_collection.py +2 -0
  45. shinestacker/retouch/shortcuts_help.py +12 -0
  46. shinestacker/retouch/unsharp_mask_filter.py +10 -10
  47. shinestacker/retouch/vignetting_filter.py +69 -0
  48. shinestacker/retouch/white_balance_filter.py +46 -14
  49. {shinestacker-0.4.0.dist-info → shinestacker-1.0.0.dist-info}/METADATA +14 -2
  50. shinestacker-1.0.0.dist-info/RECORD +90 -0
  51. shinestacker/app/app_config.py +0 -22
  52. shinestacker/gui/actions_window.py +0 -258
  53. shinestacker/retouch/image_editor.py +0 -201
  54. shinestacker/retouch/image_filters.py +0 -69
  55. shinestacker-0.4.0.dist-info/RECORD +0 -87
  56. {shinestacker-0.4.0.dist-info → shinestacker-1.0.0.dist-info}/WHEEL +0 -0
  57. {shinestacker-0.4.0.dist-info → shinestacker-1.0.0.dist-info}/entry_points.txt +0 -0
  58. {shinestacker-0.4.0.dist-info → shinestacker-1.0.0.dist-info}/licenses/LICENSE +0 -0
  59. {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,,
@@ -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)