frontbacked-svg 5.0.0 → 5.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.cjs +211 -86
- package/dist/main.d.cts +7 -4
- package/dist/main.d.ts +7 -4
- package/dist/main.mjs +211 -86
- package/package.json +2 -1
- package/src/base64Image.ts +100 -100
- package/src/cssParser.ts +58 -58
- package/src/filters.ts +225 -225
- package/src/font-utils.ts +192 -204
- package/src/get-default-fields-value.ts +63 -63
- package/src/getSvg.ts +476 -284
- package/src/imageHelper.ts +295 -293
- package/src/imagePassportUtils.ts +699 -717
- package/src/images-processor.ts +179 -186
- package/src/polyfills/DOMParser.ts +27 -27
- package/src/polyfills/File.ts +19 -19
- package/src/polyfills/Image.ts +89 -89
- package/src/svgScaler.ts +178 -178
- package/src/textGenCodeParser.ts +318 -318
- package/src/time.ts +146 -146
- package/src/toolsFunc.ts +78 -78
- package/src/types.ts +176 -176
- package/src/utils.ts +757 -757
- package/src/watermaker.ts +189 -189
package/src/imageHelper.ts
CHANGED
|
@@ -1,293 +1,295 @@
|
|
|
1
|
-
import { ImageWrapper } from './polyfills/Image.ts';
|
|
2
|
-
|
|
3
|
-
const isNode = typeof window === 'undefined';
|
|
4
|
-
|
|
5
|
-
export function cropImage(imageUrl: string): Promise<string> {
|
|
6
|
-
return new Promise((resolve, reject) => {
|
|
7
|
-
const img = new Image();
|
|
8
|
-
|
|
9
|
-
img.onload = function () {
|
|
10
|
-
const canvas = getCanvas(img.width, img.height)
|
|
11
|
-
const ctx = canvas.getContext("2d") as any;
|
|
12
|
-
if (!ctx) return reject(new Error("Failed to get canvas context"));
|
|
13
|
-
|
|
14
|
-
canvas.width = img.width;
|
|
15
|
-
canvas.height = img.height;
|
|
16
|
-
const realImage = ((img as any) as ImageWrapper).realImage? ((img as any) as ImageWrapper).realImage : img
|
|
17
|
-
ctx.drawImage(realImage, 0, 0);
|
|
18
|
-
|
|
19
|
-
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
20
|
-
const data = imageData.data;
|
|
21
|
-
|
|
22
|
-
let minX = canvas.width;
|
|
23
|
-
let minY = canvas.height;
|
|
24
|
-
let maxX = 0;
|
|
25
|
-
let maxY = 0;
|
|
26
|
-
|
|
27
|
-
for (let y = 0; y < canvas.height; y++) {
|
|
28
|
-
for (let x = 0; x < canvas.width; x++) {
|
|
29
|
-
const index = (y * canvas.width + x) * 4;
|
|
30
|
-
if (data[index + 3] > 0) {
|
|
31
|
-
minX = Math.min(minX, x);
|
|
32
|
-
minY = Math.min(minY, y);
|
|
33
|
-
maxX = Math.max(maxX, x);
|
|
34
|
-
maxY = Math.max(maxY, y);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const cropWidth = maxX - minX;
|
|
40
|
-
const cropHeight = maxY - minY;
|
|
41
|
-
const croppedCanvas = getCanvas(cropWidth, cropHeight);
|
|
42
|
-
const croppedCtx = croppedCanvas.getContext("2d") as any;
|
|
43
|
-
if (!croppedCtx) return reject(new Error("Failed to get canvas context"));
|
|
44
|
-
|
|
45
|
-
croppedCanvas.width = cropWidth;
|
|
46
|
-
croppedCanvas.height = cropHeight;
|
|
47
|
-
croppedCtx.drawImage(canvas, minX, minY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
|
|
48
|
-
|
|
49
|
-
resolve(croppedCanvas.toDataURL("image/png"));
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
img.onerror = () => reject(new Error('Error loading image'));
|
|
53
|
-
img.src = imageUrl;
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function isBlankImage(imageUrl: string): Promise<boolean> {
|
|
58
|
-
return new Promise((resolve, reject) => {
|
|
59
|
-
const img = new Image();
|
|
60
|
-
img.onload = function () {
|
|
61
|
-
const canvas = getCanvas(img.width, img.height);
|
|
62
|
-
canvas.width = img.width;
|
|
63
|
-
canvas.height = img.height;
|
|
64
|
-
const ctx = canvas.getContext("2d") as any;
|
|
65
|
-
if (!ctx) return reject(new Error("Failed to get canvas context"));
|
|
66
|
-
const realImage = ((img as any) as ImageWrapper).realImage? ((img as any) as ImageWrapper).realImage : img
|
|
67
|
-
ctx.drawImage(realImage, 0, 0);
|
|
68
|
-
|
|
69
|
-
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
|
|
70
|
-
resolve(imageData.every((value: any) => value === 0));
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
img.onerror = () => reject(new Error('Error loading image'));
|
|
74
|
-
img.src = imageUrl;
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function getImageDimension(imageUrl: string): Promise<{ width: number; height: number }> {
|
|
79
|
-
return new Promise((resolve, reject) => {
|
|
80
|
-
const img = new Image();
|
|
81
|
-
img.onload = () => resolve({ width: img.width, height: img.height });
|
|
82
|
-
img.onerror = () => reject(new Error('Error loading image'));
|
|
83
|
-
img.src = imageUrl;
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function imageToStamp(base64Image: string, color: [number, number, number], holePct: number): Promise<string | null> {
|
|
88
|
-
return new Promise((resolve, reject) => {
|
|
89
|
-
if (!base64Image) return resolve(null);
|
|
90
|
-
const img = new Image();
|
|
91
|
-
img.onload = function () {
|
|
92
|
-
const canvas = getCanvas(img.width, img.height);
|
|
93
|
-
const ctx = canvas.getContext("2d") as any;
|
|
94
|
-
if (!ctx) return reject(new Error("Failed to get canvas context"));
|
|
95
|
-
|
|
96
|
-
canvas.width = img.width;
|
|
97
|
-
canvas.height = img.height;
|
|
98
|
-
const realImage = ((img as any) as ImageWrapper).realImage? ((img as any) as ImageWrapper).realImage : img
|
|
99
|
-
ctx.drawImage(realImage, 0, 0);
|
|
100
|
-
|
|
101
|
-
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
102
|
-
const data = imageData.data;
|
|
103
|
-
|
|
104
|
-
const numTransparentPixels = Math.floor((holePct / 100) * (canvas.width * canvas.height));
|
|
105
|
-
|
|
106
|
-
for (let i = 0; i < numTransparentPixels; i++) {
|
|
107
|
-
const x = Math.floor(Math.random() * canvas.width);
|
|
108
|
-
const y = Math.floor(Math.random() * canvas.height);
|
|
109
|
-
const index = (y * canvas.width + x) * 4;
|
|
110
|
-
data[index + 3] = 0;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
ctx.putImageData(imageData, 0, 0);
|
|
114
|
-
resolve(canvas.toDataURL("image/png"));
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
img.onerror = () => reject(new Error('Error loading image'));
|
|
118
|
-
img.src = base64Image;
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export function getImageColor(base64ImageUrl: string, useDominantColor = true): Promise<[number, number, number] | null> {
|
|
123
|
-
return new Promise((resolve, reject) => {
|
|
124
|
-
if (!base64ImageUrl) return resolve(null);
|
|
125
|
-
const img = new Image();
|
|
126
|
-
img.onload = function () {
|
|
127
|
-
const canvas = getCanvas(img.width, img.height);
|
|
128
|
-
const ctx = canvas.getContext("2d") as any;
|
|
129
|
-
if (!ctx) return reject(new Error("Failed to get canvas context"));
|
|
130
|
-
|
|
131
|
-
canvas.width = img.width;
|
|
132
|
-
canvas.height = img.height;
|
|
133
|
-
const realImage = ((img as any) as ImageWrapper).realImage? ((img as any) as ImageWrapper).realImage : img
|
|
134
|
-
ctx.drawImage(realImage, 0, 0);
|
|
135
|
-
|
|
136
|
-
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
137
|
-
const data = imageData.data;
|
|
138
|
-
|
|
139
|
-
let totalRed = 0, totalGreen = 0, totalBlue = 0, totalPixels = 0;
|
|
140
|
-
const colorCount: Record<string, number> = {};
|
|
141
|
-
|
|
142
|
-
for (let i = 0; i < data.length; i += 4) {
|
|
143
|
-
const red = data[i], green = data[i + 1], blue = data[i + 2];
|
|
144
|
-
totalRed += red;
|
|
145
|
-
totalGreen += green;
|
|
146
|
-
totalBlue += blue;
|
|
147
|
-
totalPixels++;
|
|
148
|
-
const colorKey = `${red},${green},${blue}`;
|
|
149
|
-
colorCount[colorKey] = (colorCount[colorKey] || 0) + 1;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const averageColor: [number, number, number] = [
|
|
153
|
-
Math.round(totalRed / totalPixels),
|
|
154
|
-
Math.round(totalGreen / totalPixels),
|
|
155
|
-
Math.round(totalBlue / totalPixels)
|
|
156
|
-
];
|
|
157
|
-
|
|
158
|
-
if (useDominantColor) {
|
|
159
|
-
let dominantColor: [number, number, number] = averageColor;
|
|
160
|
-
let maxCount = 0;
|
|
161
|
-
for (const colorKey in colorCount) {
|
|
162
|
-
if (colorCount[colorKey] > maxCount) {
|
|
163
|
-
dominantColor = colorKey.split(",").map(Number) as [number, number, number];
|
|
164
|
-
maxCount = colorCount[colorKey];
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
resolve(dominantColor);
|
|
168
|
-
} else {
|
|
169
|
-
resolve(averageColor);
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
img.onerror = () => reject(new Error('Error loading image'));
|
|
174
|
-
img.src = base64ImageUrl;
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
export function reduceBase64Image(base64: string, targetWidth: number, quality=1) {
|
|
179
|
-
return new Promise((resolve, reject) => {
|
|
180
|
-
const img = new Image();
|
|
181
|
-
img.onload = () => {
|
|
182
|
-
const { width: originalWidth, height: originalHeight } = img;
|
|
183
|
-
|
|
184
|
-
// If the target width is greater than original, return original
|
|
185
|
-
if (targetWidth > originalWidth) {
|
|
186
|
-
return resolve(base64);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const aspectRatio = originalHeight / originalWidth;
|
|
190
|
-
let targetHeight = targetWidth * aspectRatio;
|
|
191
|
-
if(targetWidth == 0 || targetWidth == originalWidth) {
|
|
192
|
-
targetWidth = originalWidth
|
|
193
|
-
targetHeight = originalHeight;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const canvas = getCanvas(targetWidth, targetHeight);
|
|
197
|
-
//const canvas = document.createElement('canvas');
|
|
198
|
-
canvas.width = targetWidth;
|
|
199
|
-
canvas.height = targetHeight;
|
|
200
|
-
|
|
201
|
-
const ctx = canvas.getContext('2d') as any;
|
|
202
|
-
const realImage = ((img as any) as ImageWrapper).realImage? ((img as any) as ImageWrapper).realImage : img
|
|
203
|
-
|
|
204
|
-
if(targetWidth == 0 || targetHeight == 0 || !realImage) {
|
|
205
|
-
console.log("invimg:2", targetWidth, targetHeight, realImage)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
ctx.drawImage(realImage, 0, 0, targetWidth, targetHeight);
|
|
209
|
-
|
|
210
|
-
const matches = base64.match(/^data:(.+);base64,(.+)$/);
|
|
211
|
-
let resizedBase64
|
|
212
|
-
if (matches) {
|
|
213
|
-
const mimeType = matches[1]; // Extract MIME type
|
|
214
|
-
resizedBase64 = canvas.toDataURL(mimeType, quality); // default is image/png
|
|
215
|
-
|
|
216
|
-
} else {
|
|
217
|
-
resizedBase64 = canvas.toDataURL(); // default is image/png
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
resolve(resizedBase64);
|
|
222
|
-
};
|
|
223
|
-
img.onerror = (err) => reject(err);
|
|
224
|
-
img.src = base64;
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Helper to load images with a timeout
|
|
229
|
-
export const loadImageWithTimeoutFallback = (src: string, timeout = 5000): Promise<any> => {
|
|
230
|
-
return new Promise((resolve, reject) => {
|
|
231
|
-
if (isNode) {
|
|
232
|
-
const { loadImage } = require('canvas');
|
|
233
|
-
if (src.startsWith('data:image/') || src.startsWith('data:img/')) {
|
|
234
|
-
const buffer = Buffer.from(src.split(',')[1], 'base64');
|
|
235
|
-
loadImage(buffer).then(resolve).catch(reject);
|
|
236
|
-
|
|
237
|
-
} else {
|
|
238
|
-
loadImage(src).then(resolve).catch(reject);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
} else {
|
|
242
|
-
const img = new Image();
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
293
|
-
|
|
1
|
+
import { ImageWrapper } from './polyfills/Image.ts';
|
|
2
|
+
|
|
3
|
+
const isNode = typeof window === 'undefined';
|
|
4
|
+
|
|
5
|
+
export function cropImage(imageUrl: string): Promise<string> {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
const img = new Image();
|
|
8
|
+
|
|
9
|
+
img.onload = function () {
|
|
10
|
+
const canvas = getCanvas(img.width, img.height)
|
|
11
|
+
const ctx = canvas.getContext("2d") as any;
|
|
12
|
+
if (!ctx) return reject(new Error("Failed to get canvas context"));
|
|
13
|
+
|
|
14
|
+
canvas.width = img.width;
|
|
15
|
+
canvas.height = img.height;
|
|
16
|
+
const realImage = ((img as any) as ImageWrapper).realImage? ((img as any) as ImageWrapper).realImage : img
|
|
17
|
+
ctx.drawImage(realImage, 0, 0);
|
|
18
|
+
|
|
19
|
+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
20
|
+
const data = imageData.data;
|
|
21
|
+
|
|
22
|
+
let minX = canvas.width;
|
|
23
|
+
let minY = canvas.height;
|
|
24
|
+
let maxX = 0;
|
|
25
|
+
let maxY = 0;
|
|
26
|
+
|
|
27
|
+
for (let y = 0; y < canvas.height; y++) {
|
|
28
|
+
for (let x = 0; x < canvas.width; x++) {
|
|
29
|
+
const index = (y * canvas.width + x) * 4;
|
|
30
|
+
if (data[index + 3] > 0) {
|
|
31
|
+
minX = Math.min(minX, x);
|
|
32
|
+
minY = Math.min(minY, y);
|
|
33
|
+
maxX = Math.max(maxX, x);
|
|
34
|
+
maxY = Math.max(maxY, y);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const cropWidth = maxX - minX;
|
|
40
|
+
const cropHeight = maxY - minY;
|
|
41
|
+
const croppedCanvas = getCanvas(cropWidth, cropHeight);
|
|
42
|
+
const croppedCtx = croppedCanvas.getContext("2d") as any;
|
|
43
|
+
if (!croppedCtx) return reject(new Error("Failed to get canvas context"));
|
|
44
|
+
|
|
45
|
+
croppedCanvas.width = cropWidth;
|
|
46
|
+
croppedCanvas.height = cropHeight;
|
|
47
|
+
croppedCtx.drawImage(canvas, minX, minY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
|
|
48
|
+
|
|
49
|
+
resolve(croppedCanvas.toDataURL("image/png"));
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
img.onerror = () => reject(new Error('Error loading image'));
|
|
53
|
+
img.src = imageUrl;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function isBlankImage(imageUrl: string): Promise<boolean> {
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const img = new Image();
|
|
60
|
+
img.onload = function () {
|
|
61
|
+
const canvas = getCanvas(img.width, img.height);
|
|
62
|
+
canvas.width = img.width;
|
|
63
|
+
canvas.height = img.height;
|
|
64
|
+
const ctx = canvas.getContext("2d") as any;
|
|
65
|
+
if (!ctx) return reject(new Error("Failed to get canvas context"));
|
|
66
|
+
const realImage = ((img as any) as ImageWrapper).realImage? ((img as any) as ImageWrapper).realImage : img
|
|
67
|
+
ctx.drawImage(realImage, 0, 0);
|
|
68
|
+
|
|
69
|
+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
|
|
70
|
+
resolve(imageData.every((value: any) => value === 0));
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
img.onerror = () => reject(new Error('Error loading image'));
|
|
74
|
+
img.src = imageUrl;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function getImageDimension(imageUrl: string): Promise<{ width: number; height: number }> {
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
const img = new Image();
|
|
81
|
+
img.onload = () => resolve({ width: img.width, height: img.height });
|
|
82
|
+
img.onerror = () => reject(new Error('Error loading image'));
|
|
83
|
+
img.src = imageUrl;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function imageToStamp(base64Image: string, color: [number, number, number], holePct: number): Promise<string | null> {
|
|
88
|
+
return new Promise((resolve, reject) => {
|
|
89
|
+
if (!base64Image) return resolve(null);
|
|
90
|
+
const img = new Image();
|
|
91
|
+
img.onload = function () {
|
|
92
|
+
const canvas = getCanvas(img.width, img.height);
|
|
93
|
+
const ctx = canvas.getContext("2d") as any;
|
|
94
|
+
if (!ctx) return reject(new Error("Failed to get canvas context"));
|
|
95
|
+
|
|
96
|
+
canvas.width = img.width;
|
|
97
|
+
canvas.height = img.height;
|
|
98
|
+
const realImage = ((img as any) as ImageWrapper).realImage? ((img as any) as ImageWrapper).realImage : img
|
|
99
|
+
ctx.drawImage(realImage, 0, 0);
|
|
100
|
+
|
|
101
|
+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
102
|
+
const data = imageData.data;
|
|
103
|
+
|
|
104
|
+
const numTransparentPixels = Math.floor((holePct / 100) * (canvas.width * canvas.height));
|
|
105
|
+
|
|
106
|
+
for (let i = 0; i < numTransparentPixels; i++) {
|
|
107
|
+
const x = Math.floor(Math.random() * canvas.width);
|
|
108
|
+
const y = Math.floor(Math.random() * canvas.height);
|
|
109
|
+
const index = (y * canvas.width + x) * 4;
|
|
110
|
+
data[index + 3] = 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
ctx.putImageData(imageData, 0, 0);
|
|
114
|
+
resolve(canvas.toDataURL("image/png"));
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
img.onerror = () => reject(new Error('Error loading image'));
|
|
118
|
+
img.src = base64Image;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function getImageColor(base64ImageUrl: string, useDominantColor = true): Promise<[number, number, number] | null> {
|
|
123
|
+
return new Promise((resolve, reject) => {
|
|
124
|
+
if (!base64ImageUrl) return resolve(null);
|
|
125
|
+
const img = new Image();
|
|
126
|
+
img.onload = function () {
|
|
127
|
+
const canvas = getCanvas(img.width, img.height);
|
|
128
|
+
const ctx = canvas.getContext("2d") as any;
|
|
129
|
+
if (!ctx) return reject(new Error("Failed to get canvas context"));
|
|
130
|
+
|
|
131
|
+
canvas.width = img.width;
|
|
132
|
+
canvas.height = img.height;
|
|
133
|
+
const realImage = ((img as any) as ImageWrapper).realImage? ((img as any) as ImageWrapper).realImage : img
|
|
134
|
+
ctx.drawImage(realImage, 0, 0);
|
|
135
|
+
|
|
136
|
+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
137
|
+
const data = imageData.data;
|
|
138
|
+
|
|
139
|
+
let totalRed = 0, totalGreen = 0, totalBlue = 0, totalPixels = 0;
|
|
140
|
+
const colorCount: Record<string, number> = {};
|
|
141
|
+
|
|
142
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
143
|
+
const red = data[i], green = data[i + 1], blue = data[i + 2];
|
|
144
|
+
totalRed += red;
|
|
145
|
+
totalGreen += green;
|
|
146
|
+
totalBlue += blue;
|
|
147
|
+
totalPixels++;
|
|
148
|
+
const colorKey = `${red},${green},${blue}`;
|
|
149
|
+
colorCount[colorKey] = (colorCount[colorKey] || 0) + 1;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const averageColor: [number, number, number] = [
|
|
153
|
+
Math.round(totalRed / totalPixels),
|
|
154
|
+
Math.round(totalGreen / totalPixels),
|
|
155
|
+
Math.round(totalBlue / totalPixels)
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
if (useDominantColor) {
|
|
159
|
+
let dominantColor: [number, number, number] = averageColor;
|
|
160
|
+
let maxCount = 0;
|
|
161
|
+
for (const colorKey in colorCount) {
|
|
162
|
+
if (colorCount[colorKey] > maxCount) {
|
|
163
|
+
dominantColor = colorKey.split(",").map(Number) as [number, number, number];
|
|
164
|
+
maxCount = colorCount[colorKey];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
resolve(dominantColor);
|
|
168
|
+
} else {
|
|
169
|
+
resolve(averageColor);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
img.onerror = () => reject(new Error('Error loading image'));
|
|
174
|
+
img.src = base64ImageUrl;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function reduceBase64Image(base64: string, targetWidth: number, quality=1) {
|
|
179
|
+
return new Promise((resolve, reject) => {
|
|
180
|
+
const img = new Image();
|
|
181
|
+
img.onload = () => {
|
|
182
|
+
const { width: originalWidth, height: originalHeight } = img;
|
|
183
|
+
|
|
184
|
+
// If the target width is greater than original, return original
|
|
185
|
+
if (targetWidth > originalWidth) {
|
|
186
|
+
return resolve(base64);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const aspectRatio = originalHeight / originalWidth;
|
|
190
|
+
let targetHeight = targetWidth * aspectRatio;
|
|
191
|
+
if(targetWidth == 0 || targetWidth == originalWidth) {
|
|
192
|
+
targetWidth = originalWidth
|
|
193
|
+
targetHeight = originalHeight;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const canvas = getCanvas(targetWidth, targetHeight);
|
|
197
|
+
//const canvas = document.createElement('canvas');
|
|
198
|
+
canvas.width = targetWidth;
|
|
199
|
+
canvas.height = targetHeight;
|
|
200
|
+
|
|
201
|
+
const ctx = canvas.getContext('2d') as any;
|
|
202
|
+
const realImage = ((img as any) as ImageWrapper).realImage? ((img as any) as ImageWrapper).realImage : img
|
|
203
|
+
|
|
204
|
+
if(targetWidth == 0 || targetHeight == 0 || !realImage) {
|
|
205
|
+
console.log("invimg:2", targetWidth, targetHeight, realImage)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
ctx.drawImage(realImage, 0, 0, targetWidth, targetHeight);
|
|
209
|
+
|
|
210
|
+
const matches = base64.match(/^data:(.+);base64,(.+)$/);
|
|
211
|
+
let resizedBase64
|
|
212
|
+
if (matches) {
|
|
213
|
+
const mimeType = matches[1]; // Extract MIME type
|
|
214
|
+
resizedBase64 = canvas.toDataURL(mimeType, quality); // default is image/png
|
|
215
|
+
|
|
216
|
+
} else {
|
|
217
|
+
resizedBase64 = canvas.toDataURL(); // default is image/png
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
resolve(resizedBase64);
|
|
222
|
+
};
|
|
223
|
+
img.onerror = (err) => reject(err);
|
|
224
|
+
img.src = base64;
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Helper to load images with a timeout
|
|
229
|
+
export const loadImageWithTimeoutFallback = (src: string, timeout = 5000): Promise<any> => {
|
|
230
|
+
return new Promise((resolve, reject) => {
|
|
231
|
+
if (isNode) {
|
|
232
|
+
const { loadImage } = require('canvas');
|
|
233
|
+
if (src.startsWith('data:image/') || src.startsWith('data:img/')) {
|
|
234
|
+
const buffer = Buffer.from(src.split(',')[1], 'base64');
|
|
235
|
+
loadImage(buffer).then(resolve).catch(reject);
|
|
236
|
+
|
|
237
|
+
} else {
|
|
238
|
+
loadImage(src).then(resolve).catch(reject);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
} else {
|
|
242
|
+
const img = new Image();
|
|
243
|
+
if (!src.startsWith('data:')) img.crossOrigin = "anonymous";
|
|
244
|
+
const timer = setTimeout(() => {
|
|
245
|
+
reject(new Error(`Image load timeout: ${src}`));
|
|
246
|
+
}, timeout);
|
|
247
|
+
|
|
248
|
+
img.onload = () => {
|
|
249
|
+
clearTimeout(timer);
|
|
250
|
+
const realImage = ((img as any) as ImageWrapper).realImage? ((img as any) as ImageWrapper).realImage : img
|
|
251
|
+
resolve(realImage);
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
img.onerror = (error: any) => {
|
|
255
|
+
clearTimeout(timer);
|
|
256
|
+
reject(new Error(`Error loading image: ${src.substring(0, 30)}`, error));
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
img.src = src;
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
export const loadImageWithTimeout = (src: string, timeout = 5000): Promise<any> => {
|
|
265
|
+
return new Promise((resolve, reject) => {
|
|
266
|
+
const img = new Image();
|
|
267
|
+
if (!isNode && !src.startsWith('data:')) img.crossOrigin = "anonymous";
|
|
268
|
+
const timer = setTimeout(() => {
|
|
269
|
+
reject(new Error(`Image load timeout: ${src}`));
|
|
270
|
+
}, timeout);
|
|
271
|
+
|
|
272
|
+
img.onload = () => {
|
|
273
|
+
clearTimeout(timer);
|
|
274
|
+
const realImage = ((img as any) as ImageWrapper).realImage? ((img as any) as ImageWrapper).realImage : img
|
|
275
|
+
resolve(realImage);
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
img.onerror = (error: any) => {
|
|
279
|
+
clearTimeout(timer);
|
|
280
|
+
reject(new Error(`Error loading image: ${src.substring(0, 30)}`, error));
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
img.src = src;
|
|
284
|
+
});
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
export const getCanvas = (width: number, height: number) => {
|
|
288
|
+
if(isNode) {
|
|
289
|
+
const { createCanvas } = require('canvas');
|
|
290
|
+
return createCanvas(width, height)
|
|
291
|
+
|
|
292
|
+
} else {
|
|
293
|
+
return document.createElement('canvas')
|
|
294
|
+
}
|
|
295
|
+
}
|