Skip to content

Commit 1ba1ac8

Browse files
Added image copper
1 parent ff657f1 commit 1ba1ac8

File tree

5 files changed

+137
-25
lines changed

5 files changed

+137
-25
lines changed

BlackHole

-3.3 MB
Binary file not shown.

BlackHole.exe

-12.7 MB
Binary file not shown.

BlackHole.py

Lines changed: 135 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ def _enum_proc(h, lparam):
228228
FONT_ITALIC = os.path.join(SCRIPT_DIR, "Fonts", "Nunito-Italic.ttf")
229229
FONT_SEMIBOLD = os.path.join(SCRIPT_DIR, "Fonts", "Nunito-SemiBold.ttf")
230230
LICENSE_TEXT = os.path.join(SCRIPT_DIR, "LICENSE.txt")
231-
VERSION = "1.8.1"
231+
VERSION = "1.9.0"
232232
# Load all the font files for Tkinter (on Windows)
233233
if platform.system() == "Windows":
234234
fonts = [FONT_REGULAR, FONT_MEDIUM, FONT_BOLD, FONT_LIGHT, FONT_ITALIC, FONT_SEMIBOLD]
@@ -1757,10 +1757,12 @@ def update_strength(*args):
17571757
strength = self.evaluate_password_strength(pwd_var.get())
17581758
colors = {"Weak": "#ff0000", "Medium": "#ffa500", "Strong": "#00ff00"}
17591759
strength_label.configure(text=f"Strength: {strength}", text_color=colors[strength])
1760-
pwd_var.trace("w", update_strength)
1760+
pwd_var.trace_add("write", update_strength)
17611761
update_strength() # Initial
17621762
# Password generator button
17631763
def generate_password():
1764+
if not messagebox.askyesno("Generate Password", "This will overwrite the current password. Continue?", parent=popup):
1765+
return
17641766
length = 16 # Default; can add options popup
17651767
chars = string.ascii_letters + string.digits + string.punctuation
17661768
new_pwd = ''.join(secrets.choice(chars) for _ in range(length))
@@ -1777,27 +1779,137 @@ def toggle_pwd_entry():
17771779
new_icon_path = None
17781780
def upload_icon():
17791781
file = filedialog.askopenfilename(filetypes=[("Images", "*.png *.jpg *.jpeg *.ico")])
1780-
if file:
1781-
try:
1782-
pil_img = Image.open(file)
1783-
# Auto-resize to 350x350
1784-
pil_img = pil_img.resize((350, 350), Image.LANCZOS)
1785-
orig_ext = os.path.splitext(file)[1].lower()
1786-
if orig_ext == '.ico':
1787-
orig_ext = '.png'
1788-
dest = os.path.join(stored_icons_path, f"{id_}{orig_ext}")
1789-
format_to_save = 'JPEG' if orig_ext == '.jpg' or orig_ext == '.jpeg' else 'PNG'
1790-
pil_img.save(dest, format=format_to_save)
1791-
# Remove old icons with different extensions
1792-
for other_ext in ['.png', '.jpg', '.jpeg']:
1793-
if other_ext != orig_ext:
1794-
old_path = os.path.join(stored_icons_path, f"{id_}{other_ext}")
1795-
if os.path.exists(old_path):
1796-
os.remove(old_path)
1797-
new_icon_path = dest
1798-
messagebox.showinfo("Uploaded", "Icon uploaded, resized, and saved!", parent=popup)
1799-
except Exception as e:
1800-
messagebox.showerror("Error", f"Failed to process icon: {e}", parent=popup)
1782+
if not file:
1783+
return
1784+
original_img = Image.open(file)
1785+
editor_popup = ctk.CTkToplevel(popup) # 'popup' is the edit_card_popup window
1786+
editor_popup.title("Crop and Zoom Image")
1787+
editor_popup.configure(fg_color=BG)
1788+
editor_popup.resizable(False, False)
1789+
PasswordManager.set_window_icon(editor_popup)
1790+
1791+
canvas_size = 350
1792+
center = canvas_size / 2
1793+
zoom_level = 1.0
1794+
image_x = center
1795+
image_y = center
1796+
photo = None # To keep reference
1797+
start_pan_x = 0
1798+
start_pan_y = 0
1799+
start_image_x = 0
1800+
start_image_y = 0
1801+
1802+
canvas_frame = ctk.CTkFrame(editor_popup, fg_color=CARD)
1803+
canvas_frame.pack(pady=10, padx=10)
1804+
canvas = ctk.CTkCanvas(canvas_frame, width=canvas_size, height=canvas_size, bg="gray")
1805+
canvas.pack()
1806+
1807+
def display_image():
1808+
nonlocal photo
1809+
w, h = original_img.size
1810+
new_w = int(w * zoom_level)
1811+
new_h = int(h * zoom_level)
1812+
resized = original_img.resize((new_w, new_h), Image.LANCZOS)
1813+
photo = ImageTk.PhotoImage(resized)
1814+
canvas.delete("all")
1815+
canvas.create_image(image_x, image_y, image=photo, anchor="center")
1816+
1817+
display_image()
1818+
1819+
def zoom_at(mx, my, scale):
1820+
nonlocal zoom_level, image_x, image_y
1821+
old_zoom = zoom_level
1822+
zoom_level *= scale
1823+
zoom_level = max(0.1, zoom_level)
1824+
image_x = mx - (mx - image_x) * (zoom_level / old_zoom)
1825+
image_y = my - (my - image_y) * (zoom_level / old_zoom)
1826+
display_image()
1827+
1828+
def zoom_in_center():
1829+
zoom_at(center, center, 1.2)
1830+
1831+
def zoom_out_center():
1832+
zoom_at(center, center, 1 / 1.2)
1833+
1834+
def on_mouse_wheel(event):
1835+
mx = canvas.winfo_pointerx() - canvas.winfo_rootx()
1836+
my = canvas.winfo_pointery() - canvas.winfo_rooty()
1837+
scale = 1.1 if event.delta > 0 else 1 / 1.1
1838+
zoom_at(mx, my, scale)
1839+
1840+
def start_pan(event):
1841+
nonlocal start_pan_x, start_pan_y, start_image_x, start_image_y
1842+
start_pan_x = event.x
1843+
start_pan_y = event.y
1844+
start_image_x = image_x
1845+
start_image_y = image_y
1846+
1847+
def pan(event):
1848+
nonlocal image_x, image_y
1849+
dx = event.x - start_pan_x
1850+
dy = event.y - start_pan_y
1851+
image_x = start_image_x + dx
1852+
image_y = start_image_y + dy
1853+
display_image()
1854+
1855+
def reset_zoom():
1856+
nonlocal zoom_level, image_x, image_y
1857+
zoom_level = 1.0
1858+
image_x = center
1859+
image_y = center
1860+
display_image()
1861+
1862+
canvas.bind("<MouseWheel>", on_mouse_wheel)
1863+
canvas.bind("<Button-4>", lambda e: zoom_at(e.x, e.y, 1.1))
1864+
canvas.bind("<Button-5>", lambda e: zoom_at(e.x, e.y, 1 / 1.1))
1865+
canvas.bind("<Button-1>", start_pan)
1866+
canvas.bind("<B1-Motion>", pan)
1867+
1868+
btn_frame = ctk.CTkFrame(editor_popup, fg_color=BG)
1869+
btn_frame.pack(pady=10)
1870+
ctk.CTkButton(btn_frame, text="+", command=zoom_in_center, fg_color=ACCENT, text_color=BG, hover_color=ACCENT_DIM, width=40).pack(side="left", padx=5)
1871+
ctk.CTkButton(btn_frame, text="-", command=zoom_out_center, fg_color=ACCENT, text_color=BG, hover_color=ACCENT_DIM, width=40).pack(side="left", padx=5)
1872+
ctk.CTkButton(btn_frame, text="Reset", command=reset_zoom, fg_color=ACCENT, text_color=BG, hover_color=ACCENT_DIM, width=80).pack(side="left", padx=5)
1873+
1874+
def apply_crop():
1875+
w, h = original_img.size
1876+
orig_left = (w / 2) + (0 - image_x) / zoom_level
1877+
orig_top = (h / 2) + (0 - image_y) / zoom_level
1878+
orig_right = (w / 2) + (canvas_size - image_x) / zoom_level
1879+
orig_bottom = (h / 2) + (canvas_size - image_y) / zoom_level
1880+
crop_box = (max(0, orig_left), max(0, orig_top), min(w, orig_right), min(h, orig_bottom))
1881+
if crop_box[0] >= crop_box[2] or crop_box[1] >= crop_box[3]:
1882+
messagebox.showerror("Error", "No visible image area!", parent=editor_popup)
1883+
return
1884+
cropped = original_img.crop(crop_box)
1885+
crop_w = crop_box[2] - crop_box[0]
1886+
crop_h = crop_box[3] - crop_box[1]
1887+
visible_w = crop_w * zoom_level
1888+
visible_h = crop_h * zoom_level
1889+
resized_cropped = cropped.resize((int(visible_w), int(visible_h)), Image.LANCZOS)
1890+
pos_left_in_canvas = image_x + (crop_box[0] - w / 2) * zoom_level
1891+
pos_top_in_canvas = image_y + (crop_box[1] - h / 2) * zoom_level
1892+
final_img = Image.new("RGBA", (canvas_size, canvas_size), (0, 0, 0, 0))
1893+
final_img.paste(resized_cropped, (int(pos_left_in_canvas), int(pos_top_in_canvas)))
1894+
final_img = final_img.resize((350, 350), Image.LANCZOS) # Ensure final size
1895+
orig_ext = os.path.splitext(file)[1].lower()
1896+
if orig_ext == '.ico':
1897+
orig_ext = '.png'
1898+
dest = os.path.join(stored_icons_path, f"{id_}{orig_ext}")
1899+
format_to_save = 'JPEG' if orig_ext in ('.jpg', '.jpeg') else 'PNG'
1900+
final_img.save(dest, format=format_to_save)
1901+
# Remove old icons with different extensions
1902+
for other_ext in ['.png', '.jpg', '.jpeg']:
1903+
if other_ext != orig_ext:
1904+
old_path = os.path.join(stored_icons_path, f"{id_}{other_ext}")
1905+
if os.path.exists(old_path):
1906+
os.remove(old_path)
1907+
messagebox.showinfo("Success", "Image cropped, resized, and saved!", parent=editor_popup)
1908+
editor_popup.destroy()
1909+
1910+
ctk.CTkButton(editor_popup, text="Apply & Save", command=apply_crop, fg_color=ACCENT, text_color=BG, hover_color=ACCENT_DIM, width=200).pack(pady=10)
1911+
safe_modal(editor_popup)
1912+
popup.wait_window(editor_popup) # 'popup' is the edit_card_popup
18011913
ctk.CTkButton(popup, text="Upload Icon", command=upload_icon, fg_color=ACCENT, text_color=BG).pack(pady=(0,8))
18021914
# Auto-query Google Images
18031915
def auto_find_icon():

Extras/BlackHole.iss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
; Non-commercial use only
44

55
#define MyAppName "Black Hole"
6-
#define MyAppVersion "1.8.1"
6+
#define MyAppVersion "1.9.0"
77
#define MyAppPublisher "Nova Foundry"
88
#define MyAppURL "https://novafoundry.ca"
99
#define MyAppExeName "BlackHole.exe"

Extras/BlackHole.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<project>
22
<shortName>BlackHole</shortName>
33
<fullName>Black Hole Password Manager</fullName>
4-
<version>1.8.1</version>
4+
<version>1.9.0</version>
55
<readmeFile>C:/Users/jackp/Downloads/Linux/BlackHole/README.txt</readmeFile>
66
<licenseFile>C:/Users/jackp/Downloads/Linux/BlackHole/LICENSE.txt</licenseFile>
77
<logoImage>C:/Users/jackp/Downloads/Black Hole Installer.png</logoImage>

0 commit comments

Comments
 (0)