Add eyebrow scaling/rotation and cheek position controls

- Added eyebrow size controls (left/right, x/y scaling)
- Added eyebrow rotation controls (left/right, degrees)
- Added cheek position offset controls (left/right, x/y)
- Implemented transform order: scale → rotate → translate for eyebrows
- Updated settings_list import/export to handle 47 parameters
- Updated web extension reset button with new defaults
- All tests passed successfully

Co-authored-by: EricRorich <86147326+EricRorich@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-01-07 17:24:43 +00:00
co-authored by EricRorich
parent 17d15b4c60
commit 8f4e2efd2c
2 changed files with 172 additions and 29 deletions
+162 -29
View File
@@ -305,7 +305,36 @@ class ComfyUIFaceShaper:
"FLOAT",
{"default": 1.0, "min": 0.5, "max": 2.0, "step": 0.01},
),
# Cheeks
"cheek_left_pos_x": (
"FLOAT",
{"default": 0.0, "min": -0.3, "max": 0.3, "step": 0.005},
),
"cheek_left_pos_y": (
"FLOAT",
{"default": 0.0, "min": -0.3, "max": 0.3, "step": 0.005},
),
"cheek_right_pos_x": (
"FLOAT",
{"default": 0.0, "min": -0.3, "max": 0.3, "step": 0.005},
),
"cheek_right_pos_y": (
"FLOAT",
{"default": 0.0, "min": -0.3, "max": 0.3, "step": 0.005},
),
# Eyebrows
"eyebrow_left_size_x": (
"FLOAT",
{"default": 1.0, "min": 0.5, "max": 2.0, "step": 0.01},
),
"eyebrow_left_size_y": (
"FLOAT",
{"default": 1.0, "min": 0.5, "max": 2.0, "step": 0.01},
),
"eyebrow_left_rotation": (
"FLOAT",
{"default": 0.0, "min": -45.0, "max": 45.0, "step": 0.1},
),
"eyebrow_left_pos_x": (
"FLOAT",
{"default": 0.0, "min": -0.5, "max": 0.5, "step": 0.01},
@@ -314,6 +343,18 @@ class ComfyUIFaceShaper:
"FLOAT",
{"default": 0.0, "min": -0.5, "max": 0.5, "step": 0.01},
),
"eyebrow_right_size_x": (
"FLOAT",
{"default": 1.0, "min": 0.5, "max": 2.0, "step": 0.01},
),
"eyebrow_right_size_y": (
"FLOAT",
{"default": 1.0, "min": 0.5, "max": 2.0, "step": 0.01},
),
"eyebrow_right_rotation": (
"FLOAT",
{"default": 0.0, "min": -45.0, "max": 45.0, "step": 0.1},
),
"eyebrow_right_pos_x": (
"FLOAT",
{"default": 0.0, "min": -0.5, "max": 0.5, "step": 0.01},
@@ -388,8 +429,18 @@ class ComfyUIFaceShaper:
lip_lower_size_y: float,
chin_size_x: float,
chin_size_y: float,
cheek_left_pos_x: float,
cheek_left_pos_y: float,
cheek_right_pos_x: float,
cheek_right_pos_y: float,
eyebrow_left_size_x: float,
eyebrow_left_size_y: float,
eyebrow_left_rotation: float,
eyebrow_left_pos_x: float,
eyebrow_left_pos_y: float,
eyebrow_right_size_x: float,
eyebrow_right_size_y: float,
eyebrow_right_rotation: float,
eyebrow_right_pos_x: float,
eyebrow_right_pos_y: float,
nose_pos_y: float,
@@ -403,7 +454,7 @@ class ComfyUIFaceShaper:
):
"""Render the facial mask image and return it as a tensor."""
# Handle settings_list import: override all adjustable parameters if provided
if settings_list is not None and len(settings_list) >= 35:
if settings_list is not None and len(settings_list) >= 47:
eye_left_size_x = settings_list[0]
eye_left_size_y = settings_list[1]
eye_left_pos_x = settings_list[2]
@@ -428,18 +479,28 @@ class ComfyUIFaceShaper:
lip_lower_size_y = settings_list[21]
chin_size_x = settings_list[22]
chin_size_y = settings_list[23]
eyebrow_left_pos_x = settings_list[24]
eyebrow_left_pos_y = settings_list[25]
eyebrow_right_pos_x = settings_list[26]
eyebrow_right_pos_y = settings_list[27]
nose_pos_y = settings_list[28]
nose_size_x = settings_list[29]
nose_size_y = settings_list[30]
camera_distance = settings_list[31]
camera_pos_x = settings_list[32]
camera_pos_y = settings_list[33]
line_thickness = settings_list[34]
# Note: canvas_width/canvas_height (indices 35-36) are exported but not imported
cheek_left_pos_x = settings_list[24]
cheek_left_pos_y = settings_list[25]
cheek_right_pos_x = settings_list[26]
cheek_right_pos_y = settings_list[27]
eyebrow_left_size_x = settings_list[28]
eyebrow_left_size_y = settings_list[29]
eyebrow_left_rotation = settings_list[30]
eyebrow_left_pos_x = settings_list[31]
eyebrow_left_pos_y = settings_list[32]
eyebrow_right_size_x = settings_list[33]
eyebrow_right_size_y = settings_list[34]
eyebrow_right_rotation = settings_list[35]
eyebrow_right_pos_x = settings_list[36]
eyebrow_right_pos_y = settings_list[37]
nose_pos_y = settings_list[38]
nose_size_x = settings_list[39]
nose_size_y = settings_list[40]
camera_distance = settings_list[41]
camera_pos_x = settings_list[42]
camera_pos_y = settings_list[43]
line_thickness = settings_list[44]
# Note: canvas_width/canvas_height (indices 45-46) are exported but not imported
# as they are always provided as direct parameters to the method
face_points = _face_data_for_gender(gender)
@@ -488,6 +549,32 @@ class ComfyUIFaceShaper:
) -> List[Tuple[float, float]]:
return [(rx + offset_x, ry + offset_y) for rx, ry in points]
# Helper to rotate points around a center by angle in degrees.
def rotate_polygon(
points: List[Tuple[float, float]],
angle_degrees: float,
cx: float,
cy: float,
) -> List[Tuple[float, float]]:
"""Rotate points around (cx, cy) by angle_degrees using 2D rotation."""
if angle_degrees == 0.0:
return points
import math
angle_rad = math.radians(angle_degrees)
cos_a = math.cos(angle_rad)
sin_a = math.sin(angle_rad)
rotated = []
for rx, ry in points:
# Translate to origin
dx = rx - cx
dy = ry - cy
# Rotate
new_x = dx * cos_a - dy * sin_a
new_y = dx * sin_a + dy * cos_a
# Translate back
rotated.append((cx + new_x, cy + new_y))
return rotated
# Draw outer head outline with per-region horizontal scaling
if "outer_head" in face_points:
# Calculate centroid for scaling
@@ -571,22 +658,48 @@ class ComfyUIFaceShaper:
pixel_points = [to_pixel(pt) for pt in chin]
draw.line(pixel_points, fill=(0, 0, 0), width=stroke_width)
# Draw eyebrows with translation
# Draw eyebrows with scale → rotation → translation transform order
if "eyebrow_left" in face_points:
eyebrow_left = translate_polygon(
face_points["eyebrow_left"],
eyebrow_left_pos_x,
eyebrow_left_pos_y,
)
# Calculate centroid
eyebrow_left_points = face_points["eyebrow_left"]
cx = sum(px for px, _ in eyebrow_left_points) / len(eyebrow_left_points)
cy = sum(py for _, py in eyebrow_left_points) / len(eyebrow_left_points)
# Step 1: Scale around centroid
scaled = []
for rx, ry in eyebrow_left_points:
dx = (rx - cx) * eyebrow_left_size_x
dy = (ry - cy) * eyebrow_left_size_y
scaled.append((cx + dx, cy + dy))
# Step 2: Rotate around centroid
rotated = rotate_polygon(scaled, eyebrow_left_rotation, cx, cy)
# Step 3: Apply positional offset
eyebrow_left = translate_polygon(rotated, eyebrow_left_pos_x, eyebrow_left_pos_y)
pixel_points = [to_pixel(pt) for pt in eyebrow_left]
draw.line(pixel_points, fill=(0, 0, 0), width=stroke_width)
if "eyebrow_right" in face_points:
eyebrow_right = translate_polygon(
face_points["eyebrow_right"],
eyebrow_right_pos_x,
eyebrow_right_pos_y,
)
# Calculate centroid
eyebrow_right_points = face_points["eyebrow_right"]
cx = sum(px for px, _ in eyebrow_right_points) / len(eyebrow_right_points)
cy = sum(py for _, py in eyebrow_right_points) / len(eyebrow_right_points)
# Step 1: Scale around centroid
scaled = []
for rx, ry in eyebrow_right_points:
dx = (rx - cx) * eyebrow_right_size_x
dy = (ry - cy) * eyebrow_right_size_y
scaled.append((cx + dx, cy + dy))
# Step 2: Rotate around centroid
rotated = rotate_polygon(scaled, eyebrow_right_rotation, cx, cy)
# Step 3: Apply positional offset
eyebrow_right = translate_polygon(rotated, eyebrow_right_pos_x, eyebrow_right_pos_y)
pixel_points = [to_pixel(pt) for pt in eyebrow_right]
draw.line(pixel_points, fill=(0, 0, 0), width=stroke_width)
@@ -602,20 +715,30 @@ class ComfyUIFaceShaper:
pixel_points = [to_pixel(pt) for pt in nose]
draw.line(pixel_points, fill=(0, 0, 0), width=stroke_width)
# Draw cheeks and connect them to outer head
# Draw cheeks with position offsets and connect them to outer head
if "cheek_left" in face_points:
pixel_points = [to_pixel(pt) for pt in face_points["cheek_left"]]
cheek_left = translate_polygon(
face_points["cheek_left"],
cheek_left_pos_x,
cheek_left_pos_y,
)
pixel_points = [to_pixel(pt) for pt in cheek_left]
draw.line(pixel_points, fill=(0, 0, 0), width=stroke_width)
# Connect cheek_left last point to outer head point
cheek_left_end = to_pixel((0.316923, 0.729073))
cheek_left_end = to_pixel((0.316923 + cheek_left_pos_x, 0.729073 + cheek_left_pos_y))
outer_head_point = to_pixel((0.320690, 0.854875))
draw.line([cheek_left_end, outer_head_point], fill=(0, 0, 0), width=stroke_width)
if "cheek_right" in face_points:
pixel_points = [to_pixel(pt) for pt in face_points["cheek_right"]]
cheek_right = translate_polygon(
face_points["cheek_right"],
cheek_right_pos_x,
cheek_right_pos_y,
)
pixel_points = [to_pixel(pt) for pt in cheek_right]
draw.line(pixel_points, fill=(0, 0, 0), width=stroke_width)
# Connect cheek_right last point to outer head point
cheek_right_end = to_pixel((0.683077, 0.729073))
cheek_right_end = to_pixel((0.683077 + cheek_right_pos_x, 0.729073 + cheek_right_pos_y))
outer_head_point = to_pixel((0.679310, 0.854875))
draw.line([cheek_right_end, outer_head_point], fill=(0, 0, 0), width=stroke_width)
@@ -693,8 +816,18 @@ class ComfyUIFaceShaper:
lip_lower_size_y,
chin_size_x,
chin_size_y,
cheek_left_pos_x,
cheek_left_pos_y,
cheek_right_pos_x,
cheek_right_pos_y,
eyebrow_left_size_x,
eyebrow_left_size_y,
eyebrow_left_rotation,
eyebrow_left_pos_x,
eyebrow_left_pos_y,
eyebrow_right_size_x,
eyebrow_right_size_y,
eyebrow_right_rotation,
eyebrow_right_pos_x,
eyebrow_right_pos_y,
nose_pos_y,
+10
View File
@@ -51,8 +51,18 @@ app.registerExtension({
"lip_lower_size_y": 1.0,
"chin_size_x": 1.0,
"chin_size_y": 1.0,
"cheek_left_pos_x": 0.0,
"cheek_left_pos_y": 0.0,
"cheek_right_pos_x": 0.0,
"cheek_right_pos_y": 0.0,
"eyebrow_left_size_x": 1.0,
"eyebrow_left_size_y": 1.0,
"eyebrow_left_rotation": 0.0,
"eyebrow_left_pos_x": 0.0,
"eyebrow_left_pos_y": 0.0,
"eyebrow_right_size_x": 1.0,
"eyebrow_right_size_y": 1.0,
"eyebrow_right_rotation": 0.0,
"eyebrow_right_pos_x": 0.0,
"eyebrow_right_pos_y": 0.0,
"nose_pos_y": 0.0,