6 Commits
1.0 ... main

2 changed files with 310 additions and 56 deletions

294
main.py
View File

@@ -1,8 +1,9 @@
import tkinter as tk import customtkinter as ctk
from tkinter import filedialog, messagebox, simpledialog, ttk from tkinter import filedialog, messagebox, simpledialog, ttk
from PIL import Image, ImageTk from PIL import Image, ImageTk
import numpy as np import numpy as np
import json import json
import os
# Load translations from JSON file # Load translations from JSON file
with open('translations.json', 'r', encoding='utf-8') as f: with open('translations.json', 'r', encoding='utf-8') as f:
@@ -30,7 +31,7 @@ def open_file():
global file_path, image, tk_image, zoom_level global file_path, image, tk_image, zoom_level
file_path = filedialog.askopenfilename(title=translations["open_file"][language.get()], filetypes=[("All Files", "*.*")]) file_path = filedialog.askopenfilename(title=translations["open_file"][language.get()], filetypes=[("All Files", "*.*")])
if file_path: if file_path:
file_label.config(text=f"{translations['selected_file'][language.get()]} {file_path.split('/')[-1]}") file_label.configure(text=f"{translations['selected_file'][language.get()]} {file_path.split('/')[-1]}")
try: try:
# Проверка расширения # Проверка расширения
if not file_path.lower().endswith('.bin'): if not file_path.lower().endswith('.bin'):
@@ -44,7 +45,7 @@ def open_file():
except Exception as e: except Exception as e:
messagebox.showerror(translations["error"][language.get()], f"{translations['file_processing_error'][language.get()]}: {e}") messagebox.showerror(translations["error"][language.get()], f"{translations['file_processing_error'][language.get()]}: {e}")
else: else:
file_label.config(text=translations["no_file_selected"][language.get()]) file_label.configure(text=translations["no_file_selected"][language.get()])
def reload_image(): def reload_image():
global image, tk_image global image, tk_image
@@ -61,8 +62,8 @@ def display_image():
if image: if image:
zoomed_image = image.resize((int(image.width * zoom_level), int(image.height * zoom_level)), Image.NEAREST) zoomed_image = image.resize((int(image.width * zoom_level), int(image.height * zoom_level)), Image.NEAREST)
tk_image = ImageTk.PhotoImage(zoomed_image) tk_image = ImageTk.PhotoImage(zoomed_image)
canvas.config(scrollregion=(0, 0, zoomed_image.width, zoomed_image.height)) canvas.configure(scrollregion=(0, 0, zoomed_image.width, zoomed_image.height))
canvas.create_image(0, 0, anchor=tk.NW, image=tk_image) canvas.create_image(0, 0, anchor=ctk.NW, image=tk_image)
# Clear existing grid lines # Clear existing grid lines
canvas.delete("grid_line") canvas.delete("grid_line")
@@ -133,11 +134,11 @@ def open_table_window():
messagebox.showerror(translations["error"][language.get()], translations["open_image_first"][language.get()]) messagebox.showerror(translations["error"][language.get()], translations["open_image_first"][language.get()])
return return
table_window = tk.Toplevel(root) table_window = ctk.CTkToplevel(root)
table_window.title(translations["pixel_table"][language.get()]) table_window.title(translations["pixel_table"][language.get()])
table_frame = tk.Frame(table_window) table_frame = ctk.CTkFrame(table_window)
table_frame.pack(fill=tk.BOTH, expand=True) table_frame.pack(fill=ctk.BOTH, expand=True)
columns = simpledialog.askinteger(translations["table_settings"][language.get()], translations["enter_columns"][language.get()], initialvalue=16) columns = simpledialog.askinteger(translations["table_settings"][language.get()], translations["enter_columns"][language.get()], initialvalue=16)
if not columns: if not columns:
@@ -148,13 +149,13 @@ def open_table_window():
tree.heading(f"col{i}", text=f"{translations['column'][language.get()]} {i+1}") tree.heading(f"col{i}", text=f"{translations['column'][language.get()]} {i+1}")
tree.column(f"col{i}", width=50, anchor='center') tree.column(f"col{i}", width=50, anchor='center')
scrollbar_y = ttk.Scrollbar(table_frame, orient=tk.VERTICAL, command=tree.yview) scrollbar_y = ttk.Scrollbar(table_frame, orient=ctk.VERTICAL, command=tree.yview)
scrollbar_x = ttk.Scrollbar(table_frame, orient=tk.HORIZONTAL, command=tree.xview) scrollbar_x = ttk.Scrollbar(table_frame, orient=ctk.HORIZONTAL, command=tree.xview)
tree.configure(yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set) tree.configure(yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set)
scrollbar_y.pack(side=tk.RIGHT, fill=tk.Y) scrollbar_y.pack(side=ctk.RIGHT, fill=ctk.Y)
scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X) scrollbar_x.pack(side=ctk.BOTTOM, fill=ctk.X)
tree.pack(fill=tk.BOTH, expand=True) tree.pack(fill=ctk.BOTH, expand=True)
pixels = np.array(image.convert('L')) pixels = np.array(image.convert('L'))
rows = pixels.shape[0] rows = pixels.shape[0]
@@ -167,7 +168,7 @@ def open_table_window():
values.append(pixels[row, col]) values.append(pixels[row, col])
else: else:
values.append('') values.append('')
tree.insert('', tk.END, values=values) tree.insert('', ctk.END, values=values)
def export_image(): def export_image():
if not image: if not image:
@@ -219,25 +220,198 @@ def update_zoom_level(value):
def bind_hot_reload(event): def bind_hot_reload(event):
reload_image() reload_image()
def export_to_folder():
if not image:
messagebox.showerror(translations["error"][language.get()], translations["open_image_first"][language.get()])
return
folder_path = filedialog.askdirectory(title=translations["select_folder"][language.get()])
if not folder_path:
return
try:
width = int(width_entry.get())
cell_size = width
img_width, img_height = image.size
# Create info file
with open(os.path.join(folder_path, 'info.txt'), 'w') as f:
f.write(str(width))
# Calculate grid dimensions
cols = img_width // cell_size
rows = img_height // cell_size
# Save each cell
for row in range(rows):
for col in range(cols):
left = col * cell_size
upper = row * cell_size
right = left + cell_size
lower = upper + cell_size
cell = image.crop((left, upper, right, lower))
cell.save(os.path.join(folder_path, f"{row}_{col}.tiff"))
messagebox.showinfo(translations["success"][language.get()], translations["image_exported"][language.get()])
except Exception as e:
messagebox.showerror(translations["error"][language.get()], f"Export error: {str(e)}")
def import_from_folder():
global image, zoom_level
folder_path = filedialog.askdirectory(title=translations["select_folder"][language.get()])
if not folder_path:
return
try:
# Read width info
with open(os.path.join(folder_path, 'info.txt'), 'r') as f:
saved_width = int(f.read().strip())
current_width = int(width_entry.get())
if saved_width != current_width:
messagebox.showerror(
translations["error"][language.get()],
translations["width_mismatch_error"][language.get()].format(
expected=current_width, actual=saved_width
)
)
return
# Collect and sort cell files
cell_files = []
for fname in os.listdir(folder_path):
if fname.endswith('.tiff'):
try:
parts = fname.split('_')
row = int(parts[0])
col = int(parts[1].split('.')[0])
cell_files.append((row, col, fname))
except:
messagebox.showerror(
translations["error"][language.get()],
translations["invalid_filename_error"][language.get()].format(filename=fname))
return
if not cell_files:
messagebox.showerror(translations["error"][language.get()], translations["no_cell_files"][language.get()])
return
# Sort files by row and column
cell_files.sort()
max_row = max(r for r, c, _ in cell_files)
max_col = max(c for r, c, _ in cell_files)
# Create empty image
cell_size = current_width
full_width = (max_col + 1) * cell_size
full_height = (max_row + 1) * cell_size
new_image = Image.new('L', (full_width, full_height))
# Paste cells
for row, col, fname in cell_files:
cell_path = os.path.join(folder_path, fname)
cell = Image.open(cell_path).convert('L')
x = col * cell_size
y = row * cell_size
new_image.paste(cell, (x, y))
image = new_image
zoom_level = 1.0
display_image()
messagebox.showinfo(
translations["success"][language.get()],
translations["image_imported"][language.get()]
)
except Exception as e:
messagebox.showerror(translations["error"][language.get()], f"Import error: {str(e)}")
def update_language(*args): def update_language(*args):
language_code = language.get() language_code = language.get()
root.title(translations["title"][language_code]) root.title(translations["title"][language_code])
notebook.tab(editor_tab, text=translations["editor_tab"][language_code]) notebook.tab(editor_tab, text=translations["editor_tab"][language_code])
notebook.tab(settings_tab, text=translations["settings_tab"][language_code]) notebook.tab(settings_tab, text=translations["settings_tab"][language_code])
file_label.config(text=translations["no_file_selected"][language_code]) file_label.configure(text=translations["no_file_selected"][language_code])
open_button.config(text=translations["open_file_button"][language_code]) open_button.configure(text=translations["open_file_button"][language_code])
width_label.config(text=translations["width_label"][language_code]) width_label.configure(text=translations["width_label"][language_code])
table_button.config(text=translations["open_table_button"][language_code]) table_button.configure(text=translations["open_table_button"][language_code])
save_button.config(text=translations["save_image_button"][language_code]) save_button.configure(text=translations["save_image_button"][language_code])
save_bin_button.config(text=translations["save_bin_button"][language_code]) save_bin_button.configure(text=translations["save_bin_button"][language.get()])
reload_button.config(text=translations["reload_image_button"][language_code]) reload_button.configure(text=translations["reload_image_button"][language_code])
redraw_grid_button.config(text=translations["redraw_grid_button"][language_code]) redraw_grid_button.configure(text=translations["redraw_grid_button"][language_code])
export_button.config(text=translations["export_image_button"][language_code]) export_button.configure(text=translations["export_image_button"][language_code])
import_button.config(text=translations["import_image_button"][language_code]) import_button.configure(text=translations["import_image_button"][language.get()])
language_label.config(text=translations["language_label"][language_code]) language_label.configure(text=translations["language_label"][language_code])
grid_color_label.config(text=translations["grid_color_label"][language_code]) grid_color_label.configure(text=translations["grid_color_label"][language_code])
show_kanji_button.configure(text=translations["show_kanji_button"][language_code])
export_folder_button.configure(text=translations["export_folder_button"][language.get()])
import_folder_button.configure(text=translations["import_folder_button"][language.get()])
root = tk.Tk() def fromhex(a):
hex_from_str = {'0':'0000', '1':'0001', '2':'0010', '3':'0011',
'4':'0100', '5':'0101', '6':'0110', '7':'0111', '8':'1000',
'9':'1001', 'A':'1010', 'B':'1011', 'C':'1100', 'D':'1101',
'E':'1110', 'F':'1111',}
b = ''
for char in a:
b += hex_from_str[char]
return int(b, 2)
def show(kanji, file_path):
RESIZE = 5
name = kanji
if kanji > 0x8000:
kanji -= 0x8000
else:
messagebox.showerror(translations["error"][language.get()], translations["enter_hex_code"][language.get()])
return
with open(file_path, 'rb') as f:
f = f.read()
offset = kanji * 32
tile = f[offset:offset + 32]
picture = Image.new('1', (16, 16))
x = 0
y = 0
for i in range(0, 32, 2):
bitmap = int.from_bytes(tile[i:i + 2])
bitmap = bin(bitmap)[2:].zfill(16)
for number in bitmap:
if number == '1':
picture.putpixel((x, y), 1)
x += 1
else:
x += 1
y += 1
x = 0
picture = picture.resize((16 * RESIZE, 16 * RESIZE))
picture.show(title=name)
def open_kanji_window():
if not file_path:
messagebox.showerror(translations["error"][language.get()], translations["open_file_first"][language.get()])
return
kanji_window = ctk.CTkToplevel(root)
kanji_window.title(translations["show_kanji_button"][language.get()])
kanji_label = ctk.CTkLabel(kanji_window, text=translations["enter_hex_code"][language.get()])
kanji_label.pack(pady=5)
kanji_entry = ctk.CTkEntry(kanji_window)
kanji_entry.pack(pady=5)
def show_kanji():
kanji_hex = kanji_entry.get()
try:
kanji = fromhex(kanji_hex)
show(kanji, file_path)
except Exception as e:
messagebox.showerror(translations["error"][language.get()], f"{translations['file_processing_error'][language.get()]}: {e}")
show_button = ctk.CTkButton(kanji_window, text=translations["show_kanji_button"][language.get()], command=show_kanji)
show_button.pack(pady=5)
root = ctk.CTk()
root.title(translations["title"]["en"]) root.title(translations["title"]["en"])
file_path = "" file_path = ""
@@ -248,7 +422,7 @@ drawing = False
new_color = None new_color = None
# Language selection # Language selection
language = tk.StringVar(value="en") language = ctk.StringVar(value="en")
language_options = ["en", "ru", "eo", "ja", "uk"] language_options = ["en", "ru", "eo", "ja", "uk"]
# Notebook for tabs # Notebook for tabs
@@ -259,57 +433,64 @@ notebook.pack(expand=True, fill="both")
editor_tab = ttk.Frame(notebook) editor_tab = ttk.Frame(notebook)
notebook.add(editor_tab, text=translations["editor_tab"]["en"]) notebook.add(editor_tab, text=translations["editor_tab"]["en"])
file_label = tk.Label(editor_tab, text=translations["no_file_selected"]["en"]) file_label = ctk.CTkLabel(editor_tab, text=translations["no_file_selected"]["en"])
file_label.pack(pady=5) file_label.pack(pady=5)
open_button = tk.Button(editor_tab, text=translations["open_file_button"]["en"], command=open_file) open_button = ctk.CTkButton(editor_tab, text=translations["open_file_button"]["en"], command=open_file)
open_button.pack(pady=5) open_button.pack(pady=5)
width_label = tk.Label(editor_tab, text=translations["width_label"]["en"]) width_label = ctk.CTkLabel(editor_tab, text=translations["width_label"]["en"])
width_label.pack(pady=5) width_label.pack(pady=5)
width_entry = tk.Entry(editor_tab) width_entry = ctk.CTkEntry(editor_tab)
width_entry.insert(0, "128") width_entry.insert(0, "128")
width_entry.pack(pady=5) width_entry.pack(pady=5)
table_button = tk.Button(editor_tab, text=translations["open_table_button"]["en"], command=open_table_window) table_button = ctk.CTkButton(editor_tab, text=translations["open_table_button"]["en"], command=open_table_window)
table_button.pack(pady=5) table_button.pack(pady=5)
save_button = tk.Button(editor_tab, text=translations["save_image_button"]["en"], command=save_image) save_button = ctk.CTkButton(editor_tab, text=translations["save_image_button"]["en"], command=save_image)
save_button.pack(pady=5) save_button.pack(pady=5)
save_bin_button = tk.Button(editor_tab, text=translations["save_bin_button"]["en"], command=save_to_bin) save_bin_button = ctk.CTkButton(editor_tab, text=translations["save_bin_button"]["en"], command=save_to_bin)
save_bin_button.pack(pady=5) save_bin_button.pack(pady=5)
reload_button = tk.Button(editor_tab, text=translations["reload_image_button"]["en"], command=reload_image) reload_button = ctk.CTkButton(editor_tab, text=translations["reload_image_button"]["en"], command=reload_image)
reload_button.pack(pady=5) reload_button.pack(pady=5)
redraw_grid_button = tk.Button(editor_tab, text=translations["redraw_grid_button"]["en"], command=redraw_grid) redraw_grid_button = ctk.CTkButton(editor_tab, text=translations["redraw_grid_button"]["en"], command=redraw_grid)
redraw_grid_button.pack(pady=5) redraw_grid_button.pack(pady=5)
export_button = tk.Button(editor_tab, text=translations["export_image_button"]["en"], command=export_image) export_button = ctk.CTkButton(editor_tab, text=translations["export_image_button"]["en"], command=export_image)
export_button.pack(pady=5) export_button.pack(pady=5)
import_button = tk.Button(editor_tab, text=translations["import_image_button"]["en"], command=import_image) import_button = ctk.CTkButton(editor_tab, text=translations["import_image_button"]["en"], command=import_image)
import_button.pack(pady=5) import_button.pack(pady=5)
canvas_frame = tk.Frame(editor_tab) show_kanji_button = ctk.CTkButton(editor_tab, text=translations["show_kanji_button"]["en"], command=open_kanji_window)
canvas_frame.pack(fill=tk.BOTH, expand=True) show_kanji_button.pack(pady=5)
export_folder_button = ctk.CTkButton(editor_tab, text=translations["export_folder_button"][language.get()], command=export_to_folder)
export_folder_button.pack(pady=5)
canvas = tk.Canvas(canvas_frame, bg="white") import_folder_button = ctk.CTkButton(editor_tab, text=translations["import_folder_button"][language.get()], command=import_from_folder)
canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) import_folder_button.pack(pady=5)
canvas_frame = ctk.CTkFrame(editor_tab)
canvas_frame.pack(fill=ctk.BOTH, expand=True)
scrollbar_y = tk.Scrollbar(canvas_frame, orient=tk.VERTICAL, command=canvas.yview) canvas = ctk.CTkCanvas(canvas_frame, bg="white")
scrollbar_y.pack(side=tk.RIGHT, fill=tk.Y) canvas.pack(side=ctk.LEFT, fill=ctk.BOTH, expand=True)
canvas.config(yscrollcommand=scrollbar_y.set)
scrollbar_x = tk.Scrollbar(editor_tab, orient=tk.HORIZONTAL, command=canvas.xview) scrollbar_y = ctk.CTkScrollbar(canvas_frame, orientation="vertical", command=canvas.yview)
scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X) scrollbar_y.pack(side=ctk.RIGHT, fill=ctk.Y)
canvas.config(xscrollcommand=scrollbar_x.set) canvas.configure(yscrollcommand=scrollbar_y.set)
zoom_scale = tk.Scale(editor_tab, from_=100, to=500, orient=tk.HORIZONTAL, label="Zoom Level (%)", command=update_zoom_level) scrollbar_x = ctk.CTkScrollbar(editor_tab, orientation="horizontal", command=canvas.xview)
scrollbar_x.pack(side=ctk.BOTTOM, fill=ctk.X)
canvas.configure(xscrollcommand=scrollbar_x.set)
zoom_scale = ctk.CTkSlider(editor_tab, from_=100, to=500, orientation="horizontal", command=update_zoom_level)
zoom_scale.set(100) zoom_scale.set(100)
zoom_scale.pack(side=tk.BOTTOM, fill=tk.X) zoom_scale.pack(side=ctk.BOTTOM, fill=ctk.X)
canvas.bind("<Button-1>", start_drawing) canvas.bind("<Button-1>", start_drawing)
canvas.bind("<B1-Motion>", draw) canvas.bind("<B1-Motion>", draw)
@@ -323,16 +504,16 @@ root.bind('<Control-r>', bind_hot_reload)
settings_tab = ttk.Frame(notebook) settings_tab = ttk.Frame(notebook)
notebook.add(settings_tab, text=translations["settings_tab"]["en"]) notebook.add(settings_tab, text=translations["settings_tab"]["en"])
language_label = tk.Label(settings_tab, text=translations["language_label"]["en"]) language_label = ctk.CTkLabel(settings_tab, text=translations["language_label"]["en"])
language_label.pack(pady=5) language_label.pack(pady=5)
language_menu = tk.OptionMenu(settings_tab, language, *language_options, command=update_language) language_menu = ctk.CTkOptionMenu(settings_tab, variable=language, values=language_options, command=update_language)
language_menu.pack(pady=5) language_menu.pack(pady=5)
grid_color_label = tk.Label(settings_tab, text=translations["grid_color_label"]["en"]) grid_color_label = ctk.CTkLabel(settings_tab, text=translations["grid_color_label"]["en"])
grid_color_label.pack(pady=5) grid_color_label.pack(pady=5)
grid_color_entry = tk.Entry(settings_tab) grid_color_entry = ctk.CTkEntry(settings_tab)
grid_color_entry.insert(0, "255,0,0") grid_color_entry.insert(0, "255,0,0")
grid_color_entry.pack(pady=5) grid_color_entry.pack(pady=5)
@@ -341,3 +522,6 @@ update_language()
# Запуск основного цикла приложения # Запуск основного цикла приложения
root.mainloop() root.mainloop()
#TODO: Пофиксить увеличение

