lotio 1.1.54 → 1.1.56

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.
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
 
@@ -304,33 +387,122 @@ export function renderFrameRGBA(time) {
304
387
  // Create a copy to avoid issues with memory being freed
305
388
  const rgba = new Uint8Array(bufferView);
306
389
 
307
- // 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]);
390
+ // Debug: Sum ALL pixel data to check if everything is transparent
391
+ // This checks the ENTIRE frame (0,0 to width,height) - slow but 100% confidence
392
+ // Prints per frame/each frame
393
+ if (DebugMode) {
394
+ const pixelCount = rgba.length / 4;
395
+ let totalSum = 0;
396
+ let rSum = 0, gSum = 0, bSum = 0, aSum = 0;
397
+ let nonZeroPixels = 0;
398
+ let opaquePixels = 0;
399
+ let transparentPixels = 0;
400
+
401
+ // Sum all pixels in the entire frame (0,0 to width,height)
402
+ for (let i = 0; i < rgba.length; i += 4) {
403
+ const r = rgba[i];
404
+ const g = rgba[i + 1];
405
+ const b = rgba[i + 2];
406
+ const a = rgba[i + 3];
407
+
408
+ totalSum += r + g + b + a;
409
+ rSum += r;
410
+ gSum += g;
411
+ bSum += b;
412
+ aSum += a;
413
+
414
+ if (r !== 0 || g !== 0 || b !== 0 || a !== 0) {
415
+ nonZeroPixels++;
416
+ }
417
+ if (a === 255) {
418
+ opaquePixels++;
419
+ } else if (a === 0) {
420
+ transparentPixels++;
421
+ }
422
+ }
423
+
424
+ console.log(`[WASM] Frame ${time.toFixed(3)}s - Full frame pixel sum check (${width}x${height} = ${pixelCount} pixels, 0,0 to ${width},${height}):`);
425
+ console.log(`[WASM] Total sum of all RGBA values: ${totalSum}`);
426
+ console.log(`[WASM] Non-zero pixels: ${nonZeroPixels} / ${pixelCount} (${(nonZeroPixels / pixelCount * 100).toFixed(2)}%)`);
427
+ console.log(`[WASM] Opaque pixels: ${opaquePixels} (${(opaquePixels / pixelCount * 100).toFixed(2)}%)`);
428
+ console.log(`[WASM] Transparent pixels: ${transparentPixels} (${(transparentPixels / pixelCount * 100).toFixed(2)}%)`);
429
+ console.log(`[WASM] Average per channel: R=${(rSum / pixelCount).toFixed(2)}, G=${(gSum / pixelCount).toFixed(2)}, B=${(bSum / pixelCount).toFixed(2)}, A=${(aSum / pixelCount).toFixed(2)}`);
430
+
431
+ if (totalSum === 0) {
432
+ console.warn(`[WASM] ⚠️ WARNING: Sum is 0 - ALL ${pixelCount} pixels are completely transparent (R=0, G=0, B=0, A=0)`);
433
+ console.warn(`[WASM] This confirms that Skottie render() produced NO visible content`);
434
+ } else if (nonZeroPixels === 0) {
435
+ console.warn(`[WASM] ⚠️ WARNING: No non-zero pixels found despite non-zero sum (sum=${totalSum})`);
436
+ } else {
437
+ console.log(`[WASM] ✓ Frame has visible content: ${nonZeroPixels} pixels with non-zero values`);
314
438
  }
315
439
  }
316
440
 
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(', ')}]`);
441
+ // Debug: Check if we got any non-zero pixels (sampling approach - faster but less thorough)
442
+ if (DebugMode) {
443
+ let nonZeroPixels = 0;
444
+ let maxValue = 0;
445
+ for (let i = 0; i < Math.min(rgba.length, 1000); i++) {
446
+ if (rgba[i] !== 0) {
447
+ nonZeroPixels++;
448
+ maxValue = Math.max(maxValue, rgba[i]);
449
+ }
450
+ }
451
+
452
+ // Sample a few pixels from the center
453
+ const centerX = Math.floor(width / 2);
454
+ const centerY = Math.floor(height / 2);
455
+ const centerIdx = (centerY * width + centerX) * 4;
456
+ const centerPixel = centerIdx < rgba.length ? [
457
+ rgba[centerIdx],
458
+ rgba[centerIdx + 1],
459
+ rgba[centerIdx + 2],
460
+ rgba[centerIdx + 3]
461
+ ] : [0, 0, 0, 0];
462
+
463
+ // Sample corner pixels too
464
+ const topLeftPixel = [
465
+ rgba[0],
466
+ rgba[1],
467
+ rgba[2],
468
+ rgba[3]
469
+ ];
470
+ const bottomRightIdx = (height - 1) * width * 4 + (width - 1) * 4;
471
+ const bottomRightPixel = bottomRightIdx < rgba.length ? [
472
+ rgba[bottomRightIdx],
473
+ rgba[bottomRightIdx + 1],
474
+ rgba[bottomRightIdx + 2],
475
+ rgba[bottomRightIdx + 3]
476
+ ] : [0, 0, 0, 0];
477
+
478
+ if (nonZeroPixels === 0) {
479
+ console.warn(`[WASM] ⚠️ WARNING: All pixels are zero! Frame at time ${time.toFixed(3)}s is completely transparent.`);
480
+ console.warn(`[WASM] This suggests the animation may be empty or have no visible layers.`);
481
+ console.warn(`[WASM] Possible causes:`);
482
+ console.warn(`[WASM] 1. Animation JSON has no visible layers or all layers are hidden`);
483
+ console.warn(`[WASM] 2. Animation inPoint >= outPoint (no valid frame range)`);
484
+ console.warn(`[WASM] 3. Animation duration is 0 or invalid`);
485
+ console.warn(`[WASM] 4. Animation rendering failed silently`);
486
+ console.warn(`[WASM] Center pixel (${centerX}, ${centerY}): [${centerPixel.join(', ')}]`);
487
+ console.warn(`[WASM] Top-left pixel: [${topLeftPixel.join(', ')}]`);
488
+ console.warn(`[WASM] Bottom-right pixel: [${bottomRightPixel.join(', ')}]`);
489
+ } else {
490
+ console.log(`[WASM] Frame rendered at time ${time.toFixed(3)}s: ${nonZeroPixels} non-zero pixels found, max value: ${maxValue}`);
491
+ console.log(`[WASM] Center pixel (${centerX}, ${centerY}): [${centerPixel.join(', ')}]`);
492
+ console.log(`[WASM] Top-left pixel: [${topLeftPixel.join(', ')}]`);
493
+ console.log(`[WASM] Bottom-right pixel: [${bottomRightPixel.join(', ')}]`);
494
+
495
+ // Check alpha channel distribution
496
+ let opaquePixels = 0;
497
+ let transparentPixels = 0;
498
+ let semiTransparentPixels = 0;
499
+ for (let i = 3; i < Math.min(rgba.length, 10000); i += 4) {
500
+ if (rgba[i] === 0) transparentPixels++;
501
+ else if (rgba[i] === 255) opaquePixels++;
502
+ else semiTransparentPixels++;
503
+ }
504
+ console.log(`[WASM] Alpha channel stats (first 10k pixels): opaque=${opaquePixels}, transparent=${transparentPixels}, semi-transparent=${semiTransparentPixels}`);
505
+ }
334
506
  }
335
507
 
336
508
  Module._free(bufferPtr);
@@ -402,12 +574,54 @@ export function cleanup() {
402
574
 
403
575
  // Helper to render frame to canvas
404
576
  export function renderFrameToCanvas(canvas, time, bgColor = '#2a2a2a') {
577
+ // Validate and fix background color BEFORE using it
578
+ if (!bgColor || bgColor === 'transparent' || bgColor === 'undefined' || bgColor === 'null' || typeof bgColor !== 'string') {
579
+ if (DebugMode) {
580
+ console.warn(`[Canvas] WARNING: Background color is invalid (${bgColor})! Using default #2a2a2a`);
581
+ }
582
+ bgColor = '#2a2a2a';
583
+ }
584
+
405
585
  const { rgba, width, height } = renderFrameRGBA(time);
