修改了扁平结构

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]: 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: Args:
content: 海报内容数据 content: 海报内容数据
@ -640,7 +640,7 @@ class PosterService:
images: 用户上传的图片 images: 用户上传的图片
Returns: Returns:
Dict: 支持多级分层的Fabric.js JSON格式数据 Dict: 扁平化的Fabric.js JSON格式数据
""" """
try: try:
fabric_objects = [] fabric_objects = []
@ -648,21 +648,27 @@ class PosterService:
# 基础画布尺寸 # 基础画布尺寸
canvas_width, canvas_height = image_size[0], image_size[1] canvas_width, canvas_height = image_size[0], image_size[1]
# 1. 图片层组 (最底层 - 用户上传的图片) # 1. 用户上传的图片(最底层 - Level 0
image_group = self._create_image_layer(images, canvas_width, canvas_height) if images and hasattr(images, 'width'):
fabric_objects.append(image_group) 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. 背景层组 (第二层) # 2. 背景层Level 1
background_group = self._create_background_layer(canvas_width, canvas_height, template_id) if template_id:
fabric_objects.append(background_group) background_object = self._create_background_object(canvas_width, canvas_height)
fabric_objects.append(background_object)
# 3. 内容层组 (中间层) # 3. 内容文字层Level 2
content_group = self._create_content_layer(content, canvas_width, canvas_height) text_objects = self._create_text_objects(content, canvas_width, canvas_height)
fabric_objects.append(content_group) fabric_objects.extend(text_objects)
# 4. 装饰层组 (顶层) # 4. 装饰层Level 3
decoration_group = self._create_decoration_layer(content, canvas_width, canvas_height) decoration_objects = self._create_decoration_objects(canvas_width, canvas_height)
fabric_objects.append(decoration_group) fabric_objects.extend(decoration_objects)
# 构建完整的Fabric.js JSON # 构建完整的Fabric.js JSON
fabric_json = { fabric_json = {
@ -693,7 +699,7 @@ class PosterService:
"includeDefaultValues": True "includeDefaultValues": True
} }
logger.info(f"成功生成多级分层Fabric.js JSON包含 {len(fabric_objects)}层组") logger.info(f"成功生成扁平化Fabric.js JSON包含 {len(fabric_objects)}独立对象")
return fabric_json return fabric_json
except Exception as e: except Exception as e:
@ -705,117 +711,58 @@ class PosterService:
"width": image_size[0], "width": image_size[0],
"height": image_size[1] "height": image_size[1]
} }
def _create_image_layer(self, images: Image.Image, canvas_width: int, canvas_height: int) -> Dict[str, Any]: def _create_image_object(self, images: Image.Image, canvas_width: int, canvas_height: int) -> Dict[str, Any]:
"""创建图片层组(最底层)""" """创建用户上传的图片对象(最底层)"""
image_objects = [] # 将PIL图像转换为base64
image_base64 = self._image_to_base64(images)
if images and hasattr(images, 'width'): # 计算图片的缩放比例,保持宽高比
# 将PIL图像转换为base64以便在Fabric.js中使用 image_width, image_height = images.width, images.height
image_base64 = self._image_to_base64(images) scale_x = canvas_width / image_width
scale_y = canvas_height / image_height
# 计算图片的缩放比例,保持宽高比 scale = min(scale_x, scale_y) # 保持宽高比的适应缩放
image_width, image_height = images.width, images.height
scale_x = canvas_width / image_width # 计算居中位置
scale_y = canvas_height / image_height scaled_width = image_width * scale
scale = min(scale_x, scale_y) # 保持宽高比的适应缩放 scaled_height = image_height * scale
left = (canvas_width - scaled_width) / 2
# 计算居中位置 top = (canvas_height - scaled_height) / 2
scaled_width = image_width * scale
scaled_height = image_height * scale
left = (canvas_width - scaled_width) / 2
top = (canvas_height - scaled_height) / 2
image_objects.append({
"type": "image",
"version": "5.3.0",
"originX": "left",
"originY": "top",
"left": left,
"top": top,
"width": image_width,
"height": image_height,
"scaleX": scale,
"scaleY": scale,
"angle": 0,
"flipX": False,
"flipY": False,
"opacity": 1,
"visible": True,
"src": f"data:image/png;base64,{image_base64}",
"crossOrigin": "anonymous",
"name": "user_uploaded_image",
"data": {
"type": "user_image",
"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({
"type": "rect",
"version": "5.3.0",
"originX": "left",
"originY": "top",
"left": 0,
"top": 0,
"width": canvas_width,
"height": canvas_height,
"fill": "#f8f9fa",
"stroke": "#dee2e6",
"strokeWidth": 2,
"strokeDashArray": [10, 5],
"name": "image_placeholder",
"data": {
"type": "placeholder",
"replaceable": True,
"placeholder_text": "点击上传图片"
},
"selectable": True,
"evented": True
})
return { return {
"type": "group", "type": "image",
"version": "5.3.0", "version": "5.3.0",
"originX": "left", "originX": "left",
"originY": "top", "originY": "top",
"left": 0, "left": left,
"top": 0, "top": top,
"width": canvas_width, "width": image_width,
"height": canvas_height, "height": image_height,
"scaleX": scale,
"scaleY": scale,
"angle": 0, "angle": 0,
"scaleX": 1,
"scaleY": 1,
"flipX": False, "flipX": False,
"flipY": False, "flipY": False,
"opacity": 1, "opacity": 1,
"visible": True, "visible": True,
"name": "image_layer", "src": f"data:image/png;base64,{image_base64}",
"data": {"layer": "image", "level": 0, "replaceable": True}, "crossOrigin": "anonymous",
"objects": image_objects, "name": "user_uploaded_image",
"selectable": False, "data": {
"type": "user_image",
"layer": "image",
"level": 0,
"replaceable": True,
"original_size": [image_width, image_height],
"scale_ratio": scale
},
"selectable": True,
"evented": True "evented": True
} }
def _create_background_layer(self, canvas_width: int, canvas_height: int, template_id: str) -> Dict[str, Any]: def _create_placeholder_object(self, canvas_width: int, canvas_height: int) -> Dict[str, Any]:
"""创建背景层组""" """创建图片占位符对象"""
background_objects = [] return {
# 主背景
background_objects.append({
"type": "rect", "type": "rect",
"version": "5.3.0", "version": "5.3.0",
"originX": "left", "originX": "left",
@ -824,269 +771,118 @@ class PosterService:
"top": 0, "top": 0,
"width": canvas_width, "width": canvas_width,
"height": canvas_height, "height": canvas_height,
"fill": "transparent", "fill": "#f8f9fa",
"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
}
def _create_background_object(self, canvas_width: int, canvas_height: int) -> Dict[str, Any]:
"""创建半透明背景对象"""
return {
"type": "rect",
"version": "5.3.0",
"originX": "left",
"originY": "top",
"left": 0,
"top": 0,
"width": canvas_width,
"height": canvas_height,
"fill": "rgba(255, 255, 255, 0.8)",
"stroke": None, "stroke": None,
"name": "main_background", "angle": 0,
"flipX": False,
"flipY": False,
"opacity": 0.8,
"visible": True,
"name": "background_overlay",
"data": {
"type": "background",
"layer": "background",
"level": 1
},
"selectable": False, "selectable": False,
"evented": False "evented": False
}) }
def _create_text_objects(self, content: Dict[str, Any], canvas_width: int, canvas_height: int) -> List[Dict[str, Any]]:
"""创建文本对象列表"""
text_objects = []
# 渐变背景(可选) # 文本元素配置
if template_id: text_configs = {
background_objects.append({ 'title': {'fontSize': 48, 'top': 100, 'fontWeight': 'bold', 'fill': '#2c3e50'},
"type": "rect", 'slogan': {'fontSize': 24, 'top': 180, 'fontWeight': 'normal', 'fill': '#7f8c8d'},
"version": "5.3.0", 'content': {'fontSize': 18, 'top': 250, 'fontWeight': 'normal', 'fill': '#34495e'},
"originX": "left", 'price': {'fontSize': 36, 'top': 400, 'fontWeight': 'bold', 'fill': '#e74c3c'},
"originY": "top", 'remarks': {'fontSize': 14, 'top': 500, 'fontWeight': 'normal', 'fill': '#95a5a6'}
"left": 0, }
"top": 0,
"width": canvas_width, for key, config in text_configs.items():
"height": canvas_height, if key in content and content[key]:
"fill": { text_content = content[key]
"type": "linear", if isinstance(text_content, list):
"coords": { text_content = '\n'.join(text_content)
"x1": 0, elif not isinstance(text_content, str):
"y1": 0, text_content = str(text_content)
"x2": 0,
"y2": canvas_height text_object = {
"type": "textbox",
"version": "5.3.0",
"originX": "left",
"originY": "top",
"left": 50,
"top": config['top'],
"width": canvas_width - 100,
"height": 60 if key != 'content' else 120,
"fill": config['fill'],
"fontFamily": "Arial, sans-serif",
"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"
}, },
"colorStops": [ "selectable": True,
{"offset": 0, "color": "#ffffff", "opacity": 0.8}, "evented": True
{"offset": 1, "color": "#f8f9fa", "opacity": 0.9} }
] text_objects.append(text_object)
},
"name": "gradient_background",
"selectable": False,
"evented": False
})
return { return text_objects
"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": "background_layer",
"data": {"layer": "background", "level": 1},
"objects": background_objects,
"selectable": False,
"evented": False
}
def _create_content_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]]:
"""创建内容层组,包含多个子分层""" """创建装饰对象列表"""
content_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
}
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']
if isinstance(text_content, list):
text_content = '\n'.join(text_content)
body_objects.append({
"type": "textbox",
"version": "5.3.0",
"originX": "left",
"originY": "top",
"left": 50,
"top": 280,
"width": canvas_width - 100,
"height": 120,
"fill": "#34495e",
"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
}
def _create_price_group(self, content: Dict[str, Any], canvas_width: int) -> Dict[str, Any]:
"""创建价格信息分组"""
price_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]:
"""创建装饰层组"""
decoration_objects = [] decoration_objects = []
# 装饰边框 # 装饰边框
decoration_objects.append({ border_object = {
"type": "rect", "type": "rect",
"version": "5.3.0", "version": "5.3.0",
"originX": "left", "originX": "left",
@ -1099,12 +895,24 @@ class PosterService:
"stroke": "#3498db", "stroke": "#3498db",
"strokeWidth": 3, "strokeWidth": 3,
"strokeDashArray": [10, 5], "strokeDashArray": [10, 5],
"angle": 0,
"flipX": False,
"flipY": False,
"opacity": 1,
"visible": True,
"name": "decoration_border", "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", "type": "circle",
"version": "5.3.0", "version": "5.3.0",
"originX": "center", "originX": "center",
@ -1113,30 +921,20 @@ class PosterService:
"top": 50, "top": 50,
"radius": 20, "radius": 20,
"fill": "#f39c12", "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, "angle": 0,
"scaleX": 1,
"scaleY": 1,
"flipX": False, "flipX": False,
"flipY": False, "flipY": False,
"opacity": 1, "opacity": 1,
"visible": True, "visible": True,
"name": "decoration_layer", "name": "corner_decoration",
"data": {"layer": "decoration", "level": 3}, "data": {
"objects": decoration_objects, "type": "decoration",
"selectable": False "layer": "decoration",
"level": 3
},
"selectable": False,
"evented": False
} }
decoration_objects.append(corner_object)
return decoration_objects