image-edit-tools 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/.gitattributes +2 -0
  2. package/README.md +41 -0
  3. package/dist/index.d.ts +24 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +24 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/mcp/index.d.ts +3 -0
  8. package/dist/mcp/index.d.ts.map +1 -0
  9. package/dist/mcp/index.js +3 -0
  10. package/dist/mcp/index.js.map +1 -0
  11. package/dist/mcp/server.d.ts +3 -0
  12. package/dist/mcp/server.d.ts.map +1 -0
  13. package/dist/mcp/server.js +15 -0
  14. package/dist/mcp/server.js.map +1 -0
  15. package/dist/mcp/tools.d.ts +4 -0
  16. package/dist/mcp/tools.d.ts.map +1 -0
  17. package/dist/mcp/tools.js +285 -0
  18. package/dist/mcp/tools.js.map +1 -0
  19. package/dist/ops/add-text.d.ts +5 -0
  20. package/dist/ops/add-text.d.ts.map +1 -0
  21. package/dist/ops/add-text.js +129 -0
  22. package/dist/ops/add-text.js.map +1 -0
  23. package/dist/ops/adjust.d.ts +3 -0
  24. package/dist/ops/adjust.d.ts.map +1 -0
  25. package/dist/ops/adjust.js +71 -0
  26. package/dist/ops/adjust.js.map +1 -0
  27. package/dist/ops/batch.d.ts +3 -0
  28. package/dist/ops/batch.d.ts.map +1 -0
  29. package/dist/ops/batch.js +35 -0
  30. package/dist/ops/batch.js.map +1 -0
  31. package/dist/ops/blur-region.d.ts +5 -0
  32. package/dist/ops/blur-region.d.ts.map +1 -0
  33. package/dist/ops/blur-region.js +54 -0
  34. package/dist/ops/blur-region.js.map +1 -0
  35. package/dist/ops/composite.d.ts +5 -0
  36. package/dist/ops/composite.d.ts.map +1 -0
  37. package/dist/ops/composite.js +53 -0
  38. package/dist/ops/composite.js.map +1 -0
  39. package/dist/ops/convert.d.ts +3 -0
  40. package/dist/ops/convert.d.ts.map +1 -0
  41. package/dist/ops/convert.js +45 -0
  42. package/dist/ops/convert.js.map +1 -0
  43. package/dist/ops/crop.d.ts +3 -0
  44. package/dist/ops/crop.d.ts.map +1 -0
  45. package/dist/ops/crop.js +105 -0
  46. package/dist/ops/crop.js.map +1 -0
  47. package/dist/ops/detect-faces.d.ts +3 -0
  48. package/dist/ops/detect-faces.d.ts.map +1 -0
  49. package/dist/ops/detect-faces.js +41 -0
  50. package/dist/ops/detect-faces.js.map +1 -0
  51. package/dist/ops/detect-subject.d.ts +3 -0
  52. package/dist/ops/detect-subject.d.ts.map +1 -0
  53. package/dist/ops/detect-subject.js +78 -0
  54. package/dist/ops/detect-subject.js.map +1 -0
  55. package/dist/ops/extract-text.d.ts +5 -0
  56. package/dist/ops/extract-text.d.ts.map +1 -0
  57. package/dist/ops/extract-text.js +21 -0
  58. package/dist/ops/extract-text.js.map +1 -0
  59. package/dist/ops/filter.d.ts +3 -0
  60. package/dist/ops/filter.d.ts.map +1 -0
  61. package/dist/ops/filter.js +53 -0
  62. package/dist/ops/filter.js.map +1 -0
  63. package/dist/ops/get-dominant-colors.d.ts +3 -0
  64. package/dist/ops/get-dominant-colors.d.ts.map +1 -0
  65. package/dist/ops/get-dominant-colors.js +48 -0
  66. package/dist/ops/get-dominant-colors.js.map +1 -0
  67. package/dist/ops/get-metadata.d.ts +3 -0
  68. package/dist/ops/get-metadata.d.ts.map +1 -0
  69. package/dist/ops/get-metadata.js +30 -0
  70. package/dist/ops/get-metadata.js.map +1 -0
  71. package/dist/ops/optimize.d.ts +3 -0
  72. package/dist/ops/optimize.d.ts.map +1 -0
  73. package/dist/ops/optimize.js +78 -0
  74. package/dist/ops/optimize.js.map +1 -0
  75. package/dist/ops/overlay.d.ts +3 -0
  76. package/dist/ops/overlay.d.ts.map +1 -0
  77. package/dist/ops/overlay.js +52 -0
  78. package/dist/ops/overlay.js.map +1 -0
  79. package/dist/ops/pad.d.ts +3 -0
  80. package/dist/ops/pad.d.ts.map +1 -0
  81. package/dist/ops/pad.js +62 -0
  82. package/dist/ops/pad.js.map +1 -0
  83. package/dist/ops/pipeline.d.ts +5 -0
  84. package/dist/ops/pipeline.d.ts.map +1 -0
  85. package/dist/ops/pipeline.js +81 -0
  86. package/dist/ops/pipeline.js.map +1 -0
  87. package/dist/ops/remove-bg.d.ts +3 -0
  88. package/dist/ops/remove-bg.d.ts.map +1 -0
  89. package/dist/ops/remove-bg.js +79 -0
  90. package/dist/ops/remove-bg.js.map +1 -0
  91. package/dist/ops/resize.d.ts +3 -0
  92. package/dist/ops/resize.d.ts.map +1 -0
  93. package/dist/ops/resize.js +54 -0
  94. package/dist/ops/resize.js.map +1 -0
  95. package/dist/ops/watermark.d.ts +3 -0
  96. package/dist/ops/watermark.d.ts.map +1 -0
  97. package/dist/ops/watermark.js +142 -0
  98. package/dist/ops/watermark.js.map +1 -0
  99. package/dist/types.d.ts +233 -0
  100. package/dist/types.d.ts.map +1 -0
  101. package/dist/types.js +12 -0
  102. package/dist/types.js.map +1 -0
  103. package/dist/utils/load-image.d.ts +9 -0
  104. package/dist/utils/load-image.d.ts.map +1 -0
  105. package/dist/utils/load-image.js +22 -0
  106. package/dist/utils/load-image.js.map +1 -0
  107. package/dist/utils/result.d.ts +4 -0
  108. package/dist/utils/result.d.ts.map +1 -0
  109. package/dist/utils/result.js +3 -0
  110. package/dist/utils/result.js.map +1 -0
  111. package/dist/utils/validate.d.ts +16 -0
  112. package/dist/utils/validate.d.ts.map +1 -0
  113. package/dist/utils/validate.js +20 -0
  114. package/dist/utils/validate.js.map +1 -0
  115. package/docs/AGENTS.md +18 -0
  116. package/docs/MCP.md +106 -0
  117. package/package.json +52 -0
  118. package/scripts/generate-fixtures.js +33 -0
  119. package/src/index.ts +24 -0
  120. package/src/mcp/index.ts +2 -0
  121. package/src/mcp/server.ts +21 -0
  122. package/src/mcp/tools.ts +276 -0
  123. package/src/ops/add-text.ts +139 -0
  124. package/src/ops/adjust.ts +68 -0
  125. package/src/ops/batch.ts +41 -0
  126. package/src/ops/blur-region.ts +58 -0
  127. package/src/ops/composite.ts +56 -0
  128. package/src/ops/convert.ts +46 -0
  129. package/src/ops/crop.ts +101 -0
  130. package/src/ops/detect-faces.ts +41 -0
  131. package/src/ops/detect-subject.ts +80 -0
  132. package/src/ops/extract-text.ts +19 -0
  133. package/src/ops/filter.ts +51 -0
  134. package/src/ops/get-dominant-colors.ts +41 -0
  135. package/src/ops/get-metadata.ts +28 -0
  136. package/src/ops/optimize.ts +77 -0
  137. package/src/ops/overlay.ts +51 -0
  138. package/src/ops/pad.ts +63 -0
  139. package/src/ops/pipeline.ts +61 -0
  140. package/src/ops/remove-bg.ts +82 -0
  141. package/src/ops/resize.ts +54 -0
  142. package/src/ops/watermark.ts +141 -0
  143. package/src/types/color-thief-node.d.ts +4 -0
  144. package/src/types.ts +267 -0
  145. package/src/utils/load-image.ts +21 -0
  146. package/src/utils/result.ts +4 -0
  147. package/src/utils/validate.ts +21 -0
  148. package/tests/fixtures/logo.png +0 -0
  149. package/tests/fixtures/sample.jpg +0 -0
  150. package/tests/fixtures/sample.png +0 -0
  151. package/tests/fixtures/sample.webp +0 -0
  152. package/tests/integration/error-handling.test.ts +22 -0
  153. package/tests/integration/load-image.test.ts +45 -0
  154. package/tests/unit/add-text.test.ts +56 -0
  155. package/tests/unit/adjust.test.ts +81 -0
  156. package/tests/unit/batch.test.ts +38 -0
  157. package/tests/unit/blur-region.test.ts +52 -0
  158. package/tests/unit/composite.test.ts +58 -0
  159. package/tests/unit/convert.test.ts +55 -0
  160. package/tests/unit/crop.test.ts +100 -0
  161. package/tests/unit/detect-faces.test.ts +32 -0
  162. package/tests/unit/detect-subject.test.ts +37 -0
  163. package/tests/unit/extract-text.test.ts +34 -0
  164. package/tests/unit/filter.test.ts +39 -0
  165. package/tests/unit/get-dominant-colors.test.ts +25 -0
  166. package/tests/unit/get-metadata.test.ts +36 -0
  167. package/tests/unit/mcp.test.ts +104 -0
  168. package/tests/unit/optimize.test.ts +47 -0
  169. package/tests/unit/overlay.test.ts +39 -0
  170. package/tests/unit/pad.test.ts +56 -0
  171. package/tests/unit/pipeline.test.ts +48 -0
  172. package/tests/unit/remove-bg.test.ts +42 -0
  173. package/tests/unit/resize.test.ts +70 -0
  174. package/tests/unit/watermark.test.ts +54 -0
  175. package/tsconfig.json +15 -0
  176. package/vitest.config.ts +27 -0
