image-edit-tools 1.0.3 → 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 CHANGED
@@ -16,15 +16,35 @@ npm install image-edit-tools
16
16
 
17
17
  ## Quick Start (Code)
18
18
  ```typescript
19
- import { crop, resize, pad } from 'image-edit-tools';
19
+ import { crop, resize, addText, composite, pipeline } from 'image-edit-tools';
20
20
 
21
+ // Basic resize
21
22
  const result = await resize('/path/to/img.jpg', { width: 800 });
22
23
  if (!result.ok) {
23
24
  console.error(result.error);
24
25
  return;
25
26
  }
26
- // Returns a Buffer.
27
- // Can be saved, piped, or passed explicitly!
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
+ ]);
28
48
  ```
29
49
 
30
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;AAqDxF,wBAAsB,OAAO,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,SAAS,EAAE,CAAA;CAAE,GAAG,OAAO,CAAC,WAAW,CAAC,CA6FvG"}
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"}
@@ -64,6 +64,8 @@ export async function addText(input, options) {
64
64
  let defs = '';
65
65
  let svgBody = '';
66
66
  let fontImports = new Set();
67
+ const warnings = [];
68
+ let contentBottom = 0;
67
69
  for (let i = 0; i < options.layers.length; i++) {
68
70
  const layer = options.layers[i];
69
71
  const fontSize = layer.fontSize ?? 24;
@@ -111,6 +113,24 @@ export async function addText(input, options) {
111
113
  });
112
114
  layerSvg += `</text>`;
113
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
+ }
114
134
  }
115
135
  const fontStyle = fontImports.size > 0 ? `<style>${Array.from(fontImports).join('\n')}</style>` : '';
116
136
  const svgString = `<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
@@ -121,7 +141,9 @@ export async function addText(input, options) {
121
141
  const output = await sharp(buffer)
122
142
  .composite([{ input: Buffer.from(svgString), blend: 'over' }])
123
143
  .toBuffer();
124
- return ok(output);
144
+ const result = ok(output, warnings);
145
+ result.bounds = { contentBottom: Math.round(contentBottom) };
146
+ return result;
125
147
  }
126
148
  catch (e) {
127
149
  const msg = e.message || '';
@@ -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,CAkD9G"}
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"}
@@ -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;IACpB,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,IAAI,GAAG,CAAC,QAAQ,CAAC,0BAA0B,CAAC;YAAE,OAAO,GAAG,CAAC,8BAA8B,EAAE,SAAS,CAAC,aAAa,CAAC,CAAC;QAClH,OAAO,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,iBAAiB,CAAC,CAAC;IAC/C,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"composite.js","sourceRoot":"","sources":["../../src/ops/composite.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -11,6 +11,7 @@ export type ImageInput = Buffer | string;
11
11
  export type Ok<T> = {
12
12
  ok: true;
13
13
  data: T;
14
+ warnings?: string[];
14
15
  };
15
16
  export type Err = {
16
17
  ok: false;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -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,CAAyB,CAAA;AAC7D,eAAO,MAAM,GAAG,UAAW,MAAM,QAAQ,SAAS,KAAG,GAAmC,CAAA"}
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"}
@@ -1,3 +1,8 @@
1
- export const ok = (data) => ({ ok: true, 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
@@ -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,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;AAC7D,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,KAAa,EAAE,IAAe,EAAO,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA"}
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.
@@ -1,8 +1,11 @@
1
1
  /**
2
2
  * Instagram Card News Generator
3
3
  *
4
- * Demonstrates: resize, pad, addText, composite, convert, pipeline
5
- * Output: 1080×1080 Instagram carousel slides (cover + 3 content slides + closing)
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().
6
9
  *
7
10
  * Usage:
8
11
  * npx tsx examples/instagram-card-news.ts
@@ -12,7 +15,6 @@ import { writeFileSync, mkdirSync, existsSync } from 'fs';
12
15
  import { join, dirname } from 'path';
13
16
  import { fileURLToPath } from 'url';
14
17
  import { addText } from '../src/ops/add-text.js';
15
- import { composite } from '../src/ops/composite.js';
16
18
 
17
19
  const __filename = fileURLToPath(import.meta.url);
18
20
  const __dirname = dirname(__filename);
@@ -23,131 +25,151 @@ if (!existsSync(OUTPUT_DIR)) mkdirSync(OUTPUT_DIR, { recursive: true });
23
25
  // ── Design Tokens ────────────────────────────────────────────────────────────
24
26
  const SIZE = 1080;
25
27
  const PALETTE = {
26
- bg: '#0F172A', // slate-900
27
- accent: '#3B82F6', // blue-500
28
- surface: '#1E293B', // slate-800
29
- text: '#F8FAFC', // slate-50
30
- muted: '#94A3B8', // slate-400
31
- highlight:'#F59E0B', // amber-500
28
+ bg: '#0F172A',
29
+ accent: '#3B82F6',
30
+ surface: '#1E293B',
31
+ text: '#F8FAFC',
32
+ muted: '#94A3B8',
33
+ highlight:'#F59E0B',
34
+ purple: '#8B5CF6',
32
35
  };
33
36
 
34
- // ── Helper: create a solid-color 1080×1080 canvas ────────────────────────────
37
+ // ── SVG Shape Helpers ────────────────────────────────────────────────────────
35
38
  async function canvas(color: string): Promise<Buffer> {
36
- return sharp({
37
- create: { width: SIZE, height: SIZE, channels: 4, background: color }
38
- }).png().toBuffer();
39
+ return sharp({ create: { width: SIZE, height: SIZE, channels: 4, background: color } }).png().toBuffer();
39
40
  }
40
41
 
41
- // ── Helper: create a rounded rectangle shape ─────────────────────────────────
42
- async function roundedRect(
43
- w: number, h: number, color: string, radius: number, opacity = 1
44
- ): Promise<Buffer> {
45
- const svg = `<svg width="${w}" height="${h}">
46
- <rect width="${w}" height="${h}" rx="${radius}" ry="${radius}" fill="${color}" opacity="${opacity}"/>
47
- </svg>`;
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>`;
48
44
  return sharp(Buffer.from(svg)).png().toBuffer();
