hsv 色変換ツール
はじめに
参照する画像を参考にして色を変更

プログラム
import os
import tkinter as tk
from tkinter import filedialog, messagebox
from PIL import Image, ImageTk, ImageStat, Image
import numpy as np
import colorsys
from functools import partial
# -----------------------------
# ユーティリティ
# -----------------------------
def rgb_to_hex(r, g, b):
return "#{:02x}{:02x}{:02x}".format(int(r), int(g), int(b))
def rgb_to_hsv_tuple(r, g, b):
return colorsys.rgb_to_hsv(r/255.0, g/255.0, b/255.0)
def ensure_rgba(img):
if img.mode == "RGBA":
return img
return img.convert("RGBA")
def split_alpha(img):
rgba = ensure_rgba(img)
rgb = rgba.convert("RGB")
alpha = rgba.split()[-1]
return rgb, alpha
def merge_alpha(rgb_img, alpha):
rgba = rgb_img.convert("RGBA")
r, g, b, _ = rgba.split()
return Image.merge("RGBA", (r, g, b, alpha))
# -----------------------------
# チェッカーボード生成ユーティリティ
# -----------------------------
def make_checkerboard(size=(300,300), tile=10, color1=(255,255,255), color2=(220,220,220)):
w, h = size
board = Image.new("RGB", (w, h), color1)
px = board.load()
for y in range(h):
for x in range(w):
tx = (x // tile) % 2
ty = (y // tile) % 2
if (tx ^ ty) == 0:
px[x, y] = color1
else:
px[x, y] = color2
return board
# -----------------------------
# 参照色に「寄せる」マッピング(H/S/V をそれぞれターゲットへ線形に寄せる)
# アルファ対応: 入力が RGBA の場合は RGB 部分のみ処理し、アルファを再結合する
# -----------------------------
def apply_hsv_map_to_target_numpy(img, target_hsv, strength, use_h, use_s, use_v):
h_t, s_t, v_t = target_hsv
if img.mode == "RGBA":
rgb_img, alpha = split_alpha(img)
else:
rgb_img = img.convert("RGB")
alpha = None
arr = np.array(rgb_img).astype(np.float32) / 255.0
maxc = np.max(arr, axis=-1)
minc = np.min(arr, axis=-1)
delta = maxc - minc
hue = np.zeros_like(maxc)
mask = delta != 0
idx = (maxc == arr[..., 0]) & mask
hue[idx] = (arr[..., 1][idx] - arr[..., 2][idx]) / delta[idx]
idx = (maxc == arr[..., 1]) & mask
hue[idx] = 2.0 + (arr[..., 2][idx] - arr[..., 0][idx]) / delta[idx]
idx = (maxc == arr[..., 2]) & mask
hue[idx] = 4.0 + (arr[..., 0][idx] - arr[..., 1][idx]) / delta[idx]
hue = (hue / 6.0) % 1.0
sat = np.zeros_like(maxc)
sat[maxc != 0] = delta[maxc != 0] / maxc[maxc != 0]
val = maxc
if use_h:
dh = (h_t - hue + 0.5) % 1.0 - 0.5
hue = (hue + strength * dh) % 1.0
if use_s:
sat = np.clip((1 - strength) * sat + strength * s_t, 0.0, 1.0)
if use_v:
val = np.clip((1 - strength) * val + strength * v_t, 0.0, 1.0)
i = np.floor(hue * 6).astype(int)
f = hue * 6 - i
p = val * (1 - sat)
q = val * (1 - f * sat)
t = val * (1 - (1 - f) * sat)
r2 = np.zeros_like(val)
g2 = np.zeros_like(val)
b2 = np.zeros_like(val)
idx = (i % 6 == 0)
r2[idx], g2[idx], b2[idx] = val[idx], t[idx], p[idx]
idx = (i == 1)
r2[idx], g2[idx], b2[idx] = q[idx], val[idx], p[idx]
idx = (i == 2)
r2[idx], g2[idx], b2[idx] = p[idx], val[idx], t[idx]
idx = (i == 3)
r2[idx], g2[idx], b2[idx] = p[idx], q[idx], val[idx]
idx = (i == 4)
r2[idx], g2[idx], b2[idx] = t[idx], p[idx], val[idx]
idx = (i == 5)
r2[idx], g2[idx], b2[idx] = val[idx], p[idx], q[idx]
out = np.stack([r2, g2, b2], axis=-1)
out_img = Image.fromarray((out * 255).astype(np.uint8))
if alpha is not None:
out_img = merge_alpha(out_img, alpha)
return out_img
# -----------------------------
# HSV Full(オフセット + 強度ブレンド)アルファ対応
# -----------------------------
def apply_hsv_blend_offset_numpy(img, h_offset, s_offset, v_offset, strength, use_h, use_s, use_v):
if img.mode == "RGBA":
rgb_img, alpha = split_alpha(img)
else:
rgb_img = img.convert("RGB")
alpha = None
arr = np.array(rgb_img).astype(np.float32) / 255.0
maxc = np.max(arr, axis=-1)
minc = np.min(arr, axis=-1)
delta = maxc - minc
hue = np.zeros_like(maxc)
mask = delta != 0
idx = (maxc == arr[..., 0]) & mask
hue[idx] = (arr[..., 1][idx] - arr[..., 2][idx]) / delta[idx]
idx = (maxc == arr[..., 1]) & mask
hue[idx] = 2.0 + (arr[..., 2][idx] - arr[..., 0][idx]) / delta[idx]
idx = (maxc == arr[..., 2]) & mask
hue[idx] = 4.0 + (arr[..., 0][idx] - arr[..., 1][idx]) / delta[idx]
hue = (hue / 6.0) % 1.0
sat = np.zeros_like(maxc)
sat[maxc != 0] = delta[maxc != 0] / maxc[maxc != 0]
val = maxc
if use_h:
hue = (1 - strength) * hue + strength * ((hue + h_offset) % 1.0)
if use_s:
sat = (1 - strength) * sat + strength * np.clip(sat + s_offset, 0.0, 1.0)
if use_v:
val = (1 - strength) * val + strength * np.clip(val + v_offset, 0.0, 1.0)
i = np.floor(hue * 6).astype(int)
f = hue * 6 - i
p = val * (1 - sat)
q = val * (1 - f * sat)
t = val * (1 - (1 - f) * sat)
r2 = np.zeros_like(val)
g2 = np.zeros_like(val)
b2 = np.zeros_like(val)
idx = (i % 6 == 0)
r2[idx], g2[idx], b2[idx] = val[idx], t[idx], p[idx]
idx = (i == 1)
r2[idx], g2[idx], b2[idx] = q[idx], val[idx], p[idx]
idx = (i == 2)
r2[idx], g2[idx], b2[idx] = p[idx], val[idx], t[idx]
idx = (i == 3)
r2[idx], g2[idx], b2[idx] = p[idx], q[idx], val[idx]
idx = (i == 4)
r2[idx], g2[idx], b2[idx] = t[idx], p[idx], val[idx]
idx = (i == 5)
r2[idx], g2[idx], b2[idx] = val[idx], p[idx], q[idx]
out = np.stack([r2, g2, b2], axis=-1)
out_img = Image.fromarray((out * 255).astype(np.uint8))
if alpha is not None:
out_img = merge_alpha(out_img, alpha)
return out_img
# -----------------------------
# GUI
# -----------------------------
class ColorTool:
def __init__(self, root):
self.root = root
self.root.title("HSV Tool - 参照色で左下を寄せる(アルファ・チェッカーボード対応)")
self.root.geometry("1100x700")
# 左:サムネイル領域
self.frame_left = tk.Frame(root, width=220)
self.frame_left.pack(side="left", fill="y")
self.frame_left.pack_propagate(False)
self.scroll_canvas = tk.Canvas(self.frame_left, width=200, height=700)
self.scroll_canvas.pack(side="left", fill="y", expand=False)
self.scrollbar = tk.Scrollbar(self.frame_left, orient="vertical", command=self.scroll_canvas.yview)
self.scrollbar.pack(side="right", fill="y")
self.scroll_canvas.configure(yscrollcommand=self.scrollbar.set)
self.thumb_frame = tk.Frame(self.scroll_canvas)
self.thumb_window = self.scroll_canvas.create_window((0, 0), window=self.thumb_frame, anchor="nw")
self.thumb_frame.bind("<Configure>", self._on_frame_configure)
self.scroll_canvas.bind("<Enter>", lambda e: self._bind_mousewheel(True))
self.scroll_canvas.bind("<Leave>", lambda e: self._bind_mousewheel(False))
# 中央
self.frame_center = tk.Frame(root, width=620)
self.frame_center.pack(side="left", fill="y")
self.frame_center.pack_propagate(False)
# Original / Reference
self.frame_top = tk.Frame(self.frame_center)
self.frame_top.pack(pady=5)
tk.Label(self.frame_top, text="Original Image").grid(row=0, column=0, padx=5)
self.canvas_main = tk.Canvas(self.frame_top, width=300, height=300, bg="gray")
self.canvas_main.grid(row=1, column=0, padx=5, pady=2)
self.canvas_main.bind("<Button-1>", self.pick_color_main)
self.original_info_frame = tk.Frame(self.frame_top)
self.original_info_frame.grid(row=2, column=0, pady=4)
self.original_swatch = tk.Label(self.original_info_frame, width=6, height=1, bg="#808080", bd=1, relief="solid")
self.original_swatch.pack(side="left", padx=6)
self.hex_main_label = tk.Label(self.original_info_frame, text="Original HEX: ---")
self.hex_main_label.pack(side="left", padx=6)
tk.Label(self.frame_top, text="Reference Image").grid(row=0, column=1, padx=5)
self.canvas_ref = tk.Canvas(self.frame_top, width=300, height=300, bg="gray")
self.canvas_ref.grid(row=1, column=1, padx=5, pady=2)
self.canvas_ref.bind("<Button-1>", self.pick_color_ref)
self.ref_info_frame = tk.Frame(self.frame_top)
self.ref_info_frame.grid(row=2, column=1, pady=4)
self.ref_swatch = tk.Label(self.ref_info_frame, width=6, height=1, bg="#808080", bd=1, relief="solid")
self.ref_swatch.pack(side="left", padx=6)
self.hex_ref_label = tk.Label(self.ref_info_frame, text="Reference HEX: ---")
self.hex_ref_label.pack(side="left", padx=6)
# Mapped / HSV Full
self.frame_bottom = tk.Frame(self.frame_center)
self.frame_bottom.pack(pady=5)
mapped_frame = tk.Frame(self.frame_bottom)
mapped_frame.grid(row=0, column=0, padx=10)
tk.Label(mapped_frame, text="Mapped (参照色に寄せる)").pack()
self.canvas_out = tk.Canvas(mapped_frame, width=300, height=300, bg="gray")
self.canvas_out.pack()
self.canvas_out.bind("<Button-1>", self.pick_color_out)
self.hex_out_label = tk.Label(mapped_frame, text="Mapped HEX: ---")
self.hex_out_label.pack(pady=2)
full_frame = tk.Frame(self.frame_bottom)
full_frame.grid(row=0, column=1, padx=10)
tk.Label(full_frame, text="HSV Full (Offset Blend)").pack()
self.canvas_full = tk.Canvas(full_frame, width=300, height=300, bg="gray")
self.canvas_full.pack()
self.canvas_full.bind("<Button-1>", self.pick_color_full)
self.hex_full_label = tk.Label(full_frame, text="HSV Full HEX: ---")
self.hex_full_label.pack(pady=2)
# 右:コントロール
self.frame_right = tk.Frame(root, width=250)
self.frame_right.pack(side="right", fill="y")
self.frame_right.pack_propagate(False)
tk.Button(self.frame_right, text="フォルダを選択", command=self.load_folder).pack(pady=8, fill="x", padx=8)
tk.Button(self.frame_right, text="参照画像を読み込み", command=self.load_reference_image).pack(pady=8, fill="x", padx=8)
tk.Button(self.frame_right, text="Mapped を保存", command=self.save_transformed_image).pack(pady=8, fill="x", padx=8)
tk.Button(self.frame_right, text="HSV Full を保存", command=self.save_full_image).pack(pady=8, fill="x", padx=8)
tk.Button(self.frame_right, text="バッチ処理(フォルダ内全て)", command=self.batch_process_folder).pack(pady=8, fill="x", padx=8)
self.h_scale = tk.Scale(self.frame_right, from_=-180, to=180, orient="horizontal", label="Hue Offset (°)", command=self._on_slider_change)
self.h_scale.set(0)
self.h_scale.pack(fill="x", padx=8)
self.s_scale = tk.Scale(self.frame_right, from_=-100, to=100, orient="horizontal", label="Saturation Offset (%)", command=self._on_slider_change)
self.s_scale.set(0)
self.s_scale.pack(fill="x", padx=8)
self.v_scale = tk.Scale(self.frame_right, from_=-100, to=100, orient="horizontal", label="Value Offset (%)", command=self._on_slider_change)
self.v_scale.set(0)
self.v_scale.pack(fill="x", padx=8)
self.strength_scale = tk.Scale(self.frame_right, from_=0, to=100, orient="horizontal", label="Strength (%)", command=self._on_slider_change)
self.strength_scale.set(100)
self.strength_scale.pack(fill="x", padx=8)
# 初期値: Apply Hue のみ ON
self.h_var = tk.IntVar(value=1)
self.s_var = tk.IntVar(value=0)
self.v_var = tk.IntVar(value=0)
tk.Checkbutton(self.frame_right, text="Apply Hue", variable=self.h_var, command=self._on_slider_change).pack(anchor="w", padx=8)
tk.Checkbutton(self.frame_right, text="Apply Saturation", variable=self.s_var, command=self._on_slider_change).pack(anchor="w", padx=8)
tk.Checkbutton(self.frame_right, text="Apply Value", variable=self.v_var, command=self._on_slider_change).pack(anchor="w", padx=8)
# 初期化
self.main_img = None
self.ref_img = None
self.target_hsv = (0.0, 0.0, 1.0)
self.transformed_img = None
self.full_img = None
self.current_filename = None
self.thumbnails = []
# -----------------------------
# スクロール領域更新
# -----------------------------
def _on_frame_configure(self, event):
self.scroll_canvas.configure(scrollregion=self.scroll_canvas.bbox("all"))
def _bind_mousewheel(self, enable):
if enable:
self.scroll_canvas.bind_all("<MouseWheel>", self._on_mousewheel)
self.scroll_canvas.bind_all("<Button-4>", self._on_mousewheel)
self.scroll_canvas.bind_all("<Button-5>", self._on_mousewheel)
else:
self.scroll_canvas.unbind_all("<MouseWheel>")
self.scroll_canvas.unbind_all("<Button-4>")
self.scroll_canvas.unbind_all("<Button-5>")
def _on_mousewheel(self, event):
if hasattr(event, "num") and event.num in (4, 5):
if event.num == 4:
self.scroll_canvas.yview_scroll(-1, "units")
else:
self.scroll_canvas.yview_scroll(1, "units")
else:
delta = int(-1 * (event.delta / 120))
self.scroll_canvas.yview_scroll(delta, "units")
# -----------------------------
# フォルダ読み込み(サムネイル)
# -----------------------------
def load_folder(self):
folder = filedialog.askdirectory()
if not folder:
return
for widget in self.thumb_frame.winfo_children():
widget.destroy()
self.thumbnails.clear()
files = sorted([f for f in os.listdir(folder) if f.lower().endswith((".png", ".jpg", ".jpeg"))])
for f in files:
full = os.path.join(folder, f)
try:
img = Image.open(full)
except Exception:
continue
thumb = img.copy()
thumb.thumbnail((150, 150))
w_t, h_t = thumb.size
checker_thumb = make_checkerboard((150, 150), tile=6, color1=(255,255,255), color2=(220,220,220))
if thumb.mode == "RGBA":
bg_thumb = checker_thumb.copy()
bg_thumb.paste(thumb, ((150 - w_t) // 2, (150 - h_t) // 2), thumb.split()[-1])
disp_thumb = bg_thumb.convert("RGB")
else:
bg_thumb = checker_thumb.copy()
bg_thumb.paste(thumb, ((150 - w_t) // 2, (150 - h_t) // 2))
disp_thumb = bg_thumb
tk_thumb = ImageTk.PhotoImage(disp_thumb)
self.thumbnails.append(tk_thumb)
item_frame = tk.Frame(self.thumb_frame, bd=1, relief="flat")
item_frame.pack(padx=4, pady=6, fill="x")
lbl_img = tk.Label(item_frame, image=tk_thumb)
lbl_img.image = tk_thumb
lbl_img.pack(side="top", padx=4)
lbl_img.bind("<Button-1>", partial(self.on_thumbnail_click, full))
name_label = tk.Label(item_frame, text=f, wraplength=200, anchor="w", justify="left")
name_label.pack(side="top", padx=6, pady=(4,0))
self.thumb_frame.update_idletasks()
self.scroll_canvas.configure(scrollregion=self.scroll_canvas.bbox("all"))
def on_thumbnail_click(self, fullpath, event):
try:
img = Image.open(fullpath)
except Exception:
return
self.main_img = img
self.current_filename = os.path.basename(fullpath)
self.show_image(self.canvas_main, self.main_img)
self.update_hsv_from_slider()
def load_reference_image(self):
path = filedialog.askopenfilename(filetypes=[("Image files", "*.png;*.jpg;*.jpeg")])
if not path:
return
try:
self.ref_img = Image.open(path)
except Exception:
return
self.show_image(self.canvas_ref, self.ref_img)
# -----------------------------
# show_image をチェッカーボード表示対応に置き換え
# -----------------------------
def show_image(self, canvas, img):
canvas.delete("all")
tmp = img.copy()
tmp.thumbnail((300, 300))
w, h = tmp.size
checker = make_checkerboard((300, 300), tile=10, color1=(255,255,255), color2=(220,220,220))
if tmp.mode == "RGBA":
bg = checker.copy()
bg.paste(tmp, ((300 - w) // 2, (300 - h) // 2), tmp.split()[-1])
disp = bg
else:
bg = checker.copy()
bg.paste(tmp, ((300 - w) // 2, (300 - h) // 2))
disp = bg
tk_img = ImageTk.PhotoImage(disp)
canvas.image = tk_img
canvas.create_image(0, 0, anchor="nw", image=tk_img)
# -----------------------------
# クリックで色を選択(オリジナル)
# -----------------------------
def pick_color_main(self, event):
if not self.main_img:
return
self._pick_color_generic(event, self.main_img, self.original_swatch, self.hex_main_label, "Original HEX")
# -----------------------------
# 参照クリックで参照色を設定 → 左下(Mapped)を参照色に寄せる
# -----------------------------
def pick_color_ref(self, event):
if not self.ref_img:
return
result = self._pick_color_generic(event, self.ref_img, self.ref_swatch, self.hex_ref_label, "Reference HEX", return_rgb=True)
if not result:
return
r, g, b = result
h, s, v = rgb_to_hsv_tuple(r, g, b)
self.target_hsv = (h, s, v)
try:
self.h_scale.set(int(h * 360.0 - 180.0))
except Exception:
pass
try:
self.s_scale.set(int((s - 0.5) * 200.0))
self.v_scale.set(int((v - 0.5) * 200.0))
except Exception:
pass
try:
self.strength_scale.set(100)
except Exception:
pass
self.update_hsv_from_slider()
# -----------------------------
# 汎用クリック色取得(キャンバス上の座標を画像座標に変換)
# -----------------------------
def _pick_color_generic(self, event, img, swatch, label, prefix, return_rgb=False):
canvas = event.widget
canvas_x = event.x
canvas_y = event.y
img_w, img_h = img.size
scale = min(300 / img_w, 300 / img_h)
disp_w = int(img_w * scale)
disp_h = int(img_h * scale)
offset_x = (300 - disp_w) // 2
offset_y = (300 - disp_h) // 2
if not (offset_x <= canvas_x < offset_x + disp_w and offset_y <= canvas_y < offset_y + disp_h):
return None
rel_x = canvas_x - offset_x
rel_y = canvas_y - offset_y
img_x = int(rel_x / scale)
img_y = int(rel_y / scale)
img_x = max(0, min(img_w - 1, img_x))
img_y = max(0, min(img_h - 1, img_y))
px = img.getpixel((img_x, img_y))
if isinstance(px, tuple):
r, g, b = px[0], px[1], px[2]
else:
r = g = b = px
hex_color = rgb_to_hex(r, g, b)
try:
swatch.config(bg=hex_color)
label.config(text=f"{prefix}: {hex_color}")
except Exception:
pass
if return_rgb:
return (r, g, b)
return None
# -----------------------------
# Mapped / Full のクリックで色取得
# -----------------------------
def pick_color_out(self, event):
if not self.transformed_img:
return
self._pick_color_generic(event, self.transformed_img, None, self.hex_out_label, "Mapped HEX")
def pick_color_full(self, event):
if not self.full_img:
return
self._pick_color_generic(event, self.full_img, None, self.hex_full_label, "HSV Full HEX")
# -----------------------------
# スライダーで変換を更新
# -----------------------------
def _on_slider_change(self, event=None):
self.update_hsv_from_slider()
def update_hsv_from_slider(self, event=None):
if not self.main_img:
return
strength_mapped = 1.0
use_h = self.h_var.get() == 1
use_s = self.s_var.get() == 1
use_v = self.v_var.get() == 1
try:
self.transformed_img = apply_hsv_map_to_target_numpy(self.main_img, self.target_hsv, strength_mapped, use_h, use_s, use_v)
self.show_image(self.canvas_out, self.transformed_img)
except Exception:
self.transformed_img = None
try:
h_deg = self.h_scale.get()
h_offset = (h_deg / 360.0)
s_off = self.s_scale.get() / 100.0
v_off = self.v_scale.get() / 100.0
strength_full = self.strength_scale.get() / 100.0
use_h_full = self.h_var.get() == 1
use_s_full = self.s_var.get() == 1
use_v_full = self.v_var.get() == 1
self.full_img = apply_hsv_blend_offset_numpy(self.main_img, h_offset, s_off, v_off, strength_full, use_h_full, use_s_full, use_v_full)
self.show_image(self.canvas_full, self.full_img)
except Exception:
self.full_img = None
# -----------------------------
# バッチ処理(フォルダ内全て)
# -----------------------------
def batch_process_folder(self):
folder = filedialog.askdirectory(title="バッチ処理するフォルダを選択")
if not folder:
return
parent = os.path.dirname(folder)
base = os.path.basename(folder.rstrip("/\\"))
out_folder = os.path.join(parent, base + "_batch_out")
os.makedirs(out_folder, exist_ok=True)
target_hsv = self.target_hsv
if self.ref_img is None and (target_hsv is None or target_hsv == (0.0, 0.0, 1.0)):
h_deg = self.h_scale.get()
h = (h_deg / 360.0) % 1.0
s = (self.s_scale.get() / 200.0) + 0.5
v = (self.v_scale.get() / 200.0) + 0.5
target_hsv = (h, max(0.0, min(1.0, s)), max(0.0, min(1.0, v)))
use_h = self.h_var.get() == 1
use_s = self.s_var.get() == 1
use_v = self.v_var.get() == 1
strength_mapped = 1.0
strength_full = self.strength_scale.get() / 100.0
h_offset_full = self.h_scale.get() / 360.0
s_offset_full = self.s_scale.get() / 100.0
v_offset_full = self.v_scale.get() / 100.0
files = sorted([f for f in os.listdir(folder) if f.lower().endswith((".png", ".jpg", ".jpeg"))])
if not files:
messagebox.showinfo("バッチ処理", "指定フォルダに画像ファイルが見つかりません。")
return
total = len(files)
processed = 0
errors = []
for fname in files:
src = os.path.join(folder, fname)
try:
img = Image.open(src)
except Exception as e:
errors.append((fname, str(e)))
continue
base_name, _ = os.path.splitext(fname)
try:
mapped = apply_hsv_map_to_target_numpy(img, target_hsv, strength_mapped, use_h, use_s, use_v)
out_mapped = os.path.join(out_folder, base_name + "_mapping.png")
mapped.save(out_mapped, format="PNG")
except Exception as e:
errors.append((fname + " (mapped)", str(e)))
try:
full = apply_hsv_blend_offset_numpy(img, h_offset_full, s_offset_full, v_offset_full, strength_full, use_h, use_s, use_v)
out_full = os.path.join(out_folder, base_name + "_hsv_full.png")
full.save(out_full, format="PNG")
except Exception as e:
errors.append((fname + " (full)", str(e)))
processed += 1
msg = f"バッチ処理が完了しました。\n処理済み: {processed}/{total}\n出力フォルダ: {out_folder}"
if errors:
msg += f"\nエラー件数: {len(errors)}(詳細はコンソール参照)"
print("Batch errors:")
for e in errors:
print(e)
messagebox.showinfo("バッチ処理 完了", msg)
# -----------------------------
# 保存
# -----------------------------
def save_transformed_image(self):
if not self.transformed_img or not self.current_filename:
return
base, ext = os.path.splitext(self.current_filename)
save_name = base + "_mapping.png"
path = filedialog.asksaveasfilename(initialfile=save_name, defaultextension=".png")
if path:
self.transformed_img.save(path, format="PNG")
print("Saved:", path)
def save_full_image(self):
if not self.full_img or not self.current_filename:
return
base, ext = os.path.splitext(self.current_filename)
save_name = base + "_hsv_full.png"
path = filedialog.asksaveasfilename(initialfile=save_name, defaultextension=".png")
if path:
self.full_img.save(path, format="PNG")
print("Saved HSV Full:", path)
# -----------------------------
if __name__ == "__main__":
root = tk.Tk()
app = ColorTool(root)
root.mainloop()









ディスカッション
コメント一覧
まだ、コメントがありません