diff --git a/.memory/worklog.json b/.memory/worklog.json index aed135a..71299f2 100644 --- a/.memory/worklog.json +++ b/.memory/worklog.json @@ -1,19 +1,5 @@ { "entries": [ - { - "files_changed": 2, - "hash": "e6b8615", - "message": "auto-save 2026-05-12 17:23 (~2)", - "ts": "2026-05-12T17:23:21+08:00", - "type": "commit" - }, - { - "files_changed": 6, - "hash": "6a9abea", - "message": "auto-save 2026-05-12 17:28 (~6)", - "ts": "2026-05-12T17:28:54+08:00", - "type": "commit" - }, { "files_changed": 1, "hash": "f3ec026", @@ -3351,6 +3337,19 @@ "type": "session-heartbeat", "message": "Codex 会话活跃 · 最近命令:codex · 2 项未提交变更 · 最近提交:auto-save 2026-05-14 06:16 (~5)", "files_changed": 2 + }, + { + "ts": "2026-05-14T06:22:32+08:00", + "type": "commit", + "message": "auto-save 2026-05-14 06:22 (~2)", + "hash": "2d36ada", + "files_changed": 2 + }, + { + "ts": "2026-05-13T22:23:14Z", + "type": "session-heartbeat", + "message": "Claude 会话活跃 · 最近命令:claude · 1 项未提交变更 · 最近提交:auto-save 2026-05-14 06:22 (~2)", + "files_changed": 1 } ] } diff --git a/api/main.py b/api/main.py index 6fcd747..6bfccac 100644 --- a/api/main.py +++ b/api/main.py @@ -448,7 +448,7 @@ def run(cmd: list[str], cwd: Path | None = None) -> str: # ---- 启发式选帧工具 ---- import imagehash import numpy as np -from PIL import Image, ImageEnhance, ImageFilter, ImageOps +from PIL import Image, ImageChops, ImageEnhance, ImageFilter, ImageOps def _sharpness_from_gray(g: np.ndarray) -> float: @@ -592,6 +592,7 @@ def _normalize_asset_image( size: AssetSize, background: AssetBackground = "white", square: bool = False, + fill_subject: bool = False, ) -> tuple[int, int]: import io as _io target_w, target_h = _asset_target_size(source_path, size, square=square) @@ -599,7 +600,25 @@ def _normalize_asset_image( out_path.parent.mkdir(parents=True, exist_ok=True) with Image.open(_io.BytesIO(img_bytes)) as raw: img = raw.convert("RGB") - img.thumbnail((target_w, target_h), Image.Resampling.LANCZOS) + if fill_subject: + diff = ImageChops.difference(img, Image.new("RGB", img.size, bg)) + mask = diff.convert("L").point(lambda px: 255 if px > 18 else 0) + bbox = mask.getbbox() + if bbox: + left, top, right, bottom = bbox + pad_x = round((right - left) * 0.06) + pad_y = round((bottom - top) * 0.06) + img = img.crop(( + max(0, left - pad_x), + max(0, top - pad_y), + min(img.width, right + pad_x), + min(img.height, bottom + pad_y), + )) + max_w = max(1, round(target_w * 0.92)) + max_h = max(1, round(target_h * 0.94)) + img.thumbnail((max_w, max_h), Image.Resampling.LANCZOS) + else: + img.thumbnail((target_w, target_h), Image.Resampling.LANCZOS) canvas = Image.new("RGB", (target_w, target_h), bg) canvas.paste(img, ((target_w - img.width) // 2, (target_h - img.height) // 2)) canvas.save(out_path, "JPEG", quality=95) @@ -699,6 +718,8 @@ SUBJECT_VIEW_LABELS: dict[str, str] = { "back": "背面", "left": "左侧", "right": "右侧", + "three_quarter_left": "左前 45°", + "three_quarter_right": "右前 45°", "side": "侧面", "side_walk": "侧面走路", "top": "顶部视角", @@ -727,10 +748,10 @@ def _subject_view_labels(kind: SubjectKind, requested: list[str] | None = None) return [ ("front", "正面站立"), ("back", "背面站立"), - ("side", "侧面站立"), - ("side_walk", "侧面走路"), - ("expression_neutral", "中性表情"), - ("expression_relaxed", "放松表情"), + ("left", "左侧站立"), + ("right", "右侧站立"), + ("three_quarter_left", "左前 45° 站立"), + ("three_quarter_right", "右前 45° 站立"), ] return [ ("front", "正面"), diff --git a/web/components/lightbox.tsx b/web/components/lightbox.tsx index 087944a..cfe44cd 100644 --- a/web/components/lightbox.tsx +++ b/web/components/lightbox.tsx @@ -36,15 +36,10 @@ const OBJECT_VIEW_OPTIONS = [ const LIVING_VIEW_OPTIONS = [ ["front", "正面"], ["back", "背面"], - ["side", "侧面"], - ["side_walk", "走路"], - ["expression_happy", "喜"], - ["expression_angry", "怒"], - ["expression_sad", "哀"], - ["expression_relaxed", "乐/放松"], - ["action_sit", "坐"], - ["action_hold", "手持"], - ["action_use", "使用"], + ["left", "左侧"], + ["right", "右侧"], + ["three_quarter_left", "左前 45°"], + ["three_quarter_right", "右前 45°"], ] type LightboxTab = "clean" | "scene" | "subject" | "review" @@ -266,7 +261,7 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o views, }) onJobUpdate?.(updated) - toast.success(`统一主体资产包已生成 · ${views.length} 张 · ${subjectReferenceFrameIndices.length} 帧参考`) + toast.success(`统一主体重绘完成 · ${views.length} 张 · ${subjectReferenceFrameIndices.length} 帧参考`) } catch (e) { toast.error("主体资产包生成失败:" + (e instanceof Error ? e.message : String(e))) } finally { @@ -924,7 +919,7 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o → {subjectReferenceLabel}