49
45
  }
50
46
 
51
- // ── Helper: create a circle ──────────────────────────────────────────────────
52
- async function circle(r: number, color: string): Promise<Buffer> {
47
+ async function svgCircle(r: number, color: string): Promise<Buffer> {
53
48
  const d = r * 2;
54
- const svg = `<svg width="${d}" height="${d}">
55
- <circle cx="${r}" cy="${r}" r="${r}" fill="${color}"/>
56
- </svg>`;
49
+ const svg = `<svg width="${d}" height="${d}"><circle cx="${r}" cy="${r}" r="${r}" fill="${color}"/></svg>`;
57
50
  return sharp(Buffer.from(svg)).png().toBuffer();
58
51
  }
59
52
 
60
- // ── Helper: create a gradient banner ─────────────────────────────────────────
61
- async function gradientBanner(w: number, h: number): Promise<Buffer> {
53
+ async function gradientBanner(w: number, h: number, rx = 0): Promise<Buffer> {
62
54
  const svg = `<svg width="${w}" height="${h}">
63
- <defs>
64
- <linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%">
65
- <stop offset="0%" stop-color="${PALETTE.accent}"/>
66
- <stop offset="100%" stop-color="#8B5CF6"/>
67
- </linearGradient>
68
- </defs>
69
- <rect width="${w}" height="${h}" rx="24" ry="24" fill="url(#g)"/>
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)"/>
70
60
  </svg>`;
71
61
  return sharp(Buffer.from(svg)).png().toBuffer();
72
62
  }
73
63
 
74
- // ── Helper: number badge ─────────────────────────────────────────────────────
75
64
  async function numberBadge(num: number): Promise<Buffer> {
76
- const size = 80;
77
- const svg = `<svg width="${size}" height="${size}">
78
- <circle cx="${size/2}" cy="${size/2}" r="${size/2}" fill="${PALETTE.accent}"/>
79
- <text x="${size/2}" y="${size/2}" text-anchor="middle" dominant-baseline="central"
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"
80
69
  font-family="sans-serif" font-weight="bold" font-size="36" fill="white">${num}</text>
81
70
  </svg>`;
82
71
  return sharp(Buffer.from(svg)).png().toBuffer();
83
72
  }
84
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
+
85
148
  // ═══════════════════════════════════════════════════════════════════════════════
86
149
  // SLIDE 1: Cover
87
150
  // ═══════════════════════════════════════════════════════════════════════════════
88
151
  async function slide1_cover(): Promise<Buffer> {
89
152
  let bg = await canvas(PALETTE.bg);
90
153
 
91
- // Accent gradient bar at top
92
154
  const topBar = await gradientBanner(SIZE, 8);
93
- bg = await sharp(bg).composite([{ input: topBar, top: 0, left: 0 }]).png().toBuffer();
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);
94
159
 
95
- // Decorative circles
96
- const circle1 = await circle(200, PALETTE.accent + '15');
97
- const circle2 = await circle(140, PALETTE.highlight + '10');
98
160
  bg = await sharp(bg).composite([
161
+ { input: topBar, top: 0, left: 0 },
99
162
  { input: circle1, top: -60, left: -60 },
100
163
  { input: circle2, top: 800, left: 880 },
164
+ { input: card, top: 260, left: 90 },
165
+ { input: arrow, top: 170, left: SIZE / 2 - 32 },
101
166
  ]).png().toBuffer();
102
167
 
103
- // Surface card
104
- const card = await roundedRect(900, 500, PALETTE.surface, 32, 0.9);
105
- bg = await sharp(bg).composite([{ input: card, top: 260, left: 90 }]).png().toBuffer();
106
-
107
- // Main title text
108
168
  const result = await addText(bg, { layers: [
109
- {
110
- text: '🚀',
111
- x: SIZE / 2, y: 180,
112
- fontSize: 80,
113
- color: PALETTE.text,
114
- anchor: 'top-center',
115
- },
116
- {
117
- text: 'Image Edit Tools',
118
- x: SIZE / 2, y: 340,
119
- fontSize: 56,
120
- color: PALETTE.text,
121
- anchor: 'top-center',
122
- },
123
- {
124
- text: 'AI 에이전트를 위한',
125
- x: SIZE / 2, y: 430,
126
- fontSize: 36,
127
- color: PALETTE.highlight,
128
- anchor: 'top-center',
129
- },
130
- {
131
- text: '이미지 편집 SDK',
132
- x: SIZE / 2, y: 480,
133
- fontSize: 36,
134
- color: PALETTE.highlight,
135
- anchor: 'top-center',
136
- },
137
- {
138
- text: 'TypeScript · Sharp · MCP',
139
- x: SIZE / 2, y: 580,
140
- fontSize: 28,
141
- color: PALETTE.muted,
142
- anchor: 'top-center',
143
- },
144
- {
145
- text: '← 스와이프하여 알아보기',
146
- x: SIZE / 2, y: 950,
147
- fontSize: 24,
148
- color: PALETTE.muted,
149
- anchor: 'top-center',
150
- },
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' },
151
173
  ]});
152
174
  if (!result.ok) throw new Error(result.error);
153
175
  return result.data;
@@ -159,21 +181,21 @@ async function slide1_cover(): Promise<Buffer> {
159
181
  async function slide2_features(): Promise<Buffer> {
160
182
  let bg = await canvas(PALETTE.bg);
161
183
 
162
- // Header stripe
163
184
  const stripe = await gradientBanner(SIZE, 120);
164
185
  bg = await sharp(bg).composite([{ input: stripe, top: 0, left: 0 }]).png().toBuffer();
165
186
 
166
- // Feature cards
167
187
  const features = [
168
- { icon: 'A', title: 'Crop + Resize', desc: '절대/비율/종횡비 크롭' },
169
- { icon: 'B', title: 'Adjust + Filter', desc: '밝기, 대비, 채도, 온도' },
170
- { icon: 'C', title: 'Watermark', desc: '텍스트/이미지 워터마크' },
171
- { icon: 'D', title: 'Pipeline', desc: '체인 파이프라인 처리' },
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' },
172
192
  ];
173
193
 
174
194
  const cardW = 420, cardH = 180, gap = 40;
175
195
  const startY = 180;
196
+ const iconSize = 40;
176
197
 
198
+ // Create feature cards and composite icons
177
199
  for (let i = 0; i < features.length; i++) {
178
200
  const col = i % 2;
179
201
  const row = Math.floor(i / 2);
@@ -181,12 +203,16 @@ async function slide2_features(): Promise<Buffer> {
181
203
  const y = startY + row * (cardH + gap);
182
204
 
183
205
  const card = await roundedRect(cardW, cardH, PALETTE.surface, 20);
184
- bg = await sharp(bg).composite([{ input: card, top: y, left: x }]).png().toBuffer();
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();
185
211
  }
186
212
 
187
- // Add text for header and each feature
213
+ // Text layers (no emoji)
188
214
  const textLayers = [
189
- { text: '주요 기능', x: SIZE / 2, y: 50, fontSize: 40, color: '#FFFFFF', anchor: 'top-center' as const },
215
+ { text: 'Key Features', x: SIZE / 2, y: 40, fontSize: 40, color: '#FFFFFF', anchor: 'top-center' as const },
190
216
  ];
191
217
 
192
218
  for (let i = 0; i < features.length; i++) {
@@ -196,13 +222,11 @@ async function slide2_features(): Promise<Buffer> {
196
222
  const y = startY + row * (cardH + gap);
197
223
 
198
224
  textLayers.push(
199
- { text: features[i].icon, x: x + 30, y: y + 30, fontSize: 40, color: PALETTE.text, anchor: 'top-left' as const },
200
- { text: features[i].title, x: x + 90, y: y + 35, fontSize: 28, color: PALETTE.text, anchor: 'top-left' as const },
201
- { text: features[i].desc, x: x + 30, y: y + 100, fontSize: 22, color: PALETTE.muted, anchor: 'top-left' as const },
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 },
202
227
  );
203
228
  }
204
229
 
205
- // Bottom slide counter
206
230
  textLayers.push(
207
231
  { text: '2 / 5', x: SIZE / 2, y: 980, fontSize: 20, color: PALETTE.muted, anchor: 'top-center' as const },
208
232
  );
@@ -218,9 +242,12 @@ async function slide2_features(): Promise<Buffer> {
218
242
  async function slide3_code(): Promise<Buffer> {
219
243
  let bg = await canvas(PALETTE.bg);
220
244
 
221
- // Code block background
222
245
  const codeCard = await roundedRect(920, 500, '#0D1117', 20);
223
- bg = await sharp(bg).composite([{ input: codeCard, top: 250, left: 80 }]).png().toBuffer();
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();
224
251
 
225
252
  const codeLines = [
226
253
  'import resize, addText from',
@@ -237,19 +264,9 @@ async function slide3_code(): Promise<Buffer> {
237
264
  ')',
238
265
  ];
239
266
 
240
- const codeLayers = [
241
- {
242
- text: '💻 코드 예시',
243
- x: SIZE / 2, y: 100,
244
- fontSize: 44, color: PALETTE.text,
245
- anchor: 'top-center' as const,
246
- },
247
- {
248
- text: '단 몇 줄로 이미지 편집 완료',
249
- x: SIZE / 2, y: 170,
250
- fontSize: 26, color: PALETTE.muted,
251
- anchor: 'top-center' as const,
252
- },
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' },
253
270
  ];
254
271
 
255
272
  codeLines.forEach((line, i) => {
@@ -260,19 +277,14 @@ async function slide3_code(): Promise<Buffer> {
260
277
  if (line.includes('width') || line.includes('height') || line.includes('layers')) color = '#FFA657';
261
278
 
262
279
  codeLayers.push({
263
- text: line,
264
- x: 120,
265
- y: 285 + i * 38,
266
- fontSize: 24,
267
- color,
268
- anchor: 'top-left' as const,
280
+ text: line, x: 120, y: 285 + i * 38,
281
+ fontSize: 24, color, anchor: 'top-left',
269
282
  });
270
283
  });
271
284
 
272
- codeLayers.push({
273
- text: '3 / 5', x: SIZE / 2, y: 980,
274
- fontSize: 20, color: PALETTE.muted, anchor: 'top-center' as const,
275
- });
285
+ codeLayers.push(
286
+ { text: '3 / 5', x: SIZE / 2, y: 980, fontSize: 20, color: PALETTE.muted, anchor: 'top-center' },
287
+ );
276
288
 
277
289
  const res = await addText(bg, { layers: codeLayers });
278
290
  if (!res.ok) throw new Error(res.error);
@@ -285,13 +297,12 @@ async function slide3_code(): Promise<Buffer> {
285
297
  async function slide4_mcp(): Promise<Buffer> {
286
298
  let bg = await canvas(PALETTE.bg);
287
299
 
288
- // Badges
289
300
  const badge1 = await numberBadge(1);
290
301
  const badge2 = await numberBadge(2);
291
302
  const badge3 = await numberBadge(3);
292
-
293
- // Step cards
294
303
  const stepCard = await roundedRect(820, 140, PALETTE.surface, 16);
304
+ const botIcon = await iconBot(56);
305
+
295
306
  bg = await sharp(bg).composite([
296
307
  { input: stepCard, top: 280, left: 130 },
297
308
  { input: stepCard, top: 480, left: 130 },
@@ -299,22 +310,23 @@ async function slide4_mcp(): Promise<Buffer> {
299
310
  { input: badge1, top: 310, left: 60 },
300
311
  { input: badge2, top: 510, left: 60 },
301
312
  { input: badge3, top: 710, left: 60 },
313
+ { input: botIcon, top: 65, left: SIZE / 2 - 28 },
302
314
  ]).png().toBuffer();
303
315
 
304
316
  const res = await addText(bg, { layers: [
305
- { text: '🤖 MCP 연동', x: SIZE / 2, y: 80, fontSize: 44, color: PALETTE.text, anchor: 'top-center' as const },
306
- { text: 'AI 에이전트에서 바로 사용', x: SIZE / 2, y: 150, fontSize: 26, color: PALETTE.muted, anchor: 'top-center' as const },
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' },
307
319
 
308
- { text: 'npm install', x: 170, y: 310, fontSize: 26, color: PALETTE.highlight, anchor: 'top-left' as const },
309
- { text: 'image-edit-tools 설치', x: 170, y: 355, fontSize: 22, color: PALETTE.muted, anchor: 'top-left' as const },
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' },
310
322
 
311
- { text: 'MCP 설정 추가', x: 170, y: 510, fontSize: 26, color: PALETTE.highlight, anchor: 'top-left' as const },
312
- { text: 'claude_desktop_config.json 수정', x: 170, y: 555, fontSize: 22, color: PALETTE.muted, anchor: 'top-left' as const },
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' },
313
325
 
314
- { text: 'AI에게 요청', x: 170, y: 710, fontSize: 26, color: PALETTE.highlight, anchor: 'top-left' as const },
315
- { text: ' 사진 1080x1080으로 크롭해줘', x: 170, y: 755, fontSize: 22, color: PALETTE.muted, anchor: 'top-left' as const },
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' },
316
328
 
317
- { text: '4 / 5', x: SIZE / 2, y: 980, fontSize: 20, color: PALETTE.muted, anchor: 'top-center' as const },
329
+ { text: '4 / 5', x: SIZE / 2, y: 980, fontSize: 20, color: PALETTE.muted, anchor: 'top-center' },
318
330
  ]});
319
331
  if (!res.ok) throw new Error(res.error);
320
332
  return res.data;
@@ -326,25 +338,22 @@ async function slide4_mcp(): Promise<Buffer> {
326
338
  async function slide5_cta(): Promise<Buffer> {
327
339
  let bg = await canvas(PALETTE.bg);
328
340
 
329
- // Large decorative gradient circle
330
- const bigCircle = await circle(300, PALETTE.accent + '20');
331
- bg = await sharp(bg).composite([
332
- { input: bigCircle, top: SIZE / 2 - 300, left: SIZE / 2 - 300 },
333
- ]).png().toBuffer();
334
-
335
- // CTA button shape
341
+ const bigCircle = await svgCircle(300, PALETTE.accent + '20');
336
342
  const ctaBtn = await roundedRect(500, 80, PALETTE.accent, 40);
343
+ const star = await iconStar(80);
344
+
337
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 },
338
348
  { input: ctaBtn, top: 650, left: SIZE / 2 - 250 },
339
349
  ]).png().toBuffer();
340
350
 
341
351
  const res = await addText(bg, { layers: [
342
- { text: '', x: SIZE / 2, y: 250, fontSize: 100, color: PALETTE.text, anchor: 'top-center' as const },
343
- { text: '지금 시작하세요', x: SIZE / 2, y: 400, fontSize: 48, color: PALETTE.text, anchor: 'top-center' as const },
344
- { text: 'npm install image-edit-tools', x: SIZE / 2, y: 500, fontSize: 30, color: PALETTE.highlight, anchor: 'top-center' as const },
345
- { text: '설치하기', x: SIZE / 2, y: 670, fontSize: 28, color: '#FFFFFF', anchor: 'top-center' as const },
346
- { text: 'github.com/swimmingkiim/image-edit-tools', x: SIZE / 2, y: 820, fontSize: 22, color: PALETTE.muted, anchor: 'top-center' as const },
347
- { text: '5 / 5', x: SIZE / 2, y: 980, fontSize: 20, color: PALETTE.muted, anchor: 'top-center' as const },
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' },
348
357
  ]});
349
358
  if (!res.ok) throw new Error(res.error);
350
359
  return res.data;
@@ -354,7 +363,7 @@ async function slide5_cta(): Promise<Buffer> {
354
363
  // Main
355
364
  // ═══════════════════════════════════════════════════════════════════════════════
356
365
  async function main() {
357
- console.log('📸 Generating Instagram card news slides...\n');
366
+ console.log('Generating Instagram card news slides...\n');
358
367
 
359
368
  const slides = [
360
369
  { name: 'slide-1-cover', fn: slide1_cover },
@@ -368,10 +377,10 @@ async function main() {
368
377
  const buf = await slide.fn();
369
378
  const outPath = join(OUTPUT_DIR, `${slide.name}.png`);
370
379
  writeFileSync(outPath, buf);
371
- console.log(` ${slide.name}.png (${(buf.length / 1024).toFixed(1)} KB)`);
380
+ console.log(` OK ${slide.name}.png (${(buf.length / 1024).toFixed(1)} KB)`);
372
381
  }
373
382
 
374
- console.log(`\n🎉 Done! ${slides.length} slides saved to examples/output/`);
383
+ console.log(`\nDone! ${slides.length} slides saved to examples/output/`);
375
384
  }
376
385
 
377
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",
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",
@@ -66,6 +66,8 @@ export async function addText(input: ImageInput, options: { layers: TextLayer[]
66
66
  let defs = '';
67
67
  let svgBody = '';
68
68
  let fontImports = new Set<string>();
69
+ const warnings: string[] = [];
70
+ let contentBottom = 0;
69
71
 
70
72
  for (let i = 0; i < options.layers.length; i++) {
71
73
  const layer = options.layers[i];
@@ -123,6 +125,24 @@ export async function addText(input: ImageInput, options: { layers: TextLayer[]
123
125
  layerSvg += `</text>`;
124
126
 
125
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
+ }
126
146
  }
127
147
 
128
148
  const fontStyle = fontImports.size > 0 ? `<style>${Array.from(fontImports).join('\n')}</style>` : '';
@@ -137,7 +157,9 @@ export async function addText(input: ImageInput, options: { layers: TextLayer[]
137
157
  .composite([{ input: Buffer.from(svgString), blend: 'over' }])
138
158
  .toBuffer();
139
159
 
140
- return ok(output);
160
+ const result = ok(output, warnings);
161
+ (result as any).bounds = { contentBottom: Math.round(contentBottom) };
162
+ return result;
141
163
  } catch (e: any) {
142
164
  const msg = e.message || '';
143
165
  if (msg.includes('HTTP')) return err(msg, ErrorCode.FETCH_FAILED);
@@ -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/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>;
@@ -1,4 +1,8 @@
1
1
  import { Ok, Err, ErrorCode } from '../types.js'
2
2
 
3
- export const ok = <T>(data: T): Ok<T> => ({ ok: true, data })
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
  })