graphic:javascript:photoshop

Differences

This shows you the differences between two versions of the page.


Previous revision
graphic:javascript:photoshop [2023/03/05 03:02] (current) – [Doc operation code] ying
Line 1: Line 1:
 +====== Enable Javascript in Photoshop 2022 above ======
  
 +  * to avoid warning pop dialog, you need to create a file at <code>%appdata%\Adobe\Adobe Photoshop 2022\Adobe Photoshop 2022 Settings\PSUserConfig.txt
 +%appdata%\Adobe\Adobe Photoshop 2022\Adobe Photoshop 2022 Settings\PSUserConfig
 +at path:
 +%appdata%\Adobe\Adobe Photoshop 2022\Adobe Photoshop 2022 Settings\
 +</code>
 +  * with content inside PSUserConfig.txt and also PSUserConfig (no ext in case for win10) (also enable ps use wacom instead ink code here)<code>WarnRunningScripts 0
 +UseSystemStylus 0
 +</code>
 +  * more config text cmd here: https://helpx.adobe.com/sg/photoshop/kb/enable-optional-extensions-photoshop-cc.html
 +====== Javascript Version and Compatibility ======
 +
 +  * JS ES3 (1999): photoshop js based on.
 +  * JS ES4
 +  * JS ES5: .trim()
 +  * JS ES6 (2015): let const, arrow
 +  * JS 2016: array.includes()
 +  * JS 2017: string padding
 +  * JS 2018: 
 +
 +  * note
 +    * function with default parameter: <code javascript>
 +function hex2rgb(hex, asList) {
 +    asList = asList || 0; // default not as list but as dict
 +}
 +</code>
 +      * ref: https://code.tutsplus.com/tutorials/how-to-use-optional-function-parameters-in-javascript--cms-37376
 +    * startswith support
 +    * check JS version: $.about()
 +
 +  * ref: 
 +    * https://helpx.adobe.com/after-effects/using/legacy-and-extend-script-engine.html
 +    * https://www.w3schools.com/js/js_versions.asp
 +
 +====== My Photoshop Script ======
 +
 +  * From time to time, I find I need something that can be repeated in Photoshop, that is why I decide to post some photoshop javascripts here. Most of them are very useful
 +
 +    * [[appwiki:photoshop:ps_script:codeGenerator|Code Generator]]: script for building variable code/coupon code in Photoshop
 +
 +====== Photoshop Scripting and Automation ======
 +  * Photoshop script guide can be found in application directory like <code>
 +C:\Program Files (x86)\Adobe\Adobe Photoshop CS3\Scripting Guide\</code>
 +
 +  * Addition scripting method to Javascript, VBscript, AppleScript
 +    * you can use Photoshop COM interface to communicate in other programming language
 +      * python: with win32com module, it can launch Photoshop with command shell <code python>import win32com.client
 +psApp=win32com.client.Dispatch('Photoshop.Application')
 +psApp.preferences.rulerUnits=1 # use pixel as unit
 +psApp.documents.add()
 +</code>
 +      * python: with comtypes module, it can launch Photoshop with command shell (not working for me)<code python>
 +from comtypes.client import CreateObject
 +psApp=CreateObject('Photoshop.Application')
 +</code>
 +  * a ref video on Photoshop CS3 Python Interpreter Embed in C++
 +    * http://www.youtube.com/watch?v=_cHmnVjptjY
 +  * another C++ listener integration
 +    * http://tech-artists.org/forum/showthread.php?1304-Photoshop-Embedded-Python-Pipeline
 +    * https://bitbucket.org/amorano/pyps/
 +
 +  * What is new about scripting in Photoshop CS5
 +    * Guides where added to you can create/delete/move guides using the DOM instead of scriptlistner
 +    * showColorPicker was added to application for when your script needs the user to select a color
 +    * refreshFonts and isQuicktimeAvailable were also added to application.
 +    * two new blend modes
 +    * printing underwent some big changes in the GUI
 +
 +
 +====== Basic Javascript syntax ======
 +
 +  * tool study:
 +    * https://sourceforge.net/projects/ps-scripts/
 +    * get jsh.jsx for javascript script panel inside photoshop and other js conversion tool stuff
 +      * [[http://ps-scripts.cvs.sourceforge.net/*checkout*/ps-scripts/xtools/apps/jsh.jsx|http://ps-scripts.cvs.sourceforge.net/*checkout*/ps-scripts/xtools/apps/jsh.jsx]]
 +      * http://ps-scripts.sourceforge.net/xtools.html
 +    * https://www.smashingmagazine.com/2013/07/introduction-to-photoshop-scripting/
 +    * Export layer or group with trim: https://github.com/jwa107/Photoshop-Export-Layers-to-Files-Fast
 +    * a good website by a Japanese guy: http://phize.net/portfolio/photoshop/columnwizard.html
 +    * sorting layer: http://morris-photographics.com/photoshop/scripts/sort-layers.html
 +    * tons of js code: http://www.tranberry.com/panels/
 +    * http://www.tranberry.com/photoshop/photoshop_scripting/index.html
 +    * http://morris-photographics.com/photoshop/scripts/
 +    * http://www.kirupa.com/motiongraphics/scripting2.htm
 +    * http://cssdk.s3-website-us-east-1.amazonaws.com/sdk/1.0/docs/WebHelp/app_notes/ps_scripting.htm
 +
 +  * alert("Hello world");
 +
 +  * add guide line (before cs5) <code javascript>
 +// add guide; ref: http://phize.net/assets/templates/phize.net/portfolio/photoshop/download/ColumnWizard.jsx
 +function addGuide(offset, orientation) {
 +    var id1 = charIDToTypeID('Mk  ');
 +    var desc1 = new ActionDescriptor();
 +    var id2 = charIDToTypeID('Nw  ');
 +    var desc2 = new ActionDescriptor();
 +    var id3 = charIDToTypeID('Pstn');
 +    var id4 = charIDToTypeID('#Rlt');
 +    desc2.putUnitDouble(id3, id4, offset);
 +    var id5 = charIDToTypeID('Ornt');
 +    var id6 = charIDToTypeID('Ornt');
 +    var id7 = charIDToTypeID(orientation);
 +    desc2.putEnumerated(id5, id6, id7);
 +    var id8 = charIDToTypeID('Gd  ');
 +    desc1.putObject(id2, id8, desc2);
 +    executeAction(id1, desc1, DialogModes.NO);
 +}
 +</code>
 +    * after CS5 <code javascript>activeDocument.guides.add(Direction.VERTICAL, 100); // orientation and offset
 +activeDocument.guides.removeAll()
 +</code>
 +
 +
 +===== system code =====
 +
 +  * photoshop version and preference<code javascript>alert(parseInt(version));
 +app.preferences.rulerUnits = Units.PIXELS
 +app.preferences.typeUnits = TypeUnits.PIXELS
 +app.displayDialogs = DialogModes.NO
 +</code>
 +  * default check if doc open for those process script <code javascript>
 +// ensure at least one document open
 +if (!documents.length) {
 +    alert('There are no documents open. Please choose your action next.', 'No Document');
 +    defaultRunForNoDoc();
 +}
 +else {
 +    main(); // if at least one document exists, then proceed
 +}
 +</code>
 +  * ask user input <code javascript>var input_txt = prompt('Width of your new doc', '800');alert(parseInt(input_txt));</code>
 +  * confirm user <code javascript>var yes=confirm('Are you sure?');alert(yes); //true or false</code>
 +  * safe way to call a function <code javascript>
 +// begin try/catch
 +try {
 +   someUnsureFunction(newName);
 +}
 +// display error message if something goes wrong
 +catch(e) {
 +    if (confirm('An unknown error has occurred. Would you like to see more information?')) {
 +        alert(e + ': on line ' + e.line, 'Script Error', true);
 +    }
 +}
 +</code>
 +  * select tool<code javascript>
 +function selectTool(tool) {
 +    var my_desc = new ActionDescriptor();
 +    var my_ref = new ActionReference();
 +    my_ref.putClass( app.stringIDToTypeID(tool) );
 +    my_desc.putReference( app.charIDToTypeID('null'), my_ref );
 +    executeAction( app.charIDToTypeID('slct'), my_desc, DialogModes.NO );
 +}; 
 +selectTool('zoomTool');
 +/**
 +moveTool marqueeRectTool marqueeEllipTool marqueeSingleRowTool marqueeSingleColumnTool lassoTool
 +polySelTool magneticLassoTool quickSelectTool magicWandTool cropTool sliceTool sliceSelectTool 
 +spotHealingBrushTool magicStampTool patchSelection redEyeTool paintbrushTool pencilTool 
 +colorReplacementBrushTool cloneStampTool patternStampTool historyBrushTool artBrushTool eraserTool 
 +backgroundEraserTool magicEraserTool gradientTool bucketTool blurTool sharpenTool smudgeTool dodgeTool 
 +burnInTool saturationTool penTool freeformPenTool addKnotTool deleteKnotTool convertKnotTool 
 +typeCreateOrEditTool typeVerticalCreateOrEditTool typeCreateMaskTool typeVerticalCreateMaskTool 
 +pathComponentSelectTool directSelectTool rectangleTool roundedRectangleTool ellipseTool polygonTool lineTool 
 +customShapeTool textAnnotTool soundAnnotTool eyedropperTool colorSamplerTool rulerTool handTool zoomTool
 +*/
 +</code>
 +===== Tool operation code =====
 +
 +  * select a brush <code javascript>
 +function c2t(s) { return app.charIDToTypeID(s); };
 +function s2t(s) { return app.stringIDToTypeID(s); };
 +
 +function selectBrush(brushName) {
 +    var my_ref = new ActionReference();
 +    my_ref.putName( c2t( "Brsh" ), brushName);
 +    var my_desc = new ActionDescriptor();
 +    my_desc.putReference( c2t( "null" ), my_ref );
 +    executeAction( c2t("slct"), my_desc, DialogModes.NO );
 +};
 +
 +function brushSize(brushSize) {
 +    var my_ref = new ActionReference();
 +    my_ref.putEnumerated( c2t("Brsh"), c2t("Ordn"), c2t("Trgt") );
 +    var my_descBrush = new ActionDescriptor();
 +    my_descBrush.putUnitDouble( s2t("masterDiameter"), c2t("#Pxl"), brushSize );
 +    
 +    var my_desc = new ActionDescriptor();
 +    my_desc.putReference( c2t("null"), my_ref );
 +    my_desc.putObject( c2t("  "), c2t("Brsh"), my_descBrush );
 +    executeAction( c2t("setd"), my_desc, DialogModes.NO );
 +};
 +
 +function brushHard(hardness) {
 +    if(parseInt(hardness) > 100) hardness= 100;
 +    var my_ref = new ActionReference();
 +    my_ref.putEnumerated( c2t("Brsh"), c2t("Ordn"), c2t("Trgt") );
 +    var my_descBrush = new ActionDescriptor();
 +    my_descBrush.putUnitDouble( s2t("hardness"), c2t("#Pxl"), hardness);
 +    
 +    var my_desc = new ActionDescriptor();
 +    my_desc.putReference( c2t("null"), my_ref );
 +    my_desc.putObject( c2t("  "), c2t("Brsh"), my_descBrush );
 +    executeAction( c2t("setd"), my_desc, DialogModes.NO );
 +};
 +
 +selectBrush("Soft Round" );
 +brushSize(20);
 +brushHard(20);
 +</code>
 +
 +===== Layer operation code =====
 +  * get layer count <code javascript>app.activeDocument.layers.length; //<4:></code>
 +  * new/delete layer <code javascript>app.activeDocument.artLayers.add();
 +activeDocument.activeLayer.remove();
 +
 +activeDocument.activeLayer.kind = LayerKind.TEXT; // make into text layer
 +var textItem = activeDocument.activeLayer.textItem;
 +textItem.contents = "My Layer is "+"Cool";
 +textItem.size = 24;
 +textItem.font = "ComicSansMS";
 +var newColor = new SolidColor();
 +newColor.rgb.red = 255;
 +newColor.rgb.green = 255;
 +newColor.rgb.blue = 0;
 +textItem.color = newColor;
 +myTextRef.position = new Array( 20, 80); // percentage from left top
 +</code>
 +  * rename layer <code javascript>activeDocument.activeLayer.name='Test';</code>
 +  * select layer by name <code javascript>activeDocument.activeLayer = activeDocument.artLayers.getByName("layerName");
 +// inside a set
 +activeDocument.activeLayer = activeDocument.layerSets["LayerSetName"].artLayers.getByName("LayerName"); 
 +</code>
 +  * layer opacity <code javascript>activeDocument.activeLayer.opacity = 85;</code>
 +  * layer blend mode <code javascript>activeDocument.activeLayer.blendMode = BlendMode.MULTIPLY; //BlendMode.SCREEN; BlendMode.NORMAL;</code>
 +  * toggle layer lock <code javascript>activeDocument.activeLayer.allLocked = !activeDocument.activeLayer.allLocked;</code>
 +  * toggle layer visibility <code javascript>activeDocument.activeLayer.visible= !activeDocument.activeLayer.visible;</code>
 +  * clip mask layer for below <code javascript>activeDocument.activeLayer.grouped = 1;</code>
 +  * check bg layer mode <code javascript>alert(activeDocument.activeLayer.isBackgroundLayer);</code>
 +  * select up,lower,front,back layer <code javascript>
 +function selectLayerNav(direction){
 +    var my_ref = new ActionReference();
 +    var my_desc = new ActionDescriptor();
 +    my_ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID(direction) );
 +    my_desc.putReference( charIDToTypeID("null"), my_ref );
 +    my_desc.putBoolean( charIDToTypeID( "MkVs" ), false );
 +    executeAction( charIDToTypeID("slct"), my_desc, DialogModes.NO );
 +}
 +selectLayerNav("Frwr"); //"Frwr", "Bckw", "Frnt", "Back"
 +</code>
 +  * rasterize layer <code javascript>
 +if (activeDocument.activeLayer.kind == LayerKind.NORMAL)
 +{activeDocument.activeLayer.rasterize(RasterizeType.ENTIRELAYER);} // art layer with mask
 +if (activeDocument.activeLayer.kind == LayerKind.TEXT)
 +{activeDocument.activeLayer.rasterize(RasterizeType.TEXTCONTENTS);} // text layer
 +if (activeDocument.activeLayer.kind == LayerKind.SOLIDFILL)
 +{activeDocument.activeLayer.rasterize(RasterizeType.SHAPE);} // shape layer
 +</code>
 +
 +  * get all the smart object layer in document <code javascript>
 +var result_list = new Array;
 +var srcDoc = app.activeDocument;
 +var numOfLayers = srcDoc.layers.length;
 +for (var i = numOfLayers -1; i >= 0; i--)
 +{
 +    if(srcDoc.layers[i].kind == LayerKind.SMARTOBJECT)
 +    {
 +        result_list.push(srcDoc.layers[i].name);
 +    }
 +}
 +alert (result_list.join("\n"));
 +</code>
 +===== Doc operation code =====
 +
 +  * new doc file and close <code javascript>documents.add(1240, 1754, 72, "Untitled", NewDocumentMode.RGB, DocumentFill.WHITE, 1.00); 
 +// <[Document Untitled]:> ; 1240cmx1754cm
 +
 +var defaultRulerUnits = preferences.rulerUnits; 
 +preferences.rulerUnits = Units.PIXELS;
 +var newDocumentRef = documents.add(800, 600, 72.0, "Plan 800", NewDocumentMode.RGB, DocumentFill.WHITE);
 +// for PS CS and PS 7, NewDocumentMode.RGB is DocumentMode.RGB
 +preferences.rulerUnits = defaultRulerUnits;
 +// 800px, 600px
 +newDocumentRef = null;
 +
 +while (app.documents.length) {
 +app.activeDocument.close();
 +}
 +</code>
 +  * resize doc <code javascript>app.activeDocument.resizeImage('150%', '150%');</code>
 +  * resize to A4 size ratio <code javascript>
 +/*
 +reisize to fit A4
 +by ying n chatGPT
 +2023.03.05
 +*/
 +// Get the active document
 +var doc = app.activeDocument;
 +app.backgroundColor.rgb.red = 255;
 +app.backgroundColor.rgb.green = 255;
 +app.backgroundColor.rgb.blue = 255;
 +// Define the A4 size in pixels (assuming 300 DPI)
 +var a4Width = 2480; // 8.27 inches * 300 pixels per inch
 +var a4Height = 3508; // 11.69 inches * 300 pixels per inch
 +var a4Ratio = a4Width / a4Height;
 +
 +// Get the current canvas size
 +var currentWidth = doc.width.value;
 +var currentHeight = doc.height.value;
 +var currentRatio = currentWidth / currentHeight;
 +
 +// Calculate the new canvas size
 +var newWidth = currentWidth;
 +var newHeight = currentHeight;
 +if (currentRatio > a4Ratio) {
 +  // Extend the height to match the A4 ratio
 +  newHeight = Math.round(newWidth / a4Ratio);
 +} else {
 +  // Extend the width to match the A4 ratio
 +  newWidth = Math.round(newHeight * a4Ratio);
 +}
 +
 +// Only resize the canvas if the new size is larger than the current size
 +if (newWidth > currentWidth || newHeight > currentHeight) {
 +  // Calculate the position of the current content
 +  var x = Math.round((newWidth - currentWidth) / 2);
 +  var y = Math.round((newHeight - currentHeight) / 2);
 +
 +  // Resize the canvas
 +  doc.resizeCanvas(newWidth, newHeight, AnchorPosition.MIDDLECENTER);
 +}
 +
 +</code>
 +  * file and folder <code javascript>
 +var samplesFolder = Folder(app.path + "/Samples/")
 +var fileList = samplesFolder.getFiles()
 +</code>
 +  * get active doc <code javascript>app.activeDocument; //<[Document Untitled]:>
 +var docName = app.activeDocument.name; // just name.ext
 +fileFullPath = app.activeDocument.fullName; // full path to file
 +fileFolderPath = app.activeDocument.path; // full path to parent folder of the file, no end /
 +
 +index = (docName.name).indexOf(".");
 +fileName = (docName.name).substr(0, index); // file name with ext
 +</code>
 +
 +  * publishJPG.jsx : save current PSD file as fileName_publish.jpg in same folder <code javascript publishJPG.jsx>
 +//ref : http://forums.adobe.com/thread/594393
 +function saveJPEG( doc, saveFile, qty ) {
 +     var saveOptions = new JPEGSaveOptions( );
 +     saveOptions.embedColorProfile = true;
 +     saveOptions.formatOptions = FormatOptions.STANDARDBASELINE;
 +     saveOptions.matte = MatteType.NONE;
 +     saveOptions.quality = qty; 
 +     doc.saveAs( saveFile, saveOptions, true );
 +}
 +//saveJPEG( app.activeDocument, new File('~/Desktop/sample.jpg'), 10 );
 + 
 +var doc = app.activeDocument;
 +var docName = doc.name;
 +docName = docName.match(/(.*)(\.[^\.]+)/) ? docName = docName.match(/(.*)(\.[^\.]+)/):docName = [docName, docName];
 +var suffix = '_publish';
 +var savedName = decodeURI(doc.path)+'/'+docName[1]+suffix+'.jpg'
 +if (! File(savedName).exists ){
 +     var savedFile = new File(decodeURI(doc.path)+'/'+docName[1]+suffix+'.jpg');
 +     saveJPEG( app.activeDocument,savedFile, 10 );
 +}
 +</code>
 +  * print each layer <code javascript>
 +var msg = "Make sure you set your print setting correct and done,\nthen click Yes to run batch print layer"
 +if (confirm(msg)) {
 +    // Get the current active document
 +    var doc = app.activeDocument;
 +
 +    // Get all the layers except the background layer
 +    var layers = [];
 +    for (var i = 0; i < doc.layers.length; i++) {
 +        if(!doc.layers[i].isBackgroundLayer){
 +            layers.push(doc.layers[i]);
 +        }
 +    }
 +    // Hide all the layers
 +    for (var i = 0; i < layers.length; i++) {
 +        layers[i].visible = false;
 +    }
 +
 +    // Loop through each layer and print it
 +    for (var i = 0; i < layers.length; i++) {
 +        // Show the current layer
 +        layers[i].visible = true;
 +        
 +        // Print the current layer
 +        app.activeDocument.print();
 +        //alert('printing'+layers[i].name)
 +        
 +        // Hide the current layer
 +        layers[i].visible = false;
 +    }
 +
 +    // Show all the layers again
 +    for (var i = 0; i < layers.length; i++) {
 +        layers[i].visible = true;
 +    }
 +} else {
 +  alert("Printing cancelled.");
 +}
 +</code>
 +  * split image vertically equally into N part, and stack into new doc, optionally crop border pixel <code javascript>
 +//=======================================
 +//#  image split by n
 +//=======================================
 +var doc = app.activeDocument;
 +
 +// Prompt the user for the number of parts
 +var n = parseInt(prompt("Enter the number of parts to split the image into:", 4));
 +
 +// Calculate the height of each part
 +var partHeight = Math.floor(doc.height / n);
 +
 +// Create a new document with the width of the original image and the height of one part
 +var newDoc = app.documents.add(doc.width, partHeight, doc.resolution, "Split Image");
 +
 +// Loop through each part of the image
 +for (var i = 0; i < n; i++) {
 +  // Define the source rectangle for the part
 +  var sourceRect = [
 +    [0, i * partHeight],
 +    [doc.width, i * partHeight],
 +    [doc.width, (i + 1) * partHeight],
 +    [0, (i + 1) * partHeight]
 +  ];
 +
 +  // Activate the source document
 +  app.activeDocument = doc;
 +  // Deselect any active selection
 +  doc.selection.deselect();
 +  // Create a new selection
 +  doc.selection.select(sourceRect);
 +  // Copy the part to the new document
 +  doc.selection.copy();
 +  // Activate the new document
 +  app.activeDocument = newDoc;
 +  // Paste the part into the new document
 +  newDoc.paste();
 +  // Move the selection down by the height of one part
 +  app.activeDocument = doc;
 +  doc.selection.translateBoundary(0, partHeight);
 +}
 +//=======================================
 +//#  trim border
 +//=======================================
 +app.activeDocument = newDoc;
 +newDoc.trim(TrimType.TOPLEFT, true, true, true, true);
 +</code>
 +===== Execute code =====
 +
 +  * open folder <code javascript>
 +var f = app.activeDocument.path;
 +//var f = Folder(Folder.desktop+'/test');
 +f.execute(); </code>
 +  * run a cmd script <code javascript>var file = new File("/D/myFolder/myCmd.bat");
 +file.execute();
 +</code>
 +  * run js file, method 1 <code javascript>// @include "FileIncludeIncluded.jsx"
 +// above line is including other file, you need to put // in front.</code>
 +    * note: the include method actually copy over all the code in that file, means, the variable data in that include file will be pass over into current file, in addition, if no absolute pass given, the include refer to the same folder path, example <code javascript js_include_js.jsx>
 +//@include "js_alert.jsx"
 +alert(result); // catch result
 +</code> <code javascript js_alert.jsx>
 +alert('JS here'); // with alert or without doesn't matter
 +result = "Result pass";
 +</code>
 +    * example: https://github.com/moremorefor/JSXExecutor/blob/master/src/jsx/hostscript.jsx
 +  * run js file, method 2, note, can not catch result from that file unlike method 1<code javascript js_call_js.jsx>
 +var jsx_file = "D:\\Dev\\PyQT\\PyPS\\js_alert.jsx"; // fine
 +//var jsx_file = "D:/Dev/PyQT/PyPS/js_alert.jsx"; // fine and prefer for cross-platform
 +//var jsx_file = "/D/Dev/PyQT/PyPS/js_alert.jsx"; // fine and photoshop return this
 +mydesc = new ActionDescriptor();
 +mydesc.putPath(charIDToTypeID("jsCt"),  new File(jsx_file) ); // if the script already list in script menu, then just put the name like "MyScript" to call it instead of new file
 +mydesc.putString(charIDToTypeID("jsMs"), "undefined" );
 +executeAction( stringIDToTypeID( "AdobeScriptAutomation Scripts" ), mydesc, DialogModes.NO );
 +</code>
 +  * run py file <code javascript>
 +var myPy = new File("/path_to_file/pyFun.py");
 +myPy.execute();
 +</code>
 +
 +===== Memory and Data store and retrieve =====
 +
 +  * ref: http://stackoverflow.com/questions/14388531/saving-per-user-or-per-document-preferences-in-a-photoshop-script
 +  * example code with keystrock detect "ScriptUI.environment.keyboardState.shiftKey" and putCustomOptions/getCustomOptions: https://dribbble.com/shots/618442-Photoshop-Tip?list=searches&tag=photoshop_script
 +
 +===== Javascript GUI scripting =====
 +
 +  * read Photoshop sample script code like this:
 +    * http://www.pretentiousname.com/ps_exportlayersfast/index.html
 +    * http://www.joshwright.com/tips/photoshop-scripting-user-interface
 +    * ScriptUI GUI code generator (broken link): http://www.jkozniewski.com/tools/csib_loader.swf
 +      * since that link is outdated, you can grab from http://web.archive.org
 +      * or download from my save "CS UI Buider v1.0.2 by Jakub Kozniewski" {{ :appwiki:photoshop:ps_script:cs_ui_builder_1.0.2.zip |}}
 +      * note: the 2.0a is a exe version, which not work for me, you can get from here https://github.com/Faham/bric-n-brac/tree/master/tools/external
 +      * {{:graphic:javascript:photoshop:ps_ui_builder.png?400|}}
 +    * There is also AfterEffect jsx script do ScriptUI design called P9 ScriptUI builder: https://aescripts.com/p9-scriptui-builder/ 
 +
 +  * custom dialog window with button function (note: this \ style of ui code will cause BridgeTalk object method not working.) <code javascript>
 +function winRun() { 
 +    var ui_code = "dialog { \
 +    text: 'My Window Title', \
 +    bounds: [100,10,310,100], \
 +    w_label: StaticText { text:'Width mm', bounds:[10, 5, 70, 25] }, \
 +    w_input: EditText { text:'100', bounds: [75, 5, 120, 25] }, \
 +    h_label: StaticText { text:'Height mm', bounds:[10, 30, 70, 50] }, \
 +    h_input: EditText { text:'200', bounds:[75, 30, 120, 50] }, \
 +    my_btn: Button { text:'Fun', bounds:[130, 5, 200, 25] }, \
 +    run_btn: Button { text:'Run', bounds:[130, 30, 200, 50], properties:{name:'ok'}}, \
 +    quit_btn: Button { text:'Quit', bounds:[130, 55, 200, 75], properties:{name:'cancel'}} \
 +    }"; 
 +    var ui = new Window(ui_code); 
 +    ui.my_btn.onClick = function() {alert("My Function Here");
 +    var result = ui.show(); 
 +    alert(result + '\n' + ui.w_input.text + '\n' + ui.h_input.text); 
 +}
 +winRun();
 +</code>
 +
 +
 +===== ScriptUI in Detail =====
 +
 +**Window Type - Pallete and Dialog**
 +  * There are 2 window type
 +    - pallete (non-modal window, not block user and Application interaction, has close icon on title bar)
 +      * Note: Photoshop, Illustrator will not keep display it; AfterEffects will keep display it
 +    - dialog (modal window, block user and Application interaction, user has to click button to close it)
 +  * demo script, just change line 2 and line 3 to show the difference of pallete and dialog <code javascript ui_demo.jsx>
 +//var win_type = "palette";
 +var win_type = "dialog";
 +var ui = win_type+" {orientation: 'column',alignChildren: ['fill', 'top'],preferredSize:[300, 130],text: 'Title Here',margins:15,";
 +  var slider_panel = "slider_panel: Panel {orientation: 'row',alignChildren: 'right',margins:15,text: 'Panel Title',";
 +      slider_panel +="value_label: StaticText { text: 'Value:' },";
 +      slider_panel +="value_slider: Slider { minvalue: 1, maxvalue: 100, value: 30, size:[220,20] },";
 +      slider_panel +="value_edit: EditText { text: '30', characters: 5, justify: 'left'}";
 +      slider_panel +="}";
 +  ui+=slider_panel;
 +  var btm_grp = "btm_grp: Group{";
 +    btm_grp +="value_check: Checkbox { text:'Checkbox value', value: true },";
 +    btm_grp +="cancel_btn: Button { text: 'Cancel', properties:{name:'cancel'}, size: [120,24], alignment:['right', 'center'] },";
 +    btm_grp +="apply_btn: Button { text: 'Apply', properties:{name:'ok'}, size: [120,24], alignment:['right', 'center'] },";
 +    btm_grp +="}";
 +  ui+=btm_grp;
 +  ui +="}";
 +var win = new Window(ui);
 +win.btm_grp.cancel_btn.onClick = function() { win.close() };
 +win.btm_grp.apply_btn.onClick = function() { win.close() };
 +win.show();
 +</code>
 +
 +**Working Float Pallete in Photoshop and Illustrator**
 +  * As mentioned above, Pallete is ideal choice for tool panel design, as dialog prevent main App interaction until close, but Pallete is closed automatically after script code finished, thus we need following solution to work around Pallete limit in Photoshop and Illustrator
 +  * use BridgeTalk object as message to send to the Application instead ask the Application to create window. so the Message stands as its own thread
 +    * ref:
 +      * http://www.davidebarranca.com/2012/10/scriptui-window-in-photoshop-palette-vs-dialog/
 +      * http://www.davidebarranca.com/2012/11/scriptui-bridgetalk-persistent-window-examples/
 +  * sample code, change target to your application, **the script can be launch from any Adobe app**, but **only the target will receive it**, as it is like a letter with receiver name, all Adobe app will pass the message to the Target. <code javascript ui_pallete_BT.jsx>
 +function WinObject() {
 +  var win_type = "palette";
 +  var ui = win_type+" {orientation: 'column',alignChildren: ['fill', 'top'],preferredSize:[300, 130],text: 'Title Here',margins:15,";
 +    var slider_panel = "slider_panel: Panel {orientation: 'row',alignChildren: 'right',margins:15,text: 'Panel Title',";
 +        slider_panel +="value_label: StaticText { text: 'Value:' },";
 +        slider_panel +="value_slider: Slider { minvalue: 1, maxvalue: 100, value: 30, size:[220,20] },";
 +        slider_panel +="value_edit: EditText { text: '30', characters: 5, justify: 'left'}";
 +        slider_panel +="}";
 +    ui+=slider_panel;
 +    var btm_grp = "btm_grp: Group{";
 +      btm_grp +="value_check: Checkbox { text:'Checkbox value', value: true },";
 +      btm_grp +="cancel_btn: Button { text: 'Cancel', properties:{name:'cancel'}, size: [120,24], alignment:['right', 'center'] },";
 +      btm_grp +="apply_btn: Button { text: 'Apply', properties:{name:'ok'}, size: [120,24], alignment:['right', 'center'] },";
 +      btm_grp +="}";
 +    ui+=btm_grp;
 +    ui +="}";
 +  var win = new Window(ui);
 +  win.btm_grp.cancel_btn.onClick = function() { win.close() };
 +  win.btm_grp.apply_btn.onClick = function() { win.close() };
 +  win.show();
 +}; 
 +var message = WinObject.toString();
 +message += "\nnew WinObject();"
 +var bt = new BridgeTalk();
 +//bt.target = "aftereffects";
 +bt.target = "photoshop";
 +//bt.target = "illustrator";
 +bt.body = message;
 +bt.send();
 +</code>
 +  * thus, BridgeTalk is good solution for inter-Adobe app communication. <code javascript call_ps_from_ai.jsx>
 +// the BridgeTalk Object
 +var bt = new BridgeTalk();
 +// the communication target
 +bt.target = "photoshop";
 +// The script to be executed as a String
 +var message = "alert('Hello Photoshop')";
 +// assign to the object's body the message
 +bt.body = message;
 +// send the message to the target app
 +bt.send(); // an alert will pop-up in Photoshop
 +</code>
 +
 +**One-For-All solution with BridgeTalk for Working Float Pallete**
 +
 +  * As BridgeTalk is the key for working float pallet, but in a BridgeTalk, the target App is pre-defined in the script, like bt.target="Photoshop", and also the whole script is wrapped in a message,
 +  * The One-For-All solution is use BridgeTalk to launch existing normal-styped javascript jsx file, so the main function and launcher are separated into 2 files, 
 +  * example code as below <code javascript launch_js_for_ps.jsx>
 +var script_name = "ui_demo.jsx";
 +var currentPath = (new File($.fileName)).path; // retrieve the current script path
 +var scriptToLoad = new File (currentPath + "/"+script_name); // the script to load
 +try {
 +    if (!scriptToLoad.exists) { throw new Error("script not found!"); }
 +    scriptToLoad.open ("r"); // read only
 +    var message = scriptToLoad.read();
 +    scriptToLoad.close();
 +} catch (error) {
 +    alert("Error parsing the file: " + error.description);
 +}
 +var bt = new BridgeTalk();
 +bt.target = "photoshop";
 +bt.body = message;
 +bt.send();
 +</code>
 +
 +**Special Note for BridgeTalk Method**
 +  * use "\" way of ui coding will cause BridgeTalk method's button callback not working, thus the window resource string can't contain backslashes "\"
 +  * jsxbin is jsx in binary encoded (which can be exported from jsx in ExtendScript TookKit); the message also can take binary string and embed your script in the BridgeTalk object.
 +    * example code (\ added for each line from original ui_demo.jsxbin) <code javascript ui_demo_inBinary.jsx>
 +var message = "@JSXBIN@ES@2.0@MyBbyBn0ATJAnASzIjXjJjOifjUjZjQjFByBneHjQjBjMjFjUjUjFftJCnASzCjVj\
 +JCyBCzBhLDVBfyBnnejOhAjbjPjSjJjFjOjUjBjUjJjPjOhahAhHjDjPjMjVjNjOhHhMjBjMjJjHjOi\
 +DjIjJjMjEjSjFjOhahAibhHjGjJjMjMhHhMhAhHjUjPjQhHidhMjQjSjFjGjFjSjSjFjEiTjJjajFha\
 +ibhThQhQhMhAhRhThQidhMjUjFjYjUhahAhHiUjJjUjMjFhAiIjFjSjFhHhMjNjBjSjHjJjOjThahRh\
 +VhMnftJDnASzMjTjMjJjEjFjSifjQjBjOjFjMEyBneiejTjMjJjEjFjSifjQjBjOjFjMhahAiQjBjOj\
 +FjMhAjbjPjSjJjFjOjUjBjUjJjPjOhahAhHjSjPjXhHhMjBjMjJjHjOiDjIjJjMjEjSjFjOhahAhHjS\
 +jJjHjIjUhHhMjNjBjSjHjJjOjThahRhVhMjUjFjYjUhahAhHiQjBjOjFjMhAiUjJjUjMjFhHhMftJEn\
 +ASEyBCDnnnehLjWjBjMjVjFifjMjBjCjFjMhahAiTjUjBjUjJjDiUjFjYjUhAjbhAjUjFjYjUhahAhH\
 +iWjBjMjVjFhahHhAjdhMntfJFnASEyBCDnnneiOjWjBjMjVjFifjTjMjJjEjFjShahAiTjMjJjEjFjS\
 +hAjbhAjNjJjOjWjBjMjVjFhahAhRhMhAjNjBjYjWjBjMjVjFhahAhRhQhQhMhAjWjBjMjVjFhahAhTh\
 +QhMhAjTjJjajFhaibhShShQhMhShQidhAjdhMntfJGnASEyBCDnnneiCjWjBjMjVjFifjFjEjJjUhah\
 +AiFjEjJjUiUjFjYjUhAjbhAjUjFjYjUhahAhHhThQhHhMhAjDjIjBjSjBjDjUjFjSjThahAhVhMhAjK\
 +jVjTjUjJjGjZhahAhHjMjFjGjUhHjdntfJHnASEyBCDnnneBjdntfJInASCyBCDnVEfyBnnntfJJnAS\
 +zHjCjUjNifjHjSjQFyBnePjCjUjNifjHjSjQhahAiHjSjPjVjQjbftJKnASFyBCDnnnehdjWjBjMjVj\
 +FifjDjIjFjDjLhahAiDjIjFjDjLjCjPjYhAjbhAjUjFjYjUhahHiDjIjFjDjLjCjPjYhAjWjBjMjVjF\
 +hHhMhAjWjBjMjVjFhahAjUjSjVjFhAjdhMntfJLnASFyBCDnnnejRjDjBjOjDjFjMifjCjUjOhahAiC\
 +jVjUjUjPjOhAjbhAjUjFjYjUhahAhHiDjBjOjDjFjMhHhMhAjQjSjPjQjFjSjUjJjFjThajbjOjBjNj\
 +FhahHjDjBjOjDjFjMhHjdhMhAjTjJjajFhahAibhRhShQhMhShUidhMhAjBjMjJjHjOjNjFjOjUhaib\
 +hHjSjJjHjIjUhHhMhAhHjDjFjOjUjFjShHidhAjdhMntfJMnASFyBCDnnnejLjBjQjQjMjZifjCjUjO\
 +hahAiCjVjUjUjPjOhAjbhAjUjFjYjUhahAhHiBjQjQjMjZhHhMhAjQjSjPjQjFjSjUjJjFjThajbjOj\
 +BjNjFhahHjPjLhHjdhMhAjTjJjajFhahAibhRhShQhMhShUidhMhAjBjMjJjHjOjNjFjOjUhaibhHjS\
 +jJjHjIjUhHhMhAhHjDjFjOjUjFjShHidhAjdhMntfJNnASFyBCDnnneBjdntfJOnASCyBCDnVFfyBnn\
 +ntfJPnASCyBCDnnneBjdntfJQnASzDjXjJjOGyBEjzGiXjJjOjEjPjXHfRBVCfyBftnftJRnABXzHjP\
 +jOiDjMjJjDjLIfXzKjDjBjOjDjFjMifjCjUjOJfXFfVGfyBNyBnAMRbyBn0ABJRnAEXzFjDjMjPjTjF\
 +KfjGfnf0DzALCRnfJSnABXIfXzJjBjQjQjMjZifjCjUjOMfXFfVGfyBNyBnAMSbyBn0ABJSnAEXKfjG\
 +fnf0DLCSnfJTnAEXzEjTjIjPjXNfVGfyBnfAFC4B0AiAG4E0AiAB40BiAE4C0AiAF4D0AiAAFALByB";
 + 
 +var bt = new BridgeTalk();
 +bt.target = "photoshop";
 +bt.body = message;
 +bt.send();
 +</code>
 +
 +===== jsx and jsxbin =====
 +
 +  * jsxbin is encryted version of jsx, and it is not true binary encoded but mixed using encryption table, ref doc as below:
 +    * Reversing JSXBIN File Type: https://www.scip.ch/en/?labs.20140515
 +    * a jsxbin decoder project in c#: https://github.com/KarlPan/jsxbin-to-jsx-converter
 +
 +====== Python Scripting for Photoshop (win32com and comtypes method for Windows, appscript method for mac) ======
 +
 +  * use com similar function with appscript on Mac <code python>
 +import sys
 +psApp = None
 +try:
 +    if sys.platform == 'darwin':
 +        from appscript import *
 +        psApp = app('Adobe Photoshop CS5')
 +    else:
 +        from win32com.client import Dispatch
 +        psApp = Dispatch('Photoshop.Application')
 +        #import comtypes
 +        #psApp = comtypes.client.CreateObject('Photoshop.Application')
 +except:
 +    pass
 +</code>
 +
 +  * extra reading for related:
 +    * com way to automate keystroke for app automation in python (of course, win only):
 +      * http://win32com.goermezer.de/content/view/136/284/
 +    * com way to python and illustrator: http://www.noah.org/python/com/illustrator/
 +    * of course, inkscape, gimp, blender all use python by default, while Adobe suites use javascript for cross-platform and cross application
 +    * of course, all directly use Python library to generate graphics, and import back to other graphic app.
 +      * http://graphicdesign.stackexchange.com/questions/56200/programmatically-creating-radiating-rows-of-dots/56207#56207
 +
 +  * **method 1: use Com to Ask Photoshop to run a JSX file, Python + COM + javascript**
 +    * http://techarttiki.blogspot.sg/2008/08/photoshop-scripting-with-python.html
 +    * js function creation by built-in photoshop listener: http://www.kirupa.com/motiongraphics/scripting5.html
 +    * python code <code python callJS.py>
 +def runJSX (aFilePath ):
 +    id60 = psApp.stringIDToTypeID ( "AdobeScriptAutomation Scripts" )
 +    desc12 = comtypes.client.CreateObject('Photoshop.ActionDescriptor')
 +    id61 = psApp.charIDToTypeID( "jsCt" )
 +    desc12.putPath( id61, aFilePath )
 +    id62 = psApp.charIDToTypeID( "jsMs" )
 +    desc12.putString( id62, "null" )
 +    psApp.executeAction( id60, desc12, 2 )
 +
 +psApp = comtypes.client.CreateObject('Photoshop.Application')
 +runJSX ( "C:\\OpenDocument.jsx"
 +</code>
 +  * additional support, **call python from javascript** <code javascript callPy.jsx>
 +var myPy = new File("/path_to_file/pyFun.py");
 +myPy.execute();
 +</code>
 +  * **method 2: Python(Qt) + comtypes/win32com directly call Photoshop built-in API functions (as if it is in Javascript)**
 +    * http://snipplr.com/view/72974/export-jpeg-files-with-each-layerset-photoshop-python-with-pyqt/
 +    * http://peterhanshawart.blogspot.sg/2012/05/python-and-photoshop-code-snippets.html
 +    * https://evanmccall.wordpress.com/2015/03/09/how-to-develop-photoshop-tools-in-python/
 +    * http://tech-artists.org/forum/showthread.php?1304-Photoshop-Embedded-Python-Pipeline/page2
 +    * python code <code python>
 +import sys
 +from PyQt4.QtGui import *
 +import comtypes.client
 +
 +class LayerSetsExporter(QWidget):
 +
 +    def __init__(self, parent = None):
 +        super(LayerSetsExporter, self).__init__(parent)
 +        self.createLayout()
 +        self.createConnection()
 +
 +    def setLayerSetsVisible(self, layerSets, state):
 +        for layerSet in layerSets:
 +            layerSet.visible = False
 +
 +    def processRun(self):
 +        outputPath = 'D:/Python/Temp/'
 +
 +        app = comtypes.client.CreateObject('Photoshop.Application')
 +        doc = app.activeDocument
 +
 +        optionJpg = comtypes.client.CreateObject('Photoshop.JPEGSaveOptions')
 +        optionJpg.quality = 8
 +
 +        count = 0
 +        layerSets = doc.layerSets
 +        for layerSet in layerSets:
 +            self.setLayerSetsVisible(layerSets, False)
 +            layerSet.visible = True
 +            doc.saveAs(outputPath + str(count) + '.jpg', optionJpg, True)
 +            count += 1
 +
 +    def createLayout(self):
 +        self.btnRun = QPushButton('&Run')
 +        layout = QVBoxLayout()
 +        layout.addWidget(self.btnRun)
 +
 +        self.resize(200,100)
 +        self.setWindowTitle('PS LayerSets Exporter')
 +        self.setLayout(layout)
 +
 +    def createConnection(self):
 +        self.btnRun.clicked.connect(self.processRun)
 +
 +qApp = QApplication(sys.argv)
 +
 +exporter = LayerSetsExporter()
 +exporter.show()
 +
 +qApp.exec_()
 +</code>
 +  * additional support, **convert Photoshop Listener output javascript code to python code**
 +    * http://tech-artists.org/forum/showthread.php?4698-photoshop-scripting-masks
 +    * python code <code python>
 +def hueSaturation(hue, saturation, lightness, colorize=False, psApp=None):
 +    if psApp is None:
 +        psApp = win32.gencache.EnsureDispatch('Photoshop.Application')
 +    dialogMode = 3
 +    idHStr = psApp.CharIDToTypeID( "HStr" )
 +    desc169 = win32.gencache.EnsureDispatch( "Photoshop.ActionDescriptor" )
 +    idpresetKind = psApp.StringIDToTypeID( "presetKind" )
 +    idpresetKindType = psApp.StringIDToTypeID( "presetKindType" )
 +    idpresetKindCustom = psApp.StringIDToTypeID( "presetKindCustom" )
 +    desc169.PutEnumerated( idpresetKind, idpresetKindType, idpresetKindCustom )
 +    idClrz = psApp.CharIDToTypeID( "Clrz" )
 +    desc169.PutBoolean( idClrz, colorize )
 +    idAdjs = psApp.CharIDToTypeID( "Adjs" )
 +    list27 = win32.gencache.EnsureDispatch( "Photoshop.ActionList" )
 +    desc170 = win32.gencache.EnsureDispatch( "Photoshop.ActionDescriptor" )
 +    idH = psApp.CharIDToTypeID( "  " )
 +    desc170.PutInteger( idH, hue )
 +    idStrt = psApp.CharIDToTypeID( "Strt" )
 +    desc170.PutInteger( idStrt, saturation )
 +    idLght = psApp.CharIDToTypeID( "Lght" )
 +    desc170.PutInteger( idLght, lightness )
 +    idHsttwo = psApp.CharIDToTypeID( "Hst2" )
 +    list27.PutObject( idHsttwo, desc170 )
 +    desc169.PutList( idAdjs, list27 )
 +    psApp.ExecuteAction( idHStr, desc169, dialogMode )
 +</code>
 +
 +
 +
 +
 +====== Python Scripting for Photoshop (no win32com and comtypes for cross-platform) ======
 +
 +  * **case 1: with Photopshop JSX open a socket actively and listen to outside calls**, while this cost Photoshop hanging there just listening and lose interactivity.
 +    * no extra library required, since both way use socket communication
 +    * it support Bridge CS5, InDesign CS5, InCopy CS5, After Effects CS5, Photoshop CS5 and above
 +    * ref: http://techartsurvival.blogspot.sg/2014/01/talking-to-photoshop-via-tcp.html
 +    * ref - more 2 way solution: http://jfli04.blogspot.sg/2012/06/javascript-and-python-communication-by.html
 +    * instruction:
 +      * open socket in Photoshop first <code javascript ps_connect_listener.jsx>
 +// requires photoshop CS5+ and run in Photoshop
 +conn = new Socket();
 +var keep_serving = true;
 +while (keep_serving) {
 +    if (conn.listen(8123))  // any port
 +    {
 +        // wait forever for a connection
 +        var incoming;
 +        do incoming = conn.poll();
 +        while (incoming == null);
 +        
 +        new_cmd = incoming.read();
 +        try {
 +            if (null != new_cmd) {
 +                result =eval(new_cmd);
 +                incoming.writeln(result + "\nOK\n");
 +            }
 +            else {
 +                incoming.writeln("no command\nFAIL\n");
 +            }
 +        }
 +        catch (err) {
 +            incoming.writeln(err + "FAIL\n");
 +            incoming.close();
 +            delete incoming;
 +        }
 +    }
 +}
 +</code>
 +      * call from python next <code python ps_connect_caller.py>
 +import socket
 +HOST = '127.0.0.1'
 +PORT = 8123
 +
 +def send_photoshop(msg):
 +    conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 +    conn.connect((HOST,PORT))
 +    conn.send(msg)
 +    result = conn.recv(4096)
 +    conn.close()
 +    print(result)
 +    return result
 +
 +send_photoshop("alert('Got it');")
 +send_photoshop("activeDocument.artLayers.add();")
 +send_photoshop("keep_serving = false;") #stop the server
 +</code>
 +  * **case 2: with leaving photoshop alone, and outside connect to photoshop with password.**
 +    * required cs5.5+ server connect (edit menu > remote connection in Photoshop checked), since Photoshop CS 5.5, it has remote connection server built-in inside photoshop
 +      * ref : [[http://tech-artists.org/forum/showthread.php?4481-Communicating-between-Photoshop-and-Maya-(Python)/page2|http://tech-artists.org/forum/showthread.php?4481-Communicating-between-Photoshop-and-Maya-(Python)/page2]]
 +    * extra library required because of encrypted connection
 +    * https://github.com/theiviaxx/photoshopConnection
 +    * https://github.com/theiviaxx/photoshopConnection/blob/master/tests/test_ps.py
 +    * instruction <code python>
 +# get pyps module in photoshopConnection for connect function to PS from https://github.com/theiviaxx/photoshopConnection
 +# get pbkdf2 module support encrypted TCP connection for above from https://pypi.python.org/pypi/pbkdf2
 +
 +import sys
 +path = 'pathToThatModule/photoshopConnection'
 +path in sys.path or sys.path.append(path)
 +path = 'pathToThatModule/pbkdf2-1.3'
 +path in sys.path or sys.path.append(path)
 +
 +from pyps import Connection, EventListener, ConnectionError
 +c = Connection()
 +#c.connect('ps_password', '192.168.1.8') # optional remote photoshop server on other computer
 +c.connect('123456') # when you set password in photoshop as 123456 
 +c.send_sync('app.foregroundColor.rgb.red=255;') # need ; for each end command
 +c.send_sync('app.foregroundColor.rgb.green=0;')
 +c.send_sync('app.foregroundColor.rgb.blue=128;')
 +res = c.send_sync('app.activeDocument.artLayers.add();') # <[ArtLayer Layer 2]:>
 +res = c.send_sync("activeDocument.activeLayer.name='Remote layer';") # <Test:>
 +c.close()
 +
 +# it also have listerner feature, which keep listening how what Photoshop do
 +</code>
 +
 +  * ** case 3: jsx file passing method for any Photoshop version v7.0+ or any adobe app with javascript support (no need remote server feature)**
 +    * I improved based on this user case: (for win and mac, eg app using this like Quixel's dDo))
 +      * http://peterhanshawart.blogspot.sg/2013/10/python-photoshop-automation-without_29.html
 +      * http://peterhanshawart.blogspot.sg/2014/01/use-python-to-use-javascript-to-get.html
 +      * https://github.com/olihel/ps-scripting
 +    * system command to ask Photoshop run a jsx file<code bash>
 +photoshop.exe "pathToScript/my_script.jsx" # win
 +open -b com.adobe.Photoshop --args "pathToScript/my_script.jsx" # mac</code>
 +    * python code <code python module_photoshop.py>
 +'''
 +by ying: 2016.11.22 http://shining-lucy.com/wiki
 +note: make sure you have these empty 2 files next to it,
 +"process.jsx" and "process_output.txt"
 +'''
 +def ps_loc_win():
 +    import _winreg
 +    regKey = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\Photoshop.exe")
 +    regPath = _winreg.QueryValueEx(regKey, 'Path')[0] + 'Photoshop.exe'
 +    return regPath
 +
 +def readFormatFile(file):
 +    with open(file) as f:
 +        txt = f.read()
 +    return txt
 +def writeFormatFile(txt, file):    
 +    with open(file, 'w') as f:
 +        f.write(txt)
 +
 +import subprocess # for call Photoshop exe
 +import os # for file read and process
 +import sys # for os detection
 +import time # for timer to check file
 +def ps_cmd(cmd_txt, timer=1):
 +    location = os.path.dirname(os.path.realpath(__file__))
 +    jsx_file = os.path.join(location, "process.jsx")
 +    result_file = os.path.join(location, "process_output.txt")
 +    # step 1: record old result date
 +    old_output_timestamp = os.path.getmtime(result_file)
 +    
 +    txt=cmd_txt
 +    txt+="""
 +        var output_file = new File('{0}'); 
 +        output_file.open("w"); 
 +        if(typeof result !== "undefined") output_file.writeln(String(result));
 +        else output_file.writeln("");
 +        output_file.close();
 +        """.format(result_file.replace("\\","/"))
 +    writeFormatFile(txt, jsx_file)
 +    # step 2: process cmd
 +    if sys.platform in ['win32','win64']:
 +        ps_loc = ps_loc_win()
 +        subprocess.Popen(ps_loc+" "+jsx_file)
 +    elif sys.platform == 'darwin':
 +        subprocess.Popen('open -b com.adobe.Photoshop --args "{0}"'.format(jsx_file))
 +    # step 3: wait and get result
 +    timeout = time.time() + 60*timer  # default 1 minutes from now
 +    while True:
 +        time.sleep(0.1)
 +        if time.time() > timeout:
 +            print("Time Out after 1 minute and no change on result file.")
 +            break
 +        cur_output_timestamp = os.path.getmtime(result_file)
 +        if cur_output_timestamp > old_output_timestamp:
 +            print("Result completed.")
 +            break
 +    if cur_output_timestamp > old_output_timestamp:
 +        return readFormatFile(result_file)
 +    else:
 +        return None
 +
 +# actual test example, code below can be removed
 +def main():
 +    my_cmd = """
 +    app.activeDocument.artLayers.add();
 +    activeDocument.activeLayer.name='Pass by jsx';
 +    result = app.activeDocument.layers.length;
 +    result = "layer_count:"+result;
 +    """
 +
 +    my_result = ps_cmd(my_cmd)
 +    print(my_result)
 +
 +if __name__ == "__main__":
 +    main()
 +</code>
 +  * the quick ui tool with universal_tool_template.py, which can be downloaded as package here: https://github.com/shiningdesign/utt_PhotoshopUI
 +    * here is screenshot \\ {{:graphic:javascript:photoshop:photoshopui_v0.1_screenshot.png|}}
 +
 +====== Advanced Javascript Scripting for Photoshop ======
 +
 +**Quick Introduction**
 +  * Javascript Scripting in Photoshop is introduced in Photoshop 7, however Photoshop 5-7 support COM script
 +    * it is based on javascript 1.5 (2000) and new featured added, so modern javascript functions may not included. 
 +  * Javascript is cross-platform but is control Photoshop only, unlike VBscript and AppleScript controls any AppleEvents or windows COM supported application.
 +    * For com, it is like <code>app = createObject("Photoshop.Application.versionNum")</code>
 +  * download scripting guide cs2-cc: http://www.adobe.com/devnet/photoshop/scripting.html
 +  * ref:
 +    * http://jongware.mit.edu/pscs5js_html/psjscs5/
 +
 +  * **variable type**
 +    * string, number, boolean, null, object, undefined
 +  * **variable store**
 +    * value, object reference
 +  * **other**
 +    * command and methods
 +
 +**Advance scripting Start Kit**
 +  * Get **__XToolkit 2.2__** from : http://ps-scripts.sourceforge.net/xtools.html
 +    * a javascript standard library to get modern javascript functions and extras for photoshop functions
 +    * a console to test script: JShell
 +    * a list of utility to convert Photoshop asssets
 +
 +
 +====== Additional Option for custom panel in Photoshop ======
 +
 +  * for cs3 till cs6, the flash panel is supported, but it won't work in CC since they move into html5,
 +    * anyway, the flash panel is no good, since it is a dialog, it block interactivity until you close.
 +    * here is where you can still find it, FlashUISample.jsx and related: https://github.com/tahoedesigner/ExtendScript/tree/master/Adobe%20Sample%20Scripts
 +  * for cs5 and above, the Plug-ins\Panels is there for plugin panels, then the Windows > Extension to show the panels
 +
 +  * for photoshop CS5 and above, there is Adobe Configurator to create custom panel without code
 +    * https://www.youtube.com/watch?v=d_rMfjatX-M
 +    * download of adobe configurator: http://labs.adobe.com/technologies/configurator/
 +    * additional notes:
 +      * from adobe configurator 4, it moves fully to HTML5-based panels, also extension builder 3 also move to HTML5-based panels
 +
 +  * Develop HTML Panels for Adobe CC apps: Extension Builder (or just a text editor)
 +    * download: http://labs.adobe.com/technologies/extensionbuilder3/
 +    * example: http://www.adobe.com/devnet/creativesuite/articles/a-short-guide-to-HTML5-extensions.html
 +
 +  * other tools using custom panel:
 +    * Export kit: http://exportkit.com/lightning-storm-cc
 +
 +  * More news on CC version and above:
 +    * https://medium.com/@HallgrimurTh/extending-adobe-cc-2014-apps-ba1d101e27da#.cduf21vml
 +    * http://adobe-cep.github.io/CEP-Resources/
 +
 +  * other example project
 +    * cc based panel let u list jsx file and run: https://github.com/moremorefor/JSXExecutor
 +
 +====== Conclusion ======
 +
 +  * so conclusion, if you are not stuck with old technology, and you willing to move to adobe CC version, HTML5 is the way to go for all new techs instead of Python fighting into adobe apps.
 +
 +Update 2022:
 +  * PS automation tech age:
 +    * oldest: Actions (since v4) - macros by record and playback but no logic and no interface
 +    * older: ExtendScript (AppleScript/VBScript), ScriptUI, C++ 
 +      * jsx - (js v3)
 +    * old: CEP panel (Common Extensibility Platform) https://github.com/Adobe-CEP
 +      * html5
 +    * new and fresh: UXP (Unified Extensibility Platform) for PS 2021+ (v22)
 +      * new js support
 +      * ref: https://medium.com/adobetech/xd-and-creative-cloud-extensibility-faq-e615dd6ecbfe
 +      * https://gregbenzphotography.com/photography-reviews/what-are-uxp-plugins-in-photoshop
 +      * Building Plugins for Adobe Photoshop and XD using UXP: https://www.youtube.com/watch?v=6iFVozCJ1aw
 +      * Rundown of the UXP Announcement at MAX 2020: https://www.youtube.com/watch?v=zAOUBpDjc1Q
 +
 +UXP feature:
 +  * modern JS, new File API