1.完成大部分功能(圖片下載功能尚未加上)

This commit is contained in:
shouchih_chen 2025-06-22 22:16:38 +08:00
parent e2b14ca7fe
commit 0b18e4b8ab
15 changed files with 308 additions and 1 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ uploads/*
generated/* generated/*
*.pyc *.pyc
*.pyo *.pyo
/generated_images

46
CustomCircleItem.py Normal file
View File

@ -0,0 +1,46 @@
import sys
from PySide6.QtWidgets import QGraphicsItem
from PySide6.QtGui import QPainter, QBrush, QPen
from PySide6.QtCore import QRectF, Qt
# 1. 建立一個自訂的 QGraphicsItem 類別來繪製圓形/橢圓
class CustomCircleItem(QGraphicsItem):
def __init__(self, x, y, width, height, color=Qt.red, is_circle=False, parent :QGraphicsItem =None):
super().__init__(parent = parent) # 呼叫父類別的建構子
# QRectF 定義了橢圓或圓形的邊界框
self.rect = parent.boundingRect()
self.rect.setX(self.rect.x() + x)
self.rect.setY(self.rect.y() + y)
self.color = color
self.is_circle = is_circle # 用於判斷是否繪製正圓
# 為了簡化,如果 is_circle 為 True強制寬高相等
if is_circle:
side = max(width, height) # 取較大邊長作為圓的直徑
self.rect.setWidth(side)
self.rect.setHeight(side)
# 必須實作此方法:定義項目的邊界矩形
def boundingRect(self):
# 返回定義橢圓或圓形所需空間的矩形
# 這對於繪圖更新、碰撞偵測和選擇非常重要
return self.rect
# 必須實作此方法:定義如何繪製項目
def paint(self, painter: QPainter, option, widget=None):
# 設定畫筆 (邊框)
pen = QPen(Qt.white) # 使用白色邊框
pen.setWidth(0)
painter.setPen(pen)
# 設定畫刷 (填充顏色)
brush = QBrush(self.color)
painter.setBrush(brush)
# 繪製橢圓或圓形
# drawEllipse(矩形): 在給定的矩形內繪製一個橢圓。
# 如果矩形的寬高相等,則繪製一個正圓。
painter.drawEllipse(self.rect)

18
app.py
View File

@ -2,6 +2,7 @@
import os import os
import uuid import uuid
from flask import Flask, render_template, request, send_file from flask import Flask, render_template, request, send_file
import subprocess
app = Flask(__name__) app = Flask(__name__)
@ -37,9 +38,26 @@ def upload_csv():
if file.filename == '': if file.filename == '':
return "No selected file", 400 return "No selected file", 400
if file and allowed_file(file.filename): if file and allowed_file(file.filename):
unique_filename = str(uuid.uuid4()) + '.csv' unique_filename = str(uuid.uuid4()) + '.csv'
csv_path = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename) csv_path = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
file.save(csv_path) file.save(csv_path)
print(f"CSV file saved to {csv_path}")
try:
# 呼叫外部 Python 腳本來生成圖片
subprocess.run([os.path.join(os.getcwd(), '.venv', 'Scripts', 'python'), 'generate_images.py', csv_path], check=True)
except subprocess.CalledProcessError as e:
print(f"Error generating images: {e}")
return "Error generating images", 500
except FileNotFoundError as e:
print(f"File not found: {e}")
return "File not found", 404
except Exception as e:
print(f"An unexpected error occurred: {e}")
return "An unexpected error occurred", 500
return "File uploaded successfully", 200 return "File uploaded successfully", 200

View File

@ -1,2 +1,194 @@
import sys import sys
from PySide6.QtCore import QCoreApplication, QTimer, QObject, Signal, Slot import csv
from PySide6.QtCore import Qt, QTimer, QObject, Signal, Slot
from PySide6.QtWidgets import QGraphicsScene, QGraphicsRectItem, QGraphicsPixmapItem,QApplication, QGraphicsTextItem
from PySide6.QtGui import QPainter, QImage, QColor, QPixmap, QFont, QFontDatabase
import CustomCircleItem
import re
BG_WIDTH = 3508
BG_HEIGHT = 2480
HEAD_ICON_PREFIX = "resource/head_%d.png"
X_OFFSET = 1700
Y_OFFSET = 650
CIRCLE_RADIUS = 617
CIRCLE_X_OFFSET = 750
def is_chinese(text):
# Returns True if any character is Chinese
return any('\u4e00' <= char <= '\u9fff' for char in text)
def is_english(text):
# Returns True if all characters are English letters (ignores spaces)
return bool(re.fullmatch(r'[A-Za-z\s]+', text))
class ImageGenerator(QObject):
finished = Signal()
def __init__(self, parent=None):
super().__init__(parent)
self.scene = QGraphicsScene()
self._timer = QTimer(self)
self._timer.timeout.connect(self.generate_images)
self.colors = self.read_csv("resource/colors.csv")
print(f"Loaded colors: {self.colors}")
# Add the font file (provide the correct path)
self.customFont = self.read_font("resource/TaiwanPearl-SemiBold.ttf")
def read_csv(self, path):
colors = []
try:
with open(path, newline='', encoding='utf-8') as csvfile:
reader = csv.reader(csvfile)
for row in reader:
colors.append(row)
if colors:
colors.pop(0) # Remove header row if exists
except Exception as e:
print(f"Error reading {path}: {e}")
return colors
def read_font(self,path):
try:
font_id = QFontDatabase.addApplicationFont(path)
if font_id != -1:
families = QFontDatabase.applicationFontFamilies(font_id)
print("Loaded font families:", families)
family = families[1] if len(families) > 1 else families[0]
customFont = QFont(family)
customFont.setPixelSize(80)
customFont.setLetterSpacing(QFont.AbsoluteSpacing,15) # Adjust word spacing if needed
return customFont
else:
print("Failed to load font.")
return QFont() # fallback to default font
except Exception as e:
print(f"Error loading font from {path}: {e}")
return QFont()
@Slot()
def start(self):
self._timer.start(1000) # Generate images every second
@Slot()
def stop(self):
self._timer.stop()
self.finished.emit()
def scene_to_image(self, scene, filename):
print(f"Saving scene to {filename}...")
image = QImage(scene.sceneRect().size().toSize(), QImage.Format_ARGB32)
image.fill(Qt.transparent)
# Set 300 DPI
dpi = 300
dots_per_meter = int(dpi / 0.0254)
image.setDotsPerMeterX(dots_per_meter)
image.setDotsPerMeterY(dots_per_meter)
painter = QPainter(image)
scene.render(painter)
painter.end()
image.save(f"generated_images/{filename}", "PNG")
self.stop()
@Slot()
def generate_images(self, name_csv):
# Placeholder for image generation logic
print("Generating images...1")
# Group of rect
for i, name in enumerate(name_csv):
r =g =b = 0
for j, color in enumerate(self.colors):
if color[0] == name[0]:
print(f"Adding rect with color: {color}")
r, g, b = map(int, color[1:4])
break
_idx_x = i % 2
_idx_y = (i%6) // 2
if i % 6 == 0:
# Example of adding a simple item to the scene
bg_item = QGraphicsRectItem(0, 0, BG_WIDTH, BG_HEIGHT)
bg_item.setBrush(Qt.white)
self.scene.addItem(bg_item)
print("Generating images...2")
rect_item = QGraphicsRectItem(30+X_OFFSET*_idx_x, 30+(Y_OFFSET+20)*_idx_y, X_OFFSET, Y_OFFSET, parent= bg_item)
rect_item.setPen(Qt.NoPen)
#simple pixmap
# smaple_item = QGraphicsPixmapItem(QPixmap("resource/sample.jpg"), rect_item)
# smaple_item.setPos(rect_item.boundingRect().x()+100, rect_item.boundingRect().y()+25)
#draw circle
for j in range(2):
circle_item = CustomCircleItem.CustomCircleItem(100+(CIRCLE_X_OFFSET*j), 25, CIRCLE_RADIUS, 600, QColor(r, g, b), is_circle=True,parent=rect_item)
circle_item.setOpacity(1)
#draw head icon
head_icon_num = int(name[1]) if len(name) > 1 else 1
head_icon = QGraphicsPixmapItem(QPixmap(HEAD_ICON_PREFIX % head_icon_num), circle_item)
head_icon.setPos(circle_item.boundingRect().x(), circle_item.boundingRect().y())
#name item
name_item = QGraphicsTextItem()
name_str = name[2 + j] if len(name) > 2 + j else "Unknown"
if is_chinese(name_str):
if len(name_str) == 2:
self.customFont.setLetterSpacing(QFont.AbsoluteSpacing, 30) # Adjust word spacing if needed
elif len(name_str) == 3:
self.customFont.setLetterSpacing(QFont.AbsoluteSpacing, 15)
elif len(name_str) == 4:
self.customFont.setLetterSpacing(QFont.AbsoluteSpacing, 0)
else:
self.customFont.setLetterSpacing(QFont.AbsoluteSpacing, 0)
name_item.setFont(self.customFont)
name_item.setPlainText(name_str)
name_item.setDefaultTextColor(QColor(0, 0, 0))
name_item.setPos((circle_item.boundingRect().x()+ circle_item.boundingRect().width()/2 - name_item.boundingRect().width()/2)+10, circle_item.boundingRect().y()+430)
self.scene.addItem(name_item)
if i > 0 and i % 6 == 5:
# Save the current scene to an image file every 5 items
self.scene_to_image(self.scene, f"generated_image_{i//6}.png")
self.scene.clear()
print("Image generated and added to scene.")
if __name__ == "__main__":
app = QApplication(sys.argv)
generator = ImageGenerator()
generator.finished.connect(app.quit)
if len(sys.argv) > 1:
csv_file = sys.argv[1]
print(f"Loading CSV file: {csv_file}")
else:
csv_file = "resource/sample.csv"
print(f"No CSV file provided, using default: {csv_file}")
sample_data = generator.read_csv(csv_file)
print(f"Sample data loaded: {sample_data}")
generator.generate_images(sample_data)
#generator.start()
# Run the application event loop
sys.exit(0)

Binary file not shown.

25
resource/colors.csv Normal file
View File

@ -0,0 +1,25 @@
color_name,r,g,b
海天藍,74,139,204
桃花紅,213,104,111
菸灰綠,181,188,177
奶油色,242,232,212
湖水藍,162,199,206
奶茶色,226,197,159
亮黃色,246,220,85
小松黃,199,154,98
天空藍,167,204,227
甜蜜粉,227,172,189
嫩綠色,172,207,120
燕麥灰,206,199,187
焦糖橙,216,165,138
蘋果綠,147,176,90
復古綠,125,148,136
水綠色,219,226,212
草綠色,114,162,75
紫藤色,142,126,168
紫灰色,179,171,186
淺藍灰,149,172,195
裸粉色,212,179,191
法藍色,132,173,175
薰衣紫,172,162,202
淡黃色,232,225,170
1 color_name r g b
2 海天藍 74 139 204
3 桃花紅 213 104 111
4 菸灰綠 181 188 177
5 奶油色 242 232 212
6 湖水藍 162 199 206
7 奶茶色 226 197 159
8 亮黃色 246 220 85
9 小松黃 199 154 98
10 天空藍 167 204 227
11 甜蜜粉 227 172 189
12 嫩綠色 172 207 120
13 燕麥灰 206 199 187
14 焦糖橙 216 165 138
15 蘋果綠 147 176 90
16 復古綠 125 148 136
17 水綠色 219 226 212
18 草綠色 114 162 75
19 紫藤色 142 126 168
20 紫灰色 179 171 186
21 淺藍灰 149 172 195
22 裸粉色 212 179 191
23 法藍色 132 173 175
24 薰衣紫 172 162 202
25 淡黃色 232 225 170

BIN
resource/head_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
resource/head_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
resource/head_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
resource/head_4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
resource/head_5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
resource/head_6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

25
resource/sample.csv Normal file
View File

@ -0,0 +1,25 @@
color_name,icon_type,name_1,name_2
海天藍,1,陳宣瑜,Shirley
桃花紅,1,王子,陳匹股
菸灰綠,1,公主,公主
奶油色,1,吳佳鈴,吳佳鈴
湖水藍,5,陳守志,陳守志
奶茶色,6,陳宣瑜,陳宣瑜
亮黃色,1,王子,王子
小松黃,2,公主,公主
天空藍,3,吳佳鈴,吳佳鈴
甜蜜粉,4,陳守志,陳守志
嫩綠色,5,陳宣瑜,陳宣瑜
燕麥灰,6,王子,王子
焦糖橙,1,公主,公主
蘋果綠,2,吳佳鈴,吳佳鈴
復古綠,3,陳守志,陳守志
水綠色,4,陳宣瑜,陳宣瑜
草綠色,5,王子,王子
紫藤色,6,公主,公主
紫灰色,1,吳佳鈴,吳佳鈴
淺藍灰,2,陳守志,陳守志
裸粉色,3,陳宣瑜,陳宣瑜
法藍色,4,王子,王子
薰衣紫,5,公主,公主
淡黃色,6,吳佳鈴,吳佳鈴
1 color_name icon_type name_1 name_2
2 海天藍 1 陳宣瑜 Shirley
3 桃花紅 1 王子 陳匹股
4 菸灰綠 1 公主 公主
5 奶油色 1 吳佳鈴 吳佳鈴
6 湖水藍 5 陳守志 陳守志
7 奶茶色 6 陳宣瑜 陳宣瑜
8 亮黃色 1 王子 王子
9 小松黃 2 公主 公主
10 天空藍 3 吳佳鈴 吳佳鈴
11 甜蜜粉 4 陳守志 陳守志
12 嫩綠色 5 陳宣瑜 陳宣瑜
13 燕麥灰 6 王子 王子
14 焦糖橙 1 公主 公主
15 蘋果綠 2 吳佳鈴 吳佳鈴
16 復古綠 3 陳守志 陳守志
17 水綠色 4 陳宣瑜 陳宣瑜
18 草綠色 5 王子 王子
19 紫藤色 6 公主 公主
20 紫灰色 1 吳佳鈴 吳佳鈴
21 淺藍灰 2 陳守志 陳守志
22 裸粉色 3 陳宣瑜 陳宣瑜
23 法藍色 4 王子 王子
24 薰衣紫 5 公主 公主
25 淡黃色 6 吳佳鈴 吳佳鈴

BIN
resource/sample.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 784 KiB