mirror of
https://github.com/coraxcode/GIFCraft.git
synced 2026-01-06 19:40:16 +01:00
Update GIFCraft.py
This commit is contained in:
92
GIFCraft.py
92
GIFCraft.py
@@ -3,6 +3,7 @@ from tkinter import filedialog, messagebox, Frame, Canvas, Menu, Checkbutton, In
|
||||
from PIL import Image, ImageTk, ImageSequence
|
||||
import os
|
||||
|
||||
|
||||
class GIFEditor:
|
||||
def __init__(self, master):
|
||||
"""Initialize the GIF editor with the main window and UI setup."""
|
||||
@@ -45,13 +46,13 @@ class GIFEditor:
|
||||
self.create_file_menu()
|
||||
self.create_edit_menu()
|
||||
self.create_animation_menu()
|
||||
self.create_help_menu() # Add this line to include the Help menu
|
||||
self.create_help_menu()
|
||||
self.master.config(menu=self.menu_bar)
|
||||
|
||||
def create_file_menu(self):
|
||||
"""Create the File menu."""
|
||||
file_menu = Menu(self.menu_bar, tearoff=0)
|
||||
file_menu.add_command(label="New", command=self.new_file, accelerator="Ctrl+N") # Add this line
|
||||
file_menu.add_command(label="New", command=self.new_file, accelerator="Ctrl+N")
|
||||
file_menu.add_command(label="Load GIF/PNG/WebP", command=self.load_file, accelerator="Ctrl+O")
|
||||
file_menu.add_separator()
|
||||
file_menu.add_command(label="Save", command=self.save, accelerator="Ctrl+S")
|
||||
@@ -73,6 +74,8 @@ class GIFEditor:
|
||||
edit_menu.add_separator()
|
||||
edit_menu.add_command(label="Check/Uncheck All", command=self.toggle_check_all)
|
||||
edit_menu.add_separator()
|
||||
edit_menu.add_command(label="Resize All Frames", command=self.resize_all_frames_dialog)
|
||||
edit_menu.add_separator()
|
||||
edit_menu.add_command(label="Undo", command=self.undo, accelerator="Ctrl+Z")
|
||||
edit_menu.add_command(label="Redo", command=self.redo, accelerator="Ctrl+Y")
|
||||
self.menu_bar.add_cascade(label="Edit", menu=edit_menu)
|
||||
@@ -86,7 +89,7 @@ class GIFEditor:
|
||||
def create_help_menu(self):
|
||||
"""Create the Help menu."""
|
||||
help_menu = Menu(self.menu_bar, tearoff=0)
|
||||
help_menu.add_command(label="About", command=self.show_about) # Add this line to create the About menu item
|
||||
help_menu.add_command(label="About", command=self.show_about)
|
||||
self.menu_bar.add_cascade(label="Help", menu=help_menu)
|
||||
|
||||
def setup_frame_list(self):
|
||||
@@ -198,6 +201,7 @@ class GIFEditor:
|
||||
self.checkbox_vars = []
|
||||
self.current_file = None
|
||||
self.frame_index = 0
|
||||
self.base_size = None # Clear the base size
|
||||
self.update_frame_list()
|
||||
self.show_frame()
|
||||
self.update_title()
|
||||
@@ -217,14 +221,15 @@ class GIFEditor:
|
||||
|
||||
try:
|
||||
with Image.open(file_path) as img:
|
||||
for frame in ImageSequence.Iterator(img):
|
||||
self.frames.append(self.center_image(self.resize_image(frame.copy())))
|
||||
for i, frame in enumerate(ImageSequence.Iterator(img)):
|
||||
if i == 0:
|
||||
self.base_size = frame.size # Store the size of the first frame
|
||||
self.frames.append(self.resize_to_base_size(frame.copy()))
|
||||
delay = int(frame.info.get('duration', 100)) # Ensure delay is always an integer
|
||||
self.delays.append(delay)
|
||||
var = IntVar()
|
||||
var.trace_add('write', lambda *args, i=len(self.checkbox_vars): self.set_current_frame(i))
|
||||
self.checkbox_vars.append(var)
|
||||
self.resize_all_frames() # Resize all frames to the largest size
|
||||
self.frame_index = 0
|
||||
self.update_frame_list()
|
||||
self.show_frame()
|
||||
@@ -233,12 +238,54 @@ class GIFEditor:
|
||||
except Exception as e:
|
||||
messagebox.showerror("Error", f"Failed to load file: {e}")
|
||||
|
||||
def resize_all_frames(self):
|
||||
"""Resize all frames to the largest frame size."""
|
||||
max_width = max(frame.width for frame in self.frames)
|
||||
max_height = max(frame.height for frame in self.frames)
|
||||
def resize_to_base_size(self, image):
|
||||
"""Resize the image to the base size of the first frame and center it."""
|
||||
if hasattr(self, 'base_size'):
|
||||
base_width, base_height = self.base_size
|
||||
new_image = Image.new("RGBA", self.base_size, (0, 0, 0, 0))
|
||||
image = image.resize(self.base_size, Image.Resampling.LANCZOS)
|
||||
new_image.paste(image, ((base_width - image.width) // 2, (base_height - image.height) // 2))
|
||||
return new_image
|
||||
return image
|
||||
|
||||
def resize_all_frames_dialog(self):
|
||||
"""Open a dialog to get the new size and resize all frames."""
|
||||
dialog = tk.Toplevel(self.master)
|
||||
dialog.title("Resize All Frames")
|
||||
|
||||
tk.Label(dialog, text="New Width:").pack(pady=5)
|
||||
width_entry = tk.Entry(dialog)
|
||||
width_entry.pack(pady=5)
|
||||
|
||||
tk.Label(dialog, text="New Height:").pack(pady=5)
|
||||
height_entry = tk.Entry(dialog)
|
||||
height_entry.pack(pady=5)
|
||||
|
||||
def resize():
|
||||
try:
|
||||
new_width = int(width_entry.get())
|
||||
new_height = int(height_entry.get())
|
||||
self.resize_all_frames(new_width, new_height)
|
||||
dialog.destroy()
|
||||
except ValueError:
|
||||
messagebox.showerror("Invalid Input", "Please enter valid integers for width and height.")
|
||||
|
||||
tk.Button(dialog, text="Resize", command=resize).pack(pady=10)
|
||||
dialog.grab_set()
|
||||
|
||||
def resize_all_frames(self, new_width=None, new_height=None):
|
||||
"""Resize all frames to the specified width and height."""
|
||||
if new_width is None or new_height is None:
|
||||
if hasattr(self, 'base_size'):
|
||||
new_width, new_height = self.base_size
|
||||
else:
|
||||
return # If no dimensions provided and no base size, do nothing
|
||||
|
||||
self.save_state() # Save the state before making changes
|
||||
for i, frame in enumerate(self.frames):
|
||||
self.frames[i] = self.center_image(frame.resize((max_width, max_height), Image.Resampling.LANCZOS))
|
||||
self.frames[i] = frame.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
||||
self.show_frame()
|
||||
self.update_frame_list()
|
||||
|
||||
def resize_image(self, image, max_width=800, max_height=600):
|
||||
"""Resize the image to fit within the specified max width and height."""
|
||||
@@ -249,16 +296,6 @@ class GIFEditor:
|
||||
image = image.resize(new_size, Image.Resampling.LANCZOS)
|
||||
return image
|
||||
|
||||
def center_image(self, image):
|
||||
"""Center the image within the maximum frame size."""
|
||||
max_width = max((frame.width for frame in self.frames + [image]), default=image.width)
|
||||
max_height = max((frame.height for frame in self.frames + [image]), default=image.height)
|
||||
|
||||
new_image = Image.new("RGBA", (max_width, max_height), (255, 255, 255, 0))
|
||||
new_image.paste(image, ((max_width - image.width) // 2, (max_height - image.height) // 2))
|
||||
|
||||
return new_image
|
||||
|
||||
def add_image(self):
|
||||
"""Add images to the frames."""
|
||||
file_paths = filedialog.askopenfilenames(filetypes=[("Image files", "*.jpg *.jpeg *.png *.webp *.gif *.bmp")])
|
||||
@@ -269,14 +306,16 @@ class GIFEditor:
|
||||
try:
|
||||
for file_path in file_paths:
|
||||
with Image.open(file_path) as image:
|
||||
# Resize and center the new image to match the maximum dimensions
|
||||
image = self.center_image(self.resize_to_max_dimensions(image.copy()))
|
||||
# Resize the new image to match the base dimensions
|
||||
if not self.frames: # If no frames, set the base size to the first image's size
|
||||
self.base_size = image.size
|
||||
image = self.resize_to_base_size(image.copy())
|
||||
self.frames.append(image)
|
||||
self.delays.append(100) # Default delay for added images
|
||||
var = IntVar()
|
||||
var.trace_add('write', lambda *args, i=len(self.checkbox_vars): self.set_current_frame(i))
|
||||
self.checkbox_vars.append(var)
|
||||
|
||||
|
||||
self.update_frame_list()
|
||||
self.show_frame()
|
||||
except Exception as e:
|
||||
@@ -517,11 +556,13 @@ class GIFEditor:
|
||||
self.checkbox_vars = [IntVar(value=state) for state in checkbox_states]
|
||||
for i, var in enumerate(self.checkbox_vars):
|
||||
var.trace_add('write', lambda *args, i=i: self.set_current_frame(i))
|
||||
self.base_size = self.frames[0].size if self.frames else None # Reset base size based on the remaining frames
|
||||
self.update_frame_list()
|
||||
self.show_frame()
|
||||
self.update_title()
|
||||
self.check_all.set(False) # Reset the check_all variable to ensure consistency
|
||||
|
||||
|
||||
def redo(self, event=None):
|
||||
"""Redo the last undone action."""
|
||||
if self.redo_stack:
|
||||
@@ -554,7 +595,9 @@ class GIFEditor:
|
||||
"""Display the About dialog."""
|
||||
messagebox.showinfo("About GIFCraft", "GIFCraft - GIF Editor\nVersion 1.0\n© 2024 by Seehrum")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function to initialize the GIF editor."""
|
||||
root = tk.Tk()
|
||||
app = GIFEditor(master=root)
|
||||
try:
|
||||
@@ -563,5 +606,6 @@ def main():
|
||||
print("Program interrupted with Ctrl+C")
|
||||
root.destroy()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user