This commit is contained in:
2025-10-13 13:35:52 +02:00
parent 2494897360
commit bf636cbc66
21 changed files with 331 additions and 276 deletions

View File

@@ -52,8 +52,8 @@ def generate_texture_image(props, width, height):
lanczos_filter = getattr(resampling_module, 'LANCZOS', getattr(Image, 'LANCZOS', Image.BICUBIC))
bicubic_filter = getattr(resampling_module, 'BICUBIC', getattr(Image, 'BICUBIC', Image.BILINEAR))
def _iter_enabled_overlays():
"""Yield (overlay, image_path) tuples for overlays that should be applied."""
def _iter_enabled_components():
"""Yield enabled composition components."""
overlays = getattr(props, 'image_overlays', [])
try:
overlay_list = list(overlays)
@@ -62,17 +62,14 @@ def generate_texture_image(props, width, height):
for overlay in overlay_list:
if not getattr(overlay, 'enabled', True):
continue
image_path = getattr(overlay, 'image_path', '')
if not image_path:
continue
yield overlay, image_path
yield overlay
def _calculate_overlay_position(overlay, overlay_size):
"""Calculate top-left pixel position for an overlay image."""
overlay_width, overlay_height = overlay_size
canvas_width = width
canvas_height = height
mode = getattr(overlay, 'positioning_mode', 'ABSOLUTE') or 'ABSOLUTE'
mode = getattr(overlay, 'placement_mode', 'ABSOLUTE') or 'ABSOLUTE'
x_norm = float(getattr(overlay, 'x_position', 0.5) or 0.5)
y_norm = float(getattr(overlay, 'y_position', 0.5) or 0.5)
@@ -95,16 +92,57 @@ def generate_texture_image(props, width, height):
y = max(0, min(max_y, y))
return x, y
def _apply_image_overlays(pil_canvas):
"""Composite enabled image overlays onto the base canvas."""
overlays_to_apply = sorted(
_iter_enabled_overlays(),
key=lambda item: getattr(item[0], 'z_index', 0)
def _apply_composition_components(pil_canvas):
"""Composite enabled components (text or image) onto the base canvas."""
components = sorted(
_iter_enabled_components(),
key=lambda comp: getattr(comp, 'z_index', 0)
)
for overlay, image_path in overlays_to_apply:
resolved_path = image_path
scale = getattr(overlay, 'scale', 1.0)
for component in components:
if getattr(component, 'component_type', 'IMAGE') == 'TEXT':
text_value = getattr(component, 'text_content', '').strip()
if not text_value:
continue
scale = getattr(component, 'scale', 1.0)
try:
scale = float(scale if scale is not None else 1.0)
except (TypeError, ValueError):
scale = 1.0
text_size = max(8, int(round(props.font_size * max(scale, 0.1))))
text_font = load_font_for_size(text_size)
try:
text_bbox = text_font.getbbox(text_value)
text_width = text_bbox[2] - text_bbox[0]
text_height = text_bbox[3] - text_bbox[1]
except AttributeError:
try:
text_width, text_height = text_font.getsize(text_value)
except AttributeError:
# Fallback to a temporary draw context
tmp_draw = ImageDraw.Draw(Image.new('RGBA', (1, 1)))
text_width, text_height = tmp_draw.textsize(text_value, font=text_font)
overlay_img = Image.new('RGBA', (text_width, text_height), (0, 0, 0, 0))
overlay_draw = ImageDraw.Draw(overlay_img)
text_color = tuple(int(c * 255) for c in props.text_color[:4])
overlay_draw.text((0, 0), text_value, font=text_font, fill=text_color)
rotation = float(getattr(component, 'rotation', 0.0) or 0.0)
if rotation % 360:
overlay_img = overlay_img.rotate(-rotation, expand=True, resample=bicubic_filter)
x_pos, y_pos = _calculate_overlay_position(component, overlay_img.size)
overlay_layer = Image.new('RGBA', (width, height), (0, 0, 0, 0))
overlay_layer.paste(overlay_img, (x_pos, y_pos), overlay_img)
pil_canvas = Image.alpha_composite(pil_canvas, overlay_layer)
continue
resolved_path = getattr(component, 'image_path', '')
if not resolved_path:
continue
scale = getattr(component, 'scale', 1.0)
try:
scale = float(scale if scale is not None else 1.0)
except (TypeError, ValueError):
@@ -121,7 +159,7 @@ def generate_texture_image(props, width, height):
resolved_path = raster_info.path
scale_to_apply = raster_info.applied_scale
except SVGConversionError as svg_error:
print(f"WARNING: Failed to rasterize SVG overlay '{image_path}': {svg_error}")
print(f"WARNING: Failed to rasterize SVG overlay '{component.image_path}': {svg_error}")
continue
try:
@@ -139,11 +177,11 @@ def generate_texture_image(props, width, height):
if new_size != overlay_img.size:
overlay_img = overlay_img.resize(new_size, lanczos_filter)
rotation = float(getattr(overlay, 'rotation', 0.0) or 0.0)
rotation = float(getattr(component, 'rotation', 0.0) or 0.0)
if rotation % 360:
overlay_img = overlay_img.rotate(-rotation, expand=True, resample=bicubic_filter)
x_pos, y_pos = _calculate_overlay_position(overlay, overlay_img.size)
x_pos, y_pos = _calculate_overlay_position(component, overlay_img.size)
overlay_layer = Image.new('RGBA', (width, height), (0, 0, 0, 0))
overlay_layer.paste(overlay_img, (x_pos, y_pos), overlay_img)
@@ -358,7 +396,7 @@ def generate_texture_image(props, width, height):
pass
# Apply any configured image overlays before converting to Blender pixels
pil_img = _apply_image_overlays(pil_img)
pil_img = _apply_composition_components(pil_img)
# Convert PIL image to Blender format (flip vertically to fix mirroring)
pil_img = pil_img.transpose(Image.FLIP_TOP_BOTTOM) # Fix mirroring issue