#!/usr/bin/python3 # cropgui, a graphical front-end for lossless jpeg cropping # Copyright (C) 2009 Jeff Epler # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from cropgui_common import * from cropgui_common import _ import tkinter from PIL import ImageTk import tkinter.filedialog import sys import os import signal import log app = tkinter.Tk() app.wm_title(_("CropGUI -- lossless cropping and rotation of jpeg files")) app.wm_iconname(_("CropGUI")) preview = tkinter.Label(app) preview.pack(side="bottom") do_crop = tkinter.Button(app, text="Crop") do_crop.pack(side="left") crop169_button = tkinter.Menubutton(app, text="16:9") crop169_button.pack(side="left") crop169 = tkinter.Menu(crop169_button) crop169_button.config(menu=crop169) crop85_button = tkinter.Menubutton(app, text="8:5") crop85_button.pack(side="left") crop85 = tkinter.Menu(crop85_button) crop85_button.config(menu=crop85) crop32_button = tkinter.Menubutton(app, text="3:2") crop32_button.pack(side="left") crop32 = tkinter.Menu(crop32_button) crop32_button.config(menu=crop32) crop43_button = tkinter.Menubutton(app, text="4:3") crop43_button.pack(side="left") crop43 = tkinter.Menu(crop43_button) crop43_button.config(menu=crop43) crop11_button = tkinter.Menubutton(app, text="1:1") crop11_button.pack(side="left") crop11 = tkinter.Menu(crop11_button) crop11_button.config(menu=crop11) crop34_button = tkinter.Menubutton(app, text="3:4") crop34_button.pack(side="left") crop34 = tkinter.Menu(crop34_button) crop34_button.config(menu=crop34) crop23_button = tkinter.Menubutton(app, text="2:3") crop23_button.pack(side="left") crop23 = tkinter.Menu(crop23_button) crop23_button.config(menu=crop23) info = tkinter.Label(app) info.pack(side="left") task = CropTask(log) class DragManager(DragManagerBase): def __init__(self, w, b, inf): self.l = w b.configure(command=self.done) self.inf = inf w.bind("", self.press) w.bind("", self.shift_press) w.bind("", self.double) w.bind("", self.motion) w.bind("", self.idle_motion) w.bind("", self.end) w.bind("", self.enter) w.bind("", self.leave) app.bind("", self.double) app.bind("", self.escape) w.bind("", "#nothing") w.bind("", "#nothing") dummy_image = Image.new('L', (int(max_w/2),int(max_h/2)), 0xff) self.dummy_tkimage = ImageTk.PhotoImage(dummy_image) self.v = tkinter.IntVar(app) DragManagerBase.__init__(self) def image_set(self): if self.image is None: self.l.configure(image=self.dummy_tkimage) self.inf.configure(text="\n\n") else: self.tkimage = ImageTk.PhotoImage(self.image) self.l.configure(image=self.tkimage) self.render() def render(self): if not self.render_flag: self.render_flag = True app.after_idle(self.do_render) def do_render(self): if not self.render_flag: return self.render_flag = False if self.image is None: self.l.configure(image=self.dummy_tkimage) self.inf.configure(text="\n\n") return tt, ll, rr, bb = self.get_corners() ratio = self.describe_ratio() self.inf.configure(text= "Left: %4d Top: %4d Right: %4d Bottom: %4d\n" "Width: %4d Height: %4d Ratio: %8s\n" % (ll, tt, rr, bb, rr-ll, bb-tt, ratio), font="fixed", justify="l", anchor="w") self.tkimage.paste(self.rendered()) def enter(self, event): self.show_handles = True self.render() def leave(self, event): self.show_handles = False self.render() def press(self, event): self.drag_start(event.x, event.y, False) def shift_press(self, event): self.drag_start(event.x, event.y, True) def idle_motion(self, event): what = self.classify(event.x, event.y) if busy: cursor = "watch" else: cursor = self.cursor_map.get(what, "") self.l.configure(cursor=cursor) def motion(self, event): self.drag_continue(event.x, event.y) def end(self, event): self.drag_end(event.x, event.y) def close(self): self.v.set(-1) def cancel(self): self.v.set(0) def escape(self, event): self.cancel() def done(self): self.v.set(1) def double(self, event): self.done() def wait(self): app.wait_variable(self.v) value = self.v.get() return value max_h = app.winfo_screenheight() - 64 - 32 max_w = app.winfo_screenwidth() - 64 drag = DragManager(preview, do_crop, info) app.wm_protocol('WM_DELETE_WINDOW', drag.close) crop169.add_command(label='1920 x 1080',command=lambda: drag.set_stdsize(1920,1080)) crop169.add_command(label='3840 x 2160',command=lambda: drag.set_stdsize(3840,2160)) crop169.add_command(label='4000 x 2248',command=lambda: drag.set_stdsize(4000,2248)) crop85.add_command (label='1920 x 1200',command=lambda: drag.set_stdsize(1920,1200)) crop85.add_command (label='3456 x 2160',command=lambda: drag.set_stdsize(3456,2160)) crop85.add_command (label='4000 x 2496',command=lambda: drag.set_stdsize(4000,2496)) crop32.add_command (label='1136 x 760',command=lambda: drag.set_stdsize(1136, 760)) crop32.add_command (label='1440 x 960',command=lambda: drag.set_stdsize(1440, 960)) crop32.add_command (label='1536 x 1024',command=lambda: drag.set_stdsize(1536,1024)) crop32.add_command (label='1752 x 1168',command=lambda: drag.set_stdsize(1752,1168)) crop32.add_command (label='2048 x 1360',command=lambda: drag.set_stdsize(2048,1360)) crop32.add_command (label='2592 x 1728',command=lambda: drag.set_stdsize(2592,1728)) crop32.add_command (label='3072 x 2048',command=lambda: drag.set_stdsize(3072,2048)) crop32.add_command (label='3240 x 2160',command=lambda: drag.set_stdsize(3240,2160)) crop32.add_command (label='4000 x 2664',command=lambda: drag.set_stdsize(4000,2664)) crop43.add_command (label='1280 x 960',command=lambda: drag.set_stdsize(1280, 960)) crop43.add_command (label='1600 x 1200',command=lambda: drag.set_stdsize(1600,1200)) crop43.add_command (label='1720 x 1280',command=lambda: drag.set_stdsize(1720,1280)) crop43.add_command (label='2048 x 1536',command=lambda: drag.set_stdsize(2048,1536)) crop43.add_command (label='2560 x 1920',command=lambda: drag.set_stdsize(2560,1920)) crop43.add_command (label='2880 x 2160',command=lambda: drag.set_stdsize(2880,2160)) crop43.add_command (label='4000 x 3000',command=lambda: drag.set_stdsize(4000,3000)) crop43.add_command (label='4320 x 3240',command=lambda: drag.set_stdsize(4320,3240)) crop11.add_command (label='3000 x 3000',command=lambda: drag.set_stdsize(3000,3000)) crop34.add_command (label=' 960 x 1280',command=lambda: drag.set_stdsize( 960,1280)) crop34.add_command (label='1200 x 1600',command=lambda: drag.set_stdsize(1200,1600)) crop34.add_command (label='1280 x 1720',command=lambda: drag.set_stdsize(1280,1720)) crop34.add_command (label='1536 x 2048',command=lambda: drag.set_stdsize(1536,2048)) crop34.add_command (label='1920 x 2560',command=lambda: drag.set_stdsize(1920,2560)) crop34.add_command (label='2160 x 2880',command=lambda: drag.set_stdsize(2160,2880)) crop34.add_command (label='3000 x 4000',command=lambda: drag.set_stdsize(3000,4000)) crop34.add_command (label='3240 x 4320',command=lambda: drag.set_stdsize(3240,4320)) crop23.add_command (label=' 760 x 1136',command=lambda: drag.set_stdsize( 760,1136)) crop23.add_command (label=' 960 x 1440',command=lambda: drag.set_stdsize( 960,1440)) crop23.add_command (label='1024 x 1536',command=lambda: drag.set_stdsize(1024,1536)) crop23.add_command (label='1168 x 1752',command=lambda: drag.set_stdsize(1168,1752)) crop23.add_command (label='1360 x 2048',command=lambda: drag.set_stdsize(1360,2048)) crop23.add_command (label='1728 x 2592',command=lambda: drag.set_stdsize(1728,2592)) crop23.add_command (label='2048 x 3072',command=lambda: drag.set_stdsize(2048,3072)) crop23.add_command (label='2664 x 4000',command=lambda: drag.set_stdsize(2664,4000)) def image_names(): if len(sys.argv) > 1: for i in sys.argv[1:]: yield i else: while 1: names = tkinter.filedialog.askopenfilenames(master=app, defaultextension=".jpg", multiple=1, parent=app, filetypes=( (_("JPEG Image Files"), ".jpg .JPG .jpeg .JPEG"), (_("All files"), "*"), ), title=_("Select images to crop")) if not names: break for name in names: yield name pids = set() def reap(): global pids pids = set(p for p in pids if p.poll() is None) def set_busy(new_busy=True): global busy busy = new_busy if busy: drag.l.configure(cursor="watch") app.configure(cursor="watch") do_crop.configure(state="disabled") else: drag.l.configure(cursor="") app.configure(cursor="") do_crop.configure(state="normal") app.update_idletasks() cropped_any_image = False try: # Check *only* the first arg to see if it is -round-rb round_right_and_bottom = False if sys.argv[1] == "-round-rb": round_right_and_bottom = True args = [arg for arg in sys.argv[0:] if arg != "-round-rb"] sys.argv = args for image_name in image_names(): drag.save_prev_crop() drag.round_right_and_bottom = round_right_and_bottom # load new image set_busy() image = Image.open(image_name) drag.round_x, drag.round_y = image_round(image) drag.w, drag.h = image.size # compute scale to fit image on display drag.scale=1 drag.scale = max (drag.scale, (drag.w-1)//max_w+1) drag.scale = max (drag.scale, (drag.h-1)//max_h+1) # put image into drag object thumbnail = image.copy() thumbnail.thumbnail((drag.w//drag.scale, drag.h//drag.scale)) drag.image = thumbnail # get user input set_busy(0) v = drag.wait() set_busy() if v == -1: break # user closed app if v == 0: continue # user hit "next" / escape base, ext = os.path.splitext(image_name) target = base + "-crop" + ext task.add(CropRequest( image=image, image_name=image_name, corners=drag.get_corners(), rotation=drag.rotation, target=target, )) cropped_any_image = True finally: task.done() if not cropped_any_image: raise SystemExit(1) # 1. open image # 2. choose 1/2, 1/4, 1/8 scaling so that resized image fits onscreen # 3. load image at requested size # 4. run GUI to get desired crop settings # 5. write output file