image-edit-tools 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -3
- package/dist/ops/add-text.d.ts.map +1 -1
- package/dist/ops/add-text.js +32 -2
- package/dist/ops/add-text.js.map +1 -1
- package/dist/ops/composite.d.ts.map +1 -1
- package/dist/ops/composite.js +20 -1
- package/dist/ops/composite.js.map +1 -1
- package/dist/ops/detect-faces.d.ts.map +1 -1
- package/dist/ops/detect-faces.js +10 -3
- package/dist/ops/detect-faces.js.map +1 -1
- package/dist/ops/detect-subject.d.ts.map +1 -1
- package/dist/ops/detect-subject.js +10 -2
- package/dist/ops/detect-subject.js.map +1 -1
- package/dist/ops/pipeline.d.ts.map +1 -1
- package/dist/ops/pipeline.js +3 -2
- package/dist/ops/pipeline.js.map +1 -1
- package/dist/ops/remove-bg.d.ts.map +1 -1
- package/dist/ops/remove-bg.js +10 -4
- package/dist/ops/remove-bg.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/result.d.ts +1 -1
- package/dist/utils/result.d.ts.map +1 -1
- package/dist/utils/result.js +6 -1
- package/dist/utils/result.js.map +1 -1
- package/docs/AGENTS.md +2 -0
- package/examples/instagram-card-news.ts +386 -0
- package/package.json +4 -2
- package/src/ops/add-text.ts +33 -2
- package/src/ops/composite.ts +23 -1
- package/src/ops/detect-faces.ts +10 -3
- package/src/ops/detect-subject.ts +10 -2
- package/src/ops/pipeline.ts +5 -2
- package/src/ops/remove-bg.ts +10 -4
- package/src/types.ts +1 -1
- package/src/utils/result.ts +5 -1
- package/tests/unit/add-text.test.ts +21 -0
- package/tests/unit/composite.test.ts +12 -0
- package/vitest.config.ts +1 -0
package/README.md
CHANGED
|
@@ -12,17 +12,39 @@ A TypeScript-first, deterministic, purely functional image editing SDK designed
|
|
|
12
12
|
npm install image-edit-tools
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
> **Note**: AI features (`removeBg`, `detectFaces`, `detectSubject`) require `@xenova/transformers`, which is an optional dependency. All other 16+ operations work out of the box on any platform with just Node.js and sharp.
|
|
16
|
+
|
|
15
17
|
## Quick Start (Code)
|
|
16
18
|
```typescript
|
|
17
|
-
import { crop, resize,
|
|
19
|
+
import { crop, resize, addText, composite, pipeline } from 'image-edit-tools';
|
|
18
20
|
|
|
21
|
+
// Basic resize
|
|
19
22
|
const result = await resize('/path/to/img.jpg', { width: 800 });
|
|
20
23
|
if (!result.ok) {
|
|
21
24
|
console.error(result.error);
|
|
22
25
|
return;
|
|
23
26
|
}
|
|
24
|
-
|
|
25
|
-
//
|
|
27
|
+
|
|
28
|
+
// Add text overlay — note the { layers: [...] } wrapper
|
|
29
|
+
const withText = await addText(result.data, {
|
|
30
|
+
layers: [
|
|
31
|
+
{ text: 'Hello!', x: 40, y: 40, fontSize: 48, color: '#FFFFFF' },
|
|
32
|
+
],
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Composite images — same { layers: [...] } pattern
|
|
36
|
+
const merged = await composite(result.data, {
|
|
37
|
+
layers: [
|
|
38
|
+
{ input: '/path/to/overlay.png', left: 0, top: 0, blend: 'over' },
|
|
39
|
+
],
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Pipeline: chain multiple operations in one call
|
|
43
|
+
const card = await pipeline('/path/to/photo.jpg', [
|
|
44
|
+
{ op: 'resize', width: 1080, height: 1080 },
|
|
45
|
+
{ op: 'adjust', brightness: 10, contrast: 5 },
|
|
46
|
+
{ op: 'addText', layers: [{ text: 'Draft', x: 540, y: 540, fontSize: 64, color: '#FF0000', anchor: 'top-center' }] },
|
|
47
|
+
]);
|
|
26
48
|
```
|
|
27
49
|
|
|
28
50
|
## Running the MCP Server
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"add-text.d.ts","sourceRoot":"","sources":["../../src/ops/add-text.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAyB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"add-text.d.ts","sourceRoot":"","sources":["../../src/ops/add-text.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAyB,MAAM,aAAa,CAAC;AAqDxF,wBAAsB,OAAO,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,SAAS,EAAE,CAAA;CAAE,GAAG,OAAO,CAAC,WAAW,CAAC,CAmHvG"}
|
package/dist/ops/add-text.js
CHANGED
|
@@ -25,6 +25,14 @@ function wrapText(text, fontSize, maxWidth) {
|
|
|
25
25
|
lines.push(currentLine);
|
|
26
26
|
return lines;
|
|
27
27
|
}
|
|
28
|
+
function escapeXml(text) {
|
|
29
|
+
return text
|
|
30
|
+
.replace(/&/g, '&')
|
|
31
|
+
.replace(/</g, '<')
|
|
32
|
+
.replace(/>/g, '>')
|
|
33
|
+
.replace(/"/g, '"')
|
|
34
|
+
.replace(/'/g, ''');
|
|
35
|
+
}
|
|
28
36
|
function getAnchorProps(anchor = 'top-left') {
|
|
29
37
|
const parts = anchor.split('-');
|
|
30
38
|
const yAlign = parts.length === 2 ? parts[0] : parts[0] === 'center' ? 'middle' : parts[0];
|
|
@@ -56,6 +64,8 @@ export async function addText(input, options) {
|
|
|
56
64
|
let defs = '';
|
|
57
65
|
let svgBody = '';
|
|
58
66
|
let fontImports = new Set();
|
|
67
|
+
const warnings = [];
|
|
68
|
+
let contentBottom = 0;
|
|
59
69
|
for (let i = 0; i < options.layers.length; i++) {
|
|
60
70
|
const layer = options.layers[i];
|
|
61
71
|
const fontSize = layer.fontSize ?? 24;
|
|
@@ -99,10 +109,28 @@ export async function addText(input, options) {
|
|
|
99
109
|
layerSvg += `<text x="${layer.x}" y="${layer.y}" style="${style}">`;
|
|
100
110
|
lines.forEach((line, idx) => {
|
|
101
111
|
let dy = idx === 0 ? 0 : fontSize * lineHeight;
|
|
102
|
-
layerSvg += `<tspan x="${layer.x}" dy="${dy}">${line}</tspan>`;
|
|
112
|
+
layerSvg += `<tspan x="${layer.x}" dy="${dy}">${escapeXml(line)}</tspan>`;
|
|
103
113
|
});
|
|
104
114
|
layerSvg += `</text>`;
|
|
105
115
|
svgBody += `<g style="isolation: isolate">${layerSvg}</g>`;
|
|
116
|
+
// Compute bounding box for overflow detection
|
|
117
|
+
let boxX = layer.x;
|
|
118
|
+
let boxY = layer.y;
|
|
119
|
+
if (textAnchor === 'middle')
|
|
120
|
+
boxX -= approxMaxWidth / 2;
|
|
121
|
+
else if (textAnchor === 'end')
|
|
122
|
+
boxX -= approxMaxWidth;
|
|
123
|
+
if (dominantBaseline === 'middle')
|
|
124
|
+
boxY -= totalHeight / 2;
|
|
125
|
+
else if (dominantBaseline === 'alphabetic')
|
|
126
|
+
boxY -= totalHeight - fontSize;
|
|
127
|
+
const boxBottom = boxY + totalHeight;
|
|
128
|
+
const boxRight = boxX + approxMaxWidth;
|
|
129
|
+
if (boxBottom > contentBottom)
|
|
130
|
+
contentBottom = boxBottom;
|
|
131
|
+
if (boxX < 0 || boxY < 0 || boxRight > width || boxBottom > height) {
|
|
132
|
+
warnings.push(`Text layer ${i} ("${layer.text.slice(0, 20)}...") extends beyond canvas bounds.`);
|
|
133
|
+
}
|
|
106
134
|
}
|
|
107
135
|
const fontStyle = fontImports.size > 0 ? `<style>${Array.from(fontImports).join('\n')}</style>` : '';
|
|
108
136
|
const svgString = `<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
|
|
@@ -113,7 +141,9 @@ export async function addText(input, options) {
|
|
|
113
141
|
const output = await sharp(buffer)
|
|
114
142
|
.composite([{ input: Buffer.from(svgString), blend: 'over' }])
|
|
115
143
|
.toBuffer();
|
|
116
|
-
|
|
144
|
+
const result = ok(output, warnings);
|
|
145
|
+
result.bounds = { contentBottom: Math.round(contentBottom) };
|
|
146
|
+
return result;
|
|
117
147
|
}
|
|
118
148
|
catch (e) {
|
|
119
149
|
const msg = e.message || '';
|
package/dist/ops/add-text.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"add-text.js","sourceRoot":"","sources":["../../src/ops/add-text.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"add-text.js","sourceRoot":"","sources":["../../src/ops/add-text.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composite.d.ts","sourceRoot":"","sources":["../../src/ops/composite.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,WAAW,EAAa,MAAM,aAAa,CAAC;AAIjF,wBAAsB,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,cAAc,EAAE,CAAA;CAAE,GAAG,OAAO,CAAC,WAAW,CAAC,
|
|
1
|
+
{"version":3,"file":"composite.d.ts","sourceRoot":"","sources":["../../src/ops/composite.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,WAAW,EAAa,MAAM,aAAa,CAAC;AAIjF,wBAAsB,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,cAAc,EAAE,CAAA;CAAE,GAAG,OAAO,CAAC,WAAW,CAAC,CAwE9G"}
|
package/dist/ops/composite.js
CHANGED
|
@@ -31,13 +31,32 @@ export async function composite(input, options) {
|
|
|
31
31
|
};
|
|
32
32
|
};
|
|
33
33
|
const overlays = await Promise.all(options.layers.map(loadLayer));
|
|
34
|
+
// Detect potentially problematic opaque layers
|
|
35
|
+
const warnings = [];
|
|
36
|
+
const baseMeta = await sharp(buffer).metadata();
|
|
37
|
+
const canvasArea = (baseMeta.width ?? 1) * (baseMeta.height ?? 1);
|
|
38
|
+
for (let i = 0; i < options.layers.length; i++) {
|
|
39
|
+
const layer = options.layers[i];
|
|
40
|
+
const layerOpacity = layer.opacity ?? 1.0;
|
|
41
|
+
if (layerOpacity >= 0.9) {
|
|
42
|
+
try {
|
|
43
|
+
const layerBuf = await loadImage(layer.image);
|
|
44
|
+
const layerMeta = await sharp(layerBuf).metadata();
|
|
45
|
+
const layerArea = (layerMeta.width ?? 0) * (layerMeta.height ?? 0);
|
|
46
|
+
if (layerArea / canvasArea > 0.25) {
|
|
47
|
+
warnings.push(`Layer ${i} is opaque (opacity=${layerOpacity}) and covers ${Math.round(layerArea / canvasArea * 100)}% of the canvas. It may hide content underneath.`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch { /* skip analysis for unreadable layers */ }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
34
53
|
let output = buffer;
|
|
35
54
|
if (overlays.length > 0) {
|
|
36
55
|
output = await sharp(buffer)
|
|
37
56
|
.composite(overlays)
|
|
38
57
|
.toBuffer();
|
|
39
58
|
}
|
|
40
|
-
return ok(output);
|
|
59
|
+
return ok(output, warnings);
|
|
41
60
|
}
|
|
42
61
|
catch (e) {
|
|
43
62
|
const msg = e.message || '';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composite.js","sourceRoot":"","sources":["../../src/ops/composite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAA2C,SAAS,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAC;AAE7C,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,KAAiB,EAAE,OAAqC;IACtF,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,KAAK,CAAC,CAAC;QAEtC,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACtD,OAAO,GAAG,CAAC,uBAAuB,EAAE,SAAS,CAAC,aAAa,CAAC,CAAC;QAC/D,CAAC;QAED,MAAM,SAAS,GAAG,KAAK,EAAE,KAAqB,EAAiC,EAAE;YAC/E,IAAI,QAAQ,GAAG,MAAM,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAE5C,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,IAAI,CAAC,IAAI,KAAK,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;gBAC3E,QAAQ,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC;qBAC7B,WAAW,EAAE;qBACb,SAAS,CAAC;oBACT;wBACE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC;wBACpE,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE;wBACzC,IAAI,EAAE,IAAI;wBACV,KAAK,EAAE,SAAS;qBACjB;iBACF,CAAC;qBACD,QAAQ,EAAE,CAAC;YAChB,CAAC;YAED,OAAO;gBACL,KAAK,EAAE,QAAQ;gBACf,IAAI,EAAE,KAAK,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBACrD,GAAG,EAAE,KAAK,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBACpD,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,MAAM;aAC7B,CAAC;QACJ,CAAC,CAAC;QAEF,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;QAElE,IAAI,MAAM,GAAG,MAAM,CAAC;QACpB,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC;iBACzB,SAAS,CAAC,QAAQ,CAAC;iBACnB,QAAQ,EAAE,CAAC;QAChB,CAAC;QAED,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"composite.js","sourceRoot":"","sources":["../../src/ops/composite.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detect-faces.d.ts","sourceRoot":"","sources":["../../src/ops/detect-faces.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAa,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"detect-faces.d.ts","sourceRoot":"","sources":["../../src/ops/detect-faces.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAa,MAAM,aAAa,CAAC;AAKzE,wBAAsB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CA0CnF"}
|
package/dist/ops/detect-faces.js
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { ErrorCode } from '../types.js';
|
|
2
2
|
import { loadImage } from '../utils/load-image.js';
|
|
3
3
|
import { ok, err } from '../utils/result.js';
|
|
4
|
-
import { pipeline, RawImage } from '@xenova/transformers';
|
|
5
4
|
import sharp from 'sharp';
|
|
6
5
|
export async function detectFaces(input) {
|
|
7
6
|
try {
|
|
8
7
|
const buffer = await loadImage(input);
|
|
9
|
-
let detector;
|
|
8
|
+
let detector, pipeline_tf, RawImage;
|
|
10
9
|
try {
|
|
11
|
-
|
|
10
|
+
const tf = await import('@xenova/transformers');
|
|
11
|
+
pipeline_tf = tf.pipeline;
|
|
12
|
+
RawImage = tf.RawImage;
|
|
13
|
+
}
|
|
14
|
+
catch (e) {
|
|
15
|
+
return err('Failed to load @xenova/transformers (check architecture bindings).', ErrorCode.PROCESSING_FAILED);
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
detector = await pipeline_tf('object-detection', 'Xenova/detr-resnet-50', {
|
|
12
19
|
quantized: true,
|
|
13
20
|
});
|
|
14
21
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detect-faces.js","sourceRoot":"","sources":["../../src/ops/detect-faces.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmC,SAAS,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,
|
|
1
|
+
{"version":3,"file":"detect-faces.js","sourceRoot":"","sources":["../../src/ops/detect-faces.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detect-subject.d.ts","sourceRoot":"","sources":["../../src/ops/detect-subject.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAa,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"detect-subject.d.ts","sourceRoot":"","sources":["../../src/ops/detect-subject.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAa,MAAM,aAAa,CAAC;AAMzE,wBAAsB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAiFrF"}
|
|
@@ -3,12 +3,20 @@ import { loadImage } from '../utils/load-image.js';
|
|
|
3
3
|
import { err, ok } from '../utils/result.js';
|
|
4
4
|
import { getImageMetadata } from '../utils/validate.js';
|
|
5
5
|
import sharp from 'sharp';
|
|
6
|
-
import { AutoModel, AutoProcessor, RawImage } from '@xenova/transformers';
|
|
7
6
|
export async function detectSubject(input) {
|
|
8
7
|
try {
|
|
9
8
|
const buffer = await loadImage(input);
|
|
10
9
|
const meta = await getImageMetadata(buffer);
|
|
11
|
-
let model, processor;
|
|
10
|
+
let model, processor, AutoModel, AutoProcessor, RawImage;
|
|
11
|
+
try {
|
|
12
|
+
const tf = await import('@xenova/transformers');
|
|
13
|
+
AutoModel = tf.AutoModel;
|
|
14
|
+
AutoProcessor = tf.AutoProcessor;
|
|
15
|
+
RawImage = tf.RawImage;
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
return err('Failed to load @xenova/transformers (check architecture bindings).', ErrorCode.PROCESSING_FAILED);
|
|
19
|
+
}
|
|
12
20
|
try {
|
|
13
21
|
model = await AutoModel.from_pretrained('briaai/RMBG-1.4', {
|
|
14
22
|
config: { model_type: 'custom' },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detect-subject.js","sourceRoot":"","sources":["../../src/ops/detect-subject.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmC,SAAS,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"detect-subject.js","sourceRoot":"","sources":["../../src/ops/detect-subject.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/ops/pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,iBAAiB,EAAa,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/ops/pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,iBAAiB,EAAa,MAAM,aAAa,CAAC;AAapF,wBAAsB,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,iBAAiB,EAAE,GAAG,OAAO,CAAC,WAAW,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAkD3H"}
|
package/dist/ops/pipeline.js
CHANGED
|
@@ -10,7 +10,6 @@ import { composite } from './composite.js';
|
|
|
10
10
|
import { watermark } from './watermark.js';
|
|
11
11
|
import { convert } from './convert.js';
|
|
12
12
|
import { optimize } from './optimize.js';
|
|
13
|
-
import { removeBg } from './remove-bg.js';
|
|
14
13
|
export async function pipeline(input, operations) {
|
|
15
14
|
let currentImage = input;
|
|
16
15
|
for (let i = 0; i < operations.length; i++) {
|
|
@@ -51,9 +50,11 @@ export async function pipeline(input, operations) {
|
|
|
51
50
|
case 'optimize':
|
|
52
51
|
result = await optimize(currentImage, op);
|
|
53
52
|
break;
|
|
54
|
-
case 'removeBg':
|
|
53
|
+
case 'removeBg': {
|
|
54
|
+
const { removeBg } = await import('./remove-bg.js');
|
|
55
55
|
result = await removeBg(currentImage, op);
|
|
56
56
|
break;
|
|
57
|
+
}
|
|
57
58
|
default:
|
|
58
59
|
return { ok: false, error: `Unknown operation`, code: ErrorCode.INVALID_INPUT, step: i };
|
|
59
60
|
}
|
package/dist/ops/pipeline.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/ops/pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8C,SAAS,EAAE,MAAM,aAAa,CAAC;AACpF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/ops/pipeline.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remove-bg.d.ts","sourceRoot":"","sources":["../../src/ops/remove-bg.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,WAAW,EAAa,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"remove-bg.d.ts","sourceRoot":"","sources":["../../src/ops/remove-bg.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,WAAW,EAAa,MAAM,aAAa,CAAC;AAKlF,wBAAsB,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,WAAW,CAAC,CAiFrG"}
|
package/dist/ops/remove-bg.js
CHANGED
|
@@ -3,12 +3,20 @@ import { ErrorCode } from '../types.js';
|
|
|
3
3
|
import { loadImage } from '../utils/load-image.js';
|
|
4
4
|
import { err, ok } from '../utils/result.js';
|
|
5
5
|
import { getImageMetadata } from '../utils/validate.js';
|
|
6
|
-
import { AutoModel, AutoProcessor, RawImage } from '@xenova/transformers';
|
|
7
6
|
export async function removeBg(input, options = {}) {
|
|
8
7
|
try {
|
|
9
8
|
const buffer = await loadImage(input);
|
|
10
9
|
const meta = await getImageMetadata(buffer);
|
|
11
|
-
let model, processor;
|
|
10
|
+
let model, processor, AutoModel, AutoProcessor, RawImage;
|
|
11
|
+
try {
|
|
12
|
+
const tf = await import('@xenova/transformers');
|
|
13
|
+
AutoModel = tf.AutoModel;
|
|
14
|
+
AutoProcessor = tf.AutoProcessor;
|
|
15
|
+
RawImage = tf.RawImage;
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
return err('Failed to load @xenova/transformers (check architecture bindings).', ErrorCode.PROCESSING_FAILED);
|
|
19
|
+
}
|
|
12
20
|
try {
|
|
13
21
|
model = await AutoModel.from_pretrained('briaai/RMBG-1.4', {
|
|
14
22
|
config: { model_type: 'custom' },
|
|
@@ -31,8 +39,6 @@ export async function removeBg(input, options = {}) {
|
|
|
31
39
|
return err('Model unavailable. Run: npx image-edit-tools download-models', ErrorCode.MODEL_NOT_FOUND);
|
|
32
40
|
}
|
|
33
41
|
// Process image
|
|
34
|
-
const rawImage = await RawImage.fromURL(URL.createObjectURL(new Blob([buffer]))); // Alternative for node:
|
|
35
|
-
// RawImage from buffer is easier using sharp. We need uint8 array of RGB or RGBA.
|
|
36
42
|
const rawRgb = await sharp(buffer).ensureAlpha().raw().toBuffer();
|
|
37
43
|
const img = new RawImage(new Uint8ClampedArray(rawRgb), meta.width, meta.height, 4);
|
|
38
44
|
const { pixel_values } = await processor(img);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remove-bg.js","sourceRoot":"","sources":["../../src/ops/remove-bg.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAA4C,SAAS,EAAE,MAAM,aAAa,CAAC;AAClF,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"remove-bg.js","sourceRoot":"","sources":["../../src/ops/remove-bg.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAA4C,SAAS,EAAE,MAAM,aAAa,CAAC;AAClF,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExD,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,KAAiB,EAAE,UAA2B,EAAE;IAC7E,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,KAAK,CAAC,CAAC;QACtC,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,MAAM,CAAC,CAAC;QAE5C,IAAI,KAAU,EAAE,SAAc,EAAE,SAAc,EAAE,aAAkB,EAAE,QAAa,CAAC;QAClF,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAC;YAChD,SAAS,GAAG,EAAE,CAAC,SAAS,CAAC;YACzB,aAAa,GAAG,EAAE,CAAC,aAAa,CAAC;YACjC,QAAQ,GAAG,EAAE,CAAC,QAAQ,CAAC;QACzB,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,OAAO,GAAG,CAAC,oEAAoE,EAAE,SAAS,CAAC,iBAAiB,CAAC,CAAC;QAChH,CAAC;QAED,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,SAAS,CAAC,eAAe,CAAC,iBAAiB,EAAE;gBACzD,MAAM,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE;gBAChC,SAAS,EAAE,IAAI,CAAC,2CAA2C;aAC5D,CAAC,CAAC;YACH,SAAS,GAAG,MAAM,aAAa,CAAC,eAAe,CAAC,iBAAiB,EAAE;gBACjE,MAAM,EAAE;oBACN,YAAY,EAAE,IAAI;oBAClB,MAAM,EAAE,KAAK;oBACb,UAAU,EAAE,IAAI;oBAChB,SAAS,EAAE,IAAI;oBACf,UAAU,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC;oBAC3B,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;oBACpB,QAAQ,EAAE,CAAC;oBACX,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;iBACpC;aACF,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,OAAO,GAAG,CAAC,8DAA8D,EAAE,SAAS,CAAC,eAAe,CAAC,CAAC;QACxG,CAAC;QAED,gBAAgB;QAChB,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC;QAClE,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,IAAI,iBAAiB,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAEpF,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,CAAC;QAC9C,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,KAAK,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;QAExD,gFAAgF;QAChF,oCAAoC;QACpC,MAAM,QAAQ,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACpD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC;YAC5C,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,qCAAqC;QACnH,CAAC;QAED,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;aACvG,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,sBAAsB;aACvE,QAAQ,EAAE,CAAC;QAEd,IAAI,SAAS,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC;aAChC,WAAW,EAAE;aACb,WAAW,CAAC,UAAU,CAAC,CAAC,6BAA6B;aACrD,GAAG,EAAE;aACL,QAAQ,EAAE,CAAC;QAEd,yBAAyB;QACzB,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;YACzB,SAAS,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC;iBAC/B,OAAO,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC;iBAC7C,QAAQ,EAAE,CAAC;QAChB,CAAC;aAAM,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;YAChC,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;YACpD,0FAA0F;YAC1F,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;YACnG,SAAS,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC;iBAChC,SAAS,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;iBAChD,QAAQ,EAAE,CAAC;QAChB,CAAC;QAED,OAAO,EAAE,CAAC,SAAS,CAAC,CAAC;IACvB,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,MAAM,GAAG,GAAG,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC;QAC5B,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,YAAY,CAAC,CAAC;QAClE,IAAI,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAAE,OAAO,GAAG,CAAC,gBAAgB,EAAE,SAAS,CAAC,aAAa,CAAC,CAAC;QAClF,OAAO,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,iBAAiB,CAAC,CAAC;IAC/C,CAAC;AACH,CAAC"}
|
package/dist/types.d.ts
CHANGED
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;AAEA;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;AAIzC,MAAM,MAAM,EAAE,CAAC,CAAC,IAAI;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/utils/result.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { Ok, Err, ErrorCode } from '../types.js';
|
|
2
|
-
export declare const ok: <T>(data: T) => Ok<T>;
|
|
2
|
+
export declare const ok: <T>(data: T, warnings?: string[]) => Ok<T>;
|
|
3
3
|
export declare const err: (error: string, code: ErrorCode) => Err;
|
|
4
4
|
//# sourceMappingURL=result.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"result.d.ts","sourceRoot":"","sources":["../../src/utils/result.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAEhD,eAAO,MAAM,EAAE,YAAa,CAAC,KAAG,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"result.d.ts","sourceRoot":"","sources":["../../src/utils/result.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAEhD,eAAO,MAAM,EAAE,YAAa,CAAC,aAAa,MAAM,EAAE,KAAG,GAAG,CAAC,CAIxD,CAAA;AACD,eAAO,MAAM,GAAG,UAAW,MAAM,QAAQ,SAAS,KAAG,GAAmC,CAAA"}
|
package/dist/utils/result.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
-
export const ok = (data) =>
|
|
1
|
+
export const ok = (data, warnings) => {
|
|
2
|
+
const result = { ok: true, data };
|
|
3
|
+
if (warnings && warnings.length > 0)
|
|
4
|
+
result.warnings = warnings;
|
|
5
|
+
return result;
|
|
6
|
+
};
|
|
2
7
|
export const err = (error, code) => ({ ok: false, error, code });
|
|
3
8
|
//# sourceMappingURL=result.js.map
|
package/dist/utils/result.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"result.js","sourceRoot":"","sources":["../../src/utils/result.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,EAAE,GAAG,CAAI,IAAO,EAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"result.js","sourceRoot":"","sources":["../../src/utils/result.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,EAAE,GAAG,CAAI,IAAO,EAAE,QAAmB,EAAS,EAAE;IAC3D,MAAM,MAAM,GAAU,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACzC,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAChE,OAAO,MAAM,CAAC;AAChB,CAAC,CAAA;AACD,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,KAAa,EAAE,IAAe,EAAO,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA"}
|
package/docs/AGENTS.md
CHANGED
|
@@ -11,6 +11,8 @@ Welcome, fellow AI agent! `image-edit-tools` is designed specifically for you to
|
|
|
11
11
|
- **Pipelines**: Instead of calling `image_crop`, then `image_resize`, then `image_convert` using three separate tool calls (which transfers images back and forth 3 times), use the `image_pipeline` tool! Pass an array of operations. It is dramatically faster and avoids memory overhead.
|
|
12
12
|
- **Analysis First**: When instructed to edit an image based on its content (e.g. "Blur the person's face"), first call `image_detect_faces`. Parse the coordinates, then construct an `image_blur_region` payload using exactly those coordinates.
|
|
13
13
|
- **No Side Effects**: All tools are pure functions. They compute and return the image. If the user asks you to modify a file, you must take the data URI returned by the tool and write it back to the file system using your filesystem tools.
|
|
14
|
+
- **Icons, not Emoji**: The `image_add_text` tool uses SVG rendering (librsvg), which **cannot render color emoji**. If you need icons or emoji, create them as separate PNG/SVG images and use `image_composite` to layer them onto the canvas at the desired position.
|
|
15
|
+
- **Check Warnings**: Responses may include a `warnings` array. Check it for overlap, overflow, or layer ordering issues.
|
|
14
16
|
|
|
15
17
|
## Example Chain (Watermarking)
|
|
16
18
|
1. **Goal**: Add a bottom-right watermark.
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instagram Card News Generator
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates: addText, composite, sharp SVG generation
|
|
5
|
+
* Output: 1080x1080 Instagram carousel slides (cover + 3 content + closing)
|
|
6
|
+
*
|
|
7
|
+
* NOTE: librsvg cannot render color emoji. This example uses composite()
|
|
8
|
+
* with SVG-generated icon shapes instead of emoji in addText().
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* npx tsx examples/instagram-card-news.ts
|
|
12
|
+
*/
|
|
13
|
+
import sharp from 'sharp';
|
|
14
|
+
import { writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
15
|
+
import { join, dirname } from 'path';
|
|
16
|
+
import { fileURLToPath } from 'url';
|
|
17
|
+
import { addText } from '../src/ops/add-text.js';
|
|
18
|
+
|
|
19
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
20
|
+
const __dirname = dirname(__filename);
|
|
21
|
+
const OUTPUT_DIR = join(__dirname, 'output');
|
|
22
|
+
|
|
23
|
+
if (!existsSync(OUTPUT_DIR)) mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
24
|
+
|
|
25
|
+
// ── Design Tokens ────────────────────────────────────────────────────────────
|
|
26
|
+
const SIZE = 1080;
|
|
27
|
+
const PALETTE = {
|
|
28
|
+
bg: '#0F172A',
|
|
29
|
+
accent: '#3B82F6',
|
|
30
|
+
surface: '#1E293B',
|
|
31
|
+
text: '#F8FAFC',
|
|
32
|
+
muted: '#94A3B8',
|
|
33
|
+
highlight:'#F59E0B',
|
|
34
|
+
purple: '#8B5CF6',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// ── SVG Shape Helpers ────────────────────────────────────────────────────────
|
|
38
|
+
async function canvas(color: string): Promise<Buffer> {
|
|
39
|
+
return sharp({ create: { width: SIZE, height: SIZE, channels: 4, background: color } }).png().toBuffer();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function roundedRect(w: number, h: number, color: string, radius: number, opacity = 1): Promise<Buffer> {
|
|
43
|
+
const svg = `<svg width="${w}" height="${h}"><rect width="${w}" height="${h}" rx="${radius}" ry="${radius}" fill="${color}" opacity="${opacity}"/></svg>`;
|
|
44
|
+
return sharp(Buffer.from(svg)).png().toBuffer();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function svgCircle(r: number, color: string): Promise<Buffer> {
|
|
48
|
+
const d = r * 2;
|
|
49
|
+
const svg = `<svg width="${d}" height="${d}"><circle cx="${r}" cy="${r}" r="${r}" fill="${color}"/></svg>`;
|
|
50
|
+
return sharp(Buffer.from(svg)).png().toBuffer();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function gradientBanner(w: number, h: number, rx = 0): Promise<Buffer> {
|
|
54
|
+
const svg = `<svg width="${w}" height="${h}">
|
|
55
|
+
<defs><linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
56
|
+
<stop offset="0%" stop-color="${PALETTE.accent}"/>
|
|
57
|
+
<stop offset="100%" stop-color="${PALETTE.purple}"/>
|
|
58
|
+
</linearGradient></defs>
|
|
59
|
+
<rect width="${w}" height="${h}" rx="${rx}" fill="url(#g)"/>
|
|
60
|
+
</svg>`;
|
|
61
|
+
return sharp(Buffer.from(svg)).png().toBuffer();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function numberBadge(num: number): Promise<Buffer> {
|
|
65
|
+
const s = 80;
|
|
66
|
+
const svg = `<svg width="${s}" height="${s}">
|
|
67
|
+
<circle cx="${s/2}" cy="${s/2}" r="${s/2}" fill="${PALETTE.accent}"/>
|
|
68
|
+
<text x="${s/2}" y="${s/2}" text-anchor="middle" dominant-baseline="central"
|
|
69
|
+
font-family="sans-serif" font-weight="bold" font-size="36" fill="white">${num}</text>
|
|
70
|
+
</svg>`;
|
|
71
|
+
return sharp(Buffer.from(svg)).png().toBuffer();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Icon Helpers (SVG shapes to replace emoji) ───────────────────────────────
|
|
75
|
+
async function iconArrow(size: number): Promise<Buffer> {
|
|
76
|
+
const svg = `<svg width="${size}" height="${size}" viewBox="0 0 48 48">
|
|
77
|
+
<polygon points="10,24 38,24 28,14" fill="${PALETTE.accent}" stroke="${PALETTE.accent}" stroke-width="2" stroke-linejoin="round"/>
|
|
78
|
+
<polygon points="10,24 38,24 28,34" fill="${PALETTE.accent}" stroke="${PALETTE.accent}" stroke-width="2" stroke-linejoin="round"/>
|
|
79
|
+
<rect x="10" y="20" width="28" height="8" rx="4" fill="${PALETTE.accent}"/>
|
|
80
|
+
</svg>`;
|
|
81
|
+
return sharp(Buffer.from(svg)).png().toBuffer();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function iconCrop(size: number): Promise<Buffer> {
|
|
85
|
+
const svg = `<svg width="${size}" height="${size}" viewBox="0 0 48 48">
|
|
86
|
+
<path d="M12,4 L12,36 L44,36" stroke="${PALETTE.accent}" stroke-width="4" fill="none" stroke-linecap="round"/>
|
|
87
|
+
<path d="M36,44 L36,12 L4,12" stroke="${PALETTE.purple}" stroke-width="4" fill="none" stroke-linecap="round"/>
|
|
88
|
+
</svg>`;
|
|
89
|
+
return sharp(Buffer.from(svg)).png().toBuffer();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function iconPalette(size: number): Promise<Buffer> {
|
|
93
|
+
const svg = `<svg width="${size}" height="${size}" viewBox="0 0 48 48">
|
|
94
|
+
<circle cx="24" cy="24" r="20" fill="none" stroke="${PALETTE.highlight}" stroke-width="3"/>
|
|
95
|
+
<circle cx="16" cy="18" r="4" fill="#FF6B6B"/>
|
|
96
|
+
<circle cx="28" cy="14" r="4" fill="#51CF66"/>
|
|
97
|
+
<circle cx="34" cy="24" r="4" fill="#339AF0"/>
|
|
98
|
+
<circle cx="18" cy="30" r="4" fill="${PALETTE.highlight}"/>
|
|
99
|
+
</svg>`;
|
|
100
|
+
return sharp(Buffer.from(svg)).png().toBuffer();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function iconDrop(size: number): Promise<Buffer> {
|
|
104
|
+
const svg = `<svg width="${size}" height="${size}" viewBox="0 0 48 48">
|
|
105
|
+
<path d="M24,6 Q24,6 36,26 A14,14 0 1,1 12,26 Q24,6 24,6Z" fill="${PALETTE.accent}" opacity="0.8"/>
|
|
106
|
+
</svg>`;
|
|
107
|
+
return sharp(Buffer.from(svg)).png().toBuffer();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function iconLink(size: number): Promise<Buffer> {
|
|
111
|
+
const svg = `<svg width="${size}" height="${size}" viewBox="0 0 48 48">
|
|
112
|
+
<path d="M18,30 L30,18" stroke="${PALETTE.purple}" stroke-width="4" stroke-linecap="round"/>
|
|
113
|
+
<path d="M22,34 L14,34 A8,8 0 0,1 14,18 L20,18" stroke="${PALETTE.purple}" stroke-width="4" fill="none" stroke-linecap="round"/>
|
|
114
|
+
<path d="M26,14 L34,14 A8,8 0 0,1 34,30 L28,30" stroke="${PALETTE.purple}" stroke-width="4" fill="none" stroke-linecap="round"/>
|
|
115
|
+
</svg>`;
|
|
116
|
+
return sharp(Buffer.from(svg)).png().toBuffer();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function iconTerminal(size: number): Promise<Buffer> {
|
|
120
|
+
const svg = `<svg width="${size}" height="${size}" viewBox="0 0 48 48">
|
|
121
|
+
<rect x="4" y="8" width="40" height="32" rx="4" fill="#0D1117" stroke="${PALETTE.accent}" stroke-width="2"/>
|
|
122
|
+
<path d="M12,20 L20,26 L12,32" stroke="#51CF66" stroke-width="3" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
|
123
|
+
<line x1="24" y1="32" x2="36" y2="32" stroke="${PALETTE.muted}" stroke-width="3" stroke-linecap="round"/>
|
|
124
|
+
</svg>`;
|
|
125
|
+
return sharp(Buffer.from(svg)).png().toBuffer();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function iconBot(size: number): Promise<Buffer> {
|
|
129
|
+
const svg = `<svg width="${size}" height="${size}" viewBox="0 0 48 48">
|
|
130
|
+
<rect x="10" y="16" width="28" height="24" rx="6" fill="${PALETTE.accent}"/>
|
|
131
|
+
<circle cx="24" cy="12" r="4" fill="${PALETTE.accent}"/>
|
|
132
|
+
<line x1="24" y1="8" x2="24" y2="4" stroke="${PALETTE.accent}" stroke-width="3" stroke-linecap="round"/>
|
|
133
|
+
<circle cx="18" cy="26" r="3" fill="white"/>
|
|
134
|
+
<circle cx="30" cy="26" r="3" fill="white"/>
|
|
135
|
+
<rect x="18" y="33" width="12" height="3" rx="1.5" fill="white"/>
|
|
136
|
+
</svg>`;
|
|
137
|
+
return sharp(Buffer.from(svg)).png().toBuffer();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function iconStar(size: number): Promise<Buffer> {
|
|
141
|
+
const svg = `<svg width="${size}" height="${size}" viewBox="0 0 48 48">
|
|
142
|
+
<polygon points="24,4 29,18 44,18 32,28 36,42 24,34 12,42 16,28 4,18 19,18"
|
|
143
|
+
fill="${PALETTE.highlight}" stroke="${PALETTE.highlight}" stroke-width="1"/>
|
|
144
|
+
</svg>`;
|
|
145
|
+
return sharp(Buffer.from(svg)).png().toBuffer();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
149
|
+
// SLIDE 1: Cover
|
|
150
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
151
|
+
async function slide1_cover(): Promise<Buffer> {
|
|
152
|
+
let bg = await canvas(PALETTE.bg);
|
|
153
|
+
|
|
154
|
+
const topBar = await gradientBanner(SIZE, 8);
|
|
155
|
+
const circle1 = await svgCircle(200, PALETTE.accent + '15');
|
|
156
|
+
const circle2 = await svgCircle(140, PALETTE.highlight + '10');
|
|
157
|
+
const card = await roundedRect(900, 500, PALETTE.surface, 32, 0.9);
|
|
158
|
+
const arrow = await iconArrow(64);
|
|
159
|
+
|
|
160
|
+
bg = await sharp(bg).composite([
|
|
161
|
+
{ input: topBar, top: 0, left: 0 },
|
|
162
|
+
{ input: circle1, top: -60, left: -60 },
|
|
163
|
+
{ input: circle2, top: 800, left: 880 },
|
|
164
|
+
{ input: card, top: 260, left: 90 },
|
|
165
|
+
{ input: arrow, top: 170, left: SIZE / 2 - 32 },
|
|
166
|
+
]).png().toBuffer();
|
|
167
|
+
|
|
168
|
+
const result = await addText(bg, { layers: [
|
|
169
|
+
{ text: 'Image Edit Tools', x: SIZE / 2, y: 340, fontSize: 56, color: PALETTE.text, anchor: 'top-center' },
|
|
170
|
+
{ text: 'AI Agent Image Editing SDK', x: SIZE / 2, y: 420, fontSize: 32, color: PALETTE.highlight, anchor: 'top-center' },
|
|
171
|
+
{ text: 'TypeScript / Sharp / MCP', x: SIZE / 2, y: 520, fontSize: 28, color: PALETTE.muted, anchor: 'top-center' },
|
|
172
|
+
{ text: 'Swipe to learn more', x: SIZE / 2, y: 950, fontSize: 24, color: PALETTE.muted, anchor: 'top-center' },
|
|
173
|
+
]});
|
|
174
|
+
if (!result.ok) throw new Error(result.error);
|
|
175
|
+
return result.data;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
179
|
+
// SLIDE 2: Features Overview
|
|
180
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
181
|
+
async function slide2_features(): Promise<Buffer> {
|
|
182
|
+
let bg = await canvas(PALETTE.bg);
|
|
183
|
+
|
|
184
|
+
const stripe = await gradientBanner(SIZE, 120);
|
|
185
|
+
bg = await sharp(bg).composite([{ input: stripe, top: 0, left: 0 }]).png().toBuffer();
|
|
186
|
+
|
|
187
|
+
const features = [
|
|
188
|
+
{ icon: iconCrop, title: 'Crop + Resize', desc: 'Absolute, ratio, aspect crop' },
|
|
189
|
+
{ icon: iconPalette, title: 'Adjust + Filter', desc: 'Brightness, contrast, hue' },
|
|
190
|
+
{ icon: iconDrop, title: 'Watermark', desc: 'Text or image watermark' },
|
|
191
|
+
{ icon: iconLink, title: 'Pipeline', desc: 'Chain operations together' },
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
const cardW = 420, cardH = 180, gap = 40;
|
|
195
|
+
const startY = 180;
|
|
196
|
+
const iconSize = 40;
|
|
197
|
+
|
|
198
|
+
// Create feature cards and composite icons
|
|
199
|
+
for (let i = 0; i < features.length; i++) {
|
|
200
|
+
const col = i % 2;
|
|
201
|
+
const row = Math.floor(i / 2);
|
|
202
|
+
const x = 60 + col * (cardW + gap);
|
|
203
|
+
const y = startY + row * (cardH + gap);
|
|
204
|
+
|
|
205
|
+
const card = await roundedRect(cardW, cardH, PALETTE.surface, 20);
|
|
206
|
+
const icon = await features[i].icon(iconSize);
|
|
207
|
+
bg = await sharp(bg).composite([
|
|
208
|
+
{ input: card, top: y, left: x },
|
|
209
|
+
{ input: icon, top: y + 25, left: x + 25 },
|
|
210
|
+
]).png().toBuffer();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Text layers (no emoji)
|
|
214
|
+
const textLayers = [
|
|
215
|
+
{ text: 'Key Features', x: SIZE / 2, y: 40, fontSize: 40, color: '#FFFFFF', anchor: 'top-center' as const },
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
for (let i = 0; i < features.length; i++) {
|
|
219
|
+
const col = i % 2;
|
|
220
|
+
const row = Math.floor(i / 2);
|
|
221
|
+
const x = 60 + col * (cardW + gap);
|
|
222
|
+
const y = startY + row * (cardH + gap);
|
|
223
|
+
|
|
224
|
+
textLayers.push(
|
|
225
|
+
{ text: features[i].title, x: x + 80, y: y + 35, fontSize: 28, color: PALETTE.text, anchor: 'top-left' as const },
|
|
226
|
+
{ text: features[i].desc, x: x + 25, y: y + 110, fontSize: 22, color: PALETTE.muted, anchor: 'top-left' as const },
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
textLayers.push(
|
|
231
|
+
{ text: '2 / 5', x: SIZE / 2, y: 980, fontSize: 20, color: PALETTE.muted, anchor: 'top-center' as const },
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const res = await addText(bg, { layers: textLayers });
|
|
235
|
+
if (!res.ok) throw new Error(res.error);
|
|
236
|
+
return res.data;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
240
|
+
// SLIDE 3: Code Example
|
|
241
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
242
|
+
async function slide3_code(): Promise<Buffer> {
|
|
243
|
+
let bg = await canvas(PALETTE.bg);
|
|
244
|
+
|
|
245
|
+
const codeCard = await roundedRect(920, 500, '#0D1117', 20);
|
|
246
|
+
const termIcon = await iconTerminal(48);
|
|
247
|
+
bg = await sharp(bg).composite([
|
|
248
|
+
{ input: codeCard, top: 250, left: 80 },
|
|
249
|
+
{ input: termIcon, top: 95, left: SIZE / 2 - 24 },
|
|
250
|
+
]).png().toBuffer();
|
|
251
|
+
|
|
252
|
+
const codeLines = [
|
|
253
|
+
'import resize, addText from',
|
|
254
|
+
" 'image-edit-tools'",
|
|
255
|
+
'',
|
|
256
|
+
'const img = await resize(',
|
|
257
|
+
" 'photo.jpg',",
|
|
258
|
+
' width: 1080, height: 1080',
|
|
259
|
+
')',
|
|
260
|
+
'',
|
|
261
|
+
'const card = await addText(',
|
|
262
|
+
' img.data,',
|
|
263
|
+
" layers: [text: 'Hello!']",
|
|
264
|
+
')',
|
|
265
|
+
];
|
|
266
|
+
|
|
267
|
+
const codeLayers: Parameters<typeof addText>[1]['layers'] = [
|
|
268
|
+
{ text: 'Code Example', x: SIZE / 2, y: 160, fontSize: 40, color: PALETTE.text, anchor: 'top-center' },
|
|
269
|
+
{ text: 'Edit images in just a few lines', x: SIZE / 2, y: 215, fontSize: 24, color: PALETTE.muted, anchor: 'top-center' },
|
|
270
|
+
];
|
|
271
|
+
|
|
272
|
+
codeLines.forEach((line, i) => {
|
|
273
|
+
if (!line) return;
|
|
274
|
+
let color = PALETTE.text;
|
|
275
|
+
if (line.includes('import') || line.includes('const') || line.includes('await')) color = '#FF7B72';
|
|
276
|
+
if (line.startsWith(" '") || line.includes("'")) color = '#A5D6FF';
|
|
277
|
+
if (line.includes('width') || line.includes('height') || line.includes('layers')) color = '#FFA657';
|
|
278
|
+
|
|
279
|
+
codeLayers.push({
|
|
280
|
+
text: line, x: 120, y: 285 + i * 38,
|
|
281
|
+
fontSize: 24, color, anchor: 'top-left',
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
codeLayers.push(
|
|
286
|
+
{ text: '3 / 5', x: SIZE / 2, y: 980, fontSize: 20, color: PALETTE.muted, anchor: 'top-center' },
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
const res = await addText(bg, { layers: codeLayers });
|
|
290
|
+
if (!res.ok) throw new Error(res.error);
|
|
291
|
+
return res.data;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
295
|
+
// SLIDE 4: MCP Integration
|
|
296
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
297
|
+
async function slide4_mcp(): Promise<Buffer> {
|
|
298
|
+
let bg = await canvas(PALETTE.bg);
|
|
299
|
+
|
|
300
|
+
const badge1 = await numberBadge(1);
|
|
301
|
+
const badge2 = await numberBadge(2);
|
|
302
|
+
const badge3 = await numberBadge(3);
|
|
303
|
+
const stepCard = await roundedRect(820, 140, PALETTE.surface, 16);
|
|
304
|
+
const botIcon = await iconBot(56);
|
|
305
|
+
|
|
306
|
+
bg = await sharp(bg).composite([
|
|
307
|
+
{ input: stepCard, top: 280, left: 130 },
|
|
308
|
+
{ input: stepCard, top: 480, left: 130 },
|
|
309
|
+
{ input: stepCard, top: 680, left: 130 },
|
|
310
|
+
{ input: badge1, top: 310, left: 60 },
|
|
311
|
+
{ input: badge2, top: 510, left: 60 },
|
|
312
|
+
{ input: badge3, top: 710, left: 60 },
|
|
313
|
+
{ input: botIcon, top: 65, left: SIZE / 2 - 28 },
|
|
314
|
+
]).png().toBuffer();
|
|
315
|
+
|
|
316
|
+
const res = await addText(bg, { layers: [
|
|
317
|
+
{ text: 'MCP Integration', x: SIZE / 2, y: 140, fontSize: 44, color: PALETTE.text, anchor: 'top-center' },
|
|
318
|
+
{ text: 'Use directly from AI agents', x: SIZE / 2, y: 200, fontSize: 26, color: PALETTE.muted, anchor: 'top-center' },
|
|
319
|
+
|
|
320
|
+
{ text: 'npm install', x: 170, y: 310, fontSize: 26, color: PALETTE.highlight, anchor: 'top-left' },
|
|
321
|
+
{ text: 'Install image-edit-tools', x: 170, y: 355, fontSize: 22, color: PALETTE.muted, anchor: 'top-left' },
|
|
322
|
+
|
|
323
|
+
{ text: 'Add MCP config', x: 170, y: 510, fontSize: 26, color: PALETTE.highlight, anchor: 'top-left' },
|
|
324
|
+
{ text: 'Edit claude_desktop_config.json', x: 170, y: 555, fontSize: 22, color: PALETTE.muted, anchor: 'top-left' },
|
|
325
|
+
|
|
326
|
+
{ text: 'Ask the AI', x: 170, y: 710, fontSize: 26, color: PALETTE.highlight, anchor: 'top-left' },
|
|
327
|
+
{ text: 'Crop this photo to 1080x1080', x: 170, y: 755, fontSize: 22, color: PALETTE.muted, anchor: 'top-left' },
|
|
328
|
+
|
|
329
|
+
{ text: '4 / 5', x: SIZE / 2, y: 980, fontSize: 20, color: PALETTE.muted, anchor: 'top-center' },
|
|
330
|
+
]});
|
|
331
|
+
if (!res.ok) throw new Error(res.error);
|
|
332
|
+
return res.data;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
336
|
+
// SLIDE 5: Closing CTA
|
|
337
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
338
|
+
async function slide5_cta(): Promise<Buffer> {
|
|
339
|
+
let bg = await canvas(PALETTE.bg);
|
|
340
|
+
|
|
341
|
+
const bigCircle = await svgCircle(300, PALETTE.accent + '20');
|
|
342
|
+
const ctaBtn = await roundedRect(500, 80, PALETTE.accent, 40);
|
|
343
|
+
const star = await iconStar(80);
|
|
344
|
+
|
|
345
|
+
bg = await sharp(bg).composite([
|
|
346
|
+
{ input: bigCircle, top: SIZE / 2 - 300, left: SIZE / 2 - 300 },
|
|
347
|
+
{ input: star, top: 220, left: SIZE / 2 - 40 },
|
|
348
|
+
{ input: ctaBtn, top: 650, left: SIZE / 2 - 250 },
|
|
349
|
+
]).png().toBuffer();
|
|
350
|
+
|
|
351
|
+
const res = await addText(bg, { layers: [
|
|
352
|
+
{ text: 'Get Started', x: SIZE / 2, y: 380, fontSize: 48, color: PALETTE.text, anchor: 'top-center' },
|
|
353
|
+
{ text: 'npm install image-edit-tools', x: SIZE / 2, y: 480, fontSize: 30, color: PALETTE.highlight, anchor: 'top-center' },
|
|
354
|
+
{ text: 'Install Now', x: SIZE / 2, y: 670, fontSize: 28, color: '#FFFFFF', anchor: 'top-center' },
|
|
355
|
+
{ text: 'github.com/swimmingkiim/image-edit-tools', x: SIZE / 2, y: 820, fontSize: 22, color: PALETTE.muted, anchor: 'top-center' },
|
|
356
|
+
{ text: '5 / 5', x: SIZE / 2, y: 980, fontSize: 20, color: PALETTE.muted, anchor: 'top-center' },
|
|
357
|
+
]});
|
|
358
|
+
if (!res.ok) throw new Error(res.error);
|
|
359
|
+
return res.data;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
363
|
+
// Main
|
|
364
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
365
|
+
async function main() {
|
|
366
|
+
console.log('Generating Instagram card news slides...\n');
|
|
367
|
+
|
|
368
|
+
const slides = [
|
|
369
|
+
{ name: 'slide-1-cover', fn: slide1_cover },
|
|
370
|
+
{ name: 'slide-2-features', fn: slide2_features },
|
|
371
|
+
{ name: 'slide-3-code', fn: slide3_code },
|
|
372
|
+
{ name: 'slide-4-mcp', fn: slide4_mcp },
|
|
373
|
+
{ name: 'slide-5-cta', fn: slide5_cta },
|
|
374
|
+
];
|
|
375
|
+
|
|
376
|
+
for (const slide of slides) {
|
|
377
|
+
const buf = await slide.fn();
|
|
378
|
+
const outPath = join(OUTPUT_DIR, `${slide.name}.png`);
|
|
379
|
+
writeFileSync(outPath, buf);
|
|
380
|
+
console.log(` OK ${slide.name}.png (${(buf.length / 1024).toFixed(1)} KB)`);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
console.log(`\nDone! ${slides.length} slides saved to examples/output/`);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
main().catch(console.error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "image-edit-tools",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Deterministic image editing SDK for AI agents. Ships with MCP tools.",
|
|
5
5
|
"author": "swimmingkiim",
|
|
6
6
|
"homepage": "https://github.com/swimmingkiim/image-edit-tools#readme",
|
|
@@ -34,12 +34,14 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.1.0",
|
|
37
|
-
"@xenova/transformers": "^2.17.2",
|
|
38
37
|
"color-thief-node": "^1.0.4",
|
|
39
38
|
"node-fetch": "^3.3.2",
|
|
40
39
|
"sharp": "^0.33.5",
|
|
41
40
|
"tesseract.js": "^5.1.1"
|
|
42
41
|
},
|
|
42
|
+
"optionalDependencies": {
|
|
43
|
+
"@xenova/transformers": "^2.17.2"
|
|
44
|
+
},
|
|
43
45
|
"devDependencies": {
|
|
44
46
|
"@types/node": "^20.14.2",
|
|
45
47
|
"@vitest/coverage-v8": "^1.6.0",
|
package/src/ops/add-text.ts
CHANGED
|
@@ -24,6 +24,15 @@ function wrapText(text: string, fontSize: number, maxWidth?: number): string[] {
|
|
|
24
24
|
return lines;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
function escapeXml(text: string): string {
|
|
28
|
+
return text
|
|
29
|
+
.replace(/&/g, '&')
|
|
30
|
+
.replace(/</g, '<')
|
|
31
|
+
.replace(/>/g, '>')
|
|
32
|
+
.replace(/"/g, '"')
|
|
33
|
+
.replace(/'/g, ''');
|
|
34
|
+
}
|
|
35
|
+
|
|
27
36
|
function getAnchorProps(anchor: TextAnchor = 'top-left'): { textAnchor: string, dominantBaseline: string } {
|
|
28
37
|
const parts = anchor.split('-');
|
|
29
38
|
const yAlign = parts.length === 2 ? parts[0] : parts[0] === 'center' ? 'middle' : parts[0];
|
|
@@ -57,6 +66,8 @@ export async function addText(input: ImageInput, options: { layers: TextLayer[]
|
|
|
57
66
|
let defs = '';
|
|
58
67
|
let svgBody = '';
|
|
59
68
|
let fontImports = new Set<string>();
|
|
69
|
+
const warnings: string[] = [];
|
|
70
|
+
let contentBottom = 0;
|
|
60
71
|
|
|
61
72
|
for (let i = 0; i < options.layers.length; i++) {
|
|
62
73
|
const layer = options.layers[i];
|
|
@@ -109,11 +120,29 @@ export async function addText(input: ImageInput, options: { layers: TextLayer[]
|
|
|
109
120
|
layerSvg += `<text x="${layer.x}" y="${layer.y}" style="${style}">`;
|
|
110
121
|
lines.forEach((line, idx) => {
|
|
111
122
|
let dy = idx === 0 ? 0 : fontSize * lineHeight;
|
|
112
|
-
layerSvg += `<tspan x="${layer.x}" dy="${dy}">${line}</tspan>`;
|
|
123
|
+
layerSvg += `<tspan x="${layer.x}" dy="${dy}">${escapeXml(line)}</tspan>`;
|
|
113
124
|
});
|
|
114
125
|
layerSvg += `</text>`;
|
|
115
126
|
|
|
116
127
|
svgBody += `<g style="isolation: isolate">${layerSvg}</g>`;
|
|
128
|
+
|
|
129
|
+
// Compute bounding box for overflow detection
|
|
130
|
+
let boxX = layer.x;
|
|
131
|
+
let boxY = layer.y;
|
|
132
|
+
if (textAnchor === 'middle') boxX -= approxMaxWidth / 2;
|
|
133
|
+
else if (textAnchor === 'end') boxX -= approxMaxWidth;
|
|
134
|
+
if (dominantBaseline === 'middle') boxY -= totalHeight / 2;
|
|
135
|
+
else if (dominantBaseline === 'alphabetic') boxY -= totalHeight - fontSize;
|
|
136
|
+
|
|
137
|
+
const boxBottom = boxY + totalHeight;
|
|
138
|
+
const boxRight = boxX + approxMaxWidth;
|
|
139
|
+
if (boxBottom > contentBottom) contentBottom = boxBottom;
|
|
140
|
+
|
|
141
|
+
if (boxX < 0 || boxY < 0 || boxRight > width || boxBottom > height) {
|
|
142
|
+
warnings.push(
|
|
143
|
+
`Text layer ${i} ("${layer.text.slice(0, 20)}...") extends beyond canvas bounds.`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
117
146
|
}
|
|
118
147
|
|
|
119
148
|
const fontStyle = fontImports.size > 0 ? `<style>${Array.from(fontImports).join('\n')}</style>` : '';
|
|
@@ -128,7 +157,9 @@ export async function addText(input: ImageInput, options: { layers: TextLayer[]
|
|
|
128
157
|
.composite([{ input: Buffer.from(svgString), blend: 'over' }])
|
|
129
158
|
.toBuffer();
|
|
130
159
|
|
|
131
|
-
|
|
160
|
+
const result = ok(output, warnings);
|
|
161
|
+
(result as any).bounds = { contentBottom: Math.round(contentBottom) };
|
|
162
|
+
return result;
|
|
132
163
|
} catch (e: any) {
|
|
133
164
|
const msg = e.message || '';
|
|
134
165
|
if (msg.includes('HTTP')) return err(msg, ErrorCode.FETCH_FAILED);
|
package/src/ops/composite.ts
CHANGED
|
@@ -38,6 +38,28 @@ export async function composite(input: ImageInput, options: { layers: CompositeL
|
|
|
38
38
|
|
|
39
39
|
const overlays = await Promise.all(options.layers.map(loadLayer));
|
|
40
40
|
|
|
41
|
+
// Detect potentially problematic opaque layers
|
|
42
|
+
const warnings: string[] = [];
|
|
43
|
+
const baseMeta = await sharp(buffer).metadata();
|
|
44
|
+
const canvasArea = (baseMeta.width ?? 1) * (baseMeta.height ?? 1);
|
|
45
|
+
|
|
46
|
+
for (let i = 0; i < options.layers.length; i++) {
|
|
47
|
+
const layer = options.layers[i];
|
|
48
|
+
const layerOpacity = layer.opacity ?? 1.0;
|
|
49
|
+
if (layerOpacity >= 0.9) {
|
|
50
|
+
try {
|
|
51
|
+
const layerBuf = await loadImage(layer.image);
|
|
52
|
+
const layerMeta = await sharp(layerBuf).metadata();
|
|
53
|
+
const layerArea = (layerMeta.width ?? 0) * (layerMeta.height ?? 0);
|
|
54
|
+
if (layerArea / canvasArea > 0.25) {
|
|
55
|
+
warnings.push(
|
|
56
|
+
`Layer ${i} is opaque (opacity=${layerOpacity}) and covers ${Math.round(layerArea / canvasArea * 100)}% of the canvas. It may hide content underneath.`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
} catch { /* skip analysis for unreadable layers */ }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
41
63
|
let output = buffer;
|
|
42
64
|
if (overlays.length > 0) {
|
|
43
65
|
output = await sharp(buffer)
|
|
@@ -45,7 +67,7 @@ export async function composite(input: ImageInput, options: { layers: CompositeL
|
|
|
45
67
|
.toBuffer();
|
|
46
68
|
}
|
|
47
69
|
|
|
48
|
-
return ok(output);
|
|
70
|
+
return ok(output, warnings);
|
|
49
71
|
} catch (e: any) {
|
|
50
72
|
const msg = e.message || '';
|
|
51
73
|
if (msg.includes('HTTP')) return err(msg, ErrorCode.FETCH_FAILED);
|
package/src/ops/detect-faces.ts
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import { BoundingBox, ImageInput, Result, ErrorCode } from '../types.js';
|
|
2
2
|
import { loadImage } from '../utils/load-image.js';
|
|
3
3
|
import { ok, err } from '../utils/result.js';
|
|
4
|
-
import { pipeline, RawImage } from '@xenova/transformers';
|
|
5
4
|
import sharp from 'sharp';
|
|
6
5
|
|
|
7
6
|
export async function detectFaces(input: ImageInput): Promise<Result<BoundingBox[]>> {
|
|
8
7
|
try {
|
|
9
8
|
const buffer = await loadImage(input);
|
|
10
|
-
let detector: any;
|
|
9
|
+
let detector: any, pipeline_tf: any, RawImage: any;
|
|
11
10
|
try {
|
|
12
|
-
|
|
11
|
+
const tf = await import('@xenova/transformers');
|
|
12
|
+
pipeline_tf = tf.pipeline;
|
|
13
|
+
RawImage = tf.RawImage;
|
|
14
|
+
} catch (e: any) {
|
|
15
|
+
return err('Failed to load @xenova/transformers (check architecture bindings).', ErrorCode.PROCESSING_FAILED);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
detector = await pipeline_tf('object-detection', 'Xenova/detr-resnet-50', {
|
|
13
20
|
quantized: true,
|
|
14
21
|
});
|
|
15
22
|
} catch (e) {
|
|
@@ -3,14 +3,22 @@ import { loadImage } from '../utils/load-image.js';
|
|
|
3
3
|
import { err, ok } from '../utils/result.js';
|
|
4
4
|
import { getImageMetadata } from '../utils/validate.js';
|
|
5
5
|
import sharp from 'sharp';
|
|
6
|
-
import { AutoModel, AutoProcessor, RawImage } from '@xenova/transformers';
|
|
7
6
|
|
|
8
7
|
export async function detectSubject(input: ImageInput): Promise<Result<BoundingBox[]>> {
|
|
9
8
|
try {
|
|
10
9
|
const buffer = await loadImage(input);
|
|
11
10
|
const meta = await getImageMetadata(buffer);
|
|
12
11
|
|
|
13
|
-
let model: any, processor: any;
|
|
12
|
+
let model: any, processor: any, AutoModel: any, AutoProcessor: any, RawImage: any;
|
|
13
|
+
try {
|
|
14
|
+
const tf = await import('@xenova/transformers');
|
|
15
|
+
AutoModel = tf.AutoModel;
|
|
16
|
+
AutoProcessor = tf.AutoProcessor;
|
|
17
|
+
RawImage = tf.RawImage;
|
|
18
|
+
} catch (e: any) {
|
|
19
|
+
return err('Failed to load @xenova/transformers (check architecture bindings).', ErrorCode.PROCESSING_FAILED);
|
|
20
|
+
}
|
|
21
|
+
|
|
14
22
|
try {
|
|
15
23
|
model = await AutoModel.from_pretrained('briaai/RMBG-1.4', {
|
|
16
24
|
config: { model_type: 'custom' },
|
package/src/ops/pipeline.ts
CHANGED
|
@@ -10,7 +10,6 @@ import { composite } from './composite.js';
|
|
|
10
10
|
import { watermark } from './watermark.js';
|
|
11
11
|
import { convert } from './convert.js';
|
|
12
12
|
import { optimize } from './optimize.js';
|
|
13
|
-
import { removeBg } from './remove-bg.js';
|
|
14
13
|
|
|
15
14
|
export async function pipeline(input: ImageInput, operations: PipelineOperation[]): Promise<ImageResult & { step?: number }> {
|
|
16
15
|
let currentImage = input;
|
|
@@ -32,7 +31,11 @@ export async function pipeline(input: ImageInput, operations: PipelineOperation[
|
|
|
32
31
|
case 'watermark': result = await watermark(currentImage, op); break;
|
|
33
32
|
case 'convert': result = await convert(currentImage, op); break;
|
|
34
33
|
case 'optimize': result = await optimize(currentImage, op); break;
|
|
35
|
-
case 'removeBg':
|
|
34
|
+
case 'removeBg': {
|
|
35
|
+
const { removeBg } = await import('./remove-bg.js');
|
|
36
|
+
result = await removeBg(currentImage, op);
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
36
39
|
default:
|
|
37
40
|
return { ok: false, error: `Unknown operation`, code: ErrorCode.INVALID_INPUT, step: i };
|
|
38
41
|
}
|
package/src/ops/remove-bg.ts
CHANGED
|
@@ -3,14 +3,22 @@ import { RemoveBgOptions, ImageInput, ImageResult, ErrorCode } from '../types.js
|
|
|
3
3
|
import { loadImage } from '../utils/load-image.js';
|
|
4
4
|
import { err, ok } from '../utils/result.js';
|
|
5
5
|
import { getImageMetadata } from '../utils/validate.js';
|
|
6
|
-
import { AutoModel, AutoProcessor, RawImage } from '@xenova/transformers';
|
|
7
6
|
|
|
8
7
|
export async function removeBg(input: ImageInput, options: RemoveBgOptions = {}): Promise<ImageResult> {
|
|
9
8
|
try {
|
|
10
9
|
const buffer = await loadImage(input);
|
|
11
10
|
const meta = await getImageMetadata(buffer);
|
|
12
11
|
|
|
13
|
-
let model: any, processor: any;
|
|
12
|
+
let model: any, processor: any, AutoModel: any, AutoProcessor: any, RawImage: any;
|
|
13
|
+
try {
|
|
14
|
+
const tf = await import('@xenova/transformers');
|
|
15
|
+
AutoModel = tf.AutoModel;
|
|
16
|
+
AutoProcessor = tf.AutoProcessor;
|
|
17
|
+
RawImage = tf.RawImage;
|
|
18
|
+
} catch (e: any) {
|
|
19
|
+
return err('Failed to load @xenova/transformers (check architecture bindings).', ErrorCode.PROCESSING_FAILED);
|
|
20
|
+
}
|
|
21
|
+
|
|
14
22
|
try {
|
|
15
23
|
model = await AutoModel.from_pretrained('briaai/RMBG-1.4', {
|
|
16
24
|
config: { model_type: 'custom' },
|
|
@@ -33,8 +41,6 @@ export async function removeBg(input: ImageInput, options: RemoveBgOptions = {})
|
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
// Process image
|
|
36
|
-
const rawImage = await RawImage.fromURL(URL.createObjectURL(new Blob([buffer]))); // Alternative for node:
|
|
37
|
-
// RawImage from buffer is easier using sharp. We need uint8 array of RGB or RGBA.
|
|
38
44
|
const rawRgb = await sharp(buffer).ensureAlpha().raw().toBuffer();
|
|
39
45
|
const img = new RawImage(new Uint8ClampedArray(rawRgb), meta.width, meta.height, 4);
|
|
40
46
|
|
package/src/types.ts
CHANGED
|
@@ -11,7 +11,7 @@ export type ImageInput = Buffer | string;
|
|
|
11
11
|
|
|
12
12
|
// ─── Result ───────────────────────────────────────────────────────────────────
|
|
13
13
|
|
|
14
|
-
export type Ok<T> = { ok: true; data: T };
|
|
14
|
+
export type Ok<T> = { ok: true; data: T; warnings?: string[] };
|
|
15
15
|
export type Err = { ok: false; error: string; code: ErrorCode };
|
|
16
16
|
export type Result<T> = Ok<T> | Err;
|
|
17
17
|
export type ImageResult = Result<Buffer>;
|
package/src/utils/result.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { Ok, Err, ErrorCode } from '../types.js'
|
|
2
2
|
|
|
3
|
-
export const ok = <T>(data: T): Ok<T> =>
|
|
3
|
+
export const ok = <T>(data: T, warnings?: string[]): Ok<T> => {
|
|
4
|
+
const result: Ok<T> = { ok: true, data };
|
|
5
|
+
if (warnings && warnings.length > 0) result.warnings = warnings;
|
|
6
|
+
return result;
|
|
7
|
+
}
|
|
4
8
|
export const err = (error: string, code: ErrorCode): Err => ({ ok: false, error, code })
|
|
@@ -53,4 +53,25 @@ describe('addText', () => {
|
|
|
53
53
|
if (result.ok) return
|
|
54
54
|
expect(result.code).toBe('INVALID_INPUT')
|
|
55
55
|
})
|
|
56
|
+
|
|
57
|
+
it('warns when text extends beyond canvas bounds', async () => {
|
|
58
|
+
const result = await addText(sampleJpeg, {
|
|
59
|
+
layers: [{ text: 'Way out of bounds', x: 9999, y: 9999, fontSize: 48 }]
|
|
60
|
+
})
|
|
61
|
+
expect(result.ok).toBe(true)
|
|
62
|
+
if (!result.ok) return
|
|
63
|
+
expect(result.warnings).toBeDefined()
|
|
64
|
+
expect(result.warnings!.length).toBeGreaterThan(0)
|
|
65
|
+
expect(result.warnings![0]).toContain('beyond canvas bounds')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('includes bounds.contentBottom in result', async () => {
|
|
69
|
+
const result = await addText(sampleJpeg, {
|
|
70
|
+
layers: [{ text: 'Hello', x: 50, y: 50, fontSize: 24 }]
|
|
71
|
+
})
|
|
72
|
+
expect(result.ok).toBe(true)
|
|
73
|
+
if (!result.ok) return
|
|
74
|
+
expect((result as any).bounds).toBeDefined()
|
|
75
|
+
expect((result as any).bounds.contentBottom).toBeGreaterThan(50)
|
|
76
|
+
})
|
|
56
77
|
})
|
|
@@ -55,4 +55,16 @@ describe('composite', () => {
|
|
|
55
55
|
if (result.ok) return
|
|
56
56
|
expect(result.code).toBe('INVALID_INPUT')
|
|
57
57
|
})
|
|
58
|
+
|
|
59
|
+
it('warns when opaque layer covers large area', async () => {
|
|
60
|
+
// sampleJpeg is 400x300 = 120000 pixels, logoPng is 100x100 = 10000 pixels (8.3% — no warning)
|
|
61
|
+
const result = await composite(sampleJpeg, {
|
|
62
|
+
layers: [{ image: sampleJpeg, x: 0, y: 0 }]
|
|
63
|
+
})
|
|
64
|
+
expect(result.ok).toBe(true)
|
|
65
|
+
if (!result.ok) return
|
|
66
|
+
expect(result.warnings).toBeDefined()
|
|
67
|
+
expect(result.warnings!.length).toBeGreaterThan(0)
|
|
68
|
+
expect(result.warnings![0]).toContain('may hide content')
|
|
69
|
+
})
|
|
58
70
|
})
|