lotio 1.1.54 → 1.1.55

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/browser/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * @module lotio
4
4
  */
5
5
 
6
- import { initLotio, createAnimation, renderFrameToCanvas, renderFrameRGBA, cleanup, registerFont, getVersion } from './wasm.js';
6
+ import { initLotio, createAnimation, renderFrameToCanvas, renderFrameRGBA, cleanup, registerFont, getVersion, setDebugMode } from './wasm.js';
7
7
 
8
8
  /**
9
9
  * Animation states
@@ -75,6 +75,7 @@ export class Lotio extends EventEmitter {
75
75
  * @param {number} options.textPadding - Text padding factor (0.0-1.0, default: 0.97)
76
76
  * @param {string} options.textMeasurementMode - Text measurement mode: 'fast'|'accurate'|'pixel-perfect' (default: 'accurate')
77
77
  * @param {string} options.wasmPath - Path to lotio.wasm file (default: './lotio.wasm')
78
+ * @param {boolean} options.debug - Enable debug logging (default: false)
78
79
  */
79
80
  constructor(options = {}) {
80
81
  super();
@@ -86,6 +87,7 @@ export class Lotio extends EventEmitter {
86
87
  this._layerOverrides = options.layerOverrides || null;
87
88
  this._textPadding = options.textPadding !== undefined ? options.textPadding : 0.97;
88
89
  this._textMeasurementMode = options.textMeasurementMode || 'accurate';
90
+ this._debug = options.debug === true;
89
91
  this._state = State.STOPPED;
90
92
  this._wasmInitialized = false;
91
93
  this._animationInfo = null;
@@ -96,6 +98,19 @@ export class Lotio extends EventEmitter {
96
98
  this._startTime = 0;
97
99
  this._registeredFonts = new Set();
98
100
 
101
+ if (this._debug) {
102
+ console.log('[Lotio] Constructor called with options:', {
103
+ wasmPath: this._wasmPath,
104
+ fps: this._fps,
105
+ hasAnimation: !!this._animation,
106
+ hasLayerOverrides: !!this._layerOverrides,
107
+ textPadding: this._textPadding,
108
+ textMeasurementMode: this._textMeasurementMode,
109
+ fontsCount: this._fonts.length,
110
+ debug: this._debug
111
+ });
112
+ }
113
+
99
114
  // Auto-initialize if animation is provided
100
115
  if (this._animation) {
101
116
  this._init().catch(error => {
@@ -111,14 +126,34 @@ export class Lotio extends EventEmitter {
111
126
  */
112
127
  async _init() {
113
128
  if (this._wasmInitialized) {
129
+ if (this._debug) {
130
+ console.log('[Lotio] WASM already initialized, skipping');
131
+ }
114
132
  return;
115
133
  }
116
134
 
135
+ let initStartTime;
136
+ if (this._debug) {
137
+ console.log('[Lotio] Initializing WASM module from:', this._wasmPath);
138
+ initStartTime = performance.now();
139
+ setDebugMode(true);
140
+ } else {
141
+ setDebugMode(false);
142
+ }
143
+
117
144
  try {
118
145
  await initLotio(this._wasmPath);
119
146
  this._wasmInitialized = true;
120
147
 
148
+ if (this._debug) {
149
+ const initEndTime = performance.now();
150
+ console.log(`[Lotio] WASM module initialized successfully in ${initStartTime ? (initEndTime - initStartTime).toFixed(2) : '?'}ms`);
151
+ }
152
+
121
153
  // Register fonts
154
+ if (this._debug && this._fonts.length > 0) {
155
+ console.log(`[Lotio] Registering ${this._fonts.length} font(s)...`);
156
+ }
122
157
  for (const font of this._fonts) {
123
158
  await this._registerFont(font.name, font.data);
124
159
  }
@@ -128,8 +163,14 @@ export class Lotio extends EventEmitter {
128
163
  await this._loadAnimation();
129
164
  } else {
130
165
  this._setState(State.LOADED);
166
+ if (this._debug) {
167
+ console.log('[Lotio] No animation provided, state set to LOADED');
168
+ }
131
169
  }
132
170
  } catch (error) {
171
+ if (this._debug) {
172
+ console.error('[Lotio] Failed to initialize WASM:', error);
173
+ }
133
174
  this._setState(State.ERROR);
134
175
  this.emit('error', error, this);
135
176
  throw error;
@@ -142,6 +183,9 @@ export class Lotio extends EventEmitter {
142
183
  */
143
184
  async _registerFont(name, data) {
144
185
  if (this._registeredFonts.has(name)) {
186
+ if (this._debug) {
187
+ console.log(`[Lotio] Font "${name}" already registered, skipping`);
188
+ }
145
189
  return;
146
190
  }
147
191
 
@@ -149,8 +193,16 @@ export class Lotio extends EventEmitter {
149
193
  await this._init();
150
194
  }
151
195
 
196
+ if (this._debug) {
197
+ console.log(`[Lotio] Registering font "${name}" (${data.length} bytes)`);
198
+ }
199
+
152
200
  registerFont(name, data);
153
201
  this._registeredFonts.add(name);
202
+
203
+ if (this._debug) {
204
+ console.log(`[Lotio] Font "${name}" registered successfully`);
205
+ }
154
206
  }
155
207
 
156
208
  /**
@@ -162,6 +214,12 @@ export class Lotio extends EventEmitter {
162
214
  await this._init();
163
215
  }
164
216
 
217
+ let loadStartTime;
218
+ if (this._debug) {
219
+ console.log('[Lotio] Loading animation...');
220
+ loadStartTime = performance.now();
221
+ }
222
+
165
223
  try {
166
224
  const animationJson = typeof this._animation === 'string'
167
225
  ? this._animation
@@ -173,6 +231,15 @@ export class Lotio extends EventEmitter {
173
231
  : JSON.stringify(this._layerOverrides))
174
232
  : null;
175
233
 
234
+ if (this._debug) {
235
+ const animSize = animationJson.length;
236
+ console.log(`[Lotio] Animation JSON size: ${animSize} bytes`);
237
+ if (layerOverridesJson) {
238
+ console.log(`[Lotio] Layer overrides JSON size: ${layerOverridesJson.length} bytes`);
239
+ }
240
+ console.log(`[Lotio] Text padding: ${this._textPadding}, measurement mode: ${this._textMeasurementMode}`);
241
+ }
242
+
176
243
  this._animationInfo = createAnimation(
177
244
  JSON.parse(animationJson),
178
245
  layerOverridesJson ? JSON.parse(layerOverridesJson) : null,
@@ -180,11 +247,25 @@ export class Lotio extends EventEmitter {
180
247
  this._textMeasurementMode
181
248
  );
182
249
 
250
+ if (this._debug) {
251
+ const loadEndTime = performance.now();
252
+ console.log(`[Lotio] Animation loaded successfully in ${loadStartTime ? (loadEndTime - loadStartTime).toFixed(2) : '?'}ms`);
253
+ console.log('[Lotio] Animation info:', {
254
+ width: this._animationInfo.width,
255
+ height: this._animationInfo.height,
256
+ duration: this._animationInfo.duration,
257
+ fps: this._animationInfo.fps
258
+ });
259
+ }
260
+
183
261
  this._currentTime = 0;
184
262
  this._currentFrame = 0;
185
263
  this._setState(State.LOADED);
186
264
  this.emit('loaded', this);
187
265
  } catch (error) {
266
+ if (this._debug) {
267
+ console.error('[Lotio] Failed to load animation:', error);
268
+ }
188
269
  this._setState(State.ERROR);
189
270
  this.emit('error', error, this);
190
271
  throw error;
@@ -200,6 +281,9 @@ export class Lotio extends EventEmitter {
200
281
  this._state = newState;
201
282
 
202
283
  if (oldState !== newState) {
284
+ if (this._debug) {
285
+ console.log(`[Lotio] State changed: ${oldState} -> ${newState}`);
286
+ }
203
287
  this.emit('statechange', newState, oldState, this);
204
288
  }
205
289
  }
@@ -210,6 +294,9 @@ export class Lotio extends EventEmitter {
210
294
  */
211
295
  _animate() {
212
296
  if (!this._isPlaying || !this._animationInfo) {
297
+ if (this._debug) {
298
+ console.warn('[Lotio] Animation loop called but not playing or no animation info');
299
+ }
213
300
  return;
214
301
  }
215
302
 
@@ -221,8 +308,15 @@ export class Lotio extends EventEmitter {
221
308
  this._currentTime = (elapsed * speedMultiplier) % this._animationInfo.duration;
222
309
  this._currentFrame = Math.floor(this._currentTime * animationFPS);
223
310
 
311
+ if (this._debug && this._currentFrame % Math.floor(animationFPS) === 0) {
312
+ console.log(`[Lotio] Frame ${this._currentFrame} at time ${this._currentTime.toFixed(3)}s (${(this._currentTime / this._animationInfo.duration * 100).toFixed(1)}%)`);
313
+ }
314
+
224
315
  // Check if animation ended
225
316
  if (this._currentTime >= this._animationInfo.duration) {
317
+ if (this._debug) {
318
+ console.log('[Lotio] Animation ended');
319
+ }
226
320
  this.stop();
227
321
  this.emit('end', this);
228
322
  return;
@@ -344,6 +438,9 @@ export class Lotio extends EventEmitter {
344
438
  throw new Error('Animation not loaded');
345
439
  }
346
440
 
441
+ const oldTime = this._currentTime;
442
+ const oldFrame = this._currentFrame;
443
+
347
444
  // Determine if input is frame number or time
348
445
  if (frameOrTime < 1) {
349
446
  // Likely a time value (0.0 - duration)
@@ -356,6 +453,10 @@ export class Lotio extends EventEmitter {
356
453
  this._currentTime = this._currentFrame / animationFPS;
357
454
  }
358
455
 
456
+ if (this._debug && (oldTime !== this._currentTime || oldFrame !== this._currentFrame)) {
457
+ console.log(`[Lotio] Seek: frame ${oldFrame} -> ${this._currentFrame}, time ${oldTime.toFixed(3)}s -> ${this._currentTime.toFixed(3)}s`);
458
+ }
459
+
359
460
  if (this._isPlaying) {
360
461
  this._startTime = performance.now() - (this._currentTime / (this._fps / (this._animationInfo.fps || 30)) * 1000);
361
462
  }
@@ -370,9 +471,17 @@ export class Lotio extends EventEmitter {
370
471
  }
371
472
 
372
473
  if (this._isPlaying) {
474
+ if (this._debug) {
475
+ console.log('[Lotio] Already playing, ignoring start() call');
476
+ }
373
477
  return this;
374
478
  }
375
479
 
480
+ if (this._debug) {
481
+ console.log('[Lotio] Starting animation...');
482
+ console.log(`[Lotio] Starting from frame ${this._currentFrame}, time ${this._currentTime.toFixed(3)}s`);
483
+ }
484
+
376
485
  this._isPlaying = true;
377
486
  this._setState(State.PLAYING);
378
487
 
@@ -380,6 +489,10 @@ export class Lotio extends EventEmitter {
380
489
  const speedMultiplier = this._fps / animationFPS;
381
490
  this._startTime = performance.now() - (this._currentTime / speedMultiplier * 1000);
382
491
 
492
+ if (this._debug) {
493
+ console.log(`[Lotio] Animation FPS: ${animationFPS}, Target FPS: ${this._fps}, Speed multiplier: ${speedMultiplier.toFixed(3)}`);
494
+ }
495
+
383
496
  this.emit('start', this);
384
497
  this._animate();
385
498
 
@@ -388,9 +501,16 @@ export class Lotio extends EventEmitter {
388
501
 
389
502
  pause() {
390
503
  if (!this._isPlaying) {
504
+ if (this._debug) {
505
+ console.log('[Lotio] Not playing, ignoring pause() call');
506
+ }
391
507
  return this;
392
508
  }
393
509
 
510
+ if (this._debug) {
511
+ console.log(`[Lotio] Pausing at frame ${this._currentFrame}, time ${this._currentTime.toFixed(3)}s`);
512
+ }
513
+
394
514
  this._isPlaying = false;
395
515
  this._setState(State.PAUSED);
396
516
 
@@ -406,6 +526,10 @@ export class Lotio extends EventEmitter {
406
526
  stop() {
407
527
  const wasPlaying = this._isPlaying;
408
528
 
529
+ if (this._debug && wasPlaying) {
530
+ console.log(`[Lotio] Stopping animation (was at frame ${this._currentFrame}, time ${this._currentTime.toFixed(3)}s)`);
531
+ }
532
+
409
533
  this.pause();
410
534
  this.seek(0);
411
535
  this._setState(State.STOPPED);
@@ -426,7 +550,20 @@ export class Lotio extends EventEmitter {
426
550
  if (!this._animationInfo) {
427
551
  throw new Error('Animation not loaded');
428
552
  }
553
+
554
+ let renderStartTime;
555
+ if (this._debug) {
556
+ console.log(`[Lotio] Rendering frame ${this._currentFrame} (time ${this._currentTime.toFixed(3)}s) to canvas`);
557
+ renderStartTime = performance.now();
558
+ }
559
+
429
560
  renderFrameToCanvas(canvas, this._currentTime, bgColor);
561
+
562
+ if (this._debug) {
563
+ const renderEndTime = performance.now();
564
+ console.log(`[Lotio] Canvas rendered in ${renderStartTime ? (renderEndTime - renderStartTime).toFixed(2) : '?'}ms`);
565
+ }
566
+
430
567
  return this;
431
568
  }
432
569
 
@@ -434,12 +571,21 @@ export class Lotio extends EventEmitter {
434
571
  * Cleanup resources
435
572
  */
436
573
  destroy() {
574
+ if (this._debug) {
575
+ console.log('[Lotio] Destroying instance...');
576
+ }
577
+
437
578
  this.stop();
438
579
  cleanup();
439
580
  this._wasmInitialized = false;
440
581
  this._animationInfo = null;
441
582
  this._state = State.STOPPED;
442
583
  this.emit('destroy', this);
584
+
585
+ if (this._debug) {
586
+ console.log('[Lotio] Instance destroyed');
587
+ }
588
+
443
589
  return this;
444
590
  }
445
591
  }
Binary file
package/browser/wasm.js CHANGED
@@ -1,5 +1,10 @@
1
1
  // Load WASM module
2
2
  let Module = null;
3
+ let DebugMode = false;
4
+
5
+ export function setDebugMode(debug) {
6
+ DebugMode = debug === true;
7
+ }
3
8
 
4
9
  export async function initLotio(wasmPath = './lotio.wasm', wasmBinary = null) {
5
10
  // Load Emscripten module using ESM import
@@ -74,7 +79,9 @@ export async function initLotio(wasmPath = './lotio.wasm', wasmBinary = null) {
74
79
  // HEAP arrays should be exported directly, but if not, try to create them
75
80
  // First check if they're already available
76
81
  if (Module.HEAP8 && Module.HEAP32 && Module.HEAPF32 && Module.HEAPU8) {
77
- console.log('HEAP arrays already available');
82
+ if (DebugMode) {
83
+ console.log('[WASM] HEAP arrays already available');
84
+ }
78
85
  } else {
79
86
  // Try to find the memory buffer
80
87
  let memoryBuffer = null;
@@ -105,11 +112,15 @@ export async function initLotio(wasmPath = './lotio.wasm', wasmBinary = null) {
105
112
  Module.HEAPU32 = new Uint32Array(memoryBuffer);
106
113
  Module.HEAPF32 = new Float32Array(memoryBuffer);
107
114
  Module.HEAPF64 = new Float64Array(memoryBuffer);
108
- console.log('Created HEAP arrays from memory buffer');
115
+ if (DebugMode) {
116
+ console.log('[WASM] Created HEAP arrays from memory buffer');
117
+ }
109
118
  }
110
119
  }
111
120
 
112
- console.log('Module initialized successfully');
121
+ if (DebugMode) {
122
+ console.log('[WASM] Module initialized successfully');
123
+ }
113
124
  return true;
114
125
  } catch (error) {
115
126
  throw new Error(`Failed to load WASM module: ${error.message}`);
@@ -121,10 +132,21 @@ export function createAnimation(jsonData, layerOverrides = null, textPadding = 0
121
132
  throw new Error('WASM module not initialized. Call initLotio() first.');
122
133
  }
123
134
 
135
+ if (DebugMode) {
136
+ console.log('[WASM] createAnimation called');
137
+ console.log('[WASM] Parameters:', {
138
+ hasJsonData: !!jsonData,
139
+ jsonDataType: typeof jsonData,
140
+ hasLayerOverrides: !!layerOverrides,
141
+ textPadding,
142
+ textMeasurementMode
143
+ });
144
+ }
145
+
124
146
  // Check if Module has the required functions
125
147
  if (!Module._malloc || !Module._free || !Module._lotio_init) {
126
- console.error('Module object:', Module);
127
- console.error('Available functions:', Object.keys(Module).filter(k => typeof Module[k] === 'function'));
148
+ console.error('[WASM] Module object:', Module);
149
+ console.error('[WASM] Available functions:', Object.keys(Module).filter(k => typeof Module[k] === 'function'));
128
150
  throw new Error('Module is not properly initialized. Missing _malloc, _free, or _lotio_init functions.');
129
151
  }
130
152
 
@@ -177,6 +199,13 @@ export function createAnimation(jsonData, layerOverrides = null, textPadding = 0
177
199
  const jsonStr = typeof jsonData === 'string' ? jsonData : JSON.stringify(jsonData);
178
200
  const layerOverridesStr = layerOverrides ? (typeof layerOverrides === 'string' ? layerOverrides : JSON.stringify(layerOverrides)) : null;
179
201
 
202
+ if (DebugMode) {
203
+ console.log(`[WASM] JSON string length: ${jsonStr.length} bytes`);
204
+ if (layerOverridesStr) {
205
+ console.log(`[WASM] Layer overrides string length: ${layerOverridesStr.length} bytes`);
206
+ }
207
+ }
208
+
180
209
  // Convert textMeasurementMode string to integer (0=FAST, 1=ACCURATE, 2=PIXEL_PERFECT)
181
210
  const modeStr = String(textMeasurementMode).toLowerCase();
182
211
  let modeInt = 1; // Default to ACCURATE
@@ -188,11 +217,21 @@ export function createAnimation(jsonData, layerOverrides = null, textPadding = 0
188
217
  modeInt = 2;
189
218
  }
190
219
 
220
+ if (DebugMode) {
221
+ console.log(`[WASM] Text measurement mode: "${textMeasurementMode}" -> ${modeInt} (0=FAST, 1=ACCURATE, 2=PIXEL_PERFECT)`);
222
+ console.log(`[WASM] Text padding: ${textPadding}`);
223
+ }
224
+
191
225
  // Allocate memory using exported _malloc
192
226
  const jsonPtr = Module._malloc(jsonStr.length + 1);
193
227
  if (!jsonPtr) {
194
228
  throw new Error('Failed to allocate memory for JSON data');
195
229
  }
230
+
231
+ if (DebugMode) {
232
+ console.log(`[WASM] Allocated ${jsonStr.length + 1} bytes for JSON at pointer ${jsonPtr}`);
233
+ }
234
+
196
235
  Module.stringToUTF8(jsonStr, jsonPtr, jsonStr.length + 1);
197
236
 
198
237
  let layerOverridesPtr = 0;
@@ -204,17 +243,34 @@ export function createAnimation(jsonData, layerOverrides = null, textPadding = 0
204
243
  throw new Error('Failed to allocate memory for layer overrides');
205
244
  }
206
245
  layerOverridesLen = layerOverridesStr.length;
246
+ if (DebugMode) {
247
+ console.log(`[WASM] Allocated ${layerOverridesStr.length + 1} bytes for layer overrides at pointer ${layerOverridesPtr}`);
248
+ }
207
249
  Module.stringToUTF8(layerOverridesStr, layerOverridesPtr, layerOverridesStr.length + 1);
208
250
  }
209
251
 
252
+ let initStartTime;
253
+ if (DebugMode) {
254
+ console.log('[WASM] Calling _lotio_init...');
255
+ initStartTime = performance.now();
256
+ }
257
+
210
258
  const result = Module._lotio_init(jsonPtr, jsonStr.length, layerOverridesPtr, layerOverridesLen, textPadding, modeInt);
211
259
 
260
+ if (DebugMode) {
261
+ const initEndTime = performance.now();
262
+ console.log(`[WASM] _lotio_init returned: ${result} (${result === 0 ? 'success' : 'error'}) in ${initStartTime ? (initEndTime - initStartTime).toFixed(2) : '?'}ms`);
263
+ }
264
+
212
265
  Module._free(jsonPtr);
213
266
  if (layerOverridesPtr) {
214
267
  Module._free(layerOverridesPtr);
215
268
  }
216
269
 
217
270
  if (result !== 0) {
271
+ if (DebugMode) {
272
+ console.error(`[WASM] Animation initialization failed with error code: ${result}`);
273
+ }
218
274
  throw new Error('Failed to initialize animation');
219
275
  }
220
276
 
@@ -224,6 +280,10 @@ export function createAnimation(jsonData, layerOverrides = null, textPadding = 0
224
280
  const durationPtr = Module._malloc(4);
225
281
  const fpsPtr = Module._malloc(4);
226
282
 
283
+ if (DebugMode) {
284
+ console.log('[WASM] Getting animation info...');
285
+ }
286
+
227
287
  Module._lotio_get_info(widthPtr, heightPtr, durationPtr, fpsPtr);
228
288
 
229
289
  const info = {
@@ -233,6 +293,10 @@ export function createAnimation(jsonData, layerOverrides = null, textPadding = 0
233
293
  fps: Module.HEAPF32[fpsPtr / 4]
234
294
  };
235
295
 
296
+ if (DebugMode) {
297
+ console.log('[WASM] Animation info retrieved:', info);
298
+ }
299
+
236
300
  Module._free(widthPtr);
237
301
  Module._free(heightPtr);
238
302
  Module._free(durationPtr);
@@ -246,6 +310,10 @@ export function renderFrameRGBA(time) {
246
310
  throw new Error('WASM module not initialized.');
247
311
  }
248
312
 
313
+ if (DebugMode) {
314
+ console.log(`[WASM] renderFrameRGBA called for time: ${time.toFixed(3)}s`);
315
+ }
316
+
249
317
  // Ensure HEAP arrays are available - use same logic as createAnimation
250
318
  if (!Module.HEAP32 || !Module.HEAPU8) {
251
319
  let memoryBuffer = null;
@@ -283,8 +351,20 @@ export function renderFrameRGBA(time) {
283
351
  const bufferSize = width * height * 4; // RGBA
284
352
  const bufferPtr = Module._malloc(bufferSize);
285
353
 
354
+ let renderStartTime;
355
+ if (DebugMode) {
356
+ console.log(`[WASM] Allocated ${bufferSize} bytes (${width}x${height} RGBA) at pointer ${bufferPtr}`);
357
+ console.log(`[WASM] Calling _lotio_render_frame...`);
358
+ renderStartTime = performance.now();
359
+ }
360
+
286
361
  const result = Module._lotio_render_frame(time, bufferPtr, bufferSize);
287
362
 
363
+ if (DebugMode) {
364
+ const renderEndTime = performance.now();
365
+ console.log(`[WASM] _lotio_render_frame returned: ${result} (${result === 0 ? 'success' : 'error'}) in ${renderStartTime ? (renderEndTime - renderStartTime).toFixed(2) : '?'}ms`);
366
+ }
367
+
288
368
  if (result !== 0) {
289
369
  Module._free(bufferPtr);
290
370
  const errorMsg = result === 1 ? 'Animation not initialized' :
@@ -295,6 +375,9 @@ export function renderFrameRGBA(time) {
295
375
  result === 6 ? 'Failed to create conversion surface' :
296
376
  result === 7 ? 'Failed to create converted image' :
297
377
  `Unknown error: ${result}`;
378
+ if (DebugMode) {
379
+ console.error(`[WASM] Render error: ${errorMsg} (code ${result})`);
380
+ }
298
381
  throw new Error(`Failed to render frame: ${errorMsg} (code ${result})`);
299
382
  }
300
383
 
@@ -305,32 +388,64 @@ export function renderFrameRGBA(time) {
305
388
  const rgba = new Uint8Array(bufferView);
306
389
 
307
390
  // Debug: Check if we got any non-zero pixels
308
- let nonZeroPixels = 0;
309
- let maxValue = 0;
310
- for (let i = 0; i < Math.min(rgba.length, 1000); i++) {
311
- if (rgba[i] !== 0) {
312
- nonZeroPixels++;
313
- maxValue = Math.max(maxValue, rgba[i]);
391
+ if (DebugMode) {
392
+ let nonZeroPixels = 0;
393
+ let maxValue = 0;
394
+ for (let i = 0; i < Math.min(rgba.length, 1000); i++) {
395
+ if (rgba[i] !== 0) {
396
+ nonZeroPixels++;
397
+ maxValue = Math.max(maxValue, rgba[i]);
398
+ }
399
+ }
400
+
401
+ // Sample a few pixels from the center
402
+ const centerX = Math.floor(width / 2);
403
+ const centerY = Math.floor(height / 2);
404
+ const centerIdx = (centerY * width + centerX) * 4;
405
+ const centerPixel = centerIdx < rgba.length ? [
406
+ rgba[centerIdx],
407
+ rgba[centerIdx + 1],
408
+ rgba[centerIdx + 2],
409
+ rgba[centerIdx + 3]
410
+ ] : [0, 0, 0, 0];
411
+
412
+ // Sample corner pixels too
413
+ const topLeftPixel = [
414
+ rgba[0],
415
+ rgba[1],
416
+ rgba[2],
417
+ rgba[3]
418
+ ];
419
+ const bottomRightIdx = (height - 1) * width * 4 + (width - 1) * 4;
420
+ const bottomRightPixel = bottomRightIdx < rgba.length ? [
421
+ rgba[bottomRightIdx],
422
+ rgba[bottomRightIdx + 1],
423
+ rgba[bottomRightIdx + 2],
424
+ rgba[bottomRightIdx + 3]
425
+ ] : [0, 0, 0, 0];
426
+
427
+ if (nonZeroPixels === 0) {
428
+ console.warn(`[WASM] Warning: All pixels are zero! Frame at time ${time} may be empty.`);
429
+ console.warn(`[WASM] Center pixel (${centerX}, ${centerY}): [${centerPixel.join(', ')}]`);
430
+ console.warn(`[WASM] Top-left pixel: [${topLeftPixel.join(', ')}]`);
431
+ console.warn(`[WASM] Bottom-right pixel: [${bottomRightPixel.join(', ')}]`);
432
+ } else {
433
+ console.log(`[WASM] Frame rendered at time ${time.toFixed(3)}s: ${nonZeroPixels} non-zero pixels found, max value: ${maxValue}`);
434
+ console.log(`[WASM] Center pixel (${centerX}, ${centerY}): [${centerPixel.join(', ')}]`);
435
+ console.log(`[WASM] Top-left pixel: [${topLeftPixel.join(', ')}]`);
436
+ console.log(`[WASM] Bottom-right pixel: [${bottomRightPixel.join(', ')}]`);
437
+
438
+ // Check alpha channel distribution
439
+ let opaquePixels = 0;
440
+ let transparentPixels = 0;
441
+ let semiTransparentPixels = 0;
442
+ for (let i = 3; i < Math.min(rgba.length, 10000); i += 4) {
443
+ if (rgba[i] === 0) transparentPixels++;
444
+ else if (rgba[i] === 255) opaquePixels++;
445
+ else semiTransparentPixels++;
446
+ }
447
+ console.log(`[WASM] Alpha channel stats (first 10k pixels): opaque=${opaquePixels}, transparent=${transparentPixels}, semi-transparent=${semiTransparentPixels}`);
314
448
  }
315
- }
316
-
317
- // Sample a few pixels from the center
318
- const centerX = Math.floor(width / 2);
319
- const centerY = Math.floor(height / 2);
320
- const centerIdx = (centerY * width + centerX) * 4;
321
- const centerPixel = centerIdx < rgba.length ? [
322
- rgba[centerIdx],
323
- rgba[centerIdx + 1],
324
- rgba[centerIdx + 2],
325
- rgba[centerIdx + 3]
326
- ] : [0, 0, 0, 0];
327
-
328
- if (nonZeroPixels === 0) {
329
- console.warn(`[WASM] Warning: All pixels are zero! Frame at time ${time} may be empty.`);
330
- console.warn(`[WASM] Center pixel (${centerX}, ${centerY}): [${centerPixel.join(', ')}]`);
331
- } else {
332
- console.log(`[WASM] Frame rendered: ${nonZeroPixels} non-zero pixels found, max value: ${maxValue}`);
333
- console.log(`[WASM] Center pixel (${centerX}, ${centerY}): [${centerPixel.join(', ')}]`);
334
449
  }
335
450
 
336
451
  Module._free(bufferPtr);
@@ -423,22 +538,27 @@ export function renderFrameToCanvas(canvas, time, bgColor = '#2a2a2a') {
423
538
  }
424
539
 
425
540
  // Debug: Check if imageData is being populated
426
- const beforeSet = imageData.data[0];
427
- imageData.data.set(rgba);
428
- const afterSet = imageData.data[0];
429
-
430
- // Check a few sample pixels
431
- const sampleIdx = Math.floor((height / 2) * width + width / 2) * 4;
432
- const samplePixel = sampleIdx < imageData.data.length ? [
433
- imageData.data[sampleIdx],
434
- imageData.data[sampleIdx + 1],
435
- imageData.data[sampleIdx + 2],
436
- imageData.data[sampleIdx + 3]
437
- ] : [0, 0, 0, 0];
438
-
439
- console.log(`[Canvas] Rendering frame at time ${time}, size: ${width}x${height}`);
440
- console.log(`[Canvas] Sample pixel at center: [${samplePixel.join(', ')}]`);
441
- console.log(`[Canvas] ImageData first byte before set: ${beforeSet}, after set: ${afterSet}`);
541
+ if (DebugMode) {
542
+ const beforeSet = imageData.data[0];
543
+ imageData.data.set(rgba);
544
+ const afterSet = imageData.data[0];
545
+
546
+ // Check a few sample pixels
547
+ const sampleIdx = Math.floor((height / 2) * width + width / 2) * 4;
548
+ const samplePixel = sampleIdx < imageData.data.length ? [
549
+ imageData.data[sampleIdx],
550
+ imageData.data[sampleIdx + 1],
551
+ imageData.data[sampleIdx + 2],
552
+ imageData.data[sampleIdx + 3]
553
+ ] : [0, 0, 0, 0];
554
+
555
+ console.log(`[Canvas] Rendering frame at time ${time.toFixed(3)}s, size: ${width}x${height}`);
556
+ console.log(`[Canvas] Sample pixel at center: [${samplePixel.join(', ')}]`);
557
+ console.log(`[Canvas] ImageData first byte before set: ${beforeSet}, after set: ${afterSet}`);
558
+ console.log(`[Canvas] Background color: ${bgColor}`);
559
+ } else {
560
+ imageData.data.set(rgba);
561
+ }
442
562
 
443
563
  // Create a temporary canvas to composite the image over the background
444
564
  // This ensures the background color shows through transparent areas
@@ -455,21 +575,28 @@ export function renderFrameToCanvas(canvas, time, bgColor = '#2a2a2a') {
455
575
  tempCtx.putImageData(imageData, 0, 0);
456
576
 
457
577
  // Debug: Check what's actually on the temp canvas
458
- const tempImageData = tempCtx.getImageData(0, 0, width, height);
459
- const tempSamplePixel = sampleIdx < tempImageData.data.length ? [
460
- tempImageData.data[sampleIdx],
461
- tempImageData.data[sampleIdx + 1],
462
- tempImageData.data[sampleIdx + 2],
463
- tempImageData.data[sampleIdx + 3]
464
- ] : [0, 0, 0, 0];
465
- console.log(`[Canvas] Temp canvas sample pixel: [${tempSamplePixel.join(', ')}]`);
578
+ if (DebugMode) {
579
+ const sampleIdx = Math.floor((height / 2) * width + width / 2) * 4;
580
+ const tempImageData = tempCtx.getImageData(0, 0, width, height);
581
+ const tempSamplePixel = sampleIdx < tempImageData.data.length ? [
582
+ tempImageData.data[sampleIdx],
583
+ tempImageData.data[sampleIdx + 1],
584
+ tempImageData.data[sampleIdx + 2],
585
+ tempImageData.data[sampleIdx + 3]
586
+ ] : [0, 0, 0, 0];
587
+ console.log(`[Canvas] Temp canvas sample pixel: [${tempSamplePixel.join(', ')}]`);
588
+ }
466
589
 
467
590
  // Draw the composited result to the main canvas
468
591
  ctx.drawImage(tempCanvas, 0, 0);
469
592
 
470
593
  // Final check: verify what's on the main canvas
471
- const finalImageData = ctx.getImageData(0, 0, Math.min(width, 10), Math.min(height, 10));
472
- console.log(`[Canvas] Final canvas first 10x10 pixels sample: [${Array.from(finalImageData.data.slice(0, 16)).join(', ')}]`);
594
+ if (DebugMode) {
595
+ const finalImageData = ctx.getImageData(0, 0, Math.min(width, 10), Math.min(height, 10));
596
+ console.log(`[Canvas] Final canvas first 10x10 pixels sample: [${Array.from(finalImageData.data.slice(0, 16)).join(', ')}]`);
597
+ console.log(`[Canvas] Canvas dimensions: ${canvas.width}x${canvas.height}`);
598
+ console.log(`[Canvas] Canvas context type: ${ctx.getContext ? '2d' : 'unknown'}`);
599
+ }
473
600
  }
474
601
 
475
602
  // Helper to render frame as ImageData
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lotio",
3
- "version": "1.1.54",
3
+ "version": "1.1.55",
4
4
  "description": "High-performance Lottie animation frame renderer for the browser",
5
5
  "main": "browser/index.js",
6
6
  "module": "browser/index.js",