個人開発でiOSアプリを作っていると、地味に大変なのが**「アプリアイコンの準備」**です。iPhoneの通知用、設定画面用、iPad用、App Store用……と、必要な画像サイズは20種類近くあります。これを手作業でリサイズするのは苦行でしかありません。今回は、「AI(Gemini)」にデザインを描いてもらい、「AI(GitHub Copilot / Claude 4.5 Sonnet)」に画像処理ツールを書いてもらうという、AI二刀流でこの面倒な作業を爆速化した話を紹介します。
1. デザインはGeminiにお任せ
まず、開発中のアプリ(座標変換ツール)のイメージをGeminiに伝えて、アイコンのベースとなる画像を生成してもらいました。参考:GeoConverterPro 仮想操作マニュアル
「日本地図、矢印、古い測地系から新しい測地系への変換、青ベースで」といったざっくりした指示でしたが、いい感じのロゴが完成しました。
Geminiが生成した画像(これをベースにします)

2. ツール作成はGitHub Copilot (Claude 4.5)にお任せ
画像はできましたが、これをiOS用のアイコンにするには、トリミングして、正方形にして、さらに大量のサイズ違いのPNGファイルを作る必要があります。
そこで、GitHub Copilot(モデルはClaude 4.5 Sonnet)に以下のようなプロンプトを投げて、Pythonスクリプトを書いてもらいました。
「Tkinterを使ってGUIで画像を読み込み、マウスドラッグで切り抜きができ、さらにボタン一つでiOSアプリに必要な全サイズのアイコンを自動生成して保存するPythonスクリプトを書いて。」
出来上がったのが、image_editor_tk.py です。

% python image_editor_tk.py
============================================================
Image Crop & Resize Tool (Tkinter GUI)
============================================================
How to use:
1. Click '画像を開く' to select an image file
2. Drag mouse to select crop area (red dashed rectangle)
3. Click '切り取り実行' to crop selected area
4. Use scale buttons or enter width/height to resize
5. Click 'リサイズを元に戻す' to undo last resize operation
6. Click '保存' to save the edited image
7. Click '最初に戻す' to return to original image
=== Load Image ===
OK: Loaded GeoConPro.png
Size: 988 x 1045
History saved (total: 1)
OK: Resized 988x1045 -> 494x522
これを使うと、以下の手順でアイコン作成が完了します。
- 画像を開く: 生成した画像を読み込む
- 切り抜き: アイコンにしたい部分をマウスでドラッグして「切り取り実行」
- アイコン生成: 追加してもらった 「iOS App Icons」 ボタンをポチッとするだけ
これだけで、フォルダの中に Icon-AppStore-1024.png や Icon-60@3x.png など、Xcodeにそのまま放り込める画像が一瞬で生成されます。

