junjiro1129 commited on
Commit
df1f96d
·
verified ·
1 Parent(s): d811279

Upload coordinatemaker.html

Browse files
Files changed (1) hide show
  1. coordinatemaker.html +128 -27
coordinatemaker.html CHANGED
@@ -33,11 +33,13 @@
33
  background: rgba(44,222,88,0.15);
34
  box-sizing: border-box;
35
  cursor: move;
 
36
  }
37
  .rect.selected {
38
  border: 2px solid #f00;
39
  background: rgba(255,100,100,0.15);
40
  z-index: 10;
 
41
  }
42
  .handle {
43
  position: absolute;
@@ -54,6 +56,24 @@
54
  .rect.selected .handle {
55
  display: block;
56
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  #coords {
58
  font-family: monospace;
59
  margin-top: 10px;
@@ -209,11 +229,14 @@
209
  let drawing = false;
210
  let moving = false;
211
  let resizing = false;
 
212
  let startX = 0, startY = 0;
213
  let offsetX = 0, offsetY = 0;
214
  let originX = 0, originY = 0;
215
  let activeHandle = null;
216
  let memory = [];
 
 
217
 
218
  function updateFileNameLabel(name) {
219
  fileNameLabel.textContent = name ? name : "";
@@ -314,6 +337,7 @@
314
  memory.push({
315
  filename: currentImageName || '',
316
  coords: imgPoints,
 
317
  id: Date.now() + Math.random()
318
  });
319
  updateMemoryList();
@@ -328,6 +352,7 @@
328
  setSelectedRect(false);
329
  }
330
 
 
331
  container.addEventListener('mousedown', (e) => {
332
  if (!imgLoaded) return;
333
  if (!drawing) return;
@@ -341,7 +366,8 @@
341
  container.appendChild(rectEl);
342
  rectObj = {
343
  left: startX, top: startY, width: 0, height: 0,
344
- element: rectEl, handles: []
 
345
  };
346
  setSelectedRect(true);
347
  updateRectUI(rectObj);
@@ -367,6 +393,7 @@
367
  saveRectBtn.disabled = true;
368
  } else {
369
  createHandles(rectObj);
 
370
  updateCoords(rectObj);
371
  saveRectBtn.disabled = false;
372
  }
@@ -397,6 +424,7 @@
397
  rectObj.top = newTop;
398
  updateRectUI(rectObj);
399
  updateHandles(rectObj);
 
400
  updateCoords(rectObj);
401
  }
402
  function onUp(ev) {
@@ -411,6 +439,7 @@
411
  }
412
  });