406
586
 
407
- // Set canvas size
587
+ // Validate dimensions
588
+ if (width <= 0 || height <= 0) {
589
+ console.error(`[Canvas] ERROR: Invalid animation dimensions: ${width}x${height}`);
590
+ throw new Error(`Invalid animation dimensions: ${width}x${height}`);
591
+ }
592
+
593
+ // Check if RGBA data is all zeros (completely transparent)
594
+ let rgbaSum = 0;
595
+ let nonZeroPixels = 0;
596
+ for (let i = 0; i < Math.min(rgba.length, 10000); i += 4) {
597
+ const r = rgba[i];
598
+ const g = rgba[i + 1];
599
+ const b = rgba[i + 2];
600
+ const a = rgba[i + 3];
601
+ rgbaSum += r + g + b + a;
602
+ if (r !== 0 || g !== 0 || b !== 0 || a !== 0) {
603
+ nonZeroPixels++;
604
+ }
605
+ }
606
+
607
+ if (rgbaSum === 0) {
608
+ console.warn(`[Canvas] ⚠️ WARNING: RGBA data is completely transparent (all zeros) for frame at time ${time.toFixed(3)}s`);
609
+ console.warn(`[Canvas] This means the animation rendered but produced no visible content`);
610
+ console.warn(`[Canvas] Check your Lottie JSON - it may have no visible layers or all layers are hidden`);
611
+ } else if (DebugMode) {
612
+ console.log(`[Canvas] RGBA data check: sum=${rgbaSum}, non-zero pixels (first 10k): ${nonZeroPixels}`);
613
+ }
614
+
615
+ // Set canvas size (this will reset the canvas, so do it before getting context)
616
+ const oldWidth = canvas.width;
617
+ const oldHeight = canvas.height;
408
618
  canvas.width = width;
