@teamflojo/floimg 0.1.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 (134) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cli/commands/config.d.ts +3 -0
  3. package/dist/cli/commands/config.d.ts.map +1 -0
  4. package/dist/cli/commands/config.js +165 -0
  5. package/dist/cli/commands/config.js.map +1 -0
  6. package/dist/cli/commands/filter.d.ts +4 -0
  7. package/dist/cli/commands/filter.d.ts.map +1 -0
  8. package/dist/cli/commands/filter.js +102 -0
  9. package/dist/cli/commands/filter.js.map +1 -0
  10. package/dist/cli/commands/generate.d.ts +3 -0
  11. package/dist/cli/commands/generate.d.ts.map +1 -0
  12. package/dist/cli/commands/generate.js +49 -0
  13. package/dist/cli/commands/generate.js.map +1 -0
  14. package/dist/cli/commands/mcp.d.ts +3 -0
  15. package/dist/cli/commands/mcp.d.ts.map +1 -0
  16. package/dist/cli/commands/mcp.js +88 -0
  17. package/dist/cli/commands/mcp.js.map +1 -0
  18. package/dist/cli/commands/plugins.d.ts +3 -0
  19. package/dist/cli/commands/plugins.d.ts.map +1 -0
  20. package/dist/cli/commands/plugins.js +66 -0
  21. package/dist/cli/commands/plugins.js.map +1 -0
  22. package/dist/cli/commands/run.d.ts +3 -0
  23. package/dist/cli/commands/run.d.ts.map +1 -0
  24. package/dist/cli/commands/run.js +45 -0
  25. package/dist/cli/commands/run.js.map +1 -0
  26. package/dist/cli/commands/save.d.ts +3 -0
  27. package/dist/cli/commands/save.d.ts.map +1 -0
  28. package/dist/cli/commands/save.js +54 -0
  29. package/dist/cli/commands/save.js.map +1 -0
  30. package/dist/cli/commands/text.d.ts +3 -0
  31. package/dist/cli/commands/text.d.ts.map +1 -0
  32. package/dist/cli/commands/text.js +143 -0
  33. package/dist/cli/commands/text.js.map +1 -0
  34. package/dist/cli/commands/transform.d.ts +3 -0
  35. package/dist/cli/commands/transform.d.ts.map +1 -0
  36. package/dist/cli/commands/transform.js +59 -0
  37. package/dist/cli/commands/transform.js.map +1 -0
  38. package/dist/cli/commands/upload.d.ts +3 -0
  39. package/dist/cli/commands/upload.d.ts.map +1 -0
  40. package/dist/cli/commands/upload.js +59 -0
  41. package/dist/cli/commands/upload.js.map +1 -0
  42. package/dist/cli/index.d.ts +3 -0
  43. package/dist/cli/index.d.ts.map +1 -0
  44. package/dist/cli/index.js +139 -0
  45. package/dist/cli/index.js.map +1 -0
  46. package/dist/config/index.d.ts +10 -0
  47. package/dist/config/index.d.ts.map +1 -0
  48. package/dist/config/index.js +38 -0
  49. package/dist/config/index.js.map +1 -0
  50. package/dist/config/loader.d.ts +23 -0
  51. package/dist/config/loader.d.ts.map +1 -0
  52. package/dist/config/loader.js +189 -0
  53. package/dist/config/loader.js.map +1 -0
  54. package/dist/core/client.d.ts +63 -0
  55. package/dist/core/client.d.ts.map +1 -0
  56. package/dist/core/client.js +318 -0
  57. package/dist/core/client.js.map +1 -0
  58. package/dist/core/errors.d.ts +38 -0
  59. package/dist/core/errors.d.ts.map +1 -0
  60. package/dist/core/errors.js +75 -0
  61. package/dist/core/errors.js.map +1 -0
  62. package/dist/core/logger.d.ts +12 -0
  63. package/dist/core/logger.d.ts.map +1 -0
  64. package/dist/core/logger.js +26 -0
  65. package/dist/core/logger.js.map +1 -0
  66. package/dist/core/pipeline-runner.d.ts +64 -0
  67. package/dist/core/pipeline-runner.d.ts.map +1 -0
  68. package/dist/core/pipeline-runner.js +109 -0
  69. package/dist/core/pipeline-runner.js.map +1 -0
  70. package/dist/core/types.d.ts +392 -0
  71. package/dist/core/types.d.ts.map +1 -0
  72. package/dist/core/types.js +5 -0
  73. package/dist/core/types.js.map +1 -0
  74. package/dist/index.d.ts +18 -0
  75. package/dist/index.d.ts.map +1 -0
  76. package/dist/index.js +49 -0
  77. package/dist/index.js.map +1 -0
  78. package/dist/mcp/server.d.ts +3 -0
  79. package/dist/mcp/server.d.ts.map +1 -0
  80. package/dist/mcp/server.js +731 -0
  81. package/dist/mcp/server.js.map +1 -0
  82. package/dist/providers/ai/index.d.ts +3 -0
  83. package/dist/providers/ai/index.d.ts.map +1 -0
  84. package/dist/providers/ai/index.js +2 -0
  85. package/dist/providers/ai/index.js.map +1 -0
  86. package/dist/providers/ai/openai.d.ts +44 -0
  87. package/dist/providers/ai/openai.d.ts.map +1 -0
  88. package/dist/providers/ai/openai.js +133 -0
  89. package/dist/providers/ai/openai.js.map +1 -0
  90. package/dist/providers/save/FsSaveProvider.d.ts +20 -0
  91. package/dist/providers/save/FsSaveProvider.d.ts.map +1 -0
  92. package/dist/providers/save/FsSaveProvider.js +34 -0
  93. package/dist/providers/save/FsSaveProvider.js.map +1 -0
  94. package/dist/providers/save/S3SaveProvider.d.ts +26 -0
  95. package/dist/providers/save/S3SaveProvider.d.ts.map +1 -0
  96. package/dist/providers/save/S3SaveProvider.js +42 -0
  97. package/dist/providers/save/S3SaveProvider.js.map +1 -0
  98. package/dist/providers/store/fs.d.ts +20 -0
  99. package/dist/providers/store/fs.d.ts.map +1 -0
  100. package/dist/providers/store/fs.js +42 -0
  101. package/dist/providers/store/fs.js.map +1 -0
  102. package/dist/providers/store/index.d.ts +3 -0
  103. package/dist/providers/store/index.d.ts.map +1 -0
  104. package/dist/providers/store/index.js +3 -0
  105. package/dist/providers/store/index.js.map +1 -0
  106. package/dist/providers/store/s3.d.ts +62 -0
  107. package/dist/providers/store/s3.d.ts.map +1 -0
  108. package/dist/providers/store/s3.js +92 -0
  109. package/dist/providers/store/s3.js.map +1 -0
  110. package/dist/providers/svg/index.d.ts +2 -0
  111. package/dist/providers/svg/index.d.ts.map +1 -0
  112. package/dist/providers/svg/index.js +2 -0
  113. package/dist/providers/svg/index.js.map +1 -0
  114. package/dist/providers/svg/shapes.d.ts +18 -0
  115. package/dist/providers/svg/shapes.d.ts.map +1 -0
  116. package/dist/providers/svg/shapes.js +161 -0
  117. package/dist/providers/svg/shapes.js.map +1 -0
  118. package/dist/providers/transform/index.d.ts +2 -0
  119. package/dist/providers/transform/index.d.ts.map +1 -0
  120. package/dist/providers/transform/index.js +2 -0
  121. package/dist/providers/transform/index.js.map +1 -0
  122. package/dist/providers/transform/presets.d.ts +44 -0
  123. package/dist/providers/transform/presets.d.ts.map +1 -0
  124. package/dist/providers/transform/presets.js +205 -0
  125. package/dist/providers/transform/presets.js.map +1 -0
  126. package/dist/providers/transform/sharp.d.ts +64 -0
  127. package/dist/providers/transform/sharp.d.ts.map +1 -0
  128. package/dist/providers/transform/sharp.js +732 -0
  129. package/dist/providers/transform/sharp.js.map +1 -0
  130. package/dist/providers/transform/text.d.ts +38 -0
  131. package/dist/providers/transform/text.d.ts.map +1 -0
  132. package/dist/providers/transform/text.js +116 -0
  133. package/dist/providers/transform/text.js.map +1 -0
  134. package/package.json +84 -0