作成され画像
Contentsjsonは別途作成
3. Pythonスクリプトの解説
今回作ってもらったスクリプトのポイントを、技術的な視点で少し解説します。
使用ライブラリ
特別なライブラリは画像処理用の Pillow だけです。GUI部分はPython標準の tkinter を使っているので、環境構築も簡単です。
Bash
pip install Pillow
ポイント①:TkinterでのGUI構築
Copilotは ImageEditorApp というクラスを作成し、その中でCanvas(画像表示エリア)とButton(操作パネル)を綺麗に配置してくれました。
特に優秀だったのが、**「画像の表示倍率と、実際の切り抜き座標の計算」**です。
画面上では縮小表示されていても、実際のクロップ処理は元画像の解像度で行う必要があるため、倍率計算が必要ですが、そのあたりの面倒なロジックも完璧に実装されていました。
ポイント②:iOSアイコンの一括生成ロジック
このツールの目玉機能である generate_ios_icons メソッドの中身です。
必要なサイズとファイル名のペアをリストで定義し、ループで回してリサイズ・保存しています。
Python
def generate_ios_icons(self):
# (中略)
# iOS App Icon サイズ一覧
ios_icon_sizes = [
# iPhone Notification
(20, "Icon-20.png"),
(40, "Icon-20@2x.png"),
(60, "Icon-20@3x.png"),
# iPhone Settings
(29, "Icon-29.png"),
(58, "Icon-29@2x.png"),
# (中略... iPad用やAppStore用など全サイズ)
(1024, "Icon-AppStore-1024.png"),
]
# 各サイズのアイコンを生成
for size, filename in ios_icon_sizes:
# 高品質なリサイズアルゴリズム(LANCZOS)を使用
resized = source_image.resize((size, size), Image.Resampling.LANCZOS)
# PNG形式で保存
output_path = os.path.join(output_dir, filename)
resized.save(output_path, 'PNG', optimize=True)
Web上の「アイコン生成サイト」を使う手もありますが、プライバシーの懸念やアップロードの手間がありますし、大体が英語なので操作が直ぐにわからなかったり課金される事もあり、何かと面倒でした。
ローカルのPythonスクリプトなら一瞬で終わりますし、セキュリティ的にも安心です。何より、後から「Android用も欲しい」と思ったらリストにサイズを追加するだけで拡張できます。
4. まとめ:AI時代の開発スタイル
今回、私はコードをほとんど書いていません。
**「やりたいこと(要件)」**を明確にして、デザインはGeminiに、実装はCopilotに依頼しただけです。
- Gemini: クリエイティブな発想(ロゴデザイン)
- Copilot: ロジカルな実装(定型作業の自動化)
- 人間: ディレクションと最終調整
これが組み合わさると、個人開発のスピード感が劇的に上がります。
特に.NET MAUIやiOS開発において、このような「周辺ツールの自作」にAIを使うのは非常に効果的だと感じました。
もし同じようにアイコン作成で消耗している方がいれば、ぜひAIに「専用ツール」を作らせてみてください。世界が変わりますよ!
5. 参考:Pythonスクリプト
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Tkinter GUI画像切り出し・リサイズツール
ファイルダイアログを使った対話的な画像編集
"""
import os
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from PIL import Image, ImageTk
class ImageEditorApp:
"""Tkinter GUIベースの画像エディタ"""
def __init__(self, root):
self.root = root
self.root.title("Image Crop & Resize Tool")
self.root.geometry("1400x900")
# スクリプトのディレクトリを取得
self.script_dir = os.path.dirname(os.path.abspath(__file__))
# 画像データ
self.original_image = None
self.current_image = None
self.display_image = None
self.image_path = None
# 履歴管理(元に戻す用)
self.image_history = [] # リサイズ前の画像を保存
self.max_history = 10 # 最大履歴数
# 切り抜き用変数
self.crop_start_x = None
self.crop_start_y = None
self.crop_rect = None
self.crop_coords = None
# UI構築
self.setup_ui()
print("=" * 60)
print("Image Crop & Resize Tool (Tkinter GUI)")
print("=" * 60)
print("\nHow to use:")
print("1. Click '画像を開く' to select an image file")
print("2. Drag mouse to select crop area (red dashed rectangle)")
print("3. Click '切り取り実行' to crop selected area")
print("4. Use scale buttons or enter width/height to resize")
print("5. Click 'リサイズを元に戻す' to undo last resize operation")
print("6. Click '保存' to save the edited image")
print("7. Click '最初に戻す' to return to original image")
print()
def setup_ui(self):
"""UI構築"""
# メインフレーム
main_frame = tk.Frame(self.root, bg='#f0f0f0')
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 左側:画像表示エリア
left_frame = tk.Frame(main_frame, bg='white', relief=tk.SUNKEN, bd=2)
left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 10))
# タイトルラベル
title_label = tk.Label(left_frame, text="Image Display Area (Drag to select crop area)",
font=('Arial', 14, 'bold'), bg='white')
title_label.pack(pady=10)
# キャンバス(画像表示用)
self.canvas = tk.Canvas(left_frame, bg='#e0e0e0', cursor='crosshair')
self.canvas.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# マウスイベントバインド
self.canvas.bind("<Button-1>", self.on_mouse_down)
self.canvas.bind("<B1-Motion>", self.on_mouse_drag)
self.canvas.bind("<ButtonRelease-1>", self.on_mouse_up)
# 右側:コントロールパネル
right_frame = tk.Frame(main_frame, bg='#f0f0f0', width=300)
right_frame.pack(side=tk.RIGHT, fill=tk.Y)
right_frame.pack_propagate(False)
# === 情報表示エリア ===
info_frame = tk.LabelFrame(right_frame, text="Image Information",
font=('Arial', 11, 'bold'), bg='#f0f0f0')
info_frame.pack(fill=tk.X, pady=(0, 15))
self.info_text = tk.Text(info_frame, height=8, width=30, font=('Arial', 10),
bg='white', relief=tk.SUNKEN, bd=1)
self.info_text.pack(padx=10, pady=10)
self.info_text.insert('1.0', "No image loaded")
self.info_text.config(state=tk.DISABLED)
# === ファイル操作ボタン ===
file_frame = tk.LabelFrame(right_frame, text="File Operations",
font=('Arial', 11, 'bold'), bg='#f0f0f0')
file_frame.pack(fill=tk.X, pady=(0, 15))
btn_load = tk.Button(file_frame, text="画像を開く", command=self.load_image,
font=('Arial', 12, 'bold'), bg='#90EE90', fg='black',
height=2, relief=tk.RAISED, bd=3)
btn_load.pack(fill=tk.X, padx=10, pady=5)
btn_save = tk.Button(file_frame, text="保存", command=self.save_image,
font=('Arial', 12, 'bold'), bg='#87CEEB', fg='black',
height=2, relief=tk.RAISED, bd=3)
btn_save.pack(fill=tk.X, padx=10, pady=5)
btn_ios_icons = tk.Button(file_frame, text="iOS App Icons", command=self.generate_ios_icons,
font=('Arial', 12, 'bold'), bg='#FF1493', fg='black',
height=2, relief=tk.RAISED, bd=3, activebackground='#FF69B4', activeforeground='white')
btn_ios_icons.pack(fill=tk.X, padx=10, pady=5)
# === 編集操作ボタン ===
edit_frame = tk.LabelFrame(right_frame, text="Edit Operations",
font=('Arial', 11, 'bold'), bg='#f0f0f0')
edit_frame.pack(fill=tk.X, pady=(0, 15))
btn_crop = tk.Button(edit_frame, text="切り取り実行", command=self.crop_image,
font=('Arial', 12, 'bold'), bg='#FFD700', fg='black',
height=2, relief=tk.RAISED, bd=3)
btn_crop.pack(fill=tk.X, padx=10, pady=5)
btn_reset = tk.Button(edit_frame, text="最初に戻す", command=self.reset_image,
font=('Arial', 12, 'bold'), bg='#D3D3D3', fg='black',
height=2, relief=tk.RAISED, bd=3)
btn_reset.pack(fill=tk.X, padx=10, pady=5)
# === リサイズ操作 ===
resize_frame = tk.LabelFrame(right_frame, text="Resize",
font=('Arial', 11, 'bold'), bg='#f0f0f0')
resize_frame.pack(fill=tk.X, pady=(0, 15))
# 元に戻すボタン(リサイズ用)
btn_undo = tk.Button(resize_frame, text="リサイズを元に戻す", command=self.undo_resize,
font=('Arial', 11, 'bold'), bg='#B0C4DE', fg='black',
height=2, relief=tk.RAISED, bd=2)
btn_undo.pack(fill=tk.X, padx=10, pady=(5, 10))
# スケールボタン
scale_label = tk.Label(resize_frame, text="Scale:", font=('Arial', 10), bg='#f0f0f0')
scale_label.pack(anchor=tk.W, padx=10, pady=(5, 0))
scale_buttons_frame = tk.Frame(resize_frame, bg='#f0f0f0')
scale_buttons_frame.pack(fill=tk.X, padx=10, pady=5)
for scale, text in [(0.25, "25%"), (0.5, "50%"), (1.5, "150%"), (2.0, "200%")]:
btn = tk.Button(scale_buttons_frame, text=text,
command=lambda s=scale: self.resize_by_scale(s),
font=('Arial', 9), width=6)
btn.pack(side=tk.LEFT, padx=2)
# 幅指定
width_frame = tk.Frame(resize_frame, bg='#f0f0f0')
width_frame.pack(fill=tk.X, padx=10, pady=5)
tk.Label(width_frame, text="Width:", font=('Arial', 10), bg='#f0f0f0').pack(side=tk.LEFT)
self.width_entry = tk.Entry(width_frame, width=10, font=('Arial', 10))
self.width_entry.pack(side=tk.LEFT, padx=5)
tk.Button(width_frame, text="OK", command=self.resize_by_width,
font=('Arial', 9)).pack(side=tk.LEFT)
# 高さ指定
height_frame = tk.Frame(resize_frame, bg='#f0f0f0')
height_frame.pack(fill=tk.X, padx=10, pady=5)
tk.Label(height_frame, text="Height:", font=('Arial', 10), bg='#f0f0f0').pack(side=tk.LEFT)
self.height_entry = tk.Entry(height_frame, width=10, font=('Arial', 10))
self.height_entry.pack(side=tk.LEFT, padx=5)
tk.Button(height_frame, text="OK", command=self.resize_by_height,
font=('Arial', 9)).pack(side=tk.LEFT)
def load_image(self):
"""画像を読み込む(ファイルダイアログ)"""
print("\n=== Load Image ===")
# デフォルトのディレクトリをimageフォルダに設定
initial_dir = os.path.join(self.script_dir, "image")
if not os.path.exists(initial_dir):
initial_dir = self.script_dir
# ファイル選択ダイアログを表示
filetypes = [
("Image files", "*.png *.jpg *.jpeg *.gif *.bmp"),
("PNG files", "*.png"),
("JPEG files", "*.jpg *.jpeg"),
("All files", "*.*")
]
image_path = filedialog.askopenfilename(
title="Select image file",
initialdir=initial_dir,
filetypes=filetypes
)
if not image_path:
print("Cancelled")
return
try:
# 画像を読み込み
self.original_image = Image.open(image_path)
self.current_image = self.original_image.copy()
self.image_path = image_path
self.crop_coords = None
print(f"OK: Loaded {os.path.basename(image_path)}")
print(f" Size: {self.original_image.size[0]} x {self.original_image.size[1]}")
self.update_display()
self.update_info()
except Exception as e:
print(f"Error: {e}")
messagebox.showerror("Error", f"Failed to load image:\n{e}")
def update_display(self):
"""画像を表示"""
if self.current_image is None:
return
# キャンバスのサイズを取得
self.canvas.update()
canvas_width = self.canvas.winfo_width()
canvas_height = self.canvas.winfo_height()
# 画像をキャンバスに収まるようにリサイズ
img_width, img_height = self.current_image.size
scale = min(canvas_width / img_width, canvas_height / img_height, 1.0)
display_width = int(img_width * scale)
display_height = int(img_height * scale)
# 表示用画像を作成
self.display_image = self.current_image.copy()
self.display_image.thumbnail((display_width, display_height), Image.Resampling.LANCZOS)
# PhotoImageに変換
self.photo_image = ImageTk.PhotoImage(self.display_image)
# キャンバスをクリアして画像を表示
self.canvas.delete("all")
self.canvas_image_id = self.canvas.create_image(
canvas_width // 2, canvas_height // 2,
image=self.photo_image, anchor=tk.CENTER
)
# 表示スケールを保存(切り抜き座標変換用)
self.display_scale = scale
self.display_offset_x = (canvas_width - display_width) // 2
self.display_offset_y = (canvas_height - display_height) // 2
def update_info(self):
"""情報パネルを更新"""
self.info_text.config(state=tk.NORMAL)
self.info_text.delete('1.0', tk.END)
if self.current_image is None:
self.info_text.insert('1.0', "No image loaded")
else:
width, height = self.current_image.size
mode = self.current_image.mode
info = f"File:\n{os.path.basename(self.image_path) if self.image_path else 'None'}\n\n"
info += f"Size:\n{width} x {height}\n\n"
info += f"Mode: {mode}\n\n"
if self.image_path and os.path.exists(self.image_path):
file_size = os.path.getsize(self.image_path) / 1024
info += f"File size: {file_size:.1f} KB"
self.info_text.insert('1.0', info)
self.info_text.config(state=tk.DISABLED)
def on_mouse_down(self, event):
"""マウスボタン押下"""
if self.current_image is None:
return
self.crop_start_x = event.x
self.crop_start_y = event.y
# 既存の矩形を削除
if self.crop_rect:
self.canvas.delete(self.crop_rect)
# 新しい矩形を作成
self.crop_rect = self.canvas.create_rectangle(
self.crop_start_x, self.crop_start_y,
self.crop_start_x, self.crop_start_y,
outline='red', width=2, dash=(5, 5)
)
def on_mouse_drag(self, event):
"""マウスドラッグ"""
if self.crop_rect:
self.canvas.coords(self.crop_rect,
self.crop_start_x, self.crop_start_y,
event.x, event.y)
def on_mouse_up(self, event):
"""マウスボタン解放"""
if self.crop_rect:
# 表示座標から実画像座標に変換
x1 = min(self.crop_start_x, event.x) - self.display_offset_x
y1 = min(self.crop_start_y, event.y) - self.display_offset_y
x2 = max(self.crop_start_x, event.x) - self.display_offset_x
y2 = max(self.crop_start_y, event.y) - self.display_offset_y
# スケールを考慮して実画像座標に変換
left = max(0, int(x1 / self.display_scale))
top = max(0, int(y1 / self.display_scale))
right = min(self.current_image.size[0], int(x2 / self.display_scale))
bottom = min(self.current_image.size[1], int(y2 / self.display_scale))
if right > left and bottom > top:
self.crop_coords = (left, top, right, bottom)
width = right - left
height = bottom - top
print(f"\nCrop area selected: {width} x {height} pixels")
def crop_image(self):
"""画像を切り抜き"""
if self.current_image is None:
print("Error: No image loaded")
messagebox.showwarning("Warning", "No image loaded")
return
if self.crop_coords is None:
print("Error: Please select crop area")
messagebox.showwarning("Warning", "Please select crop area by dragging mouse")
return
try:
self.current_image = self.current_image.crop(self.crop_coords)
width = self.crop_coords[2] - self.crop_coords[0]
height = self.crop_coords[3] - self.crop_coords[1]
print(f"OK: Cropped to {width} x {height}")
# 切り抜き矩形を削除
if self.crop_rect:
self.canvas.delete(self.crop_rect)
self.crop_rect = None
self.crop_coords = None
self.update_display()
self.update_info()
except Exception as e:
print(f"Error: {e}")
messagebox.showerror("Error", f"Failed to crop:\n{e}")
def resize_by_scale(self, scale):
"""スケールでリサイズ"""
if self.current_image is None:
print("Error: No image loaded")
messagebox.showwarning("Warning", "No image loaded")
return
try:
# 履歴に現在の画像を保存
self.save_to_history()
original_size = self.current_image.size
new_width = int(original_size[0] * scale)
new_height = int(original_size[1] * scale)
self.current_image = self.current_image.resize(
(new_width, new_height), Image.Resampling.LANCZOS
)
print(f"OK: Resized {original_size[0]}x{original_size[1]} -> {new_width}x{new_height}")
self.update_display()
self.update_info()
except Exception as e:
print(f"Error: {e}")
messagebox.showerror("Error", f"Failed to resize:\n{e}")
def resize_by_width(self):
"""幅指定でリサイズ"""
if self.current_image is None:
print("Error: No image loaded")
messagebox.showwarning("Warning", "No image loaded")
return
try:
# 履歴に現在の画像を保存
self.save_to_history()
width = int(self.width_entry.get())
original_size = self.current_image.size
scale = width / original_size[0]
height = int(original_size[1] * scale)
self.current_image = self.current_image.resize(
(width, height), Image.Resampling.LANCZOS
)
print(f"OK: Resized {original_size[0]}x{original_size[1]} -> {width}x{height}")
self.width_entry.delete(0, tk.END)
self.update_display()
self.update_info()
except ValueError:
print("Error: Please enter a valid number")
messagebox.showerror("Error", "Please enter a valid number")
except Exception as e:
print(f"Error: {e}")
messagebox.showerror("Error", f"Failed to resize:\n{e}")
def resize_by_height(self):
"""高さ指定でリサイズ"""
if self.current_image is None:
print("Error: No image loaded")
messagebox.showwarning("Warning", "No image loaded")
return
try:
# 履歴に現在の画像を保存
self.save_to_history()
height = int(self.height_entry.get())
original_size = self.current_image.size
scale = height / original_size[1]
width = int(original_size[0] * scale)
self.current_image = self.current_image.resize(
(width, height), Image.Resampling.LANCZOS
)
print(f"OK: Resized {original_size[0]}x{original_size[1]} -> {width}x{height}")
self.height_entry.delete(0, tk.END)
self.update_display()
self.update_info()
except ValueError:
print("Error: Please enter a valid number")
messagebox.showerror("Error", "Please enter a valid number")
except Exception as e:
print(f"Error: {e}")
messagebox.showerror("Error", f"Failed to resize:\n{e}")
def reset_image(self):
"""元の画像に戻す"""
if self.original_image is None:
print("Error: No image loaded")
messagebox.showwarning("Warning", "No image loaded")
return
self.current_image = self.original_image.copy()
# 切り抜き矩形を削除
if self.crop_rect:
self.canvas.delete(self.crop_rect)
self.crop_rect = None
self.crop_coords = None
# 履歴をクリア
self.image_history = []
print("OK: Reset to original image")
self.update_display()
self.update_info()
def save_to_history(self):
"""現在の画像を履歴に保存"""
if self.current_image is None:
return
# 履歴に追加
self.image_history.append(self.current_image.copy())
# 最大履歴数を超えたら古いものを削除
if len(self.image_history) > self.max_history:
self.image_history.pop(0)
print(f" History saved (total: {len(self.image_history)})")
def undo_resize(self):
"""リサイズを元に戻す(履歴から復元)"""
if not self.image_history:
print("Error: No history to undo")
messagebox.showinfo("Info", "履歴がありません")
return
# 最後の履歴を取り出す
self.current_image = self.image_history.pop()
print(f"OK: Undo resize (remaining history: {len(self.image_history)})")
self.update_display()
self.update_info()
def generate_ios_icons(self):
"""iOS用アプリアイコン各種サイズを生成"""
if self.current_image is None:
print("Error: No image loaded")
messagebox.showwarning("Warning", "No image loaded")
return
print("\n=== Generate iOS App Icons ===")
# 出力先ディレクトリを選択
output_dir = filedialog.askdirectory(
title="Select output directory for iOS icons",
initialdir=self.script_dir
)
if not output_dir:
print("Cancelled")
return
# iOS App Icon サイズ一覧
# iPhone, iPad, App Store用の各種サイズ
ios_icon_sizes = [
# iPhone Notification
(20, "Icon-20.png"),
(40, "Icon-20@2x.png"),
(60, "Icon-20@3x.png"),
# iPhone Settings
(29, "Icon-29.png"),
(58, "Icon-29@2x.png"),
(87, "Icon-29@3x.png"),
# iPhone Spotlight
(40, "Icon-40.png"),
(80, "Icon-40@2x.png"),
(120, "Icon-40@3x.png"),
# iPhone App
(120, "Icon-60@2x.png"),
(180, "Icon-60@3x.png"),
# iPad Notifications
(20, "Icon-iPad-20.png"),
(40, "Icon-iPad-20@2x.png"),
# iPad Settings
(29, "Icon-iPad-29.png"),
(58, "Icon-iPad-29@2x.png"),
# iPad Spotlight
(40, "Icon-iPad-40.png"),
(80, "Icon-iPad-40@2x.png"),
# iPad App
(76, "Icon-iPad-76.png"),
(152, "Icon-iPad-76@2x.png"),
# iPad Pro App
(167, "Icon-iPad-83.5@2x.png"),
# App Store
(1024, "Icon-AppStore-1024.png"),
]
try:
# 元画像をRGBAモードに変換
if self.current_image.mode != 'RGBA':
source_image = self.current_image.convert('RGBA')
else:
source_image = self.current_image
generated_count = 0
errors = []
# 各サイズのアイコンを生成
for size, filename in ios_icon_sizes:
try:
# 正方形にリサイズ
resized = source_image.resize((size, size), Image.Resampling.LANCZOS)
# PNG形式で保存
output_path = os.path.join(output_dir, filename)
resized.save(output_path, 'PNG', optimize=True)
generated_count += 1
print(f" ✓ Generated: {filename} ({size}x{size})")
except Exception as e:
error_msg = f"Failed to generate {filename}: {e}"
errors.append(error_msg)
print(f" ✗ {error_msg}")
# 完了メッセージ
success_msg = f"Successfully generated {generated_count} iOS app icons!\n\nOutput: {output_dir}"
if errors:
error_detail = "\n".join(errors[:5]) # 最初の5つのエラーのみ表示
success_msg += f"\n\nWarnings:\n{error_detail}"
print(f"\nOK: Generated {generated_count} icons")
messagebox.showinfo("Success", success_msg)
except Exception as e:
print(f"Error: {e}")
messagebox.showerror("Error", f"Failed to generate iOS icons:\n{e}")
def save_image(self):
"""画像を保存(ファイル保存ダイアログ)"""
if self.current_image is None:
print("Error: No image to save")
messagebox.showwarning("Warning", "No image to save")
return
print("\n=== Save Image ===")
# デフォルトのディレクトリをimageフォルダに設定
initial_dir = os.path.join(self.script_dir, "image")
if not os.path.exists(initial_dir):
initial_dir = self.script_dir
# デフォルトのファイル名を生成
if self.image_path:
base_name = os.path.basename(self.image_path)
name, ext = os.path.splitext(base_name)
default_name = f"{name}_edited{ext}"
else:
default_name = "edited_image.png"
# ファイル保存ダイアログを表示
filetypes = [
("PNG files", "*.png"),
("JPEG files", "*.jpg *.jpeg"),
("All files", "*.*")
]
output_path = filedialog.asksaveasfilename(
title="Save image as",
initialdir=initial_dir,
initialfile=default_name,
filetypes=filetypes,
defaultextension=".png"
)
if not output_path:
print("Cancelled")
return
try:
# 拡張子に応じて保存
ext = os.path.splitext(output_path)[1].lower()
if ext in ['.jpg', '.jpeg']:
# JPEGの場合はRGBに変換
rgb_image = self.current_image.convert('RGB')
rgb_image.save(output_path, quality=95, optimize=True)
else:
self.current_image.save(output_path, quality=95, optimize=True)
file_size = os.path.getsize(output_path) / 1024
print(f"\nOK: Saved to {output_path}")
print(f" Size: {self.current_image.size[0]} x {self.current_image.size[1]}")
print(f" File size: {file_size:.1f} KB")
messagebox.showinfo("Success", f"Image saved successfully!\n\n{output_path}")
except Exception as e:
print(f"Error: {e}")
messagebox.showerror("Error", f"Failed to save image:\n{e}")
def main():
"""メイン関数"""
root = tk.Tk()
app = ImageEditorApp(root)
root.mainloop()
if __name__ == "__main__":
main()

コメントを残す