├── 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 | ![效果图](show.png) 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 | "
" 462 | "made with hcc ") 463 | self.img_lbl.setAlignment(Qt.AlignCenter) 464 | 465 | self.file_name = None 466 | 467 | self.img_size_lbl = None 468 | self.img_size_lbl = QLabel() 469 | self.img_size_lbl.setAlignment(Qt.AlignCenter) 470 | 471 | upload_btn = QPushButton("上传") 472 | upload_btn.setMinimumWidth(BTN_MIN_WIDTH) 473 | upload_btn.clicked.connect(self.on_upload) 474 | upload_btn.setStyleSheet("font-weight:bold;") 475 | 476 | self.reset_btn = QPushButton("重置") 477 | self.reset_btn.setMinimumWidth(BTN_MIN_WIDTH) 478 | self.reset_btn.clicked.connect(self.on_reset) 479 | self.reset_btn.setEnabled(False) 480 | self.reset_btn.setStyleSheet("font-weight:bold;") 481 | 482 | self.save_btn = QPushButton("保存") 483 | self.save_btn.setMinimumWidth(BTN_MIN_WIDTH) 484 | self.save_btn.clicked.connect(self.on_save) 485 | self.save_btn.setEnabled(False) 486 | self.save_btn.setStyleSheet("font-weight:bold;") 487 | 488 | self.addWidget(self.img_lbl) 489 | self.addWidget(self.img_size_lbl) 490 | self.addStretch() 491 | 492 | self.action_tabs = ActionTabs(self) 493 | self.addWidget(self.action_tabs) 494 | self.action_tabs.setVisible(False) 495 | 496 | btn_layout = QHBoxLayout() 497 | btn_layout.setAlignment(Qt.AlignCenter) 498 | btn_layout.addWidget(upload_btn) 499 | btn_layout.addWidget(self.reset_btn) 500 | btn_layout.addWidget(self.save_btn) 501 | 502 | self.addLayout(btn_layout) 503 | 504 | def place_preview_img(self): 505 | img = _get_img_with_all_operations() 506 | 507 | preview_pix = ImageQt.toqpixmap(img) 508 | self.img_lbl.setPixmap(preview_pix) 509 | 510 | def on_save(self): 511 | logger.debug("open save dialog") 512 | new_img_path, _ = QFileDialog.getSaveFileName(self.parent, "QFileDialog.getSaveFileName()", 513 | f"ez_pz_{self.file_name}", 514 | "Images (*.png *.jpg)") 515 | 516 | if new_img_path: 517 | logger.debug(f"save output image to {new_img_path}") 518 | 519 | img = _get_img_with_all_operations() 520 | img.save(new_img_path) 521 | 522 | def on_upload(self): 523 | logger.debug("upload") 524 | img_path, _ = QFileDialog.getOpenFileName(self.parent, "Open image", 525 | "/Users", 526 | "Images (*.png *jpg)") 527 | 528 | if img_path: 529 | logger.debug(f"open file {img_path}") 530 | 531 | self.file_name = ntpath.basename(img_path) 532 | 533 | pix = QPixmap(img_path) 534 | self.img_lbl.setPixmap(pix) 535 | self.img_lbl.setScaledContents(True) 536 | self.action_tabs.setVisible(True) 537 | self.action_tabs.adjustment_tab.reset_sliders() 538 | 539 | global _img_original 540 | _img_original = ImageQt.fromqpixmap(pix) 541 | 542 | self.update_img_size_lbl() 543 | 544 | if _img_original.width < _img_original.height: 545 | w = THUMB_SIZE 546 | h = _get_ratio_height(_img_original.width, _img_original.height, w) 547 | else: 548 | h = THUMB_SIZE 549 | w = _get_ratio_width(_img_original.width, _img_original.height, h) 550 | 551 | img_filter_thumb = img_helper.resize(_img_original, w, h) 552 | 553 | global _img_preview 554 | _img_preview = _img_original.copy() 555 | 556 | for thumb in self.action_tabs.filters_tab.findChildren(QLabel): 557 | if thumb.name != "none": 558 | img_filter_preview = img_helper.color_filter(img_filter_thumb, thumb.name) 559 | else: 560 | img_filter_preview = img_filter_thumb 561 | 562 | preview_pix = ImageQt.toqpixmap(img_filter_preview) 563 | thumb.setPixmap(preview_pix) 564 | 565 | self.reset_btn.setEnabled(True) 566 | self.save_btn.setEnabled(True) 567 | self.action_tabs.modification_tab.set_boxes() 568 | 569 | def update_img_size_lbl(self): 570 | logger.debug("update img size lbl") 571 | 572 | self.img_size_lbl.setText(f"" 573 | f"图片大小 {operations.size[0] if operations.size else _img_original.width} × " 574 | f"{operations.size[1] if operations.size else _img_original.height}" 575 | f"") 576 | 577 | def on_reset(self): 578 | logger.debug("reset all") 579 | 580 | global _img_preview 581 | _img_preview = _img_original.copy() 582 | 583 | operations.reset() 584 | 585 | self.action_tabs.filters_tab.toggle_thumbs() 586 | self.place_preview_img() 587 | self.action_tabs.adjustment_tab.reset_sliders() 588 | self.action_tabs.modification_tab.set_boxes() 589 | self.update_img_size_lbl() 590 | 591 | 592 | class EasyPzUI(QWidget): 593 | """Main widget""" 594 | 595 | def __init__(self): 596 | super().__init__() 597 | 598 | self.main_layout = MainLayout(self) 599 | self.setLayout(self.main_layout) 600 | 601 | self.setMinimumSize(600, 500) 602 | self.setMaximumSize(900, 900) 603 | self.setGeometry(600, 600, 600, 600) 604 | self.setWindowTitle('简易photoEdit') 605 | self.center() 606 | self.show() 607 | 608 | def center(self): 609 | """align window center""" 610 | 611 | qr = self.frameGeometry() 612 | cp = QDesktopWidget().availableGeometry().center() 613 | qr.moveCenter(cp) 614 | self.move(qr.topLeft()) 615 | 616 | def closeEvent(self, event): 617 | logger.debug("close") 618 | 619 | if operations.has_changes(): 620 | reply = QMessageBox.question(self, "", 621 | "您还没有保存
确定退出?", QMessageBox.Yes | 622 | QMessageBox.No, QMessageBox.No) 623 | 624 | if reply == QMessageBox.Yes: 625 | event.accept() 626 | else: 627 | event.ignore() 628 | 629 | def resizeEvent(self, e): 630 | pass 631 | 632 | 633 | class QVLine(QFrame): 634 | """Vertical line""" 635 | 636 | def __init__(self): 637 | super(QVLine, self).__init__() 638 | self.setFrameShape(QFrame.VLine) 639 | self.setFrameShadow(QFrame.Sunken) 640 | 641 | 642 | if __name__ == '__main__': 643 | fileConfig('logging_config.ini') 644 | 645 | app = QApplication(sys.argv) 646 | win = EasyPzUI() 647 | sys.exit(app.exec_()) 648 | 649 | --------------------------------------------------------------------------------