diff --git a/.gitignore b/.gitignore index e205a6a..b774544 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ uploads/* generated/* *.pyc *.pyo +/generated_images diff --git a/CustomCircleItem.py b/CustomCircleItem.py new file mode 100644 index 0000000..1bde5ca --- /dev/null +++ b/CustomCircleItem.py @@ -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) diff --git a/app.py b/app.py index 4e5b73b..aebc83c 100644 --- a/app.py +++ b/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 diff --git a/generate_images.py b/generate_images.py index ceb8cac..f519c5e 100644 --- a/generate_images.py +++ b/generate_images.py @@ -1,2 +1,194 @@ import sys -from PySide6.QtCore import QCoreApplication, QTimer, QObject, Signal, Slot \ No newline at end of file +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) \ No newline at end of file diff --git a/resource/TaiwanPearl-SemiBold.ttf b/resource/TaiwanPearl-SemiBold.ttf new file mode 100644 index 0000000..330224c Binary files /dev/null and b/resource/TaiwanPearl-SemiBold.ttf differ diff --git a/resource/colors.csv b/resource/colors.csv new file mode 100644 index 0000000..048861a --- /dev/null +++ b/resource/colors.csv @@ -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 \ No newline at end of file diff --git a/resource/head_1.png b/resource/head_1.png new file mode 100644 index 0000000..cf64d93 Binary files /dev/null and b/resource/head_1.png differ diff --git a/resource/head_2.png b/resource/head_2.png new file mode 100644 index 0000000..f0498ae Binary files /dev/null and b/resource/head_2.png differ diff --git a/resource/head_3.png b/resource/head_3.png new file mode 100644 index 0000000..fa6989e Binary files /dev/null and b/resource/head_3.png differ diff --git a/resource/head_4.png b/resource/head_4.png new file mode 100644 index 0000000..5061d0d Binary files /dev/null and b/resource/head_4.png differ diff --git a/resource/head_5.png b/resource/head_5.png new file mode 100644 index 0000000..26c4f9a Binary files /dev/null and b/resource/head_5.png differ diff --git a/resource/head_6.png b/resource/head_6.png new file mode 100644 index 0000000..46edf51 Binary files /dev/null and b/resource/head_6.png differ diff --git a/resource/sample.csv b/resource/sample.csv new file mode 100644 index 0000000..c2ceea4 --- /dev/null +++ b/resource/sample.csv @@ -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,吳佳鈴,吳佳鈴 diff --git a/resource/sample.jpg b/resource/sample.jpg new file mode 100644 index 0000000..e9040eb Binary files /dev/null and b/resource/sample.jpg differ diff --git a/resource/來圖圓形鑰匙圈-賣場-02.jpg b/resource/來圖圓形鑰匙圈-賣場-02.jpg new file mode 100644 index 0000000..25a4516 Binary files /dev/null and b/resource/來圖圓形鑰匙圈-賣場-02.jpg differ