fullpsd

FullPsd 开放平台文档

完整兼容的 PSD 解析、内容替换与变形调整,全部通过 HTTP 提供。这份文档里的每个端点、字段、环境变量都直接对应服务端源码;示例响应大多是真跑一次抓下来的。

概览

FullPsd 的服务端做三件事:

  • 读 —— 把 PSD/PSB 解析成完整语义模型(/api/doc/inspect、/api/doc/validate、/api/psd/info),未修改时 serialize(parse(file)) 与原文件逐字节相同。
  • 改 —— 图层属性、文字、像素、蒙版、矢量、图层样式、调整 / 填充图层、智能对象、画板(core op),以及滤镜、破坏性调整、内容识别填充、画布大小 / 裁剪 / 图像大小 / 旋转 / 翻转、移动 / 复制图层、新建透视 / 操控变形、新建画板、换智能对象源图、网格与变形写回(服务端 op)—— 编辑器里能做的,基本都能用同一个入口 /api/doc/edit 做,出文件前必过守门。全部 op 与参数见 ops 参考 或 GET /api/catalog。
  • 只改一小块就别传整份文件 —— /api/doc/edit/incremental 只往返你要改的那几段字节(改文字 / 改属性 / 改名 / 变形 / 放置描述符),产物与整文件模式逐字节相同。8.4 MB 的 PSD 改一次变形只走 ~5 KB。
  • 出 —— 新的 PSD/PSB,或 PNG / JPEG / TIFF / WebP 位图,RGB 或 CMYK(/api/render、/api/doc/render、/api/doc/composite),可以只出某一块画板,也可以每块画板各出一张打成 zip。
  • 给 AI 用 —— 标准 MCP 服务(/mcp,Streamable HTTP):Claude / Cursor / 任何 MCP 客户端配上 URL 与 key,就能让 agent 打开 PSD、看图层、批量编辑、看预览、导出。

REST 接口都是无状态的:服务端不落盘、不保留你上传的 PSD(唯一会落盘的是你主动传进字体库的字体,按 API key 隔离、可随时删)。变形写回还有一条片段协议,连 PSD 都不用传。唯一的例外是 MCP:agent 需要「打开一次、连续改多步」,所以 MCP 有一个内存里的临时工作区(闲置约 1 小时自动清理,不落盘)。

端点做什么返回
GET /health存活、渲染后端、可用 ICC、鉴权状态JSON
POST /api/session签发编辑器会话(token + 引擎密钥)JSON
GET /api/profiles服务端可用的 ICC 配置文件JSON
POST /api/psd/info图层 / 智能对象结构 + 兼容性提示JSON
POST /api/renderPSD + 替换图 → 渲染成品位图 / PSD
POST /api/doc/inspect完整文档模型摘要(不解像素)JSON
POST /api/doc/layer-preview单层像素预览PNG
POST /api/doc/composite文件里存好的合成图PNG / JPEG
POST /api/doc/patch批量改图层属性PSD
POST /api/doc/validate体检:lint + 字节级往返自检JSON
POST /api/doc/edit统一编辑端点(文件模式 / 片段模式)PSD / 位图 / JSON
POST /api/doc/edit/plandry-run,只判定 ops 可行性JSON
POST /api/doc/edit/incremental增量编辑:只传要改的那几段字节,不上传整份 PSDJSON
POST /api/so/resolve放置块字节 → 网格 / 变形结构JSON
POST /api/so/writeback编辑后的网格 / 变形 → 新放置块字节JSON
POST /api/so/filters智能滤镜列表 / 改写(/filters/writeback)JSON
POST /api/doc/new新建空白 PSD(尺寸 / 预设 / 模式 / 背景)PSD
POST /api/doc/render按图层重新合成(可只出一块画板 / 一个区域)位图
POST /api/doc/artboards画板清单;/export-artboards 每块画板一张图打成 zipJSON / zip
GET /api/catalog能力目录:全部 op 的参数 schema、滤镜、调整、预设;/openapi.json 同源生成JSON
POST /mcpMCP(Model Context Protocol)端点,给 AI agent 用JSON-RPC

本页里凡标 真实响应 的示例,都是对同一份 1200×1200 的 tshirt.psd(3 个图层:背景 / 智能对象 img / 文字层)实际请求后抓取、只做了裁剪;标 结构示意 的是按源码字段拼出的形状,字段名与源码一致,具体数值仅供示意。

快速开始

1 · 拿一个 key

对外接口一律要凭证。API key 由部署方配置,两种方式二选一(见自托管):

部署方执行 · 环境变量

# 格式: key:名字:每分钟次数(rpm 可省略,默认 60),逗号分隔多个
export FULLPSD_API_KEYS="7f1c…随机长串:客户A:120"
npm start

或写 server/api-keys.json(已 gitignore,模板见 server/api-keys.example.json),可以按 key 配 rpm / concurrency / maxUploadMB。没有配置任何 key 时,对外接口一律 401;本机调试可以用 FULLPSD_AUTH=off 完全关掉鉴权。

2 · 第一个请求

先确认服务活着(/health 不需要凭证):

bash

curl -s https://your-host/health

然后看看一份 PSD 里有什么 —— 这是绝大多数集成的第一步。下面所有示例里的 $FULLPSD_KEY 只是你本地 shell 里存放 key 的变量(export FULLPSD_KEY=…),不是服务端认的环境变量:

bash

curl -s -X POST https://your-host/api/doc/inspect \
  -H "X-Api-Key: $FULLPSD_KEY" \
  -F psd=@tshirt.psd

3 · 改点东西

拿到 flat[].index 之后就能编辑了。建议先 /api/doc/edit/plan 跑一次 dry-run,再提交 /api/doc/edit:

bash · 换智能对象源图 + 改文字,一次做完

curl -s -X POST https://your-host/api/doc/edit \
  -H "X-Api-Key: $FULLPSD_KEY" \
  -F psd=@tshirt.psd \
  -F image=@art.png \
  -F 'ops=[
    {"op":"replaceSmartObject","target":0,
     "image":{"file":"image","index":0},"fit":{"mode":"cover"}},
    {"op":"setText","index":2,"text":"新的文字"}
  ]' \
  -F 'output={"format":"psd"}' \
  -D headers.txt -o edited.psd

成功时响应体就是新 PSD 的字节,元数据在 X-Doc-Meta 响应头里;失败时是 JSON,不会给你一个半成品文件(见一致性保证)。

鉴权与会话

