├── .gitignore
├── .npmignore
├── test
├── assets
│ ├── artboard_list.psd
│ ├── document_trim.psd
│ └── selected_layers.psd
├── unittest_Text.ts
├── remove_pattern_by_name.jsx
├── socket_example.jsx
├── export_document.jsx
├── quick_export_layer_as_png.jsx
├── transform.jsx
├── penTool.jsx
├── remove_channel_fast.jsx
├── set_crop_tool_option.jsx
├── rename_slices.jsx
├── layerSplit.jsx
├── JSON.jsx
├── to_cmyk_black.jsx
├── parse_lagecy_content_data.jsx
├── http.jsx
└── descriptor-info.jsx
├── src
├── types
│ └── index.d.ts
├── lib
│ ├── Size.ts
│ ├── base
│ │ ├── Includes.ts
│ │ ├── GradientColor.ts
│ │ └── SolidColor.ts
│ ├── tool
│ │ ├── MoveTool.ts
│ │ ├── Tool.ts
│ │ └── RulerTool.ts
│ ├── fx
│ │ ├── FXColorOverlay.ts
│ │ ├── FXDropShadow.ts
│ │ ├── FXGradientFill.ts
│ │ └── FXStroke.ts
│ ├── Artboard.ts
│ ├── Utils.ts
│ ├── ColorSampler.ts
│ ├── Guide.ts
│ ├── Canvas.ts
│ ├── Application.ts
│ ├── MetaData.ts
│ ├── Rect.ts
│ ├── History.ts
│ ├── Selection.ts
│ ├── Stroke.ts
│ ├── Shape.ts
│ ├── Text.ts
│ ├── DescriptorInfo.ts
│ └── Document.ts
├── index.ts
└── main.ts
├── tsconfig.json
├── package.json
├── webpack.config.js
├── README_zh.md
├── README.md
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .idea/
3 | dist/
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src/
2 | test/
3 | .idea
4 | package-lock.json
5 | tsconfig.json
6 | webpack.config.js
7 | yarn.lock
8 |
--------------------------------------------------------------------------------
/test/assets/artboard_list.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emptykid/photoshop-script-api/HEAD/test/assets/artboard_list.psd
--------------------------------------------------------------------------------
/test/assets/document_trim.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emptykid/photoshop-script-api/HEAD/test/assets/document_trim.psd
--------------------------------------------------------------------------------
/test/assets/selected_layers.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emptykid/photoshop-script-api/HEAD/test/assets/selected_layers.psd
--------------------------------------------------------------------------------
/src/types/index.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by xiaoqiang
3 | * @date
4 | * @description
5 | */
6 |
7 | declare function stringIDToTypeID(s: string): number;
8 | declare function charIDToTypeID(s: string): number;
9 |
--------------------------------------------------------------------------------
/test/unittest_Text.ts:
--------------------------------------------------------------------------------
1 | import {Text, TextAlignment} from "../src/lib/Text";
2 | import {Point} from "../src/lib/Shape";
3 |
4 |
5 | const t = new Text("Hello World");
6 | t.setTextClickPoint(new Point(100, 100))
7 | t.setSize(30);
8 | t.setAlignment(TextAlignment.Right);
9 | t.paint();
--------------------------------------------------------------------------------
/test/remove_pattern_by_name.jsx:
--------------------------------------------------------------------------------
1 |
2 | var desc1 = new ActionDescriptor();
3 | var ref1 = new ActionReference();
4 | ref1.putName(stringIDToTypeID( "pattern" ), "Tree Tile 3")
5 | desc1.putReference( stringIDToTypeID( "null" ), ref1 );
6 | executeAction( stringIDToTypeID( "delete" ), desc1, DialogModes.NO );
7 |
8 |
--------------------------------------------------------------------------------
/test/socket_example.jsx:
--------------------------------------------------------------------------------
1 |
2 | var reply = "";
3 | var conn = new Socket();
4 |
5 | // access Adobe's home page
6 | if (conn.open ("www.baidu.com:80")) {
7 |
8 | // send a HTTP GET request
9 | conn.write ("GET / HTTP/1.0\n\n");
10 |
11 | // and read the server's reply
12 | reply = conn.read(999999);
13 |
14 | conn.close();
15 | }
16 |
17 |
18 | $.writeln(reply)
19 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "es6",
4 | "target": "ES3",
5 | "outDir": "./dist/",
6 | "noImplicitAny": true,
7 | "declaration": true,
8 | "skipLibCheck": true,
9 | "ignoreDeprecations": "5.0",
10 | "typeRoots": [
11 | "./typings"
12 | ],
13 | "sourceMap": true
14 | },
15 | "exclude": [
16 | "node_modules"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/src/lib/Size.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by xiaoqiang
3 | * @date 2021/07/29
4 | * @description represent a width & height
5 | */
6 |
7 |
8 | export class Size {
9 | width: number;
10 | height: number;
11 |
12 | constructor(width: number, height: number) {
13 | this.width = width;
14 | this.height = height;
15 | }
16 |
17 | toString(): string {
18 | return `${this.width},${this.height}`;
19 | }
20 |
21 | isEmpty(): boolean {
22 | return this.width === 0 && this.height === 0;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/lib/base/Includes.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by xiaoqiang
3 | * @date
4 | * @description
5 | */
6 |
7 | export type FontFormat = {
8 | name: string;
9 | style: string;
10 | scriptName: string;
11 | }
12 | export enum PSLayerColor {
13 | none = 'none',
14 | red = 'red',
15 | orange = 'orange',
16 | yellowColor = 'yellowColor',
17 | grain = 'grain',
18 | blue = 'blue',
19 | violet = 'violet',
20 | gray = 'gray'
21 | }
22 |
23 | export enum PSColorMode {
24 | RGB = 'RGBColorMode',
25 | CMYK = 'CMYKColorMode',
26 | LAB = 'labColorMode',
27 | GRAYSCALE = 'grayscaleMode',
28 | INDEXED = 'indexedColorMode',
29 | MULTICHANNEL = 'multichannelMode',
30 | }
--------------------------------------------------------------------------------
/test/export_document.jsx:
--------------------------------------------------------------------------------
1 |
2 | #include "./JSON.jsx";
3 | #include "./descriptor-info.jsx";
4 |
5 | var descFlags = {
6 | reference : false,
7 | extended : false,
8 | maxRawLimit : 10000,
9 | maxXMPLimit : 100000,
10 | saveToFile: Folder.desktop.absoluteURI + '/descriptor-info-output3.json'
11 | };
12 |
13 |
14 | var documentReference = new ActionReference();
15 | documentReference.putEnumerated(app.charIDToTypeID("Dcmn"), app.charIDToTypeID("Ordn"), app.charIDToTypeID("Trgt"));
16 | var documentDescriptor = app.executeActionGet(documentReference);
17 |
18 |
19 |
20 | var descObject = descriptorInfo.getProperties( documentDescriptor, descFlags );
21 | $.writeln(JSON.stringify(descObject, null, 4));
22 |
23 | /*
24 | var ref = new ActionReference();
25 | ref.putProperty( charIDToTypeID('Chnl'), charIDToTypeID('fsel') );
26 | var desc1 = executeActionGet(ref);
27 |
28 | var descObject = descriptorInfo.getProperties( desc1, descFlags );
29 | $.writeln(JSON.stringify(descObject, null, 4));
30 |
31 | */
32 |
33 | /*
34 |
35 | */
36 |
--------------------------------------------------------------------------------
/test/quick_export_layer_as_png.jsx:
--------------------------------------------------------------------------------
1 | //quick_export_png(activeDocument.path.fsName)
2 | // export activeLayer
3 | function quick_export_png(path, layer)
4 | {
5 | try {
6 | if (layer == undefined) layer = false;
7 | var d = new ActionDescriptor();
8 | var r = new ActionReference();
9 | r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
10 | d.putReference(stringIDToTypeID("null"), r);
11 | d.putString(stringIDToTypeID("fileType"), "png");
12 | d.putInteger(stringIDToTypeID("quality"), 32);
13 | d.putInteger(stringIDToTypeID("metadata"), 0);
14 | d.putString(stringIDToTypeID("destFolder"), path);
15 | d.putBoolean(stringIDToTypeID("sRGB"), true);
16 | d.putBoolean(stringIDToTypeID("openWindow"), false);
17 | executeAction(stringIDToTypeID(layer?"exportSelectionAsFileTypePressed":"exportDocumentAsFileTypePressed"), d, DialogModes.NO);
18 | }
19 | catch (e) { throw(e); }
20 | }
21 |
22 | quick_export_png(activeDocument.path.fsName, true);
23 |
--------------------------------------------------------------------------------
/test/transform.jsx:
--------------------------------------------------------------------------------
1 | var desc1 = new ActionDescriptor();
2 | var ref1 = new ActionReference();
3 | ref1.putEnumerated( stringIDToTypeID( "path" ), stringIDToTypeID( "ordinal" ), stringIDToTypeID( "targetEnum" ) );
4 | desc1.putReference( stringIDToTypeID( "null" ), ref1 );
5 | desc1.putEnumerated( stringIDToTypeID( "freeTransformCenterState" ), stringIDToTypeID( "quadCenterState" ), stringIDToTypeID( "QCSAverage" ) );
6 | var desc2 = new ActionDescriptor();
7 | desc2.putUnitDouble( stringIDToTypeID( "horizontal" ), stringIDToTypeID( "pixelsUnit" ), 0.000000 );
8 | desc2.putUnitDouble( stringIDToTypeID( "vertical" ), stringIDToTypeID( "pixelsUnit" ), 0.000000 );
9 | desc1.putObject( stringIDToTypeID( "offset" ), stringIDToTypeID( "offset" ), desc2 );
10 | desc1.putUnitDouble( stringIDToTypeID( "width" ), stringIDToTypeID( "percentUnit" ), 4.96258916582441 );
11 | desc1.putUnitDouble( stringIDToTypeID( "height" ), stringIDToTypeID( "percentUnit" ), 84.0386238744702);
12 |
13 | desc1.putBoolean( stringIDToTypeID( "transformOnlyLineEnds" ), true );
14 | executeAction( stringIDToTypeID( "transform" ), desc1, DialogModes.NO );
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "photoshop-script-api",
3 | "version": "1.0.4",
4 | "description": "photoshop script api 此项目旨在实现一套完整的API,用来封装photoshop插件开发过程中,宿主提供的能力合集。",
5 | "main": "index.jsx",
6 | "types": "index.d.ts",
7 | "scripts": {
8 | "test": "echo \"Error: no test specified\" && exit 1",
9 | "build": "webpack --mode=production",
10 | "build:watch": "webpack --mode=production --watch"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/emptykid/photoshop-script-api"
15 | },
16 | "author": "xiaoqiang",
17 | "license": "ISC",
18 | "bugs": {
19 | "url": "https://github.com/emptykid/photoshop-script-api/issues"
20 | },
21 | "homepage": "https://github.com/emptykid/photoshop-script-api",
22 | "devDependencies": {
23 | "babel-loader": "^8.2.5",
24 | "jsxbin-webpack-plugin": "^1.0.0",
25 | "ps-extendscript-types": "^1.0.2",
26 | "ts-loader": "^9.3.1",
27 | "typescript": "^5.0.4",
28 | "uglifyjs-webpack-plugin": "^2.2.0",
29 | "webpack": "^5.73.0",
30 | "webpack-cli": "^4.10.0",
31 | "webpack-dev-server": "^4.9.2"
32 | },
33 | "peerDependencies": {},
34 | "dependencies": {
35 | "cep-shim": "^4.5.12",
36 | "extendscript-es5-shim": "^0.3.1"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/lib/tool/MoveTool.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by xiaoqiang
3 | * @date
4 | * @description
5 | */
6 | import {Layer} from "../Layer";
7 | import {Tool} from "./Tool";
8 |
9 | export enum LayerAlignType {
10 | Left = "ADSLefts",
11 | CenterH = "ADSCentersH",
12 | Right = "ADSRights",
13 | Top = "ADSTops",
14 | CenterV = "ADSCentersV",
15 | Bottom = "ADSBottoms",
16 | }
17 |
18 | export class MoveTool extends Tool{
19 |
20 | constructor() {
21 | super("moveTool");
22 | }
23 |
24 | public static alignLayers(layers: Layer[], align: LayerAlignType): void {
25 | Layer.setSelectedLayers(layers);
26 | const desc1 = new ActionDescriptor();
27 | const ref1 = new ActionReference();
28 | ref1.putEnumerated( app.stringIDToTypeID( "layer" ), app.stringIDToTypeID( "ordinal" ), app.stringIDToTypeID( "targetEnum" ) );
29 | desc1.putReference( app.stringIDToTypeID( "null" ), ref1 );
30 | desc1.putEnumerated( app.stringIDToTypeID( "using" ), app.stringIDToTypeID( "alignDistributeSelector" ), app.stringIDToTypeID( align ) );
31 | desc1.putBoolean( app.stringIDToTypeID( "alignToCanvas" ), false );
32 | app.executeAction( app.stringIDToTypeID( "align" ), desc1, DialogModes.NO );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/lib/fx/FXColorOverlay.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by xiaoqiang
3 | * @date
4 | * @description
5 | */
6 | import {SolidColor} from "../base/SolidColor";
7 |
8 | export class FXColorOverlay {
9 | public enabled: boolean = true;
10 | public present: boolean = true;
11 | public mode: string = "normal";
12 | public color: SolidColor = SolidColor.blackColor();
13 | public opacity: number = 100;
14 |
15 | /**
16 | * create a FXColorOverlay object with a descriptor
17 | * @param desc
18 | * @return FXColorOverlay
19 | */
20 | static fromDescriptor(desc: ActionDescriptor): FXColorOverlay {
21 | const ins = new FXColorOverlay();
22 | ins.enabled = desc.getBoolean(app.stringIDToTypeID("enabled"));
23 | ins.present = desc.getBoolean(app.stringIDToTypeID("present"));
24 | ins.mode = app.typeIDToStringID(desc.getEnumerationValue(app.stringIDToTypeID("mode")));
25 | const colorDesc = desc.getObjectValue(app.stringIDToTypeID("color"));
26 | ins.color = SolidColor.fromDescriptor(colorDesc);
27 | ins.opacity = desc.getDouble(app.stringIDToTypeID("opacity"));
28 | return ins;
29 | }
30 |
31 | public toString(): string {
32 | return `${this.color.toHex()} ${this.opacity}% ${this.mode}`;
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by xiaoqiang
3 | * @date
4 | * @description
5 | */
6 |
7 | ///
8 |
9 | // base
10 | export {SolidColor} from "./lib/base/SolidColor";
11 | export {GradientColor} from "./lib/base/GradientColor";
12 |
13 | // fx
14 | export {FXColorOverlay} from "./lib/fx/FXColorOverlay";
15 | export {FXDropShadow} from "./lib/fx/FXDropShadow";
16 | export {FXStroke} from "./lib/fx/FXStroke";
17 |
18 | // tool
19 | export {MoveTool} from "./lib/tool/MoveTool";
20 | export {RulerTool} from "./lib/tool/RulerTool";
21 |
22 | export {Application, HostVersion } from "./lib/Application";
23 | export {Artboard } from "./lib/Artboard";
24 | export {Canvas} from "./lib/Canvas";
25 | export {ColorSampler} from "./lib/ColorSampler";
26 | export {Document} from "./lib/Document";
27 | export {Guide} from "./lib/Guide";
28 | export {History} from "./lib/History";
29 | export {Layer} from "./lib/Layer";
30 | export {MetaData} from "./lib/MetaData";
31 | export {Rect} from "./lib/Rect";
32 | export {Selection} from "./lib/Selection";
33 | export {Shape, Point, Line, Circle, Ellipse, Triangle, Rectangle, UnitType} from "./lib/Shape";
34 | export {Size} from "./lib/Size";
35 | export {Stroke, StrokeLineType} from "./lib/Stroke";
36 | export {Text, TextAntiAliasType, TextGriddingType, TextOrientation, TextStrikeThroughType, TextAlignment} from "./lib/Text";
37 | export {Utils} from "./lib/Utils";
38 |
39 |
--------------------------------------------------------------------------------
/src/lib/Artboard.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by xiaoqiang
3 | * @date 2021/07/30
4 | * @description
5 | */
6 |
7 | import { Layer } from "./Layer";
8 | import { Rect } from "./Rect";
9 |
10 | export class Artboard extends Layer {
11 |
12 | bounds(): Rect {
13 | const ref = new ActionReference();
14 | ref.putIdentifier(app.charIDToTypeID("Lyr "), this.id);
15 | const layerDesc = app.executeActionGet(ref);
16 | if (layerDesc.hasKey(app.stringIDToTypeID("artboard"))) {
17 | const artBoardRect = layerDesc.getObjectValue(app.stringIDToTypeID("artboard")).getObjectValue(app.stringIDToTypeID("artboardRect"));
18 | const theName = layerDesc.getString(app.stringIDToTypeID('name'));
19 | const left = artBoardRect.getUnitDoubleValue(app.stringIDToTypeID("left"));
20 | const top = artBoardRect.getUnitDoubleValue(app.stringIDToTypeID("top"));
21 | const right = artBoardRect.getUnitDoubleValue(app.stringIDToTypeID("right"));
22 | const bottom = artBoardRect.getUnitDoubleValue(app.stringIDToTypeID("bottom"));
23 | return new Rect(left, top, (right - left), (bottom - top));
24 | }
25 | return super.bounds();
26 | }
27 |
28 | isShapeLayer(): boolean {
29 | return false;
30 | }
31 |
32 | isTextLayer(): boolean {
33 | return false;
34 | }
35 |
36 | isGroupLayer(): boolean {
37 | return false;
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/lib/tool/Tool.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by xiaoqiang
3 | * @date
4 | * @description represent the tools in photoshop
5 | */
6 |
7 | export class Tool {
8 | private readonly name: string;
9 |
10 | constructor(name: string) {
11 | this.name = name;
12 | }
13 |
14 | /**
15 | * return tool name in stringID format
16 | * @return string
17 | */
18 | public getName(): string {
19 | return this.name;
20 | }
21 |
22 | /**
23 | * select current tool
24 | */
25 | public select() {
26 | const desc1 = new ActionDescriptor();
27 | const ref1 = new ActionReference();
28 | ref1.putClass( app.stringIDToTypeID( this.name ) );
29 | desc1.putReference( app.stringIDToTypeID( "null" ), ref1 );
30 | app.executeAction( app.stringIDToTypeID( "select" ), desc1, DialogModes.NO );
31 | }
32 |
33 | /**
34 | * get current selected tool
35 | * @return Tool
36 | */
37 | public static getActive(): Tool {
38 | const desc = new ActionDescriptor();
39 | const ref = new ActionReference();
40 | ref.putProperty(app.charIDToTypeID('Prpr'), app.stringIDToTypeID("tool"));
41 | ref.putEnumerated(app.charIDToTypeID('capp'), app.charIDToTypeID('Ordn'), app.charIDToTypeID('Trgt'));
42 | desc.putReference(app.charIDToTypeID('null'), ref);
43 | const result = app.executeAction(app.charIDToTypeID('getd'), desc, DialogModes.NO);
44 | const toolName = result.getEnumerationType(app.stringIDToTypeID("tool"));
45 | return new Tool(app.typeIDToStringID(toolName));
46 | }
47 |
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 |
2 | const path = require('path');
3 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
4 | const JSXBinWebpackPlugin = require('jsxbin-webpack-plugin')
5 |
6 | const dist_dir = "./dist";
7 |
8 | module.exports = (env, argv) => {
9 | return {
10 | mode: 'production',
11 | target: ['web', 'es3'],
12 | entry: {
13 | index: './src/index.ts',
14 | main: './src/main.ts',
15 | test: './test/unittest_Text.ts'
16 | },
17 | resolve: {
18 | extensions: ['.js', '.ts', '.json'],
19 | },
20 | devServer: {
21 | contentBase: path.join(__dirname, 'dist'),
22 | compress: false,
23 | port: 8080,
24 | },
25 | optimization: {
26 | minimizer: [
27 | new UglifyJsPlugin({
28 | test: /\.js(\?.*)?$/i,
29 | uglifyOptions: {
30 | mangle: false,
31 | compress: false,
32 | output: {
33 | beautify: true,
34 | },
35 | }
36 | }),
37 | /*
38 | new JSXBinWebpackPlugin({
39 | test: /\.js$/
40 | })
41 | */
42 | ]
43 | },
44 | module: {
45 | rules: [{
46 | test: /\.ts?$/,
47 | use: 'ts-loader',
48 | exclude: /node_modules/
49 | }]
50 | },
51 | plugins: [
52 | ],
53 | output: {
54 | filename: '[name].js',
55 | path: path.resolve(dist_dir),
56 | }
57 | }};
58 |
--------------------------------------------------------------------------------
/src/lib/tool/RulerTool.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by xiaoqiang
3 | * @date
4 | * @description represent the ruler tool of photoshop
5 | */
6 | import {Point} from "../Shape";
7 | import {Tool} from "./Tool";
8 |
9 | export class RulerTool extends Tool {
10 |
11 | constructor() {
12 | super("rulerTool");
13 | }
14 |
15 | /**
16 | * get current active ruler info
17 | * return start & end points
18 | * @return Point[]
19 | */
20 | public static get(): Point[] {
21 | const result: Point[] = [];
22 | const desc1 = new ActionDescriptor();
23 | const ref = new ActionReference();
24 | ref.putProperty( app.charIDToTypeID('Prpr'), app.charIDToTypeID('RrPt') );
25 | ref.putEnumerated( app.charIDToTypeID('Dcmn'), app.charIDToTypeID('Ordn'), app.charIDToTypeID('Trgt') );
26 | desc1.putReference( app.charIDToTypeID('null'), ref );
27 | const desc = app.executeAction( app.charIDToTypeID('getd'), desc1, DialogModes.NO );
28 | if( desc.hasKey( app.charIDToTypeID('Pts ') ) ) {
29 | const pointList = desc.getList(app.charIDToTypeID('Pts '));
30 | const startPointDesc = pointList.getObjectValue(0);
31 | const x1 = startPointDesc.getUnitDoubleValue(app.charIDToTypeID('X '));
32 | const y1 = startPointDesc.getUnitDoubleValue(app.charIDToTypeID('Y '));
33 | const start = new Point(x1, y1);
34 | const endPointDesc = pointList.getObjectValue(2);
35 | const x2 = endPointDesc.getUnitDoubleValue(app.charIDToTypeID('X '));
36 | const y2 = endPointDesc.getUnitDoubleValue(app.charIDToTypeID('Y '));
37 | const end = new Point(x2, y2);
38 | result.push(start);
39 | result.push(end);
40 | }
41 |
42 | return result;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/lib/Utils.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by xiaoqiang
3 | * @date 2022/04/29
4 | * @description
5 | */
6 |
7 | export class Utils {
8 |
9 | /**
10 | * save some text to local file
11 | * @param text
12 | * @param file
13 | */
14 | static saveFile(text: string, file: string): void {
15 | // @ts-ignore
16 | const jsonFile = new File(file);
17 | // @ts-ignore
18 | jsonFile.open("w");
19 | // @ts-ignore
20 | jsonFile.encoding = "UTF-8";
21 | // @ts-ignore
22 | jsonFile.lineFeed = "Unix";
23 | // @ts-ignore
24 | jsonFile.write(text);
25 | // @ts-ignore
26 | jsonFile.close();
27 |
28 | }
29 |
30 | /**
31 | * read data from file
32 | * @param filepath
33 | * @return string
34 | */
35 | static readFile(filepath: string): string {
36 | // @ts-ignore
37 | const f = new File(filepath);
38 | // @ts-ignore
39 | f.encoding = "UTF-8";
40 | // @ts-ignore
41 | f.open('r');
42 | // @ts-ignore
43 | const content = f.read();
44 | // @ts-ignore
45 | f.close();
46 | return content;
47 | }
48 |
49 | /**
50 | * check file exists
51 | * @param filepath
52 | * @return boolean
53 | */
54 | static fileExists(filepath: string): boolean {
55 | // @ts-ignore
56 | const f = new File(filepath);
57 | // @ts-ignore
58 | return f.exists;
59 | }
60 |
61 | /**
62 | * current os is mac or not
63 | * return boolean
64 | */
65 | static isMac(): boolean {
66 | return /mac/.test($.os.toLowerCase());
67 | }
68 |
69 | /**
70 | * current os is windows or not
71 | * return boolean
72 | */
73 | static isWin(): boolean {
74 | return /win/.test($.os.toLowerCase());
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/lib/ColorSampler.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by xiaoqiang
3 | * @date
4 | * @description
5 | */
6 | import {Point} from "./Shape";
7 | import {SolidColor} from "./base/SolidColor";
8 |
9 | export class ColorSampler {
10 | public position: Point;
11 | public color: SolidColor;
12 |
13 | constructor(position: Point, color: SolidColor = null) {
14 | this.position = position;
15 | this.color = color;
16 | }
17 |
18 | static fromDescriptor(desc: ActionDescriptor): ColorSampler {
19 | const position = desc.getObjectValue(app.stringIDToTypeID("position"));
20 | const color = desc.getObjectValue(app.stringIDToTypeID("color"));
21 | return new ColorSampler(Point.fromDescriptor(position), SolidColor.fromDescriptor(color));
22 | }
23 |
24 | static clearAll(): void {
25 | const desc1 = new ActionDescriptor();
26 | const ref1 = new ActionReference();
27 | ref1.putEnumerated( app.stringIDToTypeID( "colorSampler" ), app.stringIDToTypeID( "ordinal" ), app.stringIDToTypeID( "allEnum" ) );
28 | desc1.putReference( app.stringIDToTypeID( "null" ), ref1 );
29 | app.executeAction( app.stringIDToTypeID( "delete" ), desc1, DialogModes.NO );
30 | }
31 |
32 | public apply() {
33 | const desc1 = new ActionDescriptor();
34 | const ref1 = new ActionReference();
35 | ref1.putClass( app.stringIDToTypeID( "colorSampler" ) );
36 | desc1.putReference( app.stringIDToTypeID( "null" ), ref1 );
37 | const desc2 = new ActionDescriptor();
38 | desc2.putUnitDouble( app.stringIDToTypeID( "horizontal" ), app.stringIDToTypeID( "pixelsUnit" ), this.position.x );
39 | desc2.putUnitDouble( app.stringIDToTypeID( "vertical" ), app.stringIDToTypeID( "pixelsUnit" ), this.position.y );
40 | desc1.putObject( app.stringIDToTypeID( "position" ), app.stringIDToTypeID( "paint" ), desc2 );
41 | app.executeAction( app.stringIDToTypeID( "make" ), desc1, DialogModes.NO );
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/lib/fx/FXDropShadow.ts:
--------------------------------------------------------------------------------
1 | import {SolidColor} from "../base/SolidColor";
2 |
3 | /**
4 | * Created by xiaoqiang
5 | * @date
6 | * @description
7 | */
8 |
9 | export class FXDropShadow {
10 | public enabled: boolean = true;
11 | public present: boolean = true;
12 | public mode: string = "normal";
13 | public color: SolidColor = SolidColor.blackColor();
14 | public opacity: number = 100;
15 | public useGlobalAngle: boolean = true;
16 | public localLightingAngle: number = 120;
17 | public distance: number = 0;
18 | public chokeMatte: number = 0;
19 | public blur: number = 0;
20 | public noise: number = 0;
21 | public antiAlias: boolean = false;
22 |
23 | /**
24 | * create a FXDropShadow object from action descriptor
25 | * @param desc
26 | * @return FXDropShadow
27 | */
28 | static fromDescriptor(desc: ActionDescriptor): FXDropShadow {
29 | const ins = new FXDropShadow();
30 | ins.enabled = desc.getBoolean(app.stringIDToTypeID("enabled"));
31 | ins.present = desc.getBoolean(app.stringIDToTypeID("present"));
32 | ins.mode = app.typeIDToStringID(desc.getEnumerationValue(app.stringIDToTypeID("mode")));
33 | const colorDesc = desc.getObjectValue(app.stringIDToTypeID("color"));
34 | ins.color = SolidColor.fromDescriptor(colorDesc);
35 | ins.opacity = desc.getDouble(app.stringIDToTypeID("opacity"));
36 |
37 | ins.useGlobalAngle = desc.getBoolean(app.stringIDToTypeID("useGlobalAngle"));
38 | ins.localLightingAngle = desc.getDouble(app.stringIDToTypeID("localLightingAngle"));
39 | ins.chokeMatte = desc.getDouble(app.stringIDToTypeID("chokeMatte"));
40 | ins.blur = desc.getDouble(app.stringIDToTypeID("blur"));
41 | ins.distance = desc.getDouble(app.stringIDToTypeID("distance"));
42 | ins.noise = desc.getDouble(app.stringIDToTypeID("noise"));
43 | ins.antiAlias = desc.getBoolean(app.stringIDToTypeID("antiAlias"));
44 |
45 | return ins;
46 | }
47 |
48 | public toString(): string {
49 | return `${this.color.toHex()} ${this.distance}px ${this.blur}px ${this.chokeMatte}% (${this.localLightingAngle}') ${this.opacity}%`;
50 | }
51 |
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/test/penTool.jsx:
--------------------------------------------------------------------------------
1 |
2 | #include "./JSON.jsx";
3 | #include "./descriptor-info.jsx";
4 |
5 | var ref1 = new ActionReference();
6 | ref1.putProperty(stringIDToTypeID('property'), stringIDToTypeID("tool"));
7 | ref1.putClass(stringIDToTypeID('application'))
8 |
9 | var desc1 = executeActionGet(ref1);
10 |
11 | var ref2 = new ActionReference();
12 | ref2.putClass(stringIDToTypeID('penTool'));
13 | var desc2 = executeActionGet(ref1).getObjectValue(stringIDToTypeID('currentToolOptions'));
14 | desc2.putEnumerated(stringIDToTypeID("geometryToolMode"), stringIDToTypeID("geometryToolMode"), stringIDToTypeID("shape"));
15 | var strokeStyle = new ActionDescriptor();
16 | strokeStyle.putBoolean(stringIDToTypeID("fillEnabled"), false);
17 | var shapeStyle = new ActionDescriptor();
18 | shapeStyle.putObject(stringIDToTypeID("strokeStyle"), stringIDToTypeID("strokeStyle"), strokeStyle);
19 | desc2.putObject(stringIDToTypeID("shapeStyle"), stringIDToTypeID("shapeStyle"), shapeStyle);
20 |
21 | desc1.putReference(stringIDToTypeID('null'), ref2),
22 | desc1.putObject(stringIDToTypeID('to'), stringIDToTypeID('null'), desc2),
23 | executeAction(stringIDToTypeID('set'), desc1)
24 |
25 | /*
26 | if (desc2.hasKey(stringIDToTypeID("geometryToolMode"))) {
27 | $.writeln("has key geometryToolMode")
28 | $.writeln(stringIDToTypeID("geometryToolMode"));
29 | for (var i=0; i " + typeIDToStringID(typeID) + " => " + desc2.getType(typeID).toString());
32 | }
33 | }
34 | */
35 |
36 | /*
37 | var shapeStyle = desc2.getObjectValue(stringIDToTypeID("shapeStyle"));
38 | var strokeStyle = shapeStyle.getObjectValue(stringIDToTypeID("strokeStyle"));
39 | strokeStyle.putBoolean(stringIDToTypeID("fillEnabled"), false);
40 | strokeStyle.putBoolean(stringIDToTypeID("strokeEnabled"), true);
41 | shapeStyle.putObject(stringIDToTypeID("strokeStyle"), stringIDToTypeID("strokeStyle"), strokeStyle);
42 | desc2.putObject(stringIDToTypeID("shapeStyle"), stringIDToTypeID("shapeStyle"), shapeStyle);
43 | */
44 |
45 |
46 | /*
47 | var descFlags = {
48 | reference : false,
49 | extended : false,
50 | maxRawLimit : 10000,
51 | maxXMPLimit : 100000,
52 | saveToFile: Folder.desktop.absoluteURI + '/descriptor-info-output.json'
53 | };
54 |
55 | var descObject = descriptorInfo.getProperties( desc2, descFlags );
56 | // Running in ExtendScript
57 | $.writeln(JSON.stringify(descObject, null, 4));
58 | */
59 |
--------------------------------------------------------------------------------
/test/remove_channel_fast.jsx:
--------------------------------------------------------------------------------
1 |
2 | #include "./JSON.jsx";
3 | #include "./descriptor-info.jsx";
4 |
5 | var descFlags = {
6 | reference : false,
7 | extended : false,
8 | maxRawLimit : 10000,
9 | maxXMPLimit : 100000,
10 | saveToFile: Folder.desktop.absoluteURI + '/descriptor-info-output1.json'
11 | };
12 |
13 | var start = (new Date()).getTime();
14 |
15 | var documentReference = new ActionReference();
16 | documentReference.putProperty(app.charIDToTypeID("Prpr"), app.stringIDToTypeID("itemIndex"));
17 | documentReference.putEnumerated(app.charIDToTypeID("Dcmn"), app.charIDToTypeID("Ordn"), app.charIDToTypeID("Trgt"));
18 | var documentDescriptor = app.executeActionGet(documentReference);
19 | var docIndex = documentDescriptor.getInteger(app.stringIDToTypeID("itemIndex"));
20 |
21 |
22 | var desc1 = new ActionDescriptor();
23 | var list1 = new ActionList();
24 |
25 | var doc = app.activeDocument;
26 | var channels = doc.channels;
27 | var length = channels.length;
28 | var deleteCount = 0;
29 | for (var i= 1; i<=length; i++) {
30 | var ref = new ActionReference();
31 | ref.putIndex(charIDToTypeID("Chnl"), i);
32 | ref.putIndex(charIDToTypeID("Dcmn"), docIndex);
33 | var desc = app.executeActionGet(ref);
34 | var channelName = desc.getString(charIDToTypeID("ChnN"));
35 | if (!/(Red)|(Green)|(Blue)|(红)|(绿)|(蓝)/.test(channelName)) {
36 | var ref1 = new ActionReference();
37 | ref1.putName( app.stringIDToTypeID( "channel" ), channelName);
38 | list1.putReference( ref1 );
39 | deleteCount++;
40 | }
41 | }
42 | if (deleteCount > 0) {
43 | desc1.putList( stringIDToTypeID( "null" ), list1 );
44 | executeAction( stringIDToTypeID( "delete" ), desc1, DialogModes.NO );
45 | }
46 |
47 | var end = (new Date()).getTime();
48 | var cost = "Time: " + (end - start) + "ms";
49 | cost;
50 |
51 | /*
52 | var r = new ActionReference();
53 | r.putEnumerated(stringIDToTypeID("document"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
54 |
55 | var desc1 = executeActionGet(r);
56 |
57 |
58 | var descObject = descriptorInfo.getProperties( desc1, descFlags );
59 | $.writeln(JSON.stringify(descObject, null, 4));
60 | */
61 |
62 | /*
63 | var ref = new ActionReference();
64 | ref.putProperty( charIDToTypeID('Chnl'), charIDToTypeID('fsel') );
65 | var desc1 = executeActionGet(ref);
66 |
67 | var descObject = descriptorInfo.getProperties( desc1, descFlags );
68 | $.writeln(JSON.stringify(descObject, null, 4));
69 |
70 | */
71 |
72 | /*
73 |
74 | */
75 |
--------------------------------------------------------------------------------
/test/set_crop_tool_option.jsx:
--------------------------------------------------------------------------------
1 | //#include "./JSON.jsx";
2 | //#include "./descriptor-info.jsx";
3 |
4 |
5 | /**
6 | * 设置裁切工具的工具栏参数
7 | * 限定为 宽x高x分辨率的设置
8 | * @param width
9 | * @param height
10 | * @param resolution
11 | * @param density 1:px/inch 2:px/cm
12 | */
13 | function setCropToolOptions(width, height, resolution, density) {
14 | var ref1 = new ActionReference();
15 | ref1.putProperty(app.stringIDToTypeID('property'), app.stringIDToTypeID("tool"));
16 | ref1.putClass(app.stringIDToTypeID('application'))
17 |
18 | var desc1 = app.executeActionGet(ref1);
19 |
20 | var ref2 = new ActionReference();
21 | ref2.putClass(app.stringIDToTypeID('cropTool'));
22 |
23 | var desc2 = app.executeActionGet(ref1).getObjectValue(app.stringIDToTypeID('currentToolOptions'));
24 |
25 | var cropOption = desc2.getObjectValue(app.charIDToTypeID("CrpO"))
26 |
27 | var rate = density === 1? 1 : 2.54;
28 | cropOption.putEnumerated(app.stringIDToTypeID("cropAspectRatioModeKey"), app.stringIDToTypeID("cropAspectRatioModeKey"), app.stringIDToTypeID("targetSize"));
29 | cropOption.putInteger(app.charIDToTypeID("CrRS"), density);
30 | cropOption.putUnitDouble(app.charIDToTypeID("CrWV"),app.stringIDToTypeID( "pixelsUnit" ), width);
31 | cropOption.putUnitDouble(app.charIDToTypeID("CrHV"),app.stringIDToTypeID( "pixelsUnit" ), height);
32 | cropOption.putUnitDouble(app.charIDToTypeID("CrRV"),app.stringIDToTypeID( "densityUnit" ), resolution*rate*65536);
33 |
34 | desc2.putObject(app.charIDToTypeID("CrpO"), app.charIDToTypeID("CrpO"), cropOption);
35 |
36 | desc1.putReference(app.stringIDToTypeID('null'), ref2);
37 | desc1.putObject(app.stringIDToTypeID('to'), app.stringIDToTypeID('null'), desc2);
38 | executeAction(app.stringIDToTypeID('set'), desc1);
39 |
40 | }
41 |
42 | setCropToolOptions(150, 150, 100, 1);
43 |
44 | /*
45 | var ref1 = new ActionReference();
46 | ref1.putEnumerated(stringIDToTypeID('application'), charIDToTypeID('Ordn'), charIDToTypeID('Trgt'));
47 | var appDesc = executeActionGet(ref1);
48 | var currentToolOptions = appDesc.getObjectValue(stringIDToTypeID("currentToolOptions"));
49 |
50 | var cropOption = currentToolOptions.getObjectValue(charIDToTypeID("CrpO"));
51 |
52 | var descFlags = {
53 | reference : false,
54 | extended : false,
55 | maxRawLimit : 10000,
56 | maxXMPLimit : 100000,
57 | saveToFile: Folder.desktop.absoluteURI + '/descriptor-info-output.json'
58 | };
59 |
60 | var descObject = descriptorInfo.getProperties( cropOption, descFlags );
61 | // Running in ExtendScript
62 | var output = JSON.stringify(descObject, null, 4);
63 | output
64 |
65 | */
66 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by xiaoqiang
3 | * @date
4 | * @description
5 | */
6 | // @ts-nocheck
7 |
8 | // base
9 | import {SolidColor as _SolidColor} from "./lib/base/SolidColor";
10 | $.SolidColor = _SolidColor;
11 | import {GradientColor as _GradientColor} from "./lib/base/GradientColor";
12 | $.GradientColor = _GradientColor;
13 |
14 | // fx
15 | import {FXColorOverlay as _FXColorOverlay} from "./lib/fx/FXColorOverlay";
16 | $.FXColorOverlay = _FXColorOverlay;
17 | import {FXDropShadow as _FXDropShadow} from "./lib/fx/FXDropShadow";
18 | $.FXDropShadow = _FXDropShadow;
19 | import {FXStroke as _FXStroke} from "./lib/fx/FXStroke";
20 | $.FXStroke = _FXStroke;
21 |
22 | // tool
23 | import {MoveTool as _MoveTool} from "./lib/tool/MoveTool";
24 | $.MoveTool = _MoveTool;
25 | import {RulerTool as _RulerTool} from "./lib/tool/RulerTool";
26 | $.RulerTool = _RulerTool;
27 |
28 | import {Application as _Application, HostVersion as _HostVersion } from "./lib/Application";
29 | $.Application = _Application;
30 | $.HostVersion = _HostVersion;
31 | import {Artboard as _Artboard } from "./lib/Artboard";
32 | $.Artboard = _Artboard;
33 | import {Canvas as _Canvas} from "./lib/Canvas";
34 | $.Canvas = _Canvas;
35 | import {ColorSampler as _ColorSampler} from "./lib/ColorSampler";
36 | $.ColorSampler = _ColorSampler;
37 | import {Document as _Document} from "./lib/Document";
38 | $.Document = _Document;
39 | import {Guide as _Guide} from "./lib/Guide";
40 | $.Guide = _Guide;
41 | import {History as _History} from "./lib/History";
42 | $.History = _History;
43 | import {Layer as _Layer} from "./lib/Layer";
44 | $.Layer = _Layer;
45 | import {MetaData as _MetaData} from "./lib/MetaData";
46 | $.MetaData = _MetaData;
47 | import {Rect as _Rect} from "./lib/Rect";
48 | $.Rect = _Rect;
49 | import {Selection as _Selection} from "./lib/Selection";
50 | $.Selection = _Selection;
51 | import {Shape as _Shape, Point as _Point, Line as _Line, Circle as _Circle, Ellipse as _Ellipse, Triangle as _Triangle, Rectangle as _Rectangle, UnitType as _UnitType} from "./lib/Shape";
52 | $.Shape = _Shape;
53 | $.Point = _Point;
54 | $.Line = _Line;
55 | $.Circle = _Circle;
56 | $.Ellipse = _Ellipse;
57 | $.Triangle = _Triangle;
58 | $.Rectangle = _Rectangle;
59 | $.UnitType = _UnitType;
60 | import {Size as _Size} from "./lib/Size";
61 | $.Size = _Size;
62 | import {Stroke as _Stroke, StrokeLineType as _StrokeLineType} from "./lib/Stroke";
63 | $.Stroke = _Stroke;
64 | $.StrokeLineType = _StrokeLineType;
65 | import {Text as _Text, TextAlignment as _TextAlignment, TextStrikeThroughType as _TextStrikeThroughType, TextGriddingType as _TextGriddingType, TextOrientation as _TextOrientation, TextAntiAliasType as _TextAntiAliasType} from "./lib/Text";
66 | $.Text = _Text;
67 | $.TextAlignment = _TextAlignment;
68 | $.TextStrikeThroughType = _TextStrikeThroughType;
69 | $.TextGriddingType = _TextGriddingType;
70 | $.TextOrientation = _TextOrientation;
71 | $.TextAntiAliasType = _TextAntiAliasType;
72 | import {Utils as _Utils} from "./lib/Utils";
73 | $.Utils = _Utils;
74 |
75 |
76 |
--------------------------------------------------------------------------------
/src/lib/Guide.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by xiaoqiang
3 | * @date
4 | * @description this class represent Guide operation from photoshop
5 | */
6 |
7 | export enum GuideLineDirection {
8 | Horizontal = "horizontal",
9 | Vertical = "vertical"
10 | }
11 |
12 | export type GuideLine = {
13 | position: number;
14 | direction: GuideLineDirection;
15 | }
16 |
17 | export class Guide {
18 |
19 | /**
20 | * retrieve all the guide lines
21 | * @return GuideLine[]
22 | */
23 | public static all(): GuideLine[] {
24 | const count = this.count();
25 | const result: GuideLine[] = [];
26 | for (let i=1; i<=count; i++) {
27 | const ref = new ActionReference;
28 | ref.putIndex(app.stringIDToTypeID('guide'), i);
29 | ref.putEnumerated(app.stringIDToTypeID('document'), app.stringIDToTypeID('ordinal'), app.stringIDToTypeID('targetEnum'));
30 | const desc = app.executeActionGet(ref);
31 | const position = desc.getDouble(app.stringIDToTypeID("position"));
32 | const direction = app.typeIDToStringID(desc.getEnumerationValue(app.stringIDToTypeID("orientation")));
33 | result.push({position, direction: direction as GuideLineDirection});
34 | }
35 |
36 | return result;
37 | }
38 |
39 | /**
40 | * get the guide line count in current document
41 | * @return number
42 | */
43 | public static count(): number {
44 | const ref = new ActionReference();
45 | ref.putProperty(app.stringIDToTypeID('property'), app.stringIDToTypeID('numberOfGuides'));
46 | ref.putEnumerated(app.stringIDToTypeID('document'), app.stringIDToTypeID('ordinal'), app.stringIDToTypeID('targetEnum'));
47 | return app.executeActionGet(ref).getInteger(app.stringIDToTypeID('numberOfGuides'));
48 | }
49 |
50 | /**
51 | * add a guide line
52 | * @param line
53 | */
54 | public static add(line: GuideLine) {
55 | const desc1 = new ActionDescriptor();
56 | const desc2 = new ActionDescriptor();
57 | desc2.putUnitDouble( app.charIDToTypeID( "Pstn" ) , app.charIDToTypeID( "#Pxl" ) , line.position );
58 | desc2.putEnumerated( app.charIDToTypeID( "Ornt" ) , app.charIDToTypeID( "Ornt" ) , app.charIDToTypeID( line.direction ) );
59 | desc1.putObject( app.charIDToTypeID( "Nw " ) , app.charIDToTypeID( "Gd " ) , desc2 );
60 | app.executeAction( app.charIDToTypeID( "Mk " ) , desc1, DialogModes.NO );
61 | }
62 |
63 | /**
64 | * hide/show all guide lines
65 | */
66 | public static toggleVisibility() {
67 | const desc1 = new ActionDescriptor();
68 | const ref1 = new ActionReference();
69 | ref1.putEnumerated( app.charIDToTypeID( "Mn " ), app.charIDToTypeID( "MnIt" ), app.charIDToTypeID( "Tgld" ) );
70 | desc1.putReference( app.charIDToTypeID( "null" ), ref1 );
71 | app.executeAction( app.charIDToTypeID( "slct" ), desc1, DialogModes.NO );
72 | }
73 |
74 | /**
75 | * remove all guide lines
76 | */
77 | public static clear() {
78 | app.executeAction( app.stringIDToTypeID( "clearAllGuides" ), undefined, DialogModes.NO );
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/test/rename_slices.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * 遍历文档中的切片并重新命名
3 | * @xiaoqiang
4 | * @date 2022/11/19
5 | */
6 |
7 | function getAllSlices() {
8 | var documentReference = new ActionReference();
9 | documentReference.putEnumerated(app.charIDToTypeID("Dcmn"), app.charIDToTypeID("Ordn"), app.charIDToTypeID("Trgt"));
10 | var desc = app.executeActionGet(documentReference);
11 |
12 | var sliceItems = [];
13 | if (desc.hasKey(app.stringIDToTypeID("slices"))) {
14 | var slices = desc.getObjectValue(app.stringIDToTypeID("slices"));
15 | var slicesList = slices.getList(app.stringIDToTypeID("slices"));
16 | for (var i = 0; i < slicesList.count; i++) {
17 | var slice = slicesList.getObjectValue(i);
18 | var id = slice.getInteger(app.stringIDToTypeID("sliceID"));
19 | var bounds = slice.getObjectValue(app.stringIDToTypeID("bounds"));
20 | var left = bounds.getUnitDoubleValue(app.stringIDToTypeID("left"));
21 | var top = bounds.getUnitDoubleValue(app.stringIDToTypeID("top"));
22 | var right = bounds.getUnitDoubleValue(app.stringIDToTypeID("right"));
23 | var bottom = bounds.getUnitDoubleValue(app.stringIDToTypeID("bottom"));
24 | $.writeln(id + " " + left + " " + top + " " + right + " " + bottom);
25 | sliceItems.push({
26 | id: id,
27 | bounds: {left: left, top: top, right: right, bottom: bottom},
28 | center: {
29 | x: (left + right) / 2,
30 | y: (top + bottom) / 2
31 | }
32 | });
33 | }
34 | }
35 |
36 | return sliceItems;
37 | }
38 |
39 | function selectSlice(center) {
40 | var desc1 = new ActionDescriptor();
41 | var ref1 = new ActionReference();
42 | ref1.putClass( stringIDToTypeID( "slice" ) );
43 | desc1.putReference( stringIDToTypeID( "null" ), ref1 );
44 | var desc2 = new ActionDescriptor();
45 | desc2.putUnitDouble( stringIDToTypeID( "horizontal" ), stringIDToTypeID( "pixelsUnit" ), center.x );
46 | desc2.putUnitDouble( stringIDToTypeID( "vertical" ), stringIDToTypeID( "pixelsUnit" ), center.y );
47 | desc1.putObject( stringIDToTypeID( "at" ), stringIDToTypeID( "paint" ), desc2 );
48 | desc1.putBoolean( stringIDToTypeID( "addToSelection" ), false );
49 | executeAction( stringIDToTypeID( "select" ), desc1, DialogModes.NO );
50 | }
51 |
52 | function renameSlice(name) {
53 | var desc1 = new ActionDescriptor();
54 | var ref1 = new ActionReference();
55 | ref1.putEnumerated( stringIDToTypeID( "slice" ), stringIDToTypeID( "ordinal" ), stringIDToTypeID( "targetEnum" ) );
56 | desc1.putReference( stringIDToTypeID( "null" ), ref1 );
57 | var desc2 = new ActionDescriptor();
58 | desc2.putString( stringIDToTypeID( "name" ), name );
59 | desc2.putEnumerated( stringIDToTypeID( "sliceImageType" ), stringIDToTypeID( "sliceImageType" ), stringIDToTypeID( "image" ) );
60 | desc1.putObject( stringIDToTypeID( "to" ), stringIDToTypeID( "slice" ), desc2 );
61 | executeAction( stringIDToTypeID( "set" ), desc1, DialogModes.NO );
62 | }
63 |
64 | var sliceItems = getAllSlices();
65 | for (var i = 0; i < sliceItems.length; i++) {
66 | selectSlice(sliceItems[i].center);
67 | renameSlice("my_slice_name_" + i);
68 | }
69 |
70 |
--------------------------------------------------------------------------------
/test/layerSplit.jsx:
--------------------------------------------------------------------------------
1 |
2 | function makeSelectionFromLayer() {
3 | var desc1 = new ActionDescriptor();
4 | var ref1 = new ActionReference();
5 | ref1.putProperty( app.stringIDToTypeID( "channel" ), app.stringIDToTypeID( "selection" ) );
6 | desc1.putReference( app.stringIDToTypeID( "null" ), ref1 );
7 | var ref2 = new ActionReference();
8 | ref2.putEnumerated( app.stringIDToTypeID( "channel" ), app.stringIDToTypeID( "channel" ), app.stringIDToTypeID( "transparencyEnum" ) );
9 | desc1.putReference( app.stringIDToTypeID( "to" ), ref2 );
10 | app.executeAction( app.stringIDToTypeID( "set" ), desc1, DialogModes.NO );
11 | }
12 |
13 | function selectionToPath() {
14 | var desc1 = new ActionDescriptor();
15 | var ref1 = new ActionReference();
16 | ref1.putClass( app.stringIDToTypeID( "path" ) );
17 | desc1.putReference( app.stringIDToTypeID( "null" ), ref1 );
18 | var ref2 = new ActionReference();
19 | ref2.putProperty( app.stringIDToTypeID( "selectionClass" ), app.stringIDToTypeID( "selection" ) );
20 | desc1.putReference( app.stringIDToTypeID( "from" ), ref2 );
21 | desc1.putUnitDouble( app.stringIDToTypeID( "tolerance" ), app.stringIDToTypeID( "pixelsUnit" ), 2.000000 );
22 | app.executeAction( app.stringIDToTypeID( "make" ), desc1, DialogModes.NO );
23 | }
24 |
25 | function splitPaths() {
26 | var pathItems = app.activeDocument.pathItems;
27 | for (var i=0; i {
73 | colorStringArr.push(colorStop.color.toHex());
74 | });
75 | return `${colorStringArr.join('-')} (${this.angle}') ${this.opacity}%`;
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/src/lib/Canvas.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by xiaoqiang
3 | * @date 2022/07/26
4 | * @description this is the class that you can draw shapes with
5 | */
6 | import {Shape} from "./Shape";
7 | import {Stroke} from "./Stroke";
8 | import {SolidColor} from "./base/SolidColor";
9 | import {GradientColor} from "./base/GradientColor";
10 |
11 | export class Canvas {
12 | private shapeList: Shape[];
13 | private fill: SolidColor | GradientColor;
14 | private stroke: Stroke;
15 | private opacity: number;
16 |
17 | constructor() {
18 | this.shapeList = [];
19 | this.fill = SolidColor.blackColor();
20 | this.stroke = null;
21 | this.opacity = 100;
22 | }
23 |
24 | public setFillColor(color: SolidColor | GradientColor): void {
25 | this.fill = color;
26 | }
27 |
28 | public setStroke(stroke: Stroke): void {
29 | this.stroke = stroke;
30 | }
31 |
32 | public setOpacity(opacity: number): void {
33 | this.opacity = opacity;
34 | }
35 |
36 | public addShape(shape: Shape): void {
37 | this.shapeList.push(shape);
38 | }
39 |
40 | public addShapes(shapes: Shape[]): void {
41 | for (let i=0; i23 || v <16) {
59 | return HostVersion.Unknown;
60 | }
61 | return v as HostVersion;
62 | }
63 |
64 | /**
65 | * open a file from path
66 | * @param path
67 | */
68 | public open(path: string): void {
69 | const desc437 = new ActionDescriptor();
70 | // @ts-ignore
71 | desc437.putPath( app.charIDToTypeID( "null" ), new File(path) );
72 | app.executeAction( app.charIDToTypeID( "Opn " ), desc437, DialogModes.NO );
73 | }
74 |
75 | /**
76 | * save current units to private member
77 | */
78 | public saveUnits(): void {
79 | this.rulerUnits = this.getRulerUnits();
80 | this.typeUnits = this.getTypeUnits();
81 | }
82 |
83 | /**
84 | * restore units from member value
85 | */
86 | public restoreUnits(): void {
87 | if (this.rulerUnits && this.typeUnits) {
88 | this.setRulerUnits(this.rulerUnits);
89 | this.setTypeUnits(this.typeUnits);
90 | }
91 | }
92 |
93 | /**
94 | * set app units
95 | * @param rulerUnits
96 | * @param typeUnits
97 | */
98 | public setUnits(rulerUnits: Units, typeUnits: TypeUnits): void {
99 | this.setRulerUnits(rulerUnits);
100 | this.setTypeUnits(typeUnits);
101 | }
102 |
103 | /**
104 | * get app ruler units
105 | * @return rulerUnits
106 | */
107 | public getRulerUnits(): Units {
108 | return app.preferences.rulerUnits;
109 | }
110 |
111 | /**
112 | * set app ruler units
113 | * @param rulerUnits
114 | */
115 | public setRulerUnits(rulerUnits: Units): void {
116 | app.preferences.rulerUnits = rulerUnits;
117 | }
118 |
119 | /**
120 | * get app type units
121 | * @return typeUnits
122 | */
123 | public getTypeUnits(): TypeUnits {
124 | return app.preferences.typeUnits;
125 | }
126 |
127 | /**
128 | * set app type units
129 | * @param typeUnits
130 | */
131 | public setTypeUnits(typeUnits: TypeUnits): void {
132 | app.preferences.typeUnits = typeUnits;
133 | }
134 |
135 |
136 | }
137 |
--------------------------------------------------------------------------------
/src/lib/MetaData.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | export class MetaData {
4 | namespace: string;
5 | prefix: string;
6 |
7 | constructor(namespace: string, prefix: string) {
8 | this.namespace = namespace;
9 | this.prefix = prefix;
10 | }
11 |
12 | /**
13 | * is XMP is available
14 | * return bool
15 | */
16 | available(): boolean {
17 | return typeof ExternalObject === 'function';
18 | }
19 |
20 | /**
21 | * 设置meta data
22 | * @param key
23 | * @param value
24 | */
25 | set(key: string, value: string): void {
26 | let xmpObject = null;
27 | try {
28 | // @ts-ignore
29 | if (ExternalObject && ExternalObject.AdobeXMPScript == undefined) {
30 | // @ts-ignore
31 | ExternalObject.AdobeXMPScript = new ExternalObject("lib:AdobeXMPScript");
32 | }
33 | const xmp = app.activeDocument.xmpMetadata.rawData;
34 | xmpObject = new XMPMeta(xmp);
35 | } catch (e) {
36 | }
37 | if (xmpObject != null) {
38 | XMPMeta.registerNamespace(this.namespace, this.prefix);
39 | xmpObject.deleteProperty(this.namespace, key);
40 | xmpObject.setProperty(this.namespace, key, value);
41 | app.activeDocument.xmpMetadata.rawData = xmpObject.serialize();
42 | }
43 |
44 | }
45 |
46 | /**
47 | * 获取存在metadata中的某个key值
48 | * @param key
49 | */
50 | get(key: string): string | null {
51 | let xmp, xmpObject = null;
52 | try {
53 | // @ts-ignore
54 | if (ExternalObject && ExternalObject.AdobeXMPScript == undefined) {
55 | // @ts-ignore
56 | ExternalObject.AdobeXMPScript = new ExternalObject("lib:AdobeXMPScript");
57 | }
58 | xmp = app.activeDocument.xmpMetadata.rawData;
59 | xmpObject = new XMPMeta(xmp);
60 | } catch (e) {
61 | }
62 | if (xmpObject === null) { return null }
63 | let value = null;
64 | try {
65 | XMPMeta.registerNamespace(this.namespace, this.prefix);
66 | const property = xmpObject.getProperty(this.namespace, key);
67 | value = property.value;
68 | } catch (e) {
69 | }
70 | return value;
71 | }
72 |
73 | /**
74 | * 删除metadata中的某个key
75 | * @param key
76 | */
77 | remove(key: string): void {
78 | let xmpObject = null;
79 | try {
80 | // @ts-ignore
81 | if (ExternalObject.AdobeXMPScript == undefined) {
82 | // @ts-ignore
83 | ExternalObject.AdobeXMPScript = new ExternalObject("lib:AdobeXMPScript");
84 | }
85 | const xmp = app.activeDocument.xmpMetadata.rawData;
86 | xmpObject = new XMPMeta(xmp);
87 | } catch (e) {
88 | }
89 | if (xmpObject === null) { return; }
90 | try {
91 | XMPMeta.registerNamespace(this.namespace, this.prefix);
92 | xmpObject.deleteProperty(this.namespace, key);
93 | app.activeDocument.xmpMetadata.rawData = xmpObject.serialize();
94 | } catch (e) {
95 | }
96 | }
97 |
98 | /**
99 | * 清除所有medata数据
100 | */
101 | clear() {
102 |
103 | try {
104 | // @ts-ignore
105 | if (ExternalObject.AdobeXMPScript == undefined) {
106 | // @ts-ignore
107 | ExternalObject.AdobeXMPScript = new ExternalObject("lib:AdobeXMPScript");
108 | }
109 | const xmp = new XMPMeta( app.activeDocument.xmpMetadata.rawData);
110 | // @ts-ignore
111 | xmp.deleteProperty(XMPConst.NS_PHOTOSHOP, "DocumentAncestors");
112 | app.activeDocument.xmpMetadata.rawData = xmp.serialize();
113 | } catch (e) {
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/test/to_cmyk_black.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * 这个脚本负责批量将png文件转为CMYK格式,并设置为单黑模式(即C=0, M=0, Y=0, K=100)。
3 | * 此脚本仅提供给作业帮智能图书二维码项目使用,其他项目请勿使用。
4 | *
5 | * 使用方法:
6 | * 1. 安装photoshop
7 | * 2. 打开photoshop, 菜单栏->文件->脚本->浏览,选择本文件
8 | * 3. 在弹出的对话框中选择需要处理的png文件,可以批量选择,只支持png格式图片
9 | * 4. 等待处理完成,处理完成后会弹出提示框
10 | * 5. 处理完成的图片保存在原文件夹下的output文件夹中,以jpg格式结尾
11 | *
12 | * 注意事项:
13 | * 1. 请勿在处理过程中打开其他文件,否则会导致处理失败
14 | * 2. 请勿在处理过程中关闭photoshop,否则会导致处理失败
15 | * 3. 请勿在处理过程中进行其他操作,否则会导致处理失败
16 | *
17 | * 如果遇到使用问题,请联系 zhengqianglong@zuoyebang.com
18 | *
19 | * @author xiaoqiang
20 | * @date 2023-05-29
21 | */
22 |
23 | // 1. open the png image
24 | function selectFiles() {
25 | return File.openDialog("选择需要处理的png文件", "*.png", true);
26 | }
27 |
28 | // 2. convert the color mode to cmyk
29 | function toCMYK() {
30 | var desc1 = new ActionDescriptor();
31 | desc1.putClass( stringIDToTypeID( "to" ), stringIDToTypeID( "CMYKColorMode" ) );
32 | executeAction( stringIDToTypeID( "convertMode" ), desc1, DialogModes.NO );
33 | }
34 | // 3. remove all cmy colors and set k = 100
35 | function threshold() {
36 | var desc1 = new ActionDescriptor();
37 | var ref1 = new ActionReference();
38 | ref1.putClass( stringIDToTypeID( "adjustmentLayer" ) );
39 | desc1.putReference( stringIDToTypeID( "null" ), ref1 );
40 | var desc2 = new ActionDescriptor();
41 | var desc3 = new ActionDescriptor();
42 | desc3.putInteger( stringIDToTypeID( "level" ), 128 );
43 | desc2.putObject( stringIDToTypeID( "type" ), stringIDToTypeID( "thresholdClassEvent" ), desc3 );
44 | desc1.putObject( stringIDToTypeID( "using" ), stringIDToTypeID( "adjustmentLayer" ), desc2 );
45 | executeAction( stringIDToTypeID( "make" ), desc1, DialogModes.NO );
46 | }
47 |
48 | // 4. save the image as jpg
49 | function save(filename) {
50 | var desc1 = new ActionDescriptor();
51 | var desc2 = new ActionDescriptor();
52 | desc2.putInteger( stringIDToTypeID( "extendedQuality" ), 12 );
53 | desc2.putEnumerated( stringIDToTypeID( "matteColor" ), stringIDToTypeID( "matteColor" ), stringIDToTypeID( "none" ) );
54 | desc1.putObject( stringIDToTypeID( "as" ), stringIDToTypeID( "JPEG" ), desc2 );
55 | desc1.putPath( stringIDToTypeID( "in" ), new File( filename ) );
56 | desc1.putInteger( stringIDToTypeID( "documentID" ), app.activeDocument.id);
57 | desc1.putBoolean( stringIDToTypeID( "copy" ), true );
58 | desc1.putBoolean( stringIDToTypeID( "lowerCase" ), true );
59 | desc1.putEnumerated( stringIDToTypeID( "saveStage" ), stringIDToTypeID( "saveStageType" ), stringIDToTypeID( "saveSucceeded" ) );
60 | executeAction( stringIDToTypeID( "save" ), desc1, DialogModes.NO );
61 | }
62 |
63 | // 5. close the image
64 | function close() {
65 | var desc1 = new ActionDescriptor();
66 | desc1.putEnumerated( stringIDToTypeID( "saving" ), stringIDToTypeID( "yesNo" ), stringIDToTypeID( "no" ) );
67 | desc1.putInteger( stringIDToTypeID( "documentID" ), app.activeDocument.id);
68 | desc1.putBoolean( stringIDToTypeID( "forceNotify" ), true );
69 | executeAction( stringIDToTypeID( "close" ), desc1, DialogModes.NO );
70 | }
71 |
72 | // 6. make output folder
73 | function makeOutputFolder(folder) {
74 | var outputFolder = new Folder(folder + "/output");
75 | if (!outputFolder.exists) {
76 | outputFolder.create();
77 | }
78 | return outputFolder;
79 | }
80 |
81 | // 7. main function
82 | function main() {
83 | var files = selectFiles();
84 | for (var i=0; i 0) {
58 | return (this.height <= 0);
59 | }
60 | return true;
61 | }
62 |
63 | size(): Size {
64 | return new Size(this.width, this.height);
65 | }
66 |
67 | toString(): string {
68 | return `${this.x},${this.y} ${this.width}x${this.height}`;
69 | }
70 |
71 | toJSON(): RectItem {
72 | return {x: this.x, y: this.y, width: this.width, height: this.height};
73 | }
74 |
75 | intersectsWith(rect: Rect): boolean {
76 | return ((((rect.x < (this.x + this.width)) && (this.x < (rect.x + rect.width))) && (rect.y < (this.y + this.height))) && (this.y < (rect.y + rect.height)));
77 | }
78 |
79 | contains(rect: Rect): boolean {
80 | return ((((this.x <= rect.x) && ((rect.x + rect.width) <= (this.x + this.width))) && (this.y <= rect.y)) && ((rect.y + rect.height) <= (this.y + this.height)));
81 | }
82 |
83 | public expand(size: number, point: ExpandBasePoint) {
84 | if (point === ExpandBasePoint.LeftTop) {
85 | this.width += size;
86 | this.height += size;
87 | } else if (point === ExpandBasePoint.RightTop) {
88 | this.x -= size;
89 | this.width += size;
90 | this.height += size;
91 | } else if (point === ExpandBasePoint.RightBottom) {
92 | this.x -= size;
93 | this.y -= size;
94 | this.width += size;
95 | this.height += size;
96 | } else if (point === ExpandBasePoint.LeftBottom) {
97 | this.y -= size;
98 | this.width += size;
99 | this.height += size;
100 | } else {
101 | this.x -= size/2;
102 | this.y -= size/2;
103 | this.width += size;
104 | this.height += size;
105 | }
106 | }
107 |
108 |
109 | toDescriptor(unitType: UnitType = UnitType.Pixel): ActionDescriptor {
110 | const result = new ActionDescriptor();
111 | result.putUnitDouble(app.charIDToTypeID("Left"), app.stringIDToTypeID(unitType), this.x);
112 | result.putUnitDouble(app.charIDToTypeID("Top "), app.stringIDToTypeID(unitType), this.y);
113 | result.putUnitDouble(app.charIDToTypeID("Rght"), app.stringIDToTypeID(unitType), this.right());
114 | result.putUnitDouble(app.charIDToTypeID("Btom"), app.stringIDToTypeID(unitType), this.bottom());
115 | return result;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/test/parse_lagecy_content_data.jsx:
--------------------------------------------------------------------------------
1 | #include "./JSON.jsx";
2 |
3 | function getHueSatAdjustment(){
4 | var ref = new ActionReference();
5 | ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
6 | var desc = executeActionGet(ref).getList(stringIDToTypeID('adjustment')).getObjectValue(0);
7 | var rawData = desc.getData(stringIDToTypeID('legacyContentData'));
8 | var hueSatAdjustment = {};
9 | hueSatAdjustment.isColorized = Boolean(readInteger(rawData, 2));
10 | hueSatAdjustment.colorized = {};
11 | hueSatAdjustment.colorized.hue = readAngle(rawData, 4);
12 | hueSatAdjustment.colorized.sat = readInteger(rawData, 6);
13 | hueSatAdjustment.colorized.brightness = readInteger(rawData, 8);
14 | hueSatAdjustment.master = {};
15 | hueSatAdjustment.master.hue = readInteger(rawData, 10);
16 | hueSatAdjustment.master.sat = readInteger(rawData, 12);
17 | hueSatAdjustment.master.brightness = readInteger(rawData, 14);
18 | hueSatAdjustment.reds = {};
19 | hueSatAdjustment.reds.beginRamp = readInteger(rawData, 16);
20 | hueSatAdjustment.reds.beginSustain = readInteger(rawData, 18);
21 | hueSatAdjustment.reds.endSustain = readInteger(rawData, 20);
22 | hueSatAdjustment.reds.endRamp = readInteger(rawData, 22);
23 | hueSatAdjustment.reds.hue = readInteger(rawData, 24);
24 | hueSatAdjustment.reds.sat = readInteger(rawData, 26);
25 | hueSatAdjustment.reds.brightness = readInteger(rawData, 28);
26 | hueSatAdjustment.yellows = {};
27 | hueSatAdjustment.yellows.beginRamp = readInteger(rawData, 30);
28 | hueSatAdjustment.yellows.beginSustain = readInteger(rawData, 32);
29 | hueSatAdjustment.yellows.endSustain = readInteger(rawData, 34);
30 | hueSatAdjustment.yellows.endRamp = readInteger(rawData, 36);
31 | hueSatAdjustment.yellows.hue = readInteger(rawData, 38);
32 | hueSatAdjustment.yellows.sat = readInteger(rawData, 40);
33 | hueSatAdjustment.yellows.brightness = readInteger(rawData, 42);
34 | hueSatAdjustment.greens = {};
35 | hueSatAdjustment.greens.beginRamp = readInteger(rawData, 44);
36 | hueSatAdjustment.greens.beginSustain = readInteger(rawData, 46);
37 | hueSatAdjustment.greens.endSustain = readInteger(rawData, 48);
38 | hueSatAdjustment.greens.endRamp = readInteger(rawData, 50);
39 | hueSatAdjustment.greens.hue = readInteger(rawData, 52);
40 | hueSatAdjustment.greens.sat = readInteger(rawData, 54);
41 | hueSatAdjustment.greens.brightness = readInteger(rawData, 56);
42 | hueSatAdjustment.cyans = {};
43 | hueSatAdjustment.cyans.beginRamp = readInteger(rawData, 58);
44 | hueSatAdjustment.cyans.beginSustain = readInteger(rawData, 60);
45 | hueSatAdjustment.cyans.endSustain = readInteger(rawData, 62);
46 | hueSatAdjustment.cyans.endRamp = readInteger(rawData, 64);
47 | hueSatAdjustment.cyans.hue = readInteger(rawData, 66);
48 | hueSatAdjustment.cyans.sat = readInteger(rawData, 68);
49 | hueSatAdjustment.cyans.brightness = readInteger(rawData, 70);
50 | hueSatAdjustment.blues = {};
51 | hueSatAdjustment.blues.beginRamp = readInteger(rawData, 72);
52 | hueSatAdjustment.blues.beginSustain = readInteger(rawData, 74);
53 | hueSatAdjustment.blues.endSustain = readInteger(rawData, 76);
54 | hueSatAdjustment.blues.endRamp = readInteger(rawData, 78);
55 | hueSatAdjustment.blues.hue = readInteger(rawData, 80);
56 | hueSatAdjustment.blues.sat = readInteger(rawData, 82);
57 | hueSatAdjustment.blues.brightness = readInteger(rawData, 84);
58 | hueSatAdjustment.magentas = {};
59 | hueSatAdjustment.magentas.beginRamp = readInteger(rawData, 86);
60 | hueSatAdjustment.magentas.beginSustain = readInteger(rawData, 88);
61 | hueSatAdjustment.magentas.endSustain = readInteger(rawData, 90);
62 | hueSatAdjustment.magentas.endRamp = readInteger(rawData, 92);
63 | hueSatAdjustment.magentas.hue = readInteger(rawData, 94);
64 | hueSatAdjustment.magentas.sat = readInteger(rawData, 96);
65 | hueSatAdjustment.magentas.brightness = readInteger(rawData, 98);
66 | return hueSatAdjustment;
67 | };
68 | function readInteger(str, pointer) {
69 | var byte1 = str.charCodeAt(pointer);
70 | var byte2 = str.charCodeAt(pointer + 1);
71 | var singedsShort = (byte1 <<8) + byte2;
72 | if (singedsShort > 0x7FFF) {
73 | singedsShort = 0xFFFF0000 ^ singedsShort;
74 | }
75 | return singedsShort;
76 | }
77 | function readAngle(str, pointer) {
78 | var b1 = str.charCodeAt(pointer);
79 | var b2 = str.charCodeAt(pointer+1);
80 | if(b1==0){
81 | var ss = b2;
82 | }else{
83 | var ss = b2+104;//???
84 | }
85 | return ss;
86 | };
87 |
88 | var result = getHueSatAdjustment();
89 | JSON.stringify(result, null, 4);
90 |
--------------------------------------------------------------------------------
/src/lib/History.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * History for photoshop history control
3 | */
4 | export class History {
5 |
6 | private state: number = -1;
7 |
8 | /**
9 | * history move to previous state
10 | * @return History
11 | */
12 | public previous(): History {
13 | const desc1 = new ActionDescriptor();
14 | const ref1 = new ActionReference();
15 | ref1.putEnumerated(app.stringIDToTypeID("historyState"), app.stringIDToTypeID("ordinal"), app.stringIDToTypeID("previous"));
16 | desc1.putReference(app.stringIDToTypeID("null"), ref1);
17 | app.executeAction(app.stringIDToTypeID("select"), desc1, DialogModes.NO);
18 | return this;
19 | }
20 |
21 | /**
22 | * switch to last item of history list
23 | * @return History
24 | */
25 | public last(): History {
26 | const desc1 = new ActionDescriptor();
27 | const ref1 = new ActionReference();
28 | ref1.putEnumerated(app.stringIDToTypeID("historyState"), app.stringIDToTypeID("ordinal"), app.stringIDToTypeID("last"));
29 | desc1.putReference(app.stringIDToTypeID("null"), ref1);
30 | app.executeAction(app.stringIDToTypeID("select"), desc1, DialogModes.NO);
31 | return this;
32 | }
33 |
34 | /**
35 | * save current history state
36 | * @return History
37 | */
38 | public saveState(): History {
39 | this.state = app.activeDocument.historyStates.length ;
40 | $.writeln(`save state[${this.state}] name[${this.current().name}]`);
41 | return this;
42 | }
43 |
44 | /**
45 | * restore the state saved before
46 | * @return History
47 | */
48 | public restoreState(): History {
49 | if (this.state !== -1) {
50 | this.go(this.state);
51 | }
52 | return this;
53 | }
54 |
55 | /**
56 | * clear saved state
57 | * @return History
58 | */
59 | public clearState(): History {
60 | this.state = -1;
61 | return this;
62 | }
63 |
64 | public list(): string {
65 | const arr = [];
66 | const states = app.activeDocument.historyStates;
67 | for (let i=0; i N from top to bottom)
76 | * @param index
77 | * @return History
78 | */
79 | public go(index: number): History {
80 | const desc1 = new ActionDescriptor();
81 | const ref1 = new ActionReference();
82 | ref1.putIndex(app.stringIDToTypeID( "historyState" ), index);
83 | desc1.putReference( app.stringIDToTypeID( "null" ), ref1 );
84 | app.executeAction( app.stringIDToTypeID( "select" ), desc1, DialogModes.NO );
85 | return this;
86 | }
87 |
88 | private debug(): string {
89 | var states = app.activeDocument.historyStates;
90 | var result = [];
91 | for (var i=0; i photoshop-script-api@1.0.4
233 |
234 | 1. 添加Canvas, Guide, Text模块
235 | 2. 添加图层效果 ColorOverlay, DropShadow, GradientFill and Stroke
236 | 3. 添加了两个工具 (Move, Ruler)
237 | 4. 修改 Color 为 SolidColor
238 | 5. 添加了 GradientColor 支持
239 |
240 | ## 关于作者
241 | 业余独立开发者,前百度资深高级工程师,熟悉软件工程,熟悉web、移动端、多媒体开发技术,热爱设计。业余开发过多款设计相关的产品,产品拥有几十万的设计师用户。下面是对应产品的网站:
242 |
243 | [Design Mirror - 最好用的设计稿实时预览工具](http://www.psmirror.cn)
244 |
245 | [Cutterman - 自动化切图工具](http://www.cutterman.cn)
246 |
--------------------------------------------------------------------------------
/test/http.jsx:
--------------------------------------------------------------------------------
1 |
2 |
3 | var $http = (function() {
4 |
5 | // JSON library for javascript, I took from somewhere but i don't remember where i took it.
6 | "object"!=typeof JSON&&(JSON={}),function(){"use strict";function f(t){return 10>t?"0"+t:t}function this_value(){return this.valueOf()}function quote(t){return escapable.lastIndex=0,escapable.test(t)?'"'+t.replace(escapable,function(t){var e=meta[t];return"string"==typeof e?e:"\\u"+("0000"+t.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+t+'"'}function str(t,e){var n,r,o,u,f,i=gap,a=e[t];switch(a&&"object"==typeof a&&"function"==typeof a.toJSON&&(a=a.toJSON(t)),"function"==typeof rep&&(a=rep.call(e,t,a)),typeof a){case"string":return quote(a);case"number":return isFinite(a)?String(a):"null";case"boolean":case"null":return String(a);case"object":if(!a)return"null";if(gap+=indent,f=[],"[object Array]"===Object.prototype.toString.apply(a)){for(u=a.length,n=0;u>n;n+=1)f[n]=str(n,a)||"null";return o=0===f.length?"[]":gap?"[\n"+gap+f.join(",\n"+gap)+"\n"+i+"]":"["+f.join(",")+"]",gap=i,o}if(rep&&"object"==typeof rep)for(u=rep.length,n=0;u>n;n+=1)"string"==typeof rep[n]&&(r=rep[n],o=str(r,a),o&&f.push(quote(r)+(gap?": ":":")+o));else for(r in a)Object.prototype.hasOwnProperty.call(a,r)&&(o=str(r,a),o&&f.push(quote(r)+(gap?": ":":")+o));return o=0===f.length?"{}":gap?"{\n"+gap+f.join(",\n"+gap)+"\n"+i+"}":"{"+f.join(",")+"}",gap=i,o}}"function"!=typeof Date.prototype.toJSON&&(Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},Boolean.prototype.toJSON=this_value,Number.prototype.toJSON=this_value,String.prototype.toJSON=this_value);var cx,escapable,gap,indent,meta,rep;"function"!=typeof JSON.stringify&&(escapable=/[\\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,meta={"\b":"\\b"," ":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},JSON.stringify=function(t,e,n){var r;if(gap="",indent="","number"==typeof n)for(r=0;n>r;r+=1)indent+=" ";else"string"==typeof n&&(indent=n);if(rep=e,e&&"function"!=typeof e&&("object"!=typeof e||"number"!=typeof e.length))throw new Error("JSON.stringify");return str("",{"":t})}),"function"!=typeof JSON.parse&&(cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,JSON.parse=function(text,reviver){function walk(t,e){var n,r,o=t[e];if(o&&"object"==typeof o)for(n in o)Object.prototype.hasOwnProperty.call(o,n)&&(r=walk(o,n),void 0!==r?o[n]=r:delete o[n]);return reviver.call(t,e,o)}var j;if(text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(t){return"\\u"+("0000"+t.charCodeAt(0).toString(16)).slice(-4)})),/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return j=eval("("+text+")"),"function"==typeof reviver?walk({"":j},""):j;throw new SyntaxError("JSON.parse")})}();
7 |
8 | return function(config) {
9 | var url = (/^(.*):\/\/([A-Za-z0-9\-\.]+):?([0-9]+)?(.*)$/).exec(config.url);
10 | if(url == null) {
11 | throw "unable to parse URL";
12 | }
13 |
14 | url = {
15 | scheme: url[1],
16 | host: url[2],
17 | port: url[3] || (url[1] == "https" ? 443 : 80),
18 | path: url[4]
19 | };
20 |
21 | if(url.scheme != "http") {
22 | throw "non-http url's not supported yet!";
23 | }
24 |
25 | var s = new Socket();
26 |
27 | if(!s.open(url.host + ':' + url.port, 'binary')) {
28 | throw 'can\'t connect to ' + url.host + ':' + url.port;
29 | }
30 |
31 | var method = config.method || 'GET';
32 |
33 | var request = method + ' ' + url.path + " HTTP/1.0\r\nConnection: close\r\nHost: " + url.host;
34 | var header;
35 |
36 | if(config.payload) {
37 | if(typeof config.payload === 'object') {
38 | config.payload = JSON.stringify(config.payload);
39 | (config.headers = config.headers || {})["Content-Type"] = "application/json";
40 | }
41 |
42 | (config.headers = config.headers || {})["Content-Length"] = config.payload.length;
43 | }
44 |
45 | for(header in (config.headers || {})) {
46 | request += "\r\n" + header + ': ' + config.headers[header] ;
47 | }
48 |
49 | s.write(request+"\r\n\r\n");
50 |
51 | if(config.payload) {
52 | s.write(config.payload);
53 | }
54 |
55 | var data, response, payload, http = {};
56 |
57 | data = s.read();
58 | while(!s.eof) {
59 | data += s.read();
60 | }
61 |
62 | var response = data.indexOf("\r\n\r\n");
63 | if(response == -1) {
64 | throw "No HTTP payload found in the response!";
65 | }
66 |
67 | payload = data.substr(response + 4);
68 | response = data.substr(0, response);
69 |
70 | var http = /^HTTP\/([\d\.?]+) (\d+) (.*)\r/.exec(response), header;
71 | if(http == null) {
72 | throw "No HTTP payload found in the response!";
73 | }
74 |
75 | http = {
76 | ver: Number(http[1]),
77 | status: Number(http[2]),
78 | statusMessage: http[3],
79 | headers: {}
80 | };
81 |
82 | var httpregex = /(.*): (.*)\r/g;
83 |
84 | while(header = httpregex.exec(response)) {
85 | http.headers[header[1]] = header[2];
86 | }
87 |
88 | var contenttype = (http.headers["Content-Type"] || http.headers["content-type"] || '').split(";");
89 | var charset = config.charset || (contenttype[1] ? /charset=(.*)/.exec(contenttype[1])[1] : null);
90 | if(charset) payload = payload.toString(charset);
91 | contenttype = contenttype[0];
92 |
93 | if(config.forcejson || contenttype == "application/json") {
94 | http.payload = JSON.parse(payload);
95 | } else {
96 | http.payload = payload;
97 | }
98 |
99 | return http;
100 | };
101 | })();
102 |
103 |
--------------------------------------------------------------------------------
/src/lib/Selection.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by xiaoqiang
3 | * @date 2021/07/30
4 | * @description selection class for photoshop
5 | */
6 | import { Rect } from "./Rect";
7 |
8 | export class Selection {
9 | public bounds: Rect;
10 |
11 | constructor(rect: Rect) {
12 | this.bounds = rect;
13 | }
14 |
15 | /**
16 | * get current selection, return null if there is none
17 | * @return Selection | null
18 | */
19 | public static get(): Selection | null {
20 | try {
21 | const selection = app.activeDocument.selection.bounds;
22 | const rect = new Rect(selection[0].value, selection[1].value, selection[2].value - selection[0].value, selection[3].value - selection[1].value);
23 | return new Selection(rect);
24 | } catch (ex) {
25 | return null;
26 | }
27 | }
28 |
29 | /**
30 | * load selection from saved channel
31 | * @param selectionName
32 | * @param documentName
33 | * @return Selection
34 | */
35 | public static load(selectionName: string, documentName: string = null): Selection {
36 | const desc1 = new ActionDescriptor();
37 | const ref1 = new ActionReference();
38 | ref1.putProperty(app.stringIDToTypeID("channel"), app.stringIDToTypeID("selection"));
39 | desc1.putReference(app.stringIDToTypeID("null"), ref1);
40 | const ref2 = new ActionReference();
41 | ref2.putName(app.stringIDToTypeID("channel"), selectionName);
42 | if (documentName) {
43 | ref2.putName(app.stringIDToTypeID("document"), documentName);
44 | }
45 | desc1.putReference(app.stringIDToTypeID("to"), ref2);
46 | app.executeAction(app.stringIDToTypeID("set"), desc1, DialogModes.NO);
47 |
48 | return this.get();
49 | }
50 |
51 | /**
52 | * make selection from current selected layer
53 | * @return Selection
54 | */
55 | public static fromLayer(): Selection {
56 | const desc1 = new ActionDescriptor();
57 | const ref1 = new ActionReference();
58 | ref1.putProperty(app.stringIDToTypeID("channel"), app.stringIDToTypeID("selection"));
59 | desc1.putReference(app.stringIDToTypeID("null"), ref1);
60 | const ref2 = new ActionReference();
61 | ref2.putEnumerated(app.stringIDToTypeID("channel"), app.stringIDToTypeID("channel"), app.stringIDToTypeID("transparencyEnum"));
62 | desc1.putReference(app.stringIDToTypeID("to"), ref2);
63 | app.executeAction(app.stringIDToTypeID("set"), desc1, DialogModes.NO);
64 | return this.get();
65 | }
66 |
67 | /**
68 | * delete saved selection
69 | * @param name
70 | */
71 | public static deleteSavedSelection(name: string): void {
72 | const desc1 = new ActionDescriptor();
73 | const ref1 = new ActionReference();
74 | ref1.putName(stringIDToTypeID( "channel" ), name)
75 | desc1.putReference( stringIDToTypeID( "null" ), ref1 );
76 | app.executeAction( stringIDToTypeID( "delete" ), desc1, DialogModes.NO );
77 | }
78 |
79 | /**
80 | * create a selection area in PS with current rect
81 | * @return Selection
82 | */
83 | public create(): Selection {
84 | const selectionMode = app.charIDToTypeID("setd");
85 | const selectionDescriptor = new ActionDescriptor();
86 | const selectionReference = new ActionReference();
87 | selectionReference.putProperty(app.charIDToTypeID("Chnl"), app.charIDToTypeID("fsel"));
88 | selectionDescriptor.putReference(app.charIDToTypeID("null"), selectionReference);
89 | selectionDescriptor.putObject(app.charIDToTypeID("T "), app.charIDToTypeID("Rctn"), this.bounds.toDescriptor());
90 | app.executeAction(selectionMode, selectionDescriptor, DialogModes.NO);
91 | return this;
92 | }
93 |
94 | /**
95 | * deselect current selection
96 | * @return void
97 | */
98 | public deselect(): void {
99 | const selectionDescriptor = new ActionDescriptor();
100 | const selectionReference = new ActionReference();
101 | selectionReference.putProperty(app.charIDToTypeID("Chnl"), app.charIDToTypeID("fsel"));
102 | selectionDescriptor.putReference(app.charIDToTypeID("null"), selectionReference);
103 | selectionDescriptor.putEnumerated(app.charIDToTypeID("T "), app.charIDToTypeID("Ordn"), app.charIDToTypeID("None"));
104 | app.executeAction(app.charIDToTypeID("setd"), selectionDescriptor, DialogModes.NO);
105 | }
106 |
107 | /**
108 | * invert selection area
109 | * @return void
110 | */
111 | public invert(): void {
112 | app.executeAction(app.charIDToTypeID("Invs"), undefined, DialogModes.NO);
113 | }
114 |
115 | /**
116 | * convert current selection to path
117 | * @return void
118 | */
119 | public toPath(tolerance: number = 2): void {
120 | const desc1 = new ActionDescriptor();
121 | const ref1 = new ActionReference();
122 | ref1.putClass(app.stringIDToTypeID("path"));
123 | desc1.putReference(app.stringIDToTypeID("null"), ref1);
124 | const ref2 = new ActionReference();
125 | ref2.putProperty(app.stringIDToTypeID("selectionClass"), app.stringIDToTypeID("selection"));
126 | desc1.putReference(app.stringIDToTypeID("from"), ref2);
127 | desc1.putUnitDouble(app.stringIDToTypeID("tolerance"), app.stringIDToTypeID("pixelsUnit"), tolerance);
128 | app.executeAction(app.stringIDToTypeID("make"), desc1, DialogModes.NO);
129 | }
130 |
131 | /**
132 | * save current selection
133 | * @param name
134 | */
135 | public save(name: string): void {
136 | const desc1 = new ActionDescriptor();
137 | const ref1 = new ActionReference();
138 | ref1.putProperty(app.stringIDToTypeID("channel"), app.stringIDToTypeID("selection"));
139 | desc1.putReference(app.stringIDToTypeID("null"), ref1);
140 | desc1.putString(app.stringIDToTypeID("name"), name);
141 | app.executeAction(app.stringIDToTypeID("duplicate"), desc1, DialogModes.NO);
142 | }
143 |
144 | }
145 |
--------------------------------------------------------------------------------
/src/lib/base/GradientColor.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by xiaoqiang
3 | * @date 2022/08/01
4 | * @description this class represent a gradient color of photoshop
5 | */
6 |
7 | import {SolidColor} from "./SolidColor";
8 |
9 | export enum GradientType {
10 | Linear = "linear",
11 | }
12 |
13 | type ColorStop = {
14 | color: SolidColor;
15 | position: number;
16 | point: number;
17 | }
18 |
19 | type OpacityStop = {
20 | opacity: number;
21 | location: number;
22 | point: number;
23 | }
24 |
25 | export class GradientColor {
26 | // TODO current only support linear type
27 | private type: GradientType = GradientType.Linear;
28 | private angle: number = 90;
29 | private colorStopList: ColorStop[] = [];
30 | private opacityStopList: OpacityStop[] = [];
31 |
32 | /**
33 | * add a color stop to the gradient line
34 | * @param color
35 | * @param position
36 | * @param point
37 | */
38 | public addColorStop(color: SolidColor, position: number = 0, point: number = 50) {
39 | this.colorStopList.push({
40 | color,
41 | point,
42 | position
43 | });
44 | }
45 |
46 | /**
47 | * add a opacity stop to gradient line
48 | * @param opacity
49 | * @param location
50 | * @param point
51 | */
52 | public addOpacityStop(opacity: number = 100, location: number = 0, point: number = 50) {
53 | this.opacityStopList.push({
54 | opacity,
55 | location,
56 | point
57 | });
58 | }
59 |
60 | public static fromDescriptor(desc: ActionDescriptor): GradientColor {
61 | const gradient = new GradientColor();
62 | gradient.angle = desc.getDouble(app.stringIDToTypeID("angle"));
63 | gradient.type = app.typeIDToStringID(desc.getEnumerationValue(app.stringIDToTypeID("type"))) as GradientType;
64 |
65 | const gradientDesc = desc.getObjectValue(app.stringIDToTypeID("gradient"));
66 |
67 | const colorStopList = gradientDesc.getList(app.stringIDToTypeID("colors"));
68 | for (let i = 0; i < colorStopList.count; i++) {
69 | const stop = colorStopList.getObjectValue(i);
70 | const color = SolidColor.fromDescriptor(stop.getObjectValue(app.stringIDToTypeID("color")));
71 | const position = stop.getInteger(app.stringIDToTypeID("location"));
72 | const point = stop.getInteger(app.stringIDToTypeID("midpoint"));
73 | gradient.addColorStop(color, position, point);
74 | }
75 |
76 | const opacityStopList = gradientDesc.getList(app.stringIDToTypeID("transparency"));
77 | for (let i = 0; i < opacityStopList.count; i++) {
78 | const stop = opacityStopList.getObjectValue(i);
79 | const opacity = stop.getUnitDoubleValue(app.stringIDToTypeID("opacity"));
80 | const location = stop.getInteger(app.stringIDToTypeID("location"));
81 | const point = stop.getInteger(app.stringIDToTypeID("midpoint"));
82 | gradient.addOpacityStop(opacity, location, point);
83 | }
84 |
85 | return gradient;
86 | }
87 |
88 | /**
89 | * covert current object to ActionDescriptor format
90 | */
91 | public toDescriptor(): ActionDescriptor {
92 | const desc1 = new ActionDescriptor();
93 | desc1.putBoolean( app.stringIDToTypeID( "dither" ), true );
94 | desc1.putEnumerated( app.stringIDToTypeID( "gradientsInterpolationMethod" ), app.stringIDToTypeID( "gradientInterpolationMethodType" ), app.stringIDToTypeID( "perceptual" ) );
95 | desc1.putUnitDouble( app.stringIDToTypeID( "angle" ), app.stringIDToTypeID( "angleUnit" ), this.angle );
96 | desc1.putEnumerated( app.stringIDToTypeID( "type" ), app.stringIDToTypeID( "gradientType" ), app.stringIDToTypeID( this.type ) );
97 |
98 | const desc2 = new ActionDescriptor();
99 | desc2.putEnumerated( app.stringIDToTypeID( "gradientForm" ), app.stringIDToTypeID( "gradientForm" ), app.stringIDToTypeID( "customStops" ) );
100 | desc2.putDouble( app.stringIDToTypeID( "interfaceIconFrameDimmed" ), 4096.000000 );
101 |
102 | // color stops
103 | const list1 = new ActionList();
104 | for (let i=0; i {
136 | colorStringArr.push(colorStop.color.toHex());
137 | });
138 | return `${colorStringArr.join('-')} (${this.angle}')`;
139 | }
140 |
141 | }
142 |
--------------------------------------------------------------------------------
/src/lib/base/SolidColor.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by xiaoqiang
3 | * @date 2021/08/02
4 | * @description SolidColor class
5 | * represent a solid color objective, easily retrieve color information from photoshop
6 | */
7 |
8 | export enum ColorSpace {
9 | RGB = "RGB",
10 | HEX = "HEX",
11 | CMYK = "CMYK",
12 | HSB = "HSB"
13 | }
14 |
15 | export type ColorRGB = {red: number, green: number, blue: number};
16 | export type ColorCMYK = {cyan: number, magenta: number, yellowColor: number, black: number};
17 | export type ColorHSB = {hue: number, saturation: number, brightness: number};
18 |
19 |
20 | export class SolidColor {
21 | private readonly r: number;
22 | private readonly g: number;
23 | private readonly b: number;
24 | private readonly a: number;
25 |
26 | /**
27 | * construct a SolidColor object with rgba color
28 | * @param red
29 | * @param green
30 | * @param blue
31 | * @param alpha
32 | */
33 | constructor(red: number, green: number, blue: number, alpha: number = 255.0) {
34 | this.r = red;
35 | this.g = green;
36 | this.b = blue;
37 | this.a = alpha;
38 | }
39 |
40 | /**
41 | * quick create a black color object
42 | * @return SolidColor
43 | */
44 | public static blackColor(): SolidColor {
45 | return new SolidColor(0, 0, 0);
46 | }
47 |
48 | /**
49 | * quick create a white color object
50 | * @return SolidColor
51 | */
52 | public static whiteColor(): SolidColor {
53 | return new SolidColor(255.0, 255.0, 255.0);
54 | }
55 |
56 | /**
57 | * create a SolidColor object from a Descriptor
58 | * @param desc
59 | * @return SolidColor
60 | */
61 | public static fromDescriptor(desc: ActionDescriptor): SolidColor {
62 | const red = desc.getDouble(app.stringIDToTypeID("red"));
63 | const green = desc.getDouble(app.stringIDToTypeID("grain"));
64 | const blue = desc.getDouble(app.stringIDToTypeID("blue"));
65 | return new SolidColor(red, green, blue);
66 | }
67 |
68 | /**
69 | * create a SolidColor object from a hex color string
70 | * hex color string should be #aabbcc like
71 | * return null if hex color string invalid
72 | * @param hexColor
73 | * @return SolidColor | null
74 | */
75 | public static fromHexString(hexColor: string): SolidColor | null {
76 | const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hexColor);
77 | return result ? new SolidColor(
78 | parseInt(result[1], 16),
79 | parseInt(result[2], 16),
80 | parseInt(result[3], 16)
81 | ) : null;
82 | }
83 |
84 | /**
85 | * create a SolidColor object with red/green/blue value
86 | * @param red
87 | * @param green
88 | * @param blue
89 | * @return SolidColor
90 | */
91 | public static fromRGB(red: number, green: number, blue: number): SolidColor {
92 | return new SolidColor(red, green, blue);
93 | }
94 |
95 |
96 | /**
97 | * convert color to hex string format
98 | * @param alpha where to show alpha value
99 | * @return string
100 | */
101 | public toHex(alpha: boolean = false): string {
102 | const arr = [
103 | SolidColor.componentToHex(this.r),
104 | SolidColor.componentToHex(this.g),
105 | SolidColor.componentToHex(this.b)
106 | ];
107 | if (alpha) {
108 | arr.push(SolidColor.componentToHex(this.a));
109 | }
110 | return ("#" + arr.join("")).toUpperCase();
111 | }
112 |
113 | /**
114 | * convert color to rgb format
115 | * @return ColorRGB
116 | */
117 | public toRGB(): ColorRGB {
118 | return {red: Math.round(this.r), green: Math.round(this.g), blue: Math.round(this.b)}
119 | }
120 |
121 | /**
122 | * convert color to cmyk format
123 | * @return ColorCMYK
124 | */
125 | public toCMYK(): ColorCMYK {
126 | const desc = this.changeColorSpace(ColorSpace.CMYK);
127 | const color = desc.getObjectValue(app.stringIDToTypeID("color"));
128 | return {
129 | cyan: Math.round(color.getDouble(app.stringIDToTypeID("cyan"))),
130 | magenta: Math.round(color.getDouble(app.stringIDToTypeID("magenta"))),
131 | yellowColor: Math.round(color.getDouble(app.stringIDToTypeID("yellowColor"))),
132 | black: Math.round(color.getDouble(app.stringIDToTypeID("black")))
133 | };
134 | }
135 |
136 | /**
137 | * convert color to hsb format
138 | * @return ColorHSB
139 | */
140 | public toHSB(): ColorHSB {
141 | const desc = this.changeColorSpace(ColorSpace.HSB);
142 | const color = desc.getObjectValue(app.stringIDToTypeID("color"));
143 | return {
144 | hue: Math.round(color.getDouble(app.stringIDToTypeID("hue"))),
145 | saturation: Math.round(color.getDouble(app.stringIDToTypeID("saturation"))),
146 | brightness: Math.round(color.getDouble(app.stringIDToTypeID("brightness")))
147 | };
148 | }
149 |
150 | /**
151 | * convert SolidColor object to ActionDescriptor
152 | * @return ActionDescriptor
153 | */
154 | public toDescriptor(): ActionDescriptor {
155 | const result = new ActionDescriptor();
156 | result.putDouble(app.charIDToTypeID("Rd "), this.r);
157 | result.putDouble(app.charIDToTypeID("Grn "), this.g);
158 | result.putDouble(app.charIDToTypeID("Bl "), this.b);
159 | return result;
160 | }
161 |
162 | private static componentToHex(c:number):string {
163 | c = Math.round(c);
164 | const hex = c.toString(16);
165 | return hex.length == 1 ? "0" + hex : hex;
166 | }
167 |
168 |
169 | private changeColorSpace(colorSpace: ColorSpace): ActionDescriptor {
170 | const ref1 = new ActionReference();
171 | ref1.putClass(app.stringIDToTypeID('application'));
172 |
173 | const desc = new ActionDescriptor();
174 | desc.putReference(app.stringIDToTypeID('null'), ref1);
175 | desc.putObject( app.charIDToTypeID( "Clr " ), app.charIDToTypeID( "RGBC" ), this.toDescriptor() );
176 | desc.putEnumerated(app.stringIDToTypeID("colorSpace"), app.stringIDToTypeID("colorSpace"), app.stringIDToTypeID(colorSpace));
177 |
178 | return app.executeAction(app.stringIDToTypeID('convertColorToSpace'), desc, DialogModes.NO);
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/src/lib/Stroke.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by xiaoqiang
3 | * @date 2021/08/02
4 | * @description stroke object for photoshop
5 | */
6 |
7 | import {SolidColor} from "./base/SolidColor";
8 |
9 | export enum StrokeLineType {
10 | Solid = 0,
11 | Dash = 1,
12 | Dot = 2
13 | }
14 |
15 | export enum StrokeStyleLineCapType {
16 | ButtCap = "strokeStyleButtCap",
17 | RoundCap = 'strokeStyleRoundCap',
18 | SquareCap = 'strokeStyleSquareCap'
19 | }
20 |
21 | export enum StrokeStyleLineJoinType {
22 | MiterJoin = 'strokeStyleMiterJoin',
23 | RoundJoin = 'strokeStyleRoundJoin',
24 | BevelJoin = 'strokeStyleBevelJoin'
25 | }
26 |
27 | export enum StrokeStyleLineAlignment {
28 | AlignInside = 'strokeStyleAlignInside',
29 | AlignCenter = 'strokeStyleAlignCenter',
30 | AlignOutside = 'strokeStyleAlignOutside',
31 | }
32 |
33 | export class Stroke {
34 | width: number;
35 | lineType: StrokeLineType;
36 | resolution: number;
37 | opacity: number;
38 | strokeEnabled: boolean;
39 | fillEnabled: boolean;
40 | color: SolidColor;
41 | lineCapType: StrokeStyleLineCapType;
42 | lineJoinType: StrokeStyleLineJoinType;
43 | lineAlignment: StrokeStyleLineAlignment;
44 |
45 | constructor(width: number, lineType: StrokeLineType = StrokeLineType.Solid, color: SolidColor = SolidColor.blackColor()) {
46 | this.width = width;
47 | this.lineType = lineType;
48 | this.color = color;
49 | this.resolution = 72;
50 | this.fillEnabled = true;
51 | this.strokeEnabled = true;
52 | this.opacity = 100;
53 | this.lineCapType = StrokeStyleLineCapType.ButtCap;
54 | this.lineJoinType = StrokeStyleLineJoinType.MiterJoin;
55 | this.lineAlignment = StrokeStyleLineAlignment.AlignInside;
56 | }
57 |
58 | public static fromDescriptor(desc: ActionDescriptor): Stroke {
59 | const lineWidth = desc.getInteger(app.stringIDToTypeID("strokeStyleLineWidth"));
60 | const strokeStyleLineCapType = app.typeIDToStringID(desc.getEnumerationType(app.stringIDToTypeID("strokeStyleLineCapType")));
61 | const strokeStyleLineJoinType = app.typeIDToStringID(desc.getEnumerationType(app.stringIDToTypeID("strokeStyleLineJoinType")));
62 | const strokeStyleLineAlignment = app.typeIDToStringID( desc.getEnumerationType(app.stringIDToTypeID("strokeStyleLineAlignment")));
63 | const strokeStyleContent = desc.getObjectValue(app.stringIDToTypeID("strokeStyleContent"));
64 | const colorDesc =strokeStyleContent.getObjectValue(app.stringIDToTypeID("color"));
65 |
66 | const strokeStyleLineDashSet = desc.getList(app.stringIDToTypeID("strokeStyleLineDashSet"));
67 | let lineType = StrokeLineType.Solid;
68 | if (strokeStyleLineDashSet.count > 0) {
69 | lineType = (strokeStyleLineCapType === StrokeStyleLineCapType.RoundCap)? StrokeLineType.Dot : StrokeLineType.Dash;
70 | }
71 | const ins = new Stroke(lineWidth, lineType, SolidColor.fromDescriptor(colorDesc));
72 | ins.strokeEnabled = desc.getBoolean(app.stringIDToTypeID("strokeEnabled"));
73 | ins.fillEnabled = desc.getBoolean(app.stringIDToTypeID("fillEnabled"));
74 | ins.opacity = desc.getDouble(app.stringIDToTypeID("strokeStyleOpacity"));
75 | ins.resolution = desc.getDouble(app.stringIDToTypeID("strokeStyleResolution"));
76 | ins.lineCapType = strokeStyleLineCapType as StrokeStyleLineCapType;
77 | ins.lineAlignment = strokeStyleLineAlignment as StrokeStyleLineAlignment;
78 | ins.lineJoinType = strokeStyleLineJoinType as StrokeStyleLineJoinType;
79 | return ins;
80 | }
81 |
82 | public toString(): string {
83 | return `${this.width} ${this.opacity}% ${this.color.toHex()}`;
84 | }
85 |
86 | toDescriptor(): ActionDescriptor {
87 | const desc6 = new ActionDescriptor();
88 | desc6.putInteger(app.stringIDToTypeID("strokeStyleVersion"), 2);
89 | desc6.putBoolean(app.stringIDToTypeID("strokeEnabled"), this.strokeEnabled);
90 | desc6.putBoolean(app.stringIDToTypeID("fillEnabled"), this.fillEnabled);
91 | desc6.putUnitDouble(app.stringIDToTypeID("strokeStyleLineWidth"), app.stringIDToTypeID("pixelsUnit"), this.width);
92 | desc6.putUnitDouble(app.stringIDToTypeID("strokeStyleLineDashOffset"), app.stringIDToTypeID("pointsUnit"), 0);
93 | desc6.putDouble(app.stringIDToTypeID("strokeStyleMiterLimit"), 100);
94 | if (this.lineType === StrokeLineType.Dot) {
95 | desc6.putEnumerated(app.stringIDToTypeID("strokeStyleLineCapType"), app.stringIDToTypeID("strokeStyleLineCapType"), app.stringIDToTypeID(StrokeStyleLineCapType.RoundCap));
96 | } else {
97 | desc6.putEnumerated(app.stringIDToTypeID("strokeStyleLineCapType"), app.stringIDToTypeID("strokeStyleLineCapType"), app.stringIDToTypeID(this.lineCapType));
98 | }
99 | desc6.putEnumerated(app.stringIDToTypeID("strokeStyleLineJoinType"), app.stringIDToTypeID("strokeStyleLineJoinType"), app.stringIDToTypeID(this.lineJoinType));
100 | desc6.putEnumerated(app.stringIDToTypeID("strokeStyleLineAlignment"), app.stringIDToTypeID("strokeStyleLineAlignment"), app.stringIDToTypeID(this.lineAlignment));
101 | desc6.putBoolean(app.stringIDToTypeID("strokeStyleScaleLock"), false);
102 | desc6.putBoolean(app.stringIDToTypeID("strokeStyleStrokeAdjust"), false);
103 | const list1 = new ActionList();
104 | if (this.lineType === StrokeLineType.Dash) {
105 | list1.putUnitDouble(app.stringIDToTypeID("noneUnit"), 4);
106 | list1.putUnitDouble(app.stringIDToTypeID("noneUnit"), 4);
107 | } else if (this.lineType === StrokeLineType.Dot) {
108 | list1.putUnitDouble(app.stringIDToTypeID("noneUnit"), 0);
109 | list1.putUnitDouble(app.stringIDToTypeID("noneUnit"), 2);
110 | }
111 | desc6.putList(app.stringIDToTypeID("strokeStyleLineDashSet"), list1);
112 | desc6.putEnumerated(app.stringIDToTypeID("strokeStyleBlendMode"), app.stringIDToTypeID("blendMode"), app.stringIDToTypeID("normal"));
113 | desc6.putUnitDouble(app.stringIDToTypeID("strokeStyleOpacity"), app.stringIDToTypeID("percentUnit"), this.opacity);
114 | const desc7 = new ActionDescriptor();
115 | desc7.putObject(app.stringIDToTypeID("color"), app.stringIDToTypeID("RGBColor"), this.color.toDescriptor());
116 | desc6.putObject(app.stringIDToTypeID("strokeStyleContent"), app.stringIDToTypeID("solidColorLayer"), desc7);
117 | desc6.putDouble(app.stringIDToTypeID("strokeStyleResolution"), this.resolution);
118 |
119 | return desc6;
120 | }
121 |
122 | }
123 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # photoshop-script-api
2 |
3 | [中文](./README_zh.md)
4 |
5 | ## About
6 |
7 | When develop CEP Panels in Photoshop, we use Javascript DOM API to communicate with the host, It's easily understood and used, but insufficient interfaces offered to accomplish complex works. There is another way call "Action Descriptor" which is more powerful and comprehensive, while extremely confusing to learn and use.
8 |
9 | This project wraps the AM codes and build a friendly use js api to offer powerful and sufficient interfaces for Photoshop plugin development.
10 |
11 | ## install
12 |
13 | This project is written in [TypeScript](http://www.typescriptlang.org). If you use ts in your codes, just clone this project and import the source code as you want.
14 |
15 | ### Typescript
16 |
17 | import the index.ts file in your project
18 |
19 | ```typescript
20 | import { Document } from "./photoshop-script-api/src/index";
21 |
22 | const doc = new Document();
23 | $.writeln(doc.name());
24 | ```
25 |
26 | ### Javascript
27 |
28 | In plain javascript way, you can install the npm module which has been compiled and published.
29 |
30 | install the module from npm
31 |
32 | ```shell
33 | npm install photoshop-script-api
34 | ```
35 |
36 | include the **main.js** file in your code
37 |
38 | ```javascript
39 | #include "./node_modules/photoshop-script-api/dist/main.js"
40 |
41 | var a = new $.Application();
42 | alert(a.version());
43 | ```
44 |
45 |
46 | ## usages
47 |
48 | ### Classes
49 |
50 | This module includes some Classes listed below
51 |
52 | 1. Application
53 | 2. Document
54 | 3. Layer
55 | 4. Artboard
56 | 5. History
57 | 6. Rect
58 | 7. Selection
59 | 8. Shape
60 | 9. Size
61 | 10. Color
62 | 11. Stroke
63 | 12. MetaData
64 | 13. Text
65 |
66 | more will be added in future
67 |
68 | in order to avoid namespace conflict, all class names are prefixed with "$", so you can use them like this
69 |
70 | ```javascript
71 | var app = new $.Application();
72 | ```
73 |
74 | each Class offers some useful api to make things happen, you can check out the code to get more detail.
75 |
76 | below snippets are some simple examples to demonstrate how to use the api
77 |
78 | ### Application
79 |
80 | Application indicates the Photoshop application itself, it offers some useful api to get the application information
81 |
82 | ```javascript
83 | var theApp = new $.Application();
84 |
85 | // open a file
86 | theApp.open("/path/to/a/file");
87 |
88 | // get the host version
89 | theApp.getHostVersion(); // CC2022
90 |
91 | // get the host installation path
92 | theApp.getApplicationPath(); // /Applications/Adobe Photoshop 2022/Adobe Photoshop 2022.app
93 |
94 | ```
95 |
96 | ### Documents
97 |
98 | Document indicates the opened document in Photoshop, it offers some useful api to get the document information
99 |
100 | ```javascript
101 | // get current active document
102 | var doc = $.Document.activeDocument();
103 | if (doc == null) {
104 | alert("no doucment opened");
105 | return;
106 | }
107 | alert(doc.name()); // alert document name
108 | $.writeln(doc.length()); // document size in bytes
109 | doc.trim(); // trim document transparent area
110 | ```
111 |
112 | ### Layer
113 |
114 | Layer indicates the layer in Photoshop, it helps you to manipulate the layer easily.
115 |
116 | ```javascript
117 |
118 | // get selected layers
119 | var layers = $.Layer.getSelectedLayers();
120 | for (var i=0; i photoshop-script-api@1.0.4
234 |
235 | 1. add Canvas class
236 | 2. add Guide class
237 | 3. add Text class
238 | 4. add fx modules with ColorOverlay, DropShadow, GradientFill and Stroke
239 | 5. add two tools (Move, Ruler)
240 | 6. change Color to SolidColor
241 | 7. add GradientColor support
242 |
243 | ## About Me
244 |
245 | Ten years Software Engineer from China, former Senior Engineer of Baidu Inc. Head in web, mobile, media development, in love with design. Have several welcome products for designers in China.
246 |
247 | [Design Mirror - The best preview tool](http://www.psmirror.cn)
248 |
249 | [Cutterman - Assets expoter](http://www.cutterman.cn)
250 |
251 |
--------------------------------------------------------------------------------
/src/lib/Shape.ts:
--------------------------------------------------------------------------------
1 | import { Rect } from "./Rect";
2 | import {Stroke} from "./Stroke";
3 | import {SolidColor} from "./base/SolidColor";
4 | import {Layer} from "./Layer";
5 | import {GradientColor} from "./base/GradientColor";
6 |
7 | export enum UnitType {
8 | Pixel = "pixelsUnit",
9 | Percent = "percentUnit",
10 | Point = "pointsUnit",
11 | Distance = "distanceUnit"
12 | }
13 |
14 | export enum FillType {
15 | None,
16 | SolidColor,
17 | GradientColor,
18 | }
19 |
20 | export class Shape {
21 | descriptorType: number;
22 |
23 | constructor(descriptorType: number) {
24 | this.descriptorType = descriptorType;
25 | }
26 |
27 | toString(): string {
28 | throw new Error("Method not implemented.");
29 | }
30 |
31 | toDescriptor(): ActionDescriptor {
32 | throw new Error("Method not implemented.");
33 | }
34 |
35 | draw(fillColor: SolidColor | GradientColor, stroke: Stroke = null, opacity: number = 100, toLayer: number = 0): Layer {
36 | const desc448 = new ActionDescriptor();
37 | const ref321 = new ActionReference();
38 | ref321.putClass(app.stringIDToTypeID("contentLayer"));
39 | desc448.putReference(app.charIDToTypeID("null"), ref321);
40 |
41 | const layerDescriptor = new ActionDescriptor();
42 |
43 | if (fillColor instanceof SolidColor) {
44 | const solidColorLayerDescriptor = new ActionDescriptor();
45 | solidColorLayerDescriptor.putObject(app.charIDToTypeID("Clr "), app.charIDToTypeID("RGBC"), fillColor.toDescriptor());
46 | layerDescriptor.putObject(app.charIDToTypeID("Type"), app.stringIDToTypeID("solidColorLayer"), solidColorLayerDescriptor);
47 | } else {
48 | layerDescriptor.putObject(app.charIDToTypeID("Type"), app.stringIDToTypeID("gradientLayer"), fillColor.toDescriptor());
49 | }
50 | layerDescriptor.putUnitDouble(app.charIDToTypeID("Opct"), app.charIDToTypeID("#Prc"), opacity);
51 |
52 | // stroke
53 | if (stroke != null) {
54 | layerDescriptor.putObject(app.stringIDToTypeID("strokeStyle"), app.stringIDToTypeID("strokeStyle"), stroke.toDescriptor());
55 | }
56 |
57 | layerDescriptor.putObject(app.charIDToTypeID("Shp "), this.descriptorType, this.toDescriptor());
58 |
59 | desc448.putObject(app.charIDToTypeID("Usng"), app.stringIDToTypeID("contentLayer"), layerDescriptor);
60 | if (toLayer != 0) {
61 | desc448.putInteger(app.stringIDToTypeID( "layerID" ), toLayer);
62 | }
63 | app.executeAction(app.charIDToTypeID("Mk "), desc448, DialogModes.NO);
64 |
65 | return new Layer(app.activeDocument.activeLayer.id);
66 | }
67 |
68 | }
69 |
70 | export class Point extends Shape {
71 | x: number;
72 | y: number;
73 |
74 | constructor(x: number, y: number) {
75 | super(app.stringIDToTypeID("point"));
76 | this.x = x;
77 | this.y = y;
78 | }
79 |
80 | static fromDescriptor(desc: ActionDescriptor): Point {
81 | const x = desc.getDouble(app.stringIDToTypeID("horizontal"));
82 | const y = desc.getDouble(app.stringIDToTypeID("vertical"));
83 | return new Point(x, y);
84 | }
85 |
86 | toString(): string {
87 | return `${this.x},${this.y}`;
88 | }
89 |
90 | toDescriptor(unitType: UnitType = UnitType.Pixel): ActionDescriptor {
91 | const result = new ActionDescriptor();
92 | result.putUnitDouble(app.charIDToTypeID("Hrzn"), app.stringIDToTypeID(unitType), this.x);
93 | result.putUnitDouble(app.charIDToTypeID("Vrtc"), app.stringIDToTypeID(unitType), this.y);
94 | return result;
95 | }
96 |
97 | }
98 |
99 | export class Line extends Shape {
100 | private start: Point;
101 | private end: Point;
102 | private readonly width: number;
103 | private enableArrowStart: boolean;
104 | private enableArrowEnd: boolean;
105 | private arrowWidth: number = 10;
106 | private arrowLength: number = 10;
107 | private arrowConcavity: number = 0;
108 |
109 | constructor(start: Point, end: Point, width: number = 2) {
110 | super(app.charIDToTypeID("Ln "));
111 | this.start = start;
112 | this.end = end;
113 | this.width = width;
114 | this.enableArrowStart = false;
115 | this.enableArrowEnd = false;
116 | }
117 |
118 | public enableArrow(start: boolean, end: boolean, width: number = 10, length: number = 10, concavity: number = 0) {
119 | this.enableArrowStart = start;
120 | this.enableArrowEnd = end;
121 | if (start || end) {
122 | this.arrowConcavity = concavity;
123 | this.arrowWidth = width;
124 | this.arrowLength = length;
125 | }
126 | }
127 |
128 | public toString(): string {
129 | return `[${this.start.toString()}] [${this.end.toString()}]`;
130 | }
131 |
132 | public toDescriptor(): ActionDescriptor {
133 | const desc5 = new ActionDescriptor();
134 | const desc6 = new ActionDescriptor();
135 | desc6.putUnitDouble(app.stringIDToTypeID("horizontal"), app.stringIDToTypeID("distanceUnit"), this.start.x);
136 | desc6.putUnitDouble(app.stringIDToTypeID("vertical"), app.stringIDToTypeID("distanceUnit"), this.start.y);
137 | desc5.putObject(app.stringIDToTypeID("saturation"), app.stringIDToTypeID("paint"), desc6);
138 | const desc7 = new ActionDescriptor();
139 | desc7.putUnitDouble(app.stringIDToTypeID("horizontal"), app.stringIDToTypeID("distanceUnit"), this.end.x);
140 | desc7.putUnitDouble(app.stringIDToTypeID("vertical"), app.stringIDToTypeID("distanceUnit"), this.end.y);
141 | desc5.putObject(app.stringIDToTypeID("end"), app.stringIDToTypeID("paint"), desc7);
142 | desc5.putUnitDouble(app.stringIDToTypeID("width"), app.stringIDToTypeID("pixelsUnit"), this.width);
143 |
144 | if (this.enableArrowStart) {
145 | const desc1462 = new ActionDescriptor();
146 | desc1462.putDouble(app.stringIDToTypeID( "width" ) , this.arrowWidth );
147 | desc1462.putDouble( app.stringIDToTypeID( "length" ), this.arrowLength );
148 | desc1462.putUnitDouble( app.stringIDToTypeID( "concavity" ), app.stringIDToTypeID( "percentUnit" ), this.arrowConcavity );
149 | desc1462.putBoolean( app.stringIDToTypeID( "on" ), true );
150 | desc5.putObject( app.stringIDToTypeID( "startArrowhead" ), app.stringIDToTypeID( "arrowhead" ), desc1462 );
151 | }
152 | if (this.enableArrowEnd) {
153 | const desc1462 = new ActionDescriptor();
154 | desc1462.putDouble(app.stringIDToTypeID( "width" ) , this.arrowWidth );
155 | desc1462.putDouble( app.stringIDToTypeID( "length" ), this.arrowLength );
156 | desc1462.putUnitDouble( app.stringIDToTypeID( "concavity" ), app.stringIDToTypeID( "percentUnit" ), this.arrowConcavity );
157 | desc1462.putBoolean( app.stringIDToTypeID( "on" ), true );
158 | desc5.putObject( app.stringIDToTypeID( "endArrowhead" ), app.stringIDToTypeID( "arrowhead" ), desc1462 );
159 | }
160 |
161 | return desc5;
162 | }
163 |
164 | }
165 |
166 | export class Rectangle extends Shape {
167 | radius: number;
168 | rect: Rect;
169 |
170 | constructor (rect: Rect, radius: number = 0) {
171 | super(app.charIDToTypeID( "Rctn"));
172 | this.rect = rect;
173 | this.radius = radius;
174 | }
175 |
176 | toString(): string {
177 | return `${this.rect.toString()} radius[${this.radius}]`;
178 | }
179 |
180 | toDescriptor(): ActionDescriptor {
181 | const rectangleDescriptor = this.rect.toDescriptor();
182 | if(this.radius > 0) {
183 | rectangleDescriptor.putUnitDouble( app.charIDToTypeID( "Rds " ), app.charIDToTypeID( "#Pxl" ), this.radius);
184 | }
185 | return rectangleDescriptor
186 | }
187 |
188 | }
189 |
190 |
191 | export class Ellipse extends Shape {
192 | rect: Rect;
193 |
194 | constructor(rect: Rect) {
195 | super(app.stringIDToTypeID( "ellipse" ));
196 | this.rect = rect;
197 | }
198 |
199 | toString(): string {
200 | return '';
201 | }
202 |
203 | toDescriptor(): ActionDescriptor {
204 | return this.rect.toDescriptor();
205 | }
206 |
207 | }
208 |
209 | export class Circle extends Shape {
210 | private rect: Rect;
211 | constructor(center: Point, radius: number) {
212 | super(app.stringIDToTypeID( "ellipse" ));
213 | this.rect = new Rect(center.x - radius, center.y - radius, 2*radius, 2*radius);
214 | }
215 |
216 | toString(): string {
217 | return super.toString();
218 | }
219 |
220 | toDescriptor(): ActionDescriptor {
221 | return this.rect.toDescriptor();
222 | }
223 | }
224 |
225 |
226 | export class Triangle extends Shape {
227 | private rect: Rect;
228 | private readonly radius: number;
229 | constructor(bounds: Rect, radius: number = 0) {
230 | super(app.stringIDToTypeID( "triangle" ));
231 | this.rect = bounds;
232 | this.radius = radius;
233 | }
234 |
235 | toString(): string {
236 | return this.rect.toString();
237 | }
238 |
239 | toDescriptor(): ActionDescriptor {
240 | const desc262 = new ActionDescriptor();
241 | desc262.putInteger( app.stringIDToTypeID( "keyOriginType" ), 7 );
242 | desc262.putInteger( app.stringIDToTypeID( "keyOriginPolySides" ), 3 );
243 | desc262.putDouble( app.stringIDToTypeID( "keyOriginPolyPixelHSF" ), 1.000000 );
244 | desc262.putInteger(app.stringIDToTypeID( "sides" ) , 3 );
245 | desc262.putUnitDouble(app.stringIDToTypeID( "polygonCornerRadius" ) ,app.stringIDToTypeID( "distanceUnit" ) , this.radius );
246 | desc262.putObject( app.stringIDToTypeID( "keyOriginShapeBBox" ), app.stringIDToTypeID( "classFloatRect" ), this.rect.toDescriptor());
247 |
248 | const desc279 = new ActionDescriptor();
249 | desc279.putDouble(app.stringIDToTypeID( "xx" ) , 1 );
250 | desc279.putDouble(app.stringIDToTypeID( "xy" ) , 0 );
251 | desc279.putDouble(app.stringIDToTypeID( "yx" ) , 0 );
252 | desc279.putDouble(app.stringIDToTypeID( "yy" ) , 1 );
253 | desc279.putDouble(app.stringIDToTypeID( "tx" ) , 0 );
254 | desc279.putDouble(app.stringIDToTypeID( "ty" ) , 0 );
255 | desc262.putObject( app.stringIDToTypeID( "transform" ), app.stringIDToTypeID( "transform" ), desc279 );
256 |
257 | const corners = [
258 | {key: "rectangleCornerA", point: new Point(this.rect.x, this.rect.y)},
259 | {key: "rectangleCornerB", point: new Point(this.rect.right(), this.rect.y)},
260 | {key: "rectangleCornerC", point: new Point(this.rect.right(), this.rect.bottom())},
261 | {key: "rectangleCornerD", point: new Point(this.rect.x, this.rect.bottom())},
262 | ]
263 | const targetKeys = ["keyOriginBoxCorners", "keyOriginPolyPreviousTightBoxCorners", "keyOriginPolyTrueRectCorners"];
264 | for (let j=0; j 0) {
59 | const textStyleRange = textStyleRanges.getObjectValue(0);
60 | this.styleDesc = textStyleRange.getObjectValue(app.stringIDToTypeID("textStyle"));
61 | }
62 | } else {
63 | this.textDesc = new ActionDescriptor();
64 | this.textDesc.putString( app.stringIDToTypeID( "textKey" ), content );
65 |
66 | // default settings
67 | this.setAntiAlias(TextAntiAliasType.Smooth);
68 | this.setTextGridding(TextGriddingType.None);
69 | this.setOrientation(TextOrientation.Horizontal);
70 |
71 | // text style
72 | this.styleDesc = new ActionDescriptor();
73 | this.styleDesc.putBoolean( app.stringIDToTypeID( "styleSheetHasParent" ), true );
74 | this.setColor(SolidColor.blackColor());
75 | }
76 | this.paragraphDesc = new ActionDescriptor();
77 | this.paragraphDesc.putBoolean( app.stringIDToTypeID( "styleSheetHasParent" ), true );
78 | }
79 |
80 | static fromDescriptor(desc: ActionDescriptor): Text {
81 | const content = desc.getString(app.stringIDToTypeID("textKey"));
82 | return new Text(content, desc);
83 | }
84 |
85 | public textClickPoint(): Point {
86 | const textClickPoint = this.textDesc.getObjectValue(app.stringIDToTypeID("textClickPoint"));
87 | const x = textClickPoint.getDouble(app.stringIDToTypeID("horizontal"));
88 | const y = textClickPoint.getDouble(app.stringIDToTypeID("vertical"));
89 | return new Point(x, y);
90 | }
91 |
92 | public orientation(): TextOrientation {
93 | const orientation = this.textDesc.getEnumerationValue(app.stringIDToTypeID("orientation"));
94 | return app.typeIDToStringID(orientation) as TextOrientation;
95 | }
96 |
97 | public bounds(): Rect {
98 | return Rect.fromDescriptor(this.textDesc.getObjectValue(app.stringIDToTypeID("bounds")));
99 | }
100 |
101 | public boundingBox(): Rect {
102 | return Rect.fromDescriptor(this.textDesc.getObjectValue(app.stringIDToTypeID("boundingBox")));
103 | }
104 |
105 | public fontPostScriptName(): string {
106 | return this.safeGetStyle("fontPostScriptName");
107 | }
108 |
109 | public fontName(): string {
110 | return this.safeGetStyle("fontName");
111 | }
112 |
113 | public fontStyleName(): string {
114 | return this.safeGetStyle("fontStyleName");
115 | }
116 |
117 | private safeGetStyle(key: string): string {
118 | if (this.styleDesc) {
119 | if (this.styleDesc.hasKey(stringIDToTypeID(key))) {
120 | return this.styleDesc.getString(stringIDToTypeID(key));
121 | }
122 | if (this.styleDesc.hasKey(stringIDToTypeID("baseParentStyle"))) {
123 | const baseParentStyle = this.styleDesc.getObjectValue(stringIDToTypeID("baseParentStyle"));
124 | if (baseParentStyle.hasKey(stringIDToTypeID(key))) {
125 | return baseParentStyle.getString(stringIDToTypeID(key));
126 | }
127 | }
128 | }
129 | return "";
130 | }
131 |
132 | public size(): number {
133 | const sizeArr = ["impliedFontSize", "size"];
134 | for (let i=0; i Image -> Image Size
181 | * @param size
182 | */
183 | public resizeImage(size: Size): Document {
184 | const action = new ActionDescriptor();
185 | if (size.width > 0) {
186 | action.putUnitDouble(charIDToTypeID("Wdth"), charIDToTypeID("#Pxl"), size.width);
187 | }
188 | if (size.height > 0) {
189 | action.putUnitDouble(charIDToTypeID("Hght"), charIDToTypeID("#Pxl"), size.height);
190 | }
191 | if (size.width == 0 || size.height == 0) {
192 | action.putBoolean(charIDToTypeID("CnsP"), true);
193 | }
194 | action.putBoolean(stringIDToTypeID("scaleStyles"), true);
195 | action.putBoolean( stringIDToTypeID( "constrainProportions" ), true );
196 | //action.putEnumerated(charIDToTypeID("Intr"), charIDToTypeID("Intp"), charIDToTypeID('Blnr'));
197 | action.putEnumerated( stringIDToTypeID( "interfaceIconFrameDimmed" ), stringIDToTypeID( "interpolationType" ), stringIDToTypeID( "bicubicSharper" ) );
198 |
199 | app.executeAction(charIDToTypeID("ImgS"), action, DialogModes.NO);
200 | return this;
201 | }
202 |
203 | /**
204 | * resize document canvas, equal to menu -> Image -> Canvas Size
205 | * @param size
206 | * @return Document
207 | */
208 | public resizeCanvas(size: Size): Document {
209 | const idCnvS = charIDToTypeID( "CnvS" );
210 | const desc12 = new ActionDescriptor();
211 | desc12.putUnitDouble( charIDToTypeID( "Wdth" ), charIDToTypeID( "#Pxl" ), size.width);
212 | desc12.putUnitDouble( charIDToTypeID( "Hght" ), charIDToTypeID( "#Pxl" ), size.height);
213 | desc12.putEnumerated( charIDToTypeID( "Hrzn" ), charIDToTypeID( "HrzL" ), charIDToTypeID( "Cntr" ));
214 | desc12.putEnumerated( charIDToTypeID( "Vrtc" ), charIDToTypeID( "VrtL" ), charIDToTypeID( "Cntr" ));
215 | app.executeAction( idCnvS, desc12, DialogModes.NO );
216 | return this;
217 | }
218 |
219 | /**
220 | * check if current document is saved
221 | * @return boolean
222 | */
223 | public saved(): boolean {
224 | const a = new ActionReference();
225 | a.putEnumerated(charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
226 | const documentDescriptor = app.executeActionGet(a);
227 | return documentDescriptor.hasKey(stringIDToTypeID("fileReference"));
228 | }
229 |
230 | /**
231 | * force save current document
232 | * @return Document
233 | */
234 | public forceSave(): Document {
235 | const desc1 = new ActionDescriptor();
236 | desc1.putPath( stringIDToTypeID( "in" ), this.path() );
237 | desc1.putInteger( stringIDToTypeID( "documentID" ), this.id);
238 | desc1.putEnumerated( stringIDToTypeID( "saveStage" ), stringIDToTypeID( "saveStageType" ), stringIDToTypeID( "saveSucceeded" ) );
239 | app.executeAction( stringIDToTypeID( "save" ), desc1, DialogModes.NO );
240 | return this;
241 | }
242 |
243 | /**
244 | * save a copy of current file
245 | * @param filePath
246 | * @param format
247 | * @param saveAsCopy
248 | */
249 | public saveAs(filePath: string, format: DocumentFormat, saveAsCopy: boolean = false) {
250 | const desc1 = new ActionDescriptor();
251 | const desc2 = new ActionDescriptor();
252 | if (format == DocumentFormat.JPG) {
253 | desc2.putInteger( stringIDToTypeID( "extendedQuality" ), 12 );
254 | desc2.putEnumerated( stringIDToTypeID( "matteColor" ), stringIDToTypeID( "matteColor" ), stringIDToTypeID( "none" ) );
255 | } else if (format == DocumentFormat.PNG) {
256 | desc2.putEnumerated( stringIDToTypeID( "method" ), stringIDToTypeID( "PNGMethod" ), stringIDToTypeID( "quick" ) );
257 | desc2.putEnumerated( stringIDToTypeID( "PNGInterlaceType" ), stringIDToTypeID( "PNGInterlaceType" ), stringIDToTypeID( "PNGInterlaceNone" ) );
258 | desc2.putEnumerated( stringIDToTypeID( "PNGFilter" ), stringIDToTypeID( "PNGFilter" ), stringIDToTypeID( "PNGFilterAdaptive" ) );
259 | desc2.putInteger( stringIDToTypeID( "compression" ), 6 );
260 | desc2.putEnumerated( stringIDToTypeID( "embedIccProfileLastState" ), stringIDToTypeID( "embedOff" ), stringIDToTypeID( "embedOff" ) );
261 | } else if (format == DocumentFormat.PSD) {
262 | desc2.putBoolean( stringIDToTypeID( "maximizeCompatibility" ), true );
263 | } else if (format == DocumentFormat.BMP) {
264 | desc2.putEnumerated( stringIDToTypeID( "platform" ), stringIDToTypeID( "platform" ), stringIDToTypeID( "OS2" ) );
265 | desc2.putEnumerated( stringIDToTypeID( "bitDepth" ), stringIDToTypeID( "bitDepth" ), stringIDToTypeID( "bitDepth24" ) );
266 | }
267 | desc1.putObject( stringIDToTypeID( "as" ), stringIDToTypeID( format ), desc2 );
268 | //@ts-ignore
269 | desc1.putPath( stringIDToTypeID( "in" ), new File( filePath ) );
270 | desc1.putInteger( stringIDToTypeID( "documentID" ), this.id);
271 | desc1.putBoolean( stringIDToTypeID( "copy" ), saveAsCopy );
272 | desc1.putBoolean( stringIDToTypeID( "lowerCase" ), true );
273 | app.executeAction( stringIDToTypeID( "save" ), desc1, DialogModes.NO );
274 | }
275 |
276 |
277 | /**
278 | * return current document info in json format
279 | * @return json:string
280 | */
281 | public jsonString(): string {
282 | const af = new ActionReference();
283 | const ad = new ActionDescriptor();
284 | af.putProperty(charIDToTypeID("Prpr"), stringIDToTypeID("json"));
285 | af.putEnumerated(stringIDToTypeID("document"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
286 | ad.putReference(charIDToTypeID("null"), af);
287 | return app.executeAction(charIDToTypeID("getd"), ad, DialogModes.NO).getString(stringIDToTypeID("json"))
288 | }
289 |
290 | /**
291 | * set curent document active
292 | * @return Document
293 | */
294 | public active(): Document {
295 | const desc1 = new ActionDescriptor();
296 | const ref1 = new ActionReference();
297 | ref1.putIdentifier(charIDToTypeID( "Dcmn" ), this.id);
298 | desc1.putReference( stringIDToTypeID( "null" ), ref1 );
299 | app.executeAction( stringIDToTypeID( "select" ), desc1, DialogModes.NO );
300 | return this;
301 | }
302 |
303 |
304 | /**
305 | * close current document
306 | * @param save
307 | * @return Document
308 | */
309 | public close(save: boolean): Document {
310 | const desc904 = new ActionDescriptor();
311 | const value = (save)? charIDToTypeID("Ys ") : charIDToTypeID("N ");
312 | desc904.putEnumerated( charIDToTypeID( "Svng" ), charIDToTypeID( "YsN " ), value);
313 | app.executeAction( charIDToTypeID( "Cls " ), desc904, DialogModes.NO );
314 | return this;
315 | }
316 |
317 | /**
318 | * get current document action descriptor
319 | * @return ActionDescriptor
320 | */
321 | public toDescriptor(): ActionDescriptor {
322 | const documentReference = new ActionReference();
323 | documentReference.putEnumerated(stringIDToTypeID("document"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
324 | return app.executeActionGet(documentReference);
325 | }
326 |
327 | /**
328 | * trim transparent area of current document
329 | * equal to Menu -> Image -> Trim
330 | * @return Document
331 | */
332 | public trim(): Document {
333 | const desc1 = new ActionDescriptor();
334 | desc1.putEnumerated(stringIDToTypeID("trimBasedOn"), stringIDToTypeID("trimBasedOn"), stringIDToTypeID("transparency"));
335 | desc1.putBoolean(stringIDToTypeID("top"), true);
336 | desc1.putBoolean(stringIDToTypeID("bottom"), true);
337 | desc1.putBoolean(stringIDToTypeID("left"), true);
338 | desc1.putBoolean(stringIDToTypeID("right"), true);
339 | app.executeAction(stringIDToTypeID("trim"), desc1, DialogModes.NO);
340 | return this;
341 | }
342 |
343 | /**
344 | * crop current document with provided rect
345 | * @param rect
346 | * @return Document
347 | */
348 | public crop(rect: Rect): Document {
349 | app.activeDocument.crop([UnitValue(rect.x, 'px'), UnitValue(rect.y, 'px'), UnitValue(rect.right(), 'px'), UnitValue(rect.bottom(), 'px')]);
350 | return this;
351 | }
352 |
353 | /**
354 | * export current file to local file with ExportOptionsSaveForWeb (png/jpg/gif...)
355 | * @param path
356 | * @param filename
357 | * @param options
358 | * @return Document
359 | */
360 | public exportToWeb(path: string, filename: string, options: ExportOptionsSaveForWeb): Document {
361 | // @ts-ignore
362 | let file: any = new File(path + "/" + filename);
363 | app.activeDocument.exportDocument(file, ExportType.SAVEFORWEB, options);
364 | return this;
365 | }
366 |
367 | /**
368 | * export current document to pdf file
369 | * @param path
370 | * @param filename
371 | * @return Document
372 | */
373 | public exportToPdf(path: string, filename: string): Document {
374 | var desc1 = new ActionDescriptor();
375 | var desc2 = new ActionDescriptor();
376 | //desc2.putString( stringIDToTypeID( "pdfPresetFilename" ), "High Quality Print" );
377 | desc2.putEnumerated( stringIDToTypeID( "pdfCompatibilityLevel" ), stringIDToTypeID( "pdfCompatibilityLevel" ), stringIDToTypeID( "pdf15" ));
378 | desc2.putBoolean( stringIDToTypeID( "pdfPreserveEditing" ), false );
379 | //desc2.putBoolean( stringIDToTypeID( "pdfEmbedThumbnails" ), true );
380 | desc2.putInteger( stringIDToTypeID( "pdfCompressionType" ), 7 );
381 | desc2.putBoolean( stringIDToTypeID( "pdfIncludeProfile" ), false );
382 | desc1.putObject( charIDToTypeID( "As " ), charIDToTypeID( "PhtP" ), desc2 );
383 | //@ts-ignore
384 | desc1.putPath( charIDToTypeID( "In " ), new File( path + '/' + filename ) );
385 | desc1.putInteger( charIDToTypeID( "DocI" ), this.id );
386 | desc1.putBoolean( charIDToTypeID( "Cpy " ), true );
387 | desc1.putBoolean( charIDToTypeID( "Lyrs" ), false );
388 | desc1.putEnumerated( stringIDToTypeID( "saveStage" ), stringIDToTypeID( "saveStageType" ), stringIDToTypeID( "saveSucceeded" ) );
389 | app.executeAction( charIDToTypeID( "save" ), desc1, DialogModes.NO );
390 | return this;
391 | }
392 |
393 |
394 | /**
395 | * export current document to bmp file, current fix to windows platform and 24bit
396 | * @param path
397 | * @param filename
398 | */
399 | public exportToBMP(path: string, filename: string): Document {
400 | const desc1 = new ActionDescriptor();
401 | const desc2 = new ActionDescriptor();
402 | desc2.putEnumerated( stringIDToTypeID( "platform" ), stringIDToTypeID( "platform" ), stringIDToTypeID( "windows" ) );
403 | desc2.putEnumerated( stringIDToTypeID( "bitDepth" ), stringIDToTypeID( "bitDepth" ), stringIDToTypeID( "bitDepth24" ) );
404 | desc2.putBoolean( stringIDToTypeID( "compression" ), false );
405 | desc1.putObject( stringIDToTypeID( "as" ), stringIDToTypeID( "bMPFormat" ), desc2 );
406 | //@ts-ignore
407 | desc1.putPath( charIDToTypeID( "In " ), new File( path + '/' + filename ) );
408 | desc1.putInteger( stringIDToTypeID( "documentID" ), 219 );
409 | desc1.putBoolean( stringIDToTypeID( "copy" ), true );
410 | desc1.putBoolean( stringIDToTypeID( "lowerCase" ), true );
411 | desc1.putEnumerated( stringIDToTypeID( "saveStage" ), stringIDToTypeID( "saveStageType" ), stringIDToTypeID( "saveSucceeded" ) );
412 | app.executeAction( stringIDToTypeID( "save" ), desc1, DialogModes.NO );
413 | return this;
414 | }
415 |
416 | /**
417 | * get current user selection, return null if none
418 | * @param unit
419 | * @return Rect | null
420 | */
421 | public selection(unit: _ScaleUnit = 'px'): Rect | null {
422 | try {
423 | const selection = app.activeDocument.selection.bounds;
424 | return new Rect(selection[0].as(unit), selection[1].as(unit), selection[2].as(unit) - selection[0].as(unit), selection[3].as(unit) - selection[1].as(unit));
425 | } catch (ex) {
426 | return null;
427 | }
428 | }
429 |
430 | /**
431 | * create a selection with provided rect
432 | * @param rect
433 | */
434 | public setSelection(rect: Rect): void {
435 | const desc1 = new ActionDescriptor();
436 | const ref1 = new ActionReference();
437 | ref1.putProperty( stringIDToTypeID( "channel" ), stringIDToTypeID( "selection" ) );
438 | desc1.putReference( stringIDToTypeID( "null" ), ref1 );
439 | desc1.putObject( stringIDToTypeID( "to" ), stringIDToTypeID( "rectangle" ), rect.toDescriptor(UnitType.Pixel) );
440 | app.executeAction( stringIDToTypeID( "set" ), desc1, DialogModes.NO );
441 | }
442 |
443 | /**
444 | * get current file size in bytes
445 | * @return number
446 | */
447 | public length(): number {
448 | const doc = app.activeDocument
449 | try {
450 | // @ts-ignore
451 | const file = new File(doc.fullName)
452 | // @ts-ignore
453 | return file.length;
454 | } catch (e) {
455 | return 0;
456 | }
457 | }
458 |
459 | /**
460 | * get current color sampler list in document
461 | * @return ColorSampler[]
462 | */
463 | public colorSamplerList(): ColorSampler[] {
464 | const documentReference = new ActionReference();
465 | documentReference.putEnumerated(charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
466 | const documentDescriptor = app.executeActionGet(documentReference);
467 | const ret: ColorSampler[] = [];
468 | if (documentDescriptor.hasKey(stringIDToTypeID("colorSamplerList"))) {
469 | const colorSamplerList = documentDescriptor.getList(stringIDToTypeID("colorSamplerList"));
470 | for (let i=0; i