brilliantsole 0.0.29 → 0.0.30
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/build/brilliantsole.cjs +5630 -494
- package/build/brilliantsole.cjs.map +1 -1
- package/build/brilliantsole.js +21293 -3088
- package/build/brilliantsole.js.map +1 -1
- package/build/brilliantsole.ls.js +23153 -6240
- package/build/brilliantsole.ls.js.map +1 -1
- package/build/brilliantsole.min.js +1 -1
- package/build/brilliantsole.min.js.map +1 -1
- package/build/brilliantsole.module.d.ts +1158 -74
- package/build/brilliantsole.module.js +21259 -3089
- package/build/brilliantsole.module.js.map +1 -1
- package/build/brilliantsole.module.min.d.ts +1158 -74
- package/build/brilliantsole.module.min.js +1 -1
- package/build/brilliantsole.module.min.js.map +1 -1
- package/build/brilliantsole.node.module.d.ts +869 -70
- package/build/brilliantsole.node.module.js +5608 -495
- package/build/brilliantsole.node.module.js.map +1 -1
- package/build/dts/BS.d.ts +20 -1
- package/build/dts/Device.d.ts +135 -13
- package/build/dts/DeviceManager.d.ts +3 -3
- package/build/dts/DisplayManager.d.ts +320 -0
- package/build/dts/FileTransferManager.d.ts +10 -4
- package/build/dts/connection/BaseConnectionManager.d.ts +2 -2
- package/build/dts/connection/bluetooth/BluetoothUUID.d.ts +12 -0
- package/build/dts/devicePair/DevicePair.d.ts +5 -5
- package/build/dts/sensor/SensorConfigurationManager.d.ts +2 -1
- package/build/dts/server/BaseClient.d.ts +4 -4
- package/build/dts/server/udp/UDPUtils.d.ts +1 -1
- package/build/dts/utils/ArrayBufferUtils.d.ts +1 -0
- package/build/dts/utils/BitmapUtils.d.ts +17 -0
- package/build/dts/utils/ColorUtils.d.ts +5 -0
- package/build/dts/utils/DisplayBitmapUtils.d.ts +47 -0
- package/build/dts/utils/DisplayCanvasHelper.d.ts +270 -0
- package/build/dts/utils/DisplayContextCommand.d.ts +300 -0
- package/build/dts/utils/DisplayContextState.d.ts +51 -0
- package/build/dts/utils/DisplayContextStateHelper.d.ts +9 -0
- package/build/dts/utils/DisplayManagerInterface.d.ts +173 -0
- package/build/dts/utils/DisplaySpriteSheetUtils.d.ts +72 -0
- package/build/dts/utils/DisplayUtils.d.ts +70 -0
- package/build/dts/utils/MathUtils.d.ts +16 -0
- package/build/dts/utils/PathUtils.d.ts +4 -0
- package/build/dts/utils/RangeHelper.d.ts +7 -0
- package/build/dts/utils/SpriteSheetUtils.d.ts +20 -0
- package/build/index.d.ts +1156 -72
- package/build/index.node.d.ts +867 -68
- package/examples/3d-generic/index.html +5 -0
- package/examples/3d-generic/script.js +1 -0
- package/examples/basic/index.html +335 -0
- package/examples/basic/script.js +1303 -3
- package/examples/camera/utils.js +1 -1
- package/examples/display-3d/index.html +195 -0
- package/examples/display-3d/script.js +1235 -0
- package/examples/display-canvas/aframe.js +42950 -0
- package/examples/display-canvas/index.html +245 -0
- package/examples/display-canvas/script.js +2312 -0
- package/examples/display-image/index.html +189 -0
- package/examples/display-image/script.js +1093 -0
- package/examples/display-spritesheet/index.html +960 -0
- package/examples/display-spritesheet/script.js +4243 -0
- package/examples/display-text/index.html +195 -0
- package/examples/display-text/script.js +1418 -0
- package/examples/display-wireframe/index.html +204 -0
- package/examples/display-wireframe/script.js +1167 -0
- package/examples/glasses-gestures/index.html +6 -1
- package/examples/glasses-gestures/script.js +10 -8
- package/examples/microphone/index.html +3 -1
- package/examples/punch/index.html +4 -1
- package/examples/server/script.js +0 -1
- package/package.json +10 -2
- package/src/BS.ts +92 -1
- package/src/CameraManager.ts +6 -2
- package/src/Device.ts +544 -13
- package/src/DisplayManager.ts +2989 -0
- package/src/FileTransferManager.ts +79 -26
- package/src/InformationManager.ts +8 -7
- package/src/MicrophoneManager.ts +10 -3
- package/src/TfliteManager.ts +4 -2
- package/src/WifiManager.ts +4 -1
- package/src/connection/BaseConnectionManager.ts +2 -0
- package/src/connection/bluetooth/bluetoothUUIDs.ts +36 -1
- package/src/devicePair/DevicePairPressureSensorDataManager.ts +1 -1
- package/src/scanner/NobleScanner.ts +1 -1
- package/src/sensor/SensorConfigurationManager.ts +16 -8
- package/src/server/udp/UDPServer.ts +4 -4
- package/src/server/udp/UDPUtils.ts +1 -1
- package/src/server/websocket/WebSocketClient.ts +50 -1
- package/src/utils/ArrayBufferUtils.ts +23 -5
- package/src/utils/AudioUtils.ts +1 -1
- package/src/utils/ColorUtils.ts +66 -0
- package/src/utils/DisplayBitmapUtils.ts +695 -0
- package/src/utils/DisplayCanvasHelper.ts +4222 -0
- package/src/utils/DisplayContextCommand.ts +1566 -0
- package/src/utils/DisplayContextState.ts +138 -0
- package/src/utils/DisplayContextStateHelper.ts +48 -0
- package/src/utils/DisplayManagerInterface.ts +1356 -0
- package/src/utils/DisplaySpriteSheetUtils.ts +782 -0
- package/src/utils/DisplayUtils.ts +529 -0
- package/src/utils/EventDispatcher.ts +59 -14
- package/src/utils/MathUtils.ts +88 -2
- package/src/utils/ObjectUtils.ts +6 -1
- package/src/utils/PathUtils.ts +192 -0
- package/src/utils/RangeHelper.ts +15 -3
- package/src/utils/Timer.ts +1 -1
- package/src/utils/environment.ts +15 -6
- package/examples/microphone/gender.js +0 -54
|
@@ -0,0 +1,1093 @@
|
|
|
1
|
+
import * as BS from "../../build/brilliantsole.module.js";
|
|
2
|
+
|
|
3
|
+
// DEVICE
|
|
4
|
+
const device = new BS.Device();
|
|
5
|
+
window.device = device;
|
|
6
|
+
window.BS = BS;
|
|
7
|
+
|
|
8
|
+
// CONNECT
|
|
9
|
+
|
|
10
|
+
const toggleConnectionButton = document.getElementById("toggleConnection");
|
|
11
|
+
toggleConnectionButton.addEventListener("click", () =>
|
|
12
|
+
device.toggleConnection()
|
|
13
|
+
);
|
|
14
|
+
device.addEventListener("connectionStatus", () => {
|
|
15
|
+
let disabled = false;
|
|
16
|
+
let innerText = device.connectionStatus;
|
|
17
|
+
switch (device.connectionStatus) {
|
|
18
|
+
case "notConnected":
|
|
19
|
+
innerText = "connect";
|
|
20
|
+
break;
|
|
21
|
+
case "connected":
|
|
22
|
+
innerText = "disconnect";
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
toggleConnectionButton.disabled = disabled;
|
|
26
|
+
toggleConnectionButton.innerText = innerText;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// WEBSOCKET CLIENT
|
|
30
|
+
|
|
31
|
+
const client = new BS.WebSocketClient();
|
|
32
|
+
window.client = client;
|
|
33
|
+
|
|
34
|
+
// WEBSOCKET URL SEARCH PARAMS
|
|
35
|
+
|
|
36
|
+
const url = new URL(location);
|
|
37
|
+
function setUrlParam(key, value) {
|
|
38
|
+
if (history.pushState) {
|
|
39
|
+
let searchParams = new URLSearchParams(window.location.search);
|
|
40
|
+
if (value) {
|
|
41
|
+
searchParams.set(key, value);
|
|
42
|
+
} else {
|
|
43
|
+
searchParams.delete(key);
|
|
44
|
+
}
|
|
45
|
+
let newUrl =
|
|
46
|
+
window.location.protocol +
|
|
47
|
+
"//" +
|
|
48
|
+
window.location.host +
|
|
49
|
+
window.location.pathname +
|
|
50
|
+
"?" +
|
|
51
|
+
searchParams.toString();
|
|
52
|
+
window.history.pushState({ path: newUrl }, "", newUrl);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
client.addEventListener("isConnected", () => {
|
|
56
|
+
if (client.isConnected) {
|
|
57
|
+
setUrlParam("webSocketUrl", client.webSocket.url);
|
|
58
|
+
webSocketUrlInput.value = client.webSocket.url;
|
|
59
|
+
webSocketUrlInput.dispatchEvent(new Event("input"));
|
|
60
|
+
} else {
|
|
61
|
+
setUrlParam("webSocketUrl");
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// WEBSOCKET SERVER URL
|
|
66
|
+
|
|
67
|
+
/** @type {HTMLInputElement} */
|
|
68
|
+
const webSocketUrlInput = document.getElementById("webSocketUrl");
|
|
69
|
+
webSocketUrlInput.value = url.searchParams.get("webSocketUrl") || "";
|
|
70
|
+
webSocketUrlInput.dispatchEvent(new Event("input"));
|
|
71
|
+
|
|
72
|
+
// WEBSOCKET CONNECTION
|
|
73
|
+
|
|
74
|
+
/** @type {HTMLButtonElement} */
|
|
75
|
+
const toggleClientConnectionButton = document.getElementById(
|
|
76
|
+
"toggleClientConnection"
|
|
77
|
+
);
|
|
78
|
+
toggleClientConnectionButton.addEventListener("click", () => {
|
|
79
|
+
if (client.isConnected) {
|
|
80
|
+
client.disconnect();
|
|
81
|
+
} else {
|
|
82
|
+
/** @type {string?} */
|
|
83
|
+
let webSocketUrl;
|
|
84
|
+
if (webSocketUrlInput.value.length > 0) {
|
|
85
|
+
webSocketUrl = webSocketUrlInput.value;
|
|
86
|
+
}
|
|
87
|
+
client.connect(webSocketUrl);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
client.addEventListener("connectionStatus", () => {
|
|
91
|
+
switch (client.connectionStatus) {
|
|
92
|
+
case "connected":
|
|
93
|
+
case "notConnected":
|
|
94
|
+
toggleClientConnectionButton.disabled = false;
|
|
95
|
+
toggleClientConnectionButton.innerText = client.isConnected
|
|
96
|
+
? "disconnect from server"
|
|
97
|
+
: "connect to server";
|
|
98
|
+
break;
|
|
99
|
+
case "connecting":
|
|
100
|
+
case "disconnecting":
|
|
101
|
+
toggleClientConnectionButton.innerText = client.connectionStatus;
|
|
102
|
+
toggleClientConnectionButton.disabled = true;
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// WEBSOCKET SCANNER
|
|
108
|
+
|
|
109
|
+
/** @type {HTMLInputElement} */
|
|
110
|
+
const isScanningAvailableCheckbox = document.getElementById(
|
|
111
|
+
"isScanningAvailable"
|
|
112
|
+
);
|
|
113
|
+
client.addEventListener("isScanningAvailable", () => {
|
|
114
|
+
isScanningAvailableCheckbox.checked = client.isScanningAvailable;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
/** @type {HTMLButtonElement} */
|
|
118
|
+
const toggleScanButton = document.getElementById("toggleScan");
|
|
119
|
+
toggleScanButton.addEventListener("click", () => {
|
|
120
|
+
client.toggleScan();
|
|
121
|
+
});
|
|
122
|
+
client.addEventListener("isScanningAvailable", () => {
|
|
123
|
+
toggleScanButton.disabled = !client.isScanningAvailable;
|
|
124
|
+
});
|
|
125
|
+
client.addEventListener("isScanning", () => {
|
|
126
|
+
toggleScanButton.innerText = client.isScanning ? "stop scanning" : "scan";
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
/** @type {BS.Device?} */
|
|
130
|
+
let clientDevice;
|
|
131
|
+
client.addEventListener("discoveredDevice", (event) => {
|
|
132
|
+
console.log(event);
|
|
133
|
+
if (clientDevice) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const { discoveredDevice } = event.message;
|
|
137
|
+
if (discoveredDevice.deviceType == "glasses") {
|
|
138
|
+
console.log("connecting to discoveredDevice", discoveredDevice);
|
|
139
|
+
clientDevice = client.connectToDevice(discoveredDevice.bluetoothId);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// DEVICE
|
|
144
|
+
BS.DeviceManager.AddEventListener("deviceConnected", (event) => {
|
|
145
|
+
if (event.message.device.connectionType != "client") {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (event.message.device.isDisplayAvailable) {
|
|
149
|
+
clientDevice = event.message.device;
|
|
150
|
+
if (client.isScanning) {
|
|
151
|
+
client.stopScan();
|
|
152
|
+
}
|
|
153
|
+
displayCanvasHelper.device = clientDevice;
|
|
154
|
+
} else {
|
|
155
|
+
console.log("display not available");
|
|
156
|
+
// event.message.device.disconnect();
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// CANVAS
|
|
161
|
+
/** @type {HTMLCanvasElement} */
|
|
162
|
+
const displayCanvas = document.getElementById("display");
|
|
163
|
+
|
|
164
|
+
// DISPLAY CANVAS HELPER
|
|
165
|
+
const displayCanvasHelper = new BS.DisplayCanvasHelper();
|
|
166
|
+
// displayCanvasHelper.setBrightness("veryLow");
|
|
167
|
+
displayCanvasHelper.canvas = displayCanvas;
|
|
168
|
+
window.displayCanvasHelper = displayCanvasHelper;
|
|
169
|
+
|
|
170
|
+
device.addEventListener("connected", () => {
|
|
171
|
+
if (device.isDisplayAvailable) {
|
|
172
|
+
displayCanvasHelper.device = device;
|
|
173
|
+
} else {
|
|
174
|
+
console.error("device doesn't have a display");
|
|
175
|
+
device.disconnect();
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// BRIGHTNESS
|
|
180
|
+
/** @type {HTMLSelectElement} */
|
|
181
|
+
const setDisplayBrightnessSelect = document.getElementById(
|
|
182
|
+
"setDisplayBrightnessSelect"
|
|
183
|
+
);
|
|
184
|
+
/** @type {HTMLOptGroupElement} */
|
|
185
|
+
const setDisplayBrightnessSelectOptgroup =
|
|
186
|
+
setDisplayBrightnessSelect.querySelector("optgroup");
|
|
187
|
+
BS.DisplayBrightnesses.forEach((displayBrightness) => {
|
|
188
|
+
setDisplayBrightnessSelectOptgroup.appendChild(new Option(displayBrightness));
|
|
189
|
+
});
|
|
190
|
+
setDisplayBrightnessSelect.addEventListener("input", () => {
|
|
191
|
+
displayCanvasHelper.setBrightness(setDisplayBrightnessSelect.value);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
setDisplayBrightnessSelect.value = displayCanvasHelper.brightness;
|
|
195
|
+
|
|
196
|
+
// COLORS
|
|
197
|
+
|
|
198
|
+
/** @type {HTMLTemplateElement} */
|
|
199
|
+
const displayColorTemplate = document.getElementById("displayColorTemplate");
|
|
200
|
+
const displayColorsContainer = document.getElementById("displayColors");
|
|
201
|
+
const setDisplayColor = BS.ThrottleUtils.throttle(
|
|
202
|
+
(colorIndex, colorString) => {
|
|
203
|
+
console.log({ colorIndex, colorString });
|
|
204
|
+
displayCanvasHelper.setColor(colorIndex, colorString, true);
|
|
205
|
+
},
|
|
206
|
+
100,
|
|
207
|
+
true
|
|
208
|
+
);
|
|
209
|
+
/** @type {HTMLInputElement[]} */
|
|
210
|
+
const displayColorInputs = [];
|
|
211
|
+
const setupColors = () => {
|
|
212
|
+
displayColorsContainer.innerHTML = "";
|
|
213
|
+
for (
|
|
214
|
+
let colorIndex = 0;
|
|
215
|
+
colorIndex < displayCanvasHelper.numberOfColors;
|
|
216
|
+
colorIndex++
|
|
217
|
+
) {
|
|
218
|
+
const displayColorContainer = displayColorTemplate.content
|
|
219
|
+
.cloneNode(true)
|
|
220
|
+
.querySelector(".displayColor");
|
|
221
|
+
|
|
222
|
+
const colorIndexSpan = displayColorContainer.querySelector(".colorIndex");
|
|
223
|
+
colorIndexSpan.innerText = `color #${colorIndex}`;
|
|
224
|
+
const colorInput = displayColorContainer.querySelector("input");
|
|
225
|
+
displayColorInputs[colorIndex] = colorInput;
|
|
226
|
+
colorInput.addEventListener("input", () => {
|
|
227
|
+
setDisplayColor(colorIndex, colorInput.value);
|
|
228
|
+
});
|
|
229
|
+
displayColorsContainer.appendChild(displayColorContainer);
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
setupColors();
|
|
233
|
+
displayCanvasHelper.addEventListener("numberOfColors", () => setupColors());
|
|
234
|
+
displayCanvasHelper.addEventListener("color", (event) => {
|
|
235
|
+
const { colorHex, colorIndex } = event.message;
|
|
236
|
+
displayColorInputs[colorIndex].value = colorHex;
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// IMAGE PREVIEW
|
|
240
|
+
|
|
241
|
+
/** @type {HTMLInputElement} */
|
|
242
|
+
const imageInput = document.getElementById("imageInput");
|
|
243
|
+
/** @type {HTMLImageElement} */
|
|
244
|
+
const image = document.getElementById("image");
|
|
245
|
+
imageInput.addEventListener("input", () => {
|
|
246
|
+
const file = imageInput.files[0];
|
|
247
|
+
if (!file) return;
|
|
248
|
+
loadImage(file);
|
|
249
|
+
imageInput.value = "";
|
|
250
|
+
});
|
|
251
|
+
const loadImage = (file) => {
|
|
252
|
+
const reader = new FileReader();
|
|
253
|
+
reader.onload = () => {
|
|
254
|
+
image.src = reader.result;
|
|
255
|
+
};
|
|
256
|
+
reader.readAsDataURL(file);
|
|
257
|
+
};
|
|
258
|
+
image.addEventListener("load", () => {
|
|
259
|
+
drawImage();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const redrawImageButton = document.getElementById("redrawImage");
|
|
263
|
+
redrawImageButton.addEventListener("click", () => {
|
|
264
|
+
drawImage();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const tempCanvas = document.createElement("canvas");
|
|
268
|
+
const tempCtx = tempCanvas.getContext("2d");
|
|
269
|
+
const drawImage = async () => {
|
|
270
|
+
let srcWidth, srcHeight, src;
|
|
271
|
+
let useCameraVideo = cameraVideo.srcObject;
|
|
272
|
+
if (useCameraVideo) {
|
|
273
|
+
srcWidth = cameraVideo.videoWidth;
|
|
274
|
+
srcHeight = cameraVideo.videoHeight;
|
|
275
|
+
src = cameraVideo;
|
|
276
|
+
} else {
|
|
277
|
+
srcWidth = image.naturalWidth;
|
|
278
|
+
srcHeight = image.naturalHeight;
|
|
279
|
+
src = image;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
let inputImageScale = 1;
|
|
283
|
+
inputImageScale = drawInputHeight / srcHeight;
|
|
284
|
+
const inputImageWidth = Math.round(srcWidth * inputImageScale);
|
|
285
|
+
const inputImageHeight = Math.round(srcHeight * inputImageScale);
|
|
286
|
+
|
|
287
|
+
canvas.width = inputImageWidth;
|
|
288
|
+
canvas.height = inputImageHeight;
|
|
289
|
+
|
|
290
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
291
|
+
|
|
292
|
+
if (useGrayscale) {
|
|
293
|
+
ctx.filter = "grayscale(100%)";
|
|
294
|
+
}
|
|
295
|
+
ctx.resetTransform();
|
|
296
|
+
if (useCameraVideo && mirrorCamera) {
|
|
297
|
+
ctx.scale(-1, 1);
|
|
298
|
+
ctx.translate(-canvas.width, 0);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (useImageSegmentation && imageSegmenter) {
|
|
302
|
+
tempCanvas.width = srcWidth;
|
|
303
|
+
tempCanvas.height = srcHeight;
|
|
304
|
+
|
|
305
|
+
tempCtx.drawImage(
|
|
306
|
+
src,
|
|
307
|
+
0,
|
|
308
|
+
0,
|
|
309
|
+
srcWidth,
|
|
310
|
+
srcHeight,
|
|
311
|
+
0,
|
|
312
|
+
0,
|
|
313
|
+
tempCanvas.width,
|
|
314
|
+
tempCanvas.height
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
const result = imageSegmenter.segmentForVideo(
|
|
318
|
+
tempCanvas,
|
|
319
|
+
performance.now()
|
|
320
|
+
);
|
|
321
|
+
console.log("imageSegmenter result", result);
|
|
322
|
+
|
|
323
|
+
const { width, height } = result.categoryMask;
|
|
324
|
+
let imageData = tempCtx.getImageData(0, 0, width, height).data;
|
|
325
|
+
tempCanvas.width = width;
|
|
326
|
+
tempCanvas.height = height;
|
|
327
|
+
const mask = result.categoryMask.getAsUint8Array();
|
|
328
|
+
for (let i in mask) {
|
|
329
|
+
const isPerson = mask[i] == 0 ? 1 : 0;
|
|
330
|
+
imageData[i * 4] *= isPerson;
|
|
331
|
+
imageData[i * 4 + 1] *= isPerson;
|
|
332
|
+
imageData[i * 4 + 2] *= isPerson;
|
|
333
|
+
//imageData[i * 4 + 3] = 255;
|
|
334
|
+
}
|
|
335
|
+
const uint8Array = new Uint8ClampedArray(imageData.buffer);
|
|
336
|
+
const dataNew = new ImageData(uint8Array, width, height);
|
|
337
|
+
tempCtx.putImageData(dataNew, 0, 0);
|
|
338
|
+
src = tempCanvas;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
ctx.drawImage(
|
|
342
|
+
src,
|
|
343
|
+
0,
|
|
344
|
+
0,
|
|
345
|
+
srcWidth,
|
|
346
|
+
srcHeight,
|
|
347
|
+
0,
|
|
348
|
+
0,
|
|
349
|
+
canvas.width,
|
|
350
|
+
canvas.height
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
draw();
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
// DRAGOVER
|
|
357
|
+
window.addEventListener("dragover", (e) => {
|
|
358
|
+
e.preventDefault();
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
window.addEventListener("drop", (e) => {
|
|
362
|
+
e.preventDefault();
|
|
363
|
+
const file = e.dataTransfer.files[0];
|
|
364
|
+
if (file) {
|
|
365
|
+
if (file.type.startsWith("image/")) {
|
|
366
|
+
loadImage(file);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// CONTEXT FILTER
|
|
372
|
+
let useGrayscale = false;
|
|
373
|
+
const useGrayscaleInput = document.getElementById("useGrayscale");
|
|
374
|
+
useGrayscaleInput.addEventListener("input", () => {
|
|
375
|
+
setUseGrayscale(useGrayscaleInput.checked);
|
|
376
|
+
});
|
|
377
|
+
const setUseGrayscale = (newUseGrayscale) => {
|
|
378
|
+
useGrayscale = newUseGrayscale;
|
|
379
|
+
console.log({ useGrayscale });
|
|
380
|
+
useGrayscaleInput.checked = useGrayscale;
|
|
381
|
+
if (redrawOnChange) {
|
|
382
|
+
drawImage();
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
// REDRAW ON CHANGE
|
|
387
|
+
let redrawOnChange = false;
|
|
388
|
+
const redrawOnChangeInput = document.getElementById("redrawOnChange");
|
|
389
|
+
redrawOnChangeInput.addEventListener("input", () => {
|
|
390
|
+
setRedrawOnChange(redrawOnChangeInput.checked);
|
|
391
|
+
});
|
|
392
|
+
const setRedrawOnChange = (newRedrawOnChange) => {
|
|
393
|
+
redrawOnChange = newRedrawOnChange;
|
|
394
|
+
console.log({ redrawOnChange });
|
|
395
|
+
redrawOnChangeInput.checked = redrawOnChange;
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
// AUTO DRAW VIDEO
|
|
399
|
+
let autoDrawVideo = false;
|
|
400
|
+
const autoDrawVideoInput = document.getElementById("autoDrawVideo");
|
|
401
|
+
autoDrawVideoInput.addEventListener("input", () => {
|
|
402
|
+
setAutoDrawVideo(autoDrawVideoInput.checked);
|
|
403
|
+
});
|
|
404
|
+
const setAutoDrawVideo = (newAutoDrawVideo) => {
|
|
405
|
+
autoDrawVideo = newAutoDrawVideo;
|
|
406
|
+
console.log({ autoDrawVideo });
|
|
407
|
+
autoDrawVideoInput.checked = autoDrawVideo;
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
// PASTE
|
|
411
|
+
function isValidUrl(string) {
|
|
412
|
+
try {
|
|
413
|
+
new URL(string);
|
|
414
|
+
return true;
|
|
415
|
+
} catch (_) {
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
window.addEventListener("paste", (event) => {
|
|
420
|
+
const string = event.clipboardData.getData("text");
|
|
421
|
+
if (!isValidUrl(string)) {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
image.src = string;
|
|
425
|
+
});
|
|
426
|
+
window.addEventListener("paste", (event) => {
|
|
427
|
+
const items = event.clipboardData.items;
|
|
428
|
+
for (let i = 0; i < items.length; i++) {
|
|
429
|
+
const item = items[i];
|
|
430
|
+
if (item.type.startsWith("image/")) {
|
|
431
|
+
const file = item.getAsFile();
|
|
432
|
+
loadImage(file);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// CAMERA
|
|
439
|
+
/** @type {HTMLVideoElement} */
|
|
440
|
+
const cameraVideo = document.getElementById("cameraVideo");
|
|
441
|
+
cameraVideo.volume = 0.0001;
|
|
442
|
+
cameraVideo.addEventListener("loadedmetadata", () => {
|
|
443
|
+
const { videoWidth, videoHeight } = cameraVideo;
|
|
444
|
+
cameraVideo.removeAttribute("hidden");
|
|
445
|
+
});
|
|
446
|
+
const toggleMirrorCameraButton = document.getElementById("toggleMirrorCamera");
|
|
447
|
+
let mirrorCamera = false;
|
|
448
|
+
const setMirrorCamera = (newMirrorCamera) => {
|
|
449
|
+
mirrorCamera = newMirrorCamera;
|
|
450
|
+
// console.log({ mirrorCamera });
|
|
451
|
+
cameraVideo.style.transform = mirrorCamera ? "scaleX(-1)" : "";
|
|
452
|
+
toggleMirrorCameraButton.innerText = mirrorCamera
|
|
453
|
+
? "unmirror camera"
|
|
454
|
+
: "mirror camera";
|
|
455
|
+
};
|
|
456
|
+
toggleMirrorCameraButton.addEventListener("click", () => {
|
|
457
|
+
setMirrorCamera(!mirrorCamera);
|
|
458
|
+
});
|
|
459
|
+
setMirrorCamera(true);
|
|
460
|
+
|
|
461
|
+
/** @type {HTMLSelectElement} */
|
|
462
|
+
const cameraInput = document.getElementById("cameraInput");
|
|
463
|
+
const cameraInputOptgroup = cameraInput.querySelector("optgroup");
|
|
464
|
+
cameraInput.addEventListener("input", () => {
|
|
465
|
+
selectCameraInput(cameraInput.value);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
cameraInput.addEventListener("click", async () => {
|
|
469
|
+
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
470
|
+
const videoDevices = devices.filter((device) => device.kind == "videoinput");
|
|
471
|
+
console.log("videoDevices", videoDevices);
|
|
472
|
+
if (videoDevices.length == 1 && videoDevices[0].deviceId == "") {
|
|
473
|
+
console.log("getting camera");
|
|
474
|
+
const cameraStream = await navigator.mediaDevices.getUserMedia({
|
|
475
|
+
video: true,
|
|
476
|
+
});
|
|
477
|
+
cameraStream.getVideoTracks().forEach((track) => track.stop());
|
|
478
|
+
updateCameraSources();
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
const updateCameraSources = async () => {
|
|
483
|
+
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
484
|
+
cameraInputOptgroup.innerHTML = "";
|
|
485
|
+
cameraInputOptgroup.appendChild(new Option("none"));
|
|
486
|
+
devices
|
|
487
|
+
.filter((device) => device.kind == "videoinput")
|
|
488
|
+
.forEach((videoInputDevice) => {
|
|
489
|
+
cameraInputOptgroup.appendChild(
|
|
490
|
+
new Option(videoInputDevice.label, videoInputDevice.deviceId)
|
|
491
|
+
);
|
|
492
|
+
});
|
|
493
|
+
cameraInput.value = "none";
|
|
494
|
+
selectCameraInput(cameraInput.value);
|
|
495
|
+
};
|
|
496
|
+
/** @type {MediaStream?} */
|
|
497
|
+
let cameraStream;
|
|
498
|
+
const selectCameraInput = async (deviceId) => {
|
|
499
|
+
stopCameraStream();
|
|
500
|
+
if (deviceId != "none") {
|
|
501
|
+
cameraStream = await navigator.mediaDevices.getUserMedia({
|
|
502
|
+
video: {
|
|
503
|
+
deviceId: { exact: deviceId },
|
|
504
|
+
width: { ideal: 1280 },
|
|
505
|
+
height: { ideal: 1280 },
|
|
506
|
+
},
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
cameraVideo.srcObject = cameraStream;
|
|
510
|
+
console.log("got cameraStream", deviceId, cameraStream);
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
const stopCameraStream = () => {
|
|
514
|
+
if (cameraStream) {
|
|
515
|
+
console.log("stopping cameraStream");
|
|
516
|
+
cameraStream.getVideoTracks().forEach((track) => track.stop());
|
|
517
|
+
}
|
|
518
|
+
cameraStream = undefined;
|
|
519
|
+
cameraVideo.srcObject = undefined;
|
|
520
|
+
cameraVideo.setAttribute("hidden", "");
|
|
521
|
+
};
|
|
522
|
+
navigator.mediaDevices.addEventListener("devicechange", () =>
|
|
523
|
+
updateCameraSources()
|
|
524
|
+
);
|
|
525
|
+
updateCameraSources();
|
|
526
|
+
|
|
527
|
+
// DRAW PARAMS
|
|
528
|
+
|
|
529
|
+
const drawXContainer = document.getElementById("drawX");
|
|
530
|
+
const drawXInput = drawXContainer.querySelector("input");
|
|
531
|
+
const drawXSpan = drawXContainer.querySelector("span.value");
|
|
532
|
+
let drawX = Number(drawXInput.value);
|
|
533
|
+
|
|
534
|
+
drawXInput.addEventListener("input", () => {
|
|
535
|
+
drawX = Number(drawXInput.value);
|
|
536
|
+
// console.log({ drawX });
|
|
537
|
+
drawXSpan.innerText = drawX;
|
|
538
|
+
});
|
|
539
|
+
drawXInput.addEventListener("input", () => {
|
|
540
|
+
if (redrawOnChange) {
|
|
541
|
+
drawImage();
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
const drawYContainer = document.getElementById("drawY");
|
|
546
|
+
const drawYInput = drawYContainer.querySelector("input");
|
|
547
|
+
const drawYSpan = drawYContainer.querySelector("span.value");
|
|
548
|
+
let drawY = Number(drawYInput.value);
|
|
549
|
+
|
|
550
|
+
drawYInput.addEventListener("input", () => {
|
|
551
|
+
drawY = Number(drawYInput.value);
|
|
552
|
+
//console.log({ drawY });
|
|
553
|
+
drawYSpan.innerText = drawY;
|
|
554
|
+
});
|
|
555
|
+
drawYInput.addEventListener("input", () => {
|
|
556
|
+
if (redrawOnChange) {
|
|
557
|
+
drawImage();
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
const drawInputHeightContainer = document.getElementById("drawInputHeight");
|
|
562
|
+
const drawInputHeightInput = drawInputHeightContainer.querySelector("input");
|
|
563
|
+
const drawInputHeightSpan =
|
|
564
|
+
drawInputHeightContainer.querySelector("span.value");
|
|
565
|
+
let drawInputHeight = Number(drawInputHeightInput.value);
|
|
566
|
+
|
|
567
|
+
drawInputHeightInput.addEventListener("input", () => {
|
|
568
|
+
drawInputHeight = Number(drawInputHeightInput.value);
|
|
569
|
+
//console.log({ drawInputHeight });
|
|
570
|
+
drawInputHeightSpan.innerText = drawInputHeight;
|
|
571
|
+
});
|
|
572
|
+
drawInputHeightInput.addEventListener("change", () => {
|
|
573
|
+
if (redrawOnChange) {
|
|
574
|
+
drawImage();
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
const drawOutputHeightContainer = document.getElementById("drawOutputHeight");
|
|
579
|
+
const drawOutputHeightInput = drawOutputHeightContainer.querySelector("input");
|
|
580
|
+
const drawOutputHeightSpan =
|
|
581
|
+
drawOutputHeightContainer.querySelector("span.value");
|
|
582
|
+
let drawOutputHeight = Number(drawOutputHeightInput.value);
|
|
583
|
+
|
|
584
|
+
drawOutputHeightInput.addEventListener("input", () => {
|
|
585
|
+
drawOutputHeight = Number(drawOutputHeightInput.value);
|
|
586
|
+
//console.log({ drawOutputHeight });
|
|
587
|
+
drawOutputHeightSpan.innerText = drawOutputHeight;
|
|
588
|
+
});
|
|
589
|
+
drawOutputHeightInput.addEventListener("change", () => {
|
|
590
|
+
if (redrawOnChange) {
|
|
591
|
+
drawImage();
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
// PIXEL DEPTH
|
|
596
|
+
|
|
597
|
+
let pixelDepth = BS.DisplayPixelDepths[2];
|
|
598
|
+
const setPixelDepth = (newPixelDepth) => {
|
|
599
|
+
pixelDepth = newPixelDepth;
|
|
600
|
+
console.log({ pixelDepth });
|
|
601
|
+
if (redrawOnChange) {
|
|
602
|
+
drawImage();
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
const pixelDepthSelect = document.getElementById("pixelDepth");
|
|
606
|
+
const pixelDepthOptgroup = pixelDepthSelect.querySelector("optgroup");
|
|
607
|
+
pixelDepthSelect.addEventListener("input", () => {
|
|
608
|
+
setPixelDepth(pixelDepthSelect.value);
|
|
609
|
+
});
|
|
610
|
+
BS.DisplayPixelDepths.forEach((pixelDepth) => {
|
|
611
|
+
pixelDepthOptgroup.appendChild(
|
|
612
|
+
new Option(
|
|
613
|
+
`${BS.pixelDepthToNumberOfColors(pixelDepth)} colors`,
|
|
614
|
+
pixelDepth
|
|
615
|
+
)
|
|
616
|
+
);
|
|
617
|
+
});
|
|
618
|
+
pixelDepthSelect.value = pixelDepth;
|
|
619
|
+
|
|
620
|
+
// DRAW
|
|
621
|
+
let defaultMaxFileLength = 10 * 1024; // 10kb
|
|
622
|
+
let defaultMtu = 247;
|
|
623
|
+
let currentSpriteIndexBeingDrawn = 0;
|
|
624
|
+
let isDrawing = false;
|
|
625
|
+
/** @type {BS.DisplaySpriteSheet} */
|
|
626
|
+
let spriteSheet;
|
|
627
|
+
let drawWhenReady = false;
|
|
628
|
+
|
|
629
|
+
/** @type {HTMLCanvasElement} */
|
|
630
|
+
const canvas = document.getElementById("canvas");
|
|
631
|
+
const ctx = canvas.getContext("2d");
|
|
632
|
+
|
|
633
|
+
let drawUsingBitmap = false;
|
|
634
|
+
|
|
635
|
+
const draw = async () => {
|
|
636
|
+
if (!canvas.height || !canvas.width) {
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
if (isDrawing) {
|
|
640
|
+
//console.warn("busy drawing");
|
|
641
|
+
drawWhenReady = true;
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
isDrawing = true;
|
|
645
|
+
|
|
646
|
+
console.log("drawing...");
|
|
647
|
+
|
|
648
|
+
canvas.removeAttribute("hidden");
|
|
649
|
+
cameraVideo.setAttribute("hidden", "");
|
|
650
|
+
|
|
651
|
+
const { width, height } = canvas;
|
|
652
|
+
|
|
653
|
+
let spriteScale = 1;
|
|
654
|
+
spriteScale = drawOutputHeight / height;
|
|
655
|
+
|
|
656
|
+
const outputImageWidth = Math.round(width * spriteScale);
|
|
657
|
+
const outputImageHeight = Math.round(height * spriteScale);
|
|
658
|
+
|
|
659
|
+
console.log({ spriteScale, outputImageHeight, outputImageWidth });
|
|
660
|
+
|
|
661
|
+
const numberOfColors = 2 ** pixelDepth;
|
|
662
|
+
|
|
663
|
+
if (drawUsingBitmap) {
|
|
664
|
+
const mtu = displayCanvasHelper.device?.isConnected
|
|
665
|
+
? displayCanvasHelper.device.mtu
|
|
666
|
+
: defaultMtu;
|
|
667
|
+
|
|
668
|
+
const resizedCanvas = BS.resizeImage(
|
|
669
|
+
canvas,
|
|
670
|
+
outputImageWidth,
|
|
671
|
+
outputImageHeight
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
const { bitmapRows, colors } = await BS.canvasToBitmaps(
|
|
675
|
+
resizedCanvas,
|
|
676
|
+
numberOfColors,
|
|
677
|
+
mtu
|
|
678
|
+
);
|
|
679
|
+
console.log("bitmapRows", bitmapRows, colors);
|
|
680
|
+
|
|
681
|
+
let offsetX = drawX - outputImageWidth / 2;
|
|
682
|
+
let offsetY = drawY - outputImageHeight / 2;
|
|
683
|
+
await displayCanvasHelper.setHorizontalAlignment("start");
|
|
684
|
+
await displayCanvasHelper.setVerticalAlignment("start");
|
|
685
|
+
for (let bitmapRowIndex in bitmapRows) {
|
|
686
|
+
const bitmapRow = bitmapRows[bitmapRowIndex];
|
|
687
|
+
offsetX = drawX - outputImageWidth / 2;
|
|
688
|
+
for (let bitmapIndex in bitmapRow) {
|
|
689
|
+
const bitmap = bitmapRow[bitmapIndex];
|
|
690
|
+
//console.log("drawing bitmap", { offsetX, offsetY }, bitmap);
|
|
691
|
+
await displayCanvasHelper.drawBitmap(offsetX, offsetY, bitmap);
|
|
692
|
+
offsetX += bitmap.width;
|
|
693
|
+
}
|
|
694
|
+
offsetY += bitmapRow[0].height;
|
|
695
|
+
}
|
|
696
|
+
for (let colorIndex in colors) {
|
|
697
|
+
await displayCanvasHelper.setColor(colorIndex, colors[colorIndex]);
|
|
698
|
+
await displayCanvasHelper.selectBitmapColor(colorIndex, colorIndex);
|
|
699
|
+
}
|
|
700
|
+
} else {
|
|
701
|
+
const maxFileLength = displayCanvasHelper.device?.isConnected
|
|
702
|
+
? displayCanvasHelper.device.maxFileLength
|
|
703
|
+
: defaultMaxFileLength;
|
|
704
|
+
|
|
705
|
+
spriteSheet = await BS.canvasToSpriteSheet(
|
|
706
|
+
canvas,
|
|
707
|
+
"image",
|
|
708
|
+
numberOfColors,
|
|
709
|
+
"image",
|
|
710
|
+
maxFileLength
|
|
711
|
+
);
|
|
712
|
+
console.log("spriteSheet", spriteSheet);
|
|
713
|
+
|
|
714
|
+
await displayCanvasHelper.setSpriteScale(spriteScale);
|
|
715
|
+
await displayCanvasHelper.resetSpriteColors();
|
|
716
|
+
/** @type {BS.DisplaySpriteColorPair[]} */
|
|
717
|
+
const spriteColorPairs = [];
|
|
718
|
+
for (let i = 0; i < numberOfColors; i++) {
|
|
719
|
+
spriteColorPairs.push({ colorIndex: i, spriteColorIndex: i });
|
|
720
|
+
}
|
|
721
|
+
await displayCanvasHelper.selectSpriteColors(spriteColorPairs);
|
|
722
|
+
|
|
723
|
+
const offsetX = drawX;
|
|
724
|
+
let offsetYTop = drawY - outputImageHeight / 2;
|
|
725
|
+
drawProgress.value = 0;
|
|
726
|
+
|
|
727
|
+
for (
|
|
728
|
+
currentSpriteIndexBeingDrawn = 0;
|
|
729
|
+
currentSpriteIndexBeingDrawn < spriteSheet.sprites.length;
|
|
730
|
+
currentSpriteIndexBeingDrawn++
|
|
731
|
+
) {
|
|
732
|
+
const sprite = spriteSheet.sprites[currentSpriteIndexBeingDrawn];
|
|
733
|
+
const scaledSpriteHeight = sprite.height * spriteScale;
|
|
734
|
+
let offsetY = offsetYTop + scaledSpriteHeight / 2;
|
|
735
|
+
console.log(`drawing sprite "${sprite.name}"`, sprite, {
|
|
736
|
+
offsetX,
|
|
737
|
+
offsetY,
|
|
738
|
+
});
|
|
739
|
+
await displayCanvasHelper.drawSpriteFromSpriteSheet(
|
|
740
|
+
offsetX,
|
|
741
|
+
offsetY,
|
|
742
|
+
sprite.name,
|
|
743
|
+
spriteSheet,
|
|
744
|
+
undefined,
|
|
745
|
+
true
|
|
746
|
+
);
|
|
747
|
+
offsetYTop += scaledSpriteHeight;
|
|
748
|
+
}
|
|
749
|
+
drawProgress.value = 0;
|
|
750
|
+
|
|
751
|
+
for (let i = 0; i < displayCanvasHelper.numberOfColors; i++) {
|
|
752
|
+
if (i >= numberOfColors) {
|
|
753
|
+
await displayCanvasHelper.setColor(i, "black");
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
await displayCanvasHelper.selectSpriteSheetPalette("image");
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
await displayCanvasHelper.show();
|
|
760
|
+
|
|
761
|
+
canvas.setAttribute("hidden", "");
|
|
762
|
+
if (cameraVideo.srcObject) {
|
|
763
|
+
cameraVideo.removeAttribute("hidden");
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
displayCanvasHelper.addEventListener("ready", () => {
|
|
768
|
+
isDrawing = false;
|
|
769
|
+
if (drawWhenReady) {
|
|
770
|
+
drawWhenReady = false;
|
|
771
|
+
//drawImage();
|
|
772
|
+
}
|
|
773
|
+
if (cameraVideo.srcObject && autoDrawVideo) {
|
|
774
|
+
console.log("redrawing video");
|
|
775
|
+
drawImage();
|
|
776
|
+
}
|
|
777
|
+
if (autoPicture && device.isConnected) {
|
|
778
|
+
device.takePicture();
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
// PROGRESS
|
|
783
|
+
|
|
784
|
+
/** @type {HTMLProgressElement} */
|
|
785
|
+
const fileTransferProgress = document.getElementById("fileTransferProgress");
|
|
786
|
+
|
|
787
|
+
device.addEventListener("fileTransferProgress", (event) => {
|
|
788
|
+
const progress = event.message.progress;
|
|
789
|
+
//console.log({ progress });
|
|
790
|
+
fileTransferProgress.value = progress == 1 ? 0 : progress;
|
|
791
|
+
});
|
|
792
|
+
device.addEventListener("fileTransferStatus", () => {
|
|
793
|
+
if (device.fileTransferStatus == "idle") {
|
|
794
|
+
fileTransferProgress.value = 0;
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
/** @type {HTMLProgressElement} */
|
|
799
|
+
const drawProgress = document.getElementById("drawProgress");
|
|
800
|
+
|
|
801
|
+
device.addEventListener("fileTransferProgress", (event) => {
|
|
802
|
+
const progress = event.message.progress;
|
|
803
|
+
console.log({ progress });
|
|
804
|
+
const baseProgress =
|
|
805
|
+
(currentSpriteIndexBeingDrawn + progress) / spriteSheet.sprites.length;
|
|
806
|
+
drawProgress.value = baseProgress;
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
// FRAME CAMERA
|
|
810
|
+
|
|
811
|
+
/** @type {HTMLButtonElement} */
|
|
812
|
+
const takePictureButton = document.getElementById("takePicture");
|
|
813
|
+
takePictureButton.addEventListener("click", () => {
|
|
814
|
+
if (device.cameraStatus == "idle") {
|
|
815
|
+
device.takePicture(10);
|
|
816
|
+
} else {
|
|
817
|
+
device.stopCamera();
|
|
818
|
+
}
|
|
819
|
+
});
|
|
820
|
+
device.addEventListener("connected", () => {
|
|
821
|
+
updateTakePictureButton();
|
|
822
|
+
});
|
|
823
|
+
device.addEventListener("getSensorConfiguration", () => {
|
|
824
|
+
updateTakePictureButton();
|
|
825
|
+
});
|
|
826
|
+
const updateTakePictureButton = () => {
|
|
827
|
+
takePictureButton.disabled =
|
|
828
|
+
!device.isConnected || device.cameraStatus != "idle";
|
|
829
|
+
};
|
|
830
|
+
device.addEventListener("cameraStatus", () => {
|
|
831
|
+
updateTakePictureButton();
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
/** @type {HTMLProgressElement} */
|
|
835
|
+
const cameraImageProgress = document.getElementById("cameraImageProgress");
|
|
836
|
+
device.addEventListener("cameraImageProgress", (event) => {
|
|
837
|
+
if (event.message.type == "image") {
|
|
838
|
+
cameraImageProgress.value = event.message.progress;
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
device.addEventListener("cameraImage", (event) => {
|
|
843
|
+
image.src = event.message.url;
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
/** @type {HTMLButtonElement} */
|
|
847
|
+
const focusCameraButton = document.getElementById("focusCamera");
|
|
848
|
+
focusCameraButton.addEventListener("click", () => {
|
|
849
|
+
if (device.cameraStatus == "idle") {
|
|
850
|
+
device.focusCamera();
|
|
851
|
+
} else {
|
|
852
|
+
device.stopCamera();
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
device.addEventListener("connected", () => {
|
|
856
|
+
updateFocusCameraButton();
|
|
857
|
+
});
|
|
858
|
+
device.addEventListener("getSensorConfiguration", () => {
|
|
859
|
+
updateFocusCameraButton();
|
|
860
|
+
});
|
|
861
|
+
const updateFocusCameraButton = () => {
|
|
862
|
+
focusCameraButton.disabled =
|
|
863
|
+
!device.isConnected || device.cameraStatus != "idle";
|
|
864
|
+
};
|
|
865
|
+
device.addEventListener("cameraStatus", (event) => {
|
|
866
|
+
updateFocusCameraButton();
|
|
867
|
+
if (
|
|
868
|
+
device.cameraStatus == "idle" &&
|
|
869
|
+
event.message.previousCameraStatus == "focusing"
|
|
870
|
+
) {
|
|
871
|
+
device.takePicture();
|
|
872
|
+
}
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
// CAMERA CONFIG
|
|
876
|
+
|
|
877
|
+
/** @type {HTMLInputElement} */
|
|
878
|
+
const autoPictureCheckbox = document.getElementById("autoPicture");
|
|
879
|
+
let autoPicture = autoPictureCheckbox.checked;
|
|
880
|
+
autoPictureCheckbox.addEventListener("input", () => {
|
|
881
|
+
autoPicture = autoPictureCheckbox.checked;
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
/** @type {HTMLPreElement} */
|
|
885
|
+
const cameraConfigurationPre = document.getElementById(
|
|
886
|
+
"cameraConfigurationPre"
|
|
887
|
+
);
|
|
888
|
+
device.addEventListener("getCameraConfiguration", () => {
|
|
889
|
+
cameraConfigurationPre.textContent = JSON.stringify(
|
|
890
|
+
device.cameraConfiguration,
|
|
891
|
+
null,
|
|
892
|
+
2
|
|
893
|
+
);
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
const cameraConfigurationContainer = document.getElementById(
|
|
897
|
+
"cameraConfiguration"
|
|
898
|
+
);
|
|
899
|
+
/** @type {HTMLTemplateElement} */
|
|
900
|
+
const cameraConfigurationTypeTemplate = document.getElementById(
|
|
901
|
+
"cameraConfigurationTypeTemplate"
|
|
902
|
+
);
|
|
903
|
+
BS.CameraConfigurationTypes.forEach((cameraConfigurationType) => {
|
|
904
|
+
const cameraConfigurationTypeContainer =
|
|
905
|
+
cameraConfigurationTypeTemplate.content
|
|
906
|
+
.cloneNode(true)
|
|
907
|
+
.querySelector(".cameraConfigurationType");
|
|
908
|
+
|
|
909
|
+
cameraConfigurationContainer.appendChild(cameraConfigurationTypeContainer);
|
|
910
|
+
|
|
911
|
+
cameraConfigurationTypeContainer.querySelector(".type").innerText =
|
|
912
|
+
cameraConfigurationType;
|
|
913
|
+
|
|
914
|
+
/** @type {HTMLInputElement} */
|
|
915
|
+
const input = cameraConfigurationTypeContainer.querySelector("input");
|
|
916
|
+
|
|
917
|
+
/** @type {HTMLSpanElement} */
|
|
918
|
+
const span = cameraConfigurationTypeContainer.querySelector("span");
|
|
919
|
+
|
|
920
|
+
device.addEventListener("isConnected", () => {
|
|
921
|
+
updateisInputDisabled();
|
|
922
|
+
});
|
|
923
|
+
device.addEventListener("cameraStatus", () => {
|
|
924
|
+
updateisInputDisabled();
|
|
925
|
+
});
|
|
926
|
+
const updateisInputDisabled = () => {
|
|
927
|
+
input.disabled =
|
|
928
|
+
!device.isConnected || !device.hasCamera || device.cameraStatus != "idle";
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
const updateInput = () => {
|
|
932
|
+
const value = device.cameraConfiguration[cameraConfigurationType];
|
|
933
|
+
span.innerText = value;
|
|
934
|
+
input.value = value;
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
device.addEventListener("connected", () => {
|
|
938
|
+
if (!device.hasCamera) {
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
const range = device.cameraConfigurationRanges[cameraConfigurationType];
|
|
942
|
+
input.min = range.min;
|
|
943
|
+
input.max = range.max;
|
|
944
|
+
|
|
945
|
+
updateInput();
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
device.addEventListener("getCameraConfiguration", () => {
|
|
949
|
+
updateInput();
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
input.addEventListener("change", () => {
|
|
953
|
+
const value = Number(input.value);
|
|
954
|
+
// console.log(`updating ${cameraConfigurationType} to ${value}`);
|
|
955
|
+
device.setCameraConfiguration({
|
|
956
|
+
[cameraConfigurationType]: value,
|
|
957
|
+
});
|
|
958
|
+
if (takePictureAfterUpdate) {
|
|
959
|
+
device.addEventListener(
|
|
960
|
+
"getCameraConfiguration",
|
|
961
|
+
() => {
|
|
962
|
+
setTimeout(() => device.takePicture()), 100;
|
|
963
|
+
},
|
|
964
|
+
{ once: true }
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
});
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
/** @type {HTMLInputElement} */
|
|
971
|
+
const takePictureAfterUpdateCheckbox = document.getElementById(
|
|
972
|
+
"takePictureAfterUpdate"
|
|
973
|
+
);
|
|
974
|
+
let takePictureAfterUpdate = false;
|
|
975
|
+
takePictureAfterUpdateCheckbox.addEventListener("input", () => {
|
|
976
|
+
takePictureAfterUpdate = takePictureAfterUpdateCheckbox.checked;
|
|
977
|
+
console.log({ takePictureAfterUpdate });
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
/** @type {HTMLInputElement} */
|
|
981
|
+
const cameraWhiteBalanceInput = document.getElementById("cameraWhiteBalance");
|
|
982
|
+
const updateWhiteBalance = BS.ThrottleUtils.throttle(
|
|
983
|
+
(config) => {
|
|
984
|
+
if (device.cameraStatus != "idle") {
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
device.setCameraConfiguration(config);
|
|
989
|
+
|
|
990
|
+
if (takePictureAfterUpdate) {
|
|
991
|
+
device.addEventListener(
|
|
992
|
+
"getCameraConfiguration",
|
|
993
|
+
() => {
|
|
994
|
+
setTimeout(() => device.takePicture()), 100;
|
|
995
|
+
},
|
|
996
|
+
{ once: true }
|
|
997
|
+
);
|
|
998
|
+
}
|
|
999
|
+
},
|
|
1000
|
+
200,
|
|
1001
|
+
true
|
|
1002
|
+
);
|
|
1003
|
+
cameraWhiteBalanceInput.addEventListener("input", () => {
|
|
1004
|
+
let [redGain, greenGain, blueGain] = cameraWhiteBalanceInput.value
|
|
1005
|
+
.replace("#", "")
|
|
1006
|
+
.match(/.{1,2}/g)
|
|
1007
|
+
.map((value) => Number(`0x${value}`))
|
|
1008
|
+
.map((value) => value / 255)
|
|
1009
|
+
.map((value) => value * device.cameraConfigurationRanges.blueGain.max)
|
|
1010
|
+
.map((value) => Math.round(value));
|
|
1011
|
+
|
|
1012
|
+
updateWhiteBalance({ redGain, greenGain, blueGain });
|
|
1013
|
+
});
|
|
1014
|
+
const updateCameraWhiteBalanceInput = () => {
|
|
1015
|
+
if (!device.hasCamera) {
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
cameraWhiteBalanceInput.disabled =
|
|
1019
|
+
!device.isConnected || !device.hasCamera || device.cameraStatus != "idle";
|
|
1020
|
+
|
|
1021
|
+
const { redGain, blueGain, greenGain } = device.cameraConfiguration;
|
|
1022
|
+
|
|
1023
|
+
cameraWhiteBalanceInput.value = `#${[redGain, blueGain, greenGain]
|
|
1024
|
+
.map((value) => value / device.cameraConfigurationRanges.redGain.max)
|
|
1025
|
+
.map((value) => value * 255)
|
|
1026
|
+
.map((value) => Math.round(value))
|
|
1027
|
+
.map((value) => value.toString(16))
|
|
1028
|
+
.join("")}`;
|
|
1029
|
+
};
|
|
1030
|
+
device.addEventListener("connected", () => {
|
|
1031
|
+
updateCameraWhiteBalanceInput();
|
|
1032
|
+
});
|
|
1033
|
+
device.addEventListener("getCameraConfiguration", () => {
|
|
1034
|
+
updateCameraWhiteBalanceInput();
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
// IMAGE SEGMENTATION
|
|
1038
|
+
|
|
1039
|
+
let imageSegmenter = undefined;
|
|
1040
|
+
let runningMode = "LIVE_STREAM";
|
|
1041
|
+
let labels;
|
|
1042
|
+
|
|
1043
|
+
import {
|
|
1044
|
+
ImageSegmenter,
|
|
1045
|
+
FilesetResolver,
|
|
1046
|
+
} from "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.2";
|
|
1047
|
+
|
|
1048
|
+
let useImageSegmentation = false;
|
|
1049
|
+
const useImageSegmentationInput = document.getElementById(
|
|
1050
|
+
"useImageSegmentation"
|
|
1051
|
+
);
|
|
1052
|
+
useImageSegmentationInput.addEventListener("input", () => {
|
|
1053
|
+
setUseImageSegmentation(useImageSegmentationInput.checked);
|
|
1054
|
+
});
|
|
1055
|
+
const setUseImageSegmentation = (newUseImageSegmentation) => {
|
|
1056
|
+
useImageSegmentation = newUseImageSegmentation;
|
|
1057
|
+
console.log({ useImageSegmentation });
|
|
1058
|
+
useImageSegmentationInput.checked = useImageSegmentation;
|
|
1059
|
+
|
|
1060
|
+
if (!imageSegmenter) {
|
|
1061
|
+
createImageSegmenter();
|
|
1062
|
+
}
|
|
1063
|
+
};
|
|
1064
|
+
|
|
1065
|
+
const modelAssetPaths = {
|
|
1066
|
+
selfieMulticlass:
|
|
1067
|
+
"https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_multiclass_256x256/float32/latest/selfie_multiclass_256x256.tflite",
|
|
1068
|
+
hairSegmenter:
|
|
1069
|
+
"https://storage.googleapis.com/mediapipe-models/image_segmenter/hair_segmenter/float32/latest/hair_segmenter.tflite",
|
|
1070
|
+
selfieSegmenter:
|
|
1071
|
+
"https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_segmenter/float16/latest/selfie_segmenter.tflite",
|
|
1072
|
+
deeplab:
|
|
1073
|
+
"https://storage.googleapis.com/mediapipe-models/image_segmenter/deeplab_v3/float32/latest/deeplab_v3.tflite",
|
|
1074
|
+
};
|
|
1075
|
+
const createImageSegmenter = async () => {
|
|
1076
|
+
const vision = await FilesetResolver.forVisionTasks(
|
|
1077
|
+
"https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.2/wasm"
|
|
1078
|
+
);
|
|
1079
|
+
|
|
1080
|
+
imageSegmenter = await ImageSegmenter.createFromOptions(vision, {
|
|
1081
|
+
baseOptions: {
|
|
1082
|
+
modelAssetPath: modelAssetPaths.selfieSegmenter,
|
|
1083
|
+
delegate: "GPU",
|
|
1084
|
+
},
|
|
1085
|
+
|
|
1086
|
+
runningMode: runningMode,
|
|
1087
|
+
outputCategoryMask: true,
|
|
1088
|
+
outputConfidenceMasks: false,
|
|
1089
|
+
});
|
|
1090
|
+
labels = imageSegmenter.getLabels();
|
|
1091
|
+
console.log("created imageSegmenter", imageSegmenter, labels);
|
|
1092
|
+
};
|
|
1093
|
+
createImageSegmenter();
|