update
This commit is contained in:
Binary file not shown.
@@ -1,5 +1,5 @@
|
|||||||
from tkinter import Toplevel, Text
|
from tkinter import Toplevel, Text
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
class LogWindow(Toplevel):
|
class LogWindow(Toplevel):
|
||||||
def __init__(self, master=None, **kwargs):
|
def __init__(self, master=None, **kwargs):
|
||||||
@@ -11,7 +11,7 @@ class LogWindow(Toplevel):
|
|||||||
self.protocol("WM_DELETE_WINDOW", self.hide)
|
self.protocol("WM_DELETE_WINDOW", self.hide)
|
||||||
|
|
||||||
def log(self, message):
|
def log(self, message):
|
||||||
self.text.insert("end", message + "\n")
|
self.text.insert("end", pprint(message) + "\n")
|
||||||
self.text.see("end")
|
self.text.see("end")
|
||||||
|
|
||||||
def hide(self):
|
def hide(self):
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk, colorchooser, messagebox
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
class OptionsWindow(tk.Toplevel):
|
class OptionsWindow(tk.Toplevel):
|
||||||
def __init__(self, parent, apply_callback, current_options):
|
def __init__(self, parent, apply_callback, current_options):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.title("Options")
|
self.title("Options")
|
||||||
self.geometry("400x400")
|
self.geometry("400x500")
|
||||||
|
|
||||||
self.apply_callback = apply_callback
|
self.apply_callback = apply_callback
|
||||||
self.options = current_options
|
self.options = current_options
|
||||||
@@ -32,6 +31,12 @@ class OptionsWindow(tk.Toplevel):
|
|||||||
self.add_text_input(name, details["label"], details["default"])
|
self.add_text_input(name, details["label"], details["default"])
|
||||||
elif details["type"] == "checkbox":
|
elif details["type"] == "checkbox":
|
||||||
self.add_checkbox(name, details["label"], details["default"])
|
self.add_checkbox(name, details["label"], details["default"])
|
||||||
|
elif details["type"] == "dropdown":
|
||||||
|
self.add_dropdown(
|
||||||
|
name, details["label"], details["options"], details["default"]
|
||||||
|
)
|
||||||
|
elif details["type"] == "color":
|
||||||
|
self.add_color_picker(name, details["label"], details["default"])
|
||||||
|
|
||||||
self.create_apply_button()
|
self.create_apply_button()
|
||||||
|
|
||||||
@@ -47,11 +52,11 @@ class OptionsWindow(tk.Toplevel):
|
|||||||
max_val (int): The maximum value.
|
max_val (int): The maximum value.
|
||||||
"""
|
"""
|
||||||
lbl = tk.Label(self, text=label)
|
lbl = tk.Label(self, text=label)
|
||||||
lbl.grid(row=self.row_index, column=0, padx=5, pady=5, sticky="w")
|
lbl.grid(row=self.row_index, columnspan=1,column=0, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
entry = tk.Entry(self)
|
entry = tk.Entry(self)
|
||||||
entry.insert(0, str(default))
|
entry.insert(0, str(default))
|
||||||
entry.grid(row=self.row_index, column=1, padx=5, pady=5, sticky="w")
|
entry.grid(row=self.row_index, columnspan=2, column=1, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
self.inputs[name] = {
|
self.inputs[name] = {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
@@ -75,7 +80,7 @@ class OptionsWindow(tk.Toplevel):
|
|||||||
|
|
||||||
entry = tk.Entry(self)
|
entry = tk.Entry(self)
|
||||||
entry.insert(0, default)
|
entry.insert(0, default)
|
||||||
entry.grid(row=self.row_index, column=1, padx=5, pady=5, sticky="w")
|
entry.grid(row=self.row_index, columnspan=2, column=1, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
self.inputs[name] = {"type": "text", "widget": entry}
|
self.inputs[name] = {"type": "text", "widget": entry}
|
||||||
self.row_index += 1
|
self.row_index += 1
|
||||||
@@ -94,7 +99,78 @@ class OptionsWindow(tk.Toplevel):
|
|||||||
chk.grid(row=self.row_index, column=0,
|
chk.grid(row=self.row_index, column=0,
|
||||||
columnspan=2, padx=5, pady=5, sticky="w")
|
columnspan=2, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
self.inputs[name] = {"type": "checkbox", "variable": var}
|
self.inputs[name] = {"type": "checkbox", "variable": var, 'label': label, 'default': default}
|
||||||
|
self.row_index += 1
|
||||||
|
|
||||||
|
def add_dropdown(self, name, label, options, default):
|
||||||
|
"""
|
||||||
|
Add a dropdown field.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): The name of the dropdown.
|
||||||
|
label (str): The label for the dropdown.
|
||||||
|
options (list): The list of options.
|
||||||
|
default (str): The default value.
|
||||||
|
"""
|
||||||
|
lbl = tk.Label(self, text=label)
|
||||||
|
lbl.grid(row=self.row_index, column=0, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
|
combo = ttk.Combobox(self, values=options, state="readonly")
|
||||||
|
combo.set(default)
|
||||||
|
combo.grid(row=self.row_index,columnspan=2, column=1, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
|
self.inputs[name] = {"type": "dropdown", "widget": combo}
|
||||||
|
self.row_index += 1
|
||||||
|
|
||||||
|
def check_transparent(self, var, color_entry, pick_button, color_preview):
|
||||||
|
if var.get():
|
||||||
|
color_entry.config(state="disabled")
|
||||||
|
pick_button.config(state="disabled")
|
||||||
|
color_preview.config(bg="white")
|
||||||
|
else:
|
||||||
|
color_entry.config(state="normal")
|
||||||
|
pick_button.config(state="normal")
|
||||||
|
color_preview.config(bg=color_entry.get())
|
||||||
|
|
||||||
|
def pick_color(self, color_entry, color_preview):
|
||||||
|
color_code = colorchooser.askcolor(title="Choose color")[1]
|
||||||
|
if color_code:
|
||||||
|
color_entry.delete(0, tk.END)
|
||||||
|
color_entry.insert(0, color_code)
|
||||||
|
color_preview.config(bg=color_code)
|
||||||
|
|
||||||
|
def add_color_picker(self, name, label, default):
|
||||||
|
"""
|
||||||
|
Add a color picker.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): The name of the color picker.
|
||||||
|
label (str): The label for the color picker.
|
||||||
|
default (str): The default color.
|
||||||
|
"""
|
||||||
|
if default == "transparent":
|
||||||
|
default = "#00000000"
|
||||||
|
var = tk.BooleanVar(value=True)
|
||||||
|
else:
|
||||||
|
var = tk.BooleanVar(value=False)
|
||||||
|
lbl = tk.Label(self, text=label)
|
||||||
|
lbl.grid(row=self.row_index, column=0, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
|
color_preview = tk.Label(self, bg=default, width=2, height=1)
|
||||||
|
color_preview.grid(row=self.row_index, column=1, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
|
color_entry = tk.Entry(self)
|
||||||
|
color_entry.insert(0, default)
|
||||||
|
color_entry.grid(row=self.row_index, column=2, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
|
pick_button = tk.Button(self, text="Pick", command=lambda: self.pick_color(color_entry, color_preview))
|
||||||
|
pick_button.grid(row=self.row_index, column=3, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
|
|
||||||
|
chk = tk.Checkbutton(self, text="Transparent", variable=var, command=lambda: self.check_transparent(var, color_entry, pick_button, color_preview))
|
||||||
|
chk.grid(row=self.row_index, column=4, padx=5, pady=5, sticky="w")
|
||||||
|
|
||||||
|
self.inputs[name] = {"type": "color", "entry": color_entry, "transparent_var": var}
|
||||||
self.row_index += 1
|
self.row_index += 1
|
||||||
|
|
||||||
def create_apply_button(self):
|
def create_apply_button(self):
|
||||||
@@ -126,6 +202,70 @@ class OptionsWindow(tk.Toplevel):
|
|||||||
options[name] = details["widget"].get()
|
options[name] = details["widget"].get()
|
||||||
elif details["type"] == "checkbox":
|
elif details["type"] == "checkbox":
|
||||||
options[name] = details["variable"].get()
|
options[name] = details["variable"].get()
|
||||||
|
elif details["type"] == "dropdown":
|
||||||
|
options[name] = details["widget"].get()
|
||||||
|
elif details["type"] == "color":
|
||||||
|
if "value" in details:
|
||||||
|
options[name] = details["value"]
|
||||||
|
else:
|
||||||
|
options[name] = "transparent"
|
||||||
|
|
||||||
self.apply_callback(options)
|
self.apply_callback(options)
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
|
def add_conditional_setting(self, name, condition):
|
||||||
|
"""
|
||||||
|
Add a conditional setting that is displayed based on another setting.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): The name of the conditional setting.
|
||||||
|
condition (function): The condition function that returns a boolean.
|
||||||
|
"""
|
||||||
|
if condition():
|
||||||
|
if self.inputs[name]["type"] == "number":
|
||||||
|
self.add_number_input(
|
||||||
|
name,
|
||||||
|
self.inputs[name]["label"],
|
||||||
|
self.inputs[name]["default"],
|
||||||
|
self.inputs[name]["min"],
|
||||||
|
self.inputs[name]["max"],
|
||||||
|
)
|
||||||
|
elif self.inputs[name]["type"] == "text":
|
||||||
|
self.add_text_input(
|
||||||
|
name, self.inputs[name]["label"], self.inputs[name]["default"]
|
||||||
|
)
|
||||||
|
elif self.inputs[name]["type"] == "checkbox":
|
||||||
|
self.add_checkbox(
|
||||||
|
name, self.inputs[name]["label"], self.inputs[name]["default"]
|
||||||
|
)
|
||||||
|
elif self.inputs[name]["type"] == "dropdown":
|
||||||
|
self.add_dropdown(
|
||||||
|
name,
|
||||||
|
self.inputs[name]["label"],
|
||||||
|
self.inputs[name]["options"],
|
||||||
|
self.inputs[name]["default"],
|
||||||
|
)
|
||||||
|
elif self.inputs[name]["type"] == "color":
|
||||||
|
self.add_color_picker(
|
||||||
|
name, self.inputs[name]["label"], self.inputs[name]["default"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Example usage
|
||||||
|
if __name__ == "__main__":
|
||||||
|
def apply_options(options):
|
||||||
|
print(options)
|
||||||
|
|
||||||
|
root = tk.Tk()
|
||||||
|
current_options = {
|
||||||
|
"canvas_width": {"type": "number", "label": "Width:", "default": 900, "min": 1, "max": 2540},
|
||||||
|
"canvas_height": {"type": "number", "label": "Height:", "default": 900, "min": 1, "max": 2540},
|
||||||
|
"template": {"type": "text", "label": "Filename Template:", "default": "{slug}_{sku}_{width}x{height}"},
|
||||||
|
"delete_images": {"type": "checkbox", "label": "Delete image when done", "default": False},
|
||||||
|
"background_color": {"type": "color", "label": "Background Color:", "default": "#FFFFFF"},
|
||||||
|
"image_format": {"type": "dropdown", "label": "Image Format:", "options": ["JPEG", "PNG", "GIF"], "default": "JPEG"}
|
||||||
|
}
|
||||||
|
|
||||||
|
app = OptionsWindow(root, apply_options, current_options)
|
||||||
|
|
||||||
|
root.mainloop()
|
||||||
|
|||||||
Binary file not shown.
@@ -2,7 +2,7 @@ import os
|
|||||||
import shutil
|
import shutil
|
||||||
from tkinter import filedialog, messagebox
|
from tkinter import filedialog, messagebox
|
||||||
from utils.image_processing import ImageProcessor
|
from utils.image_processing import ImageProcessor
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
class FileProcessor:
|
class FileProcessor:
|
||||||
"""
|
"""
|
||||||
@@ -134,17 +134,44 @@ class FileProcessor:
|
|||||||
log (function): The log function to use.
|
log (function): The log function to use.
|
||||||
"""
|
"""
|
||||||
image = ImageProcessor()
|
image = ImageProcessor()
|
||||||
|
image.set_background_color(options.get("background_color", "transparent"))
|
||||||
|
image.set_image_size(options.get("image_size", "contain"))
|
||||||
for file_path in image_paths:
|
for file_path in image_paths:
|
||||||
output_path = os.path.join(
|
# output_path = os.path.join(
|
||||||
output_directory, os.path.relpath(
|
# output_directory, os.path.relpath(
|
||||||
file_path, self.selected_directory)
|
# file_path, self.selected_directory)
|
||||||
)
|
# )
|
||||||
|
output_path = self.generate_output_path(output_directory, file_path, options)
|
||||||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||||
|
self.log_message(f"Running: {file_path}", log)
|
||||||
image.resize_image(
|
image.resize_image(
|
||||||
file_path, output_path, options.get("additional_name", "")
|
file_path, output_path, options
|
||||||
)
|
)
|
||||||
|
|
||||||
if os.path.exists(file_path) and options.get("is_checked", False):
|
if os.path.exists(file_path) and options.get("delete_images", False):
|
||||||
self.log_message(f"Removing: {file_path}", log)
|
self.log_message(f"Removing: {file_path}", log)
|
||||||
os.remove(file_path)
|
os.remove(file_path)
|
||||||
self.log_message(f"Processed: {file_path}", log)
|
self.log_message(f"Processed: {file_path}", log)
|
||||||
|
|
||||||
|
def generate_output_path(self, output_directory, file_path, options, product = None):
|
||||||
|
"""
|
||||||
|
Generate the output path for resized images based on a template.
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The generated output path.
|
||||||
|
"""
|
||||||
|
sku = slug = title = ""
|
||||||
|
name, ext = os.path.splitext(os.path.basename(file_path))
|
||||||
|
width = options.get("canvas_width")
|
||||||
|
height = options.get("canvas_height")
|
||||||
|
if product:
|
||||||
|
sku = product.get("sku", "")
|
||||||
|
slug = product.get("name", "")
|
||||||
|
title = product.get("slug", "")
|
||||||
|
|
||||||
|
new_filename = options.get('template', '{name}').format(
|
||||||
|
name=name, sku=sku, width=width, height=height, slug=slug, title=title
|
||||||
|
)
|
||||||
|
pprint(new_filename)
|
||||||
|
return os.path.join(output_directory, new_filename + ext)
|
||||||
|
|||||||
@@ -4,15 +4,14 @@ from wand.color import Color
|
|||||||
|
|
||||||
|
|
||||||
class ImageProcessor:
|
class ImageProcessor:
|
||||||
def __init__(
|
def __init__(self, canvas_width=900, canvas_height=900, background_color="transparent", image_size="fit"):
|
||||||
self, canvas_width=900, canvas_height=900, background_color="transparent"
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Initialize the ImageProcessor with default values.
|
Initialize the ImageProcessor with default values.
|
||||||
"""
|
"""
|
||||||
self.canvas_width = canvas_width
|
self.canvas_width = canvas_width
|
||||||
self.canvas_height = canvas_height
|
self.canvas_height = canvas_height
|
||||||
self.background_color = background_color
|
self.background_color = Color(background_color)
|
||||||
|
self.image_size = image_size
|
||||||
|
|
||||||
def set_canvas_size(self, width, height):
|
def set_canvas_size(self, width, height):
|
||||||
"""
|
"""
|
||||||
@@ -27,40 +26,106 @@ class ImageProcessor:
|
|||||||
"""
|
"""
|
||||||
self.background_color = Color(color)
|
self.background_color = Color(color)
|
||||||
|
|
||||||
def resize_image(self, image_path, output_path, additional_name=None):
|
def set_image_size(self, size):
|
||||||
|
"""
|
||||||
|
Set the image size mode.
|
||||||
|
"""
|
||||||
|
self.image_size = size
|
||||||
|
|
||||||
|
def resize_image(self, image_path, output_path, options):
|
||||||
"""
|
"""
|
||||||
Resize and process the image.
|
Resize and process the image.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image_path (str): The path to the input image.
|
||||||
|
output_path (str): The path to the output image.
|
||||||
|
additional_name (str, optional): Additional name to append to the output filename.
|
||||||
|
mode (str, optional): The resizing mode ("contain", "cover", "fit"). Default is "contain".
|
||||||
"""
|
"""
|
||||||
|
log = options.get("log_message", None)
|
||||||
|
|
||||||
# Normalize the paths to ensure consistency
|
# Normalize the paths to ensure consistency
|
||||||
image_path = os.path.normpath(image_path)
|
image_path = os.path.normpath(image_path)
|
||||||
output_path = os.path.normpath(output_path)
|
output_path = os.path.normpath(output_path)
|
||||||
print(image_path)
|
|
||||||
print(output_path)
|
|
||||||
with Image(filename=image_path) as img:
|
with Image(filename=image_path) as img:
|
||||||
img.transform(resize=f"{self.canvas_width}x{self.canvas_height}>")
|
self.log_message(f"Original image size: {img.width}x{img.height}", log)
|
||||||
|
if self.image_size == "contain":
|
||||||
|
self._contain(img)
|
||||||
|
elif self.image_size == "cover":
|
||||||
|
self._cover(img)
|
||||||
|
# elif self.image_size == "fit":
|
||||||
|
# self._fit(img)
|
||||||
|
|
||||||
x_offset = int((self.canvas_width - img.width) / 2)
|
x_offset = int((self.canvas_width - img.width) / 2)
|
||||||
y_offset = int((self.canvas_height - img.height) / 2)
|
y_offset = int((self.canvas_height - img.height) / 2)
|
||||||
|
|
||||||
with Image(
|
with Image(width=self.canvas_width, height=self.canvas_height, background=self.background_color) as canvas:
|
||||||
width=self.canvas_width,
|
|
||||||
height=self.canvas_height,
|
|
||||||
background=self.background_color,
|
|
||||||
) as canvas:
|
|
||||||
canvas.composite(img, left=x_offset, top=y_offset)
|
canvas.composite(img, left=x_offset, top=y_offset)
|
||||||
# Create a new filename
|
# Create a new filename
|
||||||
new_filename = os.path.splitext(
|
new_filename = os.path.splitext(os.path.basename(output_path))[0]
|
||||||
os.path.basename(output_path))[0]
|
|
||||||
if additional_name:
|
|
||||||
new_filename += " - " + additional_name.strip()
|
|
||||||
new_filename += os.path.splitext(output_path)[1]
|
new_filename += os.path.splitext(output_path)[1]
|
||||||
# Construct the final output path
|
# Construct the final output path
|
||||||
final_output_path = os.path.join(
|
final_output_path = os.path.join(os.path.dirname(output_path), new_filename)
|
||||||
os.path.dirname(output_path), new_filename
|
|
||||||
)
|
|
||||||
# Save the image to the final output path
|
# Save the image to the final output path
|
||||||
canvas.save(filename=final_output_path)
|
canvas.save(filename=final_output_path)
|
||||||
print(f"Saved to: {final_output_path}")
|
self.log_message(f"Saved to: {final_output_path}", log)
|
||||||
|
|
||||||
|
|
||||||
|
def _cover(self, img:Image):
|
||||||
|
"""
|
||||||
|
Resize the image to cover the entire canvas.
|
||||||
|
"""
|
||||||
|
base_width = self.canvas_width
|
||||||
|
base_heigth = self.canvas_height
|
||||||
|
wpercent = (base_width / float(img.size[0]))
|
||||||
|
hpercent = (base_heigth / float(img.size[1]))
|
||||||
|
hsize = int((float(img.size[1]) * float(wpercent)))
|
||||||
|
wsize = int((float(img.size[0]) * float(hpercent)))
|
||||||
|
img.resize(wsize, base_heigth)
|
||||||
|
|
||||||
|
|
||||||
|
aspect_ratio_img = img.width / img.height
|
||||||
|
aspect_ratio_canvas = self.canvas_width / self.canvas_height
|
||||||
|
print(f"Image aspect ratio: {aspect_ratio_img}, Canvas aspect ratio: {aspect_ratio_canvas}")
|
||||||
|
print(f"Cover resized image size: {img.width}x{img.height}")
|
||||||
|
|
||||||
|
|
||||||
|
def _contain(self, img):
|
||||||
|
"""
|
||||||
|
Resize the image to cover the entire canvas.
|
||||||
|
"""
|
||||||
|
aspect_ratio_img = img.width / img.height
|
||||||
|
aspect_ratio_canvas = self.canvas_width / self.canvas_height
|
||||||
|
print(f"Image aspect ratio: {aspect_ratio_img}, Canvas aspect ratio: {aspect_ratio_canvas}")
|
||||||
|
|
||||||
|
if aspect_ratio_img > aspect_ratio_canvas:
|
||||||
|
img.transform(resize=f"{self.canvas_width}x")
|
||||||
|
else:
|
||||||
|
img.transform(resize=f"x{self.canvas_height}")
|
||||||
|
print(f"Cover resized image size: {img.width}x{img.height}")
|
||||||
|
|
||||||
|
# def _fit(self, img):
|
||||||
|
# """
|
||||||
|
# Fit the image within the canvas without scaling up if it's smaller.
|
||||||
|
# """
|
||||||
|
# if img.width > self.canvas_width or img.height > self.canvas_height:
|
||||||
|
# img.transform(resize=f"{self.canvas_width}x{self.canvas_height}>")
|
||||||
|
# print(f"Fit resized image size: {img.width}x{img.height}")
|
||||||
|
|
||||||
|
def log_message(self, message, log=None):
|
||||||
|
"""
|
||||||
|
Log a message or print it if no log function is provided.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message (str): The message to log or print.
|
||||||
|
log (function, optional): The log function to use. Defaults to None.
|
||||||
|
"""
|
||||||
|
if log:
|
||||||
|
log(message)
|
||||||
|
else:
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
|
||||||
# Example usage
|
# Example usage
|
||||||
@@ -68,4 +133,15 @@ if __name__ == "__main__":
|
|||||||
processor = ImageProcessor()
|
processor = ImageProcessor()
|
||||||
processor.set_canvas_size(900, 900)
|
processor.set_canvas_size(900, 900)
|
||||||
processor.set_background_color("white")
|
processor.set_background_color("white")
|
||||||
processor.resize_image("input_image.jpg", "output_image.jpg", "example")
|
|
||||||
|
# Contain mode
|
||||||
|
processor.set_image_size("contain")
|
||||||
|
processor.resize_image("input_image.jpg", "output_image_contain.jpg", "example")
|
||||||
|
|
||||||
|
# Cover mode
|
||||||
|
processor.set_image_size("cover")
|
||||||
|
processor.resize_image("input_image.jpg", "output_image_cover.jpg", "example")
|
||||||
|
|
||||||
|
# Fit mode
|
||||||
|
processor.set_image_size("fit")
|
||||||
|
processor.resize_image("input_image.jpg", "output_image_fit.jpg", "example")
|
||||||
|
|||||||
Reference in New Issue
Block a user