-# This application is based on:\r
-# https://github.com/foobar167/junkyard/blob/master/zoom_advanced.py\r
-\r
-# -*- coding: utf-8 -*-\r
-# Advanced zoom example. Like in Google Maps.\r
-# It zooms only a tile, but not the whole image. So the zoomed tile occupies\r
-# constant memory and not cram it with a huge resized image for the large zooms.\r
-\r
-import tkinter as tk\r
-from tkinter import ttk\r
-from PIL import Image, ImageTk\r
-import os\r
-import argparse\r
-# sudo apt-get install python3-pil.imagetk\r
-\r
-parser = argparse.ArgumentParser()\r
-parser.add_argument('-i', '--input', type=str, required=True, help="input directory")\r
-parser.add_argument('-o', '--output', type=str, default="", help="output directory")\r
-\r
-\r
-class AutoScrollbar(ttk.Scrollbar):\r
- def set(self, lo, hi):\r
- self.grid_remove()\r
-\r
- def pack(self, **kw):\r
- raise tk.TclError('Cannot use pack with this widget')\r
-\r
- def place(self, **kw):\r
- raise tk.TclError('Cannot use place with this widget')\r
-\r
-\r
-def load_paths(in_path, out_path):\r
- in_list = []\r
- out_list = []\r
- for fname in os.listdir(in_path):\r
- in_list.append(os.path.join(in_path, fname))\r
- out_list.append(os.path.join(out_path, fname.split('.')[0] + "-crop.png"))\r
- return in_list, out_list\r
-\r
-\r
-class ZoomAdvanced(ttk.Frame):\r
- # Advanced zoom of the image\r
- def __init__(self, mainframe, input, output):\r
- # Initialize the main Frame\r
- ttk.Frame.__init__(self, master=mainframe)\r
- # Vertical and horizontal scrollbars for canvas\r
- vbar = AutoScrollbar(self.master, orient='vertical')\r
- hbar = AutoScrollbar(self.master, orient='horizontal')\r
- vbar.grid(row=0, column=1, sticky='ns')\r
- hbar.grid(row=1, column=0, sticky='we')\r
- # Create canvas and put image on it\r
- self.canvas = tk.Canvas(self.master, highlightthickness=0,\r
- xscrollcommand=hbar.set, yscrollcommand=vbar.set)\r
- self.canvas.grid(row=0, column=0, sticky='nswe')\r
- self.canvas.update() # wait till canvas is created\r
- vbar.configure(command=self.scroll_y) # bind scrollbars to the canvas\r
- hbar.configure(command=self.scroll_x)\r
- # Make the canvas expandable\r
- self.master.rowconfigure(0, weight=1)\r
- self.master.columnconfigure(0, weight=1)\r
- # Bind events to the Canvas\r
- self.canvas.bind('<Configure>', self.show_image) # canvas is resized\r
- self.canvas.bind('<ButtonPress-1>', self.move_from)\r
- self.canvas.bind('<B1-Motion>', self.move_to)\r
- self.canvas.bind('<MouseWheel>', self.wheel) # with Windows and MacOS, but not Linux\r
- self.canvas.bind('<Button-5>', self.wheel) # only with Linux, wheel scroll down\r
- self.canvas.bind('<Button-4>', self.wheel) # only with Linux, wheel scroll up\r
- self.canvas.bind_all('<Return>', self.save_img)\r
-\r
- self.in_paths, self.out_paths = load_paths(input, output)\r
- self.counter = 0\r
- self.image = None\r
- self.destination = None\r
- self.width = 0\r
- self.height = 0\r
- self.imscale = 1.0\r
- self.delta = 1.1\r
- self.container = None\r
- self.img = None\r
- # open first image\r
- self.load_img()\r
- self.show_image()\r
-\r
- def load_img(self):\r
- while os.path.exists(self.out_paths[self.counter]):\r
- self.counter += 1\r
- self.image = Image.open(self.in_paths[self.counter])\r
- self.destination = self.out_paths[self.counter]\r
- print(str(os.path.basename(self.in_paths[self.counter])))\r
- self.master.title(os.path.basename(self.in_paths[self.counter]))\r
- self.width, self.height = self.image.size\r
- self.imscale = 1.0 # scale for the canvas image\r
- # Put image into container rectangle and use it to set proper coordinates to the image\r
- self.container = self.canvas.create_rectangle(0, 0, self.width, self.height, width=0)\r
- self.img = None\r
-\r
- def save_img(self, event):\r
- print("Saving cropped image...")\r
- self.img.save(self.destination)\r
- self.counter += 1\r
- if self.counter < len(self.in_paths):\r
- print("Loading new image...")\r
- self.load_img()\r
- self.show_image()\r
- else:\r
- print("Exiting...")\r
- self.master.destroy()\r
-\r
- def scroll_y(self, *args, **kwargs):\r
- # Scroll canvas vertically and redraw the image\r
- self.canvas.yview(*args, **kwargs) # scroll vertically\r
- self.show_image() # redraw the image\r
-\r
- def scroll_x(self, *args, **kwargs):\r
- # Scroll canvas horizontally and redraw the image\r
- self.canvas.xview(*args, **kwargs) # scroll horizontally\r
- self.show_image() # redraw the image\r
-\r
- def move_from(self, event):\r
- # Remember previous coordinates for scrolling with the mouse\r
- self.canvas.scan_mark(event.x, event.y)\r
-\r
- def move_to(self, event):\r
- # Drag (move) canvas to the new position\r
- self.canvas.scan_dragto(event.x, event.y, gain=1)\r
- self.show_image() # redraw the image\r
-\r
- def wheel(self, event):\r
- # Zoom with mouse wheel\r
- x = self.canvas.canvasx(event.x)\r
- y = self.canvas.canvasy(event.y)\r
- bbox = self.canvas.bbox(self.container) # get image area\r
- if bbox[0] < x < bbox[2] and bbox[1] < y < bbox[3]:\r
- pass # Ok! Inside the image\r
- else:\r
- return # zoom only inside image area\r
- scale = 1.0\r
- # Respond to Linux (event.num) or Windows (event.delta) wheel event\r
- if event.num == 5 or event.delta == -120: # scroll down\r
- i = min(self.width, self.height)\r
- if int(i * self.imscale) < 30:\r
- return # image is less than 30 pixels\r
- self.imscale /= self.delta\r
- scale /= self.delta\r
- if event.num == 4 or event.delta == 120: # scroll up\r
- i = min(self.canvas.winfo_width(), self.canvas.winfo_height())\r
- if i < self.imscale:\r
- return # 1 pixel is bigger than the visible area\r
- self.imscale *= self.delta\r
- scale *= self.delta\r
- self.canvas.scale('all', x, y, scale, scale) # rescale all canvas objects\r
- self.show_image()\r
-\r
- def show_image(self, event=None):\r
- # Show image on the Canvas\r
- bbox1 = self.canvas.bbox(self.container) # get image area\r
- # Remove 1 pixel shift at the sides of the bbox1\r
- bbox1 = (bbox1[0] + 1, bbox1[1] + 1, bbox1[2] - 1, bbox1[3] - 1)\r
- bbox2 = (self.canvas.canvasx(0), # get visible area of the canvas\r
- self.canvas.canvasy(0),\r
- self.canvas.canvasx(self.canvas.winfo_width()),\r
- self.canvas.canvasy(self.canvas.winfo_height()))\r
- bbox = [min(bbox1[0], bbox2[0]), min(bbox1[1], bbox2[1]), # get scroll region box\r
- max(bbox1[2], bbox2[2]), max(bbox1[3], bbox2[3])]\r
- if bbox[0] == bbox2[0] and bbox[2] == bbox2[2]: # whole image in the visible area\r
- bbox[0] = bbox1[0]\r
- bbox[2] = bbox1[2]\r
- if bbox[1] == bbox2[1] and bbox[3] == bbox2[3]: # whole image in the visible area\r
- bbox[1] = bbox1[1]\r
- bbox[3] = bbox1[3]\r
- self.canvas.configure(scrollregion=bbox) # set scroll region\r
- x1 = max(bbox2[0] - bbox1[0], 0) # get coordinates (x1,y1,x2,y2) of the image tile\r
- y1 = max(bbox2[1] - bbox1[1], 0)\r
- x2 = min(bbox2[2], bbox1[2]) - bbox1[0]\r
- y2 = min(bbox2[3], bbox1[3]) - bbox1[1]\r
- if int(x2 - x1) > 0 and int(y2 - y1) > 0: # show image if it in the visible area\r
- x = min(int(x2 / self.imscale), self.width) # sometimes it is larger on 1 pixel...\r
- y = min(int(y2 / self.imscale), self.height) # ...and sometimes not\r
- image = self.image.crop((int(x1 / self.imscale), int(y1 / self.imscale), x, y))\r
- imagetk = ImageTk.PhotoImage(image.resize((int(x2 - x1), int(y2 - y1))))\r
- imageid = self.canvas.create_image(max(bbox2[0], bbox1[0]), max(bbox2[1], bbox1[1]),\r
- anchor='nw', image=imagetk)\r
- self.canvas.lower(imageid) # set image into background\r
- self.canvas.imagetk = imagetk # keep an extra reference to prevent garbage-collection\r
- self.img = image.resize((int(x2 - x1), int(y2 - y1)))\r
-\r
-\r
-root = tk.Tk()\r
-root.geometry("500x500")\r
-root.resizable(width=False, height=False)\r
-args = parser.parse_args()\r
-app = ZoomAdvanced(root, args.input, args.output if args.output != "" else args.input)\r
-root.mainloop()\r
+# This application is based on:
+# https://github.com/foobar167/junkyard/blob/master/zoom_advanced.py
+
+# -*- coding: utf-8 -*-
+# Advanced zoom example. Like in Google Maps.
+# It zooms only a tile, but not the whole image. So the zoomed tile occupies
+# constant memory and not cram it with a huge resized image for the large zooms.
+
+import tkinter as tk
+from tkinter import ttk
+from PIL import Image, ImageTk
+import os
+import argparse
+# sudo apt-get install python3-pil.imagetk
+
+parser = argparse.ArgumentParser()
+parser.add_argument('-i', '--input', type=str, required=True, help="input directory")
+parser.add_argument('-o', '--output', type=str, default="", help="output directory")
+
+
+class AutoScrollbar(ttk.Scrollbar):
+ def set(self, lo, hi):
+ self.grid_remove()
+
+ def pack(self, **kw):
+ raise tk.TclError('Cannot use pack with this widget')
+
+ def place(self, **kw):
+ raise tk.TclError('Cannot use place with this widget')
+
+
+def load_paths(in_path, out_path):
+ in_list = []
+ out_list = []
+ for fname in os.listdir(in_path):
+ in_list.append(os.path.join(in_path, fname))
+ out_list.append(os.path.join(out_path, fname.split('.')[0] + "-crop.png"))
+ return in_list, out_list
+
+
+class ZoomAdvanced(ttk.Frame):
+ # Advanced zoom of the image
+ def __init__(self, mainframe, input, output):
+ # Initialize the main Frame
+ ttk.Frame.__init__(self, master=mainframe)
+ # Vertical and horizontal scrollbars for canvas
+ vbar = AutoScrollbar(self.master, orient='vertical')
+ hbar = AutoScrollbar(self.master, orient='horizontal')
+ vbar.grid(row=0, column=1, sticky='ns')
+ hbar.grid(row=1, column=0, sticky='we')
+ # Create canvas and put image on it
+ self.canvas = tk.Canvas(self.master, highlightthickness=0,
+ xscrollcommand=hbar.set, yscrollcommand=vbar.set)
+ self.canvas.grid(row=0, column=0, sticky='nswe')
+ self.canvas.update() # wait till canvas is created
+ vbar.configure(command=self.scroll_y) # bind scrollbars to the canvas
+ hbar.configure(command=self.scroll_x)
+ # Make the canvas expandable
+ self.master.rowconfigure(0, weight=1)
+ self.master.columnconfigure(0, weight=1)
+ # Bind events to the Canvas
+ self.canvas.bind('<Configure>', self.show_image) # canvas is resized
+ self.canvas.bind('<ButtonPress-1>', self.move_from)
+ self.canvas.bind('<B1-Motion>', self.move_to)
+ self.canvas.bind('<MouseWheel>', self.wheel) # with Windows and MacOS, but not Linux
+ self.canvas.bind('<Button-5>', self.wheel) # only with Linux, wheel scroll down
+ self.canvas.bind('<Button-4>', self.wheel) # only with Linux, wheel scroll up
+ self.canvas.bind_all('<Return>', self.save_img)
+
+ self.in_paths, self.out_paths = load_paths(input, output)
+ self.counter = 0
+ self.image = None
+ self.destination = None
+ self.width = 0
+ self.height = 0
+ self.imscale = 1.0
+ self.delta = 1.1
+ self.container = None
+ self.img = None
+ # open first image
+ self.load_img()
+ self.show_image()
+
+ def load_img(self):
+ while os.path.exists(self.out_paths[self.counter]):
+ self.counter += 1
+ self.image = Image.open(self.in_paths[self.counter])
+ self.destination = self.out_paths[self.counter]
+ print(str(os.path.basename(self.in_paths[self.counter])))
+ self.master.title(os.path.basename(self.in_paths[self.counter]))
+ self.width, self.height = self.image.size
+ self.imscale = 1.0 # scale for the canvas image
+ # Put image into container rectangle and use it to set proper coordinates to the image
+ self.container = self.canvas.create_rectangle(0, 0, self.width, self.height, width=0)
+ self.img = None
+
+ def save_img(self, event):
+ print("Saving cropped image...")
+ self.img.save(self.destination)
+ self.counter += 1
+ if self.counter < len(self.in_paths):
+ print("Loading new image...")
+ self.load_img()
+ self.show_image()
+ else:
+ print("Exiting...")
+ self.master.destroy()
+
+ def scroll_y(self, *args, **kwargs):
+ # Scroll canvas vertically and redraw the image
+ self.canvas.yview(*args, **kwargs) # scroll vertically
+ self.show_image() # redraw the image
+
+ def scroll_x(self, *args, **kwargs):
+ # Scroll canvas horizontally and redraw the image
+ self.canvas.xview(*args, **kwargs) # scroll horizontally
+ self.show_image() # redraw the image
+
+ def move_from(self, event):
+ # Remember previous coordinates for scrolling with the mouse
+ self.canvas.scan_mark(event.x, event.y)
+
+ def move_to(self, event):
+ # Drag (move) canvas to the new position
+ self.canvas.scan_dragto(event.x, event.y, gain=1)
+ self.show_image() # redraw the image
+
+ def wheel(self, event):
+ # Zoom with mouse wheel
+ x = self.canvas.canvasx(event.x)
+ y = self.canvas.canvasy(event.y)
+ bbox = self.canvas.bbox(self.container) # get image area
+ if bbox[0] < x < bbox[2] and bbox[1] < y < bbox[3]:
+ pass # Ok! Inside the image
+ else:
+ return # zoom only inside image area
+ scale = 1.0
+ # Respond to Linux (event.num) or Windows (event.delta) wheel event
+ if event.num == 5 or event.delta == -120: # scroll down
+ i = min(self.width, self.height)
+ if int(i * self.imscale) < 30:
+ return # image is less than 30 pixels
+ self.imscale /= self.delta
+ scale /= self.delta
+ if event.num == 4 or event.delta == 120: # scroll up
+ i = min(self.canvas.winfo_width(), self.canvas.winfo_height())
+ if i < self.imscale:
+ return # 1 pixel is bigger than the visible area
+ self.imscale *= self.delta
+ scale *= self.delta
+ self.canvas.scale('all', x, y, scale, scale) # rescale all canvas objects
+ self.show_image()
+
+ def show_image(self, event=None):
+ # Show image on the Canvas
+ bbox1 = self.canvas.bbox(self.container) # get image area
+ # Remove 1 pixel shift at the sides of the bbox1
+ bbox1 = (bbox1[0] + 1, bbox1[1] + 1, bbox1[2] - 1, bbox1[3] - 1)
+ bbox2 = (self.canvas.canvasx(0), # get visible area of the canvas
+ self.canvas.canvasy(0),
+ self.canvas.canvasx(self.canvas.winfo_width()),
+ self.canvas.canvasy(self.canvas.winfo_height()))
+ bbox = [min(bbox1[0], bbox2[0]), min(bbox1[1], bbox2[1]), # get scroll region box
+ max(bbox1[2], bbox2[2]), max(bbox1[3], bbox2[3])]
+ if bbox[0] == bbox2[0] and bbox[2] == bbox2[2]: # whole image in the visible area
+ bbox[0] = bbox1[0]
+ bbox[2] = bbox1[2]
+ if bbox[1] == bbox2[1] and bbox[3] == bbox2[3]: # whole image in the visible area
+ bbox[1] = bbox1[1]
+ bbox[3] = bbox1[3]
+ self.canvas.configure(scrollregion=bbox) # set scroll region
+ x1 = max(bbox2[0] - bbox1[0], 0) # get coordinates (x1,y1,x2,y2) of the image tile
+ y1 = max(bbox2[1] - bbox1[1], 0)
+ x2 = min(bbox2[2], bbox1[2]) - bbox1[0]
+ y2 = min(bbox2[3], bbox1[3]) - bbox1[1]
+ if int(x2 - x1) > 0 and int(y2 - y1) > 0: # show image if it in the visible area
+ x = min(int(x2 / self.imscale), self.width) # sometimes it is larger on 1 pixel...
+ y = min(int(y2 / self.imscale), self.height) # ...and sometimes not
+ image = self.image.crop((int(x1 / self.imscale), int(y1 / self.imscale), x, y))
+ imagetk = ImageTk.PhotoImage(image.resize((int(x2 - x1), int(y2 - y1))))
+ imageid = self.canvas.create_image(max(bbox2[0], bbox1[0]), max(bbox2[1], bbox1[1]),
+ anchor='nw', image=imagetk)
+ self.canvas.lower(imageid) # set image into background
+ self.canvas.imagetk = imagetk # keep an extra reference to prevent garbage-collection
+ self.img = image.resize((int(x2 - x1), int(y2 - y1)))
+
+
+root = tk.Tk()
+root.geometry("500x500")
+root.resizable(width=False, height=False)
+args = parser.parse_args()
+app = ZoomAdvanced(root, args.input, args.output if args.output != "" else args.input)
+root.mainloop()