@mentra/sdk 2.1.2 → 2.1.4
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/dist/app/session/events.d.ts +3 -2
- package/dist/app/session/events.d.ts.map +1 -1
- package/dist/app/session/events.js +3 -0
- package/dist/app/session/index.d.ts +8 -1
- package/dist/app/session/index.d.ts.map +1 -1
- package/dist/app/session/index.js +9 -0
- package/dist/app/session/layouts.d.ts +71 -14
- package/dist/app/session/layouts.d.ts.map +1 -1
- package/dist/app/session/layouts.js +114 -31
- package/dist/app/session/modules/audio.d.ts +2 -2
- package/dist/app/session/modules/audio.d.ts.map +1 -1
- package/dist/app/session/modules/audio.js +21 -17
- package/dist/app/session/modules/camera-managed-extension.d.ts +3 -1
- package/dist/app/session/modules/camera-managed-extension.d.ts.map +1 -1
- package/dist/app/session/modules/camera-managed-extension.js +26 -18
- package/dist/app/session/modules/camera.d.ts +4 -4
- package/dist/app/session/modules/camera.d.ts.map +1 -1
- package/dist/app/session/modules/camera.js +28 -22
- package/dist/app/session/modules/location.d.ts +3 -3
- package/dist/app/session/modules/location.d.ts.map +1 -1
- package/dist/app/session/modules/location.js +8 -5
- package/dist/examples/managed-rtmp-streaming-example.js +3 -0
- package/dist/examples/managed-rtmp-streaming-with-restream-example.d.ts +11 -0
- package/dist/examples/managed-rtmp-streaming-with-restream-example.d.ts.map +1 -0
- package/dist/examples/managed-rtmp-streaming-with-restream-example.js +124 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/types/enums.d.ts +3 -1
- package/dist/types/enums.d.ts.map +1 -1
- package/dist/types/enums.js +2 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/layouts.d.ts +16 -1
- package/dist/types/layouts.d.ts.map +1 -1
- package/dist/types/message-types.d.ts +2 -2
- package/dist/types/message-types.d.ts.map +1 -1
- package/dist/types/message-types.js +3 -3
- package/dist/types/messages/app-to-cloud.d.ts +11 -0
- package/dist/types/messages/app-to-cloud.d.ts.map +1 -1
- package/dist/types/messages/cloud-to-app.d.ts +19 -0
- package/dist/types/messages/cloud-to-app.d.ts.map +1 -1
- package/dist/types/messages/glasses-to-cloud.d.ts +8 -4
- package/dist/types/messages/glasses-to-cloud.d.ts.map +1 -1
- package/dist/types/messages/glasses-to-cloud.js +3 -3
- package/dist/types/models.d.ts +1 -0
- package/dist/types/models.d.ts.map +1 -1
- package/dist/types/models.js +1 -0
- package/dist/types/streams.d.ts +1 -1
- package/dist/types/streams.d.ts.map +1 -1
- package/dist/types/streams.js +2 -2
- package/dist/types/user-session.d.ts +8 -8
- package/dist/types/user-session.d.ts.map +1 -1
- package/dist/utils/animation-utils.d.ts +206 -0
- package/dist/utils/animation-utils.d.ts.map +1 -0
- package/dist/utils/animation-utils.js +340 -0
- package/dist/utils/bitmap-utils.d.ts +149 -0
- package/dist/utils/bitmap-utils.d.ts.map +1 -0
- package/dist/utils/bitmap-utils.js +465 -0
- package/package.json +2 -1
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"bitmap-utils.d.ts","sourceRoot":"","sources":["../../src/utils/bitmap-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAMH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uCAAuC;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,yCAAyC;IACzC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE;QACT,iDAAiD;QACjD,UAAU,CAAC,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;QAC/C,8BAA8B;QAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yCAAyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yDAAyD;IACzD,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,8DAA8D;IAC9D,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;GAEG;AACH,qBAAa,WAAW;IACtB;;;;;;;;;;;;OAYG;WACU,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;WAevD,qBAAqB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;WA+G7D,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAiDnE;;;;;;;;;;;;;;;;;;;OAmBG;WACU,aAAa,CACxB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,MAAM,EAAE,CAAC;IAgEpB;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB;IA+F1D;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,aAAa,CAClB,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,UAAU,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,EACvC,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,GACpC,MAAM,GAAG,MAAM;IA+BlB;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG;QACvC,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,OAAO,CAAC;KACrB;CAsCF"}
|
@@ -0,0 +1,465 @@
|
|
1
|
+
"use strict";
|
2
|
+
/**
|
3
|
+
* 🎨 Bitmap Utilities Module
|
4
|
+
*
|
5
|
+
* Provides helper functions for working with bitmap images in MentraOS applications.
|
6
|
+
* Includes file loading, data validation, and format conversion utilities.
|
7
|
+
*
|
8
|
+
* @example
|
9
|
+
* ```typescript
|
10
|
+
* import { BitmapUtils } from '@mentra/sdk';
|
11
|
+
*
|
12
|
+
* // Load a single BMP file
|
13
|
+
* const bmpHex = await BitmapUtils.loadBmpAsHex('./my-image.bmp');
|
14
|
+
* session.layouts.showBitmapView(bmpHex);
|
15
|
+
*
|
16
|
+
* // Load multiple animation frames
|
17
|
+
* const frames = await BitmapUtils.loadBmpFrames('./animations', 10);
|
18
|
+
* session.layouts.showBitmapAnimation(frames, 1500, true);
|
19
|
+
* ```
|
20
|
+
*/
|
21
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
22
|
+
if (k2 === undefined) k2 = k;
|
23
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
24
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
25
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
26
|
+
}
|
27
|
+
Object.defineProperty(o, k2, desc);
|
28
|
+
}) : (function(o, m, k, k2) {
|
29
|
+
if (k2 === undefined) k2 = k;
|
30
|
+
o[k2] = m[k];
|
31
|
+
}));
|
32
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
33
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
34
|
+
}) : function(o, v) {
|
35
|
+
o["default"] = v;
|
36
|
+
});
|
37
|
+
var __importStar = (this && this.__importStar) || (function () {
|
38
|
+
var ownKeys = function(o) {
|
39
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
40
|
+
var ar = [];
|
41
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
42
|
+
return ar;
|
43
|
+
};
|
44
|
+
return ownKeys(o);
|
45
|
+
};
|
46
|
+
return function (mod) {
|
47
|
+
if (mod && mod.__esModule) return mod;
|
48
|
+
var result = {};
|
49
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
50
|
+
__setModuleDefault(result, mod);
|
51
|
+
return result;
|
52
|
+
};
|
53
|
+
})();
|
54
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
55
|
+
exports.BitmapUtils = void 0;
|
56
|
+
const fs = __importStar(require("fs/promises"));
|
57
|
+
const path = __importStar(require("path"));
|
58
|
+
const jimp_1 = require("jimp");
|
59
|
+
/**
|
60
|
+
* Utility class for working with bitmap images in MentraOS applications
|
61
|
+
*/
|
62
|
+
class BitmapUtils {
|
63
|
+
/**
|
64
|
+
* Load a BMP file as hex string from filesystem
|
65
|
+
*
|
66
|
+
* @param filePath - Path to the BMP file
|
67
|
+
* @returns Promise resolving to hex-encoded bitmap data
|
68
|
+
* @throws Error if file cannot be read or is not a valid BMP
|
69
|
+
*
|
70
|
+
* @example
|
71
|
+
* ```typescript
|
72
|
+
* const bmpHex = await BitmapUtils.loadBmpAsHex('./assets/icon.bmp');
|
73
|
+
* session.layouts.showBitmapView(bmpHex);
|
74
|
+
* ```
|
75
|
+
*/
|
76
|
+
static async loadBmpFromFileAsHex(filePath) {
|
77
|
+
try {
|
78
|
+
const bmpData = await fs.readFile(filePath);
|
79
|
+
return this.loadBmpFromDataAsHex(bmpData);
|
80
|
+
}
|
81
|
+
catch (error) {
|
82
|
+
if (error instanceof Error) {
|
83
|
+
throw new Error(`Failed to load BMP file ${filePath}: ${error.message}`);
|
84
|
+
}
|
85
|
+
throw new Error(`Failed to load BMP file ${filePath}: Unknown error`);
|
86
|
+
}
|
87
|
+
}
|
88
|
+
static async convert24BitTo1BitBMP(input24BitBmp) {
|
89
|
+
// Read header information from 24-bit BMP
|
90
|
+
const width = input24BitBmp.readUInt32LE(18);
|
91
|
+
const height = Math.abs(input24BitBmp.readInt32LE(22)); // Height can be negative (top-down BMP)
|
92
|
+
const isTopDown = input24BitBmp.readInt32LE(22) < 0;
|
93
|
+
const bitsPerPixel = input24BitBmp.readUInt16LE(28);
|
94
|
+
if (bitsPerPixel !== 24) {
|
95
|
+
throw new Error("Input must be a 24-bit BMP");
|
96
|
+
}
|
97
|
+
// Calculate row sizes (both must be 4-byte aligned)
|
98
|
+
const rowSize24 = Math.ceil((width * 3) / 4) * 4;
|
99
|
+
const rowSize1 = Math.ceil(width / 32) * 4; // 32 pixels per 4 bytes
|
100
|
+
// Calculate sizes for 1-bit BMP
|
101
|
+
const colorTableSize = 8; // 2 colors * 4 bytes each
|
102
|
+
const headerSize = 54 + colorTableSize;
|
103
|
+
const pixelDataSize = rowSize1 * height;
|
104
|
+
const fileSize = headerSize + pixelDataSize;
|
105
|
+
// Create new buffer for 1-bit BMP
|
106
|
+
const output1BitBmp = Buffer.alloc(fileSize);
|
107
|
+
let offset = 0;
|
108
|
+
// Write BMP file header (14 bytes)
|
109
|
+
output1BitBmp.write("BM", offset);
|
110
|
+
offset += 2; // Signature
|
111
|
+
output1BitBmp.writeUInt32LE(fileSize, offset);
|
112
|
+
offset += 4; // File size
|
113
|
+
output1BitBmp.writeUInt16LE(0, offset);
|
114
|
+
offset += 2; // Reserved 1
|
115
|
+
output1BitBmp.writeUInt16LE(0, offset);
|
116
|
+
offset += 2; // Reserved 2
|
117
|
+
output1BitBmp.writeUInt32LE(headerSize, offset);
|
118
|
+
offset += 4; // Pixel data offset
|
119
|
+
// Write DIB header (40 bytes)
|
120
|
+
output1BitBmp.writeUInt32LE(40, offset);
|
121
|
+
offset += 4; // DIB header size
|
122
|
+
output1BitBmp.writeInt32LE(width, offset);
|
123
|
+
offset += 4; // Width
|
124
|
+
output1BitBmp.writeInt32LE(height, offset);
|
125
|
+
offset += 4; // Height (positive for bottom-up)
|
126
|
+
output1BitBmp.writeUInt16LE(1, offset);
|
127
|
+
offset += 2; // Planes
|
128
|
+
output1BitBmp.writeUInt16LE(1, offset);
|
129
|
+
offset += 2; // Bits per pixel (1-bit)
|
130
|
+
output1BitBmp.writeUInt32LE(0, offset);
|
131
|
+
offset += 4; // Compression (none)
|
132
|
+
output1BitBmp.writeUInt32LE(pixelDataSize, offset);
|
133
|
+
offset += 4; // Image size
|
134
|
+
output1BitBmp.writeInt32LE(2835, offset);
|
135
|
+
offset += 4; // X pixels per meter (72 DPI)
|
136
|
+
output1BitBmp.writeInt32LE(2835, offset);
|
137
|
+
offset += 4; // Y pixels per meter (72 DPI)
|
138
|
+
output1BitBmp.writeUInt32LE(2, offset);
|
139
|
+
offset += 4; // Colors used
|
140
|
+
output1BitBmp.writeUInt32LE(2, offset);
|
141
|
+
offset += 4; // Important colors
|
142
|
+
// Write color table (8 bytes)
|
143
|
+
// Black (index 0): B=0, G=0, R=0, Reserved=0
|
144
|
+
output1BitBmp.writeUInt32LE(0x00000000, offset);
|
145
|
+
offset += 4;
|
146
|
+
// White (index 1): B=255, G=255, R=255, Reserved=0
|
147
|
+
output1BitBmp.writeUInt8(255, offset++); // Blue
|
148
|
+
output1BitBmp.writeUInt8(255, offset++); // Green
|
149
|
+
output1BitBmp.writeUInt8(255, offset++); // Red
|
150
|
+
output1BitBmp.writeUInt8(0, offset++); // Reserved
|
151
|
+
// Convert pixel data from 24-bit to 1-bit
|
152
|
+
const pixelDataStart24 = 54; // 24-bit BMP has no color table
|
153
|
+
for (let y = 0; y < height; y++) {
|
154
|
+
// BMP files are usually stored bottom-up
|
155
|
+
const sourceY = isTopDown ? y : height - 1 - y;
|
156
|
+
const destY = height - 1 - y; // Always write bottom-up for compatibility
|
157
|
+
// Initialize the row with zeros
|
158
|
+
const rowData = Buffer.alloc(rowSize1);
|
159
|
+
for (let x = 0; x < width; x++) {
|
160
|
+
// Get pixel from 24-bit BMP
|
161
|
+
const offset24 = pixelDataStart24 + sourceY * rowSize24 + x * 3;
|
162
|
+
const blue = input24BitBmp[offset24];
|
163
|
+
const green = input24BitBmp[offset24 + 1];
|
164
|
+
const red = input24BitBmp[offset24 + 2];
|
165
|
+
// Determine if pixel is white (assuming pure black or white)
|
166
|
+
// White = 1, Black = 0
|
167
|
+
const isWhite = red > 128 || green > 128 || blue > 128 ? 1 : 0;
|
168
|
+
// Calculate bit position
|
169
|
+
const byteIndex = Math.floor(x / 8);
|
170
|
+
const bitPosition = 7 - (x % 8); // MSB first
|
171
|
+
// Set bit if white
|
172
|
+
if (isWhite) {
|
173
|
+
rowData[byteIndex] |= 1 << bitPosition;
|
174
|
+
}
|
175
|
+
}
|
176
|
+
// Write row to output buffer
|
177
|
+
const destOffset = offset + destY * rowSize1;
|
178
|
+
rowData.copy(output1BitBmp, destOffset);
|
179
|
+
}
|
180
|
+
return output1BitBmp;
|
181
|
+
}
|
182
|
+
static async loadBmpFromDataAsHex(bmpData) {
|
183
|
+
try {
|
184
|
+
// Basic BMP validation - check for BMP signature
|
185
|
+
if (bmpData.length < 14 || bmpData[0] !== 0x42 || bmpData[1] !== 0x4d) {
|
186
|
+
throw new Error(`Bmp data is not a valid BMP file (missing BM signature)`);
|
187
|
+
}
|
188
|
+
let finalBmpData = bmpData;
|
189
|
+
// Load the image with Jimp
|
190
|
+
const image = await jimp_1.Jimp.read(bmpData);
|
191
|
+
// Check if we need to add padding
|
192
|
+
if (image.width !== 576 || image.height !== 135) {
|
193
|
+
console.log(`Adding padding to BMP since it isn't 576x135 (current: ${image.width}x${image.height})`);
|
194
|
+
// Create a new 576x135 white canvas
|
195
|
+
const paddedImage = new jimp_1.Jimp({
|
196
|
+
width: 576,
|
197
|
+
height: 135,
|
198
|
+
color: 0xffffffff,
|
199
|
+
});
|
200
|
+
// // Calculate position to place the original image (with padding)
|
201
|
+
const leftPadding = 40; // 40px padding on left
|
202
|
+
const topPadding = 35; // 35px padding on top
|
203
|
+
// Composite the original image onto the white canvas
|
204
|
+
paddedImage.composite(image, leftPadding, topPadding);
|
205
|
+
finalBmpData = await this.convert24BitTo1BitBMP(await paddedImage.getBuffer("image/bmp"));
|
206
|
+
}
|
207
|
+
// No padding needed, just return as hex
|
208
|
+
console.log(`finalBmpData: ${finalBmpData.length} bytes`);
|
209
|
+
return finalBmpData.toString("hex");
|
210
|
+
}
|
211
|
+
catch (error) {
|
212
|
+
if (error instanceof Error) {
|
213
|
+
throw new Error(`Failed to load BMP data: ${error.message}`);
|
214
|
+
}
|
215
|
+
throw new Error(`Failed to load BMP data: Unknown error`);
|
216
|
+
}
|
217
|
+
}
|
218
|
+
/**
|
219
|
+
* Load multiple BMP frames as hex array for animations
|
220
|
+
*
|
221
|
+
* @param basePath - Directory containing the frame files
|
222
|
+
* @param frameCount - Number of frames to load
|
223
|
+
* @param options - Loading options and configuration
|
224
|
+
* @returns Promise resolving to array of hex-encoded bitmap data
|
225
|
+
*
|
226
|
+
* @example
|
227
|
+
* ```typescript
|
228
|
+
* // Load 10 frames with default pattern
|
229
|
+
* const frames = await BitmapUtils.loadBmpFrames('./animations', 10);
|
230
|
+
*
|
231
|
+
* // Load with custom pattern
|
232
|
+
* const customFrames = await BitmapUtils.loadBmpFrames('./sprites', 8, {
|
233
|
+
* filePattern: 'sprite_{i}.bmp',
|
234
|
+
* startFrame: 0
|
235
|
+
* });
|
236
|
+
* ```
|
237
|
+
*/
|
238
|
+
static async loadBmpFrames(basePath, frameCount, options = {}) {
|
239
|
+
const { filePattern = "animation_10_frame_{i}.bmp", startFrame = 1, validateFrames = true, skipMissingFrames = false, } = options;
|
240
|
+
const frames = [];
|
241
|
+
const errors = [];
|
242
|
+
for (let i = 0; i < frameCount; i++) {
|
243
|
+
const frameNumber = startFrame + i;
|
244
|
+
const fileName = filePattern.replace("{i}", frameNumber.toString());
|
245
|
+
const filePath = path.join(basePath, fileName);
|
246
|
+
try {
|
247
|
+
const frameHex = await this.loadBmpFromFileAsHex(filePath);
|
248
|
+
if (validateFrames) {
|
249
|
+
const validation = this.validateBmpHex(frameHex);
|
250
|
+
if (!validation.isValid) {
|
251
|
+
const errorMsg = `Frame ${frameNumber} validation failed: ${validation.errors.join(", ")}`;
|
252
|
+
if (skipMissingFrames) {
|
253
|
+
console.warn(`⚠️ ${errorMsg} - skipping`);
|
254
|
+
continue;
|
255
|
+
}
|
256
|
+
else {
|
257
|
+
throw new Error(errorMsg);
|
258
|
+
}
|
259
|
+
}
|
260
|
+
console.log(`✅ Frame ${frameNumber} validated (${validation.blackPixels} black pixels)`);
|
261
|
+
}
|
262
|
+
frames.push(frameHex);
|
263
|
+
}
|
264
|
+
catch (error) {
|
265
|
+
const errorMsg = `Failed to load frame ${frameNumber} (${fileName}): ${error instanceof Error ? error.message : "Unknown error"}`;
|
266
|
+
if (skipMissingFrames) {
|
267
|
+
console.warn(`⚠️ ${errorMsg} - skipping`);
|
268
|
+
continue;
|
269
|
+
}
|
270
|
+
else {
|
271
|
+
errors.push(errorMsg);
|
272
|
+
}
|
273
|
+
}
|
274
|
+
}
|
275
|
+
if (errors.length > 0) {
|
276
|
+
throw new Error(`Failed to load frames:\n${errors.join("\n")}`);
|
277
|
+
}
|
278
|
+
if (frames.length === 0) {
|
279
|
+
throw new Error(`No valid frames loaded from ${basePath}`);
|
280
|
+
}
|
281
|
+
console.log(`📚 Loaded ${frames.length} animation frames from ${basePath}`);
|
282
|
+
return frames;
|
283
|
+
}
|
284
|
+
/**
|
285
|
+
* Validate BMP hex data integrity and extract metadata
|
286
|
+
*
|
287
|
+
* @param hexString - Hex-encoded bitmap data
|
288
|
+
* @returns Validation result with detailed information
|
289
|
+
*
|
290
|
+
* @example
|
291
|
+
* ```typescript
|
292
|
+
* const validation = BitmapUtils.validateBmpHex(bmpHex);
|
293
|
+
* if (!validation.isValid) {
|
294
|
+
* console.error('Invalid bitmap:', validation.errors);
|
295
|
+
* } else {
|
296
|
+
* console.log(`Valid bitmap: ${validation.blackPixels} black pixels`);
|
297
|
+
* }
|
298
|
+
* ```
|
299
|
+
*/
|
300
|
+
static validateBmpHex(hexString) {
|
301
|
+
const errors = [];
|
302
|
+
let byteCount = 0;
|
303
|
+
let blackPixels = 0;
|
304
|
+
const metadata = {};
|
305
|
+
try {
|
306
|
+
// Basic hex validation
|
307
|
+
if (typeof hexString !== "string" || hexString.length === 0) {
|
308
|
+
errors.push("Hex string is empty or invalid");
|
309
|
+
return { isValid: false, byteCount: 0, blackPixels: 0, errors };
|
310
|
+
}
|
311
|
+
if (hexString.length % 2 !== 0) {
|
312
|
+
errors.push("Hex string length must be even");
|
313
|
+
return { isValid: false, byteCount: 0, blackPixels: 0, errors };
|
314
|
+
}
|
315
|
+
// Convert to buffer
|
316
|
+
const buffer = Buffer.from(hexString, "hex");
|
317
|
+
byteCount = buffer.length;
|
318
|
+
// BMP signature validation
|
319
|
+
if (buffer.length < 14) {
|
320
|
+
errors.push("File too small to be a valid BMP (minimum 14 bytes for header)");
|
321
|
+
}
|
322
|
+
else {
|
323
|
+
if (buffer[0] !== 0x42 || buffer[1] !== 0x4d) {
|
324
|
+
errors.push('Invalid BMP signature (should start with "BM")');
|
325
|
+
}
|
326
|
+
}
|
327
|
+
// Size validation for MentraOS (576x135 = ~9782 bytes expected)
|
328
|
+
const expectedSize = 9782;
|
329
|
+
if (buffer.length < expectedSize - 100) {
|
330
|
+
// Allow some tolerance
|
331
|
+
errors.push(`BMP too small (${buffer.length} bytes, expected ~${expectedSize})`);
|
332
|
+
}
|
333
|
+
else if (buffer.length > expectedSize + 1000) {
|
334
|
+
// Allow some tolerance
|
335
|
+
errors.push(`BMP too large (${buffer.length} bytes, expected ~${expectedSize})`);
|
336
|
+
}
|
337
|
+
// Extract BMP metadata if header is valid
|
338
|
+
if (buffer.length >= 54) {
|
339
|
+
try {
|
340
|
+
// BMP width and height are at offsets 18 and 22 (little-endian)
|
341
|
+
const width = buffer.readUInt32LE(18);
|
342
|
+
const height = buffer.readUInt32LE(22);
|
343
|
+
metadata.dimensions = { width, height };
|
344
|
+
metadata.format = "BMP";
|
345
|
+
// Validate dimensions for MentraOS glasses
|
346
|
+
if (width !== 576 || height !== 135) {
|
347
|
+
errors.push(`Invalid dimensions (${width}x${height}, expected 576x135 for MentraOS)`);
|
348
|
+
}
|
349
|
+
}
|
350
|
+
catch (e) {
|
351
|
+
errors.push("Failed to parse BMP header metadata");
|
352
|
+
}
|
353
|
+
}
|
354
|
+
// Pixel data validation (assumes 54-byte header + pixel data)
|
355
|
+
if (buffer.length > 62) {
|
356
|
+
const pixelData = buffer.slice(62); // Skip BMP header
|
357
|
+
blackPixels = Array.from(pixelData).filter((b) => b !== 0xff).length;
|
358
|
+
if (blackPixels === 0) {
|
359
|
+
errors.push("No black pixels found (image appears to be all white)");
|
360
|
+
}
|
361
|
+
}
|
362
|
+
else {
|
363
|
+
errors.push("File too small to contain pixel data");
|
364
|
+
}
|
365
|
+
}
|
366
|
+
catch (error) {
|
367
|
+
errors.push(`Failed to parse hex data: ${error instanceof Error ? error.message : "Unknown error"}`);
|
368
|
+
}
|
369
|
+
return {
|
370
|
+
isValid: errors.length === 0,
|
371
|
+
byteCount,
|
372
|
+
blackPixels,
|
373
|
+
errors,
|
374
|
+
metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
|
375
|
+
};
|
376
|
+
}
|
377
|
+
/**
|
378
|
+
* Convert bitmap data between different formats
|
379
|
+
*
|
380
|
+
* @param data - Input bitmap data
|
381
|
+
* @param fromFormat - Source format ('hex' | 'base64' | 'buffer')
|
382
|
+
* @param toFormat - Target format ('hex' | 'base64' | 'buffer')
|
383
|
+
* @returns Converted bitmap data
|
384
|
+
*
|
385
|
+
* @example
|
386
|
+
* ```typescript
|
387
|
+
* const base64Data = BitmapUtils.convertFormat(hexData, 'hex', 'base64');
|
388
|
+
* const bufferData = BitmapUtils.convertFormat(base64Data, 'base64', 'buffer');
|
389
|
+
* ```
|
390
|
+
*/
|
391
|
+
static convertFormat(data, fromFormat, toFormat) {
|
392
|
+
let buffer;
|
393
|
+
// Convert input to buffer
|
394
|
+
switch (fromFormat) {
|
395
|
+
case "hex":
|
396
|
+
buffer = Buffer.from(data, "hex");
|
397
|
+
break;
|
398
|
+
case "base64":
|
399
|
+
buffer = Buffer.from(data, "base64");
|
400
|
+
break;
|
401
|
+
case "buffer":
|
402
|
+
buffer = data;
|
403
|
+
break;
|
404
|
+
default:
|
405
|
+
throw new Error(`Unsupported source format: ${fromFormat}`);
|
406
|
+
}
|
407
|
+
// Convert buffer to target format
|
408
|
+
switch (toFormat) {
|
409
|
+
case "hex":
|
410
|
+
return buffer.toString("hex");
|
411
|
+
case "base64":
|
412
|
+
return buffer.toString("base64");
|
413
|
+
case "buffer":
|
414
|
+
return buffer;
|
415
|
+
default:
|
416
|
+
throw new Error(`Unsupported target format: ${toFormat}`);
|
417
|
+
}
|
418
|
+
}
|
419
|
+
/**
|
420
|
+
* Get bitmap information without full validation
|
421
|
+
*
|
422
|
+
* @param hexString - Hex-encoded bitmap data
|
423
|
+
* @returns Basic bitmap information
|
424
|
+
*
|
425
|
+
* @example
|
426
|
+
* ```typescript
|
427
|
+
* const info = BitmapUtils.getBitmapInfo(bmpHex);
|
428
|
+
* console.log(`Bitmap: ${info.width}x${info.height}, ${info.blackPixels} black pixels`);
|
429
|
+
* ```
|
430
|
+
*/
|
431
|
+
static getBitmapInfo(hexString) {
|
432
|
+
try {
|
433
|
+
const buffer = Buffer.from(hexString, "hex");
|
434
|
+
const isValidBmp = buffer.length >= 14 && buffer[0] === 0x42 && buffer[1] === 0x4d;
|
435
|
+
let width;
|
436
|
+
let height;
|
437
|
+
if (isValidBmp && buffer.length >= 54) {
|
438
|
+
try {
|
439
|
+
width = buffer.readUInt32LE(18);
|
440
|
+
height = buffer.readUInt32LE(22);
|
441
|
+
}
|
442
|
+
catch (e) {
|
443
|
+
// Ignore metadata parsing errors
|
444
|
+
}
|
445
|
+
}
|
446
|
+
const pixelData = buffer.slice(62);
|
447
|
+
const blackPixels = Array.from(pixelData).filter((b) => b !== 0xff).length;
|
448
|
+
return {
|
449
|
+
byteCount: buffer.length,
|
450
|
+
blackPixels,
|
451
|
+
width,
|
452
|
+
height,
|
453
|
+
isValidBmp,
|
454
|
+
};
|
455
|
+
}
|
456
|
+
catch (error) {
|
457
|
+
return {
|
458
|
+
byteCount: 0,
|
459
|
+
blackPixels: 0,
|
460
|
+
isValidBmp: false,
|
461
|
+
};
|
462
|
+
}
|
463
|
+
}
|
464
|
+
}
|
465
|
+
exports.BitmapUtils = BitmapUtils;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@mentra/sdk",
|
3
|
-
"version": "2.1.
|
3
|
+
"version": "2.1.4",
|
4
4
|
"description": "Build apps for MentraOS smartglasses. This SDK provides everything you need to create real-time smartglasses applications.",
|
5
5
|
"source": "src/index.ts",
|
6
6
|
"main": "dist/index.js",
|
@@ -29,6 +29,7 @@
|
|
29
29
|
"cookie-parser": "^1.4.7",
|
30
30
|
"dotenv": "^16.4.0",
|
31
31
|
"express": "^4.18.2",
|
32
|
+
"jimp": "^1.6.0",
|
32
33
|
"jsonwebtoken": "^8.5.1",
|
33
34
|
"jsrsasign": "^11.1.0",
|
34
35
|
"multer": "^2.0.1",
|