drp-imagesdk 2.0.0

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 (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +665 -0
  3. package/dist/adjustments.d.ts +3 -0
  4. package/dist/adjustments.d.ts.map +1 -0
  5. package/dist/adjustments.js +57 -0
  6. package/dist/adjustments.js.map +1 -0
  7. package/dist/batch.d.ts +27 -0
  8. package/dist/batch.d.ts.map +1 -0
  9. package/dist/batch.js +99 -0
  10. package/dist/batch.js.map +1 -0
  11. package/dist/compress.d.ts +11 -0
  12. package/dist/compress.d.ts.map +1 -0
  13. package/dist/compress.js +273 -0
  14. package/dist/compress.js.map +1 -0
  15. package/dist/convert.d.ts +8 -0
  16. package/dist/convert.d.ts.map +1 -0
  17. package/dist/convert.js +116 -0
  18. package/dist/convert.js.map +1 -0
  19. package/dist/crop.d.ts +4 -0
  20. package/dist/crop.d.ts.map +1 -0
  21. package/dist/crop.js +32 -0
  22. package/dist/crop.js.map +1 -0
  23. package/dist/drp-class.d.ts +58 -0
  24. package/dist/drp-class.d.ts.map +1 -0
  25. package/dist/drp-class.js +191 -0
  26. package/dist/drp-class.js.map +1 -0
  27. package/dist/drp.d.ts +14 -0
  28. package/dist/drp.d.ts.map +1 -0
  29. package/dist/drp.js +23 -0
  30. package/dist/drp.js.map +1 -0
  31. package/dist/editor.d.ts +24 -0
  32. package/dist/editor.d.ts.map +1 -0
  33. package/dist/editor.js +153 -0
  34. package/dist/editor.js.map +1 -0
  35. package/dist/filters.d.ts +19 -0
  36. package/dist/filters.d.ts.map +1 -0
  37. package/dist/filters.js +207 -0
  38. package/dist/filters.js.map +1 -0
  39. package/dist/image-sdk.iife.js +1979 -0
  40. package/dist/image-sdk.iife.js.map +1 -0
  41. package/dist/image-sdk.min.js +1978 -0
  42. package/dist/image-sdk.umd.js +1980 -0
  43. package/dist/image-sdk.umd.js.map +1 -0
  44. package/dist/index.d.ts +25 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +19 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/layers.d.ts +12 -0
  49. package/dist/layers.d.ts.map +1 -0
  50. package/dist/layers.js +174 -0
  51. package/dist/layers.js.map +1 -0
  52. package/dist/loader.d.ts +10 -0
  53. package/dist/loader.d.ts.map +1 -0
  54. package/dist/loader.js +114 -0
  55. package/dist/loader.js.map +1 -0
  56. package/dist/metadata.d.ts +6 -0
  57. package/dist/metadata.d.ts.map +1 -0
  58. package/dist/metadata.js +127 -0
  59. package/dist/metadata.js.map +1 -0
  60. package/dist/presets.d.ts +11 -0
  61. package/dist/presets.d.ts.map +1 -0
  62. package/dist/presets.js +28 -0
  63. package/dist/presets.js.map +1 -0
  64. package/dist/resize.d.ts +4 -0
  65. package/dist/resize.d.ts.map +1 -0
  66. package/dist/resize.js +68 -0
  67. package/dist/resize.js.map +1 -0
  68. package/dist/transform.d.ts +6 -0
  69. package/dist/transform.d.ts.map +1 -0
  70. package/dist/transform.js +94 -0
  71. package/dist/transform.js.map +1 -0
  72. package/dist/types.d.ts +153 -0
  73. package/dist/types.d.ts.map +1 -0
  74. package/dist/types.js +2 -0
  75. package/dist/types.js.map +1 -0
  76. package/dist/worker.d.ts +8 -0
  77. package/dist/worker.d.ts.map +1 -0
  78. package/dist/worker.js +124 -0
  79. package/dist/worker.js.map +1 -0
  80. package/dist/zoom.d.ts +14 -0
  81. package/dist/zoom.d.ts.map +1 -0
  82. package/dist/zoom.js +55 -0
  83. package/dist/zoom.js.map +1 -0
  84. package/package.json +92 -0
@@ -0,0 +1,1978 @@
1
+ var DRP = (function (exports) {
2
+ 'use strict';
3
+
4
+ const VERSION = '1.0.0';
5
+
6
+ const FORMAT_SIGNATURES = {
7
+ jpeg: new Uint8Array([0xff, 0xd8, 0xff]),
8
+ png: new Uint8Array([0x89, 0x50, 0x4e, 0x47]),
9
+ webp: new Uint8Array([0x52, 0x49, 0x46, 0x46]),
10
+ gif: new Uint8Array([0x47, 0x49, 0x46]),
11
+ bmp: new Uint8Array([0x42, 0x4d]),
12
+ };
13
+ function validateFile(file, maxSize) {
14
+ if (!file || !(file instanceof File)) {
15
+ return maxSize !== undefined
16
+ ? { valid: false, size: 0, maxSize, message: 'Not a valid File object' }
17
+ : false;
18
+ }
19
+ if (file.size === 0) {
20
+ return maxSize !== undefined
21
+ ? { valid: false, size: 0, maxSize, message: 'File is empty' }
22
+ : false;
23
+ }
24
+ const validTypes = [
25
+ 'image/jpeg',
26
+ 'image/png',
27
+ 'image/webp',
28
+ 'image/gif',
29
+ 'image/bmp',
30
+ 'image/avif',
31
+ ];
32
+ if (!validTypes.includes(file.type)) {
33
+ return maxSize !== undefined
34
+ ? { valid: false, size: file.size, maxSize, message: `Unsupported file type: ${file.type}` }
35
+ : false;
36
+ }
37
+ if (maxSize !== undefined) {
38
+ const valid = file.size <= maxSize;
39
+ return {
40
+ valid,
41
+ size: file.size,
42
+ maxSize,
43
+ message: valid
44
+ ? 'File validation passed'
45
+ : `File size exceeds maximum allowed size`,
46
+ };
47
+ }
48
+ return true;
49
+ }
50
+ async function detectFormat(file) {
51
+ const buffer = await file.slice(0, 12).arrayBuffer();
52
+ const bytes = new Uint8Array(buffer);
53
+ for (const [format, signature] of Object.entries(FORMAT_SIGNATURES)) {
54
+ if (signature.every((byte, i) => bytes[i] === byte)) {
55
+ if (format === 'webp' && bytes[8] === 0x57 && bytes[9] === 0x45) {
56
+ return 'webp';
57
+ }
58
+ if (format === 'webp' && bytes[8] === 0x41 && bytes[9] === 0x56) {
59
+ return 'avif';
60
+ }
61
+ return format;
62
+ }
63
+ }
64
+ throw new Error('Unable to detect image format');
65
+ }
66
+ async function loadImage(source) {
67
+ return new Promise((resolve, reject) => {
68
+ const img = new Image();
69
+ img.crossOrigin = 'anonymous';
70
+ img.onload = () => {
71
+ URL.revokeObjectURL(img.src);
72
+ resolve(img);
73
+ };
74
+ img.onerror = () => {
75
+ reject(new Error('Failed to load image'));
76
+ };
77
+ if (typeof source === 'string') {
78
+ img.src = source;
79
+ }
80
+ else {
81
+ if (!validateFile(source)) {
82
+ reject(new Error('Invalid file'));
83
+ return;
84
+ }
85
+ img.src = URL.createObjectURL(source);
86
+ }
87
+ });
88
+ }
89
+ async function getImageInfo(file) {
90
+ const format = await detectFormat(file);
91
+ const img = await loadImage(file);
92
+ return {
93
+ width: img.width,
94
+ height: img.height,
95
+ format,
96
+ size: file.size,
97
+ };
98
+ }
99
+ function imageToCanvas(img) {
100
+ const canvas = document.createElement('canvas');
101
+ canvas.width = img.width;
102
+ canvas.height = img.height;
103
+ const ctx = canvas.getContext('2d');
104
+ ctx.drawImage(img, 0, 0);
105
+ return canvas;
106
+ }
107
+ function canvasToImageData(canvas) {
108
+ const ctx = canvas.getContext('2d');
109
+ return ctx.getImageData(0, 0, canvas.width, canvas.height);
110
+ }
111
+ function imageDataToCanvas(imageData) {
112
+ const canvas = document.createElement('canvas');
113
+ canvas.width = imageData.width;
114
+ canvas.height = imageData.height;
115
+ const ctx = canvas.getContext('2d');
116
+ ctx.putImageData(new window.ImageData(imageData.data, imageData.width, imageData.height), 0, 0);
117
+ return canvas;
118
+ }
119
+
120
+ async function cropImage(source, options) {
121
+ let img;
122
+ if (typeof source === 'string' || source instanceof File) {
123
+ img = await loadImage(source);
124
+ }
125
+ else if (source instanceof HTMLImageElement) {
126
+ img = source;
127
+ }
128
+ else {
129
+ img = new Image();
130
+ img.src = source.toDataURL();
131
+ await new Promise((resolve) => {
132
+ img.onload = () => resolve();
133
+ });
134
+ }
135
+ const canvas = document.createElement('canvas');
136
+ canvas.width = options.width;
137
+ canvas.height = options.height;
138
+ const ctx = canvas.getContext('2d');
139
+ ctx.drawImage(img, options.x, options.y, options.width, options.height, 0, 0, options.width, options.height);
140
+ return canvas;
141
+ }
142
+ function cropCanvas(canvas, options) {
143
+ const cropped = document.createElement('canvas');
144
+ cropped.width = options.width;
145
+ cropped.height = options.height;
146
+ const ctx = cropped.getContext('2d');
147
+ ctx.drawImage(canvas, options.x, options.y, options.width, options.height, 0, 0, options.width, options.height);
148
+ return cropped;
149
+ }
150
+
151
+ function calculateDimensions(originalWidth, originalHeight, options) {
152
+ let { width, height } = options;
153
+ const maintainAspectRatio = options.maintainAspectRatio ?? true;
154
+ if (!width && !height) {
155
+ return { width: originalWidth, height: originalHeight };
156
+ }
157
+ if (maintainAspectRatio) {
158
+ const aspectRatio = originalWidth / originalHeight;
159
+ if (width && !height) {
160
+ height = Math.round(width / aspectRatio);
161
+ }
162
+ else if (!width && height) {
163
+ width = Math.round(height * aspectRatio);
164
+ }
165
+ else {
166
+ const targetRatio = width / height;
167
+ if (targetRatio > aspectRatio) {
168
+ width = Math.round(height * aspectRatio);
169
+ }
170
+ else {
171
+ height = Math.round(width / aspectRatio);
172
+ }
173
+ }
174
+ }
175
+ else {
176
+ width = width ?? originalWidth;
177
+ height = height ?? originalHeight;
178
+ }
179
+ return { width: width, height: height };
180
+ }
181
+ async function resizeImage(source, options) {
182
+ let img;
183
+ if (typeof source === 'string' || source instanceof File) {
184
+ img = await loadImage(source);
185
+ }
186
+ else if (source instanceof HTMLImageElement) {
187
+ img = source;
188
+ }
189
+ else {
190
+ img = new Image();
191
+ img.src = source.toDataURL();
192
+ await new Promise((resolve) => {
193
+ img.onload = () => resolve();
194
+ });
195
+ }
196
+ const { width, height } = calculateDimensions(img.width, img.height, options);
197
+ const canvas = document.createElement('canvas');
198
+ canvas.width = width;
199
+ canvas.height = height;
200
+ const ctx = canvas.getContext('2d');
201
+ ctx.imageSmoothingEnabled = true;
202
+ ctx.imageSmoothingQuality = 'high';
203
+ ctx.drawImage(img, 0, 0, width, height);
204
+ return canvas;
205
+ }
206
+ function resizeCanvas(canvas, options) {
207
+ const { width, height } = calculateDimensions(canvas.width, canvas.height, options);
208
+ const resized = document.createElement('canvas');
209
+ resized.width = width;
210
+ resized.height = height;
211
+ const ctx = resized.getContext('2d');
212
+ ctx.imageSmoothingEnabled = true;
213
+ ctx.imageSmoothingQuality = 'high';
214
+ ctx.drawImage(canvas, 0, 0, width, height);
215
+ return resized;
216
+ }
217
+
218
+ async function rotateImage(source, options) {
219
+ let canvas;
220
+ if (typeof source === 'string' || source instanceof File) {
221
+ const img = await loadImage(source);
222
+ canvas = imageToCanvas(img);
223
+ }
224
+ else if (source instanceof HTMLImageElement) {
225
+ canvas = imageToCanvas(source);
226
+ }
227
+ else {
228
+ canvas = source;
229
+ }
230
+ const { degrees, backgroundColor = 'transparent' } = options;
231
+ const radians = (degrees * Math.PI) / 180;
232
+ const sin = Math.abs(Math.sin(radians));
233
+ const cos = Math.abs(Math.cos(radians));
234
+ const newWidth = Math.round(canvas.width * cos + canvas.height * sin);
235
+ const newHeight = Math.round(canvas.width * sin + canvas.height * cos);
236
+ const rotated = document.createElement('canvas');
237
+ rotated.width = newWidth;
238
+ rotated.height = newHeight;
239
+ const ctx = rotated.getContext('2d');
240
+ ctx.fillStyle = backgroundColor;
241
+ ctx.fillRect(0, 0, newWidth, newHeight);
242
+ ctx.translate(newWidth / 2, newHeight / 2);
243
+ ctx.rotate(radians);
244
+ ctx.drawImage(canvas, -canvas.width / 2, -canvas.height / 2);
245
+ return rotated;
246
+ }
247
+ async function flipImage(source, options) {
248
+ let canvas;
249
+ if (typeof source === 'string' || source instanceof File) {
250
+ const img = await loadImage(source);
251
+ canvas = imageToCanvas(img);
252
+ }
253
+ else if (source instanceof HTMLImageElement) {
254
+ canvas = imageToCanvas(source);
255
+ }
256
+ else {
257
+ canvas = source;
258
+ }
259
+ const { horizontal = false, vertical = false } = options;
260
+ const flipped = document.createElement('canvas');
261
+ flipped.width = canvas.width;
262
+ flipped.height = canvas.height;
263
+ const ctx = flipped.getContext('2d');
264
+ if (horizontal) {
265
+ ctx.translate(canvas.width, 0);
266
+ ctx.scale(-1, 1);
267
+ }
268
+ if (vertical) {
269
+ ctx.translate(0, canvas.height);
270
+ ctx.scale(1, -1);
271
+ }
272
+ ctx.drawImage(canvas, 0, 0);
273
+ return flipped;
274
+ }
275
+ function rotateCanvas(canvas, options) {
276
+ const { degrees, backgroundColor = 'transparent' } = options;
277
+ const radians = (degrees * Math.PI) / 180;
278
+ const sin = Math.abs(Math.sin(radians));
279
+ const cos = Math.abs(Math.cos(radians));
280
+ const newWidth = Math.round(canvas.width * cos + canvas.height * sin);
281
+ const newHeight = Math.round(canvas.width * sin + canvas.height * cos);
282
+ const rotated = document.createElement('canvas');
283
+ rotated.width = newWidth;
284
+ rotated.height = newHeight;
285
+ const ctx = rotated.getContext('2d');
286
+ ctx.fillStyle = backgroundColor;
287
+ ctx.fillRect(0, 0, newWidth, newHeight);
288
+ ctx.translate(newWidth / 2, newHeight / 2);
289
+ ctx.rotate(radians);
290
+ ctx.drawImage(canvas, -canvas.width / 2, -canvas.height / 2);
291
+ return rotated;
292
+ }
293
+ function flipCanvas(canvas, options) {
294
+ const { horizontal = false, vertical = false } = options;
295
+ const flipped = document.createElement('canvas');
296
+ flipped.width = canvas.width;
297
+ flipped.height = canvas.height;
298
+ const ctx = flipped.getContext('2d');
299
+ if (horizontal) {
300
+ ctx.translate(canvas.width, 0);
301
+ ctx.scale(-1, 1);
302
+ }
303
+ if (vertical) {
304
+ ctx.translate(0, canvas.height);
305
+ ctx.scale(1, -1);
306
+ }
307
+ ctx.drawImage(canvas, 0, 0);
308
+ return flipped;
309
+ }
310
+
311
+ function applyAdjustments(canvas, adjustments) {
312
+ const ctx = canvas.getContext('2d');
313
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
314
+ const data = imageData.data;
315
+ let { brightness = 0, contrast = 0, saturation = 0, temperature = 0, tint = 0, grayscale = false, sepia = false, } = adjustments;
316
+ const brightnessValue = brightness / 100;
317
+ const contrastValue = (contrast + 100) / 100;
318
+ const saturationValue = (saturation + 100) / 100;
319
+ for (let i = 0; i < data.length; i += 4) {
320
+ let r = data[i];
321
+ let g = data[i + 1];
322
+ let b = data[i + 2];
323
+ r = r + brightnessValue * 255;
324
+ g = g + brightnessValue * 255;
325
+ b = b + brightnessValue * 255;
326
+ r = ((r / 255 - 0.5) * contrastValue + 0.5) * 255;
327
+ g = ((g / 255 - 0.5) * contrastValue + 0.5) * 255;
328
+ b = ((b / 255 - 0.5) * contrastValue + 0.5) * 255;
329
+ const gray = 0.2126 * r + 0.7152 * g + 0.0722 * b;
330
+ r = r + (gray - r) * (1 - saturationValue);
331
+ g = g + (gray - g) * (1 - saturationValue);
332
+ b = b + (gray - b) * (1 - saturationValue);
333
+ if (temperature !== 0) {
334
+ const tempValue = temperature / 100;
335
+ r = r + tempValue * 30;
336
+ b = b - tempValue * 30;
337
+ }
338
+ if (tint !== 0) {
339
+ const tintValue = tint / 100;
340
+ g = g + tintValue * 30;
341
+ }
342
+ if (grayscale) {
343
+ const gray = 0.2126 * r + 0.7152 * g + 0.0722 * b;
344
+ r = gray;
345
+ g = gray;
346
+ b = gray;
347
+ }
348
+ if (sepia) {
349
+ const sepiaR = r * 0.393 + g * 0.769 + b * 0.189;
350
+ const sepiaG = r * 0.349 + g * 0.686 + b * 0.168;
351
+ const sepiaB = r * 0.272 + g * 0.534 + b * 0.131;
352
+ r = sepiaR;
353
+ g = sepiaG;
354
+ b = sepiaB;
355
+ }
356
+ data[i] = Math.max(0, Math.min(255, r));
357
+ data[i + 1] = Math.max(0, Math.min(255, g));
358
+ data[i + 2] = Math.max(0, Math.min(255, b));
359
+ }
360
+ const result = document.createElement('canvas');
361
+ result.width = canvas.width;
362
+ result.height = canvas.height;
363
+ const resultCtx = result.getContext('2d');
364
+ resultCtx.putImageData(imageData, 0, 0);
365
+ return result;
366
+ }
367
+
368
+ const FILTERS = {
369
+ original: {
370
+ brightness: 0,
371
+ contrast: 0,
372
+ saturation: 0,
373
+ temperature: 0,
374
+ tint: 0,
375
+ grayscale: false,
376
+ sepia: false,
377
+ hueRotate: 0,
378
+ blur: 0,
379
+ opacity: 100,
380
+ },
381
+ vintage: {
382
+ brightness: 10,
383
+ contrast: -10,
384
+ saturation: -30,
385
+ temperature: 20,
386
+ tint: 10,
387
+ grayscale: false,
388
+ sepia: true,
389
+ hueRotate: 0,
390
+ blur: 0,
391
+ opacity: 100,
392
+ },
393
+ blackAndWhite: {
394
+ brightness: 0,
395
+ contrast: 20,
396
+ saturation: -100,
397
+ temperature: 0,
398
+ tint: 0,
399
+ grayscale: true,
400
+ sepia: false,
401
+ hueRotate: 0,
402
+ blur: 0,
403
+ opacity: 100,
404
+ },
405
+ warm: {
406
+ brightness: 5,
407
+ contrast: 5,
408
+ saturation: 10,
409
+ temperature: 30,
410
+ tint: 5,
411
+ grayscale: false,
412
+ sepia: false,
413
+ hueRotate: 0,
414
+ blur: 0,
415
+ opacity: 100,
416
+ },
417
+ cool: {
418
+ brightness: 5,
419
+ contrast: 5,
420
+ saturation: 10,
421
+ temperature: -30,
422
+ tint: -5,
423
+ grayscale: false,
424
+ sepia: false,
425
+ hueRotate: 0,
426
+ blur: 0,
427
+ opacity: 100,
428
+ },
429
+ cinematic: {
430
+ brightness: -5,
431
+ contrast: 30,
432
+ saturation: -20,
433
+ temperature: -10,
434
+ tint: 5,
435
+ grayscale: false,
436
+ sepia: false,
437
+ hueRotate: 0,
438
+ blur: 0,
439
+ opacity: 100,
440
+ },
441
+ fade: {
442
+ brightness: 15,
443
+ contrast: -20,
444
+ saturation: -30,
445
+ temperature: 0,
446
+ tint: 0,
447
+ grayscale: false,
448
+ sepia: false,
449
+ hueRotate: 0,
450
+ blur: 0,
451
+ opacity: 90,
452
+ },
453
+ dramatic: {
454
+ brightness: -10,
455
+ contrast: 40,
456
+ saturation: -40,
457
+ temperature: -10,
458
+ tint: 0,
459
+ grayscale: false,
460
+ sepia: false,
461
+ hueRotate: 0,
462
+ blur: 0,
463
+ opacity: 100,
464
+ },
465
+ soft: {
466
+ brightness: 10,
467
+ contrast: -10,
468
+ saturation: -10,
469
+ temperature: 5,
470
+ tint: 0,
471
+ grayscale: false,
472
+ sepia: false,
473
+ hueRotate: 0,
474
+ blur: 1,
475
+ opacity: 95,
476
+ },
477
+ highContrast: {
478
+ brightness: 5,
479
+ contrast: 50,
480
+ saturation: 15,
481
+ temperature: 0,
482
+ tint: 0,
483
+ grayscale: false,
484
+ sepia: false,
485
+ hueRotate: 0,
486
+ blur: 0,
487
+ opacity: 100,
488
+ },
489
+ };
490
+ function getFilterConfig(preset) {
491
+ return { ...FILTERS[preset] };
492
+ }
493
+ function applyFilter(canvas, preset) {
494
+ if (preset === 'original') {
495
+ return canvas;
496
+ }
497
+ const config = FILTERS[preset];
498
+ const result = document.createElement('canvas');
499
+ result.width = canvas.width;
500
+ result.height = canvas.height;
501
+ const ctx = result.getContext('2d');
502
+ let filterString = '';
503
+ if (config.brightness !== 0) {
504
+ filterString += `brightness(${100 + config.brightness}%) `;
505
+ }
506
+ if (config.contrast !== 0) {
507
+ filterString += `contrast(${100 + config.contrast}%) `;
508
+ }
509
+ if (config.saturation !== 0) {
510
+ filterString += `saturate(${100 + config.saturation}%) `;
511
+ }
512
+ if (config.hueRotate !== 0) {
513
+ filterString += `hue-rotate(${config.hueRotate}deg) `;
514
+ }
515
+ if (config.blur > 0) {
516
+ filterString += `blur(${config.blur}px) `;
517
+ }
518
+ if (config.opacity < 100) {
519
+ filterString += `opacity(${config.opacity}%) `;
520
+ }
521
+ if (config.grayscale) {
522
+ filterString += 'grayscale(100%) ';
523
+ }
524
+ if (config.sepia) {
525
+ filterString += 'sepia(100%) ';
526
+ }
527
+ ctx.filter = filterString.trim() || 'none';
528
+ ctx.drawImage(canvas, 0, 0);
529
+ if (config.temperature !== 0 || config.tint !== 0) {
530
+ const imageData = ctx.getImageData(0, 0, result.width, result.height);
531
+ const data = imageData.data;
532
+ const tempValue = config.temperature / 100;
533
+ const tintValue = config.tint / 100;
534
+ for (let i = 0; i < data.length; i += 4) {
535
+ if (tempValue !== 0) {
536
+ data[i] = Math.max(0, Math.min(255, data[i] + tempValue * 30));
537
+ data[i + 2] = Math.max(0, Math.min(255, data[i + 2] - tempValue * 30));
538
+ }
539
+ if (tintValue !== 0) {
540
+ data[i + 1] = Math.max(0, Math.min(255, data[i + 1] + tintValue * 30));
541
+ }
542
+ }
543
+ ctx.putImageData(imageData, 0, 0);
544
+ }
545
+ return result;
546
+ }
547
+ const AVAILABLE_FILTERS = [
548
+ 'original',
549
+ 'vintage',
550
+ 'blackAndWhite',
551
+ 'warm',
552
+ 'cool',
553
+ 'cinematic',
554
+ 'fade',
555
+ 'dramatic',
556
+ 'soft',
557
+ 'highContrast',
558
+ ];
559
+ function getFilterName(preset) {
560
+ const names = {
561
+ original: 'Original',
562
+ vintage: 'Vintage',
563
+ blackAndWhite: 'Black & White',
564
+ warm: 'Warm',
565
+ cool: 'Cool',
566
+ cinematic: 'Cinematic',
567
+ fade: 'Fade',
568
+ dramatic: 'Dramatic',
569
+ soft: 'Soft',
570
+ highContrast: 'High Contrast',
571
+ };
572
+ return names[preset];
573
+ }
574
+
575
+ const MIME_TYPES = {
576
+ jpeg: 'image/jpeg',
577
+ png: 'image/png',
578
+ webp: 'image/webp',
579
+ avif: 'image/avif',
580
+ gif: 'image/gif',
581
+ bmp: 'image/bmp',
582
+ };
583
+ const EXTENSIONS = {
584
+ jpeg: '.jpg',
585
+ png: '.png',
586
+ webp: '.webp',
587
+ avif: '.avif',
588
+ gif: '.gif',
589
+ bmp: '.bmp',
590
+ };
591
+ function getMimeType(format) {
592
+ return MIME_TYPES[format];
593
+ }
594
+ function getExtension(format) {
595
+ return EXTENSIONS[format];
596
+ }
597
+ function getSupportedFormats() {
598
+ return ['jpeg', 'png', 'webp', 'avif', 'gif', 'bmp'];
599
+ }
600
+ async function convertImage(source, options) {
601
+ const onProgress = options.onProgress;
602
+ let canvas;
603
+ onProgress?.({ phase: 'loading', percent: 0, message: 'Loading image...' });
604
+ if ('maxFileSize' in options && options.maxFileSize && source instanceof File) {
605
+ if (source.size > options.maxFileSize) {
606
+ throw new Error(`File size exceeds maximum allowed size of ${options.maxFileSize} bytes`);
607
+ }
608
+ }
609
+ if (typeof source === 'string' || source instanceof File) {
610
+ const img = await loadImage(source);
611
+ canvas = imageToCanvas(img);
612
+ }
613
+ else if (source instanceof HTMLImageElement) {
614
+ canvas = imageToCanvas(source);
615
+ }
616
+ else {
617
+ canvas = source;
618
+ }
619
+ onProgress?.({ phase: 'processing', percent: 30, message: 'Processing image...' });
620
+ if (options.width || options.height) {
621
+ const { width, height } = calculateOutputDimensions(canvas.width, canvas.height, options);
622
+ const resized = document.createElement('canvas');
623
+ resized.width = width;
624
+ resized.height = height;
625
+ const ctx = resized.getContext('2d');
626
+ ctx.imageSmoothingEnabled = true;
627
+ ctx.imageSmoothingQuality = 'high';
628
+ ctx.drawImage(canvas, 0, 0, width, height);
629
+ canvas = resized;
630
+ }
631
+ onProgress?.({ phase: 'encoding', percent: 70, message: 'Encoding image...' });
632
+ const mimeType = MIME_TYPES[options.format];
633
+ const quality = options.quality ? options.quality / 100 : undefined;
634
+ const blob = await new Promise((resolve) => {
635
+ canvas.toBlob(resolve, mimeType, quality);
636
+ });
637
+ if (!blob) {
638
+ throw new Error(`Failed to convert to ${options.format}`);
639
+ }
640
+ onProgress?.({ phase: 'complete', percent: 100, message: 'Done' });
641
+ return blob;
642
+ }
643
+ async function convertToFile(source, options, filename) {
644
+ const blob = await convertImage(source, options);
645
+ const ext = EXTENSIONS[options.format];
646
+ const name = filename.replace(/\.[^/.]+$/, '') + ext;
647
+ return new File([blob], name, { type: MIME_TYPES[options.format] });
648
+ }
649
+ function calculateOutputDimensions(originalWidth, originalHeight, options) {
650
+ let { width, height } = options;
651
+ const maintainAspectRatio = options.maintainAspectRatio ?? true;
652
+ if (!width && !height) {
653
+ return { width: originalWidth, height: originalHeight };
654
+ }
655
+ if (maintainAspectRatio) {
656
+ const aspectRatio = originalWidth / originalHeight;
657
+ if (width && !height) {
658
+ height = Math.round(width / aspectRatio);
659
+ }
660
+ else if (!width && height) {
661
+ width = Math.round(height * aspectRatio);
662
+ }
663
+ else {
664
+ const targetRatio = width / height;
665
+ if (targetRatio > aspectRatio) {
666
+ width = Math.round(height * aspectRatio);
667
+ }
668
+ else {
669
+ height = Math.round(width / aspectRatio);
670
+ }
671
+ }
672
+ }
673
+ else {
674
+ width = width ?? originalWidth;
675
+ height = height ?? originalHeight;
676
+ }
677
+ return { width: width, height: height };
678
+ }
679
+ function isFormatSupported(format) {
680
+ const canvas = document.createElement('canvas');
681
+ canvas.width = 1;
682
+ canvas.height = 1;
683
+ return new Promise((resolve) => {
684
+ canvas.toBlob((blob) => {
685
+ resolve(blob !== null);
686
+ }, MIME_TYPES[format]);
687
+ });
688
+ }
689
+
690
+ let layerCounter = 0;
691
+ function generateLayerId() {
692
+ return `layer_${Date.now()}_${++layerCounter}`;
693
+ }
694
+ function createTextLayer(options = {}) {
695
+ return {
696
+ id: generateLayerId(),
697
+ type: 'text',
698
+ name: options.name || 'Text',
699
+ visible: true,
700
+ locked: false,
701
+ opacity: options.opacity ?? 100,
702
+ x: options.x ?? 50,
703
+ y: options.y ?? 50,
704
+ width: options.width ?? 200,
705
+ height: options.height ?? 50,
706
+ rotation: options.rotation ?? 0,
707
+ zIndex: options.zIndex ?? 0,
708
+ content: options.content || 'Hello World',
709
+ fontFamily: options.fontFamily || 'Arial',
710
+ fontSize: options.fontSize ?? 24,
711
+ fontWeight: options.fontWeight || 'normal',
712
+ color: options.color || '#000000',
713
+ alignment: options.alignment || 'left',
714
+ letterSpacing: options.letterSpacing ?? 0,
715
+ lineHeight: options.lineHeight ?? 1.5,
716
+ };
717
+ }
718
+ function createShapeLayer(options = {}) {
719
+ return {
720
+ id: generateLayerId(),
721
+ type: 'shape',
722
+ name: options.name || 'Shape',
723
+ visible: true,
724
+ locked: false,
725
+ opacity: options.opacity ?? 100,
726
+ x: options.x ?? 50,
727
+ y: options.y ?? 50,
728
+ width: options.width ?? 100,
729
+ height: options.height ?? 100,
730
+ rotation: options.rotation ?? 0,
731
+ zIndex: options.zIndex ?? 0,
732
+ shapeType: options.shapeType || 'rectangle',
733
+ fill: options.fill || '#3b82f6',
734
+ stroke: options.stroke || '#1e40af',
735
+ strokeWidth: options.strokeWidth ?? 2,
736
+ };
737
+ }
738
+ function createImageLayer(imageElement, options = {}) {
739
+ return {
740
+ id: generateLayerId(),
741
+ type: 'image',
742
+ name: options.name || 'Image',
743
+ visible: true,
744
+ locked: false,
745
+ opacity: options.opacity ?? 100,
746
+ x: options.x ?? 0,
747
+ y: options.y ?? 0,
748
+ width: options.width ?? imageElement.width,
749
+ height: options.height ?? imageElement.height,
750
+ rotation: options.rotation ?? 0,
751
+ zIndex: options.zIndex ?? 0,
752
+ imageElement,
753
+ originalWidth: imageElement.width,
754
+ originalHeight: imageElement.height,
755
+ };
756
+ }
757
+ function sortLayersByZIndex(layers) {
758
+ return [...layers].sort((a, b) => a.zIndex - b.zIndex);
759
+ }
760
+ function getMaxZIndex(layers) {
761
+ if (layers.length === 0)
762
+ return 0;
763
+ return Math.max(...layers.map((l) => l.zIndex));
764
+ }
765
+ function moveLayerUp(layers, layerId) {
766
+ const index = layers.findIndex((l) => l.id === layerId);
767
+ if (index === -1 || index === layers.length - 1)
768
+ return layers;
769
+ const newLayers = [...layers];
770
+ const current = newLayers[index];
771
+ const next = newLayers[index + 1];
772
+ newLayers[index] = { ...next, zIndex: current.zIndex };
773
+ newLayers[index + 1] = { ...current, zIndex: next.zIndex };
774
+ return newLayers;
775
+ }
776
+ function moveLayerDown(layers, layerId) {
777
+ const index = layers.findIndex((l) => l.id === layerId);
778
+ if (index <= 0)
779
+ return layers;
780
+ const newLayers = [...layers];
781
+ const current = newLayers[index];
782
+ const prev = newLayers[index - 1];
783
+ newLayers[index] = { ...prev, zIndex: current.zIndex };
784
+ newLayers[index - 1] = { ...current, zIndex: prev.zIndex };
785
+ return newLayers;
786
+ }
787
+ function duplicateLayer(layer) {
788
+ const newLayer = { ...layer };
789
+ newLayer.id = generateLayerId();
790
+ newLayer.name = `${layer.name} Copy`;
791
+ return newLayer;
792
+ }
793
+ function renderLayersToCanvas(layers, width, height) {
794
+ const canvas = document.createElement('canvas');
795
+ canvas.width = width;
796
+ canvas.height = height;
797
+ const ctx = canvas.getContext('2d');
798
+ const sortedLayers = sortLayersByZIndex(layers);
799
+ for (const layer of sortedLayers) {
800
+ if (!layer.visible)
801
+ continue;
802
+ ctx.save();
803
+ ctx.globalAlpha = layer.opacity / 100;
804
+ if (layer.rotation !== 0) {
805
+ const centerX = layer.x + layer.width / 2;
806
+ const centerY = layer.y + layer.height / 2;
807
+ ctx.translate(centerX, centerY);
808
+ ctx.rotate((layer.rotation * Math.PI) / 180);
809
+ ctx.translate(-centerX, -centerY);
810
+ }
811
+ switch (layer.type) {
812
+ case 'text':
813
+ renderTextLayer(ctx, layer);
814
+ break;
815
+ case 'shape':
816
+ renderShapeLayer(ctx, layer);
817
+ break;
818
+ case 'image':
819
+ renderImageLayer(ctx, layer);
820
+ break;
821
+ }
822
+ ctx.restore();
823
+ }
824
+ return canvas;
825
+ }
826
+ function renderTextLayer(ctx, layer) {
827
+ ctx.fillStyle = layer.color;
828
+ ctx.font = `${layer.fontWeight} ${layer.fontSize}px ${layer.fontFamily}`;
829
+ ctx.textAlign = layer.alignment;
830
+ ctx.textBaseline = 'top';
831
+ const lines = layer.content.split('\n');
832
+ const lineHeight = layer.fontSize * layer.lineHeight;
833
+ lines.forEach((line, index) => {
834
+ ctx.fillText(line, layer.x, layer.y + index * lineHeight);
835
+ });
836
+ }
837
+ function renderShapeLayer(ctx, layer) {
838
+ ctx.fillStyle = layer.fill;
839
+ ctx.strokeStyle = layer.stroke;
840
+ ctx.lineWidth = layer.strokeWidth;
841
+ switch (layer.shapeType) {
842
+ case 'rectangle':
843
+ ctx.fillRect(layer.x, layer.y, layer.width, layer.height);
844
+ ctx.strokeRect(layer.x, layer.y, layer.width, layer.height);
845
+ break;
846
+ case 'circle':
847
+ ctx.beginPath();
848
+ ctx.ellipse(layer.x + layer.width / 2, layer.y + layer.height / 2, layer.width / 2, layer.height / 2, 0, 0, Math.PI * 2);
849
+ ctx.fill();
850
+ ctx.stroke();
851
+ break;
852
+ case 'line':
853
+ ctx.beginPath();
854
+ ctx.moveTo(layer.x, layer.y);
855
+ ctx.lineTo(layer.x + layer.width, layer.y + layer.height);
856
+ ctx.stroke();
857
+ break;
858
+ }
859
+ }
860
+ function renderImageLayer(ctx, layer) {
861
+ ctx.drawImage(layer.imageElement, layer.x, layer.y, layer.width, layer.height);
862
+ }
863
+
864
+ async function readMetadata(file) {
865
+ const metadata = {};
866
+ if (file.type === 'image/jpeg') {
867
+ try {
868
+ metadata.exif = await readExifData(file);
869
+ }
870
+ catch {
871
+ metadata.exif = undefined;
872
+ }
873
+ }
874
+ return metadata;
875
+ }
876
+ async function readExifData(file) {
877
+ const buffer = await file.arrayBuffer();
878
+ const view = new DataView(buffer);
879
+ if (view.getUint16(0, false) !== 0xffd8) {
880
+ return {};
881
+ }
882
+ const metadata = {};
883
+ let offset = 2;
884
+ while (offset < buffer.byteLength) {
885
+ const marker = view.getUint16(offset, false);
886
+ offset += 2;
887
+ if (marker === 0xffe1) {
888
+ view.getUint16(offset, false);
889
+ const exifData = parseExif(view, offset + 2);
890
+ Object.assign(metadata, exifData);
891
+ break;
892
+ }
893
+ if ((marker & 0xff00) === 0xff00) {
894
+ const length = view.getUint16(offset, false);
895
+ offset += length;
896
+ }
897
+ else {
898
+ break;
899
+ }
900
+ }
901
+ return metadata;
902
+ }
903
+ function parseExif(view, start, _length) {
904
+ const metadata = {};
905
+ const header = String.fromCharCode(view.getUint8(start), view.getUint8(start + 1), view.getUint8(start + 2), view.getUint8(start + 3));
906
+ if (header !== 'Exif') {
907
+ return metadata;
908
+ }
909
+ const byteOrder = view.getUint16(start + 4, false);
910
+ const littleEndian = byteOrder === 0x4949;
911
+ metadata.byteOrder = littleEndian ? 'Little-endian' : 'Big-endian';
912
+ return metadata;
913
+ }
914
+ function generateCustomMetadata(options) {
915
+ const custom = {};
916
+ if (options.custom) {
917
+ Object.assign(custom, options.custom);
918
+ }
919
+ custom.application = '@our-org/image-sdk';
920
+ custom.exportedAt = new Date().toISOString();
921
+ return custom;
922
+ }
923
+ async function exportWithMetadata(blob, format, metadata) {
924
+ if (!metadata) {
925
+ return blob;
926
+ }
927
+ if (format === 'png') {
928
+ return addPngTextChunk(blob, metadata);
929
+ }
930
+ return blob;
931
+ }
932
+ async function addPngTextChunk(blob, metadata) {
933
+ const buffer = await blob.arrayBuffer();
934
+ const view = new DataView(buffer);
935
+ if (view.getUint32(0, false) !== 0x89504e47) {
936
+ return blob;
937
+ }
938
+ const chunks = [];
939
+ chunks.push(buffer.slice(0, 8));
940
+ let offset = 8;
941
+ while (offset < buffer.byteLength) {
942
+ const chunkLength = view.getUint32(offset, false);
943
+ const chunkType = String.fromCharCode(view.getUint8(offset + 4), view.getUint8(offset + 5), view.getUint8(offset + 6), view.getUint8(offset + 7));
944
+ if (chunkType === 'IEND') {
945
+ const textData = createPngTextData(metadata);
946
+ if (textData) {
947
+ const textChunk = createPngChunk('tEXt', textData);
948
+ chunks.push(textChunk);
949
+ }
950
+ }
951
+ chunks.push(buffer.slice(offset, offset + 12 + chunkLength));
952
+ offset += 12 + chunkLength;
953
+ }
954
+ const totalLength = chunks.reduce((acc, chunk) => acc + chunk.byteLength, 0);
955
+ const result = new Uint8Array(totalLength);
956
+ let resultOffset = 0;
957
+ for (const chunk of chunks) {
958
+ result.set(new Uint8Array(chunk), resultOffset);
959
+ resultOffset += chunk.byteLength;
960
+ }
961
+ return new Blob([result], { type: 'image/png' });
962
+ }
963
+ function createPngTextData(metadata) {
964
+ const custom = generateCustomMetadata(metadata);
965
+ const text = JSON.stringify(custom);
966
+ const encoder = new TextEncoder();
967
+ return encoder.encode(`Comment\0${text}`);
968
+ }
969
+ function createPngChunk(type, data) {
970
+ const length = data.byteLength;
971
+ const chunk = new ArrayBuffer(12 + length);
972
+ const view = new DataView(chunk);
973
+ view.setUint32(0, length, false);
974
+ const typeBytes = new TextEncoder().encode(type);
975
+ new Uint8Array(chunk).set(typeBytes, 4);
976
+ new Uint8Array(chunk).set(data, 8);
977
+ let crc = 0xffffffff;
978
+ for (let i = 4; i < 8 + length; i++) {
979
+ crc ^= new Uint8Array(chunk)[i];
980
+ for (let j = 0; j < 8; j++) {
981
+ crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
982
+ }
983
+ }
984
+ view.setUint32(8 + length, (crc ^ 0xffffffff) >>> 0, false);
985
+ return chunk;
986
+ }
987
+ function removeMetadata(blob) {
988
+ return blob;
989
+ }
990
+
991
+ const MAX_BINARY_SEARCH_ITERATIONS = 8;
992
+ const PRESET_CONFIGS = {
993
+ web: { format: 'webp', quality: 75, maxDimension: 1920 },
994
+ email: { format: 'jpeg', quality: 70, maxDimension: 800 },
995
+ print: { format: 'jpeg', quality: 95, maxDimension: 3000 },
996
+ thumbnail: { format: 'webp', quality: 60, maxDimension: 400 },
997
+ };
998
+ function canvasToBlob$1(canvas, mimeType, quality) {
999
+ return new Promise((resolve) => {
1000
+ canvas.toBlob(resolve, mimeType, quality);
1001
+ });
1002
+ }
1003
+ function estimateSize(canvas, format, quality) {
1004
+ const mimeType = getMimeType(format);
1005
+ const q = quality / 100;
1006
+ if (format === 'png' || format === 'bmp' || format === 'gif') {
1007
+ return canvasToBlob$1(canvas, mimeType).then((blob) => blob?.size ?? 0);
1008
+ }
1009
+ return canvasToBlob$1(canvas, mimeType, q).then((blob) => blob?.size ?? 0);
1010
+ }
1011
+ async function compressToTargetSize(source, options) {
1012
+ const { targetSize, tolerance = 5, maxFileSize, format = 'jpeg', width, height, maintainAspectRatio = true, onProgress, } = options;
1013
+ let canvas;
1014
+ onProgress?.({ phase: 'loading', percent: 0, message: 'Loading image...' });
1015
+ if (typeof source === 'string' || source instanceof File) {
1016
+ const img = await loadImage(source);
1017
+ canvas = imageToCanvas(img);
1018
+ }
1019
+ else if (source instanceof HTMLImageElement) {
1020
+ canvas = imageToCanvas(source);
1021
+ }
1022
+ else {
1023
+ canvas = source;
1024
+ }
1025
+ if (maxFileSize) {
1026
+ const initialBlob = await canvasToBlob$1(canvas, getMimeType(format));
1027
+ if (initialBlob && initialBlob.size > maxFileSize) {
1028
+ throw new Error(`File size (${formatBytes(initialBlob.size)}) exceeds max allowed size (${formatBytes(maxFileSize)})`);
1029
+ }
1030
+ }
1031
+ if (options.maxDimension) {
1032
+ const maxDim = Math.max(canvas.width, canvas.height);
1033
+ if (maxDim > options.maxDimension) {
1034
+ const scale = options.maxDimension / maxDim;
1035
+ const scaled = document.createElement('canvas');
1036
+ scaled.width = Math.round(canvas.width * scale);
1037
+ scaled.height = Math.round(canvas.height * scale);
1038
+ const ctx = scaled.getContext('2d');
1039
+ ctx.imageSmoothingEnabled = true;
1040
+ ctx.imageSmoothingQuality = 'high';
1041
+ ctx.drawImage(canvas, 0, 0, scaled.width, scaled.height);
1042
+ canvas = scaled;
1043
+ }
1044
+ }
1045
+ if (width || height) {
1046
+ canvas = resizeCanvasForCompression(canvas, {
1047
+ width,
1048
+ height,
1049
+ maintainAspectRatio,
1050
+ });
1051
+ }
1052
+ onProgress?.({ phase: 'processing', percent: 20, message: 'Analyzing image...' });
1053
+ if (!targetSize) {
1054
+ const blob = await canvasToBlob$1(canvas, getMimeType(format), options.quality ? options.quality / 100 : undefined);
1055
+ if (!blob)
1056
+ throw new Error('Failed to encode image');
1057
+ return {
1058
+ blob,
1059
+ actualSize: blob.size,
1060
+ withinTarget: !maxFileSize || blob.size <= maxFileSize,
1061
+ quality: options.quality ?? 100,
1062
+ format,
1063
+ };
1064
+ }
1065
+ const mimeType = getMimeType(format);
1066
+ if (format === 'png' || format === 'bmp' || format === 'gif') {
1067
+ const blob = await canvasToBlob$1(canvas, mimeType);
1068
+ if (!blob)
1069
+ throw new Error('Failed to encode image');
1070
+ return {
1071
+ blob,
1072
+ actualSize: blob.size,
1073
+ targetSize,
1074
+ withinTarget: blob.size <= targetSize,
1075
+ quality: 100,
1076
+ format,
1077
+ };
1078
+ }
1079
+ onProgress?.({ phase: 'encoding', percent: 40, message: 'Optimizing compression...' });
1080
+ let low = 0.01;
1081
+ let high = 1.0;
1082
+ let bestQuality = 0.5;
1083
+ let bestBlob = null;
1084
+ let bestDiff = Infinity;
1085
+ const baselineBlob = await canvasToBlob$1(canvas, mimeType, 0.5);
1086
+ if (!baselineBlob)
1087
+ throw new Error('Failed to encode image');
1088
+ if (baselineBlob.size <= targetSize) {
1089
+ low = 0.5;
1090
+ bestBlob = baselineBlob;
1091
+ bestQuality = 0.5;
1092
+ bestDiff = targetSize - baselineBlob.size;
1093
+ }
1094
+ else {
1095
+ high = 0.5;
1096
+ bestBlob = baselineBlob;
1097
+ bestQuality = 0.5;
1098
+ bestDiff = baselineBlob.size - targetSize;
1099
+ }
1100
+ for (let i = 0; i < MAX_BINARY_SEARCH_ITERATIONS; i++) {
1101
+ const mid = (low + high) / 2;
1102
+ const blob = await canvasToBlob$1(canvas, mimeType, mid);
1103
+ if (!blob)
1104
+ break;
1105
+ const diff = Math.abs(blob.size - targetSize);
1106
+ if (diff < bestDiff) {
1107
+ bestDiff = diff;
1108
+ bestBlob = blob;
1109
+ bestQuality = mid;
1110
+ }
1111
+ if (blob.size > targetSize) {
1112
+ high = mid;
1113
+ }
1114
+ else {
1115
+ low = mid;
1116
+ }
1117
+ const progress = 40 + ((i + 1) / MAX_BINARY_SEARCH_ITERATIONS) * 50;
1118
+ onProgress?.({
1119
+ phase: 'encoding',
1120
+ percent: progress,
1121
+ message: `Optimizing... pass ${i + 1}/${MAX_BINARY_SEARCH_ITERATIONS}`,
1122
+ });
1123
+ }
1124
+ if (!bestBlob) {
1125
+ throw new Error('Failed to compress image to target size');
1126
+ }
1127
+ const toleranceBytes = targetSize * (tolerance / 100);
1128
+ const withinTarget = bestBlob.size <= targetSize + toleranceBytes;
1129
+ onProgress?.({ phase: 'complete', percent: 100, message: 'Done' });
1130
+ return {
1131
+ blob: bestBlob,
1132
+ actualSize: bestBlob.size,
1133
+ targetSize,
1134
+ withinTarget,
1135
+ quality: Math.round(bestQuality * 100),
1136
+ format,
1137
+ };
1138
+ }
1139
+ function validateFileSize(file, maxSize) {
1140
+ if (!maxSize) {
1141
+ return { valid: true, size: file.size, message: 'File size check passed' };
1142
+ }
1143
+ const valid = file.size <= maxSize;
1144
+ return {
1145
+ valid,
1146
+ size: file.size,
1147
+ maxSize,
1148
+ message: valid
1149
+ ? 'File size check passed'
1150
+ : `File size (${formatBytes(file.size)}) exceeds limit (${formatBytes(maxSize)})`,
1151
+ };
1152
+ }
1153
+ async function getCompressionPreview(source, format = 'jpeg') {
1154
+ let canvas;
1155
+ if (typeof source === 'string' || source instanceof File) {
1156
+ const img = await loadImage(source);
1157
+ canvas = imageToCanvas(img);
1158
+ }
1159
+ else if (source instanceof HTMLImageElement) {
1160
+ canvas = imageToCanvas(source);
1161
+ }
1162
+ else {
1163
+ canvas = source;
1164
+ }
1165
+ const originalSize = await estimateSize(canvas, 'png', 100);
1166
+ const quality100 = await estimateSize(canvas, format, 100);
1167
+ const quality75 = await estimateSize(canvas, format, 75);
1168
+ const quality50 = await estimateSize(canvas, format, 50);
1169
+ let estimatedSize;
1170
+ let recommendedQuality;
1171
+ if (quality75 <= originalSize * 0.7) {
1172
+ estimatedSize = quality75;
1173
+ recommendedQuality = 75;
1174
+ }
1175
+ else if (quality50 <= originalSize * 0.5) {
1176
+ estimatedSize = quality50;
1177
+ recommendedQuality = 50;
1178
+ }
1179
+ else {
1180
+ estimatedSize = quality100;
1181
+ recommendedQuality = 100;
1182
+ }
1183
+ const estimatedSavingsPercent = originalSize > 0 ? ((originalSize - estimatedSize) / originalSize) * 100 : 0;
1184
+ const recommendedFormat = format === 'png' ? 'webp' : format;
1185
+ return {
1186
+ originalWidth: canvas.width,
1187
+ originalHeight: canvas.height,
1188
+ originalSize,
1189
+ estimatedSize,
1190
+ estimatedSavingsPercent: Math.max(0, Math.round(estimatedSavingsPercent)),
1191
+ recommendedFormat,
1192
+ recommendedQuality,
1193
+ };
1194
+ }
1195
+ async function smartCompress(source, preset = 'web', onProgress) {
1196
+ const config = PRESET_CONFIGS[preset];
1197
+ const options = {
1198
+ format: config.format,
1199
+ quality: config.quality,
1200
+ maxDimension: config.maxDimension,
1201
+ onProgress,
1202
+ };
1203
+ if (preset === 'email') {
1204
+ options.targetSize = 1024 * 1024;
1205
+ options.tolerance = 10;
1206
+ }
1207
+ else if (preset === 'thumbnail') {
1208
+ options.targetSize = 100 * 1024;
1209
+ options.tolerance = 15;
1210
+ }
1211
+ else if (preset === 'web') {
1212
+ options.targetSize = 500 * 1024;
1213
+ options.tolerance = 10;
1214
+ }
1215
+ return compressToTargetSize(source, options);
1216
+ }
1217
+ function resizeCanvasForCompression(canvas, options) {
1218
+ let { width, height } = options;
1219
+ const { maintainAspectRatio } = options;
1220
+ if (!width && !height)
1221
+ return canvas;
1222
+ const aspectRatio = canvas.width / canvas.height;
1223
+ if (maintainAspectRatio) {
1224
+ if (width && !height) {
1225
+ height = Math.round(width / aspectRatio);
1226
+ }
1227
+ else if (!width && height) {
1228
+ width = Math.round(height * aspectRatio);
1229
+ }
1230
+ else {
1231
+ const targetRatio = width / height;
1232
+ if (targetRatio > aspectRatio) {
1233
+ width = Math.round(height * aspectRatio);
1234
+ }
1235
+ else {
1236
+ height = Math.round(width / aspectRatio);
1237
+ }
1238
+ }
1239
+ }
1240
+ else {
1241
+ width = width ?? canvas.width;
1242
+ height = height ?? canvas.height;
1243
+ }
1244
+ const resized = document.createElement('canvas');
1245
+ resized.width = width;
1246
+ resized.height = height;
1247
+ const ctx = resized.getContext('2d');
1248
+ ctx.imageSmoothingEnabled = true;
1249
+ ctx.imageSmoothingQuality = 'high';
1250
+ ctx.drawImage(canvas, 0, 0, width, height);
1251
+ return resized;
1252
+ }
1253
+ function formatBytes(bytes) {
1254
+ if (bytes === 0)
1255
+ return '0 B';
1256
+ const k = 1024;
1257
+ const sizes = ['B', 'KB', 'MB', 'GB'];
1258
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
1259
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
1260
+ }
1261
+
1262
+ async function createEditor(source) {
1263
+ let img;
1264
+ if (typeof source === 'string' || source instanceof File) {
1265
+ img = await loadImage(source);
1266
+ }
1267
+ else {
1268
+ img = source;
1269
+ }
1270
+ const originalCanvas = imageToCanvas(img);
1271
+ let currentCanvas = originalCanvas;
1272
+ const state = {
1273
+ width: originalCanvas.width,
1274
+ height: originalCanvas.height,
1275
+ layers: [],
1276
+ adjustments: {},
1277
+ filter: 'original',
1278
+ history: [],
1279
+ historyIndex: -1,
1280
+ };
1281
+ function saveState() {
1282
+ state.history = state.history.slice(0, state.historyIndex + 1);
1283
+ state.history.push({
1284
+ ...state,
1285
+ layers: [...state.layers],
1286
+ adjustments: { ...state.adjustments },
1287
+ });
1288
+ state.historyIndex = state.history.length - 1;
1289
+ }
1290
+ function applyAllTransformations() {
1291
+ let canvas = originalCanvas;
1292
+ canvas = applyAdjustments(canvas, state.adjustments);
1293
+ canvas = applyFilter(canvas, state.filter);
1294
+ if (state.layers.length > 0) {
1295
+ const layersCanvas = renderLayersToCanvas(state.layers, state.width, state.height);
1296
+ const ctx = canvas.getContext('2d');
1297
+ ctx.drawImage(layersCanvas, 0, 0);
1298
+ }
1299
+ return canvas;
1300
+ }
1301
+ saveState();
1302
+ return {
1303
+ getWidth: () => currentCanvas.width,
1304
+ getHeight: () => currentCanvas.height,
1305
+ getCanvas: () => currentCanvas,
1306
+ crop: (options) => {
1307
+ currentCanvas = cropCanvas(currentCanvas, options);
1308
+ state.width = currentCanvas.width;
1309
+ state.height = currentCanvas.height;
1310
+ saveState();
1311
+ },
1312
+ resize: (options) => {
1313
+ currentCanvas = resizeCanvas(currentCanvas, options);
1314
+ state.width = currentCanvas.width;
1315
+ state.height = currentCanvas.height;
1316
+ saveState();
1317
+ },
1318
+ rotate: (options) => {
1319
+ currentCanvas = rotateCanvas(currentCanvas, options);
1320
+ state.width = currentCanvas.width;
1321
+ state.height = currentCanvas.height;
1322
+ saveState();
1323
+ },
1324
+ flip: (options) => {
1325
+ currentCanvas = flipCanvas(currentCanvas, options);
1326
+ saveState();
1327
+ },
1328
+ adjust: (adjustments) => {
1329
+ state.adjustments = { ...state.adjustments, ...adjustments };
1330
+ currentCanvas = applyAllTransformations();
1331
+ saveState();
1332
+ },
1333
+ applyFilter: (filter) => {
1334
+ state.filter = filter;
1335
+ currentCanvas = applyAllTransformations();
1336
+ saveState();
1337
+ },
1338
+ addLayer: (layer) => {
1339
+ const maxZ = getMaxZIndex(state.layers);
1340
+ layer.zIndex = maxZ + 1;
1341
+ state.layers.push(layer);
1342
+ currentCanvas = applyAllTransformations();
1343
+ saveState();
1344
+ },
1345
+ removeLayer: (layerId) => {
1346
+ state.layers = state.layers.filter((l) => l.id !== layerId);
1347
+ currentCanvas = applyAllTransformations();
1348
+ saveState();
1349
+ },
1350
+ getLayers: () => [...state.layers],
1351
+ export: async (options) => {
1352
+ let blob = await convertImage(currentCanvas, options);
1353
+ if (options.metadata) {
1354
+ blob = await exportWithMetadata(blob, options.format, options.metadata);
1355
+ }
1356
+ return blob;
1357
+ },
1358
+ compress: async (options) => {
1359
+ const result = await compressToTargetSize(currentCanvas, options);
1360
+ if (options.metadata) {
1361
+ return exportWithMetadata(result.blob, options.format, options.metadata);
1362
+ }
1363
+ return result.blob;
1364
+ },
1365
+ undo: () => {
1366
+ if (state.historyIndex > 0) {
1367
+ state.historyIndex--;
1368
+ const prev = state.history[state.historyIndex];
1369
+ state.width = prev.width;
1370
+ state.height = prev.height;
1371
+ state.layers = [...prev.layers];
1372
+ state.adjustments = { ...prev.adjustments };
1373
+ state.filter = prev.filter;
1374
+ currentCanvas = applyAllTransformations();
1375
+ }
1376
+ },
1377
+ redo: () => {
1378
+ if (state.historyIndex < state.history.length - 1) {
1379
+ state.historyIndex++;
1380
+ const next = state.history[state.historyIndex];
1381
+ state.width = next.width;
1382
+ state.height = next.height;
1383
+ state.layers = [...next.layers];
1384
+ state.adjustments = { ...next.adjustments };
1385
+ state.filter = next.filter;
1386
+ currentCanvas = applyAllTransformations();
1387
+ }
1388
+ },
1389
+ canUndo: () => state.historyIndex > 0,
1390
+ canRedo: () => state.historyIndex < state.history.length - 1,
1391
+ reset: () => {
1392
+ currentCanvas = originalCanvas;
1393
+ state.width = originalCanvas.width;
1394
+ state.height = originalCanvas.height;
1395
+ state.layers = [];
1396
+ state.adjustments = {};
1397
+ state.filter = 'original';
1398
+ state.history = [];
1399
+ state.historyIndex = -1;
1400
+ saveState();
1401
+ },
1402
+ };
1403
+ }
1404
+
1405
+ function createZoomState() {
1406
+ return { zoom: 1, panX: 0, panY: 0 };
1407
+ }
1408
+ function zoomIn(state, step) {
1409
+ const s = step ?? 0.25;
1410
+ const newZoom = Math.min(state.zoom + s, 5);
1411
+ return { ...state, zoom: newZoom };
1412
+ }
1413
+ function zoomOut(state, step) {
1414
+ const s = step ?? 0.25;
1415
+ const newZoom = Math.max(state.zoom - s, 0.1);
1416
+ return { ...state, zoom: newZoom };
1417
+ }
1418
+ function zoomToFit(_state, canvasWidth, canvasHeight, containerWidth, containerHeight) {
1419
+ const scaleX = containerWidth / canvasWidth;
1420
+ const scaleY = containerHeight / canvasHeight;
1421
+ const zoom = Math.min(scaleX, scaleY) * 0.9;
1422
+ return {
1423
+ zoom: Math.max(0.1, Math.min(zoom, 5)),
1424
+ panX: 0,
1425
+ panY: 0,
1426
+ };
1427
+ }
1428
+ function zoomTo100(_state, canvasWidth, canvasHeight, containerWidth, containerHeight) {
1429
+ return {
1430
+ zoom: 1,
1431
+ panX: (containerWidth - canvasWidth) / 2,
1432
+ panY: (containerHeight - canvasHeight) / 2,
1433
+ };
1434
+ }
1435
+ function pan(state, deltaX, deltaY) {
1436
+ return {
1437
+ ...state,
1438
+ panX: state.panX + deltaX,
1439
+ panY: state.panY + deltaY,
1440
+ };
1441
+ }
1442
+ function zoomAtPoint(state, delta, clientX, clientY, containerRect) {
1443
+ const factor = delta > 0 ? 1.1 : 0.9;
1444
+ const newZoom = Math.max(0.1, Math.min(state.zoom * factor, 5));
1445
+ const mouseX = clientX - containerRect.left;
1446
+ const mouseY = clientY - containerRect.top;
1447
+ const zoomRatio = newZoom / state.zoom;
1448
+ const newPanX = mouseX - (mouseX - state.panX) * zoomRatio;
1449
+ const newPanY = mouseY - (mouseY - state.panY) * zoomRatio;
1450
+ return {
1451
+ zoom: newZoom,
1452
+ panX: newPanX,
1453
+ panY: newPanY,
1454
+ };
1455
+ }
1456
+ function getCanvasTransform(state) {
1457
+ return `translate(${state.panX}px, ${state.panY}px) scale(${state.zoom})`;
1458
+ }
1459
+
1460
+ const EXPORT_PRESETS = [
1461
+ { name: 'instagram-post', label: 'Instagram Post', width: 1080, height: 1080, category: 'Instagram' },
1462
+ { name: 'instagram-story', label: 'Instagram Story', width: 1080, height: 1920, category: 'Instagram' },
1463
+ { name: 'instagram-reel', label: 'Instagram Reel', width: 1080, height: 1920, category: 'Instagram' },
1464
+ { name: 'twitter-post', label: 'Twitter/X Post', width: 1200, height: 675, category: 'Twitter' },
1465
+ { name: 'twitter-header', label: 'Twitter/X Header', width: 1500, height: 500, category: 'Twitter' },
1466
+ { name: 'facebook-post', label: 'Facebook Post', width: 1200, height: 630, category: 'Facebook' },
1467
+ { name: 'facebook-cover', label: 'Facebook Cover', width: 820, height: 312, category: 'Facebook' },
1468
+ { name: 'youtube-thumbnail', label: 'YouTube Thumbnail', width: 1280, height: 720, category: 'YouTube' },
1469
+ { name: 'youtube-banner', label: 'YouTube Banner', width: 2560, height: 1440, category: 'YouTube' },
1470
+ { name: 'linkedin-post', label: 'LinkedIn Post', width: 1200, height: 627, category: 'LinkedIn' },
1471
+ { name: 'linkedin-cover', label: 'LinkedIn Cover', width: 1584, height: 396, category: 'LinkedIn' },
1472
+ { name: 'pinterest-pin', label: 'Pinterest Pin', width: 1000, height: 1500, category: 'Pinterest' },
1473
+ { name: 'tiktok', label: 'TikTok', width: 1080, height: 1920, category: 'TikTok' },
1474
+ ];
1475
+ function getExportPreset(name) {
1476
+ return EXPORT_PRESETS.find((p) => p.name === name);
1477
+ }
1478
+ function getPresetsByCategory() {
1479
+ return EXPORT_PRESETS.reduce((acc, preset) => {
1480
+ if (!acc[preset.category]) {
1481
+ acc[preset.category] = [];
1482
+ }
1483
+ acc[preset.category].push(preset);
1484
+ return acc;
1485
+ }, {});
1486
+ }
1487
+
1488
+ let worker = null;
1489
+ let messageId = 0;
1490
+ const pendingMessages = new Map();
1491
+ function getWorker() {
1492
+ if (typeof Worker === 'undefined')
1493
+ return null;
1494
+ if (!worker) {
1495
+ try {
1496
+ const workerCode = `
1497
+ self.onmessage = function(e) {
1498
+ const { id, type, payload } = e.data;
1499
+
1500
+ try {
1501
+ let result;
1502
+
1503
+ switch (type) {
1504
+ case 'resize': {
1505
+ const { imageData, width, height } = payload;
1506
+ const canvas = new OffscreenCanvas(width, height);
1507
+ const ctx = canvas.getContext('2d');
1508
+ const imgData = new ImageData(
1509
+ new Uint8ClampedArray(imageData.data),
1510
+ imageData.width,
1511
+ imageData.height
1512
+ );
1513
+ const tempCanvas = new OffscreenCanvas(imageData.width, imageData.height);
1514
+ const tempCtx = tempCanvas.getContext('2d');
1515
+ tempCtx.putImageData(imgData, 0, 0);
1516
+ ctx.imageSmoothingEnabled = true;
1517
+ ctx.imageSmoothingQuality = 'high';
1518
+ ctx.drawImage(tempCanvas, 0, 0, width, height);
1519
+ result = ctx.getImageData(0, 0, width, height);
1520
+ break;
1521
+ }
1522
+
1523
+ case 'adjust': {
1524
+ const { imageData: imgData2, adjustments } = payload;
1525
+ const data = new Uint8ClampedArray(imgData2.data);
1526
+ const { brightness = 0, contrast = 0, saturation = 0 } = adjustments;
1527
+
1528
+ const brightnessValue = brightness / 100;
1529
+ const contrastValue = (contrast + 100) / 100;
1530
+ const saturationValue = (saturation + 100) / 100;
1531
+
1532
+ for (let i = 0; i < data.length; i += 4) {
1533
+ let r = data[i] + brightnessValue * 255;
1534
+ let g = data[i + 1] + brightnessValue * 255;
1535
+ let b = data[i + 2] + brightnessValue * 255;
1536
+
1537
+ r = ((r / 255 - 0.5) * contrastValue + 0.5) * 255;
1538
+ g = ((g / 255 - 0.5) * contrastValue + 0.5) * 255;
1539
+ b = ((b / 255 - 0.5) * contrastValue + 0.5) * 255;
1540
+
1541
+ const gray = 0.2126 * r + 0.7152 * g + 0.0722 * b;
1542
+ r = r + (gray - r) * (1 - saturationValue);
1543
+ g = g + (gray - g) * (1 - saturationValue);
1544
+ b = b + (gray - b) * (1 - saturationValue);
1545
+
1546
+ data[i] = Math.max(0, Math.min(255, r));
1547
+ data[i + 1] = Math.max(0, Math.min(255, g));
1548
+ data[i + 2] = Math.max(0, Math.min(255, b));
1549
+ }
1550
+
1551
+ result = { data: data.buffer, width: imgData2.width, height: imgData2.height };
1552
+ break;
1553
+ }
1554
+
1555
+ default:
1556
+ throw new Error('Unknown operation: ' + type);
1557
+ }
1558
+
1559
+ self.postMessage({ id, type: 'result', payload: result });
1560
+ } catch (err) {
1561
+ self.postMessage({ id, type: 'error', error: err.message });
1562
+ }
1563
+ };
1564
+ `;
1565
+ const blob = new Blob([workerCode], { type: 'application/javascript' });
1566
+ worker = new Worker(URL.createObjectURL(blob));
1567
+ worker.onmessage = (e) => {
1568
+ const { id, type, payload, error } = e.data;
1569
+ const pending = pendingMessages.get(id);
1570
+ if (pending) {
1571
+ pendingMessages.delete(id);
1572
+ if (type === 'error') {
1573
+ pending.reject(new Error(error));
1574
+ }
1575
+ else {
1576
+ pending.resolve(payload);
1577
+ }
1578
+ }
1579
+ };
1580
+ worker.onerror = (e) => {
1581
+ console.error('Worker error:', e);
1582
+ };
1583
+ }
1584
+ catch {
1585
+ return null;
1586
+ }
1587
+ }
1588
+ return worker;
1589
+ }
1590
+ function isWorkerSupported() {
1591
+ return typeof Worker !== 'undefined' && typeof OffscreenCanvas !== 'undefined';
1592
+ }
1593
+ async function processInWorker(operation) {
1594
+ const w = getWorker();
1595
+ if (!w) {
1596
+ throw new Error('Web Worker not supported');
1597
+ }
1598
+ const id = `msg_${++messageId}`;
1599
+ return new Promise((resolve, reject) => {
1600
+ pendingMessages.set(id, { resolve, reject });
1601
+ w.postMessage({ id, ...operation });
1602
+ });
1603
+ }
1604
+ function terminateWorker() {
1605
+ if (worker) {
1606
+ worker.terminate();
1607
+ worker = null;
1608
+ pendingMessages.clear();
1609
+ }
1610
+ }
1611
+
1612
+ const DRP_NAME = 'Digital Resolution Pro';
1613
+ const DRP_VERSION = '1.0.0';
1614
+ const DRP_TAG = 'drp';
1615
+ function getDRPVersion() {
1616
+ return DRP_VERSION;
1617
+ }
1618
+ function getDRPInfo() {
1619
+ return { name: DRP_NAME, version: DRP_VERSION, tag: DRP_TAG };
1620
+ }
1621
+ function getDRPFilename(originalName, format) {
1622
+ const baseName = originalName.replace(/\.[^/.]+$/, '');
1623
+ const ext = format ? getExtension(format) : '.' + originalName.split('.').pop();
1624
+ return `${baseName}-${DRP_TAG}${ext}`;
1625
+ }
1626
+ function isDRPFile(filename) {
1627
+ const pattern = new RegExp(`-${DRP_TAG}\\.[^.]+$`);
1628
+ return pattern.test(filename);
1629
+ }
1630
+ function stripDRPTag(filename) {
1631
+ return filename.replace(new RegExp(`-${DRP_TAG}(\\.[^.]+)$`), '$1');
1632
+ }
1633
+
1634
+ async function batchProcess(files, operation, onProgress) {
1635
+ const results = [];
1636
+ for (let i = 0; i < files.length; i++) {
1637
+ const file = files[i];
1638
+ onProgress?.({
1639
+ current: i + 1,
1640
+ total: files.length,
1641
+ file,
1642
+ phase: 'processing',
1643
+ });
1644
+ try {
1645
+ let blob;
1646
+ switch (operation.type) {
1647
+ case 'convert':
1648
+ blob = await convertImage(file, operation.options);
1649
+ break;
1650
+ case 'resize': {
1651
+ const canvas = await resizeImage(file, {
1652
+ width: operation.options?.width,
1653
+ height: operation.options?.height,
1654
+ maintainAspectRatio: operation.options?.maintainAspectRatio,
1655
+ });
1656
+ blob = await convertImage(canvas, {
1657
+ format: operation.options?.format || 'png',
1658
+ quality: operation.options?.quality,
1659
+ });
1660
+ break;
1661
+ }
1662
+ case 'filter': {
1663
+ const img = await resizeImage(file, { width: 800, maintainAspectRatio: true });
1664
+ const filtered = applyFilter(img, operation.options?.preset || 'original');
1665
+ blob = await convertImage(filtered, {
1666
+ format: operation.options?.format || 'png',
1667
+ quality: operation.options?.quality,
1668
+ });
1669
+ break;
1670
+ }
1671
+ case 'compress': {
1672
+ const result = await compressToTargetSize(file, {
1673
+ format: operation.options?.format || 'webp',
1674
+ targetSize: operation.options?.targetSize,
1675
+ tolerance: operation.options?.tolerance,
1676
+ maxFileSize: operation.options?.maxFileSize,
1677
+ maxDimension: operation.options?.maxDimension,
1678
+ quality: operation.options?.quality,
1679
+ width: operation.options?.width,
1680
+ height: operation.options?.height,
1681
+ maintainAspectRatio: operation.options?.maintainAspectRatio,
1682
+ });
1683
+ blob = result.blob;
1684
+ break;
1685
+ }
1686
+ default:
1687
+ throw new Error(`Unknown operation: ${operation.type}`);
1688
+ }
1689
+ results.push({ file, blob, success: true });
1690
+ }
1691
+ catch (error) {
1692
+ results.push({
1693
+ file,
1694
+ blob: new Blob(),
1695
+ success: false,
1696
+ error: error instanceof Error ? error.message : 'Unknown error',
1697
+ });
1698
+ }
1699
+ }
1700
+ onProgress?.({
1701
+ current: files.length,
1702
+ total: files.length,
1703
+ file: files[files.length - 1],
1704
+ phase: 'complete',
1705
+ });
1706
+ return results;
1707
+ }
1708
+ function downloadBatchResult(result) {
1709
+ if (!result.success)
1710
+ return;
1711
+ const url = URL.createObjectURL(result.blob);
1712
+ const a = document.createElement('a');
1713
+ a.href = url;
1714
+ const ext = result.file.name.split('.').pop() || 'png';
1715
+ a.download = getDRPFilename(result.file.name, ext);
1716
+ a.click();
1717
+ URL.revokeObjectURL(url);
1718
+ }
1719
+ async function downloadAllBatchResults(results) {
1720
+ for (const result of results) {
1721
+ if (result.success) {
1722
+ downloadBatchResult(result);
1723
+ await new Promise((r) => setTimeout(r, 100));
1724
+ }
1725
+ }
1726
+ }
1727
+
1728
+ function parseSize(input) {
1729
+ if (typeof input === 'number')
1730
+ return input;
1731
+ const str = input.trim().toUpperCase();
1732
+ const match = str.match(/^([\d.]+)\s*(B|KB|MB|GB)$/);
1733
+ if (!match)
1734
+ throw new Error(`Invalid size format: "${input}". Use "500KB", "2MB", etc.`);
1735
+ const value = parseFloat(match[1]);
1736
+ const unit = match[2];
1737
+ switch (unit) {
1738
+ case 'B': return value;
1739
+ case 'KB': return value * 1024;
1740
+ case 'MB': return value * 1024 * 1024;
1741
+ case 'GB': return value * 1024 * 1024 * 1024;
1742
+ default: return value;
1743
+ }
1744
+ }
1745
+ function parseFlip(input) {
1746
+ if (input === true)
1747
+ return { horizontal: true };
1748
+ if (input === false)
1749
+ return {};
1750
+ switch (input) {
1751
+ case 'horizontal': return { horizontal: true };
1752
+ case 'vertical': return { vertical: true };
1753
+ case 'both': return { horizontal: true, vertical: true };
1754
+ default: return {};
1755
+ }
1756
+ }
1757
+ function normalizeCrop(crop) {
1758
+ return {
1759
+ x: crop.x,
1760
+ y: crop.y,
1761
+ width: 'width' in crop ? crop.width : crop.width,
1762
+ height: 'height' in crop ? crop.height : crop.height,
1763
+ };
1764
+ }
1765
+ async function process(source, options = {}) {
1766
+ const { format = 'webp', quality = 80, targetSize, tolerance = 10, width, height, maintainAspectRatio = true, crop: cropOpts, rotate: rotateDeg, flip: flipOpts, filter: filterPreset, adjustments: adjOpts, metadata: metaOpts, onProgress, } = options;
1767
+ onProgress?.({ phase: 'loading', percent: 0, message: 'Loading image...' });
1768
+ let canvas;
1769
+ if (source instanceof Blob && !(source instanceof File)) {
1770
+ const url = URL.createObjectURL(source);
1771
+ const img = await loadImage(url);
1772
+ URL.revokeObjectURL(url);
1773
+ canvas = imageToCanvas(img);
1774
+ }
1775
+ else if (typeof source === 'string' || source instanceof File) {
1776
+ const img = await loadImage(source);
1777
+ canvas = imageToCanvas(img);
1778
+ }
1779
+ else if (source instanceof HTMLImageElement) {
1780
+ canvas = imageToCanvas(source);
1781
+ }
1782
+ else if (source instanceof HTMLCanvasElement) {
1783
+ canvas = source;
1784
+ }
1785
+ else {
1786
+ throw new Error('DRP: Invalid source. Provide a File, Blob, URL string, HTMLImageElement, or HTMLCanvasElement.');
1787
+ }
1788
+ canvas.width;
1789
+ canvas.height;
1790
+ let step = 0;
1791
+ const totalSteps = [cropOpts, width || height, rotateDeg, flipOpts, adjOpts, filterPreset, true].filter(Boolean).length;
1792
+ function progress(phase, msg) {
1793
+ step++;
1794
+ onProgress?.({ phase, percent: Math.round((step / totalSteps) * 80), message: msg });
1795
+ }
1796
+ if (cropOpts) {
1797
+ progress('processing', 'Cropping...');
1798
+ canvas = await cropImage(canvas, normalizeCrop(cropOpts));
1799
+ }
1800
+ if (width || height) {
1801
+ progress('processing', 'Resizing...');
1802
+ canvas = await resizeImage(canvas, {
1803
+ width,
1804
+ height,
1805
+ maintainAspectRatio,
1806
+ });
1807
+ }
1808
+ if (rotateDeg) {
1809
+ progress('processing', 'Rotating...');
1810
+ canvas = await rotateImage(canvas, { degrees: rotateDeg });
1811
+ }
1812
+ if (flipOpts) {
1813
+ progress('processing', 'Flipping...');
1814
+ canvas = await flipImage(canvas, parseFlip(flipOpts));
1815
+ }
1816
+ if (adjOpts) {
1817
+ progress('processing', 'Adjusting...');
1818
+ canvas = applyAdjustments(canvas, adjOpts);
1819
+ }
1820
+ if (filterPreset && filterPreset !== 'original') {
1821
+ progress('processing', 'Applying filter...');
1822
+ canvas = applyFilter(canvas, filterPreset);
1823
+ }
1824
+ onProgress?.({ phase: 'encoding', percent: 80, message: 'Encoding...' });
1825
+ let blob;
1826
+ let actualQuality = quality;
1827
+ let actualFormat = format;
1828
+ if (targetSize) {
1829
+ const targetBytes = parseSize(targetSize);
1830
+ const result = await compressToTargetSize(canvas, {
1831
+ format,
1832
+ quality,
1833
+ targetSize: targetBytes,
1834
+ tolerance,
1835
+ onProgress: onProgress
1836
+ ? (e) => onProgress({ ...e, percent: 80 + Math.round(e.percent * 0.2) })
1837
+ : undefined,
1838
+ });
1839
+ blob = result.blob;
1840
+ actualQuality = result.quality;
1841
+ actualFormat = result.format;
1842
+ }
1843
+ else {
1844
+ const mimeType = getMimeTypeForFormat(format);
1845
+ const q = quality / 100;
1846
+ let encoded;
1847
+ if (format === 'png' || format === 'bmp' || format === 'gif') {
1848
+ encoded = await canvasToBlob(canvas, mimeType);
1849
+ }
1850
+ else {
1851
+ encoded = await canvasToBlob(canvas, mimeType, q);
1852
+ }
1853
+ if (!encoded) {
1854
+ throw new Error(`DRP: Failed to encode to ${format}`);
1855
+ }
1856
+ blob = encoded;
1857
+ }
1858
+ if (metaOpts && (metaOpts.preserve || metaOpts.custom)) {
1859
+ blob = await exportWithMetadata(blob, actualFormat, metaOpts);
1860
+ }
1861
+ onProgress?.({ phase: 'complete', percent: 100, message: 'Done' });
1862
+ return {
1863
+ blob,
1864
+ size: blob.size,
1865
+ format: actualFormat,
1866
+ quality: actualQuality,
1867
+ width: canvas.width,
1868
+ height: canvas.height,
1869
+ };
1870
+ }
1871
+ function getMimeTypeForFormat(format) {
1872
+ const map = {
1873
+ jpeg: 'image/jpeg',
1874
+ png: 'image/png',
1875
+ webp: 'image/webp',
1876
+ avif: 'image/avif',
1877
+ gif: 'image/gif',
1878
+ bmp: 'image/bmp',
1879
+ };
1880
+ return map[format] || 'image/webp';
1881
+ }
1882
+ function canvasToBlob(canvas, mimeType, quality) {
1883
+ return new Promise((resolve) => {
1884
+ canvas.toBlob(resolve, mimeType, quality);
1885
+ });
1886
+ }
1887
+ function DRP(source, options) {
1888
+ return process(source, options);
1889
+ }
1890
+ DRP.process = process;
1891
+ DRP.version = () => VERSION;
1892
+ DRP.formats = () => ['jpeg', 'png', 'webp', 'avif', 'gif', 'bmp'];
1893
+ DRP.filters = () => [
1894
+ 'original', 'vintage', 'blackAndWhite', 'warm', 'cool',
1895
+ 'cinematic', 'fade', 'dramatic', 'soft', 'highContrast',
1896
+ ];
1897
+ DRP.presets = () => EXPORT_PRESETS;
1898
+ DRP.editor = (source) => createEditor(source);
1899
+ DRP.batch = (files, operation, onProgress) => batchProcess(files, operation, onProgress);
1900
+ DRP.validate = (file, maxSize) => validateFileSize(file, maxSize);
1901
+ DRP.preview = (source, format) => getCompressionPreview(source, format);
1902
+ DRP.readMetadata = (source) => readMetadata(source);
1903
+
1904
+ exports.AVAILABLE_FILTERS = AVAILABLE_FILTERS;
1905
+ exports.DRP = DRP;
1906
+ exports.DRP_NAME = DRP_NAME;
1907
+ exports.DRP_TAG = DRP_TAG;
1908
+ exports.DRP_VERSION = DRP_VERSION;
1909
+ exports.EXPORT_PRESETS = EXPORT_PRESETS;
1910
+ exports.VERSION = VERSION;
1911
+ exports.applyAdjustments = applyAdjustments;
1912
+ exports.applyFilter = applyFilter;
1913
+ exports.batchProcess = batchProcess;
1914
+ exports.canvasToImageData = canvasToImageData;
1915
+ exports.compressToTargetSize = compressToTargetSize;
1916
+ exports.convertImage = convertImage;
1917
+ exports.convertToFile = convertToFile;
1918
+ exports.createEditor = createEditor;
1919
+ exports.createImageLayer = createImageLayer;
1920
+ exports.createShapeLayer = createShapeLayer;
1921
+ exports.createTextLayer = createTextLayer;
1922
+ exports.createZoomState = createZoomState;
1923
+ exports.cropCanvas = cropCanvas;
1924
+ exports.cropImage = cropImage;
1925
+ exports.detectFormat = detectFormat;
1926
+ exports.downloadAllBatchResults = downloadAllBatchResults;
1927
+ exports.downloadBatchResult = downloadBatchResult;
1928
+ exports.duplicateLayer = duplicateLayer;
1929
+ exports.exportWithMetadata = exportWithMetadata;
1930
+ exports.flipCanvas = flipCanvas;
1931
+ exports.flipImage = flipImage;
1932
+ exports.generateCustomMetadata = generateCustomMetadata;
1933
+ exports.getCanvasTransform = getCanvasTransform;
1934
+ exports.getCompressionPreview = getCompressionPreview;
1935
+ exports.getDRPFilename = getDRPFilename;
1936
+ exports.getDRPInfo = getDRPInfo;
1937
+ exports.getDRPVersion = getDRPVersion;
1938
+ exports.getExportPreset = getExportPreset;
1939
+ exports.getExtension = getExtension;
1940
+ exports.getFilterConfig = getFilterConfig;
1941
+ exports.getFilterName = getFilterName;
1942
+ exports.getImageInfo = getImageInfo;
1943
+ exports.getMaxZIndex = getMaxZIndex;
1944
+ exports.getMimeType = getMimeType;
1945
+ exports.getPresetsByCategory = getPresetsByCategory;
1946
+ exports.getSupportedFormats = getSupportedFormats;
1947
+ exports.imageDataToCanvas = imageDataToCanvas;
1948
+ exports.imageToCanvas = imageToCanvas;
1949
+ exports.isDRPFile = isDRPFile;
1950
+ exports.isFormatSupported = isFormatSupported;
1951
+ exports.isWorkerSupported = isWorkerSupported;
1952
+ exports.loadImage = loadImage;
1953
+ exports.moveLayerDown = moveLayerDown;
1954
+ exports.moveLayerUp = moveLayerUp;
1955
+ exports.pan = pan;
1956
+ exports.processInWorker = processInWorker;
1957
+ exports.readMetadata = readMetadata;
1958
+ exports.removeMetadata = removeMetadata;
1959
+ exports.renderLayersToCanvas = renderLayersToCanvas;
1960
+ exports.resizeCanvas = resizeCanvas;
1961
+ exports.resizeImage = resizeImage;
1962
+ exports.rotateCanvas = rotateCanvas;
1963
+ exports.rotateImage = rotateImage;
1964
+ exports.smartCompress = smartCompress;
1965
+ exports.sortLayersByZIndex = sortLayersByZIndex;
1966
+ exports.stripDRPTag = stripDRPTag;
1967
+ exports.terminateWorker = terminateWorker;
1968
+ exports.validateFile = validateFile;
1969
+ exports.validateFileSize = validateFileSize;
1970
+ exports.zoomAtPoint = zoomAtPoint;
1971
+ exports.zoomIn = zoomIn;
1972
+ exports.zoomOut = zoomOut;
1973
+ exports.zoomTo100 = zoomTo100;
1974
+ exports.zoomToFit = zoomToFit;
1975
+
1976
+ return exports;
1977
+
1978
+ })({});