karthikeya1212 commited on
Commit
e0c2cd7
·
verified ·
1 Parent(s): d46dbc2

Update app/core/shadowGenerator.js

Browse files
Files changed (1) hide show
  1. app/core/shadowGenerator.js +226 -1627
app/core/shadowGenerator.js CHANGED
@@ -1,1627 +1,226 @@
1
- // const { createCanvas, loadImage } = require('canvas');
2
- // const path = require('path');
3
- // const fs = require('fs');
4
-
5
- // /**
6
- // * Generate shadow for a PNG image
7
- // * @param {string} imagePath - Path to input PNG
8
- // * @param {object} options - { mode: 'drop'|'cast', dir: 'front'|'back'|'left'|'right', blur: number, opacity: number }
9
- // * @returns {string} - Path to output shadowed image
10
- // */
11
- // async function generateShadow(imagePath, options = {}) {
12
- // const {
13
- // mode = 'drop',
14
- // dir = 'back',
15
- // blur = 20,
16
- // opacity = 0.4
17
- // } = options;
18
-
19
- // const img = await loadImage(imagePath);
20
- // const w = img.width;
21
- // const h = img.height;
22
-
23
- // const canvas = createCanvas(w * 2, h * 2); // allow room for shadow
24
- // const ctx = canvas.getContext('2d');
25
-
26
- // ctx.clearRect(0, 0, canvas.width, canvas.height);
27
-
28
- // if (mode === 'drop') {
29
- // drawDropShadow(ctx, img, w, h, dir, blur, opacity);
30
- // } else {
31
- // drawCastShadow(ctx, img, w, h, dir, blur, opacity);
32
- // }
33
-
34
- // // draw original image centered
35
- // ctx.globalAlpha = 1;
36
- // ctx.filter = 'none';
37
- // ctx.drawImage(img, (canvas.width - w) / 2, (canvas.height - h) / 2, w, h);
38
-
39
- // // save file
40
- // const outPath = path.join(
41
- // path.dirname(imagePath),
42
- // `shadow_${Date.now()}.png`
43
- // );
44
- // const buffer = canvas.toBuffer('image/png');
45
- // fs.writeFileSync(outPath, buffer);
46
- // return outPath;
47
- // }
48
-
49
- // function drawDropShadow(ctx, img, w, h, dir, blur, opacity) {
50
- // let dx = 0, dy = 0, offset = 40;
51
- // if (dir === 'back') dy = offset;
52
- // if (dir === 'front') dy = -offset;
53
- // if (dir === 'left') dx = -offset;
54
- // if (dir === 'right') dx = offset;
55
-
56
- // ctx.save();
57
- // ctx.filter = `blur(${blur}px)`;
58
- // ctx.globalAlpha = opacity;
59
- // ctx.drawImage(
60
- // img,
61
- // (ctx.canvas.width - w) / 2 + dx,
62
- // (ctx.canvas.height - h) / 2 + dy,
63
- // w,
64
- // h
65
- // );
66
- // ctx.restore();
67
- // }
68
-
69
- // function drawCastShadow(ctx, img, w, h, dir, blur, opacity) {
70
- // // make a shadow silhouette
71
- // const temp = createCanvas(w, h);
72
- // const tctx = temp.getContext('2d');
73
- // tctx.drawImage(img, 0, 0, w, h);
74
- // tctx.globalCompositeOperation = 'source-in';
75
- // tctx.fillStyle = 'black';
76
- // tctx.fillRect(0, 0, w, h);
77
-
78
- // ctx.save();
79
- // ctx.translate(ctx.canvas.width / 2, ctx.canvas.height * 0.85);
80
- // let skewX = 0, stretchY = 1.5;
81
- // if (dir === 'back') stretchY = 0.5;
82
- // if (dir === 'front') stretchY = 2.0;
83
- // if (dir === 'left') skewX = -0.8;
84
- // if (dir === 'right') skewX = 0.8;
85
-
86
- // ctx.transform(1, 0, skewX, stretchY * 0.3, 0, 0);
87
- // ctx.globalAlpha = opacity;
88
- // ctx.filter = `blur(${blur}px)`;
89
- // ctx.drawImage(temp, -w / 2, -h, w, h);
90
- // ctx.restore();
91
- // }
92
-
93
- // module.exports = { generateShadow };
94
-
95
- // const { createCanvas, loadImage } = require('canvas');
96
-
97
- // async function generateShadow(imageBuffer, options = {}) {
98
- // const {
99
- // type = 'cast', // cast | drop | flat | box
100
- // dir = 'back', // used for non-cast
101
- // blur = 8,
102
- // opacity = 0.35,
103
- // lightX,
104
- // lightY
105
- // } = options;
106
-
107
- // const img = await loadImage(imageBuffer);
108
- // const trimmed = trimImage(img);
109
-
110
- // const W = 1000, H = 800; // base canvas size (can be resized)
111
- // const canvas = createCanvas(W, H);
112
- // const ctx = canvas.getContext('2d');
113
- // ctx.clearRect(0, 0, W, H);
114
-
115
- // // Scale object to fit
116
- // const maxW = W * 0.5;
117
- // const maxH = H * 0.5;
118
- // const scale = Math.min(maxW / trimmed.width, maxH / trimmed.height, 1);
119
- // const scaledW = trimmed.width * scale;
120
- // const scaledH = trimmed.height * scale;
121
- // const baseY = H * 0.85;
122
-
123
- // switch (type) {
124
- // case 'drop':
125
- // drawDropShadow(ctx, trimmed.canvas, scaledW, scaledH, dir, blur, opacity, W, H, baseY);
126
- // break;
127
- // case 'flat':
128
- // drawFlatShadow(ctx, trimmed.canvas, scaledW, scaledH, dir, opacity, W, H, baseY);
129
- // break;
130
- // case 'box':
131
- // drawBoxShadow(ctx, scaledW, scaledH, dir, blur, opacity, W, H, baseY);
132
- // break;
133
- // case 'cast':
134
- // default:
135
- // drawCastShadow(ctx, trimmed.canvas, scaledW, scaledH, blur, opacity, W, H, baseY, lightX, lightY, dir);
136
- // break;
137
- // }
138
-
139
- // // Draw object on top
140
- // ctx.globalAlpha = 1;
141
- // ctx.filter = "none";
142
- // ctx.drawImage(trimmed.canvas, (W - scaledW) / 2, baseY - scaledH, scaledW, scaledH);
143
-
144
- // return canvas.toBuffer("image/png");
145
- // }
146
-
147
- // // ----------------- Shadow Types ------------------
148
-
149
- // function drawDropShadow(ctx, img, w, h, dir, blur, opacity, W, H, baseY) {
150
- // let dx=0, dy=0, off=40;
151
- // if (dir==='back') dy=off;
152
- // if (dir==='front') dy=-off;
153
- // if (dir==='left') dx=-off;
154
- // if (dir==='right') dx=off;
155
-
156
- // ctx.save();
157
- // ctx.filter = `blur(${blur}px)`;
158
- // ctx.globalAlpha = opacity;
159
- // ctx.drawImage(img, (W-w)/2+dx, baseY-h+dy, w, h);
160
- // ctx.restore();
161
- // }
162
-
163
- // function drawFlatShadow(ctx, img, w, h, dir, opacity, W, H, baseY) {
164
- // let dx=0, dy=0, off=30;
165
- // if (dir==='back') dy=off;
166
- // if (dir==='front') dy=-off;
167
- // if (dir==='left') dx=-off;
168
- // if (dir==='right') dx=off;
169
-
170
- // ctx.save();
171
- // ctx.globalAlpha = opacity;
172
- // ctx.drawImage(img, (W-w)/2+dx, baseY-h+dy, w, h);
173
- // ctx.restore();
174
- // }
175
-
176
- // function drawBoxShadow(ctx, w, h, dir, blur, opacity, W, H, baseY) {
177
- // let dx=0, dy=0, off=30;
178
- // if (dir==='back') dy=off;
179
- // if (dir==='front') dy=-off;
180
- // if (dir==='left') dx=-off;
181
- // if (dir==='right') dx=off;
182
-
183
- // ctx.save();
184
- // ctx.fillStyle = "rgba(0,0,0,0.3)";
185
- // ctx.filter = `blur(${blur}px)`;
186
- // ctx.fillRect((W-w)/2+dx, baseY-h+dy, w, h);
187
- // ctx.restore();
188
- // }
189
-
190
- // function drawCastShadow(ctx, img, w, h, blur, opacity, W, H, baseY, lightX, lightY, dir) {
191
- // const shadowCanvas = createCanvas(w, h);
192
- // const sctx = shadowCanvas.getContext('2d');
193
- // sctx.drawImage(img, 0, 0, w, h);
194
- // sctx.globalCompositeOperation = "source-in";
195
- // sctx.fillStyle = "black";
196
- // sctx.fillRect(0, 0, w, h);
197
-
198
- // // relative light (0 = top, 1 = bottom)
199
- // const relX = lightX ? (lightX / W - 0.5) * 2 : (dir==='left'?-1:dir==='right'?1:0);
200
- // const relY = lightY ? (lightY / H) : (dir==='front'?0.2:dir==='back'?0.8:0.5);
201
-
202
- // const stretch = 1 + (1 - relY) * 2;
203
- // const skew = relX * 0.8;
204
-
205
- // ctx.save();
206
- // ctx.translate(W/2, baseY);
207
-
208
- // if (relY > 0.5 || dir === 'back') {
209
- // // Light is behind shadow comes forward
210
- // ctx.transform(1, 0, skew, -stretch * 0.3, 0, 0);
211
- // ctx.globalAlpha = opacity;
212
- // ctx.filter = `blur(${blur}px)`;
213
- // ctx.drawImage(shadowCanvas, -w/2, 0, w, h);
214
- // } else {
215
- // // Light in front/top → shadow behind
216
- // ctx.transform(1, 0, skew, stretch * 0.3, 0, 0);
217
- // ctx.globalAlpha = opacity;
218
- // ctx.filter = `blur(${blur}px)`;
219
- // ctx.drawImage(shadowCanvas, -w/2, -h, w, h);
220
- // }
221
-
222
- // ctx.restore();
223
- // }
224
-
225
- // // ----------------- Trimming ------------------
226
-
227
- // function trimImage(image) {
228
- // const temp = createCanvas(image.width, image.height);
229
- // const tctx = temp.getContext('2d');
230
- // tctx.drawImage(image, 0, 0);
231
- // const imgData = tctx.getImageData(0, 0, temp.width, temp.height);
232
-
233
- // let top=temp.height, left=temp.width, right=0, bottom=0;
234
- // for (let y=0;y<temp.height;y++) {
235
- // for (let x=0;x<temp.width;x++) {
236
- // const alpha = imgData.data[(y*temp.width+x)*4+3];
237
- // if (alpha>0) {
238
- // if (x<left) left=x;
239
- // if (x>right) right=x;
240
- // if (y<top) top=y;
241
- // if (y>bottom) bottom=y;
242
- // }
243
- // }
244
- // }
245
-
246
- // const w = right-left+1;
247
- // const h = bottom-top+1;
248
- // const trimmedCanvas = createCanvas(w,h);
249
- // const t2 = trimmedCanvas.getContext('2d');
250
- // t2.drawImage(image, left, top, w, h, 0, 0, w, h);
251
-
252
- // return { canvas: trimmedCanvas, width:w, height:h };
253
- // }
254
-
255
- // module.exports = { generateShadow };
256
-
257
-
258
- // const { createCanvas, loadImage } = require('canvas');
259
-
260
- // async function generateShadow(imageBuffer, options = {}) {
261
- // const {
262
- // type = 'cast', // cast | drop | flat | box
263
- // dir = 'back', // back | front | left | right
264
- // blur = 8,
265
- // opacity = 0.35,
266
- // lightX, // optional custom light position
267
- // lightY
268
- // } = options;
269
-
270
- // const img = await loadImage(imageBuffer);
271
- // const trimmed = trimImage(img);
272
-
273
- // const W = 1000, H = 800; // base canvas size
274
- // const canvas = createCanvas(W, H);
275
- // const ctx = canvas.getContext('2d');
276
- // ctx.clearRect(0, 0, W, H);
277
-
278
- // // Scale object to fit canvas
279
- // const maxW = W * 0.5;
280
- // const maxH = H * 0.5;
281
- // const scale = Math.min(maxW / trimmed.width, maxH / trimmed.height, 1);
282
- // const scaledW = trimmed.width * scale;
283
- // const scaledH = trimmed.height * scale;
284
- // const baseY = H * 0.85;
285
-
286
- // // Draw shadow
287
- // switch (type) {
288
- // case 'drop':
289
- // drawDropShadow(ctx, trimmed.canvas, scaledW, scaledH, dir, blur, opacity, W, H, baseY);
290
- // break;
291
- // case 'flat':
292
- // drawFlatShadow(ctx, trimmed.canvas, scaledW, scaledH, dir, opacity, W, H, baseY);
293
- // break;
294
- // case 'box':
295
- // drawBoxShadow(ctx, scaledW, scaledH, dir, blur, opacity, W, H, baseY);
296
- // break;
297
- // case 'cast':
298
- // default:
299
- // drawCastShadow(ctx, trimmed.canvas, scaledW, scaledH, blur, opacity, W, H, baseY, lightX, lightY, dir);
300
- // break;
301
- // }
302
-
303
- // // Draw object on top
304
- // ctx.globalAlpha = 1;
305
- // ctx.filter = "none";
306
- // ctx.drawImage(trimmed.canvas, (W - scaledW) / 2, baseY - scaledH, scaledW, scaledH);
307
-
308
- // return canvas.toBuffer("image/png");
309
- // }
310
-
311
- // // ----------------- Shadow Types ------------------
312
-
313
- // function drawDropShadow(ctx, img, w, h, dir, blur, opacity, W, H, baseY) {
314
- // let dx = 0, dy = 0, off = 40;
315
- // if (dir === 'back') dy = off;
316
- // if (dir === 'front') dy = -off;
317
- // if (dir === 'left') dx = -off;
318
- // if (dir === 'right') dx = off;
319
-
320
- // ctx.save();
321
- // ctx.filter = `blur(${blur}px)`;
322
- // ctx.globalAlpha = opacity;
323
- // ctx.drawImage(img, (W - w) / 2 + dx, baseY - h + dy, w, h);
324
- // ctx.restore();
325
- // }
326
-
327
- // function drawFlatShadow(ctx, img, w, h, dir, opacity, W, H, baseY) {
328
- // let dx = 0, dy = 0, off = 30;
329
- // if (dir === 'back') dy = off;
330
- // if (dir === 'front') dy = -off;
331
- // if (dir === 'left') dx = -off;
332
- // if (dir === 'right') dx = off;
333
-
334
- // ctx.save();
335
- // ctx.globalAlpha = opacity;
336
- // ctx.drawImage(img, (W - w) / 2 + dx, baseY - h + dy, w, h);
337
- // ctx.restore();
338
- // }
339
-
340
- // function drawBoxShadow(ctx, w, h, dir, blur, opacity, W, H, baseY) {
341
- // let dx = 0, dy = 0, off = 30;
342
- // if (dir === 'back') dy = off;
343
- // if (dir === 'front') dy = -off;
344
- // if (dir === 'left') dx = -off;
345
- // if (dir === 'right') dx = off;
346
-
347
- // ctx.save();
348
- // ctx.fillStyle = `rgba(0,0,0,${opacity})`;
349
- // ctx.filter = `blur(${blur}px)`;
350
- // ctx.fillRect((W - w) / 2 + dx, baseY - h + dy, w, h);
351
- // ctx.restore();
352
- // }
353
-
354
- // function drawCastShadow(ctx, img, w, h, blur, opacity, W, H, baseY, lightX, lightY, dir) {
355
- // const shadowCanvas = createCanvas(w, h);
356
- // const sctx = shadowCanvas.getContext('2d');
357
- // sctx.drawImage(img, 0, 0, w, h);
358
- // sctx.globalCompositeOperation = "source-in";
359
- // sctx.fillStyle = "black";
360
- // sctx.fillRect(0, 0, w, h);
361
-
362
- // // Light/skew calculations
363
- // const relX = lightX ? (lightX / W - 0.5) * 2 : (dir === 'left' ? -1 : dir === 'right' ? 1 : 0);
364
- // const relY = lightY ? (lightY / H) : (dir === 'front' ? 0.2 : dir === 'back' ? 0.8 : 0.5);
365
-
366
- // const skew = relX * 0.8;
367
- // const stretch = 1 + (1 - relY) * 2;
368
-
369
- // ctx.save();
370
- // ctx.translate(W / 2, baseY);
371
-
372
- // if (dir === 'front' || relY < 0.5) {
373
- // // Front-cast shadow anchored to bottom edge
374
- // ctx.transform(1, 0, skew, -0.5, 0, 0); // negative Y stretch
375
- // ctx.globalAlpha = opacity;
376
- // ctx.filter = `blur(${blur}px)`;
377
- // ctx.drawImage(shadowCanvas, -w / 2, -h, w, h); // <- use -h to anchor correctly
378
- // } else {
379
- // // Shadow behind
380
- // ctx.transform(1, 0, skew, 0.3, 0, 0);
381
- // ctx.globalAlpha = opacity;
382
- // ctx.filter = `blur(${blur}px)`;
383
- // ctx.drawImage(shadowCanvas, -w / 2, -h, w, h);
384
- // }
385
-
386
- // ctx.restore();
387
- // }
388
-
389
- // // ----------------- Trimming ------------------
390
-
391
- // function trimImage(image) {
392
- // const temp = createCanvas(image.width, image.height);
393
- // const tctx = temp.getContext('2d');
394
- // tctx.drawImage(image, 0, 0);
395
- // const imgData = tctx.getImageData(0, 0, temp.width, temp.height);
396
-
397
- // let top = temp.height, left = temp.width, right = 0, bottom = 0;
398
- // for (let y = 0; y < temp.height; y++) {
399
- // for (let x = 0; x < temp.width; x++) {
400
- // const alpha = imgData.data[(y * temp.width + x) * 4 + 3];
401
- // if (alpha > 0) {
402
- // if (x < left) left = x;
403
- // if (x > right) right = x;
404
- // if (y < top) top = y;
405
- // if (y > bottom) bottom = y;
406
- // }
407
- // }
408
- // }
409
-
410
- // const w = right - left + 1;
411
- // const h = bottom - top + 1;
412
- // const trimmedCanvas = createCanvas(w, h);
413
- // const t2 = trimmedCanvas.getContext('2d');
414
- // t2.drawImage(image, left, top, w, h, 0, 0, w, h);
415
-
416
- // return { canvas: trimmedCanvas, width: w, height: h };
417
- // }
418
-
419
- // module.exports = { generateShadow };
420
-
421
-
422
-
423
- // const { createCanvas, loadImage } = require('canvas');
424
-
425
- // async function generateShadow(imageBuffer, options = {}) {
426
- // const {
427
- // type = 'cast', // cast | drop | flat | box
428
- // dir = 'back', // back | front | left | right
429
- // blur = 8,
430
- // opacity = 0.35,
431
- // lightX, // optional custom light position
432
- // lightY
433
- // } = options;
434
-
435
- // const img = await loadImage(imageBuffer);
436
- // const trimmed = trimImage(img);
437
-
438
- // const W = 1000, H = 800; // base canvas size
439
- // const canvas = createCanvas(W, H);
440
- // const ctx = canvas.getContext('2d');
441
-
442
- // ctx.clearRect(0, 0, W, H); // transparent background
443
-
444
- // // Scale object to fit canvas
445
- // const maxW = W * 0.5;
446
- // const maxH = H * 0.5;
447
- // const scale = Math.min(maxW / trimmed.width, maxH / trimmed.height, 1);
448
- // const scaledW = trimmed.width * scale;
449
- // const scaledH = trimmed.height * scale;
450
- // const baseY = H * 0.85;
451
-
452
- // // Draw shadow
453
- // switch (type) {
454
- // case 'drop':
455
- // drawDropShadow(ctx, trimmed.canvas, scaledW, scaledH, dir, blur, opacity, W, H, baseY);
456
- // break;
457
- // case 'flat':
458
- // drawFlatShadow(ctx, trimmed.canvas, scaledW, scaledH, dir, opacity, W, H, baseY);
459
- // break;
460
- // case 'box':
461
- // drawBoxShadow(ctx, scaledW, scaledH, dir, blur, opacity, W, H, baseY);
462
- // break;
463
- // case 'cast':
464
- // default:
465
- // drawCastShadow(ctx, trimmed.canvas, scaledW, scaledH, blur, opacity, W, H, baseY, lightX, lightY, dir);
466
- // break;
467
- // }
468
-
469
- // // Draw object on top
470
- // ctx.globalAlpha = 1;
471
- // ctx.filter = "none";
472
- // ctx.drawImage(trimmed.canvas, (W - scaledW) / 2, baseY - scaledH, scaledW, scaledH);
473
-
474
- // return canvas.toBuffer("image/png");
475
- // }
476
-
477
- // // ----------------- Shadow Types ------------------
478
-
479
- // function drawDropShadow(ctx, img, w, h, dir, blur, opacity, W, H, baseY) {
480
- // let dx = 0, dy = 0, off = 40;
481
- // if (dir === 'back') dy = off;
482
- // if (dir === 'front') dy = -off;
483
- // if (dir === 'left') dx = -off;
484
- // if (dir === 'right') dx = off;
485
-
486
- // ctx.save();
487
- // ctx.filter = `blur(${blur}px)`;
488
- // ctx.globalAlpha = opacity;
489
- // ctx.drawImage(img, (W - w) / 2 + dx, baseY - h + dy, w, h);
490
- // ctx.restore();
491
- // }
492
-
493
- // function drawFlatShadow(ctx, img, w, h, dir, opacity, W, H, baseY) {
494
- // let dx = 0, dy = 0, off = 30;
495
- // if (dir === 'back') dy = off;
496
- // if (dir === 'front') dy = -off;
497
- // if (dir === 'left') dx = -off;
498
- // if (dir === 'right') dx = off;
499
-
500
- // ctx.save();
501
- // ctx.globalAlpha = opacity;
502
- // ctx.drawImage(img, (W - w) / 2 + dx, baseY - h + dy, w, h);
503
- // ctx.restore();
504
- // }
505
-
506
- // function drawBoxShadow(ctx, w, h, dir, blur, opacity, W, H, baseY) {
507
- // let dx = 0, dy = 0, off = 30;
508
- // if (dir === 'back') dy = off;
509
- // if (dir === 'front') dy = -off;
510
- // if (dir === 'left') dx = -off;
511
- // if (dir === 'right') dx = off;
512
-
513
- // ctx.save();
514
- // ctx.fillStyle = `rgba(0,0,0,${opacity})`;
515
- // ctx.filter = `blur(${blur}px)`;
516
- // ctx.fillRect((W - w) / 2 + dx, baseY - h + dy, w, h);
517
- // ctx.restore();
518
- // }
519
-
520
- // function drawCastShadow(ctx, img, w, h, blur, opacity, W, H, baseY, lightX, lightY, dir) {
521
- // const shadowCanvas = createCanvas(w, h);
522
- // const sctx = shadowCanvas.getContext('2d');
523
-
524
- // // Draw object into shadow canvas
525
- // sctx.drawImage(img, 0, 0, w, h);
526
- // sctx.globalCompositeOperation = "source-in";
527
- // sctx.fillStyle = "black";
528
- // sctx.fillRect(0, 0, w, h);
529
-
530
- // const relX = lightX ? (lightX / W - 0.5) * 2 : (dir === 'left' ? -1 : dir === 'right' ? 1 : 0);
531
- // const relY = lightY ? (lightY / H) : (dir === 'front' ? 0.2 : dir === 'back' ? 0.8 : 0.5);
532
-
533
- // const skew = relX * 0.8;
534
-
535
- // ctx.save();
536
- // ctx.translate(W / 2, baseY);
537
-
538
- // if (dir === 'front' || relY < 0.5) {
539
- // ctx.transform(1, 0, skew, -0.5, 0, 0);
540
- // ctx.globalAlpha = opacity;
541
- // ctx.filter = `blur(${blur}px)`;
542
- // ctx.drawImage(shadowCanvas, -w / 2, -h, w, h); // anchored to bottom edge
543
- // } else {
544
- // ctx.transform(1, 0, skew, 0.3, 0, 0);
545
- // ctx.globalAlpha = opacity;
546
- // ctx.filter = `blur(${blur}px)`;
547
- // ctx.drawImage(shadowCanvas, -w / 2, -h, w, h);
548
- // }
549
-
550
- // ctx.restore();
551
- // }
552
-
553
- // // ----------------- Trimming ------------------
554
-
555
- // function trimImage(image) {
556
- // const temp = createCanvas(image.width, image.height);
557
- // const tctx = temp.getContext('2d');
558
- // tctx.drawImage(image, 0, 0);
559
- // const imgData = tctx.getImageData(0, 0, temp.width, temp.height);
560
-
561
- // let top = temp.height, left = temp.width, right = 0, bottom = 0;
562
- // for (let y = 0; y < temp.height; y++) {
563
- // for (let x = 0; x < temp.width; x++) {
564
- // const alpha = imgData.data[(y * temp.width + x) * 4 + 3];
565
- // if (alpha > 0) {
566
- // if (x < left) left = x;
567
- // if (x > right) right = x;
568
- // if (y < top) top = y;
569
- // if (y > bottom) bottom = y;
570
- // }
571
- // }
572
- // }
573
-
574
- // const w = right - left + 1;
575
- // const h = bottom - top + 1;
576
- // const trimmedCanvas = createCanvas(w, h);
577
- // const t2 = trimmedCanvas.getContext('2d');
578
- // t2.drawImage(image, left, top, w, h, 0, 0, w, h);
579
-
580
- // return { canvas: trimmedCanvas, width: w, height: h };
581
- // }
582
-
583
- // module.exports = { generateShadow };
584
-
585
-
586
-
587
-
588
- // const { createCanvas, loadImage } = require('canvas');
589
- // const fs = require('fs'); // <-- add this
590
-
591
- // async function generateShadow(imageBuffer, options = {}) {
592
- // const {
593
- // type = 'cast', // cast | drop | flat | box
594
- // dir = 'back', // back | front | left | right
595
- // blur = 8,
596
- // opacity = 0.35,
597
- // lightX,
598
- // lightY
599
- // } = options;
600
-
601
- // const img = await loadImage(imageBuffer);
602
- // const trimmed = trimImage(img);
603
-
604
- // // ---------------- Scale object ----------------
605
- // const W = 1000, H = 800;
606
- // const maxW = W * 0.5;
607
- // const maxH = H * 0.5;
608
- // const scale = Math.min(maxW / trimmed.width, maxH / trimmed.height, 1);
609
- // const scaledW = trimmed.width * scale;
610
- // const scaledH = trimmed.height * scale;
611
-
612
- // // ---------------- Shadow offset padding ----------------
613
- // const shadowOffset = type === 'drop' ? 50 : type === 'flat' ? 40 : type === 'box' ? 40 : 200;
614
- // let extraTop = 0, extraBottom = 0, extraLeft = 0, extraRight = 0;
615
-
616
- // if (dir === 'back') extraBottom = shadowOffset;
617
- // if (dir === 'front') extraTop = shadowOffset;
618
- // if (dir === 'left') extraLeft = shadowOffset;
619
- // if (dir === 'right') extraRight = shadowOffset;
620
-
621
- // const canvasW = W + extraLeft + extraRight;
622
- // const canvasH = H + extraTop + extraBottom;
623
- // const canvas = createCanvas(canvasW, canvasH);
624
- // const ctx = canvas.getContext('2d');
625
- // ctx.clearRect(0, 0, canvasW, canvasH);
626
-
627
- // const baseX = extraLeft + (canvasW - extraLeft - extraRight) / 2;
628
- // const baseY = extraTop + H * 0.85;
629
-
630
- // // ---------------- Draw shadow ----------------
631
- // switch (type) {
632
- // case 'drop':
633
- // drawDropShadow(ctx, trimmed.canvas, scaledW, scaledH, dir, blur, opacity, canvasW, canvasH, baseX, baseY);
634
- // break;
635
- // case 'flat':
636
- // drawFlatShadow(ctx, trimmed.canvas, scaledW, scaledH, dir, opacity, canvasW, canvasH, baseX, baseY);
637
- // break;
638
- // case 'box':
639
- // drawBoxShadow(ctx, scaledW, scaledH, dir, blur, opacity, canvasW, canvasH, baseX, baseY);
640
- // break;
641
- // case 'cast':
642
- // default:
643
- // drawCastShadow(ctx, trimmed.canvas, scaledW, scaledH, blur, opacity, canvasW, canvasH, baseX, baseY, lightX, lightY, dir);
644
- // break;
645
- // }
646
-
647
- // // ---------------- Draw object ----------------
648
- // ctx.globalAlpha = 1;
649
- // ctx.filter = "none";
650
- // ctx.drawImage(trimmed.canvas, baseX - scaledW / 2, baseY - scaledH, scaledW, scaledH);
651
-
652
- // // ---------------- Debug: save image ----------------
653
- // fs.writeFileSync('debug_shadow.png', canvas.toBuffer("image/png"));
654
-
655
- // return canvas.toBuffer("image/png");
656
- // }
657
-
658
- // // ---------------- Shadow functions ----------------
659
-
660
- // function drawDropShadow(ctx, img, w, h, dir, blur, opacity, W, H, baseX, baseY) {
661
- // let dx = 0, dy = 0, off = 40;
662
- // if (dir === 'back') dy = off;
663
- // if (dir === 'front') dy = -off;
664
- // if (dir === 'left') dx = -off;
665
- // if (dir === 'right') dx = off;
666
-
667
- // ctx.save();
668
- // ctx.filter = `blur(${blur}px)`;
669
- // ctx.globalAlpha = opacity;
670
- // ctx.drawImage(img, baseX - w / 2 + dx, baseY - h + dy, w, h);
671
- // ctx.restore();
672
- // }
673
-
674
- // function drawFlatShadow(ctx, img, w, h, dir, opacity, W, H, baseX, baseY) {
675
- // let dx = 0, dy = 0, off = 30;
676
- // if (dir === 'back') dy = off;
677
- // if (dir === 'front') dy = -off;
678
- // if (dir === 'left') dx = -off;
679
- // if (dir === 'right') dx = off;
680
-
681
- // ctx.save();
682
- // ctx.globalAlpha = opacity;
683
- // ctx.drawImage(img, baseX - w / 2 + dx, baseY - h + dy, w, h);
684
- // ctx.restore();
685
- // }
686
-
687
- // function drawBoxShadow(ctx, w, h, dir, blur, opacity, W, H, baseX, baseY) {
688
- // let dx = 0, dy = 0, off = 30;
689
- // if (dir === 'back') dy = off;
690
- // if (dir === 'front') dy = -off;
691
- // if (dir === 'left') dx = -off;
692
- // if (dir === 'right') dx = off;
693
-
694
- // ctx.save();
695
- // ctx.fillStyle = `rgba(0,0,0,${opacity})`;
696
- // ctx.filter = `blur(${blur}px)`;
697
- // ctx.fillRect(baseX - w / 2 + dx, baseY - h + dy, w, h);
698
- // ctx.restore();
699
- // }
700
-
701
- // function drawCastShadow(ctx, img, w, h, blur, opacity, W, H, baseX, baseY, lightX, lightY, dir) {
702
- // const shadowCanvas = createCanvas(w, h);
703
- // const sctx = shadowCanvas.getContext('2d');
704
-
705
- // sctx.drawImage(img, 0, 0, w, h);
706
- // sctx.globalCompositeOperation = "source-in";
707
- // sctx.fillStyle = "black";
708
- // sctx.fillRect(0, 0, w, h);
709
-
710
- // const relX = lightX ? (lightX / W - 0.5) * 2 : (dir === 'left' ? -1 : dir === 'right' ? 1 : 0);
711
- // const relY = lightY ? (lightY / H) : (dir === 'front' ? 0.2 : dir === 'back' ? 0.8 : 0.5);
712
-
713
- // const skew = relX * 0.8;
714
-
715
- // ctx.save();
716
- // ctx.translate(baseX, baseY);
717
-
718
- // if (dir === 'front' || relY < 0.5) {
719
- // ctx.transform(1, 0, skew, -0.5, 0, 0);
720
- // ctx.globalAlpha = opacity;
721
- // ctx.filter = `blur(${blur}px)`;
722
- // ctx.drawImage(shadowCanvas, -w / 2, -h, w, h);
723
- // } else {
724
- // ctx.transform(1, 0, skew, 0.3, 0, 0);
725
- // ctx.globalAlpha = opacity;
726
- // ctx.filter = `blur(${blur}px)`;
727
- // ctx.drawImage(shadowCanvas, -w / 2, -h, w, h);
728
- // }
729
-
730
- // ctx.restore();
731
- // }
732
-
733
- // // ---------------- Trimming ----------------
734
- // function trimImage(image) {
735
- // const temp = createCanvas(image.width, image.height);
736
- // const tctx = temp.getContext('2d');
737
- // tctx.drawImage(image, 0, 0);
738
- // const imgData = tctx.getImageData(0, 0, temp.width, temp.height);
739
-
740
- // let top = temp.height, left = temp.width, right = 0, bottom = 0;
741
- // for (let y = 0; y < temp.height; y++) {
742
- // for (let x = 0; x < temp.width; x++) {
743
- // const alpha = imgData.data[(y * temp.width + x) * 4 + 3];
744
- // if (alpha > 0) {
745
- // if (x < left) left = x;
746
- // if (x > right) right = x;
747
- // if (y < top) top = y;
748
- // if (y > bottom) bottom = y;
749
- // }
750
- // }
751
- // }
752
-
753
- // const w = right - left + 1;
754
- // const h = bottom - top + 1;
755
- // const trimmedCanvas = createCanvas(w, h);
756
- // const t2 = trimmedCanvas.getContext('2d');
757
- // t2.drawImage(image, left, top, w, h, 0, 0, w, h);
758
-
759
- // return { canvas: trimmedCanvas, width: w, height: h };
760
- // }
761
-
762
- // module.exports = { generateShadow };
763
-
764
-
765
-
766
-
767
- // const { createCanvas, loadImage } = require('canvas');
768
- // const fs = require('fs');
769
-
770
- // async function generateShadow(imageBuffer, options = {}) {
771
- // const {
772
- // type = 'cast', // cast | drop | flat | box
773
- // dir = 'back', // back | front | left | right
774
- // blur = 8,
775
- // opacity = 0.35,
776
- // lightX,
777
- // lightY
778
- // } = options;
779
-
780
- // const img = await loadImage(imageBuffer);
781
- // const trimmed = trimImage(img);
782
-
783
- // // ---------------- Scale object ----------------
784
- // const W = 1000, H = 800;
785
- // const maxW = W * 0.5;
786
- // const maxH = H * 0.5;
787
- // const scale = Math.min(maxW / trimmed.width, maxH / trimmed.height, 1);
788
- // const scaledW = trimmed.width * scale;
789
- // const scaledH = trimmed.height * scale;
790
-
791
- // // ---------------- Shadow offset padding ----------------
792
- // const shadowOffset = type === 'drop' ? 50 : type === 'flat' ? 40 : type === 'box' ? 40 : 200;
793
- // let extraTop = 0, extraBottom = 0, extraLeft = 0, extraRight = 0;
794
-
795
- // if (dir === 'back') extraBottom = shadowOffset;
796
- // if (dir === 'front') extraTop = shadowOffset;
797
- // if (dir === 'left') extraLeft = shadowOffset;
798
- // if (dir === 'right') extraRight = shadowOffset;
799
-
800
- // // Extra padding for cast shadows to prevent clipping
801
- // if (type === 'cast') {
802
- // extraTop += scaledH * 0.5;
803
- // extraBottom += scaledH * 0.5;
804
- // extraLeft += scaledW * 0.3;
805
- // extraRight += scaledW * 0.3;
806
- // }
807
-
808
- // const canvasW = W + extraLeft + extraRight;
809
- // const canvasH = H + extraTop + extraBottom;
810
- // const canvas = createCanvas(canvasW, canvasH);
811
- // const ctx = canvas.getContext('2d');
812
- // ctx.clearRect(0, 0, canvasW, canvasH);
813
-
814
- // // ---------------- Base position ----------------
815
- // const baseX = extraLeft + W / 2;
816
- // const baseY = extraTop + H; // bottom of object
817
-
818
- // // ---------------- Draw shadow ----------------
819
- // switch (type) {
820
- // case 'drop':
821
- // drawDropShadow(ctx, trimmed.canvas, scaledW, scaledH, dir, blur, opacity, canvasW, canvasH, baseX, baseY);
822
- // break;
823
- // case 'flat':
824
- // drawFlatShadow(ctx, trimmed.canvas, scaledW, scaledH, dir, opacity, canvasW, canvasH, baseX, baseY);
825
- // break;
826
- // case 'box':
827
- // drawBoxShadow(ctx, scaledW, scaledH, dir, blur, opacity, canvasW, canvasH, baseX, baseY);
828
- // break;
829
- // case 'cast':
830
- // default:
831
- // drawCastShadow(ctx, trimmed.canvas, scaledW, scaledH, blur, opacity, canvasW, canvasH, baseX, baseY, lightX, lightY, dir);
832
- // break;
833
- // }
834
-
835
- // // ---------------- Draw object ----------------
836
- // ctx.globalAlpha = 1;
837
- // ctx.filter = "none";
838
- // ctx.drawImage(trimmed.canvas, baseX - scaledW / 2, baseY - scaledH, scaledW, scaledH);
839
-
840
- // // ---------------- Debug: save image ----------------
841
- // fs.writeFileSync('debug_shadow.png', canvas.toBuffer("image/png"));
842
-
843
- // return canvas.toBuffer("image/png");
844
- // }
845
-
846
- // // ---------------- Shadow functions ----------------
847
-
848
- // function drawDropShadow(ctx, img, w, h, dir, blur, opacity, W, H, baseX, baseY) {
849
- // let dx = 0, dy = 0, off = 40;
850
- // if (dir === 'back') dy = off;
851
- // if (dir === 'front') dy = -off;
852
- // if (dir === 'left') dx = -off;
853
- // if (dir === 'right') dx = off;
854
-
855
- // ctx.save();
856
- // ctx.filter = `blur(${blur}px)`;
857
- // ctx.globalAlpha = opacity;
858
- // ctx.drawImage(img, baseX - w / 2 + dx, baseY - h + dy, w, h);
859
- // ctx.restore();
860
- // }
861
-
862
- // function drawFlatShadow(ctx, img, w, h, dir, opacity, W, H, baseX, baseY) {
863
- // let dx = 0, dy = 0, off = 30;
864
- // if (dir === 'back') dy = off;
865
- // if (dir === 'front') dy = -off;
866
- // if (dir === 'left') dx = -off;
867
- // if (dir === 'right') dx = off;
868
-
869
- // ctx.save();
870
- // ctx.globalAlpha = opacity;
871
- // ctx.drawImage(img, baseX - w / 2 + dx, baseY - h + dy, w, h);
872
- // ctx.restore();
873
- // }
874
-
875
- // function drawBoxShadow(ctx, w, h, dir, blur, opacity, W, H, baseX, baseY) {
876
- // let dx = 0, dy = 0, off = 30;
877
- // if (dir === 'back') dy = off;
878
- // if (dir === 'front') dy = -off;
879
- // if (dir === 'left') dx = -off;
880
- // if (dir === 'right') dx = off;
881
-
882
- // ctx.save();
883
- // ctx.fillStyle = `rgba(0,0,0,${opacity})`;
884
- // ctx.filter = `blur(${blur}px)`;
885
- // ctx.fillRect(baseX - w / 2 + dx, baseY - h + dy, w, h);
886
- // ctx.restore();
887
- // }
888
-
889
- // function drawCastShadow(ctx, img, w, h, blur, opacity, W, H, baseX, baseY, lightX, lightY, dir) {
890
- // const shadowCanvas = createCanvas(w, h);
891
- // const sctx = shadowCanvas.getContext('2d');
892
-
893
- // sctx.drawImage(img, 0, 0, w, h);
894
- // sctx.globalCompositeOperation = "source-in";
895
- // sctx.fillStyle = "black";
896
- // sctx.fillRect(0, 0, w, h);
897
-
898
- // const relX = lightX ? (lightX / W - 0.5) * 2 : (dir === 'left' ? -1 : dir === 'right' ? 1 : 0);
899
- // const relY = lightY ? (lightY / H) : (dir === 'front' ? 0.2 : dir === 'back' ? 0.8 : 0.5);
900
-
901
- // const skew = relX * 0.8;
902
-
903
- // ctx.save();
904
- // ctx.translate(baseX, baseY);
905
-
906
- // if (dir === 'front' || relY < 0.5) {
907
- // ctx.transform(1, 0, skew, -0.5, 0, 0);
908
- // } else {
909
- // ctx.transform(1, 0, skew, 0.3, 0, 0);
910
- // }
911
- // ctx.globalAlpha = opacity;
912
- // ctx.filter = `blur(${blur}px)`;
913
- // ctx.drawImage(shadowCanvas, -w / 2, -h, w, h);
914
-
915
- // ctx.restore();
916
- // }
917
-
918
- // // ---------------- Trimming ----------------
919
- // function trimImage(image) {
920
- // const temp = createCanvas(image.width, image.height);
921
- // const tctx = temp.getContext('2d');
922
- // tctx.drawImage(image, 0, 0);
923
- // const imgData = tctx.getImageData(0, 0, temp.width, temp.height);
924
-
925
- // let top = temp.height, left = temp.width, right = 0, bottom = 0;
926
- // for (let y = 0; y < temp.height; y++) {
927
- // for (let x = 0; x < temp.width; x++) {
928
- // const alpha = imgData.data[(y * temp.width + x) * 4 + 3];
929
- // if (alpha > 0) {
930
- // if (x < left) left = x;
931
- // if (x > right) right = x;
932
- // if (y < top) top = y;
933
- // if (y > bottom) bottom = y;
934
- // }
935
- // }
936
- // }
937
-
938
- // const w = right - left + 1;
939
- // const h = bottom - top + 1;
940
- // const trimmedCanvas = createCanvas(w, h);
941
- // const t2 = trimmedCanvas.getContext('2d');
942
- // t2.drawImage(image, left, top, w, h, 0, 0, w, h);
943
-
944
- // return { canvas: trimmedCanvas, width: w, height: h };
945
- // }
946
-
947
- // module.exports = { generateShadow };
948
-
949
-
950
- // const { createCanvas, loadImage } = require('canvas');
951
- // const fs = require('fs');
952
- // const StackBlur = require('stackblur-canvas'); // Node-compatible
953
-
954
- // async function generateShadow(imageBuffer, options = {}) {
955
- // const {
956
- // type = 'cast', // cast | drop | flat | box
957
- // dir = 'back', // back | front | left | right
958
- // blur = 20, // px
959
- // opacity = 0.35,
960
- // lightX,
961
- // lightY
962
- // } = options;
963
-
964
- // const img = await loadImage(imageBuffer);
965
- // const trimmed = trimImage(img); // <-- precise trimming
966
-
967
- // // ---------------- Scale object ----------------
968
- // const W = 1000, H = 800;
969
- // const maxW = W * 0.5;
970
- // const maxH = H * 0.5;
971
- // const scale = Math.min(maxW / trimmed.width, maxH / trimmed.height, 1);
972
- // const scaledW = trimmed.width * scale;
973
- // const scaledH = trimmed.height * scale;
974
-
975
- // // ---------------- Shadow padding ----------------
976
- // let shadowOffset = type === 'drop' ? 50 : type === 'flat' ? 40 : type === 'box' ? 40 : 200;
977
- // let extraTop = 0, extraBottom = 0, extraLeft = 0, extraRight = 0;
978
-
979
- // if (dir === 'back') extraBottom = shadowOffset;
980
- // if (dir === 'front') extraTop = shadowOffset;
981
- // if (dir === 'left') extraLeft = shadowOffset;
982
- // if (dir === 'right') extraRight = shadowOffset;
983
-
984
- // if (type === 'cast') {
985
- // extraTop += scaledH * 0.5;
986
- // extraBottom += scaledH * 0.5;
987
- // extraLeft += scaledW * 0.3;
988
- // extraRight += scaledW * 0.3;
989
- // }
990
-
991
- // const canvasW = W + extraLeft + extraRight;
992
- // const canvasH = H + extraTop + extraBottom;
993
- // const canvas = createCanvas(canvasW, canvasH);
994
- // const ctx = canvas.getContext('2d');
995
- // ctx.clearRect(0, 0, canvasW, canvasH);
996
-
997
- // // ---------------- Base position ----------------
998
- // const baseX = extraLeft + W / 2;
999
- // const baseY = extraTop + H; // bottom of object
1000
-
1001
- // // ---------------- Draw shadow ----------------
1002
- // switch (type) {
1003
- // case 'drop':
1004
- // drawDropShadow(ctx, trimmed.canvas, scaledW, scaledH, dir, blur, opacity, baseX, baseY);
1005
- // break;
1006
- // case 'flat':
1007
- // drawFlatShadow(ctx, trimmed.canvas, scaledW, scaledH, dir, opacity, baseX, baseY);
1008
- // break;
1009
- // case 'box':
1010
- // drawBoxShadow(ctx, scaledW, scaledH, dir, blur, opacity, baseX, baseY);
1011
- // break;
1012
- // case 'cast':
1013
- // default:
1014
- // drawCastShadow(ctx, trimmed.canvas, scaledW, scaledH, blur, opacity, baseX, baseY, lightX, lightY, dir);
1015
- // break;
1016
- // }
1017
-
1018
- // // ---------------- Draw object ----------------
1019
- // ctx.globalAlpha = 1;
1020
- // ctx.filter = "none";
1021
- // ctx.drawImage(trimmed.canvas, baseX - scaledW / 2, baseY - scaledH, scaledW, scaledH);
1022
-
1023
- // // ---------------- Debug save ----------------
1024
- // fs.writeFileSync('debug_shadow.png', canvas.toBuffer("image/png"));
1025
-
1026
- // return canvas.toBuffer("image/png");
1027
- // }
1028
-
1029
- // // ---------------- Shadow helpers ----------------
1030
-
1031
- // // Compute padding (based on blur) and overlap so blurred shadow touches the image.
1032
- // function computePadOverlap(blur) {
1033
- // const pad = Math.max(4, Math.ceil(blur * 2)); // ensures enough padding for blur spread
1034
- // const overlap = Math.max(1, Math.round(blur * 0.6)); // how many px the shadow will overlap the image edge
1035
- // return { pad, overlap };
1036
- // }
1037
-
1038
- // function drawDropShadow(ctx, img, w, h, dir, blur, opacity, baseX, baseY) {
1039
- // let dx = 0, dy = 0, off = 40;
1040
- // if (dir === 'back') dy = off;
1041
- // if (dir === 'front') dy = -off;
1042
- // if (dir === 'left') dx = -off;
1043
- // if (dir === 'right') dx = off;
1044
-
1045
- // const { pad, overlap } = computePadOverlap(blur);
1046
-
1047
- // // Create shadow canvas with padding so blur doesn't get clipped
1048
- // const shadowCanvas = createCanvas(Math.round(w + pad), Math.round(h + pad));
1049
- // const sctx = shadowCanvas.getContext('2d');
1050
-
1051
- // const drawX = pad / 2;
1052
- // const drawY = pad / 2;
1053
- // sctx.clearRect(0, 0, shadowCanvas.width, shadowCanvas.height);
1054
- // sctx.drawImage(img, drawX, drawY, w, h);
1055
- // sctx.globalCompositeOperation = "source-in";
1056
- // sctx.fillStyle = "black";
1057
- // sctx.fillRect(0, 0, shadowCanvas.width, shadowCanvas.height);
1058
-
1059
- // StackBlur.canvasRGBA(shadowCanvas, 0, 0, shadowCanvas.width, shadowCanvas.height, blur);
1060
-
1061
- // ctx.save();
1062
- // ctx.globalAlpha = opacity;
1063
- // // When drawing, position so the padded canvas overlaps the object's bottom by `overlap`
1064
- // const drawPosX = baseX - w / 2 + dx - pad / 2;
1065
- // const drawPosY = baseY - h + dy - pad / 2 + overlap;
1066
- // ctx.drawImage(shadowCanvas, drawPosX, drawPosY, shadowCanvas.width, shadowCanvas.height);
1067
- // ctx.restore();
1068
- // }
1069
-
1070
- // function drawFlatShadow(ctx, img, w, h, dir, opacity, baseX, baseY) {
1071
- // let dx = 0, dy = 0, off = 30;
1072
- // if (dir === 'back') dy = off;
1073
- // if (dir === 'front') dy = -off;
1074
- // if (dir === 'left') dx = -off;
1075
- // if (dir === 'right') dx = off;
1076
-
1077
- // ctx.save();
1078
- // ctx.globalAlpha = opacity;
1079
- // // flat shadow should sit directly under, so small upward overlap to avoid gap
1080
- // ctx.drawImage(img, baseX - w/2 + dx, baseY - h + dy + 1, w, h);
1081
- // ctx.restore();
1082
- // }
1083
-
1084
- // function drawBoxShadow(ctx, w, h, dir, blur, opacity, baseX, baseY) {
1085
- // let dx = 0, dy = 0, off = 30;
1086
- // if (dir === 'back') dy = off;
1087
- // if (dir === 'front') dy = -off;
1088
- // if (dir === 'left') dx = -off;
1089
- // if (dir === 'right') dx = off;
1090
-
1091
- // const { pad, overlap } = computePadOverlap(blur);
1092
-
1093
- // const shadowCanvas = createCanvas(Math.round(w + pad), Math.round(h + pad));
1094
- // const sctx = shadowCanvas.getContext('2d');
1095
- // sctx.clearRect(0, 0, shadowCanvas.width, shadowCanvas.height);
1096
-
1097
- // // draw a filled rectangle centered with padding
1098
- // sctx.fillStyle = `black`;
1099
- // sctx.fillRect(pad/2, pad/2, w, h);
1100
-
1101
- // StackBlur.canvasRGBA(shadowCanvas, 0, 0, shadowCanvas.width, shadowCanvas.height, blur);
1102
-
1103
- // ctx.save();
1104
- // ctx.globalAlpha = opacity;
1105
- // const drawPosX = baseX - w/2 + dx - pad / 2;
1106
- // const drawPosY = baseY - h + dy - pad / 2 + overlap;
1107
- // ctx.drawImage(shadowCanvas, drawPosX, drawPosY, shadowCanvas.width, shadowCanvas.height);
1108
- // ctx.restore();
1109
- // }
1110
-
1111
- // function drawCastShadow(ctx, img, w, h, blur, opacity, baseX, baseY, lightX, lightY, dir) {
1112
- // // We'll pad by blur*2 so StackBlur has room, then draw the original at pad/2,pad/2
1113
- // const { pad, overlap } = computePadOverlap(blur);
1114
- // const shadowW = Math.round(w + pad);
1115
- // const shadowH = Math.round(h + pad);
1116
- // const shadowCanvas = createCanvas(shadowW, shadowH);
1117
- // const sctx = shadowCanvas.getContext('2d');
1118
-
1119
- // sctx.clearRect(0, 0, shadowW, shadowH);
1120
- // const drawX = pad / 2;
1121
- // const drawY = pad / 2;
1122
- // sctx.drawImage(img, drawX, drawY, w, h);
1123
- // sctx.globalCompositeOperation = "source-in";
1124
- // sctx.fillStyle = "black";
1125
- // sctx.fillRect(0, 0, shadowW, shadowH);
1126
-
1127
- // // Apply exact StackBlur (unchanged logic)
1128
- // StackBlur.canvasRGBA(shadowCanvas, 0, 0, shadowW, shadowH, blur);
1129
-
1130
- // // Compute relative light/ skew similar to your original logic but keep consistent units
1131
- // // Use the canvas display size W/H approximation from wrapper (we don't have W,H here) so interpret rels relative to w/h:
1132
- // const relX = lightX ? (lightX / (w * 2) - 0.5) * 2 : (dir === 'left' ? -1 : dir === 'right' ? 1 : 0);
1133
- // const relY = lightY ? (lightY / (h * 2)) : (dir === 'front' ? 0.2 : dir === 'back' ? 0.8 : 0.5);
1134
- // const skew = relX * 0.8;
1135
-
1136
- // ctx.save();
1137
- // ctx.translate(baseX, baseY);
1138
-
1139
- // if (dir === 'front' || relY < 0.5) ctx.transform(1, 0, skew, -0.5, 0, 0);
1140
- // else ctx.transform(1, 0, skew, 0.3, 0, 0);
1141
-
1142
- // ctx.globalAlpha = opacity;
1143
-
1144
- // // Draw shadowCanvas so its inner image area overlaps the object's bottom by `overlap`
1145
- // // The object's top-left in local coords is (-w/2, -h)
1146
- // // Our padded image top-left is (-w/2 - pad/2, -h - pad/2)
1147
- // const drawPosX = -w / 2 - pad / 2;
1148
- // const drawPosY = -h - pad / 2 + overlap; // +overlap moves the shadow slightly up so blur touches object
1149
- // ctx.drawImage(shadowCanvas, drawPosX, drawPosY, shadowW, shadowH);
1150
-
1151
- // ctx.restore();
1152
- // }
1153
-
1154
- // // ---------------- Trimming ----------------
1155
- // function trimImage(image) {
1156
- // const temp = createCanvas(image.width, image.height);
1157
- // const tctx = temp.getContext('2d');
1158
- // tctx.drawImage(image, 0, 0);
1159
- // const imgData = tctx.getImageData(0, 0, temp.width, temp.height);
1160
-
1161
- // let top = temp.height, left = temp.width, right = 0, bottom = 0;
1162
- // for (let y = 0; y < temp.height; y++) {
1163
- // for (let x = 0; x < temp.width; x++) {
1164
- // const alpha = imgData.data[(y * temp.width + x) * 4 + 3];
1165
- // if (alpha > 0) {
1166
- // if (x < left) left = x;
1167
- // if (x > right) right = x;
1168
- // if (y < top) top = y;
1169
- // if (y > bottom) bottom = y;
1170
- // }
1171
- // }
1172
- // }
1173
-
1174
- // const w = right - left + 1;
1175
- // const h = bottom - top + 1;
1176
- // const trimmedCanvas = createCanvas(w, h);
1177
- // const t2 = trimmedCanvas.getContext('2d');
1178
- // t2.drawImage(image, left, top, w, h, 0, 0, w, h);
1179
-
1180
- // return { canvas: trimmedCanvas, width: w, height: h };
1181
- // }
1182
-
1183
- // module.exports = { generateShadow };
1184
-
1185
-
1186
- // const { createCanvas, loadImage } = require('canvas');
1187
- // const fs = require('fs');
1188
- // const StackBlur = require('stackblur-canvas'); // Node-compatible
1189
-
1190
- // async function generateShadow(imageBuffer, options = {}) {
1191
- // const {
1192
- // type = 'cast', // cast | drop | flat | box
1193
- // dir = 'back', // back | front | left | right
1194
- // blur = 20, // px
1195
- // opacity = 0.35,
1196
- // lightX,
1197
- // lightY
1198
- // } = options;
1199
-
1200
- // const img = await loadImage(imageBuffer);
1201
- // const trimmed = trimImage(img); // precise trimming
1202
-
1203
- // // ---------------- Scale object ----------------
1204
- // const W = 1000, H = 800;
1205
- // const maxW = W * 0.5;
1206
- // const maxH = H * 0.5;
1207
- // const scale = Math.min(maxW / trimmed.width, maxH / trimmed.height, 1);
1208
- // const scaledW = trimmed.width * scale;
1209
- // const scaledH = trimmed.height * scale;
1210
-
1211
- // // ---------------- Shadow padding ----------------
1212
- // let shadowOffset = type === 'drop' ? 50 : type === 'flat' ? 40 : type === 'box' ? 40 : 200;
1213
- // let extraTop = 0, extraBottom = 0, extraLeft = 0, extraRight = 0;
1214
-
1215
- // if (dir === 'back') {
1216
- // extraBottom = shadowOffset;
1217
- // // left/right padding applied only for back direction
1218
- // extraLeft = shadowOffset;
1219
- // extraRight = shadowOffset;
1220
- // }
1221
- // if (dir === 'front') extraTop = shadowOffset;
1222
-
1223
- // if (type === 'cast') {
1224
- // extraTop += scaledH * 0.5;
1225
- // extraBottom += scaledH * 0.5;
1226
- // if (dir === 'back') {
1227
- // extraLeft += scaledW * 0.3;
1228
- // extraRight += scaledW * 0.3;
1229
- // }
1230
- // }
1231
-
1232
- // const canvasW = W + extraLeft + extraRight;
1233
- // const canvasH = H + extraTop + extraBottom;
1234
- // const canvas = createCanvas(canvasW, canvasH);
1235
- // const ctx = canvas.getContext('2d');
1236
- // ctx.clearRect(0, 0, canvasW, canvasH);
1237
-
1238
- // // ---------------- Base position ----------------
1239
- // const baseX = extraLeft + W / 2;
1240
- // const baseY = extraTop + H; // bottom of object
1241
-
1242
- // // ---------------- Draw shadow ----------------
1243
- // switch (type) {
1244
- // case 'drop':
1245
- // drawDropShadow(ctx, trimmed.canvas, scaledW, scaledH, dir, blur, opacity, baseX, baseY);
1246
- // break;
1247
- // case 'flat':
1248
- // drawFlatShadow(ctx, trimmed.canvas, scaledW, scaledH, dir, opacity, baseX, baseY);
1249
- // break;
1250
- // case 'box':
1251
- // drawBoxShadow(ctx, scaledW, scaledH, dir, blur, opacity, baseX, baseY);
1252
- // break;
1253
- // case 'cast':
1254
- // default:
1255
- // drawCastShadow(ctx, trimmed.canvas, scaledW, scaledH, blur, opacity, baseX, baseY, lightX, lightY, dir);
1256
- // break;
1257
- // }
1258
-
1259
- // // ---------------- Draw object ----------------
1260
- // ctx.globalAlpha = 1;
1261
- // ctx.filter = "none";
1262
- // ctx.drawImage(trimmed.canvas, baseX - scaledW / 2, baseY - scaledH, scaledW, scaledH);
1263
-
1264
- // // ---------------- Debug save ----------------
1265
-
1266
- // return canvas.toBuffer("image/png");
1267
- // }
1268
-
1269
- // // ---------------- Shadow helpers ----------------
1270
- // function computePadOverlap(blur) {
1271
- // const pad = Math.max(4, Math.ceil(blur * 2)); // ensures enough padding for blur spread
1272
- // const overlap = Math.max(1, Math.round(blur * 0.6)); // shadow overlap with object
1273
- // return { pad, overlap };
1274
- // }
1275
-
1276
- // function drawDropShadow(ctx, img, w, h, dir, blur, opacity, baseX, baseY) {
1277
- // let dx = 0, dy = 0, off = 40;
1278
- // if (dir === 'back') dy = off;
1279
- // if (dir === 'front') dy = -off;
1280
- // if (dir === 'left') dx = -off;
1281
- // if (dir === 'right') dx = off;
1282
-
1283
- // const { pad, overlap } = computePadOverlap(blur);
1284
-
1285
- // const shadowCanvas = createCanvas(Math.round(w + pad), Math.round(h + pad));
1286
- // const sctx = shadowCanvas.getContext('2d');
1287
-
1288
- // const drawX = pad / 2;
1289
- // const drawY = pad / 2;
1290
- // sctx.clearRect(0, 0, shadowCanvas.width, shadowCanvas.height);
1291
- // sctx.drawImage(img, drawX, drawY, w, h);
1292
- // sctx.globalCompositeOperation = "source-in";
1293
- // sctx.fillStyle = "black";
1294
- // sctx.fillRect(0, 0, shadowCanvas.width, shadowCanvas.height);
1295
-
1296
- // StackBlur.canvasRGBA(shadowCanvas, 0, 0, shadowCanvas.width, shadowCanvas.height, blur);
1297
-
1298
- // ctx.save();
1299
- // ctx.globalAlpha = opacity;
1300
- // const drawPosX = baseX - w / 2 + dx - pad / 2;
1301
- // const drawPosY = baseY - h + dy - pad / 2 + overlap;
1302
- // ctx.drawImage(shadowCanvas, drawPosX, drawPosY, shadowCanvas.width, shadowCanvas.height);
1303
- // ctx.restore();
1304
- // }
1305
-
1306
- // function drawFlatShadow(ctx, img, w, h, dir, opacity, baseX, baseY) {
1307
- // let dx = 0, dy = 0, off = 30;
1308
- // if (dir === 'back') dy = off;
1309
- // if (dir === 'front') dy = -off;
1310
- // if (dir === 'left') dx = -off;
1311
- // if (dir === 'right') dx = off;
1312
-
1313
- // ctx.save();
1314
- // ctx.globalAlpha = opacity;
1315
- // ctx.drawImage(img, baseX - w/2 + dx, baseY - h + dy + 1, w, h);
1316
- // ctx.restore();
1317
- // }
1318
-
1319
- // function drawBoxShadow(ctx, w, h, dir, blur, opacity, baseX, baseY) {
1320
- // let dx = 0, dy = 0, off = 30;
1321
- // if (dir === 'back') dy = off;
1322
- // if (dir === 'front') dy = -off;
1323
- // if (dir === 'left') dx = -off;
1324
- // if (dir === 'right') dx = off;
1325
-
1326
- // const { pad, overlap } = computePadOverlap(blur);
1327
-
1328
- // const shadowCanvas = createCanvas(Math.round(w + pad), Math.round(h + pad));
1329
- // const sctx = shadowCanvas.getContext('2d');
1330
- // sctx.clearRect(0, 0, shadowCanvas.width, shadowCanvas.height);
1331
-
1332
- // sctx.fillStyle = `black`;
1333
- // sctx.fillRect(pad/2, pad/2, w, h);
1334
-
1335
- // StackBlur.canvasRGBA(shadowCanvas, 0, 0, shadowCanvas.width, shadowCanvas.height, blur);
1336
-
1337
- // ctx.save();
1338
- // ctx.globalAlpha = opacity;
1339
- // const drawPosX = baseX - w/2 + dx - pad / 2;
1340
- // const drawPosY = baseY - h + dy - pad / 2 + overlap;
1341
- // ctx.drawImage(shadowCanvas, drawPosX, drawPosY, shadowCanvas.width, shadowCanvas.height);
1342
- // ctx.restore();
1343
- // }
1344
-
1345
- // function drawCastShadow(ctx, img, w, h, blur, opacity, baseX, baseY, lightX, lightY, dir) {
1346
- // const { pad, overlap } = computePadOverlap(blur);
1347
- // const shadowW = Math.round(w + pad);
1348
- // const shadowH = Math.round(h + pad);
1349
- // const shadowCanvas = createCanvas(shadowW, shadowH);
1350
- // const sctx = shadowCanvas.getContext('2d');
1351
-
1352
- // sctx.clearRect(0, 0, shadowW, shadowH);
1353
- // const drawX = pad / 2;
1354
- // const drawY = pad / 2;
1355
- // sctx.drawImage(img, drawX, drawY, w, h);
1356
- // sctx.globalCompositeOperation = "source-in";
1357
- // sctx.fillStyle = "black";
1358
- // sctx.fillRect(0, 0, shadowW, shadowH);
1359
-
1360
- // StackBlur.canvasRGBA(shadowCanvas, 0, 0, shadowW, shadowH, blur);
1361
-
1362
- // const relX = lightX ? (lightX / (w * 2) - 0.5) * 2 : (dir === 'left' ? -1 : dir === 'right' ? 1 : 0);
1363
- // const relY = lightY ? (lightY / (h * 2)) : (dir === 'front' ? 0.2 : dir === 'back' ? 0.8 : 0.5);
1364
- // const skew = relX * 0.8;
1365
-
1366
- // ctx.save();
1367
- // ctx.translate(baseX, baseY);
1368
- // if (dir === 'front' || relY < 0.5) ctx.transform(1, 0, skew, -0.5, 0, 0);
1369
- // else ctx.transform(1, 0, skew, 0.3, 0, 0);
1370
-
1371
- // ctx.globalAlpha = opacity;
1372
- // const drawPosX = -w / 2 - pad / 2;
1373
- // const drawPosY = -h - pad / 2 + overlap;
1374
- // ctx.drawImage(shadowCanvas, drawPosX, drawPosY, shadowW, shadowH);
1375
-
1376
- // ctx.restore();
1377
- // }
1378
-
1379
- // // ---------------- Trimming ----------------
1380
- // function trimImage(image) {
1381
- // const temp = createCanvas(image.width, image.height);
1382
- // const tctx = temp.getContext('2d');
1383
- // tctx.drawImage(image, 0, 0);
1384
- // const imgData = tctx.getImageData(0, 0, temp.width, temp.height);
1385
-
1386
- // let top = temp.height, left = temp.width, right = 0, bottom = 0;
1387
- // for (let y = 0; y < temp.height; y++) {
1388
- // for (let x = 0; x < temp.width; x++) {
1389
- // const alpha = imgData.data[(y * temp.width + x) * 4 + 3];
1390
- // if (alpha > 0) {
1391
- // if (x < left) left = x;
1392
- // if (x > right) right = x;
1393
- // if (y < top) top = y;
1394
- // if (y > bottom) bottom = y;
1395
- // }
1396
- // }
1397
- // }
1398
-
1399
- // const w = right - left + 1;
1400
- // const h = bottom - top + 1;
1401
- // const trimmedCanvas = createCanvas(w, h);
1402
- // const t2 = trimmedCanvas.getContext('2d');
1403
- // t2.drawImage(image, left, top, w, h, 0, 0, w, h);
1404
-
1405
- // return { canvas: trimmedCanvas, width: w, height: h };
1406
- // }
1407
-
1408
- // module.exports = { generateShadow };
1409
-
1410
-
1411
-
1412
- const { createCanvas, loadImage } = require('canvas');
1413
- const StackBlur = require('stackblur-canvas'); // Node-compatible
1414
-
1415
- async function generateShadow(imageBuffer, options = {}) {
1416
- const {
1417
- type = 'cast', // cast | drop | flat | box
1418
- dir = 'back', // back | front | left | right
1419
- blur = 20, // px
1420
- opacity = 0.35,
1421
- lightX,
1422
- lightY
1423
- } = options;
1424
-
1425
- const img = await loadImage(imageBuffer);
1426
- const trimmed = trimImage(img); // precise trimming
1427
-
1428
- // ---------------- Use ORIGINAL object size (no fixed scaling) ----------------
1429
- const scaledW = trimmed.width;
1430
- const scaledH = trimmed.height;
1431
-
1432
- // ---------------- Shadow padding ----------------
1433
- let shadowOffset = type === 'drop' ? 50 : type === 'flat' ? 40 : type === 'box' ? 40 : 200;
1434
- let extraTop = 0, extraBottom = 0, extraLeft = 0, extraRight = 0;
1435
-
1436
- if (dir === 'back') {
1437
- extraBottom = shadowOffset;
1438
- extraLeft = shadowOffset;
1439
- extraRight = shadowOffset;
1440
- }
1441
- if (dir === 'front') extraTop = shadowOffset;
1442
-
1443
- if (type === 'cast') {
1444
- extraTop += scaledH * 0.5;
1445
- extraBottom += scaledH * 0.5;
1446
- if (dir === 'back') {
1447
- extraLeft += scaledW * 0.3;
1448
- extraRight += scaledW * 0.3;
1449
- }
1450
- }
1451
-
1452
- // ---------------- Final canvas size = original size + padding ----------------
1453
- const canvasW = scaledW + extraLeft + extraRight;
1454
- const canvasH = scaledH + extraTop + extraBottom;
1455
- const canvas = createCanvas(canvasW, canvasH);
1456
- const ctx = canvas.getContext('2d');
1457
- ctx.clearRect(0, 0, canvasW, canvasH);
1458
-
1459
- // ---------------- Base position ----------------
1460
- const baseX = extraLeft + scaledW / 2; // center of object
1461
- const baseY = extraTop + scaledH; // bottom of object
1462
-
1463
- // ---------------- Draw shadow ----------------
1464
- switch (type) {
1465
- case 'drop':
1466
- drawDropShadow(ctx, trimmed.canvas, scaledW, scaledH, dir, blur, opacity, baseX, baseY);
1467
- break;
1468
- case 'flat':
1469
- drawFlatShadow(ctx, trimmed.canvas, scaledW, scaledH, dir, opacity, baseX, baseY);
1470
- break;
1471
- case 'box':
1472
- drawBoxShadow(ctx, scaledW, scaledH, dir, blur, opacity, baseX, baseY);
1473
- break;
1474
- case 'cast':
1475
- default:
1476
- drawCastShadow(ctx, trimmed.canvas, scaledW, scaledH, blur, opacity, baseX, baseY, lightX, lightY, dir);
1477
- break;
1478
- }
1479
-
1480
- // ---------------- Draw object ----------------
1481
- ctx.globalAlpha = 1;
1482
- ctx.filter = "none";
1483
- ctx.drawImage(trimmed.canvas, baseX - scaledW / 2, baseY - scaledH, scaledW, scaledH);
1484
-
1485
- return canvas.toBuffer("image/png");
1486
- }
1487
-
1488
- // ---------------- Shadow helpers ----------------
1489
- function computePadOverlap(blur) {
1490
- const pad = Math.max(4, Math.ceil(blur * 2)); // ensures enough padding for blur spread
1491
- const overlap = Math.max(1, Math.round(blur * 0.6)); // shadow overlap with object
1492
- return { pad, overlap };
1493
- }
1494
-
1495
- function drawDropShadow(ctx, img, w, h, dir, blur, opacity, baseX, baseY) {
1496
- let dx = 0, dy = 0, off = 40;
1497
- if (dir === 'back') dy = off;
1498
- if (dir === 'front') dy = -off;
1499
- if (dir === 'left') dx = -off;
1500
- if (dir === 'right') dx = off;
1501
-
1502
- const { pad, overlap } = computePadOverlap(blur);
1503
-
1504
- const shadowCanvas = createCanvas(Math.round(w + pad), Math.round(h + pad));
1505
- const sctx = shadowCanvas.getContext('2d');
1506
-
1507
- const drawX = pad / 2;
1508
- const drawY = pad / 2;
1509
- sctx.clearRect(0, 0, shadowCanvas.width, shadowCanvas.height);
1510
- sctx.drawImage(img, drawX, drawY, w, h);
1511
- sctx.globalCompositeOperation = "source-in";
1512
- sctx.fillStyle = "black";
1513
- sctx.fillRect(0, 0, shadowCanvas.width, shadowCanvas.height);
1514
-
1515
- StackBlur.canvasRGBA(shadowCanvas, 0, 0, shadowCanvas.width, shadowCanvas.height, blur);
1516
-
1517
- ctx.save();
1518
- ctx.globalAlpha = opacity;
1519
- const drawPosX = baseX - w / 2 + dx - pad / 2;
1520
- const drawPosY = baseY - h + dy - pad / 2 + overlap;
1521
- ctx.drawImage(shadowCanvas, drawPosX, drawPosY, shadowCanvas.width, shadowCanvas.height);
1522
- ctx.restore();
1523
- }
1524
-
1525
- function drawFlatShadow(ctx, img, w, h, dir, opacity, baseX, baseY) {
1526
- let dx = 0, dy = 0, off = 30;
1527
- if (dir === 'back') dy = off;
1528
- if (dir === 'front') dy = -off;
1529
- if (dir === 'left') dx = -off;
1530
- if (dir === 'right') dx = off;
1531
-
1532
- ctx.save();
1533
- ctx.globalAlpha = opacity;
1534
- ctx.drawImage(img, baseX - w/2 + dx, baseY - h + dy + 1, w, h);
1535
- ctx.restore();
1536
- }
1537
-
1538
- function drawBoxShadow(ctx, w, h, dir, blur, opacity, baseX, baseY) {
1539
- let dx = 0, dy = 0, off = 30;
1540
- if (dir === 'back') dy = off;
1541
- if (dir === 'front') dy = -off;
1542
- if (dir === 'left') dx = -off;
1543
- if (dir === 'right') dx = off;
1544
-
1545
- const { pad, overlap } = computePadOverlap(blur);
1546
-
1547
- const shadowCanvas = createCanvas(Math.round(w + pad), Math.round(h + pad));
1548
- const sctx = shadowCanvas.getContext('2d');
1549
- sctx.clearRect(0, 0, shadowCanvas.width, shadowCanvas.height);
1550
-
1551
- sctx.fillStyle = `black`;
1552
- sctx.fillRect(pad/2, pad/2, w, h);
1553
-
1554
- StackBlur.canvasRGBA(shadowCanvas, 0, 0, shadowCanvas.width, shadowCanvas.height, blur);
1555
-
1556
- ctx.save();
1557
- ctx.globalAlpha = opacity;
1558
- const drawPosX = baseX - w/2 + dx - pad / 2;
1559
- const drawPosY = baseY - h + dy - pad / 2 + overlap;
1560
- ctx.drawImage(shadowCanvas, drawPosX, drawPosY, shadowCanvas.width, shadowCanvas.height);
1561
- ctx.restore();
1562
- }
1563
-
1564
- function drawCastShadow(ctx, img, w, h, blur, opacity, baseX, baseY, lightX, lightY, dir) {
1565
- const { pad, overlap } = computePadOverlap(blur);
1566
- const shadowW = Math.round(w + pad);
1567
- const shadowH = Math.round(h + pad);
1568
- const shadowCanvas = createCanvas(shadowW, shadowH);
1569
- const sctx = shadowCanvas.getContext('2d');
1570
-
1571
- sctx.clearRect(0, 0, shadowW, shadowH);
1572
- const drawX = pad / 2;
1573
- const drawY = pad / 2;
1574
- sctx.drawImage(img, drawX, drawY, w, h);
1575
- sctx.globalCompositeOperation = "source-in";
1576
- sctx.fillStyle = "black";
1577
- sctx.fillRect(0, 0, shadowW, shadowH);
1578
-
1579
- StackBlur.canvasRGBA(shadowCanvas, 0, 0, shadowW, shadowH, blur);
1580
-
1581
- const relX = lightX ? (lightX / (w * 2) - 0.5) * 2 : (dir === 'left' ? -1 : dir === 'right' ? 1 : 0);
1582
- const relY = lightY ? (lightY / (h * 2)) : (dir === 'front' ? 0.2 : dir === 'back' ? 0.8 : 0.5);
1583
- const skew = relX * 0.8;
1584
-
1585
- ctx.save();
1586
- ctx.translate(baseX, baseY);
1587
- if (dir === 'front' || relY < 0.5) ctx.transform(1, 0, skew, -0.5, 0, 0);
1588
- else ctx.transform(1, 0, skew, 0.3, 0, 0);
1589
-
1590
- ctx.globalAlpha = opacity;
1591
- const drawPosX = -w / 2 - pad / 2;
1592
- const drawPosY = -h - pad / 2 + overlap;
1593
- ctx.drawImage(shadowCanvas, drawPosX, drawPosY, shadowW, shadowH);
1594
-
1595
- ctx.restore();
1596
- }
1597
-
1598
- // ---------------- Trimming ----------------
1599
- function trimImage(image) {
1600
- const temp = createCanvas(image.width, image.height);
1601
- const tctx = temp.getContext('2d');
1602
- tctx.drawImage(image, 0, 0);
1603
- const imgData = tctx.getImageData(0, 0, temp.width, temp.height);
1604
-
1605
- let top = temp.height, left = temp.width, right = 0, bottom = 0;
1606
- for (let y = 0; y < temp.height; y++) {
1607
- for (let x = 0; x < temp.width; x++) {
1608
- const alpha = imgData.data[(y * temp.width + x) * 4 + 3];
1609
- if (alpha > 0) {
1610
- if (x < left) left = x;
1611
- if (x > right) right = x;
1612
- if (y < top) top = y;
1613
- if (y > bottom) bottom = y;
1614
- }
1615
- }
1616
- }
1617
-
1618
- const w = right - left + 1;
1619
- const h = bottom - top + 1;
1620
- const trimmedCanvas = createCanvas(w, h);
1621
- const t2 = trimmedCanvas.getContext('2d');
1622
- t2.drawImage(image, left, top, w, h, 0, 0, w, h);
1623
-
1624
- return { canvas: trimmedCanvas, width: w, height: h };
1625
- }
1626
-
1627
- module.exports = { generateShadow };
 