409
619
  canvas.height = height;
410
620
 
621
+ if (DebugMode && (oldWidth !== width || oldHeight !== height)) {
622
+ console.log(`[Canvas] Canvas size changed from ${oldWidth}x${oldHeight} to ${width}x${height}`);
623
+ }
624
+
411
625
  const ctx = canvas.getContext('2d');
412
626
 
413
627
  // Fill with background color first
@@ -423,22 +637,27 @@ export function renderFrameToCanvas(canvas, time, bgColor = '#2a2a2a') {
423
637
  }
424
638
 
425
639
  // 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}`);
640
+ if (DebugMode) {
641
+ const beforeSet = imageData.data[0];
642
+ imageData.data.set(rgba);
643
+ const afterSet = imageData.data[0];
644
+
645
+ // Check a few sample pixels
646
+ const sampleIdx = Math.floor((height / 2) * width + width / 2) * 4;
647
+ const samplePixel = sampleIdx < imageData.data.length ? [
648
+ imageData.data[sampleIdx],
649
+ imageData.data[sampleIdx + 1],
650
+ imageData.data[sampleIdx + 2],
651
+ imageData.data[sampleIdx + 3]
652
+ ] : [0, 0, 0, 0];
653
+
654
+ console.log(`[Canvas] Rendering frame at time ${time.toFixed(3)}s, size: ${width}x${height}`);
655
+ console.log(`[Canvas] Sample pixel at center: [${samplePixel.join(', ')}]`);
656
+ console.log(`[Canvas] ImageData first byte before set: ${beforeSet}, after set: ${afterSet}`);
657
+ console.log(`[Canvas] Background color used: ${bgColor}`);
658
+ } else {
659
+ imageData.data.set(rgba);
660
+ }
442
661
 
443
662
  // Create a temporary canvas to composite the image over the background
444
663
  // This ensures the background color shows through transparent areas
