├── img_modifier
├── a
├── __init__.py
├── color_filter.py
└── img_helper.py
├── logo.jpg
├── show.png
├── test.jpg
├── 41724260-胡成成.docx
├── 41724260-胡成成.pdf
├── README.md
├── logging_config.ini
├── img_modifier.py
└── photo_editor.py
/img_modifier/a:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackHCC/Photo-Edit/HEAD/logo.jpg
--------------------------------------------------------------------------------
/show.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackHCC/Photo-Edit/HEAD/show.png
--------------------------------------------------------------------------------
/test.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackHCC/Photo-Edit/HEAD/test.jpg
--------------------------------------------------------------------------------
/41724260-胡成成.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackHCC/Photo-Edit/HEAD/41724260-胡成成.docx
--------------------------------------------------------------------------------
/41724260-胡成成.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JackHCC/Photo-Edit/HEAD/41724260-胡成成.pdf
--------------------------------------------------------------------------------
/img_modifier/__init__.py:
--------------------------------------------------------------------------------
1 | from logging.config import fileConfig
2 |
3 | # init logger from config file
4 | fileConfig('logging_config.ini')
5 |
6 | __all__ = ["color_filter", "img_modifier"]
7 |
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Photo-Edit
2 |
3 | ### 文件说明
4 | 1. 报告:包括pdf和word版本
5 | 2. 图片:个人证件照logo.jpg,这也是程序运行主界面背景图;编辑测试图片test.jpg
6 | 3. python文件:包括img_modifier文件夹,img_modifier.py和photo_editor.py主程序
7 | 4. logging_config.ini:日志配置文件
8 |
9 | ### 程序运行结果图
10 |
11 | 
12 |
--------------------------------------------------------------------------------
/logging_config.ini:
--------------------------------------------------------------------------------
1 | [loggers]
2 | keys=root
3 |
4 | [handlers]
5 | keys=stream_handler
6 |
7 | [formatters]
8 | keys=formatter
9 |
10 | [logger_root]
11 | level=DEBUG
12 | handlers=stream_handler
13 |
14 | [handler_stream_handler]
15 | class=StreamHandler
16 | level=DEBUG
17 | formatter=formatter
18 | args=(sys.stderr,)
19 |
20 | [formatter_formatter]
21 | format=%(asctime)s %(name)-12s %(levelname)-8s %(message)s
--------------------------------------------------------------------------------
/img_modifier/color_filter.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | logger = logging.getLogger()
4 |
5 |
6 | class ColorFilters:
7 | filters = {"sepia": "Sepia", "negative": "Negative", "black_white": "Black & White"}
8 | SEPIA, NEGATIVE, BLACK_WHITE = filters.keys()
9 |
10 | #黑白滤镜
11 | def sepia(img):
12 | pix = img.load()
13 | for i in range(img.width):
14 | for j in range(img.height):
15 | s = sum(pix[i, j]) // 3
16 | k = 30
17 | pix[i, j] = (s+k*2, s+k, s)
18 |
19 | #均值滤波
20 | def black_white(img):
21 | pix = img.load()
22 | for i in range(img.width):
23 | for j in range(img.height):
24 | s = sum(pix[i, j]) // 3
25 | pix[i, j] = (s, s, s)
26 |
27 | #负滤镜,底片滤镜
28 | def negative(img):
29 | pix = img.load()
30 | for i in range(img.width):
31 | for j in range(img.height):
32 | pix[i, j] = (255 - pix[i, j][0], 255 - pix[i, j][1], 255 - pix[i, j][2])
33 |
34 |
35 | def color_filter(img, filter_name):
36 | img_copy = img.copy()
37 | if filter_name == ColorFilters.SEPIA:
38 | sepia(img_copy)
39 | elif filter_name == ColorFilters.NEGATIVE:
40 | negative(img_copy)
41 | elif filter_name == ColorFilters.BLACK_WHITE:
42 | black_white(img_copy)
43 | else:
44 | logger.error(f"can't find filter {filter_name}")
45 | raise ValueError(f"can't find filter {filter_name}")
46 |
47 | return img_copy
48 |
--------------------------------------------------------------------------------
/img_modifier.py:
--------------------------------------------------------------------------------
1 | import getopt
2 | import sys
3 | import logging
4 |
5 | from img_modifier import img_helper
6 |
7 | logger = logging.getLogger()
8 |
9 |
10 | def init():
11 | """Get and parse parameters from console"""
12 |
13 | args = sys.argv[1:]
14 |
15 | if len(args) == 0:
16 | logger.error("-p can't be empty")
17 | raise ValueError("-p can't be empty")
18 |
19 | logger.debug(f"run with params: {args}")
20 |
21 | # transform arguments from console
22 | opts, rem = getopt.getopt(args, "p:", ["rotate=", "resize=", "color_filter=", "flip_top", "flip_left"])
23 | rotate_angle = resize = color_filter = flip_top = flip_left = None
24 |
25 | path = None
26 | for opt, arg in opts:
27 | if opt == "-p":
28 | path = arg
29 | elif opt == "--rotate":
30 | rotate_angle = int(arg)
31 | elif opt == "--resize":
32 | resize = arg
33 | elif opt == "--color_filter":
34 | color_filter = arg
35 | elif opt == "--flip_top":
36 | flip_top = True
37 | elif opt == "--flip_left":
38 | flip_left = arg
39 |
40 | if not path:
41 | raise ValueError("No path")
42 |
43 | img = img_helper.get_img(path)
44 | if rotate_angle:
45 | img = img_helper.rotate(img, rotate_angle)
46 |
47 | if resize:
48 | w, h = map(int, resize.split(','))
49 | img = img_helper.resize(img, w, h)
50 |
51 | if color_filter:
52 | img = img_helper.color_filter(img, color_filter)
53 |
54 | if flip_left:
55 | img = img_helper.flip_left(img)
56 |
57 | if flip_top:
58 | img = img_helper.flip_top(img)
59 |
60 | if __debug__:
61 | img.show()
62 |
63 |
64 | if __name__ == "__main__":
65 | init()
66 |
--------------------------------------------------------------------------------
/img_modifier/img_helper.py:
--------------------------------------------------------------------------------
1 | from PIL import Image, ImageEnhance
2 | import logging
3 |
4 | import img_modifier.color_filter as cf
5 |
6 | logger = logging.getLogger()
7 |
8 | # constants
9 | CONTRAST_FACTOR_MAX = 1.5
10 | CONTRAST_FACTOR_MIN = 0.5
11 |
12 | SHARPNESS_FACTOR_MAX = 3
13 | SHARPNESS_FACTOR_MIN = -1
14 |
15 | BRIGHTNESS_FACTOR_MAX = 1.5
16 | BRIGHTNESS_FACTOR_MIN = 0.5
17 |
18 |
19 | def get_img(path):
20 | """Return PIL.Image object"""
21 |
22 | if path == "":
23 | logger.error("path is empty of has bad format")
24 | raise ValueError("path is empty of has bad format")
25 |
26 | try:
27 | return Image.open(path)
28 | except Exception:
29 | logger.error(f"can't open the file {path}")
30 | raise ValueError(f"can't open the file {path}")
31 |
32 |
33 | def resize(img, width, height):
34 | """Resize image"""
35 |
36 | return img.resize((width, height))
37 |
38 |
39 | def rotate(img, angle):
40 | """Rotate image"""
41 |
42 | return img.rotate(angle, expand=True)
43 |
44 |
45 | def color_filter(img, filter_name):
46 | """Filter image"""
47 |
48 | return cf.color_filter(img, filter_name)
49 |
50 |
51 | def brightness(img, factor):
52 | """Adjust image brightness form 0.5-2 (1 - original)"""
53 |
54 | if factor > BRIGHTNESS_FACTOR_MAX or factor < BRIGHTNESS_FACTOR_MIN:
55 | raise ValueError("factor should be [0-2]")
56 |
57 | enhancer = ImageEnhance.Brightness(img)
58 | return enhancer.enhance(factor)
59 |
60 |
61 | def contrast(img, factor):
62 | """Adjust image contrast form 0.5-1.5 (1 - original)"""
63 |
64 | if factor > CONTRAST_FACTOR_MAX or factor < CONTRAST_FACTOR_MIN:
65 | raise ValueError("factor should be [0.5-1.5]")
66 |
67 | enhancer = ImageEnhance.Contrast(img)
68 | return enhancer.enhance(factor)
69 |
70 |
71 | def sharpness(img, factor):
72 | """Adjust image sharpness form 0-2 (1 - original)"""
73 |
74 | if factor > SHARPNESS_FACTOR_MAX or factor < SHARPNESS_FACTOR_MIN:
75 | raise ValueError("factor should be [0.5-1.5]")
76 |
77 | enhancer = ImageEnhance.Sharpness(img)
78 | return enhancer.enhance(factor)
79 |
80 |
81 | def flip_left(img):
82 | """Flip left to right"""
83 |
84 | return img.transpose(Image.FLIP_LEFT_RIGHT)
85 |
86 |
87 | def flip_top(img):
88 | """Flip top to bottom"""
89 |
90 | return img.transpose(Image.FLIP_TOP_BOTTOM)
91 |
92 |
93 | def save(img, path):
94 | """Save image to hard drive"""
95 |
96 | img.save(path)
97 |
98 |
99 | def open_img(img):
100 | """
101 | Open image in temporary file
102 | !use it only for debug!
103 | """
104 |
105 | img.open()
106 |
--------------------------------------------------------------------------------
/photo_editor.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import ntpath
3 |
4 | from PyQt5.QtWidgets import *
5 | from PyQt5.QtCore import Qt
6 | from PyQt5.QtGui import *
7 |
8 | from functools import partial
9 |
10 | from img_modifier import img_helper
11 | from img_modifier import color_filter
12 |
13 | from PIL import ImageQt
14 |
15 | from logging.config import fileConfig
16 | import logging
17 |
18 | logger = logging.getLogger()
19 |
20 | # original img, can't be modified
21 | _img_original = None
22 | _img_preview = None
23 |
24 | # constants
25 | THUMB_BORDER_COLOR_ACTIVE = "#3893F4"
26 | THUMB_BORDER_COLOR = "#ccc"
27 | BTN_MIN_WIDTH = 120
28 | ROTATION_BTN_SIZE = (70, 30)
29 | THUMB_SIZE = 120
30 |
31 | SLIDER_MIN_VAL = -100
32 | SLIDER_MAX_VAL = 100
33 | SLIDER_DEF_VAL = 0
34 |
35 |
36 | class Operations:
37 | def __init__(self):
38 | self.color_filter = None
39 |
40 | self.flip_left = False
41 | self.flip_top = False
42 | self.rotation_angle = 0
43 |
44 | self.size = None
45 |
46 | self.brightness = 0
47 | self.sharpness = 0
48 | self.contrast = 0
49 |
50 | def reset(self):
51 | self.color_filter = None
52 |
53 | self.brightness = 0
54 | self.sharpness = 0
55 | self.contrast = 0
56 |
57 | self.size = None
58 |
59 | self.flip_left = False
60 | self.flip_top = False
61 | self.rotation_angle = 0
62 |
63 | def has_changes(self):
64 | return self.color_filter or self.flip_left\
65 | or self.flip_top or self.rotation_angle\
66 | or self.contrast or self.brightness\
67 | or self.sharpness or self.size
68 |
69 | def __str__(self):
70 | return f"size: {self.size}, filter: {self.color_filter}, " \
71 | f"b: {self.brightness} c: {self.contrast} s: {self.sharpness}, " \
72 | f"flip-left: {self.flip_left} flip-top: {self.flip_top} rotation: {self.rotation_angle}"
73 |
74 |
75 | operations = Operations()
76 |
77 |
78 | def _get_ratio_height(width, height, r_width):
79 | return int(r_width/width*height)
80 |
81 |
82 | def _get_ratio_width(width, height, r_height):
83 | return int(r_height/height*width)
84 |
85 |
86 | def _get_converted_point(user_p1, user_p2, p1, p2, x):
87 |
88 | r = (x - user_p1) / (user_p2 - user_p1)
89 | return p1 + r * (p2 - p1)
90 |
91 |
92 | def _get_img_with_all_operations():
93 | logger.debug(operations)
94 |
95 | b = operations.brightness
96 | c = operations.contrast
97 | s = operations.sharpness
98 |
99 | img = _img_preview
100 | if b != 0:
101 | img = img_helper.brightness(img, b)
102 |
103 | if c != 0:
104 | img = img_helper.contrast(img, c)
105 |
106 | if s != 0:
107 | img = img_helper.sharpness(img, s)
108 |
109 | if operations.rotation_angle:
110 | img = img_helper.rotate(img, operations.rotation_angle)
111 |
112 | if operations.flip_left:
113 | img = img_helper.flip_left(img)
114 |
115 | if operations.flip_top:
116 | img = img_helper.flip_top(img)
117 |
118 | if operations.size:
119 | img = img_helper.resize(img, *operations.size)
120 |
121 | return img
122 |
123 |
124 | class ActionTabs(QTabWidget):
125 | """Action tabs widget"""
126 |
127 | def __init__(self, parent):
128 | super().__init__()
129 | self.parent = parent
130 |
131 | self.filters_tab = FiltersTab(self)
132 | self.adjustment_tab = AdjustingTab(self)
133 | self.modification_tab = ModificationTab(self)
134 | self.rotation_tab = RotationTab(self)
135 |
136 | self.addTab(self.filters_tab, "滤镜")
137 | self.addTab(self.adjustment_tab, "调整")
138 | self.addTab(self.modification_tab, "尺寸")
139 | self.addTab(self.rotation_tab, "旋转")
140 |
141 | self.setMaximumHeight(190)
142 |
143 |
144 | class RotationTab(QWidget):
145 | """Rotation tab widget"""
146 |
147 | def __init__(self, parent):
148 | super().__init__()
149 | self.parent = parent
150 |
151 | rotate_left_btn = QPushButton("↺ 90°")
152 | rotate_left_btn.setMinimumSize(*ROTATION_BTN_SIZE)
153 | rotate_left_btn.clicked.connect(self.on_rotate_left)
154 |
155 | rotate_right_btn = QPushButton("↻ 90°")
156 | rotate_right_btn.setMinimumSize(*ROTATION_BTN_SIZE)
157 | rotate_right_btn.clicked.connect(self.on_rotate_right)
158 |
159 | flip_left_btn = QPushButton("⇆")
160 | flip_left_btn.setMinimumSize(*ROTATION_BTN_SIZE)
161 | flip_left_btn.clicked.connect(self.on_flip_left)
162 |
163 | flip_top_btn = QPushButton("↑↓")
164 | flip_top_btn.setMinimumSize(*ROTATION_BTN_SIZE)
165 | flip_top_btn.clicked.connect(self.on_flip_top)
166 |
167 | rotate_lbl = QLabel("旋转")
168 | rotate_lbl.setAlignment(Qt.AlignCenter)
169 | rotate_lbl.setFixedWidth(140)
170 |
171 | flip_lbl = QLabel("翻转")
172 | flip_lbl.setAlignment(Qt.AlignCenter)
173 | flip_lbl.setFixedWidth(140)
174 |
175 | lbl_layout = QHBoxLayout()
176 | lbl_layout.setAlignment(Qt.AlignCenter)
177 | lbl_layout.addWidget(rotate_lbl)
178 | lbl_layout.addWidget(flip_lbl)
179 |
180 | btn_layout = QHBoxLayout()
181 | btn_layout.setAlignment(Qt.AlignCenter)
182 | btn_layout.addWidget(rotate_left_btn)
183 | btn_layout.addWidget(rotate_right_btn)
184 | btn_layout.addWidget(QVLine())
185 | btn_layout.addWidget(flip_left_btn)
186 | btn_layout.addWidget(flip_top_btn)
187 |
188 | main_layout = QVBoxLayout()
189 | main_layout.setAlignment(Qt.AlignCenter)
190 | main_layout.addLayout(lbl_layout)
191 | main_layout.addLayout(btn_layout)
192 |
193 | self.setLayout(main_layout)
194 |
195 | def on_rotate_left(self):
196 | logger.debug("rotate left")
197 |
198 | operations.rotation_angle = 0 if operations.rotation_angle == 270 else operations.rotation_angle + 90
199 | self.parent.parent.place_preview_img()
200 |
201 | def on_rotate_right(self):
202 | logger.debug("rotate left")
203 |
204 | operations.rotation_angle = 0 if operations.rotation_angle == -270 else operations.rotation_angle - 90
205 | self.parent.parent.place_preview_img()
206 |
207 | def on_flip_left(self):
208 | logger.debug("flip left-right")
209 |
210 | operations.flip_left = not operations.flip_left
211 | self.parent.parent.place_preview_img()
212 |
213 | def on_flip_top(self):
214 | logger.debug("flip top-bottom")
215 |
216 | operations.flip_top = not operations.flip_top
217 | self.parent.parent.place_preview_img()
218 |
219 |
220 | class ModificationTab(QWidget):
221 | """Modification tab widget"""
222 |
223 | def __init__(self, parent):
224 | super().__init__()
225 | self.parent = parent
226 |
227 | self.width_lbl = QLabel('宽度:', self)
228 | self.width_lbl.setFixedWidth(100)
229 |
230 | self.height_lbl = QLabel('高度:', self)
231 | self.height_lbl.setFixedWidth(100)
232 |
233 | self.width_box = QLineEdit(self)
234 | self.width_box.textEdited.connect(self.on_width_change)
235 | self.width_box.setMaximumWidth(100)
236 |
237 | self.height_box = QLineEdit(self)
238 | self.height_box.textEdited.connect(self.on_height_change)
239 | self.height_box.setMaximumWidth(100)
240 |
241 | self.unit_lbl = QLabel("px")
242 | self.unit_lbl.setMaximumWidth(50)
243 |
244 | self.ratio_check = QCheckBox('纵横比', self)
245 | self.ratio_check.stateChanged.connect(self.on_ratio_change)
246 |
247 | self.apply_btn = QPushButton("应用")
248 | self.apply_btn.setFixedWidth(90)
249 | self.apply_btn.clicked.connect(self.on_apply)
250 |
251 | width_layout = QHBoxLayout()
252 | width_layout.addWidget(self.width_box)
253 | width_layout.addWidget(self.height_box)
254 | width_layout.addWidget(self.unit_lbl)
255 |
256 | apply_layout = QHBoxLayout()
257 | apply_layout.addWidget(self.apply_btn)
258 | apply_layout.setAlignment(Qt.AlignRight)
259 |
260 | lbl_layout = QHBoxLayout()
261 | lbl_layout.setAlignment(Qt.AlignLeft)
262 | lbl_layout.addWidget(self.width_lbl)
263 | lbl_layout.addWidget(self.height_lbl)
264 |
265 | main_layout = QVBoxLayout()
266 | main_layout.setAlignment(Qt.AlignCenter)
267 |
268 | main_layout.addLayout(lbl_layout)
269 | main_layout.addLayout(width_layout)
270 | main_layout.addWidget(self.ratio_check)
271 | main_layout.addLayout(apply_layout)
272 |
273 | self.setLayout(main_layout)
274 |
275 | def set_boxes(self):
276 | self.width_box.setText(str(_img_original.width))
277 | self.height_box.setText(str(_img_original.height))
278 |
279 | def on_width_change(self, e):
280 | logger.debug(f"type width {self.width_box.text()}")
281 |
282 | if self.ratio_check.isChecked():
283 | r_height = _get_ratio_height(_img_original.width, _img_original.height, int(self.width_box.text()))
284 | self.height_box.setText(str(r_height))
285 |
286 | def on_height_change(self, e):
287 | logger.debug(f"type height {self.height_box.text()}")
288 |
289 | if self.ratio_check.isChecked():
290 | r_width = _get_ratio_width(_img_original.width, _img_original.height, int(self.height_box.text()))
291 | self.width_box.setText(str(r_width))
292 |
293 | def on_ratio_change(self, e):
294 | logger.debug("ratio change")
295 |
296 | def on_apply(self, e):
297 | logger.debug("apply")
298 |
299 | operations.size = int(self.width_box.text()), int(self.height_box.text())
300 |
301 | self.parent.parent.update_img_size_lbl()
302 | self.parent.parent.place_preview_img()
303 |
304 |
305 | class AdjustingTab(QWidget):
306 | """Adjusting tab widget"""
307 |
308 | def __init__(self, parent):
309 | super().__init__()
310 | self.parent = parent
311 |
312 | contrast_lbl = QLabel("对比度")
313 | contrast_lbl.setAlignment(Qt.AlignCenter)
314 |
315 | brightness_lbl = QLabel("亮度")
316 | brightness_lbl.setAlignment(Qt.AlignCenter)
317 |
318 | sharpness_lbl = QLabel("锐化")
319 | sharpness_lbl.setAlignment(Qt.AlignCenter)
320 |
321 | self.contrast_slider = QSlider(Qt.Horizontal, self)
322 | self.contrast_slider.setMinimum(SLIDER_MIN_VAL)
323 | self.contrast_slider.setMaximum(SLIDER_MAX_VAL)
324 | self.contrast_slider.sliderReleased.connect(self.on_contrast_slider_released)
325 | self.contrast_slider.setToolTip(str(SLIDER_MAX_VAL))
326 |
327 | self.brightness_slider = QSlider(Qt.Horizontal, self)
328 | self.brightness_slider.setMinimum(SLIDER_MIN_VAL)
329 | self.brightness_slider.setMaximum(SLIDER_MAX_VAL)
330 | self.brightness_slider.sliderReleased.connect(self.on_brightness_slider_released)
331 | self.brightness_slider.setToolTip(str(SLIDER_MAX_VAL))
332 |
333 | self.sharpness_slider = QSlider(Qt.Horizontal, self)
334 | self.sharpness_slider.setMinimum(SLIDER_MIN_VAL)
335 | self.sharpness_slider.setMaximum(SLIDER_MAX_VAL)
336 | self.sharpness_slider.sliderReleased.connect(self.on_sharpness_slider_released)
337 | self.sharpness_slider.setToolTip(str(SLIDER_MAX_VAL))
338 |
339 | main_layout = QVBoxLayout()
340 | main_layout.setAlignment(Qt.AlignCenter)
341 |
342 | main_layout.addWidget(contrast_lbl)
343 | main_layout.addWidget(self.contrast_slider)
344 |
345 | main_layout.addWidget(brightness_lbl)
346 | main_layout.addWidget(self.brightness_slider)
347 |
348 | main_layout.addWidget(sharpness_lbl)
349 | main_layout.addWidget(self.sharpness_slider)
350 |
351 | self.reset_sliders()
352 | self.setLayout(main_layout)
353 |
354 | def reset_sliders(self):
355 | self.brightness_slider.setValue(SLIDER_DEF_VAL)
356 | self.sharpness_slider.setValue(SLIDER_DEF_VAL)
357 | self.contrast_slider.setValue(SLIDER_DEF_VAL)
358 |
359 | def on_brightness_slider_released(self):
360 | logger.debug(f"brightness selected value: {self.brightness_slider.value()}")
361 |
362 | self.brightness_slider.setToolTip(str(self.brightness_slider.value()))
363 |
364 | factor = _get_converted_point(SLIDER_MIN_VAL, SLIDER_MAX_VAL, img_helper.BRIGHTNESS_FACTOR_MIN,
365 | img_helper.BRIGHTNESS_FACTOR_MAX, self.brightness_slider.value())
366 | logger.debug(f"brightness factor: {factor}")
367 |
368 | operations.brightness = factor
369 |
370 | self.parent.parent.place_preview_img()
371 |
372 | def on_sharpness_slider_released(self):
373 | logger.debug(self.sharpness_slider.value())
374 |
375 | self.sharpness_slider.setToolTip(str(self.sharpness_slider.value()))
376 |
377 | factor = _get_converted_point(SLIDER_MIN_VAL, SLIDER_MAX_VAL, img_helper.SHARPNESS_FACTOR_MIN,
378 | img_helper.SHARPNESS_FACTOR_MAX, self.sharpness_slider.value())
379 | logger.debug(f"sharpness factor: {factor}")
380 |
381 | operations.sharpness = factor
382 |
383 | self.parent.parent.place_preview_img()
384 |
385 | def on_contrast_slider_released(self):
386 | logger.debug(self.contrast_slider.value())
387 |
388 | self.contrast_slider.setToolTip(str(self.contrast_slider.value()))
389 |
390 | factor = _get_converted_point(SLIDER_MIN_VAL, SLIDER_MAX_VAL, img_helper.CONTRAST_FACTOR_MIN,
391 | img_helper.CONTRAST_FACTOR_MAX, self.contrast_slider.value())
392 | logger.debug(f"contrast factor: {factor}")
393 |
394 | operations.contrast = factor
395 |
396 | self.parent.parent.place_preview_img()
397 |
398 |
399 | class FiltersTab(QWidget):
400 | """Color filters widget"""
401 |
402 | def __init__(self, parent):
403 | super().__init__()
404 | self.parent = parent
405 |
406 | self.main_layout = QHBoxLayout()
407 | self.main_layout.setAlignment(Qt.AlignCenter)
408 |
409 | self.add_filter_thumb("none")
410 | for key, val in color_filter.ColorFilters.filters.items():
411 | self.add_filter_thumb(key, val)
412 |
413 | self.setLayout(self.main_layout)
414 |
415 | def add_filter_thumb(self, name, title=""):
416 | logger.debug(f"create lbl thumb for: {name}")
417 |
418 | thumb_lbl = QLabel()
419 | thumb_lbl.name = name
420 | thumb_lbl.setStyleSheet("border:2px solid #ccc;")
421 |
422 | if name != "none":
423 | thumb_lbl.setToolTip(f"Apply {title} filter")
424 | else:
425 | thumb_lbl.setToolTip('No filter')
426 |
427 | thumb_lbl.setCursor(Qt.PointingHandCursor)
428 | thumb_lbl.setFixedSize(THUMB_SIZE, THUMB_SIZE)
429 | thumb_lbl.mousePressEvent = partial(self.on_filter_select, name)
430 |
431 | self.main_layout.addWidget(thumb_lbl)
432 |
433 | def on_filter_select(self, filter_name, e):
434 | logger.debug(f"apply color filter: {filter_name}")
435 |
436 | global _img_preview
437 | if filter_name != "none":
438 | _img_preview = img_helper.color_filter(_img_original, filter_name)
439 | else:
440 | _img_preview = _img_original.copy()
441 |
442 | operations.color_filter = filter_name
443 | self.toggle_thumbs()
444 |
445 | self.parent.parent.place_preview_img()
446 |
447 | def toggle_thumbs(self):
448 | for thumb in self.findChildren(QLabel):
449 | color = THUMB_BORDER_COLOR_ACTIVE if thumb.name == operations.color_filter else THUMB_BORDER_COLOR
450 | thumb.setStyleSheet(f"border:2px solid {color};")
451 |
452 |
453 | class MainLayout(QVBoxLayout):
454 | """Main layout"""
455 |
456 | def __init__(self, parent):
457 | super().__init__()
458 | self.parent = parent
459 |
460 | self.img_lbl = QLabel("点击 '上传' 开始编辑
"
461 | "