除 GET /health 与 POST /api/session 外,所有 /api/* 都要凭证。有两类主体:

主体怎么带用途默认 rpm
API keyX-Api-Key: <key>对外集成按档位:基础 60 · 高级 240(可按 key 配)
编辑器会话Authorization: Bearer <token>浏览器编辑器(页面本身不带密钥)按会话档位(默认高级 240;FULLPSD_SESSION_RPM 可覆盖)

API key

  • 来源一:环境变量 FULLPSD_API_KEYS="key:名字:每分钟次数:档位,key2:名字2",rpm / 档位省略时按档位默认(没写档位 = 基础档)。
  • 来源二:JSON 文件 FULLPSD_API_KEYS_FILE(默认 server/api-keys.json,示例见 server/api-keys.example.json),条目形如 { "key":"…", "name":"客户A", "plan":"advanced", "features":["-op.contentAwareFill"], "rates":{"api.doc.edit":30}, "rpm":60, "concurrency":2, "maxUploadMB":200 }。plan / features / rates 见 档位与权限;rpm / concurrency / maxUploadMB 不写就用档位的 limits。
  • 比对用 crypto.timingSafeEqual(先比长度,再比字节)。key 短于 16 字符只会在服务端日志里告警,不会被拒绝 —— 请用随机长串。
  • maxUploadMB 会在 /api/render、/api/doc/* 里对上传的 PSD 单独设限,超出 413。
  • concurrency 是这把 key 自己的重接口并发上限(不配则不限,只受全局闸约束),与全局 FULLPSD_MAX_CONCURRENT 叠加:任一满都 429,响应体里的 scope 标明撞的是哪道闸。见重接口并发闸。

编辑器会话

POST /api/session 签发一个 HMAC 签名的无状态 token(默认 12 小时,FULLPSD_SESSION_TTL_HOURS)。签名密钥取 FULLPSD_SESSION_SECRET,不设则每次启动随机生成 —— 重启后旧会话全部失效。

私有部署可设 FULLPSD_EDITOR_KEY=1,这样连签发会话都要求 X-Api-Key(且必须是 API key 主体,不能拿会话再换会话)。

响应 真实响应

{
  "token": "eyJzaWQiOiIwMmYwNjlmOTZlYzA5YzNlNTg4MDkyNWZlN2FlNjdjMiIsImV4cCI6MTc4ODgzNTU1MDk1NCwic2NvcGUiOiJlZGl0b3IifQ.ethWUEZ6erMTWjYPRV_T8D7GzOxQf1an9k7o7eY60vs",
  "sid": "02f069f96ec09c3e5880925fe7ae67c2",
  "expiresAt": 1788835550954,
  "engineKey": "4SKcKsW8j2nsjpynp48bRclcreMS46TlQnzhjeE6S84"
}

engineKey 只用于解密 GET /api/engine 下发的前端变形引擎,与业务 API 无关;响应带 Cache-Control: no-store。

哪些端点认哪种凭证

端点API key会话不带凭证
/health、/api/session——✅ 放行
/api/profiles、/api/psd/info、/api/render✅✅401
/api/doc/*、/api/so/*✅✅401
/api/engine403✅401

FULLPSD_AUTH=off 会让所有请求以匿名主体放行(rpm 视同无限),只应在本机调试用。

CORS 与安全响应头

  • 默认只允许同源。FULLPSD_CORS_ORIGINS="https://a.com,https://b.com" 放行指定来源,值为 * 时放行任意 Origin(回显该来源,不是字面 *)。
  • 放行时附带:Access-Control-Allow-Headers: Content-Type, X-Api-Key, Authorization、Allow-Methods: GET,POST,OPTIONS、Max-Age: 600,并 Expose-Headers: X-Render-Meta, X-Doc-Meta, Content-Disposition —— 浏览器要读元数据头就靠这一条。
  • OPTIONS 一律直接 204。
  • 每个响应都带 X-Content-Type-Options: nosniff 与 Referrer-Policy: no-referrer;服务不返回 X-Powered-By。

限流与并发

令牌桶(每主体)

每个主体(key:<名字> 或 sess:<sid>)一个桶:容量 = rpm,按 rpm/分钟 匀速回填,每个请求扣 1。桶空即 429,并带 Retry-After: 10。10 分钟不活动的桶会被回收。

rpm=2 的 key 连打 4 次 真实响应

200 200 429 429

HTTP/1.1 429 Too Many Requests
Retry-After: 10
{"error":"超过速率限制(2/分钟)"}

重接口并发闸

要真解像素 / 真渲染的接口另外挂两道叠加的并发闸,任一满都立即 429,不排队(排队只会拖垮内存):

  • 每凭证闸 —— API key 的 concurrency(api-keys.json 里按 key 配;不配则这把 key 不受此闸约束)。编辑器会话不受此闸约束。
  • 全局闸 —— FULLPSD_MAX_CONCURRENT(默认 2),整机口径。

429 的响应体带 scope 区分撞的是哪道闸:"key" = 你自己把配额打满了(减少在途请求即可),"global" = 整机忙(要么等,要么加机器 / 调大上限)。同时带 limit(该闸的上限)与 Retry-After。

429 真实响应

HTTP/1.1 429 Too Many Requests
Retry-After: 2
{"error":"该凭证的并发已满(1),请稍后重试","scope":"key","limit":1,"inflight":1}

HTTP/1.1 429 Too Many Requests
Retry-After: 2
{"error":"服务端渲染并发已满(2),请稍后重试","scope":"global","limit":2,"inflight":2}
过并发闸不过并发闸
/api/render、/api/doc/layer-preview、/api/doc/composite、/api/doc/edit(仅 multipart 文件模式) /api/doc/inspect、/api/doc/patch、/api/doc/validate、/api/doc/edit/plan、/api/doc/edit(JSON 片段模式)、/api/doc/edit/incremental、/api/so/*、/api/psd/info、/api/profiles

体积、像素与耗时

限制默认值环境变量超出时
单文件上传上限200 MBMAX_UPLOAD_MB413
单请求文件数32(image 字段最多 16、icc 1)—400 / 413
按 key 的上传上限不限key 的 maxUploadMB413
解像素的单图上限4000 万像素FULLPSD_DOC_MAX_PIXELS413
/api/doc/* 阶段耗时闸20 000 msFULLPSD_DOC_TIMEOUT_MS504
/api/doc/edit 阶段耗时闸max(DOC_TIMEOUT_MS, 60000)FULLPSD_EDIT_TIMEOUT_MS504
单次 ops 条数512FULLPSD_DOC_MAX_OPS400
片段模式 JSON 体积32 mbFULLPSD_EDIT_JSON_LIMIT / FULLPSD_SO_JSON_LIMIT413
片段一次处理的智能对象数64—400

耗时闸是在阶段边界上掐的(解析 / 执行 ops / 写回 / 守门之间),因为解析与写回本身是同步的,没法在中途打断。

档位与权限

每个凭证属于一个档位(plan):决定能用哪些功能、总频率(rpm)、并发、上传上限,以及按功能的频率限制。内置两档:基础(basic,API key 默认)与高级(advanced,编辑器会话默认)—— 目前高级档多出来的是自传字体与变形(网格变形 warp / 透视变形 / 操控变形)—— 以后新增的高级能力也挂在这里;哪些功能归哪档全在配置里,挪一个功能到另一档只改配置、不改代码。

功能 id

服务端每个可控的动作都有一个点分的功能 id,一次请求涉及的 id 全部在档位里才放行:

形式例子管什么
api.<路径>api.render · api.doc.edit · api.doc.export-artboardsREST 端点(表见下)
op.<op 名>op.applyFilter · op.resizeImage · op.renameLayer/api/doc/edit(含 plan / incremental)与 MCP 里的每条编辑 op;别名(renameLayer)要求别名与规范名(setLayerProps)都在档位里
mcp · mcp.tool.<名>mcp.tool.render_previewMCP 端点与每个工具。tools/list 只列当前档位能用的工具
能力fonts.library · fonts.request · fonts.map跨端点的高级能力(同一个能力可能出现在 REST 字段、MCP 参数、专门端点里)

变形怎么算:「改」或「新建」变形才要高级档 —— createDeform(按 kind:deform.perspective / deform.puppet)、deform op 与片段写回(/api/so/writeback、/api/doc/edit 的 JSON 片段模式、增量编辑里的 deform;points 网格 → deform.warp,deform.kind 透视 / 操控 → 对应 id)、换图时顺带改网格(replaceSmartObject / /api/render 的 mesh → deform.warp)。只读解析(/api/so/resolve、inspect)和换图渲染进已有网格 / 变形(不带 mesh 的 replaceSmartObject、/api/render)仍是基础档 —— 样机换图不受影响。

例:基础档的 key 打 /api/doc/edit 并带了 font 字段 → 涉及 api.doc.edit、每条 op.*、fonts.request,最后一个不在基础档 → 403。

档位配置 server/plans.json

不放这个文件就用内置默认;放了就整体替换(示例 server/plans.example.json,路径可用 FULLPSD_PLANS_FILE 改)。改了自动重载(约 3 秒内生效,不用重启);文件写坏了保留上一份好配置,/health 的 plans.error 会写原因。

plans.json(示意;真实文件是纯 JSON,不能带 // 注释,可直接复制 plans.example.json)

{
  "default": "basic",            // API key 没写 plan 时
  "session": "advanced",         // 编辑器会话
  "anon": "advanced",            // FULLPSD_AUTH=off(本机调试)
  "plans": {
    "basic": {
      "title": "基础",
      "features": ["api.*", "op.*", "mcp", "mcp.tool.*", "!op.contentAwareFill"],   // 把内容识别填充挪进高级档
      "limits": { "rpm": 60 },
      "rates": { "api.render": { "perMinute": 20, "perDay": 2000 }, "op.*": 600 }
    },
    "advanced": {
      "title": "高级",
      "inherits": "basic",                                     // 先展开 basic 的规则,再接自己的
      "features": ["*"],                                       // 高级 = 全部(含以后新增的高级能力、被基础档去掉的 op)
      "limits": { "rpm": 240, "concurrency": 4, "maxUploadMB": 500 },
      "rates": { "api.render": 120 }
    }
  }
}
  • features:按顺序匹配、后面的覆盖前面的;"x" / "+x" = 含,"!x" / "-x" = 不含;支持通配 *。
  • 默认归属的约定:内置基础档只放行 api.* / op.* / mcp / mcp.tool.* 这几族,高级档是 *。所以新增的端点 / op / 工具默认进基础档,新增的能力类功能(像 fonts.* 这样的高级能力)默认只进高级档;要把一个基础功能升为高级,只需在基础档加一条 "!功能 id"。
  • limits:rpm(总请求 / 分钟,令牌桶)· concurrency(重接口并发)· maxUploadMB。
  • rates(按功能的频率):{ "<功能 id 或通配>": 次数/分钟 | { "perMinute", "perHour", "perDay", "perMonth" } }。精确 id 各自计数;带 * 的规则是「匹配到的功能共用一个桶」("op.*": 600 = 所有 op 合计每分钟 600 条)。op 按条计数(一次请求 5 条 op 记 5 次);dry-run(/plan、MCP dry_run)只查不记。窗口按自然分钟 / 小时 / 天对齐;perMonth 是按自然月的配额(UTC 每月 1 日 0 点重置,例如 "api.*": { "perMonth": 100000 } = 所有端点合计每月 10 万次)。计数在进程内存里、每分钟落一次盘(重启不丢;多实例部署各算各的)。
  • 档位可以不止两个(如再加 enterprise 继承 advanced);档位 id 只能是字母数字 _-。

按 key 单独调

api-keys.json 里每把 key 可以在档位之上加减功能、改频率、改限额(key 写的优先):

api-keys.json

[{ "key": "…", "name": "客户C", "plan": "basic",
   "features": ["+fonts.map", "-op.contentAwareFill", "-mcp.tool.export_artboards"],
   "rates": { "api.doc.edit": { "perMinute": 30, "perDay": 5000 } },
   "rpm": 120, "concurrency": 2 }]

查询

  • GET /api/plans(公开)—— 各档位的规则、限额、频率,以及全部功能 id 与它们在哪些档位里(做价格页 / 对比表用)。
  • GET /api/account(带凭证)—— 我的生效档位、可用 / 不可用功能、限额、每条频率规则的已用 / 剩余 / 重置秒数。MCP 里对应工具 get_account。
  • GET /api/catalog 的每个 op 带 feature;/openapi.json 的每个端点带 x-feature / x-capabilities。
被拒时 真实响应

HTTP/1.1 403 Forbidden
{"error":"当前档位「基础」不含「自传字体 · 字体库」(fonts.library);「高级」档可用",
 "code":"feature_not_in_plan","feature":"fonts.library","plan":"basic","availableIn":["advanced"]}

HTTP/1.1 403 Forbidden
{"error":"该凭证未开通「applyFilter · 对普通像素图层应用滤镜」(op.applyFilter,按 key 单独关闭;档位「基础」本身包含)",
 "code":"feature_disabled_for_key","feature":"op.applyFilter","plan":"basic","availableIn":["basic","advanced"]}

HTTP/1.1 429 Too Many Requests
Retry-After: 24
{"error":"「图层树」超过频率限制(api.doc.inspect 每分钟 2 次),24 秒后再试",
 "code":"feature_rate_limited","feature":"api.doc.inspect","rule":"api.doc.inspect","window":"perMinute","limit":2,"retryAfter":24}

MCP 工具被拒时返回 isError: true 的工具结果,文本第二行是同样的 JSON(code / feature / availableIn …)。字体库这类「用不了就不用」的能力在编辑时静默跳过(不报错,只是不匹配库里的字体);显式要求的(带 font 字段、写 fontMap)才 403。

内置档位

档位 id名称继承features 规则limits说明
basic
API key 默认
基础—api.* op.* mcp mcp.tool.*rpm 60全部端点 / 编辑 op / MCP 工具;不含自传字体、变形等高级能力
advanced
编辑器会话
高级basic*rpm 240基础档全部能力 + 全部高级能力(目前是自传字体与变形:网格 warp / 透视 / 操控),更高的频率

能力开关(跨端点)

功能 id名称内置档位管什么
fonts.library自传字体 · 字体库高级把字体存进这把 key 的字体库(/api/fonts、MCP upload_font),编辑时服务端按 PostScript 名自动使用
fonts.request自传字体 · 单次上传高级/api/doc/edit 的 multipart 里带 font 字段,只在这次请求里用
fonts.map自传字体 · 字体映射高级output.fontMap / MCP font_map:指定 PSD 里某个字体名用哪款字体
deform.warp变形 · 网格变形(warp)高级改智能对象的 Bezier 网格(PS「编辑 › 变换 › 变形」,含拆分网格 quiltWarp):deform op 的 points、replaceSmartObject / /api/render 的 mesh、片段写回的 points
deform.perspective变形 · 透视变形高级新建或修改透视变形:createDeform kind=perspectiveWarp、deform op / 片段写回里 kind=perspectiveWarp 的变形
deform.puppet变形 · 操控变形高级新建或修改操控变形:createDeform kind=puppetWarp、deform op / 片段写回里 kind=puppetWarp 的变形

端点 → 功能 id

端点功能 id另需内置档位
GET /api/profilesapi.profiles—基础 / 高级
POST /api/psd/infoapi.psd.info—基础 / 高级
POST /api/renderapi.render—基础 / 高级
POST /api/doc/newapi.doc.new—基础 / 高级
POST /api/doc/inspectapi.doc.inspect—基础 / 高级
POST /api/doc/layer-previewapi.doc.layer-preview—基础 / 高级
POST /api/doc/compositeapi.doc.composite—基础 / 高级
POST /api/doc/renderapi.doc.render—基础 / 高级
POST /api/doc/artboardsapi.doc.artboards—基础 / 高级
POST /api/doc/export-artboardsapi.doc.export-artboards—基础 / 高级
POST /api/doc/patchapi.doc.patch—基础 / 高级
POST /api/doc/validateapi.doc.validate—基础 / 高级
POST /api/doc/editapi.doc.edit—基础 / 高级
POST /api/doc/edit/planapi.doc.edit.plan—基础 / 高级
POST /api/doc/edit/incrementalapi.doc.edit.incremental—基础 / 高级
POST /api/so/resolveapi.so.resolve—基础 / 高级
POST /api/so/writebackapi.so.writeback—基础 / 高级
POST /api/so/filtersapi.so.filters—基础 / 高级
POST /api/so/filters/writebackapi.so.filters.writeback—基础 / 高级
GET /api/fontsapi.fontsfonts.library高级
POST /api/fontsapi.fontsfonts.library高级
DELETE /api/fonts/:idapi.fontsfonts.library高级
GET /api/sampleapi.sample—基础 / 高级
POST /mcpmcp—基础 / 高级
POST /mcp/k/:keymcp—基础 / 高级
POST /mcp/uploadmcp—基础 / 高级

编辑 op 的功能 id 是 op.<op 名>(共 78 个,见 ops 参考;内置档位全部包含),MCP 工具是 mcp.tool.<工具名>(见上方工具表)。完整清单以部署的 GET /api/plans 为准。

错误码总表

所有错误响应都是 JSON,只给一句话,不带堆栈(FULLPSD_DEV=1 时才额外附 stack)。

基本形状

{ "error": "缺少 psd 文件字段(multipart 字段名 psd)" }

/api/doc/edit、/api/doc/edit/plan 与 /api/doc/edit/incremental 会在此基础上附加定位字段(存在才出现):opIndex、opName、stage、code、offset、ops、guard、applied、need(增量模式:还缺哪几个片段)、fragmentId(增量模式:是哪个片段的校验和不符)。

状态码什么时候出现响应体
400输入不合法:缺 psd 字段、ops 不是 JSON / 空数组 / 未知 op、字段类型错、图层下标越界、混合模式键不在白名单、target 找不到或不是智能对象、图片引用对不上、edit 与图层类型不匹配、PSD 解析失败、format 不支持{ error, opIndex?, opName?, code? }
401没带 X-Api-Key / Authorization: Bearer,或 key 无效、会话过期{ error }
403凭证类型无权访问该接口(例如拿 API key 打 /api/engine);或档位不含这个功能(code: feature_not_in_plan)/ 这把 key 单独关了它(feature_disabled_for_key){ error, code?, feature?, plan?, availableIn? }
413超过 MAX_UPLOAD_MB 或该 key 的 maxUploadMB;画布 / 图层 / 上传图片超过 FULLPSD_DOC_MAX_PIXELS{ error }
415仅 /api/doc/edit:Content-Type 既不是 multipart/form-data 也不是 application/json;/api/doc/edit/plan 只收 multipart,/api/doc/edit/incremental 只收 JSON{ error }
422产物没过守门(lintPsd 不通过 / 无法重新解析 / 自身不再字节往返),或写回失败没有产物 —— 不返回文件字节。/api/render、/api/doc/edit、/api/doc/patch 同一语义同一状态码:422 表示「我们产出的文件不合格」,与「你的入参错了」(400)分开{ error, stage, code?, offset?, guard, ops }(/patch 是 { error, lint, applied };/api/render 是 { error, stage:"lint", code:"artifact_lint_failed", lint })
429超过该主体的 rpm(带 Retry-After: 10);超过按功能的频率(code: feature_rate_limited,带 rule / window / limit / Retry-After);或撞上并发闸(带 Retry-After: 2 与 scope:key = 该凭证的 concurrency,global = FULLPSD_MAX_CONCURRENT){ error, scope?, limit?, inflight? }
500服务端自身出错。/api/doc/edit 明确区分:能归因于输入的一律 4xx,剩下的才 500 —— 看到 500 请报 bug{ error }
504某个阶段超过耗时闸(FULLPSD_DOC_TIMEOUT_MS / FULLPSD_EDIT_TIMEOUT_MS){ error }

实际抓到的几条

真实错误响应 真实响应

# 400:POST /api/doc/inspect 不带文件
{"error":"缺少 psd 文件字段(multipart 字段名 psd)"}

# 400:未知 op(错误信息里会列出全部可用 op)
{"error":"ops[0] 未知 op「nope」(可用:deform deleteLayer removeImageResource renameLayer
 reorderLayers replaceLayerPixels replaceSmartObject replaceSmartObjectImage setBlendMode
 setClipping setImageResource setLayerProps setMesh setOpacity setText setVisible)"}

# 401:没带凭证
{"error":"需要鉴权:对外接口请带 X-Api-Key;编辑器请先 POST /api/session 取会话"}

# 401:key 不对
{"error":"API key 无效"}

# 403:拿 API key 打 /api/engine
{"error":"当前凭证无权访问该接口"}

# 413:MAX_UPLOAD_MB=1 时上传 8.8 MB 的 PSD 到 /api/doc/inspect
{"error":"上传的文件超过本服务的上限(MAX_UPLOAD_MB=1 MB)"}

# 415:/api/doc/edit 的 Content-Type 不对
{"error":"Content-Type 必须是 multipart/form-data(文件模式:psd + ops)或 application/json(片段模式:{ fragment, edit })"}

# 404:路径不存在
{"error":"Not found"}
中间件层的错也是 JSON:上传超限(multer)、请求体不是合法 JSON、请求体超过接口上限,这些错发生在进入处理器之前,由 app 级错误中间件统一收口 —— 任何路由(/api/render 与 /api/doc/* 一视同仁)都返回 4xx JSON:超限 413、坏 JSON 400,同样是一句话、非 FULLPSD_DEV=1 不带 stack、不含服务端路径。仍建议在反向代理上另设请求体上限,让超大请求在更前面就被挡掉。
中间件层错误 真实响应

# 413:MAX_UPLOAD_MB=1 时上传 2 MB 的 PSD 到 /api/render
{"error":"上传的文件超过本服务的上限(MAX_UPLOAD_MB=1 MB)","code":"LIMIT_FILE_SIZE"}

# 400:请求体不是合法 JSON(/api/session、/api/so/*、/api/doc/edit 片段模式同)
{"error":"请求体不是合法 JSON"}

# 413:JSON 请求体超过该接口的上限
{"error":"请求体超过本接口的上限"}

端点参考

下面每个端点按同一套模板写:用途 → 鉴权 → 请求 → 响应 → 错误 → 限制 → 可复制的示例。每个端点右上角的「在调试台里打开」会跳到 /console 对应的表单。

GET/health在调试台里打开 →

存活探针:渲染后端、可用 ICC、上传上限、鉴权与构建状态。

鉴权
不需要
请求
无参数
并发闸
不占

响应字段

字段类型说明
okboolean恒为 true(能返回就说明活着)
backendstring渲染后端:cpu(纯 JS,默认)或 gl(部署方自行装了 headless-gl)
fitModesarray[{ value, label }],覆盖模式清单
outputsobject{ format:[…], colorMode:[…] }
iccProfilesstring[]server/profiles/ 下可用 ICC 的名字
maxUploadMBnumber本服务的上传上限
authstringoff / api-key / api-key(未配置任何 key,对外接口不可用)
editorstringready / dev / 未构建(npm run build)
capabilitiesobject能力位(集成方据此判断这套部署支持到哪儿):incrementalEdit、fragmentProtocol、docEdit、psb、deformKinds、serverOps(/api/doc/edit 的服务端扩展 op 清单)、newDocument、layerRender、artboardExport、catalog、openapi、mcp:{ endpoint, transport, protocolVersions }
textRenderobject服务端文字重绘:{ available, fonts, dirs, fallback:{ han, latin } };available:false 说明没装上可选依赖 @napi-rs/canvas
workspaceobjectMCP 临时工作区的占用:{ docs, bytes, maxBytes, files, ttlMinutes, maxDocsPerKey, history }
nodestring服务端 Node 版本
响应 真实响应

{
  "ok": true,
  "backend": "cpu",
  "fitModes": [
    { "value": "cover",   "label": "撑满(裁掉溢出)" },
    { "value": "contain", "label": "适应(留白)" },
    { "value": "tile",    "label": "铺满(平铺重复)" },
    { "value": "stretch", "label": "拉伸(非等比填满)" }
  ],
  "outputs": { "format": ["png","jpg","tiff","webp","psd"], "colorMode": ["rgb","cmyk"] },
  "iccProfiles": ["a98","default_cmyk","default_gray","default_rgb","esrgb","FOGRA39L_coated",
                  "gray_to_k","lab","ps_cmyk","ps_gray","ps_rgb","rommrgb","scrgb","sgray","srgb","sRGB"],
  "maxUploadMB": 200,
  "auth": "off",
  "editor": "ready",
  "node": "v22.23.2"
}
bash

curl -s https://your-host/health

POST/api/session在调试台里打开 →

签发一个短期编辑器会话(无状态 HMAC token)。

鉴权
默认不需要;FULLPSD_EDITOR_KEY=1 时要求 X-Api-Key
Content-Type
application/json(请求体可以为空对象,上限 4 kb)
响应头
Cache-Control: no-store

响应字段

字段类型说明
tokenstring放进 Authorization: Bearer。格式 base64url(payload).base64url(HMAC-SHA256)
sidstring会话 id(16 字节 hex)
expiresAtnumber过期时刻(Unix 毫秒)
engineKeystringbase64url 的 32 字节密钥,只用于解密 GET /api/engine 下发的前端引擎

错误

401 —— 仅当 FULLPSD_EDITOR_KEY=1 且没带有效 X-Api-Key:{"error":"此部署的编辑器需要 API key"}。

bash

curl -s -X POST https://your-host/api/session \
  -H "Content-Type: application/json" -d '{}'

GET/api/profiles在调试台里打开 →

列出服务端 server/profiles/ 下可以按名字引用的 ICC 配置文件。

鉴权
API key 或会话
请求
无参数

响应字段

字段类型说明
profiles[].namestring写进 output.icc 的名字(只接受名字,不接受路径)
profiles[].sizeKBnumber文件大小,四舍五入到 KB
notestring关于 CMYK 不带 ICC 时颜色不准的固定说明
响应(裁剪) 真实响应

{
  "profiles": [
    { "name": "a98", "sizeKB": 1 },
    { "name": "default_cmyk", "sizeKB": 183 },
    { "name": "FOGRA39L_coated", "sizeKB": 119 },
    { "name": "ps_cmyk", "sizeKB": 5 },
    { "name": "sRGB", "sizeKB": 3 }
  ],
  "note": "CMYK 输出不传 icc 时走 libvips 内置公式,颜色不准,只能预览。出印刷文件请传目标印刷条件的 ICC(放进 server/profiles/ 按名字引用,或直接上传文件内容)。"
}
bash

curl -s https://your-host/api/profiles -H "X-Api-Key: $FULLPSD_KEY"

POST/api/psd/info在调试台里打开 →

渲染视角的结构信息:图层清单 + 智能对象的网格 / 变形 / 源图,外加合成兼容性提示。

鉴权
API key 或会话
Content-Type
multipart/form-data
并发闸
不占
这个端点服务的是 /api/render:smartObjects[].index 就是 /api/render 里 replacements[].target 认的智能对象序号。要图层树、蒙版、图像资源这类文档结构,请用 /api/doc/inspect(两者的 index 口径不同,别混用)。

请求字段

字段类型必填说明
psdfile是PSD / PSB 文件

响应字段

字段类型说明
width / heightnumber画布尺寸
layers[]array{ name, hidden, bounds, blendMode, isSmartObject, isText, layerType, adjustment, fill }
smartObjects[]array{ index, name, kind, bounds, originalSize, warpStyle, deformFilter?, mesh, sourceImage }
smartObjects[].meshobject{ xn, yn, sliceX, sliceY, patches, regular };regular:false 表示不是规则网格,不能直接渲染
smartObjects[].deformFilterobject仅变形滤镜层才有:{ kind, name }
parseWarningsarray解析告警,为空时字段不出现
compatibilityWarningCount
compatibilityWarnings
number
array
合成兼容性提示(最多 20 条明细)。为空时两个字段都不出现
响应 真实响应

{
  "width": 1200,
  "height": 1200,
  "layers": [
    { "name": "背景", "hidden": false, "bounds": { "top": 0, "left": 0, "bottom": 1200, "right": 1200 },
      "blendMode": "norm", "isSmartObject": false, "isText": false,
      "layerType": "pixel", "adjustment": null, "fill": null },
    { "name": "img", "hidden": false, "bounds": { "top": 370, "left": 461, "bottom": 807, "right": 746 },
      "blendMode": "mul ", "isSmartObject": true, "isText": false,
      "layerType": "smartObject", "adjustment": null, "fill": null },
    { "name": "无法无天", "hidden": false, "bounds": { "top": 200, "left": 431, "bottom": 372, "right": 679 },
      "blendMode": "norm", "isSmartObject": false, "isText": true,
      "layerType": "text", "adjustment": null, "fill": null }
  ],
  "smartObjects": [
    { "index": 0, "name": "img", "kind": "SoLd",
      "bounds": { "top": 370, "left": 461, "bottom": 807, "right": 746 },
      "originalSize": { "width": 800, "height": 800 },
      "warpStyle": "warpCustom",
      "mesh": { "xn": 4, "yn": 4, "sliceX": [], "sliceY": [], "patches": "1×1", "regular": true },
      "sourceImage": { "filename": "LULI2874.png", "mime": "image/png", "bytes": 779016 } }
  ]
}

错误与限制

400 缺 psd 字段或解析失败 · 413 超过 MAX_UPLOAD_MB · 401/403 凭证问题 · 429 rpm。会解码全部图层栅格,大文件比 /api/doc/inspect 慢得多。

bash

curl -s -X POST https://your-host/api/psd/info \
  -H "X-Api-Key: $FULLPSD_KEY" \
  -F psd=@tshirt.psd

POST/api/render在调试台里打开 →

PSD + 替换图 → 成品:按智能对象的网格 / 变形把新图扭曲上去,输出位图或可继续编辑的 PSD。

鉴权
API key 或会话
Content-Type
multipart/form-data(推荐)或 application/json(图片走 base64)
并发闸
占(每 key concurrency + 全局 FULLPSD_MAX_CONCURRENT,默认 2)
响应
二进制;元数据在 X-Render-Meta

请求字段(multipart)

字段类型必填说明
psdfile ×1是PSD / PSB
imagefile ×≤16—替换图。第 n 个 image 字段对应 spec.replacements[n]
iccfile ×1—直接上传 ICC;给了就覆盖 output.icc
specstring(JSON)—{ replacements, output, render }。不是合法 JSON → 400

纯 JSON 模式:整个请求体就是 spec,PSD 放 psdBase64,每个替换图放 replacements[].imageBase64(接受 data: 前缀)。

spec.replacements[]

字段类型默认说明
targetnumber | string | null0智能对象序号(/api/psd/info 的 smartObjects[].index)或智能对象图层名。省略 = 第 0 个
fitobject{"mode":"cover"}覆盖模式,见下
meshobject—覆盖网格,见下
sourceNamestring上传文件名写进 PSD 的内嵌源图文件名
imageBase64string—纯 JSON 模式下的替换图

fit(覆盖模式)

字段类型默认说明
modestringcovercover 撑满裁溢出 · contain 适应留白 · tile 平铺重复 · stretch 非等比填满
alignX / alignYnumber0.50 = 左/上,0.5 = 居中,1 = 右/下
scalenumber1额外缩放(tile 下即平铺密度的倒数)
offsetX / offsetYnumber0额外平移,单位 = 设计区像素
rotatenumber0绕设计区中心旋转,单位是弧度(度数请自行 deg * Math.PI / 180)

mesh(可选,改网格)

二选一:{ points:[{x,y}…], xn, yn, sliceX?, sliceY? } 直接给控制点(点数必须等于 xn*yn,且 xn/yn 必须同时给),或 { splits:{ u:[0.33,0.66], v:[0.5] } } 在指定纹理坐标处加经/纬线。拆分过的网格写回时走 quiltWarp,PS 里仍可继续拖。变形滤镜层(透视/操控变形)不接受 mesh —— 会 400。

output / render

字段类型默认说明
output.formatstringpngpng / jpg / tiff / webp / psd
output.colorModestringrgbrgb / cmyk。PNG 不支持 CMYK(400)
output.qualitynumber92JPEG / TIFF / WebP 质量
output.dpinumber72写进位图元数据
output.backgroundstring | number[]#ffffffJPEG / CMYK 拍平 alpha 时的底色,"#rrggbb" 或 [r,g,b]
output.width / heightnumber—输出前 resize(fit: inside,允许放大)
output.iccstring—/api/profiles 里的名字。含 / 或 \ 一律 400;也可以直接上传 icc 文件字段
output.attachIccbooleantruefalse 时只做分色、不嵌 ICC
render.backendstringautoauto / cpu / gl。要求 gl 但没装 headless-gl → 400
render.supersamplenumber2超采样倍数
render.subdivnumber20网格细分密度

响应

响应体是二进制。Content-Type 随格式(image/png / image/jpeg / image/tiff / image/webp / image/vnd.adobe.photoshop),Content-Disposition: inline; filename="render.<ext>",元数据在 X-Render-Meta。

为什么元数据里的中文是 \uXXXX:HTTP 头只能放 Latin-1,而图层名和告警文案里有中文。服务端把 JSON 里 U+0080 以上的字符转成 \uXXXX 转义 —— 转义后仍是合法 JSON,客户端直接 JSON.parse 就能还原。X-Doc-Meta 同理。
字段说明
docW / docH画布尺寸
applied[]每个替换的落地情况:index(智能对象序号)、name、layerIndex、mesh("4×4" 或变形滤镜名)、fit
applied[].warpWritten仅 PSD 输出且改过网格:"warp" / "quiltWarp" / "失败(…),已降级为烘焙像素"
applied[].sourceReplaced仅 PSD 输出:内嵌源图是否换掉(决定 PS 里双击进去看到的是不是新图)
applied[].filterCacheUpdated仅变形滤镜层:FEid 缓存是否同步(false/失败字符串意味着 PS 双击后可能恢复原样)
backendcpu / gl
colorMode / icc / iccWarning实际色彩模式、用了哪个 ICC;没传 ICC 的 CMYK 会带告警
compatibilityWarningCount
compatibilityWarnings
合成兼容性提示(调整层 / 填充层 / 矢量蒙版 / 图层样式 / 未实现混合模式 / 组隔离),最多 20 条明细。为空时不出现
lintWarnings仅 PSD 输出且 lint 有告警时出现
ms服务端耗时
X-Render-Meta(PNG 输出) 真实响应

{ "docW": 1200, "docH": 1200,
  "applied": [ { "index": 0, "name": "img", "layerIndex": 1, "mesh": "4×4", "fit": "cover" } ],
  "backend": "cpu", "colorMode": "rgb", "icc": null, "ms": 869 }
X-Render-Meta(PSD 输出,字段来自源码) 结构示意

{ "docW": 1200, "docH": 1200,
  "applied": [ { "index": 0, "name": "img", "layerIndex": 1, "mesh": "4×4", "fit": "cover",
                 "warpWritten": "warp", "sourceReplaced": true, "filterCacheUpdated": true } ],
  "backend": "cpu", "ms": 1240 }

错误与限制

400 缺 PSD / spec 不是 JSON / 找不到 target / 智能对象不是规则网格 / 变形滤镜层传了 mesh / PNG+CMYK / output.icc 带路径 · 413 超过 MAX_UPLOAD_MB 或按 key 的 maxUploadMB(JSON,非 HTML)· 422 生成的 PSD 未过 lintPsd(与 /api/doc/edit 的守门同语义,响应体带 stage:"lint" / code:"artifact_lint_failed" / lint,且不返回文件字节)· 429 rpm 或并发闸(scope 区分 key / global)。

PSD 转 CMYK 只支持 8 位/通道的 RGB 或灰度源文档;文件里没有智能对象(SoLd/PlLd)时直接 400。

bash

curl -s -X POST https://your-host/api/render \
  -H "X-Api-Key: $FULLPSD_KEY" \
  -F psd=@tshirt.psd \
  -F image=@art.png \
  -F 'spec={
    "replacements":[{"target":0,"fit":{"mode":"cover","alignX":0.5,"alignY":0.5}}],
    "output":{"format":"png","dpi":72},
    "render":{"backend":"auto","supersample":2}
  }' \
  -D headers.txt -o render.png

POST/api/doc/inspect在调试台里打开 →

完整文档模型摘要:图层树 + 扁平表 + 图像资源 + 链接文件 + 全局块,纯 JSON、不解一个像素。

鉴权
API key 或会话
Content-Type
multipart/form-data
并发闸
不占
响应头
Cache-Control: no-store

解析层是 core/psd-document.js:七段语义树,每段都带绝对偏移与 _raw 兜底。响应体保证是纯 JSON —— 任何字节数组都被换成 { "bytes": n },不会有大数组。

请求字段

字段类型必填说明
psdfile是PSD / PSB

响应字段

字段类型说明
headerobject{ signature, version, psb, width, height, channels, depth, mode, modeName }
colorModeDataobject{ kind, length },kind ∈ empty / indexed / duotone
imageResources[]array{ id, sig, name, label, length }
layerCountnumber图层记录条数(含分组头与分隔层)
layers[]array图层树:分组节点有 children、isGroup:true、depth
flat[]array扁平表,字段同上,index 就是所有编辑接口用的图层下标(底 = 0)
globalAdditionalKeys[]string[]全局附加块 key(如 Patt、Txt2、lnk2、FMsk)
linkedFiles[]array{ id, name, type, fileType, embedded, byteLength };type:"liFD" 即内嵌
imageDataobject末尾合成图段:{ compression, length }
warnings[]array解析告警(读不懂的东西进这里,不抛错)
smartObjects[]array从 flat 里挑出的智能对象:{ index, name, bounds, kind, linkId, source }
textLayers[]array{ index, name, text, length }
imageResourceIds[]number[]资源 ID 清单
countsobject按 kind 计数
metaobject{ bytes, filename, ms }

flat[] / layers[] 的单层字段

字段说明
index图层下标(底 = 0)。所有 core op 的 index 都用这个
name / pascalNameUnicode 名(luni 块,PS 显示的就是它)/ 老式 Pascal 名(非 ASCII 会是乱码,属正常)
kindraster / group / divider / text / shape / smartObject,再由 API 层细分出 adjustment / fill
bounds / width / height包围盒与尺寸
section图层记录所在段:layerInfo,或 16/32 位文档的 Lr16 / Lr32
visible / opacity / clipping / blendKey / passThrough混合属性。opacity 是 0…255
groupType1 展开组 · 2 折叠组 · 3 组结束分隔层
locked / colorLabel / layerId锁定标志 / 颜色标签 / lyid
hasMask / maskDisabled / hasVectorMask / vectorKnots蒙版与矢量路径
isSmartObject / smartObject{ kind:"SoLd"|"PlLd", linkId, source }
isText / text / textLength / textTruncated文字内容。超过 1024 字符会截断并置 textTruncated:true,textLength 是截断前长度
adjustment / fill{ key, label } 或 null(如 curv 曲线、SoCo 纯色填充)
effects{ keys, items:[{ key, label, enabled }] } 或 null
channels[]{ id, kind, name, length, compression };id:-1 是透明度通道
additionalKeys[]该层的附加块 key 列表(TySh、SoLd、luni…)
响应(裁剪:省略了 flat 的其余条目与部分资源) 真实响应

{
  "header": { "signature": "8BPS", "version": 1, "psb": false, "width": 1200, "height": 1200,
              "channels": 3, "depth": 8, "mode": 3, "modeName": "RGB" },
  "colorModeData": { "kind": "empty", "length": 0 },
  "imageResources": [
    { "id": 1028, "sig": "8BIM", "name": "", "label": "IPTC-NAA record", "length": 15 },
    { "id": 1061, "sig": "8BIM", "name": "", "label": "Caption digest (MD5)", "length": 16 },
    { "id": 1060, "sig": "8BIM", "name": "", "label": "XMP metadata", "length": 15345 }
  ],
  "layerCount": 3,
  "layers": [
    { "index": 2, "name": "无法无天", "pascalName": "ÎÞ·¨ÎÞÌì",
      "bounds": { "top": 200, "left": 431, "bottom": 372, "right": 679 },
      "width": 248, "height": 172, "kind": "text", "section": "layerInfo",
      "visible": true, "opacity": 255, "clipping": false, "blendKey": "norm",
      "passThrough": null, "groupType": null, "locked": false, "colorLabel": "none",
      "layerId": 4, "hasMask": false, "maskDisabled": false, "hasVectorMask": false,
      "vectorKnots": 0, "isSmartObject": false, "isText": true, "text": "无法无天",
      "smartObject": null, "effects": null,
      "channels": [ { "id": -1, "kind": "transparency", "name": "Transparency", "length": 6047, "compression": 1 },
                    { "id": 0, "kind": "color", "name": "R", "length": 1034, "compression": 1 } ],
      "additionalKeys": [ "TySh", "luni", "lnsr", "lyid", "clbl", "infx", "knko", "lspf", "lclr", "shmd", "fxrp" ],
      "depth": 0, "isGroup": false, "children": null,
      "adjustment": null, "fill": null, "textLength": 4 },
    { "index": 1, "name": "img", "kind": "smartObject", "blendKey": "mul ",
      "bounds": { "top": 370, "left": 461, "bottom": 807, "right": 746 },
      "isSmartObject": true,
      "smartObject": { "kind": "SoLd", "linkId": "deeadabe-5ccd-2f49-9041-f8a33b59f73f",
                       "source": { "id": "deeadabe-5ccd-2f49-9041-f8a33b59f73f",
                                   "name": "LULI2874.png", "fileType": "png",
                                   "embedded": true, "byteLength": 779016 } },
      "additionalKeys": [ "luni", "lnsr", "lyid", "clbl", "infx", "knko", "lspf", "lclr", "shmd", "PlLd", "SoLd", "fxrp" ] }
  ],
  "flat": [ "…与 layers 同样的对象,只是不分层…" ],
  "globalAdditionalKeys": [ "Patt", "Txt2", "CAI ", "OCIO", "GenI", "lnk2", "lnkE", "FMsk", "cinf" ],
  "linkedFiles": [ { "id": "deeadabe-5ccd-2f49-9041-f8a33b59f73f", "name": "LULI2874.png",
                     "type": "liFD", "fileType": "png", "embedded": true, "byteLength": 779016 } ],
  "imageData": { "compression": 1, "length": 3874862 },
  "warnings": [],
  "smartObjects": [ { "index": 1, "name": "img",
                      "bounds": { "top": 370, "left": 461, "bottom": 807, "right": 746 },
                      "kind": "SoLd", "linkId": "deeadabe-5ccd-2f49-9041-f8a33b59f73f",
                      "source": { "name": "LULI2874.png", "embedded": true, "byteLength": 779016 } } ],
  "textLayers": [ { "index": 2, "name": "无法无天", "text": "无法无天", "length": 4 } ],
  "imageResourceIds": [ 1028, 1061, 1060, 1082, 1083, 1005, 1062, 1010, 1037, 1049, 1011, 10000,
                        1013, 1016, 1024, 1026, 1072, 1069, 1032, 1092, 1097, 1054, 1050, 1064,
                        1041, 1044, 1036, 1057, 1058 ],
  "counts": { "raster": 1, "smartObject": 1, "text": 1 },
  "meta": { "bytes": 8805986, "filename": "tshirt.psd", "ms": 21 }
}
两个 index 别混:inspect 的 smartObjects[].index 是图层下标(上例里是 1);/api/psd/info 的 smartObjects[].index 是智能对象序号(上例里是 0),也就是 /api/render 的 target。/api/doc/edit 的 target 两种都认,用 {"layerIndex":n} / {"smartObjectIndex":n} 显式区分最保险。

错误与限制

400 缺 psd / 解析失败 · 413 上传超限 · 504 解析超过 FULLPSD_DOC_TIMEOUT_MS · 不解码任何像素,所以对大文件也很快。

bash

curl -s -X POST https://your-host/api/doc/inspect \
  -H "X-Api-Key: $FULLPSD_KEY" \
  -F psd=@tshirt.psd

POST/api/doc/layer-preview在调试台里打开 →

解一个图层的像素,输出带 alpha 的 PNG。

鉴权
API key 或会话
Content-Type
multipart/form-data
并发闸
占
响应
image/png;元数据在 X-Doc-Meta

请求字段

字段类型默认说明
psdfile—必填
layerIndexinteger—必填,对应 /api/doc/inspect 的 flat[].index
maxSizeinteger0长边上限,等比缩小(0 = 原尺寸,不放大)
applyMaskstring应用传 0 或 false 时不应用图层蒙版

响应头 X-Doc-Meta

字段说明
index / name / bounds图层下标、Unicode 名、包围盒
width / height解出来的像素尺寸(缩放前)
depth / mode位深与色彩模式号
warnings[]像素解码告警,最多 20 条
ms耗时
响应头 真实响应

HTTP/1.1 200 OK
Content-Type: image/png
Content-Disposition: inline; filename="layer-1.png"
X-Doc-Meta: {"index":1,"name":"img","bounds":{"top":370,"left":461,"bottom":807,"right":746},
             "width":285,"height":437,"depth":8,"mode":3,"warnings":[],"ms":213}

错误与限制

400 缺 layerIndex / 下标越界 / 该层没有像素(分组、分隔层宽或高为 0)/ 像素解码失败 · 413 单层像素数超过 FULLPSD_DOC_MAX_PIXELS · 429 并发闸 · 504 超时。

bash

curl -s -X POST https://your-host/api/doc/layer-preview \
  -H "X-Api-Key: $FULLPSD_KEY" \
  -F psd=@tshirt.psd -F layerIndex=1 -F maxSize=512 \
  -D headers.txt -o layer-1.png
JavaScript · fetch

const fd = new FormData();
fd.append('psd', psdFile);
fd.append('layerIndex', '1');
fd.append('maxSize', '512');

const res = await fetch('/api/doc/layer-preview', {
  method: 'POST', headers: { 'X-Api-Key': key }, body: fd
});
if (!res.ok) throw new Error((await res.json()).error);
const meta = JSON.parse(res.headers.get('X-Doc-Meta'));
const url = URL.createObjectURL(await res.blob());

POST/api/doc/composite在调试台里打开 →

取文件末尾 Image Data 段里 Photoshop 存好的合成图,输出 PNG / JPEG。

鉴权
API key 或会话
Content-Type
multipart/form-data
并发闸
占
响应
image/png 或 image/jpeg;元数据在 X-Doc-Meta
这是「取」不是「渲染」。返回的是 PS 保存时写进文件的那张合成图,不重新合成图层 —— 快、而且与 PS 所见完全一致。要按新素材重新合成,用 /api/render。

请求字段

字段类型默认说明
psdfile—必填
formatstringpngpng / jpg(jpeg 同义);其它值 400
qualityinteger92JPEG 质量,钳到 1…100
maxSizeinteger0长边上限,等比缩小、不放大

响应头 X-Doc-Meta

{ width, height, depth, mode, modeName, compression, warnings, ms }。compression:0 RAW · 1 RLE · 2 ZIP · 3 ZIP+prediction。

响应头 真实响应

HTTP/1.1 200 OK
Content-Type: image/jpeg
Content-Disposition: inline; filename="composite.jpg"
X-Doc-Meta: {"width":1200,"height":1200,"depth":8,"mode":3,"modeName":"RGB",
             "compression":1,"warnings":[],"ms":140}

错误与限制

400 format 不支持 / 合成图解码失败(文件没有合成图段,或压缩方式不支持)· 413 画布像素数超上限 · 429 并发闸 · 504 超时。JPEG 会先在白底上拍平 alpha。

bash

curl -s -X POST https://your-host/api/doc/composite \
  -H "X-Api-Key: $FULLPSD_KEY" \
  -F psd=@tshirt.psd -F format=jpg -F quality=80 -F maxSize=800 \
  -o composite.jpg

POST/api/doc/patch在调试台里打开 →

批量改图层属性(名字 / 可见 / 不透明度 / 剪贴 / 混合模式)→ 新 PSD 字节。

鉴权
API key 或会话
Content-Type
multipart/form-data
并发闸
不占
响应
image/vnd.adobe.photoshop;元数据在 X-Doc-Meta

只改属性,不动像素、不动智能对象内容。要做更多(文字 / 像素 / 换源图 / 变形),用 /api/doc/edit;这个端点保留是为了老集成不用改。

请求字段

字段类型必填说明
psdfile是PSD / PSB
opsstring(JSON 数组)是见下。也接受 patch 字段(JSON 对象,取其中的 .ops)

ops[] 每条

字段类型说明
indexinteger必填,flat[].index;越界 400
namestring非空、≤255 字符。写 Unicode 图层名
visibleboolean—
opacityinteger0…255(255 = 不透明)
clippingboolean是否剪贴到下层
blendKeystring4 字符混合模式键,不足补空格。白名单校验,见 ops 参考里的模式表

除 index 外至少要给一个字段,否则 400。

响应头 X-Doc-Meta

字段说明
applied[]{ index, changed:["name","opacity"…] }
bytes / inputBytes产物与输入的字节数
lint{ ok:true, warnings, summary };summary 里有 psb/channels/W/H/depth/mode/resources/layers/linkedFiles/globalBlocks
parseWarnings输入文件的解析告警,最多 20 条
ms耗时
响应头 真实响应

HTTP/1.1 200 OK
Content-Type: image/vnd.adobe.photoshop
Content-Disposition: attachment; filename="patched.psd"
X-Doc-Meta: {"applied":[{"index":1,"changed":["name","opacity"]},
                        {"index":2,"changed":["visible","blendKey"]}],
             "bytes":8805986,"inputBytes":8805986,
             "lint":{"ok":true,"warnings":[],
                     "summary":{"psb":false,"channels":3,"W":1200,"H":1200,"depth":8,"mode":3,
                                "resources":29,"layers":3,"linkedFiles":1,"globalBlocks":9}},
             "parseWarnings":[],"ms":54}

422:体检不过

写回后必须先过 lintPsd,再被自己重新解析一遍。任何一步不过只返回报告、不返回文件 —— 长度字段一旦不自洽,Photoshop 只会说「意外地遇到文件尾」。

422 响应体 结构示意

{
  "error": "写回结果未通过结构体检(lintPsd),已放弃返回文件",
  "lint": { "ok": false, "errors": ["…"], "warnings": ["…"], "summary": { } },
  "applied": [ { "index": 1, "changed": ["name"] } ]
}

错误与限制

400 缺 ops / ops 不是 JSON 数组或空数组 / 超过 FULLPSD_DOC_MAX_OPS(默认 512)/ index 越界 / 字段类型错 / blendKey 不在白名单 / 附加区被降级成原样字节的图层不支持改名 · 422 见上 · 504 超时。

bash

curl -s -X POST https://your-host/api/doc/patch \
  -H "X-Api-Key: $FULLPSD_KEY" \
  -F psd=@tshirt.psd \
  -F 'ops=[{"index":1,"name":"新图案","opacity":200},
           {"index":2,"visible":false,"blendKey":"mul "}]' \
  -D headers.txt -o patched.psd

POST/api/doc/validate在调试台里打开 →

体检:解析 + lintPsd 逐段走长度字段 + 字节级往返自检。排查「PS 打不开」时先打这个。

鉴权
API key 或会话
Content-Type
multipart/form-data
并发闸
不占
响应
JSON(文件有问题也返回 200,ok 才是结论)

响应字段

字段类型说明
okbooleanlint.ok && roundTripIdentical && 解析告警为空,三者都满足才 true
warnings[]array解析告警(有东西没读懂,但不影响往返)
lintobject{ ok, errors, warnings, summary }
roundTripIdenticalbooleanserialize(parse(file)) 是否与原文件逐字节相同
firstDiffOffsetnumber第一个不同字节的偏移;完全一致时 -1。报 bug 请带上它
roundTripErrorstring重新序列化时抛错才出现
documentobject{ psb, width, height, depth, mode, modeName, layerCount, imageResources, linkedFiles, globalAdditionalKeys }
bytesobject{ input, serialized }
msnumber耗时
响应 真实响应

{
  "ok": true,
  "warnings": [],
  "lint": { "ok": true, "errors": [], "warnings": [],
            "summary": { "psb": false, "channels": 3, "W": 1200, "H": 1200, "depth": 8, "mode": 3,
                         "resources": 29, "layers": 3, "linkedFiles": 1, "globalBlocks": 9 } },
  "roundTripIdentical": true,
  "firstDiffOffset": -1,
  "document": { "psb": false, "width": 1200, "height": 1200, "depth": 8, "mode": 3,
                "modeName": "RGB", "layerCount": 3, "imageResources": 29, "linkedFiles": 1,
                "globalAdditionalKeys": ["Patt","Txt2","CAI ","OCIO","GenI","lnk2","lnkE","FMsk","cinf"] },
  "bytes": { "input": 8805986, "serialized": 8805986 },
  "ms": 71
}

怎么读

  • lint.errors 非空 = 文件的长度字段不自洽,PS 大概率打不开。
  • roundTripIdentical:false = 我们的模型还没完全吃透这个文件;firstDiffOffset 指向第一个分歧字节。这不代表文件坏了,但意味着基于它的编辑会更保守。
  • warnings 非空 = 有块读不懂、被按 _raw 原样保留了 —— 往返仍然安全。

400 只在「连 PSD 都不是」时出现。

bash

curl -s -X POST https://your-host/api/doc/validate \
  -H "X-Api-Key: $FULLPSD_KEY" \
  -F psd=@suspect.psd

POST/api/doc/edit在调试台里打开 →

统一编辑端点:一个路径、两种模式,按 Content-Type 分派。改属性 / 文字 / 像素(core op)与换智能对象源图 / 变形写回(服务端 op)可以在同一个 ops 数组里任意混排、按顺序执行。

鉴权
API key 或会话
Content-Type
multipart/form-data(文件模式)· application/json(片段模式)· 其它 → 415
并发闸
文件模式 占;片段模式不占
耗时闸
FULLPSD_EDIT_TIMEOUT_MS(默认 60 s)
编辑器能做的,这里基本都能做:除了改属性 / 文字 / 像素 / 蒙版 / 矢量 / 样式等 core op,服务端扩展 op 还覆盖了滤镜(75 种)、破坏性调整、内容识别填充、填充 / 清除选区、画布大小、裁剪、图像大小、旋转 / 翻转画布、移动 / 复制图层、新建透视 / 操控变形、新建画板、改智能滤镜。完整清单见 ops 参考。
顺序语义:服务端会把连续同类的 op 切成分段批处理,但这只是批处理手段 —— 段内段间都严格保持数组顺序,与逐条执行完全一致。中间分段不重复守门,整串跑完后统一守门一次。

① 文件模式(multipart/form-data)

字段类型必填说明
psdfile ×1是PSD / PSB
imagefile ×≤16—换源图 / 换像素用的图片,op 里按下标或文件名引用
iccfile ×1—仅 output.format 为 psd/psb 时生效
fontfile ×≤8—临时字体(TTF / OTF / TTC / OTC / WOFF):只在本次请求的文字重绘里用,不落盘。长期用请传进字体库
opsstring(JSON 数组)是见 ops 参考。也可以放在 spec.ops 里
outputstring(JSON 对象)—见下。也可以放在 spec.output
renderstring(JSON 对象)—{ backend, supersample, subdiv },只对 replaceSmartObject 有意义。也可放 spec.render
op 里怎么引用上传的图片
写法含义
{"image":{"file":"image","index":0}}第 0 个 image 字段(推荐,最不容易歧义)
{"image":{"name":"logo.png"}}按上传文件名;重名会 400,要你改用 index
{"image":0}下标简写
{"image":"data:image/png;base64,…"}内联(也接受长度 > 64 的裸 base64)
{"image":{"base64":"…"}}同上,对象写法(dataURL 同义)

file 字段只能是 "image"(本端点的图片字段名固定),写别的会 400。

output
字段类型默认说明
formatstringpsdpsd / psb / png / jpg / tiff / webp
colorModestringrgb位图输出时 cmyk 只支持 jpg / tiff(PNG/WebP 会 400)
qualitynumber92JPEG / TIFF / WebP
maxSizenumber0位图长边上限,等比缩小不放大
sourcestring见说明位图从哪来:layers = 按图层重新合成(与 /api/doc/render 同一套内核);composite = 取文件末尾的合成图快照。默认:本次已重建合成图时取快照(两者一致且更快),否则按图层合成
artboardinteger | string—位图只出这一块画板(画板组头的 index 或画板名);给了就强制 source:"layers"
recomposite"auto" | boolean"auto"是否按图层重建文件末尾的合成图快照与缩略图(资源 1036)。auto:本次 ops 改了像素 / 几何 / 可见性 / 样式 / 结构时重建(只改名、颜色标签、图像资源、文字内容时不重建);false = 保留原快照(旧行为)。改画布尺寸的 op 必然重建(否则文件长度对不上)
renderTextbooleantrue内容 / 样式改过的文字层(以及新建出来还没有像素的)按服务端字体重排重绘栅格;只做了移动 / 缩放 / 旋转的文字层不重绘(像素已跟着变换)。false = 不重绘(PS 打开时反正会自己重排)
fontMapobject—文字重绘的字体映射 { "PSD 里的 PostScript 名": "字体 PostScript 名 / 族名 / 字体库 id" }。PSD 里记的名字与你上传 / 服务器装的字体对不上时用;对得上不用写。见 自传字体
icc / attachIccstring / boolean—仅 PSD/PSB 输出时传给写回管线;icc 只能是 /api/profiles 里的名字,带路径分隔符会 400
位图输出的 CMYK 只能预览:这个端点的 CMYK 走 libvips 内置公式、不带 ICC。要出印刷文件请用 /api/render(会带 ICC 分色),响应元数据里也会附上同样意思的 iccWarning。
响应

成功时响应体就是文件字节,Content-Disposition: attachment; filename="edited.<ext>",元数据在 X-Doc-Meta:

字段说明
ops[]每条 op 的结果:i(在数组里的位置)、kind(core/server)、op(规范名)、ok、alias(用了别名才有)、changed、changedFields、affected(受影响图层下标,最多 32 个)、note、meta
bytes / psdBytes / inputBytes响应体字节数 / 编辑后 PSD 的字节数 / 输入字节数
output{ format },CMYK 时另有 colorMode 与 iccWarning
guard{ ok, lint:{ok,warnings}, roundTripIdentical, firstDiffOffset, inputWasHealthy? }
warnings[]执行与守门告警,每条截到 160 字符、最多 20 条
ms耗时
X-Doc-Meta(两条 core op;\uXXXX 已还原成中文) 真实响应

{
  "ops": [
    { "i": 0, "kind": "core", "op": "setLayerProps", "ok": true, "alias": "renameLayer",
      "changed": true, "changedFields": ["name"], "affected": [2],
      "note": "图层 2「无法无天」→ name" },
    { "i": 1, "kind": "core", "op": "setLayerProps", "ok": true, "alias": "setOpacity",
      "changed": true, "changedFields": ["opacity"], "affected": [1],
      "note": "图层 1「img」→ opacity" }
  ],
  "bytes": 8805978, "psdBytes": 8805978, "inputBytes": 8805986,
  "output": { "format": "psd" },
  "guard": { "ok": true, "lint": { "ok": true, "warnings": [] },
             "roundTripIdentical": true, "firstDiffOffset": -1 },
  "warnings": ["图层 2 的 Pascal 名含非 ASCII 字符,已按 '?' 落地(真名在 luni 块里,PS 显示的是 luni)"],
  "ms": 125
}
X-Doc-Meta(换智能对象源图 + 改文字) 真实响应

{
  "ops": [
    { "i": 0, "kind": "server", "op": "replaceSmartObject", "ok": true, "changed": true,
      "affected": [1], "note": "智能对象「img」换源图",
      "meta": { "target": { "layerIndex": 1, "layerName": "img", "smartObjectIndex": 0 },
                "warpWritten": null, "sourceReplaced": true,
                "filterCacheUpdated": null, "deformKind": null, "bytes": 7778372 } },
    { "i": 1, "kind": "core", "op": "setText", "ok": true, "changed": true,
      "changedFields": ["text"], "affected": [2], "note": "图层 2「无法无天」文字 → 4 字符" }
  ],
  "bytes": 7765236, "psdBytes": 7765236, "inputBytes": 8805986,
  "output": { "format": "psd" },
  "guard": { "ok": true, "lint": { "ok": true, "warnings": [] },
             "roundTripIdentical": true, "firstDiffOffset": -1 },
  "warnings": [], "ms": 1032
}

warpWritten 为 null 表示这次没改网格(没写回 warp);filterCacheUpdated 为 null 表示这层不是变形滤镜层,没有 FEid 缓存要同步。

422:守门不过
422 响应体(字段来自源码) 结构示意

{
  "error": "产物未通过守门(lintPsd + 可解析 + 自身字节往返),已放弃返回文件",
  "stage": "lint",
  "offset": 8808,
  "guard": {
    "fresh": [ { "kind": "lint", "message": "…这次编辑新增的那条错误…" } ],
    "lint": { "ok": false, "errors": ["…"] },
    "parseOk": true, "parseError": null,
    "roundTripIdentical": false, "firstDiffOffset": 8808,
    "inputWasHealthy": true, "bytes": 8805990
  },
  "ops": [ { "i": 0, "kind": "core", "op": "setLayerProps", "ok": true } ]
}

stage ∈ lint / parse / roundtrip。guard.fresh 是这次编辑新增的问题 —— 输入文件本来就体检不过的,会以输入为基线放行并降级成 warnings,不算到本次编辑头上。详见一致性保证。

② 片段模式(application/json,不传 PSD)

请求体 { fragment, edit } 或 { items:[{fragment,edit}…] }(一次最多 64 个)。与 /api/so/writeback 完全等价 —— 同一段 writebackFragment 实现、同样的字段与顺序,只是走统一鉴权与统一错误约定。不占并发闸。

请求体里出现 ops 或 psdBase64 会直接 400(这是文件模式的字段,说明你用错了 Content-Type)。

请求 / 响应 真实响应(base64 已截断)

// 请求
{
  "fragment": { "layerName": "img", "kind": "SoLd", "psb": false,
                "bounds": { "top": 370, "left": 461, "bottom": 807, "right": 746 },
                "blocks": [ { "key": "PlLd", "data": "cGxjTAAAAAMkZGVl…" },
                            { "key": "SoLd", "data": "c29MRAAAAAUAAAAB…" } ] },
  "edit": { "points": [ { "x": 418.00000000000006, "y": 417.9085020932756 }, "…共 16 个…" ],
            "xn": 4, "yn": 4, "sliceX": [], "sliceY": [] }
}

// 响应
{ "mode": "warp",
  "blocks": [ { "key": "PlLd", "data": "cGxjTAAAAAMkZGVl…" } ],
  "ms": 2 }

错误码

码什么情况
400缺 psd/ops、ops 不是数组或为空、未知 op、op 字段类型错、index 越界、blendKey 非法、target 找不到 / 不唯一 / 不是智能对象、图片引用对不上、edit 与图层类型不匹配(变形滤镜层传 points、普通层传 deform)、PSD 解析失败、output.format 不支持
413上传超限;画布或上传图片超过 FULLPSD_DOC_MAX_PIXELS
415Content-Type 不是那两种之一
422守门不过(stage = lint/parse/roundtrip),或放置块写回失败(code:"writeback")。不返回文件字节
429 / 504rpm 或并发闸 / 阶段超时
500这个端点明确区分输入错与服务端 bug:能归因于输入的一律 4xx,剩下的才 500。看到 500 请报 bug
bash · 混排 core op 与服务端 op

curl -s -X POST https://your-host/api/doc/edit \
  -H "X-Api-Key: $FULLPSD_KEY" \
  -F psd=@tshirt.psd \
  -F image=@art.png \
  -F 'ops=[
    {"op":"replaceSmartObject","target":0,
     "image":{"file":"image","index":0},"fit":{"mode":"cover"}},
    {"op":"setText","index":2,"text":"新的文字"},
    {"op":"setOpacity","index":1,"opacity":180}
  ]' \
  -F 'output={"format":"psd"}' \
  -D headers.txt -o edited.psd

# 只要位图预览:output={"format":"jpg","quality":85,"maxSize":1600}

POST/api/doc/edit/plan在调试台里打开 →

dry-run:逐条判定 ops 可行性,不产出文件。给集成方「先校验再提交」。

鉴权
API key 或会话
Content-Type
只收 multipart/form-data(其它 415)
并发闸
不占;含服务端扩展 op(要在内存里真跑)时占

字段与文件模式相同(psd + ops),但图片可以不传 —— replaceSmartObject 只检查你是否声明了 image 引用。deform 则是拿真实放置块(KB 级)跑一次真写回,可行性判定与执行路径完全一致。

只有 core op 与老三条服务端 op 时走静态推演:core op 的 index 漂移(deleteLayer / reorderLayers 会让后续下标变)已经模拟进去了。
只要 ops 里有服务端扩展 op(滤镜 / 画布 / 复制图层 / 新建画板 / 新建变形…),plan 改为在内存里真跑一遍(不重建合成图、不出文件):这些 op 可能改图层表或依赖像素内容,静态推演不可靠。此时响应多一个 mode:"execute",document.after 给出执行后的尺寸与图层数;失败时 errors[0].opIndex 指向第一条出错的 op,它之后的 op 标 ok:null(未校验)。plan 说不行的,真提交一定是 4xx。

响应字段

字段说明
okerrors 为空才 true
ops[]{ i, kind, op, alias, ok, affected, changed, note };服务端 op 另有 target、deformFilter、mesh、mode。ok:null = 前面的 op 已失败,这条没继续校验
errors[]{ opIndex, op, message }
layerCount全部 core op 跑完后的图层数
document{ psb, width, height, layerCount, smartObjectCount, textLayerCount }
input{ bytes, filename }
可行 真实响应

{
  "ok": true,
  "ops": [
    { "i": 0, "kind": "core", "op": "setLayerProps", "alias": "renameLayer", "ok": true,
      "affected": [2], "changed": ["name"], "note": "图层 2「无法无天」→ name" },
    { "i": 1, "kind": "server", "op": "replaceSmartObject", "alias": null, "ok": true,
      "affected": [1],
      "target": { "layerIndex": 1, "layerName": "img",
                  "bounds": { "top": 370, "left": 461, "bottom": 807, "right": 746 },
                  "isSmartObject": true, "smartObjectIndex": 0,
                  "smartObjectName": "img", "kind": "SoLd" },
      "deformFilter": null, "mesh": "4×4",
      "note": "换源图:智能对象 0「img」→ 图层 1" }
  ],
  "errors": [],
  "layerCount": 3,
  "document": { "psb": false, "width": 1200, "height": 1200,
                "layerCount": 3, "smartObjectCount": 1, "textLayerCount": 1 },
  "input": { "bytes": 8805986, "filename": "tshirt.psd" },
  "ms": 41
}
不可行(index 越界) 真实响应

{
  "ok": false,
  "ops": [ { "i": 0, "kind": "core", "op": "setLayerProps", "alias": "renameLayer", "ok": false,
             "affected": [], "changed": [],
             "note": "ops[0]「setLayerProps」:index=3 越界(本文档 3 个图层,合法范围 0…2)" } ],
  "errors": [ { "opIndex": 0, "op": "setLayerProps",
                "message": "ops[0]「setLayerProps」:index=3 越界(本文档 3 个图层,合法范围 0…2)" } ],
  "layerCount": 3,
  "document": { "psb": false, "width": 1200, "height": 1200,
                "layerCount": 3, "smartObjectCount": 1, "textLayerCount": 1 },
  "input": { "bytes": 8805986, "filename": "tshirt.psd" },
  "ms": 16
}

注意:dry-run 的 ok:false 仍然是 HTTP 200 —— 判定结果在响应体里。只有请求本身有问题(缺 psd、ops 不是数组、未知 op)才是 4xx。

bash

curl -s -X POST https://your-host/api/doc/edit/plan \
  -H "X-Api-Key: $FULLPSD_KEY" \
  -F psd=@tshirt.psd \
  -F 'ops=[{"op":"renameLayer","index":2,"name":"A"},
           {"op":"replaceSmartObject","target":"img","image":{"file":"image","index":0}}]'

POST/api/doc/edit/incremental

只传要改的那几段字节,不上传整份 PSD。你在本地把片段切出来 → 服务端返回新片段 → 你在本地拼回。产物与整文件模式逐字节相同。

鉴权
API key 或会话
Content-Type
只收 application/json(其它 415),上限 FULLPSD_EDIT_JSON_LIMIT(默认 32 mb)
并发闸
不占(KB 级、纯 CPU、无渲染)
响应头
Cache-Control: no-store
为什么要这条路:网格 / 变形 / 放置描述符的算法只能在服务端跑(加密下发),但为了这几 KB 把 8 MB 的 PSD 上传一遍、再回传一遍,纯属浪费。片段协议解决了智能对象那一块;这个端点把同样的思路推广到改文字、改图层属性、改名、各类变形写回、换智能对象源图的描述符部分。像素、内嵌源图这类 MB 级数据继续留在本地处理,不进这套协议。

片段类型

「片段」= 你在本地文件里切出来的一段连续字节 + 它的绝对偏移、长度、校验和。四种:

kind是哪段字节用于变长
layerBlend图层记录里定长的 8 字节:blendKey(4) opacity(1) clipping(1) flags(1) filler(1)可见性 / 不透明度 / 剪贴 / 混合模式否(必须等长)
layerName图层 extra 区里的 Pascal 名(含 4 字节对齐填充)改名(老工具与 PS 的 fallback 名)是
layerBlock一个附加信息块整体:8BIM + key(4) + len(4) + data + padluni(真名)· TySh(文字)是
placedBlocks智能对象放置块的块数据(SoLd/PlLd,不含块头),可多块变形写回 · 放置描述符是

可用 op

op需要的片段说明
setLayerProps / setVisible / setOpacity / setBlendMode / setClippinglayerBlend字段与整文件模式同名同校验(opacity 0…255、blendKey 白名单)
renameLayer / setLayerProps(带 name)layerBlock:luni + layerName该层没有 luni 块时,给一个 length:0 的插入点片段(偏移取 extra 区末尾),服务端会新建
setTextlayerBlock:TySh同步改描述符 Txt 与 EngineData;响应会带一条 removeGlobalBlocks:["Txt2"] 的 sideEffect,由你在本地执行(Txt2 可能是 MB 级,不值得往返)
deform / setMeshplacedBlockswarp · quiltWarp · 透视变形(714)· 操控变形(687),points / deform 形态与 /api/so/writeback 的 edit 完全一致
setSmartObjectDescriptorplacedBlocks换源图的描述符部分:linkId(Idnt)· placedId(placed)· originalSize(Sz )。像素与内嵌源图字节在本地换

不走这条路的:replaceLayerPixels(像素是 MB 级,本地做)· deleteLayer / reorderLayers(要重排整个层信息段,没有「一小段」可切)· setImageResource / removeImageResource(资源段有自己的对齐重试规则)· replaceSmartObject(要真渲染)。这些请继续用 /api/doc/edit 的文件模式,传错会 400 并在文案里说明。

请求体

字段类型说明
docobject{ psb, layerCount?, bytes? }。没有文件字节;psb 决定块长度字段宽度与填充粒度,必须给对
fragments[]array一次最多 64 个,见下
ops[]array一次最多 FULLPSD_DOC_MAX_OPS(默认 512)条,按数组顺序作用在同一批片段上
fragment 字段类型说明
idstring请求内唯一。约定写法 kind:layerIndex[:key:occurrence],op 按 (kind, layerIndex, key) 自动匹配
kindstring见上表四种
layerIndexint文档图层下标(与 /api/doc/inspect 的 flat[].index 同一坐标系)
offset / lengthint该段在你本地文件里的绝对偏移与长度。服务端不解释它、只原样回带;length 与解码后的字节数对不上 → 400
checksumstring"sha256:<hex>",片段字节的哈希。服务端独立复算,对不上 → 400(切错区间不会被静默接受)。可省略,但强烈建议给
database64片段字节(placedBlocks 不用这个字段)
blocks[]array仅 placedBlocks:{ key, occurrence, offset, length, data },最多 8 块。此时 checksum 算在各块字节拼接后的结果上
layerName / bounds—仅 placedBlocks 需要(放置块的解析要用到),语义同片段协议
metaobjectlayerBlock 用 { key, occurrence } 指明是哪个块

响应字段

字段说明
fragments[]只含变了的片段:{ id, kind, layerIndex, offset, length, checksum, newLength, newChecksum, data|blocks }。offset/length/checksum 是请求里那一份,原样回带,方便你定位并再核一次
sideEffects[]要你在本地做的事,目前只有 { "type":"removeGlobalBlocks", "keys":["Txt2"] }。必须在拼回片段之后执行,顺序与整文件模式一致
ops[]{ i, op, alias, ok, fragments:[id…], changedFields?, mode?, note }
bytes{ up, down, docBytes },片段净字节数(不含 base64 与 JSON 外壳)
warnings[] / ms同其它端点

拼回规则(自己实现客户端时看这里)

  • layerBlend:等长覆写,不动任何长度字段。
  • layerName / layerBlock:把 [offset, offset+length) 换成新字节,然后依次修正图层 extraLen(恒 4 字节,PSB 也是)→ LayerInfo 段长 → Layer&Mask 段长,最后把 LayerInfo 段补零到 4 的倍数。
  • placedBlocks:块数据按 key 出现顺序对应,块长度字段写裸长度、块整体补到偶数(这与附加块 buildBlock 的 4 字节口径不同),三个外层长度字段同步、不重新对齐 LayerInfo —— 与 /api/so/writeback 的拼回完全一致。
  • 多个片段一起回来时按偏移从高到低逐条应用:高地址先写,低地址片段的偏移不受影响;且每条各自做一次 4 字节对齐 —— 合并成一次会少补零,产物就不再逐字节相同了。
  • 拼完请自己跑一次 lintPsd:服务端看不见整份文件,做不了整文件守门(它只自检产出的片段本身)。

core/psd-parse.js 里已经有现成的实现:buildIncrementalRequest(buffer, ops) 组请求、applyIncrementalResponse(buffer, res) 拼回、fragmentChecksum(bytes) 算校验和(纯 JS SHA-256,浏览器 / Node 同源)。web/api-client.js 的 incrementalEdit() 是完整例子。

错误

400 校验和不符(带 fragmentId)· 长度声明与实际字节数不符 · 未知 kind · 缺片段(带 need:[{kind,layerIndex,key?}] 告诉你还差哪几段)· op 参数非法 · op 不支持增量 · 片段区间重叠 · 413 请求体超限 · 415 Content-Type 不对 · 422 产出的片段自身解不回来(例如新 TySh 无法重新解析、新放置块无法 resolve),此时不返回任何字节 · 429 限流(scope 区分 key / global)。

传输量实测

操作语料增量上行增量下行整份往返省
改不透明度 / 可见性 / 混合模式 / 剪贴multilayer-2.psd(8.6 KB)0.41 KB0.54 KB17.19 KB94.5%
改名(luni + Pascal 名)multilayer-2.psd0.75 KB0.98 KB17.19 KB89.9%
改文字point-text.psd(22 KB)11.57 KB11.69 KB44.34 KB47.6%
变形 warptshirt.psd(8.4 MB)4.80 KB4.12 KB16.80 MB99.95%
变形 quiltWarp(拆分加线)tshirt.psd6.28 KB6.60 KB16.80 MB99.93%
智能对象放置描述符tshirt.psd4.10 KB4.15 KB16.80 MB99.95%
透视变形(714)透视变形.psd(171 KB)6.48 KB5.80 KB341 KB96.4%
操控变形(687)操作变形.psd(246 KB)185 KB117 KB492 KB38.6%

真实 HTTP body 字节数(含 base64 与 JSON 外壳),由 server/test-incremental.js 每次跑测试时打印。「整份往返」= 上传一遍 + 回传一遍。文件越大、要改的越小,省得越多;反过来,操控变形的顶点数组本身就有 87 KB(占了 246 KB 文件的三分之一),这时候增量的意义就有限了 —— 判断标准是「片段 / 文件」的比例,不是文件绝对大小。

等价性是有测试锁住的:server/test-incremental.js 对上面每一种操作都跑两遍 —— 一遍整文件模式(applyEdits / applyDeform / applySmartObjectDescriptor),一遍增量往返,然后逐字节比较。任何一边的实现漂了,测试立刻红。
bash —— 改一个图层的不透明度(整份 8.6 KB 的文件,只上行 8 字节片段)

curl -s -X POST https://your-host/api/doc/edit/incremental \
  -H "X-Api-Key: $FULLPSD_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "doc": { "psb": false, "bytes": 8802 },
    "fragments": [ { "id": "layerBlend:0", "kind": "layerBlend", "layerIndex": 0,
                     "offset": 112, "length": 8,
                     "checksum": "sha256:216e949ff3e…",
                     "data": "bm9ybf8AAAA=" } ],
    "ops": [ { "op": "setOpacity", "index": 0, "opacity": 128 } ]
  }'
响应

{ "ok": true,
  "fragments": [ { "id": "layerBlend:0", "kind": "layerBlend", "layerIndex": 0,
                   "offset": 112, "length": 8, "checksum": "sha256:216e949ff3e…",
                   "newLength": 8, "newChecksum": "sha256:…", "data": "bm9ybYAAAAA=" } ],
  "sideEffects": [],
  "ops": [ { "i": 0, "op": "setLayerProps", "alias": "setOpacity", "ok": true,
             "fragments": ["layerBlend:0"], "changedFields": ["opacity"], "note": "图层 0 → opacity" } ],
  "bytes": { "up": 8, "down": 8, "docBytes": 8802 }, "ms": 1 }
JavaScript —— 用 core 里现成的实现(浏览器 / Node 通用)

import { buildIncrementalRequest, applyIncrementalResponse } from './core/psd-parse.js';

const ops = [{ op: 'setText', index: 3, text: '新文字' },
             { op: 'deform', index: 5, points, xn, yn, sliceX, sliceY }];

// ① 本地切片段(整份 PSD 不上传)
const req = buildIncrementalRequest(psdBytes, ops);
const body = { doc: req.doc, ops: req.ops,
  fragments: req.fragments.map(f => ({ ...f,
    ...(f.blocks ? { blocks: f.blocks.map(b => ({ ...b, data: b64(b.data) })) } : { data: b64(f.data) }) })) };

// ② 服务端只见到这几 KB
const res = await (await fetch('/api/doc/edit/incremental', {
  method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Api-Key': KEY },
  body: JSON.stringify(body) })).json();

// ③ 本地拼回(含 sideEffects,如删除全局 Txt2)
const { buffer } = applyIncrementalResponse(psdBytes, {
  sideEffects: res.sideEffects,
  fragments: res.fragments.map(f => ({ ...f,
    ...(f.blocks ? { blocks: f.blocks.map(b => ({ ...b, data: unb64(b.data) })) } : { data: unb64(f.data) }) })) });

// ④ 自己守门(服务端看不见整份文件)
if (!lintPsd(buffer).ok) throw new Error('拼回后 lint 不过');

POST/api/so/resolve在调试台里打开 →

放置块字节 → 可编辑的网格 / 变形结构。不传 PSD、不传像素,见片段协议。

鉴权
API key 或会话
Content-Type
application/json(上限 FULLPSD_SO_JSON_LIMIT,默认 32 mb)
并发闸
不占
响应头
Cache-Control: no-store

请求体

单个 fragment,或 { "items": [fragment, …] }(一次最多 64 个)。

字段类型说明
layerNamestring图层名,超过 256 字符会被截断
boundsobject{ top, left, bottom, right },四个值都必须是有限数
kindstringSoLd 或 PlLd
psbboolean源文件是不是 PSB(影响块长度字段宽度)
blocks[]array{ key:"SoLd"|"PlLd", data:<base64> }。最多 8 块,单块 ≤ 8 MB;其它 key 直接报错

响应字段

字段说明
layerInfo渲染 / 编辑用的放置信息,见下
layerInfoBase仅变形滤镜层非空:「滤镜之前」的放置(重建 FEid 缓存要用);普通层为 null
summary{ kind, originalSize, hasWarp, isQuilt, meshPointCount, deformFilter, placedId, linkId }
ms耗时
layerInfo 的字段
字段说明
layerName / layType图层名 / 放置类型(如 normalImg)
points[]Bezier 网格控制点 {x,y},文档坐标,共 xn*yn 个
xn / yn网格列数 / 行数(4×4 = 1 个面片)
sliceX / sliceY经/纬拆分线(quiltWarp 才非空)
sourceUV[]每个控制点对应的纹理坐标 {u,v}
width / height / originalSize设计区(智能对象原始)尺寸
warpBounds / rect / BoundingRectwarp 定义域 / 图层矩形 / 包围盒
Matrix3放置变换矩阵(9 个数,行主序)
warpStyle如 warpNone / warpCustom
deform / deformName变形滤镜结构(perspectiveWarp / puppetWarp)与中文名;普通层为 null
响应(裁剪) 真实响应

{
  "layerInfo": {
    "layerName": "img", "layType": "normalImg",
    "sliceX": [], "sliceY": [],
    "warpBounds": { "top": 0, "left": 0, "bottom": 800, "right": 800 },
    "originalSize": "800,800", "width": 800, "height": 800,
    "Matrix3": [0.45625000000000004, 0, 418,
                1.1102230246251565e-16, 0.45592642925714166, 417.9085020932757,
                0, 0, 1],
    "rect": [461, 370, 285, 437],
    "BoundingRect": [461, 370, 746, 807],
    "points": [ { "x": 418.00000000000006, "y": 417.9085020932756 },
                { "x": 539.6666666666667,  "y": 417.9085020932757 },
                { "x": 690.0781250000002,  "y": 311.00000000000006 } ],
    "sourceUV": [ { "u": 0, "v": 0 }, { "u": 0.3333333333333333, "v": 0 } ],
    "xn": 4, "yn": 4, "warpStyle": "warpCustom",
    "deform": null, "deformName": null
  },
  "layerInfoBase": null,
  "summary": { "kind": "SoLd", "originalSize": { "width": 800, "height": 800 },
               "hasWarp": true, "isQuilt": false, "meshPointCount": 16,
               "deformFilter": null,
               "placedId": "c2745df0-917e-0944-8e16-34f289987dd1",
               "linkId": "deeadabe-5ccd-2f49-9041-f8a33b59f73f" },
  "ms": 3
}
两种模式的失败语义不同(按状态码就能判):
  • 批量(items):部分失败是正常情况 —— HTTP 恒 200,失败的条目变成 { "error": "…" },另给汇总 failed / ok 计数,不用遍历就知道有没有失败。
  • 单条(直接传 fragment):整个请求就这一件事,失败即请求失败 —— 返回 400 + { error },不再是 200 把错误藏在响应体里。
批量响应的汇总字段 真实响应

{ "items": [ { "layerInfo": { }, "layerInfoBase": null, "summary": { } },
             { "error": "放置块 key 只能是 SoLd / PlLd" } ],
  "failed": 1, "ok": 1, "ms": 4 }

错误

400 超过 64 个条目 · 单条模式解析失败(fragment 格式错、放置块无法解析、块过大 / key 不对 / bounds 无效;批量模式下同样的错落在条目的 error 里,HTTP 仍 200)· 413 JSON 体积超限。

bash

curl -s -X POST https://your-host/api/so/resolve \
  -H "X-Api-Key: $FULLPSD_KEY" \
  -H "Content-Type: application/json" \
  -d @fragment.json

POST/api/so/writeback在调试台里打开 →

编辑后的网格 / 变形 → 新的放置块字节。客户端拿去替换原块即可。

鉴权
API key 或会话
Content-Type
application/json
并发闸
不占
等价端点
POST /api/doc/edit 的片段模式(同一实现,逐字节相同)

请求体

字段类型说明
fragmentobject同 /api/so/resolve 的 fragment
editobject二选一,见下
edit 形态字段用于
网格{ points:[{x,y}…], xn, yn, sliceX:[], sliceY:[] }普通 warp / quiltWarp。给了 xn/yn 时点数必须等于 xn*yn;点数上限 4096,坐标必须是有限数
变形滤镜{ deform: { kind:"perspectiveWarp"|"puppetWarp", … } }透视变形 / 操控变形,结构见变形能力
叠加多条{ deforms: [ { deform, at?, create? }, … ] }一层可以挂好几条变形滤镜(PS 允许,例如先透视变形再操控变形)。at = 它在 filterFXList 里的下标(改现有的按它定位,/resolve 的 layerInfo.deforms[].at 就是它);create = 这条从零建、**追加**到列表末尾。应用次序 = 下标升序,渲染也按这个次序多遍叠加
新建变形滤镜{ deform: { … }, create: true }这一层本来没有变形滤镜时,整条 filterFX 条目从零写进 SoLd。透视要带齐 vertices / warped / quadIdx,操控每片要带齐 orig / def / indices(可选 boundary 作为网格边界路径)。放置四点由服务端取自该层的 Trnf,不从请求里读

响应字段

字段说明
modewarp(原地改点)· quiltWarp(拆分过,走变长重写)· deform(改现有变形滤镜)· deformCreate(新建了一条变形滤镜)
blocks[]需要替换的块:{ key, data:<base64> }。没变的块不会出现
ms耗时
响应(base64 已省略) 真实响应

{ "mode": "warp",
  "blocks": [ { "key": "PlLd", "data": "cGxjTAAAAAMkZGVl…" },
              { "key": "SoLd", "data": "c29MRAAAAAUAAAAB…" } ],
  "ms": 2 }
类型校验会拦住「能过 lint 但效果不对」的写法:变形滤镜层传 points(它没有 Bezier 网格)、普通层传 deform 又不带 create、已经有变形滤镜还带 create(一层只保留一组变形),都会 400。当前不是规则网格、又要改点数时,必须同时给出 xn / yn。

错误一律 400 + { error }(fragment 格式错、edit 缺失、控制点非法、未知变形类型、写回失败)。

bash

curl -s -X POST https://your-host/api/so/writeback \
  -H "X-Api-Key: $FULLPSD_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "fragment": { "layerName":"img", "kind":"SoLd", "psb":false,
                      "bounds":{"top":370,"left":461,"bottom":807,"right":746},
                      "blocks":[{"key":"SoLd","data":"…base64…"}] },
        "edit": { "points":[{"x":418,"y":417.9}], "xn":4, "yn":4,
                  "sliceX":[], "sliceY":[] } }'

POST/api/so/filters在调试台里打开 →

智能滤镜(挂在智能对象上的高斯模糊 / USM 锐化 / 添加杂色…)的只读列表;配套的 POST /api/so/filters/writeback 改开关、参数、混合选项、删除与换序。与 /resolve、/writeback 一样只传放置块片段,不传 PSD。

鉴权
API key 或会话
Content-Type
application/json
并发闸
不占

请求

端点请求体响应
/api/so/filtersfragment 或 { items:[fragment…] }(≤64)单条:{ style, filters:[…] };批量:{ items, failed, ok }(恒 200,逐条带 error)
/api/so/filters/writeback{ fragment, edits }{ blocks:[{ key, data }], count, changed, warnings }

edits 的形状

结构示意 结构示意

{
  "style":   { "enabled": true, "filterMaskEnable": false },
  "filters": [
    { "index": 0, "enabled": false },
    { "index": 1, "blendOptions": { "opacity": 60, "mode": "scrn" }, "values": { "Rds ": 8 } },
    { "index": 2, "remove": true }
  ],
  "order": [1, 0]
}

每条滤镜在列表里带 owner:变形滤镜(透视变形 714 / 操控变形 687)标 owner:"deform",任何针对它们的编辑一律 400 —— 那两种请走 /api/so/writeback 的 deform。写回有「前置探针 → 只改叶子 → 后置复核」三道纪律,别的字段变了一个就拒绝、不返回字节。片段协议只改描述符(PS 打开时按新参数重新求值)。整文件场景用 /api/doc/edit 的 setSmartFilters / addSmartFilter:改完 / 加完会按「变形栈 → 各条智能滤镜」把缓存栅格也重渲染一遍(栈里全是服务端认识的滤镜时)。查询串 /api/so/resolve?filters=1 可以顺带返回每个条目的 smartFilters,省一次往返。

POST/api/doc/new在调试台里打开 →

新建一份空白 PSD(与编辑器「文件 › 新建」同一个内核 core/psd-new.js)。拿到之后直接喂给 /api/doc/edit 往上加图层、文字、形状、画板。

鉴权
API key 或会话
Content-Type
application/json
并发闸
不占
响应
image/vnd.adobe.photoshop;元数据在 X-Doc-Meta

请求字段

字段类型默认说明
presetstring—预设名(如 默认 Photoshop 大小、电商主图、方形海报、桌面横幅…,全表见 /api/catalog 的 newDocument.presets)。与 width/height 同给时以 width/height 为准
width / heightinteger—1…30000;没有 preset 时必填
resolutionnumber72像素/英寸
modestringrgbrgb / gray / cmyk
depthinteger88 / 16
backgroundstringwhitewhite / black / transparent / color
backgroundColorstring—background:"color" 时的颜色,"#rrggbb"
namestring未标题-1下载文件名(不是图层名)
layerNamestring「背景」/「图层 1」唯一那一层的名字;透明背景默认「图层 1」
响应头 真实响应

HTTP/1.1 200 OK
Content-Type: image/vnd.adobe.photoshop
X-Doc-Meta: {"width":1080,"height":1080,"mode":"rgb","depth":8,"resolution":72,"background":"color","bytes":151392}
bash

curl -s -X POST https://your-host/api/doc/new \
  -H "X-Api-Key: $FULLPSD_KEY" -H "Content-Type: application/json" \
  -d '{"preset":"方形海报","background":"color","backgroundColor":"#ffeecc"}' -o new.psd

POST/api/doc/render在调试台里打开 →

按图层重新合成一张位图 —— 与编辑器画布同一套合成内核(图层样式、蒙版、调整层、剪贴、混合模式、画板底色与裁剪)。可以只出一块画板、一个区域,或临时隐藏几层再合成。

鉴权
API key 或会话
Content-Type
multipart/form-data
并发闸
占
响应
位图;元数据在 X-Doc-Meta({ width, height, x, y, artboard?, ms })
和 /api/doc/composite 的区别:composite 是「取」PS 存盘时拍的快照(快、与 PS 所见完全一致,但编辑之后可能是旧的);render 是「算」,看的是图层现在的样子。文字层:没改过的用文件里 PS 排好的栅格;/api/doc/edit 里改过内容 / 样式的文字层会按服务端字体重排重绘(见 服务端文字重绘),所以这里看到的是新字。

请求字段

字段类型默认说明
psdfile—必填
artboardstring—只出这块画板:画板组头的 index,或画板名(见 /api/doc/artboards)
regionstring(JSON)—{ "x", "y", "width", "height" } 文档坐标裁剪;与 artboard 互斥
hidestring(JSON)—[layerIndex…] 临时隐藏再合成(不改文件)
formatstringpngpng / jpg / webp / tiff
qualityinteger92有损格式质量
maxSizeinteger0长边上限,等比缩小不放大
background / flattenstring#ffffffJPG 或 flatten=true 时拍平透明的底色
响应头 真实响应(测试.psd,两块画板)

HTTP/1.1 200 OK
Content-Type: image/png
X-Doc-Meta: {"width":1271,"height":916,"x":1888,"y":92,"artboard":{"index":5,"name":"画板 2"},"ms":242}
bash

curl -s -X POST https://your-host/api/doc/render \
  -H "X-Api-Key: $FULLPSD_KEY" \
  -F psd=@design.psd -F artboard="画板 2" -F format=jpg -F maxSize=1200 -o artboard2.jpg

400 画板找不到 / 文档没有画板 / region 非法 · 413 画布像素数超上限 · 429 并发闸。

POST/api/doc/artboards在调试台里打开 →

列出文档里的画板;配套的 POST /api/doc/export-artboards 把每块画板各渲染一张图、打成 zip(PS 的「导出 › 画板至文件」)。

鉴权
API key 或会话
Content-Type
multipart/form-data
并发闸
/artboards 不占;/export-artboards 占
/api/doc/artboards 响应 真实响应

{"artboards":[
  {"index":2,"name":"画板 1","presetName":"iPhone X","rect":{"x":0,"y":0,"w":1125,"h":2436},"color":[255,255,255],"bgType":1},
  {"index":5,"name":"画板 2","presetName":"","rect":{"x":1888,"y":92,"w":1271,"h":916},"color":[255,255,255],"bgType":1}],
 "count":2}

index 是画板组头的图层下标(ops 里改画板用它,如 setArtboard);bgType:1 白 · 2 黑 · 3 透明 · 4 自定(color)。

/api/doc/export-artboards 请求字段

字段类型默认说明
psdfile—必填
formatstringpngpng / jpg / webp
artboardsstring(JSON)全部只导出这些:[index 或 名字…]
quality / maxSizeinteger—同 /api/doc/render
bash

curl -s -X POST https://your-host/api/doc/export-artboards \
  -H "X-Api-Key: $FULLPSD_KEY" -F psd=@design.psd -F format=jpg -o artboards.zip
# X-Doc-Meta: {"count":2,"files":[{"name":"画板 1.jpg","bytes":42862},{"name":"画板 2.jpg","bytes":25217}],"ms":285}

POST/api/fonts

字体库:把 PSD 用到、服务器上又没有的字体(方正 / 汉仪 / 品牌字体…)传上来,之后这把 key 的 /api/doc/edit 与 MCP edit_document 改文字时,服务端就用它重绘,不再用替代字体。配套 GET /api/fonts 列出、DELETE /api/fonts/{id} 删除。

档位
高级(功能 id fonts.library;单次 font 字段是 fonts.request,fontMap 是 fonts.map)。基础档 403 feature_not_in_plan
鉴权
仅 API key(字体库按 key 隔离:A key 传的字体 B key 看不到、也匹配不到);编辑器会话 403 —— 会话请在 /api/doc/edit 里直接带 font 字段(单次有效)
Content-Type
multipart/form-data
格式
TTF · OTF · TTC / OTC(拆成单款入库)· WOFF · WOFF2(服务端解成 TTF / OTF 入库)
字段类型说明
fontfile ×≤8必填,可多个
facesstring只收 TTC 里的这几款(逗号分隔的 PostScript 名)。TTC 拆开后每款都是完整字体(共享的字形表会各复制一份),一个 20MB 的 Noto CJK TTC 全收是 ~170MB —— 只要简中就写 faces=NotoSansCJKsc-Bold。拆开后超过剩余配额会直接 413 / 400,不会先占内存;/api/doc/edit 的单次 font 字段没法挑款,大 TTC 请先进字体库
上传 → 编辑 结构示意

curl -H "X-Api-Key: $KEY" -F font=@FZLanTingHei-B-GBK.ttf https://fullpsd.zaopod.com/api/fonts
# {"added":[{"id":"8355e11f39c96cfaf49f03f8","postScriptName":"FZLTZHK--GBK1-0","family":"FZLanTingHei-B-GBK",
#   "style":"Regular","size":100924,"originalName":"FZLanTingHei-B-GBK.ttf","uploadedAt":"…"}],"skipped":[],
#  "usage":{"count":1,"bytes":100924,"limits":{"fileMB":40,"count":100,"quotaMB":500,"perRequest":8}}}

# PSD 里的 PostScript 名和上传的一致 → 自动命中;不一致就用 output.fontMap 指过去
curl -H "X-Api-Key: $KEY" -F psd=@poster.psd -F 'ops=[{"op":"setText","index":2,"text":"限时八折"}]' \
     -F 'output={"fontMap":{"AidianFengYaHeiChangTi":"FZLTZHK--GBK1-0"}}' https://fullpsd.zaopod.com/api/doc/edit -o out.psd

curl -X DELETE -H "X-Api-Key: $KEY" https://fullpsd.zaopod.com/api/fonts/8355e11f39c96cfaf49f03f8   # 或用 PostScript 名

同 PostScript 名再传一次 = 替换旧版本(skipped[].reason 会写明);内容完全相同的文件去重。400 不是字体 / 结构损坏 / 缺必需表 · 403 会话凭证 · 404 删除时没这款 · 413 单文件 / 款数 / 总量超限(见环境变量 FULLPSD_USER_FONT_*)。

GET/api/catalog

能力目录:这套部署支持的全部 op(core + 服务端,含字段 schema、约束、示例)、滤镜(含参数表)、破坏性调整、调整图层种类、图层样式、混合模式、新建文档预设、选区写法。GET /openapi.json 从同一份注册表生成 OpenAPI 3.1 描述,可直接导入 Postman / Apifox / openapi-generator。

鉴权
不需要(只描述能力,不碰任何文件)
缓存
Cache-Control: public, max-age=300
字段说明
ops[]{ op, kind:"core"|"server", category, summary, structural, fields:[{ name, type, required, desc, enum?, default? }], notes?, example, aliasOf? }
opNames全部 op 名(含别名)
filters[]{ kind, label, group, params:[{ key, label, def, min, max, type?, options? }] }
adjustments破坏性调整:type → { label, accepts, lut, neutral }
adjustmentLayerKinds / layerEffectsaddAdjustmentLayer / addLayerEffect 可建的种类与字段
blendModes[{ key, label }]
newDocument{ modes, presets:[{ group, items:[{ name, width, height, resolution }] }], backgrounds }
selection / outputs / deformKinds选区写法、输出能力、支持的变形种类
bash

curl -s https://your-host/api/catalog | jq '.ops[] | select(.op=="applyFilter")'
curl -s https://your-host/openapi.json -o fullpsd-openapi.json

ops 参考

ops 是一个有序数组,每条形如 { "op": "setLayerProps", "index": 3, … }。两类 op 可以任意混排:

  • core op(core/psd-edit*.js)—— 纯格式知识:图层属性 / 文字 / 像素 / 蒙版 / 矢量 / 图层样式 / 调整与填充图层 / 智能对象 / 画板 / 锁定 / 图像资源。定位用 index(/api/doc/inspect 的 flat[].index,底 = 0)。
  • 服务端 op(server/server-ops.js + server/edit-ops.js)—— 需要渲染内核或重采样的:滤镜、调整、内容识别、画布 / 裁剪 / 图像大小 / 旋转 / 翻转、移动 / 复制图层、新建变形 / 画板、智能滤镜;以及换智能对象源图、变形写回。新扩展的这批用 index(或 target 写图层名)定位;老的 replaceSmartObject / deform / setSmartObjectDescriptor 用 target。
混排时的 index 口径:每条 op 都作用在「前面所有 op 执行完之后」的文件上。会改图层表的 op(deleteLayer / reorderLayers / createLayer / duplicateLayer / groupLayers / createArtboard / 普通层上的 createDeform…,下表标「改图层表」的)之后,后续 op 的 index 以新的图层表为准。拿不准就先跑一次 /api/doc/edit/plan:含服务端扩展 op 时它会在内存里真跑一遍,判定与真实执行完全一致。
扩展 op 里 number 型 target 指图层下标:新扩展的服务端 op(applyFilter、moveLayer 等)的 target: 3 与 index: 3 同义;只有老三条(replaceSmartObject / deform / setSmartObjectDescriptor)和 setSmartFilters 的数字 target 仍按「第几个智能对象」算,与 /api/render 保持一致。

全部 op 一览

共 78 条(core 58 · server 20)。机器可读版:GET /api/catalog。

op来自定位做什么
文档
addPatternscorepatterns把图案写进文档全局图案资源(Patt / Pat2 / Pat3),供图案填充层 / 图案叠加按 Idnt 引用
cropserverindex / target裁剪到矩形(画布 = 该矩形,图层整体平移)
flipCanvasserverindex / target水平 / 垂直翻转画布
resizeCanvasserverindex / target画布大小(只挪位置、不重采样);背景图层扩出来的部分用 extColor 填
resizeImageserverindex / target图像大小:整份文档重采样(像素层 lanczos3;文字改变换矩阵仍可编辑;智能对象改放置四点)
rotateCanvasserverindex / target旋转画布(任意角度,顺时针;新画布 = 外接矩形,90° 倍数无损)
几何
moveLayerserverindex / target平移图层(组 = 带上整组);文字仍可编辑、智能对象仍是智能对象、矢量路径与蒙版跟着走
像素
applyAdjustmentserverindex / target破坏性图像调整(直接改像素):曲线 / 色阶 / 色相饱和度 / 亮度对比度 / 黑白 / 色彩平衡 / 渐变映射…
applyFilterserverindex / target对普通像素图层应用滤镜(75 种:模糊 / 锐化 / 杂色 / 扭曲 / 风格化 / 像素化 / 渲染 / 其它 / 自动调整…)
clearPixelsserverindex / target清除选区里的像素(PS 的 Delete)
contentAwareFillserverindex / target内容识别填充:用图层其余像素补掉选区(去水印 / 去杂物)
fillPixelsserverindex / target用纯色填充选区(PS 的「编辑 › 填充」);空图层也能填(按选区长出像素)
rasterizeLayercoreindex栅格化:摘掉决定图层类型的附加块(文字 / 矢量 / 填充 / 智能对象),变成普通像素层
replaceLayerPixelscoreindex替换一个图层的像素(RGBA),可选同时更新整幅合成图
结构
addFrameLayercoreat新建 Photoshop 原生画框组(矩形或椭圆)
copyLayerBlockscorefrom把一层的若干附加信息块原样拷到另一层(已有同名块则替换)
createLayercoreat新建一个空像素图层(bounds 全 0,像素之后用 replaceLayerPixels 落地)
deleteLayercoreindex删除图层;指向组头或分隔记录时整组删除(或只拆外壳)
duplicateLayerserverindex / target复制图层 / 组 / 画板(字节级克隆,放在源正上方;智能对象为浅拷贝,与 PS 默认一致)
ensureLayerIdscore—给没有 lyid 的图层补一个唯一图层 id(链接组 / 图层复合 / 变量都按 lyid 对齐)
groupLayerscoreindex把若干图层包进一个新建的组(PS 的 ⌘G,支持不连续多选与组中组)
newGroupcoreat新建一个空组(分隔记录 + 组头两条空记录)
reorderLayerscoreorder按原始 index 数组重排图层;数组里省略的 index 即被删除
setFrameRectcoreindex修改已有原生画框边界并同步矢量路径
setLayerAnimMetacoreentries写各图层的帧动画记录(shmd 块里的 mlst / mdyn 子块)
setLayerCompsMetacoreentries写各图层的图层复合记录(shmd 块里的 cmls 子块)
setLinkGroupscoreindices链接 / 取消链接图层,或停用 / 启用链接(图像资源 1026 / 1072,按 lyid 对齐)
图层属性
renameLayercoreindexsetLayerProps 的别名
setBlendModecoreindexsetLayerProps 的别名
setClippingcoreindexsetLayerProps 的别名
setLayerPropscoreindex改图层名 / 可见性 / 不透明度 / 剪贴 / 混合模式(可一次改多项)
setOpacitycoreindexsetLayerProps 的别名
setVisiblecoreindexsetLayerProps 的别名
文字
addTextLayercoretext新建一个文字图层(以内置模板 TySh 为骨架,可设字号 / 颜色 / 字体 / 段落框)
renderTextserverindex / target按服务端字体重排重绘文字层栅格(setText / 改样式后会自动重绘,这条用于强制重绘,如新装了字体)
setTextcoreindex改文字图层的文字内容(可顺带换字体),并删除全局 Txt2
蒙版
addLayerMaskcoreindex给图层新建用户蒙版(通道 -2),纯色填充或给灰度位图
applyLayerMaskcoreindex应用蒙版:灰度乘进 alpha 后删除蒙版(停用的蒙版直接删、像素不变)
deleteLayerMaskcoreindex删除图层蒙版(蒙版段 + 通道同删)
replaceLayerMaskcoreindex整张换掉用户蒙版(-2)的像素与范围,保留其余蒙版属性(随图层变换用)
setLayerMaskPixelscoreindex改现有蒙版的像素(整张 / 局部矩形 / 连同 bounds),画笔的落地出口
setMaskPropscoreindex改图层蒙版的开关位与浓度 / 羽化(像素蒙版与矢量蒙版各一份)
矢量
addShapeLayercoresubpaths新建形状图层(SoCo 纯色填充 + vmsk 路径,可选 vstk 描边)
addVectorMaskcoreindex给没有矢量蒙版的图层新建矢量蒙版(vmsk)
setShapeContentcoreindex改形状 / 填充图层的填充类型与描边类型(属性面板「外观」两个下拉)
setShapeOrigincoreindex写 / 删形状层的实时形状属性(vogk:矩形 / 圆角矩形 / 椭圆的外框与四角半径)
setVectorMaskFlagscoreindex改矢量蒙版标志位(反相 / 不链接 / 停用)与初始填充规则
setVectorMaskPathcoreindex整份替换已有矢量蒙版(vmsk/vsms)的路径
setVectorStrokecoreindex改矢量描边参数(vstk 描述符里的宽度 / 颜色 / 端点 / 虚线等)
图层样式
addLayerEffectcoreindex给图层新增一条图层样式效果(没有样式块时新建 lfx2)
clearLayerEffectscoreindex整块摘掉图层样式(lfx2 / lmfx / lrFX),用于样式已烘进像素之后
removeLayerEffectcoreindex删除一条图层样式效果(删到一条不剩时删掉整个样式块)
setLayerEffectcoreindex启用 / 停用一条已有的图层样式效果(enab)
setLayerEffectsEnabledcoreindex图层样式整体开关(masterFXSwitch)
setLayerParamscoreindex按路径改已有图层样式 / 调整图层 / 填充图层块里的参数(可增删渐变色标)
setLayerStylecoreindex整份替换一个图层的图层样式(样式面板「点样式 = 应用」;effects:null = 清除样式)
调整 / 填充图层
addAdjustmentLayercorekind新建一个调整图层(无像素记录 + 对应调整块)
addFillLayercorekind新建填充图层:纯色(SoCo)/ 渐变(GdFl)/ 图案(PtFl),铺满画布
智能对象
addGallerySmartFiltercoreindex给智能对象追加一条「滤镜库」智能滤镜(SoLd.filterFX.filterFXList 末尾 = 最后应用,面板里显示在最上面)
addSmartFilterserverindex / target新增一条智能滤镜(非破坏,PS 里可双击改参数 / 关眼睛);普通层自动转智能对象,已有智能滤镜 = 追加
convertToSmartObjectcoreindex把图层转换为内嵌智能对象(写内嵌 PSD + PlLd/SoLd + 全局 lnk2)
createDeformserverindex / target新建透视变形 / 操控变形(PS 的「编辑 › 透视变形 / 操控变形」);普通图层自动转智能对象,已有变形 = 再叠一条 高级:deform.perspective / deform.puppet
deformservertarget智能对象网格 / 变形滤镜写回(warp、quiltWarp、透视变形、操控变形)(别名 setMesh) 高级:deform.warp / deform.perspective / deform.puppet
detachSmartObjectcoreindex把共享内嵌文件的智能对象拆成独立副本(复制内嵌文件、换新 id)
replaceSmartObjectservertarget换智能对象源图(按覆盖模式渲染进网格 / 变形,写回内嵌源图、缓存栅格、FEid 与缩略图)(别名 replaceSmartObjectImage) 高级:deform.warp
setSmartFilterEntrycoreindex改智能对象上的一条智能滤镜(SoLd.filterFX.filterFXList[at]):参数(Fltr)/ 混合选项 / 前景背景色 / 启用位
setSmartFiltersserverindex / target改智能对象上已有的智能滤镜(开关 / 参数 / 混合选项 / 删除 / 换序);变形滤镜请用 deform
setSmartObjectDescriptorservertarget改智能对象放置描述符(Idnt / placed / 原始尺寸 Sz)
setStackModecoreindex智能对象 › 堆栈模式:写进放置描述符(SoLd Type = 3「图像堆栈」+ imageStackPlugin 类;PlLd 的 placedLayerType 同步)
画板
createArtboardserverindex / target新建画板:给 indices 就把这些图层装进去(来自图层的画板),否则新建空画板放最上面
setArtboardcoreindex把组头设为画板 / 改画板矩形与背景,或取消画板身份
锁定 / 标签
setBlendOptionscoreindex改混合选项的高级混合 / 通道 / 混合颜色带(infx · clbl · tsly · lmgm · vmgm · brst · Blend If)
setFillOpacitycoreindex改填充不透明度(iOpa,面板上的「填充」)
setLayerColorLabelcoreindex改图层面板的颜色标签(lclr)
setLayerFlagscoreindex改图层锁定(lspf:锁透明像素 / 锁图像像素 / 锁位置 / 画板嵌套 / 全部锁定)
setLayerKnockoutcoreindex改混合选项里的「挖空」(knko)
图像资源
removeImageResourcecoreid删除一条图像资源(不存在则不改动)
setImageResourcecoreid新增或替换一条图像资源(8BIM 资源块)

选区写法(selection)

像素类扩展 op(applyFilter / applyAdjustment / contentAwareFill / fillPixels / clearPixels)都接受同一种选区描述,坐标一律是文档像素:

写法含义
{"type":"rect","x":10,"y":10,"width":200,"height":100}矩形;可加 feather(羽化半径,像素)与 antiAlias:false
{"type":"ellipse","x":0,"y":0,"width":300,"height":300,"feather":8}椭圆
{"type":"polygon","points":[[0,0],[100,20],[40,90]]}多边形(套索)
{"type":"all"}全选
[ 形状1, { …形状2, "mode":"subtract" } ]多个形状组合,第二个起按各自 mode(add 默认 / subtract / intersect / xor)
{"shapes":[…],"invert":true}组合后反选

省略 selection = 作用于整层。结果按选区覆盖率混回(羽化边缘是渐变的),与编辑器里「先做选区再执行」同一口径。

示例:只把画面下三分之一做动感模糊,再在右上角去掉一块水印

[
  { "op": "applyFilter", "index": 0, "filter": "motionBlur", "params": { "angle": 0, "distance": 30 },
    "selection": { "type": "rect", "x": 0, "y": 800, "width": 1200, "height": 400, "feather": 20 } },
  { "op": "contentAwareFill", "index": 0,
    "selection": { "type": "rect", "x": 1020, "y": 30, "width": 150, "height": 60 } }
]
op来自定位做什么
setLayerPropscoreindex改图层名 / 可见 / 不透明度 / 剪贴 / 混合模式
renameLayercoreindexsetLayerProps 的别名,只带 name
setVisiblecoreindex别名,只带 visible
setOpacitycoreindex别名,只带 opacity
setBlendModecoreindex别名,只带 blendKey
setClippingcoreindex别名,只带 clipping
setTextcoreindex改文字层内容(可换字体)
deleteLayercoreindex删图层 / 删分组
reorderLayerscoreorder重排,顺带批量删除
replaceLayerPixelscoreindex换整层像素
setImageResourcecoreid写入 / 替换图像资源块
removeImageResourcecoreid删除图像资源块
replaceSmartObjectservertarget换智能对象源图(别名 replaceSmartObjectImage)
deformservertarget网格 / 变形写回(别名 setMesh)

服务端 op 的 target 怎么写

写法含义
省略 / null第 0 个智能对象。文件里没有智能对象时 400
0 或 "0"智能对象序号(与 /api/render 的 target 一致)
"img"图层名(unicodeName 优先)。必须唯一,重名会 400 并告诉你重在第几层;图层树里找不到时,会再在智能对象名单里按放置块里的名字找一次
{"layerIndex":1}文档图层下标(/api/doc/inspect 口径)
{"smartObjectIndex":0}智能对象序号(别名 smartObject / soIndex)
{"name":"img"}同字符串写法

解析结果里两个下标同时给出(layerIndex + smartObjectIndex),互相打通用的是「名字 + 包围盒」打分;名字对不上就不认,避免张冠李戴。

core op 逐个说明

setLayerProps(及五个别名)

字段类型约束
indexinteger必填,越界 400
namestring非空、≤255 字符。同时写 luni 与 Pascal 名
unicodeNamestring非空。只写 luni(PS 显示的就是它)
visibleboolean—
opacityinteger0…255(255 = 不透明)
clippingboolean | 0 | 1—
blendKeystring恰好 4 个字符且在白名单里

至少给一个可改字段,否则 400。附加区被降级成原样字节(extraRaw)的图层不允许改名 —— 改了会被静默丢掉,所以直接拒绝。用 name 写非 ASCII 名字时,Pascal 名那份会按 ? 落地并给一条 warning,这是正常的(PS 读 luni)。

混合模式键(白名单)
键模式键模式键模式键模式
pass穿透(仅图层组)norm正常diss溶解dark变暗
mul 正片叠底idiv颜色加深lbrn线性加深dkCl深色
lite变亮scrn滤色div 颜色减淡lddg线性减淡
lgCl浅色over叠加sLit柔光hLit强光
vLit亮光lLit线性光pLit点光hMix实色混合
diff差值smud排除fsub减去fdiv划分
hue 色相sat 饱和度colr颜色lum 明度

不足 4 字符的键要补空格:"mul "、"div "、"hue "、"sat "、"lum "。写进白名单以外的键,Photoshop 会当文件损坏,所以这里直接 400。

示例

[
  { "op": "setLayerProps", "index": 1, "name": "新图案", "opacity": 200, "blendKey": "mul " },
  { "op": "renameLayer",   "index": 2, "name": "新标题" },
  { "op": "setVisible",    "index": 0, "visible": false },
  { "op": "setClipping",   "index": 2, "clipping": true }
]

setText

字段类型说明
indexinteger必须是文字图层(有 TySh 块),否则 400
textstring必填
opts.fontstring换字体。走 setEngineFont,会同时改 EngineData 里的两份 FontSet 副本(ResourceDict 与 DocumentResources)—— 只改一份 PS 双击内嵌文档会崩
opts.keepTxt2boolean保留全局 Txt2。默认删掉它
为什么要删 Txt2:PS CC 2015+ 优先读全局 Txt2(里面是旧文字与旧字形),留着它 PS 就会继续显示旧文字。删掉之后 PS 打开会提示「某些文本图层可能需要更新…」,点「更新」即可 —— 这是预期行为。若显式 keepTxt2:true 且 Txt2 仍在,服务端会给一条 warning;非 keepTxt2 的情况下 Txt2 没删掉,会直接报错、不出文件。

结果里额外带 textChanged、txt2Removed、textBefore、textAfter。两份 FontSet 对不上时会给 warning(条目数或字体名不同)。

示例

[ { "op": "setText", "index": 2, "text": "限时 8 折",
    "opts": { "font": "SourceHanSansSC-Bold" } } ]

deleteLayer

字段类型说明
indexinteger必填
keepChildrenboolean删分组时只拆外壳、保留子图层

删分组默认连内容一起删(分组头 + 内容 + 分隔层整段)。会删光全部图层时直接 400。16/32 位文档的图层记录在 Lr16/Lr32 全局块里,暂不支持结构性增删改(改名 / 属性 / 文字仍可用)。

reorderLayers

字段类型说明
orderinteger[]原始图层 index 的新顺序(底 = 0)。省略某个 index 就等于删除它

校验:不能越界、不能重复、不能为空、分组边界必须自洽。删掉文字图层但全局 Txt2 还在时会给 warning(建议同一批里再跑一次 setText)。

示例:把第 2 层挪到底,并删掉第 0 层

[ { "op": "reorderLayers", "order": [2, 1] } ]

replaceLayerPixels

字段类型说明
indexinteger必填
image引用上传的图片(见图片引用写法)。服务端用 sharp 解成 RGBA
rgbaobject直接给 { data, width, height },data 长度必须等于 width*height*4
boundsobject可选。四个整数,尺寸必须与图片一致。省略时以原图层左上角为锚点
mergedobject可选。整幅新合成图 RGBA(长度必须等于画布 W*H*4)。不传就保留原合成图,只改这一层

只支持 8 位 / 16 位文档。解出来不是 4 通道、或像素数超过 FULLPSD_DOC_MAX_PIXELS 会 400 / 413。结果里带 compositeUpdated 说明合成图有没有跟着重建。

示例

[ { "op": "replaceLayerPixels", "index": 0,
    "image": { "file": "image", "index": 0 } } ]

setImageResource / removeImageResource

字段类型说明
idinteger0…65535。常见:1005 分辨率、1036 缩略图、1060 XMP
datastring | number[]仅 setImageResource:base64 字符串(接受 data: 前缀)或字节数组

资源已存在就替换、不存在就新增。删除不存在的资源不算错误,changed 为 false。

服务端 op 逐个说明

replaceSmartObject(别名 replaceSmartObjectImage)

字段类型说明
target见上必须解析到智能对象,否则 400
image引用必填,见图片引用写法
fitobject覆盖模式,默认 {"mode":"cover"}。字段同 /api/render
meshobject顺带改网格:{points,xn,yn,sliceX,sliceY} 或 {splits:{u,v}}。变形滤镜层不接受
sourceName / filenamestring写进 PSD 的内嵌源图文件名;不给就用上传文件名
与 /api/render 逐字节相同:这个 op 复用的是 pipeline.js 抽出来的同一段 preparePsdRender + writePsdOutput,所以同参数下产物与 /api/render(output.format=psd)逐字节一致 —— 这是结构性保证,不是巧合,server/test-edit-api.js 里有逐字节断言。

结果 meta:target、warpWritten、sourceReplaced、filterCacheUpdated、deformKind、bytes。三个「是否成功」字段可能是 true / null(不适用)/ 失败说明字符串 —— 服务端如实上报而不是假装成功。

deform(别名 setMesh)

字段类型说明
target见上必须解析到智能对象
points + xn/yn + sliceX/sliceY—Bezier 网格写回
deformobject变形滤镜写回,见变形能力

内部先把文件切成 fragment,走与 /api/so/writeback 完全相同的那段代码,再 replacePlacedBlocks 拼回 —— 与「片段模式 + 客户端拼回」的产物逐字节相同。结果 meta:target、mode、blocks[].bytes、bytesDelta。

示例:换图 + 拆一条经线

[
  { "op": "replaceSmartObject", "target": { "smartObjectIndex": 0 },
    "image": { "file": "image", "index": 0 },
    "fit": { "mode": "contain", "alignY": 0, "scale": 0.9 },
    "mesh": { "splits": { "u": [0.5] } },
    "sourceName": "design-v2.png" }
]

服务端扩展 op 逐个说明

下面这批与编辑器的菜单命令一一对应,算法用的是编辑器同一份内核(滤镜注册表、调整、内容识别、选区、合成、变形),所以同参数下服务端结果与编辑器画布一致。产物同样过守门;改了像素 / 几何的,收尾时默认按图层重建合成图快照(见 output.recomposite)。

applyFilter 像素

对普通像素图层应用滤镜(75 种:模糊 / 锐化 / 杂色 / 扭曲 / 风格化 / 像素化 / 渲染 / 其它 / 自动调整…)

字段类型必填说明
indexinteger—图层下标(/api/doc/inspect 的 flat[].index,底 = 0);与 target 二选一
targetinteger|string|object—图层名(须唯一)/ 下标 / { layerIndex } / { name }
filterstring是滤镜名(GET /api/catalog 的 filters[].kind,如 gaussianBlur / unsharpMask / motionBlur / mosaic)
paramsobject—滤镜参数;缺省项用默认值;不认识的键会 400
selectionobject|array—选区(文档坐标):{ type:"rect"|"ellipse", x, y, width, height, feather? } | { type:"polygon", points:[[x,y],…] } | { type:"all" };数组 = 多个形状按各自 mode(add/subtract/intersect/xor)组合;{ shapes:[…], invert:true } = 反选。省略 = 整层
fadenumber—渐隐 0…100(PS 的「编辑 › 渐隐」不透明度),默认 100
foregroundstring|array—前景色(云彩 / 纤维等用到前景背景色的滤镜)
backgroundstring|array—背景色
expandboolean|integer—空间类滤镜自动往外补边(默认按半径 ×3);false = 不补,整数 = 指定像素

只作用于 raster 图层;文字 / 智能对象 / 形状先 rasterizeLayer。智能滤镜(非破坏)暂未开放。

示例

[
  {
    "op": "applyFilter",
    "index": 1,
    "filter": "gaussianBlur",
    "params": {
      "radius": 4
    }
  }
]

applyAdjustment 像素

破坏性图像调整(直接改像素):曲线 / 色阶 / 色相饱和度 / 亮度对比度 / 黑白 / 色彩平衡 / 渐变映射…

字段类型必填说明
indexinteger—图层下标(/api/doc/inspect 的 flat[].index,底 = 0);与 target 二选一
targetinteger|string|object—图层名(须唯一)/ 下标 / { layerIndex } / { name }
adjustmentobject是{ type, …参数 };type 见 GET /api/catalog 的 adjustments(curves / levels / hueSaturation / brightnessContrast / …)
selectionobject|array—选区(文档坐标):{ type:"rect"|"ellipse", x, y, width, height, feather? } | { type:"polygon", points:[[x,y],…] } | { type:"all" };数组 = 多个形状按各自 mode(add/subtract/intersect/xor)组合;{ shapes:[…], invert:true } = 反选。省略 = 整层
fadenumber—渐隐 0…100

要非破坏的调整图层请用 core op addAdjustmentLayer。

示例

[
  {
    "op": "applyAdjustment",
    "index": 1,
    "adjustment": {
      "type": "brightnessContrast",
      "brightness": 20,
      "contrast": 10
    }
  }
]

contentAwareFill 像素

内容识别填充:用图层其余像素补掉选区(去水印 / 去杂物)

字段类型必填说明
indexinteger—图层下标(/api/doc/inspect 的 flat[].index,底 = 0);与 target 二选一
targetinteger|string|object—图层名(须唯一)/ 下标 / { layerIndex } / { name }
selectionobject|array是要填掉的区域(必填)
patchinteger—补丁半径 1…6,默认 3
samplesinteger—每像素候选样本数,默认 120
seedinteger—随机种子(同种子结果可复现),默认 1
示例

[
  {
    "op": "contentAwareFill",
    "index": 0,
    "selection": {
      "type": "rect",
      "x": 100,
      "y": 100,
      "width": 60,
      "height": 40
    }
  }
]

fillPixels 像素

用纯色填充选区(PS 的「编辑 › 填充」);空图层也能填(按选区长出像素)

字段类型必填说明
indexinteger—图层下标(/api/doc/inspect 的 flat[].index,底 = 0);与 target 二选一
targetinteger|string|object—图层名(须唯一)/ 下标 / { layerIndex } / { name }
colorstring|array是"#rrggbb" 或 [r,g,b]
selectionobject|array—选区(文档坐标):{ type:"rect"|"ellipse", x, y, width, height, feather? } | { type:"polygon", points:[[x,y],…] } | { type:"all" };数组 = 多个形状按各自 mode(add/subtract/intersect/xor)组合;{ shapes:[…], invert:true } = 反选。省略 = 整层
opacitynumber—0…100,默认 100
preserveTransparencyboolean—保留透明区域(只染已有像素)
示例

[
  {
    "op": "fillPixels",
    "index": 1,
    "color": "#ff3366",
    "selection": {
      "type": "ellipse",
      "x": 50,
      "y": 50,
      "width": 200,
      "height": 120
    }
  }
]

clearPixels 像素

清除选区里的像素(PS 的 Delete)

字段类型必填说明
indexinteger—图层下标(/api/doc/inspect 的 flat[].index,底 = 0);与 target 二选一
targetinteger|string|object—图层名(须唯一)/ 下标 / { layerIndex } / { name }
selectionobject|array是要清除的区域(必填)
示例

[
  {
    "op": "clearPixels",
    "index": 1,
    "selection": {
      "type": "rect",
      "x": 0,
      "y": 0,
      "width": 100,
      "height": 100
    }
  }
]

moveLayer 几何

平移图层(组 = 带上整组);文字仍可编辑、智能对象仍是智能对象、矢量路径与蒙版跟着走

字段类型必填说明
indexinteger—图层下标(/api/doc/inspect 的 flat[].index,底 = 0);与 target 二选一
targetinteger|string|object—图层名(须唯一)/ 下标 / { layerIndex } / { name }
indicesinteger[]—一次平移多个图层 / 组
dxinteger是水平位移(像素,右为正)
dyinteger是垂直位移(像素,下为正)
示例

[
  {
    "op": "moveLayer",
    "index": 1,
    "dx": 40,
    "dy": -20
  }
]

duplicateLayer 结构 改图层表

复制图层 / 组 / 画板(字节级克隆,放在源正上方;智能对象为浅拷贝,与 PS 默认一致)

字段类型必填说明
indexinteger—图层下标(/api/doc/inspect 的 flat[].index,底 = 0);与 target 二选一
targetinteger|string|object—图层名(须唯一)/ 下标 / { layerIndex } / { name }
namestring—副本名,默认「原名 拷贝」
dxinteger—副本平移(像素)
dyinteger—副本平移

给组头 index = 连同全部成员整段复制。复制画板且没给 dx/dy 时,副本放到原画板右侧 100 像素处,画布不够自动撑大。副本整段插在源(组)正上方,其上所有图层下标相应后移。

示例

[
  {
    "op": "duplicateLayer",
    "index": 1,
    "dx": 30,
    "dy": 30
  }
]

resizeCanvas 文档

画布大小(只挪位置、不重采样);背景图层扩出来的部分用 extColor 填

字段类型必填说明
widthinteger是新宽(像素,1…30000)
heightinteger是新高
anchorstring|integer—定位:top-left / top / top-right / left / center / right / bottom-left / bottom / bottom-right(或 0…8),默认 center
extColorstring|array—画布扩展颜色(背景图层用),默认白
示例

[
  {
    "op": "resizeCanvas",
    "width": 1600,
    "height": 1200,
    "anchor": "center"
  }
]

crop 文档

裁剪到矩形(画布 = 该矩形,图层整体平移)

字段类型必填说明
xinteger是裁剪框左
yinteger是裁剪框上
widthinteger是宽
heightinteger是高
deleteCroppedboolean—true = 普通像素层裁掉画布外的像素(PS 的「删除裁剪的像素」);默认 false 保留
示例

[
  {
    "op": "crop",
    "x": 100,
    "y": 80,
    "width": 800,
    "height": 600
  }
]

resizeImage 文档

图像大小:整份文档重采样(像素层 lanczos3;文字改变换矩阵仍可编辑;智能对象改放置四点)

字段类型必填说明
widthinteger—新宽;只给一边 = 等比
heightinteger—新高
scalenumber—整体倍率(与 width/height 二选一)
resolutionnumber—新分辨率(像素/英寸),写入资源 1005
resamplestring—重采样:lanczos3(默认)/ bicubic / bilinear / nearest
可选:lanczos3 bicubic bilinear nearest
scaleStylesboolean—PS 的「缩放样式」:图层样式里的像素尺寸、图案缩放、矢量描边宽度按比例缩放(默认 true)
默认 true

非等比缩放时样式按面积比例的平方根缩放(与 PS 同口径);实时形状的尺寸与圆角、切片矩形同步更新。

示例

[
  {
    "op": "resizeImage",
    "width": 800
  }
]

rotateCanvas 文档

旋转画布(任意角度,顺时针;新画布 = 外接矩形,90° 倍数无损)

字段类型必填说明
anglenumber是角度(度,顺时针)
extColorstring|array—背景图层露出部分的颜色,默认白
示例

[
  {
    "op": "rotateCanvas",
    "angle": 90
  }
]

flipCanvas 文档

水平 / 垂直翻转画布

字段类型必填说明
axisstring是horizontal / vertical
可选:horizontal vertical
示例

[
  {
    "op": "flipCanvas",
    "axis": "horizontal"
  }
]

createDeform 智能对象 改图层表 高级

新建透视变形 / 操控变形(PS 的「编辑 › 透视变形 / 操控变形」);普通图层自动转智能对象,已有变形 = 再叠一条

档位:需 deform.perspective / deform.puppet(按 kind:perspectiveWarp → deform.perspective,puppetWarp → deform.puppet),见 档位与权限。

字段类型必填说明
indexinteger—图层下标(/api/doc/inspect 的 flat[].index,底 = 0);与 target 二选一
targetinteger|string|object—图层名(须唯一)/ 下标 / { layerIndex } / { name }
kindstring是perspectiveWarp(透视)/ puppetWarp(操控)
可选:perspectiveWarp puppetWarp
gridobject—透视:{ cols, rows } 面的行列数 1…16,默认 1×1
cornersarray—透视:把放置四角拉到这 4 点 [[x,y]×4](左上 / 右上 / 右下 / 左下,文档坐标)
pinsarray—操控:[{ from:[x,y], to?:[x,y], rotate?:度 }],from 吸附到最近的网格顶点
modestring—操控:rigid(刚性,默认)/ similarity(正常)/ affine(扭曲)
可选:rigid similarity affine
cellinteger—操控:网格间距(源图像素,越小越密)
expandinteger—操控:网格外扩格数 0…8,默认 1

写完放置块后会按新变形重渲染缓存栅格;之后可用 deform op(target 同一层)继续改顶点。

示例

[
  {
    "op": "createDeform",
    "index": 1,
    "kind": "perspectiveWarp",
    "corners": [
      [
        100,
        100
      ],
      [
        500,
        130
      ],
      [
        480,
        420
      ],
      [
        120,
        400
      ]
    ]
  }
]

addSmartFilter 智能对象 改图层表

新增一条智能滤镜(非破坏,PS 里可双击改参数 / 关眼睛);普通层自动转智能对象,已有智能滤镜 = 追加

字段类型必填说明
indexinteger—图层下标(/api/doc/inspect 的 flat[].index,底 = 0);与 target 二选一
targetinteger|string|object—图层名(须唯一)/ 下标 / { layerIndex } / { name }
filterstring是滤镜名
可选:gaussianBlur boxBlur blur blurMore motionBlur radialBlur unsharpMask sharpen sharpenMore sharpenEdges addNoise median despeckle dustScratches highPass mosaic findEdges solarize emboss maximum minimum twirl pinch spherize cameraRaw lensCorrection
paramsobject—参数,与 applyFilter 同名同范围(见 get_catalog section=filters)
opacitynumber—滤镜混合选项的不透明度 0…100,默认 100
modestring—滤镜混合模式,默认 normal(预览只精确支持 normal)
enabledboolean—false = 挂上但先关着

写完描述符后按「变形栈 → 全部智能滤镜」重渲染缓存栅格;栈里有服务端不认识的滤镜时只在现有缓存上叠加这一条。cameraRaw / lensCorrection 是 FullPsd 私有条目(PS 里只显示缓存像素、不能在 PS 里再调);液化智能滤镜只能在编辑器里画。

示例

[
  {
    "op": "addSmartFilter",
    "index": 1,
    "filter": "gaussianBlur",
    "params": {
      "radius": 6
    },
    "opacity": 80
  }
]

setSmartFilters 智能对象

改智能对象上已有的智能滤镜(开关 / 参数 / 混合选项 / 删除 / 换序);变形滤镜请用 deform

字段类型必填说明
targetinteger|string|object—智能对象序号 / 图层名 / { layerIndex }(number = 智能对象序号,与老 op 一致)
indexinteger—图层下标(与 target 二选一)
editsobject是{ style?:{ enabled?, filterMaskEnable?, … }, filters?:[{ index, enabled?, blendOptions?:{ opacity?, mode? }, values?:{ "参数路径": 值 }, remove? }], order?:[原下标…] };可先用 POST /api/so/filters 或 MCP 的 get_smart_filters 看现有列表

改完按新描述符重渲染缓存栅格(栈里全是服务端认识的滤镜时);新增请用 addSmartFilter。

示例

[
  {
    "op": "setSmartFilters",
    "target": 0,
    "edits": {
      "filters": [
        {
          "index": 0,
          "enabled": false
        }
      ]
    }
  }
]

renderText 文字

按服务端字体重排重绘文字层栅格(setText / 改样式后会自动重绘,这条用于强制重绘,如新装了字体)

字段类型必填说明
indicesinteger[]—要重绘的文字层;省略 = 全部文字层
indexinteger—单个文字层

字体按 PostScript 名在服务端字体目录里找(server/fonts/、FULLPSD_FONT_DIRS、系统字体目录),缺字体用替代字体并给 warning;PS 打开时仍按它自己的字体重排。

示例

[
  {
    "op": "renderText",
    "indices": [
      2
    ]
  }
]

createArtboard 画板 改图层表

新建画板:给 indices 就把这些图层装进去(来自图层的画板),否则新建空画板放最上面

字段类型必填说明
namestring—画板名,默认「画板 N」
rectobject—{ x, y, width, height } 或 { left, top, right, bottom };给了 indices 时可省略(= 图层外接框)
indicesinteger[]—要装进画板的图层 / 组
backgroundTypeinteger|string—1 白 / 2 黑 / 3 透明 / 4 自定(white / black / transparent / custom)
默认 1
colorstring|array—backgroundType=4 时的底色
presetNamestring—画板预设名
autoSizeCanvasboolean—画板超出画布时撑大画布(默认 true)
示例

[
  {
    "op": "createArtboard",
    "name": "手机",
    "rect": {
      "x": 0,
      "y": 0,
      "width": 1125,
      "height": 2436
    },
    "backgroundType": "white"
  }
]

全部 op 参数表(自动生成)

由 tools/gen-api-docs.js 从运行时注册表生成(与 GET /api/catalog 同源),示例都在真实文件上跑通过。上面手写的几条讲了背景与坑,这里是字段级的全集。

addFrameLayer — 新建 Photoshop 原生画框组(矩形或椭圆)
字段类型必填说明
atinteger—插入位置,默认图层栈顶部;创建分隔记录和组头两条记录
namestring—图框名称,默认自动编号
rectobject是{x,y,w,h} 文档像素;坐标可负,宽高必须为正有限数
shapestring—矩形或椭圆,默认rect
可选:rect ellipse

仅验证RGB/8新建。返回index/headerIndex和dividerIndex。内容需作为组内子图层,clipping=0。

示例

[
  {
    "op": "addFrameLayer",
    "name": "原生图框",
    "rect": {
      "x": 20,
      "y": 30,
      "w": 200,
      "h": 150
    },
    "shape": "rect"
  }
]
setFrameRect — 修改已有原生画框边界并同步矢量路径
字段类型必填说明
indexinteger是具有frgb的画框组头下标
rectobject是{x,y,w,h} 文档像素;同步frgb/vsms/vogk并移除过期派生蒙版缓存

只调整图框边界,不缩放或移动组内内容。自定路径按当前矩形比例变换。

示例前置(让示例能跑通的准备步骤):[{"op":"addFrameLayer","at":6,"name":"原生图框","rect":{"x":20,"y":30,"w":200,"h":150}}]

示例

[
  {
    "op": "setFrameRect",
    "index": 7,
    "rect": {
      "x": 25,
      "y": 35,
      "w": 240,
      "h": 180
    }
  }
]
setLayerProps — 改图层名 / 可见性 / 不透明度 / 剪贴 / 混合模式(可一次改多项)
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1)
namestring—新图层名,非空、≤255 字符;同时写 luni(真名)与 Pascal 名(非 ASCII 落地为 ?)
unicodeNamestring—只改 luni 里的 Unicode 名(非空),不动 Pascal 名
visibleboolean—是否可见
opacityinteger—不透明度 0…255(255 = 不透明)
clippingboolean|integer—剪贴蒙版:布尔值或 0/1
blendKeystring—图层混合模式 4 字符键(不足补空格,如 'mul ')
可选:pass norm diss dark mul idiv lbrn dkCl lite scrn div lddg lgCl over sLit hLit vLit lLit pLit hMix diff smud fsub fdiv hue sat colr lum

name/unicodeName/visible/opacity/clipping/blendKey 至少给一个;附加区解析已降级(extraRaw)的图层拒绝改名。

示例

[
  {
    "op": "setLayerProps",
    "index": 1,
    "name": "标题",
    "opacity": 200,
    "blendKey": "mul "
  }
]
renameLayer — setLayerProps 的别名
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1)
namestring是新图层名,非空、≤255 字符
示例

[
  {
    "op": "renameLayer",
    "index": 1,
    "name": "主标题"
  }
]
setVisible — setLayerProps 的别名
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1)
visibleboolean是是否可见
示例

[
  {
    "op": "setVisible",
    "index": 1,
    "visible": false
  }
]
setOpacity — setLayerProps 的别名
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1)
opacityinteger是不透明度 0…255(255 = 不透明)
示例

[
  {
    "op": "setOpacity",
    "index": 1,
    "opacity": 128
  }
]
setBlendMode — setLayerProps 的别名
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1)
blendKeystring是图层混合模式 4 字符键(不足补空格,如 'mul ')
可选:pass norm diss dark mul idiv lbrn dkCl lite scrn div lddg lgCl over sLit hLit vLit lLit pLit hMix diff smud fsub fdiv hue sat colr lum
示例

[
  {
    "op": "setBlendMode",
    "index": 1,
    "blendKey": "scrn"
  }
]
setClipping — setLayerProps 的别名
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1)
clippingboolean|integer是剪贴蒙版:布尔值或 0/1
示例

[
  {
    "op": "setClipping",
    "index": 1,
    "clipping": true
  }
]
setText — 改文字图层的文字内容(可顺带换字体),并删除全局 Txt2
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1),必须是文字图层(带 TySh 块)
textstring是新文字内容(可为空串)
optsobject—可选:{ font?:PostScript 字体名(同步改两份 FontSet), keepTxt2?:boolean(保留全局 Txt2,PS 可能仍显示旧字), orientation?:'horizontal'|'vertical', antiAlias?, bounds? }

默认删全局 Txt2(否则 PS 优先读 Txt2 显示旧文字);只改内容与 run 长度,不改字号 / 颜色。

示例

[
  {
    "op": "setText",
    "index": 1,
    "text": "新的标题文字"
  }
]
addTextLayer — 新建一个文字图层(以内置模板 TySh 为骨架,可设字号 / 颜色 / 字体 / 段落框)
字段类型必填说明
textstring是文字内容,非空、≤30000 字符
atinteger—新图层在「底=0」序里的插入下标,0…图层数;默认 = 图层数(最上面)
namestring—图层名,非空、≤255 字符
默认 "文字首行(前 60 字)"
xnumber—文字基线原点的文档 x 坐标(段落文字为框左上角)
默认 0
ynumber—文字基线原点的文档 y 坐标
默认 0
orientationstring—排版方向
可选:horizontal vertical
fontSizenumber—字号,0 < 值 ≤ 5000
colorstring|array—文字颜色:'#rrggbb' / '#rgb' / [r,g,b](0…255)
boxarray—段落文字框 [宽, 高],正数;给了就是段落文字
fontstring—PostScript 字体名(如 ArialMT),非空
opacityinteger—不透明度 0…255(255 = 不透明)
visibleboolean—是否可见

新层继承模板的字体与排版默认值;新建时无像素(bounds 全 0),由 PS 按 EngineData 排版;会删除全局 Txt2。传 at2 会被拒绝(位置用 x/y)。

示例

[
  {
    "op": "addTextLayer",
    "text": "你好世界",
    "x": 200,
    "y": 300,
    "fontSize": 48,
    "color": "#222222"
  }
]
deleteLayer — 删除图层;指向组头或分隔记录时整组删除(或只拆外壳)
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1)
keepChildrenboolean—index 是组头 / 分隔记录时:true = 只删组头+分隔两条记录、成员升一级;false = 整组连根删
默认 false

不能删光全部图层;16/32 位文档(Lr16/Lr32)不支持;删掉文字层但保留全局 Txt2 时给 warning。

示例

[
  {
    "op": "deleteLayer",
    "index": 4
  }
]
reorderLayers — 按原始 index 数组重排图层;数组里省略的 index 即被删除
字段类型必填说明
orderarray是原始图层 index 组成的新顺序(底→顶),元素唯一、在 0…图层数-1 内;非空;省略的 index 表示删除

不能拆散分组:组头与其分隔记录必须同去同留、配对关系不变;16/32 位文档不支持。

示例

[
  {
    "op": "reorderLayers",
    "order": [
      3,
      4,
      5,
      0,
      1,
      2
    ]
  }
]
groupLayers — 把若干图层包进一个新建的组(PS 的 ⌘G,支持不连续多选与组中组)
字段类型必填说明
indicesarray—要包进组的图层 index 数组(底=0);选中组头(groupType 1)= 带上整个组;不能选分隔记录
indexinteger—单个图层(旧口径);与 indices 二选一
namestring—图层名,非空、≤255 字符
默认 "组"

indices / index 必须给一个;新组插在最顶层成员原来的位置,组头混合模式 pass。只识别 groupType=1(展开)的组头会带整组 —— 折叠组头(groupType=2)会被当成单条记录处理。

示例

[
  {
    "op": "groupLayers",
    "indices": [
      1
    ],
    "name": "新组"
  }
]
newGroup — 新建一个空组(分隔记录 + 组头两条空记录)
字段类型必填说明
atinteger—插入下标,0…图层数;默认最上层;分隔落在 at、组头在 at+1
namestring—图层名,非空、≤255 字符
默认 "组 N(N = 现有组头数 + 1)"
示例

[
  {
    "op": "newGroup",
    "at": 6,
    "name": "空组"
  }
]
createLayer — 新建一个空像素图层(bounds 全 0,像素之后用 replaceLayerPixels 落地)
字段类型必填说明
atinteger—新图层在「底=0」序里的插入下标,0…图层数;默认 = 图层数(最上面)
namestring—图层名,非空、≤255 字符
默认 "图层 N(N = 图层数 + 1)"
visibleboolean—是否可见
示例

[
  {
    "op": "createLayer",
    "name": "新图层"
  }
]
replaceLayerPixels — 替换一个图层的像素(RGBA),可选同时更新整幅合成图
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1)
rgbaobject是{ data:字节(长度 = width×height×4,非预乘 RGBA), width, height };HTTP 可省略 rgba、改传 image(上传文件引用 { file:'image', index } / 文件名 / data URL / 裸 base64),服务端用 sharp 解码成 RGBA
boundsobject—{ top, left, bottom, right } 整数;尺寸必须等于 rgba 的宽高;默认 = 图层原左上角 + rgba 尺寸
mergedobject—{ data:字节 } 整幅合成图 RGBA(长度 = 画布宽×高×4);不给则原样保留旧合成图
optsobject—透传给 rebuildPsdReplacingLayerPixels 的选项(premultiplied / sourcePatch / filterEffectsPatch / maskOriginDelta 等,一般不用)

只支持 8/16 位文档;不给 merged 时缩略图 / 合成图仍是旧的。

示例

[
  {
    "op": "replaceLayerPixels",
    "index": 1,
    "rgba": {
      "data": [
        255,
        0,
        0,
        255,
        0,
        255,
        0,
        255,
        0,
        0,
        255,
        255,
        255,
        255,
        255,
        128
      ],
      "width": 2,
      "height": 2
    }
  }
]
rasterizeLayer — 栅格化:摘掉决定图层类型的附加块(文字 / 矢量 / 填充 / 智能对象),变成普通像素层
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1)
keysarray—只摘这些块(非空数组);默认全摘
可选:TySh vmsk vsms vogk vstk vscg SoCo GdFl PtFl SoLd PlLd

只摘块、不渲染像素 —— 渲染结果要另用 replaceLayerPixels 写;样式 / 锁定 / 名字等块保留;全局 Txt2 不在这里删。

示例

[
  {
    "op": "rasterizeLayer",
    "index": 1
  }
]
setImageResource — 新增或替换一条图像资源(8BIM 资源块)
字段类型必填说明
idinteger是资源 id,0…65535
dataarray是资源数据字节(Uint8Array / number[]);HTTP 也接受 base64 字符串
示例

[
  {
    "op": "setImageResource",
    "id": 1034,
    "data": [
      0
    ]
  }
]
removeImageResource — 删除一条图像资源(不存在则不改动)
字段类型必填说明
idinteger是资源 id,0…65535
示例

[
  {
    "op": "removeImageResource",
    "id": 1054
  }
]
setLayerFlags — 改图层锁定(lspf:锁透明像素 / 锁图像像素 / 锁位置 / 画板嵌套 / 全部锁定)
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1);不能是分组结束分隔记录
lockTransparencyboolean—锁定透明像素
lockCompositeboolean—锁定图像像素
lockPositionboolean—锁定位置
lockAllboolean—全部锁定
lockNestingboolean—防止在画板和画框内外自动嵌套(lspf bit3)

五个字段至少给一个;从不删块(原本没有 lspf 且目标全 0 时不新建)。

示例

[
  {
    "op": "setLayerFlags",
    "index": 1,
    "lockPosition": true
  }
]
setLayerColorLabel — 改图层面板的颜色标签(lclr)
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1);不能是分组结束分隔记录
colorinteger|string是0…7(0 无 1 红 2 橙 3 黄 4 绿 5 蓝 6 紫 7 灰)或颜色名(另认 grey / 无)
可选:none red orange yellow green blue violet gray
示例

[
  {
    "op": "setLayerColorLabel",
    "index": 1,
    "color": "red"
  }
]
setFillOpacity — 改填充不透明度(iOpa,面板上的「填充」)
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1);不能是分组结束分隔记录
fillOpacityinteger是0…255(255 = 填充 100%)
示例

[
  {
    "op": "setFillOpacity",
    "index": 1,
    "fillOpacity": 128
  }
]
setLayerKnockout — 改混合选项里的「挖空」(knko)
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1);不能是分组结束分隔记录
knockoutinteger是0 无 / 1 浅 / 2 深
可选:0 1 2
示例

[
  {
    "op": "setLayerKnockout",
    "index": 2,
    "knockout": 1
  }
]
setBlendOptions — 改混合选项的高级混合 / 通道 / 混合颜色带(infx · clbl · tsly · lmgm · vmgm · brst · Blend If)
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1);不能是分组结束分隔记录
blendInteriorboolean—将内部效果混合成组(infx,默认 false)
blendClippedboolean—将剪贴图层混合成组(clbl,默认 true)
transparencyShapesboolean—透明形状图层(tsly,默认 true)
layerMaskHidesEffectsboolean—图层蒙版隐藏效果(lmgm,默认 false)
vectorMaskHidesEffectsboolean—矢量蒙版隐藏效果(vmgm,默认 false)
channelsobject—{ r?, g?, b? } 布尔:false = 该通道不参与混合(brst)
blendIfobject—{ gray|red|green|blue: { this?: [黑低,黑高,白低,白高], under?: […] } },0…255 且递增;只做等长覆写,记录里没有该通道的混合范围段会报错
示例

[
  {
    "op": "setBlendOptions",
    "index": 2,
    "blendIf": {
      "gray": {
        "under": [
          0,
          0,
          180,
          230
        ]
      }
    },
    "channels": {
      "b": false
    }
  }
]
setMaskProps — 改图层蒙版的开关位与浓度 / 羽化(像素蒙版与矢量蒙版各一份)
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1);不能是分组结束分隔记录;改像素蒙版字段时图层必须已有蒙版,改矢量蒙版字段时必须已有矢量蒙版
disabledboolean—停用蒙版
invertboolean—混合时反相(PS 已废弃,仍原样读写)
relativeboolean—蒙版位置相对于图层数据
densityinteger—像素蒙版浓度 0…255(255 = 100%)
feathernumber—像素蒙版羽化 0…1000 像素
vectorDensityinteger—矢量蒙版浓度 0…255(255 = 100%)
vectorFeathernumber—矢量蒙版羽化 0…1000 像素

七个字段至少给一个。参数段里已有对应项时等长覆写;没有时补写参数段(蒙版段变长,只有矢量蒙版的图层会新建一个空矩形的蒙版段)。没有蒙版 / 没有矢量蒙版在 apply 阶段才报错(planEdits 查不出)。

示例前置(让示例能跑通的准备步骤):[{"op":"addLayerMask","index":1}]

示例

[
  {
    "op": "setMaskProps",
    "index": 1,
    "disabled": true
  }
]
addLayerMask — 给图层新建用户蒙版(通道 -2),纯色填充或给灰度位图
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1);不能是分组结束分隔记录;图层不能已有蒙版
fillstring|integer—'reveal'(全白 255,默认)/ 'hide'(全黑 0)/ 0…255;与 gray 互斥
grayarray|object—8 位灰度字节(长度 = bounds 宽×高)或 { data, width, height };与 fill 互斥;JSON 请传 number[](不解 base64)
boundsobject—{ top, left, bottom, right } 整数,宽高 > 0;默认 = 图层包围盒(空图层必须显式给)
defaultColorinteger—蒙版矩形之外的取值 0…255;默认 = fill 值(给 gray 时默认 0)
disabledboolean—新建即停用
invertboolean—反相位
示例

[
  {
    "op": "addLayerMask",
    "index": 1,
    "fill": "reveal"
  }
]
deleteLayerMask — 删除图层蒙版(蒙版段 + 通道同删)
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1);不能是分组结束分隔记录
whichstring—user = 用户蒙版 -2 / real = real 用户蒙版 -3 / all = 两个都删
可选:user real all
默认 "user"

示例前置(让示例能跑通的准备步骤):[{"op":"addLayerMask","index":1}]

示例

[
  {
    "op": "deleteLayerMask",
    "index": 1
  }
]
applyLayerMask — 应用蒙版:灰度乘进 alpha 后删除蒙版(停用的蒙版直接删、像素不变)
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1);不能是分组结束分隔记录
whichstring—用哪个蒙版;不给则优先 -2、没有再用 -3
可选:user real

只支持 8/16 位;蒙版带非默认浓度 / 羽化时拒绝;不重编合成图(给 warning)。

示例前置(让示例能跑通的准备步骤):[{"op":"addLayerMask","index":1,"fill":128}]

示例

[
  {
    "op": "applyLayerMask",
    "index": 1
  }
]
setLayerMaskPixels — 改现有蒙版的像素(整张 / 局部矩形 / 连同 bounds),画笔的落地出口
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1);不能是分组结束分隔记录;图层必须已有对应蒙版
grayarray|object是灰度样本(Uint8Array / Uint16Array / number[])或 { data, width, height };长度 = 目标区域面积;JSON 请传 number[]
whichstring—user(-2)/ real(-3)
可选:user real
默认 "user"
xinteger—局部矩形左上角 x(相对蒙版左上角,≥0,默认 0)
yinteger—局部矩形左上角 y(相对蒙版左上角,≥0,默认 0)
winteger—局部矩形宽(给了 x/y/w/h 任一就必须给 w 与 h)
hinteger—局部矩形高
boundsobject—同时改蒙版 bounds { top, left, bottom, right }(此时 gray 要覆盖整张新蒙版);与 x/y/w/h 互斥

不给 x/y/w/h/bounds 时 gray 必须覆盖整张蒙版;保持原压缩方式;非 structural。

示例前置(让示例能跑通的准备步骤):[{"op":"addLayerMask","index":1}]

示例

[
  {
    "op": "setLayerMaskPixels",
    "index": 1,
    "x": 0,
    "y": 0,
    "w": 2,
    "h": 2,
    "gray": [
      0,
      64,
      128,
      255
    ]
  }
]
replaceLayerMask — 整张换掉用户蒙版(-2)的像素与范围,保留其余蒙版属性(随图层变换用)
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1);不能是分组结束分隔记录;图层必须已有用户蒙版
boundsobject是新蒙版范围 { top, left, bottom, right },整数,宽高 > 0
grayarray是8 位灰度(长度 = bounds 宽×高);JSON 请传 number[] —— 传字符串会被逐字符当字节,长度凑巧相等时不报错
defaultColorinteger—只能是 0 或 255
可选:0 255

示例前置(让示例能跑通的准备步骤):[{"op":"addLayerMask","index":1}]

示例

[
  {
    "op": "replaceLayerMask",
    "index": 1,
    "bounds": {
      "top": 748,
      "left": 266,
      "bottom": 750,
      "right": 268
    },
    "gray": [
      255,
      0,
      0,
      255
    ]
  }
]
addShapeLayer — 新建形状图层(SoCo 纯色填充 + vmsk 路径,可选 vstk 描边)
字段类型必填说明
subpathsarray是子路径数组 [{ closed?:boolean(默认 true), operation?:'exclude'|'combine'|'subtract'|'intersect'|0…3|null, knots:[{ anchor:[x,y]|{x,y}, in?:[x,y], out?:[x,y], linked?:boolean }] }];in/out 缺省 = 与 anchor 相同(角点);至少一条子路径
unitsstring—坐标单位:pixel(绝对像素)/ normalized(0…1)/ fixed(8.24 定点整数)
可选:pixel normalized fixed
默认 "pixel"
colorstring|array—填充色:'#rrggbb' / '#rgb' / [r,g,b](0…255),默认黑
默认 "#000000"
fillobject—{ type:'solid', color }(color 的另一种写法;目前只支持 solid)
strokeobject—{ color, width(0.01…1000,默认1), opacity(0…100), dashOffset, cap:butt|round|square, join:miter|round|bevel, align:inside|center|outside, dash:[正数…], enabled:false=不描边 }
originobject—实时形状属性(vogk):{ type:'rect'|'roundRect'|'ellipse', left, top, right, bottom, radii?:[左上,右上,右下,左下] }(像素);PS 属性面板据此显示可调的 W/H 与四角圆角
atinteger—新图层在「底=0」序里的插入下标,0…图层数;默认 = 图层数(最上面)
namestring—图层名,非空、≤255 字符
默认 "形状 1"
opacityinteger—不透明度 0…255(255 = 不透明)
visibleboolean—是否可见
clippingboolean|integer—剪贴蒙版:布尔值或 0/1
blendKeystring—图层混合模式 4 字符键(不足补空格,如 'mul ')
可选:pass norm diss dark mul idiv lbrn dkCl lite scrn div lddg lgCl over sLit hLit vLit lLit pLit hMix diff smud fsub fdiv hue sat colr lum

只支持 RGB 文档;像素坐标需落在画布附近(超 int32 定点范围拒绝)。

示例

[
  {
    "op": "addShapeLayer",
    "name": "矩形",
    "color": "#ff6600",
    "subpaths": [
      {
        "closed": true,
        "knots": [
          {
            "anchor": [
              100,
              100
            ]
          },
          {
            "anchor": [
              400,
              100
            ]
          },
          {
            "anchor": [
              400,
              300
            ]
          },
          {
            "anchor": [
              100,
              300
            ]
          }
        ]
      }
    ]
  }
]
setShapeContent — 改形状 / 填充图层的填充类型与描边类型(属性面板「外观」两个下拉)
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1);须是形状或填充图层
fillobject—{ type:'none'|'solid'|'gradient'|'pattern', color?, gradient?, pattern? }(gradient/pattern 结构同 addFillLayer)
strokeobject—{ type:'none'|'solid'|'gradient'|'pattern', color?, gradient?, pattern?, width?(0…1000) }

fill / stroke 至少给一个;只支持 RGB;「无填充」与描边需要图层有 vmsk 路径。

示例前置(让示例能跑通的准备步骤):[{"op":"addShapeLayer","color":"#ff6600","subpaths":[{"closed":true,"knots":[{"anchor":[100,100]},{"anchor":[400,100]},{"anchor":[400,300]},{"anchor":[100,300]}]}]}]

示例

[
  {
    "op": "setShapeContent",
    "index": 6,
    "stroke": {
      "type": "solid",
      "color": "#000000",
      "width": 3
    }
  }
]
setShapeOrigin — 写 / 删形状层的实时形状属性(vogk:矩形 / 圆角矩形 / 椭圆的外框与四角半径)
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1);须是形状图层
originobject是{ type:'rect'|'roundRect'|'ellipse', left, top, right, bottom, radii?:[左上,右上,右下,左下] };传 null = 删掉 vogk(形状在 PS 里变成普通路径)

只改 vogk,不改路径:改圆角 / 尺寸时与 setVectorMaskPath 一起下发,两者要描述同一个形状。

示例前置(让示例能跑通的准备步骤):[{"op":"addShapeLayer","color":"#ff6600","subpaths":[{"closed":true,"knots":[{"anchor":[100,100]},{"anchor":[400,100]},{"anchor":[400,300]},{"anchor":[100,300]}]}]}]

示例

[
  {
    "op": "setShapeOrigin",
    "index": 6,
    "origin": {
      "type": "roundRect",
      "left": 10,
      "top": 10,
      "right": 110,
      "bottom": 60,
      "radii": [
        8,
        8,
        8,
        8
      ]
    }
  }
]
addVectorMask — 给没有矢量蒙版的图层新建矢量蒙版(vmsk)
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1);不能是分组结束分隔记录;不能已有 vmsk/vsms
subpathsarray是子路径数组 [{ closed?:boolean(默认 true), operation?:'exclude'|'combine'|'subtract'|'intersect'|0…3|null, knots:[{ anchor:[x,y]|{x,y}, in?:[x,y], out?:[x,y], linked?:boolean }] }];in/out 缺省 = 与 anchor 相同(角点);实际不能为空
unitsstring—坐标单位:pixel(绝对像素)/ normalized(0…1)/ fixed(8.24 定点整数)
可选:pixel normalized fixed
默认 "pixel"

代码注释说「subpaths 为空 = 隐藏全部」,但 freshPathRecords 会拒绝空数组 —— 目前必须至少一条子路径。

示例

[
  {
    "op": "addVectorMask",
    "index": 1,
    "subpaths": [
      {
        "closed": true,
        "knots": [
          {
            "anchor": [
              100,
              100
            ]
          },
          {
            "anchor": [
              400,
              100
            ]
          },
          {
            "anchor": [
              400,
              300
            ]
          },
          {
            "anchor": [
              100,
              300
            ]
          }
        ]
      }
    ]
  }
]
setVectorMaskPath — 整份替换已有矢量蒙版(vmsk/vsms)的路径
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1);图层必须已有矢量蒙版
subpathsarray是子路径数组 [{ closed?:boolean(默认 true), operation?:'exclude'|'combine'|'subtract'|'intersect'|0…3|null, knots:[{ anchor:[x,y]|{x,y}, in?:[x,y], out?:[x,y], linked?:boolean }] }];in/out 缺省 = 与 anchor 相同(角点);空数组 = 清空路径(给 warning)
unitsstring—坐标单位:pixel(绝对像素)/ normalized(0…1)/ fixed(8.24 定点整数)
可选:pixel normalized fixed
默认 "pixel"

operation / closed 缺省时沿用原子路径的值;没有矢量蒙版在 apply 阶段报错。

示例前置(让示例能跑通的准备步骤):[{"op":"addVectorMask","index":1,"subpaths":[{"closed":true,"knots":[{"anchor":[100,100]},{"anchor":[400,100]},{"anchor":[400,300]},{"anchor":[100,300]}]}]}]

示例

[
  {
    "op": "setVectorMaskPath",
    "index": 1,
    "subpaths": [
      {
        "closed": true,
        "knots": [
          {
            "anchor": [
              120,
              120
            ]
          },
          {
            "anchor": [
              360,
              120
            ]
          },
          {
            "anchor": [
              240,
              320
            ]
          }
        ]
      }
    ]
  }
]
setVectorStroke — 改矢量描边参数(vstk 描述符里的宽度 / 颜色 / 端点 / 虚线等)
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1);图层必须已有 vstk 块
valuesobject是非空对象 { 字段: 新值 };width/dashOffset/opacity(0…100) 数字,cap:butt|round|square,join:miter|round|bevel,align:inside|center|outside,dashes:[≥0…],color:'#rrggbb'|[r,g,b]|{r,g,b}(仅纯色描边),blendMode 友好名,enabled/fillEnabled/scaleLock/strokeAdjust 布尔
可选:enabled fillEnabled width dashOffset miterLimit cap join align dashes opacity blendMode resolution scaleLock strokeAdjust color

values 的键也接受描述符字段名(strokeStyleLineWidth)与 lineWidth / size / dash 等同义写法。

示例前置(让示例能跑通的准备步骤):[{"op":"addShapeLayer","color":"#ff6600","subpaths":[{"closed":true,"knots":[{"anchor":[100,100]},{"anchor":[400,100]},{"anchor":[400,300]},{"anchor":[100,300]}]}],"stroke":{"color":"#000000","width":2}}]

示例

[
  {
    "op": "setVectorStroke",
    "index": 6,
    "values": {
      "width": 4,
      "color": "#ff0000"
    }
  }
]
setVectorMaskFlags — 改矢量蒙版标志位(反相 / 不链接 / 停用)与初始填充规则
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1);图层必须已有矢量蒙版
invertboolean—反相
notLinkboolean—不与图层链接
disableboolean—停用
fillStartsWithAllPixelsboolean—初始填充规则记录(selector 8)

四个字段至少给一个。

示例前置(让示例能跑通的准备步骤):[{"op":"addVectorMask","index":1,"subpaths":[{"closed":true,"knots":[{"anchor":[100,100]},{"anchor":[400,100]},{"anchor":[400,300]},{"anchor":[100,300]}]}]}]

示例

[
  {
    "op": "setVectorMaskFlags",
    "index": 1,
    "disable": true
  }
]
setLayerStyle — 整份替换一个图层的图层样式(样式面板「点样式 = 应用」;effects:null = 清除样式)
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1)
effectsobject—lfx2 口径的效果描述符(class null:Scl / masterFXSwitch / DrSh / FrFX …);null = 清除全部样式
effectsBytesstring—或直接给序列化后的描述符字节(.asl 里 Styl.Lefx 那一段)

写 lfx2,并摘掉 lmfx / lrFX;样式里引用的图案需先用 addPatterns 写进文档。

示例

[
  {
    "op": "setLayerStyle",
    "index": 1,
    "effects": null
  }
]
addPatterns — 把图案写进文档全局图案资源(Patt / Pat2 / Pat3),供图案填充层 / 图案叠加按 Idnt 引用
字段类型必填说明
patternsarray是[{ id(ASCII uuid), name, width, height, rgba(base64 或字节,width×height×4) }];已有同 id 的跳过
示例

[
  {
    "op": "addPatterns",
    "patterns": [
      {
        "id": "00000000-0000-4000-8000-000000000001",
        "name": "1×1",
        "width": 1,
        "height": 1,
        "rgba": "/wAA/w=="
      }
    ]
  }
]
addLayerEffect — 给图层新增一条图层样式效果(没有样式块时新建 lfx2)
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1)
effectstring是效果名(规范名或 4 字符 PS key,大小写不敏感;另认 drop-shadow / bevel / stroke 等同义写法)
可选:dropShadow innerShadow outerGlow innerGlow bevelEmboss satin colorOverlay gradientOverlay patternOverlay stroke DrSh IrSh OrGl IrGl ebbl ChFX SoFi GrFl patternFill FrFX
valuesobject—覆盖默认模板的字段 { 路径: 值 }(如 Opct / Dstn / 'Clr ':'#000000');各效果可用路径见 core/psd-edit-create.js listCreatableEffects().fields;百分比 -100…100、角度 -360…360、像素 0…30000

新条目插在最前(at=0);只允许一条的效果重复添加会被拒绝;patternOverlay 必须用 values 指定 Ptrn.Nm / Ptrn.Idnt;lfx2 上加第二条可多重效果会整块升级为 lmfx(不可逆)。

示例

[
  {
    "op": "addLayerEffect",
    "index": 1,
    "effect": "dropShadow",
    "values": {
      "Opct": 60,
      "Dstn": 8,
      "Clr ": "#000000"
    }
  }
]
removeLayerEffect — 删除一条图层样式效果(删到一条不剩时删掉整个样式块)
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1)
effectstring是效果名(规范名或 4 字符 PS key,大小写不敏感;另认 drop-shadow / bevel / stroke 等同义写法)
可选:dropShadow innerShadow outerGlow innerGlow bevelEmboss satin colorOverlay gradientOverlay patternOverlay stroke DrSh IrSh OrGl IrGl ebbl ChFX SoFi GrFl patternFill FrFX
atinteger—多重效果里删第几条(≥0)
默认 0

示例前置(让示例能跑通的准备步骤):[{"op":"addLayerEffect","index":1,"effect":"dropShadow"}]

示例

[
  {
    "op": "removeLayerEffect",
    "index": 1,
    "effect": "dropShadow"
  }
]
setLayerEffect — 启用 / 停用一条已有的图层样式效果(enab)
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1)
effectstring是效果名(规范名或 4 字符 PS key,大小写不敏感;另认 drop-shadow / bevel / stroke 等同义写法)
可选:dropShadow innerShadow outerGlow innerGlow bevelEmboss satin colorOverlay gradientOverlay patternOverlay stroke DrSh IrSh OrGl IrGl ebbl ChFX SoFi GrFl patternFill FrFX
enabledboolean是启用或停用
atinteger—多重效果里改第几条(≥0)
默认 0

图层没有描述符型样式块 / 没有该效果时在 apply 阶段才报错(planEdits 查不出);老式 lrFX 不改。

示例前置(让示例能跑通的准备步骤):[{"op":"addLayerEffect","index":1,"effect":"dropShadow"}]

示例

[
  {
    "op": "setLayerEffect",
    "index": 1,
    "effect": "dropShadow",
    "enabled": false
  }
]
setLayerEffectsEnabled — 图层样式整体开关(masterFXSwitch)
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1)
enabledboolean是启用或停用全部图层样式

图层没有样式块时在 apply 阶段才报错。

示例前置(让示例能跑通的准备步骤):[{"op":"addLayerEffect","index":1,"effect":"dropShadow"}]

示例

[
  {
    "op": "setLayerEffectsEnabled",
    "index": 1,
    "enabled": false
  }
]
clearLayerEffects — 整块摘掉图层样式(lfx2 / lmfx / lrFX),用于样式已烘进像素之后
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1)

没有样式块时不改动(changed=false)。

示例前置(让示例能跑通的准备步骤):[{"op":"addLayerEffect","index":1,"effect":"dropShadow"}]

示例

[
  {
    "op": "clearLayerEffects",
    "index": 1
  }
]
setLayerParams — 按路径改已有图层样式 / 调整图层 / 填充图层块里的参数(可增删渐变色标)
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1)
targetstring是'effects'(样式顶层)/ 'effect:<效果名或 key>'(也可裸写效果名)/ 'adjustment'(主调整块)/ 'adjustment:<块 key>';块 key 见 enum
可选:effects effect:DrSh effect:IrSh effect:OrGl effect:IrGl effect:ebbl effect:ChFX effect:SoFi effect:GrFl effect:patternFill effect:FrFX adjustment adjustment:SoCo adjustment:GdFl adjustment:PtFl adjustment:CgEd adjustment:blwh adjustment:vibA adjustment:clrL adjustment:brit adjustment:levl adjustment:curv adjustment:hue adjustment:hue2 adjustment:blnc adjustment:phfl adjustment:mixr adjustment:selc adjustment:expA adjustment:post adjustment:thrs adjustment:nvrt adjustment:grdm
valuesobject—{ 字段路径: 新值 }('.' 分段,如 'Clr .Rd '、'records.3.midtoneInput';颜色对象可整体赋 '#rrggbb');≤256 个;无 arrayOps 时必填且非空
atinteger—多重效果里改第几条(≥0)
默认 0
arrayOpsarray—数组增删(≤64 条):{ path, insertAt|removeAt|sortBy(三选一), copyFrom?, value? };仅白名单数组(渐变 Clrs/Trns、grdm 色标);先于 values 执行

只改已有块里已存在的字段;字段是否存在、块能否原样重建在 apply 阶段校验(planEdits 只查参数形状)。可用字段见 core/psd-edit-params.js getLayerParams()。

示例前置(让示例能跑通的准备步骤):[{"op":"addLayerEffect","index":1,"effect":"dropShadow"}]

示例

[
  {
    "op": "setLayerParams",
    "index": 1,
    "target": "effect:DrSh",
    "values": {
      "Opct": 40
    }
  }
]
addAdjustmentLayer — 新建一个调整图层(无像素记录 + 对应调整块)
字段类型必填说明
kindstring是调整类型(也认块 key 与 brightness / hue / bw / curve / level / mixer / selective 等别名,忽略大小写与连字符)
可选:brightnessContrast levels curves hueSaturation invert posterize threshold blackWhite colorBalance exposure channelMixer selectiveColor vibrance photoFilter colorLookup gradientMap
valuesobject—按 kind 不同的参数,未知键报错;见 valuesByKind
atinteger—新图层在「底=0」序里的插入下标,0…图层数;默认 = 图层数(最上面)
namestring—图层名,非空、≤255 字符
默认 "该 kind 的中文名"
opacityinteger—不透明度 0…255(255 = 不透明)
visibleboolean—是否可见
clippingboolean|integer—剪贴蒙版:布尔值或 0/1
blendKeystring—图层混合模式 4 字符键(不足补空格,如 'mul ')
可选:pass norm diss dark mul idiv lbrn dkCl lite scrn div lddg lgCl over sLit hLit vLit lLit pLit hMix diff smud fsub fdiv hue sat colr lum

文档必须已有图层;支持位图/灰度/索引/RGB/CMYK/多通道/双色调/Lab 模式;16/32 位文档不支持。

示例

[
  {
    "op": "addAdjustmentLayer",
    "kind": "hueSaturation",
    "values": {
      "saturation": -30
    }
  }
]
addFillLayer — 新建填充图层:纯色(SoCo)/ 渐变(GdFl)/ 图案(PtFl),铺满画布
字段类型必填说明
kindstring—填充类型
可选:solid gradient pattern
默认 "solid"
colorstring|array—kind=solid 的颜色:'#rrggbb' / '#rgb' / [r,g,b]
默认 "#000000"
gradientobject—kind=gradient:{ type:linear|radial|angle|reflected|diamond, angle(-360…360,默认90), scale(1…1000), stops:[{color,location 0…1,midpoint 1…99}]≥2 个≤64 个, opacityStops:[{opacity 0…100,location,midpoint}], name, dither, reverse, align }
patternobject—kind=pattern 时必填:{ name, id(文档 Patt 资源里的图案 uuid), scale(1…1000), linked, angle }
atinteger—新图层在「底=0」序里的插入下标,0…图层数;默认 = 图层数(最上面)
namestring—图层名,非空、≤255 字符
默认 "纯色填充 / 渐变填充 / 图案填充"
opacityinteger—不透明度 0…255(255 = 不透明)
visibleboolean—是否可见
clippingboolean|integer—剪贴蒙版:布尔值或 0/1
blendKeystring—图层混合模式 4 字符键(不足补空格,如 'mul ')
可选:pass norm diss dark mul idiv lbrn dkCl lite scrn div lddg lgCl over sLit hLit vLit lLit pLit hMix diff smud fsub fdiv hue sat colr lum

只支持 RGB 文档;没有 vmsk(要限定范围请再加蒙版)。

示例

[
  {
    "op": "addFillLayer",
    "kind": "solid",
    "color": "#3366ff",
    "opacity": 128
  }
]
convertToSmartObject — 把图层转换为内嵌智能对象(写内嵌 PSD + PlLd/SoLd + 全局 lnk2)
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1);不能是组,不能已是智能对象
sourceobject是{ data:RGBA 字节(≥ width×height×4,非预乘), width, height } 内嵌文档像素(通常就是该层像素);JSON 可传 number[]
boundsobject—智能对象在画布上的位置 { left, top, right, bottom };默认 = 图层 bounds
namestring—内嵌文件名;默认「图层名.psd」
embeddedobject—现成的内嵌 PSD 字节(Uint8Array,8BPS 开头);只能在进程内调用时传,JSON 无法表达
idstring—内嵌文件 uuid(测试用;默认随机)
placedIdstring—放置实例 uuid(默认随机)
resolutionnumber—内嵌文档分辨率 dpi
默认 72

图层原有像素 / 蒙版 / 样式不动。

示例

[
  {
    "op": "convertToSmartObject",
    "index": 1,
    "source": {
      "data": [
        200,
        200,
        200,
        255,
        200,
        200,
        200,
        255,
        200,
        200,
        200,
        255,
        200,
        200,
        200,
        255
      ],
      "width": 2,
      "height": 2
    }
  }
]
detachSmartObject — 把共享内嵌文件的智能对象拆成独立副本(复制内嵌文件、换新 id)
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1);必须是智能对象

外部链接的智能对象(没有内嵌文件)拒绝。

示例

[
  {
    "op": "detachSmartObject",
    "index": 1
  }
]
addGallerySmartFilter — 给智能对象追加一条「滤镜库」智能滤镜(SoLd.filterFX.filterFXList 末尾 = 最后应用,面板里显示在最上面)
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1);必须是智能对象且带 SoLd 放置描述符
entryobject是filterFXList 的一条描述符(__classID 'filterFX',含 Fltr:{ classID 'GEfc', GEfs:[…] });由 core/filter-gallery-spec.js 的 galleryFilterFXEntry(layers, { fg, bg }) 生成

只写描述符,不重算缓存像素:图层像素请在同一批里用 replaceLayerPixels 一起写(一步撤销)。没有 filterFX 时新建(键紧跟 Rslt)。写完自检读回,末条不是刚写的滤镜就放弃。HTTP 走服务端 op addSmartFilter(kind:'filterGallery') 更方便(它会顺带算像素)。

setSmartFilterEntry — 改智能对象上的一条智能滤镜(SoLd.filterFX.filterFXList[at]):参数(Fltr)/ 混合选项 / 前景背景色 / 启用位
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1);必须是智能对象且带 SoLd 放置描述符
atinteger是filterFXList 下标(0 = 最先应用 = PS 图层面板上最下面那条)
Fltrobject—整份新的滤镜描述符(__classID 必须与原条目相同;换滤镜种类请删掉重加)。core/smart-filter-spec.js 的 fltrWithParams(原 Fltr, kind, 参数) / 滤镜库用 galleryDescriptor(layers) 生成
blendOptionsobject—{ opacity?: 0…100, mode?: 图层混合键(norm / mul / scrn …)},写成 PS 的 blendOptions(Opct #Prc + Md BlnM)
fgarray—[r,g,b] 执行时的前景色(素描类滤镜库效果用;只在条目本来有 FrgC 时改)
bgarray—[r,g,b] 执行时的背景色(BckC)
enabledboolean—这条滤镜的启用位(enab)

只改描述符,不重算缓存像素:图层像素请在同一批里用 replaceLayerPixels 一起写(一步撤销)。编辑器「双击智能滤镜重开对话框 / 混合选项」走的就是它。

setStackMode — 智能对象 › 堆栈模式:写进放置描述符(SoLd Type = 3「图像堆栈」+ imageStackPlugin 类;PlLd 的 placedLayerType 同步)
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1);必须是智能对象且带 SoLd
modestring是none / entropy / kurtosis / max / mean / median / min / range / skewness / stdDev / sum / variance(对应 PS 插件类 entr kurt maxx avrg medn minn rang skew stdv summ vari)
可选:none entropy kurtosis max mean median min range skewness stdDev sum variance

只写描述符(none = 去掉 imageStackPlugin、Type 回 2);统计后的缓存像素由调用方用 replaceLayerPixels 写(core/stack-modes.js 的 stackCombine)。imageStackPlugin 的存放位置按 Action Manager 口径(applyImageStackPluginRenderer 事件的 imageStackPlugin 类)推定,未拿 PS 样本逐字节核对。

示例

[
  {
    "op": "setStackMode",
    "index": 1,
    "mode": "median"
  }
]
copyLayerBlocks — 把一层的若干附加信息块原样拷到另一层(已有同名块则替换)
字段类型必填说明
frominteger是源图层下标(底=0)
tointeger是目标图层下标
keysarray是四字符块名数组,如 ['lfx2', 'vmsk'];源层没有的键跳过。不允许 lsct / lsdk / luni / lyid / SoLd / PlLd / TySh(会破坏图层身份)

「智能对象 › 转换为图层」用它把智能对象的图层样式 / 矢量蒙版带到外包组上。矢量蒙版坐标是相对画布归一化的,同一文档内拷贝不用换算。

示例

[
  {
    "op": "copyLayerBlocks",
    "from": 1,
    "to": 4,
    "keys": [
      "lfx2"
    ]
  }
]
ensureLayerIds — 给没有 lyid 的图层补一个唯一图层 id(链接组 / 图层复合 / 变量都按 lyid 对齐)

无参数。

已有 id 的不动;新 id = 当前最大 id + 1 起递增。全部都有时 changed:false。

示例

[
  {
    "op": "ensureLayerIds"
  }
]
setLinkGroups — 链接 / 取消链接图层,或停用 / 启用链接(图像资源 1026 / 1072,按 lyid 对齐)
字段类型必填说明
indicesarray是图层下标数组(底=0);非空
linkboolean—true = 链成一组(已在组里的并进最小组号,PS 同);false = 从各自链接组里拿出(剩一层的组自动解散);link:true 时至少两个图层(给了 disable 时除外)
disableboolean—给了就只改「在组里是否停用」(PS ⇧ 点链条图标出现红叉),忽略 link

会先自动补齐缺失的 lyid。

示例

[
  {
    "op": "setLinkGroups",
    "indices": [
      1,
      4
    ],
    "link": true
  }
]
setLayerCompsMeta — 写各图层的图层复合记录(shmd 块里的 cmls 子块)
字段类型必填说明
entriesarray是[{ index, cmls: 描述符|null }];cmls = 该层在各图层复合里的可见 / 位置 / 外观记录(PS 的 layerSettings 列表),null = 去掉该子块

没有 shmd 就新建;shmd 里其它子块原样保留。复合本身的列表在图像资源 1065(用 setImageResource 写),与这里按 compID 对应。

示例

[
  {
    "op": "setLayerCompsMeta",
    "entries": [
      {
        "index": 1,
        "cmls": null
      }
    ]
  }
]
setLayerAnimMeta — 写各图层的帧动画记录(shmd 块里的 mlst / mdyn 子块)
字段类型必填说明
entriesarray是[{ index, mlst: 描述符|null, mdyn: 整数|null }];mlst = 该层在各帧里的可见性 / 位置偏移 / 不透明度(PS 的 LaSt 列表,按帧 id 分组),mdyn = 统一位置 / 样式 / 可见性与「传播帧 1」标志;两者都 null = 去掉这两个子块

没有 shmd 就新建;shmd 里其它子块原样保留。帧列表本身在图像资源 4000('mani' / 'AnDs'),与这里按帧 id 对应。

示例

[
  {
    "op": "setLayerAnimMeta",
    "entries": [
      {
        "index": 1,
        "mlst": null,
        "mdyn": null
      }
    ]
  }
]
setArtboard — 把组头设为画板 / 改画板矩形与背景,或取消画板身份
字段类型必填说明
indexinteger是图层下标(底=0,0…图层数-1);设画板时必须是组头(groupType 1/2)
rectobject—画板矩形 { top, left, bottom, right }(数字,宽高 > 0);remove 为假时必填
presetNamestring—画板预设名;默认 = 图层名
colorstring|array—背景色(backgroundType=4 时生效)
默认 "#ffffff"
backgroundTypeinteger|string—1 白 / 2 黑 / 3 透明 / 4 其它(用 color);也认 white / black / transparent / custom / other
可选:1 2 3 4 white black transparent custom other
默认 1
removeboolean—true = 删掉 artb/artd/abdd,取消画板(此时其余字段忽略;本来不是画板则报错)

写 artb + abdd,原有 artd 一并更新;保留原 guideIDs。

示例

[
  {
    "op": "setArtboard",
    "index": 2,
    "rect": {
      "top": 0,
      "left": 0,
      "bottom": 1080,
      "right": 1920
    },
    "backgroundType": "white"
  }
]
replaceSmartObject — 换智能对象源图(按覆盖模式渲染进网格 / 变形,写回内嵌源图、缓存栅格、FEid 与缩略图)
字段类型必填说明
targetinteger|string|object—智能对象序号 / 图层名 / { layerIndex } / { smartObjectIndex };省略 = 第 0 个智能对象
imageobject|integer|string是图片引用:{ file:"image", index:n } / 上传文件名 / data:URL / 裸 base64
fitobject—{ mode:"cover"|"contain"|"tile"|"stretch", alignX?, alignY?, scale? },默认 cover
meshobject—顺带改网格:{ points, xn, yn, sliceX, sliceY } 或 { splits:{ u, v } }
sourceNamestring—写进 PSD 的内嵌源图文件名

与 /api/render(output.format=psd)逐字节相同。注意:number 型 target 在这三条老 op 里指「智能对象序号」。

示例

[
  {
    "op": "replaceSmartObject",
    "target": 0,
    "image": {
      "file": "image",
      "index": 0
    },
    "fit": {
      "mode": "cover"
    }
  }
]
deform — 智能对象网格 / 变形滤镜写回(warp、quiltWarp、透视变形、操控变形)
字段类型必填说明
targetinteger|string|object—同 replaceSmartObject
pointsarray—Bezier 网格控制点 [{x,y}](配 xn / yn / sliceX / sliceY)
deformobject—变形滤镜 JSON(/api/so/resolve 的 deform 结构);create:true = 新挂一条
deformsarray—多条变形:[{ deform, at?, create? }]
示例

[
  {
    "op": "deform",
    "target": 0,
    "deform": "<来自 POST /api/so/resolve 的 layerInfo.deform,改过 warped 顶点>"
  }
]
setSmartObjectDescriptor — 改智能对象放置描述符(Idnt / placed / 原始尺寸 Sz)
字段类型必填说明
targetinteger|string|object—同 replaceSmartObject
linkIdstring—改 Idnt(指向哪份内嵌文件)
placedIdstring—改 placed(实例 id)
originalSizeobject—{ width, height }
示例

[
  {
    "op": "setSmartObjectDescriptor",
    "target": 0,
    "originalSize": {
      "width": 800,
      "height": 800
    }
  }
]

滤镜 / 调整清单

滤镜清单(75 种,applyFilter 的 filter / params)
filter名称分组params
gaussianBlur高斯模糊模糊radius 半径(0…250 像素) 默认 2
boxBlur方框模糊模糊radius 半径(0…250 像素) 默认 4
blur模糊模糊—
blurMore进一步模糊模糊—
motionBlur动感模糊模糊angle 角度(-180…180 度) 默认 0;distance 距离(0…2000 像素) 默认 20
radialBlur径向模糊模糊amount 数量(0…100) 默认 20;method 方法(spin/zoom) 默认 "spin"
average平均模糊—
surfaceBlur表面模糊模糊radius 半径(1…100 像素) 默认 5;threshold 阈值(2…255 色阶) 默认 15
lensBlur镜头模糊模糊iris 光圈形状(disc/hexagon/square/triangle) 默认 "hexagon";radius 半径(0…100) 默认 15;brightness 镜面高光 亮度(0…100) 默认 0;threshold 镜面高光 阈值(0…255) 默认 255;noise 杂色 数量(0…100) 默认 0
smartBlur特殊模糊模糊radius 半径(0.1…100) 默认 3;threshold 阈值(0.1…100) 默认 25;quality 品质(low/medium/high) 默认 "medium";mode 模式(normal/edgeOnly/overlayEdge) 默认 "normal"
shapeBlur形状模糊模糊radius 半径(4…1000 像素) 默认 10;shape 形状(disc/ring/square/triangle/hexagon/star/heart) 默认 "disc"
unsharpMaskUSM 锐化锐化amount 数量(0…500 %) 默认 100;radius 半径(0.1…250 像素) 默认 1;threshold 阈值(0…255 色阶) 默认 0
sharpen锐化锐化—
sharpenMore进一步锐化锐化—
smartSharpen智能锐化锐化amount 数量(0…500 %) 默认 200;radius 半径(0.1…64 像素) 默认 1;reduceNoise 减少杂色(0…100 %) 默认 10;remove 移去(gaussian/lens/motion) 默认 "gaussian";angle 角度(动感)(-180…180 度) 默认 0
sharpenEdges锐化边缘锐化—
shake防抖锐化length 模糊描摹边界(1…199 像素) 默认 10;angle 模糊方向(-180…180 度) 默认 0;smoothing 平滑(0…100 %) 默认 20;iterations 迭代次数(1…40) 默认 10
addNoise添加杂色杂色amount 数量(0…400 %) 默认 12;distribution 分布(uniform/gaussian) 默认 "gaussian";monochromatic 单色(bool) 默认 false;seed 随机种子(0…999999) 默认 12345
median中间值杂色radius 半径(0…16 像素) 默认 1
despeckle去斑杂色—
reduceNoise减少杂色杂色strength 强度(0…10) 默认 6;preserveDetails 保留细节(0…100 %) 默认 60;colorNoise 减少杂色(0…100 %) 默认 45;sharpen 锐化细节(0…100 %) 默认 25
dustScratches蒙尘与划痕杂色radius 半径(1…100 像素) 默认 1;threshold 阈值(0…255 色阶) 默认 0
mosaic马赛克像素化cell 单元格大小(1…200 方形) 默认 10
facet彩块化像素化—
colorHalftone彩色半调像素化maxRadius 最大半径(4…127 像素) 默认 8;angle1 通道 1(-360…360 度) 默认 108;angle2 通道 2(-360…360 度) 默认 162;angle3 通道 3(-360…360 度) 默认 90
pointillize点状化像素化cell 单元格大小(3…300) 默认 5;seed 随机种子(0…999999) 默认 2
crystallize晶格化像素化cell 单元格大小(3…300) 默认 10;seed 随机种子(0…999999) 默认 1
fragment碎片像素化—
mezzotint铜版雕刻像素化type 类型(fineDots/mediumDots/grainyDots/coarseDots/shortLines/mediumLines/longLines/shortStrokes/mediumStrokes/longStrokes) 默认 "fineDots";seed 随机种子(0…999999) 默认 4
findEdges查找边缘风格化—
emboss浮雕效果风格化angle 角度(-180…180 度) 默认 135;height 高度(1…100 像素) 默认 3;amount 数量(1…500 %) 默认 100
solarize曝光过度风格化—
traceContour等高线风格化level 色阶(0…255) 默认 128;edge 边缘(lower/upper) 默认 "lower"
wind风风格化method 方法(wind/blast/stagger) 默认 "wind";direction 方向(fromRight/fromLeft) 默认 "fromRight";seed 随机种子(0…999999) 默认 7
diffuse扩散风格化mode 模式(normal/darkenOnly/lightenOnly/anisotropic) 默认 "normal";seed 随机种子(0…999999) 默认 3
tiles拼贴风格化number 拼贴数(1…99) 默认 10;maxOffset 最大位移(1…99 %) 默认 10;fill 填充空白区域用(background/foreground/inverse/unaltered) 默认 "background";seed 随机种子(0…999999) 默认 5
extrude凸出风格化type 类型(blocks/pyramids) 默认 "blocks";size 大小(2…255 像素) 默认 30;depth 深度(1…255) 默认 30;depthMode 深度依据(random/level) 默认 "random";solidFront 立方体正面(bool) 默认 true;seed 随机种子(0…999999) 默认 9
oilPaint油画风格化stylization 描边样式(0.1…10) 默认 3;cleanliness 描边清洁度(0…10) 默认 5;scale 缩放(0.1…10) 默认 1;bristle 硬毛刷细节(0…10) 默认 5;lighting 光照(bool) 默认 true;angle 角度(-180…180 度) 默认 -60;shine 闪亮(0…10) 默认 1
highPass高反差保留其它radius 半径(0.1…250 像素) 默认 3
offset位移其它dx 水平(-4000…4000 像素) 默认 10;dy 垂直(-4000…4000 像素) 默认 10;wrap 折回(bool) 默认 true
maximum最大值其它radius 半径(0…100 像素) 默认 1
minimum最小值其它radius 半径(0…100 像素) 默认 1
hsbHslHSB/HSL其它mode 行序(hsb/hsl) 默认 "hsb"
custom自定其它matrix 卷积核 默认 [0,0,0,0,0,0,0,-1,0,0,0,-1,5,-1,0,0,0,-1,0,0,0,0,0,0,0];scale 缩放(1…9999) 默认 1;offset 位移(-9999…9999) 默认 0
clouds云彩渲染octaves 细节层数(1…9) 默认 6;seed 随机种子(0…999999) 默认 1
differenceClouds分层云彩渲染seed 随机种子(0…999999) 默认 1
fibers纤维渲染variance 差异(1…64) 默认 16;strength 强度(1…64) 默认 4;seed 随机种子(0…999999) 默认 1
lensFlare镜头光晕渲染brightness 亮度(10…300 %) 默认 100;centerX 中心 X(0…100 %) 默认 30;centerY 中心 Y(0…100 %) 默认 30;lens 镜头类型(zoom/prime35/prime105/movie) 默认 "zoom"
lighting光照效果渲染type 光源(spot/point/infinite) 默认 "spot";intensity 强度(-100…100) 默认 35;centerX 中心 X(0…100 %) 默认 50;centerY 中心 Y(0…100 %) 默认 50;radius 范围(1…200 %) 默认 50;angle 方向(-180…180 度) 默认 -45;ambience 环境(-100…100) 默认 8;gloss 光泽(-100…100) 默认 0;bump 纹理高度(亮度)(0…100) 默认 0;useFg 光色用前景色(bool) 默认 false
wave波浪扭曲generators 生成器数(1…99) 默认 5;wavelengthMin 波长 最小(1…998) 默认 10;wavelengthMax 波长 最大(2…999) 默认 120;amplitudeMin 波幅 最小(0…998) 默认 5;amplitudeMax 波幅 最大(1…999) 默认 35;scaleH 比例 水平(0…100 %) 默认 100;scaleV 比例 垂直(0…100 %) 默认 100;type 类型(sine/triangle/square) 默认 "sine";edge 未定义区域(wrap/repeat) 默认 "wrap";seed 随机种子(0…999999) 默认 1
ripple波纹扭曲amount 数量(-999…999 %) 默认 100;size 大小(small/medium/large) 默认 "medium"
polar极坐标扭曲mode 方式(rectToPolar/polarToRect) 默认 "rectToPolar"
pinch挤压扭曲amount 数量(-100…100 %) 默认 50
shear切变扭曲amount 弯曲(-100…100 %) 默认 30;shape 曲线形状(bow/s) 默认 "bow";edge 未定义区域(wrap/repeat) 默认 "wrap"
spherize球面化扭曲amount 数量(-100…100 %) 默认 100;mode 模式(normal/horizontal/vertical) 默认 "normal"
zigzag水波扭曲amount 数量(-100…100) 默认 10;ridges 起伏(1…20) 默认 5;style 样式(aroundCenter/outFromCenter/pondRipples) 默认 "pondRipples"
displace置换扭曲scaleH 水平比例(-999…999 %) 默认 10;scaleV 垂直比例(-999…999 %) 默认 10;mode 置换图(stretch/tile) 默认 "stretch";edge 未定义区域(wrap/repeat) 默认 "repeat"
twirl旋转扭曲扭曲angle 角度(-999…999 度) 默认 50
fieldBlur场景模糊模糊画廊blur 模糊(0…500 像素) 默认 15
irisBlur光圈模糊模糊画廊blur 模糊(0…500 像素) 默认 15;centerX 中心 X(0…100 %) 默认 50;centerY 中心 Y(0…100 %) 默认 50;radiusX 半径 X(1…200 %) 默认 30;radiusY 半径 Y(1…200 %) 默认 30;feather 过渡(0…100 %) 默认 60
tiltShift移轴模糊模糊画廊blur 模糊(0…500 像素) 默认 15;centerY 中心 Y(0…100 %) 默认 50;angle 角度(-90…90 度) 默认 0;focus 清晰带(半高)(0…100 %) 默认 10;transition 过渡(0…100 %) 默认 20
spinBlur旋转模糊模糊画廊angle 模糊角度(0…360 度) 默认 15;centerX 中心 X(0…100 %) 默认 50;centerY 中心 Y(0…100 %) 默认 50;radius 半径(1…200 %) 默认 40
ntscNTSC 颜色视频—
deinterlace逐行视频eliminate 消除(odd/even) 默认 "odd";create 创建新场方式(duplication/interpolation) 默认 "interpolation"
bumpMap生成凹凸图3Dblur 模糊(0…50 像素) 默认 1;contrast 对比度(-100…100) 默认 0;invert 反相高度(bool) 默认 false
normalMap生成法线图3Dblur 模糊(0…50 像素) 默认 1;strength 细节缩放(0.1…20) 默认 2;contrast 对比度(-100…100) 默认 0;invert 反相高度(bool) 默认 false
autoTone自动色调调整—
autoContrast自动对比度调整—
autoColor自动颜色调整—
shadowsHighlights阴影/高光调整shadowAmount 阴影 数量(0…100 %) 默认 35;shadowTone 阴影 色调(0…100 %) 默认 50;highlightAmount 高光 数量(0…100 %) 默认 0;highlightTone 高光 色调(0…100 %) 默认 50;radius 半径(0…2500 像素) 默认 30;colorCorrection 颜色(-100…100) 默认 20;midtoneContrast 中间调(-100…100) 默认 0
hdrToningHDR 色调调整radius 半径(1…500 像素) 默认 50;strength 强度(0.1…4) 默认 0.5;gamma 灰度系数(0.1…2) 默认 1;exposure 曝光度(-5…5) 默认 0;detail 细节(-100…300 %) 默认 30;vibrance 自然饱和度(-100…100 %) 默认 0;saturation 饱和度(-100…100 %) 默认 20
matchColor匹配颜色调整luminance 明亮度(1…200) 默认 100;intensity 颜色强度(1…200) 默认 100;fade 渐隐(0…100) 默认 0;neutralize 中和(bool) 默认 true
replaceColor替换颜色调整color 取样颜色 默认 [255,0,0];fuzziness 颜色容差(0…200) 默认 40;hue 色相(-180…180) 默认 0;saturation 饱和度(-100…100) 默认 0;lightness 明度(-100…100) 默认 0
cameraRawCamera Raw 滤镜校正temperature 色温(-100…100) 默认 0;tint 色调(-100…100) 默认 0;exposure 曝光(-5…5) 默认 0;contrast 对比度(-100…100) 默认 0;highlights 高光(-100…100) 默认 0;shadows 阴影(-100…100) 默认 0;whites 白色(-100…100) 默认 0;blacks 黑色(-100…100) 默认 0;texture 纹理(-100…100) 默认 0;clarity 清晰度(-100…100) 默认 0;dehaze 去除薄雾(-100…100) 默认 0;vibrance 自然饱和度(-100…100) 默认 0;saturation 饱和度(-100…100) 默认 0
lensCorrection镜头校正校正distortion 移去扭曲(-100…100) 默认 0;caRed 修复红/青边(-100…100) 默认 0;caBlue 修复蓝/黄边(-100…100) 默认 0;vignette 晕影 数量(-100…100) 默认 0;midpoint 晕影 中点(0…100) 默认 50;vertical 垂直透视(-100…100) 默认 0;horizontal 水平透视(-100…100) 默认 0;angle 角度(-180…180 度) 默认 0;scale 比例(50…150 %) 默认 100;edge 边缘(clear/repeat) 默认 "clear"
破坏性调整清单(16 种,applyAdjustment 的 adjustment.type)
type名称参数
exposure曝光度exposure(-20…20) offset(-0.5…0.5) gamma(0.01…9.99) workingGamma(1…3 或 "srgb",默认 2.2)
levels色阶inputBlack(0…254) inputWhite(1…255) gamma(0.01…9.99) outputBlack(0…255) outputWhite(0…255) channel(rgb|r|g|b) channels({rgb,r,g,b})
curves曲线points([[输入,输出],…] 2…19 个点,输入严格递增,0…255) channel(rgb|r|g|b) channels({rgb,r,g,b}) interpolation(spline|monotone|linear,默认 spline = PS 的自然三次样条)
hueSaturation色相/饱和度hue(-180…180) saturation(-100…100) lightness(-100…100) colorize colorizeHue(0…360) colorizeSaturation(0…100) reds/yellows/greens/cyans/blues/magentas(各 { hue, saturation, lightness },可带 range:[外低,内低,内高,外高])
vibrance自然饱和度(近似)vibrance(-100…100) saturation(-100…100)
photoFilter照片滤镜(近似)color([r,g,b],每项 0…255) density(0…100) preserveLuminosity(boolean)
selectiveColor可选颜色plates(10×4 个 -100…100 整数,plates[0] 为 PSD 占位) method(0 相对 / 1 绝对)
brightnessContrast亮度/对比度(旧式)brightness(-150…150) contrast(-100…100,PS 面板只给 -50…100) useLegacy(必须 true) meanValue(0…255,默认 127)
invert反相(无参数)
posterize色调分离levels(2…255 整数)
threshold阈值level(1…255 整数)
desaturate去色(无参数)
blackWhite黑白red yellow green cyan blue magenta(均 -200…300,默认 40/60/40/60/20/80)useTint(boolean) tintColor([r,g,b],useTint 时必填)
channelMixer通道混合器monochrome red/green/blue/gray 各 { red, green, blue, constant }(-200…200)
colorBalance色彩平衡(近似)shadows/midtones/highlights(各 3 个 -100…100:[青-红, 洋红-绿, 黄-蓝])preserveLuminosity
gradientMap渐变映射stops([{ position:0…1, color:[r,g,b], midpoint?:0…1 }, …] 至少 2 个) reverse

片段协议:为什么不用上传 PSD

编辑智能对象的网格 / 变形,需要的只是那一层的放置块字节(SoLd / PlLd),通常 1–5 KB,操控变形大一些(约 100 KB)。像素、内嵌源图、其它图层,一个字节都不需要送上来。

浏览器(你的客户端) FullPsd 服务端 整份 PSD(可能几百 MB) smartObjectFragment() 切出放置块 replacePlacedBlocks() 拼回 同步修正各级长度字段 → 导出 resolveFragment() 放置块 → 网格 / 变形结构 writebackFragment() 编辑结果 → 新放置块字节 POST /api/so/resolve · KB 级 base64 POST /api/so/writeback · 只回变化的块 服务端不落盘 · 不留状态 · 看不到像素与源图
片段协议的字节流向。整份 PSD 始终留在客户端;跨网络的只有放置块。

三步

  1. 切 —— 客户端从 PSD 里取出该智能对象的放置块,连同图层名与包围盒组成 fragment。
  2. 问 / 写 —— /api/so/resolve 把放置块翻译成可编辑的网格与变形结构;编辑完再用 /api/so/writeback(或 /api/doc/edit 的片段模式)换回新块字节。
  3. 拼 —— 客户端把返回的块替换进原文件,并同步修正块长度、图层 extra 长度、层信息段与 LMI 长度。

两个端点都是纯函数:不保存任何客户数据、不需要像素、也不需要文件其余部分。

什么时候该用哪条路

片段协议文件模式(/api/doc/edit multipart)
上行数据KB 级放置块整份 PSD
谁来拼回客户端服务端
能做什么只有网格 / 变形写回全部 op(属性 / 文字 / 像素 / 换源图 / 变形)
并发闸不占占
适合浏览器编辑器、隐私敏感场景、大文件服务端集成、批处理
两条路的产物逐字节相同。文件模式内部就是先切 fragment、走同一段 writebackFragment、再拼回 —— 不是两份实现。server/test-edit-api.js 对此有逐字节断言。

变形能力

Photoshop 里能对智能对象做的变形有两大类,落在 PSD 里的位置完全不同。FullPsd 四种都能读、能改、能写回,导出后在 PS 里仍可继续编辑(不是烘死的像素)。

类型PSD 里的位置可编辑的东西写回 mode
普通 warpSoLd 的 warp 描述符4×4 控制点(1 个面片)warp
quiltWarp(拆分网格)同上,变长重写任意 xn×yn 控制点 + 经纬拆分线quiltWarp
透视变形(智能滤镜 714)filterFX → perspectiveWarpTransform网格顶点(相邻面共用)deform / 新建 deformCreate
操控变形(智能滤镜 687)filterFX → rigidTransform图钉(位置 / 固定旋转 / 深度),其余顶点按 MLS 跟随deform / 新建 deformCreate

网格(warp / quiltWarp)

控制点是文档坐标的 {x,y},按行主序排列,共 xn*yn 个。4×4 就是一个 Bezier 面片;每加一条经线 xn += 3,每加一条纬线 yn += 3。

写回时服务端自己判断:点数 / 拆分线没变就原地改点(mode:"warp"),加过拆分线就走变长重写(mode:"quiltWarp")。两种在 PS 里都还能继续拖网格(Ctrl/Cmd + T → 变形)。

edit:网格

{ "points": [ { "x": 418, "y": 417.9 }, { "x": 539.7, "y": 417.9 }, "…共 xn*yn 个…" ],
  "xn": 4, "yn": 4,
  "sliceX": [], "sliceY": [] }

约束:控制点数 1…4096,坐标必须是有限数;给了 xn/yn 时点数必须等于 xn*yn;当前不是规则网格又要改点数时,xn/yn 必须同时给。

透视变形(714)

edit.deform 结构(字段来自 deformToJSON) 结构示意

{ "deform": {
    "kind": "perspectiveWarp",
    "HplaceInv": [ "…3×3 放置逆矩阵…" ],
    "vertices": [ { "x": 0,   "y": 0 }, { "x": 800, "y": 0 } ],
    "warped":   [ { "x": 12,  "y": 30 }, { "x": 790, "y": 8 } ],
    "quadIdx":  [ [0, 1, 2, 3] ]
} }
字段说明
vertices原始顶点表(共享,相邻四边形引用同一批下标)
warped你要改的就是它 —— 拖动后的顶点位置,写回时落到 warpedVertices
quadIdx每个四边形引用的 4 个顶点下标
HplaceInv放置逆矩阵,用于文档坐标与滤镜坐标之间换算
at / enabled / name这条在 filterFXList 里的下标 / 启用位(PS 图层面板上那只眼睛)/ 显示名。一层挂好几条时靠 at 分辨改的是哪一条

操控变形(687)

edit.deform 结构 结构示意

{ "deform": {
    "kind": "puppetWarp",
    "HplaceInv": [ "…3×3…" ],
    "shapes": [ {
      "srcIndex": 0,
      "orig": [ { "x": 0, "y": 0 } ],
      "def":  [ { "x": 4, "y": -2 } ],
      "indices": [ 0, 1, 2 ],
      "pins": [ { "orig": { "x": 120, "y": 300 },
                  "pos":  { "x": 128, "y": 292 },
                  "vertex": 17,
                  "rot": 0, "depth": 0 } ]
    } ]
} }
字段说明
shapes[].orig / def三角网的原始顶点 / 变形后顶点(写回时落到 deformedVertexArray)
shapes[].indices三角形索引
shapes[].pins图钉:{ orig, pos, vertex, rot, depth }(写回时分别落到 PinP / posFinalPins / pinVertexIndices / PnRt / PnDp)
pins[].rot这枚钉的固定旋转角(度,0 = 自动,对应 PS 选项栏的「旋转」)。落到 PnRt
pins[].depth图钉深度:网格自重叠时谁压着谁(对应 PS 的「图钉前移 / 后移」)。落到 PnDp
shapes[].boundary可选,网格片的边界折线(一圈或多圈,文档坐标)。新建时写成 meshBoundaryPath;改现有滤镜时不需要

交互上只需要拖图钉:其余顶点按 MLS(Moving Least Squares,Schaefer 2006)跟随,约束强度由「模式」决定 —— 刚性只允许旋转、正常再加等比缩放、扭曲放开完整仿射。写回时 def 与 pins 两处必须一起更新 —— PS 两处都读。

FEid:为什么双击智能对象会「恢复原样」

带智能滤镜(透视 / 操控变形)的图层,Photoshop 重新求值时用的不是内嵌源图,而是全局 FEid 块里那份「滤镜之前」的放置栅格缓存。只换源图不更新这份缓存,双击进去再出来,PS 就会拿旧缓存重算 —— 看起来像「恢复原样」。

所以 /api/render 与 replaceSmartObject 换源图时会同步重写 FEid。响应元数据里的 applied[].filterCacheUpdated / ops[].meta.filterCacheUpdated 就是这件事的回执:true 已更新 · null 这层没有变形滤镜、不适用 · 失败字符串则说明没更新成功(PS 双击后可能恢复原图)。

/api/so/resolve 返回的 layerInfoBase 就是这份「滤镜之前」的放置信息,重建 FEid 用得上。

不能做的事

  • 变形滤镜层没有 Bezier 网格:传 points / mesh 会 400,请传 deform。
  • 普通图层上没有变形滤镜:传 deform 而不带 create 会 400 —— 要么传 points,要么带 create:true 新建一条。
  • 一层最多叠 8 条变形滤镜,再加会 400;要改的条数比层上有的还多也会 400。
  • 智能对象不是规则网格(points.length !== xn*yn)时无法渲染 —— /api/psd/info 的 mesh.regular 会告诉你。
  • 外部链接的智能对象(非内嵌)无法替换源图。

一致性保证

PSD 是一个「长度字段套长度字段」的格式。只要有一处对不上,Photoshop 就只会说一句「意外地遇到文件尾」或者「程序错误」,不会告诉你哪里坏了。所以本平台的底线是:宁可不给文件,也不给一个坏文件。

出文件前必过的三道守门

  1. lintPsd —— 按长度字段逐段走完整个文件,任何一段对不上就失败。
  2. 能重新解析 —— 用 parsePsdDocument 把刚生成的字节再读一遍。读不回来的文件不发出去。
  3. 自身字节往返 —— serialize(parse(新文件)) === 新文件。新文件必须自洽,否则说明我们的模型对它已经不准了。

三道都过才返回文件字节。任何一道不过 → 422 + 结构化定位(stage 是哪一道、offset 首个差异偏移、ops 每条 op 的执行情况),响应体里没有文件。

/api/render(output.format=psd)导出前同样会过 lintPsd,不过时也是 422(stage:"lint" / code:"artifact_lint_failed")—— 「产物不合格」在所有出文件的端点上是同一个状态码,和「入参错了」(400)分得开。

以输入为基线

真实世界里有大量本来就体检不过的 PSD(我们自己的语料里就有一批)。如果一律拦下来,等于把别人的历史包袱算到本次编辑头上。

所以守门不过时会再体检一遍输入文件,只比对「错误种类」(偏移与图层名会随编辑漂移,先归一化再比):

  • 出现了输入文件没有的新错误 → 422,这是本次编辑的账;guard.fresh 里就是这些新错误。
  • 全是输入本来就有的问题 → 放行,降级成 warnings,并在里面写清楚「输入文件本身就没过体检,本次编辑没有新增毛病」。

guard.inputWasHealthy 会告诉你输入文件本身健不健康。这套口径在 core/psd-edit.js 与 server/edit-api.js 里是同一套。

三条写回路径产物相同

这条路与这条路关系
/api/doc/edit 的 replaceSmartObject/api/render(output.format=psd)同参数下逐字节相同(复用同一段 preparePsdRender + writePsdOutput)
/api/doc/edit 的 deform(文件模式)/api/so/writeback + 客户端拼回逐字节相同(同一段 writebackFragment)
/api/doc/edit 片段模式/api/so/writeback同一实现、同字段同顺序
/api/doc/edit/incremental(改文字 / 改属性 / 改名 / 变形 / 放置描述符)/api/doc/edit 文件模式的同名 op逐字节相同(server/test-incremental.js 对每种操作跑两遍再逐字节比较)

这些不是「碰巧一样」,而是共用同一段代码带来的结构性保证;server/test-edit-api.js 里有逐字节断言。

解析层的红线

parsePsdDocument 把文件解析成七段语义树,每段都同时保留绝对偏移与 _raw 原始字节。未修改时 serializePsdDocument 与原文件逐字节相同 —— 读不懂的东西一律退回 _raw 原样保留,绝不为了语义牺牲往返。想自己验证任何一份文件,打 /api/doc/validate。

兼容性与覆盖率

下面的数字来自 docs/psd-coverage.md(由 node tools/coverage.js 生成,报告时间 2026-09-07),语料共 167 个文件:ag-psd 写的特性矩阵、ImageMagick 写的色彩模式与位深矩阵、手写字节造的规范边界样本,外加真实 Photoshop 导出文件。

项目覆盖情况
字节级 round-trip167 / 167 全部通过
图像资源 ID语料里出现 41 个,其中 37 个走语义解析(其余原样往返)
附加图层信息 key注册表共 86 个;语料里出现 56 个,其中 50 个走语义解析
色彩模式全部覆盖(Bitmap / Grayscale / Indexed / RGB / CMYK / Multichannel / Duotone / Lab)
位深 × 压缩1/8/16/32 位 × RAW / RLE / ZIP 全部解码通过
混合模式未覆盖:无
EngineData(文字引擎)9 / 9 段做到 serialize(parse(b)) === b

未覆盖项分三类原因

原因意思例子
规范未公开Adobe 没有公开这段格式,只能 _raw 原样保留资源 1001/1002(Mac 打印记录)、1089–1093/1096/1097(CC 之后新增、规范未收录)、双色调设定块
缺语料已实现或已知格式,但语料里没出现过,未经验证压缩方式 ZIP+prediction;描述符 OSType comp/type/GlbC;路径 selector OPEN_LENGTH 等
待实现能做、还没做报告会在重新生成时自动列出

还有一类是另有独立规范的外部数据流,不属于 PSD 语义范围,一律原样往返:1028 IPTC-NAA、1058 EXIF。对这些块只记录可复核的观察(长度、按 uint32 切开的字、是否全 0),不做语义猜测、不编造字段名。

位图合成的兼容性提示

PSD 的「图层像素」并不总等于 Photoshop 的最终合成结果。服务端位图合成不会重现下面这些,遇到时会明确提示而不是默默给你一张看似成功、实则有偏差的图:

code说明
adjustment-layer调整图层(曲线、色阶…)不会被应用
fill-layer参数化填充图层(纯色 / 渐变 / 图案)可能无法重现
vector-mask矢量蒙版:只用已栅格化的图层数据
layer-effects图层样式不会被重绘
unsupported-blend-mode未实现的混合模式,按正常模式预览
group-isolation组不透明度 / 组混合模式:按子图层合成,可能与 PS 不同
thumbnail缩略图资源没能更新

这些出现在 /api/psd/info 的 compatibilityWarnings 与 /api/render 的 X-Render-Meta.compatibilityWarnings 里(最多 20 条明细,另给总数)。PSD 输出不受影响 —— 那条路保留原始结构,这些提示只针对扁平化的位图预览。

MCP 服务(给 AI agent 用)

标准 Model Context Protocol 服务端,挂在同一个服务的 /mcp 上。Claude、Cursor、VS Code 或任何 MCP 客户端配好地址与 key,agent 就能打开 PSD、看图层、批量编辑、看预览、导出 —— 背后走的是与 /api/doc/edit 完全相同的编辑管线与守门,产物照样是 PS 可编辑的 PSD。

端点
POST https://your-host/mcp(Streamable HTTP;响应一律 application/json,不开 SSE 流)
鉴权
Authorization: Bearer <API key> 或 X-Api-Key(与 REST 同一套 key、同一套限流与并发闸)
协议版本
2025-11-25 / 2025-06-18 / 2025-03-26 / 2024-11-05,按客户端请求协商
能力
tools(19 个,含字体库 upload_font / list_fonts / delete_font 与档位查询 get_account;只列当前档位能用的)· resources(能力目录 + 每份打开文档的大纲 / 预览)· prompts(2 个)

接入

Claude Code

claude mcp add --transport http fullpsd https://your-host/mcp \
  --header "Authorization: Bearer $FULLPSD_KEY"
Cursor(~/.cursor/mcp.json)

{
  "mcpServers": {
    "fullpsd": {
      "url": "https://your-host/mcp",
      "headers": { "Authorization": "Bearer YOUR_FULLPSD_KEY" }
    }
  }
}
VS Code(.vscode/mcp.json)

{
  "servers": {
    "fullpsd": {
      "type": "http",
      "url": "https://your-host/mcp",
      "headers": { "Authorization": "Bearer ${input:fullpsdKey}" }
    }
  },
  "inputs": [{ "id": "fullpsdKey", "type": "promptString", "description": "FullPsd API key", "password": true }]
}
只支持 stdio 的客户端(经 mcp-remote 桥接)

{
  "mcpServers": {
    "fullpsd": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://your-host/mcp", "--header", "Authorization:${FULLPSD_AUTH}"],
      "env": { "FULLPSD_AUTH": "Bearer YOUR_FULLPSD_KEY" }
    }
  }
}
只支持 stdio 的客户端(仓库自带的本地代理:还能直接读 / 存本机文件)

{
  "mcpServers": {
    "fullpsd": {
      "command": "node",
      "args": ["/path/to/fullpsd/server/mcp-stdio.js"],
      "env": { "FULLPSD_MCP_URL": "https://your-host/mcp", "FULLPSD_API_KEY": "YOUR_FULLPSD_KEY" }
    }
  }
}

server/mcp-stdio.js 只用 Node 自带模块(不用 npm install),把 JSON-RPC 原样转给远程 /mcp,另外多给工具三个本地参数:path(代替 base64,如 open_document(path="~/a.psd")、upload_font(path=…))、image_path(代替 image_base64)、导出类工具的 save_to(导出后存到本机目录 / 文件)。读本机文件上限 FULLPSD_MCP_STDIO_MAX_MB(默认 200)。

只能填一个 URL、没法配请求头的客户端:部署方设 FULLPSD_MCP_URL_KEY=1 后可以用 https://your-host/mcp/k/<API key>。代价是 key 会出现在 URL 与访问日志里,只建议给专用的低权限 key 用。

用 curl 验证(应返回 serverInfo 与 instructions)

curl -s -X POST https://your-host/mcp \
  -H "Authorization: Bearer $FULLPSD_KEY" -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'

调试台 /console#mcp 有一个现成的 MCP 调试面板:initialize → 选工具 → 填 arguments → 直接看 agent 会拿到的结果(含预览图)。

工具

工具做什么参数(* 必填)性质
get_catalog
能力目录
查询本服务支持的全部编辑 op(含参数 schema 与示例)、滤镜、破坏性调整、调整图层、图层样式、混合模式、新建文档预设。先用它弄清 ops 怎么写。section op category只读
new_document
新建文档
新建空白 PSD(同编辑器「文件 › 新建」)。可给 preset(如「默认 Photoshop 大小」「电商主图」,见 get_catalog section=newDocument)或 width/height。width height preset resolution mode depth background backgroundColor name写
open_document
打开文档
把一份 PSD/PSB 放进服务端工作区,返回 doc_id 与图层大纲。来源:url(http/https 公网地址)或 base64。大文件也可以先 POST /mcp/upload。url base64 name写 · 访问外网
list_documents
工作区文档
列出当前凭证在工作区里打开着的文档(doc_id、版本、可撤销步数、剩余存活时间)。—只读
inspect_document
查看图层
文档尺寸、画板、图层大纲(缩进 = 层级,[n] 就是 ops 里用的 index;底 = 0)。detail=true 附带每层的结构化字段。doc_id* detail只读
render_preview
渲染预览
按图层重新合成并返回预览图(可只出一块画板或一个区域)。用来确认编辑效果。doc_id* artboard max_size region format hide只读
render_layer
单层预览
只渲染一个图层的像素(不含其它层),看某层到底长什么样。doc_id* index* max_size只读
edit_document
编辑文档
按顺序执行一组 ops(与 REST 的 /api/doc/edit 完全相同:改属性 / 文字 / 像素 / 滤镜 / 调整 / 画布 / 图像大小 / 旋转 / 移动 / 复制 / 蒙版 / 矢量 / 样式 / 智能对象 / 变形 / 画板…)。成功后生成新版本(可 undo),失败则文档不变。ops 写法:[{ "op":"setLayerProps", "index":3, "opacity":128 }, { "op":"applyFilter", "index":1, "filter":"gaussianBlur", "params":{"radius":4} }];图片参数(replaceSmartObject / replaceLayerPixels 的 image)可写 { "url": "https://…" } 或 data URL。完整参数见 get_catalog。doc_id* ops* dry_run label recomposite font_map写 · 访问外网
replace_smart_object
换智能对象图片
样机最常用的一步:把某个智能对象的内容换成一张图(按网格 / 透视 / 操控变形渲染,PS 里双击进去也是新图)。等价于 edit_document 的 replaceSmartObject op。doc_id* target image_url image_base64 fit写 · 访问外网
undo
撤销
回到上一个版本(可一次撤销多步)。doc_id* steps写 · 破坏性
export_document
导出
导出 PSD 或位图(png / jpg / webp / tiff;可只导出一块画板),返回限时下载链接。doc_id* format artboard max_size quality filename只读
export_artboards
按画板导出
每块画板导出一张图(PS 的「画板至文件」),打包成 zip 返回下载链接。doc_id* format artboards max_size quality只读
validate_document
体检
结构体检:lintPsd + 可解析 + 字节往返自检(与编辑出口的守门同一套)。doc_id*只读
get_smart_filters
智能滤镜
查看某个智能对象上的智能滤镜列表(改它们用 edit_document 的 setSmartFilters op)。doc_id* target只读
get_account
我的档位
当前凭证的档位(基础 / 高级…)、能用哪些功能、rpm / 并发 / 上传上限,以及按功能频率限制的剩余次数。某个工具或 op 报「不在档位」时用它确认。—只读
list_fonts
字体库
需 fonts.library
列出这把 API key 上传过的字体(服务端文字重绘时按 PostScript 名优先匹配)。PSD 里缺的字体可以用 upload_font 补上。—只读
upload_font
上传字体
需 fonts.library
把字体文件(TTF / OTF / TTC / OTC / WOFF)存进这把 key 的字体库,之后 edit_document 改文字时服务端用它重绘,不再用替代字体。TTC 会拆成多个字体。同 PostScript 名的旧字体会被替换。字体授权由上传方负责。url base64 filename faces写 · 访问外网
delete_font
删除字体
需 fonts.library
从字体库删掉一款字体(按 list_fonts 给的 id 或 PostScript 名)。id*写 · 破坏性
close_document
关闭文档
从工作区移除文档(释放空间)。没导出的改动会丢失。doc_id*写 · 破坏性

一次典型的会话

agent 视角 结构示意

open_document     { "url": "https://cdn.example.com/mockup/tshirt.psd" }
  → { "doc_id": "doc_pIWK1YrqGGKf", "width": 1200, "height": 1200,
      "outline": "[2] 无法无天  (text · 248×172 @431,200 · 文字「无法无天」)\n[1] img  (smartObject · 285×437 @461,370 · 混合 mul)\n[0] 背景  (raster · 1200×1200 @0,0)" }
replace_smart_object { "doc_id": "doc_pIWK1YrqGGKf", "target": "img", "image_url": "https://cdn.example.com/art/v2.png", "fit": "contain" }
edit_document     { "doc_id": "doc_pIWK1YrqGGKf", "ops": [
                      { "op": "setText", "index": 2, "text": "限时 8 折" },
                      { "op": "applyAdjustment", "index": 0, "adjustment": { "type": "hueSaturation", "hue": 20 } },
                      { "op": "moveLayer", "index": 2, "dx": 0, "dy": -60 } ] }
render_preview    { "doc_id": "doc_pIWK1YrqGGKf", "max_size": 800 }        → 一张 PNG(agent 直接看图确认)
export_document   { "doc_id": "doc_pIWK1YrqGGKf", "format": "psd" }       → { "url": "https://your-host/mcp/files/…/tshirt.psd", "expiresAt": "…" }

临时工作区

  • doc_id 归属凭证:文档挂在打开它的 API key 名下(按 key 本身区分,不按名字),别的 key 拿到 doc_id 也读不到。Mcp-Session-Id 会签发但不强制回带 —— 断线重连、负载均衡都不会丢文档;客户端发 DELETE /mcp 结束会话时,只关掉这个会话里打开的文档。
  • 版本与撤销:每次成功的 edit_document 生成新版本;默认保留最近 5 个历史版本(FULLPSD_MCP_HISTORY),undo 回退。失败的编辑不会产生版本(守门不过的产物进不了工作区)。
  • 上限:每个 key 同时最多 8 份(FULLPSD_MCP_MAX_DOCS);全进程 1 GB(FULLPSD_MCP_MAX_MB,含历史版本,满了先丢最老的历史);闲置 60 分钟清理(FULLPSD_MCP_TTL_MIN)。进程重启即清空。
  • 进出文件:进 —— open_document 的 url(服务端去取)或 base64;大文件用 POST /mcp/upload(multipart 字段 file,返回 doc_id,比 base64 省 1/3 流量)。出 —— export_document / export_artboards 返回 /mcp/files/<token>/文件名 的限时下载链接(默认 30 分钟,token 本身就是凭证),协议 ≥ 2025-06-18 时同时附一个 resource_link。
  • ops 里的图片:replaceSmartObject / replaceLayerPixels 的 image 可写 { "url": "https://…" }(服务端去取)或 data URL / base64。

安全

  • 按 URL 取文件防 SSRF:只允许 http/https;解析到内网 / 回环 / 链路本地 / 保留地址的主机一律拒绝,每一跳重定向都复查;有大小与超时上限。可用 FULLPSD_MCP_FETCH_HOSTS 收紧到白名单,FULLPSD_MCP_FETCH=off 整体关闭。
  • Origin 校验:带 Origin 头的请求只放行同源或 FULLPSD_MCP_ORIGINS 白名单(MCP 规范要求的 DNS 重绑定防护)。
  • 并发与限流:渲染 / 编辑 / 导出类工具占用与 REST 同一套重接口并发闸(撞闸时工具返回 isError 并提示稍后重试;按 URL 下载图片发生在占闸之前,慢链接不会占着名额);每次 HTTP 请求计一次 rpm,批量请求里每个额外的 tools/call 再各计一次。api-keys.json 里按 key 配的 maxUploadMB 对 MCP 的上传 / base64 / URL 取文件同样生效。

资源与提示

URI / 名字内容
fullpsd://catalog能力目录(同 GET /api/catalog)
fullpsd://documents/{doc_id}/summary文档尺寸、画板与图层大纲(JSON)
fullpsd://documents/{doc_id}/preview按图层合成的预览 PNG(长边 ≤1024)
prompt mockup-replace样机换图并导出(参数 psd_url、image_url)
prompt batch-resize改尺寸出图(参数 psd_url、width、height?)

错误

请求头 MCP-Protocol-Version 给了但不在支持列表里时回 400(规范要求);不给则按 2025-03-26 处理。

工具执行层面的失败(参数不对、op 校验失败、守门不过、文档过期…)按 MCP 规范放在工具结果里:{ "isError": true, "content": [{ "type": "text", "text": "一句话原因\n{定位 JSON}" }] },agent 能读到并自行修正;定位 JSON 与 REST 同口径(opIndex / op / stage / code)。协议层面的错误走 JSON-RPC error:-32601 方法不存在 · -32602 工具名 / 参数形状不对 · -32002 资源不存在 · -32700 JSON 解析失败。HTTP 层:401 缺 key 或 key 无效 · 403 Origin 不允许 · 405 GET(本服务不开 SSE 流)· 429 超出 rpm。

本机命令行(fullpsd)

同一套内核与编辑管线的本机版本:不用起服务、不上传文件,读 / 改 / 合成 / 转格式都在本进程里做,结果与 /api/doc/edit 相同。所有命令往 stdout 写一行 JSON,适合脚本和本地 agent(Claude Code、Codex 等)调用。

位置
bin/fullpsd.js(仓库根目录与 server/ 各 npm ci 一次即可用;npm link 后可直接敲 fullpsd)
失败
{"ok":false,"error":{"code","message"}},退出码 1;REVISION_CONFLICT 退出码 3
远端
给 --server URL(或环境变量 FULLPSD_SERVER)就改成发给那台服务的 /api/doc/edit(鉴权用 FULLPSD_API_KEY)
命令做什么
capabilities [--query 词] [--section filters|adjustments|…]能做什么:op 清单、滤镜 / 调整 / 样式 / 混合模式(与 GET /api/catalog 同一份)
describe <op>一个 op 的字段、说明与例子
inspect <file.psd>尺寸 / 模式 / 图层表(index 底 = 0)/ 组树 + revision(内容指纹)
render <file> [--out a.png] [--max N] [--layers 0,2]合成预览(输出格式按扩展名)
execute <file> --op 名 --params JSON [--revision R] [--out f] [--dry-run]执行一个 op
batch <file> --input steps.json [--revision R] [--out f]一批 op = 一个事务(全成或全不成);as 给结果起名,后面用 {"$ref":"名.字段"} 引用
new · convert <in> <out>新建 PSD;PSD / PNG / JPEG / WebP / TIFF / TGA / BMP / PPM / PAM / HDR / EXR 互转
droplet create --input steps.json --name N --format png生成拖放批处理(macOS / Linux .command,Windows .cmd)
skills install [--target claude|codex|agents]给本地 agent 装技能说明(编辑 / 批量 / 预览导出 / 样机换图)
mcp · mcp --remotestdio MCP:默认按本机文件路径干活(render 会把预览图直接附给 agent);--remote 转发到 FULLPSD_MCP_URL 的完整服务 MCP
steps.json

[
  { "op": "createLayer", "name": "Logo", "as": "logo" },
  { "op": "replaceLayerPixels", "index": { "$ref": "logo.index" }, "image": "@logo.png" },
  { "op": "addTextLayer", "text": "限时 5 折", "x": 120, "y": 160, "fontSize": 64, "color": "#d0021b" },
  { "op": "applyFilter", "index": 0, "filter": "gaussianBlur", "params": { "radius": 4 } }
]

图片参数直接给本机路径:"@logo.png" 或 { "path": "logo.png" }(相对 steps 文件所在目录)。改之前先 inspect 拿 revision,编辑时带上:文件在你看过之后被别人改过(比如在编辑器里)就拒绝,不会在旧状态上盲改。

自托管与环境变量

bash

npm ci                       # 根目录:构建工具
npm ci --prefix server       # 服务端依赖
npm run build                # 生成 web/dist 与 server/engine/engine.js
npm test
npm start                    # 默认 http://localhost:8787

站点路由:/ 官网 · /editor 编辑器 · /docs 本文档 · /console 调试台。旧地址 /api.html 会 301 到 /console。开发模式 npm run dev(FULLPSD_DEV=1 FULLPSD_AUTH=off)直接托管 web/ 源码与 core/,改完刷新即可。

服务端文字重绘(字体)

改文字(setText / addTextLayer / 改样式)之后,服务端会用编辑器同一套排版(字号、字距、行距、段落框、对齐、仿粗斜、下划线…)把文字层重新画一遍,预览、合成图、缩略图才是新字。画字用可选依赖 @napi-rs/canvas(npm ci 时按平台自动装;装不上也不影响编辑,只是文字栅格保持旧的,/health 的 textRender.available 会是 false)。

  • 字体按 PostScript 名找(PSD 里记的就是它,如 SourceHanSansSC-Bold):扫描 server/fonts/、FULLPSD_FONT_DIRS、/usr/share/fonts 等系统目录里的 TTF / OTF / TTC,读 name 表建索引。设计稿常用的商用字体请自行放进 server/fonts/(注意授权)。
  • 缺字体:先按族名找同族其它字重(缺粗体就合成粗体),再按书写系统用替代字体(中文优先 Noto Sans CJK / 思源黑体 / 文泉驿,西文优先 Liberation Sans / Arial),并在 warnings 里写明「哪款 → 哪款」。PS 打开文件时仍按它自己的字体重排,文件里的文字数据不受替代影响。
  • 服务器上至少装一套中文字体,否则中文会是方框:Debian / Ubuntu apt install fonts-noto-cjk,CentOS / 宝塔 yum install google-noto-sans-cjk-ttc-fonts(或把 ttf 丢进 server/fonts/)。
  • 自传字体:服务器上没有的字体,调用方自己给 —— 三种方式,匹配优先级从高到低:
    1. output.fontMap(MCP:edit_document.font_map)显式指定「PSD 里的名字 → 哪款字体」;
    2. 本次请求 multipart 里带的 font 文件(单次有效、不落盘,编辑器会话也能用);
    3. 这把 API key 的字体库(POST /api/fonts,MCP:upload_font / list_fonts / delete_font);
    之后才是服务器字体目录与替代字体。自传字体只在带它的请求里可见(按 key / 按请求隔离)。入库前做结构校验(表目录在界内、必需表齐全、name 表可读),有单文件 / 款数 / 总量上限。字体授权由上传方负责。
  • 不重现的:文字变形(warp)与路径文字按直排近似(给 warning);只挪 / 缩放 / 旋转过的文字层不重绘(像素已跟着变换,保留 PS 原字体排的栅格)。强制重绘用 op renderText。

环境变量

变量默认作用
PORT8787监听端口
MAX_UPLOAD_MB200单文件上传上限(multer)
FULLPSD_AUTH—设为 off 关闭鉴权(仅限本机调试)
FULLPSD_API_KEYS—"key:名字:rpm,key2:名字2"
FULLPSD_API_KEYS_FILEserver/api-keys.jsonkey 的 JSON 文件路径
FULLPSD_EDITOR_KEY—设为 1 时签发会话也要求 X-Api-Key
FULLPSD_SESSION_SECRET每次启动随机会话签名密钥。不设则重启后旧会话失效
FULLPSD_SESSION_TTL_HOURS12会话有效期
FULLPSD_SESSION_RPM240会话主体的每分钟请求数
FULLPSD_MAX_CONCURRENT2重接口全局并发上限(超出 429 scope:"global",不排队);每 key 的上限另在 api-keys.json 的 concurrency 配,两道叠加
FULLPSD_DOC_MAX_PIXELS40000000解像素的单图上限(超出 413)
FULLPSD_DOC_TIMEOUT_MS20000/api/doc/* 阶段耗时闸(超出 504)
FULLPSD_DOC_MAX_OPS512单次 ops 条数上限
FULLPSD_EDIT_TIMEOUT_MSmax(DOC_TIMEOUT, 60000)/api/doc/edit 阶段耗时闸
FULLPSD_EDIT_JSON_LIMIT32mb片段模式 JSON 上限
FULLPSD_SO_JSON_LIMIT32mb/api/so/* 的 JSON 上限(也是上一项的回退值)
FULLPSD_CORS_ORIGINS—放行的来源列表,逗号分隔;* 放行任意来源
FULLPSD_TRUST_PROXY—设为 1 时信任反向代理(trust proxy)
FULLPSD_DEV—设为 1 进开发模式:托管源码、错误响应附 stack
FULLPSD_FONT_DIRS—服务端文字重绘的额外字体目录(冒号或逗号分隔);另外总会找 server/fonts/ 与系统字体目录
FULLPSD_PLANS_FILEserver/plans.json档位配置(不存在则用内置默认:基础 / 高级);改了自动重载。见 档位与权限
FULLPSD_USER_FONT_DIRserver/data/user-fonts字体库存放目录(每把 key 一个子目录)。多实例部署请指向共享盘
FULLPSD_USER_FONT_MAX_MB40单个字体文件上限(MB)
FULLPSD_USER_FONT_MAX_COUNT100每把 key 的字体库最多几款
FULLPSD_USER_FONT_QUOTA_MB500每把 key 的字体库总量(MB)
FULLPSD_REQUEST_FONT_MAX8单次请求最多带几个 font 文件
FULLPSD_USER_FONT_CACHE64进程里同时注册的自传字体上限(超出按最久未用卸载,下次用到再装)
FULLPSD_FALLBACK_FONT自动缺字体时优先用的替代字体(族名或 PostScript 名);不设则中文用 Noto Sans CJK / 思源黑体 / 文泉驿…,西文用 Liberation Sans / Arial…
FULLPSD_PUBLIC_URL按请求推断对外地址(如 https://fullpsd.zaopod.com),MCP 导出链接用它拼;反向代理后面建议显式设置(或设 FULLPSD_TRUST_PROXY=1)
FULLPSD_MCP_MAX_DOCS8MCP 工作区:每个凭证同时打开的文档数上限
FULLPSD_MCP_MAX_MB1024MCP 工作区:全进程字节总量上限(含撤销历史;满了先丢最老的历史版本)
FULLPSD_MCP_TTL_MIN60MCP 文档闲置多少分钟后清理
FULLPSD_MCP_HISTORY5每份文档保留几个历史版本(undo 用;0 关闭)
FULLPSD_MCP_FILE_TTL_MIN30导出下载链接的有效期
FULLPSD_MCP_FILES_MB512待下载产物的内存上限(满了先丢最早过期的)
FULLPSD_MCP_FETCH开设为 off 禁止 MCP 按 URL 取文件 / 取图
FULLPSD_MCP_FETCH_HOSTS—只允许从这些主机(含子域)取文件,逗号分隔
FULLPSD_MCP_FETCH_ALLOW_PRIVATE—设为 1 允许取内网地址(默认拒绝内网 / 回环 / 保留地址,防 SSRF)
FULLPSD_MCP_FETCH_TIMEOUT_MS30000按 URL 取文件的总超时
FULLPSD_MCP_URL_KEY—设为 1 额外开放 /mcp/k/<key>(给没法配请求头的客户端;key 会出现在 URL 与访问日志里)
FULLPSD_MCP_ORIGINS同 FULLPSD_CORS_ORIGINS浏览器里调 MCP 时放行的 Origin(默认只放行同源;防 DNS 重绑定)
FULLPSD_MCP_JSON_LIMITMAX_UPLOAD_MB × 1.4/mcp 请求体上限(base64 传 PSD 会膨胀约 1/3)
FULLPSD_STATE_DIRserver/data/state内存状态落盘目录:频率计数 / 按月配额每分钟落一次,MCP 工作区在正常退出(SIGTERM / SIGINT)时落盘、下次启动恢复
FULLPSD_STATE开设为 off 不落盘(重启即清空)
FULLPSD_SHUTDOWN_TIMEOUT_MS25000优雅退出时等在途请求结束的上限;退出期间新请求 503 code:"shutting_down"
FULLPSD_ACCESS_LOG开访问日志(一行一请求,含 X-Request-Id、耗时、状态码、凭证名)。off 关闭;all 连静态文件也记
FULLPSD_LOG_FORMAT文本设为 json 每行一个 JSON(接日志系统用)
FULLPSD_CSP生产开 / 开发关页面的 Content-Security-Policy。off 关闭;on 在开发模式也发;也可以直接给整条策略覆盖默认值
FULLPSD_FRAME_ANCESTORS'self'允许把站点嵌进 iframe 的来源(空格分隔;设了别的值就不再发 X-Frame-Options)
FULLPSD_HSTS按请求HTTPS 请求(反向代理后面需 FULLPSD_TRUST_PROXY=1)自动带 HSTS;1 总是发,0 从不发
FULLPSD_SAMPLE_PSD—官网「直接体验」用的示例 PSD 路径(不设则 /api/sample 恒 404)
FULLPSD_ENGINE_FILEserver/engine/engine.js加密下发给编辑器的 warp 引擎(构建产物)路径

仓库里的 .env.example 列了全部变量,deploy/fullpsd.service 是 systemd 单元示例(EnvironmentFile 指向 .env,KillSignal=SIGTERM 配合上面的优雅退出)。每个响应都带 X-Request-Id(请求里带了合法的同名头就沿用它,方便串起调用链);对外接口也可以用 /api/v1/… 前缀访问(与 /api/… 等价,响应带 X-Api-Version: v1),将来有破坏性改动会走 /api/v2。

部署边界:server/private/、server/engine/、core/ 在任何模式下都不会被静态托管,web/dist 是唯一对外的前端目录。服务自带 key 鉴权、限流(含按月配额)、并发闸、同源 CORS、安全响应头(CSP / HSTS / X-Frame-Options)与访问日志,建议仍放在反向代理之后(TLS、请求体上限)。按单实例部署设计:计数与工作区在本进程里(落盘见 FULLPSD_STATE_DIR),多实例需要共享存储,不在本项目范围内。

常见问题

编辑之后 /api/doc/composite 看到的还是旧图?

/api/doc/composite 取的是文件末尾那张合成图快照,不重新合成。/api/doc/edit 现在默认会在改了画面的时候按图层重建这张快照(output.recomposite:"auto");如果你传了 recomposite:false,或者用的是片段 / 增量协议自己拼文件,快照就还是旧的 —— 想看改完的样子用 /api/doc/render(按图层重新合成)。

MCP 客户端连不上 / 报 401?

先用 curl 确认 key 可用:curl -s -X POST https://your-host/mcp -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1,"method":"ping"}' 应返回 {"jsonrpc":"2.0","id":1,"result":{}}。401 = key 没带上或不对;403 = 浏览器 Origin 不在白名单(FULLPSD_MCP_ORIGINS);405 = 客户端在用 GET 开 SSE 流 —— 本服务只回 JSON,符合规范的客户端会自动退回纯 POST。

Photoshop 打开我拿到的文件报错,怎么排查?

  1. 先把文件丢给 /api/doc/validate。lint.errors 非空 = 长度字段不自洽,PS 大概率打不开。
  2. 看 roundTripIdentical 与 firstDiffOffset:不一致时那个偏移就是第一处分歧,报 bug 请带上它。
  3. 如果文件是从本平台某个端点拿到的 —— 正常情况下不可能:出文件前的三道守门任何一道不过都会返回 422 而不是文件。把请求参数和 X-Doc-Meta 一起附上。
  4. 如果输入文件本身就体检不过(guard.inputWasHealthy:false / 响应 warnings 里有提示),先修输入。

PS 打开时提示「某些文本图层可能需要更新后才能用于基于矢量的输出」

这是预期行为,点「更新」即可。原因:改文字时会删掉全局 Txt2 块(PS CC 2015+ 优先读它,里面是旧文字与旧字形,留着就会继续显示旧文字),PS 于是按各层 EngineData 用真实字体重排并重建 Txt2。

如果你确实要保留旧引擎文档,可以传 opts.keepTxt2:true,但那样 PS 打开很可能仍显示旧文字 —— 服务端会给你一条 warning。

双击智能对象进去再出来,主文档恢复原样了

这层多半带智能滤镜(透视变形 / 操控变形)。PS 重算用的是全局 FEid 块里「滤镜之前」的放置栅格缓存,不是内嵌源图。检查响应元数据里的 filterCacheUpdated:

  • true —— 已同步,正常。
  • null —— 这层没有变形滤镜,不适用(那就是别的问题)。
  • 失败字符串("失败(…),PS 双击后可能恢复原图")—— 没更新成功,请把这条信息一起报上来。

另外确认 sourceReplaced 为 true:它决定你双击进去看到的是不是新图。外部链接(非内嵌)的智能对象无法替换源图。

Finder / PS 打开对话框里的缩略图还是旧的

缩略图不在合成图里,而是图像资源 1036。走 PSD 输出的路径(/api/render 的 output.format=psd、/api/doc/edit 的 replaceSmartObject)会用新合成图重写 1036;重写失败时会在兼容性提示里给一条 code:"thumbnail"。如果你是自己拼回文件的(片段协议),缩略图需要你自己更新。

系统的缩略图缓存也可能滞后 —— 换个文件名或清一次缓存再看。

CMYK 出来的颜色不对

路径CMYK 支持
/api/render 位图传 output.icc(或上传 icc 文件)走 ICC 分色;不传则用 libvips 内置公式,颜色不准,只能预览(元数据里会有 iccWarning)
/api/render PSD 输出整份文档转 CMYK:所有图层通道 + 合成图 + FEid 缓存一起分色,header 改成 mode=4 并嵌入 ICC。只支持 8 位的 RGB / 灰度源文档
/api/doc/edit 位图只走 libvips 内置公式,仅供预览;PNG / WebP 不支持 CMYK(400)

出印刷文件请用 /api/render 并传目标印刷条件的 ICC:把可分发的 profile 放进 server/profiles/,用 /api/profiles 里的名字引用(不接受路径),或者直接上传 icc 文件字段。attachIcc:false 可以只分色不嵌入。

index 到底是哪个 index?

  • core op 的 index、layerIndex、layer-preview 的 layerIndex —— 都是文档图层下标,来自 /api/doc/inspect 的 flat[].index(底 = 0,分组头与分隔层也各占一条)。
  • /api/render 的 target、{"smartObjectIndex":n} —— 是智能对象序号,来自 /api/psd/info 的 smartObjects[].index。
  • 拿不准就用对象写法显式区分,或先跑一次 /api/doc/edit/plan 看 target 解析成了什么。

收到 429 了怎么办

三种 429,看响应体就能区分,处理方式不同:

  • 带 Retry-After: 10、错误信息是「超过速率限制(N/分钟)」(响应体没有 scope)—— 你的 rpm 用完了,按 Retry-After 退避。
  • "scope":"key" —— 撞上你这把 key 自己的并发上限(concurrency)。这是你自己的在途请求太多,减少并发即可,重试立刻能过;服务端不排队。
  • "scope":"global" —— 撞上全局并发闸 FULLPSD_MAX_CONCURRENT,整机忙,别人的请求也在占。加抖动重试;持续出现就该扩容或调大上限。

两道并发闸都只作用在重接口上(/api/render、layer-preview、composite、/api/doc/edit 文件模式);片段协议与结构类接口不占。

为什么我期待文件,却收到了 JSON?

因为出错了。成功时 Content-Type 一定是 image/*;出错时一律是 application/json。集成时请先判状态码或 Content-Type,再决定是存文件还是读错误 —— 特别是 422,那意味着产物没过守门,服务端故意不给你文件。

响应头里的中文变成了 \u56fe\u5c42

HTTP 头只能放 Latin-1,所以 X-Render-Meta / X-Doc-Meta 里 U+0080 以上的字符被转成了 \uXXXX。转义后仍是合法 JSON,直接 JSON.parse(Python 里 json.loads)就能还原成中文,不需要额外处理。


文档对应的代码:server/index.js · server/auth.js · server/doc-api.js · server/edit-api.js · server/edit-ops.js · server/so-api.js · server/pipeline.js · core/psd-edit.js · core/psd-document.js · core/placement.js。覆盖率数字来自 docs/psd-coverage.md。有出入以代码为准,并请告诉我们。