Skip to content

Instantly share code, notes, and snippets.

@HiroshiOkada
Created January 23, 2024 08:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save HiroshiOkada/3ae65f399ffbcc09ec4bf0626a19e57d to your computer and use it in GitHub Desktop.
Save HiroshiOkada/3ae65f399ffbcc09ec4bf0626a19e57d to your computer and use it in GitHub Desktop.
# 画像の上か下にテキストを追加し、リサイズして保存するプログラム
# 動作確認環境 Windows 11 Personal 日本語版
# Python 3.10.6 64bit
# このプログラムは ChatGPT 4 を使って作成したプログラムに
# 岡田洋が多少の改変を加えたものです。
import tkinter as tk
from tkinter import filedialog, colorchooser
from PIL import Image, ImageTk, ImageDraw, ImageFont
import os
import re
# フォントのパス
FONT_PATH = "C:/Windows/Fonts/BIZ-UDGothicB.ttc"
class ImageEditorApp:
def __init__(self, root):
self.root = root
self.root.title("Image Editor")
self.create_menu()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
self.canvas = tk.Canvas(root, bg="grey")
self.canvas.grid(row=0, column=0, sticky="nsew")
self.scrollbar_v = tk.Scrollbar(
root, orient="vertical", command=self.canvas.yview
)
self.scrollbar_v.grid(row=0, column=1, sticky="ns")
self.scrollbar_h = tk.Scrollbar(
root, orient="horizontal", command=self.canvas.xview
)
self.scrollbar_h.grid(row=1, column=0, sticky="ew")
self.canvas.config(
yscrollcommand=self.scrollbar_v.set, xscrollcommand=self.scrollbar_h.set
)
# 画像とテキストの属性
self.image = None
self.text = ""
self.text_color = "black"
self.text_position = "bottom"
self.font_size = 20
# テキスト入力と数値入力
self.text_entry = tk.Entry(root)
self.text_entry.grid(row=2, column=0, sticky="ew")
self.text_entry.bind("<KeyRelease>", self.update_text)
self.text_entry.insert(0, string="input text here")
self.width_entry = tk.Entry(root)
self.width_entry.grid(row=3, column=0, sticky="ew")
# テキスト色選択
self.color_button = tk.Button(root, text="テキスト色選択", command=self.choose_color)
self.color_button.grid(row=4, column=0, sticky="ew")
# テキスト位置選択
self.position_var = tk.StringVar(value="bottom")
self.top_radio = tk.Radiobutton(
root,
text="上",
variable=self.position_var,
value="top",
command=self.update_text_position,
)
self.bottom_radio = tk.Radiobutton(
root,
text="下",
variable=self.position_var,
value="bottom",
command=self.update_text_position,
)
self.top_radio.grid(row=5, column=0, sticky="ew")
self.bottom_radio.grid(row=6, column=0, sticky="ew")
def create_menu(self):
menubar = tk.Menu(self.root)
self.root.config(menu=menubar)
file_menu = tk.Menu(menubar, tearoff=0)
file_menu.add_command(label="開く(O)...", command=self.open_image)
file_menu.add_command(label="名前をつけて保存(A)...", command=self.save_image)
menubar.add_cascade(label="ファイル(F)", menu=file_menu)
def open_image(self):
file_path = filedialog.askopenfilename()
if file_path:
self.image = Image.open(file_path)
self.resize_image_if_large()
self.display_image()
self.image_file_path = file_path
# 保存時の幅をテキストボックスに設定
self.width_entry.delete(0, tk.END) # 既存のテキストをクリア
self.width_entry.insert(0, str(self.image.width))
def resize_image_if_large(self):
max_width = 2400
if self.image.width > max_width:
# 画像の縦横比を保持しながらリサイズ
ratio = max_width / self.image.width
new_height = int(self.image.height * ratio)
self.image = self.image.resize((max_width, new_height), Image.LANCZOS)
def choose_color(self):
color_code = colorchooser.askcolor(title="色を選択")[1]
if color_code:
self.text_color = color_code
self.update_text()
def update_text_position(self):
self.text_position = self.position_var.get()
self.update_text()
def update_text(self, event=None):
if self.image:
self.edited_image = self.image.copy()
draw = ImageDraw.Draw(self.edited_image)
width, height = self.edited_image.size
text = self.text_entry.get()
font_size = self.calculate_font_size(draw, text, width, height)
try:
font_path = FONT_PATH
font = ImageFont.truetype(
font_path, font_size, index=0
) # index=0 は最初のフォントを示す
except IOError:
font = ImageFont.load_default(size=font_size)
bbox = draw.textbbox((0, 0), text, font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
# テキスト位置を決定
if self.text_position == "top":
position = (width / 2 - text_width / 2, 10)
else:
position = (width / 2 - text_width / 2, height - text_height - 10)
draw.text(position, text, fill=self.text_color, font=font)
self.display_image(self.edited_image)
def calculate_font_size(self, draw, text, width, height):
font_size = 1
while True:
try:
font_path = FONT_PATH
font = ImageFont.truetype(
font_path, font_size, index=0
) # index=0 は最初のフォントを示す
except IOError:
font = ImageFont.load_default(size=font_size)
# "WW"を追加してbboxを計算するこてで、テキストの幅に少し余裕を持たせる
bbox = draw.textbbox((0, 0), text + "WW", font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
if text_width >= width or text_height >= height or font_size > 100:
break
font_size += 1
return font_size
def display_image(self, image=None):
if image:
image_to_show = image
else:
image_to_show = self.image
self.canvas_image = ImageTk.PhotoImage(image_to_show)
self.canvas.create_image(0, 0, image=self.canvas_image, anchor=tk.NW)
# Canvasのスクロールリージョンを更新
self.canvas.config(scrollregion=self.canvas.bbox("all"))
def save_image(self):
if not self.edited_image: # 編集後の画像がある場合のみ保存処理を行う
return
# 元のファイル名とテキストを取得
original_file_name = os.path.splitext(os.path.basename(self.image_file_path))[0]
text = self.text_entry.get()
safe_text = re.sub(r'[<>:"/\\|?*]', "_", text)
default_file_name = f"{original_file_name}_{safe_text}.jpg"
# 保存ダイアログを開く
file_path = filedialog.asksaveasfilename(
initialfile=default_file_name,
filetypes=[("JPEG", "*.jpg")],
defaultextension=".jpg",
)
if file_path:
target_width = int(self.width_entry.get())
ratio = target_width / self.edited_image.width
target_height = int(self.edited_image.height * ratio)
resized_image = self.edited_image.resize(
(target_width, target_height), Image.LANCZOS
)
resized_image.save(file_path, "JPEG")
root = tk.Tk()
app = ImageEditorApp(root)
root.mainloop()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment