jonigata commited on
Commit
e9a85fd
1 Parent(s): 5c79e21

add examples

Browse files
Files changed (2) hide show
  1. js/poseTweak.js +862 -0
  2. static/sample1.png +0 -0
js/poseTweak.js ADDED
@@ -0,0 +1,862 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var canvas = null;
2
+ var ctx = null;
3
+ var offscreen = null;
4
+ var initialPoseField = null;
5
+ var canvasBg = null;
6
+ var debugLines = [];
7
+
8
+ const wheelDisplayTime = 500;
9
+
10
+ const colors = [[255, 0, 0], [255, 85, 0], [255, 170, 0], [255, 255, 0], [170, 255, 0], [85, 255, 0], [0, 255, 0],
11
+ [0, 255, 85], [0, 255, 170], [0, 255, 255], [0, 170, 255], [0, 85, 255], [0, 0, 255], [85, 0, 255],
12
+ [170, 0, 255], [255, 0, 255], [255, 0, 170], [255, 0, 85]];
13
+
14
+ function cutOffLimb(pose, cutOffIndex) {
15
+ console.log(`cutOffLimb: ${cutOffIndex}`);
16
+ // 末端ノードの座標を削除する
17
+ var newPose = deepCopy(pose);
18
+ for (let i = 0; i < 18; i++) {
19
+ if (newPose[i] == null) {continue;}
20
+ // ルートまで検索し、その間にcuttOffIndexがあれば削除
21
+ var curr = i;
22
+ while (curr !== 1) {
23
+ console.log(`checking: ${i} -> ${curr}`);
24
+ let parent = findParentNodeIndex(curr);
25
+ if (parent === cutOffIndex) {
26
+ console.log(`cutOffLimb: ${i} -> ${cutOffIndex}`);
27
+ newPose[i] = null;
28
+ break;
29
+ }
30
+ curr = parent;
31
+ }
32
+ }
33
+ return newPose;
34
+ }
35
+
36
+ function repairPose(sourcePose) {
37
+ // TODO: ループには対応してないかも
38
+ var pose = sourcePose;
39
+ var newPose = new Array(18)
40
+ for (var k = 0; k < 3; k++) {
41
+ var processed = 0; // イテレーション用
42
+ for (let i = 0; i < 18; i++) {
43
+ if (pose[i] == null) {
44
+ let parent = findParentNodeIndex(i);
45
+ if (parent === -1) {continue;} // あり得ない
46
+ if (pose[parent] == null) {
47
+ console.log(`repair failed(A): ${i} -> parent loss`);
48
+ continue;
49
+ }
50
+
51
+ // サンプルデータから引っ張ってくる
52
+ var v = sampleCandidateSource[i].map((x, j) => x - sampleCandidateSource[parent][j]);
53
+ newPose[i] = pose[parent].map((x, j) => x + v[j]);
54
+ console.log(`repaired: ${i} -> ${newPose[newPose.length - 1]}`);
55
+ processed++;
56
+ } else {
57
+ newPose[i] = pose[i].map(x => x);
58
+ }
59
+ }
60
+ if (processed === 0) {break;}
61
+ pose = newPose;
62
+ }
63
+ return newPose;
64
+ }
65
+
66
+ function deepCopy(arr) {
67
+ return JSON.parse(JSON.stringify(arr));
68
+ }
69
+
70
+ function distSq(p0, p1) {
71
+ return (p0[0] - p1[0]) ** 2 + (p0[1] - p1[1]) ** 2;
72
+ }
73
+
74
+ // poseDataの形式:[[[x1, y1], [x2, y2], ...],[[x3, y3], [x4, y4], ...], ...]
75
+ // 各要素が人間
76
+ // 人間の各要素が関節
77
+
78
+ function poseDataToCandidateAndSubset(poseData) {
79
+ let candidate = [];
80
+ let subset = [];
81
+ for (let i = 0; i < poseData.length; i++) {
82
+ let person = poseData[i];
83
+ let subsetElement = [];
84
+ for (let j = 0; j < person.length; j++) {
85
+ candidate.push(person[j]);
86
+ subsetElement.push(candidate.length - 1);
87
+ }
88
+ subset.push(subsetElement);
89
+ }
90
+ return [candidate, subset];
91
+ }
92
+
93
+ // サンプルデータ
94
+ const sampleOffset = [0, -70];
95
+ const sampleCandidateSource = [[235, 158],[234, 220],[193, 222],[138, 263],[89, 308],[276, 220],[325, 264],[375, 309],[207, 347],[203, 433],[199, 523],[261, 347],[262, 430],[261, 522],[227, 148],[245, 148],[208, 158],[258, 154]].map((p) => [p[0] + sampleOffset[0], p[1] + sampleOffset[1]]);
96
+ const sampleSubsetElementSource = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17];
97
+
98
+ // const sampleCandidateSource = [[618.00, 0.00], [618.00, 44.00], [304.00, 81.00], [482.00, 96.00], [66.00, 270.00], [171.00, 280.00], [618.00, 82.00], [307.00, 112.00], [460.00, 143.00], [0.00, 301.00], [65.00, 301.00], [172.00, 303.00], [584.00, 86.00], [275.00, 119.00], [420.00, 139.00], [0.00, 301.00], [41.00, 301.00], [144.00, 303.00], [544.00, 131.00], [348.00, 139.00], [262.00, 160.00], [0.00, 337.00], [52.00, 339.00], [130.00, 348.00], [570.00, 175.00], [283.00, 177.00], [78.00, 338.00], [172.00, 380.00], [651.00, 78.00], [338.00, 111.00], [505.00, 144.00], [92.00, 301.00], [198.00, 305.00], [661.00, 132.00], [349.00, 156.00], [541.00, 179.00], [106.00, 336.00], [203.00, 348.00], [305.00, 159.00], [665.00, 160.00], [563.00, 192.00], [80.00, 343.00], [181.00, 385.00], [614.00, 205.00], [291.00, 220.00], [432.00, 320.00], [152.00, 372.00], [43.00, 380.00], [0.00, 386.00], [623.00, 281.00], [306.00, 290.00], [92.00, 357.00], [509.00, 434.00], [304.00, 357.00], [622.00, 368.00], [47.00, 394.00], [0.00, 395.00], [142.00, 405.00], [535.00, 565.00], [655.00, 200.00], [337.00, 217.00], [467.00, 322.00], [191.00, 372.00], [83.00, 375.00], [344.00, 282.00], [655.00, 282.00], [103.00, 343.00], [237.00, 368.00], [22.00, 377.00], [0.00, 379.00], [460.00, 459.00], [305.00, 352.00], [638.00, 355.00], [0.00, 401.00], [110.00, 412.00], [411.00, 570.00], [608.00, 0.00], [608.00, 40.00], [297.00, 75.00], [469.00, 84.00], [0.00, 261.00], [58.00, 263.00], [165.00, 275.00], [625.00, 0.00], [625.00, 39.00], [309.00, 74.00], [486.00, 83.00], [71.00, 264.00], [180.00, 276.00], [599.00, 0.00], [599.00, 44.00], [284.00, 80.00], [440.00, 93.00], [48.00, 271.00], [0.00, 272.00], [157.00, 277.00], [634.00, 0.00], [633.00, 41.00], [319.00, 77.00], [79.00, 269.00], [190.00, 277.00]];
99
+ // const sampleSubsetElementSource = [1.00,6.00,12.00,18.00,24.00,28.00,33.00,39.00,43.00,49.00,54.00,59.00,65.00,72.00,77.00,84.00,90.00,97.00,32.98,18.00],[5.00,11.00,17.00,23.00,27.00,32.00,37.00,42.00,46.00,-1.00,-1.00,62.00,67.00,-1.00,82.00,88.00,95.00,100.00,25.45,15.00],[4.00,10.00,16.00,22.00,26.00,31.00,36.00,41.00,47.00,51.00,57.00,63.00,66.00,74.00,81.00,87.00,93.00,99.00,26.97,18.00],[3.00,8.00,14.00,19.00,25.00,30.00,35.00,40.00,45.00,52.00,58.00,61.00,70.00,75.00,79.00,86.00,92.00,-1.00,30.45,17.00],[2.00,7.00,13.00,20.00,-1.00,29.00,34.00,38.00,44.00,50.00,53.00,60.00,64.00,71.00,78.00,85.00,91.00,98.00,27.89,17.00],[0.00,-1.00,-1.00,-1.00,-1.00,-1.00,-1.00,-1.00,-1.00,-1.00,-1.00,-1.00,-1.00,-1.00,76.00,83.00,-1.00,96.00,3.33,4.00];
100
+
101
+ function makePoseFromCandidateAndSubsetElement(candidate, subsetElement) {
102
+ var pose = [];
103
+ for (let j = 0 ; j < 18; j++) {
104
+ let i = subsetElement[j];
105
+ pose.push(i < 0 || candidate[i] == null ? null : candidate[i].map((x)=>x));
106
+ }
107
+ return pose;
108
+ }
109
+
110
+ function makePoseDataFromCandidateAndSubset(candidate, subset) {
111
+ return subset.map(subsetElement => makePoseFromCandidateAndSubsetElement(candidate, subsetElement));
112
+ }
113
+
114
+ function addPerson() {
115
+ var dx = Math.random() * 100;
116
+ var dy = Math.random() * 100;
117
+
118
+ poseData.push(
119
+ makePoseFromCandidateAndSubsetElement(
120
+ sampleCandidateSource.map(point => [point[0] + dx, point[1] + dy]),
121
+ sampleSubsetElementSource));
122
+
123
+ addHistory();
124
+ Redraw();
125
+ }
126
+
127
+ function removePerson(personIndex) {
128
+ poseData.splice(personIndex, 1);
129
+ addHistory();
130
+ Redraw();
131
+ }
132
+
133
+ function repairPerson(personIndex) {
134
+ poseData[personIndex] = repairPose(poseData[personIndex]);
135
+ addHistory();
136
+ Redraw();
137
+ }
138
+
139
+ function cutOffPersonLimb(personIndex, limbIndex) {
140
+ poseData[personIndex] = cutOffLimb(poseData[personIndex], limbIndex);
141
+ console.log(poseData[personIndex]);
142
+ console.log(poseData);
143
+ addHistory();
144
+ Redraw();
145
+ }
146
+
147
+ // ドラッグ中の各キーが押されているかどうかのフラグ
148
+ var keyDownFlags = {};
149
+ // マウスカーソル
150
+ var mouseCursor = [-1, -1];
151
+
152
+ function cross(lhs, rhs) {return lhs[0] * rhs[1] - lhs[1] * rhs[0];}
153
+ function dot(lhs, rhs) {return lhs[0] * rhs[0] + lhs[1] * rhs[1];}
154
+ function directedAngleTo(lhs, rhs) {return Math.atan2(cross(lhs, rhs), dot(lhs, rhs));}
155
+
156
+ function isMouseOnCanvas() {
157
+ // mouseCursorがcanvasの範囲内にあるかどうかを判定
158
+ var rect = canvas.getBoundingClientRect();
159
+ var f = 0 <= mouseCursor[0] && mouseCursor[0] <= rect.width && 0 <= mouseCursor[1] && mouseCursor[1] <= rect.height;
160
+ return f;
161
+ }
162
+
163
+ function clearCanvas() {
164
+ var w = canvas.width;
165
+ var h = canvas.height;
166
+ ctx.fillStyle = 'black';
167
+ ctx.fillRect(0, 0, w, h);
168
+ }
169
+
170
+ function drawBackground() {
171
+ if (canvasBg != null) {
172
+ ctx.drawImage(canvasBg, 0, 0);
173
+ }
174
+ }
175
+
176
+ function resizeCanvas(width, height) {
177
+ canvas.width = width ? width : canvas.width;
178
+ canvas.height = height ? height : canvas.height;
179
+ Redraw();
180
+ }
181
+
182
+ function calculateCenter(shape) {
183
+ var center = shape.reduce(function(acc, point) {
184
+ if (point === null) {
185
+ acc[0] += point[0];
186
+ acc[1] += point[1];
187
+ }
188
+ return acc;
189
+ }, [0, 0]);
190
+ center[0] /= shape.length;
191
+ center[1] /= shape.length;
192
+ return center;
193
+ }
194
+
195
+ // v2d -> v3d
196
+ function rotateX(vector, angle) {
197
+ var x = vector[0];
198
+ var y = vector[1];
199
+ var z = 0;
200
+
201
+ // X軸に対して回転する
202
+ var x1 = x;
203
+ var y1 = y * Math.cos(angle) - z * Math.sin(angle);
204
+ var z1 = y * Math.sin(angle) + z * Math.cos(angle);
205
+
206
+ return [x1, y1, z1];
207
+ }
208
+
209
+ // v2d -> v3d
210
+ function rotateY(vector, angle) {
211
+ var x = vector[0];
212
+ var y = vector[1];
213
+ var z = 0;
214
+
215
+ // Y軸に対して回転する
216
+ var x1 = x * Math.cos(angle) + z * Math.sin(angle);
217
+ var y1 = y;
218
+ var z1 = -x * Math.sin(angle) + z * Math.cos(angle);
219
+
220
+ return [x1, y1, z1];
221
+ }
222
+
223
+ // v3d -> v2d
224
+ function perspectiveProjection(vector, cameraDistance) {
225
+ var x = vector[0];
226
+ var y = vector[1];
227
+ var z = vector[2];
228
+
229
+ if (z === 0) {
230
+ return [x, y];
231
+ }
232
+
233
+ var scale = cameraDistance / (cameraDistance - z);
234
+ var x1 = x * scale;
235
+ var y1 = y * scale;
236
+
237
+ return [x1, y1];
238
+ }
239
+
240
+ // v2d -> v3d
241
+ function rotateAndProject(f, p, c, angle) {
242
+ var v = [p[0] - c[0], p[1] - c[1]];
243
+ var v1 = f(v, angle);
244
+ var v2 = perspectiveProjection(v1, 500);
245
+ return [v2[0] + c[0], v2[1] + c[1]];
246
+ }
247
+
248
+ function drawPicture() {
249
+ ctx.globalAlpha = 1.0;
250
+ // if (picture != null) {
251
+ //var [w, h] = [canvas.width, canvas.height];
252
+ //var [pw, ph] = [picture.width, picture.height];
253
+ //var [x, y] = [(w-pw)/2, (h-ph)/2];
254
+ // ctx.drawImage(picture, sampleOffset[0], sampleOffset[1]);
255
+ // ctx.drawImage(picture, 0, 0);
256
+ // }
257
+
258
+ ctx.globalAlpha = 1.0;
259
+ if (offscreen != null) {
260
+ ctx.drawImage(offscreen, 0, 0);
261
+ }
262
+ }
263
+
264
+ function drawBodyPose() {
265
+ drawBodyPoseTo(ctx, poseData);
266
+ }
267
+
268
+ function drawBodyPoseTo(ctx, poseData) {
269
+ let stickWidth = 4;
270
+ let imageSize = Math.min(canvas.width, canvas.height);
271
+ stickWidth *= imageSize / 512;
272
+
273
+ ctx.globalAlpha = 0.6;
274
+
275
+ // edge
276
+ for (let i = 0; i < poseData.length; i++) {
277
+ const pose = poseData[i];
278
+
279
+ for (let j = 0; j < 17; j++) {
280
+ const p = pose[limbSeq[j][0]];
281
+ const q = pose[limbSeq[j][1]];
282
+ if (p == null || q == null) continue;
283
+ const [X0, Y0] = p;
284
+ const [X1, Y1] = q;
285
+ let angle = Math.atan2(Y1 - Y0, X1 - X0);
286
+ let magnitude = ((X0 - X1) ** 2 + (Y0 - Y1) ** 2) ** 0.5
287
+ let polygon = new Path2D();
288
+ polygon.ellipse((X0+X1)/2, (Y0+Y1)/2, magnitude / 2, stickWidth, angle, 0, 2 * Math.PI);
289
+ //ctx.fillStyle = `rgb(${colors[j].join(',')})`;
290
+ ctx.fillStyle = 'gray';
291
+ ctx.fill(polygon);
292
+ }
293
+ }
294
+
295
+ ctx.globalAlpha = 1.0;
296
+
297
+ // node
298
+ for (let i = 0; i < poseData.length; i++) {
299
+ const pose = poseData[i];
300
+
301
+ ctx.font = '12px serif';
302
+ for (let j = 0; j < 18; j++) {
303
+ const p = pose[j];
304
+ if (p == null) continue;
305
+ const [x, y] = p;
306
+ ctx.beginPath();
307
+ ctx.arc(x, y, stickWidth, 0, 2 * Math.PI);
308
+ ctx.fillStyle = `rgb(${colors[j].join(',')})`;
309
+ ctx.fill();
310
+ // ctx.fillStyle = 'rgb(255,255,255)'
311
+ // ctx.fillText(j, x-3, y+4);
312
+ }
313
+ }
314
+ }
315
+
316
+ let lastWheeling = 0;
317
+
318
+ function drawUI() {
319
+ if (keyDownFlags['Space'] || keyDownFlags['BracketLeft'] || keyDownFlags['BracketRight'] ||
320
+ new Date().getTime() - lastWheeling < wheelDisplayTime) {
321
+ ctx.beginPath();
322
+ ctx.lineWidth=4;
323
+ ctx.arc(mouseCursor[0], mouseCursor[1], dragRange, 0, 2 * Math.PI);
324
+ ctx.strokeStyle = 'rgb(255,255,255)';
325
+ ctx.stroke();
326
+ }
327
+
328
+ if (isDragging && (dragMode == "rotate" || dragMode == "rotate2")) {
329
+ ctx.beginPath();
330
+ ctx.lineWidth=1;
331
+ ctx.strokeStyle = 'rgb(255,255,255)';
332
+ ctx.moveTo(dragStart[0], dragStart[1]);
333
+ ctx.lineTo(dragStart[0]+rotateBaseVector[0], dragStart[1]+rotateBaseVector[1]);
334
+ ctx.stroke();
335
+ }
336
+
337
+ ctx.globalAlpha = 0.1;
338
+
339
+ for (let i = 0 ; i < debugLines.length ; i++) {
340
+ const p = debugLines[i];
341
+ const l = 50;
342
+ ctx.beginPath();
343
+ ctx.lineWidth= 2;
344
+ ctx.strokeStyle = `rgb(${colors[i].join(',')})`;
345
+ ctx.moveTo(p[0][0], p[0][1]);
346
+ ctx.lineTo(p[0][0]+p[1][0] * l, p[0][1]+p[1][1] * l);
347
+ ctx.stroke();
348
+ }
349
+ ctx.globalAlpha = 1.0;
350
+
351
+ let operationTextFlags = {
352
+ "Space": "Range Move",
353
+ "AltLeft": "Body Move",
354
+ "AltRight": "Body Move",
355
+ "ControlLeft": "Scale",
356
+ "ControlRight": "Scale",
357
+ "ShiftLeft": "Rotate",
358
+ "ShiftRight": "Rotate",
359
+ "KeyQ": "CutOff",
360
+ "KeyD": "Delete",
361
+ "KeyX": "X-Axis",
362
+ "KeyC": "Y-Axis",
363
+ "KeyR": "Repair",
364
+ }
365
+
366
+ // operationTextFlagsに含まれるものがkeyDownFlagsに含まれるばあい、そのキーの文字列を取得
367
+ let activeOperations = Object.keys(operationTextFlags).filter(key => keyDownFlags[key]);
368
+ if (activeOperations.length > 0) {
369
+ // 左上に表示
370
+ ctx.font = '20px serif';
371
+ ctx.fillStyle = 'rgb(255,255,255)';
372
+ ctx.fillText(operationTextFlags[activeOperations[0]], 10, 30);
373
+ }
374
+
375
+ // draw mouse cursor position at right bottom
376
+ ctx.font = '20px serif';
377
+ ctx.fillStyle = 'rgb(255,255,255)';
378
+ ctx.fillText(`(${mouseCursor[0]}, ${mouseCursor[1]})`, canvas.width-200, canvas.height-10);
379
+
380
+ }
381
+
382
+ function Redraw() {
383
+ clearCanvas();
384
+ drawBackground();
385
+ drawPicture();
386
+ drawBodyPose();
387
+ drawUI();
388
+ }
389
+
390
+ function getNearestNode(p) {
391
+ let minDistSq = Infinity;
392
+ let personIndex = -1;
393
+ let nodeIndex = -1;
394
+ for (let i = 0; i < poseData.length; i++) {
395
+ const pose = poseData[i];
396
+ for (let j = 0; j < pose.length; j++) {
397
+ const q = pose[j];
398
+ if (q == null) continue;
399
+ const d = distSq(p, q);
400
+ if (d < minDistSq) {
401
+ minDistSq = d;
402
+ personIndex = i;
403
+ nodeIndex = j;
404
+ }
405
+ }
406
+ }
407
+ return [personIndex, nodeIndex, Math.sqrt(minDistSq)];
408
+ }
409
+
410
+ let dragRange = 64;
411
+ let dragRangeDelta = 16;
412
+
413
+ // ドラッグ中に座標を保持するための変数
414
+ let isDragging = false;
415
+ let dragStart = [0, 0];
416
+ let dragPersonIndex = -1;
417
+ let dragMarks = [];
418
+ let dragMode = "";
419
+ let rotateBaseVector = null;
420
+ let history = [];
421
+ let historyIndex = 0;
422
+
423
+ function clearHistory() {
424
+ history = [];
425
+ historyIndex = 0;
426
+ }
427
+
428
+ function addHistory() {
429
+ history = history.slice(0, historyIndex);
430
+ history.push(JSON.parse(JSON.stringify(poseData)));
431
+ historyIndex = history.length;
432
+ }
433
+
434
+ function undo() {
435
+ if (1 < historyIndex) {
436
+ historyIndex--;
437
+ poseData = deepCopy(history[historyIndex-1]);
438
+ Redraw();
439
+ }
440
+ }
441
+
442
+ function redo() {
443
+ if (historyIndex < history.length) {
444
+ historyIndex++;
445
+ poseData = deepCopy(history[historyIndex-1]);
446
+ Redraw();
447
+ }
448
+ }
449
+
450
+ function fetchLatestPoseData() {
451
+ return history[historyIndex-1];
452
+ }
453
+
454
+ function getCanvasPosition(event) {
455
+ const rect = canvas.getBoundingClientRect();
456
+ const x = Math.floor(event.clientX - rect.left);
457
+ const y = Math.floor(event.clientY - rect.top);
458
+ return [x, y];
459
+ }
460
+
461
+ function forEachMarkedNodes(fn) {
462
+ for (let i = 0; i < dragMarks.length; i++) {
463
+ for (let j = 0; j < dragMarks[i].length; j++) {
464
+ if (dragMarks[i][j]) {
465
+ fn(i, j, poseData[i][j]);
466
+ }
467
+ }
468
+ }
469
+ }
470
+
471
+ // Canvas要素上でマウスが押された場合に呼び出される関数
472
+ function handleMouseDown(event) {
473
+ const p = getCanvasPosition(event);
474
+ const [personIndex, nodeIndex, minDist] = getNearestNode(p);
475
+
476
+ if (keyDownFlags["KeyD"]) {removePerson(personIndex);return;}
477
+ if (keyDownFlags["KeyR"]) {repairPerson(personIndex);return;}
478
+
479
+ if (keyDownFlags["KeyQ"] && minDist < 16) {
480
+ console.log("pressed KeyQ");
481
+ cutOffPersonLimb(personIndex, nodeIndex);
482
+ return;
483
+ }
484
+
485
+ // ドラッグ処理の開始
486
+ dragStart = p;
487
+ dragMarks = poseData.map(pose => pose.map(node => false));
488
+
489
+ if (event.altKey || event.ctrlKey || event.shiftKey ||
490
+ keyDownFlags["KeyX"] || keyDownFlags["KeyC"]) {
491
+ // dragMarksを設定
492
+ dragMarks[personIndex] =
493
+ poseData[personIndex].map((node) => node != null);
494
+ isDragging = true;
495
+ if (event.altKey) {
496
+ dragMode = "move";
497
+ } else if (event.ctrlKey) {
498
+ dragMode = "scale";
499
+ } else if (event.shiftKey) {
500
+ dragMode = "rotate";
501
+ rotateBaseVector = [0, 0];
502
+ } else if (keyDownFlags["KeyX"]) {
503
+ dragMode = "rotateX";
504
+ } else if (keyDownFlags["KeyC"]) {
505
+ dragMode = "rotateY";
506
+ }
507
+ } else if (keyDownFlags["Space"]) {
508
+ dragMarks[personIndex] =
509
+ poseData[personIndex].map(
510
+ (node) => node != null && distSq(p, node) < dragRange ** 2);
511
+ isDragging = dragMarks[personIndex].some((mark) => mark);
512
+ dragMode = "move";
513
+ } else if (minDist < 16) {
514
+ dragMarks[personIndex][nodeIndex] = true;
515
+ isDragging = true;
516
+ dragMode = "move";
517
+ }
518
+ }
519
+
520
+ // Canvas要素上でマウスが動いた場合に呼び出される関数
521
+ function handleMouseMove(event) {
522
+ mouseCursor = getCanvasPosition(event);
523
+ if (isDragging) {
524
+ const p = getCanvasPosition(event);
525
+ const dragOffset = [p[0] - dragStart[0], p[1] - dragStart[1]];
526
+ const latestPoseData = fetchLatestPoseData();
527
+
528
+ if (dragMode == "scale") {
529
+ // 拡大縮小
530
+ let xScale = 1 + dragOffset[0] / canvas.width;
531
+ let yScale = 1 + dragOffset[0] / canvas.height;
532
+ forEachMarkedNodes((i, j, node) => {
533
+ const lp = latestPoseData[i][j];
534
+ node[0] = (lp[0] - dragStart[0]) * xScale + dragStart[0];
535
+ node[1] = (lp[1] - dragStart[1]) * yScale + dragStart[1];
536
+ });
537
+ } else if (dragMode == "rotate") {
538
+ rotateBaseVector = dragOffset;
539
+ if (!event.shiftKey) {
540
+ dragMode = "rotate2";
541
+ }
542
+ } else if (dragMode == "rotate2") {
543
+ // 回転
544
+ let angle = directedAngleTo(rotateBaseVector, dragOffset);
545
+ forEachMarkedNodes((i, j, node) => {
546
+ const lp = latestPoseData[i][j];
547
+ let x = lp[0] - dragStart[0];
548
+ let y = lp[1] - dragStart[1];
549
+ let sin = Math.sin(angle);
550
+ let cos = Math.cos(angle);
551
+ node[0] = x * cos - y * sin + dragStart[0];
552
+ node[1] = x * sin + y * cos + dragStart[1];
553
+ });
554
+ } else if (dragMode == "rotateX") {
555
+ const center = dragStart;
556
+ const angle = dragOffset[1] / -40;
557
+ forEachMarkedNodes((i, j, node) => {
558
+ const lp = latestPoseData[i][j];
559
+ const np = rotateAndProject(rotateX, lp, center, angle);
560
+ node[0] = np[0];
561
+ node[1] = np[1];
562
+ });
563
+ } else if (dragMode == "rotateY") {
564
+ const center = dragStart;
565
+ const angle = dragOffset[0] / 40;
566
+ forEachMarkedNodes((i, j, node) => {
567
+ const lp = latestPoseData[i][j];
568
+ const np = rotateAndProject(rotateY, lp, center, angle);
569
+ node[0] = np[0];
570
+ node[1] = np[1];
571
+ });
572
+ } else if (dragMode == "move") {
573
+ // 移動
574
+ forEachMarkedNodes((i, j, node) => {
575
+ const lp = latestPoseData[i][j];
576
+ node[0] = lp[0] + dragOffset[0];
577
+ node[1] = lp[1] + dragOffset[1];
578
+ });
579
+ }
580
+
581
+ // animatePicture(document.getElementById("canvas4"),[canvas.width, canvas.height], history[0][0], poseData[0], pictureImageData, initialPoseField);
582
+ }
583
+
584
+ Redraw();
585
+ }
586
+
587
+ function handleMouseUp(event) {
588
+ console.profile("animate");
589
+ animatePicture(
590
+ document.getElementById("canvas4"),
591
+ [canvas.width, canvas.height], history[0][0], poseData[0], pictureImageData, initialPoseField);
592
+ console.profileEnd("animate");
593
+ isDragging = false;
594
+ addHistory();
595
+ Redraw();
596
+ }
597
+
598
+ function handleMouseLeave(event) {
599
+ mouseCursor = [-1,-1];
600
+ if (isDragging) {
601
+ handleMouseUp(event);
602
+ }
603
+ keyDownFlags = {};
604
+ }
605
+
606
+ function ModifyDragRange(delta) { dragRange = Math.max(dragRangeDelta, Math.min(512, dragRange + delta)); }
607
+
608
+ document.addEventListener('wheel', function(event) {
609
+ if (!isMouseOnCanvas()) {return;}
610
+ if (!event.altKey && !keyDownFlags['Space']) {return;}
611
+
612
+ event.preventDefault();
613
+ const deltaY = event.deltaY;
614
+ if (deltaY < 0) {ModifyDragRange(-dragRangeDelta);}
615
+ if (0 < deltaY) {ModifyDragRange(dragRangeDelta);}
616
+ lastWheeling = new Date().getTime();
617
+ Redraw();
618
+ window.setTimeout(function() { Redraw(); }, wheelDisplayTime+10);
619
+ }, {passive: false});
620
+
621
+ document.addEventListener("keydown", (event) => {
622
+ if (!isMouseOnCanvas()) {return;}
623
+
624
+ if (event.code == "BracketLeft") { ModifyDragRange(-dragRangeDelta); }
625
+ if (event.code == "BracketRight") { ModifyDragRange(dragRangeDelta); }
626
+ keyDownFlags[event.code] = true;
627
+ Redraw();
628
+ event.preventDefault();
629
+ });
630
+ document.addEventListener("keyup", (event) => {
631
+ if (!isMouseOnCanvas()) {return;}
632
+
633
+ keyDownFlags[event.code] = false;
634
+ if (event.ctrlKey && event.code == "KeyE") {
635
+ addPerson();
636
+ } else if (event.ctrlKey && event.code == "KeyZ") {
637
+ if (event.shiftKey) {
638
+ redo();
639
+ } else {
640
+ undo();
641
+ }
642
+ }
643
+ Redraw();
644
+ event.preventDefault();
645
+ });
646
+
647
+ function initializeEditor() {
648
+ console.log("initializeEditor");
649
+
650
+ canvas = document.getElementById('canvas');
651
+ ctx = canvas.getContext('2d');
652
+
653
+ canvas.addEventListener('mousedown', handleMouseDown);
654
+ canvas.addEventListener('mousemove', handleMouseMove);
655
+ canvas.addEventListener('mouseup', handleMouseUp);
656
+ canvas.addEventListener('mouseleave', handleMouseLeave);
657
+ poseData = [];
658
+ clearHistory();
659
+ }
660
+
661
+ function importPose(jsonData) {
662
+ if (jsonData != null) {
663
+ newPoseData = makePoseDataFromCandidateAndSubset(jsonData.candidate, jsonData.subset);
664
+ if (jsonData.width != null && jsonData.height != null) {
665
+ resizeCanvas(jsonData.width, jsonData.height);
666
+ }
667
+ } else {
668
+ newPoseData = makePoseDataFromCandidateAndSubset(sampleCandidateSource, [sampleSubsetElementSource]);
669
+ }
670
+ poseData = poseData.concat(newPoseData);
671
+ addHistory();
672
+ Redraw();
673
+ }
674
+
675
+ // crc32
676
+ // CRC32を初期化
677
+ function initCrc32Table() {
678
+ const crcTable = new Uint32Array(256);
679
+ for (let i = 0; i < 256; i++) {
680
+ let c = i;
681
+ for (let j = 0; j < 8; j++) {
682
+ c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1);
683
+ }
684
+ crcTable[i] = c;
685
+ }
686
+ return crcTable;
687
+ }
688
+
689
+ // データのCRC32を計算
690
+ function getCrc32(data, crc=0) {
691
+ const crcTable = initCrc32Table();
692
+ crc = (crc ^ 0xFFFFFFFF) >>> 0;
693
+ for (let i = 0; i < data.length; i++) {
694
+ crc = crcTable[(crc ^ data[i]) & 0xFF] ^ (crc >>> 8);
695
+ }
696
+ return (crc ^ 0xFFFFFFFF) >>> 0;
697
+ }
698
+
699
+ function stringToUint8Array(str) {
700
+ var arr = new Uint8Array(str.length);
701
+ for (var i = 0; i < str.length; i++) {
702
+ arr[i] = str.charCodeAt(i);
703
+ }
704
+ return arr;
705
+ }
706
+
707
+ function base64ToUint8Array(base64Str) {
708
+ return stringToUint8Array(atob(base64Str));
709
+ }
710
+
711
+ function visitPng(png, type) {
712
+ var dataLength;
713
+ var chunkType;
714
+ var nextChunkPos;
715
+ var Signature = String.fromCharCode(137, 80, 78, 71, 13, 10, 26, 10);
716
+ var rpos = 0;
717
+
718
+ // シグネチャの確認
719
+ if (String.fromCharCode.apply(null, png.subarray(rpos, rpos += 8)) !== Signature) {
720
+ throw new Error('invalid signature');
721
+ }
722
+
723
+ // チャンクの探索
724
+ while (rpos < png.length) {
725
+ dataLength = (
726
+ (png[rpos++] << 24) |
727
+ (png[rpos++] << 16) |
728
+ (png[rpos++] << 8) |
729
+ (png[rpos++] )
730
+ ) >>> 0;
731
+
732
+ nextChunkPos = rpos + dataLength + 8;
733
+
734
+ chunkType = String.fromCharCode.apply(null, png.subarray(rpos, rpos += 4));
735
+
736
+ if (chunkType === type) {
737
+ return [rpos - 8, dataLength, nextChunkPos];
738
+ }
739
+
740
+ rpos = nextChunkPos;
741
+ }
742
+ }
743
+
744
+ function createChunk(type, data) {
745
+ var dataLength = data.length;
746
+ var chunk = new Uint8Array(4 + 4 + dataLength + 4);
747
+ var type = stringToUint8Array(type);
748
+ var pos = 0;
749
+
750
+ // length
751
+ chunk[pos++] = (dataLength >> 24) & 0xff;
752
+ chunk[pos++] = (dataLength >> 16) & 0xff;
753
+ chunk[pos++] = (dataLength >> 8) & 0xff;
754
+ chunk[pos++] = (dataLength ) & 0xff;
755
+
756
+ // type
757
+ chunk[pos++] = type[0];
758
+ chunk[pos++] = type[1];
759
+ chunk[pos++] = type[2];
760
+ chunk[pos++] = type[3];
761
+
762
+ // data
763
+ for (let i = 0; i < dataLength; ++i) {
764
+ chunk[pos++] = data[i];
765
+ }
766
+
767
+ //crc
768
+ initCrc32Table();
769
+ let crc = getCrc32(type);
770
+ crc = getCrc32(data, crc);
771
+ chunk[pos++] = (crc >> 24) & 0xff;
772
+ chunk[pos++] = (crc >> 16) & 0xff;
773
+ chunk[pos++] = (crc >> 8) & 0xff;
774
+ chunk[pos++] = (crc ) & 0xff;
775
+
776
+ return chunk;
777
+ }
778
+
779
+ function insertChunk(destBuffer, sourceBuffer, rpos, chunk) {
780
+ var pos = 0;
781
+
782
+ // IDAT チャンクの前までコピー
783
+ destBuffer.set(sourceBuffer.subarray(0, rpos), pos);
784
+ pos += rpos;
785
+
786
+ // hoGe チャンクをコピー
787
+ destBuffer.set(chunk, pos);
788
+ pos += chunk.length;
789
+
790
+ // IDAT チャンク以降をコピー
791
+ destBuffer.set(sourceBuffer.subarray(rpos), pos);
792
+ }
793
+
794
+ function mergeCanvasWithPose(keyword, content) {
795
+ const canvasUrl = canvas.toDataURL();
796
+
797
+ var insertion = stringToUint8Array(`${keyword}\0${content}`);
798
+ var chunk = createChunk("tEXt", insertion);
799
+ var sourceBuffer = base64ToUint8Array(canvasUrl.split(',')[1]);
800
+ var destBuffer = new Uint8Array(sourceBuffer.length + insertion.length + 12);
801
+
802
+ var [rpos, dataLength, nextChunkPos] = visitPng(sourceBuffer, "IHDR");
803
+ insertChunk(destBuffer, sourceBuffer, nextChunkPos, chunk);
804
+
805
+ var blob = new Blob([destBuffer], {type: "image/png"});
806
+ var url = URL.createObjectURL(blob);
807
+ return url;
808
+ }
809
+
810
+ function savePose() {
811
+ var [candidate, subset] = poseDataToCandidateAndSubset(poseData);
812
+ let jsonData = {candidate: candidate, subset: subset};
813
+
814
+ var url = mergeCanvasWithPose("openpose", JSON.stringify(jsonData));
815
+
816
+ const createEl = document.createElement('a');
817
+ createEl.href = url;
818
+
819
+ // This is the name of our downloaded file
820
+ createEl.download = "pose.png";
821
+
822
+ createEl.click();
823
+ createEl.remove();
824
+
825
+ return jsonData;
826
+ }
827
+
828
+ function importPicture(image) {
829
+ var pic = new Image();
830
+ pic.onload = () => {
831
+ const size = [canvas.width, canvas.height];
832
+
833
+ const canvas3 = document.getElementById("canvas3");
834
+ pictureImageData = makeImageDataFromPicture(canvas3, size, pic);
835
+
836
+ const canvas4 = document.getElementById("canvas4");
837
+ offscreen = canvas4;
838
+
839
+ initialPoseField = buildWeightMap(size, poseData[0]);
840
+ animatePicture(canvas4, size, poseData[0], poseData[0], pictureImageData, initialPoseField);
841
+
842
+ Redraw();
843
+ };
844
+ pic.src = image;
845
+
846
+ initMicroscope();
847
+ }
848
+
849
+ function importBackground(image) {
850
+ if (image == null) {
851
+ canvasBg = null;
852
+ Redraw();
853
+ return;
854
+ }
855
+
856
+ let m = new Image();
857
+ m.src = image;
858
+ m.onload = function() {
859
+ canvasBg = m;
860
+ Redraw();
861
+ }
862
+ }
static/sample1.png ADDED