import tkinter as tk
from tkinter import filedialog, ttk
from PIL import Image, ImageTk
import requests
import base64
import os
import io
import random
import threading
import time
def encode_pil_to_base64(image):
with io.BytesIO() as output_bytes:
image.save(output_bytes, format="PNG")
bytes_data = output_bytes.getvalue()
return base64.b64encode(bytes_data).decode("utf-8")
def save_decoded_image(b64_image, folder_path, image_name):
seq = 0
output_path = os.path.join(folder_path, f"{image_name}.png")
while os.path.exists(output_path):
seq += 1
output_path = os.path.join(folder_path, f"{image_name}({seq}).png")
with open(output_path, 'wb') as image_file:
image_file.write(base64.b64decode(b64_image))
print(f"Image saved to: {output_path}")
return output_path # 返回保存的图片路径
class PhotoSketchGUI:
def __init__(self, root):
self.root = root
self.root.title("建筑效果图转彩色手绘")
self.root.configure(bg='black')
self.root.state('zoomed')
self.folder_path = '' # 初始化 folder_path
self.setup_ui()
# 类的其余定义...
def select_save_folder(self):
selected_folder_path = filedialog.askdirectory()
if selected_folder_path: # 检查是否选择了文件夹
self.folder_path = selected_folder_path
# 在文件夹选择后继续其他操作
def setup_ui(self):
screen_width = self.root.winfo_screenwidth()
screen_height = self.root.winfo_screenheight()
self.canvas_width = screen_width * 0.4
self.canvas_height = screen_height * 0.8
self.left_canvas_x = screen_width * 0.25 - self.canvas_width / 2
self.right_canvas_x = screen_width * 0.75 - self.canvas_width / 2
self.setup_buttons_and_canvas()
self.add_intro_labels()
def select_save_folder(self):
folder_selected = filedialog.askdirectory()
if folder_selected: # 确保用户选择了一个文件夹
self.folder_path = folder_selected
print(f"Selected folder: {self.folder_path}")
def upload_image(self):
filepath = filedialog.askopenfilename()
if filepath:
# 这里可以添加更新左侧画布图片的代码
print(f"Uploaded image: {filepath}")
def setup_buttons_and_canvas(self):
self.left_frame = tk.Canvas(self.root, width=self.canvas_width, height=self.canvas_height, bg='black',
highlightthickness=0)
self.left_frame.place(x=self.left_canvas_x, rely=0.5, anchor='w')
self.left_frame.create_rectangle(2, 2, self.canvas_width - 2, self.canvas_height - 2, outline='white',
dash=(5, 5))
# 将按钮放置在左框下方,下移40像素
buttons_y = self.left_frame.winfo_y() + self.canvas_height + 90 # 在画布下方15像素处,额外下移40像素
# 上传参考图的按钮
self.upload_btn = tk.Button(self.root, text="上传参考图", command=self.upload_image, bg='darkgrey', fg='white',
font=('微软雅黑', 12))
self.upload_btn.place(x=self.left_canvas_x, y=buttons_y, width=(self.canvas_width / 2 - 5), height=30,
anchor='nw')
# 选择保存路径的按钮
self.save_dir_btn = tk.Button(self.root, text="选择保存路径", command=self.select_save_folder, bg='darkgrey',
fg='white', font=('微软雅黑', 12))
self.save_dir_btn.place(x=self.left_canvas_x + self.canvas_width / 2 + 5, y=buttons_y,
width=(self.canvas_width / 2 - 5), height=30, anchor='nw')
self.right_frame = tk.Canvas(self.root, width=self.canvas_width, height=self.canvas_height, bg='black',
highlightthickness=0)
self.right_frame.place(x=self.right_canvas_x, rely=0.5, anchor='w')
self.right_frame.create_rectangle(2, 2, self.canvas_width - 2, self.canvas_height - 2, outline='white',
dash=(5, 5))
def add_intro_labels(self):
intro_label1 = tk.Label(self.root, text="建筑效果图转彩色手绘", bg='black', fg='white',
font=('微软雅黑', 18, 'bold'))
intro_label1.place(x=self.left_canvas_x, y=10)
intro_label2 = tk.Label(self.root, text="使用介绍: 请在左侧框内上传一张建筑渲染图", bg='black', fg='white',
font=('微软雅黑', 10))
intro_label2.place(x=self.left_canvas_x, y=45)
def upload_image(self):
self.upload_btn.config(state='disabled') # Disable the button to prevent multiple uploads during processing
filepath = filedialog.askopenfilename()
if filepath:
self.display_image(filepath, self.left_frame, 'image_label', self.left_canvas_x)
threading.Thread(target=self.call_api_for_sketch, args=(filepath,)).start()
def display_image(self, image_path, canvas, label_attr, canvas_x):
img = Image.open(image_path)
img.thumbnail((canvas.winfo_width(), canvas.winfo_height()))
img_tk = ImageTk.PhotoImage(img)
# 检查是否已经存在对应的标签属性,如果存在,就更新它
if hasattr(self, label_attr):
getattr(self, label_attr).configure(image=img_tk)
# 重要:更新引用,防止新的PhotoImage对象被垃圾回收
getattr(self, label_attr).image = img_tk
else:
# 如果标签属性不存在,创建一个新的标签并设置图片
label = tk.Label(self.root, image=img_tk, bg='black')
setattr(self, label_attr, label)
# 保存PhotoImage对象的引用,防止被垃圾回收
getattr(self, label_attr).image = img_tk
label.place(x=canvas_x, rely=0.5, anchor='w')
def call_api_for_sketch(self, image_path):
# 模拟一些处理时间
time.sleep(1)
with Image.open(image_path) as img:
encoded_image = encode_pil_to_base64(img)
data = {
"prompt": "<lora:CWG_archisketch_v1:1>,Building,pre sketch,masterpiece,best quality,featuring markers,(3D:0.7)",
"negative_prompt": "blurry, lower quality, glossy finish,insufficient contrast",
"init_images": [encoded_image],
"steps": 30,
"width": img.width,
"height": img.height,
"seed": random.randint(1, 10000000),
"alwayson_scripts": {
"ControlNet": {
"args": [
{
"enabled": "true",
"pixel_perfect": "true",
"module": "canny",
"model": "control_v11p_sd15_canny_fp16 [b18e0966]",
"weight": 1,
"image": encoded_image
},
{
"enabled": "true",
"pixel_perfect": "true",
"module": "depth",
"model": "control_v11f1p_sd15_depth_fp16 [4b72d323]",
"weight": 1,
"image": encoded_image
}
]
}
}
}
url = "http://127.0.0.1:7860/sdapi/v1/txt2img"
response = requests.post(url, json=data)
if response.status_code == 200:
response_json = response.json()
saved_image_path = save_decoded_image(response_json['images'][0], self.folder_path, "sketched_image")
self.root.after(0, self.display_image, saved_image_path, self.right_frame, 'generated_image_label',
self.right_canvas_x)
self.root.after(0, lambda: self.upload_btn.config(
state='normal')) # Re-enable the upload button after processing
else:
print("Failed to get response from API, status code:", response.status_code)
self.root.after(0, lambda: self.upload_btn.config(
state='normal')) # Re-enable the upload button if the API call fails
if __name__ == "__main__":
root = tk.Tk()
app = PhotoSketchGUI(root)
root.mainloop()