@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.
Files changed (60) hide show
  1. package/dist/app/session/events.d.ts +3 -2
  2. package/dist/app/session/events.d.ts.map +1 -1
  3. package/dist/app/session/events.js +3 -0
  4. package/dist/app/session/index.d.ts +8 -1
  5. package/dist/app/session/index.d.ts.map +1 -1
  6. package/dist/app/session/index.js +9 -0
  7. package/dist/app/session/layouts.d.ts +71 -14
  8. package/dist/app/session/layouts.d.ts.map +1 -1
  9. package/dist/app/session/layouts.js +114 -31
  10. package/dist/app/session/modules/audio.d.ts +2 -2
  11. package/dist/app/session/modules/audio.d.ts.map +1 -1
  12. package/dist/app/session/modules/audio.js +21 -17
  13. package/dist/app/session/modules/camera-managed-extension.d.ts +3 -1
  14. package/dist/app/session/modules/camera-managed-extension.d.ts.map +1 -1
  15. package/dist/app/session/modules/camera-managed-extension.js +26 -18
  16. package/dist/app/session/modules/camera.d.ts +4 -4
  17. package/dist/app/session/modules/camera.d.ts.map +1 -1
  18. package/dist/app/session/modules/camera.js +28 -22
  19. package/dist/app/session/modules/location.d.ts +3 -3
  20. package/dist/app/session/modules/location.d.ts.map +1 -1
  21. package/dist/app/session/modules/location.js +8 -5
  22. package/dist/examples/managed-rtmp-streaming-example.js +3 -0
  23. package/dist/examples/managed-rtmp-streaming-with-restream-example.d.ts +11 -0
  24. package/dist/examples/managed-rtmp-streaming-with-restream-example.d.ts.map +1 -0
  25. package/dist/examples/managed-rtmp-streaming-with-restream-example.js +124 -0
  26. package/dist/index.d.ts +6 -4
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +5 -1
  29. package/dist/types/enums.d.ts +3 -1
  30. package/dist/types/enums.d.ts.map +1 -1
  31. package/dist/types/enums.js +2 -0
  32. package/dist/types/index.d.ts +2 -2
  33. package/dist/types/index.d.ts.map +1 -1
  34. package/dist/types/layouts.d.ts +16 -1
  35. package/dist/types/layouts.d.ts.map +1 -1
  36. package/dist/types/message-types.d.ts +2 -2
  37. package/dist/types/message-types.d.ts.map +1 -1
  38. package/dist/types/message-types.js +3 -3
  39. package/dist/types/messages/app-to-cloud.d.ts +11 -0
  40. package/dist/types/messages/app-to-cloud.d.ts.map +1 -1
  41. package/dist/types/messages/cloud-to-app.d.ts +19 -0
  42. package/dist/types/messages/cloud-to-app.d.ts.map +1 -1
  43. package/dist/types/messages/glasses-to-cloud.d.ts +8 -4
  44. package/dist/types/messages/glasses-to-cloud.d.ts.map +1 -1
  45. package/dist/types/messages/glasses-to-cloud.js +3 -3
  46. package/dist/types/models.d.ts +1 -0
  47. package/dist/types/models.d.ts.map +1 -1
  48. package/dist/types/models.js +1 -0
  49. package/dist/types/streams.d.ts +1 -1
  50. package/dist/types/streams.d.ts.map +1 -1
  51. package/dist/types/streams.js +2 -2
  52. package/dist/types/user-session.d.ts +8 -8
  53. package/dist/types/user-session.d.ts.map +1 -1
  54. package/dist/utils/animation-utils.d.ts +206 -0
  55. package/dist/utils/animation-utils.d.ts.map +1 -0
  56. package/dist/utils/animation-utils.js +340 -0
  57. package/dist/utils/bitmap-utils.d.ts +149 -0
  58. package/dist/utils/bitmap-utils.d.ts.map +1 -0
  59. package/dist/utils/bitmap-utils.js +465 -0
  60. 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.2",
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",