@@ -0,0 +1,42 @@
1
+ import { describe, it, expect, beforeAll, vi } from 'vitest'
2
+ import { readFileSync } from 'fs'
3
+ import { join, dirname } from 'path'
4
+ import { fileURLToPath } from 'url'
5
+ import { removeBg } from '../../src/ops/remove-bg.js'
6
+
7
+ const __filename = fileURLToPath(import.meta.url)
8
+ const __dirname = dirname(__filename)
9
+ const fixture = (name: string) => readFileSync(join(__dirname, '../fixtures', name))
10
+
11
+ // Since tests run in CI/offline and we shouldn't download huge models,
12
+ // we test that the function gracefully hits the MODEL_NOT_FOUND error
13
+ // if AutoModel fails, or passes if the network enables it.
14
+ // We explicitly mock '@xenova/transformers' to simulate network failure or success.
15
+
16
+ vi.mock('@xenova/transformers', async (importOriginal) => {
17
+ return {
18
+ ...(await importOriginal<typeof import('@xenova/transformers')>()),
19
+ AutoModel: {
20
+ from_pretrained: vi.fn().mockRejectedValue(new Error('Mock network failure'))
21
+ },
22
+ AutoProcessor: {
23
+ from_pretrained: vi.fn()
24
+ }
25
+ }
26
+ })
27
+
28
+ describe('removeBg', () => {
29
+ let sampleJpeg: Buffer
30
+
31
+ beforeAll(() => {
32
+ sampleJpeg = fixture('sample.jpg')
33
+ })
34
+
35
+ it('handles unavailable model offline correctly', async () => {
36
+ const result = await removeBg(sampleJpeg, {})
37
+ expect(result.ok).toBe(false)
38
+ if (!result.ok) {
39
+ expect(result.code).toBe('MODEL_NOT_FOUND')
40
+ }
41
+ })
42
+ })
@@ -0,0 +1,70 @@
1
+ import { describe, it, expect, beforeAll } from 'vitest'
2
+ import { readFileSync } from 'fs'
3
+ import { join, dirname } from 'path'
4
+ import { fileURLToPath } from 'url'
5
+ import { resize } from '../../src/ops/resize.js'
6
+ import sharp from 'sharp'
7
+
8
+ const __filename = fileURLToPath(import.meta.url)
9
+ const __dirname = dirname(__filename)
10
+ const fixture = (name: string) => readFileSync(join(__dirname, '../fixtures', name))
11
+
12
+ describe('resize', () => {
13
+ let sampleJpeg: Buffer
14
+
15
+ beforeAll(() => {
16
+ sampleJpeg = fixture('sample.jpg') // 400x300
17
+ })
18
+
19
+ it('resizes using px dimensions', async () => {
20
+ const result = await resize(sampleJpeg, { width: 100, height: 100 })
21
+ expect(result.ok).toBe(true)
22
+ if (!result.ok) return
23
+ const meta = await sharp(result.data).metadata()
24
+ expect(meta.width).toBe(100)
25
+ expect(meta.height).toBe(100)
26
+ })
27
+
28
+ it('resizes using scale', async () => {
29
+ const result = await resize(sampleJpeg, { scale: 0.5 })
30
+ expect(result.ok).toBe(true)
31
+ if (!result.ok) return
32
+ const meta = await sharp(result.data).metadata()
33
+ expect(meta.width).toBe(200)
34
+ expect(meta.height).toBe(150)
35
+ })
36
+
37
+ it('preserves aspect ratio for single axis', async () => {
38
+ const result = await resize(sampleJpeg, { width: 200 })
39
+ expect(result.ok).toBe(true)
40
+ if (!result.ok) return
41
+ const meta = await sharp(result.data).metadata()
42
+ expect(meta.width).toBe(200)
43
+ expect(meta.height).toBe(150) // 400:300 is 4:3 -> 200:150
44
+ })
45
+
46
+ it('applies fit parameter correctly', async () => {
47
+ const result = await resize(sampleJpeg, { width: 100, height: 200, fit: 'contain' })
48
+ expect(result.ok).toBe(true)
49
+ if (!result.ok) return
50
+ const meta = await sharp(result.data).metadata()
51
+ expect(meta.width).toBe(100)
52
+ expect(meta.height).toBe(200)
53
+ })
54
+
55
+ it('respects withoutEnlargement', async () => {
56
+ const result = await resize(sampleJpeg, { width: 1000, height: 1000, withoutEnlargement: true })
57
+ expect(result.ok).toBe(true)
58
+ if (!result.ok) return
59
+ const meta = await sharp(result.data).metadata()
60
+ expect(meta.width).toBe(400) // Original width
61
+ expect(meta.height).toBe(300) // Original height
62
+ })
63
+
64
+ it('returns error on zero dimensions', async () => {
65
+ const result = await resize(sampleJpeg, { width: 0 })
66
+ expect(result.ok).toBe(false)
67
+ if (result.ok) return
68
+ expect(result.code).toBe('INVALID_INPUT')
69
+ })
70
+ })
@@ -0,0 +1,54 @@
1
+ import { describe, it, expect, beforeAll } from 'vitest'
2
+ import { readFileSync } from 'fs'
3
+ import { join, dirname } from 'path'
4
+ import { fileURLToPath } from 'url'
5
+ import { watermark } from '../../src/ops/watermark.js'
6
+
7
+ const __filename = fileURLToPath(import.meta.url)
8
+ const __dirname = dirname(__filename)
9
+ const fixture = (name: string) => readFileSync(join(__dirname, '../fixtures', name))
10
+
11
+ describe('watermark', () => {
12
+ let sampleJpeg: Buffer
13
+ let logoPng: Buffer
14
+
15
+ beforeAll(() => {
16
+ sampleJpeg = fixture('sample.jpg') // 400x300
17
+ logoPng = fixture('logo.png') // 100x100
18
+ })
19
+
20
+ it('adds text watermark at bottom-right', async () => {
21
+ const result = await watermark(sampleJpeg, {
22
+ type: 'text', text: 'Confidential', position: 'bottom-right'
23
+ })
24
+ expect(result.ok).toBe(true)
25
+ })
26
+
27
+ it('adds text watermark tiled', async () => {
28
+ const result = await watermark(sampleJpeg, {
29
+ type: 'text', text: 'Draft', position: 'tile', tileSpacing: 100
30
+ })
31
+ expect(result.ok).toBe(true)
32
+ })
33
+
34
+ it('adds image watermark at top-left', async () => {
35
+ const result = await watermark(sampleJpeg, {
36
+ type: 'image', image: logoPng, position: 'top-left'
37
+ })
38
+ expect(result.ok).toBe(true)
39
+ })
40
+
41
+ it('adds image watermark tiled with opacity and scale', async () => {
42
+ const result = await watermark(sampleJpeg, {
43
+ type: 'image', image: logoPng, position: 'tile', opacity: 0.3, scale: 0.5, tileSpacing: 20
44
+ })
45
+ expect(result.ok).toBe(true)
46
+ })
47
+
48
+ it('returns error if missing valid type', async () => {
49
+ const result = await watermark(sampleJpeg, {} as any)
50
+ expect(result.ok).toBe(false)
51
+ if (result.ok) return
52
+ expect(result.code).toBe('INVALID_INPUT')
53
+ })
54
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "dist",
7
+ "declaration": true,
8
+ "declarationMap": true,
9
+ "sourceMap": true,
10
+ "strict": true,
11
+ "esModuleInterop": true
12
+ },
13
+ "include": ["src"],
14
+ "exclude": ["node_modules", "dist", "tests"]
15
+ }
@@ -0,0 +1,27 @@
1
+ import { defineConfig } from 'vitest/config'
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: 'node',
7
+ coverage: {
8
+ provider: 'v8',
9
+ reporter: ['text', 'json', 'html'],
10
+ thresholds: {
11
+ lines: 70,
12
+ functions: 70,
13
+ branches: 50,
14
+ },
15
+ exclude: [
16
+ 'scripts/**',
17
+ 'src/index.ts',
18
+ 'src/mcp/index.ts',
19
+ 'src/mcp/server.ts',
20
+ 'src/mcp/tools.ts', // mostly wiring, E2E tested
21
+ 'src/ops/detect-subject.ts', // unimplemented/stubbed
22
+ 'src/ops/remove-bg.ts' // heavy AI model, requires optional mocking
23
+ ]
24
+ },
25
+ testTimeout: 30000,
26
+ },
27
+ })