Tento projekt pracuje s obsahem systémové schránky systému Windows (clipboard).
Jakýkoli text, obrázek nebo soubor, který je zkopírován, je okamžitě vložen do jednoho omezeného prostoru.
Obsah není upravován ani tříděn. Jednotlivé prvky se náhodně umisťují do formátu a vzájemně se překrývají.
Plátno se postupně zaplňuje bez možnosti návratu nebo editace.
Každý z obrázků níže je výstup z jednoho dne a zachycuje průběh mé práce u počítače.
Zdrojový kód
Zdrojový Python kód pro tento projekt je dostupný zde:
import os
import random
import textwrap
from datetime import datetime
import tkinter as tk
import pyperclip
from PIL import Image, ImageTk, ImageGrab, ImageDraw, ImageFont
# =========================
# Nastavení
# =========================
CANVAS_SIZE = 700
BG_COLOR = "white"
POLL_MS = 500
MAX_IMAGE_SIZE = 320
TEXT_WRAP_CHARS = 38
TEXT_FONT = ("Arial", 14)
TEXT_COLOR = "black"
FILE_COLOR = "#1f4e79"
# =========================
# Tkinter okno
# =========================
root = tk.Tk()
root.title("Clipboard Canvas")
root.geometry(f"{CANVAS_SIZE}x{CANVAS_SIZE}")
canvas = tk.Canvas(root, width=CANVAS_SIZE, height=CANVAS_SIZE, bg=BG_COLOR, highlightthickness=0)
canvas.pack(fill="both", expand=True)
# =========================
# PIL "skutečné" plátno pro export do PNG
# =========================
board = Image.new("RGB", (CANVAS_SIZE, CANVAS_SIZE), "white")
board_draw = ImageDraw.Draw(board)
try:
pil_font = ImageFont.truetype("arial.ttf", 20)
except Exception:
pil_font = ImageFont.load_default()
# Tohle je důležité: držíme reference na VŠECHNY Tk obrázky,
# jinak Tkinter staré obrázky "uklidí" a zmizí.
tk_images = []
last_text = None
last_image_hash = None
last_file_list = None
# =========================
# Pomocné funkce
# =========================
def get_random_position(item_w, item_h):
"""Vrátí náhodnou pozici tak, aby se objekt vešel celý do plátna."""
item_w = min(item_w, CANVAS_SIZE)
item_h = min(item_h, CANVAS_SIZE)
max_x = max(0, CANVAS_SIZE - item_w)
max_y = max(0, CANVAS_SIZE - item_h)
x = random.randint(0, max_x)
y = random.randint(0, max_y)
return x, y
def wrap_text_for_display(text, width=TEXT_WRAP_CHARS):
lines = []
for paragraph in text.splitlines() or [""]:
wrapped = textwrap.wrap(paragraph, width=width) or [""]
lines.extend(wrapped)
return "\n".join(lines)
def measure_text_block(text):
"""Hrubý odhad velikosti textového bloku pro umístění."""
lines = text.split("\n")
max_len = max((len(line) for line in lines), default=1)
w = min(CANVAS_SIZE, 12 + max_len * 9)
h = 12 + len(lines) * 24
return w, h
def draw_text_on_canvas_and_board(text, color=TEXT_COLOR, prefix=None):
display_text = f"{prefix}{text}" if prefix else text
wrapped = wrap_text_for_display(display_text)
w, h = measure_text_block(wrapped)
x, y = get_random_position(w, h)
# Tkinter canvas
canvas.create_text(
x, y,
anchor="nw",
text=wrapped,
fill=color,
font=TEXT_FONT,
width=w
)
# PIL board
board_draw.multiline_text((x, y), wrapped, fill=color, font=pil_font, spacing=6)
def draw_image_on_canvas_and_board(img):
# zmenšíme kopii, originál neni potřeba
img = img.convert("RGB")
img.thumbnail((MAX_IMAGE_SIZE, MAX_IMAGE_SIZE))
w, h = img.size
x, y = get_random_position(w, h)
# Tkinter canvas
tk_img = ImageTk.PhotoImage(img)
tk_images.append(tk_img)
canvas.create_image(x, y, anchor="nw", image=tk_img)
# PIL board
board.paste(img, (x, y))
def hash_pil_image(img):
# Rychlejší než hash(img.tobytes()) na obrovských datech:
tiny = img.copy()
tiny.thumbnail((64, 64))
return hash(tiny.tobytes())
def save_canvas_png(event=None):
filename = f"clipboard_canvas_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
board.save(filename, "PNG")
print(f"Uloženo: {os.path.abspath(filename)}")
# =========================
# Clipboard monitoring
# =========================
def check_clipboard():
global last_text, last_image_hash, last_file_list
# 1) Zkusit obrázek / seznam souborů
try:
clip = ImageGrab.grabclipboard()
# Obrázek
if isinstance(clip, Image.Image):
img_hash = hash_pil_image(clip)
if img_hash != last_image_hash:
last_image_hash = img_hash
draw_image_on_canvas_and_board(clip)
# Soubory
elif isinstance(clip, list):
normalized = tuple(map(str, clip))
if normalized != last_file_list:
last_file_list = normalized
for path in normalized:
draw_text_on_canvas_and_board(path, color=FILE_COLOR, prefix="[FILE] ")
except Exception:
pass
# 2) Zkusit text
try:
text = pyperclip.paste()
if isinstance(text, str):
text = text.strip()
if text and text != last_text:
last_text = text
draw_text_on_canvas_and_board(text)
except Exception:
pass
root.after(POLL_MS, check_clipboard)
# =========================
# Klávesy
# =========================
root.bind("<KeyPress-s>", save_canvas_png)
root.bind("<KeyPress-S>", save_canvas_png)
root.focus_force()
# start
check_clipboard()
root.mainloop()