1.完成大部分功能(圖片下載功能尚未加上)
1
.gitignore
vendored
@ -3,3 +3,4 @@ uploads/*
|
||||
generated/*
|
||||
*.pyc
|
||||
*.pyo
|
||||
/generated_images
|
||||
|
||||
46
CustomCircleItem.py
Normal 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
@ -2,6 +2,7 @@
|
||||
import os
|
||||
import uuid
|
||||
from flask import Flask, render_template, request, send_file
|
||||
import subprocess
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@ -37,9 +38,26 @@ def upload_csv():
|
||||
if file.filename == '':
|
||||
return "No selected file", 400
|
||||
if file and allowed_file(file.filename):
|
||||
|
||||
unique_filename = str(uuid.uuid4()) + '.csv'
|
||||
csv_path = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
|
||||
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
|
||||
|
||||
|
||||
|
||||
@ -1,2 +1,194 @@
|
||||
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)
|
||||
BIN
resource/TaiwanPearl-SemiBold.ttf
Normal file
25
resource/colors.csv
Normal 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
|
||||
|
BIN
resource/head_1.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
resource/head_2.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
resource/head_3.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
resource/head_4.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
resource/head_5.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
resource/head_6.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
25
resource/sample.csv
Normal 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,吳佳鈴,吳佳鈴
|
||||
|
BIN
resource/sample.jpg
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
resource/來圖圓形鑰匙圈-賣場-02.jpg
Normal file
|
After Width: | Height: | Size: 784 KiB |