From 27562186dfb05efb1b75b45f4f8d8085329812ab Mon Sep 17 00:00:00 2001 From: laggykiller Date: Thu, 4 Jan 2024 13:53:58 +0800 Subject: [PATCH] Allow customize scale filter --- src/sticker_convert/cli.py | 3 ++- src/sticker_convert/converter.py | 14 ++++++++++++- src/sticker_convert/gui.py | 6 ++++-- .../gui_components/frames/comp_frame.py | 1 + .../windows/advanced_compression_window.py | 20 +++++++++++++------ src/sticker_convert/job_option.py | 2 ++ .../resources/compression.json | 10 ++++++++++ src/sticker_convert/resources/help.json | 1 + 8 files changed, 47 insertions(+), 10 deletions(-) diff --git a/src/sticker_convert/cli.py b/src/sticker_convert/cli.py index e722315..c3c025e 100755 --- a/src/sticker_convert/cli.py +++ b/src/sticker_convert/cli.py @@ -67,7 +67,7 @@ def cli(self): 'color_min', 'color_max', 'duration_min', 'duration_max', 'vid_size_max', 'img_size_max') - flags_str = ('vid_format', 'img_format', 'cache_dir') + flags_str = ('vid_format', 'img_format', 'cache_dir', 'scale_filter') flags_bool = ('fake_vid') for k, v in self.help['comp'].items(): if k in flags_int: @@ -229,6 +229,7 @@ def get_opt_comp(self, args) -> dict: 'steps': self.compression_presets[preset]['steps'] if args.steps == None else args.steps, 'fake_vid': self.compression_presets[preset]['fake_vid'] if args.fake_vid == None else args.fake_vid, 'cache_dir': args.cache_dir, + 'scale_filter': args.scale_filter, 'default_emoji': args.default_emoji, 'no_compress': args.no_compress, 'processes': args.processes if args.processes else math.ceil(cpu_count() / 2) diff --git a/src/sticker_convert/converter.py b/src/sticker_convert/converter.py index b232dd4..88b8b30 100755 --- a/src/sticker_convert/converter.py +++ b/src/sticker_convert/converter.py @@ -283,7 +283,19 @@ def frames_resize(self, frames_in: list[np.ndarray]) -> list[np.ndarray]: else: height_new = self.res_h width_new = width * self.res_h // height - im = im.resize((width_new, height_new), resample=Image.LANCZOS) + + if self.opt_comp.scale_filter == 'nearest': + resample = Image.NEAREST + elif self.opt_comp.scale_filter == 'bilnear': + resample = Image.BILINEAR + elif self.opt_comp.scale_filter == 'bicubic': + resample = Image.BICUBIC + elif self.opt_comp.scale_filter == 'lanczos': + resample = Image.LANCZOS + else: + resample = Image.LANCZOS + + im = im.resize((width_new, height_new), resample=resample) im_new = Image.new('RGBA', (self.res_w, self.res_h), (0, 0, 0, 0)) im_new.paste( im, ((self.res_w - width_new) // 2, (self.res_h - height_new) // 2) diff --git a/src/sticker_convert/gui.py b/src/sticker_convert/gui.py index 5b26afd..29d9122 100755 --- a/src/sticker_convert/gui.py +++ b/src/sticker_convert/gui.py @@ -125,6 +125,7 @@ def declare_variables(self): self.img_format_var = StringVar(self) self.vid_format_var = StringVar(self) self.fake_vid_var = BooleanVar() + self.scale_filter_var = StringVar(self) self.cache_dir_var = StringVar(self) self.default_emoji_var = StringVar(self) self.steps_var = IntVar(self) @@ -381,6 +382,7 @@ def get_opt_comp(self) -> dict: }, 'steps': self.steps_var.get(), 'fake_vid': self.fake_vid_var.get(), + 'scale_filter': self.scale_filter_var.get(), 'cache_dir': self.cache_dir_var.get() if self.cache_dir_var.get() != '' else None, 'default_emoji': self.default_emoji_var.get(), 'no_compress': self.no_compress_var.get(), @@ -545,7 +547,7 @@ def highlight_fields(self) -> bool: if os.path.isdir(self.input_setdir_var.get()): self.input_frame.input_setdir_entry.config(bootstyle='default') else: - self.input_frame.input_setdir_entry.config(bootstyle='danger') + self.input_frame.input_setdir_entry.config(bootstyle='warning') self.input_frame.address_lbl.config(text=self.input_presets[input_option_display]['address_lbls']) self.input_frame.address_entry.config(bootstyle='default') @@ -575,7 +577,7 @@ def highlight_fields(self) -> bool: if os.path.isdir(self.output_setdir_var.get()): self.output_frame.output_setdir_entry.config(bootstyle='default') else: - self.output_frame.output_setdir_entry.config(bootstyle='danger') + self.output_frame.output_setdir_entry.config(bootstyle='warning') if (MetadataHandler.check_metadata_required(output_option, 'title') and not MetadataHandler.check_metadata_provided(self.input_setdir_var.get(), input_option, 'title') and diff --git a/src/sticker_convert/gui_components/frames/comp_frame.py b/src/sticker_convert/gui_components/frames/comp_frame.py index 8c336f1..75c80f4 100644 --- a/src/sticker_convert/gui_components/frames/comp_frame.py +++ b/src/sticker_convert/gui_components/frames/comp_frame.py @@ -81,6 +81,7 @@ def cb_comp_apply_preset(self, *args): self.gui.img_format_var.set(self.gui.compression_presets[selection]['format']['img']) self.gui.vid_format_var.set(self.gui.compression_presets[selection]['format']['vid']) self.gui.fake_vid_var.set(self.gui.compression_presets[selection]['fake_vid']) + self.gui.scale_filter_var.set(self.gui.compression_presets[selection]['scale_filter']) self.gui.default_emoji_var.set(self.gui.compression_presets[selection]['default_emoji']) self.gui.steps_var.set(self.gui.compression_presets[selection]['steps']) diff --git a/src/sticker_convert/gui_components/windows/advanced_compression_window.py b/src/sticker_convert/gui_components/windows/advanced_compression_window.py index 7696001..dc027aa 100644 --- a/src/sticker_convert/gui_components/windows/advanced_compression_window.py +++ b/src/sticker_convert/gui_components/windows/advanced_compression_window.py @@ -112,6 +112,10 @@ def __init__(self, *args, **kwargs): self.fake_vid_lbl = Label(self.frame_advcomp, text='Convert (faking) image to video') self.fake_vid_cbox = Checkbutton(self.frame_advcomp, variable=self.gui.fake_vid_var, onvalue=True, offvalue=False, bootstyle='success-round-toggle') + self.scale_filter_help_btn = Button(self.frame_advcomp, text='?', width=1, command=lambda: cb_msg_block_adv_comp_win(self.gui.help['comp']['scale_filter']), bootstyle='secondary') + self.scale_filter_lbl = Label(self.frame_advcomp, text='Scale filter') + self.scale_filter_opt = OptionMenu(self.frame_advcomp, self.gui.scale_filter_var, self.gui.scale_filter_var.get(), 'nearest', 'bilnear', 'bicubic', 'lanczos', bootstyle='secondary') + self.cache_dir_help_btn = Button(self.frame_advcomp, text='?', width=1, command=lambda: cb_msg_block_adv_comp_win(self.gui.help['comp']['cache_dir']), bootstyle='secondary') self.cache_dir_lbl = Label(self.frame_advcomp, text='Custom cache directory') self.cache_dir_entry = Entry(self.frame_advcomp, textvariable=self.gui.cache_dir_var, width=30) @@ -190,13 +194,17 @@ def __init__(self, *args, **kwargs): self.fake_vid_lbl.grid(column=1, row=10, sticky='w', padx=3, pady=3) self.fake_vid_cbox.grid(column=6, row=10, sticky='nes', padx=3, pady=3) - self.cache_dir_help_btn.grid(column=0, row=11, sticky='w', padx=3, pady=3) - self.cache_dir_lbl.grid(column=1, row=11, sticky='w', padx=3, pady=3) - self.cache_dir_entry.grid(column=2, row=11, columnspan=4, sticky='nes', padx=3, pady=3) + self.scale_filter_help_btn.grid(column=0, row=11, sticky='w', padx=3, pady=3) + self.scale_filter_lbl.grid(column=1, row=11, sticky='w', padx=3, pady=3) + self.scale_filter_opt.grid(column=2, row=11, columnspan=4, sticky='nes', padx=3, pady=3) + + self.cache_dir_help_btn.grid(column=0, row=12, sticky='w', padx=3, pady=3) + self.cache_dir_lbl.grid(column=1, row=12, sticky='w', padx=3, pady=3) + self.cache_dir_entry.grid(column=2, row=12, columnspan=4, sticky='nes', padx=3, pady=3) - self.default_emoji_help_btn.grid(column=0, row=12, sticky='w', padx=3, pady=3) - self.default_emoji_lbl.grid(column=1, row=12, sticky='w', padx=3, pady=3) - self.default_emoji_dsp.grid(column=6, row=12, sticky='nes', padx=3, pady=3) + self.default_emoji_help_btn.grid(column=0, row=13, sticky='w', padx=3, pady=3) + self.default_emoji_lbl.grid(column=1, row=13, sticky='w', padx=3, pady=3) + self.default_emoji_dsp.grid(column=6, row=13, sticky='nes', padx=3, pady=3) # https://stackoverflow.com/questions/43731784/tkinter-canvas-scrollbar-with-grid # Create a frame for the canvas with non-zero row&column weights diff --git a/src/sticker_convert/job_option.py b/src/sticker_convert/job_option.py index af557b7..a6dd85f 100644 --- a/src/sticker_convert/job_option.py +++ b/src/sticker_convert/job_option.py @@ -118,6 +118,7 @@ def __init__(self, comp_config_dict: dict): self.steps = to_int(comp_config_dict.get('steps')) self.fake_vid = comp_config_dict.get('fake_vid') + self.scale_filter = comp_config_dict.get('scale_filter', 'lanczos') self.cache_dir = comp_config_dict.get('cache_dir') self.default_emoji = comp_config_dict.get('default_emoji') self.no_compress = comp_config_dict.get('no_compress') @@ -166,6 +167,7 @@ def to_dict(self) -> dict: }, 'steps': self.steps, 'fake_vid': self.fake_vid, + 'scale_filter': self.scale_filter, 'cache_dir': self.cache_dir, 'default_emoji': self.default_emoji, 'no_compress': self.no_compress, diff --git a/src/sticker_convert/resources/compression.json b/src/sticker_convert/resources/compression.json index 40572cd..114db4a 100755 --- a/src/sticker_convert/resources/compression.json +++ b/src/sticker_convert/resources/compression.json @@ -36,6 +36,7 @@ }, "steps": 16, "fake_vid": false, + "scale_filter": "lanczos", "default_emoji": "😀" }, "signal": { @@ -75,6 +76,7 @@ }, "steps": 16, "fake_vid": false, + "scale_filter": "lanczos", "default_emoji": "😀" }, "telegram": { @@ -114,6 +116,7 @@ }, "steps": 16, "fake_vid": false, + "scale_filter": "lanczos", "default_emoji": "😀" }, "whatsapp": { @@ -153,6 +156,7 @@ }, "steps": 16, "fake_vid": true, + "scale_filter": "lanczos", "default_emoji": "😀" }, "line": { @@ -192,6 +196,7 @@ }, "steps": 16, "fake_vid": false, + "scale_filter": "lanczos", "default_emoji": "😀" }, "kakao": { @@ -231,6 +236,7 @@ }, "steps": 16, "fake_vid": false, + "scale_filter": "lanczos", "default_emoji": "😀" }, "imessage_small": { @@ -270,6 +276,7 @@ }, "steps": 8, "fake_vid": false, + "scale_filter": "lanczos", "default_emoji": "😀" }, "imessage_medium": { @@ -309,6 +316,7 @@ }, "steps": 8, "fake_vid": false, + "scale_filter": "lanczos", "default_emoji": "😀" }, "imessage_large": { @@ -348,6 +356,7 @@ }, "steps": 8, "fake_vid": false, + "scale_filter": "lanczos", "default_emoji": "😀" }, "custom": { @@ -387,6 +396,7 @@ }, "steps": 16, "fake_vid": false, + "scale_filter": "lanczos", "default_emoji": "😀" } } \ No newline at end of file diff --git a/src/sticker_convert/resources/help.json b/src/sticker_convert/resources/help.json index 3252145..8dda760 100644 --- a/src/sticker_convert/resources/help.json +++ b/src/sticker_convert/resources/help.json @@ -41,6 +41,7 @@ "vid_format": "Set file format if input is animated.", "img_format": "Set file format if input is static.", "fake_vid": "Convert (faking) image to video.\nUseful if:\n(1) Size limit for video is larger than image;\n(2) Mix image and video into same pack.", + "scale_filter": "Set scale filter. Default as lanczos. Valid options are:\n- nearest = Use nearest neighbour (Suitable for pixel art)\n- bilnear = linear interpolation\n- bicubic = Cubic spline interpolation\n- lanczos = A high-quality downsampling filter", "cache_dir": "Set custom cache directory.\nUseful for debugging, or speed up conversion if cache_dir is on RAM disk.", "default_emoji": "Set the default emoji for uploading Signal and Telegram sticker packs." },