View File

@@ -174,6 +174,13 @@
"ja": "まず画像を開いてください", "ja": "まず画像を開いてください",
"uk": "Спочатку відкрийте зображення" "uk": "Спочатку відкрийте зображення"
}, },
"open_file_first": {
"en": "Open a file first",
"ru": "Сначала откройте файл",
"eo": "Unue malfermu dosieron",
"ja": "まずファイルを開いてください",
"uk": "Спочатку відкрийте файл"
},
"pixel_table": { "pixel_table": {
"en": "Pixel Table", "en": "Pixel Table",
"ru": "Таблица пикселей", "ru": "Таблица пикселей",
@@ -229,5 +236,68 @@
"eo": "Okazis eraro dum la importado de la bildo", "eo": "Okazis eraro dum la importado de la bildo",
"ja": "画像のインポート中にエラーが発生しました", "ja": "画像のインポート中にエラーが発生しました",
"uk": "Сталася помилка під час імпорту зображення" "uk": "Сталася помилка під час імпорту зображення"
},
"show_kanji_button": {
"en": "Show Kanji",
"ru": "Показать кандзи",
"eo": "Montri Kandzi",
"ja": "漢字を表示",
"uk": "Показати кандзі"
},
"enter_hex_code": {
"en": "Enter the hex code of the character:",
"ru": "Введите hex-код символа:",
"eo": "Enigu la heksan kodon de la signo:",
"ja": "文字の16進コードを入力してください:",
"uk": "Введіть hex-код символу:"
},
"export_folder_button": {
"en": "Export as Folder",
"ru": "Экспортировать как папку",
"eo": "Eksporti kiel Dosierujo",
"ja": "フォルダとしてエクスポート",
"uk": "Експортувати як папку"
},
"import_folder_button": {
"en": "Import from Folder",
"ru": "Импортировать из папки",
"eo": "Importi el Dosierujo",
"ja": "フォルダからインポート",
"uk": "Імпортувати з папки"
},
"width_mismatch_error": {
"en": "Width mismatch! Expected: {expected}, Actual: {actual}",
"ru": "Несоответствие ширины! Ожидалось: {expected}, Фактическая: {actual}",
"eo": "Larĝo-neakordo! Atendita: {expected}, Reala: {actual}",
"ja": "幅が一致しません! 期待: {expected}, 実際: {actual}",
"uk": "Невідповідність ширини! Очікувано: {expected}, Фактична: {actual}"
},
"invalid_filename_error": {
"en": "Invalid filename format: {filename}",
"ru": "Недопустимый формат имени файла: {filename}",
"eo": "Nevalida dosiernoma formato: {filename}",
"ja": "無効なファイル名形式: {filename}",
"uk": "Невірний формат імені файлу: {filename}"
},
"select_folder": {
"en": "Select Folder",
"ru": "Выберите папку",
"eo": "Elektu Dosierujon",
"ja": "フォルダを選択",
"uk": "Виберіть папку"
},
"image_imported": {
"en": "Image imported successfully!",
"ru": "Изображение успешно импортировано!",
"eo": "Bildo importita sukcese!",
"ja": "画像のインポートが成功しました!",
"uk": "Зображення успішно імпортовано!"
},
"no_cell_files": {
"en": "No cell files found!",
"ru": "Файлы клеток не найдены!",
"eo": "Neniu ĉeldosieroj trovitaj!",
"ja": "セルファイルが見つかりません!",
"uk": "Файли клітинок не знайдено!"
} }
} }