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.
- package/browser/index.js +154 -2
- package/browser/lotio.js +1 -1
- package/browser/lotio.wasm +0 -0
- package/browser/wasm.js +368 -58
- package/package.json +1 -1
package/browser/lotio.wasm
CHANGED
|
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
|
-
|
|
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
|
-
|
|
115
|
+
if (DebugMode) {
|
|
116
|
+
console.log('[WASM] Created HEAP arrays from memory buffer');
|
|
117
|
+
}
|
|
109
118
|
}
|
|
110
119
|
}
|
|
111
120
|
|
|
112
|
-
|
|
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:
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
-
//
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
imageData.data[
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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
|
-
//
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
tempImageData.data
|
|
464
|
-
|
|
465
|
-
|
|
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
|
-
//
|
|
471
|
-
|
|
472
|
-
|
|
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
|