Post by Uncle Buddy on Nov 25, 2022 3:15:17 GMT -8
<drive>:\treebard_gps\app\python\graphics.py Last Changed 2022-12-04
# graphics
import os
import tkinter as tk
from tkinter import colorchooser
import sqlite3
from PIL import Image, ImageTk, ImageDraw, ImageFont
from files import current_drive, get_current_file
from widgets import (
Toplevel, Frame, Label, Entry, Button, Scrollbar, Border, Canvas,
LabelStay, LabelHeader, LabelFrame, Radiobutton, CanvasHilited, open_message)
from utes import center_dialog, stop_flashing
from messages import graphics_msg
from query_strings import (
insert_image_new, select_image_caption, update_image_caption)
import dev_tools as dt
from dev_tools import looky, seeline
WIDTH, HEIGHT = 900, 900
def go_to_graphics(graphics_tab_frame, out_path, main):
'''
If frame with this name already exists it's replaced:
https://stackoverflow.com/questions/59518905/
naming-a-widget-to-auto-destroy-replace-it
'''
picwin = Frame(graphics_tab_frame, name='exists')
picwin.grid()
main.to_destroy.append(picwin)
edit_pic = Image.open(out_path)
edit_img = ImageTk.PhotoImage(edit_pic)
editlab = LabelStay(picwin, image=edit_img)
editlab.image = edit_img
editlab.grid() # When this grids a big pic, the whole tabbook gets big
# prevent large pics from blowing up size of the whole tabbook
# when placed here by edit button on a gallery
# Will need more attention when ready to make the graphics tab.
editlab.config(width=700, height=700)
def err_done(entry, msg):
msg[0].grab_release()
msg[0].destroy()
entry.focus_set()
class BorderTextTool(Toplevel):
def __init__(
self, master, root, treebard, tabbook, img, in_path=None,
*args, **kwargs):
Toplevel.__init__(self, master, *args, **kwargs)
self.master = master
self.root = root
self.treebard = treebard
self.tabbook = tabbook
self.graphics_tab_frame = self.tabbook.store["graphics"]
img = img
self.in_path = in_path
MAXW = int(self.winfo_screenwidth() * 0.9)
MAXH = int(self.winfo_screenheight() * 0.9)
self.maxsize(width=MAXW, height= MAXH)
self.title("Add Border with Optional Caption")
self.caption = ""
self.original_image_path = tk.StringVar(None, self.in_path)
self.north_south = tk.IntVar(None, 1)
self.black_white = tk.IntVar(None, 0)
self.make_widgets(img)
self.make_inputs()
self.canvas.config(scrollregion=self.canvas.bbox('all'))
center_dialog(self)
self.grab_set()
self.protocol("WM_DELETE_WINDOW", self.cancel)
self.text_input.focus_set()
def make_widgets(self, img):
self.canvas = CanvasHilited(
self,
width=img.width, height=img.height,
borderwidth=0, highlightthickness=0)
image = ImageTk.PhotoImage(img)
self.canvas.img = image
self.canvas.create_image(0, 0, image=image, anchor="nw")
self.sbv = Scrollbar(
self,
command=self.canvas.yview,
hideable=True)
self.canvas.config(yscrollcommand=self.sbv.set)
self.sbh = Scrollbar(
self,
orient='horizontal',
command=self.canvas.xview,
hideable=True)
self.canvas.config(xscrollcommand=self.sbh.set)
def make_inputs(self):
controls = Frame(self)
instrux = LabelHeader(
controls,
text="Borders will be 1% of image width. Text will span 100% of "
"image width if left blank. For dark background (border color) "
"use light text color & vice versa.",
wraplength=600)
textshow = Label(
controls, textvariable=self.original_image_path,
wraplength=600, justify="left")
textlab = Label(controls, text="Text:")
self.text_input = Entry(controls)
bglab = Label(controls, text="Background Color:")
self.bg_input = Entry(controls)
self.bg_input.bind("<Double-Button-1>", self.open_color_chooser)
self.bg_input.bind("<space>", self.open_color_chooser)
widthlab = Label(controls, text="Caption Width (% of Image Width):")
self.width_input = Entry(controls, width=6)
radframe = LabelFrame(controls, text="Caption Position and Color")
radframe_ns = Frame(radframe)
radio_n = Radiobutton(radframe_ns, text="above image", variable=self.north_south, value=0)
radio_s = Radiobutton(radframe_ns, text="below image", variable=self.north_south, value=1)
radframe_bw = Frame(radframe)
radio_b = Radiobutton(radframe_bw, text="black", variable=self.black_white, value=0)
radio_w = Radiobutton(radframe_bw, text="white", variable=self.black_white, value=1)
previewer = Button(controls, text="PREVIEW EDITED IMAGE", command=self.make_image)
savelab = Label(controls, text="Save in current project as:")
self.save_input = Entry(controls)
buttons = Frame(controls)
saver = Button(buttons, text="SAVE", command=self.save)
canceler = Button(buttons, text="CANCEL", command=self.cancel)
# children of self
self.columnconfigure(0, weight=1)
self.rowconfigure(1, weight=1)
controls.grid(column=0, row=0, sticky="news", padx=12, pady=12)
self.sbv.grid(column=1, row=1, sticky='ns')
self.canvas.grid(column=0, row=1, sticky="news", padx=12, pady=12)
self.sbh.grid(column=0, row=2, sticky='ew')
# children of controls
controls.columnconfigure(1, weight=1)
instrux.grid(column=0, row=0, ipadx=6, ipady=6, columnspan=2, pady=(6,0))
textshow.grid(column=0, row=1, columnspan=2, pady=(6,0))
textlab.grid(column=0, row=2, sticky="e", pady=(6,0))
self.text_input.grid(column=1, row=2, sticky="ew", pady=(6,0))
bglab.grid(column=0, row=3, sticky="e", pady=(6,0))
self.bg_input.grid(column=1, row=3, sticky="w", pady=(6,0))
widthlab.grid(column=0, row=4, sticky="e", pady=(6,0))
self.width_input.grid(column=1, row=4, sticky="w", pady=(6,0))
radframe.grid(column=0, row=5, pady=(6,0))
previewer.grid(column=1, row=5, sticky="se", pady=(6,0))
savelab.grid(column=0, row=6, sticky="e", pady=(6,0))
self.save_input.grid(column=1, row=6, sticky="ew", pady=(6,0))
buttons.grid(column=1, row=7, sticky="e", pady=(6,0))
# children of buttons
saver.grid(column=0, row=0, sticky="e")
canceler.grid(column=1, row=0, sticky="e")
# children of radframe
radframe_ns.grid(column=0, row=0)
radframe_bw.grid(column=0, row=1)
# children of radframe_ns
radio_n.grid(column=0, row=0, sticky="w")
radio_s.grid(column=1, row=0, sticky="w")
# children of radframe_bw
radio_b.grid(column=0, row=0, sticky="w")
radio_w.grid(column=1, row=0, sticky="w")
def make_image(self):
bg_color = "white"
max_width_ratio = 95
font_color = "black"
font_family = "arial.ttf"
text = self.text_input.get()
bg = self.bg_input.get().strip()
user_input = self.width_input.get().strip()
if len(user_input) != 0:
user_ratio = int(user_input)
if user_ratio > 0 and user_ratio < max_width_ratio:
pass
else:
user_ratio = max_width_ratio
else:
user_ratio = max_width_ratio
img = Image.open(self.in_path)
width, height = img.size
bd = int(0.01 * width)
caption_space = height / 9
if len(text) != 0:
self.caption = text
else:
caption_space = bd
if len(bg) != 0:
bg_color = bg
if self.black_white.get() == 1:
font_color = "white"
if self.north_south.get() == 0:
paste_position = (bd, int(caption_space), (width + bd), int((caption_space) + height))
elif self.north_south.get() == 1:
paste_position = (bd, bd, (width + bd), (height + bd))
self.preview = Image.new("RGBA", (width + (bd * 2), height + int((caption_space) + bd)), bg_color)
self.preview.paste(img, paste_position)
if len(text) != 0:
font = ImageFont.truetype(font_family, 100)
sample_width, sample_height = font.getsize(self.caption)
user_width = int((user_ratio / 100) * width)
sample_ratio = user_width / sample_width
font_size = int(sample_ratio * 100)
font = ImageFont.truetype(font_family, font_size)
font_width, font_height = font.getsize(self.caption)
if self.north_south.get() == 0:
caption_place = ((bd + width - font_width) / 2, (((caption_space) - font_height) / 2))
elif self.north_south.get() == 1:
caption_place = ((bd + width - font_width) / 2, (height + ((caption_space) - font_height) / 2))
draw = ImageDraw.Draw(self.preview)
draw.text(
(caption_place),
self.caption, font=font,
fill=font_color)
image = ImageTk.PhotoImage(self.preview)
self.canvas.img = image
self.canvas.create_image(0, 0, image=image, anchor="nw")
stop_flashing(self)
self.canvas.config(scrollregion=self.canvas.bbox('all'))
def open_color_chooser(self, evt):
chosen_color = colorchooser.askcolor(parent=self.master)[1]
if chosen_color:
evt.widget.delete(0, 'end')
evt.widget.insert(0, chosen_color)
def save(self):
tree, current_dir = get_current_file()
conn = sqlite3.connect(tree)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
filename = self.save_input.get().strip()
if len(filename) == 0:
msg1 = open_message(
self,
graphics_msg[0],
"File Name Required",
"OK")
msg1[0].grab_set()
msg1[2].config(
command=lambda entry=self.save_input, msg=msg1: err_done(entry, msg))
return
cur.execute(insert_image_new, ("{}.png".format(filename), self.caption))
conn.commit()
self.out_path = "{}treebard_gps/data/{}/images/{}.png".format(
current_drive, current_dir, filename)
self.preview.save(self.out_path)
go_to_graphics(self.graphics_tab_frame, self.out_path, self.treebard.main)
self.destroy()
cur.close()
conn.close()
self.treebard.main.edit_image = self.out_path
def cancel(self):
self.treebard.main.edit_image = None
self.destroy()
class CropTool(Toplevel):
def __init__(self, master, root, treebard, tabbook, img, *args, **kwargs):
Toplevel.__init__(self, master, *args, **kwargs)
self.master = master
self.root = root
self.treebard = treebard
self.tabbook = tabbook
self.graphics_tab_frame = self.tabbook.store["graphics"]
self.title("Select Crop Area")
img = img
self.x1, self.y1, self.x2, self.y2 = 0, 0, 0, 0
self.rect_id = None
MAXW = int(self.winfo_screenwidth() * 0.9)
MAXH = int(self.winfo_screenheight() * 0.9)
self.maxsize(width=MAXW, height= MAXH)
self.open_crop_tool(img)
self.protocol("WM_DELETE_WINDOW", self.cancel)
self.grab_set()
def open_crop_tool(self, img):
self.canvas = Canvas(
self,
width=img.width, height=img.height,
borderwidth=0, highlightthickness=0)
image = ImageTk.PhotoImage(img)
self.canvas.img = image
self.canvas.create_image(0, 0, image=image, anchor="nw")
sbv = Scrollbar(
self,
command=self.canvas.yview,
hideable=True)
self.canvas.config(yscrollcommand=sbv.set)
sbh = Scrollbar(
self,
orient='horizontal',
command=self.canvas.xview,
hideable=True)
self.canvas.config(xscrollcommand=sbh.set)
self.rect_id = self.canvas.create_rectangle(
self.x1, self.y1, self.x1, self.y1, dash=(2,2), fill='', outline='white')
self.canvas.bind('<Button-1>', self.get_mouse_posn)
self.canvas.bind('<B1-Motion>', self.update_rectangle)
frame = Frame(self)
lab = Label(frame, text="New File Name:")
self.ent = Entry(frame)
buttons = Frame(frame)
crop_button = Button(
buttons, text="CROP",
command=lambda img=img: self.get_rect(img))
canceler = Button(buttons, text="CANCEL", command=self.cancel)
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
# children of self
self.canvas.grid(column=0, row=0, sticky="news")
sbv.grid(column=1, row=0, sticky='ns')
frame.grid(column=0, row=1, padx=12, pady=(0,12))
sbh.grid(column=0, row=2, sticky='ew')
# children of frame
lab.grid(column=0, row=0, padx=6)
self.ent.grid(column=1, row=0, padx=(0,6), sticky="ew", pady=(6,0))
buttons.grid(column=1, row=1, sticky="e", pady=(12,0))
# children of buttons
crop_button.grid(column=0, row=0, padx=(0,6), sticky="e")
canceler.grid(column=1, row=0, sticky="e")
stop_flashing(self, scrollbar_width=24)
self.canvas.config(scrollregion=self.canvas.bbox('all'))
def get_mouse_posn(self, evt):
self.x1 = self.canvas.canvasx(evt.x)
self.y1 = self.canvas.canvasy(evt.y)
def update_rectangle(self, evt):
self.x2 = self.canvas.canvasx(evt.x)
self.y2 = self.canvas.canvasy(evt.y)
self.canvas.coords(self.rect_id, self.x1, self.y1, self.x2, self.y2)
def get_rect(self, img):
""" Copy the file and crop the copy only. If the original has an alpha
channel, Pillow can only save the copy as a `.png` file so this tool
will only put out `.png` files.
"""
def err_done3():
msg3[0].grab_release()
msg3[0].destroy()
self.destroy()
tree, current_dir = get_current_file()
conn = sqlite3.connect(tree)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
filename = self.ent.get().strip()
for stg in (".jpg", ".gif", ".bmp", ".png"):
if filename.endswith(stg):
filename = filename.rstrip(stg)
break
if len(filename) == 0:
msg2 = open_message(
self,
graphics_msg[0],
"File Name Required",
"OK")
msg2[0].grab_set()
msg2[2].config(
command=lambda entry=self.ent, msg=msg2: err_done(entry, msg))
return
cur.execute(insert_image_new, ("{}.png".format(filename), ""))
conn.commit()
self.out_path = "{}treebard_gps/data/{}/images/{}.png".format(
current_drive, current_dir, filename)
img.save(self.out_path)
self.img2 = Image.open(self.out_path)
rect = self.canvas.coords(self.rect_id)
if rect[2] <= rect[0] or rect[3] <= rect[1]:
msg3 = open_message(
self,
graphics_msg[1],
"Crop Rectangle Not Provided",
"OK")
msg3[0].grab_set()
msg3[2].config(command=err_done3)
return
new_image = self.img2.crop(rect)
new_image.save(self.out_path, quality=100)
go_to_graphics(self.graphics_tab_frame, self.out_path, self.treebard.main)
self.destroy()
cur.close()
conn.close()
self.treebard.main.edit_image = self.out_path
def cancel(self):
self.treebard.main.edit_image = None
self.destroy()
class ResizeTool(Toplevel):
def __init__(self, master, root, treebard, tabbook, img, in_path=None,
*args, **kwargs):
Toplevel.__init__(self, master, *args, **kwargs)
self.master = master
self.root = root
self.treebard = treebard
self.tabbook = tabbook
self.graphics_tab_frame = self.tabbook.store["graphics"]
img = img
self.in_path = in_path
self.title("Resize Image")
MAXW = int(self.winfo_screenwidth() * 0.9)
MAXH = int(self.winfo_screenheight() * 0.9)
self.maxsize(width=MAXW, height= MAXH)
self.open_resize_tool(img)
self.protocol("WM_DELETE_WINDOW", self.cancel)
self.grab_set()
def open_resize_tool(self, img):
self.canvas = Canvas(
self,
width=img.width, height=img.height,
borderwidth=0, highlightthickness=0)
image = ImageTk.PhotoImage(img)
self.canvas.img = image
self.canvas.create_image(0, 0, image=image, anchor="nw")
sbv = Scrollbar(
self,
command=self.canvas.yview,
hideable=True)
self.canvas.config(yscrollcommand=sbv.set)
sbh = Scrollbar(
self,
orient='horizontal',
command=self.canvas.xview,
hideable=True)
self.canvas.config(xscrollcommand=sbh.set)
frame = Frame(self)
instrux = LabelHeader(
frame, justify="left",
text="A copy of the image will be saved as a `.png` file and "
"added to the current tree. Leave the size input blank if no "
"resizing is desired.",
wraplength=480)
sizelab = Label(frame, text="New Image Size (% of original):")
self.size_input = Entry(frame, width=6)
filelab = Label(frame, text="New Image File Name:")
self.file_input = Entry(frame)
buttons = Frame(frame)
resizer = Button(
buttons, text="OK", width=8,
command=lambda img=img: self.save(img))
canceler = Button(buttons, text="CANCEL", command=self.cancel, width=8)
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
# children of self
self.canvas.grid(column=0, row=0, sticky="news", padx=12, pady=12)
sbv.grid(column=1, row=0, sticky='ns')
frame.grid(column=0, row=1, padx=12, pady=(0,12))
sbh.grid(column=0, row=2, sticky='ew')
# children of frame
instrux.grid(column=0, row=0, pady=(0,6), columnspan=2, ipadx=6, ipady=6)
sizelab.grid(column=0, row=1, padx=6, sticky="e")
self.size_input.grid(column=1, row=1, sticky="w", pady=(0,6))
filelab.grid(column=0, row=2, pady=(0,6), sticky="e", padx=6)
self.file_input.grid(column=1, row=2, pady=(0,6), sticky="w", padx=(0, 12))
buttons.grid(column=1, row=3, sticky="e")
# children of buttons
resizer.grid(column=0, row=0, padx=(0,6), sticky="e")
canceler.grid(column=1, row=0, sticky="e")
stop_flashing(self, scrollbar_width=24)
self.canvas.config(scrollregion=self.canvas.bbox('all'))
self.size_input.focus_set()
def save(self, img):
tree, current_dir = get_current_file()
conn = sqlite3.connect(tree)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
ratio = 100
if len(self.size_input.get().strip()) != 0:
ratio = int(self.size_input.get().strip())
ratio = ratio / 100
filename = self.file_input.get().strip()
if len(filename) == 0:
msg5 = open_message(
self,
graphics_msg[0],
"File Name Required",
"OK")
msg5[0].grab_set()
msg5[2].config(
command=lambda entry=self.file_input, msg=msg5: err_done(entry, msg))
return
new_width = int(ratio * img.width)
new_height = int(ratio * img.height)
filename = self.file_input.get().strip()
cur.execute(insert_image_new, ("{}.png".format(filename), ""))
conn.commit()
self.out_path = "{}treebard_gps/data/{}/images/{}.png".format(
current_drive, current_dir, filename)
img2 = img.save(self.out_path)
img2 = Image.open(self.out_path)
new_image = img2.resize((new_width, new_height))
new_image.save(self.out_path, quality=100)
go_to_graphics(self.graphics_tab_frame, self.out_path, self.treebard.main)
self.destroy()
cur.close()
conn.close()
self.treebard.main.edit_image = self.out_path
def cancel(self):
self.treebard.main.edit_image = None
self.destroy()
class CaptionTool(Toplevel):
def __init__(self, master, root, treebard, tabbook, img, img_id=None, in_path=None, *args, **kwargs):
Toplevel.__init__(self, master, *args, **kwargs)
self.master = master
self.root = root
self.treebard = treebard
self.tabbook = tabbook
self.graphics_tab_frame = self.tabbook.store["graphics"]
img = img
self.img_id = img_id
self.in_path = in_path
self.title("Add or Edit an Image Caption")
self.inpathvar = tk.StringVar(0, self.in_path)
MAXW = int(self.winfo_screenwidth() * 0.9)
MAXH = int(self.winfo_screenheight() * 0.9)
self.maxsize(width=MAXW, height= MAXH)
self.open_caption_tool(img)
if self.img_id:
self.get_current_caption()
self.protocol("WM_DELETE_WINDOW", self.cancel)
self.grab_set()
def open_caption_tool(self, img):
self.canvas = Canvas(
self,
width=img.width, height=img.height,
borderwidth=0, highlightthickness=0)
image = ImageTk.PhotoImage(img)
self.canvas.img = image
self.canvas.create_image(0, 0, image=image, anchor="nw")
sbv = Scrollbar(
self,
command=self.canvas.yview,
hideable=True)
self.canvas.config(yscrollcommand=sbv.set)
sbh = Scrollbar(
self,
orient='horizontal',
command=self.canvas.xview,
hideable=True)
self.canvas.config(xscrollcommand=sbh.set)
frame = Frame(self)
instrux = LabelHeader(
frame, justify="left",
text="Unlike the text tool, which places a text graphic on an image "
"border, this tool stores a text caption in the database. The "
"caption can then be displayed separately from the image as "
"text, for example in an image gallery. It doesn't have to be "
"the same as any text that might be added to the image border. "
"This tool doesn't alter the image. It's used to edit a caption "
"for an image that's already in the tree. ",
wraplength=600)
pathlab = Label(frame, textvariable=self.inpathvar)
captionlab = Label(frame, text="New caption for this image:")
self.caption_input = Entry(frame, width=60)
buttons = Frame(frame)
captioner = Button(
buttons, text="OK", width=8,
command=lambda img=img: self.save(img))
canceler = Button(buttons, text="CANCEL", command=self.cancel, width=8)
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
# children of self
self.canvas.grid(column=0, row=0, sticky="news", padx=12, pady=12)
sbv.grid(column=1, row=0, sticky='ns')
frame.grid(column=0, row=1, padx=12, pady=(0,12))
sbh.grid(column=0, row=2, sticky='ew')
# children of frame
instrux.grid(column=0, row=0, pady=(0,6), columnspan=2, ipadx=6, ipady=6)
pathlab.grid(column=0, row=1, pady=(0,6), columnspan=2)
captionlab.grid(column=0, row=2, padx=6, sticky="e", pady=(0,6))
self.caption_input.grid(column=1, row=2, sticky="w", pady=(0,6))
buttons.grid(column=1, row=3, sticky="e")
# children of buttons
captioner.grid(column=0, row=0, padx=(0,6), sticky="e")
canceler.grid(column=1, row=0, sticky="e")
stop_flashing(self, scrollbar_width=24)
self.canvas.config(scrollregion=self.canvas.bbox('all'))
self.caption_input.focus_set()
def get_current_caption(self):
tree, current_dir = get_current_file()
conn = sqlite3.connect(tree)
cur = conn.cursor()
cur.execute(select_image_caption, (self.img_id,))
caption = cur.fetchone()[0]
cur.close()
conn.close()
self.caption_input.delete(0, "end")
self.caption_input.insert(0, caption)
def save(self, img):
tree, current_dir = get_current_file()
conn = sqlite3.connect(tree)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
new_caption = self.caption_input.get().strip()
cur.execute(update_image_caption, (new_caption, self.img_id))
conn.commit()
self.destroy()
cur.close()
conn.close()
def cancel(self):
self.treebard.main.edit_image = None
self.destroy()