opencode-skills-collection 3.0.20 → 3.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled-skills/.antigravity-install-manifest.json +3 -1
- package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
- package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
- package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
- package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
- package/bundled-skills/docs/users/bundles.md +1 -1
- package/bundled-skills/docs/users/claude-code-skills.md +1 -1
- package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
- package/bundled-skills/docs/users/getting-started.md +1 -1
- package/bundled-skills/docs/users/kiro-integration.md +1 -1
- package/bundled-skills/docs/users/usage.md +4 -4
- package/bundled-skills/docs/users/visual-guide.md +4 -4
- package/bundled-skills/linux-privilege-escalation/SKILL.md +5 -4
- package/bundled-skills/mercury-mcp/SKILL.md +126 -0
- package/bundled-skills/photopea-embedded-editor/SKILL.md +1399 -0
- package/package.json +4 -4
- package/skills_index.json +44 -0
|
@@ -0,0 +1,1399 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: photopea-embedded-editor
|
|
3
|
+
description: Embed Photopea in web apps using photopea.js. Covers embedding, file I/O, scripting, exporting, layers, text, filters, and the full Photoshop-compatible API.
|
|
4
|
+
risk: safe
|
|
5
|
+
source: community
|
|
6
|
+
source_repo: yikuansun/PhotopeaAPI
|
|
7
|
+
source_type: community
|
|
8
|
+
license: MIT
|
|
9
|
+
license_source: "https://github.com/yikuansun/PhotopeaAPI/blob/master/LICENSE"
|
|
10
|
+
date_added: 2026-05-20
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Photopea Embedded Editor Skill
|
|
14
|
+
## Using photopea.js (yikuansun/PhotopeaAPI) in Websites & Apps
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## When to Use This Skill
|
|
19
|
+
|
|
20
|
+
Use this skill for **every task** that involves:
|
|
21
|
+
- Embedding Photopea as an image editor inside a webpage or web app
|
|
22
|
+
- Controlling an embedded Photopea instance from your JavaScript code
|
|
23
|
+
- Automating image editing workflows from a host page (open files, run scripts, export results)
|
|
24
|
+
- Building an image editing feature into your product using Photopea as the engine
|
|
25
|
+
- Writing scripts to manipulate documents, layers, text, selections, filters, colors, and paths
|
|
26
|
+
|
|
27
|
+
**Do NOT** use raw `postMessage` wiring — always use `photopea.js` as the wrapper.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Library: photopea.js
|
|
32
|
+
|
|
33
|
+
`photopea.js` is a Promises-based JavaScript wrapper around the Photopea Live Messaging API.
|
|
34
|
+
Repository: https://github.com/yikuansun/PhotopeaAPI
|
|
35
|
+
npm package: https://www.npmjs.com/package/photopea
|
|
36
|
+
|
|
37
|
+
### Installation
|
|
38
|
+
|
|
39
|
+
**CDN (no build step)**
|
|
40
|
+
```html
|
|
41
|
+
<script src="https://cdn.jsdelivr.net/npm/photopea@1.1.1/dist/photopea.min.js"></script>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Self-hosted**
|
|
45
|
+
```html
|
|
46
|
+
<script src="./photopea.min.js"></script>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**npm (Webpack / Vite / Rollup)**
|
|
50
|
+
```bash
|
|
51
|
+
npm install photopea
|
|
52
|
+
```
|
|
53
|
+
```js
|
|
54
|
+
import Photopea from "photopea";
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Core API: The `Photopea` Class
|
|
60
|
+
|
|
61
|
+
| Method | Description |
|
|
62
|
+
|--------|-------------|
|
|
63
|
+
| `Photopea.createEmbed(container)` | Creates + injects the iframe, resolves when ready |
|
|
64
|
+
| `new Photopea(window.parent)` | Plugin mode: wrap the parent window |
|
|
65
|
+
| `pea.runScript(script)` | Run JS string inside Photopea; returns output array |
|
|
66
|
+
| `pea.loadAsset(arrayBuffer)` | Load binary file (image, font, brush, etc.) |
|
|
67
|
+
| `pea.openFromURL(url, asSmart)` | Open remote URL as new doc or smart object layer |
|
|
68
|
+
| `pea.exportImage(type)` | Export current doc; returns `Blob` (`"png"` or `"jpg"`) |
|
|
69
|
+
|
|
70
|
+
All methods return Promises — always `await` or `.then()`.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Step 1 — Embed
|
|
75
|
+
|
|
76
|
+
The container `<div>` **must** have a fixed width and height before calling `createEmbed`.
|
|
77
|
+
|
|
78
|
+
```html
|
|
79
|
+
<div id="editor" style="width:1000px; height:650px;"></div>
|
|
80
|
+
<script src="https://cdn.jsdelivr.net/npm/photopea@1.1.1/dist/photopea.min.js"></script>
|
|
81
|
+
<script>
|
|
82
|
+
Photopea.createEmbed(document.getElementById("editor")).then(async (pea) => {
|
|
83
|
+
// pea is ready
|
|
84
|
+
});
|
|
85
|
+
</script>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**React:**
|
|
89
|
+
```jsx
|
|
90
|
+
import { useEffect, useRef } from "react";
|
|
91
|
+
import Photopea from "photopea";
|
|
92
|
+
|
|
93
|
+
export default function Editor() {
|
|
94
|
+
const containerRef = useRef(null);
|
|
95
|
+
const peaRef = useRef(null);
|
|
96
|
+
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (!containerRef.current || peaRef.current) return;
|
|
99
|
+
Photopea.createEmbed(containerRef.current).then((pea) => {
|
|
100
|
+
peaRef.current = pea;
|
|
101
|
+
});
|
|
102
|
+
}, []);
|
|
103
|
+
|
|
104
|
+
return <div ref={containerRef} style={{ width: "100%", height: "650px" }} />;
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Step 2 — Opening Files
|
|
111
|
+
|
|
112
|
+
```js
|
|
113
|
+
// Remote URL → new document
|
|
114
|
+
await pea.openFromURL("https://example.com/design.psd", false);
|
|
115
|
+
|
|
116
|
+
// Remote URL → smart object layer inside current document
|
|
117
|
+
await pea.openFromURL("https://example.com/overlay.png", true);
|
|
118
|
+
|
|
119
|
+
// Local file (user input → ArrayBuffer → loadAsset)
|
|
120
|
+
document.getElementById("fileInput").addEventListener("change", async (e) => {
|
|
121
|
+
const buf = await e.target.files[0].arrayBuffer();
|
|
122
|
+
await pea.loadAsset(buf);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Base64 data URI via runScript
|
|
126
|
+
await pea.runScript(`app.open("data:image/png;base64,iVBORw0...");`);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Step 3 — Running Scripts
|
|
132
|
+
|
|
133
|
+
`runScript` sends a JS string, returns an array of `app.echoToOE(...)` values + `"done"` last.
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
const result = await pea.runScript(`app.echoToOE("hello");`);
|
|
137
|
+
// result → ["hello", "done"]
|
|
138
|
+
|
|
139
|
+
// Return structured data
|
|
140
|
+
const out = await pea.runScript(`
|
|
141
|
+
app.echoToOE(JSON.stringify({
|
|
142
|
+
width: app.activeDocument.width,
|
|
143
|
+
height: app.activeDocument.height,
|
|
144
|
+
layers: app.activeDocument.layers.length
|
|
145
|
+
}));
|
|
146
|
+
`);
|
|
147
|
+
const info = JSON.parse(out[0]);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Step 4 — Exporting
|
|
153
|
+
|
|
154
|
+
```js
|
|
155
|
+
// PNG Blob (via exportImage)
|
|
156
|
+
const blob = await pea.exportImage("png");
|
|
157
|
+
document.getElementById("preview").src = URL.createObjectURL(blob);
|
|
158
|
+
|
|
159
|
+
// JPEG Blob
|
|
160
|
+
const blob = await pea.exportImage("jpg");
|
|
161
|
+
|
|
162
|
+
// WebP / PSD / quality-controlled JPEG via saveToOE
|
|
163
|
+
const result = await pea.runScript(`app.activeDocument.saveToOE("webp:0.85");`);
|
|
164
|
+
const webpBlob = new Blob([result[0]], { type: "image/webp" });
|
|
165
|
+
|
|
166
|
+
const result = await pea.runScript(`app.activeDocument.saveToOE("psd:true");`);
|
|
167
|
+
const psdBlob = new Blob([result[0]], { type: "application/octet-stream" });
|
|
168
|
+
|
|
169
|
+
// Trigger download
|
|
170
|
+
async function download(pea, filename = "export.png") {
|
|
171
|
+
const blob = await pea.exportImage("png");
|
|
172
|
+
const a = Object.assign(document.createElement("a"), {
|
|
173
|
+
href: URL.createObjectURL(blob),
|
|
174
|
+
download: filename
|
|
175
|
+
});
|
|
176
|
+
a.click();
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Export format strings for `saveToOE`:**
|
|
181
|
+
|
|
182
|
+
| String | Format |
|
|
183
|
+
|--------|--------|
|
|
184
|
+
| `"png"` | PNG lossless |
|
|
185
|
+
| `"jpg"` | JPEG default |
|
|
186
|
+
| `"jpg:0.8"` | JPEG quality 0.0–1.0 |
|
|
187
|
+
| `"webp:0.7"` | WebP quality 0.0–1.0 |
|
|
188
|
+
| `"psd"` | Full PSD |
|
|
189
|
+
| `"psd:true"` | Minified PSD |
|
|
190
|
+
| `"svg:true"` | SVG |
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Step 5 — Loading Assets
|
|
195
|
+
|
|
196
|
+
```js
|
|
197
|
+
// Font
|
|
198
|
+
const buf = await (await fetch("https://example.com/MyFont.otf")).arrayBuffer();
|
|
199
|
+
await pea.loadAsset(buf);
|
|
200
|
+
// Now usable in textItem.font
|
|
201
|
+
|
|
202
|
+
// Brush
|
|
203
|
+
await pea.loadAsset(await (await fetch("Nature.ABR")).arrayBuffer());
|
|
204
|
+
|
|
205
|
+
// Gradient
|
|
206
|
+
await pea.loadAsset(await (await fetch("Gradients.GRD")).arrayBuffer());
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Step 6 — Plugin Mode
|
|
212
|
+
|
|
213
|
+
```js
|
|
214
|
+
// Your page is inside Photopea's sidebar iframe
|
|
215
|
+
const pea = new Photopea(window.parent);
|
|
216
|
+
|
|
217
|
+
const out = await pea.runScript(`app.echoToOE(app.activeDocument.width);`);
|
|
218
|
+
console.log("Width:", out[0]);
|
|
219
|
+
|
|
220
|
+
// Load an asset from your plugin
|
|
221
|
+
const buf = await (await fetch("https://my-assets.com/sticker.png")).arrayBuffer();
|
|
222
|
+
await pea.loadAsset(buf);
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Plugin config:
|
|
226
|
+
```json
|
|
227
|
+
{
|
|
228
|
+
"environment": {
|
|
229
|
+
"plugins": [{
|
|
230
|
+
"name": "My Plugin",
|
|
231
|
+
"url": "https://my-plugin.example.com",
|
|
232
|
+
"icon": "===https://my-plugin.example.com/icon.png"
|
|
233
|
+
}]
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Utility Patterns
|
|
241
|
+
|
|
242
|
+
### addImageAndWait — robust async layer insertion
|
|
243
|
+
```js
|
|
244
|
+
async function addImageAndWait(pea, imgURI) {
|
|
245
|
+
let count = "done";
|
|
246
|
+
while (count === "done")
|
|
247
|
+
count = (await pea.runScript(`app.echoToOE(app.activeDocument.layers.length)`))[0];
|
|
248
|
+
count = parseInt(count);
|
|
249
|
+
|
|
250
|
+
await pea.runScript(`app.open("${imgURI}", null, true);`);
|
|
251
|
+
|
|
252
|
+
return new Promise((resolve) => {
|
|
253
|
+
const check = async () => {
|
|
254
|
+
const n = parseInt((await pea.runScript(
|
|
255
|
+
`app.echoToOE(app.activeDocument.layers.length)`
|
|
256
|
+
))[0]);
|
|
257
|
+
n === count + 1 ? resolve() : setTimeout(check, 50);
|
|
258
|
+
};
|
|
259
|
+
check();
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### getDocumentAsImage — returns `<img>` element
|
|
265
|
+
```js
|
|
266
|
+
async function getDocumentAsImage(pea) {
|
|
267
|
+
const result = await pea.runScript(`app.activeDocument.saveToOE('png')`);
|
|
268
|
+
return new Promise((resolve) => {
|
|
269
|
+
const fr = new FileReader();
|
|
270
|
+
fr.addEventListener("load", (e) => {
|
|
271
|
+
const img = new Image(); img.src = e.target.result; resolve(img);
|
|
272
|
+
});
|
|
273
|
+
fr.readAsDataURL(new Blob([result[0]], { type: "image/png" }));
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## Real-World Patterns
|
|
281
|
+
|
|
282
|
+
### Pattern A — Open + Export UI
|
|
283
|
+
```html
|
|
284
|
+
<input type="file" id="fileInput" accept="image/*,.psd">
|
|
285
|
+
<button id="exportBtn">Export PNG</button>
|
|
286
|
+
<div id="editor" style="width:100%;height:600px;"></div>
|
|
287
|
+
<script src="https://cdn.jsdelivr.net/npm/photopea@1.1.1/dist/photopea.min.js"></script>
|
|
288
|
+
<script>
|
|
289
|
+
let pea;
|
|
290
|
+
Photopea.createEmbed(document.getElementById("editor")).then(p => pea = p);
|
|
291
|
+
|
|
292
|
+
document.getElementById("fileInput").addEventListener("change", async e => {
|
|
293
|
+
await pea.loadAsset(await e.target.files[0].arrayBuffer());
|
|
294
|
+
});
|
|
295
|
+
document.getElementById("exportBtn").addEventListener("click", async () => {
|
|
296
|
+
const blob = await pea.exportImage("png");
|
|
297
|
+
const a = Object.assign(document.createElement("a"), {
|
|
298
|
+
href: URL.createObjectURL(blob), download: "export.png"
|
|
299
|
+
});
|
|
300
|
+
a.click();
|
|
301
|
+
});
|
|
302
|
+
</script>
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Pattern B — Template + Text Edit + Export
|
|
306
|
+
```js
|
|
307
|
+
async function generateCard(pea, name, tagline) {
|
|
308
|
+
await pea.openFromURL("https://example.com/card.psd", false);
|
|
309
|
+
await pea.runScript(`
|
|
310
|
+
app.activeDocument.layers.getByName("Name").textItem.contents = "${name}";
|
|
311
|
+
app.activeDocument.layers.getByName("Tagline").textItem.contents = "${tagline}";
|
|
312
|
+
`);
|
|
313
|
+
return await pea.exportImage("png");
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Pattern C — Batch Watermark
|
|
318
|
+
```js
|
|
319
|
+
async function batchWatermark(pea, imageURLs, watermarkURL) {
|
|
320
|
+
const results = [];
|
|
321
|
+
for (const url of imageURLs) {
|
|
322
|
+
await pea.openFromURL(url, false);
|
|
323
|
+
await pea.openFromURL(watermarkURL, true);
|
|
324
|
+
await pea.runScript(`
|
|
325
|
+
var doc = app.activeDocument, wm = doc.activeLayer;
|
|
326
|
+
wm.translate(doc.width - wm.bounds[2] - 20, doc.height - wm.bounds[3] - 20);
|
|
327
|
+
wm.opacity = 70;
|
|
328
|
+
`);
|
|
329
|
+
results.push(await pea.exportImage("png"));
|
|
330
|
+
await pea.runScript(`app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);`);
|
|
331
|
+
}
|
|
332
|
+
return results;
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
# FULL SCRIPTING API REFERENCE
|
|
339
|
+
|
|
340
|
+
> All code in this section runs **inside `pea.runScript("...")`** strings.
|
|
341
|
+
> Photopea implements the Adobe Photoshop CC 2015 JavaScript scripting interface.
|
|
342
|
+
> Any Photoshop script targeting that version should work in Photopea.
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## `app` — Application Object
|
|
347
|
+
|
|
348
|
+
### Properties
|
|
349
|
+
|
|
350
|
+
| Property | Type | R/W | Description |
|
|
351
|
+
|----------|------|-----|-------------|
|
|
352
|
+
| `app.activeDocument` | Document | R/W | The currently active document |
|
|
353
|
+
| `app.documents` | Documents | R | Collection of all open documents |
|
|
354
|
+
| `app.documents.length` | number | R | Count of open documents |
|
|
355
|
+
| `app.documents[i]` | Document | R | Access by zero-based index |
|
|
356
|
+
| `app.foregroundColor` | SolidColor | R/W | Current foreground color |
|
|
357
|
+
| `app.backgroundColor` | SolidColor | R/W | Current background color |
|
|
358
|
+
| `app.preferences.rulerUnits` | Units | R/W | `Units.PIXELS`, `Units.CM`, `Units.INCHES`, `Units.MM`, `Units.PICAS`, `Units.POINTS`, `Units.PERCENT` |
|
|
359
|
+
| `app.preferences.typeUnits` | TypeUnits | R/W | `TypeUnits.PIXELS`, `TypeUnits.MM`, `TypeUnits.POINTS` |
|
|
360
|
+
| `app.displayDialogs` | DialogModes | R/W | `DialogModes.NO`, `DialogModes.ALL`, `DialogModes.ERROR` |
|
|
361
|
+
|
|
362
|
+
### Methods
|
|
363
|
+
|
|
364
|
+
| Method | Description |
|
|
365
|
+
|--------|-------------|
|
|
366
|
+
| `app.open(url)` | Open URL as new document |
|
|
367
|
+
| `app.open(url, null, true)` | Open URL as smart object layer in active document |
|
|
368
|
+
| `app.echoToOE(string)` | **Photopea extension** — send string to host page (captured by `runScript`) |
|
|
369
|
+
| `app.showWindow("magiccut")` | **Photopea extension** — open Magic Cut panel |
|
|
370
|
+
| `app.showWindow("vbitmap")` | **Photopea extension** — open Vectorize Bitmap panel |
|
|
371
|
+
| `app.UI.zoomIn()` | Zoom in |
|
|
372
|
+
| `app.UI.zoomOut()` | Zoom out |
|
|
373
|
+
| `app.UI.fitTheArea()` | Fit canvas to viewport |
|
|
374
|
+
| `app.UI.pixelToPixel()` | 100% zoom |
|
|
375
|
+
| `app.UI.switchFullscreen()` | Toggle fullscreen |
|
|
376
|
+
| `app.UI.scroll(dx, dy)` | Scroll by delta |
|
|
377
|
+
| `app.UI.scrollTo(x, y)` | Scroll to absolute position |
|
|
378
|
+
|
|
379
|
+
**Important:** Always set ruler units to pixels at the start of any script that uses pixel measurements:
|
|
380
|
+
```js
|
|
381
|
+
var savedUnits = app.preferences.rulerUnits;
|
|
382
|
+
app.preferences.rulerUnits = Units.PIXELS;
|
|
383
|
+
// ... your code ...
|
|
384
|
+
app.preferences.rulerUnits = savedUnits;
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## `Document` — Document Object
|
|
390
|
+
|
|
391
|
+
Access via `app.activeDocument` or `app.documents[i]`.
|
|
392
|
+
|
|
393
|
+
### Properties
|
|
394
|
+
|
|
395
|
+
| Property | Type | R/W | Description |
|
|
396
|
+
|----------|------|-----|-------------|
|
|
397
|
+
| `width` | number | R | Document width in current ruler units |
|
|
398
|
+
| `height` | number | R | Document height in current ruler units |
|
|
399
|
+
| `resolution` | number | R | DPI (pixels per inch) |
|
|
400
|
+
| `name` | string | **R/W** | **Photopea extension** — display label (no history step) |
|
|
401
|
+
| `source` | string | **R/W** | **Photopea extension** — file origin URL or `"local,X,NAME"` |
|
|
402
|
+
| `mode` | DocumentMode | R | `DocumentMode.RGB`, `GRAYSCALE`, `CMYK`, `LAB`, `BITMAP`, `INDEXEDCOLOR`, `MULTICHANNEL` |
|
|
403
|
+
| `bitsPerChannel` | BitsPerChannelType | R | `BitsPerChannelType.EIGHT`, `SIXTEEN`, `THIRTYTWO` |
|
|
404
|
+
| `colorProfileName` | string | R | Name of embedded color profile |
|
|
405
|
+
| `activeLayer` | Layer/ArtLayer/LayerSet | R/W | Set to activate a layer |
|
|
406
|
+
| `currentLayer` | ArtLayer | R/W | Alias for `activeLayer` |
|
|
407
|
+
| `layers` | Layers | R | All top-level layers (both art + group) |
|
|
408
|
+
| `artLayers` | ArtLayers | R | All top-level art layers only |
|
|
409
|
+
| `layerSets` | LayerSets | R | All top-level group layers only |
|
|
410
|
+
| `selection` | Selection | R | The current selection |
|
|
411
|
+
| `channels` | Channels | R | All channels |
|
|
412
|
+
| `historyStates` | HistoryStates | R | Undo history |
|
|
413
|
+
| `activeHistoryState` | HistoryState | R/W | Current history position |
|
|
414
|
+
| `layerComps` | LayerComps | R | Layer comps collection |
|
|
415
|
+
| `guides` | Guides | R | Guides collection |
|
|
416
|
+
| `pathItems` | PathItems | R | Vector paths |
|
|
417
|
+
| `id` | number | R | Unique document ID |
|
|
418
|
+
| `saved` | boolean | R | Whether document has unsaved changes |
|
|
419
|
+
| `quickMaskMode` | boolean | R | Whether in Quick Mask mode |
|
|
420
|
+
| `backgroundLayer` | ArtLayer | R | The background layer |
|
|
421
|
+
| `pixelAspectRatio` | number | R | Custom pixel aspect ratio (0.1–10.0) |
|
|
422
|
+
| `histogram` | array | R | 256-element histogram array |
|
|
423
|
+
|
|
424
|
+
### Methods
|
|
425
|
+
|
|
426
|
+
| Method | Signature | Description |
|
|
427
|
+
|--------|-----------|-------------|
|
|
428
|
+
| `resizeImage` | `(w, h, res, resampleMethod)` | Resize image pixels. ResampleMethod: `BICUBIC`, `BILINEAR`, `NEARESTNEIGHBOR`, `NONE`, `BICUBICSHARPER`, `BICUBICSMOOTHER` |
|
|
429
|
+
| `resizeCanvas` | `(w, h, anchor)` | Resize canvas without scaling. AnchorPosition: `TOPLEFT`, `TOPCENTER`, `TOPRIGHT`, `MIDDLELEFT`, `MIDDLECENTER`, `MIDDLERIGHT`, `BOTTOMLEFT`, `BOTTOMCENTER`, `BOTTOMRIGHT` |
|
|
430
|
+
| `rotateCanvas` | `(degrees)` | Rotate entire canvas. Positive = clockwise |
|
|
431
|
+
| `flipCanvas` | `(direction)` | `Direction.HORIZONTAL` or `Direction.VERTICAL` |
|
|
432
|
+
| `crop` | `([x1,y1,x2,y2], angle, w, h)` | Crop canvas. Angle and dimensions are optional |
|
|
433
|
+
| `trim` | `(trimType, top, left, bottom, right)` | Trim transparent/background-color borders. TrimType: `TRANSPARENT`, `TOPLEFT`, `BOTTOMRIGHT` |
|
|
434
|
+
| `revealAll` | `()` | Expand canvas to show clipped content |
|
|
435
|
+
| `flatten` | `()` | Merge all layers into one |
|
|
436
|
+
| `mergeVisibleLayers` | `()` | Merge all visible layers |
|
|
437
|
+
| `rasterizeAllLayers` | `()` | Rasterize all vector/text layers |
|
|
438
|
+
| `changeMode` | `(mode, options)` | Convert color mode (e.g., `ChangeMode.GRAYSCALE`) |
|
|
439
|
+
| `convertProfile` | `(profileName, renderingIntent, blackPointCompensation, dither)` | Convert color profile |
|
|
440
|
+
| `duplicate` | `(name, mergedLayers)` | Duplicate the document |
|
|
441
|
+
| `close` | `(saveOptions)` | Close document. SaveOptions: `DONOTSAVECHANGES`, `SAVECHANGES`, `PROMPTTOSAVECHANGES` |
|
|
442
|
+
| `save` | `()` | Save (requires server config in embed) |
|
|
443
|
+
| `saveToOE` | `(format)` | **Photopea extension** — send binary to host. Formats: `"png"`, `"jpg:0.8"`, `"webp:0.7"`, `"psd:true"`, `"svg:true"` |
|
|
444
|
+
| `clearHistory` | `()` | **Photopea extension** — clear undo history to free RAM |
|
|
445
|
+
| `exportDocument` | `(file, exportType, options)` | Export to filesystem (triggers ZIP). ExportType: `SAVEFORWEB` |
|
|
446
|
+
| `paste` | `(intoSelection)` | Paste clipboard into document |
|
|
447
|
+
| `suspendHistory` | `(historyName, callback)` | Wrap multiple ops in one history state |
|
|
448
|
+
|
|
449
|
+
**Practical examples:**
|
|
450
|
+
```js
|
|
451
|
+
var doc = app.activeDocument;
|
|
452
|
+
|
|
453
|
+
// Resize image to 1920×1080 at 72dpi bicubic
|
|
454
|
+
doc.resizeImage(1920, 1080, 72, ResampleMethod.BICUBIC);
|
|
455
|
+
|
|
456
|
+
// Expand canvas to 2000px wide, keeping content centered
|
|
457
|
+
doc.resizeCanvas(2000, doc.height, AnchorPosition.MIDDLECENTER);
|
|
458
|
+
|
|
459
|
+
// Crop to a region
|
|
460
|
+
doc.crop([100, 100, 900, 600]);
|
|
461
|
+
|
|
462
|
+
// Trim transparent edges
|
|
463
|
+
doc.trim(TrimType.TRANSPARENT, true, true, true, true);
|
|
464
|
+
|
|
465
|
+
// Flip horizontal
|
|
466
|
+
doc.flipCanvas(Direction.HORIZONTAL);
|
|
467
|
+
|
|
468
|
+
// Change to grayscale
|
|
469
|
+
doc.changeMode(ChangeMode.GRAYSCALE);
|
|
470
|
+
|
|
471
|
+
// One undo step for many operations
|
|
472
|
+
doc.suspendHistory("Batch Edit", "action");
|
|
473
|
+
// (Inside Photopea, all ops become one history state)
|
|
474
|
+
|
|
475
|
+
// Export PNG to filesystem (triggers ZIP download)
|
|
476
|
+
var opts = new ExportOptionsSaveForWeb();
|
|
477
|
+
opts.format = SaveDocumentType.PNG;
|
|
478
|
+
opts.PNG8 = false;
|
|
479
|
+
opts.quality = 100;
|
|
480
|
+
doc.exportDocument(new File("/output.png"), ExportType.SAVEFORWEB, opts);
|
|
481
|
+
|
|
482
|
+
// Close without saving
|
|
483
|
+
doc.close(SaveOptions.DONOTSAVECHANGES);
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## `Layers` / `ArtLayers` / `LayerSets` Collections
|
|
489
|
+
|
|
490
|
+
These collections exist on `Document`, `LayerSet` (groups within groups), and can be iterated.
|
|
491
|
+
|
|
492
|
+
```js
|
|
493
|
+
var doc = app.activeDocument;
|
|
494
|
+
|
|
495
|
+
// Access
|
|
496
|
+
doc.layers // all top-level (art + groups)
|
|
497
|
+
doc.artLayers // top-level art layers only
|
|
498
|
+
doc.layerSets // top-level group layers only
|
|
499
|
+
|
|
500
|
+
// By index (0 = topmost)
|
|
501
|
+
doc.layers[0]
|
|
502
|
+
doc.layers[doc.layers.length - 1] // bottommost
|
|
503
|
+
|
|
504
|
+
// By name (throws if not found)
|
|
505
|
+
doc.layers.getByName("Background")
|
|
506
|
+
doc.artLayers.getByName("Logo")
|
|
507
|
+
doc.layerSets.getByName("Header Group")
|
|
508
|
+
|
|
509
|
+
// Add
|
|
510
|
+
var newLayer = doc.artLayers.add(); // new blank art layer
|
|
511
|
+
var newGroup = doc.layerSets.add(); // new group
|
|
512
|
+
var innerLayer = newGroup.artLayers.add(); // layer inside a group
|
|
513
|
+
|
|
514
|
+
// Remove
|
|
515
|
+
doc.artLayers.getByName("Temp").remove();
|
|
516
|
+
|
|
517
|
+
// Iterate all layers recursively
|
|
518
|
+
function walkLayers(parent) {
|
|
519
|
+
for (var i = 0; i < parent.layers.length; i++) {
|
|
520
|
+
var l = parent.layers[i];
|
|
521
|
+
if (l.typename === "LayerSet") walkLayers(l);
|
|
522
|
+
else /* ArtLayer */ processLayer(l);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
walkLayers(doc);
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
## `ArtLayer` — Individual Layer
|
|
531
|
+
|
|
532
|
+
### Properties
|
|
533
|
+
|
|
534
|
+
| Property | Type | R/W | Description |
|
|
535
|
+
|----------|------|-----|-------------|
|
|
536
|
+
| `name` | string | R/W | Layer name |
|
|
537
|
+
| `visible` | boolean | R/W | Layer visibility |
|
|
538
|
+
| `opacity` | number | R/W | Layer opacity 0–100 |
|
|
539
|
+
| `fillOpacity` | number | R | Fill opacity 0–100 |
|
|
540
|
+
| `blendMode` | BlendMode | R/W | Blend mode (see enum below) |
|
|
541
|
+
| `kind` | LayerKind | R/W | Layer type (can set to `LayerKind.TEXT` on empty layer) |
|
|
542
|
+
| `textItem` | TextItem | R | Text object (only when `kind === LayerKind.TEXT`) |
|
|
543
|
+
| `bounds` | array | R | `[left, top, right, bottom]` in current ruler units |
|
|
544
|
+
| `parent` | Document/LayerSet | R | Containing object |
|
|
545
|
+
| `typename` | string | R | Always `"ArtLayer"` |
|
|
546
|
+
| `selected` | boolean | R | **Photopea extension** — is layer highlighted in panel |
|
|
547
|
+
| `isBackgroundLayer` | boolean | R | Is this the locked background layer |
|
|
548
|
+
| `grouped` | boolean | R | Is clipping mask applied |
|
|
549
|
+
| `pixelsLocked` | boolean | R | Pixels locked |
|
|
550
|
+
| `positionLocked` | boolean | R | Position locked |
|
|
551
|
+
| `transparentPixelsLocked` | boolean | R | Transparent pixels locked |
|
|
552
|
+
| `layerMaskDensity` | number | R | Layer mask density 0–100 |
|
|
553
|
+
| `layerMaskFeather` | number | R | Layer mask feather 0–250 |
|
|
554
|
+
| `vectorMaskDensity` | number | R | Vector mask density 0–100 |
|
|
555
|
+
| `vectorMaskFeather` | number | R | Vector mask feather 0–250 |
|
|
556
|
+
|
|
557
|
+
### Transform Methods
|
|
558
|
+
|
|
559
|
+
| Method | Signature | Description |
|
|
560
|
+
|--------|-----------|-------------|
|
|
561
|
+
| `translate` | `(deltaX, deltaY)` | Move layer by offset |
|
|
562
|
+
| `rotate` | `(angle, anchor)` | Rotate by degrees. AnchorPosition optional (default center) |
|
|
563
|
+
| `resize` | `(widthPct, heightPct, anchor)` | Scale as percentage of current size |
|
|
564
|
+
| `rasterize` | `(target)` | Rasterize. RasterizeType: `ENTIRE`, `FILLCONTENT`, `LAYERCLIPPINGMASK`, `LINKEDLAYERS`, `SHAPE`, `TEXTCONTENTS`, `VECTORMASK` |
|
|
565
|
+
|
|
566
|
+
### Layer Management Methods
|
|
567
|
+
|
|
568
|
+
| Method | Signature | Description |
|
|
569
|
+
|--------|-----------|-------------|
|
|
570
|
+
| `duplicate` | `()` | Duplicate to same document, returns new layer |
|
|
571
|
+
| `duplicate` | `(doc, placement)` | Duplicate to another document |
|
|
572
|
+
| `remove` | `()` | Delete the layer |
|
|
573
|
+
| `merge` | `()` | Merge down; returns the merged ArtLayer |
|
|
574
|
+
| `move` | `(relativeLayer, placement)` | Reorder. ElementPlacement: `PLACEBEFORE`, `PLACEAFTER`, `PLACEATBEGINNING`, `PLACEATEND`, `INSIDE` |
|
|
575
|
+
| `copy` | `(merged)` | Copy to clipboard |
|
|
576
|
+
| `cut` | `()` | Cut to clipboard |
|
|
577
|
+
| `clear` | `()` | Cut without clipboard |
|
|
578
|
+
|
|
579
|
+
### Adjustment Methods on ArtLayer
|
|
580
|
+
|
|
581
|
+
| Method | Signature | Description |
|
|
582
|
+
|--------|-----------|-------------|
|
|
583
|
+
| `adjustBrightnessContrast` | `(brightness, contrast)` | Brightness -100–100, Contrast -100–100 |
|
|
584
|
+
| `adjustColorBalance` | `(shadows, midtones, highlights, preserveLuminosity)` | Each is `[cyan-red, magenta-green, yellow-blue]` array |
|
|
585
|
+
| `adjustCurves` | `(curveShape)` | Array of `[input,output]` pairs per channel |
|
|
586
|
+
| `adjustLevels` | `(inputRangeStart, inputRangeEnd, gamma, outputRangeStart, outputRangeEnd)` | Levels adjustment |
|
|
587
|
+
| `autoLevels` | `()` | Auto levels |
|
|
588
|
+
| `autoContrast` | `()` | Auto contrast |
|
|
589
|
+
| `desaturate` | `()` | Convert to grayscale values in current mode |
|
|
590
|
+
| `equalize` | `()` | Equalize brightness distribution |
|
|
591
|
+
| `invert` | `()` | Invert pixel colors |
|
|
592
|
+
| `posterize` | `(levels)` | Posterize (2–255 levels) |
|
|
593
|
+
| `threshold` | `(level)` | B&W threshold (1–255) |
|
|
594
|
+
| `shadowHighlight` | `(shadowAmount, shadowWidth, shadowRadius, highlightAmount, highlightWidth, highlightRadius, colorCorrection, midtoneContrast, blackClip, whiteClip)` | Shadows/Highlights |
|
|
595
|
+
| `photoFilter` | `(fillColor, density, luminosity)` | Photo filter |
|
|
596
|
+
| `mixChannels` | `(outputChannels, monochrome)` | Channel mixer |
|
|
597
|
+
| `selectiveColor` | `(colors, cyan, magenta, yellow, black, method)` | Selective color |
|
|
598
|
+
|
|
599
|
+
### Filter Methods on ArtLayer
|
|
600
|
+
|
|
601
|
+
| Method | Signature | Description |
|
|
602
|
+
|--------|-----------|-------------|
|
|
603
|
+
| `applyGaussianBlur` | `(radius)` | Gaussian blur (0.1–250 px radius) |
|
|
604
|
+
| `applyMotionBlur` | `(angle, distance)` | Motion blur |
|
|
605
|
+
| `applyRadialBlur` | `(amount, blurMethod, blurQuality)` | Radial blur |
|
|
606
|
+
| `applySmartBlur` | `(radius, threshold, blurQuality, blurMode)` | Smart blur |
|
|
607
|
+
| `applyBlur` | `()` | Simple blur |
|
|
608
|
+
| `applyBlurMore` | `()` | Blur more |
|
|
609
|
+
| `applyUnSharpMask` | `(amount, radius, threshold)` | Unsharp mask |
|
|
610
|
+
| `applySharpen` | `()` | Sharpen |
|
|
611
|
+
| `applySharpenEdges` | `()` | Sharpen edges |
|
|
612
|
+
| `applySharpenMore` | `()` | Sharpen more |
|
|
613
|
+
| `applyAddNoise` | `(amount, distribution, monochromatic)` | Add noise. NoiseDistribution: `GAUSSIAN`, `UNIFORM` |
|
|
614
|
+
| `applyDespeckle` | `()` | Despeckle |
|
|
615
|
+
| `applyDustAndScratches` | `(radius, threshold)` | Dust and scratches |
|
|
616
|
+
| `applyMedianNoise` | `(radius)` | Median noise reduction |
|
|
617
|
+
| `applyMaximum` | `(radius)` | Maximum filter (dilate) |
|
|
618
|
+
| `applyMinimum` | `(radius)` | Minimum filter (erode) |
|
|
619
|
+
| `applyHighPass` | `(radius)` | High pass |
|
|
620
|
+
| `applyOffset` | `(horizontal, vertical, undefinedAreas)` | Offset. UndefinedAreas: `SETTOBACKGROUND`, `WRAPAROUND`, `REPEATEDGEPIXELS` |
|
|
621
|
+
| `applyRipple` | `(amount, size)` | Ripple. RippleSize: `SMALL`, `MEDIUM`, `LARGE` |
|
|
622
|
+
| `applyWave` | `(generators, minWavelength, maxWavelength, minAmplitude, maxAmplitude, horizScale, vertScale, waveType, undefinedAreas, randomSeed)` | Wave filter |
|
|
623
|
+
| `applyZigZag` | `(amount, ridges, style)` | Zig-Zag |
|
|
624
|
+
| `applyTwirl` | `(angle)` | Twirl |
|
|
625
|
+
| `applyPolarCoordinates` | `(conversion)` | Polar coordinates |
|
|
626
|
+
| `applySpherize` | `(amount, mode)` | Spherize |
|
|
627
|
+
| `applyPinch` | `(amount)` | Pinch (-100–100) |
|
|
628
|
+
| `applyShear` | `(curve, undefinedAreas)` | Shear |
|
|
629
|
+
| `applyDisplace` | `(horizontalScale, verticalScale, displacementType, undefinedAreas, displacementMapFile)` | Displace |
|
|
630
|
+
| `applyClouds` | `()` | Render Clouds |
|
|
631
|
+
| `applyDifferenceClouds` | `()` | Difference Clouds |
|
|
632
|
+
| `applyLensFlare` | `(brightness, flareCenter, lensType)` | Lens Flare. LensType: `ZOOMWIDE, ZOOMNORMAL, MOVIE` |
|
|
633
|
+
| `applyDiffuseGlow` | `(graininess, glowAmount, clearAmount)` | Diffuse Glow |
|
|
634
|
+
| `applyGlassEffect` | `(distortion, smoothness, scaling, invert, texture, textureFile)` | Glass |
|
|
635
|
+
| `applyOceanRipple` | `(size, magnitude)` | Ocean Ripple |
|
|
636
|
+
| `applyLensBlur` | `(source, focalDistance, invertDepthMap, shape, radius, bladeCurvature, rotation, brightness, threshold, amount, distribution, monochromatic)` | Lens Blur |
|
|
637
|
+
| `applyAverage` | `()` | Average blur |
|
|
638
|
+
| `applyDeInterlace` | `(eliminateFields, createFields)` | De-interlace |
|
|
639
|
+
| `applyNTSC` | `()` | NTSC colors |
|
|
640
|
+
| `applyCustomFilter` | `(characteristics, scale, offset)` | Custom filter (5×5 matrix) |
|
|
641
|
+
| `applyTextureFill` | `(textureFile)` | Texture fill |
|
|
642
|
+
| `applyStyle` | `(styleName)` | Apply a layer style preset by name |
|
|
643
|
+
| `photoFilter` | `(fillColor, density, luminosity)` | Photo Filter |
|
|
644
|
+
|
|
645
|
+
**Practical examples:**
|
|
646
|
+
```js
|
|
647
|
+
var layer = app.activeDocument.activeLayer;
|
|
648
|
+
|
|
649
|
+
// Move to absolute position (layer.bounds[0] = current left edge)
|
|
650
|
+
layer.translate(200 - layer.bounds[0], 100 - layer.bounds[1]);
|
|
651
|
+
|
|
652
|
+
// Rotate 45° around center
|
|
653
|
+
layer.rotate(45);
|
|
654
|
+
|
|
655
|
+
// Scale to 50% keeping center
|
|
656
|
+
layer.resize(50, 50, AnchorPosition.MIDDLECENTER);
|
|
657
|
+
|
|
658
|
+
// Gaussian blur radius 10
|
|
659
|
+
layer.applyGaussianBlur(10);
|
|
660
|
+
|
|
661
|
+
// Unsharp mask
|
|
662
|
+
layer.applyUnSharpMask(50, 2, 0);
|
|
663
|
+
|
|
664
|
+
// Levels: input 0–200, gamma 1.2, output 0–255
|
|
665
|
+
layer.adjustLevels(0, 200, 1.2, 0, 255);
|
|
666
|
+
|
|
667
|
+
// Brightness +20, Contrast +10
|
|
668
|
+
layer.adjustBrightnessContrast(20, 10);
|
|
669
|
+
|
|
670
|
+
// Invert
|
|
671
|
+
layer.invert();
|
|
672
|
+
|
|
673
|
+
// Rasterize text
|
|
674
|
+
layer.rasterize(RasterizeType.TEXTCONTENTS);
|
|
675
|
+
|
|
676
|
+
// Duplicate layer
|
|
677
|
+
var copy = layer.duplicate();
|
|
678
|
+
copy.name = "Layer Copy";
|
|
679
|
+
|
|
680
|
+
// Move layer below another
|
|
681
|
+
var target = doc.layers.getByName("Background");
|
|
682
|
+
layer.move(target, ElementPlacement.PLACEAFTER);
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
---
|
|
686
|
+
|
|
687
|
+
## `LayerSet` — Group Layer
|
|
688
|
+
|
|
689
|
+
A LayerSet is a folder/group in the Layers panel. It has the same layer management methods as `Document`.
|
|
690
|
+
|
|
691
|
+
### Properties
|
|
692
|
+
|
|
693
|
+
| Property | Type | R/W | Description |
|
|
694
|
+
|----------|------|-----|-------------|
|
|
695
|
+
| `name` | string | R/W | Group name |
|
|
696
|
+
| `visible` | boolean | R/W | Group visibility |
|
|
697
|
+
| `opacity` | number | R/W | Group opacity 0–100 |
|
|
698
|
+
| `blendMode` | BlendMode | R/W | Group blend mode |
|
|
699
|
+
| `bounds` | array | R | Bounding box `[left,top,right,bottom]` |
|
|
700
|
+
| `layers` | Layers | R | All layers inside this group |
|
|
701
|
+
| `artLayers` | ArtLayers | R | Art layers inside this group |
|
|
702
|
+
| `layerSets` | LayerSets | R | Sub-groups inside this group |
|
|
703
|
+
| `parent` | Document/LayerSet | R | Parent container |
|
|
704
|
+
| `typename` | string | R | Always `"LayerSet"` |
|
|
705
|
+
|
|
706
|
+
### Methods
|
|
707
|
+
Same as Document for layer management: `layers.add()`, `artLayers.add()`, `layerSets.add()`, `.getByName()`, plus `duplicate()`, `remove()`, `move()`.
|
|
708
|
+
|
|
709
|
+
```js
|
|
710
|
+
// Create group with layers inside
|
|
711
|
+
var group = doc.layerSets.add();
|
|
712
|
+
group.name = "Product Card";
|
|
713
|
+
|
|
714
|
+
var bgLayer = group.artLayers.add(); bgLayer.name = "Background";
|
|
715
|
+
var textLayer = group.artLayers.add(); textLayer.kind = LayerKind.TEXT;
|
|
716
|
+
textLayer.textItem.contents = "Buy Now";
|
|
717
|
+
textLayer.textItem.size = 36;
|
|
718
|
+
|
|
719
|
+
// Collapse/expand group (Photopea specific, not in standard DOM)
|
|
720
|
+
// Use visibility as workaround
|
|
721
|
+
|
|
722
|
+
// Get specific layer inside a group
|
|
723
|
+
var innerLayer = doc.layerSets.getByName("Header Group").artLayers.getByName("Title");
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
---
|
|
727
|
+
|
|
728
|
+
## `TextItem` — Text Layer Content
|
|
729
|
+
|
|
730
|
+
Access via `layer.textItem` on any layer with `layer.kind === LayerKind.TEXT`.
|
|
731
|
+
|
|
732
|
+
### Core Properties (most commonly used)
|
|
733
|
+
|
|
734
|
+
| Property | Type | R/W | Description |
|
|
735
|
+
|----------|------|-----|-------------|
|
|
736
|
+
| `contents` | string | R/W | The actual text content |
|
|
737
|
+
| `font` | string | R/W | Font PostScript name (e.g. `"ArialMT"`, `"Verdana-Bold"`) |
|
|
738
|
+
| `size` | number | R/W | Font size in points |
|
|
739
|
+
| `color` | SolidColor | R/W | Text color |
|
|
740
|
+
| `position` | array | R/W | `[x, y]` origin of text (point text) or bounding box top-left |
|
|
741
|
+
| `justification` | Justification | R/W | `Justification.LEFT`, `CENTER`, `RIGHT`, `FULLJUSTIFY` |
|
|
742
|
+
| `kind` | TextType | R/W | `TextType.POINTTEXT` or `TextType.PARAGRAPHTEXT` |
|
|
743
|
+
| `width` | number | R/W | Width of bounding box (paragraph text only) |
|
|
744
|
+
| `height` | number | R/W | Height of bounding box (paragraph text only) |
|
|
745
|
+
| `direction` | Direction | R/W | `Direction.HORIZONTAL` or `Direction.VERTICAL` |
|
|
746
|
+
|
|
747
|
+
### Typography Properties
|
|
748
|
+
|
|
749
|
+
| Property | Type | R/W | Description |
|
|
750
|
+
|----------|------|-----|-------------|
|
|
751
|
+
| `leading` | number | R/W | Line spacing in points |
|
|
752
|
+
| `tracking` | number | R/W | Letter spacing -1000–10000 (1000 = 1 em) |
|
|
753
|
+
| `horizontalScale` | number | R/W | Horizontal scaling 0–1000% |
|
|
754
|
+
| `verticalScale` | number | R/W | Vertical scaling 0–1000% |
|
|
755
|
+
| `baselineShift` | number | R/W | Baseline offset in points |
|
|
756
|
+
| `capitalization` | Case | R/W | `Case.NORMAL`, `ALLCAPS`, `SMALLCAPS` |
|
|
757
|
+
| `fauxBold` | boolean | R/W | Simulated bold |
|
|
758
|
+
| `fauxItalic` | boolean | R/W | Simulated italic |
|
|
759
|
+
| `underline` | UnderlineType | R/W | `UnderlineType.NONE`, `UNDERLINELEFT`, `UNDERLINERIGHT` |
|
|
760
|
+
| `strikeThru` | StrikeThruType | R/W | `StrikeThruType.NONE`, `STRIKEBOX`, `STRIKEHEIGHT` |
|
|
761
|
+
| `antiAliasMethod` | AntiAlias | R/W | `AntiAlias.NONE`, `SHARP`, `CRISP`, `STRONG`, `SMOOTH` |
|
|
762
|
+
| `autoKerning` | AutoKernType | R/W | `AutoKernType.MANUAL`, `METRICS`, `OPTICAL` |
|
|
763
|
+
| `language` | Language | R/W | `Language.ENGLISH`, etc. |
|
|
764
|
+
| `ligatures` | boolean | R/W | Enable ligatures |
|
|
765
|
+
| `alternateLigatures` | boolean | R/W | Enable alternate ligatures |
|
|
766
|
+
| `oldStyle` | boolean | R/W | Old-style numerals |
|
|
767
|
+
| `noBreak` | boolean | R/W | Prevent line breaks in this text |
|
|
768
|
+
| `useAutoLeading` | boolean | R/W | Use font's built-in leading |
|
|
769
|
+
| `autoLeadingAmount` | number | R/W | Auto leading percentage 0.01–5000 |
|
|
770
|
+
| `hyphenation` | boolean | R/W | Enable hyphenation |
|
|
771
|
+
|
|
772
|
+
### Paragraph Properties
|
|
773
|
+
|
|
774
|
+
| Property | Type | R/W | Description |
|
|
775
|
+
|----------|------|-----|-------------|
|
|
776
|
+
| `leftIndent` | number | R/W | Left indent -1296–1296 |
|
|
777
|
+
| `rightIndent` | number | R/W | Right indent -1296–1296 |
|
|
778
|
+
| `firstLineIndent` | number | R/W | First line indent -1296–1296 |
|
|
779
|
+
| `spaceBefore` | number | R/W | Space before paragraph -1296–1296 |
|
|
780
|
+
| `spaceAfter` | number | R/W | Space after paragraph -1296–1296 |
|
|
781
|
+
| `hangingPuntuation` | boolean | R/W | Roman hanging punctuation |
|
|
782
|
+
| `textComposer` | TextComposer | R/W | `TextComposer.ADOBEEVERYLINE`, `ADOBESINGLELINE` |
|
|
783
|
+
|
|
784
|
+
### Warp Properties
|
|
785
|
+
|
|
786
|
+
| Property | Type | R/W | Description |
|
|
787
|
+
|----------|------|-----|-------------|
|
|
788
|
+
| `warpStyle` | WarpStyle | R/W | `WarpStyle.NONE`, `ARC`, `ARCH`, `BULGE`, `SHELLLOWER`, `SHELLUPPER`, `FLAG`, `WAVE`, `FISH`, `RISE`, `FISHEYE`, `INFLATE`, `SQUEEZE`, `TWIST` |
|
|
789
|
+
| `warpDirection` | Direction | R/W | `Direction.HORIZONTAL` or `Direction.VERTICAL` |
|
|
790
|
+
| `warpBend` | number | R/W | Warp bend -100–100 |
|
|
791
|
+
| `warpHorizontalDistortion` | number | R/W | Horizontal distortion -100–100 |
|
|
792
|
+
| `warpVerticalDistortion` | number | R/W | Vertical distortion -100–100 |
|
|
793
|
+
|
|
794
|
+
### Photopea Extensions
|
|
795
|
+
|
|
796
|
+
| Property | Type | Description |
|
|
797
|
+
|----------|------|-------------|
|
|
798
|
+
| `totalTextStyle` | string | JSON string with ALL style parameters of the text |
|
|
799
|
+
| `transform` | string | JSON array — the affine transform matrix of the text |
|
|
800
|
+
|
|
801
|
+
### Methods
|
|
802
|
+
|
|
803
|
+
| Method | Description |
|
|
804
|
+
|--------|-------------|
|
|
805
|
+
| `convertToShape()` | Convert text to a filled shape layer with text as clipping path |
|
|
806
|
+
| `createPath()` | Create work path from text outlines |
|
|
807
|
+
|
|
808
|
+
**Practical examples:**
|
|
809
|
+
```js
|
|
810
|
+
var layer = doc.layers.getByName("Headline");
|
|
811
|
+
var text = layer.textItem;
|
|
812
|
+
|
|
813
|
+
// Set content
|
|
814
|
+
text.contents = "Hello World";
|
|
815
|
+
|
|
816
|
+
// Style
|
|
817
|
+
text.font = "Verdana-Bold";
|
|
818
|
+
text.size = 72;
|
|
819
|
+
text.color.rgb.hexValue = "FF0000"; // red
|
|
820
|
+
|
|
821
|
+
// Position point text at (50, 100)
|
|
822
|
+
text.position = [50, 100];
|
|
823
|
+
|
|
824
|
+
// Center align
|
|
825
|
+
text.justification = Justification.CENTER;
|
|
826
|
+
|
|
827
|
+
// Paragraph text with bounding box
|
|
828
|
+
text.kind = TextType.PARAGRAPHTEXT;
|
|
829
|
+
text.width = new UnitValue("400 pixels");
|
|
830
|
+
text.height = new UnitValue("200 pixels");
|
|
831
|
+
|
|
832
|
+
// Letter spacing
|
|
833
|
+
text.tracking = 100; // 10% spacing
|
|
834
|
+
|
|
835
|
+
// Scale text horizontally to 80%
|
|
836
|
+
text.horizontalScale = 80;
|
|
837
|
+
|
|
838
|
+
// Warp arc
|
|
839
|
+
text.warpStyle = WarpStyle.ARC;
|
|
840
|
+
text.warpBend = 30;
|
|
841
|
+
|
|
842
|
+
// Read all text styles as JSON (Photopea extension)
|
|
843
|
+
var styles = JSON.parse(text.totalTextStyle);
|
|
844
|
+
app.echoToOE(JSON.stringify(styles));
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
---
|
|
848
|
+
|
|
849
|
+
## Creating a Text Layer from Scratch
|
|
850
|
+
|
|
851
|
+
```js
|
|
852
|
+
app.preferences.rulerUnits = Units.PIXELS;
|
|
853
|
+
|
|
854
|
+
var layer = doc.artLayers.add();
|
|
855
|
+
layer.kind = LayerKind.TEXT; // Convert blank layer to text
|
|
856
|
+
layer.name = "My Title";
|
|
857
|
+
|
|
858
|
+
var text = layer.textItem;
|
|
859
|
+
text.contents = "Welcome";
|
|
860
|
+
text.font = "ArialMT";
|
|
861
|
+
text.size = 48;
|
|
862
|
+
text.justification = Justification.CENTER;
|
|
863
|
+
text.position = [doc.width / 2, 100];
|
|
864
|
+
|
|
865
|
+
var color = new SolidColor();
|
|
866
|
+
color.rgb.red = 255;
|
|
867
|
+
color.rgb.green = 255;
|
|
868
|
+
color.rgb.blue = 255;
|
|
869
|
+
text.color = color;
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
---
|
|
873
|
+
|
|
874
|
+
## `SolidColor` — Color Object
|
|
875
|
+
|
|
876
|
+
```js
|
|
877
|
+
// RGB (most common in Photopea)
|
|
878
|
+
var c = new SolidColor();
|
|
879
|
+
c.rgb.red = 255; // 0–255
|
|
880
|
+
c.rgb.green = 128;
|
|
881
|
+
c.rgb.blue = 0;
|
|
882
|
+
c.rgb.hexValue = "FF8000"; // Set via hex string (no #)
|
|
883
|
+
|
|
884
|
+
// CMYK
|
|
885
|
+
var c2 = new SolidColor();
|
|
886
|
+
c2.cmyk.cyan = 0; // 0–100
|
|
887
|
+
c2.cmyk.magenta = 50;
|
|
888
|
+
c2.cmyk.yellow = 100;
|
|
889
|
+
c2.cmyk.black = 0;
|
|
890
|
+
|
|
891
|
+
// Grayscale
|
|
892
|
+
var c3 = new SolidColor();
|
|
893
|
+
c3.gray.gray = 50; // 0–100
|
|
894
|
+
|
|
895
|
+
// HSB
|
|
896
|
+
var c4 = new SolidColor();
|
|
897
|
+
c4.hsb.hue = 30; // 0–360
|
|
898
|
+
c4.hsb.saturation = 100; // 0–100
|
|
899
|
+
c4.hsb.brightness = 100; // 0–100
|
|
900
|
+
|
|
901
|
+
// Lab
|
|
902
|
+
var c5 = new SolidColor();
|
|
903
|
+
c5.lab.l = 50; // 0–100
|
|
904
|
+
c5.lab.a = 20; // -128–127
|
|
905
|
+
c5.lab.b = 40; // -128–127
|
|
906
|
+
|
|
907
|
+
// Set as foreground color
|
|
908
|
+
app.foregroundColor = c;
|
|
909
|
+
|
|
910
|
+
// Use with selection fill
|
|
911
|
+
doc.selection.selectAll();
|
|
912
|
+
doc.selection.fill(c);
|
|
913
|
+
doc.selection.deselect();
|
|
914
|
+
```
|
|
915
|
+
|
|
916
|
+
---
|
|
917
|
+
|
|
918
|
+
## `Selection` — Selection Object
|
|
919
|
+
|
|
920
|
+
Access via `doc.selection`.
|
|
921
|
+
|
|
922
|
+
### Properties
|
|
923
|
+
|
|
924
|
+
| Property | Type | Description |
|
|
925
|
+
|----------|------|-------------|
|
|
926
|
+
| `bounds` | array | `[left, top, right, bottom]` bounding rectangle |
|
|
927
|
+
| `solid` | boolean | Whether selection is a solid rectangle |
|
|
928
|
+
|
|
929
|
+
### Methods
|
|
930
|
+
|
|
931
|
+
| Method | Signature | Description |
|
|
932
|
+
|--------|-----------|-------------|
|
|
933
|
+
| `selectAll` | `()` | Select entire document |
|
|
934
|
+
| `deselect` | `()` | Remove selection |
|
|
935
|
+
| `invert` | `()` | Invert the selection |
|
|
936
|
+
| `select` | `(region, type, feather, antiAlias)` | Select polygon region. Region is array of `[x,y]` points. SelectionType: `REPLACE`, `ADD`, `SUBTRACT`, `INTERSECT` |
|
|
937
|
+
| `feather` | `(radius)` | Feather the selection edges |
|
|
938
|
+
| `contract` | `(radius)` | Contract (shrink) selection |
|
|
939
|
+
| `expand` | `(radius)` | Expand selection |
|
|
940
|
+
| `grow` | `(tolerance, antiAlias)` | Grow selection to similar adjacent pixels |
|
|
941
|
+
| `similar` | `(tolerance, antiAlias)` | Select similar pixels throughout document |
|
|
942
|
+
| `smooth` | `(radius)` | Smooth selection edges |
|
|
943
|
+
| `selectBorder` | `(width)` | Select only the border of the current selection |
|
|
944
|
+
| `resize` | `(widthPct, heightPct, anchor)` | Resize selection boundary |
|
|
945
|
+
| `rotate` | `(angle, anchor)` | Rotate selection boundary |
|
|
946
|
+
| `translate` | `(deltaX, deltaY)` | Move selection boundary |
|
|
947
|
+
| `fill` | `(fillWith, mode, opacity, preserveTransparency)` | Fill selection with color or content. fillWith is SolidColor or string |
|
|
948
|
+
| `stroke` | `(strokeColor, width, location, mode, opacity, preserveTransparency)` | Stroke selection border. StrokeLocation: `INSIDE`, `OUTSIDE`, `CENTER` |
|
|
949
|
+
| `copy` | `(merged)` | Copy selection to clipboard |
|
|
950
|
+
| `cut` | `()` | Cut selection to clipboard |
|
|
951
|
+
| `clear` | `()` | Delete selection content |
|
|
952
|
+
| `load` | `(from, type, invert)` | Load selection from channel |
|
|
953
|
+
| `store` | `(into, type)` | Save selection as channel |
|
|
954
|
+
| `makeWorkPath` | `(tolerance)` | Convert to work path |
|
|
955
|
+
|
|
956
|
+
**Practical examples:**
|
|
957
|
+
```js
|
|
958
|
+
var sel = doc.selection;
|
|
959
|
+
|
|
960
|
+
// Rectangle select (top-left to bottom-right)
|
|
961
|
+
sel.select([[0,0],[500,0],[500,300],[0,300]]);
|
|
962
|
+
|
|
963
|
+
// Select all
|
|
964
|
+
sel.selectAll();
|
|
965
|
+
|
|
966
|
+
// Add to existing selection
|
|
967
|
+
sel.select([[600,0],[900,0],[900,300],[600,300]], SelectionType.ADD);
|
|
968
|
+
|
|
969
|
+
// Feather 10px
|
|
970
|
+
sel.feather(10);
|
|
971
|
+
|
|
972
|
+
// Contract by 5px
|
|
973
|
+
sel.contract(5);
|
|
974
|
+
|
|
975
|
+
// Fill with red
|
|
976
|
+
var red = new SolidColor();
|
|
977
|
+
red.rgb.red = 255; red.rgb.green = 0; red.rgb.blue = 0;
|
|
978
|
+
sel.fill(red);
|
|
979
|
+
|
|
980
|
+
// Stroke selection with black, 3px, inside
|
|
981
|
+
var black = new SolidColor();
|
|
982
|
+
black.rgb.hexValue = "000000";
|
|
983
|
+
sel.stroke(black, 3, StrokeLocation.INSIDE);
|
|
984
|
+
|
|
985
|
+
// Copy, paste as new layer
|
|
986
|
+
sel.copy();
|
|
987
|
+
doc.paste();
|
|
988
|
+
|
|
989
|
+
// Invert and delete (remove background)
|
|
990
|
+
sel.invert();
|
|
991
|
+
sel.clear();
|
|
992
|
+
sel.deselect();
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
---
|
|
996
|
+
|
|
997
|
+
## `BlendMode` Enum — All Values
|
|
998
|
+
|
|
999
|
+
Used in `layer.blendMode` (string form in Photopea) and `BlendMode` constant (standard):
|
|
1000
|
+
|
|
1001
|
+
| `BlendMode` Constant | Photopea String | Name |
|
|
1002
|
+
|----------------------|-----------------|------|
|
|
1003
|
+
| `BlendMode.NORMAL` | `"norm"` | Normal |
|
|
1004
|
+
| `BlendMode.DISSOLVE` | `"diss"` | Dissolve |
|
|
1005
|
+
| `BlendMode.DARKEN` | `"dark"` | Darken |
|
|
1006
|
+
| `BlendMode.MULTIPLY` | `"mul "` | Multiply |
|
|
1007
|
+
| `BlendMode.COLORBURN` | `"idiv"` | Color Burn |
|
|
1008
|
+
| `BlendMode.LINEARBURN` | `"lbrn"` | Linear Burn |
|
|
1009
|
+
| `BlendMode.DARKERCOLOR` | `"dkCl"` | Darker Color |
|
|
1010
|
+
| `BlendMode.LIGHTEN` | `"lite"` | Lighten |
|
|
1011
|
+
| `BlendMode.SCREEN` | `"scrn"` | Screen |
|
|
1012
|
+
| `BlendMode.COLORDODGE` | `"div "` | Color Dodge |
|
|
1013
|
+
| `BlendMode.LINEARDODGE` | `"lddg"` | Linear Dodge (Add) |
|
|
1014
|
+
| `BlendMode.LIGHTERCOLOR` | `"lgCl"` | Lighter Color |
|
|
1015
|
+
| `BlendMode.OVERLAY` | `"over"` | Overlay |
|
|
1016
|
+
| `BlendMode.SOFTLIGHT` | `"sLit"` | Soft Light |
|
|
1017
|
+
| `BlendMode.HARDLIGHT` | `"hLit"` | Hard Light |
|
|
1018
|
+
| `BlendMode.VIVIDLIGHT` | `"vLit"` | Vivid Light |
|
|
1019
|
+
| `BlendMode.LINEARLIGHT` | `"lLit"` | Linear Light |
|
|
1020
|
+
| `BlendMode.PINLIGHT` | `"pLit"` | Pin Light |
|
|
1021
|
+
| `BlendMode.HARDMIX` | `"hMix"` | Hard Mix |
|
|
1022
|
+
| `BlendMode.DIFFERENCE` | `"diff"` | Difference |
|
|
1023
|
+
| `BlendMode.EXCLUSION` | `"smud"` | Exclusion |
|
|
1024
|
+
| `BlendMode.SUBTRACT` | `"fsub"` | Subtract |
|
|
1025
|
+
| `BlendMode.DIVIDE` | `"fdiv"` | Divide |
|
|
1026
|
+
| `BlendMode.HUE` | `"hue "` | Hue |
|
|
1027
|
+
| `BlendMode.SATURATION` | `"sat "` | Saturation |
|
|
1028
|
+
| `BlendMode.COLOR` | `"colr"` | Color |
|
|
1029
|
+
| `BlendMode.LUMINOSITY` | `"lum "` | Luminosity |
|
|
1030
|
+
| `BlendMode.PASSTHROUGH` | `"pass"` | Pass Through (groups only) |
|
|
1031
|
+
|
|
1032
|
+
```js
|
|
1033
|
+
// Use either form:
|
|
1034
|
+
layer.blendMode = BlendMode.SCREEN; // constant
|
|
1035
|
+
layer.blendMode = "scrn"; // string (Photopea internal form)
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
---
|
|
1039
|
+
|
|
1040
|
+
## `LayerKind` Enum
|
|
1041
|
+
|
|
1042
|
+
| Constant | Description |
|
|
1043
|
+
|----------|-------------|
|
|
1044
|
+
| `LayerKind.NORMAL` | Regular pixel layer |
|
|
1045
|
+
| `LayerKind.TEXT` | Text layer |
|
|
1046
|
+
| `LayerKind.SMARTOBJECT` | Smart Object / linked layer |
|
|
1047
|
+
| `LayerKind.SOLIDFILL` | Solid color fill layer |
|
|
1048
|
+
| `LayerKind.GRADIENTFILL` | Gradient fill layer |
|
|
1049
|
+
| `LayerKind.PATTERNFILL` | Pattern fill layer |
|
|
1050
|
+
| `LayerKind.BRIGHTNESSCONTRAST` | Brightness/Contrast adjustment layer |
|
|
1051
|
+
| `LayerKind.CURVES` | Curves adjustment layer |
|
|
1052
|
+
| `LayerKind.LEVELS` | Levels adjustment layer |
|
|
1053
|
+
| `LayerKind.HUESATURATION` | Hue/Saturation adjustment layer |
|
|
1054
|
+
| `LayerKind.COLORBALANCE` | Color Balance adjustment layer |
|
|
1055
|
+
| `LayerKind.CHANNELMIXER` | Channel Mixer adjustment layer |
|
|
1056
|
+
| `LayerKind.GRADIENTMAP` | Gradient Map adjustment layer |
|
|
1057
|
+
| `LayerKind.INVERSION` | Invert adjustment layer |
|
|
1058
|
+
| `LayerKind.POSTERIZE` | Posterize adjustment layer |
|
|
1059
|
+
| `LayerKind.THRESHOLD` | Threshold adjustment layer |
|
|
1060
|
+
| `LayerKind.SELECTIVECOLOR` | Selective Color adjustment layer |
|
|
1061
|
+
| `LayerKind.PHOTOFILTER` | Photo Filter adjustment layer |
|
|
1062
|
+
| `LayerKind.EXPOSURE` | Exposure adjustment layer |
|
|
1063
|
+
| `LayerKind.VIBRANCE` | Vibrance adjustment layer |
|
|
1064
|
+
| `LayerKind.COLORLOOKUP` | Color Lookup adjustment layer |
|
|
1065
|
+
| `LayerKind.LAYER3D` | 3D layer (not generally useful in Photopea) |
|
|
1066
|
+
| `LayerKind.VIDEO` | Video layer |
|
|
1067
|
+
|
|
1068
|
+
```js
|
|
1069
|
+
// Identify layer type
|
|
1070
|
+
var layer = doc.activeLayer;
|
|
1071
|
+
if (layer.kind === LayerKind.TEXT) /* text layer */;
|
|
1072
|
+
if (layer.kind === LayerKind.SMARTOBJECT) /* smart object */;
|
|
1073
|
+
if (layer.typename === "LayerSet") /* group */;
|
|
1074
|
+
|
|
1075
|
+
// Filter: collect all text layers recursively
|
|
1076
|
+
var textLayers = [];
|
|
1077
|
+
function collectText(parent) {
|
|
1078
|
+
for (var i = 0; i < parent.layers.length; i++) {
|
|
1079
|
+
var l = parent.layers[i];
|
|
1080
|
+
if (l.typename === "LayerSet") collectText(l);
|
|
1081
|
+
else if (l.kind === LayerKind.TEXT) textLayers.push(l);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
collectText(doc);
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
---
|
|
1088
|
+
|
|
1089
|
+
## `AnchorPosition` Enum
|
|
1090
|
+
|
|
1091
|
+
| Constant | Position |
|
|
1092
|
+
|----------|----------|
|
|
1093
|
+
| `AnchorPosition.TOPLEFT` | Top left |
|
|
1094
|
+
| `AnchorPosition.TOPCENTER` | Top center |
|
|
1095
|
+
| `AnchorPosition.TOPRIGHT` | Top right |
|
|
1096
|
+
| `AnchorPosition.MIDDLELEFT` | Middle left |
|
|
1097
|
+
| `AnchorPosition.MIDDLECENTER` | Center |
|
|
1098
|
+
| `AnchorPosition.MIDDLERIGHT` | Middle right |
|
|
1099
|
+
| `AnchorPosition.BOTTOMLEFT` | Bottom left |
|
|
1100
|
+
| `AnchorPosition.BOTTOMCENTER` | Bottom center |
|
|
1101
|
+
| `AnchorPosition.BOTTOMRIGHT` | Bottom right |
|
|
1102
|
+
|
|
1103
|
+
---
|
|
1104
|
+
|
|
1105
|
+
## `ElementPlacement` Enum
|
|
1106
|
+
|
|
1107
|
+
Used with `layer.move(relativeObject, placement)`:
|
|
1108
|
+
|
|
1109
|
+
| Constant | Effect |
|
|
1110
|
+
|----------|--------|
|
|
1111
|
+
| `ElementPlacement.PLACEBEFORE` | Above the target layer in the panel |
|
|
1112
|
+
| `ElementPlacement.PLACEAFTER` | Below the target layer in the panel |
|
|
1113
|
+
| `ElementPlacement.PLACEATBEGINNING` | Top of the layer stack |
|
|
1114
|
+
| `ElementPlacement.PLACEATEND` | Bottom of the layer stack |
|
|
1115
|
+
| `ElementPlacement.INSIDE` | Into a LayerSet (makes layer a child) |
|
|
1116
|
+
|
|
1117
|
+
---
|
|
1118
|
+
|
|
1119
|
+
## `ResampleMethod` Enum
|
|
1120
|
+
|
|
1121
|
+
Used with `doc.resizeImage()`:
|
|
1122
|
+
|
|
1123
|
+
| Constant | Description |
|
|
1124
|
+
|----------|-------------|
|
|
1125
|
+
| `ResampleMethod.BICUBIC` | High quality, good for smooth gradients |
|
|
1126
|
+
| `ResampleMethod.BICUBICSHARPER` | Best for reduction |
|
|
1127
|
+
| `ResampleMethod.BICUBICSMOOTHER` | Best for enlargement |
|
|
1128
|
+
| `ResampleMethod.BILINEAR` | Medium quality |
|
|
1129
|
+
| `ResampleMethod.NEARESTNEIGHBOR` | No anti-aliasing, fastest |
|
|
1130
|
+
| `ResampleMethod.NONE` | No resampling (change resolution only) |
|
|
1131
|
+
|
|
1132
|
+
---
|
|
1133
|
+
|
|
1134
|
+
## `SaveOptions` Enum
|
|
1135
|
+
|
|
1136
|
+
Used with `doc.close(saveOption)`:
|
|
1137
|
+
|
|
1138
|
+
| Constant | Meaning |
|
|
1139
|
+
|----------|---------|
|
|
1140
|
+
| `SaveOptions.DONOTSAVECHANGES` | Discard all changes and close |
|
|
1141
|
+
| `SaveOptions.SAVECHANGES` | Save then close |
|
|
1142
|
+
| `SaveOptions.PROMPTTOSAVECHANGES` | Show dialog (may block in headless) |
|
|
1143
|
+
|
|
1144
|
+
---
|
|
1145
|
+
|
|
1146
|
+
## `ExportOptionsSaveForWeb` — Export to Filesystem
|
|
1147
|
+
|
|
1148
|
+
Used with `doc.exportDocument()` to write files that Photopea packages into a ZIP.
|
|
1149
|
+
|
|
1150
|
+
```js
|
|
1151
|
+
// Export PNG
|
|
1152
|
+
var pngOpts = new ExportOptionsSaveForWeb();
|
|
1153
|
+
pngOpts.format = SaveDocumentType.PNG;
|
|
1154
|
+
pngOpts.PNG8 = false; // PNG-24
|
|
1155
|
+
pngOpts.quality = 100;
|
|
1156
|
+
pngOpts.transparency = true;
|
|
1157
|
+
doc.exportDocument(new File("/export.png"), ExportType.SAVEFORWEB, pngOpts);
|
|
1158
|
+
|
|
1159
|
+
// Export JPEG
|
|
1160
|
+
var jpgOpts = new ExportOptionsSaveForWeb();
|
|
1161
|
+
jpgOpts.format = SaveDocumentType.JPEG;
|
|
1162
|
+
jpgOpts.quality = 80; // 0–100
|
|
1163
|
+
doc.exportDocument(new File("/export.jpg"), ExportType.SAVEFORWEB, jpgOpts);
|
|
1164
|
+
|
|
1165
|
+
// Export GIF
|
|
1166
|
+
var gifOpts = new ExportOptionsSaveForWeb();
|
|
1167
|
+
gifOpts.format = SaveDocumentType.GIF;
|
|
1168
|
+
gifOpts.colors = 256;
|
|
1169
|
+
gifOpts.dither = 100;
|
|
1170
|
+
gifOpts.transparency = true;
|
|
1171
|
+
doc.exportDocument(new File("/export.gif"), ExportType.SAVEFORWEB, gifOpts);
|
|
1172
|
+
```
|
|
1173
|
+
|
|
1174
|
+
---
|
|
1175
|
+
|
|
1176
|
+
## `executeAction` — Advanced Operations
|
|
1177
|
+
|
|
1178
|
+
Used for operations not exposed in the standard DOM (adjustments applied as adjustment layers,
|
|
1179
|
+
Smart Object editing, etc.). Takes the Photoshop Action Manager approach.
|
|
1180
|
+
|
|
1181
|
+
```js
|
|
1182
|
+
// Open Smart Object for editing
|
|
1183
|
+
var l = doc.layers.getByName("SmartObj");
|
|
1184
|
+
doc.activeLayer = l;
|
|
1185
|
+
executeAction(stringIDToTypeID("placedLayerEditContents"));
|
|
1186
|
+
// Smart Object is now the active document
|
|
1187
|
+
doc.activeLayer.rotate(90);
|
|
1188
|
+
doc.save();
|
|
1189
|
+
doc.close();
|
|
1190
|
+
|
|
1191
|
+
// Apply Hue/Saturation as destructive adjustment
|
|
1192
|
+
var desc = new ActionDescriptor();
|
|
1193
|
+
var list = new ActionList();
|
|
1194
|
+
var channel = new ActionDescriptor();
|
|
1195
|
+
channel.putEnumerated(stringIDToTypeID("presetKind"), stringIDToTypeID("presetKindType"), stringIDToTypeID("presetKindDefault"));
|
|
1196
|
+
channel.putInteger(stringIDToTypeID("hue"), 20); // hue shift
|
|
1197
|
+
channel.putInteger(stringIDToTypeID("saturation"), 30); // saturation
|
|
1198
|
+
channel.putInteger(stringIDToTypeID("lightness"), 0);
|
|
1199
|
+
list.putObject(stringIDToTypeID("hueSaturationAdjustmentV2Layer"), channel);
|
|
1200
|
+
desc.putList(stringIDToTypeID("adjustment"), list);
|
|
1201
|
+
executeAction(stringIDToTypeID("hueSaturation"), desc, DialogModes.NO);
|
|
1202
|
+
|
|
1203
|
+
// Select a layer by name using AM
|
|
1204
|
+
function selectLayerByName(name) {
|
|
1205
|
+
var desc = new ActionDescriptor();
|
|
1206
|
+
var ref = new ActionReference();
|
|
1207
|
+
ref.putName(charIDToTypeID("Lyr "), name);
|
|
1208
|
+
desc.putReference(charIDToTypeID("null"), ref);
|
|
1209
|
+
desc.putBoolean(charIDToTypeID("MkVs"), false);
|
|
1210
|
+
executeAction(charIDToTypeID("slct"), desc, DialogModes.NO);
|
|
1211
|
+
}
|
|
1212
|
+
```
|
|
1213
|
+
|
|
1214
|
+
---
|
|
1215
|
+
|
|
1216
|
+
## Complete Practical Script Examples
|
|
1217
|
+
|
|
1218
|
+
### 1. Rename all text layers based on their contents
|
|
1219
|
+
```js
|
|
1220
|
+
app.preferences.rulerUnits = Units.PIXELS;
|
|
1221
|
+
var doc = app.activeDocument;
|
|
1222
|
+
|
|
1223
|
+
function processLayers(parent) {
|
|
1224
|
+
for (var i = 0; i < parent.layers.length; i++) {
|
|
1225
|
+
var l = parent.layers[i];
|
|
1226
|
+
if (l.typename === "LayerSet") processLayers(l);
|
|
1227
|
+
else if (l.kind === LayerKind.TEXT) {
|
|
1228
|
+
l.name = l.textItem.contents.substring(0, 30);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
processLayers(doc);
|
|
1233
|
+
app.echoToOE("done");
|
|
1234
|
+
```
|
|
1235
|
+
|
|
1236
|
+
### 2. Export each layer as a separate PNG
|
|
1237
|
+
```js
|
|
1238
|
+
app.preferences.rulerUnits = Units.PIXELS;
|
|
1239
|
+
var doc = app.activeDocument;
|
|
1240
|
+
|
|
1241
|
+
for (var i = 0; i < doc.layers.length; i++) {
|
|
1242
|
+
// Hide all layers
|
|
1243
|
+
for (var j = 0; j < doc.layers.length; j++) doc.layers[j].visible = false;
|
|
1244
|
+
// Show only this layer
|
|
1245
|
+
doc.layers[i].visible = true;
|
|
1246
|
+
// Export
|
|
1247
|
+
var opts = new ExportOptionsSaveForWeb();
|
|
1248
|
+
opts.format = SaveDocumentType.PNG;
|
|
1249
|
+
opts.PNG8 = false;
|
|
1250
|
+
opts.quality = 100;
|
|
1251
|
+
doc.exportDocument(
|
|
1252
|
+
new File("/" + doc.layers[i].name + ".png"),
|
|
1253
|
+
ExportType.SAVEFORWEB, opts
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// Restore visibility
|
|
1258
|
+
for (var i = 0; i < doc.layers.length; i++) doc.layers[i].visible = true;
|
|
1259
|
+
```
|
|
1260
|
+
|
|
1261
|
+
### 3. Find and replace text across all text layers
|
|
1262
|
+
```js
|
|
1263
|
+
var searchText = "2024";
|
|
1264
|
+
var replaceText = "2025";
|
|
1265
|
+
|
|
1266
|
+
function findReplaceText(parent) {
|
|
1267
|
+
for (var i = 0; i < parent.layers.length; i++) {
|
|
1268
|
+
var l = parent.layers[i];
|
|
1269
|
+
if (l.typename === "LayerSet") findReplaceText(l);
|
|
1270
|
+
else if (l.kind === LayerKind.TEXT) {
|
|
1271
|
+
var t = l.textItem;
|
|
1272
|
+
if (t.contents.indexOf(searchText) !== -1) {
|
|
1273
|
+
t.contents = t.contents.split(searchText).join(replaceText);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
findReplaceText(app.activeDocument);
|
|
1279
|
+
app.echoToOE("Find & Replace complete");
|
|
1280
|
+
```
|
|
1281
|
+
|
|
1282
|
+
### 4. Grid of duplicate layers
|
|
1283
|
+
```js
|
|
1284
|
+
app.preferences.rulerUnits = Units.PIXELS;
|
|
1285
|
+
var doc = app.activeDocument;
|
|
1286
|
+
var layer = doc.activeLayer;
|
|
1287
|
+
var cols = 4, rows = 3;
|
|
1288
|
+
var padX = 20, padY = 20;
|
|
1289
|
+
var w = layer.bounds[2] - layer.bounds[0];
|
|
1290
|
+
var h = layer.bounds[3] - layer.bounds[1];
|
|
1291
|
+
|
|
1292
|
+
for (var r = 0; r < rows; r++) {
|
|
1293
|
+
for (var c = 0; c < cols; c++) {
|
|
1294
|
+
if (r === 0 && c === 0) continue; // skip original
|
|
1295
|
+
var copy = layer.duplicate();
|
|
1296
|
+
var targetX = layer.bounds[0] + c * (w + padX);
|
|
1297
|
+
var targetY = layer.bounds[1] + r * (h + padY);
|
|
1298
|
+
copy.translate(targetX - copy.bounds[0], targetY - copy.bounds[1]);
|
|
1299
|
+
copy.opacity = 100 - (r * cols + c) * 5;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
```
|
|
1303
|
+
|
|
1304
|
+
### 5. Apply watermark from URL
|
|
1305
|
+
```js
|
|
1306
|
+
app.preferences.rulerUnits = Units.PIXELS;
|
|
1307
|
+
var doc = app.activeDocument;
|
|
1308
|
+
|
|
1309
|
+
// Open watermark as smart object layer
|
|
1310
|
+
app.open("https://example.com/watermark.png", null, true);
|
|
1311
|
+
var wm = doc.activeLayer;
|
|
1312
|
+
|
|
1313
|
+
// Resize to 20% of document width
|
|
1314
|
+
var wmW = wm.bounds[2] - wm.bounds[0];
|
|
1315
|
+
var targetW = doc.width * 0.2;
|
|
1316
|
+
var scalePct = (targetW / wmW) * 100;
|
|
1317
|
+
wm.resize(scalePct, scalePct, AnchorPosition.TOPLEFT);
|
|
1318
|
+
|
|
1319
|
+
// Move to bottom-right with 20px margin
|
|
1320
|
+
var wmNewW = wm.bounds[2] - wm.bounds[0];
|
|
1321
|
+
var wmNewH = wm.bounds[3] - wm.bounds[1];
|
|
1322
|
+
wm.translate(
|
|
1323
|
+
doc.width - wmNewW - 20 - wm.bounds[0],
|
|
1324
|
+
doc.height - wmNewH - 20 - wm.bounds[1]
|
|
1325
|
+
);
|
|
1326
|
+
wm.opacity = 60;
|
|
1327
|
+
app.echoToOE("watermark applied");
|
|
1328
|
+
```
|
|
1329
|
+
|
|
1330
|
+
### 6. Get all layer info as JSON
|
|
1331
|
+
```js
|
|
1332
|
+
function getLayerInfo(parent, depth) {
|
|
1333
|
+
depth = depth || 0;
|
|
1334
|
+
var result = [];
|
|
1335
|
+
for (var i = 0; i < parent.layers.length; i++) {
|
|
1336
|
+
var l = parent.layers[i];
|
|
1337
|
+
var info = {
|
|
1338
|
+
name: l.name,
|
|
1339
|
+
type: l.typename,
|
|
1340
|
+
visible: l.visible,
|
|
1341
|
+
opacity: l.opacity,
|
|
1342
|
+
depth: depth
|
|
1343
|
+
};
|
|
1344
|
+
if (l.typename === "ArtLayer") {
|
|
1345
|
+
info.kind = l.kind.toString();
|
|
1346
|
+
info.bounds = [l.bounds[0], l.bounds[1], l.bounds[2], l.bounds[3]];
|
|
1347
|
+
if (l.kind === LayerKind.TEXT) {
|
|
1348
|
+
info.text = l.textItem.contents;
|
|
1349
|
+
info.font = l.textItem.font;
|
|
1350
|
+
info.size = l.textItem.size;
|
|
1351
|
+
}
|
|
1352
|
+
} else if (l.typename === "LayerSet") {
|
|
1353
|
+
info.children = getLayerInfo(l, depth + 1);
|
|
1354
|
+
}
|
|
1355
|
+
result.push(info);
|
|
1356
|
+
}
|
|
1357
|
+
return result;
|
|
1358
|
+
}
|
|
1359
|
+
app.echoToOE(JSON.stringify(getLayerInfo(app.activeDocument)));
|
|
1360
|
+
```
|
|
1361
|
+
|
|
1362
|
+
---
|
|
1363
|
+
|
|
1364
|
+
## Common Mistakes & Gotchas
|
|
1365
|
+
|
|
1366
|
+
| Problem | Cause | Fix |
|
|
1367
|
+
|---------|-------|-----|
|
|
1368
|
+
| `createEmbed` never resolves | Container has no size | Add `width` + `height` CSS to the container `<div>` |
|
|
1369
|
+
| `runScript` returns `["done"]` with no data | No `echoToOE` in script | Add `app.echoToOE(value)` for anything you want back |
|
|
1370
|
+
| `result[0]` is `"done"`, not the expected value | `echoToOE` not reached | Check script logic for early exit or errors |
|
|
1371
|
+
| Images won't load (network error) | CORS | Server must respond with `Access-Control-Allow-Origin: *` |
|
|
1372
|
+
| `openFromURL(url, true)` layer not ready | Async loading lag | Use `addImageAndWait` utility |
|
|
1373
|
+
| `exportImage` only PNG/JPG | `exportImage` limitation | Use `runScript("saveToOE('webp:0.85')")` for other formats |
|
|
1374
|
+
| Pixel coordinates behave unexpectedly | Wrong ruler units | Always set `app.preferences.rulerUnits = Units.PIXELS` first |
|
|
1375
|
+
| Text `size` set but looks different | Wrong type units | Set `app.preferences.typeUnits = TypeUnits.PIXELS` |
|
|
1376
|
+
| Layer not found by name | Wrong layer level | Layers are scoped; use recursive search for nested layers |
|
|
1377
|
+
| `layer.bounds[0]` returns a UnitValue, not number | Ruler units issue | Force `Units.PIXELS` before reading bounds |
|
|
1378
|
+
| Smart Object edit hangs | Missing `doc.save(); doc.close()` | Always save + close when done editing SO |
|
|
1379
|
+
| React double-mount in dev | Strict Mode | Use `if (peaRef.current) return` guard in `useEffect` |
|
|
1380
|
+
|
|
1381
|
+
---
|
|
1382
|
+
|
|
1383
|
+
## Limitations
|
|
1384
|
+
|
|
1385
|
+
- This skill covers host-page integration patterns; it does not replace Photopea's own terms, API documentation, or licensing guidance.
|
|
1386
|
+
- Remote URL loading depends on browser CORS behavior, network availability, and the user's Photopea account/session state.
|
|
1387
|
+
- `runScript` executes scripts inside the embedded Photopea document context. Only run scripts you understand and only with user-approved files.
|
|
1388
|
+
- Export behavior can vary by document size, browser memory limits, and the formats supported by the active Photopea runtime.
|
|
1389
|
+
|
|
1390
|
+
---
|
|
1391
|
+
|
|
1392
|
+
## Sources
|
|
1393
|
+
|
|
1394
|
+
- photopea.js: https://github.com/yikuansun/PhotopeaAPI
|
|
1395
|
+
- npm: https://www.npmjs.com/package/photopea
|
|
1396
|
+
- Photopea Live Messaging API: https://www.photopea.com/api/live
|
|
1397
|
+
- Photopea Script reference: https://www.photopea.com/learn/scripts
|
|
1398
|
+
- Photoshop JS Scripting reference (compatible): https://theiviaxx.github.io/photoshop-docs/Photoshop/index.html
|
|
1399
|
+
- Plugin dev gists (addImageAndWait, getDocumentAsImage): https://gist.github.com/yikuansun/c0f1a602b4e9d4e344a41c4f49ded3bf
|