From bd69978e8291e273db7ea098fc2085bde603b6d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl=20BOURGAULT?= Date: Fri, 10 Nov 2017 15:14:23 +0100 Subject: [PATCH] #42: poster saving quality can be chosen and remembered MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Joël BOURGAULT --- photocollage/gtkgui.py | 36 ++++++++++++++++++++++++++++++++- photocollage/render.py | 45 +++++++++++++++++++++++++++++------------- 2 files changed, 66 insertions(+), 15 deletions(-) diff --git a/photocollage/gtkgui.py b/photocollage/gtkgui.py index 5eaa0b8..4dda874 100644 --- a/photocollage/gtkgui.py +++ b/photocollage/gtkgui.py @@ -31,6 +31,8 @@ from photocollage import APP_NAME, artwork, collage, render from photocollage.render import PIL_SUPPORTED_EXTS as EXTS +from photocollage.render import QUALITIES, QUALITY_FAST, QUALITY_BEST,\ + QUALITY_SKEL gettext.textdomain(APP_NAME) @@ -168,6 +170,7 @@ def __init__(self): self.border_c = "black" self.out_w = 800 self.out_h = 600 + self.quality = QUALITY_FAST self.opts = Options() @@ -344,6 +347,7 @@ def on_fail(exception): border_width=self.opts.border_w * max(collage.page.w, collage.page.h), border_color=self.opts.border_c, + quality=QUALITY_FAST, # for preview, use NEAREST on_update=gtk_run_in_main_thread(on_update), on_complete=gtk_run_in_main_thread(on_complete), on_fail=gtk_run_in_main_thread(on_fail)) @@ -392,7 +396,8 @@ def save_poster(self, button): enlargement = float(self.opts.out_w) / collage.page.w collage.page.scale(enlargement) - dialog = Gtk.FileChooserDialog(_("Save image"), button.get_toplevel(), + dialog = Gtk.FileChooserDialog(_("Save image"), + button.get_toplevel(), Gtk.FileChooserAction.SAVE) dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) dialog.add_button(Gtk.STOCK_OK, Gtk.ResponseType.OK) @@ -427,6 +432,7 @@ def on_fail(exception): collage.page, output_file=savefile, border_width=self.opts.border_w * max(collage.page.w, collage.page.h), + quality=self.opts.quality, # for saving, use user setting border_color=self.opts.border_c, on_update=gtk_run_in_main_thread(on_update), on_complete=gtk_run_in_main_thread(on_complete), @@ -714,6 +720,29 @@ def apply_template(combo): vbox.pack_start(Gtk.SeparatorToolItem(), True, True, 0) + label = Gtk.Label(xalign=0) + label.set_markup("%s" % _("Image quality")) + vbox.pack_start(label, False, False, 0) + + box = Gtk.Box(spacing=6) + vbox.pack_start(box, False, False, 0) + label = Gtk.Label(_("Resizing method:"), xalign=1) + box.pack_start(label, False, False, 0) + + self.qualities = ( + (QUALITIES[QUALITY_SKEL]['str'], QUALITY_SKEL), + (QUALITIES[QUALITY_FAST]['str'], QUALITY_FAST), + (QUALITIES[QUALITY_BEST]['str'], QUALITY_BEST), + ) + + self.cmb_quality = Gtk.ComboBoxText() + for q, r in self.qualities: + self.cmb_quality.append(q, q) + self.cmb_quality.set_active(parent.opts.quality - 1) + box.pack_end(self.cmb_quality, False, False, 0) + + vbox.pack_start(Gtk.SeparatorToolItem(), True, True, 0) + self.show_all() def validate_int(self, entry): @@ -737,6 +766,11 @@ def apply_opts(self, opts): opts.out_h = int(self.etr_outh.get_text() or '1') opts.border_w = float(self.etr_border.get_text() or '0') / 100.0 opts.border_c = self.colorbutton.get_rgba().to_string() + # TODO: test following line; not sure it works that easily. + q = self.cmb_quality.get_model()[ + self.cmb_quality.get_active_iter()][1] + if q: + opts.quality = dict(self.qualities)[q] class ComputingDialog(Gtk.Dialog): diff --git a/photocollage/render.py b/photocollage/render.py index f86258d..d0eeb93 100644 --- a/photocollage/render.py +++ b/photocollage/render.py @@ -15,6 +15,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +import gettext import random from threading import Thread import time @@ -22,12 +23,24 @@ import PIL.Image import PIL.ImageDraw +from photocollage import APP_NAME from photocollage.collage import Photo +gettext.textdomain(APP_NAME) +_ = gettext.gettext +_n = gettext.ngettext -QUALITY_SKEL = 0 -QUALITY_FAST = 1 -QUALITY_BEST = 2 +QUALITY_SKEL = 1 +QUALITY_FAST = 2 +QUALITY_BEST = 3 +QUALITIES = { + QUALITY_SKEL: {'str': _("Skeleton (no rendering)"), + 'filter': None}, + QUALITY_FAST: {'str': _("Standard (nearest neighbor; quicker)"), + 'filter': PIL.Image.NEAREST}, + QUALITY_BEST: {'str': _("Best (antialiasing; slower)"), + 'filter': PIL.Image.ANTIALIAS}, +} class PIL_SUPPORTED_EXTS(object): @@ -213,21 +226,24 @@ def resize_photo(self, cell, use_cache=False): elif cell.photo.orientation == 8: img = img.rotate(90, expand=True) - if self.quality == QUALITY_FAST: - method = PIL.Image.NEAREST - else: - method = PIL.Image.ANTIALIAS + method = QUALITIES[self.quality]['filter'] shape = img.size[0] * cell.h - img.size[1] * cell.w if shape > 0: # image is too thick - img = img.resize((int(round(cell.h * img.size[0] / img.size[1])), - int(round(cell.h))), method) + img = img.resize( + (int(round(cell.h * img.size[0] / img.size[1])), + int(round(cell.h))), + method) elif shape < 0: # image is too tall - img = img.resize((int(round(cell.w)), - int(round(cell.w * img.size[1] / img.size[0]))), - method) + img = img.resize( + (int(round(cell.w)), + int(round(cell.w * img.size[1] / img.size[0]))), + method) else: - img = img.resize((int(round(cell.w)), int(round(cell.h))), method) + img = img.resize( + (int(round(cell.w)), + int(round(cell.h))), + method) # Save this new image to cache (if it is larger than the previous one) if (use_cache and (cell.photo.filename not in cache or @@ -269,7 +285,8 @@ def run(self): if self.quality != QUALITY_SKEL: n = sum([len([cell for cell in col.cells if not - cell.is_extension()]) for col in self.page.cols]) + cell.is_extension()]) + for col in self.page.cols]) i = 0.0 if self.on_update: self.on_update(canvas, 0.0)