以下は『Φωτοκρίτης』のソースコード全文です:
import tkinter as tk
from tkinter import filedialog, messagebox
from tkinter import ttk
from PIL import Image, ImageTk, ImageOps
import colorsys
import os
class ImageAdjuster:
def __init__(self, root):
self.root = root
self.root.title("色調変換ツール - Φωτοκρίτης")
self.image_path = None
self.original_image = None
self.thumb_size = (200, 200)
self.save_dir = None # 保存先パス
self.save_format = tk.StringVar(value="png") # 保存形式
ttk.Button(root, text="画像選択", command=self.select_image).pack(pady=5)
self.path_label = ttk.Label(root, text="画像が未選択です")
self.path_label.pack()
self.thumbnail_label = ttk.Label(root)
self.thumbnail_label.pack(pady=5)
# チェックボックス
self.chk_invert = tk.BooleanVar()
self.chk_brightness = tk.BooleanVar()
self.chk_grayscale = tk.BooleanVar()
ttk.Checkbutton(root, text="階調の反転", variable=self.chk_invert).pack()
ttk.Checkbutton(root, text="光度の反転", variable=self.chk_brightness).pack()
ttk.Checkbutton(root, text="モノクロ化", variable=self.chk_grayscale).pack()
# プレビュー
ttk.Button(root, text="プレビュー", command=self.preview_images).pack(pady=10)
self.preview_frame = ttk.Frame(root)
self.preview_frame.pack()
# 保存形式選択(ラジオボタン)
ttk.Label(root, text="保存形式:").pack(pady=(10, 0))
fmt_frame = ttk.Frame(root)
fmt_frame.pack()
for fmt in ["png", "jpg", "bmp"]:
ttk.Radiobutton(fmt_frame, text=fmt, variable=self.save_format, value=fmt).pack(side=tk.LEFT, padx=5)
# 保存先選択
ttk.Button(root, text="保存先選択", command=self.select_save_dir).pack(pady=5)
self.save_dir_label = ttk.Label(root, text="保存先:未指定(元画像と同じフォルダ)")
self.save_dir_label.pack()
# 保存ボタン
ttk.Button(root, text="保存", command=self.save_images).pack(pady=5)
def select_image(self):
path = filedialog.askopenfilename(filetypes=[("画像ファイル", "*.png *.jpg *.jpeg *.bmp")])
if path:
self.image_path = path
self.original_image = Image.open(path)
self.path_label.config(text=self.image_path)
thumbnail = self.original_image.copy()
thumbnail.thumbnail(self.thumb_size)
self.tk_thumb = ImageTk.PhotoImage(thumbnail)
self.thumbnail_label.config(image=self.tk_thumb)
def select_save_dir(self):
dir_path = filedialog.askdirectory()
if dir_path:
self.save_dir = dir_path
self.save_dir_label.config(text=f"保存先:{self.save_dir}")
else:
self.save_dir = None
self.save_dir_label.config(text="保存先:未指定(元画像と同じフォルダ)")
def preview_images(self):
for widget in self.preview_frame.winfo_children():
widget.destroy()
if not self.original_image:
return
col = 0
if self.chk_invert.get():
inv_img = self.invert_with_alpha(self.original_image)
self.display_preview(inv_img, "階調の反転", col)
col += 1
if self.chk_brightness.get():
bright_img = self.invert_brightness(self.original_image)
self.display_preview(bright_img, "光度の反転", col)
col += 1
if self.chk_grayscale.get():
gray_img = self.original_image.convert("L")
self.display_preview(gray_img, "モノクロ化", col)
def display_preview(self, img, label_text, col):
preview = img.copy()
preview.thumbnail(self.thumb_size)
tk_img = ImageTk.PhotoImage(preview)
frame = ttk.Frame(self.preview_frame)
frame.grid(row=0, column=col, padx=10)
label = ttk.Label(frame, text=label_text)
label.pack()
img_label = ttk.Label(frame, image=tk_img)
img_label.image = tk_img
img_label.pack()
def invert_brightness(self, img):
if img.mode in ("RGBA", "LA"):
rgb_img = img.convert("RGBA")
else:
rgb_img = img.convert("RGB")
pixels = rgb_img.load()
width, height = rgb_img.size
result = Image.new("RGBA" if "A" in rgb_img.mode else "RGB", (width, height))
result_pixels = result.load()
for y in range(height):
for x in range(width):
r, g, b, *a = pixels[x, y]
h, l, s = colorsys.rgb_to_hls(r / 255, g / 255, b / 255)
l = 1.0 - l
r2, g2, b2 = colorsys.hls_to_rgb(h, l, s)
result_pixels[x, y] = (int(r2 * 255), int(g2 * 255), int(b2 * 255)) + tuple(a)
return result
def invert_with_alpha(self, img):
if img.mode == "RGBA":
r, g, b, a = img.split()
rgb_img = Image.merge("RGB", (r, g, b))
inverted = ImageOps.invert(rgb_img)
return Image.merge("RGBA", (*inverted.split(), a))
else:
return ImageOps.invert(img.convert("RGB"))
def save_images(self):
if not self.original_image or not self.image_path:
return
base_dir = self.save_dir if self.save_dir else os.path.dirname(self.image_path)
base_name = os.path.splitext(os.path.basename(self.image_path))[0]
ext = self.save_format.get()
count = 0
if self.chk_invert.get():
img = self.invert_with_alpha(self.original_image)
self.save_image(img, base_dir, f"{base_name}_invert.{ext}", ext)
count += 1
if self.chk_brightness.get():
img = self.invert_brightness(self.original_image)
self.save_image(img, base_dir, f"{base_name}_brightness_invert.{ext}", ext)
count += 1
if self.chk_grayscale.get():
img = self.original_image.convert("L")
self.save_image(img, base_dir, f"{base_name}_grayscale.{ext}", ext)
count += 1
messagebox.showinfo("保存完了", f"{count} 枚の画像を保存しました。")
def save_image(self, img, path, filename, fmt):
full_path = os.path.join(path, filename)
save_params = {}
if fmt == "jpg":
img = img.convert("RGB") # JPEGは透明非対応
elif fmt == "png" and "A" in img.getbands():
save_params["compress_level"] = 1 # PNG圧縮軽め
img.save(full_path, format=fmt.upper(), **save_params)
if __name__ == "__main__":
root = tk.Tk()
app = ImageAdjuster(root)
root.mainloop()