senangwebs-photobooth 1.0.2 → 2.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.
Files changed (43) hide show
  1. package/README.md +220 -236
  2. package/dist/swp.css +790 -256
  3. package/dist/swp.js +1 -1
  4. package/examples/data-attribute.html +69 -0
  5. package/examples/index.html +56 -51
  6. package/examples/studio.html +83 -0
  7. package/package.json +10 -6
  8. package/src/css/swp.css +790 -256
  9. package/src/js/core/Canvas.js +398 -0
  10. package/src/js/core/EventEmitter.js +188 -0
  11. package/src/js/core/History.js +250 -0
  12. package/src/js/core/Keyboard.js +323 -0
  13. package/src/js/filters/FilterManager.js +248 -0
  14. package/src/js/index.js +48 -0
  15. package/src/js/io/Clipboard.js +52 -0
  16. package/src/js/io/FileManager.js +150 -0
  17. package/src/js/layers/BlendModes.js +342 -0
  18. package/src/js/layers/Layer.js +415 -0
  19. package/src/js/layers/LayerManager.js +459 -0
  20. package/src/js/selection/Selection.js +167 -0
  21. package/src/js/swp.js +247 -761
  22. package/src/js/tools/BaseTool.js +264 -0
  23. package/src/js/tools/BrushTool.js +314 -0
  24. package/src/js/tools/CropTool.js +400 -0
  25. package/src/js/tools/EraserTool.js +155 -0
  26. package/src/js/tools/EyedropperTool.js +184 -0
  27. package/src/js/tools/FillTool.js +109 -0
  28. package/src/js/tools/GradientTool.js +141 -0
  29. package/src/js/tools/HandTool.js +51 -0
  30. package/src/js/tools/MarqueeTool.js +103 -0
  31. package/src/js/tools/MoveTool.js +465 -0
  32. package/src/js/tools/ShapeTool.js +285 -0
  33. package/src/js/tools/TextTool.js +253 -0
  34. package/src/js/tools/ToolManager.js +277 -0
  35. package/src/js/tools/ZoomTool.js +68 -0
  36. package/src/js/ui/ColorManager.js +71 -0
  37. package/src/js/ui/UI.js +1211 -0
  38. package/swp_preview1.png +0 -0
  39. package/swp_preview2.png +0 -0
  40. package/webpack.config.js +4 -11
  41. package/dist/styles.js +0 -1
  42. package/examples/customization.html +0 -360
  43. package/swp_preview.png +0 -0
