├── .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