413
 
 
414
  function createHandles(rectObj) {
415
  if (rectObj.handles && rectObj.handles.length) {
416
  rectObj.handles.forEach(h=>h.remove());
@@ -428,11 +457,13 @@
428
  originX = e.clientX;
429
  originY = e.clientY;
430
  h._orig = {...rectObj};
 
431
  document.body.style.cursor = h.style.cursor;
432
  function onMove(ev) {
433
  resizeRect(rectObj, ev, h._orig);
434
  updateRectUI(rectObj);
435
  updateHandles(rectObj);
 
436
  updateCoords(rectObj);
437
  }
438
  function onUp(ev) {
@@ -462,16 +493,13 @@
462
  function updateHandles(rectObj) {
463
  const {width, height, handles} = rectObj;
464
  if(!handles || handles.length!==4) return;
465
- handles[0].style.left = '0px';
466
- handles[0].style.top = '0px';
467
- handles[1].style.left = width + 'px';
468
- handles[1].style.top = '0px';
469
- handles[2].style.left = width + 'px';
470
- handles[2].style.top = height + 'px';
471
- handles[3].style.left = '0px';
472
- handles[3].style.top = height + 'px';
473
  }
474
  function resizeRect(rectObj, e, orig) {
 
475
  let dx = e.clientX - originX;
476
  let dy = e.clientY - originY;
477
  let nd = {...orig};
@@ -507,12 +535,75 @@
507
  nd.height = Math.max(10, Math.min(nd.height, container.clientHeight - nd.top));
508
  Object.assign(rectObj, nd);
509
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
510
  function updateRectUI(rectObj) {
511
  rectObj.element.style.left = rectObj.left + 'px';
512
  rectObj.element.style.top = rectObj.top + 'px';
513
  rectObj.element.style.width = rectObj.width + 'px';
514
  rectObj.element.style.height = rectObj.height + 'px';
515
  rectObj.element.classList.add('selected');
 
 
 
516
  }
517
 
518
  function setSelectedRect(selected) {
@@ -528,18 +619,29 @@
528
 
529
  function getRectImageCoords(rectObj) {
530
  const {left:imgL, top:imgT, width:imgW, height:imgH} = dispImgInfo;
531
- const clamp = (v, min, max) => Math.max(min, Math.min(v, max));
532
- const dispPoints = [
533
- { x: clamp(rectObj.left, imgL, imgL+imgW), y: clamp(rectObj.top, imgT, imgT+imgH) },
534
- { x: clamp(rectObj.left+rectObj.width, imgL, imgL+imgW), y: clamp(rectObj.top, imgT, imgT+imgH) },
535
- { x: clamp(rectObj.left+rectObj.width, imgL, imgL+imgW), y: clamp(rectObj.top+rectObj.height, imgT, imgT+imgH) },
536
- { x: clamp(rectObj.left, imgL, imgL+imgW), y: clamp(rectObj.top+rectObj.height, imgT, imgT+imgH) }
 
 
 
 
 
537
  ];
538
- const toImgPx = (p) => [
539
- Math.round( (p.x - imgL) * imgNaturalWidth / imgW ),
540
- Math.round( (p.y - imgT) * imgNaturalHeight / imgH )
541
- ];
542
- return dispPoints.map(toImgPx);
 
 
 
 
 
 
543
  }
544
 
545
  function updateCoords(rectObj) {
@@ -553,18 +655,20 @@
553
  1. (${imgPoints[0][0]}, ${imgPoints[0][1]})<br>
554
  2. (${imgPoints[1][0]}, ${imgPoints[1][1]})<br>
555
  3. (${imgPoints[2][0]}, ${imgPoints[2][1]})<br>
556
- 4. (${imgPoints[3][0]}, ${imgPoints[3][1]})<br>`;
 
557
  }
558
 
559
  function updateMemoryList() {
560
  let html = '';
561
- memory.forEach((mem, idx) => {
562
  html += `<div class="memory-item" data-id="${mem.id}">
563
  <span class="memory-label"><b>${mem.filename}</b> :
564
  TL(${mem.coords[0][0]},${mem.coords[0][1]})
565
  TR(${mem.coords[1][0]},${mem.coords[1][1]})
566
  BR(${mem.coords[2][0]},${mem.coords[2][1]})
567
  BL(${mem.coords[3][0]},${mem.coords[3][1]})
 
568
  </span>
569
  <button class="memory-delete-btn" data-id="${mem.id}" title="Delete this memory">&times;</button>
570
  </div>`;
@@ -586,10 +690,10 @@
586
  alert("No memory to save.");
587
  return;
588
  }
589
- // Convert memory to required JSON format
590
- // { "filename": { "print_area": [[x,y], ...] }, ... }
591
  let outObj = {};
592
  memory.forEach(mem => {
 
593
  outObj[mem.filename] = {
594
  print_area: mem.coords
595
  };
@@ -599,7 +703,6 @@
599
  let filename = filenameInput.value.trim() || "templates.json";
600
  filename = filename.replace(/[\\\/:*?"<>|]/g, "_");
601
 
602
- // Prefer File System Access API if available
603
  if (window.showSaveFilePicker) {
604
  try {
605
  const opts = {
@@ -623,8 +726,6 @@
623
  }
624
  }
625
  }
626
-
627
- // Fallback: Classic download
628
  const blob = new Blob([jsonString], {type: "application/json"});
629
  const url = URL.createObjectURL(blob);
630
  const a = document.createElement('a');
 
33
  background: rgba(44,222,88,0.15);
34
  box-sizing: border-box;
35
  cursor: move;
36
+ transition: box-shadow 0.2s;
37
  }
38
  .rect.selected {
39
  border: 2px solid #f00;
40
  background: rgba(255,100,100,0.15);
41
  z-index: 10;
42
+ box-shadow: 0 0 10px #f00a;
43
  }
44
  .handle {
45
  position: absolute;
 
56
  .rect.selected .handle {
57
  display: block;
58
  }
59
+ .rotate-handle {
60
+ position: absolute;
61
+ left: 50%;
62
+ top: -32px;
63
+ width: 18px;
64
+ height: 18px;
65
+ margin-left: -9px;
66
+ background: #ff0;
67
+ border: 2px solid #f90;
68
+ border-radius: 50%;
69
+ cursor: pointer;
70
+ z-index: 30;
71
+ display: none;
72
+ box-shadow: 0 0 4px #0006;
73
+ }
74
+ .rect.selected .rotate-handle {
75
+ display: block;
76
+ }
77
  #coords {
78
  font-family: monospace;
79
  margin-top: 10px;
 
229
  let drawing = false;
230
  let moving = false;
231
  let resizing = false;
232
+ let rotating = false;
233
  let startX = 0, startY = 0;
234
  let offsetX = 0, offsetY = 0;
235
  let originX = 0, originY = 0;
236
  let activeHandle = null;
237
  let memory = [];
238
+ let rotateStartAngle = 0;
239
+ let rotateCenter = {x:0, y:0};
240
 
241
  function updateFileNameLabel(name) {
242
  fileNameLabel.textContent = name ? name : "";
 
337
  memory.push({
338
  filename: currentImageName || '',
339
  coords: imgPoints,
340
+ angle: rectObj.angle || 0,
341
  id: Date.now() + Math.random()
342
  });
343
  updateMemoryList();
 
352
  setSelectedRect(false);
353
  }
354
 
355
+ // ----- Main Rectangle Creation -----
356
  container.addEventListener('mousedown', (e) => {
357
  if (!imgLoaded) return;
358
  if (!drawing) return;
 
366
  container.appendChild(rectEl);
367
  rectObj = {
368
  left: startX, top: startY, width: 0, height: 0,
369
+ element: rectEl, handles: [],
370
+ angle: 0
371
  };
372
  setSelectedRect(true);
373
  updateRectUI(rectObj);
 
393
  saveRectBtn.disabled = true;
394
  } else {
395
  createHandles(rectObj);
396
+ createRotateHandle(rectObj);
397
  updateCoords(rectObj);
398
  saveRectBtn.disabled = false;
399
  }
 
424
  rectObj.top = newTop;
425
  updateRectUI(rectObj);
426
  updateHandles(rectObj);
427
+ updateRotateHandle(rectObj);
428
  updateCoords(rectObj);
429
  }
430
  function onUp(ev) {
 
439
  }
440
  });
441
 
442
+ // ---- Handles (resize) ----
443
  function createHandles(rectObj) {
444
  if (rectObj.handles && rectObj.handles.length) {
445
  rectObj.handles.forEach(h=>h.remove());
 
457
  originX = e.clientX;
458
  originY = e.clientY;
459
  h._orig = {...rectObj};
460
+ h._orig.angle = rectObj.angle;
461
  document.body.style.cursor = h.style.cursor;
462
  function onMove(ev) {
463
  resizeRect(rectObj, ev, h._orig);
464
  updateRectUI(rectObj);
465
  updateHandles(rectObj);
466
+ updateRotateHandle(rectObj);
467
  updateCoords(rectObj);
468
  }
469
  function onUp(ev) {
 
493
  function updateHandles(rectObj) {
494
  const {width, height, handles} = rectObj;
495
  if(!handles || handles.length!==4) return;
496
+ handles[0].style.left = '0px'; handles[0].style.top = '0px';
497
+ handles[1].style.left = width + 'px'; handles[1].style.top = '0px';
498
+ handles[2].style.left = width + 'px'; handles[2].style.top = height + 'px';
499
+ handles[3].style.left = '0px'; handles[3].style.top = height + 'px';
 
 
 
 
500
  }
501
  function resizeRect(rectObj, e, orig) {
502
+ // ignore angle for resizing, keep axis-aligned bounding box
503
  let dx = e.clientX - originX;
504
  let dy = e.clientY - originY;
505
  let nd = {...orig};
 
535
  nd.height = Math.max(10, Math.min(nd.height, container.clientHeight - nd.top));
536
  Object.assign(rectObj, nd);
537
  }
538
+
539
+ // ---- Rotation Handle ----
540
+ function createRotateHandle(rectObj) {
541
+ if(rectObj.rotateHandle) rectObj.rotateHandle.remove();
542
+ let h = document.createElement('div');
543
+ h.className = 'rotate-handle';
544
+ rectObj.element.appendChild(h);
545
+ rectObj.rotateHandle = h;
546
+ updateRotateHandle(rectObj);
547
+
548
+ h.addEventListener('mousedown', function(e) {
549
+ rotating = true;
550
+ const rectC = container.getBoundingClientRect();
551
+ // center of rectangle in container coords
552
+ let cx = rectObj.left + rectObj.width/2;
553
+ let cy = rectObj.top + rectObj.height/2;
554
+ rotateCenter = {x: cx, y: cy};
555
+ // 角度初期値
556
+ const mx = e.clientX - rectC.left, my = e.clientY - rectC.top;
557
+ rotateStartAngle = Math.atan2(my - cy, mx - cx) * 180/Math.PI - (rectObj.angle||0);
558
+ document.body.style.cursor = "crosshair";
559
+ function onMove(ev) {
560
+ const mx2 = ev.clientX - rectC.left, my2 = ev.clientY - rectC.top;
561
+ let angle = Math.atan2(my2 - cy, mx2 - cx) * 180/Math.PI - rotateStartAngle;
562
+ // angleを0-360に正規化
563
+ angle = ((angle % 360) + 360) % 360;
564
+ rectObj.angle = angle;
565
+ updateRectUI(rectObj);
566
+ updateHandles(rectObj);
567
+ updateRotateHandle(rectObj);
568
+ updateCoords(rectObj);
569
+ }
570
+ function onUp(ev) {
571
+ rotating = false;
572
+ document.body.style.cursor = "";
573
+ document.removeEventListener('mousemove', onMove);
574
+ document.removeEventListener('mouseup', onUp);
575
+ }
576
+ document.addEventListener('mousemove', onMove);
577
+ document.addEventListener('mouseup', onUp);
578
+ e.stopPropagation();
579
+ });
580
+ }
581
+ function updateRotateHandle(rectObj) {
582
+ if(!rectObj.rotateHandle) return;
583
+ // 回転ハンドルの位置は矩形の中心上方向に固定
584
+ let w = rectObj.width, h = rectObj.height;
585
+ let angle = rectObj.angle || 0;
586
+ // 中心
587
+ let cx = w/2, cy = h/2;
588
+ // ハンドルの相対座標(矩形の中心から上へ)
589
+ let r = Math.max(w, h)/2 + 24;
590
+ let rad = (-90 + angle) * Math.PI / 180.0;
591
+ let hx = cx + r * Math.cos(rad);
592
+ let hy = cy + r * Math.sin(rad);
593
+ rectObj.rotateHandle.style.left = `${hx}px`;
594
+ rectObj.rotateHandle.style.top = `${hy}px`;
595
+ }
596
+
597
+ // --- 矩形のUI更新: 回転も反映
598
  function updateRectUI(rectObj) {
599
  rectObj.element.style.left = rectObj.left + 'px';
600
  rectObj.element.style.top = rectObj.top + 'px';
601
  rectObj.element.style.width = rectObj.width + 'px';
602
  rectObj.element.style.height = rectObj.height + 'px';
603
  rectObj.element.classList.add('selected');
604
+ rectObj.angle = typeof rectObj.angle === "number" ? rectObj.angle : 0;
605
+ rectObj.element.style.transform = `rotate(${rectObj.angle||0}deg)`;
606
+ rectObj.element.style.transformOrigin = "50% 50%";
607
  }
608
 
609
  function setSelectedRect(selected) {
 
619
 
620
  function getRectImageCoords(rectObj) {
621
  const {left:imgL, top:imgT, width:imgW, height:imgH} = dispImgInfo;
622
+ let x = rectObj.left, y = rectObj.top, w = rectObj.width, h = rectObj.height;
623
+ // 中心
624
+ let cx = x + w/2, cy = y + h/2;
625
+ // 角度
626
+ let angle = ((typeof rectObj.angle === "number" ? rectObj.angle : 0) * Math.PI) / 180;
627
+ // 各頂点
628
+ let box = [
629
+ [x, y ], // TL
630
+ [x+w, y ], // TR
631
+ [x+w, y+h ], // BR
632
+ [x, y+h ] // BL
633
  ];
634
+ // 回転適用
635
+ let rot = box.map(([px,py]) => {
636
+ let dx = px - cx, dy = py - cy;
637
+ let rx = dx * Math.cos(angle) - dy * Math.sin(angle) + cx;
638
+ let ry = dx * Math.sin(angle) + dy * Math.cos(angle) + cy;
639
+ // img領域→元画像座標
640
+ let ix = Math.round((rx - imgL) * imgNaturalWidth / imgW);
641
+ let iy = Math.round((ry - imgT) * imgNaturalHeight / imgH);
642
+ return [ix, iy];
643
+ });
644
+ return rot;
645
  }
646
 
647
  function updateCoords(rectObj) {
 
655
  1. (${imgPoints[0][0]}, ${imgPoints[0][1]})<br>
656
  2. (${imgPoints[1][0]}, ${imgPoints[1][1]})<br>
657
  3. (${imgPoints[2][0]}, ${imgPoints[2][1]})<br>
658
+ 4. (${imgPoints[3][0]}, ${imgPoints[3][1]})<br>
659
+ [angle: ${rectObj.angle ? rectObj.angle.toFixed(1) : 0}°]`;
660
  }
661
 
662
  function updateMemoryList() {
663
  let html = '';
664
+ memory.forEach((mem) => {
665
  html += `<div class="memory-item" data-id="${mem.id}">
666
  <span class="memory-label"><b>${mem.filename}</b> :
667
  TL(${mem.coords[0][0]},${mem.coords[0][1]})
668
  TR(${mem.coords[1][0]},${mem.coords[1][1]})
669
  BR(${mem.coords[2][0]},${mem.coords[2][1]})
670
  BL(${mem.coords[3][0]},${mem.coords[3][1]})
671
+ [angle: ${mem.angle || 0}°]
672
  </span>
673
  <button class="memory-delete-btn" data-id="${mem.id}" title="Delete this memory">&times;</button>
674
  </div>`;
 
690
  alert("No memory to save.");
691
  return;
692
  }
693
+ // 画像名に「bases/」を付けないように修正
 
694
  let outObj = {};
695
  memory.forEach(mem => {
696
+ // ファイル名そのままをキーに
697
  outObj[mem.filename] = {
698
  print_area: mem.coords
699
  };
 
703
  let filename = filenameInput.value.trim() || "templates.json";
704
  filename = filename.replace(/[\\\/:*?"<>|]/g, "_");
705
 
 
706
  if (window.showSaveFilePicker) {
707
  try {
708
  const opts = {
 
726
  }
727
  }
728
  }
 
 
729
  const blob = new Blob([jsonString], {type: "application/json"});
730
  const url = URL.createObjectURL(blob);
731
  const a = document.createElement('a');