90 lines
3.0 KiB
Python
90 lines
3.0 KiB
Python
"""Image conversion utilities for ATOMS3 MacroPad."""
|
|
|
|
import math
|
|
import os
|
|
import random
|
|
import shutil
|
|
from PIL import Image, ImageTk
|
|
from .constants import SCREEN_W, SCREEN_H, IMAGES_DIR
|
|
|
|
|
|
def ensure_images_dir():
|
|
os.makedirs(IMAGES_DIR, exist_ok=True)
|
|
|
|
|
|
def generate_gradient_image() -> str:
|
|
"""Generate a random linear gradient PNG, save it to images dir, return its path."""
|
|
ensure_images_dir()
|
|
|
|
c1 = (random.randint(30, 220), random.randint(30, 220), random.randint(30, 220))
|
|
c2 = (random.randint(30, 220), random.randint(30, 220), random.randint(30, 220))
|
|
angle = math.radians(random.randint(0, 359))
|
|
cos_a = math.cos(angle)
|
|
sin_a = math.sin(angle)
|
|
|
|
size = SCREEN_W # 128x128 to match device display
|
|
img = Image.new("RGB", (size, size))
|
|
pixels = img.load()
|
|
for y in range(size):
|
|
for x in range(size):
|
|
nx = (x / (size - 1)) * 2 - 1
|
|
ny = (y / (size - 1)) * 2 - 1
|
|
t = max(0.0, min(1.0, (nx * cos_a + ny * sin_a + 1) / 2))
|
|
pixels[x, y] = (
|
|
int(c1[0] + (c2[0] - c1[0]) * t),
|
|
int(c1[1] + (c2[1] - c1[1]) * t),
|
|
int(c1[2] + (c2[2] - c1[2]) * t),
|
|
)
|
|
|
|
token = "%08x" % random.getrandbits(32)
|
|
path = os.path.join(IMAGES_DIR, f"gradient_{token}.png")
|
|
img.save(path)
|
|
return path
|
|
|
|
|
|
def copy_image_to_appdata(image_path: str) -> str:
|
|
"""Copy an image to the AppData images folder, return new path."""
|
|
ensure_images_dir()
|
|
basename = os.path.basename(image_path)
|
|
# Hash-suffix the filename to avoid collisions between same-named imports
|
|
name, ext = os.path.splitext(basename)
|
|
import hashlib
|
|
h = hashlib.md5(open(image_path, "rb").read()).hexdigest()[:8]
|
|
dest = os.path.join(IMAGES_DIR, f"{name}_{h}{ext}")
|
|
if not os.path.exists(dest):
|
|
shutil.copy2(image_path, dest)
|
|
return dest
|
|
|
|
|
|
def convert_to_rgb565(image_path: str) -> bytes:
|
|
"""Convert image to 128x128 RGB565 big-endian bytes (32768 bytes)."""
|
|
img = Image.open(image_path).convert("RGB")
|
|
img = img.resize((SCREEN_W, SCREEN_H), Image.LANCZOS)
|
|
|
|
data = bytearray(SCREEN_W * SCREEN_H * 2)
|
|
pixels = img.load()
|
|
|
|
for y in range(SCREEN_H):
|
|
for x in range(SCREEN_W):
|
|
r, g, b = pixels[x, y]
|
|
rgb565 = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)
|
|
offset = (y * SCREEN_W + x) * 2
|
|
data[offset] = (rgb565 >> 8) & 0xFF # MSB
|
|
data[offset + 1] = rgb565 & 0xFF # LSB
|
|
|
|
return bytes(data)
|
|
|
|
|
|
def create_thumbnail(image_path: str, size: tuple = (64, 64)):
|
|
"""Create a tkinter-compatible thumbnail from an image file."""
|
|
img = Image.open(image_path).convert("RGB")
|
|
img.thumbnail(size, Image.LANCZOS)
|
|
return ImageTk.PhotoImage(img)
|
|
|
|
|
|
def create_preview(image_path: str, size: tuple = (128, 128)):
|
|
"""Larger preview image for the properties panel."""
|
|
img = Image.open(image_path).convert("RGB")
|
|
img = img.resize(size, Image.LANCZOS)
|
|
return ImageTk.PhotoImage(img)
|