#!/usr/bin/env python3 import base64 import random import tkinter as tk from io import BytesIO from tkinter import filedialog, messagebox from PIL import Image from pillow_heif import register_heif_opener """ Simple meme crusher that resizes and compresses images multiple times. It uses the Pillow library to load and save images and the pillow_heif library to load HEIC images. To install the dependencies, run: ``` pip install pillow pillow-heif ``` To run the application, run: ``` python meme.py ``` """ class MemeCrusherApp(tk.Tk): def __init__(self): super().__init__() register_heif_opener() self.title("Meme Crusher") self.geometry("900x600") self.orig_img = None self.img = None self.tkimg = None self._build_ui() def _build_ui(self): controls = tk.Frame(self) controls.pack(side=tk.LEFT, fill=tk.Y, padx=10, pady=10) tk.Button(controls, text="Load Image", command=self.load_image).pack( fill=tk.X, pady=5 ) self.iter_slider = tk.Scale( controls, from_=1, to=100, orient=tk.HORIZONTAL, label="Iterations" ) self.iter_slider.set(10) self.iter_slider.pack(fill=tk.X, pady=5) self.min_scale = tk.Scale( controls, from_=0.1, to=1.0, resolution=0.01, orient=tk.HORIZONTAL, label="Min Scale", ) self.min_scale.set(0.5) self.min_scale.pack(fill=tk.X, pady=5) self.max_scale = tk.Scale( controls, from_=0.1, to=1.0, resolution=0.01, orient=tk.HORIZONTAL, label="Max Scale", ) self.max_scale.set(1.0) self.max_scale.pack(fill=tk.X, pady=5) self.qmin = tk.Scale( controls, from_=1, to=95, orient=tk.HORIZONTAL, label="Quality Min" ) self.qmin.set(10) self.qmin.pack(fill=tk.X, pady=5) self.qmax = tk.Scale( controls, from_=1, to=95, orient=tk.HORIZONTAL, label="Quality Max" ) self.qmax.set(90) self.qmax.pack(fill=tk.X, pady=5) tk.Button(controls, text="Crush!", command=self.crush).pack(fill=tk.X, pady=10) self.save_btn = tk.Button( controls, text="Save Image", state=tk.DISABLED, command=self.save_image ) self.save_btn.pack(fill=tk.X) self.display = tk.Label(self, bg="#333") self.display.pack(side=tk.RIGHT, expand=True, fill=tk.BOTH, padx=10, pady=10) def load_image(self): path = filedialog.askopenfilename( filetypes=[ ( "Image files", "*.png *.jpg *.jpeg *.bmp *.gif *.avif *.webp *.heic", ) ] ) if not path: return self.orig_img = Image.open(path).convert("RGB") self.img = self.orig_img.copy() self._show(self.img) self.save_btn.config(state=tk.DISABLED) def _show(self, img): w, h = img.size max_dim = min( self.display.winfo_width() or 400, self.display.winfo_height() or 400 ) scale = min(max_dim / w, max_dim / h, 1) disp = img.resize((int(w * scale), int(h * scale)), Image.Resampling.LANCZOS) # Encode the PIL image as PNG and display via Tkinter PhotoImage buf = BytesIO() disp.save(buf, format="PNG") buf.seek(0) b64 = base64.b64encode(buf.getvalue()) self.tkimg = tk.PhotoImage(data=b64) self.display.config(image=self.tkimg) def crush(self): if self.orig_img is None: messagebox.showwarning("No Image", "Load an image first!") return # start from original image copy img = self.orig_img.copy() for _ in range(int(self.iter_slider.get())): # determine new size based on original dimensions (non-compounding scale) scale = random.uniform(self.min_scale.get(), self.max_scale.get()) new_size = ( max(1, int(self.orig_img.width * scale)), max(1, int(self.orig_img.height * scale)), ) # resize the last compressed image to the new size resized = img.resize( new_size, random.choice( [ Image.Resampling.NEAREST, Image.Resampling.BILINEAR, Image.Resampling.BICUBIC, Image.Resampling.LANCZOS, ] ), ) # compress and reopen to chain JPEG artifacts buf = BytesIO() quality = random.randint(int(self.qmin.get()), int(self.qmax.get())) resized.save(buf, format="JPEG", quality=quality) buf.seek(0) img = Image.open(buf) self.img = img self._show(self.img) self.save_btn.config(state=tk.NORMAL) def save_image(self): if not self.img: return path = filedialog.asksaveasfilename( defaultextension=".jpg", filetypes=[("JPEG", "*.jpg")] ) if not path: return self.img.save(path, format="JPEG") messagebox.showinfo("Saved", f"Saved to {path}") if __name__ == "__main__": app = MemeCrusherApp() app.mainloop()