Commit
·
fed3109
1
Parent(s):
77a58be
Refactor code structure for improved readability and maintainability
Browse files- app.py +7 -3
- static/pose_editor.js +1275 -61
app.py
CHANGED
|
@@ -68,7 +68,7 @@ def main():
|
|
| 68 |
# ポーズ描画キャンバス
|
| 69 |
pose_canvas = gr.HTML(
|
| 70 |
elem_id="pose_canvas_container",
|
| 71 |
-
value='<canvas id="pose_canvas" width="640" height="640" style="border: 1px solid #ccc;
|
| 72 |
)
|
| 73 |
|
| 74 |
# キャンバス設定(超コンパクトなグループ)
|
|
@@ -180,8 +180,12 @@ def main():
|
|
| 180 |
|
| 181 |
def on_display_settings_change(draw_hand, draw_face, edit_mode):
|
| 182 |
"""表示設定変更時"""
|
| 183 |
-
# JavaScript
|
| 184 |
-
js_code = f"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
return gr.update(value=js_code)
|
| 186 |
|
| 187 |
def load_template_pose(template_name):
|
|
|
|
| 68 |
# ポーズ描画キャンバス
|
| 69 |
pose_canvas = gr.HTML(
|
| 70 |
elem_id="pose_canvas_container",
|
| 71 |
+
value='<canvas id="pose_canvas" width="640" height="640" style="border: 1px solid #ccc;"></canvas>'
|
| 72 |
)
|
| 73 |
|
| 74 |
# キャンバス設定(超コンパクトなグループ)
|
|
|
|
| 180 |
|
| 181 |
def on_display_settings_change(draw_hand, draw_face, edit_mode):
|
| 182 |
"""表示設定変更時"""
|
| 183 |
+
# 🔧 JavaScript設定更新機能を呼び出し
|
| 184 |
+
js_code = f"""
|
| 185 |
+
if (window.updateDisplaySettings) {{
|
| 186 |
+
window.updateDisplaySettings({str(draw_hand).lower()}, {str(draw_face).lower()}, '{edit_mode}');
|
| 187 |
+
}}
|
| 188 |
+
"""
|
| 189 |
return gr.update(value=js_code)
|
| 190 |
|
| 191 |
def load_template_pose(template_name):
|
static/pose_editor.js
CHANGED
|
@@ -5,7 +5,18 @@ window.poseEditorGlobals = {
|
|
| 5 |
canvas: null,
|
| 6 |
ctx: null,
|
| 7 |
poseData: null, // 追加!
|
| 8 |
-
isUpdating: false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
};
|
| 10 |
|
| 11 |
let canvas = null;
|
|
@@ -83,6 +94,9 @@ function initializePoseEditor() {
|
|
| 83 |
// 初期描画
|
| 84 |
clearCanvas();
|
| 85 |
|
|
|
|
|
|
|
|
|
|
| 86 |
isInitialized = true;
|
| 87 |
debugLog("Canvas initialized successfully");
|
| 88 |
notifyCanvasStateChange('initialized');
|
|
@@ -227,7 +241,7 @@ function findNearestKeypoint(mouseX, mouseY, maxDistance = 20) {
|
|
| 227 |
return nearestIndex; // 🔧 refs互換:インデックス数値を返す
|
| 228 |
}
|
| 229 |
|
| 230 |
-
// マウスダウン処理(refs
|
| 231 |
function handleMouseDown(event) {
|
| 232 |
debugLog(`handleMouseDown called: ${event.type}`);
|
| 233 |
|
|
@@ -239,78 +253,241 @@ function handleMouseDown(event) {
|
|
| 239 |
const mousePos = getMousePos(event);
|
| 240 |
debugLog(`Mouse position: (${mousePos.x}, ${mousePos.y})`);
|
| 241 |
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 266 |
|
| 267 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
}
|
|
|
|
| 269 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 270 |
|
| 271 |
-
|
|
|
|
| 272 |
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
}
|
| 278 |
}
|
| 279 |
|
| 280 |
-
// マウス移動処理(refs
|
| 281 |
function handleMouseMove(event) {
|
| 282 |
if (!isCanvasReady()) return;
|
| 283 |
|
| 284 |
const mousePos = getMousePos(event);
|
| 285 |
|
| 286 |
-
if (isDragging
|
| 287 |
-
// 🔧
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
|
| 293 |
-
//
|
| 294 |
-
|
|
|
|
|
|
|
|
|
|
| 295 |
|
| 296 |
-
//
|
| 297 |
-
|
| 298 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
|
| 300 |
} else {
|
| 301 |
-
//
|
| 302 |
-
|
| 303 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 304 |
}
|
| 305 |
}
|
| 306 |
|
| 307 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 308 |
function handleMouseUp(event) {
|
| 309 |
if (isDragging) {
|
| 310 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 311 |
|
| 312 |
isDragging = false;
|
| 313 |
-
draggedKeypoint = -1;
|
| 314 |
canvas.style.cursor = 'default';
|
| 315 |
|
| 316 |
// Gradioに更新データを送信
|
|
@@ -398,32 +575,85 @@ function isCanvasReady() {
|
|
| 398 |
return ready;
|
| 399 |
}
|
| 400 |
|
| 401 |
-
//
|
| 402 |
function drawPose(poseData, enableHands = true, enableFace = true, highlightIndex = -1) {
|
| 403 |
if (!isCanvasReady() || !poseData) return;
|
| 404 |
|
|
|
|
|
|
|
|
|
|
| 405 |
const canvas = window.poseEditorGlobals.canvas;
|
| 406 |
const ctx = window.poseEditorGlobals.ctx;
|
| 407 |
|
| 408 |
// キャンバスクリア
|
| 409 |
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
| 410 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 411 |
// 📐 解像度情報の取得(手と顔描画のため)
|
| 412 |
-
const originalRes =
|
| 413 |
const scaleX = canvas.width / originalRes[0];
|
| 414 |
const scaleY = canvas.height / originalRes[1];
|
| 415 |
|
| 416 |
-
|
| 417 |
-
drawBody(poseData, highlightIndex);
|
| 418 |
|
| 419 |
-
//
|
| 420 |
-
|
| 421 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 422 |
}
|
| 423 |
|
| 424 |
-
//
|
| 425 |
-
if (enableFace &&
|
| 426 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 427 |
}
|
| 428 |
}
|
| 429 |
|
|
@@ -812,9 +1042,13 @@ window.gradioCanvasUpdate = function(pose_json_str) {
|
|
| 812 |
const ctx = window.poseEditorGlobals.ctx;
|
| 813 |
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
| 814 |
|
| 815 |
-
//
|
| 816 |
if (poseData && Object.keys(poseData).length > 0) {
|
| 817 |
-
drawPose(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 818 |
}
|
| 819 |
|
| 820 |
} catch (error) {
|
|
@@ -848,6 +1082,24 @@ window.getPoseData = function() {
|
|
| 848 |
return poseData;
|
| 849 |
};
|
| 850 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 851 |
// Gradioトースト通知のトリガー
|
| 852 |
window.showToast = function(type, message) {
|
| 853 |
debugLog(`Showing toast: ${type} - ${message}`);
|
|
@@ -918,6 +1170,968 @@ function safeCanvasOperation(operation) {
|
|
| 918 |
}
|
| 919 |
|
| 920 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 921 |
// 🎨 推定接続の描画(少ないキーポイント用の補間機能)
|
| 922 |
function drawEstimatedConnections(candidates, originalRes, scaleX, scaleY) {
|
| 923 |
const ctx = window.poseEditorGlobals.ctx;
|
|
|
|
| 5 |
canvas: null,
|
| 6 |
ctx: null,
|
| 7 |
poseData: null, // 追加!
|
| 8 |
+
isUpdating: false,
|
| 9 |
+
// 🔧 表示・編集設定
|
| 10 |
+
enableHands: true,
|
| 11 |
+
enableFace: true,
|
| 12 |
+
editMode: "簡易モード", // "簡易モード" or "詳細モード"
|
| 13 |
+
// 🔧 矩形編集状態(refs互換)
|
| 14 |
+
rectEditMode: null, // 'leftHand', 'rightHand', 'face', null
|
| 15 |
+
rectEditModeActive: false, // 矩形編集モード状態
|
| 16 |
+
currentRects: { leftHand: null, rightHand: null, face: null },
|
| 17 |
+
draggedRectControl: null, // ドラッグ中のコントロールポイント
|
| 18 |
+
draggedRect: null, // ドラッグ中の矩形
|
| 19 |
+
dragStartPos: { x: 0, y: 0 }
|
| 20 |
};
|
| 21 |
|
| 22 |
let canvas = null;
|
|
|
|
| 94 |
// 初期描画
|
| 95 |
clearCanvas();
|
| 96 |
|
| 97 |
+
// 🔧 デフォルトカーソル設定
|
| 98 |
+
canvas.style.cursor = 'default';
|
| 99 |
+
|
| 100 |
isInitialized = true;
|
| 101 |
debugLog("Canvas initialized successfully");
|
| 102 |
notifyCanvasStateChange('initialized');
|
|
|
|
| 241 |
return nearestIndex; // 🔧 refs互換:インデックス数値を返す
|
| 242 |
}
|
| 243 |
|
| 244 |
+
// マウスダウン処理(refs互換 + 矩形編集対応)
|
| 245 |
function handleMouseDown(event) {
|
| 246 |
debugLog(`handleMouseDown called: ${event.type}`);
|
| 247 |
|
|
|
|
| 253 |
const mousePos = getMousePos(event);
|
| 254 |
debugLog(`Mouse position: (${mousePos.x}, ${mousePos.y})`);
|
| 255 |
|
| 256 |
+
// 🔧 簡易モードでの矩形編集優先処理
|
| 257 |
+
if (window.poseEditorGlobals.editMode === "簡易モード") {
|
| 258 |
+
|
| 259 |
+
// 矩形編集モード中の処理
|
| 260 |
+
if (window.poseEditorGlobals.rectEditModeActive) {
|
| 261 |
+
const controlPoint = findNearestRectControlPoint(mousePos.x, mousePos.y);
|
| 262 |
+
debugLog(`Control point search result: ${controlPoint ? JSON.stringify(controlPoint) : 'null'}`);
|
| 263 |
+
|
| 264 |
+
if (controlPoint) {
|
| 265 |
+
// 🔧 元の矩形座標を保存(重要!)
|
| 266 |
+
const rectType = window.poseEditorGlobals.rectEditMode;
|
| 267 |
+
const currentRect = window.poseEditorGlobals.currentRects[rectType];
|
| 268 |
+
if (currentRect) {
|
| 269 |
+
window.poseEditorGlobals.originalRect = { ...currentRect };
|
| 270 |
+
debugLog(`🔧 Original rect saved for resize: ${JSON.stringify(window.poseEditorGlobals.originalRect)}`);
|
| 271 |
+
|
| 272 |
+
// 🔧 キーポイントの元座標も保存(refs互換)
|
| 273 |
+
const currentPoseData = window.poseEditorGlobals.poseData || poseData;
|
| 274 |
+
if (currentPoseData) {
|
| 275 |
+
window.poseEditorGlobals.originalKeypoints = JSON.parse(JSON.stringify(currentPoseData));
|
| 276 |
+
debugLog(`🔧 Original keypoints saved for ${rectType}`);
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
// 🔧 rectEditInfo初期化(キーポイントインデックス情報)
|
| 280 |
+
if (!window.poseEditorGlobals.rectEditInfo) {
|
| 281 |
+
window.poseEditorGlobals.rectEditInfo = {};
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
// 矩形タイプごとのキーポイントインデックスを設定
|
| 285 |
+
window.poseEditorGlobals.rectEditInfo[rectType] = {
|
| 286 |
+
originalRect: { ...currentRect },
|
| 287 |
+
keypointIndices: getRectKeypointIndices(rectType)
|
| 288 |
+
};
|
| 289 |
+
|
| 290 |
+
debugLog(`🔧 rectEditInfo initialized for ${rectType}:`, window.poseEditorGlobals.rectEditInfo[rectType]);
|
| 291 |
+
}
|
| 292 |
|
| 293 |
+
// コントロールポイントドラッグ(リサイズ)
|
| 294 |
+
window.poseEditorGlobals.draggedRectControl = controlPoint;
|
| 295 |
+
window.poseEditorGlobals.dragStartPos = { x: mousePos.x, y: mousePos.y };
|
| 296 |
+
isDragging = true;
|
| 297 |
+
debugLog(`Control point drag started: ${controlPoint.type} ${controlPoint.position}`);
|
| 298 |
+
return;
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
// 矩形内ドラッグ(移動)
|
| 302 |
+
const rectType = findRectContaining(mousePos.x, mousePos.y);
|
| 303 |
+
if (rectType === window.poseEditorGlobals.rectEditMode) {
|
| 304 |
+
// 🔧 矩形移動時もrectEditInfo初期化
|
| 305 |
+
const currentRect = window.poseEditorGlobals.currentRects[rectType];
|
| 306 |
+
if (currentRect) {
|
| 307 |
+
if (!window.poseEditorGlobals.rectEditInfo) {
|
| 308 |
+
window.poseEditorGlobals.rectEditInfo = {};
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
window.poseEditorGlobals.rectEditInfo[rectType] = {
|
| 312 |
+
originalRect: { ...currentRect },
|
| 313 |
+
keypointIndices: getRectKeypointIndices(rectType)
|
| 314 |
+
};
|
| 315 |
+
|
| 316 |
+
debugLog(`🔧 rectEditInfo initialized for move: ${rectType}`);
|
| 317 |
+
}
|
| 318 |
|
| 319 |
+
window.poseEditorGlobals.draggedRect = rectType;
|
| 320 |
+
window.poseEditorGlobals.dragStartPos = { x: mousePos.x, y: mousePos.y };
|
| 321 |
+
isDragging = true;
|
| 322 |
+
debugLog(`Rectangle move drag started: ${rectType}`);
|
| 323 |
+
return;
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
// 他の矩形クリック → 切り替え
|
| 327 |
+
if (rectType && rectType !== window.poseEditorGlobals.rectEditMode) {
|
| 328 |
+
window.poseEditorGlobals.rectEditMode = rectType;
|
| 329 |
+
debugLog(`Switched to rectangle edit mode: ${rectType}`);
|
| 330 |
+
// 再描画で新しい矩形のコントロールポイントを表示
|
| 331 |
+
const currentPoseData = window.poseEditorGlobals.poseData || poseData;
|
| 332 |
+
if (currentPoseData) {
|
| 333 |
+
drawPose(currentPoseData, window.poseEditorGlobals.enableHands, window.poseEditorGlobals.enableFace);
|
| 334 |
+
}
|
| 335 |
+
return;
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
// 矩形外クリック → 編集モード終了
|
| 339 |
+
debugLog("Clicked outside rectangles, exiting edit mode");
|
| 340 |
+
window.poseEditorGlobals.rectEditModeActive = false;
|
| 341 |
+
window.poseEditorGlobals.rectEditMode = null;
|
| 342 |
+
|
| 343 |
+
// 再描画でコントロールポイントを非表示
|
| 344 |
+
const currentPoseData = window.poseEditorGlobals.poseData || poseData;
|
| 345 |
+
if (currentPoseData) {
|
| 346 |
+
drawPose(currentPoseData, window.poseEditorGlobals.enableHands, window.poseEditorGlobals.enableFace);
|
| 347 |
+
}
|
| 348 |
+
return;
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
// 矩形編集モードでない場合の矩形クリック → 編集モード開始
|
| 352 |
+
const rectType = findRectContaining(mousePos.x, mousePos.y);
|
| 353 |
+
if (rectType) {
|
| 354 |
+
window.poseEditorGlobals.rectEditModeActive = true;
|
| 355 |
+
window.poseEditorGlobals.rectEditMode = rectType;
|
| 356 |
+
debugLog(`Entered rectangle edit mode: ${rectType}`);
|
| 357 |
+
|
| 358 |
+
// 🔧 rectEditInfo全体を初期化(全矩形タイプ対応)
|
| 359 |
+
initializeRectEditInfo();
|
| 360 |
+
|
| 361 |
+
// 再描画でコントロールポイントを表示
|
| 362 |
+
const currentPoseData = window.poseEditorGlobals.poseData || poseData;
|
| 363 |
+
if (currentPoseData) {
|
| 364 |
+
drawPose(currentPoseData, window.poseEditorGlobals.enableHands, window.poseEditorGlobals.enableFace);
|
| 365 |
}
|
| 366 |
+
return;
|
| 367 |
}
|
| 368 |
+
}
|
| 369 |
+
|
| 370 |
+
// 🔧 詳細モードまたは矩形編集モードでない場合:通常のキーポイント編集
|
| 371 |
+
if (window.poseEditorGlobals.editMode === "詳細モード" || !window.poseEditorGlobals.rectEditModeActive) {
|
| 372 |
|
| 373 |
+
const keypointIndex = findNearestKeypoint(mousePos.x, mousePos.y);
|
| 374 |
+
debugLog(`Found keypoint index: ${keypointIndex}`);
|
| 375 |
|
| 376 |
+
if (keypointIndex >= 0) {
|
| 377 |
+
isDragging = true;
|
| 378 |
+
draggedKeypoint = keypointIndex;
|
| 379 |
+
|
| 380 |
+
// 🔧 refs互換:ドラッグオフセット計算(キーポイントの正確な位置からのオフセット)
|
| 381 |
+
const currentPoseData = window.poseEditorGlobals.poseData || poseData;
|
| 382 |
+
if (currentPoseData && currentPoseData.bodies && currentPoseData.bodies.candidate) {
|
| 383 |
+
const candidates = currentPoseData.bodies.candidate;
|
| 384 |
+
const originalRes = currentPoseData.resolution || [512, 512];
|
| 385 |
+
const scaleX = canvas.width / originalRes[0];
|
| 386 |
+
const scaleY = canvas.height / originalRes[1];
|
| 387 |
+
|
| 388 |
+
const point = candidates[keypointIndex];
|
| 389 |
+
if (point) {
|
| 390 |
+
const keypointX = point[0] * scaleX;
|
| 391 |
+
const keypointY = point[1] * scaleY;
|
| 392 |
+
|
| 393 |
+
dragOffset = {
|
| 394 |
+
x: mousePos.x - keypointX,
|
| 395 |
+
y: mousePos.y - keypointY
|
| 396 |
+
};
|
| 397 |
+
|
| 398 |
+
debugLog(`Drag offset calculated: (${dragOffset.x.toFixed(1)}, ${dragOffset.y.toFixed(1)})`);
|
| 399 |
+
}
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
debugLog(`Drag started: keypoint ${keypointIndex} at (${mousePos.x}, ${mousePos.y})`);
|
| 403 |
+
|
| 404 |
+
// カーソルを変更
|
| 405 |
+
canvas.style.cursor = 'grabbing';
|
| 406 |
+
} else {
|
| 407 |
+
debugLog("No keypoint found near mouse position");
|
| 408 |
+
}
|
| 409 |
}
|
| 410 |
}
|
| 411 |
|
| 412 |
+
// マウス移動処理(refs互換 + 矩形編集対応)
|
| 413 |
function handleMouseMove(event) {
|
| 414 |
if (!isCanvasReady()) return;
|
| 415 |
|
| 416 |
const mousePos = getMousePos(event);
|
| 417 |
|
| 418 |
+
if (isDragging) {
|
| 419 |
+
// 🔧 矩形コントロールポイントドラッグ処理
|
| 420 |
+
if (window.poseEditorGlobals.draggedRectControl) {
|
| 421 |
+
updateRectControlDrag(mousePos.x, mousePos.y);
|
| 422 |
+
return;
|
| 423 |
+
}
|
| 424 |
|
| 425 |
+
// 🔧 矩形移動ドラッグ処理
|
| 426 |
+
if (window.poseEditorGlobals.draggedRect) {
|
| 427 |
+
updateRectMoveDrag(mousePos.x, mousePos.y);
|
| 428 |
+
return;
|
| 429 |
+
}
|
| 430 |
|
| 431 |
+
// 🔧 通常のキーポイントドラッグ処理
|
| 432 |
+
if (draggedKeypoint >= 0) {
|
| 433 |
+
// 🔧 refs互換:オフセット考慮の新座標計算
|
| 434 |
+
const newX = mousePos.x - dragOffset.x;
|
| 435 |
+
const newY = mousePos.y - dragOffset.y;
|
| 436 |
+
|
| 437 |
+
// debugLog(`Mouse move: raw(${mousePos.x}, ${mousePos.y}), offset(${dragOffset.x.toFixed(1)}, ${dragOffset.y.toFixed(1)}), new(${newX.toFixed(1)}, ${newY.toFixed(1)})`);
|
| 438 |
+
|
| 439 |
+
// ドラッグ中の座標更新(オフセット適用済み座標で)
|
| 440 |
+
updateKeypointPosition(draggedKeypoint, newX, newY);
|
| 441 |
+
|
| 442 |
+
// リアルタイム再描画(ハイライト付き)- グローバルposeDataを使用
|
| 443 |
+
const currentPoseData = window.poseEditorGlobals.poseData || poseData;
|
| 444 |
+
drawPose(currentPoseData, window.poseEditorGlobals.enableHands, window.poseEditorGlobals.enableFace, draggedKeypoint);
|
| 445 |
+
}
|
| 446 |
|
| 447 |
} else {
|
| 448 |
+
// 🔧 ホバー時のカーソル変更(編集モードに応じて)
|
| 449 |
+
if (window.poseEditorGlobals.editMode === "簡易モード" && window.poseEditorGlobals.rectEditModeActive) {
|
| 450 |
+
// 矩形編集モード中:コントロールポイントまたは矩形内でカーソル変更
|
| 451 |
+
const controlPoint = findNearestRectControlPoint(mousePos.x, mousePos.y);
|
| 452 |
+
if (controlPoint) {
|
| 453 |
+
canvas.style.cursor = getControlPointCursor(controlPoint.position);
|
| 454 |
+
} else {
|
| 455 |
+
const rectType = findRectContaining(mousePos.x, mousePos.y);
|
| 456 |
+
canvas.style.cursor = rectType === window.poseEditorGlobals.rectEditMode ? 'move' : 'default';
|
| 457 |
+
}
|
| 458 |
+
} else {
|
| 459 |
+
// 通常モード:キーポイント近くでカーソル変更
|
| 460 |
+
const keypointIndex = findNearestKeypoint(mousePos.x, mousePos.y);
|
| 461 |
+
canvas.style.cursor = keypointIndex >= 0 ? 'grab' : 'default';
|
| 462 |
+
}
|
| 463 |
}
|
| 464 |
}
|
| 465 |
|
| 466 |
+
// 🔧 コントロールポイント位置に応じたカーソル取得
|
| 467 |
+
function getControlPointCursor(position) {
|
| 468 |
+
const cursors = {
|
| 469 |
+
'tl': 'nw-resize', 'tr': 'ne-resize', 'bl': 'sw-resize', 'br': 'se-resize',
|
| 470 |
+
't': 'n-resize', 'r': 'e-resize', 'b': 's-resize', 'l': 'w-resize'
|
| 471 |
+
};
|
| 472 |
+
return cursors[position] || 'pointer';
|
| 473 |
+
}
|
| 474 |
+
|
| 475 |
+
// マウスアップ処理(refs互換 + 矩形編集対応)
|
| 476 |
function handleMouseUp(event) {
|
| 477 |
if (isDragging) {
|
| 478 |
+
if (window.poseEditorGlobals.draggedRectControl) {
|
| 479 |
+
debugLog(`Control point drag ended: ${window.poseEditorGlobals.draggedRectControl.position}`);
|
| 480 |
+
window.poseEditorGlobals.draggedRectControl = null;
|
| 481 |
+
window.poseEditorGlobals.originalRect = null; // 🔧 元の矩形情報をクリア
|
| 482 |
+
} else if (window.poseEditorGlobals.draggedRect) {
|
| 483 |
+
debugLog(`Rectangle move drag ended: ${window.poseEditorGlobals.draggedRect}`);
|
| 484 |
+
window.poseEditorGlobals.draggedRect = null;
|
| 485 |
+
} else if (draggedKeypoint >= 0) {
|
| 486 |
+
debugLog(`Keypoint drag ended: ${draggedKeypoint}`);
|
| 487 |
+
draggedKeypoint = -1;
|
| 488 |
+
}
|
| 489 |
|
| 490 |
isDragging = false;
|
|
|
|
| 491 |
canvas.style.cursor = 'default';
|
| 492 |
|
| 493 |
// Gradioに更新データを送信
|
|
|
|
| 575 |
return ready;
|
| 576 |
}
|
| 577 |
|
| 578 |
+
// ポーズ全体の描画(ハイライト対応・設定制御)
|
| 579 |
function drawPose(poseData, enableHands = true, enableFace = true, highlightIndex = -1) {
|
| 580 |
if (!isCanvasReady() || !poseData) return;
|
| 581 |
|
| 582 |
+
// 🚀 refs互換:常に最新の編集済みデータを使用
|
| 583 |
+
const currentPoseData = window.poseEditorGlobals.poseData || poseData;
|
| 584 |
+
|
| 585 |
const canvas = window.poseEditorGlobals.canvas;
|
| 586 |
const ctx = window.poseEditorGlobals.ctx;
|
| 587 |
|
| 588 |
// キャンバスクリア
|
| 589 |
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
| 590 |
|
| 591 |
+
// 🔧 矩形編集モード中でない場合のみ矩形を再計算
|
| 592 |
+
if (window.poseEditorGlobals.editMode === "簡易モード" && !window.poseEditorGlobals.rectEditModeActive) {
|
| 593 |
+
window.poseEditorGlobals.currentRects = { leftHand: null, rightHand: null, face: null };
|
| 594 |
+
}
|
| 595 |
+
|
| 596 |
// 📐 解像度情報の取得(手と顔描画のため)
|
| 597 |
+
const originalRes = currentPoseData.resolution || [512, 512];
|
| 598 |
const scaleX = canvas.width / originalRes[0];
|
| 599 |
const scaleY = canvas.height / originalRes[1];
|
| 600 |
|
| 601 |
+
debugLog(`Drawing pose: hands=${enableHands}, face=${enableFace}, highlight=${highlightIndex}`);
|
|
|
|
| 602 |
|
| 603 |
+
// ボディの描画(ハイライト対応)
|
| 604 |
+
drawBody(currentPoseData, highlightIndex);
|
| 605 |
+
|
| 606 |
+
// 手の描画(設定制御・座標変換パラメータ付き)
|
| 607 |
+
if (enableHands && currentPoseData.hands) {
|
| 608 |
+
debugLog(`Drawing hands: ${currentPoseData.hands.length} hand(s)`);
|
| 609 |
+
// 🚀 refs互換:編集済みのhand_keypoints_2dを優先使用
|
| 610 |
+
const handsDataForDrawing = currentPoseData.people && currentPoseData.people[0]
|
| 611 |
+
? [
|
| 612 |
+
currentPoseData.people[0].hand_left_keypoints_2d || (currentPoseData.hands && currentPoseData.hands[0]),
|
| 613 |
+
currentPoseData.people[0].hand_right_keypoints_2d || (currentPoseData.hands && currentPoseData.hands[1])
|
| 614 |
+
]
|
| 615 |
+
: currentPoseData.hands;
|
| 616 |
+
drawHands(handsDataForDrawing, originalRes, scaleX, scaleY);
|
| 617 |
+
|
| 618 |
+
// 🔧 簡易モード:手の矩形描画(編集モード中は再計算しない)
|
| 619 |
+
if (window.poseEditorGlobals.editMode === "簡易モード") {
|
| 620 |
+
if (!window.poseEditorGlobals.rectEditModeActive) {
|
| 621 |
+
// 通常時:矩形を新規計算して描画
|
| 622 |
+
drawHandRectangles(currentPoseData.hands, originalRes, scaleX, scaleY);
|
| 623 |
+
} else {
|
| 624 |
+
// 編集モード中:既存の矩形を描画(再計算しない)
|
| 625 |
+
drawExistingRectangles(['leftHand', 'rightHand']);
|
| 626 |
+
}
|
| 627 |
+
}
|
| 628 |
+
} else if (!enableHands) {
|
| 629 |
+
debugLog("Hand drawing disabled by setting");
|
| 630 |
+
} else {
|
| 631 |
+
debugLog("No hand data available");
|
| 632 |
}
|
| 633 |
|
| 634 |
+
// 顔の描画(設定制御・座標変換パラメータ付き)
|
| 635 |
+
if (enableFace && currentPoseData.faces) {
|
| 636 |
+
debugLog(`Drawing faces: ${currentPoseData.faces.length} face(s)`);
|
| 637 |
+
// 🚀 refs互換:編集済みのface_keypoints_2dを優先使用
|
| 638 |
+
const facesDataForDrawing = currentPoseData.people && currentPoseData.people[0] && currentPoseData.people[0].face_keypoints_2d
|
| 639 |
+
? [currentPoseData.people[0].face_keypoints_2d]
|
| 640 |
+
: currentPoseData.faces;
|
| 641 |
+
drawFaces(facesDataForDrawing, originalRes, scaleX, scaleY);
|
| 642 |
+
|
| 643 |
+
// 🔧 簡易モード:顔の矩形描画(編集モード中は再計算しない)
|
| 644 |
+
if (window.poseEditorGlobals.editMode === "簡易モード") {
|
| 645 |
+
if (!window.poseEditorGlobals.rectEditModeActive) {
|
| 646 |
+
// 通常時:矩形を新規計算して描画
|
| 647 |
+
drawFaceRectangles(currentPoseData.faces, originalRes, scaleX, scaleY);
|
| 648 |
+
} else {
|
| 649 |
+
// 編集モード中:既存の矩形を描画(再計算しない)
|
| 650 |
+
drawExistingRectangles(['face']);
|
| 651 |
+
}
|
| 652 |
+
}
|
| 653 |
+
} else if (!enableFace) {
|
| 654 |
+
debugLog("Face drawing disabled by setting");
|
| 655 |
+
} else {
|
| 656 |
+
debugLog("No face data available");
|
| 657 |
}
|
| 658 |
}
|
| 659 |
|
|
|
|
| 1042 |
const ctx = window.poseEditorGlobals.ctx;
|
| 1043 |
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
| 1044 |
|
| 1045 |
+
// 🔧 グローバル設定で描画(手・顔表示設定を反映)
|
| 1046 |
if (poseData && Object.keys(poseData).length > 0) {
|
| 1047 |
+
drawPose(
|
| 1048 |
+
poseData,
|
| 1049 |
+
window.poseEditorGlobals.enableHands,
|
| 1050 |
+
window.poseEditorGlobals.enableFace
|
| 1051 |
+
);
|
| 1052 |
}
|
| 1053 |
|
| 1054 |
} catch (error) {
|
|
|
|
| 1082 |
return poseData;
|
| 1083 |
};
|
| 1084 |
|
| 1085 |
+
// 🔧 Gradio設定更新用(新機能)
|
| 1086 |
+
window.updateDisplaySettings = function(enableHands, enableFace, editMode) {
|
| 1087 |
+
debugLog(`Settings update: hands=${enableHands}, face=${enableFace}, mode=${editMode}`);
|
| 1088 |
+
|
| 1089 |
+
// グローバル設定を更新
|
| 1090 |
+
window.poseEditorGlobals.enableHands = enableHands;
|
| 1091 |
+
window.poseEditorGlobals.enableFace = enableFace;
|
| 1092 |
+
window.poseEditorGlobals.editMode = editMode;
|
| 1093 |
+
|
| 1094 |
+
// 既存データがあれば再描画
|
| 1095 |
+
const currentPoseData = window.poseEditorGlobals.poseData || poseData;
|
| 1096 |
+
if (currentPoseData && Object.keys(currentPoseData).length > 0) {
|
| 1097 |
+
drawPose(currentPoseData, enableHands, enableFace);
|
| 1098 |
+
}
|
| 1099 |
+
|
| 1100 |
+
debugLog(`Settings updated successfully - Mode: ${editMode}`);
|
| 1101 |
+
};
|
| 1102 |
+
|
| 1103 |
// Gradioトースト通知のトリガー
|
| 1104 |
window.showToast = function(type, message) {
|
| 1105 |
debugLog(`Showing toast: ${type} - ${message}`);
|
|
|
|
| 1170 |
}
|
| 1171 |
|
| 1172 |
|
| 1173 |
+
// 🔧 簡易モード:手の矩形描画(refs互換)
|
| 1174 |
+
function drawHandRectangles(handsData, originalRes, scaleX, scaleY) {
|
| 1175 |
+
if (!handsData || handsData.length === 0) return;
|
| 1176 |
+
|
| 1177 |
+
const ctx = window.poseEditorGlobals.ctx;
|
| 1178 |
+
|
| 1179 |
+
// 矩形の色定義(refs互換)
|
| 1180 |
+
const HAND_RECT_COLORS = ['rgb(255,165,0)', 'rgb(255,69,0)']; // オレンジ系
|
| 1181 |
+
const HAND_TYPES = ['leftHand', 'rightHand'];
|
| 1182 |
+
|
| 1183 |
+
handsData.forEach((hand, handIndex) => {
|
| 1184 |
+
if (hand && hand.length > 0) {
|
| 1185 |
+
const rect = calculateHandRect(hand, originalRes, scaleX, scaleY);
|
| 1186 |
+
if (rect) {
|
| 1187 |
+
const handType = HAND_TYPES[handIndex] || `hand_${handIndex}`;
|
| 1188 |
+
|
| 1189 |
+
// 🔧 グローバルに矩形情報保存
|
| 1190 |
+
window.poseEditorGlobals.currentRects[handType] = rect;
|
| 1191 |
+
|
| 1192 |
+
drawEditableRect(ctx, rect, HAND_RECT_COLORS[handIndex % 2], handType);
|
| 1193 |
+
debugLog(`Hand ${handIndex} (${handType}) rectangle: ${JSON.stringify(rect)}`);
|
| 1194 |
+
}
|
| 1195 |
+
}
|
| 1196 |
+
});
|
| 1197 |
+
}
|
| 1198 |
+
|
| 1199 |
+
// 🔧 簡易モード:顔の矩形描画(refs互換)
|
| 1200 |
+
function drawFaceRectangles(facesData, originalRes, scaleX, scaleY) {
|
| 1201 |
+
if (!facesData || facesData.length === 0) return;
|
| 1202 |
+
|
| 1203 |
+
const ctx = window.poseEditorGlobals.ctx;
|
| 1204 |
+
|
| 1205 |
+
// 矩形の色定義(refs互換)
|
| 1206 |
+
const FACE_RECT_COLOR = 'rgb(34,139,34)'; // 緑
|
| 1207 |
+
|
| 1208 |
+
const face = facesData[0]; // 最初の顔のみ(編集済みデータ)
|
| 1209 |
+
if (face && face.length > 0) {
|
| 1210 |
+
const rect = calculateFaceRect(face, originalRes, scaleX, scaleY);
|
| 1211 |
+
if (rect) {
|
| 1212 |
+
// 🔧 グローバルに矩形情報保存
|
| 1213 |
+
window.poseEditorGlobals.currentRects.face = rect;
|
| 1214 |
+
|
| 1215 |
+
drawEditableRect(ctx, rect, FACE_RECT_COLOR, 'face');
|
| 1216 |
+
debugLog(`Face rectangle: ${JSON.stringify(rect)}`);
|
| 1217 |
+
}
|
| 1218 |
+
}
|
| 1219 |
+
}
|
| 1220 |
+
|
| 1221 |
+
// 🔧 キーポイントから矩形を計算(refs互換)
|
| 1222 |
+
function calculateHandRect(handData, originalRes, scaleX, scaleY) {
|
| 1223 |
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
| 1224 |
+
let validPointCount = 0;
|
| 1225 |
+
|
| 1226 |
+
// 3要素ごとに処理(x, y, confidence)
|
| 1227 |
+
for (let i = 0; i < handData.length; i += 3) {
|
| 1228 |
+
const x = handData[i];
|
| 1229 |
+
const y = handData[i + 1];
|
| 1230 |
+
const confidence = handData[i + 2];
|
| 1231 |
+
|
| 1232 |
+
if (confidence > 0.3) { // refs互換の閾値
|
| 1233 |
+
const scaledX = x * scaleX;
|
| 1234 |
+
const scaledY = y * scaleY;
|
| 1235 |
+
|
| 1236 |
+
minX = Math.min(minX, scaledX);
|
| 1237 |
+
minY = Math.min(minY, scaledY);
|
| 1238 |
+
maxX = Math.max(maxX, scaledX);
|
| 1239 |
+
maxY = Math.max(maxY, scaledY);
|
| 1240 |
+
validPointCount++;
|
| 1241 |
+
}
|
| 1242 |
+
}
|
| 1243 |
+
|
| 1244 |
+
if (validPointCount < 3) return null; // 有効ポイントが少なすぎる
|
| 1245 |
+
|
| 1246 |
+
// 10px余白付きで矩形返却(refs互換)
|
| 1247 |
+
const margin = 10;
|
| 1248 |
+
return {
|
| 1249 |
+
x: minX - margin,
|
| 1250 |
+
y: minY - margin,
|
| 1251 |
+
width: (maxX - minX) + (margin * 2),
|
| 1252 |
+
height: (maxY - minY) + (margin * 2)
|
| 1253 |
+
};
|
| 1254 |
+
}
|
| 1255 |
+
|
| 1256 |
+
// 🔧 顔キーポイントから矩形を計算(refs互換)
|
| 1257 |
+
function calculateFaceRect(faceData, originalRes, scaleX, scaleY) {
|
| 1258 |
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
| 1259 |
+
let validPointCount = 0;
|
| 1260 |
+
|
| 1261 |
+
// 3要素ごとに処理(x, y, confidence)
|
| 1262 |
+
for (let i = 0; i < faceData.length; i += 3) {
|
| 1263 |
+
const x = faceData[i];
|
| 1264 |
+
const y = faceData[i + 1];
|
| 1265 |
+
const confidence = faceData[i + 2];
|
| 1266 |
+
|
| 1267 |
+
if (confidence > 0.3) { // refs互換の閾値
|
| 1268 |
+
const scaledX = x * scaleX;
|
| 1269 |
+
const scaledY = y * scaleY;
|
| 1270 |
+
|
| 1271 |
+
minX = Math.min(minX, scaledX);
|
| 1272 |
+
minY = Math.min(minY, scaledY);
|
| 1273 |
+
maxX = Math.max(maxX, scaledX);
|
| 1274 |
+
maxY = Math.max(maxY, scaledY);
|
| 1275 |
+
validPointCount++;
|
| 1276 |
+
}
|
| 1277 |
+
}
|
| 1278 |
+
|
| 1279 |
+
if (validPointCount < 10) return null; // 顔は多めのポイントが必要
|
| 1280 |
+
|
| 1281 |
+
// 15px余白付きで矩形返却(顔は少し大きめ)
|
| 1282 |
+
const margin = 15;
|
| 1283 |
+
return {
|
| 1284 |
+
x: minX - margin,
|
| 1285 |
+
y: minY - margin,
|
| 1286 |
+
width: (maxX - minX) + (margin * 2),
|
| 1287 |
+
height: (maxY - minY) + (margin * 2)
|
| 1288 |
+
};
|
| 1289 |
+
}
|
| 1290 |
+
|
| 1291 |
+
// 🔧 編集可能矩形の描画(refs互換)
|
| 1292 |
+
function drawEditableRect(ctx, rect, color, id) {
|
| 1293 |
+
if (!rect) return;
|
| 1294 |
+
|
| 1295 |
+
// 太め破線で矩形描画(refs互換)
|
| 1296 |
+
ctx.strokeStyle = color;
|
| 1297 |
+
ctx.lineWidth = 3;
|
| 1298 |
+
ctx.setLineDash([8, 8]); // 破線パターン
|
| 1299 |
+
|
| 1300 |
+
ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);
|
| 1301 |
+
|
| 1302 |
+
// 🔧 編集モード時のみコントロールポイントを表示(refs互換)
|
| 1303 |
+
if (window.poseEditorGlobals.rectEditModeActive &&
|
| 1304 |
+
window.poseEditorGlobals.rectEditMode === id) {
|
| 1305 |
+
|
| 1306 |
+
drawRectControlPoints(ctx, rect, color);
|
| 1307 |
+
} else {
|
| 1308 |
+
// 通常時は角のポイントのみ(小さめ)
|
| 1309 |
+
const controlSize = 6;
|
| 1310 |
+
ctx.fillStyle = color;
|
| 1311 |
+
ctx.setLineDash([]); // 実線に戻す
|
| 1312 |
+
|
| 1313 |
+
// 4角のコントロールポイント
|
| 1314 |
+
const corners = [
|
| 1315 |
+
{ x: rect.x, y: rect.y }, // 左上
|
| 1316 |
+
{ x: rect.x + rect.width, y: rect.y }, // 右上
|
| 1317 |
+
{ x: rect.x, y: rect.y + rect.height }, // 左下
|
| 1318 |
+
{ x: rect.x + rect.width, y: rect.y + rect.height } // 右下
|
| 1319 |
+
];
|
| 1320 |
+
|
| 1321 |
+
corners.forEach(corner => {
|
| 1322 |
+
ctx.fillRect(corner.x - controlSize/2, corner.y - controlSize/2, controlSize, controlSize);
|
| 1323 |
+
});
|
| 1324 |
+
}
|
| 1325 |
+
}
|
| 1326 |
+
|
| 1327 |
+
// 🔧 既存の矩形を描画(編集モード中用)
|
| 1328 |
+
function drawExistingRectangles(rectTypes) {
|
| 1329 |
+
const ctx = window.poseEditorGlobals.ctx;
|
| 1330 |
+
const colorMap = {
|
| 1331 |
+
'leftHand': 'rgb(255,165,0)', // オレンジ
|
| 1332 |
+
'rightHand': 'rgb(255,69,0)', // 濃いオレンジ
|
| 1333 |
+
'face': 'rgb(34,139,34)' // 緑
|
| 1334 |
+
};
|
| 1335 |
+
|
| 1336 |
+
rectTypes.forEach(rectType => {
|
| 1337 |
+
const rect = window.poseEditorGlobals.currentRects[rectType];
|
| 1338 |
+
if (rect) {
|
| 1339 |
+
const color = colorMap[rectType] || 'rgb(255,165,0)';
|
| 1340 |
+
drawEditableRect(ctx, rect, color, rectType);
|
| 1341 |
+
debugLog(`Drew existing rectangle: ${rectType}`);
|
| 1342 |
+
}
|
| 1343 |
+
});
|
| 1344 |
+
}
|
| 1345 |
+
|
| 1346 |
+
// 🔧 矩形再計算なしの描画(編集中専用)
|
| 1347 |
+
function redrawPoseWithoutRecalculation() {
|
| 1348 |
+
if (!isCanvasReady()) return;
|
| 1349 |
+
|
| 1350 |
+
const currentPoseData = window.poseEditorGlobals.poseData || poseData;
|
| 1351 |
+
if (!currentPoseData) return;
|
| 1352 |
+
|
| 1353 |
+
const canvas = window.poseEditorGlobals.canvas;
|
| 1354 |
+
const ctx = window.poseEditorGlobals.ctx;
|
| 1355 |
+
|
| 1356 |
+
// キャンバスクリア
|
| 1357 |
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
| 1358 |
+
|
| 1359 |
+
// 📐 解像度情報の取得
|
| 1360 |
+
const originalRes = currentPoseData.resolution || [512, 512];
|
| 1361 |
+
const scaleX = canvas.width / originalRes[0];
|
| 1362 |
+
const scaleY = canvas.height / originalRes[1];
|
| 1363 |
+
|
| 1364 |
+
// ボディの描画(ハイライトなし)
|
| 1365 |
+
drawBody(currentPoseData, -1);
|
| 1366 |
+
|
| 1367 |
+
// 手の描画(設定制御・座標変換パラメータ付き)
|
| 1368 |
+
if (window.poseEditorGlobals.enableHands && currentPoseData.hands) {
|
| 1369 |
+
drawHands(currentPoseData.hands, originalRes, scaleX, scaleY);
|
| 1370 |
+
}
|
| 1371 |
+
|
| 1372 |
+
// 顔の描画(設定制御・座標変換パラメータ付き)
|
| 1373 |
+
if (window.poseEditorGlobals.enableFace && currentPoseData.faces) {
|
| 1374 |
+
drawFaces(currentPoseData.faces, originalRes, scaleX, scaleY);
|
| 1375 |
+
}
|
| 1376 |
+
|
| 1377 |
+
// 🔧 既存の���形のみ描画(再計算なし)
|
| 1378 |
+
if (window.poseEditorGlobals.editMode === "簡易モード") {
|
| 1379 |
+
const allRectTypes = ['leftHand', 'rightHand', 'face'];
|
| 1380 |
+
drawExistingRectangles(allRectTypes);
|
| 1381 |
+
}
|
| 1382 |
+
|
| 1383 |
+
debugLog("Redraw completed without rectangle recalculation");
|
| 1384 |
+
}
|
| 1385 |
+
|
| 1386 |
+
// 🔧 矩形コントロールポイント描画(編集モード時)
|
| 1387 |
+
function drawRectControlPoints(ctx, rect, color) {
|
| 1388 |
+
const controlSize = 10;
|
| 1389 |
+
ctx.fillStyle = color;
|
| 1390 |
+
ctx.strokeStyle = 'white';
|
| 1391 |
+
ctx.lineWidth = 2;
|
| 1392 |
+
ctx.setLineDash([]); // 実線
|
| 1393 |
+
|
| 1394 |
+
// 8つのコントロールポイント(4角 + 4辺の中央)
|
| 1395 |
+
const controlPoints = [
|
| 1396 |
+
{ x: rect.x, y: rect.y, type: 'corner', position: 'tl' }, // 左上
|
| 1397 |
+
{ x: rect.x + rect.width, y: rect.y, type: 'corner', position: 'tr' }, // 右上
|
| 1398 |
+
{ x: rect.x, y: rect.y + rect.height, type: 'corner', position: 'bl' }, // 左下
|
| 1399 |
+
{ x: rect.x + rect.width, y: rect.y + rect.height, type: 'corner', position: 'br' }, // 右下
|
| 1400 |
+
{ x: rect.x + rect.width / 2, y: rect.y, type: 'edge', position: 't' }, // 上辺
|
| 1401 |
+
{ x: rect.x + rect.width, y: rect.y + rect.height / 2, type: 'edge', position: 'r' }, // 右辺
|
| 1402 |
+
{ x: rect.x + rect.width / 2, y: rect.y + rect.height, type: 'edge', position: 'b' }, // 下辺
|
| 1403 |
+
{ x: rect.x, y: rect.y + rect.height / 2, type: 'edge', position: 'l' } // 左辺
|
| 1404 |
+
];
|
| 1405 |
+
|
| 1406 |
+
controlPoints.forEach(point => {
|
| 1407 |
+
// 白い枠付きの四角形
|
| 1408 |
+
ctx.fillRect(point.x - controlSize/2, point.y - controlSize/2, controlSize, controlSize);
|
| 1409 |
+
ctx.strokeRect(point.x - controlSize/2, point.y - controlSize/2, controlSize, controlSize);
|
| 1410 |
+
});
|
| 1411 |
+
}
|
| 1412 |
+
|
| 1413 |
+
// 🔧 点が矩形内にあるかチェック(refs互換)
|
| 1414 |
+
function isPointInRect(x, y, rect) {
|
| 1415 |
+
if (!rect) return false;
|
| 1416 |
+
return x >= rect.x && x <= rect.x + rect.width &&
|
| 1417 |
+
y >= rect.y && y <= rect.y + rect.height;
|
| 1418 |
+
}
|
| 1419 |
+
|
| 1420 |
+
// 🔧 どの矩形にクリックが含まれてるか探す(refs互換)
|
| 1421 |
+
function findRectContaining(x, y) {
|
| 1422 |
+
const rects = window.poseEditorGlobals.currentRects;
|
| 1423 |
+
|
| 1424 |
+
if (rects.leftHand && isPointInRect(x, y, rects.leftHand)) {
|
| 1425 |
+
return 'leftHand';
|
| 1426 |
+
}
|
| 1427 |
+
if (rects.rightHand && isPointInRect(x, y, rects.rightHand)) {
|
| 1428 |
+
return 'rightHand';
|
| 1429 |
+
}
|
| 1430 |
+
if (rects.face && isPointInRect(x, y, rects.face)) {
|
| 1431 |
+
return 'face';
|
| 1432 |
+
}
|
| 1433 |
+
|
| 1434 |
+
return null;
|
| 1435 |
+
}
|
| 1436 |
+
|
| 1437 |
+
// 🔧 矩形コントロールポイント検出(refs互換)
|
| 1438 |
+
function findNearestRectControlPoint(x, y) {
|
| 1439 |
+
const rectType = window.poseEditorGlobals.rectEditMode;
|
| 1440 |
+
debugLog(`findNearestRectControlPoint: rectType=${rectType}, mouse=(${x.toFixed(1)}, ${y.toFixed(1)})`);
|
| 1441 |
+
|
| 1442 |
+
if (!rectType) {
|
| 1443 |
+
debugLog("No rectEditMode active");
|
| 1444 |
+
return null;
|
| 1445 |
+
}
|
| 1446 |
+
|
| 1447 |
+
const rect = window.poseEditorGlobals.currentRects[rectType];
|
| 1448 |
+
if (!rect) {
|
| 1449 |
+
debugLog(`No rect found for ${rectType}`);
|
| 1450 |
+
return null;
|
| 1451 |
+
}
|
| 1452 |
+
|
| 1453 |
+
debugLog(`Target rect: ${JSON.stringify(rect)}`);
|
| 1454 |
+
|
| 1455 |
+
const threshold = 15; // クリック判定の閾値
|
| 1456 |
+
|
| 1457 |
+
// 8つのコントロールポイント
|
| 1458 |
+
const controlPoints = [
|
| 1459 |
+
{ x: rect.x, y: rect.y, type: 'corner', position: 'tl' }, // 左上
|
| 1460 |
+
{ x: rect.x + rect.width, y: rect.y, type: 'corner', position: 'tr' }, // 右上
|
| 1461 |
+
{ x: rect.x, y: rect.y + rect.height, type: 'corner', position: 'bl' }, // 左下
|
| 1462 |
+
{ x: rect.x + rect.width, y: rect.y + rect.height, type: 'corner', position: 'br' }, // 右下
|
| 1463 |
+
{ x: rect.x + rect.width / 2, y: rect.y, type: 'edge', position: 't' }, // 上辺
|
| 1464 |
+
{ x: rect.x + rect.width, y: rect.y + rect.height / 2, type: 'edge', position: 'r' }, // 右辺
|
| 1465 |
+
{ x: rect.x + rect.width / 2, y: rect.y + rect.height, type: 'edge', position: 'b' }, // 下辺
|
| 1466 |
+
{ x: rect.x, y: rect.y + rect.height / 2, type: 'edge', position: 'l' } // 左辺
|
| 1467 |
+
];
|
| 1468 |
+
|
| 1469 |
+
// 最も近いコントロールポイントを探す
|
| 1470 |
+
let nearestPoint = null;
|
| 1471 |
+
let minDistance = Infinity;
|
| 1472 |
+
|
| 1473 |
+
debugLog(`Checking ${controlPoints.length} control points with threshold=${threshold}`);
|
| 1474 |
+
|
| 1475 |
+
for (const point of controlPoints) {
|
| 1476 |
+
const distance = Math.sqrt((x - point.x) ** 2 + (y - point.y) ** 2);
|
| 1477 |
+
debugLog(`Control point ${point.position}: (${point.x.toFixed(1)}, ${point.y.toFixed(1)}) distance=${distance.toFixed(1)}`);
|
| 1478 |
+
|
| 1479 |
+
if (distance < threshold && distance < minDistance) {
|
| 1480 |
+
minDistance = distance;
|
| 1481 |
+
nearestPoint = point;
|
| 1482 |
+
debugLog(`New nearest point: ${point.position} at distance ${distance.toFixed(1)}`);
|
| 1483 |
+
}
|
| 1484 |
+
}
|
| 1485 |
+
|
| 1486 |
+
if (nearestPoint) {
|
| 1487 |
+
debugLog(`Found control point: ${nearestPoint.position}`);
|
| 1488 |
+
} else {
|
| 1489 |
+
debugLog(`No control point found within threshold ${threshold}`);
|
| 1490 |
+
}
|
| 1491 |
+
|
| 1492 |
+
return nearestPoint;
|
| 1493 |
+
}
|
| 1494 |
+
|
| 1495 |
+
// 🔧 矩形コントロールポイ���トドラッグ処理(refs互換・絶対座標計算)
|
| 1496 |
+
function updateRectControlDrag(mouseX, mouseY) {
|
| 1497 |
+
const controlPoint = window.poseEditorGlobals.draggedRectControl;
|
| 1498 |
+
const rectType = window.poseEditorGlobals.rectEditMode;
|
| 1499 |
+
|
| 1500 |
+
debugLog(`updateRectControlDrag called: mouse=(${mouseX.toFixed(1)}, ${mouseY.toFixed(1)}), control=${controlPoint ? controlPoint.position : 'null'}, rectType=${rectType}`);
|
| 1501 |
+
|
| 1502 |
+
if (!controlPoint) {
|
| 1503 |
+
debugLog("No draggedRectControl found");
|
| 1504 |
+
return;
|
| 1505 |
+
}
|
| 1506 |
+
|
| 1507 |
+
if (!rectType) {
|
| 1508 |
+
debugLog("No rectEditMode found");
|
| 1509 |
+
return;
|
| 1510 |
+
}
|
| 1511 |
+
|
| 1512 |
+
// 🔧 元の矩形座標を取得(初回保存済み)
|
| 1513 |
+
const originalRect = window.poseEditorGlobals.originalRect;
|
| 1514 |
+
if (!originalRect) {
|
| 1515 |
+
debugLog("Original rect not found");
|
| 1516 |
+
return;
|
| 1517 |
+
}
|
| 1518 |
+
|
| 1519 |
+
debugLog(`Original rect: ${JSON.stringify(originalRect)}`);
|
| 1520 |
+
|
| 1521 |
+
// 🔧 絶対座標で新しい矩形を計算(refs互換)
|
| 1522 |
+
let newRect = { ...originalRect };
|
| 1523 |
+
|
| 1524 |
+
// コントロールポイントの位置に応じて絶対座標で計算
|
| 1525 |
+
switch (controlPoint.position) {
|
| 1526 |
+
case 'tl': // 左上
|
| 1527 |
+
newRect.width = originalRect.width + (originalRect.x - mouseX);
|
| 1528 |
+
newRect.height = originalRect.height + (originalRect.y - mouseY);
|
| 1529 |
+
newRect.x = mouseX;
|
| 1530 |
+
newRect.y = mouseY;
|
| 1531 |
+
break;
|
| 1532 |
+
case 'tr': // 右上
|
| 1533 |
+
newRect.width = mouseX - originalRect.x;
|
| 1534 |
+
newRect.height = originalRect.height + (originalRect.y - mouseY);
|
| 1535 |
+
newRect.y = mouseY;
|
| 1536 |
+
break;
|
| 1537 |
+
case 'bl': // 左下
|
| 1538 |
+
newRect.width = originalRect.width + (originalRect.x - mouseX);
|
| 1539 |
+
newRect.height = mouseY - originalRect.y;
|
| 1540 |
+
newRect.x = mouseX;
|
| 1541 |
+
break;
|
| 1542 |
+
case 'br': // 右下
|
| 1543 |
+
newRect.width = mouseX - originalRect.x;
|
| 1544 |
+
newRect.height = mouseY - originalRect.y;
|
| 1545 |
+
break;
|
| 1546 |
+
case 't': // 上辺
|
| 1547 |
+
newRect.y = mouseY;
|
| 1548 |
+
newRect.height = originalRect.height + (originalRect.y - mouseY);
|
| 1549 |
+
break;
|
| 1550 |
+
case 'r': // 右辺
|
| 1551 |
+
newRect.width = mouseX - originalRect.x;
|
| 1552 |
+
break;
|
| 1553 |
+
case 'b': // 下辺
|
| 1554 |
+
newRect.height = mouseY - originalRect.y;
|
| 1555 |
+
break;
|
| 1556 |
+
case 'l': // 左辺
|
| 1557 |
+
newRect.x = mouseX;
|
| 1558 |
+
newRect.width = originalRect.width + (originalRect.x - mouseX);
|
| 1559 |
+
break;
|
| 1560 |
+
}
|
| 1561 |
+
|
| 1562 |
+
// 🔧 最小サイズ制限(refs互換)
|
| 1563 |
+
const minSize = 20;
|
| 1564 |
+
if (newRect.width < minSize) {
|
| 1565 |
+
if (controlPoint.position.includes('l')) {
|
| 1566 |
+
newRect.x = newRect.x + newRect.width - minSize;
|
| 1567 |
+
}
|
| 1568 |
+
newRect.width = minSize;
|
| 1569 |
+
}
|
| 1570 |
+
if (newRect.height < minSize) {
|
| 1571 |
+
if (controlPoint.position.includes('t')) {
|
| 1572 |
+
newRect.y = newRect.y + newRect.height - minSize;
|
| 1573 |
+
}
|
| 1574 |
+
newRect.height = minSize;
|
| 1575 |
+
}
|
| 1576 |
+
|
| 1577 |
+
// 🔧 矩形を更新(累積変形防止のため直接設定)
|
| 1578 |
+
window.poseEditorGlobals.currentRects[rectType] = newRect;
|
| 1579 |
+
|
| 1580 |
+
// debugLog(`Rect control drag: ${rectType} ${controlPoint.position} -> ${newRect.x.toFixed(1)},${newRect.y.toFixed(1)} ${newRect.width.toFixed(1)}x${newRect.height.toFixed(1)}`);
|
| 1581 |
+
|
| 1582 |
+
// 🔧 手・顔キーポイントの座標も更新(refs互換シンプル版)
|
| 1583 |
+
updateKeypointsByRectSimple(rectType, newRect);
|
| 1584 |
+
|
| 1585 |
+
// 🚀 refs互換:編集中もリアルタイム描画
|
| 1586 |
+
const currentPoseData = window.poseEditorGlobals.poseData || poseData;
|
| 1587 |
+
if (currentPoseData) {
|
| 1588 |
+
drawPose(
|
| 1589 |
+
currentPoseData,
|
| 1590 |
+
window.poseEditorGlobals.enableHands,
|
| 1591 |
+
window.poseEditorGlobals.enableFace
|
| 1592 |
+
);
|
| 1593 |
+
}
|
| 1594 |
+
}
|
| 1595 |
+
|
| 1596 |
+
// 🔧 rectEditInfo全体初期化(矩形編集モード開始時)
|
| 1597 |
+
function initializeRectEditInfo() {
|
| 1598 |
+
debugLog('🚀 Initializing rectEditInfo for all rect types');
|
| 1599 |
+
|
| 1600 |
+
if (!window.poseEditorGlobals.rectEditInfo) {
|
| 1601 |
+
window.poseEditorGlobals.rectEditInfo = {};
|
| 1602 |
+
}
|
| 1603 |
+
|
| 1604 |
+
const rectTypes = ['face', 'leftHand', 'rightHand'];
|
| 1605 |
+
|
| 1606 |
+
for (const rectType of rectTypes) {
|
| 1607 |
+
const currentRect = window.poseEditorGlobals.currentRects[rectType];
|
| 1608 |
+
if (currentRect) {
|
| 1609 |
+
window.poseEditorGlobals.rectEditInfo[rectType] = {
|
| 1610 |
+
originalRect: { ...currentRect },
|
| 1611 |
+
keypointIndices: getRectKeypointIndices(rectType)
|
| 1612 |
+
};
|
| 1613 |
+
debugLog(`🔧 rectEditInfo initialized: ${rectType}`, window.poseEditorGlobals.rectEditInfo[rectType]);
|
| 1614 |
+
}
|
| 1615 |
+
}
|
| 1616 |
+
|
| 1617 |
+
// 元のキーポイントも保存
|
| 1618 |
+
const currentPoseData = window.poseEditorGlobals.poseData || poseData;
|
| 1619 |
+
if (currentPoseData) {
|
| 1620 |
+
window.poseEditorGlobals.originalKeypoints = JSON.parse(JSON.stringify(currentPoseData));
|
| 1621 |
+
debugLog('🔧 Original keypoints saved for all rect types');
|
| 1622 |
+
}
|
| 1623 |
+
}
|
| 1624 |
+
|
| 1625 |
+
// 🔧 矩形タイプに対応するキーポ���ントインデックスを取得(refs互換:詳細キーポイントのみ)
|
| 1626 |
+
function getRectKeypointIndices(rectType) {
|
| 1627 |
+
// 🔧 refs互換:矩形編集では体のキーポイントは編集しない、詳細キーポイントのみ
|
| 1628 |
+
// 体のキーポイント(pose_keypoints_2d)は触らずに、詳細キーポイントのみを編集
|
| 1629 |
+
return []; // 空配列を返して体のキーポイント編集を無効化
|
| 1630 |
+
}
|
| 1631 |
+
|
| 1632 |
+
// 🔧 refs互換のシンプル矩形キーポイント更新
|
| 1633 |
+
// 🚀 refs互換:シンプル矩形編集(detail keypointsのみ)
|
| 1634 |
+
function updateKeypointsByRectSimple(rectType, newRect) {
|
| 1635 |
+
debugLog(`🚀 updateKeypointsByRectSimple for ${rectType}`);
|
| 1636 |
+
|
| 1637 |
+
const currentPoseData = window.poseEditorGlobals.poseData || poseData;
|
| 1638 |
+
if (!currentPoseData || !currentPoseData.people || !currentPoseData.people[0]) {
|
| 1639 |
+
debugLog('❌ No pose data');
|
| 1640 |
+
return;
|
| 1641 |
+
}
|
| 1642 |
+
|
| 1643 |
+
const person = currentPoseData.people[0];
|
| 1644 |
+
const originalRect = window.poseEditorGlobals.originalRect;
|
| 1645 |
+
|
| 1646 |
+
if (!originalRect) {
|
| 1647 |
+
debugLog('❌ No original rect saved');
|
| 1648 |
+
return;
|
| 1649 |
+
}
|
| 1650 |
+
|
| 1651 |
+
// 🚀 refs互換:detail keypointsを直接リサイズ
|
| 1652 |
+
updateDetailKeypointsByRect(rectType, originalRect, newRect);
|
| 1653 |
+
|
| 1654 |
+
debugLog(`✅ Updated keypoints for ${rectType}`);
|
| 1655 |
+
}
|
| 1656 |
+
|
| 1657 |
+
// 🚀 refs互換:シンプル矩形移動(detail keypointsのみ)
|
| 1658 |
+
function moveKeypointsByRectSimple(rectType, deltaX, deltaY) {
|
| 1659 |
+
debugLog(`🚀 moveKeypointsByRectSimple for ${rectType}: delta=(${deltaX.toFixed(1)}, ${deltaY.toFixed(1)})`);
|
| 1660 |
+
|
| 1661 |
+
const currentPoseData = window.poseEditorGlobals.poseData || poseData;
|
| 1662 |
+
if (!currentPoseData || !currentPoseData.people || !currentPoseData.people[0]) {
|
| 1663 |
+
debugLog('❌ No pose data');
|
| 1664 |
+
return;
|
| 1665 |
+
}
|
| 1666 |
+
|
| 1667 |
+
const person = currentPoseData.people[0];
|
| 1668 |
+
|
| 1669 |
+
// 🚀 refs互換:detail keypointsを直接移動
|
| 1670 |
+
if (rectType === 'leftHand' && person.hand_left_keypoints_2d) {
|
| 1671 |
+
moveKeypointsArray(person.hand_left_keypoints_2d, deltaX, deltaY, 'left_hand');
|
| 1672 |
+
} else if (rectType === 'rightHand' && person.hand_right_keypoints_2d) {
|
| 1673 |
+
moveKeypointsArray(person.hand_right_keypoints_2d, deltaX, deltaY, 'right_hand');
|
| 1674 |
+
} else if (rectType === 'face' && person.face_keypoints_2d) {
|
| 1675 |
+
moveKeypointsArray(person.face_keypoints_2d, deltaX, deltaY, 'face');
|
| 1676 |
+
}
|
| 1677 |
+
|
| 1678 |
+
debugLog(`✅ Moved keypoints for ${rectType}`);
|
| 1679 |
+
}
|
| 1680 |
+
|
| 1681 |
+
// 🚀 refs互換:detail keypointsをリサイズ(originalKeypointsから復元)
|
| 1682 |
+
function updateDetailKeypointsByRect(rectType, originalRect, newRect) {
|
| 1683 |
+
const currentPoseData = window.poseEditorGlobals.poseData || poseData;
|
| 1684 |
+
const originalKeypoints = window.poseEditorGlobals.originalKeypoints;
|
| 1685 |
+
|
| 1686 |
+
if (!currentPoseData || !originalKeypoints ||
|
| 1687 |
+
!currentPoseData.people || !originalKeypoints.people ||
|
| 1688 |
+
!currentPoseData.people[0] || !originalKeypoints.people[0]) {
|
| 1689 |
+
debugLog('❌ No pose data available for detail rect update');
|
| 1690 |
+
return;
|
| 1691 |
+
}
|
| 1692 |
+
|
| 1693 |
+
const person = currentPoseData.people[0];
|
| 1694 |
+
const originalPerson = originalKeypoints.people[0];
|
| 1695 |
+
|
| 1696 |
+
if (rectType === 'leftHand' && person.hand_left_keypoints_2d && originalPerson.hand_left_keypoints_2d) {
|
| 1697 |
+
updateKeypointsArrayByRect(person.hand_left_keypoints_2d, originalPerson.hand_left_keypoints_2d, originalRect, newRect, 'left_hand');
|
| 1698 |
+
} else if (rectType === 'rightHand' && person.hand_right_keypoints_2d && originalPerson.hand_right_keypoints_2d) {
|
| 1699 |
+
updateKeypointsArrayByRect(person.hand_right_keypoints_2d, originalPerson.hand_right_keypoints_2d, originalRect, newRect, 'right_hand');
|
| 1700 |
+
} else if (rectType === 'face' && person.face_keypoints_2d && originalPerson.face_keypoints_2d) {
|
| 1701 |
+
updateKeypointsArrayByRect(person.face_keypoints_2d, originalPerson.face_keypoints_2d, originalRect, newRect, 'face');
|
| 1702 |
+
}
|
| 1703 |
+
}
|
| 1704 |
+
|
| 1705 |
+
// 🔧 キーポイント配列をリサイズ(3要素ずつ)
|
| 1706 |
+
function updateKeypointsArrayByRect(keypointsArray, originalKeypointsArray, originalRect, newRect, label) {
|
| 1707 |
+
let updatedCount = 0;
|
| 1708 |
+
|
| 1709 |
+
for (let i = 0; i < keypointsArray.length; i += 3) {
|
| 1710 |
+
if (i + 2 < keypointsArray.length && i + 2 < originalKeypointsArray.length) {
|
| 1711 |
+
const confidence = originalKeypointsArray[i + 2];
|
| 1712 |
+
if (confidence > 0.1) { // 有効なキーポイントのみ
|
| 1713 |
+
const origX = originalKeypointsArray[i];
|
| 1714 |
+
const origY = originalKeypointsArray[i + 1];
|
| 1715 |
+
|
| 1716 |
+
if (origX > 0 && origY > 0) {
|
| 1717 |
+
// 🚀 座標変換:表示座標→データ座標(シンプル版)
|
| 1718 |
+
const scaleX = 640 / 512; // 1.25
|
| 1719 |
+
const scaleY = 640 / 512; // 1.25
|
| 1720 |
+
|
| 1721 |
+
const origRectDataX = originalRect.x / scaleX;
|
| 1722 |
+
const origRectDataY = originalRect.y / scaleY;
|
| 1723 |
+
const newRectDataX = newRect.x / scaleX;
|
| 1724 |
+
const newRectDataY = newRect.y / scaleY;
|
| 1725 |
+
const origRectWidthData = originalRect.width / scaleX;
|
| 1726 |
+
const origRectHeightData = originalRect.height / scaleY;
|
| 1727 |
+
const newRectWidthData = newRect.width / scaleX;
|
| 1728 |
+
const newRectHeightData = newRect.height / scaleY;
|
| 1729 |
+
|
| 1730 |
+
// 元矩形内の相対位置を計算
|
| 1731 |
+
const relativeX = (origX - origRectDataX) / origRectWidthData;
|
| 1732 |
+
const relativeY = (origY - origRectDataY) / origRectHeightData;
|
| 1733 |
+
|
| 1734 |
+
// 新矩形での絶対位置を計算(データ座標)
|
| 1735 |
+
const newX = newRectDataX + (relativeX * newRectWidthData);
|
| 1736 |
+
const newY = newRectDataY + (relativeY * newRectHeightData);
|
| 1737 |
+
|
| 1738 |
+
// キーポイント更新(512x512にクランプ)
|
| 1739 |
+
keypointsArray[i] = Math.max(0, Math.min(512, newX));
|
| 1740 |
+
keypointsArray[i + 1] = Math.max(0, Math.min(512, newY));
|
| 1741 |
+
|
| 1742 |
+
updatedCount++;
|
| 1743 |
+
}
|
| 1744 |
+
}
|
| 1745 |
+
}
|
| 1746 |
+
}
|
| 1747 |
+
|
| 1748 |
+
debugLog(`Updated ${updatedCount} ${label} detail keypoints by rect transform`);
|
| 1749 |
+
}
|
| 1750 |
+
|
| 1751 |
+
// 🔧 手と顔の詳細キーポイントを移動(refs互換)
|
| 1752 |
+
function moveDetailKeypoints(rectType, deltaX, deltaY) {
|
| 1753 |
+
debugLog(`🎯 moveDetailKeypoints called: ${rectType}, delta=(${deltaX.toFixed(1)}, ${deltaY.toFixed(1)})`);
|
| 1754 |
+
|
| 1755 |
+
const person = window.poseEditorGlobals.poseData.people[0];
|
| 1756 |
+
|
| 1757 |
+
if (rectType === 'leftHand' && person.hand_left_keypoints_2d) {
|
| 1758 |
+
debugLog(`🔧 Moving left hand keypoints: ${person.hand_left_keypoints_2d.length} elements`);
|
| 1759 |
+
moveKeypointsArray(person.hand_left_keypoints_2d, deltaX, deltaY, 'left_hand');
|
| 1760 |
+
} else if (rectType === 'rightHand' && person.hand_right_keypoints_2d) {
|
| 1761 |
+
debugLog(`🔧 Moving right hand keypoints: ${person.hand_right_keypoints_2d.length} elements`);
|
| 1762 |
+
moveKeypointsArray(person.hand_right_keypoints_2d, deltaX, deltaY, 'right_hand');
|
| 1763 |
+
} else if (rectType === 'face' && person.face_keypoints_2d) {
|
| 1764 |
+
debugLog(`🔧 Moving face keypoints: ${person.face_keypoints_2d.length} elements`);
|
| 1765 |
+
moveKeypointsArray(person.face_keypoints_2d, deltaX, deltaY, 'face');
|
| 1766 |
+
} else {
|
| 1767 |
+
debugLog(`❌ No matching detail keypoints for ${rectType}:`, {
|
| 1768 |
+
hasLeftHand: !!person.hand_left_keypoints_2d,
|
| 1769 |
+
hasRightHand: !!person.hand_right_keypoints_2d,
|
| 1770 |
+
hasFace: !!person.face_keypoints_2d
|
| 1771 |
+
});
|
| 1772 |
+
}
|
| 1773 |
+
}
|
| 1774 |
+
|
| 1775 |
+
// 🔧 キーポイント配列を移動(3要素ずつ)
|
| 1776 |
+
function moveKeypointsArray(keypointsArray, deltaX, deltaY, label) {
|
| 1777 |
+
let movedCount = 0;
|
| 1778 |
+
|
| 1779 |
+
for (let i = 0; i < keypointsArray.length; i += 3) {
|
| 1780 |
+
if (i + 2 < keypointsArray.length) {
|
| 1781 |
+
const confidence = keypointsArray[i + 2];
|
| 1782 |
+
if (confidence > 0.1) { // 有効なキーポイントのみ
|
| 1783 |
+
const currentX = keypointsArray[i];
|
| 1784 |
+
const currentY = keypointsArray[i + 1];
|
| 1785 |
+
|
| 1786 |
+
// 🚀 座標変換:表示座標の移動量→データ座標の移動量(シンプル版)
|
| 1787 |
+
const scaleX = 640 / 512; // 1.25
|
| 1788 |
+
const scaleY = 640 / 512; // 1.25
|
| 1789 |
+
const dataDeltaX = deltaX / scaleX;
|
| 1790 |
+
const dataDeltaY = deltaY / scaleY;
|
| 1791 |
+
|
| 1792 |
+
// 移動(512x512にクランプ)
|
| 1793 |
+
const newX = Math.max(0, Math.min(512, currentX + dataDeltaX));
|
| 1794 |
+
const newY = Math.max(0, Math.min(512, currentY + dataDeltaY));
|
| 1795 |
+
|
| 1796 |
+
keypointsArray[i] = newX;
|
| 1797 |
+
keypointsArray[i + 1] = newY;
|
| 1798 |
+
|
| 1799 |
+
movedCount++;
|
| 1800 |
+
}
|
| 1801 |
+
}
|
| 1802 |
+
}
|
| 1803 |
+
|
| 1804 |
+
debugLog(`Moved ${movedCount} ${label} detail keypoints by (${deltaX.toFixed(1)}, ${deltaY.toFixed(1)})`);
|
| 1805 |
+
}
|
| 1806 |
+
|
| 1807 |
+
// 🔧 矩形移動ドラッグ処理(refs互換)
|
| 1808 |
+
function updateRectMoveDrag(mouseX, mouseY) {
|
| 1809 |
+
const rectType = window.poseEditorGlobals.draggedRect;
|
| 1810 |
+
if (!rectType) return;
|
| 1811 |
+
|
| 1812 |
+
const rect = window.poseEditorGlobals.currentRects[rectType];
|
| 1813 |
+
if (!rect) return;
|
| 1814 |
+
|
| 1815 |
+
// ドラッグ量を計算
|
| 1816 |
+
const deltaX = mouseX - window.poseEditorGlobals.dragStartPos.x;
|
| 1817 |
+
const deltaY = mouseY - window.poseEditorGlobals.dragStartPos.y;
|
| 1818 |
+
|
| 1819 |
+
// 開始位置を更新(連続ドラッグ対応)
|
| 1820 |
+
window.poseEditorGlobals.dragStartPos.x = mouseX;
|
| 1821 |
+
window.poseEditorGlobals.dragStartPos.y = mouseY;
|
| 1822 |
+
|
| 1823 |
+
// 矩形位置を移動
|
| 1824 |
+
rect.x += deltaX;
|
| 1825 |
+
rect.y += deltaY;
|
| 1826 |
+
|
| 1827 |
+
// Canvas境界制限
|
| 1828 |
+
const canvas = window.poseEditorGlobals.canvas;
|
| 1829 |
+
rect.x = Math.max(0, Math.min(canvas.width - rect.width, rect.x));
|
| 1830 |
+
rect.y = Math.max(0, Math.min(canvas.height - rect.height, rect.y));
|
| 1831 |
+
|
| 1832 |
+
// 矩形を更新
|
| 1833 |
+
window.poseEditorGlobals.currentRects[rectType] = rect;
|
| 1834 |
+
|
| 1835 |
+
debugLog(`Rect move drag: ${rectType} moved by (${deltaX.toFixed(1)}, ${deltaY.toFixed(1)})`);
|
| 1836 |
+
|
| 1837 |
+
// 手・顔キーポイントの座標も移動(refs互換シンプル版)
|
| 1838 |
+
moveKeypointsByRectSimple(rectType, deltaX, deltaY);
|
| 1839 |
+
|
| 1840 |
+
// 🚀 refs互換:編集中もリアルタイム描画
|
| 1841 |
+
const currentPoseData = window.poseEditorGlobals.poseData || poseData;
|
| 1842 |
+
if (currentPoseData) {
|
| 1843 |
+
drawPose(
|
| 1844 |
+
currentPoseData,
|
| 1845 |
+
window.poseEditorGlobals.enableHands,
|
| 1846 |
+
window.poseEditorGlobals.enableFace
|
| 1847 |
+
);
|
| 1848 |
+
}
|
| 1849 |
+
}
|
| 1850 |
+
|
| 1851 |
+
// 🔧 矩形タイプに応じた色取得
|
| 1852 |
+
function getColorForRectType(rectType) {
|
| 1853 |
+
const colors = {
|
| 1854 |
+
'leftHand': 'rgb(255,165,0)', // オレンジ
|
| 1855 |
+
'rightHand': 'rgb(255,69,0)', // 濃いオレンジ
|
| 1856 |
+
'face': 'rgb(34,139,34)' // 緑
|
| 1857 |
+
};
|
| 1858 |
+
return colors[rectType] || 'rgb(255,165,0)';
|
| 1859 |
+
}
|
| 1860 |
+
|
| 1861 |
+
// 🔧 矩形リサイズに合わせてキーポイント更新(refs互換)
|
| 1862 |
+
function updateKeypointsByRect(rectType, newRect) {
|
| 1863 |
+
const currentPoseData = window.poseEditorGlobals.poseData || poseData;
|
| 1864 |
+
if (!currentPoseData) return;
|
| 1865 |
+
|
| 1866 |
+
// 元矩形情報を取得
|
| 1867 |
+
const originalRect = window.poseEditorGlobals.originalRect;
|
| 1868 |
+
if (!originalRect) return;
|
| 1869 |
+
|
| 1870 |
+
debugLog(`🔧 Updating ${rectType} keypoints: original=${JSON.stringify(originalRect)}, new=${JSON.stringify(newRect)}`);
|
| 1871 |
+
|
| 1872 |
+
// 最小サイズ制限(refs互換)
|
| 1873 |
+
const minSize = 20;
|
| 1874 |
+
if (newRect.width < minSize || newRect.height < minSize) {
|
| 1875 |
+
debugLog(`Rectangle too small, skipping update`);
|
| 1876 |
+
return;
|
| 1877 |
+
}
|
| 1878 |
+
|
| 1879 |
+
// 変換比率を計算
|
| 1880 |
+
const scaleX = newRect.width / originalRect.width;
|
| 1881 |
+
const scaleY = newRect.height / originalRect.height;
|
| 1882 |
+
|
| 1883 |
+
debugLog(`Transform ratios: scaleX=${scaleX.toFixed(3)}, scaleY=${scaleY.toFixed(3)}`);
|
| 1884 |
+
|
| 1885 |
+
// 対象キーポイントデータを取得(refs互換の構造もサポート)
|
| 1886 |
+
let targetKeypoints = null;
|
| 1887 |
+
let fieldName = null;
|
| 1888 |
+
|
| 1889 |
+
// まず新しい構造(people[0].xxx_keypoints_2d)をチェック
|
| 1890 |
+
if (currentPoseData.people && currentPoseData.people[0]) {
|
| 1891 |
+
const person = currentPoseData.people[0];
|
| 1892 |
+
if (rectType === 'leftHand' && person.hand_left_keypoints_2d) {
|
| 1893 |
+
targetKeypoints = person.hand_left_keypoints_2d;
|
| 1894 |
+
fieldName = 'hand_left_keypoints_2d';
|
| 1895 |
+
} else if (rectType === 'rightHand' && person.hand_right_keypoints_2d) {
|
| 1896 |
+
targetKeypoints = person.hand_right_keypoints_2d;
|
| 1897 |
+
fieldName = 'hand_right_keypoints_2d';
|
| 1898 |
+
} else if (rectType === 'face' && person.face_keypoints_2d) {
|
| 1899 |
+
targetKeypoints = person.face_keypoints_2d;
|
| 1900 |
+
fieldName = 'face_keypoints_2d';
|
| 1901 |
+
}
|
| 1902 |
+
}
|
| 1903 |
+
|
| 1904 |
+
// 古い構造(hands[], faces[])にフォールバック
|
| 1905 |
+
if (!targetKeypoints) {
|
| 1906 |
+
if (rectType === 'leftHand' && currentPoseData.hands && currentPoseData.hands[0]) {
|
| 1907 |
+
targetKeypoints = currentPoseData.hands[0];
|
| 1908 |
+
} else if (rectType === 'rightHand' && currentPoseData.hands && currentPoseData.hands[1]) {
|
| 1909 |
+
targetKeypoints = currentPoseData.hands[1];
|
| 1910 |
+
} else if (rectType === 'face' && currentPoseData.faces && currentPoseData.faces[0]) {
|
| 1911 |
+
targetKeypoints = currentPoseData.faces[0];
|
| 1912 |
+
}
|
| 1913 |
+
}
|
| 1914 |
+
|
| 1915 |
+
if (!targetKeypoints) {
|
| 1916 |
+
debugLog(`No keypoints found for ${rectType}`);
|
| 1917 |
+
return;
|
| 1918 |
+
}
|
| 1919 |
+
|
| 1920 |
+
// 📐 解像度情報の取得(refs互換)
|
| 1921 |
+
let dataResolutionWidth = 512; // デフォルト値
|
| 1922 |
+
let dataResolutionHeight = 512;
|
| 1923 |
+
|
| 1924 |
+
// metadata.resolutionをチェック(refs形式)
|
| 1925 |
+
if (currentPoseData.metadata &&
|
| 1926 |
+
currentPoseData.metadata.resolution &&
|
| 1927 |
+
Array.isArray(currentPoseData.metadata.resolution) &&
|
| 1928 |
+
currentPoseData.metadata.resolution.length >= 2) {
|
| 1929 |
+
dataResolutionWidth = currentPoseData.metadata.resolution[0];
|
| 1930 |
+
dataResolutionHeight = currentPoseData.metadata.resolution[1];
|
| 1931 |
+
} else if (currentPoseData.resolution) {
|
| 1932 |
+
// 通常のresolutionフィールド
|
| 1933 |
+
dataResolutionWidth = currentPoseData.resolution[0];
|
| 1934 |
+
dataResolutionHeight = currentPoseData.resolution[1];
|
| 1935 |
+
}
|
| 1936 |
+
|
| 1937 |
+
const coordScaleX = canvas.width / dataResolutionWidth;
|
| 1938 |
+
const coordScaleY = canvas.height / dataResolutionHeight;
|
| 1939 |
+
|
| 1940 |
+
// 正規化座標かピクセル座標かを判定(refs互換)
|
| 1941 |
+
let isNormalized = false;
|
| 1942 |
+
if (targetKeypoints.length > 0) {
|
| 1943 |
+
for (let i = 0; i < targetKeypoints.length; i += 3) {
|
| 1944 |
+
if (i + 2 < targetKeypoints.length && targetKeypoints[i + 2] > 0) {
|
| 1945 |
+
const x = targetKeypoints[i];
|
| 1946 |
+
const y = targetKeypoints[i + 1];
|
| 1947 |
+
isNormalized = (x >= 0 && x <= 1 && y >= 0 && y <= 1);
|
| 1948 |
+
break;
|
| 1949 |
+
}
|
| 1950 |
+
}
|
| 1951 |
+
}
|
| 1952 |
+
|
| 1953 |
+
debugLog(`Coordinate system: ${isNormalized ? 'Normalized' : 'Pixel'}, fieldName: ${fieldName || 'legacy'}, dataRes: ${dataResolutionWidth}x${dataResolutionHeight}`);
|
| 1954 |
+
|
| 1955 |
+
let updatedCount = 0;
|
| 1956 |
+
|
| 1957 |
+
// 各キーポイントを変換(3要素ずつ:x, y, confidence)
|
| 1958 |
+
for (let i = 0; i < targetKeypoints.length; i += 3) {
|
| 1959 |
+
if (i + 2 < targetKeypoints.length) {
|
| 1960 |
+
const confidence = targetKeypoints[i + 2];
|
| 1961 |
+
if (confidence > 0.1) { // refs互換の閾値
|
| 1962 |
+
let x = targetKeypoints[i];
|
| 1963 |
+
let y = targetKeypoints[i + 1];
|
| 1964 |
+
|
| 1965 |
+
// データ座標→Canvas座標
|
| 1966 |
+
let canvasX, canvasY;
|
| 1967 |
+
if (isNormalized) {
|
| 1968 |
+
canvasX = (x * dataResolutionWidth) * coordScaleX;
|
| 1969 |
+
canvasY = (y * dataResolutionHeight) * coordScaleY;
|
| 1970 |
+
} else {
|
| 1971 |
+
canvasX = x * coordScaleX;
|
| 1972 |
+
canvasY = y * coordScaleY;
|
| 1973 |
+
}
|
| 1974 |
+
|
| 1975 |
+
// 🔧 元矩形内での相対位置を計算(refs互換・安全範囲チェック)
|
| 1976 |
+
let relativeX = (canvasX - originalRect.x) / originalRect.width;
|
| 1977 |
+
let relativeY = (canvasY - originalRect.y) / originalRect.height;
|
| 1978 |
+
|
| 1979 |
+
// 🔧 相対位置を0-1の範囲内にクランプ(refs互換)
|
| 1980 |
+
relativeX = Math.max(0, Math.min(1, relativeX));
|
| 1981 |
+
relativeY = Math.max(0, Math.min(1, relativeY));
|
| 1982 |
+
|
| 1983 |
+
// 🔧 新矩形での新しい位置を計算
|
| 1984 |
+
const newCanvasX = newRect.x + (relativeX * newRect.width);
|
| 1985 |
+
const newCanvasY = newRect.y + (relativeY * newRect.height);
|
| 1986 |
+
|
| 1987 |
+
// 🔧 Canvas座標→データ座標に戻す(refs互換・範囲制限付き)
|
| 1988 |
+
if (isNormalized) {
|
| 1989 |
+
const dataX = newCanvasX / coordScaleX;
|
| 1990 |
+
const dataY = newCanvasY / coordScaleY;
|
| 1991 |
+
let newNormX = dataX / dataResolutionWidth;
|
| 1992 |
+
let newNormY = dataY / dataResolutionHeight;
|
| 1993 |
+
|
| 1994 |
+
// 正規化座標の範囲制限(0-1)
|
| 1995 |
+
newNormX = Math.max(0, Math.min(1, newNormX));
|
| 1996 |
+
newNormY = Math.max(0, Math.min(1, newNormY));
|
| 1997 |
+
|
| 1998 |
+
targetKeypoints[i] = newNormX;
|
| 1999 |
+
targetKeypoints[i + 1] = newNormY;
|
| 2000 |
+
} else {
|
| 2001 |
+
let newDataX = newCanvasX / coordScaleX;
|
| 2002 |
+
let newDataY = newCanvasY / coordScaleY;
|
| 2003 |
+
|
| 2004 |
+
// ピクセル座標の範囲制限
|
| 2005 |
+
newDataX = Math.max(0, Math.min(dataResolutionWidth, newDataX));
|
| 2006 |
+
newDataY = Math.max(0, Math.min(dataResolutionHeight, newDataY));
|
| 2007 |
+
|
| 2008 |
+
targetKeypoints[i] = newDataX;
|
| 2009 |
+
targetKeypoints[i + 1] = newDataY;
|
| 2010 |
+
}
|
| 2011 |
+
|
| 2012 |
+
updatedCount++;
|
| 2013 |
+
|
| 2014 |
+
// 🔧 デバッグログ(最初の3つのキーポイントのみ)
|
| 2015 |
+
if (updatedCount <= 3) {
|
| 2016 |
+
debugLog(`Keypoint ${updatedCount}: (${x.toFixed(3)}, ${y.toFixed(3)}) -> canvas(${canvasX.toFixed(1)}, ${canvasY.toFixed(1)}) -> rel(${relativeX.toFixed(3)}, ${relativeY.toFixed(3)}) -> new(${targetKeypoints[i].toFixed(3)}, ${targetKeypoints[i + 1].toFixed(3)})`);
|
| 2017 |
+
}
|
| 2018 |
+
}
|
| 2019 |
+
}
|
| 2020 |
+
}
|
| 2021 |
+
|
| 2022 |
+
debugLog(`Updated ${updatedCount} keypoints for ${rectType} [${isNormalized ? 'normalized' : 'pixel'} coordinates]`);
|
| 2023 |
+
|
| 2024 |
+
// 矩形情報を更新(refs互換)
|
| 2025 |
+
if (window.poseEditorGlobals.rects) {
|
| 2026 |
+
window.poseEditorGlobals.rects[rectType] = newRect;
|
| 2027 |
+
}
|
| 2028 |
+
}
|
| 2029 |
+
|
| 2030 |
+
// 🔧 矩形移動に合わせてキーポイント移動(refs互換)
|
| 2031 |
+
function moveKeypointsByRect(rectType, deltaX, deltaY) {
|
| 2032 |
+
const currentPoseData = window.poseEditorGlobals.poseData || poseData;
|
| 2033 |
+
if (!currentPoseData) return;
|
| 2034 |
+
|
| 2035 |
+
debugLog(`Moving ${rectType} keypoints by (${deltaX.toFixed(1)}, ${deltaY.toFixed(1)})`);
|
| 2036 |
+
|
| 2037 |
+
// 対象キーポイントデータを取得(refs互換の構造もサポート)
|
| 2038 |
+
let targetKeypoints = null;
|
| 2039 |
+
let fieldName = null;
|
| 2040 |
+
|
| 2041 |
+
// まず新しい構造(people[0].xxx_keypoints_2d)をチェック
|
| 2042 |
+
if (currentPoseData.people && currentPoseData.people[0]) {
|
| 2043 |
+
const person = currentPoseData.people[0];
|
| 2044 |
+
if (rectType === 'leftHand' && person.hand_left_keypoints_2d) {
|
| 2045 |
+
targetKeypoints = person.hand_left_keypoints_2d;
|
| 2046 |
+
fieldName = 'hand_left_keypoints_2d';
|
| 2047 |
+
} else if (rectType === 'rightHand' && person.hand_right_keypoints_2d) {
|
| 2048 |
+
targetKeypoints = person.hand_right_keypoints_2d;
|
| 2049 |
+
fieldName = 'hand_right_keypoints_2d';
|
| 2050 |
+
} else if (rectType === 'face' && person.face_keypoints_2d) {
|
| 2051 |
+
targetKeypoints = person.face_keypoints_2d;
|
| 2052 |
+
fieldName = 'face_keypoints_2d';
|
| 2053 |
+
}
|
| 2054 |
+
}
|
| 2055 |
+
|
| 2056 |
+
// 古い構造(hands[], faces[])にフォールバック
|
| 2057 |
+
if (!targetKeypoints) {
|
| 2058 |
+
if (rectType === 'leftHand' && currentPoseData.hands && currentPoseData.hands[0]) {
|
| 2059 |
+
targetKeypoints = currentPoseData.hands[0];
|
| 2060 |
+
} else if (rectType === 'rightHand' && currentPoseData.hands && currentPoseData.hands[1]) {
|
| 2061 |
+
targetKeypoints = currentPoseData.hands[1];
|
| 2062 |
+
} else if (rectType === 'face' && currentPoseData.faces && currentPoseData.faces[0]) {
|
| 2063 |
+
targetKeypoints = currentPoseData.faces[0];
|
| 2064 |
+
}
|
| 2065 |
+
}
|
| 2066 |
+
|
| 2067 |
+
if (!targetKeypoints) {
|
| 2068 |
+
debugLog(`No keypoints found for ${rectType}`);
|
| 2069 |
+
return;
|
| 2070 |
+
}
|
| 2071 |
+
|
| 2072 |
+
// 📐 解像度情報の取得(refs互換)
|
| 2073 |
+
let dataResolutionWidth = 512; // デフォルト値
|
| 2074 |
+
let dataResolutionHeight = 512;
|
| 2075 |
+
|
| 2076 |
+
// metadata.resolutionをチェック(refs形式)
|
| 2077 |
+
if (currentPoseData.metadata &&
|
| 2078 |
+
currentPoseData.metadata.resolution &&
|
| 2079 |
+
Array.isArray(currentPoseData.metadata.resolution) &&
|
| 2080 |
+
currentPoseData.metadata.resolution.length >= 2) {
|
| 2081 |
+
dataResolutionWidth = currentPoseData.metadata.resolution[0];
|
| 2082 |
+
dataResolutionHeight = currentPoseData.metadata.resolution[1];
|
| 2083 |
+
} else if (currentPoseData.resolution) {
|
| 2084 |
+
// 通常のresolutionフィールド
|
| 2085 |
+
dataResolutionWidth = currentPoseData.resolution[0];
|
| 2086 |
+
dataResolutionHeight = currentPoseData.resolution[1];
|
| 2087 |
+
}
|
| 2088 |
+
|
| 2089 |
+
const coordScaleX = canvas.width / dataResolutionWidth;
|
| 2090 |
+
const coordScaleY = canvas.height / dataResolutionHeight;
|
| 2091 |
+
|
| 2092 |
+
// 正規化座標かピクセル座標かを判定(refs互換)
|
| 2093 |
+
let isNormalized = false;
|
| 2094 |
+
if (targetKeypoints.length > 0) {
|
| 2095 |
+
for (let i = 0; i < targetKeypoints.length; i += 3) {
|
| 2096 |
+
if (i + 2 < targetKeypoints.length && targetKeypoints[i + 2] > 0) {
|
| 2097 |
+
const x = targetKeypoints[i];
|
| 2098 |
+
const y = targetKeypoints[i + 1];
|
| 2099 |
+
isNormalized = (x >= 0 && x <= 1 && y >= 0 && y <= 1);
|
| 2100 |
+
break;
|
| 2101 |
+
}
|
| 2102 |
+
}
|
| 2103 |
+
}
|
| 2104 |
+
|
| 2105 |
+
// Canvas座標での移動量をデータ座標での移動量に変換
|
| 2106 |
+
const dataDeltaX = deltaX / coordScaleX;
|
| 2107 |
+
const dataDeltaY = deltaY / coordScaleY;
|
| 2108 |
+
|
| 2109 |
+
debugLog(`Coordinate system: ${isNormalized ? 'Normalized' : 'Pixel'}, move in data coords: (${dataDeltaX.toFixed(3)}, ${dataDeltaY.toFixed(3)})`);
|
| 2110 |
+
|
| 2111 |
+
let movedCount = 0;
|
| 2112 |
+
|
| 2113 |
+
// 各キーポイントを移動(3要素ずつ:x, y, confidence)
|
| 2114 |
+
for (let i = 0; i < targetKeypoints.length; i += 3) {
|
| 2115 |
+
if (i + 2 < targetKeypoints.length) {
|
| 2116 |
+
const confidence = targetKeypoints[i + 2];
|
| 2117 |
+
if (confidence > 0.1) { // refs互換の閾値
|
| 2118 |
+
if (isNormalized) {
|
| 2119 |
+
// 正規化座標の場合
|
| 2120 |
+
targetKeypoints[i] += dataDeltaX / dataResolutionWidth;
|
| 2121 |
+
targetKeypoints[i + 1] += dataDeltaY / dataResolutionHeight;
|
| 2122 |
+
} else {
|
| 2123 |
+
// ピクセル座標の場合
|
| 2124 |
+
targetKeypoints[i] += dataDeltaX;
|
| 2125 |
+
targetKeypoints[i + 1] += dataDeltaY;
|
| 2126 |
+
}
|
| 2127 |
+
movedCount++;
|
| 2128 |
+
}
|
| 2129 |
+
}
|
| 2130 |
+
}
|
| 2131 |
+
|
| 2132 |
+
debugLog(`Moved ${movedCount} keypoints for ${rectType}`);
|
| 2133 |
+
}
|
| 2134 |
+
|
| 2135 |
// 🎨 推定接続の描画(少ないキーポイント用の補間機能)
|
| 2136 |
function drawEstimatedConnections(candidates, originalRes, scaleX, scaleY) {
|
| 2137 |
const ctx = window.poseEditorGlobals.ctx;
|