@@ -454,22 +673,113 @@ export function renderFrameToCanvas(canvas, time, bgColor = '#2a2a2a') {
454
673
  // Draw the RGBA image data on top
455
674
  tempCtx.putImageData(imageData, 0, 0);
456
675
 
457
- // 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(', ')}]`);
676
+ // Calculate and print sum of all pixels from temp canvas (before drawing to main canvas)
677
+ if (DebugMode) {
678
+ const tempImageData = tempCtx.getImageData(0, 0, width, height);
679
+ let totalSum = 0;
680
+ let rSum = 0, gSum = 0, bSum = 0, aSum = 0;
681
+
682
+ for (let i = 0; i < tempImageData.data.length; i += 4) {
683
+ const r = tempImageData.data[i];
684
+ const g = tempImageData.data[i + 1];
685
+ const b = tempImageData.data[i + 2];
686
+ const a = tempImageData.data[i + 3];
687
+
688
+ totalSum += r + g + b + a;
689
+ rSum += r;
690
+ gSum += g;
691
+ bSum += b;
692
+ aSum += a;
693
+ }
694
+
695
+ const pixelCount = tempImageData.data.length / 4;
696
+ console.log(`[Canvas] Frame ${time.toFixed(3)}s - Temp canvas sum (${width}x${height} = ${pixelCount} pixels): Total=${totalSum}, R=${rSum}, G=${gSum}, B=${bSum}, A=${aSum}`);
697
+ }
466
698
 
467
699
  // Draw the composited result to the main canvas
468
700
  ctx.drawImage(tempCanvas, 0, 0);
469
701
 
470
- // 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(', ')}]`);
702
+ // Print per frame - always show frame info
703
+ console.log(`[Canvas] Frame ${time.toFixed(3)}s - Rendering at coordinates: 0,0 to ${width},${height}`);
704
+
705
+ // Final check: verify what's on the main canvas - check ENTIRE frame (0,0 to width,height)
706
+ // This is different from the temp canvas check above - this checks the final result after drawing
707
+ if (DebugMode) {
708
+ // Get the entire canvas image data (0,0 to width,height)
709
+ const fullImageData = ctx.getImageData(0, 0, width, height);
710
+
711
+ // Sum all pixel data to check if everything is transparent
712
+ // This is slow but gives 100% confidence for the entire frame
713
+ let totalSum = 0;
714
+ let rSum = 0, gSum = 0, bSum = 0, aSum = 0;
715
+ let nonZeroPixels = 0;
716
+ let opaquePixels = 0;
717
+ let transparentPixels = 0;
718
+
719
+ for (let i = 0; i < fullImageData.data.length; i += 4) {
720
+ const r = fullImageData.data[i];
721
+ const g = fullImageData.data[i + 1];
722
+ const b = fullImageData.data[i + 2];
723
+ const a = fullImageData.data[i + 3];
724
+
725
+ totalSum += r + g + b + a;
726
+ rSum += r;
727
+ gSum += g;
728
+ bSum += b;
729
+ aSum += a;
730
+
731
+ if (r !== 0 || g !== 0 || b !== 0 || a !== 0) {
732
+ nonZeroPixels++;
733
+ }
734
+ if (a === 255) {
735
+ opaquePixels++;
736
+ } else if (a === 0) {
737
+ transparentPixels++;
738
+ }
739
+ }
740
+
741
+ const pixelCount = fullImageData.data.length / 4;
742
+
743
+ console.log(`[Canvas] Frame ${time.toFixed(3)}s - Full frame analysis (${width}x${height} = ${pixelCount} pixels):`);
744
+ console.log(`[Canvas] Total sum of all RGBA values: ${totalSum}`);
745
+ console.log(`[Canvas] Non-zero pixels: ${nonZeroPixels} / ${pixelCount} (${(nonZeroPixels / pixelCount * 100).toFixed(2)}%)`);
746
+ console.log(`[Canvas] Opaque pixels: ${opaquePixels} (${(opaquePixels / pixelCount * 100).toFixed(2)}%)`);
747
+ console.log(`[Canvas] Transparent pixels: ${transparentPixels} (${(transparentPixels / pixelCount * 100).toFixed(2)}%)`);
748
+ console.log(`[Canvas] Average per channel: R=${(rSum / pixelCount).toFixed(2)}, G=${(gSum / pixelCount).toFixed(2)}, B=${(bSum / pixelCount).toFixed(2)}, A=${(aSum / pixelCount).toFixed(2)}`);
749
+
750
+ if (totalSum === 0) {
751
+ console.warn(`[Canvas] ⚠️ WARNING: Sum is 0 - ALL ${pixelCount} pixels are completely transparent (R=0, G=0, B=0, A=0)`);
752
+ console.warn(`[Canvas] This confirms the frame has NO visible content - only background color is showing`);
753
+ } else if (nonZeroPixels === 0) {
754
+ console.warn(`[Canvas] ⚠️ WARNING: No non-zero pixels found despite non-zero sum (sum=${totalSum})`);
755
+ } else {
756
+ console.log(`[Canvas] ✓ Frame has visible content: ${nonZeroPixels} pixels with non-zero values`);
757
+ }
758
+
759
+ // Sample a few key pixels for reference
760
+ const centerX = Math.floor(width / 2);
761
+ const centerY = Math.floor(height / 2);
762
+ const centerIdx = (centerY * width + centerX) * 4;
763
+ const centerPixel = [
764
+ fullImageData.data[centerIdx],
765
+ fullImageData.data[centerIdx + 1],
766
+ fullImageData.data[centerIdx + 2],
767
+ fullImageData.data[centerIdx + 3]
768
+ ];
769
+
770
+ // Text region sample (bottom-center)
771
+ const textRegionY = Math.floor(height * 0.78);
772
+ const textRegionX = Math.floor(width / 2);
773
+ const textIdx = (textRegionY * width + textRegionX) * 4;
774
+ const textPixel = [
775
+ fullImageData.data[textIdx],
776
+ fullImageData.data[textIdx + 1],
777
+ fullImageData.data[textIdx + 2],
778
+ fullImageData.data[textIdx + 3]
779
+ ];
780
+
781
+ console.log(`[Canvas] Sample pixels: center(${centerX},${centerY})=[${centerPixel.join(',')}], text-region(${textRegionX},${textRegionY})=[${textPixel.join(',')}]`);
782
+ }
473
783
  }
474
784
 
475
785
  // 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.56",
4
4
  "description": "High-performance Lottie animation frame renderer for the browser",
5
5
  "main": "browser/index.js",
6
6
  "module": "browser/index.js",