pose-ai-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +348 -0
- package/dist/_virtual/_commonjsHelpers.js +5 -0
- package/dist/_virtual/_commonjsHelpers.js.map +1 -0
- package/dist/_virtual/camera_utils.js +6 -0
- package/dist/_virtual/camera_utils.js.map +1 -0
- package/dist/_virtual/camera_utils2.js +5 -0
- package/dist/_virtual/camera_utils2.js.map +1 -0
- package/dist/_virtual/drawing_utils.js +6 -0
- package/dist/_virtual/drawing_utils.js.map +1 -0
- package/dist/_virtual/drawing_utils2.js +5 -0
- package/dist/_virtual/drawing_utils2.js.map +1 -0
- package/dist/_virtual/pose.js +6 -0
- package/dist/_virtual/pose.js.map +1 -0
- package/dist/_virtual/pose2.js +5 -0
- package/dist/_virtual/pose2.js.map +1 -0
- package/dist/components/AnglePanel/index.css +1 -0
- package/dist/components/AnglePanel/index.js +79 -0
- package/dist/components/AnglePanel/index.js.map +1 -0
- package/dist/components/PoseCanvas/index.js +86 -0
- package/dist/components/PoseCanvas/index.js.map +1 -0
- package/dist/components/ROMVisualizer/index.css +1 -0
- package/dist/components/ROMVisualizer/index.js +208 -0
- package/dist/components/ROMVisualizer/index.js.map +1 -0
- package/dist/components/SkeletonOverlay/index.css +1 -0
- package/dist/components/SkeletonOverlay/index.js +45 -0
- package/dist/components/SkeletonOverlay/index.js.map +1 -0
- package/dist/components/styles.js +270 -0
- package/dist/components/styles.js.map +1 -0
- package/dist/components/utils/drawingUtils.js +45 -0
- package/dist/components/utils/drawingUtils.js.map +1 -0
- package/dist/engine/angleCalculator.js +196 -0
- package/dist/engine/angleCalculator.js.map +1 -0
- package/dist/engine/directionAnalyzer.js +97 -0
- package/dist/engine/directionAnalyzer.js.map +1 -0
- package/dist/engine/eventBus.js +45 -0
- package/dist/engine/eventBus.js.map +1 -0
- package/dist/engine/guidedAssessmentManager.js +252 -0
- package/dist/engine/guidedAssessmentManager.js.map +1 -0
- package/dist/engine/motionDetector.js +127 -0
- package/dist/engine/motionDetector.js.map +1 -0
- package/dist/engine/poseEngine.js +131 -0
- package/dist/engine/poseEngine.js.map +1 -0
- package/dist/engine.d.ts +153 -0
- package/dist/engine.js +10 -0
- package/dist/engine.js.map +1 -0
- package/dist/hooks/usePoseDetection.js +278 -0
- package/dist/hooks/usePoseDetection.js.map +1 -0
- package/dist/hooks/usePoseEngine.js +83 -0
- package/dist/hooks/usePoseEngine.js.map +1 -0
- package/dist/index.d.ts +596 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/node_modules/.pnpm/@mediapipe_camera_utils@0.3.1675466862/node_modules/@mediapipe/camera_utils/camera_utils.js +377 -0
- package/dist/node_modules/.pnpm/@mediapipe_camera_utils@0.3.1675466862/node_modules/@mediapipe/camera_utils/camera_utils.js.map +1 -0
- package/dist/node_modules/.pnpm/@mediapipe_drawing_utils@0.3.1675466124/node_modules/@mediapipe/drawing_utils/drawing_utils.js +112 -0
- package/dist/node_modules/.pnpm/@mediapipe_drawing_utils@0.3.1675466124/node_modules/@mediapipe/drawing_utils/drawing_utils.js.map +1 -0
- package/dist/node_modules/.pnpm/@mediapipe_pose@0.5.1675469404/node_modules/@mediapipe/pose/pose.js +1796 -0
- package/dist/node_modules/.pnpm/@mediapipe_pose@0.5.1675469404/node_modules/@mediapipe/pose/pose.js.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { jsxs as t, jsx as e } from "react/jsx-runtime";
|
|
2
|
+
import { useRef as y, useState as m } from "react";
|
|
3
|
+
import { usePoseEngine as w } from "../../hooks/usePoseEngine.js";
|
|
4
|
+
import { SkeletonOverlay as C } from "../SkeletonOverlay/index.js";
|
|
5
|
+
import { AnglePanel as R } from "../AnglePanel/index.js";
|
|
6
|
+
/* empty css */
|
|
7
|
+
const V = ({
|
|
8
|
+
engineOptions: i = {},
|
|
9
|
+
width: o = 640,
|
|
10
|
+
height: s = 480,
|
|
11
|
+
showAnglePanel: v = !0,
|
|
12
|
+
showROM: p = !0,
|
|
13
|
+
drawingOptions: k = {},
|
|
14
|
+
className: N = "",
|
|
15
|
+
onStart: f,
|
|
16
|
+
onStop: L
|
|
17
|
+
}) => {
|
|
18
|
+
const n = y(null), [g, l] = m(null), [c, d] = m(!1), { start: z, stop: b, isRunning: a, angles: u, romData: M, error: h } = w({
|
|
19
|
+
...i,
|
|
20
|
+
onResults: (r) => {
|
|
21
|
+
l(r), i.onResults?.(r);
|
|
22
|
+
}
|
|
23
|
+
}), j = async () => {
|
|
24
|
+
if (n.current)
|
|
25
|
+
try {
|
|
26
|
+
await z(n.current), d(!0), f?.();
|
|
27
|
+
} catch (r) {
|
|
28
|
+
console.error("Failed to start pose detection:", r);
|
|
29
|
+
}
|
|
30
|
+
}, x = () => {
|
|
31
|
+
b(), d(!1), l(null), L?.();
|
|
32
|
+
};
|
|
33
|
+
return /* @__PURE__ */ t("div", { className: `rom-visualizer ${N}`, children: [
|
|
34
|
+
/* @__PURE__ */ t("div", { className: "visualizer-container", children: [
|
|
35
|
+
/* @__PURE__ */ t("div", { className: "video-container", style: { width: o, height: s }, children: [
|
|
36
|
+
/* @__PURE__ */ e(
|
|
37
|
+
"video",
|
|
38
|
+
{
|
|
39
|
+
ref: n,
|
|
40
|
+
className: "video-element",
|
|
41
|
+
width: o,
|
|
42
|
+
height: s,
|
|
43
|
+
autoPlay: !0,
|
|
44
|
+
playsInline: !0,
|
|
45
|
+
muted: !0
|
|
46
|
+
}
|
|
47
|
+
),
|
|
48
|
+
a && /* @__PURE__ */ e(
|
|
49
|
+
C,
|
|
50
|
+
{
|
|
51
|
+
results: g,
|
|
52
|
+
angles: u,
|
|
53
|
+
width: o,
|
|
54
|
+
height: s,
|
|
55
|
+
drawingOptions: k
|
|
56
|
+
}
|
|
57
|
+
),
|
|
58
|
+
!c && /* @__PURE__ */ e("div", { className: "video-overlay", children: /* @__PURE__ */ t("div", { className: "overlay-content", children: [
|
|
59
|
+
/* @__PURE__ */ e(
|
|
60
|
+
"svg",
|
|
61
|
+
{
|
|
62
|
+
className: "camera-icon",
|
|
63
|
+
fill: "none",
|
|
64
|
+
stroke: "currentColor",
|
|
65
|
+
viewBox: "0 0 24 24",
|
|
66
|
+
width: "64",
|
|
67
|
+
height: "64",
|
|
68
|
+
children: /* @__PURE__ */ e(
|
|
69
|
+
"path",
|
|
70
|
+
{
|
|
71
|
+
strokeLinecap: "round",
|
|
72
|
+
strokeLinejoin: "round",
|
|
73
|
+
strokeWidth: 2,
|
|
74
|
+
d: "M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
),
|
|
79
|
+
/* @__PURE__ */ e("p", { children: "点击开始按钮启动摄像头" })
|
|
80
|
+
] }) })
|
|
81
|
+
] }),
|
|
82
|
+
v && /* @__PURE__ */ e(
|
|
83
|
+
R,
|
|
84
|
+
{
|
|
85
|
+
angles: u,
|
|
86
|
+
romData: M,
|
|
87
|
+
showROM: p,
|
|
88
|
+
className: "visualizer-angle-panel"
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
] }),
|
|
92
|
+
/* @__PURE__ */ t("div", { className: "controls", children: [
|
|
93
|
+
c ? /* @__PURE__ */ t(
|
|
94
|
+
"button",
|
|
95
|
+
{
|
|
96
|
+
className: "control-button stop-button",
|
|
97
|
+
onClick: x,
|
|
98
|
+
children: [
|
|
99
|
+
/* @__PURE__ */ t(
|
|
100
|
+
"svg",
|
|
101
|
+
{
|
|
102
|
+
className: "button-icon",
|
|
103
|
+
fill: "none",
|
|
104
|
+
stroke: "currentColor",
|
|
105
|
+
viewBox: "0 0 24 24",
|
|
106
|
+
width: "20",
|
|
107
|
+
height: "20",
|
|
108
|
+
children: [
|
|
109
|
+
/* @__PURE__ */ e(
|
|
110
|
+
"path",
|
|
111
|
+
{
|
|
112
|
+
strokeLinecap: "round",
|
|
113
|
+
strokeLinejoin: "round",
|
|
114
|
+
strokeWidth: 2,
|
|
115
|
+
d: "M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
116
|
+
}
|
|
117
|
+
),
|
|
118
|
+
/* @__PURE__ */ e(
|
|
119
|
+
"path",
|
|
120
|
+
{
|
|
121
|
+
strokeLinecap: "round",
|
|
122
|
+
strokeLinejoin: "round",
|
|
123
|
+
strokeWidth: 2,
|
|
124
|
+
d: "M9 10a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z"
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
),
|
|
130
|
+
"停止检测"
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
) : /* @__PURE__ */ t(
|
|
134
|
+
"button",
|
|
135
|
+
{
|
|
136
|
+
className: "control-button start-button",
|
|
137
|
+
onClick: j,
|
|
138
|
+
disabled: a,
|
|
139
|
+
children: [
|
|
140
|
+
/* @__PURE__ */ t(
|
|
141
|
+
"svg",
|
|
142
|
+
{
|
|
143
|
+
className: "button-icon",
|
|
144
|
+
fill: "none",
|
|
145
|
+
stroke: "currentColor",
|
|
146
|
+
viewBox: "0 0 24 24",
|
|
147
|
+
width: "20",
|
|
148
|
+
height: "20",
|
|
149
|
+
children: [
|
|
150
|
+
/* @__PURE__ */ e(
|
|
151
|
+
"path",
|
|
152
|
+
{
|
|
153
|
+
strokeLinecap: "round",
|
|
154
|
+
strokeLinejoin: "round",
|
|
155
|
+
strokeWidth: 2,
|
|
156
|
+
d: "M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"
|
|
157
|
+
}
|
|
158
|
+
),
|
|
159
|
+
/* @__PURE__ */ e(
|
|
160
|
+
"path",
|
|
161
|
+
{
|
|
162
|
+
strokeLinecap: "round",
|
|
163
|
+
strokeLinejoin: "round",
|
|
164
|
+
strokeWidth: 2,
|
|
165
|
+
d: "M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
166
|
+
}
|
|
167
|
+
)
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
),
|
|
171
|
+
"开始检测"
|
|
172
|
+
]
|
|
173
|
+
}
|
|
174
|
+
),
|
|
175
|
+
a && /* @__PURE__ */ t("div", { className: "status-indicator", children: [
|
|
176
|
+
/* @__PURE__ */ e("span", { className: "status-dot" }),
|
|
177
|
+
/* @__PURE__ */ e("span", { className: "status-text", children: "检测中" })
|
|
178
|
+
] })
|
|
179
|
+
] }),
|
|
180
|
+
h && /* @__PURE__ */ t("div", { className: "error-message", children: [
|
|
181
|
+
/* @__PURE__ */ e(
|
|
182
|
+
"svg",
|
|
183
|
+
{
|
|
184
|
+
className: "error-icon",
|
|
185
|
+
fill: "none",
|
|
186
|
+
stroke: "currentColor",
|
|
187
|
+
viewBox: "0 0 24 24",
|
|
188
|
+
width: "20",
|
|
189
|
+
height: "20",
|
|
190
|
+
children: /* @__PURE__ */ e(
|
|
191
|
+
"path",
|
|
192
|
+
{
|
|
193
|
+
strokeLinecap: "round",
|
|
194
|
+
strokeLinejoin: "round",
|
|
195
|
+
strokeWidth: 2,
|
|
196
|
+
d: "M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
197
|
+
}
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
),
|
|
201
|
+
/* @__PURE__ */ e("span", { children: h.message })
|
|
202
|
+
] })
|
|
203
|
+
] });
|
|
204
|
+
};
|
|
205
|
+
export {
|
|
206
|
+
V as ROMVisualizer
|
|
207
|
+
};
|
|
208
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../src/components/ROMVisualizer/index.tsx"],"sourcesContent":["import React, { useRef, useState } from 'react'\nimport type { Results } from '@mediapipe/pose'\nimport { usePoseEngine, UsePoseEngineOptions } from '../../hooks/usePoseEngine'\nimport { SkeletonOverlay } from '../SkeletonOverlay'\nimport { AnglePanel } from '../AnglePanel'\nimport type { DrawingOptions } from '../utils/drawingUtils'\nimport './index.css'\n\nexport interface ROMVisualizerProps {\n engineOptions?: UsePoseEngineOptions\n width?: number\n height?: number\n showAnglePanel?: boolean\n showROM?: boolean\n drawingOptions?: DrawingOptions\n className?: string\n onStart?: () => void\n onStop?: () => void\n}\n\nexport const ROMVisualizer: React.FC<ROMVisualizerProps> = ({\n engineOptions = {},\n width = 640,\n height = 480,\n showAnglePanel = true,\n showROM = true,\n drawingOptions = {},\n className = '',\n onStart,\n onStop,\n}) => {\n const videoRef = useRef<HTMLVideoElement>(null)\n const [results, setResults] = useState<Results | null>(null)\n const [isStarted, setIsStarted] = useState(false)\n\n const { start, stop, isRunning, angles, romData, error } = usePoseEngine({\n ...engineOptions,\n onResults: (newResults) => {\n setResults(newResults)\n engineOptions.onResults?.(newResults)\n },\n })\n\n const handleStart = async () => {\n if (!videoRef.current) return\n\n try {\n await start(videoRef.current)\n setIsStarted(true)\n onStart?.()\n } catch (err) {\n console.error('Failed to start pose detection:', err)\n }\n }\n\n const handleStop = () => {\n stop()\n setIsStarted(false)\n setResults(null)\n onStop?.()\n }\n\n return (\n <div className={`rom-visualizer ${className}`}>\n <div className=\"visualizer-container\">\n <div className=\"video-container\" style={{ width, height }}>\n <video\n ref={videoRef}\n className=\"video-element\"\n width={width}\n height={height}\n autoPlay\n playsInline\n muted\n />\n {isRunning && (\n <SkeletonOverlay\n results={results}\n angles={angles}\n width={width}\n height={height}\n drawingOptions={drawingOptions}\n />\n )}\n {!isStarted && (\n <div className=\"video-overlay\">\n <div className=\"overlay-content\">\n <svg\n className=\"camera-icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n width=\"64\"\n height=\"64\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z\"\n />\n </svg>\n <p>点击开始按钮启动摄像头</p>\n </div>\n </div>\n )}\n </div>\n\n {showAnglePanel && (\n <AnglePanel\n angles={angles}\n romData={romData}\n showROM={showROM}\n className=\"visualizer-angle-panel\"\n />\n )}\n </div>\n\n <div className=\"controls\">\n {!isStarted ? (\n <button\n className=\"control-button start-button\"\n onClick={handleStart}\n disabled={isRunning}\n >\n <svg\n className=\"button-icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n width=\"20\"\n height=\"20\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z\"\n />\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n 开始检测\n </button>\n ) : (\n <button\n className=\"control-button stop-button\"\n onClick={handleStop}\n >\n <svg\n className=\"button-icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n width=\"20\"\n height=\"20\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 10a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z\"\n />\n </svg>\n 停止检测\n </button>\n )}\n\n {isRunning && (\n <div className=\"status-indicator\">\n <span className=\"status-dot\"></span>\n <span className=\"status-text\">检测中</span>\n </div>\n )}\n </div>\n\n {error && (\n <div className=\"error-message\">\n <svg\n className=\"error-icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n width=\"20\"\n height=\"20\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n <span>{error.message}</span>\n </div>\n )}\n </div>\n )\n}\n"],"names":["ROMVisualizer","engineOptions","width","height","showAnglePanel","showROM","drawingOptions","className","onStart","onStop","videoRef","useRef","results","setResults","useState","isStarted","setIsStarted","start","stop","isRunning","angles","romData","error","usePoseEngine","newResults","handleStart","err","handleStop","jsxs","jsx","SkeletonOverlay","AnglePanel"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.skeleton-overlay{position:absolute;top:0;left:0;pointer-events:none;z-index:10;transform:scaleX(-1)}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { jsx as d } from "react/jsx-runtime";
|
|
2
|
+
import { useRef as z, useEffect as x } from "react";
|
|
3
|
+
import { resizeCanvas as C, clearCanvas as L, drawPoseLandmarks as b, drawAngle as A } from "../utils/drawingUtils.js";
|
|
4
|
+
/* empty css */
|
|
5
|
+
const P = ({
|
|
6
|
+
results: e,
|
|
7
|
+
angles: f = [],
|
|
8
|
+
width: n = 640,
|
|
9
|
+
height: o = 480,
|
|
10
|
+
drawingOptions: a = {},
|
|
11
|
+
className: u = ""
|
|
12
|
+
}) => {
|
|
13
|
+
const m = z(null);
|
|
14
|
+
return x(() => {
|
|
15
|
+
const s = m.current;
|
|
16
|
+
s && C(s, n, o);
|
|
17
|
+
}, [n, o]), x(() => {
|
|
18
|
+
const s = m.current;
|
|
19
|
+
if (!s) return;
|
|
20
|
+
const c = s.getContext("2d");
|
|
21
|
+
if (!c || (L(c), !e || !e.poseLandmarks)) return;
|
|
22
|
+
const t = e.poseLandmarks.map((r) => ({
|
|
23
|
+
x: r.x,
|
|
24
|
+
y: r.y,
|
|
25
|
+
z: r.z,
|
|
26
|
+
visibility: r.visibility
|
|
27
|
+
}));
|
|
28
|
+
b(c, t, a), a.showAngles && f.length > 0 && f.forEach((r) => {
|
|
29
|
+
const [k, l, y] = r.points, v = t[k], i = t[l], p = t[y];
|
|
30
|
+
v && i && p && A(c, v, i, p, r.angle, n, o, a);
|
|
31
|
+
});
|
|
32
|
+
}, [e, f, n, o, a]), /* @__PURE__ */ d(
|
|
33
|
+
"canvas",
|
|
34
|
+
{
|
|
35
|
+
ref: m,
|
|
36
|
+
className: `skeleton-overlay ${u}`,
|
|
37
|
+
width: n,
|
|
38
|
+
height: o
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
export {
|
|
43
|
+
P as SkeletonOverlay
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../src/components/SkeletonOverlay/index.tsx"],"sourcesContent":["import React, { useEffect, useRef } from 'react'\nimport type { Results } from '@mediapipe/pose'\nimport type { JointAngle, PosePoint } from '../../types'\nimport { drawPoseLandmarks, drawAngle, clearCanvas, resizeCanvas, DrawingOptions } from '../utils/drawingUtils'\nimport './index.css'\n\nexport interface SkeletonOverlayProps {\n results: Results | null\n angles?: JointAngle[]\n width?: number\n height?: number\n drawingOptions?: DrawingOptions\n className?: string\n}\n\nexport const SkeletonOverlay: React.FC<SkeletonOverlayProps> = ({\n results,\n angles = [],\n width = 640,\n height = 480,\n drawingOptions = {},\n className = '',\n}) => {\n const canvasRef = useRef<HTMLCanvasElement>(null)\n\n useEffect(() => {\n const canvas = canvasRef.current\n if (!canvas) return\n\n resizeCanvas(canvas, width, height)\n }, [width, height])\n\n useEffect(() => {\n const canvas = canvasRef.current\n if (!canvas) return\n\n const ctx = canvas.getContext('2d')\n if (!ctx) return\n\n clearCanvas(ctx)\n\n if (!results || !results.poseLandmarks) return\n\n const landmarks: PosePoint[] = results.poseLandmarks.map((lm) => ({\n x: lm.x,\n y: lm.y,\n z: lm.z,\n visibility: lm.visibility,\n }))\n\n drawPoseLandmarks(ctx, landmarks, drawingOptions)\n\n if (drawingOptions.showAngles && angles.length > 0) {\n angles.forEach((angleData) => {\n const [idx1, idx2, idx3] = angleData.points\n const p1 = landmarks[idx1]\n const p2 = landmarks[idx2]\n const p3 = landmarks[idx3]\n\n if (p1 && p2 && p3) {\n drawAngle(ctx, p1, p2, p3, angleData.angle, width, height, drawingOptions)\n }\n })\n }\n }, [results, angles, width, height, drawingOptions])\n\n return (\n <canvas\n ref={canvasRef}\n className={`skeleton-overlay ${className}`}\n width={width}\n height={height}\n />\n )\n}\n"],"names":["SkeletonOverlay","results","angles","width","height","drawingOptions","className","canvasRef","useRef","useEffect","canvas","resizeCanvas","ctx","clearCanvas","landmarks","lm","drawPoseLandmarks","angleData","idx1","idx2","idx3","p1","p2","p3","drawAngle","jsx"],"mappings":";;;;AAeO,MAAMA,IAAkD,CAAC;AAAA,EAC9D,SAAAC;AAAA,EACA,QAAAC,IAAS,CAAA;AAAA,EACT,OAAAC,IAAQ;AAAA,EACR,QAAAC,IAAS;AAAA,EACT,gBAAAC,IAAiB,CAAA;AAAA,EACjB,WAAAC,IAAY;AACd,MAAM;AACJ,QAAMC,IAAYC,EAA0B,IAAI;AAEhD,SAAAC,EAAU,MAAM;AACd,UAAMC,IAASH,EAAU;AACzB,IAAKG,KAELC,EAAaD,GAAQP,GAAOC,CAAM;AAAA,EACpC,GAAG,CAACD,GAAOC,CAAM,CAAC,GAElBK,EAAU,MAAM;AACd,UAAMC,IAASH,EAAU;AACzB,QAAI,CAACG,EAAQ;AAEb,UAAME,IAAMF,EAAO,WAAW,IAAI;AAKlC,QAJI,CAACE,MAELC,EAAYD,CAAG,GAEX,CAACX,KAAW,CAACA,EAAQ,eAAe;AAExC,UAAMa,IAAyBb,EAAQ,cAAc,IAAI,CAACc,OAAQ;AAAA,MAChE,GAAGA,EAAG;AAAA,MACN,GAAGA,EAAG;AAAA,MACN,GAAGA,EAAG;AAAA,MACN,YAAYA,EAAG;AAAA,IAAA,EACf;AAEF,IAAAC,EAAkBJ,GAAKE,GAAWT,CAAc,GAE5CA,EAAe,cAAcH,EAAO,SAAS,KAC/CA,EAAO,QAAQ,CAACe,MAAc;AAC5B,YAAM,CAACC,GAAMC,GAAMC,CAAI,IAAIH,EAAU,QAC/BI,IAAKP,EAAUI,CAAI,GACnBI,IAAKR,EAAUK,CAAI,GACnBI,IAAKT,EAAUM,CAAI;AAEzB,MAAIC,KAAMC,KAAMC,KACdC,EAAUZ,GAAKS,GAAIC,GAAIC,GAAIN,EAAU,OAAOd,GAAOC,GAAQC,CAAc;AAAA,IAE7E,CAAC;AAAA,EAEL,GAAG,CAACJ,GAASC,GAAQC,GAAOC,GAAQC,CAAc,CAAC,GAGjD,gBAAAoB;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAKlB;AAAA,MACL,WAAW,oBAAoBD,CAAS;AAAA,MACxC,OAAAH;AAAA,MACA,QAAAC;AAAA,IAAA;AAAA,EAAA;AAGN;"}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
function t() {
|
|
2
|
+
if (typeof document > "u") return;
|
|
3
|
+
const r = "pose-ai-core-styles";
|
|
4
|
+
if (document.getElementById(r)) return;
|
|
5
|
+
const e = document.createElement("style");
|
|
6
|
+
e.id = r, e.textContent = `
|
|
7
|
+
/* SkeletonOverlay */
|
|
8
|
+
.skeleton-overlay {
|
|
9
|
+
position: absolute;
|
|
10
|
+
top: 0;
|
|
11
|
+
left: 0;
|
|
12
|
+
pointer-events: none;
|
|
13
|
+
z-index: 10;
|
|
14
|
+
transform: scaleX(-1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* ROMVisualizer */
|
|
18
|
+
.rom-visualizer {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
gap: 1.5rem;
|
|
22
|
+
width: 100%;
|
|
23
|
+
max-width: 1200px;
|
|
24
|
+
margin: 0 auto;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.visualizer-container {
|
|
28
|
+
display: flex;
|
|
29
|
+
gap: 1.5rem;
|
|
30
|
+
flex-wrap: wrap;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.video-container {
|
|
34
|
+
position: relative;
|
|
35
|
+
background: #000;
|
|
36
|
+
border-radius: 12px;
|
|
37
|
+
overflow: hidden;
|
|
38
|
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.video-element {
|
|
42
|
+
display: block;
|
|
43
|
+
width: 100%;
|
|
44
|
+
height: 100%;
|
|
45
|
+
transform: scaleX(-1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.video-overlay {
|
|
49
|
+
position: absolute;
|
|
50
|
+
top: 0;
|
|
51
|
+
left: 0;
|
|
52
|
+
right: 0;
|
|
53
|
+
bottom: 0;
|
|
54
|
+
display: flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
justify-content: center;
|
|
57
|
+
background: rgba(0, 0, 0, 0.7);
|
|
58
|
+
z-index: 5;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.overlay-content {
|
|
62
|
+
text-align: center;
|
|
63
|
+
color: white;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.camera-icon {
|
|
67
|
+
margin: 0 auto 1rem;
|
|
68
|
+
opacity: 0.8;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.controls {
|
|
72
|
+
display: flex;
|
|
73
|
+
gap: 1rem;
|
|
74
|
+
justify-content: center;
|
|
75
|
+
flex-wrap: wrap;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.control-button {
|
|
79
|
+
padding: 0.75rem 2rem;
|
|
80
|
+
font-size: 1rem;
|
|
81
|
+
font-weight: 600;
|
|
82
|
+
border: none;
|
|
83
|
+
border-radius: 8px;
|
|
84
|
+
cursor: pointer;
|
|
85
|
+
transition: all 0.2s;
|
|
86
|
+
display: flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
gap: 0.5rem;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.control-button:hover {
|
|
92
|
+
transform: translateY(-2px);
|
|
93
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.control-button:active {
|
|
97
|
+
transform: translateY(0);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.start-button {
|
|
101
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
102
|
+
color: white;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.stop-button {
|
|
106
|
+
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
|
107
|
+
color: white;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.status-indicator {
|
|
111
|
+
display: inline-flex;
|
|
112
|
+
align-items: center;
|
|
113
|
+
gap: 0.5rem;
|
|
114
|
+
padding: 0.5rem 1rem;
|
|
115
|
+
background: rgba(255, 255, 255, 0.1);
|
|
116
|
+
border-radius: 20px;
|
|
117
|
+
color: white;
|
|
118
|
+
font-size: 0.9rem;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.status-dot {
|
|
122
|
+
width: 8px;
|
|
123
|
+
height: 8px;
|
|
124
|
+
border-radius: 50%;
|
|
125
|
+
background: #4ade80;
|
|
126
|
+
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@keyframes pulse {
|
|
130
|
+
0%, 100% {
|
|
131
|
+
opacity: 1;
|
|
132
|
+
}
|
|
133
|
+
50% {
|
|
134
|
+
opacity: 0.5;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.error-message {
|
|
139
|
+
padding: 1rem;
|
|
140
|
+
background: #fee;
|
|
141
|
+
border: 1px solid #fcc;
|
|
142
|
+
border-radius: 8px;
|
|
143
|
+
color: #c00;
|
|
144
|
+
margin-top: 1rem;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* AnglePanel */
|
|
148
|
+
.angle-panel {
|
|
149
|
+
flex: 1;
|
|
150
|
+
min-width: 300px;
|
|
151
|
+
background: white;
|
|
152
|
+
border-radius: 12px;
|
|
153
|
+
padding: 1.5rem;
|
|
154
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
155
|
+
max-height: 600px;
|
|
156
|
+
overflow-y: auto;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.angle-panel h3 {
|
|
160
|
+
margin: 0 0 1rem 0;
|
|
161
|
+
color: #333;
|
|
162
|
+
font-size: 1.2rem;
|
|
163
|
+
font-weight: 600;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.angle-list {
|
|
167
|
+
display: flex;
|
|
168
|
+
flex-direction: column;
|
|
169
|
+
gap: 0.75rem;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.angle-item {
|
|
173
|
+
padding: 1rem;
|
|
174
|
+
background: #f8f9fa;
|
|
175
|
+
border-radius: 8px;
|
|
176
|
+
transition: all 0.2s;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.angle-item:hover {
|
|
180
|
+
background: #e9ecef;
|
|
181
|
+
transform: translateX(4px);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.angle-header {
|
|
185
|
+
display: flex;
|
|
186
|
+
justify-content: space-between;
|
|
187
|
+
align-items: center;
|
|
188
|
+
margin-bottom: 0.5rem;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.angle-name {
|
|
192
|
+
font-weight: 600;
|
|
193
|
+
color: #495057;
|
|
194
|
+
text-transform: capitalize;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.angle-value {
|
|
198
|
+
font-size: 1.25rem;
|
|
199
|
+
font-weight: 700;
|
|
200
|
+
color: #667eea;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.rom-info {
|
|
204
|
+
display: flex;
|
|
205
|
+
flex-direction: column;
|
|
206
|
+
gap: 0.25rem;
|
|
207
|
+
font-size: 0.85rem;
|
|
208
|
+
color: #6c757d;
|
|
209
|
+
margin-top: 0.5rem;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.rom-range {
|
|
213
|
+
display: flex;
|
|
214
|
+
justify-content: space-between;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.rom-progress {
|
|
218
|
+
width: 100%;
|
|
219
|
+
height: 6px;
|
|
220
|
+
background: #e9ecef;
|
|
221
|
+
border-radius: 3px;
|
|
222
|
+
overflow: hidden;
|
|
223
|
+
margin-top: 0.5rem;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.rom-progress-bar {
|
|
227
|
+
height: 100%;
|
|
228
|
+
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
|
|
229
|
+
transition: width 0.3s ease;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.angle-panel::-webkit-scrollbar {
|
|
233
|
+
width: 8px;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.angle-panel::-webkit-scrollbar-track {
|
|
237
|
+
background: #f1f1f1;
|
|
238
|
+
border-radius: 4px;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.angle-panel::-webkit-scrollbar-thumb {
|
|
242
|
+
background: #667eea;
|
|
243
|
+
border-radius: 4px;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.angle-panel::-webkit-scrollbar-thumb:hover {
|
|
247
|
+
background: #5568d3;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
@media (max-width: 768px) {
|
|
251
|
+
.rom-visualizer {
|
|
252
|
+
gap: 1rem;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.visualizer-container {
|
|
256
|
+
flex-direction: column;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.angle-panel {
|
|
260
|
+
min-width: auto;
|
|
261
|
+
max-height: 400px;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
`, document.head.appendChild(e);
|
|
265
|
+
}
|
|
266
|
+
typeof window < "u" && t();
|
|
267
|
+
export {
|
|
268
|
+
t as injectStyles
|
|
269
|
+
};
|
|
270
|
+
//# sourceMappingURL=styles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.js","sources":["../../src/components/styles.ts"],"sourcesContent":["// 将所有组件样式集中管理,确保样式被正确加载\n\nexport function injectStyles() {\n if (typeof document === 'undefined') return\n \n const styleId = 'pose-ai-core-styles'\n if (document.getElementById(styleId)) return\n\n const style = document.createElement('style')\n style.id = styleId\n style.textContent = `\n/* SkeletonOverlay */\n.skeleton-overlay {\n position: absolute;\n top: 0;\n left: 0;\n pointer-events: none;\n z-index: 10;\n transform: scaleX(-1);\n}\n\n/* ROMVisualizer */\n.rom-visualizer {\n display: flex;\n flex-direction: column;\n gap: 1.5rem;\n width: 100%;\n max-width: 1200px;\n margin: 0 auto;\n}\n\n.visualizer-container {\n display: flex;\n gap: 1.5rem;\n flex-wrap: wrap;\n}\n\n.video-container {\n position: relative;\n background: #000;\n border-radius: 12px;\n overflow: hidden;\n box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);\n}\n\n.video-element {\n display: block;\n width: 100%;\n height: 100%;\n transform: scaleX(-1);\n}\n\n.video-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(0, 0, 0, 0.7);\n z-index: 5;\n}\n\n.overlay-content {\n text-align: center;\n color: white;\n}\n\n.camera-icon {\n margin: 0 auto 1rem;\n opacity: 0.8;\n}\n\n.controls {\n display: flex;\n gap: 1rem;\n justify-content: center;\n flex-wrap: wrap;\n}\n\n.control-button {\n padding: 0.75rem 2rem;\n font-size: 1rem;\n font-weight: 600;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.2s;\n display: flex;\n align-items: center;\n gap: 0.5rem;\n}\n\n.control-button:hover {\n transform: translateY(-2px);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n}\n\n.control-button:active {\n transform: translateY(0);\n}\n\n.start-button {\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n}\n\n.stop-button {\n background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);\n color: white;\n}\n\n.status-indicator {\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.5rem 1rem;\n background: rgba(255, 255, 255, 0.1);\n border-radius: 20px;\n color: white;\n font-size: 0.9rem;\n}\n\n.status-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #4ade80;\n animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;\n}\n\n@keyframes pulse {\n 0%, 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.5;\n }\n}\n\n.error-message {\n padding: 1rem;\n background: #fee;\n border: 1px solid #fcc;\n border-radius: 8px;\n color: #c00;\n margin-top: 1rem;\n}\n\n/* AnglePanel */\n.angle-panel {\n flex: 1;\n min-width: 300px;\n background: white;\n border-radius: 12px;\n padding: 1.5rem;\n box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n max-height: 600px;\n overflow-y: auto;\n}\n\n.angle-panel h3 {\n margin: 0 0 1rem 0;\n color: #333;\n font-size: 1.2rem;\n font-weight: 600;\n}\n\n.angle-list {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n}\n\n.angle-item {\n padding: 1rem;\n background: #f8f9fa;\n border-radius: 8px;\n transition: all 0.2s;\n}\n\n.angle-item:hover {\n background: #e9ecef;\n transform: translateX(4px);\n}\n\n.angle-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 0.5rem;\n}\n\n.angle-name {\n font-weight: 600;\n color: #495057;\n text-transform: capitalize;\n}\n\n.angle-value {\n font-size: 1.25rem;\n font-weight: 700;\n color: #667eea;\n}\n\n.rom-info {\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n font-size: 0.85rem;\n color: #6c757d;\n margin-top: 0.5rem;\n}\n\n.rom-range {\n display: flex;\n justify-content: space-between;\n}\n\n.rom-progress {\n width: 100%;\n height: 6px;\n background: #e9ecef;\n border-radius: 3px;\n overflow: hidden;\n margin-top: 0.5rem;\n}\n\n.rom-progress-bar {\n height: 100%;\n background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);\n transition: width 0.3s ease;\n}\n\n.angle-panel::-webkit-scrollbar {\n width: 8px;\n}\n\n.angle-panel::-webkit-scrollbar-track {\n background: #f1f1f1;\n border-radius: 4px;\n}\n\n.angle-panel::-webkit-scrollbar-thumb {\n background: #667eea;\n border-radius: 4px;\n}\n\n.angle-panel::-webkit-scrollbar-thumb:hover {\n background: #5568d3;\n}\n\n@media (max-width: 768px) {\n .rom-visualizer {\n gap: 1rem;\n }\n\n .visualizer-container {\n flex-direction: column;\n }\n\n .angle-panel {\n min-width: auto;\n max-height: 400px;\n }\n}\n`\n\n document.head.appendChild(style)\n}\n\n// 自动注入样式\nif (typeof window !== 'undefined') {\n injectStyles()\n}\n"],"names":["injectStyles","styleId","style"],"mappings":"AAEO,SAASA,IAAe;AAC7B,MAAI,OAAO,WAAa,IAAa;AAErC,QAAMC,IAAU;AAChB,MAAI,SAAS,eAAeA,CAAO,EAAG;AAEtC,QAAMC,IAAQ,SAAS,cAAc,OAAO;AAC5C,EAAAA,EAAM,KAAKD,GACXC,EAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAoQpB,SAAS,KAAK,YAAYA,CAAK;AACjC;AAGI,OAAO,SAAW,OACpBF,EAAA;"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { d as m } from "../../_virtual/drawing_utils.js";
|
|
2
|
+
import { p as T } from "../../_virtual/pose.js";
|
|
3
|
+
const g = {
|
|
4
|
+
showLandmarks: !0,
|
|
5
|
+
showConnections: !0,
|
|
6
|
+
showAngles: !0,
|
|
7
|
+
landmarkColor: "#00FF00",
|
|
8
|
+
connectionColor: "#00FF00",
|
|
9
|
+
angleColor: "#FF0000",
|
|
10
|
+
landmarkRadius: 4,
|
|
11
|
+
lineWidth: 2
|
|
12
|
+
};
|
|
13
|
+
function L(o, e, s = {}) {
|
|
14
|
+
const n = { ...g, ...s };
|
|
15
|
+
n.showConnections && m.drawConnectors(o, e, T.POSE_CONNECTIONS, {
|
|
16
|
+
color: n.connectionColor,
|
|
17
|
+
lineWidth: n.lineWidth
|
|
18
|
+
}), n.showLandmarks && m.drawLandmarks(o, e, {
|
|
19
|
+
color: n.landmarkColor,
|
|
20
|
+
radius: n.landmarkRadius,
|
|
21
|
+
lineWidth: n.lineWidth
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function M(o, e, s, n, k, r, i, p = {}) {
|
|
25
|
+
const l = { ...g, ...p }, c = e.x * r, f = e.y * i, t = s.x * r, a = s.y * i, u = n.x * r, w = n.y * i;
|
|
26
|
+
o.save(), o.strokeStyle = l.angleColor, o.lineWidth = l.lineWidth, o.fillStyle = l.angleColor, o.font = "16px Arial", o.beginPath(), o.moveTo(c, f), o.lineTo(t, a), o.lineTo(u, w), o.stroke();
|
|
27
|
+
const d = 30, h = Math.atan2(f - a, c - t), C = Math.atan2(w - a, u - t);
|
|
28
|
+
o.beginPath(), o.arc(t, a, d, h, C, !1), o.stroke();
|
|
29
|
+
const y = t + Math.cos((h + C) / 2) * (d + 20), F = a + Math.sin((h + C) / 2) * (d + 20);
|
|
30
|
+
o.fillText(`${k.toFixed(1)}°`, y, F), o.restore();
|
|
31
|
+
}
|
|
32
|
+
function O(o) {
|
|
33
|
+
o.clearRect(0, 0, o.canvas.width, o.canvas.height);
|
|
34
|
+
}
|
|
35
|
+
function P(o, e, s) {
|
|
36
|
+
o.width = e, o.height = s;
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
O as clearCanvas,
|
|
40
|
+
g as defaultDrawingOptions,
|
|
41
|
+
M as drawAngle,
|
|
42
|
+
L as drawPoseLandmarks,
|
|
43
|
+
P as resizeCanvas
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=drawingUtils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drawingUtils.js","sources":["../../../src/components/utils/drawingUtils.ts"],"sourcesContent":["import { drawConnectors, drawLandmarks } from '@mediapipe/drawing_utils'\nimport { POSE_CONNECTIONS } from '@mediapipe/pose'\nimport type { PosePoint } from '../../types'\n\nexport interface DrawingOptions {\n showLandmarks?: boolean\n showConnections?: boolean\n showAngles?: boolean\n landmarkColor?: string\n connectionColor?: string\n angleColor?: string\n landmarkRadius?: number\n lineWidth?: number\n}\n\nexport const defaultDrawingOptions: Required<DrawingOptions> = {\n showLandmarks: true,\n showConnections: true,\n showAngles: true,\n landmarkColor: '#00FF00',\n connectionColor: '#00FF00',\n angleColor: '#FF0000',\n landmarkRadius: 4,\n lineWidth: 2,\n}\n\nexport function drawPoseLandmarks(\n ctx: CanvasRenderingContext2D,\n landmarks: PosePoint[],\n options: DrawingOptions = {}\n): void {\n const opts = { ...defaultDrawingOptions, ...options }\n\n if (opts.showConnections) {\n drawConnectors(ctx, landmarks, POSE_CONNECTIONS, {\n color: opts.connectionColor,\n lineWidth: opts.lineWidth,\n })\n }\n\n if (opts.showLandmarks) {\n drawLandmarks(ctx, landmarks, {\n color: opts.landmarkColor,\n radius: opts.landmarkRadius,\n lineWidth: opts.lineWidth,\n })\n }\n}\n\nexport function drawAngle(\n ctx: CanvasRenderingContext2D,\n p1: PosePoint,\n p2: PosePoint,\n p3: PosePoint,\n angle: number,\n canvasWidth: number,\n canvasHeight: number,\n options: DrawingOptions = {}\n): void {\n const opts = { ...defaultDrawingOptions, ...options }\n\n const x1 = p1.x * canvasWidth\n const y1 = p1.y * canvasHeight\n const x2 = p2.x * canvasWidth\n const y2 = p2.y * canvasHeight\n const x3 = p3.x * canvasWidth\n const y3 = p3.y * canvasHeight\n\n ctx.save()\n ctx.strokeStyle = opts.angleColor\n ctx.lineWidth = opts.lineWidth\n ctx.fillStyle = opts.angleColor\n ctx.font = '16px Arial'\n\n ctx.beginPath()\n ctx.moveTo(x1, y1)\n ctx.lineTo(x2, y2)\n ctx.lineTo(x3, y3)\n ctx.stroke()\n\n const arcRadius = 30\n const angle1 = Math.atan2(y1 - y2, x1 - x2)\n const angle2 = Math.atan2(y3 - y2, x3 - x2)\n\n ctx.beginPath()\n ctx.arc(x2, y2, arcRadius, angle1, angle2, false)\n ctx.stroke()\n\n const textX = x2 + Math.cos((angle1 + angle2) / 2) * (arcRadius + 20)\n const textY = y2 + Math.sin((angle1 + angle2) / 2) * (arcRadius + 20)\n ctx.fillText(`${angle.toFixed(1)}°`, textX, textY)\n\n ctx.restore()\n}\n\nexport function clearCanvas(ctx: CanvasRenderingContext2D): void {\n ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)\n}\n\nexport function resizeCanvas(\n canvas: HTMLCanvasElement,\n width: number,\n height: number\n): void {\n canvas.width = width\n canvas.height = height\n}\n"],"names":["defaultDrawingOptions","drawPoseLandmarks","ctx","landmarks","options","opts","drawConnectors","POSE_CONNECTIONS","drawLandmarks","drawAngle","p1","p2","p3","angle","canvasWidth","canvasHeight","x1","y1","x2","y2","x3","y3","arcRadius","angle1","angle2","textX","textY","clearCanvas","resizeCanvas","canvas","width","height"],"mappings":";;AAeO,MAAMA,IAAkD;AAAA,EAC7D,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,WAAW;AACb;AAEO,SAASC,EACdC,GACAC,GACAC,IAA0B,CAAA,GACpB;AACN,QAAMC,IAAO,EAAE,GAAGL,GAAuB,GAAGI,EAAA;AAE5C,EAAIC,EAAK,mBACPC,iBAAeJ,GAAKC,GAAWI,oBAAkB;AAAA,IAC/C,OAAOF,EAAK;AAAA,IACZ,WAAWA,EAAK;AAAA,EAAA,CACjB,GAGCA,EAAK,iBACPG,EAAAA,cAAcN,GAAKC,GAAW;AAAA,IAC5B,OAAOE,EAAK;AAAA,IACZ,QAAQA,EAAK;AAAA,IACb,WAAWA,EAAK;AAAA,EAAA,CACjB;AAEL;AAEO,SAASI,EACdP,GACAQ,GACAC,GACAC,GACAC,GACAC,GACAC,GACAX,IAA0B,IACpB;AACN,QAAMC,IAAO,EAAE,GAAGL,GAAuB,GAAGI,EAAA,GAEtCY,IAAKN,EAAG,IAAII,GACZG,IAAKP,EAAG,IAAIK,GACZG,IAAKP,EAAG,IAAIG,GACZK,IAAKR,EAAG,IAAII,GACZK,IAAKR,EAAG,IAAIE,GACZO,IAAKT,EAAG,IAAIG;AAElB,EAAAb,EAAI,KAAA,GACJA,EAAI,cAAcG,EAAK,YACvBH,EAAI,YAAYG,EAAK,WACrBH,EAAI,YAAYG,EAAK,YACrBH,EAAI,OAAO,cAEXA,EAAI,UAAA,GACJA,EAAI,OAAOc,GAAIC,CAAE,GACjBf,EAAI,OAAOgB,GAAIC,CAAE,GACjBjB,EAAI,OAAOkB,GAAIC,CAAE,GACjBnB,EAAI,OAAA;AAEJ,QAAMoB,IAAY,IACZC,IAAS,KAAK,MAAMN,IAAKE,GAAIH,IAAKE,CAAE,GACpCM,IAAS,KAAK,MAAMH,IAAKF,GAAIC,IAAKF,CAAE;AAE1C,EAAAhB,EAAI,UAAA,GACJA,EAAI,IAAIgB,GAAIC,GAAIG,GAAWC,GAAQC,GAAQ,EAAK,GAChDtB,EAAI,OAAA;AAEJ,QAAMuB,IAAQP,IAAK,KAAK,KAAKK,IAASC,KAAU,CAAC,KAAKF,IAAY,KAC5DI,IAAQP,IAAK,KAAK,KAAKI,IAASC,KAAU,CAAC,KAAKF,IAAY;AAClE,EAAApB,EAAI,SAAS,GAAGW,EAAM,QAAQ,CAAC,CAAC,KAAKY,GAAOC,CAAK,GAEjDxB,EAAI,QAAA;AACN;AAEO,SAASyB,EAAYzB,GAAqC;AAC/D,EAAAA,EAAI,UAAU,GAAG,GAAGA,EAAI,OAAO,OAAOA,EAAI,OAAO,MAAM;AACzD;AAEO,SAAS0B,EACdC,GACAC,GACAC,GACM;AACN,EAAAF,EAAO,QAAQC,GACfD,EAAO,SAASE;AAClB;"}
|