karthikeya1212 commited on
Commit
e890ceb
·
verified ·
1 Parent(s): fe137ba

Upload 7 files

Browse files
Files changed (7) hide show
  1. Dockerfile +0 -0
  2. app/api/shadow.js +142 -0
  3. app/core/shadowGenerator.js +1627 -0
  4. app/queues/taskQueue.js +162 -0
  5. app/server.js +114 -0
  6. package.json +29 -0
  7. server.js +114 -0
Dockerfile ADDED
File without changes
app/api/shadow.js ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // const express = require('express');
2
+ // const multer = require('multer');
3
+ // const { generateShadow } = require('../core/shadowGenerator');
4
+
5
+ // const router = express.Router();
6
+
7
+ // // Store file in memory (not on disk)
8
+ // const storage = multer.memoryStorage();
9
+ // const upload = multer({ storage });
10
+
11
+ // // POST /api/shadow/generate
12
+ // router.post('/generate', upload.single('image'), async (req, res) => {
13
+ // try {
14
+ // if (!req.file) {
15
+ // return res.status(400).json({ success: false, error: 'No image uploaded' });
16
+ // }
17
+
18
+ // const options = {
19
+ // type: req.body.type || 'cast', // 'cast' | 'drop' | 'flat' | 'box'
20
+ // dir: req.body.dir || 'back', // used only for drop/flat/box
21
+ // blur: parseInt(req.body.blur) || 20,
22
+ // opacity: parseFloat(req.body.opacity) || 0.4,
23
+ // lightX: req.body.lightX ? parseInt(req.body.lightX) : null,
24
+ // lightY: req.body.lightY ? parseInt(req.body.lightY) : null
25
+ // };
26
+
27
+ // const buffer = await generateShadow(req.file.buffer, options);
28
+
29
+ // // Send back as image/png directly
30
+ // res.set('Content-Type', 'image/png');
31
+ // res.send(buffer);
32
+
33
+ // } catch (err) {
34
+ // console.error('❌ Shadow generation error:', err);
35
+ // res.status(500).json({ success: false, error: 'Shadow generation failed' });
36
+ // }
37
+ // });
38
+
39
+ // module.exports = router;
40
+
41
+
42
+ // const express = require('express');
43
+ // const multer = require('multer');
44
+ // const { generateShadow } = require('../core/shadowGenerator');
45
+
46
+ // const router = express.Router();
47
+
48
+ // // Use memory storage for uploaded file
49
+ // const storage = multer.memoryStorage();
50
+ // const upload = multer({ storage });
51
+
52
+ // router.post('/generate', upload.single('image'), async (req, res) => {
53
+ // try {
54
+ // if (!req.file) {
55
+ // return res.status(400).json({ success: false, error: 'No image uploaded' });
56
+ // }
57
+
58
+ // // Parse options correctly
59
+ // const options = {
60
+ // type: req.body.mode || 'cast', // match frontend "mode"
61
+ // dir: req.body.dir || 'back',
62
+ // blur: req.body.blur ? parseFloat(req.body.blur) : 20, // ensure number
63
+ // opacity: req.body.opacity ? parseFloat(req.body.opacity) : 0.35,
64
+ // lightX: req.body.lightX ? parseFloat(req.body.lightX) : null,
65
+ // lightY: req.body.lightY ? parseFloat(req.body.lightY) : null
66
+ // };
67
+
68
+ // const buffer = await generateShadow(req.file.buffer, options);
69
+
70
+ // res.set('Content-Type', 'image/png');
71
+ // res.send(buffer);
72
+
73
+ // } catch (err) {
74
+ // console.error('❌ Shadow generation error:', err);
75
+ // res.status(500).json({ success: false, error: 'Shadow generation failed' });
76
+ // }
77
+ // });
78
+
79
+ // module.exports = router;
80
+
81
+
82
+
83
+ const express = require('express');
84
+ const multer = require('multer');
85
+ const {
86
+ addTask,
87
+ getTaskStatus,
88
+ getTaskResult,
89
+ getQueueLength
90
+ } = require('../../queue');
91
+
92
+ const router = express.Router();
93
+ const upload = multer({ storage: multer.memoryStorage() });
94
+
95
+ // ---------------- Submit new shadow task ----------------
96
+ router.post('/add-shadow', upload.single('file'), async (req, res) => {
97
+ try {
98
+ if (!req.file) {
99
+ return res.status(400).json({ error: 'No image uploaded' });
100
+ }
101
+
102
+ const options = {
103
+ type: req.body.type || 'cast',
104
+ dir: req.body.dir || 'back',
105
+ blur: req.body.blur ? parseFloat(req.body.blur) : 20,
106
+ opacity: req.body.opacity ? parseFloat(req.body.opacity) : 0.35,
107
+ lightX: req.body.lightX ? parseFloat(req.body.lightX) : null,
108
+ lightY: req.body.lightY ? parseFloat(req.body.lightY) : null,
109
+ };
110
+
111
+ const taskId = await addTask(req.file.buffer, options);
112
+ res.json({ task_id: taskId });
113
+ } catch (err) {
114
+ console.error('❌ Error adding shadow task:', err);
115
+ res.status(500).json({ error: 'Failed to add shadow task' });
116
+ }
117
+ });
118
+
119
+ // ---------------- Queue status ----------------
120
+ router.get('/status', (req, res) => {
121
+ res.json({
122
+ status: 'ok',
123
+ queue_length: getQueueLength(),
124
+ });
125
+ });
126
+
127
+ // ---------------- Fetch result ----------------
128
+ router.get('/result/:id', (req, res) => {
129
+ const taskId = req.params.id;
130
+ const status = getTaskStatus(taskId);
131
+
132
+ if (status === 'pending') return res.sendStatus(202);
133
+ if (status === 'failed') return res.status(500).json({ error: 'Shadow generation failed' });
134
+
135
+ const result = getTaskResult(taskId);
136
+ if (!result) return res.status(404).json({ error: 'Result not found' });
137
+
138
+ res.set('Content-Type', 'image/png');
139
+ res.send(result);
140
+ });
141
+
142
+ module.exports = router;
app/core/shadowGenerator.js ADDED
@@ -0,0 +1,1627 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 };
app/queues/taskQueue.js ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // const { Queue, Worker, QueueScheduler, Job } = require('bullmq');
2
+ // const IORedis = require('ioredis');
3
+ // const fs = require('fs-extra');
4
+ // const path = require('path');
5
+ // const { v4: uuidv4 } = require('uuid');
6
+ // const { generateShadow } = require('./shadowGenerator'); // your existing shadow logic
7
+
8
+ // // Redis connection
9
+ // const connection = new IORedis();
10
+
11
+ // // Queue setup
12
+ // const shadowQueue = new Queue('shadowQueue', { connection });
13
+ // new QueueScheduler('shadowQueue', { connection });
14
+
15
+ // // RAM and disk buffers
16
+ // const RAM_LIMIT = 20; // max tasks in RAM
17
+ // const RAM_BUFFER = [];
18
+ // const DISK_BUFFER = path.join(__dirname, 'diskQueue');
19
+ // fs.ensureDirSync(DISK_BUFFER);
20
+
21
+ // // In-memory results
22
+ // const RESULTS = {};
23
+
24
+ // // ---------------- Add Task ----------------
25
+ // async function addTask(fileBuffer, options = {}) {
26
+ // const taskId = uuidv4(); // unique ID for each task
27
+ // const task = { id: taskId, file: fileBuffer, options };
28
+
29
+ // if (RAM_BUFFER.length < RAM_LIMIT) {
30
+ // RAM_BUFFER.push(task);
31
+ // processRAMBuffer();
32
+ // } else {
33
+ // // Move to disk
34
+ // const filePath = path.join(DISK_BUFFER, `${taskId}.json`);
35
+ // await fs.writeJson(filePath, task, { spaces: 0 });
36
+ // }
37
+
38
+ // return taskId;
39
+ // }
40
+
41
+ // // ---------------- Process RAM tasks ----------------
42
+ // let processing = false;
43
+
44
+ // async function processRAMBuffer() {
45
+ // if (processing || RAM_BUFFER.length === 0) return;
46
+ // processing = true;
47
+
48
+ // while (RAM_BUFFER.length > 0) {
49
+ // const task = RAM_BUFFER.shift();
50
+ // try {
51
+ // const imgBuffer = await generateShadow(task.file, task.options);
52
+ // RESULTS[task.id] = imgBuffer;
53
+ // } catch (err) {
54
+ // console.error(`Task ${task.id} failed:`, err);
55
+ // RESULTS[task.id] = null;
56
+ // }
57
+
58
+ // // Move tasks from disk to RAM if space available
59
+ // const diskFiles = await fs.readdir(DISK_BUFFER);
60
+ // while (RAM_BUFFER.length < RAM_LIMIT && diskFiles.length > 0) {
61
+ // const diskFile = diskFiles.shift();
62
+ // const taskFromDisk = await fs.readJson(path.join(DISK_BUFFER, diskFile));
63
+ // RAM_BUFFER.push(taskFromDisk);
64
+ // await fs.remove(path.join(DISK_BUFFER, diskFile));
65
+ // }
66
+ // }
67
+
68
+ // processing = false;
69
+ // }
70
+
71
+ // // ---------------- Check Task Status ----------------
72
+ // function getTaskStatus(taskId) {
73
+ // if (RESULTS[taskId] === undefined) return 'pending';
74
+ // if (RESULTS[taskId] === null) return 'failed';
75
+ // return 'done';
76
+ // }
77
+
78
+ // // ---------------- Fetch Result ----------------
79
+ // function getTaskResult(taskId) {
80
+ // return RESULTS[taskId] || null;
81
+ // }
82
+
83
+ // module.exports = {
84
+ // addTask,
85
+ // getTaskStatus,
86
+ // getTaskResult
87
+ // };
88
+
89
+
90
+ const { v4: uuidv4 } = require('uuid');
91
+ const { generateShadow } = require('../core/shadowGenerator');
92
+
93
+ const RAM_LIMIT = 20;
94
+ const RAM_BUFFER = [];
95
+ const RESULTS = {};
96
+ const CLEANUP_TIME = 10 * 60 * 1000; // 10 minutes
97
+
98
+ let processing = false;
99
+
100
+ // ---------------- Add Task ----------------
101
+ async function addTask(fileBuffer, options = {}) {
102
+ const taskId = uuidv4();
103
+ const task = { id: taskId, file: fileBuffer, options };
104
+
105
+ if (RAM_BUFFER.length < RAM_LIMIT) {
106
+ RAM_BUFFER.push(task);
107
+ processRAMBuffer();
108
+ } else {
109
+ return Promise.reject(new Error('Server busy, try again'));
110
+ }
111
+
112
+ return taskId;
113
+ }
114
+
115
+ // ---------------- Process RAM tasks ----------------
116
+ async function processRAMBuffer() {
117
+ if (processing || RAM_BUFFER.length === 0) return;
118
+ processing = true;
119
+
120
+ while (RAM_BUFFER.length > 0) {
121
+ const task = RAM_BUFFER.shift();
122
+ try {
123
+ const imgBuffer = await generateShadow(task.file, task.options);
124
+ RESULTS[task.id] = imgBuffer;
125
+
126
+ // Auto-clean after CLEANUP_TIME
127
+ setTimeout(() => {
128
+ delete RESULTS[task.id];
129
+ }, CLEANUP_TIME);
130
+
131
+ } catch (err) {
132
+ console.error(`Task ${task.id} failed:`, err);
133
+ RESULTS[task.id] = null;
134
+ }
135
+ }
136
+
137
+ processing = false;
138
+ }
139
+
140
+ // ---------------- Check Task Status ----------------
141
+ function getTaskStatus(taskId) {
142
+ if (RESULTS[taskId] === undefined) return 'pending';
143
+ if (RESULTS[taskId] === null) return 'failed';
144
+ return 'done';
145
+ }
146
+
147
+ // ---------------- Fetch Result ----------------
148
+ function getTaskResult(taskId) {
149
+ return RESULTS[taskId] || null;
150
+ }
151
+
152
+ // ---------------- Queue Length ----------------
153
+ function getQueueLength() {
154
+ return RAM_BUFFER.length;
155
+ }
156
+
157
+ module.exports = {
158
+ addTask,
159
+ getTaskStatus,
160
+ getTaskResult,
161
+ getQueueLength
162
+ };
app/server.js ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // const express = require('express');
2
+ // const multer = require('multer');
3
+ // const { addTask, getTaskStatus, getTaskResult } = require('../../queue');
4
+
5
+ // const router = express.Router();
6
+ // const upload = multer();
7
+
8
+ // // ---------------- Submit new shadow task ----------------
9
+ // router.post('/generate', upload.single('image'), async (req, res) => {
10
+ // if (!req.file) return res.status(400).json({ error: 'No image uploaded' });
11
+
12
+ // const options = {
13
+ // type: req.body.mode || 'cast',
14
+ // dir: req.body.dir || 'back',
15
+ // blur: parseInt(req.body.blur) || 20,
16
+ // opacity: parseFloat(req.body.opacity) || 0.35,
17
+ // lightX: req.body.lightX ? parseFloat(req.body.lightX) : undefined,
18
+ // lightY: req.body.lightY ? parseFloat(req.body.lightY) : undefined
19
+ // };
20
+
21
+ // try {
22
+ // const taskId = await addTask(req.file.buffer, options);
23
+ // res.json({ taskId });
24
+ // } catch (err) {
25
+ // console.error('Error adding task:', err);
26
+ // res.status(500).json({ error: 'Failed to add task' });
27
+ // }
28
+ // });
29
+
30
+ // // ---------------- Check task status ----------------
31
+ // router.get('/status/:id', (req, res) => {
32
+ // const status = getTaskStatus(req.params.id);
33
+ // res.json({ status });
34
+ // });
35
+
36
+ // // ---------------- Fetch completed result ----------------
37
+ // router.get('/result/:id', (req, res) => {
38
+ // const result = getTaskResult(req.params.id);
39
+ // if (!result) return res.status(404).json({ error: 'Result not ready' });
40
+
41
+ // res.set('Content-Type', 'image/png');
42
+ // res.send(result);
43
+ // });
44
+
45
+ // module.exports = router;
46
+
47
+
48
+ const express = require('express');
49
+ const multer = require('multer');
50
+ const cors = require('cors');
51
+ const {
52
+ addTask,
53
+ getTaskStatus,
54
+ getTaskResult,
55
+ getQueueLength
56
+ } = require('./queues/taskQueue');
57
+
58
+ const app = express();
59
+ const router = express.Router();
60
+ const upload = multer();
61
+
62
+ app.use(cors());
63
+
64
+ // ---------------- Submit new shadow task ----------------
65
+ router.post('/add-shadow', upload.single('file'), async (req, res) => {
66
+ if (!req.file) return res.status(400).json({ error: 'No image uploaded' });
67
+
68
+ const options = {
69
+ type: req.body.type || 'cast',
70
+ dir: req.body.dir || 'back',
71
+ blur: req.body.blur ? parseInt(req.body.blur) : 20,
72
+ opacity: req.body.opacity ? parseFloat(req.body.opacity) : 0.35,
73
+ lightX: req.body.lightX ? parseFloat(req.body.lightX) : undefined,
74
+ lightY: req.body.lightY ? parseFloat(req.body.lightY) : undefined,
75
+ };
76
+
77
+ try {
78
+ const taskId = await addTask(req.file.buffer, options);
79
+ res.json({ task_id: taskId });
80
+ } catch (err) {
81
+ console.error('❌ Error adding shadow task:', err);
82
+ res.status(500).json({ error: 'Server busy, try again' });
83
+ }
84
+ });
85
+
86
+ // ---------------- Server load / status ----------------
87
+ router.get('/status', (req, res) => {
88
+ res.json({
89
+ status: 'ok',
90
+ queue_length: getQueueLength(),
91
+ });
92
+ });
93
+
94
+ // ---------------- Poll for result ----------------
95
+ router.get('/result/:id', (req, res) => {
96
+ const status = getTaskStatus(req.params.id);
97
+
98
+ if (status === 'pending') return res.sendStatus(202);
99
+ if (status === 'failed') return res.status(500).json({ error: 'Shadow generation failed' });
100
+
101
+ const result = getTaskResult(req.params.id);
102
+ if (!result) return res.status(404).json({ error: 'Result not found' });
103
+
104
+ res.set('Content-Type', 'image/png');
105
+ res.send(result);
106
+ });
107
+
108
+ app.use('/api/shadow', router);
109
+
110
+ // ---------------- Start server ----------------
111
+ const PORT = process.env.PORT || 5000;
112
+ app.listen(PORT, () => console.log(`🚀 Shadow backend running on http://localhost:${PORT}`));
113
+
114
+ module.exports = app;
package.json ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "shadowbackend",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "scripts": {
6
+ "test": "echo \"Error: no test specified\" && exit 1"
7
+ },
8
+ "keywords": [],
9
+ "author": "",
10
+ "license": "ISC",
11
+ "description": "",
12
+ "dependencies": {
13
+ "body-parser": "^2.2.0",
14
+ "bullmq": "^5.58.2",
15
+ "canvas": "^3.2.0",
16
+ "cors": "^2.8.5",
17
+ "dotenv": "^17.2.1",
18
+ "express": "^5.1.0",
19
+ "fs-extra": "^11.3.1",
20
+ "ioredis": "^5.7.0",
21
+ "multer": "^2.0.2",
22
+ "sharp": "^0.34.3",
23
+ "stackblur-canvas": "^2.7.0",
24
+ "uuid": "^11.1.0"
25
+ },
26
+ "devDependencies": {
27
+ "nodemon": "^3.1.10"
28
+ }
29
+ }
server.js ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // const express = require('express');
2
+ // const multer = require('multer');
3
+ // const { addTask, getTaskStatus, getTaskResult } = require('../../queue');
4
+
5
+ // const router = express.Router();
6
+ // const upload = multer();
7
+
8
+ // // ---------------- Submit new shadow task ----------------
9
+ // router.post('/generate', upload.single('image'), async (req, res) => {
10
+ // if (!req.file) return res.status(400).json({ error: 'No image uploaded' });
11
+
12
+ // const options = {
13
+ // type: req.body.mode || 'cast',
14
+ // dir: req.body.dir || 'back',
15
+ // blur: parseInt(req.body.blur) || 20,
16
+ // opacity: parseFloat(req.body.opacity) || 0.35,
17
+ // lightX: req.body.lightX ? parseFloat(req.body.lightX) : undefined,
18
+ // lightY: req.body.lightY ? parseFloat(req.body.lightY) : undefined
19
+ // };
20
+
21
+ // try {
22
+ // const taskId = await addTask(req.file.buffer, options);
23
+ // res.json({ taskId });
24
+ // } catch (err) {
25
+ // console.error('Error adding task:', err);
26
+ // res.status(500).json({ error: 'Failed to add task' });
27
+ // }
28
+ // });
29
+
30
+ // // ---------------- Check task status ----------------
31
+ // router.get('/status/:id', (req, res) => {
32
+ // const status = getTaskStatus(req.params.id);
33
+ // res.json({ status });
34
+ // });
35
+
36
+ // // ---------------- Fetch completed result ----------------
37
+ // router.get('/result/:id', (req, res) => {
38
+ // const result = getTaskResult(req.params.id);
39
+ // if (!result) return res.status(404).json({ error: 'Result not ready' });
40
+
41
+ // res.set('Content-Type', 'image/png');
42
+ // res.send(result);
43
+ // });
44
+
45
+ // module.exports = router;
46
+
47
+
48
+ const express = require('express');
49
+ const multer = require('multer');
50
+ const cors = require('cors');
51
+ const {
52
+ addTask,
53
+ getTaskStatus,
54
+ getTaskResult,
55
+ getQueueLength
56
+ } = require('./queues/taskQueue');
57
+
58
+ const app = express();
59
+ const router = express.Router();
60
+ const upload = multer();
61
+
62
+ app.use(cors());
63
+
64
+ // ---------------- Submit new shadow task ----------------
65
+ router.post('/add-shadow', upload.single('file'), async (req, res) => {
66
+ if (!req.file) return res.status(400).json({ error: 'No image uploaded' });
67
+
68
+ const options = {
69
+ type: req.body.type || 'cast',
70
+ dir: req.body.dir || 'back',
71
+ blur: req.body.blur ? parseInt(req.body.blur) : 20,
72
+ opacity: req.body.opacity ? parseFloat(req.body.opacity) : 0.35,
73
+ lightX: req.body.lightX ? parseFloat(req.body.lightX) : undefined,
74
+ lightY: req.body.lightY ? parseFloat(req.body.lightY) : undefined,
75
+ };
76
+
77
+ try {
78
+ const taskId = await addTask(req.file.buffer, options);
79
+ res.json({ task_id: taskId });
80
+ } catch (err) {
81
+ console.error('❌ Error adding shadow task:', err);
82
+ res.status(500).json({ error: 'Server busy, try again' });
83
+ }
84
+ });
85
+
86
+ // ---------------- Server load / status ----------------
87
+ router.get('/status', (req, res) => {
88
+ res.json({
89
+ status: 'ok',
90
+ queue_length: getQueueLength(),
91
+ });
92
+ });
93
+
94
+ // ---------------- Poll for result ----------------
95
+ router.get('/result/:id', (req, res) => {
96
+ const status = getTaskStatus(req.params.id);
97
+
98
+ if (status === 'pending') return res.sendStatus(202);
99
+ if (status === 'failed') return res.status(500).json({ error: 'Shadow generation failed' });
100
+
101
+ const result = getTaskResult(req.params.id);
102
+ if (!result) return res.status(404).json({ error: 'Result not found' });
103
+
104
+ res.set('Content-Type', 'image/png');
105
+ res.send(result);
106
+ });
107
+
108
+ app.use('/api/shadow', router);
109
+
110
+ // ---------------- Start server ----------------
111
+ const PORT = process.env.PORT || 5000;
112
+ app.listen(PORT, () => console.log(`🚀 Shadow backend running on http://localhost:${PORT}`));
113
+
114
+ module.exports = app;