gearmachine commited on
Commit
fed3109
·
1 Parent(s): 77a58be

Refactor code structure for improved readability and maintainability

Browse files
Files changed (2) hide show
  1. app.py +7 -3
  2. 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; cursor: crosshair;"></canvas>'
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"if(poseData) drawPose(poseData, {str(draw_hand).lower()}, {str(draw_face).lower()});"
 
 
 
 
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
- const keypointIndex = findNearestKeypoint(mousePos.x, mousePos.y);
243
- debugLog(`Found keypoint index: ${keypointIndex}`);
244
-
245
- if (keypointIndex >= 0) {
246
- isDragging = true;
247
- draggedKeypoint = keypointIndex;
248
-
249
- // 🔧 refs互換:ドラッグオフセット計算(キーポイントの正確な位置からのオフセット)
250
- const currentPoseData = window.poseEditorGlobals.poseData || poseData;
251
- if (currentPoseData && currentPoseData.bodies && currentPoseData.bodies.candidate) {
252
- const candidates = currentPoseData.bodies.candidate;
253
- const originalRes = currentPoseData.resolution || [512, 512];
254
- const scaleX = canvas.width / originalRes[0];
255
- const scaleY = canvas.height / originalRes[1];
256
-
257
- const point = candidates[keypointIndex];
258
- if (point) {
259
- const keypointX = point[0] * scaleX;
260
- const keypointY = point[1] * scaleY;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
 
262
- dragOffset = {
263
- x: mousePos.x - keypointX,
264
- y: mousePos.y - keypointY
265
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
 
267
- debugLog(`Drag offset calculated: (${dragOffset.x.toFixed(1)}, ${dragOffset.y.toFixed(1)})`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
  }
 
269
  }
 
 
 
 
270
 
271
- debugLog(`Drag started: keypoint ${keypointIndex} at (${mousePos.x}, ${mousePos.y})`);
 
272
 
273
- // カーソルを変更
274
- canvas.style.cursor = 'grabbing';
275
- } else {
276
- debugLog("No keypoint found near mouse position");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
  }
278
  }
279
 
280
- // マウス移動処理(refs互換)
281
  function handleMouseMove(event) {
282
  if (!isCanvasReady()) return;
283
 
284
  const mousePos = getMousePos(event);
285
 
286
- if (isDragging && draggedKeypoint >= 0) {
287
- // 🔧 refs互換:オフセット考慮の新座標計算
288
- const newX = mousePos.x - dragOffset.x;
289
- const newY = mousePos.y - dragOffset.y;
290
-
291
- debugLog(`Mouse move: raw(${mousePos.x}, ${mousePos.y}), offset(${dragOffset.x.toFixed(1)}, ${dragOffset.y.toFixed(1)}), new(${newX.toFixed(1)}, ${newY.toFixed(1)})`);
292
 
293
- // ドラッグ中の座標更新(オフセット適用済み座標で)
294
- updateKeypointPosition(draggedKeypoint, newX, newY);
 
 
 
295
 
296
- // リアルタイム再描画(ハイライト付き)- グローバルposeDataを使用
297
- const currentPoseData = window.poseEditorGlobals.poseData || poseData;
298
- drawPose(currentPoseData, true, true, draggedKeypoint);
 
 
 
 
 
 
 
 
 
 
 
 
299
 
300
  } else {
301
- // ホバー時のカーソル変更
302
- const keypointIndex = findNearestKeypoint(mousePos.x, mousePos.y);
303
- canvas.style.cursor = keypointIndex >= 0 ? 'grab' : 'default';
 
 
 
 
 
 
 
 
 
 
 
 
304
  }
305
  }
306
 
307
- // マウスアップ処理(refs互換)
 
 
 
 
 
 
 
 
 
308
  function handleMouseUp(event) {
309
  if (isDragging) {
310
- debugLog(`Drag ended: keypoint ${draggedKeypoint}`);
 
 
 
 
 
 
 
 
 
 
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 = poseData.resolution || [512, 512];
413
  const scaleX = canvas.width / originalRes[0];
414
  const scaleY = canvas.height / originalRes[1];
415
 
416
- // ボディの描画(ハイライト対応)
417
- drawBody(poseData, highlightIndex);
418
 
419
- // 手の描画(座標変換パラメータ付き)
420
- if (enableHands && poseData.hands) {
421
- drawHands(poseData.hands, originalRes, scaleX, scaleY);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
  }
423
 
424
- // 顔の描画(座標変換パラメータ付き)
425
- if (enableFace && poseData.faces) {
426
- drawFaces(poseData.faces, originalRes, scaleX, scaleY);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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(poseData, true, true);
 
 
 
 
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;