@@ -0,0 +1,732 @@
1
+ import sharp from "sharp";
2
+ import { Resvg } from "@resvg/resvg-js";
3
+ import { TransformError } from "../../core/errors.js";
4
+ import { TextRenderer } from "./text.js";
5
+ import { FilterPresets } from "./presets.js";
6
+ /**
7
+ * Operation schemas for the Sharp transform provider
8
+ */
9
+ export const sharpOperationSchemas = {
10
+ convert: {
11
+ name: "convert",
12
+ description: "Convert image to a different format",
13
+ category: "Format",
14
+ parameters: {
15
+ to: {
16
+ type: "string",
17
+ title: "Target Format",
18
+ description: "Target image format",
19
+ enum: ["image/png", "image/jpeg", "image/webp", "image/avif"],
20
+ },
21
+ },
22
+ requiredParameters: ["to"],
23
+ },
24
+ resize: {
25
+ name: "resize",
26
+ description: "Resize image dimensions",
27
+ category: "Size",
28
+ parameters: {
29
+ width: {
30
+ type: "number",
31
+ title: "Width",
32
+ description: "Target width in pixels",
33
+ minimum: 1,
34
+ maximum: 4096,
35
+ },
36
+ height: {
37
+ type: "number",
38
+ title: "Height",
39
+ description: "Target height in pixels",
40
+ minimum: 1,
41
+ maximum: 4096,
42
+ },
43
+ fit: {
44
+ type: "string",
45
+ title: "Fit Mode",
46
+ description: "How to fit the image into the target dimensions",
47
+ enum: ["cover", "contain", "fill"],
48
+ default: "cover",
49
+ },
50
+ },
51
+ requiredParameters: [],
52
+ },
53
+ composite: {
54
+ name: "composite",
55
+ description: "Overlay images on top of a base image",
56
+ category: "Composition",
57
+ parameters: {
58
+ overlays: {
59
+ type: "array",
60
+ title: "Overlays",
61
+ description: "Array of images to overlay with their positions",
62
+ items: {
63
+ type: "object",
64
+ properties: {
65
+ left: { type: "number", title: "Left Position" },
66
+ top: { type: "number", title: "Top Position" },
67
+ },
68
+ },
69
+ },
70
+ },
71
+ requiredParameters: ["overlays"],
72
+ },
73
+ blur: {
74
+ name: "blur",
75
+ description: "Apply Gaussian blur to the image",
76
+ category: "Filters",
77
+ parameters: {
78
+ sigma: {
79
+ type: "number",
80
+ title: "Blur Amount",
81
+ description: "Sigma value for Gaussian blur (0.3-1000)",
82
+ default: 3,
83
+ minimum: 0.3,
84
+ maximum: 1000,
85
+ },
86
+ },
87
+ requiredParameters: [],
88
+ },
89
+ sharpen: {
90
+ name: "sharpen",
91
+ description: "Sharpen the image",
92
+ category: "Filters",
93
+ parameters: {
94
+ sigma: {
95
+ type: "number",
96
+ title: "Sigma",
97
+ description: "Sigma value for sharpening",
98
+ },
99
+ m1: {
100
+ type: "number",
101
+ title: "Flat",
102
+ description: "Flat level",
103
+ },
104
+ m2: {
105
+ type: "number",
106
+ title: "Jagged",
107
+ description: "Jagged level",
108
+ },
109
+ },
110
+ requiredParameters: [],
111
+ },
112
+ grayscale: {
113
+ name: "grayscale",
114
+ description: "Convert image to grayscale",
115
+ category: "Filters",
116
+ parameters: {},
117
+ requiredParameters: [],
118
+ },
119
+ negate: {
120
+ name: "negate",
121
+ description: "Invert the colors of the image",
122
+ category: "Filters",
123
+ parameters: {},
124
+ requiredParameters: [],
125
+ },
126
+ normalize: {
127
+ name: "normalize",
128
+ description: "Auto-enhance contrast by stretching luminance",
129
+ category: "Filters",
130
+ parameters: {},
131
+ requiredParameters: [],
132
+ },
133
+ threshold: {
134
+ name: "threshold",
135
+ description: "Convert image to pure black and white",
136
+ category: "Filters",
137
+ parameters: {
138
+ value: {
139
+ type: "number",
140
+ title: "Threshold",
141
+ description: "Threshold value (0-255)",
142
+ default: 128,
143
+ minimum: 0,
144
+ maximum: 255,
145
+ },
146
+ },
147
+ requiredParameters: [],
148
+ },
149
+ modulate: {
150
+ name: "modulate",
151
+ description: "Adjust brightness, saturation, and hue",
152
+ category: "Adjustments",
153
+ parameters: {
154
+ brightness: {
155
+ type: "number",
156
+ title: "Brightness",
157
+ description: "Brightness multiplier (1 = no change)",
158
+ default: 1,
159
+ minimum: 0,
160
+ },
161
+ saturation: {
162
+ type: "number",
163
+ title: "Saturation",
164
+ description: "Saturation multiplier (1 = no change)",
165
+ default: 1,
166
+ minimum: 0,
167
+ },
168
+ hue: {
169
+ type: "number",
170
+ title: "Hue",
171
+ description: "Hue rotation in degrees",
172
+ default: 0,
173
+ },
174
+ lightness: {
175
+ type: "number",
176
+ title: "Lightness",
177
+ description: "Lightness adjustment",
178
+ },
179
+ },
180
+ requiredParameters: [],
181
+ },
182
+ tint: {
183
+ name: "tint",
184
+ description: "Apply a color tint overlay",
185
+ category: "Filters",
186
+ parameters: {
187
+ color: {
188
+ type: "string",
189
+ title: "Tint Color",
190
+ description: "Color to tint the image (hex or named color)",
191
+ },
192
+ },
193
+ requiredParameters: ["color"],
194
+ },
195
+ extend: {
196
+ name: "extend",
197
+ description: "Add borders/padding around the image",
198
+ category: "Size",
199
+ parameters: {
200
+ top: {
201
+ type: "number",
202
+ title: "Top",
203
+ description: "Padding on top in pixels",
204
+ default: 0,
205
+ minimum: 0,
206
+ },
207
+ bottom: {
208
+ type: "number",
209
+ title: "Bottom",
210
+ description: "Padding on bottom in pixels",
211
+ default: 0,
212
+ minimum: 0,
213
+ },
214
+ left: {
215
+ type: "number",
216
+ title: "Left",
217
+ description: "Padding on left in pixels",
218
+ default: 0,
219
+ minimum: 0,
220
+ },
221
+ right: {
222
+ type: "number",
223
+ title: "Right",
224
+ description: "Padding on right in pixels",
225
+ default: 0,
226
+ minimum: 0,
227
+ },
228
+ background: {
229
+ type: "string",
230
+ title: "Background",
231
+ description: "Background color for the padding",
232
+ default: "#000000",
233
+ },
234
+ },
235
+ requiredParameters: [],
236
+ },
237
+ extract: {
238
+ name: "extract",
239
+ description: "Crop a region from the image",
240
+ category: "Size",
241
+ parameters: {
242
+ left: {
243
+ type: "number",
244
+ title: "Left",
245
+ description: "Left edge of crop region",
246
+ minimum: 0,
247
+ },
248
+ top: {
249
+ type: "number",
250
+ title: "Top",
251
+ description: "Top edge of crop region",
252
+ minimum: 0,
253
+ },
254
+ width: {
255
+ type: "number",
256
+ title: "Width",
257
+ description: "Width of crop region",
258
+ minimum: 1,
259
+ },
260
+ height: {
261
+ type: "number",
262
+ title: "Height",
263
+ description: "Height of crop region",
264
+ minimum: 1,
265
+ },
266
+ },
267
+ requiredParameters: ["left", "top", "width", "height"],
268
+ },
269
+ roundCorners: {
270
+ name: "roundCorners",
271
+ description: "Round the corners of the image",
272
+ category: "Effects",
273
+ parameters: {
274
+ radius: {
275
+ type: "number",
276
+ title: "Radius",
277
+ description: "Corner radius in pixels",
278
+ minimum: 0,
279
+ },
280
+ },
281
+ requiredParameters: ["radius"],
282
+ },
283
+ addText: {
284
+ name: "addText",
285
+ description: "Add text overlay to the image",
286
+ category: "Text",
287
+ parameters: {
288
+ text: {
289
+ type: "string",
290
+ title: "Text",
291
+ description: "Text to add to the image",
292
+ },
293
+ x: {
294
+ type: "number",
295
+ title: "X Position",
296
+ description: "Horizontal position",
297
+ },
298
+ y: {
299
+ type: "number",
300
+ title: "Y Position",
301
+ description: "Vertical position",
302
+ },
303
+ fontSize: {
304
+ type: "number",
305
+ title: "Font Size",
306
+ description: "Font size in pixels",
307
+ default: 32,
308
+ },
309
+ color: {
310
+ type: "string",
311
+ title: "Text Color",
312
+ description: "Color of the text",
313
+ default: "#ffffff",
314
+ },
315
+ fontFamily: {
316
+ type: "string",
317
+ title: "Font Family",
318
+ description: "Font family to use",
319
+ default: "sans-serif",
320
+ },
321
+ },
322
+ requiredParameters: ["text"],
323
+ },
324
+ addCaption: {
325
+ name: "addCaption",
326
+ description: "Add a caption bar to the image",
327
+ category: "Text",
328
+ parameters: {
329
+ text: {
330
+ type: "string",
331
+ title: "Caption",
332
+ description: "Caption text",
333
+ },
334
+ position: {
335
+ type: "string",
336
+ title: "Position",
337
+ description: "Where to place the caption",
338
+ enum: ["top", "bottom"],
339
+ default: "bottom",
340
+ },
341
+ fontSize: {
342
+ type: "number",
343
+ title: "Font Size",
344
+ description: "Font size in pixels",
345
+ default: 24,
346
+ },
347
+ backgroundColor: {
348
+ type: "string",
349
+ title: "Background Color",
350
+ description: "Caption bar background color",
351
+ default: "#000000",
352
+ },
353
+ textColor: {
354
+ type: "string",
355
+ title: "Text Color",
356
+ description: "Caption text color",
357
+ default: "#ffffff",
358
+ },
359
+ },
360
+ requiredParameters: ["text"],
361
+ },
362
+ preset: {
363
+ name: "preset",
364
+ description: "Apply a preset filter effect",
365
+ category: "Effects",
366
+ parameters: {
367
+ name: {
368
+ type: "string",
369
+ title: "Preset Name",
370
+ description: "Name of the preset filter to apply",
371
+ enum: ["vintage", "sepia", "cool", "warm", "dramatic", "muted"],
372
+ },
373
+ },
374
+ requiredParameters: ["name"],
375
+ },
376
+ };
377
+ /**
378
+ * Transform provider using Sharp for image manipulation and Resvg for SVG rendering
379
+ */
380
+ export class SharpTransformProvider {
381
+ name = "sharp";
382
+ operationSchemas = sharpOperationSchemas;
383
+ async convert(input, to) {
384
+ try {
385
+ let sharpInstance;
386
+ // If input is SVG, use Resvg to render it to PNG first
387
+ if (input.mime === "image/svg+xml") {
388
+ const resvg = new Resvg(input.bytes, {
389
+ fitTo: {
390
+ mode: "original",
391
+ },
392
+ });
393
+ const pngData = resvg.render();
394
+ const pngBuffer = pngData.asPng();
395
+ // Now we have PNG, continue with Sharp for format conversion
396
+ if (to === "image/png") {
397
+ return {
398
+ bytes: pngBuffer,
399
+ mime: "image/png",
400
+ width: pngData.width,
401
+ height: pngData.height,
402
+ source: input.source,
403
+ };
404
+ }
405
+ sharpInstance = sharp(pngBuffer);
406
+ }
407
+ else {
408
+ sharpInstance = sharp(input.bytes);
409
+ }
410
+ // Get metadata for width/height
411
+ const metadata = await sharpInstance.metadata();
412
+ // Convert to target format
413
+ let outputBuffer;
414
+ switch (to) {
415
+ case "image/png":
416
+ outputBuffer = await sharpInstance.png().toBuffer();
417
+ break;
418
+ case "image/jpeg":
419
+ outputBuffer = await sharpInstance.jpeg({ quality: 90 }).toBuffer();
420
+ break;
421
+ case "image/webp":
422
+ outputBuffer = await sharpInstance.webp({ quality: 90 }).toBuffer();
423
+ break;
424
+ case "image/avif":
425
+ outputBuffer = await sharpInstance.avif({ quality: 90 }).toBuffer();
426
+ break;
427
+ case "image/svg+xml":
428
+ throw new TransformError("Cannot convert raster images to SVG");
429
+ default:
430
+ throw new TransformError(`Unsupported target format: ${to}`);
431
+ }
432
+ return {
433
+ bytes: outputBuffer,
434
+ mime: to,
435
+ width: metadata.width,
436
+ height: metadata.height,
437
+ source: input.source,
438
+ };
439
+ }
440
+ catch (error) {
441
+ if (error instanceof TransformError) {
442
+ throw error;
443
+ }
444
+ throw new TransformError(`Failed to convert image: ${error instanceof Error ? error.message : String(error)}`);
445
+ }
446
+ }
447
+ async resize(input, opts) {
448
+ try {
449
+ let sharpInstance;
450
+ // Handle SVG input
451
+ if (input.mime === "image/svg+xml") {
452
+ const resvg = new Resvg(input.bytes, {
453
+ fitTo: {
454
+ mode: "width",
455
+ value: opts.width || input.width || 1200,
456
+ },
457
+ });
458
+ const pngData = resvg.render();
459
+ const pngBuffer = pngData.asPng();
460
+ sharpInstance = sharp(pngBuffer);
461
+ }
462
+ else {
463
+ sharpInstance = sharp(input.bytes);
464
+ }
465
+ // Apply resize
466
+ const resized = sharpInstance.resize({
467
+ width: opts.width,
468
+ height: opts.height,
469
+ fit: opts.fit || "cover",
470
+ });
471
+ const outputBuffer = await resized.toBuffer();
472
+ const metadata = await sharp(outputBuffer).metadata();
473
+ return {
474
+ bytes: outputBuffer,
475
+ mime: input.mime === "image/svg+xml" ? "image/png" : input.mime,
476
+ width: metadata.width,
477
+ height: metadata.height,
478
+ source: input.source,
479
+ };
480
+ }
481
+ catch (error) {
482
+ throw new TransformError(`Failed to resize image: ${error instanceof Error ? error.message : String(error)}`);
483
+ }
484
+ }
485
+ async composite(base, overlays) {
486
+ try {
487
+ let baseInstance = sharp(base.bytes);
488
+ const compositeInputs = overlays.map((overlay) => ({
489
+ input: overlay.blob.bytes,
490
+ left: overlay.left,
491
+ top: overlay.top,
492
+ }));
493
+ const result = await baseInstance.composite(compositeInputs).toBuffer();
494
+ const metadata = await sharp(result).metadata();
495
+ return {
496
+ bytes: result,
497
+ mime: base.mime,
498
+ width: metadata.width,
499
+ height: metadata.height,
500
+ source: base.source,
501
+ };
502
+ }
503
+ catch (error) {
504
+ throw new TransformError(`Failed to composite images: ${error instanceof Error ? error.message : String(error)}`);
505
+ }
506
+ }
507
+ async optimizeSvg(svg) {
508
+ if (svg.mime !== "image/svg+xml") {
509
+ throw new TransformError("optimizeSvg only works with SVG images");
510
+ }
511
+ // For MVP, we'll just return the SVG as-is
512
+ // In the future, we could integrate SVGO here
513
+ return svg;
514
+ }
515
+ // ===== Filter Operations =====
516
+ async blur(input, sigma) {
517
+ try {
518
+ const sharpInstance = sharp(input.bytes);
519
+ const result = await sharpInstance.blur(sigma).toBuffer();
520
+ const metadata = await sharp(result).metadata();
521
+ return {
522
+ bytes: result,
523
+ mime: input.mime,
524
+ width: metadata.width,
525
+ height: metadata.height,
526
+ source: input.source,
527
+ };
528
+ }
529
+ catch (error) {
530
+ throw new TransformError(`Failed to blur image: ${error instanceof Error ? error.message : String(error)}`);
531
+ }
532
+ }
533
+ async sharpen(input, opts) {
534
+ try {
535
+ const sharpInstance = sharp(input.bytes);
536
+ const result = opts
537
+ ? await sharpInstance.sharpen(opts).toBuffer()
538
+ : await sharpInstance.sharpen().toBuffer();
539
+ const metadata = await sharp(result).metadata();
540
+ return {
541
+ bytes: result,
542
+ mime: input.mime,
543
+ width: metadata.width,
544
+ height: metadata.height,
545
+ source: input.source,
546
+ };
547
+ }
548
+ catch (error) {
549
+ throw new TransformError(`Failed to sharpen image: ${error instanceof Error ? error.message : String(error)}`);
550
+ }
551
+ }
552
+ async grayscale(input) {
553
+ try {
554
+ const sharpInstance = sharp(input.bytes);
555
+ const result = await sharpInstance.grayscale().toBuffer();
556
+ const metadata = await sharp(result).metadata();
557
+ return {
558
+ bytes: result,
559
+ mime: input.mime,
560
+ width: metadata.width,
561
+ height: metadata.height,
562
+ source: input.source,
563
+ };
564
+ }
565
+ catch (error) {
566
+ throw new TransformError(`Failed to convert image to grayscale: ${error instanceof Error ? error.message : String(error)}`);
567
+ }
568
+ }
569
+ async negate(input) {
570
+ try {
571
+ const sharpInstance = sharp(input.bytes);
572
+ const result = await sharpInstance.negate().toBuffer();
573
+ const metadata = await sharp(result).metadata();
574
+ return {
575
+ bytes: result,
576
+ mime: input.mime,
577
+ width: metadata.width,
578
+ height: metadata.height,
579
+ source: input.source,
580
+ };
581
+ }
582
+ catch (error) {
583
+ throw new TransformError(`Failed to negate image: ${error instanceof Error ? error.message : String(error)}`);
584
+ }
585
+ }
586
+ async normalize(input) {
587
+ try {
588
+ const sharpInstance = sharp(input.bytes);
589
+ const result = await sharpInstance.normalize().toBuffer();
590
+ const metadata = await sharp(result).metadata();
591
+ return {
592
+ bytes: result,
593
+ mime: input.mime,
594
+ width: metadata.width,
595
+ height: metadata.height,
596
+ source: input.source,
597
+ };
598
+ }
599
+ catch (error) {
600
+ throw new TransformError(`Failed to normalize image: ${error instanceof Error ? error.message : String(error)}`);
601
+ }
602
+ }
603
+ async threshold(input, value) {
604
+ try {
605
+ const sharpInstance = sharp(input.bytes);
606
+ const result = await sharpInstance.threshold(value).toBuffer();
607
+ const metadata = await sharp(result).metadata();
608
+ return {
609
+ bytes: result,
610
+ mime: input.mime,
611
+ width: metadata.width,
612
+ height: metadata.height,
613
+ source: input.source,
614
+ };
615
+ }
616
+ catch (error) {
617
+ throw new TransformError(`Failed to threshold image: ${error instanceof Error ? error.message : String(error)}`);
618
+ }
619
+ }
620
+ async modulate(input, opts) {
621
+ try {
622
+ const sharpInstance = sharp(input.bytes);
623
+ const result = await sharpInstance.modulate(opts).toBuffer();
624
+ const metadata = await sharp(result).metadata();
625
+ return {
626
+ bytes: result,
627
+ mime: input.mime,
628
+ width: metadata.width,
629
+ height: metadata.height,
630
+ source: input.source,
631
+ };
632
+ }
633
+ catch (error) {
634
+ throw new TransformError(`Failed to modulate image: ${error instanceof Error ? error.message : String(error)}`);
635
+ }
636
+ }
637
+ async tint(input, color) {
638
+ try {
639
+ const sharpInstance = sharp(input.bytes);
640
+ const result = await sharpInstance.tint(color).toBuffer();
641
+ const metadata = await sharp(result).metadata();
642
+ return {
643
+ bytes: result,
644
+ mime: input.mime,
645
+ width: metadata.width,
646
+ height: metadata.height,
647
+ source: input.source,
648
+ };
649
+ }
650
+ catch (error) {
651
+ throw new TransformError(`Failed to tint image: ${error instanceof Error ? error.message : String(error)}`);
652
+ }
653
+ }
654
+ // ===== Border & Frame Operations =====
655
+ async extend(input, opts) {
656
+ try {
657
+ const sharpInstance = sharp(input.bytes);
658
+ const result = await sharpInstance.extend(opts).toBuffer();
659
+ const metadata = await sharp(result).metadata();
660
+ return {
661
+ bytes: result,
662
+ mime: input.mime,
663
+ width: metadata.width,
664
+ height: metadata.height,
665
+ source: input.source,
666
+ };
667
+ }
668
+ catch (error) {
669
+ throw new TransformError(`Failed to extend image: ${error instanceof Error ? error.message : String(error)}`);
670
+ }
671
+ }
672
+ async extract(input, region) {
673
+ try {
674
+ const sharpInstance = sharp(input.bytes);
675
+ const result = await sharpInstance.extract(region).toBuffer();
676
+ const metadata = await sharp(result).metadata();
677
+ return {
678
+ bytes: result,
679
+ mime: input.mime,
680
+ width: metadata.width,
681
+ height: metadata.height,
682
+ source: input.source,
683
+ };
684
+ }
685
+ catch (error) {
686
+ throw new TransformError(`Failed to extract region: ${error instanceof Error ? error.message : String(error)}`);
687
+ }
688
+ }
689
+ async roundCorners(input, radius) {
690
+ try {
691
+ const sharpInstance = sharp(input.bytes);
692
+ const metadata = await sharpInstance.metadata();
693
+ const width = metadata.width || 100;
694
+ const height = metadata.height || 100;
695
+ // Create SVG mask for rounded corners
696
+ const mask = Buffer.from(`<svg width="${width}" height="${height}">
697
+ <rect x="0" y="0" width="${width}" height="${height}" rx="${radius}" ry="${radius}" fill="white"/>
698
+ </svg>`);
699
+ // Apply mask using composite
700
+ const result = await sharpInstance
701
+ .composite([
702
+ {
703
+ input: mask,
704
+ blend: "dest-in",
705
+ },
706
+ ])
707
+ .toBuffer();
708
+ return {
709
+ bytes: result,
710
+ mime: input.mime === "image/svg+xml" ? "image/png" : input.mime,
711
+ width,
712
+ height,
713
+ source: input.source,
714
+ };
715
+ }
716
+ catch (error) {
717
+ throw new TransformError(`Failed to round corners: ${error instanceof Error ? error.message : String(error)}`);
718
+ }
719
+ }
720
+ // ===== Text Operations =====
721
+ async addText(input, options) {
722
+ return TextRenderer.addText(input, options);
723
+ }
724
+ async addCaption(input, options) {
725
+ return TextRenderer.addCaption(input, options);
726
+ }
727
+ // ===== Preset Filters =====
728
+ async preset(input, presetName) {
729
+ return FilterPresets.applyPreset(input, presetName);
730
+ }
731
+ }
732
+ //# sourceMappingURL=sharp.js.map