修改了扁平结构
This commit is contained in:
parent
5acbe8d7b7
commit
d15a72e489
@ -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
|
||||
Loading…
x
Reference in New Issue
Block a user