修改了扁平结构

This commit is contained in:
jinye_huang 2025-08-04 12:17:54 +08:00
parent 5acbe8d7b7
commit d15a72e489

View File

@ -631,7 +631,7 @@ class PosterService:
def _generate_fabric_json(self, content: Dict[str, Any], template_id: str, image_size: List[int], images: Image.Image = None) -> Dict[str, Any]:
"""
生成支持多级分层的Fabric.js JSON格式
生成扁平化的Fabric.js JSON格式不使用group每个对象独立成层
Args:
content: 海报内容数据
@ -640,7 +640,7 @@ class PosterService:
images: 用户上传的图片
Returns:
Dict: 支持多级分层的Fabric.js JSON格式数据
Dict: 扁平化的Fabric.js JSON格式数据
"""
try:
fabric_objects = []
@ -648,21 +648,27 @@ class PosterService:
# 基础画布尺寸
canvas_width, canvas_height = image_size[0], image_size[1]
# 1. 图片层组 (最底层 - 用户上传的图片)
image_group = self._create_image_layer(images, canvas_width, canvas_height)
fabric_objects.append(image_group)
# 1. 用户上传的图片(最底层 - Level 0
if images and hasattr(images, 'width'):
image_object = self._create_image_object(images, canvas_width, canvas_height)
fabric_objects.append(image_object)
else:
# 占位符
placeholder_object = self._create_placeholder_object(canvas_width, canvas_height)
fabric_objects.append(placeholder_object)
# 2. 背景层组 (第二层)
background_group = self._create_background_layer(canvas_width, canvas_height, template_id)
fabric_objects.append(background_group)
# 2. 背景层Level 1
if template_id:
background_object = self._create_background_object(canvas_width, canvas_height)
fabric_objects.append(background_object)
# 3. 内容层组 (中间层)
content_group = self._create_content_layer(content, canvas_width, canvas_height)
fabric_objects.append(content_group)
# 3. 内容文字层Level 2
text_objects = self._create_text_objects(content, canvas_width, canvas_height)
fabric_objects.extend(text_objects)
# 4. 装饰层组 (顶层)
decoration_group = self._create_decoration_layer(content, canvas_width, canvas_height)
fabric_objects.append(decoration_group)
# 4. 装饰层Level 3
decoration_objects = self._create_decoration_objects(canvas_width, canvas_height)
fabric_objects.extend(decoration_objects)
# 构建完整的Fabric.js JSON
fabric_json = {
@ -693,7 +699,7 @@ class PosterService:
"includeDefaultValues": True
}
logger.info(f"成功生成多级分层Fabric.js JSON包含 {len(fabric_objects)}层组")
logger.info(f"成功生成扁平化Fabric.js JSON包含 {len(fabric_objects)}独立对象")
return fabric_json
except Exception as e:
@ -706,12 +712,9 @@ class PosterService:
"height": image_size[1]
}
def _create_image_layer(self, images: Image.Image, canvas_width: int, canvas_height: int) -> Dict[str, Any]:
"""创建图片层组(最底层)"""
image_objects = []
if images and hasattr(images, 'width'):
# 将PIL图像转换为base64以便在Fabric.js中使用
def _create_image_object(self, images: Image.Image, canvas_width: int, canvas_height: int) -> Dict[str, Any]:
"""创建用户上传的图片对象(最底层)"""
# 将PIL图像转换为base64
image_base64 = self._image_to_base64(images)
# 计算图片的缩放比例,保持宽高比
@ -726,7 +729,7 @@ class PosterService:
left = (canvas_width - scaled_width) / 2
top = (canvas_height - scaled_height) / 2
image_objects.append({
return {
"type": "image",
"version": "5.3.0",
"originX": "left",
@ -747,24 +750,19 @@ class PosterService:
"name": "user_uploaded_image",
"data": {
"type": "user_image",
"layer": "image",
"level": 0,
"replaceable": True,
"original_size": [image_width, image_height],
"scale_ratio": scale
},
"selectable": True,
"evented": True,
"moveCursor": "move",
"cornerStyle": "circle",
"cornerSize": 12,
"transparentCorners": False,
"cornerColor": "#4dabf7",
"cornerStrokeColor": "#ffffff",
"borderColor": "#4dabf7",
"borderScaleFactor": 2
})
else:
# 如果没有图片,创建一个占位符
image_objects.append({
"evented": True
}
def _create_placeholder_object(self, canvas_width: int, canvas_height: int) -> Dict[str, Any]:
"""创建图片占位符对象"""
return {
"type": "rect",
"version": "5.3.0",
"originX": "left",
@ -777,45 +775,26 @@ class PosterService:
"stroke": "#dee2e6",
"strokeWidth": 2,
"strokeDashArray": [10, 5],
"angle": 0,
"flipX": False,
"flipY": False,
"opacity": 1,
"visible": True,
"name": "image_placeholder",
"data": {
"type": "placeholder",
"layer": "image",
"level": 0,
"replaceable": True,
"placeholder_text": "点击上传图片"
},
"selectable": True,
"evented": True
})
return {
"type": "group",
"version": "5.3.0",
"originX": "left",
"originY": "top",
"left": 0,
"top": 0,
"width": canvas_width,
"height": canvas_height,
"angle": 0,
"scaleX": 1,
"scaleY": 1,
"flipX": False,
"flipY": False,
"opacity": 1,
"visible": True,
"name": "image_layer",
"data": {"layer": "image", "level": 0, "replaceable": True},
"objects": image_objects,
"selectable": False,
"evented": True
}
def _create_background_layer(self, canvas_width: int, canvas_height: int, template_id: str) -> Dict[str, Any]:
"""创建背景层组"""
background_objects = []
# 主背景
background_objects.append({
def _create_background_object(self, canvas_width: int, canvas_height: int) -> Dict[str, Any]:
"""创建半透明背景对象"""
return {
"type": "rect",
"version": "5.3.0",
"originX": "left",
@ -824,269 +803,86 @@ class PosterService:
"top": 0,
"width": canvas_width,
"height": canvas_height,
"fill": "transparent",
"fill": "rgba(255, 255, 255, 0.8)",
"stroke": None,
"name": "main_background",
"selectable": False,
"evented": False
})
# 渐变背景(可选)
if template_id:
background_objects.append({
"type": "rect",
"version": "5.3.0",
"originX": "left",
"originY": "top",
"left": 0,
"top": 0,
"width": canvas_width,
"height": canvas_height,
"fill": {
"type": "linear",
"coords": {
"x1": 0,
"y1": 0,
"x2": 0,
"y2": canvas_height
},
"colorStops": [
{"offset": 0, "color": "#ffffff", "opacity": 0.8},
{"offset": 1, "color": "#f8f9fa", "opacity": 0.9}
]
},
"name": "gradient_background",
"selectable": False,
"evented": False
})
return {
"type": "group",
"version": "5.3.0",
"originX": "left",
"originY": "top",
"left": 0,
"top": 0,
"width": canvas_width,
"height": canvas_height,
"angle": 0,
"scaleX": 1,
"scaleY": 1,
"flipX": False,
"flipY": False,
"opacity": 1,
"opacity": 0.8,
"visible": True,
"name": "background_layer",
"data": {"layer": "background", "level": 1},
"objects": background_objects,
"name": "background_overlay",
"data": {
"type": "background",
"layer": "background",
"level": 1
},
"selectable": False,
"evented": False
}
def _create_content_layer(self, content: Dict[str, Any], canvas_width: int, canvas_height: int) -> Dict[str, Any]:
"""创建内容层组,包含多个子分层"""
content_objects = []
def _create_text_objects(self, content: Dict[str, Any], canvas_width: int, canvas_height: int) -> List[Dict[str, Any]]:
"""创建文本对象列表"""
text_objects = []
# 标题组
title_group = self._create_title_group(content, canvas_width)
if title_group["objects"]:
content_objects.append(title_group)
# 正文组
body_group = self._create_body_group(content, canvas_width)
if body_group["objects"]:
content_objects.append(body_group)
# 价格信息组
price_group = self._create_price_group(content, canvas_width)
if price_group["objects"]:
content_objects.append(price_group)
return {
"type": "group",
"version": "5.3.0",
"originX": "left",
"originY": "top",
"left": 0,
"top": 0,
"width": canvas_width,
"height": canvas_height,
"angle": 0,
"scaleX": 1,
"scaleY": 1,
"flipX": False,
"flipY": False,
"opacity": 1,
"visible": True,
"name": "content_layer",
"data": {"layer": "content", "level": 2},
"objects": content_objects
# 文本元素配置
text_configs = {
'title': {'fontSize': 48, 'top': 100, 'fontWeight': 'bold', 'fill': '#2c3e50'},
'slogan': {'fontSize': 24, 'top': 180, 'fontWeight': 'normal', 'fill': '#7f8c8d'},
'content': {'fontSize': 18, 'top': 250, 'fontWeight': 'normal', 'fill': '#34495e'},
'price': {'fontSize': 36, 'top': 400, 'fontWeight': 'bold', 'fill': '#e74c3c'},
'remarks': {'fontSize': 14, 'top': 500, 'fontWeight': 'normal', 'fill': '#95a5a6'}
}
def _create_title_group(self, content: Dict[str, Any], canvas_width: int) -> Dict[str, Any]:
"""创建标题分组"""
title_objects = []
# 主标题
if content.get('title'):
title_objects.append({
"type": "textbox",
"version": "5.3.0",
"originX": "left",
"originY": "top",
"left": 50,
"top": 80,
"width": canvas_width - 100,
"height": 80,
"fill": "#2c3e50",
"fontFamily": "Arial, sans-serif",
"fontWeight": "bold",
"fontSize": 48,
"text": str(content['title']),
"textAlign": "center",
"name": "main_title",
"data": {"type": "title", "priority": "high"}
})
# 副标题
if content.get('slogan'):
title_objects.append({
"type": "textbox",
"version": "5.3.0",
"originX": "left",
"originY": "top",
"left": 50,
"top": 170,
"width": canvas_width - 100,
"height": 40,
"fill": "#7f8c8d",
"fontFamily": "Arial, sans-serif",
"fontWeight": "normal",
"fontSize": 24,
"text": str(content['slogan']),
"textAlign": "center",
"name": "subtitle",
"data": {"type": "subtitle", "priority": "medium"}
})
return {
"type": "group",
"version": "5.3.0",
"originX": "left",
"originY": "top",
"left": 0,
"top": 0,
"width": canvas_width,
"height": 250,
"name": "title_group",
"data": {"section": "title", "level": 3},
"objects": title_objects
}
def _create_body_group(self, content: Dict[str, Any], canvas_width: int) -> Dict[str, Any]:
"""创建正文分组"""
body_objects = []
if content.get('content'):
text_content = content['content']
for key, config in text_configs.items():
if key in content and content[key]:
text_content = content[key]
if isinstance(text_content, list):
text_content = '\n'.join(text_content)
elif not isinstance(text_content, str):
text_content = str(text_content)
body_objects.append({
text_object = {
"type": "textbox",
"version": "5.3.0",
"originX": "left",
"originY": "top",
"left": 50,
"top": 280,
"top": config['top'],
"width": canvas_width - 100,
"height": 120,
"fill": "#34495e",
"height": 60 if key != 'content' else 120,
"fill": config['fill'],
"fontFamily": "Arial, sans-serif",
"fontWeight": "normal",
"fontSize": 18,
"text": str(text_content),
"textAlign": "left",
"lineHeight": 1.4,
"name": "main_content",
"data": {"type": "content", "priority": "medium"}
})
return {
"type": "group",
"version": "5.3.0",
"originX": "left",
"originY": "top",
"left": 0,
"top": 250,
"width": canvas_width,
"height": 150,
"name": "body_group",
"data": {"section": "body", "level": 3},
"objects": body_objects
"fontWeight": config['fontWeight'],
"fontSize": config['fontSize'],
"text": text_content,
"textAlign": "center" if key in ['title', 'slogan', 'price'] else "left",
"lineHeight": 1.2,
"angle": 0,
"flipX": False,
"flipY": False,
"opacity": 1,
"visible": True,
"name": f"text_{key}",
"data": {
"type": "text",
"layer": "content",
"level": 2,
"content_type": key,
"priority": "high" if key in ['title', 'price'] else "medium"
},
"selectable": True,
"evented": True
}
text_objects.append(text_object)
def _create_price_group(self, content: Dict[str, Any], canvas_width: int) -> Dict[str, Any]:
"""创建价格信息分组"""
price_objects = []
return text_objects
if content.get('price'):
# 价格背景
price_objects.append({
"type": "rect",
"version": "5.3.0",
"originX": "left",
"originY": "top",
"left": 40,
"top": 420,
"width": canvas_width - 80,
"height": 80,
"fill": "#e74c3c",
"rx": 10,
"ry": 10,
"name": "price_background"
})
# 价格文字
price_objects.append({
"type": "textbox",
"version": "5.3.0",
"originX": "left",
"originY": "top",
"left": 50,
"top": 440,
"width": canvas_width - 100,
"height": 40,
"fill": "#ffffff",
"fontFamily": "Arial, sans-serif",
"fontWeight": "bold",
"fontSize": 36,
"text": str(content['price']),
"textAlign": "center",
"name": "price_text",
"data": {"type": "price", "priority": "high"}
})
return {
"type": "group",
"version": "5.3.0",
"originX": "left",
"originY": "top",
"left": 0,
"top": 400,
"width": canvas_width,
"height": 100,
"name": "price_group",
"data": {"section": "price", "level": 3},
"objects": price_objects
}
def _create_decoration_layer(self, content: Dict[str, Any], canvas_width: int, canvas_height: int) -> Dict[str, Any]:
"""创建装饰层组"""
def _create_decoration_objects(self, canvas_width: int, canvas_height: int) -> List[Dict[str, Any]]:
"""创建装饰对象列表"""
decoration_objects = []
# 装饰边框
decoration_objects.append({
border_object = {
"type": "rect",
"version": "5.3.0",
"originX": "left",
@ -1099,12 +895,24 @@ class PosterService:
"stroke": "#3498db",
"strokeWidth": 3,
"strokeDashArray": [10, 5],
"angle": 0,
"flipX": False,
"flipY": False,
"opacity": 1,
"visible": True,
"name": "decoration_border",
"selectable": False
})
"data": {
"type": "decoration",
"layer": "decoration",
"level": 3
},
"selectable": False,
"evented": False
}
decoration_objects.append(border_object)
# 角落装饰
decoration_objects.append({
corner_object = {
"type": "circle",
"version": "5.3.0",
"originX": "center",
@ -1113,30 +921,20 @@ class PosterService:
"top": 50,
"radius": 20,
"fill": "#f39c12",
"name": "corner_decoration",
"selectable": False
})
return {
"type": "group",
"version": "5.3.0",
"originX": "left",
"originY": "top",
"left": 0,
"top": 0,
"width": canvas_width,
"height": canvas_height,
"angle": 0,
"scaleX": 1,
"scaleY": 1,
"flipX": False,
"flipY": False,
"opacity": 1,
"visible": True,
"name": "decoration_layer",
"data": {"layer": "decoration", "level": 3},
"objects": decoration_objects,
"selectable": False
"name": "corner_decoration",
"data": {
"type": "decoration",
"layer": "decoration",
"level": 3
},
"selectable": False,
"evented": False
}
decoration_objects.append(corner_object)
return decoration_objects