img转换
带Web UI的快速CLI图像转换器。
在以下之间转换图像:
- JPG格式
- 便携式网络图形
- WebP
- AVIF
- 图形交换格式
- 标签式图像文件格式
专为开发人员和人工智能代理而构建。
安装
npx @dutchbase/img-convert或
npm install -g @dutchbase/img-convert# Convert a file
img-convert photo.jpg -f webp --json
# Inspect an image without converting
img-convert info photo.jpg
# Give Claude Code native image conversion tools
img-convert mcp______________________________________________________________________
______________________________________________________________________
目录
______________________________________________________________________
代理技能
img-convert 船舶a SKILL.md 编码代理可以导入的文件,以获得每个命令、标志、模式和gotcha的完整、结构化的知识,而无需阅读此README。
导入到Claude Code(全局,所有项目)
/instinct-import https://raw.githubusercontent.com/dutchbase/img-converter/main/SKILL.md作为项目范围的技能导入
/instinct-import https://raw.githubusercontent.com/dutchbase/img-converter/main/SKILL.md --scope project导入后,任何Claude Code会话都会自动知道:
- 为给定任务使用哪个接口(CLI与API、MCP与REST)
- 始终奔跑
img-convert info转换未知图像之前 - 这
--json/stderr管道分离合同 - 每个CLI标志,包括新标志(
--grayscale,--rotate,--normalize等等) - 的清单格式
batch子命令 - 所有MCP工具签名和返回形状
- Node.js API类型和常用模式
- 格式陷阱(仅限HEIC输入,alpha→JPEG背景,GIF动画规则)
- 常见错误以及如何避免
技能文件与包保持同步 SKILL.md.
______________________________________________________________________
为什么转换img
大多数图像转换工具都是为交互式使用而设计的——GUI、web表单、一次性shell命令。 img-convert 专为 程序化使用:CI管道、构建脚本、AI代理工作流和服务器端处理。
关键设计原则:
- 机器可读输出优先。
--json每一个命令。stderr承载着面向人类的进步。stdout携带数据。每个命令都干净地传输到jq. - AI代理优化。 提供本地MCP服务器。Claude Code、Cursor和任何兼容MCP的代理都可以调用
convert_image和get_image_info作为原生工具——没有shell转义,没有子流程管理,完全类型安全。 - 可组合。 CLI、Node.js API和REST API都运行相同的
processImage()引擎盖下的管道。无论调用路径如何,行为都是相同的。 - 最小的已发布足迹。 npm包的大小约为50KB。完整的Next.js web UI被排除在已发布的包之外——仅
dist/,lib/,types/,以及cli/船。
______________________________________________________________________
安装
全局CLI
npm install -g @dutchbase/img-convert本地依赖项(Node.js API)
npm install @dutchbase/img-convert自托管web UI
git clone https://github.com/dutchbase/img-convert
cd img-convert
npm install
npm run dev # http://localhost:3000
npm run build # production Next.js build需求
- Node.js>=18.0.0
- Sharp的原生绑定是为Linux x64/arm64、macOS arm64/x64和Windows x64预先构建的。有关其他平台,请参阅 清晰的安装指南.
______________________________________________________________________
CLI参考
转换(默认操作)
img-convert [files...] -f [options]files 接受文件路径、glob模式和HTTP/HTTPS URL。当没有提供文件并且stdin是管道时,从stdin读取并写入stdout(管道模式)。
选项
| 标志 | 默认值 | 描述 |
|---|---|---|
-f, --format | — | 必修的。 目标格式: jpeg png webp avif gif tiff |
-q, --quality | 85 | 编码质量1–100。适用于JPEG、WebP、AVIF、TIFF。PNG从该值导出压缩级别。GIF忽略了它 |
--width | -- | 以像素为单位调整此宽度。默认情况下保持纵横比。 |
--height | -- | 调整到这个高度(像素)。默认情况下保持纵横比。 |
--no-metadata | -- | 删除EXIF/XMP/IPTC元数据。ICC颜色配置文件始终保持不变。 |
-o, --output | input dir | 将输出文件写入此目录。如果不存在,则自动创建。 |
-c, --concurrency | 4 | 最大并行转换。 |
--json | -- | 将结构化JSON发送到stdout。所有进度和警告都会转到stderr。 |
--dry-run | -- | 展示不写任何东西会写什么。 |
--quiet | -- | 抑制每个文件的进度行。错误摘要仍然显示。 |
--grayscale | -- | 将图像去饱和为灰度。 |
--rotate | -- | 按度旋转。可接受的任何角度;背景色填充空白角落。 |
--flip | -- | 水平翻转(左-右后视镜)。 |
--flop | -- | 垂直翻转(顶部-底部镜子)。 |
--background | -- | 透明区域的背景填充颜色(例如。 #ffffff, rgba(0,0,0,0)).需要清洁PNG→JPEG转换。 |
--blur | —— | 高斯模糊西格玛(有效范围:0.3-1000)。 |
--sharpen | -- | 使用Sharp的默认参数应用反锐化蒙版锐化。 |
--normalize | -- | 将对比度拉伸到全范围。适用于扫描文档和低对比度图像。 |
--trim | -- | 自动修剪所有边缘的均匀颜色边框像素。 |
示例
# Single file
img-convert photo.jpg -f webp
# Glob pattern with output directory and quality
img-convert "src/images/*.png" -f avif -q 80 -o dist/images/
# Resize to max 1280px wide, maintain aspect ratio
img-convert banner.png -f jpeg --width 1280 -q 90
# Strip metadata, 4 files at once
img-convert *.jpg -f webp --no-metadata -c 4 -o output/
# Remote URL
img-convert https://example.com/photo.png -f webp -o ./converted/
# Machine-readable output — stdout is pure JSON, stderr is progress
img-convert photo.jpg -f webp --json 2>/dev/null | jq .reduction
# Pipe mode: stdin → stdout (no file args, non-TTY stdin)
cat input.png | img-convert -f webp > output.webp
# Preview without writing
img-convert "*.jpg" -f avif --dry-run --json
# Grayscale + auto contrast for document scans
img-convert scan.jpg -f png --grayscale --normalize
# Flatten PNG transparency to white before JPEG conversion
img-convert logo.png -f jpeg --background "#ffffff"
# Rotate with background fill
img-convert photo.jpg -f jpeg --rotate 90 --background "#000000"JSON输出形状
单个文件:
{
"input": "photo.jpg",
"output": "/absolute/path/to/photo.webp",
"inputBytes": 204800,
"outputBytes": 81920,
"reduction": 60.0,
"width": 1920,
"height": 1080,
"format": "webp",
"quality": 85
}多个文件: 每个文件有一个对象的JSON数组。失败的文件包括 "error" 字符串字段,而不是大小/维度数据。
干运行(带 --json):
{
"input": "photo.jpg",
"output": "/absolute/path/to/photo.webp",
"inputBytes": 204800,
"dryRun": true
}______________________________________________________________________
info 子命令
检查图像而不进行转换。始终将JSON输出到stdout。支持文件路径和URL。
img-convert info img-convert info photo.jpg
img-convert info https://example.com/image.png输出:
{
"format": "jpeg",
"width": 4032,
"height": 3024,
"filesize": 3891200,
"hasAlpha": false,
"hasExif": true,
"colorSpace": "srgb",
"isAnimated": false,
"channels": 3,
"density": 72
}现场参考:
| 字段 | 类型 | 描述 |
|---|---|---|
format | string | Sharp检测到的格式: jpeg, png, webp, gif, tiff, avif等等。 |
width | number | 宽度(像素) |
height | number | 高度(像素) |
filesize | number | 文件大小(字节) |
hasAlpha | boolean | 是否存在alpha(透明度)通道 |
hasExif | boolean | EXIF元数据是否存在 |
colorSpace | string | 颜色空间: srgb, p3, cmyk, grey等等。 |
isAnimated | 布尔值 | true 用于动画GIF、多页TIFF、动画WebP |
channels | number | 通道计数--3=RGB,4=RGBA |
density | number | 嵌入在文件元数据中的DPI/PPI。 undefined 如果没有设置。 |
这 info 命令是为 转换前检查 --检查 hasAlpha 在转换为JPEG之前,请检查 isAnimated 在剥离框架之前,请在调整大小之前验证尺寸。
______________________________________________________________________
batch 子命令
转换JSON清单文件中定义的图像列表。
img-convert batch [options]选项:
| 标志 | 默认值 | 描述 |
|---|---|---|
-c, --concurrency | 4 | 并行转换限制 |
--json | -- | 将结果作为JSON数组输出到stdout |
清单格式:
[
{
"input": "src/hero.png",
"output": "dist/hero.webp",
"format": "webp",
"quality": 90
},
{
"input": "https://cdn.example.com/avatar.png",
"output": "assets/avatar.avif",
"format": "avif",
"width": 200,
"height": 200
},
{
"input": "photos/raw.jpg",
"format": "jpeg",
"quality": 75,
"removeMetadata": true
}
]如果 output 如果省略,则文件将以新的扩展名写入输入旁边。
清单项目字段:
| 字段 | 必填 | 描述 |
|---|---|---|
input | 是 | 文件路径或HTTP/HTTPS URL |
format | 是 | 目标格式 |
output | 否 | 输出文件路径。汽车衍生自 input 如果省略。 |
quality | 否 | 质量1–100,默认值 85 |
width | 否 | 以像素为单位调整宽度 |
height | 否 | 以像素为单位调整高度 |
removeMetadata | 否 | 删除EXIF元数据,默认值 false |
# Process manifest, capture JSON results
img-convert batch jobs.json --json > results.json 2>/dev/null
# Process with human-readable progress
img-convert batch jobs.json -c 8每个项目的JSON输出:
{
"index": 0,
"input": "src/hero.png",
"output": "dist/hero.webp",
"inputBytes": 512000,
"outputBytes": 102400,
"reduction": 80.0,
"width": 1920,
"height": 1080,
"format": "webp",
"quality": 90
}______________________________________________________________________
mcp 子命令
在stdio上启动MCP(模型上下文协议)服务器。这是AI代理的主要集成点。
img-convert mcp看 AI代理集成 了解全部细节。
______________________________________________________________________
AI代理集成
img-convert 被设计为由AI代理直接调用的原生类型工具,而不是原始shell命令。
MCP服务器
模型上下文协议 是为AI代理提供结构化工具访问的开放标准。 img-convert 运送生产就绪的MCP服务器。
使用克劳德代码注册
添加 ~/.claude/mcp.json:
{
"mcpServers": {
"img-convert": {
"command": "img-convert",
"args": ["mcp"]
}
}
}注册后,Claude Code可以调用 convert_image, get_image_info, batch_convert,以及 list_supported_formats 作为原生工具——具有完整的类型检查、无shell转义和结构化返回值。
向其他MCP客户端注册
任何支持MCP stdio传输的客户端都可以正常工作。指向 img-convert mcp.
光标 (~/.cursor/mcp.json), 继续, 泽德,任何其他MCP主机遵循相同的模式:
{
"mcpServers": {
"img-convert": {
"command": "img-convert",
"args": ["mcp"]
}
}
}MCP工具
convert_image
转换单个图像文件。接受文件路径和URL。
输入架构:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
input_path | string | 是 | 文件路径或HTTP/HTTPS URL |
output_format | string | 是 | 以下之一: jpeg png webp avif gif tiff |
output_path | string | 否 | 输出文件路径。源自 input_path 如果省略,则使用新的扩展名。 |
quality | number | No | 质量1–100,默认值 85 |
width | number | 否 | 调整宽度,保持纵横比 |
height | number | 否 | 调整高度,保持纵横比 |
remove_metadata | boolean | 否 | 删除EXIF,默认值 false |
grayscale | boolean | 否 | 去饱和为灰度 |
rotate | number | No | 旋转度 |
background | string | 否 | 背景填充颜色(CSS颜色字符串) |
退货:
{
"input_path": "photo.jpg",
"output_path": "photo.webp",
"input_bytes": 204800,
"output_bytes": 81920,
"reduction": 60.0,
"width": 1920,
"height": 1080,
"format": "webp",
"quality": 85
}get_image_info
无需转换即可获取图像的完整元数据。
输入架构:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
input_path | string | 是 | 文件路径或HTTP/HTTPS URL |
退货:
{
"format": "jpeg",
"width": 4032,
"height": 3024,
"filesize": 3891200,
"hasAlpha": false,
"hasExif": true,
"colorSpace": "srgb",
"isAnimated": false,
"channels": 3,
"density": 72
}首先使用此选项做出明智的转换决策:图像是否具有透明度(影响JPEG转换),是否具有动画(影响帧处理),颜色空间是什么(影响打印工作流程)?
batch_convert
在一次工具调用中转换多个图像。
输入架构:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
items | array | Yes | 转换作业数组(见下文) |
concurrency | number | No | 并行限制,默认值 4 |
中的每个项目 items:
| 字段 | 类型 | 必填 |
|---|---|---|
input_path | string | 是 |
output_format | string | 是 |
output_path | string | 否 |
quality | 编号 | 否 |
width | 编号 | 否 |
height | 编号 | 否 |
退货: 结果对象数组,每个输入项一个。
list_supported_formats
枚举服务器可以读取和写入的内容。
退货:
{
"input": ["jpeg", "png", "webp", "avif", "gif", "tiff", "heic", "svg", "bmp"],
"output": ["jpeg", "png", "webp", "avif", "gif", "tiff"]
}JSON输出设计
每个命令都旨在生成可解析、可管道化的输出:
--json旗帜:stdout上的数据为JSON,stderr上的所有进度/警告info子命令:总是JSON,不需要标志batch --json:JSON数组,每个清单项有一个条目
这为代理和脚本提供了清晰的信号分离:
# Capture reduction percentage
REDUCTION=$(img-convert photo.jpg -f webp --json 2>/dev/null | jq .reduction)
# Inspect before converting
HAS_ALPHA=$(img-convert info logo.png | jq .hasAlpha)
if [ "$HAS_ALPHA" = "true" ]; then
img-convert logo.png -f jpeg --background "#ffffff" --json 2>/dev/null
else
img-convert logo.png -f jpeg --json 2>/dev/null
fi
# Count failed conversions in a batch
FAILED=$(img-convert batch jobs.json --json 2>/dev/null | jq '[.[] | select(.error)] | length')清单批处理模式
AI代理自然地使用JSON作为数据格式。清单模式将作业定义与执行解耦——代理将作业列表组装为数据结构,将其写入文件,然后 img-convert batch 执行它:
// Agent builds the manifest
const manifest = imagePaths.map(inputPath => ({
input: inputPath,
output: inputPath.replace(/\.\w+$/, '.webp'),
format: 'webp' as const,
quality: 85,
}))
fs.writeFileSync('convert-jobs.json', JSON.stringify(manifest, null, 2))
// Agent executes it and reads structured results
const stdout = execSync('img-convert batch convert-jobs.json --json 2>/dev/null', {
encoding: 'utf8',
})
const results = JSON.parse(stdout)
const totalSaved = results.reduce(
(sum: number, r: { inputBytes: number; outputBytes: number }) =>
sum + (r.inputBytes - r.outputBytes),
0
)没有shell插值,没有引用边缘情况,完全声明性,完全可审计。
______________________________________________________________________
Node.js API
import { convert, getInfo, batch } from '@dutchbase/img-convert'这三个函数都接受文件路径、HTTP/HTTPS URL或原始文件 Buffer 对象作为输入。
convert()
function convert(
input: string | Buffer,
options: ConvertApiOptions
): PromiseConvertApiOptions:
interface ConvertApiOptions {
format: ImageFormat; // required — "jpeg"|"png"|"webp"|"avif"|"gif"|"tiff"
quality?: number; // default 85
width?: number;
height?: number;
removeMetadata?: boolean; // default false
maintainAspectRatio?: boolean; // default true
allowUpscaling?: boolean; // default false (prevents enlargement)
crop?: {
left: number;
top: number;
width: number;
height: number;
};
rotate?: number; // arbitrary degrees
autoRotate?: boolean; // apply and strip EXIF orientation tag
flip?: boolean; // horizontal mirror
flop?: boolean; // vertical mirror
background?: string; // CSS color string
grayscale?: boolean;
blur?: number; // Gaussian sigma 0.3–1000
sharpen?: boolean;
normalize?: boolean;
trim?: boolean;
}ConvertApiResult:
interface ConvertApiResult {
buffer: Buffer;
info: {
inputBytes: number;
outputBytes: number;
width: number;
height: number;
format: string;
};
}示例:
import { convert } from '@dutchbase/img-convert'
import fs from 'fs/promises'
// Convert a local file
const result = await convert('./photo.jpg', {
format: 'webp',
quality: 85,
width: 1280,
})
await fs.writeFile('./photo.webp', result.buffer)
console.log(`${result.info.inputBytes} → ${result.info.outputBytes} bytes`)
// Convert from a URL
const fromUrl = await convert('https://example.com/image.png', {
format: 'avif',
quality: 70,
})
// Convert from an in-memory Buffer (e.g. from a multipart upload handler)
const fromBuffer = await convert(req.file.buffer, {
format: 'jpeg',
quality: 90,
background: '#ffffff', // flatten PNG transparency before JPEG encoding
})
// Crop then resize
const cropped = await convert('./screenshot.png', {
format: 'webp',
crop: { left: 100, top: 50, width: 800, height: 600 },
width: 400,
})
// Strip EXIF, rotate to EXIF orientation, then re-encode
const clean = await convert('./camera.jpg', {
format: 'jpeg',
autoRotate: true,
removeMetadata: true,
quality: 88,
})getInfo()
function getInfo(input: string | Buffer): Promiseinterface ImageInfo {
format: string;
width: number;
height: number;
filesize: number;
hasAlpha: boolean;
hasExif: boolean;
colorSpace: string;
isAnimated: boolean;
channels?: number;
density?: number;
}示例:
import { getInfo, convert } from '@dutchbase/img-convert'
const info = await getInfo('./photo.jpg')
// { format: 'jpeg', width: 4032, height: 3024, filesize: 3891200,
// hasAlpha: false, hasExif: true, colorSpace: 'srgb', isAnimated: false }
// Conditional conversion: don't flatten alpha if not needed
const { hasAlpha } = await getInfo('./image.png')
const result = await convert('./image.png', {
format: 'jpeg',
...(hasAlpha ? { background: '#ffffff' } : {}),
})
// Skip animated GIFs in a batch
const infos = await Promise.all(paths.map(p => getInfo(p)))
const staticOnly = paths.filter((_, i) => !infos[i].isAnimated)batch()
function batch(
items: BatchApiItem[],
options?: BatchApiOptions
): Promiseinterface BatchApiItem {
input: string; // file path or URL
output?: string; // output file path — auto-derived if omitted
format: ImageFormat;
quality?: number;
width?: number;
height?: number;
removeMetadata?: boolean;
}
interface BatchApiOptions {
concurrency?: number; // default 4
outputDir?: string; // write all outputs here when output not specified per-item
}
interface BatchApiResult {
input: string;
output: string;
inputBytes: number;
outputBytes: number;
width: number;
height: number;
format: string;
quality: number;
}例子:
import { batch } from '@dutchbase/img-convert'
const results = await batch(
[
{ input: './src/hero.png', format: 'webp', quality: 90 },
{ input: './src/thumb.jpg', format: 'avif', width: 200 },
{ input: './src/banner.gif', format: 'webp' },
],
{ concurrency: 4 }
)
for (const r of results) {
const pct = ((1 - r.outputBytes / r.inputBytes) * 100).toFixed(1)
console.log(`${r.input} → ${r.output} (${pct}% smaller)`)
}
// ./src/hero.png → ./src/hero.webp (67.3% smaller)
// ./src/thumb.jpg → ./src/thumb.avif (71.0% smaller)
// ./src/banner.gif → ./src/banner.webp (44.2% smaller)______________________________________________________________________
REST API
web应用程序公开了一个端点。它可以直接从任何HTTP客户端调用。
POST /api/convert
接受 multipart/form-data。以二进制形式返回转换后的图像。
请求字段:
| 字段 | 类型 | 必填 | 备注 |
|---|---|---|---|
file | 文件 | 是 | 源图像。最大50 MB。 |
targetFormat | string | 是 | jpeg png webp avif gif tiff |
quality | string | 否 | 整数1–100,默认值 85 |
resizeWidth | string | 否 | 目标宽度(像素) |
resizeHeight | string | 否 | 目标高度(像素) |
maintainAspectRatio | "true" | 否 | 默认值 false |
removeMetadata | "true" | 否 | 删除EXIF,默认值 false |
allowUpscaling | "true" | 否 | 默认情况下允许放大 false |
成功响应:
- 状态:
200 - 正文:原始图像字节
- 标题:
- Content-Type --格式MIME类型 - Content-Disposition: attachment; filename="." - X-Output-Size --输出大小(字节(字符串)) - X-Output-Filename --经过净化的输出文件名
错误响应形状:
interface ApiErrorResponse {
error: string; // machine-readable error code
message: string; // human-readable description
field?: string; // which form field caused the error, if applicable
}错误代码:
| HTTP状态 | 错误代码 | 原因 |
|---|---|---|
400 | MISSING_FILE | 请求中没有文件 |
400 | MISSING_TARGET_FORMAT | targetFormat 未提供 |
400 | UNSUPPORTED_TARGET_FORMAT | 仅输入请求的输出格式 |
400 | INVALID_QUALITY | 质量不是1到100之间的整数 |
400 | INVALID_DIMENSION | 宽度或高度不是正整数 |
413 | FILE_TOO_LARGE | 文件超过50 MB |
415 | UNSUPPORTED_FORMAT | 魔术字节检查失败(声明MIME≠实际内容) |
422 | IMAGE_TOO_LARGE | 像素尺寸超过2500万像素 |
422 | LIVE_PHOTO_NOT_SUPPORTED | 检测到HEIC实时照片 |
500 | CONVERSION_FAILED | 未处理的严重错误 |
卷曲示例:
curl -s -X POST http://localhost:3000/api/convert \
-F "file=@photo.jpg" \
-F "targetFormat=webp" \
-F "quality=85" \
-o output.webp
# Check output size from response header
curl -sI -X POST http://localhost:3000/api/convert \
-F "file=@photo.jpg" \
-F "targetFormat=webp" \
| grep X-Output-Size______________________________________________________________________
格式支持
输入格式
| 格式 | MIME类型 | 注释 |
|---|---|---|
| JPEG | image/jpeg | |
| PNG | image/png | 支持透明度 |
| WebP | image/webp | 支持动画WebP |
| AVIF | image/avif | |
| GIF | image/gif | 支持GIF动画 |
| TIFF | image/tiff | |
| HEIC/HEIF | image/heic, image/heif, image/heic-sequence, image/heif-sequence | 通过预解码 heic-convert。每个文件增加约200-500毫秒。 |
| SVG | image/svg+xml | 通过librsvg(夏普内置)进行光栅化。输出大小=SVG声明的尺寸,除非被覆盖 --width/--height. |
| BMP | image/bmp | 只读。夏普没有BMP输出编码器。 |
输出格式
| 格式 | 质量标志 | 典型用途 |
|---|---|---|
jpeg | 是 | 照片,无透明度要求 |
png | 压缩衍生 | 无损、透明、截图 |
webp | 是 | 网络图像——大多数内容的最佳尺寸/质量权衡 |
avif | 是 | 文件最小,每字节质量最高。编码速度较慢。 |
gif | 否 | 动画图像 |
tiff | 是 | 打印工作流、存档存储 |
格式转换说明
透明度→ JPEG. JPEG没有alpha通道。没有 --background,透明像素变为黑色。总是通过 --background "#ffffff" (或您的目标填充颜色)将具有透明度的PNG/WebP/AVIF转换为JPEG时。
将GIF动画转换为静态格式。 将动态GIF转换为JPEG或PNG只会捕获第一帧。要保留动画,请转换为WebP(支持动画)。
SVG光栅化。 Sharp使用librsvg来光栅化SVG。默认光栅大小是SVG声明的 width/height 属性。通过 --width 或 --height 以控制输出像素尺寸。
HEIC解码。 夏普无法直接解码苹果的HEIC格式。 img-convert 使用 heic-convert 库首先将HEIC解码为PNG缓冲区,然后将其传递给Sharp。这增加了延迟,并且每个文件都是单线程的。
PNG质量。 PNG是无损的,所以 --quality 控制夏普 compressionLevel (来源于 Math.round((100 - quality) / 11)).更高的质量=更低的压缩=更快的编码+更大的文件。无论哪种方式,图像数据都是相同的。
______________________________________________________________________
处理选项
管道按此固定顺序运行。每一步都是选择加入和独立的。
Input
→ HEIC pre-decode (if source is HEIC)
→ Decompression bomb guard (rejects > 25 megapixels)
→ Metadata handling (strip or preserve)
→ Auto-rotate / Rotate
→ Flip / Flop
→ Crop
→ Resize
→ Grayscale
→ Normalize
→ Blur
→ Sharpen
→ Trim
→ Background flatten (before JPEG encoding)
→ Format encode
→ Output| 选项 | CLI | API字段 | 注释 |
|---|---|---|---|
| 质量 | --quality | quality | 1–100.适用于JPEG、WebP、AVIF、TIFF。 |
| 调整大小 | --width / --height | width / height | 符合尺寸要求。除非满足以下条件,否则不得升级 allowUpscaling: true. |
| 元数据 | --no-metadata | removeMetadata | 剥离EXIF/XMP/IPTC。ICC档案始终保留。 |
| 作物 | -- | crop: { left, top, width, height } | 在调整大小之前运行。原始图像空间中的像素坐标。 |
| 自动旋转 | -- | autoRotate | 应用EXIF方向并剥离标签。 |
| 旋转 | --rotate | rotate | 任何角度。空的角落充满了 background 颜色。 |
| 翻转 | --flip | flip | 左-右后视镜。 |
| Flop | --flop | flop | 顶部-底部镜子。 |
背景 --background | background | CSS颜色字符串。用于旋转角点和JPEG展平。 | |
| 灰度 | --grayscale | grayscale | 去饱和为单个亮度通道。 |
| 模糊 | --blur | blur | 高斯模糊,西格玛0.3-1000。 |
| 锐化 | --sharpen | sharpen | 使用Sharp默认值取消锐化蒙版。 |
| 正常化 | --normalize | normalize | 将直方图拉伸到全范围。 |
| 修剪 | --trim | trim | 删除均匀颜色边缘像素。 |
______________________________________________________________________
建筑
img-convert/
├── cli/
│ ├── index.ts # Commander CLI — convert, info, batch, mcp subcommands
│ ├── helpers.ts # Pure functions: path building, format detection, option mapping
│ └── mcp.ts # MCP server — registers tools, handles stdio transport
├── lib/
│ ├── imageProcessor.ts # Core Sharp pipeline — single source of truth for all interfaces
│ ├── api.ts # Programmatic Node.js API: convert(), getInfo(), batch()
│ ├── heicDecoder.ts # HEIC → PNG buffer pre-decode step
│ └── processingQueue.ts # Concurrency semaphore for the REST endpoint
├── types/
│ ├── index.ts # Shared types: ImageFormat, ConvertOptions, API types
│ └── client.ts # Browser-safe re-export + MIME → ImageFormat detection helper
├── app/
│ ├── api/convert/
│ │ └── route.ts # Next.js Route Handler: POST /api/convert
│ ├── layout.tsx
│ └── page.tsx
├── components/
│ ├── ImageConverter.tsx # Top-level stateful client component
│ ├── DropZone.tsx # Drag-and-drop file input
│ ├── ConvertOptions.tsx # Format selector, quality slider, resize controls
│ ├── ConvertResult.tsx # Download link + size comparison
│ ├── BatchQueue.tsx # Multi-file batch UI with per-item status
│ └── ImagePreview.tsx # Source image preview
├── dist/
│ └── cli/ # Compiled CLI output (CommonJS, aliases resolved by tsc-alias)
└── __tests__/
├── imageProcessor.test.ts
├── cli.test.ts
├── route.test.ts
├── batchQueue.test.ts
└── ...单管路,四个接口
这 processImage() 函数 lib/imageProcessor.ts 是夏普的标准管道。它被称为:
- 命令行界面 (
cli/index.ts)--读取文件或stdin,写入磁盘 - Node.js API (
lib/api.ts)--用输入分辨率和结构化结果对象包装processImage - REST API (
app/api/convert/route.ts)--验证多部分表单字段并返回二进制HTTP响应 - MCP服务器 (
cli/mcp.ts)--将工具调用参数转换为processImage选项,写入文件,返回JSON
所有四个接口对相同的输入产生相同的输出。任何接口都没有单独的代码路径。
并发模型
| 接口 | 机制 | 默认限制 |
|---|---|---|
| CLI | p-limit 每次调用 | --concurrency 4 |
| Node.js API | p-limit 每 batch() 呼叫 | options.concurrency ?? 4 |
| REST API | async-sema 所有请求的信号量 | processingQueue (1个插槽) |
| MCP批次 | p-limit 每 batch_convert 呼叫 | concurrency ?? 4 |
REST端点的信号量是有意保守的(单槽),以防止并发浏览器请求下的内存耗尽。CLI和API并发是用户控制的。
构建系统
| 配置 | 目的 |
|---|---|
tsconfig.json | Next.js应用程序-- moduleResolution: "bundler", noEmit: true |
tsconfig.cli.json | CLI+API- moduleResolution: "node", module: "CommonJS",发射到 dist/cli/ |
tsc-alias | 后处理编译JS进行重写 @/* 相对路径的路径别名 |
两个tsconfig方法是有意的:Next.js bundler处理模块解析的方式与Node.js require()不同。共享一个配置需要在两个方向上做出妥协。
______________________________________________________________________
发展
设置
git clone https://github.com/dutchbase/img-convert
cd img-convert
npm install命令
npm run dev # Start Next.js dev server at http://localhost:3000
npm run build # Production Next.js build + type-check
npm run build:cli # Compile CLI + API to dist/cli/ (required before running img-convert locally)
npm run lint # ESLint
npm test # Jest unit tests
npm run test:coverage # Jest with coverage report
npm run test:e2e # Playwright end-to-end tests
npm run test:all # Unit + E2E添加新的输出格式
- 将格式密钥添加到
ImageFormat工会types/index.ts - 将条目添加到
FORMAT_LABELS,FORMAT_MIME,FORMAT_EXTENSIONS - 将格式添加到
OUTPUT_FORMATS(或INPUT_ONLY_FORMATS如果Sharp无法对其进行编码) - 将案例添加到
applyFormat()在lib/imageProcessor.ts - 将MIME类型添加到
detectFormat()在lib/imageProcessor.ts - 将MIME类型添加到
detectFormatFromMime()在types/client.ts - 将扩展名添加到
EXT_TO_FORMAT在cli/helpers.ts - 将MIME类型添加到
accept属性于components/DropZone.tsx
添加新的处理选项
- 将字段添加到
ConvertOptions在types/index.ts - 添加
ConvertApiOptions在types/index.ts如果它应该是公共API的一部分 - 申请
lib/imageProcessor.ts在正确的管道位置 - 将CLI标志添加到
program在cli/index.ts - 电线穿过
buildConvertOptions()在cli/helpers.ts - 暴露在MCP中
convert_image中的工具输入模式cli/mcp.ts - 在中添加UI控件
components/ConvertOptions.tsx如果它应该在web UI中
测试结构
现场测试 __tests__/ 和杰斯特一起跑+ ts-jest。测试环境是按中的每个文件配置的 jest.config.ts:
- 节点环境:
imageProcessor.test.ts,route.test.ts,cli.test.ts,heicDecoder.test.ts,animatedGif.test.ts - JSDOM环境:
imageConverter.test.tsx,dropZone.test.ts,batchQueue.test.ts,processingQueue.test.ts
Sharp操作在测试中使用实际的Sharp库(无模拟),并在 __tests__/fixtures/.
安全注意事项
REST端点应用了多个防御层:
- 文件大小限制 --读取正文前使用50 MB硬盖
- MIME分配列表 --源格式必须是可识别的图像类型
- 魔术字节验证 —
file-type检查实际文件内容,而不仅仅是浏览器提供的MIME标头 - 像素尺寸检查 --在分配解码缓冲区之前,拒绝超过2500万像素的图像
- 急剧减压极限 —
limitInputPixels: 25_000_000传递给每个夏普制造商 - 文件名清理 —
Content-Disposition文件名中除以下字符外的所有字符都被删除[a-zA-Z0-9._-]
CI/CD
.github/workflows/ci.yml 在每次推送和公关中运行:
- 节点18、20和22的测试
- 跑
npm test,npm run build,npm run build:cli - 验证编译后的CLI二进制文件是否正确执行
.github/workflows/release.yml 触发器打开 v* 标签:
- 运行完整的测试套件
- 构建CLI
- 发布到npm 来源证明
# Publish a new release
npm version patch # or minor / major
git push --follow-tags
# GitHub Actions handles the rest______________________________________________________________________
贡献
欢迎拉取请求。
- 首先打开一个问题,进行非琐碎的更改。
- 保持
processImage()作为单个管道-不要在CLI、API、REST和MCP之间分叉处理逻辑。 - 维护stderr/ststdout合约:stdout上的数据,stderr上的进度。
--json应始终生成可解析的输出。 - 添加新功能和错误修复的测试。测试套件应保持绿色
npm test. - 跑
npm test && npm run build:cli在提交之前。
______________________________________________________________________
许可证
麻省理工学院
