From 4e82f20a27752685a62ca0681046a2a5ed320633 Mon Sep 17 00:00:00 2001 From: Klemek Date: Fri, 15 Mar 2024 17:36:32 +0100 Subject: [PATCH] Revert "OVERALL FIX" This reverts commit c26ab2521217aed75d2bce879ea0b483b6554499. --- watchy-image-editor/__init__.py | 0 watchy-image-editor/app.py | 325 +++++++++++++++++++++++++++++ watchy-image-editor/bitmap.py | 119 +++++++++++ watchy-image-editor/explorer.py | 111 ++++++++++ watchy-image-editor/file.py | 76 +++++++ watchy-image-editor/image.py | 94 +++++++++ watchy-image-editor/image_view.py | 103 +++++++++ watchy-image-editor/input_popup.py | 26 +++ watchy-image-editor/main.py | 9 + watchy-image-editor/preview.png | Bin 0 -> 65097 bytes 10 files changed, 863 insertions(+) create mode 100644 watchy-image-editor/__init__.py create mode 100644 watchy-image-editor/app.py create mode 100644 watchy-image-editor/bitmap.py create mode 100644 watchy-image-editor/explorer.py create mode 100644 watchy-image-editor/file.py create mode 100644 watchy-image-editor/image.py create mode 100644 watchy-image-editor/image_view.py create mode 100644 watchy-image-editor/input_popup.py create mode 100644 watchy-image-editor/main.py create mode 100644 watchy-image-editor/preview.png diff --git a/watchy-image-editor/__init__.py b/watchy-image-editor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/watchy-image-editor/app.py b/watchy-image-editor/app.py new file mode 100644 index 0000000..c2fb7cc --- /dev/null +++ b/watchy-image-editor/app.py @@ -0,0 +1,325 @@ +import tkinter as tk +from tkinter import ttk, filedialog, messagebox +from typing import Optional +from enum import Enum +import os.path + +from explorer import Explorer +from image_view import ImageView +from input_popup import InputPopup +from file import File +from image import Image +from bitmap import Bitmap, BitmapError + + +class MenuEntryType(Enum): + DEFAULT = 0 + NEED_FILE = 1 + NEED_IMAGE = 2 + SEPARATOR = 4 + + +class App(ttk.Frame): + MENU_ENTRIES = { + "File": [ + ("New File", "_file_new", MenuEntryType.DEFAULT), + ("Open File...", "_file_open", MenuEntryType.DEFAULT), + ("", "", MenuEntryType.SEPARATOR), + ("Save File", "_file_save", MenuEntryType.NEED_FILE), + ("Save File As...", "_file_save_as", MenuEntryType.NEED_FILE), + ("Close File", "_file_close", MenuEntryType.NEED_FILE), + ("", "", MenuEntryType.SEPARATOR), + ( + "New image...", + "_file_new_image", + MenuEntryType.NEED_FILE, + ), + ("", "", MenuEntryType.SEPARATOR), + ("Quit", "_file_quit", MenuEntryType.DEFAULT), + ], + "Image": [ + ( + "Edit Image Name...", + "_image_edit_name", + MenuEntryType.NEED_IMAGE, + ), + ( + "Edit Image Size...", + "_image_edit_size", + MenuEntryType.NEED_IMAGE, + ), # TODO _image_edit_size + ( + "Move Image Up", + "_image_move_up", + MenuEntryType.NEED_IMAGE, + ), + ( + "Move Image Down", + "_image_move_down", + MenuEntryType.NEED_IMAGE, + ), + ( + "Delete Image", + "_image_delete", + MenuEntryType.NEED_IMAGE, + ), + ], + "Bitmap": [ + ( + "Bulk .bmp Import...", + "_bmp_import_all", + MenuEntryType.NEED_FILE, + ), + ( + "Export All To .bmp...", + "_bmp_export_all", + MenuEntryType.NEED_FILE, + ), + ("", "", MenuEntryType.SEPARATOR), + ( + "Import .bmp Into Image...", + "_bmp_import_image", + MenuEntryType.NEED_IMAGE, + ), + ("Export Image To .bmp...", "_bmp_export_image", MenuEntryType.NEED_IMAGE), + ], + } + + def __init__(self, parent) -> None: + super().__init__(parent) + + parent.option_add("*tearOff", tk.FALSE) + parent.resizable(False, False) + + self.parent = parent + self.current_file = None + + self.explorer = Explorer(self, self.update) + self.explorer.grid(column=0, row=0, sticky="nsw") + + self.image_view = ImageView(self) + self.image_view.grid(column=1, row=0, sticky="nsew") + + self.init_menus() + + self.grid_rowconfigure(0, weight=1) + self.grid_columnconfigure(0, weight=1) + + self.open_file(None) + + self.pack(fill="both", expand=True) + + @property + def current_image(self) -> Optional[Image]: + if self.current_file is None: + return None + else: + return self.explorer.current_image + + def update(self, force: bool = False) -> None: + self.update_title() + self.update_menus() + self.image_view.update(self.current_image) + self.explorer.update(self.current_file, force) + + def update_title(self) -> None: + title = "Watchy Image Editor" + if self.current_file is not None: + title += "- " + if self.current_file.path is None: + title += "New file" + else: + title += self.current_file.filename + if self.current_file.modified: + title += "*" + self.parent.title(title) + + def init_menus(self) -> None: + self.menubar = tk.Menu(self.parent) + self.parent["menu"] = self.menubar + + self.menus = {} + + for menu_name in self.MENU_ENTRIES: + self.menus[menu_name] = tk.Menu(self.menubar) + self.menubar.add_cascade(menu=self.menus[menu_name], label=menu_name) + + for entry_name, entry_action_name, entry_type in self.MENU_ENTRIES[ + menu_name + ]: + if entry_type == MenuEntryType.SEPARATOR: + self.menus[menu_name].add_separator() + else: + try: + entry_action = getattr(self, entry_action_name) + except AttributeError: + entry_action = lambda: print("missing menu action") + self.menus[menu_name].add_command( + label=entry_name, command=entry_action + ) + + def update_menus(self) -> None: + for menu_name in self.MENU_ENTRIES: + any_enabled = False + for entry_name, entry_action, entry_type in self.MENU_ENTRIES[menu_name]: + if entry_type == MenuEntryType.NEED_FILE: + self.menus[menu_name].entryconfigure( + entry_name, + state=( + "normal" if self.current_file is not None else "disabled" + ), + ) + any_enabled |= self.current_file is not None + elif entry_type == MenuEntryType.NEED_IMAGE: + self.menus[menu_name].entryconfigure( + entry_name, + state=( + "normal" if self.current_image is not None else "disabled" + ), + ) + any_enabled |= self.current_image is not None + elif entry_type == MenuEntryType.DEFAULT: + any_enabled = True + + self.menubar.entryconfigure( + menu_name, state=("normal" if any_enabled else "disabled") + ) + + def open_file(self, path: Optional[str], new: bool = False) -> None: + if path is None and not new: + self.current_file = None + else: + self.current_file = File(path) + self.update(force=True) + + def save_file(self, path: Optional[str] = None) -> None: + if path == "": + path = filedialog.asksaveasfilename() + self.current_file.export(path) + self.open_file(path) + + def _file_new(self) -> None: + self.open_file(None, True) + + def _file_open(self) -> None: + path = filedialog.askopenfilename( + filetypes=File.FILE_TYPES, + defaultextension=File.FILE_TYPES, + initialfile=( + os.path.basename(self.current_file.path) + if self.current_file is not None + else None + ), + initialdir=( + os.path.dirname(self.current_file.path) + if self.current_file is not None + else None + ), + ) + if path: + self.open_file(path) + + def _file_save(self) -> None: + if self.current_file.path is None: + self._file_save_as() + else: + self.save_file(self.current_file.path) + + def _file_save_as(self) -> None: + path = filedialog.asksaveasfilename( + filetypes=File.FILE_TYPES, + defaultextension=File.FILE_TYPES, + initialfile=( + os.path.basename(self.current_file.path) + if self.current_file.path is not None + else None + ), + initialdir=( + os.path.dirname(self.current_file.path) + if self.current_file.path is not None + else None + ), + ) + if path: + self.save_file(path) + + def _file_close(self) -> None: + self.open_file(None) + + def _file_new_image(self) -> None: + popup = InputPopup( + self, + title="New image", + message="Please enter image name", + ) + if popup.value: + self.current_file.images += [Image(popup.value, 20, 20, empty=True)] + self.update() + + def _file_quit(self) -> None: + self.parent.destroy() + + def _image_edit_name(self) -> None: + popup = InputPopup( + self, + title="Edit image name", + message="Please enter image name", + initial_value=self.explorer.current_image.name, + ) + if popup.value: + self.explorer.current_image.name = popup.value + self.update() + + def _image_move_up(self) -> None: + self.explorer.move_up() + + def _image_move_down(self) -> None: + self.explorer.move_down() + + def _image_delete(self) -> None: + self.explorer.delete() + + def _bmp_import_all(self) -> None: + paths = filedialog.askopenfilenames( + filetypes=Bitmap.FILE_TYPES, + defaultextension=Bitmap.FILE_TYPES, + ) + if paths and len(paths) > 0: + for path in paths: + name = os.path.basename(path).rstrip(".bmp") + image = self.current_file.search(name) + if image is None: + image = Image(name, 20, 20, empty=True) + self.current_file.images += [image] + try: + image.import_bmp(path) + except BitmapError as e: + pass + self.update() + + def _bmp_export_all(self) -> None: + dir_path = filedialog.askdirectory() + if dir_path: + for image in self.current_file.images: + image.export_bmp(os.path.join(dir_path, f"{image.name}.bmp")) + + def _bmp_import_image(self) -> None: + path = filedialog.askopenfilename( + filetypes=Bitmap.FILE_TYPES, + defaultextension=Bitmap.FILE_TYPES, + ) + if path: + try: + self.current_image.import_bmp(path) + self.update() + except BitmapError as e: + messagebox.showerror(title="Bitmap import error", message=str(e)) + + def _bmp_export_image(self) -> None: + path = filedialog.asksaveasfilename( + filetypes=Bitmap.FILE_TYPES, + defaultextension=Bitmap.FILE_TYPES, + initialfile=f"{self.current_image.name}.bmp", + ) + if path: + self.current_image.export_bmp(path) diff --git a/watchy-image-editor/bitmap.py b/watchy-image-editor/bitmap.py new file mode 100644 index 0000000..5a28577 --- /dev/null +++ b/watchy-image-editor/bitmap.py @@ -0,0 +1,119 @@ +from typing import Tuple + + +class BitmapError(Exception): + pass + + +class Bitmap: + HEADER_SIZE = 54 + FILE_TYPES = [("Bitmap Image", "*.bmp"), ("All Files", "*.*")] + + @classmethod + def write_bmp(cls, path: str, width: int, color_depth: int, data: bytes) -> None: + with open(path, mode="wb") as f: + f.write(cls.__get_bmp_data(width, color_depth, data)) + + @classmethod + def __get_bmp_data(cls, width: int, color_depth: int, data: bytes) -> bytes: + height = len(data) // (width * 3) + return cls.__get_header( + width, height, color_depth, len(data) + ) + cls.__format_data(width, height, color_depth, data) + + @classmethod + def __get_header( + cls, width: int, height: int, color_depth: int, data_len: int + ) -> bytes: + header = bytes() + # BMP header + header += "BM".encode() # (0, 2) BM + header += (cls.HEADER_SIZE + data_len).to_bytes( + 4, byteorder="little" + ) # (2, 4) file size + header += bytes([0]) * 4 # (6, 4) application reserved + header += (cls.HEADER_SIZE).to_bytes( + 4, byteorder="little" + ) # (10, 4) data offset + # DIB header + header += (40).to_bytes(4, byteorder="little") # (14, 4) DIB header size + header += width.to_bytes(4, byteorder="little") # (18, 4) width + header += height.to_bytes(4, byteorder="little") # (22, 4) height + header += (1).to_bytes(2, byteorder="little") # (26, 2) color panes + header += (color_depth * 8).to_bytes( + 2, byteorder="little" + ) # (28, 2) bits per pixel + header += bytes([0]) * 4 # (30, 4) BI_RGB, no compression + header += (data_len).to_bytes( + 4, byteorder="little" + ) # (34, 4) size of raw bitmap data + header += (2835).to_bytes( + 4, byteorder="little" + ) # (38, 4) horizontal print resolution + header += (2835).to_bytes( + 4, byteorder="little" + ) # (42, 4) vertical print resolution + header += bytes([0]) * 4 # (46, 4) color in palette + header += bytes([0]) * 4 # (50, 4) 0 important colors + return header + + @classmethod + def __format_data( + cls, width: int, height: int, color_depth: int, data: bytes + ) -> bytes: + size = width * height * color_depth + if len(data) < size: + data += bytes([0]) * (size - len(data)) + elif len(data) > size: + data = data[:size] + line_padding = (width * color_depth) % 4 + output_data = bytes() + for y in range(height): + start = (height - y - 1) * color_depth * width + output_data += data[start : start + width * color_depth] + if line_padding > 0: + output_data += bytes([0]) * (4 - line_padding) + return output_data + + @classmethod + def read_bmp(cls, path: str) -> Tuple[int, int, int, bytes]: + with open(path, mode="rb") as f: + bmp_data = f.read() + width, height, color_depth, data_start, data_size = cls.__read_header(bmp_data) + content_data = bmp_data[data_start:] + if data_size > 0: + content_data = content_data[:data_size] + output_data = cls.__read_formated_data(width, height, color_depth, content_data) + return width, height, color_depth, output_data + + @classmethod + def __read_header(cls, bmp_data: bytes) -> Tuple[int, int, int, int, int]: + if bmp_data[0:2].decode() != "BM": + raise BitmapError("Not a Bitmap Image") + if int.from_bytes(bmp_data[30:34], byteorder="little") != 0: + raise BitmapError("Cannot read Bitmap: need no compression") + if int.from_bytes(bmp_data[26:28], byteorder="little") != 1: + raise BitmapError("Cannot read Bitmap: need 1 color panes") + width = int.from_bytes(bmp_data[18:22], byteorder="little") + height = int.from_bytes(bmp_data[22:26], byteorder="little") + color_depth = int.from_bytes(bmp_data[28:30], byteorder="little") // 8 + if color_depth < 1: + raise BitmapError("Cannot read Bitmap: bits per pixels is < 8") + data_start = int.from_bytes(bmp_data[10:14], byteorder="little") + data_size = int.from_bytes(bmp_data[34:38], byteorder="little") + return width, height, color_depth, data_start, data_size + + @classmethod + def __read_formated_data( + cls, width: int, height: int, color_depth: int, bmp_data: bytes + ) -> bytes: + line_padding = (width * color_depth) % 4 + if line_padding > 0: + real_width = width * color_depth + (4 - line_padding) + else: + real_width = width * color_depth + output_data = bytes() + for y in range(height): + start = (height - y - 1) * real_width + output_data += bmp_data[start : start + width * color_depth] + return output_data diff --git a/watchy-image-editor/explorer.py b/watchy-image-editor/explorer.py new file mode 100644 index 0000000..bdb3862 --- /dev/null +++ b/watchy-image-editor/explorer.py @@ -0,0 +1,111 @@ +from tkinter import ttk +from typing import Optional + +from file import File +from image import Image + + +class Explorer(ttk.Frame): + def __init__(self, parent, update_callback) -> None: + super().__init__(parent) + + self.current_file = None + self.current_id = None + self.update_callback = update_callback + + self.explorer = ttk.Treeview(self, columns=("size")) + self.explorer.heading("#0", text="name") + self.explorer.heading("size", text="size") + self.explorer.column("#0", width=150, anchor="w") + self.explorer.column("size", width=80, anchor="w") + self.explorer.grid(row=0, column=0, sticky="nsw") + self.explorer.bind("<>", self.explorer_item_click) + + yscrollbar = ttk.Scrollbar(self, orient="vertical", command=self.explorer.yview) + yscrollbar.grid(row=0, column=1, sticky="nsw") + self.explorer.configure(yscrollcommand=yscrollbar.set) + + self.grid_rowconfigure(0, weight=1) + self.grid_columnconfigure(0, weight=1) + + @property + def current_image(self) -> Optional[Image]: + if self.current_file is None or self.current_id is None: + return None + else: + return self.current_file.images[self.current_id] + + @property + def size(self) -> int: + if self.current_file is None: + return 0 + else: + return len(self.current_file.images) + + def focus(self, id: int) -> None: + if self.current_file is not None and id >= 0 and id < self.size: + self.current_id = id + self.explorer.selection_set(str(id)) + + def move_up(self) -> None: + if self.current_id > 0: + id = self.current_id + images = self.current_file.images + images[id], images[id - 1] = images[id - 1], images[id] + self.current_id -= 1 + self.focus(self.current_id) + self.update(self.current_file, False) + + def move_down(self) -> None: + if self.current_id < self.size - 1: + id = self.current_id + images = self.current_file.images + images[id], images[id + 1] = images[id + 1], images[id] + self.current_id += 1 + self.focus(self.current_id) + self.update(self.current_file, False) + + def delete(self) -> None: + del self.current_file.images[self.current_id] + self.current_id = min(self.current_id, self.size - 1) + self.update(self.current_file, True) + + def update(self, file: File, force: bool): + focus_id = self.current_id + + if force or file != self.current_file: + focus_id = 0 + if file is not None and file == self.current_file: + focus_id = self.current_id + ids = self.explorer.get_children() + if len(ids) > 0: + self.explorer.delete(*ids) + self.current_id = None + + self.current_file = file + + if self.current_file is not None: + for i, image in enumerate(self.current_file.images): + if self.explorer.exists(str(i)): + self.explorer.item( + str(i), + text=f"{image.name}{'*' if image.modified else ''}", + values=[f"{image.width}x{image.height}"], + ) + else: + self.explorer.insert( + "", + "end", + iid=str(i), + text=f"{image.name}{'*' if image.modified else ''}", + values=[f"{image.width}x{image.height}"], + ) + if self.size > 0 and (focus_id != self.current_id or force): + self.focus(focus_id) + + def explorer_item_click(self, event) -> None: + if self.current_file is None or len(self.explorer.selection()) == 0: + self.current_id = None + else: + self.current_id = int(self.explorer.selection()[0]) + self.update_callback() diff --git a/watchy-image-editor/file.py b/watchy-image-editor/file.py new file mode 100644 index 0000000..f3a130f --- /dev/null +++ b/watchy-image-editor/file.py @@ -0,0 +1,76 @@ +from typing import List, Optional +import re +import os.path + +from image import Image + + +class File: + FILE_TYPES = [("Header File", "*.h"), ("All Files", "*.*")] + + def __init__(self, path: str) -> None: + self.path = path + if path is None: + self.images = [] + else: + self.images = self.__read_file() + + @property + def filename(self) -> str: + if self.path is None: + return None + return os.path.basename(self.path) + + @property + def modified(self) -> bool: + return any(image.modified for image in self.images) + + def search(self, name) -> Optional[Image]: + for image in self.images: + if image.name == name: + return image + return None + + def __read_file(self) -> List[Image]: + images = [] + + current_image = None + + with open(self.path) as f: + for line in f: + header = re.match( + r"const unsigned char (\w+) \[\] PROGMEM \= \{", + line, + ) + if header: + groups = header.groups() + if current_image is None: + current_image = Image(groups[0], 0, 0) + current_image.name = groups[0] + elif current_image is not None and current_image.name is not None: + data = re.match(r"((0x\w+,? ?)+)", line.strip()) + if data: + current_image.add_data( + data.groups()[0].strip().strip(",").split(", ") + ) + else: + images += [current_image] + current_image.finalize() + current_image = None + comment_header = re.match(r"// '(\w+)', (\d+)x(\d+)px", line) + if comment_header: + groups = comment_header.groups() + current_image = Image(groups[0], int(groups[1]), int(groups[2])) + + return images + + def export(self, path: str) -> None: + with open(path, mode="w") as f: + for image in self.images: + f.write(image.export_cpp()) + + def __eq__(self, other: object) -> bool: + if isinstance(other, self.__class__): + return self.path == other.path + else: + return False diff --git a/watchy-image-editor/image.py b/watchy-image-editor/image.py new file mode 100644 index 0000000..c78c985 --- /dev/null +++ b/watchy-image-editor/image.py @@ -0,0 +1,94 @@ +from typing import List +from math import sqrt + +from bitmap import Bitmap + + +class Image: + def __init__(self, name: str, width: int, height: int, empty: bool = False) -> None: + self.name = name + self.width = width + self.height = height + self.modified = False + if empty: + self.data = [0] * ((width * height) // 8) + self.modified = True + else: + self.data = [] + + def finalize(self) -> None: + if self.width == 0: + pixels = len(self.data) * 8 + width = int(sqrt(pixels)) + while width > 1 and pixels % width != 0: + width -= 1 + self.width = width + self.height = pixels // width + + def add_data(self, raw_data: List[str]) -> None: + for v in raw_data: + self.data += [int(v, 16)] + + def __get_position(self, x: int, y: int) -> int: + real_width = (len(self.data) * 8) // self.height + return y * real_width + x + + def get_pixel(self, x: int, y: int) -> bool: + position = self.__get_position(x, y) + chunk_id = position // 8 + try: + return self.data[chunk_id] & (1 << (7 - position % 8)) > 0 + except: + return False + + def set_pixel(self, x: int, y: int, v: bool) -> None: + position = self.__get_position(x, y) + chunk_id = position // 8 + if v != self.get_pixel(x, y): + try: + if v: + self.data[chunk_id] |= 1 << (7 - position % 8) + else: + self.data[chunk_id] &= ~(1 << (7 - position % 8)) + except: + pass + self.modified = True + + def __get_color_bytes(self) -> bytes: + output = bytes() + for y in range(self.height): + for x in range(self.width): + if self.get_pixel(x, y): + output += bytes([0, 0, 0]) + else: + output += bytes([255, 255, 255]) + return output + + def export_bmp(self, path: str) -> None: + Bitmap.write_bmp(path, self.width, 3, self.__get_color_bytes()) + + def __set_color_bytes(self, color_depth: int, data: bytes) -> None: + for y in range(self.height): + for x in range(self.width): + position = (y * self.width + x) * color_depth + colors = data[position : position + color_depth] + mean_color = sum(c for c in colors) / color_depth + if mean_color < 128: + self.set_pixel(x, y, True) + + def import_bmp(self, path: str) -> None: + self.width, self.height, color_depth, bmp_data = Bitmap.read_bmp(path) + self.data = [0] * ((self.width * self.height) // 8) + self.__set_color_bytes(color_depth, bmp_data) + + def export_cpp(self) -> str: + # 16 per line + output = [ + f"// '{self.name}', {self.width}x{self.height}px", + f"const unsigned char {self.name} [] PROGMEM = {{", + ] + while len(self.data) > 16: + output += ["\t" + ", ".join(f"0x{v:02x}" for v in self.data[0:16]) + ","] + self.data = self.data[16:] + output += ["\t" + ", ".join(f"0x{v:02x}" for v in self.data), "};", ""] + return "\n".join(output) diff --git a/watchy-image-editor/image_view.py b/watchy-image-editor/image_view.py new file mode 100644 index 0000000..f9a840c --- /dev/null +++ b/watchy-image-editor/image_view.py @@ -0,0 +1,103 @@ +import tkinter as tk +from tkinter import ttk + +from image import Image + + +class ImageView(ttk.Frame): + INITIAL_DRAW_SCALE = 3 + + def __init__(self, parent) -> None: + super().__init__(parent, height=650, width=650) + + self.draw_scale = self.INITIAL_DRAW_SCALE + + self.current_image = None + + self.canvas = tk.Canvas(self, width=0, height=0, background="white") + self.canvas.place(in_=self, anchor="c", relx=0.5, rely=0.5) + self.canvas.bind("", self.click_canvas_b1) + self.canvas.bind("", self.click_canvas_b1) + self.canvas.bind( + "", lambda event: self.update(self.current_image) + ) + self.canvas.bind("", self.click_canvas_b3) + self.canvas.bind("", self.click_canvas_b3) + self.canvas.bind( + "", lambda event: self.update(self.current_image) + ) + + self.canvas.bind("", self.zoom_canvas) + self.canvas.bind("", self.zoom_canvas_up) + self.canvas.bind("", self.zoom_canvas_down) + + self.bind("", self.zoom_canvas) + self.bind("", self.zoom_canvas_up) + self.bind("", self.zoom_canvas_down) + + def update(self, image: Image) -> None: + if self.current_image != image: + self.draw_scale = self.INITIAL_DRAW_SCALE + if image is None: + self.canvas.configure( + width=0, + height=0, + background="white", + ) + else: + try: + self.canvas.configure( + width=(image.width * self.draw_scale), + height=(image.height * self.draw_scale), + background="white", + ) + self.canvas.delete("all") + for x in range(image.width): + for y in range(image.height): + if image.get_pixel(x, y): + self.canvas.create_rectangle( + x * self.draw_scale + 1, + y * self.draw_scale + 1, + (x + 1) * self.draw_scale + 1, + (y + 1) * self.draw_scale + 1, + fill="black", + outline="", + ) + except tk.TclError: + pass + self.current_image = image + + def click_canvas_b1(self, event): + self.click_canvas(True, event) + + def click_canvas_b3(self, event): + self.click_canvas(False, event) + + def click_canvas(self, value: bool, event): + if self.current_image is None: + return + x = int(event.x / self.draw_scale) + y = int(event.y / self.draw_scale) + self.current_image.set_pixel(x, y, value) + self.canvas.create_rectangle( + x * self.draw_scale + 1, + y * self.draw_scale + 1, + (x + 1) * self.draw_scale + 1, + (y + 1) * self.draw_scale + 1, + fill=("black" if value else "white"), + outline="", + ) + + def zoom_canvas(self, event): + if event.delta > 0: + self.zoom_canvas_up() + else: + self.zoom_canvas_down() + + def zoom_canvas_up(self, event=None): + self.draw_scale *= 2 + self.update(self.current_image) + + def zoom_canvas_down(self, event=None): + self.draw_scale /= 2 + self.update(self.current_image) diff --git a/watchy-image-editor/input_popup.py b/watchy-image-editor/input_popup.py new file mode 100644 index 0000000..4ecb54c --- /dev/null +++ b/watchy-image-editor/input_popup.py @@ -0,0 +1,26 @@ +import tkinter as tk +from tkinter import ttk + + +class InputPopup(tk.Toplevel): + def __init__(self, parent, *, title: str, message: str, initial_value: str = ""): + super().__init__(parent) + self.title(title) + + self.value = None + + label = ttk.Label(self, text=message) + label.pack() + + self.entry = ttk.Entry(self) + self.entry.insert(0, initial_value) + self.entry.pack() + + button = ttk.Button(self, text="Ok", command=self.cleanup) + button.pack() + + parent.wait_window(self) + + def cleanup(self): + self.value = self.entry.get() + self.destroy() \ No newline at end of file diff --git a/watchy-image-editor/main.py b/watchy-image-editor/main.py new file mode 100644 index 0000000..30b0417 --- /dev/null +++ b/watchy-image-editor/main.py @@ -0,0 +1,9 @@ +import tkinter as tk +import os.path + +from app import App + +if __name__ == "__main__": + app = App(tk.Tk()) + + app.mainloop() diff --git a/watchy-image-editor/preview.png b/watchy-image-editor/preview.png new file mode 100644 index 0000000000000000000000000000000000000000..4b133d3b44a70de341e171d67901a14fdbee7380 GIT binary patch literal 65097 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 MueUF0kJZKh13lw