1
+
2
+
3
+
4
+ const { createCanvas, loadImage } = require('canvas');
5
+ const StackBlur = require('stackblur-canvas'); // Node-compatible
6
+
7
+ async function generateShadow(imageBuffer, options = {}) {
8
+ const {
9
+ type = 'cast', // cast | drop | flat | box
10
+ dir = 'back', // back | front | left | right
11
+ blur = 20, // px
12
+ opacity = 0.35,
13
+ lightX,
14
+ lightY
15
+ } = options;
16
+
17
+ const img = await loadImage(imageBuffer);
18
+ const trimmed = trimImage(img); // precise trimming
19
+
20
+ // ---------------- Use ORIGINAL object size (no fixed scaling) ----------------
21
+ const scaledW = trimmed.width;
22
+ const scaledH = trimmed.height;
23
+
24
+ // ---------------- Shadow padding ----------------
25
+ let shadowOffset = type === 'drop' ? 50 : type === 'flat' ? 40 : type === 'box' ? 40 : 200;
26
+ let extraTop = 0, extraBottom = 0, extraLeft = 0, extraRight = 0;
27
+
28
+ if (dir === 'back') {
29
+ extraBottom = shadowOffset;
30
+ extraLeft = shadowOffset;
31
+ extraRight = shadowOffset;
32
+ }
33
+ if (dir === 'front')
34
+ {
35
+ extraTop = shadowOffset;
36
+ extraBottom = shadowOffset; // add bottom to avoid shrinking
37
+ extraLeft = shadowOffset;
38
+ extraRight = shadowOffset;
39
+ }
40
+
41
+
42
+ if (type === 'cast') {
43
+ extraTop += scaledH * 0.5;
44
+ extraBottom += scaledH * 0.5;
45
+ if (dir === 'back') {
46
+ extraLeft += scaledW * 0.3;
47
+ extraRight += scaledW * 0.3;
48
+ }
49
+ }
50
+
51
+ // ---------------- Final canvas size = original size + padding ----------------
52
+ const canvasW = scaledW + extraLeft + extraRight;
53
+ const canvasH = scaledH + extraTop + extraBottom;
54
+ const canvas = createCanvas(canvasW, canvasH);
55
+ const ctx = canvas.getContext('2d');
56
+ ctx.clearRect(0, 0, canvasW, canvasH);
57
+
58
+ // ---------------- Base position ----------------
59
+ const baseX = extraLeft + scaledW / 2; // center of object
60
+ const baseY = extraTop + scaledH; // bottom of object
61
+
62
+ // ---------------- Draw shadow ----------------
63
+ switch (type) {
64
+ case 'drop':
65
+ drawDropShadow(ctx, trimmed.canvas, scaledW, scaledH, dir, blur, opacity, baseX, baseY);
66
+ break;
67
+ case 'flat':
68
+ drawFlatShadow(ctx, trimmed.canvas, scaledW, scaledH, dir, opacity, baseX, baseY);
69
+ break;
70
+ case 'box':
71
+ drawBoxShadow(ctx, scaledW, scaledH, dir, blur, opacity, baseX, baseY);
72
+ break;
73
+ case 'cast':
74
+ default:
75
+ drawCastShadow(ctx, trimmed.canvas, scaledW, scaledH, blur, opacity, baseX, baseY, lightX, lightY, dir);
76
+ break;
77
+ }
78
+
79
+ // ---------------- Draw object ----------------
80
+ ctx.globalAlpha = 1;
81
+ ctx.filter = "none";
82
+ ctx.drawImage(trimmed.canvas, baseX - scaledW / 2, baseY - scaledH, scaledW, scaledH);
83
+
84
+ return canvas.toBuffer("image/png");
85
+ }
86
+
87
+ // ---------------- Shadow helpers ----------------
88
+ function computePadOverlap(blur) {
89
+ const pad = Math.max(4, Math.ceil(blur * 2)); // ensures enough padding for blur spread
90
+ const overlap = Math.max(1, Math.round(blur * 0.6)); // shadow overlap with object
91
+ return { pad, overlap };
92
+ }
93
+
94
+ function drawDropShadow(ctx, img, w, h, dir, blur, opacity, baseX, baseY) {
95
+ let dx = 0, dy = 0, off = 40;
96
+ if (dir === 'back') dy = off;
97
+ if (dir === 'front') dy = -off;
98
+ if (dir === 'left') dx = -off;
99
+ if (dir === 'right') dx = off;
100
+
101
+ const { pad, overlap } = computePadOverlap(blur);
102
+
103
+ const shadowCanvas = createCanvas(Math.round(w + pad), Math.round(h + pad));
104
+ const sctx = shadowCanvas.getContext('2d');
105
+
106
+ const drawX = pad / 2;
107
+ const drawY = pad / 2;
108
+ sctx.clearRect(0, 0, shadowCanvas.width, shadowCanvas.height);
109
+ sctx.drawImage(img, drawX, drawY, w, h);
110
+ sctx.globalCompositeOperation = "source-in";
111
+ sctx.fillStyle = "black";
112
+ sctx.fillRect(0, 0, shadowCanvas.width, shadowCanvas.height);
113
+
114
+ StackBlur.canvasRGBA(shadowCanvas, 0, 0, shadowCanvas.width, shadowCanvas.height, blur);
115
+
116
+ ctx.save();
117
+ ctx.globalAlpha = opacity;
118
+ const drawPosX = baseX - w / 2 + dx - pad / 2;
119
+ const drawPosY = baseY - h + dy - pad / 2 + overlap;
120
+ ctx.drawImage(shadowCanvas, drawPosX, drawPosY, shadowCanvas.width, shadowCanvas.height);
121
+ ctx.restore();
122
+ }
123
+
124
+ function drawFlatShadow(ctx, img, w, h, dir, opacity, baseX, baseY) {
125
+ let dx = 0, dy = 0, off = 30;
126
+ if (dir === 'back') dy = off;
127
+ if (dir === 'front') dy = -off;
128
+ if (dir === 'left') dx = -off;
129
+ if (dir === 'right') dx = off;
130
+
131
+ ctx.save();
132
+ ctx.globalAlpha = opacity;
133
+ ctx.drawImage(img, baseX - w/2 + dx, baseY - h + dy + 1, w, h);
134
+ ctx.restore();
135
+ }
136
+
137
+ function drawBoxShadow(ctx, w, h, dir, blur, opacity, baseX, baseY) {
138
+ let dx = 0, dy = 0, off = 30;
139
+ if (dir === 'back') dy = off;
140
+ if (dir === 'front') dy = -off;
141
+ if (dir === 'left') dx = -off;
142
+ if (dir === 'right') dx = off;
143
+
144
+ const { pad, overlap } = computePadOverlap(blur);
145
+
146
+ const shadowCanvas = createCanvas(Math.round(w + pad), Math.round(h + pad));
147
+ const sctx = shadowCanvas.getContext('2d');
148
+ sctx.clearRect(0, 0, shadowCanvas.width, shadowCanvas.height);
149
+
150
+ sctx.fillStyle = `black`;
151
+ sctx.fillRect(pad/2, pad/2, w, h);
152
+
153
+ StackBlur.canvasRGBA(shadowCanvas, 0, 0, shadowCanvas.width, shadowCanvas.height, blur);
154
+
155
+ ctx.save();
156
+ ctx.globalAlpha = opacity;
157
+ const drawPosX = baseX - w/2 + dx - pad / 2;
158
+ const drawPosY = baseY - h + dy - pad / 2 + overlap;
159
+ ctx.drawImage(shadowCanvas, drawPosX, drawPosY, shadowCanvas.width, shadowCanvas.height);
160
+ ctx.restore();
161
+ }
162
+
163
+ function drawCastShadow(ctx, img, w, h, blur, opacity, baseX, baseY, lightX, lightY, dir) {
164
+ const { pad, overlap } = computePadOverlap(blur);
165
+ const shadowW = Math.round(w + pad);
166
+ const shadowH = Math.round(h + pad);
167
+ const shadowCanvas = createCanvas(shadowW, shadowH);
168
+ const sctx = shadowCanvas.getContext('2d');
169
+
170
+ sctx.clearRect(0, 0, shadowW, shadowH);
171
+ const drawX = pad / 2;
172
+ const drawY = pad / 2;
173
+ sctx.drawImage(img, drawX, drawY, w, h);
174
+ sctx.globalCompositeOperation = "source-in";
175
+ sctx.fillStyle = "black";
176
+ sctx.fillRect(0, 0, shadowW, shadowH);
177
+
178
+ StackBlur.canvasRGBA(shadowCanvas, 0, 0, shadowW, shadowH, blur);
179
+
180
+ const relX = lightX ? (lightX / (w * 2) - 0.5) * 2 : (dir === 'left' ? -1 : dir === 'right' ? 1 : 0);
181
+ const relY = lightY ? (lightY / (h * 2)) : (dir === 'front' ? 0.2 : dir === 'back' ? 0.8 : 0.5);
182
+ const skew = relX * 0.8;
183
+
184
+ ctx.save();
185
+ ctx.translate(baseX, baseY);
186
+ if (dir === 'front' || relY < 0.5) ctx.transform(1, 0, skew, -0.5, 0, 0);
187
+ else ctx.transform(1, 0, skew, 0.3, 0, 0);
188
+
189
+ ctx.globalAlpha = opacity;
190
+ const drawPosX = -w / 2 - pad / 2;
191
+ const drawPosY = -h - pad / 2 + overlap;
192
+ ctx.drawImage(shadowCanvas, drawPosX, drawPosY, shadowW, shadowH);
193
+
194
+ ctx.restore();
195
+ }
196
+
197
+ // ---------------- Trimming ----------------
198
+ function trimImage(image) {
199
+ const temp = createCanvas(image.width, image.height);
200
+ const tctx = temp.getContext('2d');
201
+ tctx.drawImage(image, 0, 0);
202
+ const imgData = tctx.getImageData(0, 0, temp.width, temp.height);
203
+
204
+ let top = temp.height, left = temp.width, right = 0, bottom = 0;
205
+ for (let y = 0; y < temp.height; y++) {
206
+ for (let x = 0; x < temp.width; x++) {
207
+ const alpha = imgData.data[(y * temp.width + x) * 4 + 3];
208
+ if (alpha > 0) {
209
+ if (x < left) left = x;
210
+ if (x > right) right = x;
211
+ if (y < top) top = y;
212
+ if (y > bottom) bottom = y;
213
+ }
214
+ }
215
+ }
216
+
217
+ const w = right - left + 1;
218
+ const h = bottom - top + 1;
219
+ const trimmedCanvas = createCanvas(w, h);
220
+ const t2 = trimmedCanvas.getContext('2d');
221
+ t2.drawImage(image, left, top, w, h, 0, 0, w, h);
222
+
223
+ return { canvas: trimmedCanvas, width: w, height: h };
224
+ }
225
+
226
+ module.exports = { generateShadow };