@@ -0,0 +1,342 @@
1
+ /**
2
+ * SenangWebs Studio - Blend Modes
3
+ * Custom blend mode implementations for pixel-level operations
4
+ * @version 2.0.0
5
+ */
6
+
7
+ /**
8
+ * Blend mode functions
9
+ * Each function takes two values (base and blend) and returns the result
10
+ * Values are normalized to 0-1 range
11
+ */
12
+ export const BlendModes = {
13
+ // Normal
14
+ normal: (base, blend) => blend,
15
+
16
+ // Darken modes
17
+ darken: (base, blend) => Math.min(base, blend),
18
+
19
+ multiply: (base, blend) => base * blend,
20
+
21
+ colorBurn: (base, blend) => {
22
+ if (blend === 0) return 0;
23
+ return Math.max(0, 1 - (1 - base) / blend);
24
+ },
25
+
26
+ linearBurn: (base, blend) => Math.max(0, base + blend - 1),
27
+
28
+ // Lighten modes
29
+ lighten: (base, blend) => Math.max(base, blend),
30
+
31
+ screen: (base, blend) => 1 - (1 - base) * (1 - blend),
32
+
33
+ colorDodge: (base, blend) => {
34
+ if (blend === 1) return 1;
35
+ return Math.min(1, base / (1 - blend));
36
+ },
37
+
38
+ linearDodge: (base, blend) => Math.min(1, base + blend),
39
+
40
+ // Contrast modes
41
+ overlay: (base, blend) => {
42
+ return base < 0.5
43
+ ? 2 * base * blend
44
+ : 1 - 2 * (1 - base) * (1 - blend);
45
+ },
46
+
47
+ softLight: (base, blend) => {
48
+ if (blend < 0.5) {
49
+ return base - (1 - 2 * blend) * base * (1 - base);
50
+ }
51
+ const d = base <= 0.25
52
+ ? ((16 * base - 12) * base + 4) * base
53
+ : Math.sqrt(base);
54
+ return base + (2 * blend - 1) * (d - base);
55
+ },
56
+
57
+ hardLight: (base, blend) => {
58
+ return blend < 0.5
59
+ ? 2 * base * blend
60
+ : 1 - 2 * (1 - base) * (1 - blend);
61
+ },
62
+
63
+ vividLight: (base, blend) => {
64
+ if (blend < 0.5) {
65
+ return blend === 0 ? 0 : Math.max(0, 1 - (1 - base) / (2 * blend));
66
+ }
67
+ return blend === 1 ? 1 : Math.min(1, base / (2 * (1 - blend)));
68
+ },
69
+
70
+ linearLight: (base, blend) => {
71
+ return Math.max(0, Math.min(1, base + 2 * blend - 1));
72
+ },
73
+
74
+ pinLight: (base, blend) => {
75
+ if (blend < 0.5) {
76
+ return Math.min(base, 2 * blend);
77
+ }
78
+ return Math.max(base, 2 * blend - 1);
79
+ },
80
+
81
+ hardMix: (base, blend) => {
82
+ return (base + blend >= 1) ? 1 : 0;
83
+ },
84
+
85
+ // Inversion modes
86
+ difference: (base, blend) => Math.abs(base - blend),
87
+
88
+ exclusion: (base, blend) => base + blend - 2 * base * blend,
89
+
90
+ subtract: (base, blend) => Math.max(0, base - blend),
91
+
92
+ divide: (base, blend) => {
93
+ if (blend === 0) return 1;
94
+ return Math.min(1, base / blend);
95
+ },
96
+
97
+ // Component modes (require special handling for HSL)
98
+ hue: null, // Handled separately
99
+ saturation: null,
100
+ color: null,
101
+ luminosity: null
102
+ };
103
+
104
+ /**
105
+ * Apply blend mode to two image data arrays
106
+ * @param {ImageData} baseData - Base image data
107
+ * @param {ImageData} blendData - Blend image data
108
+ * @param {string} mode - Blend mode name
109
+ * @param {number} opacity - Opacity (0-1)
110
+ * @returns {ImageData}
111
+ */
112
+ export function applyBlendMode(baseData, blendData, mode, opacity = 1) {
113
+ const base = baseData.data;
114
+ const blend = blendData.data;
115
+ const result = new Uint8ClampedArray(base.length);
116
+
117
+ const blendFn = BlendModes[mode];
118
+
119
+ if (!blendFn) {
120
+ // Handle HSL-based blend modes
121
+ if (['hue', 'saturation', 'color', 'luminosity'].includes(mode)) {
122
+ return applyHSLBlendMode(baseData, blendData, mode, opacity);
123
+ }
124
+ // Default to normal
125
+ for (let i = 0; i < base.length; i += 4) {
126
+ const alpha = (blend[i + 3] / 255) * opacity;
127
+ result[i] = base[i] * (1 - alpha) + blend[i] * alpha;
128
+ result[i + 1] = base[i + 1] * (1 - alpha) + blend[i + 1] * alpha;
129
+ result[i + 2] = base[i + 2] * (1 - alpha) + blend[i + 2] * alpha;
130
+ result[i + 3] = Math.min(255, base[i + 3] + blend[i + 3] * opacity);
131
+ }
132
+ return new ImageData(result, baseData.width, baseData.height);
133
+ }
134
+
135
+ for (let i = 0; i < base.length; i += 4) {
136
+ const blendAlpha = (blend[i + 3] / 255) * opacity;
137
+
138
+ if (blendAlpha === 0) {
139
+ result[i] = base[i];
140
+ result[i + 1] = base[i + 1];
141
+ result[i + 2] = base[i + 2];
142
+ result[i + 3] = base[i + 3];
143
+ continue;
144
+ }
145
+
146
+ // Apply blend function to each channel
147
+ for (let c = 0; c < 3; c++) {
148
+ const baseVal = base[i + c] / 255;
149
+ const blendVal = blend[i + c] / 255;
150
+ const blended = blendFn(baseVal, blendVal);
151
+
152
+ // Mix based on blend alpha
153
+ const mixed = baseVal * (1 - blendAlpha) + blended * blendAlpha;
154
+ result[i + c] = Math.round(mixed * 255);
155
+ }
156
+
157
+ // Combine alpha
158
+ result[i + 3] = Math.min(255, base[i + 3] + blend[i + 3] * opacity);
159
+ }
160
+
161
+ return new ImageData(result, baseData.width, baseData.height);
162
+ }
163
+
164
+ /**
165
+ * Apply HSL-based blend mode
166
+ * @param {ImageData} baseData - Base image data
167
+ * @param {ImageData} blendData - Blend image data
168
+ * @param {string} mode - Blend mode (hue, saturation, color, luminosity)
169
+ * @param {number} opacity - Opacity (0-1)
170
+ * @returns {ImageData}
171
+ */
172
+ function applyHSLBlendMode(baseData, blendData, mode, opacity) {
173
+ const base = baseData.data;
174
+ const blend = blendData.data;
175
+ const result = new Uint8ClampedArray(base.length);
176
+
177
+ for (let i = 0; i < base.length; i += 4) {
178
+ const blendAlpha = (blend[i + 3] / 255) * opacity;
179
+
180
+ if (blendAlpha === 0) {
181
+ result[i] = base[i];
182
+ result[i + 1] = base[i + 1];
183
+ result[i + 2] = base[i + 2];
184
+ result[i + 3] = base[i + 3];
185
+ continue;
186
+ }
187
+
188
+ const baseHSL = rgbToHsl(base[i], base[i + 1], base[i + 2]);
189
+ const blendHSL = rgbToHsl(blend[i], blend[i + 1], blend[i + 2]);
190
+
191
+ let resultHSL;
192
+ switch (mode) {
193
+ case 'hue':
194
+ resultHSL = [blendHSL[0], baseHSL[1], baseHSL[2]];
195
+ break;
196
+ case 'saturation':
197
+ resultHSL = [baseHSL[0], blendHSL[1], baseHSL[2]];
198
+ break;
199
+ case 'color':
200
+ resultHSL = [blendHSL[0], blendHSL[1], baseHSL[2]];
201
+ break;
202
+ case 'luminosity':
203
+ resultHSL = [baseHSL[0], baseHSL[1], blendHSL[2]];
204
+ break;
205
+ default:
206
+ resultHSL = baseHSL;
207
+ }
208
+
209
+ const rgb = hslToRgb(...resultHSL);
210
+
211
+ // Mix based on blend alpha
212
+ result[i] = Math.round(base[i] * (1 - blendAlpha) + rgb[0] * blendAlpha);
213
+ result[i + 1] = Math.round(base[i + 1] * (1 - blendAlpha) + rgb[1] * blendAlpha);
214
+ result[i + 2] = Math.round(base[i + 2] * (1 - blendAlpha) + rgb[2] * blendAlpha);
215
+ result[i + 3] = Math.min(255, base[i + 3] + blend[i + 3] * opacity);
216
+ }
217
+
218
+ return new ImageData(result, baseData.width, baseData.height);
219
+ }
220
+
221
+ /**
222
+ * Convert RGB to HSL
223
+ * @param {number} r - Red (0-255)
224
+ * @param {number} g - Green (0-255)
225
+ * @param {number} b - Blue (0-255)
226
+ * @returns {number[]} [h, s, l] (0-1 range)
227
+ */
228
+ function rgbToHsl(r, g, b) {
229
+ r /= 255;
230
+ g /= 255;
231
+ b /= 255;
232
+
233
+ const max = Math.max(r, g, b);
234
+ const min = Math.min(r, g, b);
235
+ const l = (max + min) / 2;
236
+
237
+ if (max === min) {
238
+ return [0, 0, l];
239
+ }
240
+
241
+ const d = max - min;
242
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
243
+
244
+ let h;
245
+ switch (max) {
246
+ case r:
247
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
248
+ break;
249
+ case g:
250
+ h = ((b - r) / d + 2) / 6;
251
+ break;
252
+ case b:
253
+ h = ((r - g) / d + 4) / 6;
254
+ break;
255
+ }
256
+
257
+ return [h, s, l];
258
+ }
259
+
260
+ /**
261
+ * Convert HSL to RGB
262
+ * @param {number} h - Hue (0-1)
263
+ * @param {number} s - Saturation (0-1)
264
+ * @param {number} l - Lightness (0-1)
265
+ * @returns {number[]} [r, g, b] (0-255 range)
266
+ */
267
+ function hslToRgb(h, s, l) {
268
+ if (s === 0) {
269
+ const v = Math.round(l * 255);
270
+ return [v, v, v];
271
+ }
272
+
273
+ const hue2rgb = (p, q, t) => {
274
+ if (t < 0) t += 1;
275
+ if (t > 1) t -= 1;
276
+ if (t < 1/6) return p + (q - p) * 6 * t;
277
+ if (t < 1/2) return q;
278
+ if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
279
+ return p;
280
+ };
281
+
282
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
283
+ const p = 2 * l - q;
284
+
285
+ return [
286
+ Math.round(hue2rgb(p, q, h + 1/3) * 255),
287
+ Math.round(hue2rgb(p, q, h) * 255),
288
+ Math.round(hue2rgb(p, q, h - 1/3) * 255)
289
+ ];
290
+ }
291
+
292
+ /**
293
+ * Get list of available blend modes
294
+ * @returns {Object[]} Blend mode options
295
+ */
296
+ export function getBlendModeList() {
297
+ return [
298
+ { group: 'Normal', modes: ['normal'] },
299
+ { group: 'Darken', modes: ['darken', 'multiply', 'colorBurn', 'linearBurn'] },
300
+ { group: 'Lighten', modes: ['lighten', 'screen', 'colorDodge', 'linearDodge'] },
301
+ { group: 'Contrast', modes: ['overlay', 'softLight', 'hardLight', 'vividLight', 'linearLight', 'pinLight', 'hardMix'] },
302
+ { group: 'Inversion', modes: ['difference', 'exclusion', 'subtract', 'divide'] },
303
+ { group: 'Component', modes: ['hue', 'saturation', 'color', 'luminosity'] }
304
+ ];
305
+ }
306
+
307
+ /**
308
+ * Get human-readable blend mode name
309
+ * @param {string} mode - Blend mode key
310
+ * @returns {string} Human-readable name
311
+ */
312
+ export function getBlendModeName(mode) {
313
+ const names = {
314
+ normal: 'Normal',
315
+ darken: 'Darken',
316
+ multiply: 'Multiply',
317
+ colorBurn: 'Color Burn',
318
+ linearBurn: 'Linear Burn',
319
+ lighten: 'Lighten',
320
+ screen: 'Screen',
321
+ colorDodge: 'Color Dodge',
322
+ linearDodge: 'Linear Dodge (Add)',
323
+ overlay: 'Overlay',
324
+ softLight: 'Soft Light',
325
+ hardLight: 'Hard Light',
326
+ vividLight: 'Vivid Light',
327
+ linearLight: 'Linear Light',
328
+ pinLight: 'Pin Light',
329
+ hardMix: 'Hard Mix',
330
+ difference: 'Difference',
331
+ exclusion: 'Exclusion',
332
+ subtract: 'Subtract',
333
+ divide: 'Divide',
334
+ hue: 'Hue',
335
+ saturation: 'Saturation',
336
+ color: 'Color',
337
+ luminosity: 'Luminosity'
338
+ };
339
+ return names[mode] || mode;
340
+ }
341
+
342
+ export default BlendModes;